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); } } }
public void Pull(ref Context context, string src, string dest) { bool srcEndsWithSeparator = OSPath.IsPathSeparator(src.Last()); // src is a repository path. We use unix style for it. src = LexicalPath.Clean(src); // dest is local file system path dest = Path.GetFullPath(dest); Queue <Tuple <string, string> > queue = new Queue <Tuple <string, string> >(); if (src == "/") { queue.Enqueue(Tuple.Create(src, dest)); } else if (context.Storage.GetItemInfo(src).IsDir) { // src is a folder, let's define the desired behaviour, following // the design of rsync: // // cync pull /user/documents/ c:/home/user // Content of /user/documents goes into c:/home/user // // cync pull /user/documents c:/home/user // Content of /user/documents goes into c:/home/user/documents // // Notice the difference caused by the presence/lack of the final slash // in the source (documents/ vs just documents). var fileName = LexicalPath.GetFileName(src); var destPath = dest; if (fileName.Length > 0 && !srcEndsWithSeparator) { destPath = Path.Combine(destPath, ValidLocalPath(fileName)); Directory.CreateDirectory(destPath); } queue.Enqueue(Tuple.Create(src, destPath)); } else { throw new NotSupportedException("Only directories are supported currently."); } // Process the queue while (queue.Count > 0) { var tt = queue.Dequeue(); var items = context.Storage.ListDirectory(tt.Item1); foreach (var item in items) { if (item.IsDir) { var destDirPath = Path.Combine(tt.Item2, ValidLocalPath(item.Name)); var srcDirPath = LexicalPath.Combine(tt.Item1, item.Name); Directory.CreateDirectory(destDirPath); queue.Enqueue(Tuple.Create(srcDirPath, destDirPath)); _logger.Debug($"Queueing directory '{srcDirPath}' [target: '{destDirPath}']"); } else { var srcFullPath = LexicalPath.Combine(tt.Item1, item.Name); var destFullPath = Path.Combine(tt.Item2, ValidLocalPath(item.Name)); var fileInfo = new FileInfo(destFullPath); var download = !fileInfo.Exists || fileInfo.Length != item.Size || item.LastWriteTime == null || DateTime.Compare(fileInfo.LastWriteTimeUtc, item.LastWriteTime.Value) < 0; if (download) { _logger.Debug($"Copying '{srcFullPath}' to '{destFullPath}'"); context.Storage.Download(srcFullPath, destFullPath); File.SetLastWriteTime(destFullPath, item.LastWriteTime.Value); _logger.Debug($"Copied '{srcFullPath}' to '{destFullPath}'"); } else { _logger.Debug($"Skipped '{srcFullPath}'. The local file ({destFullPath}) is either newer or the same as the repository file."); } } } } }
public void Pull(ref Context context, string src, string dest) { bool srcEndsWithSeparator = OSPath.IsPathSeparator(src.Last()); // src is a repository path. We use unix style for it. src = LexicalPath.Clean(src); // dest is local file system path dest = Path.GetFullPath(dest); Queue <Tuple <string, string> > queue = new Queue <Tuple <string, string> >(); ObjectInfo oi = new ObjectInfo(context, src); if (oi.IsDir()) { // src is a folder, let's define the desired behaviour, following // the design of rsync: // // cync pull /user/documents/ c:/home/user // Content of /user/documents goes into c:/home/user // // cync pull /user/documents c:/home/user // Content of /user/documents goes into c:/home/user/documents // // Notice the difference caused by the presence/lack of the final slash // in the source (documents/ vs just documents). var fileName = LexicalPath.GetFileName(src); var destPath = dest; if (fileName.Length > 0 && !srcEndsWithSeparator) { destPath = Path.Combine(destPath, fileName); Directory.CreateDirectory(destPath); } queue.Enqueue(Tuple.Create(src, destPath)); } else { throw new NotSupportedException("Only directories are supported currently."); } // Process the queue while (queue.Count > 0) { var tt = queue.Dequeue(); var srcDir = Dir.Open(ref context, tt.Item1, false, false); foreach (var pp in srcDir) { if (Dir.IsDir(pp.Value)) { var destDirPath = Path.Combine(tt.Item2, pp.Key); var srcDirPath = LexicalPath.Combine(tt.Item1, pp.Key); Directory.CreateDirectory(destDirPath); queue.Enqueue(Tuple.Create(srcDirPath, destDirPath)); context.InfoWriteLine($"Queueing directory {srcDirPath} [target: {destDirPath}]"); } else { var srcFullPath = LexicalPath.Combine(tt.Item1, pp.Key); var destFullPath = Path.Combine(tt.Item2, pp.Key); var bb = srcDir.PullFile(pp.Key, destFullPath, false); if (bb) { context.InfoWriteLine($"Copied {srcFullPath} to {destFullPath}"); } else { context.InfoWriteLine($"Skipped {srcFullPath}. The local file [{destFullPath}] is either newer or the same as the repository file."); } } } } }