/// <summary> /// Validate folder and parameters /// </summary> /// <param name="destDir"></param> /// <param name="parameters"></param> /// <param name="srcDir"></param> private bool Validate(string srcDir, string destDir, InputParams parameters) { if (((parameters.IncludeFiles != null) && (parameters.ExcludeFiles != null)) || ((parameters.IncludeDirs != null) && (parameters.ExcludeDirs != null))) { PrintUsage(); return false; } string fullSrcDir = Path.GetFullPath(srcDir); string fullDestDir = Path.GetFullPath(destDir); if (destDir.StartsWith(fullSrcDir) || srcDir.StartsWith(fullDestDir)) { Trace("Error: source directory {0} and destination directory {1} cannot contain each other", fullSrcDir, fullDestDir); return false; } if (((parameters.DeleteExcludeFiles != null) || (parameters.DeleteExcludeDirs != null)) && (!parameters.DeleteFromDest)) { Trace("Error: exclude-from-deletion options (-ndf and -ndd) require deletion (-d) enabled."); return false; } // ensure source directory exists if (!Directory.Exists(srcDir)) { this.Trace("Error: source directory {0} not found", srcDir); return false; } return true; }
/// <summary> /// Performs one-way synchronization from source directory tree to destination directory tree /// </summary> /// <param name="configuration"></param> public virtual SyncResults Start(InputParams configuration) { this.Configuration = configuration; return this.Start(); }
/// <summary> /// Recursively performs one-way synchronization from a single source to destination directory /// </summary> /// <param name="srcDir"></param> /// <param name="destDir"></param> /// <param name="inputParams"></param> /// <param name="results"></param> private bool ProcessDirectory(string srcDir, string destDir, InputParams inputParams, ref SyncResults results) { DirectoryInfo diSrc = new DirectoryInfo(srcDir); DirectoryInfo diDest = new DirectoryInfo(destDir); // create destination directory if it doesn't exist if (!diDest.Exists) { try { Trace("Creating directory: {0}", diDest.FullName); // create the destination directory diDest.Create(); results.DirectoriesCreated++; } catch (Exception ex) { Trace("Error: failed to create directory {0}. {1}", destDir, ex.Message); return false; } } // get list of selected files from source directory FileInfo[] fiSrc = GetFiles(diSrc, inputParams, ref results); // get list of files in destination directory FileInfo[] fiDest = GetFiles(diDest, null, ref results); // put the source files and destination files into hash tables Hashtable hashSrc = new Hashtable(fiSrc.Length); foreach (FileInfo srcFile in fiSrc) { hashSrc.Add(srcFile.Name, srcFile); } Hashtable hashDest = new Hashtable(fiDest.Length); foreach (FileInfo destFile in fiDest) { hashDest.Add(destFile.Name, destFile); } // make sure all the selected source files exist in destination foreach (FileInfo srcFile in fiSrc) { bool isUpToDate = false; // look up in hash table to see if file exists in destination FileInfo destFile = (FileInfo)hashDest[srcFile.Name]; // if file exists and length, write time and attributes match, it's up to date if ((destFile != null) && (srcFile.Length == destFile.Length) && (srcFile.LastWriteTime == destFile.LastWriteTime) && (srcFile.Attributes == destFile.Attributes)) { isUpToDate = true; results.FilesUpToDate++; } // if the file doesn't exist or is different, copy the source file to destination if (!isUpToDate) { string destPath = Path.Combine(destDir, srcFile.Name); // make sure destination is not read-only if (destFile != null && destFile.IsReadOnly) { destFile.IsReadOnly = false; } try { Trace("Copying: {0} -> {1}", srcFile.FullName, Path.GetFullPath(destPath)); // copy the file srcFile.CopyTo(destPath, true); // set attributes appropriately File.SetAttributes(destPath, srcFile.Attributes); results.FilesCopied++; } catch (Exception ex) { Trace("Error: failed to copy file from {0} to {1}. {2}", srcFile.FullName, destPath, ex.Message); return false; } } } // delete extra files in destination directory if specified if (inputParams.DeleteFromDest) { foreach (FileInfo destFile in fiDest) { FileInfo srcFile = (FileInfo)hashSrc[destFile.Name]; if (srcFile == null) { // if this file is specified in exclude-from-deletion list, don't delete it if (ShouldExclude(inputParams.DeleteExcludeFiles, null, destFile.Name)) continue; try { Trace("Deleting: {0} ", destFile.FullName); destFile.IsReadOnly = false; // delete the file destFile.Delete(); results.FilesDeleted++; } catch (Exception ex) { Trace("Error: failed to delete file from {0}. {1}", destFile.FullName, ex.Message); return false; } } } } // Get list of selected subdirectories in source directory DirectoryInfo[] diSrcSubdirs = GetDirectories(diSrc, inputParams, ref results); // Get list of subdirectories in destination directory DirectoryInfo[] diDestSubdirs = GetDirectories(diDest, null, ref results); // add selected source subdirectories to hash table, and recursively process them Hashtable hashSrcSubdirs = new Hashtable(diSrcSubdirs.Length); foreach (DirectoryInfo diSrcSubdir in diSrcSubdirs) { hashSrcSubdirs.Add(diSrcSubdir.Name, diSrcSubdir); // recurse into this directory if (!ProcessDirectory(diSrcSubdir.FullName, Path.Combine(destDir, diSrcSubdir.Name), inputParams, ref results)) return false; } // delete extra directories in destination if specified if (inputParams.DeleteFromDest) { foreach (DirectoryInfo diDestSubdir in diDestSubdirs) { // does this destination subdirectory exist in the source subdirs? if (!hashSrcSubdirs.ContainsKey(diDestSubdir.Name)) { // if this directory is specified in exclude-from-deletion list, don't delete it if (ShouldExclude(inputParams.DeleteExcludeDirs, null, diDestSubdir.Name)) continue; try { Trace("Deleting directory: {0} ", diDestSubdir.FullName); // delete directory DeleteDirectory(diDestSubdir); results.DirectoriesDeleted++; } catch (Exception ex) { Trace("Error: failed to delete directory {0}. {1}", diDestSubdir.FullName, ex.Message); return false; } } } } return true; }
/// <summary> /// Recursively performs one-way synchronization from a single source to destination directory /// </summary> /// <param name="srcDir"></param> /// <param name="destDir"></param> /// <param name="inputParams"></param> /// <param name="results"></param> private bool ProcessDirectory(string srcDir, string destDir, InputParams inputParams, ref SyncResults results) { DirectoryInfo diSrc = new DirectoryInfo(srcDir); DirectoryInfo diDest = new DirectoryInfo(destDir); // create destination directory if it doesn't exist if (!diDest.Exists) { try { Trace("Creating directory: {0}", diDest.FullName); // create the destination directory diDest.Create(); results.DirectoriesCreated++; } catch (Exception ex) { Trace("Error: failed to create directory {0}. {1}", destDir, ex.Message); return(false); } } // get list of selected files from source directory FileInfo[] fiSrc = GetFiles(diSrc, inputParams, ref results); // get list of files in destination directory FileInfo[] fiDest = GetFiles(diDest, inputParams.IncludesApplyToDest ? inputParams : null, ref results); // put the source files and destination files into hash tables Hashtable hashSrc = new Hashtable(fiSrc.Length); foreach (FileInfo srcFile in fiSrc) { hashSrc.Add(srcFile.Name, srcFile); } Hashtable hashDest = new Hashtable(fiDest.Length); foreach (FileInfo destFile in fiDest) { hashDest.Add(destFile.Name, destFile); } // make sure all the selected source files exist in destination var compareMethod = inputParams.CompareMethod; foreach (FileInfo srcFile in fiSrc) { bool isUpToDate = false; // look up in hash table to see if file exists in destination FileInfo destFile = (FileInfo)hashDest[srcFile.Name]; // if file exists and length, write time and attributes match, it's up to date if ((destFile != null) && CompareFile.AreEqual(srcFile, destFile, compareMethod)) { isUpToDate = true; results.FilesUpToDate++; } // if the file doesn't exist or is different, copy the source file to destination if (!isUpToDate) { string destPath = Path.Combine(destDir, srcFile.Name); // make sure destination is not read-only if (destFile != null && destFile.IsReadOnly) { destFile.IsReadOnly = false; } try { Trace("Copying: {0} -> {1}", srcFile.FullName, Path.GetFullPath(destPath)); // copy the file srcFile.CopyTo(destPath, true); // set attributes appropriately File.SetAttributes(destPath, srcFile.Attributes); results.FilesCopied++; } catch (Exception ex) { Trace("Error: failed to copy file from {0} to {1}. {2}", srcFile.FullName, destPath, ex.Message); return(false); } } } // delete extra files in destination directory if specified if (inputParams.DeleteFromDest) { foreach (FileInfo destFile in fiDest) { FileInfo srcFile = (FileInfo)hashSrc[destFile.Name]; if (srcFile == null) { // if this file is specified in exclude-from-deletion list, don't delete it if (ShouldExclude(inputParams.DeleteExcludeFiles, null, destFile.Name)) { continue; } try { Trace("Deleting: {0} ", destFile.FullName); destFile.IsReadOnly = false; // delete the file destFile.Delete(); results.FilesDeleted++; } catch (Exception ex) { Trace("Error: failed to delete file from {0}. {1}", destFile.FullName, ex.Message); return(false); } } } } // Get list of selected subdirectories in source directory DirectoryInfo[] diSrcSubdirs = GetDirectories(diSrc, inputParams, ref results); // Get list of subdirectories in destination directory DirectoryInfo[] diDestSubdirs = GetDirectories(diDest, inputParams.IncludesApplyToDest ? inputParams : null, ref results); // add selected source subdirectories to hash table, and recursively process them Hashtable hashSrcSubdirs = new Hashtable(diSrcSubdirs.Length); foreach (DirectoryInfo diSrcSubdir in diSrcSubdirs) { hashSrcSubdirs.Add(diSrcSubdir.Name, diSrcSubdir); // recurse into this directory if (!ProcessDirectory(diSrcSubdir.FullName, Path.Combine(destDir, diSrcSubdir.Name), inputParams, ref results)) { return(false); } } // delete extra directories in destination if specified if (inputParams.DeleteFromDest) { foreach (DirectoryInfo diDestSubdir in diDestSubdirs) { // does this destination subdirectory exist in the source subdirs? if (!hashSrcSubdirs.ContainsKey(diDestSubdir.Name)) { // if this directory is specified in exclude-from-deletion list, don't delete it if (ShouldExclude(inputParams.DeleteExcludeDirs, null, diDestSubdir.Name)) { continue; } try { Trace("Deleting directory: {0} ", diDestSubdir.FullName); // delete directory DeleteDirectory(diDestSubdir); results.DirectoriesDeleted++; } catch (Exception ex) { Trace("Error: failed to delete directory {0}. {1}", diDestSubdir.FullName, ex.Message); return(false); } } } } return(true); }
/// <summary> /// Gets list of files in specified directory, optionally filtered by specified input parameters /// </summary> /// <param name="directoryInfo"></param> /// <param name="inputParams"></param> /// <param name="results"></param> public virtual FileInfo[] GetFiles(DirectoryInfo directoryInfo, InputParams inputParams, ref SyncResults results) { // get all files List<FileInfo> fileList = new List<FileInfo>(directoryInfo.GetFiles()); // do we need to do any filtering? bool needFilter = (inputParams != null) && (inputParams.AreSourceFilesFiltered); if (needFilter) { for (int i = 0; i < fileList.Count; i++) { FileInfo fileInfo = fileList[i]; // filter out any files based on hiddenness and exclude/include filespecs if ((inputParams.ExcludeHidden && ((fileInfo.Attributes & FileAttributes.Hidden) > 0)) || ShouldExclude(inputParams.ExcludeFiles, inputParams.IncludeFiles, fileInfo.Name)) { fileList.RemoveAt(i); results.FilesIgnored++; i--; } } } return fileList.ToArray(); }
/// <summary> /// Performs one-way synchronization from source directory tree to destination directory tree /// </summary> /// <param name="configuration"></param> public virtual SyncResults Start(InputParams configuration) { this.Configuration = configuration; return(this.Start()); }
/// <summary> /// Runs a test case /// </summary> private static void TestOneCase(string[] srcDirectories, string[] srcFiles, string[] destDirectories, string[] destFiles, InputParams inputParams, SyncResults expectedResults) { // delete base directories in case they were hanging around from a previous failed test DeleteTestDirectory(baseDirSrc); DeleteTestDirectory(baseDirDest); // create source directories and files specified by test CreateTestDirectories(baseDirSrc, srcDirectories); CreateTestFiles(baseDirSrc, null, srcFiles); // create destination directories and files specified by test if (destDirectories != null) { CreateTestDirectories(baseDirDest, destDirectories); } if (destFiles != null) { CreateTestFiles(baseDirDest, baseDirSrc, destFiles); } // perform the directory sync SyncResults results = new SyncResults(); results = new Sync(baseDirSrc, baseDirDest).Start(inputParams); // Assert we have expected results Assert.IsTrue(SyncTools.CompareTo(expectedResults, results)); // If we are deleting extra files from destination, verify we have exactly the same files as filtered source files if (inputParams.DeleteFromDest && (!(inputParams.DeleteExcludeFiles != null) && !(inputParams.DeleteExcludeDirs != null))) { // calc hash of filtered files & directories in source tree byte[] hashSrc = CalcHash(baseDirSrc, inputParams); // calc hash of all files & directories in destination tree byte[] hashDest = CalcHash(baseDirDest, null); // hashes must match bool hashesMatch = SyncTools.CompareByteArrays(hashSrc, hashDest); Assert.IsTrue(hashesMatch); } DeleteTestDirectory(baseDirSrc); DeleteTestDirectory(baseDirDest); }
/// <summary> /// Calculate a hash of the specified directory tree, filtered by inputParams if non-null /// </summary> private static byte[] CalcHash(string directory, InputParams inputParams) { MemoryStream memoryStream = new MemoryStream(); // build a stream of the directory contents we want to hash BuildCRCStream(directory, inputParams, ref memoryStream); // create the hash MD5 md5 = MD5.Create(); return md5.ComputeHash(memoryStream.GetBuffer()); }
/// <summary> /// Fill a stream with relevant contents of directory tree for hashing. Directory tree is filtered by /// inputParams if non-null. /// </summary> private static void BuildCRCStream(string directory, InputParams inputParams, ref MemoryStream memoryStream) { DirectoryInfo directoryInfo = new DirectoryInfo(directory); SyncResults results = new SyncResults(); // get filtered list of files in this directory FileInfo[] files = new Sync().GetFiles(directoryInfo, inputParams, ref results); // sort by name for deterministic order Array.Sort(files, new FileInfoComparer()); // write information about each file to stream foreach (FileInfo fileInfo in files) { byte[] bytes = ASCIIEncoding.UTF8.GetBytes(fileInfo.Name); memoryStream.Write(bytes, 0, bytes.Length); bytes = BitConverter.GetBytes(fileInfo.Length); memoryStream.Write(bytes, 0, bytes.Length); bytes = BitConverter.GetBytes(fileInfo.LastWriteTime.ToBinary()); memoryStream.Write(bytes, 0, bytes.Length); bytes = ASCIIEncoding.UTF8.GetBytes(fileInfo.Attributes.ToString()); memoryStream.Write(bytes, 0, bytes.Length); } // get filtered list of subdirectories DirectoryInfo[] subdirs = new Sync().GetDirectories(directoryInfo, inputParams, ref results); // sort by name for deterministic order Array.Sort(subdirs, new DirectoryInfoComparer()); foreach (DirectoryInfo subdir in subdirs) { // write information about each subdirectory to stream byte[] bytes = ASCIIEncoding.UTF8.GetBytes(subdir.Name); memoryStream.Write(bytes, 0, bytes.Length); // recurse BuildCRCStream(Path.Combine(directory, subdir.Name), inputParams, ref memoryStream); } }
public void Initialization() { inputParams = new InputParams(); expectedResults = new SyncResults(); }