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 // // cync push c:/home/user/documents /user/documents // Content of c:/home/user/documents goes into c:/home/user/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). // We need to figure out whether the destionation exists, and if so, what // is the destionation's type [file vs folder]. dest = LexicalPath.Clean(dest); var item = context.Storage.GetItemInfo(dest); if (item != null) { // The destination exists if (!item.IsDir) { throw new PathException("The directory '" + src + "' cannot be copied - a file with the same name exists."); } string fullDestPath = srcEndsWithSeparator ? dest : LexicalPath.Combine(dest, Path.GetFileName(src)); queue.Enqueue(Tuple.Create(src, fullDestPath)); _logger.Debug($"Queueing directory '{src}' [target: '{fullDestPath}']"); } else { string fullDestPath = srcEndsWithSeparator ? dest : LexicalPath.Combine(dest, Path.GetFileName(src)); queue.Enqueue(Tuple.Create(src, fullDestPath)); _logger.Debug($"Queueing directory '{src}' [target: '{fullDestPath}']"); } // 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 if (tt.Item2.Length > 0 && tt.Item2 != "/") { context.Storage.CreateDirectory(tt.Item2); } 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)); _logger.Debug($"Queueing directory {ee} [target: '{destFullPath}']"); } else { // A file - check size and timestamp var fullDestPath = LexicalPath.Combine(tt.Item2, Path.GetFileName(ee)); var destItem = context.Storage.GetItemInfo(fullDestPath); var upload = destItem == null || destItem.Size == null || fi.Length != destItem.Size || destItem.LastWriteTime == null || DateTime.Compare(destItem.LastWriteTime.Value, fi.LastWriteTime) < 0; if (upload) { _logger.Debug($"Adding '{ee}' as '{fullDestPath}'"); context.Storage.Upload(ee, fullDestPath, finalizeLocal: false); _logger.Debug($"Added '{ee}' as '{fullDestPath}'"); } else { _logger.Debug($"Skipped '{ee}'. The repository file '{fullDestPath}' is newer or the same."); } } } } } }
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); } } }