/// <summary> /// Configure the OptionsSet object used to parse command line parameters /// </summary> /// <param name="parameters"></param> /// <returns></returns> private static OptionSet ConfigureOptions(ProgramParams parameters) { return(new OptionSet() .Add("a|analyse", "analysis only, no synchronisation", a => parameters.AnalyseOnly = true) .Add("af|analyse first", "analysis only first and only synch if within limits defined in -ld and -lf options", af => parameters.AnalyseFirst = true) .Add("xh|exclude hidden", "exclude hidden files and directories", xh => parameters.ExcludeHidden = true) .Add("xi|exclude identical", "exclude identical files from the report", xi => parameters.ExcludeIdenticalFiles = true) .Add("df|delete files", "delete files in destination which do not appear in source", df => parameters.DeleteFilesFromDest = true) .Add("dd|delete dirs", "delete directories in destination which do not appear in source", dd => parameters.DeleteDirsFromDest = true) .Add("xf|exclude files=", "exclude files from source that match any of the filespecs", xf => parameters.ExcludeFiles = FormRegexListfromString(xf, parameters.UseRegex)) .Add("xd|exclude directories=", "exclude directories from source that match any of the filespecs", xd => parameters.ExcludeDirs = FormRegexListfromString(xd, parameters.UseRegex)) .Add("if|include files=", "only include files from source that match one of the filespecs", inf => parameters.IncludeFiles = FormRegexListfromString(inf, parameters.UseRegex)) .Add("id|include directories=", "include directories from source that match one of the filespecs", ind => parameters.IncludeDirs = FormRegexListfromString(ind, parameters.UseRegex)) .Add("ndf|exclude delete files=", "exclude files from deletion that match any of the filespecs", ndf => parameters.DeleteExcludeFiles = FormRegexListfromString(ndf, parameters.UseRegex)) .Add("ndd|exclude delete directories=", "Exclude directories from deletion that match any of the filespecs", ndd => parameters.DeleteExcludeDirs = FormRegexListfromString(ndd, parameters.UseRegex)) .Add("ld|limit directory synch=", "limit on the number of directories that will be synchronised if the --af option is set", ( uint ld ) => parameters.DirectorySynchLimit = ld) .Add("lf|limit file synch=", "limit on the number of files that will be synchronised if the --af option is set", ( uint lf ) => parameters.FileSynchLimit = lf)); }
/// <summary> /// Deserialise an xml configuration file into a set of backup jobs /// </summary> /// <param name="backups"></param> /// <param name="configFile"></param> /// <returns></returns> private static bool GetOptionsFromXmlFile(List <ProgramParams> backups, string configFile) { bool success = true; try { // Deserialise the file contents into a Configuration object Configuration xmlConfig = (Configuration) new XmlSerializer(typeof(Configuration)).Deserialize(new FileStream(configFile, FileMode.Open)); // Now copy fields from the Configuration object to the parameters, one InputParams per backup job foreach (Backup backup in xmlConfig.Backups) { ProgramParams parameters = new ProgramParams(); // Common options parameters.AnalyseOnly = xmlConfig.Options.analyseOnly; parameters.ExcludeHidden = xmlConfig.Options.excludeHidden; parameters.ExcludeIdenticalFiles = xmlConfig.Options.excludeIdentical; parameters.DeleteDirsFromDest = xmlConfig.Options.deleteDirectories; parameters.DeleteFilesFromDest = xmlConfig.Options.deleteFiles; parameters.UseRegex = xmlConfig.Options.useRegex; // Specific to this backup parameters.Name = backup.name; parameters.SourceDirectory = new DirectoryInfo(backup.source).FullName; parameters.DestinationDirectory = new DirectoryInfo(backup.destination).FullName; if (backup.directoryExcludes.directoryFilter != null) { parameters.ExcludeDirs = RegexListFromStringList(backup.directoryExcludes.directoryFilter, parameters.UseRegex); } if (backup.directoryIncludes.directoryFilter != null) { parameters.IncludeDirs = RegexListFromStringList(backup.directoryIncludes.directoryFilter, parameters.UseRegex); } backups.Add(parameters); } } catch (FileNotFoundException nfException) { Console.Write("SynchApp: "); Console.WriteLine(nfException.Message); success = false; } catch (InvalidOperationException ioException) { Console.Write("SynchApp: "); Console.WriteLine(ioException.Message); if (ioException.InnerException != null) { Console.WriteLine(ioException.InnerException.Message); } success = false; } return(success); }
/// <summary> /// Validate option combinations /// </summary> /// <param name="options"></param> /// <param name="parameters"></param> /// <returns></returns> private static bool ValidateOptions(OptionSet options, ProgramParams parameters) { bool success = false; // Check that both includes and excludes have not been defined if (((parameters.IncludeFiles == null) || (parameters.ExcludeFiles == null)) && ((parameters.IncludeDirs == null) || (parameters.ExcludeDirs == null))) { // Deletion exclusions should only be defined if destination deletion has been specified if (((parameters.DeleteFilesFromDest == true) || (parameters.DeleteExcludeFiles == null)) && ((parameters.DeleteDirsFromDest == true) || (parameters.DeleteExcludeDirs == null))) { // If the Analyse First option has been set then one of the limits should also be specified if ((parameters.AnalyseFirst == false) || ((parameters.AnalyseOnly == false) && ((parameters.DirectorySynchLimit + parameters.FileSynchLimit) > 0))) { success = true; } else { if (parameters.AnalyseOnly == true) { Console.WriteLine("Error: analyse first and analyse only options cannot both be set."); DisplayHelp(options); } else { Console.WriteLine("Error: analyse first option requries a limit ( -ld or -lf )."); DisplayHelp(options); } } } else { Console.WriteLine("Error: exclude-from-deletion options (-ndf and -ndd) require deletion (-df or -dd) enabled."); DisplayHelp(options); } } else { Console.WriteLine("Error: cannot include and exclude items at the same time."); DisplayHelp(options); } return(success); }
/// <summary> /// Main entry point for the SynchApp program /// Parse the command line arguements, perform some basic validation and start the synchronisation process /// </summary> /// <param name="args"></param> static int Main(string[] args) { ExitCode success = ExitCode.Success; int errorCode = ( int )success; bool commandLineJob = true; try { // Collection of InputParams one per backup job List <ProgramParams> backupJobs = new List <ProgramParams>(); // Assume that the options for a single backup is specified on the command line and only a single InputParams object // is required ProgramParams commandParameters = new ProgramParams(); OptionSet options = ConfigureOptions(commandParameters); // Parse the options and the left over directories List <string> directories = options.Parse(args); // If two unmatched strings have been specified then assume that the command line includes the source and destination // and that the InputParams will be set by the OptionSet if (directories.Count == 2) { commandParameters.SourceDirectory = new DirectoryInfo(directories[0]).FullName; commandParameters.DestinationDirectory = new DirectoryInfo(directories[1]).FullName; backupJobs.Add(commandParameters); } else if (directories.Count == 1) { // Assume that if a single file has been specified then it is an xml configuration file commandLineJob = false; if (GetOptionsFromXmlFile(backupJobs, directories[0]) == false) { DisplayHelp(options); success = ExitCode.XmlError; } } else { DisplayHelp(options); success = ExitCode.DirectoryError; } if (success == ExitCode.Success) { // If this is a command line job then perform it and return its success code if (commandLineJob == true) { success = PerformBackup(backupJobs[0], options); // Include destination file and directory deletions in the error code if successfull if (success == ExitCode.Success) { errorCode += ( int )(((UnmatchedDestinationFile > 255 ? 255 : UnmatchedDestinationFile) & 255) << 8); errorCode += ( int )(((UnmatchedDestinationDirectory > 255 ? 255 : UnmatchedDestinationDirectory) & 255) << 16); } } else { // Set up alternative console logging Console.SetOut(new Logging(30)); // Process each backup job until foreach (ProgramParams job in backupJobs) { PerformBackup(job, options); } } } } catch (OptionException oException) { Console.Write("SynchApp: "); Console.WriteLine(oException.Message); Console.WriteLine("Try 'SynchApp --help' for more information"); success = ExitCode.DirectoryError; } catch (NotSupportedException nsException) { Console.Write("SynchApp: "); Console.WriteLine(nsException.Message); Console.WriteLine("Try 'SynchApp --help' for more information"); success = ExitCode.DirectoryError; } // Include the success code in the erorr code errorCode += ( int )success; return(errorCode); }
/// <summary> /// Carry out a backup job as defined by options in the ProgramParams /// </summary> /// <param name="parameters"></param> /// <param name="options"></param> /// <returns></returns> private static ExitCode PerformBackup(ProgramParams parameters, OptionSet options) { ExitCode success = ExitCode.Success; ResetLocalCounts(); // Check that at least the source directory exists and that the directories don't overlap in some way if (Directory.Exists(parameters.SourceDirectory) == true) { string fullSrcDir = Path.GetFullPath(parameters.SourceDirectory); string fullDestDir = Path.GetFullPath(parameters.DestinationDirectory); if ((parameters.DestinationDirectory.StartsWith(fullSrcDir) == false) && (parameters.SourceDirectory.StartsWith(fullDestDir) == false)) { if (ValidateOptions(options, parameters) == true) { Console.WriteLine("=================================================="); Console.WriteLine("Job started : {0}", DateTime.Now.TimeOfDay); if (parameters.Name.Length > 0) { Console.WriteLine(parameters.Name); } Console.WriteLine("Synchronising source '{0}' and destination '{1}'", fullSrcDir, fullDestDir); Console.WriteLine("=================================================="); Sync synchroniseFiles = new Sync(parameters) { Log = LogResult }; if (parameters.AnalyseFirst == true) { // Analyse only Console.WriteLine("Analysing..."); parameters.AnalyseOnly = true; synchroniseFiles.Start(); if (((parameters.DirectorySynchLimit > 0) && (DirectoriesMissing > parameters.DirectorySynchLimit)) || ((parameters.FileSynchLimit > 0) && (FilesMissing > parameters.FileSynchLimit))) { Console.WriteLine("+++++Synchronisation limits exceeded, no synchronisation performed {0} directories {1} files", DirectoriesMissing, FilesMissing); success = ExitCode.LimitsReached; } else { // Check if any synchronisation is required if ((DirectoriesMissing > 0) || (FilesMissing > 0) || (FilesChanged > 0)) { // Reset parameters for second pass below parameters.AnalyseOnly = false; parameters.AnalyseFirst = false; parameters.FileSynchLimit = 0; parameters.DirectorySynchLimit = 0; Console.WriteLine("Synchronising..."); } else { Console.WriteLine("No synchronisation required"); } } } if (parameters.AnalyseFirst == false) { synchroniseFiles.Start(); if ((DirectoriesMissing == 0) && (FilesMissing == 0) && (FilesChanged == 0)) { Console.WriteLine("No synchronisation required"); } } Console.WriteLine("=================================================="); Console.WriteLine("Job finished : {0}", DateTime.Now.TimeOfDay); Console.WriteLine("=================================================="); Console.WriteLine(""); Console.WriteLine(""); } else { success = ExitCode.OptionError; } } else { Console.WriteLine("Error: source directory {0} and destination directory {1} cannot contain each other", fullSrcDir, fullDestDir); DisplayHelp(options); success = ExitCode.DirectoryError; } } else { Console.WriteLine("Error: source directory {0} does not exist", parameters.SourceDirectory); DisplayHelp(options); success = ExitCode.DirectoryError; } return(success); }