public void Push(ref Context context, string src, string dest) { bool srcEndsWithSeparator = OSPath.IsPathSeparator(src.Last()); // src is a local file system path, thus, use the Path and the OSPath classes src = Path.GetFullPath(OSPath.Clean(src)); FileAttributes fa = File.GetAttributes(src); Queue <Tuple <string, string> > queue = new Queue <Tuple <string, string> >(); if ((fa & FileAttributes.Directory) == FileAttributes.Directory) { // src is a folder, let's define the desired behaviour, following // the design of rsync: // // cync push c:/home/user/documents/ /user/documents // Content of c:/home/user/documents goes into /user/documents // // cync push c:/home/user/documents /user/documents // Content of c:/home/user/documents goes into /user/documents/documents // // Notice the difference caused by the presence/lack of the final // directory separator (slash in the above example) in the source // (documents/ vs just documents). if (srcEndsWithSeparator) { string fullDestPath = dest; queue.Enqueue(Tuple.Create(src, fullDestPath)); context.InfoWriteLine($"Queueing directory '{src}' [target: '{fullDestPath}']"); } else { string fullDestPath = LexicalPath.Combine(dest, Path.GetFileName(src)); queue.Enqueue(Tuple.Create(src, fullDestPath)); context.InfoWriteLine($"Queueing directory '{src}' [target: '{fullDestPath}']"); } // Compute total size var sizeQueue = new Queue <string>(queue.Select(t => t.Item1).ToList()); var totalSize = 0L; while (sizeQueue.Count > 0) { var path = sizeQueue.Dequeue(); foreach (var ee in Directory.EnumerateFileSystemEntries(path)) { FileInfo fi = new FileInfo(ee); if ((fi.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { sizeQueue.Enqueue(ee); } else { totalSize += fi.Length; } } } var progress = new ProgressBar(totalSize, flush: true); var processedBytes = 0L; // Process the queue while (queue.Count > 0) { var tt = queue.Dequeue(); // Process all directory entries. Files are added to the repo directly, // sub directories are queued for further processing. var entries = Directory.EnumerateFileSystemEntries(tt.Item1); // Create the path if it doesn't exist Dir curDestDir = Dir.Open(ref context, LexicalPath.GetDirectoryName(tt.Item2), true); if (tt.Item2.Length > 0 && tt.Item2 != "/") { string destPath = LexicalPath.GetFileName(tt.Item2); if (!curDestDir.HasEntry(destPath)) { curDestDir.AddDir(destPath, tt.Item1); } curDestDir.ChangeDirDown(destPath); } foreach (var ee in entries) { FileInfo fi = new FileInfo(ee); if ((fi.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { // Enque the directory for later processing var destFullPath = LexicalPath.Combine(tt.Item2, Path.GetFileName(ee)); queue.Enqueue(Tuple.Create(ee, destFullPath)); context.InfoWriteLine($"Queueing directory '{ee}' [target: '{destFullPath}']"); } else { if (!context.Verbose) { progress.Text = $"Processing '{ee}'"; progress.Update(processedBytes); } // A file - copy it var added = curDestDir.PushFile(ee, Path.GetFileName(ee), false); if (added) { context.InfoWriteLine($"Added '{ee}' as '{LexicalPath.Combine(tt.Item2, Path.GetFileName(ee))}'"); } else { context.InfoWriteLine($"Skipped '{ee}'. The repository file '{LexicalPath.Combine(tt.Item2, Path.GetFileName(ee))}' is newer or the same."); } processedBytes += fi.Length; } } } if (!context.Verbose) { progress.Text = ""; progress.Update(processedBytes); } } }