public static int RealMain(string[] args) { bool verboseErrors = false; bool verbose = false; try { List <string> cargs = new List <string>(args); var tmpparsed = FilterCollector.ExtractOptions(cargs); var options = tmpparsed.Item1; var filter = tmpparsed.Item2; verboseErrors = Library.Utility.Utility.ParseBoolOption(options, "debug-output"); verbose = Library.Utility.Utility.ParseBoolOption(options, "verbose"); //If we are on Windows, append the bundled "win-tools" programs to the search path //We add it last, to allow the user to override with other versions if (!Library.Utility.Utility.IsClientLinux) { string wintools = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "win-tools"); Environment.SetEnvironmentVariable("PATH", Environment.GetEnvironmentVariable("PATH") + System.IO.Path.PathSeparator.ToString() + wintools + System.IO.Path.PathSeparator.ToString() + System.IO.Path.Combine(wintools, "gpg") //GPG needs to be in a subfolder for wrapping reasons ); } #if DEBUG if (cargs.Count > 1 && cargs[0].ToLower() == "unittest") { //The unit test is only enabled in DEBUG builds //it works by getting a list of folders, and treats them as //if they have the same data, but on different times //The first folder is used to make a full backup, //and each subsequent folder is used to make an incremental backup //After all backups are made, the files are restored and verified against //the original folders. //The best way to test it, is to use SVN checkouts at different //revisions, as this is how a regular folder would evolve cargs.RemoveAt(0); UnitTest.RunTest(cargs.ToArray(), options); return(0); } #endif if (cargs.Count == 1 && string.Equals(cargs[0], "changelog", StringComparison.InvariantCultureIgnoreCase)) { var path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "changelog.txt"); Console.WriteLine(System.IO.File.ReadAllText(path)); return(0); } foreach (string internaloption in Library.Main.Options.InternalOptions) { if (options.ContainsKey(internaloption)) { Console.WriteLine(Strings.Program.InternalOptionUsedError, internaloption); return(200); } } // Probe for "help" to avoid extra processing bool isHelp = cargs.Count == 0 || (cargs.Count >= 1 && string.Equals(cargs[0], "help", StringComparison.InvariantCultureIgnoreCase)); if (!isHelp && ((options.ContainsKey("parameters-file") && !string.IsNullOrEmpty("parameters-file")) || (options.ContainsKey("parameter-file") && !string.IsNullOrEmpty("parameter-file")) || (options.ContainsKey("parameterfile") && !string.IsNullOrEmpty("parameterfile")))) { string filename; if (options.ContainsKey("parameters-file") && !string.IsNullOrEmpty("parameters-file")) { filename = options["parameters-file"]; options.Remove("parameters-file"); } else { filename = options["parameter-file"]; options.Remove("parameter-file"); } if (!ReadOptionsFromFile(filename, ref filter, cargs, options)) { return(100); } } string command; if (cargs.Count > 0) { command = cargs[0]; cargs.RemoveAt(0); } else { command = "help"; } // Update probe for help isHelp = string.Equals(command, "help", StringComparison.InvariantCultureIgnoreCase); // Skip the env read if the command is help, otherwise we may report weirdness if (!isHelp) { if (!options.ContainsKey("passphrase")) { if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("PASSPHRASE"))) { options["passphrase"] = System.Environment.GetEnvironmentVariable("PASSPHRASE"); } } if (!options.ContainsKey("auth-password")) { if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("AUTH_PASSWORD"))) { options["auth-password"] = System.Environment.GetEnvironmentVariable("AUTH_PASSWORD"); } } if (!options.ContainsKey("auth-username")) { if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("AUTH_USERNAME"))) { options["auth-username"] = System.Environment.GetEnvironmentVariable("AUTH_USERNAME"); } } } var knownCommands = new Dictionary <string, Func <List <string>, Dictionary <string, string>, Library.Utility.IFilter, int> >(StringComparer.InvariantCultureIgnoreCase); knownCommands["help"] = Commands.Help; knownCommands["find"] = Commands.List; knownCommands["list"] = Commands.List; knownCommands["delete"] = Commands.Delete; knownCommands["backup"] = Commands.Backup; knownCommands["restore"] = Commands.Restore; knownCommands["repair"] = Commands.Repair; knownCommands["compact"] = Commands.Compact; knownCommands["create-report"] = Commands.CreateBugReport; knownCommands["compare"] = Commands.ListChanges; knownCommands["test"] = Commands.Test; knownCommands["verify"] = Commands.Test; knownCommands["test-filters"] = Commands.TestFilters; knownCommands["affected"] = Commands.Affected; if (!isHelp && verbose) { Console.WriteLine("Input command: {0}", command); Console.WriteLine("Input arguments: "); foreach (var a in cargs) { Console.WriteLine("\t{0}", a); } Console.WriteLine(); Console.WriteLine("Input options: "); foreach (var n in options) { Console.WriteLine("{0}: {1}", n.Key, n.Value); } Console.WriteLine(); } Duplicati.Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => { if (verbose) { Console.WriteLine(string.Format("Failed to delete temp file: {0}", path)); } }); var autoupdate = Library.Utility.Utility.ParseBoolOption(options, "auto-update"); options.Remove("auto-update"); if (knownCommands.ContainsKey(command)) { var res = knownCommands[command](cargs, options, filter); if (autoupdate) { var update = Library.AutoUpdater.UpdaterManager.LastUpdateCheckVersion; if (update == null) { update = Library.AutoUpdater.UpdaterManager.CheckForUpdate(); } if (update != null && update.Version != Library.AutoUpdater.UpdaterManager.SelfVersion.Version) { Console.WriteLine("Found update \"{0}\", downloading ...", update.Displayname); long lastpg = 0; Library.AutoUpdater.UpdaterManager.DownloadAndUnpackUpdate(update, f => { var npg = (long)(f * 100); if (Math.Abs(npg - lastpg) >= 5 || (npg == 100 && lastpg != 100)) { lastpg = npg; Console.WriteLine("Downloading {0}% ...", npg); } }); Console.WriteLine("Update \"{0}\" ({1}) installed, using on next launch", update.Displayname, update.Version); } } return(res); } else { Commands.PrintInvalidCommand(command, cargs); return(200); } } catch (Exception ex) { while (ex is System.Reflection.TargetInvocationException && ex.InnerException != null) { ex = ex.InnerException; } if (!(ex is Library.Interface.CancelException)) { if (!string.IsNullOrEmpty(ex.Message)) { Console.Error.WriteLine(); Console.Error.WriteLine(verboseErrors ? ex.ToString() : ex.Message); } } else { Console.Error.WriteLine(Strings.Program.UnhandledException, ex.ToString()); while (ex.InnerException != null) { ex = ex.InnerException; Console.Error.WriteLine(); Console.Error.WriteLine(Strings.Program.UnhandledInnerException, ex.ToString()); } } //Error = 100 return(100); } }
static void Main(string[] args) { try { List <string> cargs = new List <string>(args); string filter = Duplicati.Library.Utility.FilenameFilter.EncodeAsFilter(Duplicati.Library.Utility.FilenameFilter.ParseCommandLine(cargs, true)); Dictionary <string, string> options = CommandLineParser.ExtractOptions(cargs); //If we are on Windows, append the bundled "win-tools" programs to the search path //We add it last, to allow the user to override with other versions if (!Library.Utility.Utility.IsClientLinux) { string wintools = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "win-tools"); Environment.SetEnvironmentVariable("PATH", Environment.GetEnvironmentVariable("PATH") + System.IO.Path.PathSeparator.ToString() + wintools + System.IO.Path.PathSeparator.ToString() + System.IO.Path.Combine(wintools, "gpg") //GPG needs to be in a subfolder for wrapping reasons ); } #if DEBUG if (cargs.Count > 1 && cargs[0].ToLower() == "unittest") { //The unit test is only enabled in DEBUG builds //it works by getting a list of folders, and treats them as //if they have the same data, but on different times //The first folder is used to make a full backup, //and each subsequent folder is used to make an incremental backup //After all backups are made, the files are restored and verified against //the original folders. //The best way to test it, is to use SVN checkouts at different //revisions, as this is how a regular folder would evolve cargs.RemoveAt(0); UnitTest.RunTest(cargs.ToArray(), options); return; } #endif if (cargs.Count > 0) { //Because options are of the format --name=value, it seems natural to write "delete-all-but-n-full=5", // so we allow that format as well foreach (string s in COMMANDS_AS_ARGUMENTS) { if (cargs[0].Trim().ToLower().StartsWith(s + "=")) { cargs.Insert(1, cargs[0].Substring(s.Length + 1)); cargs[0] = s; } } } //AFTER converting options to commands, we check for internal switches foreach (string internaloption in Library.Main.Options.InternalOptions) { if (options.ContainsKey(internaloption)) { Console.WriteLine(Strings.Program.InternalOptionUsedError, internaloption); return; } } if ((options.ContainsKey("parameters-file") && !string.IsNullOrEmpty("parameters-file")) || (options.ContainsKey("parameter-file") && !string.IsNullOrEmpty("parameter-file"))) { string filename; if (options.ContainsKey("parameters-file") && !string.IsNullOrEmpty("parameters-file")) { filename = options["parameters-file"]; options.Remove("parameters-file"); } else { filename = options["parameter-file"]; options.Remove("parameter-file"); } if (!ReadOptionsFromFile(filename, ref filter, cargs, options)) { return; } } //After checking for internal options, we set the filter option if (!string.IsNullOrEmpty(filter)) { options["filter"] = filter; } if (cargs.Count == 1) { switch (cargs[0].Trim().ToLower()) { case "purge-signature-cache": Library.Main.Interface.PurgeSignatureCache(options); return; } } if (cargs.Count < 2 || cargs[0].Trim().Equals("help", StringComparison.InvariantCultureIgnoreCase)) { if (cargs.Count < 2) { Help.PrintUsage("help", options); } else { Help.PrintUsage(cargs[1], options); } return; } string source = cargs[0]; string target = cargs[1]; if (source.Trim().ToLower() == "restore" && cargs.Count == 3) { source = target; target = cargs[2]; options["restore"] = null; cargs.RemoveAt(0); } else if (source.Trim().ToLower() == "backup" && cargs.Count == 3) { source = target; target = cargs[2]; cargs.RemoveAt(0); } if (!options.ContainsKey("passphrase")) { if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("PASSPHRASE"))) { options["passphrase"] = System.Environment.GetEnvironmentVariable("PASSPHRASE"); } } if (!options.ContainsKey("ftp-password")) { if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("FTP_PASSWORD"))) { options["ftp-password"] = System.Environment.GetEnvironmentVariable("FTP_PASSWORD"); } } if (!options.ContainsKey("ftp-username")) { if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("FTP_USERNAME"))) { options["ftp-username"] = System.Environment.GetEnvironmentVariable("FTP_USERNAME"); } } if (source.Trim().ToLower() == "list") { Console.WriteLine(string.Join("\r\n", Duplicati.Library.Main.Interface.List(target, options))); } else if (source.Trim().ToLower() == "list-current-files") { cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } Console.WriteLine(string.Join("\r\n", new List <string>(Duplicati.Library.Main.Interface.ListCurrentFiles(target, options)).ToArray())); } else if (source.Trim().ToLower() == "list-source-folders") { cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } Console.WriteLine(string.Join("\r\n", Duplicati.Library.Main.Interface.ListSourceFolders(target, options) ?? new string[0])); } else if (source.Trim().ToLower() == "list-actual-signature-files") { cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } List <KeyValuePair <Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> > files = Duplicati.Library.Main.Interface.ListActualSignatureFiles(cargs[0], options); Console.WriteLine("* " + Strings.Program.DeletedFoldersHeader + ":"); foreach (KeyValuePair <Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files) { if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.DeletedFolder) { Console.WriteLine(x.Value); } } Console.WriteLine(); Console.WriteLine("* " + Strings.Program.AddedFoldersHeader + ":"); foreach (KeyValuePair <Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files) { if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.AddedFolder) { Console.WriteLine(x.Value); } } Console.WriteLine(); Console.WriteLine("* " + Strings.Program.DeletedFilesHeader + ":"); foreach (KeyValuePair <Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files) { if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.DeletedFile) { Console.WriteLine(x.Value); } } bool hasCombinedSignatures = false; foreach (KeyValuePair <Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files) { if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.AddedOrUpdatedFile) { hasCombinedSignatures = true; break; } } if (hasCombinedSignatures) { Console.WriteLine(); Console.WriteLine("* " + Strings.Program.NewOrModifiedFilesHeader + ":"); foreach (KeyValuePair <Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files) { if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.AddedOrUpdatedFile) { Console.WriteLine(x.Value); } } } else { Console.WriteLine(); Console.WriteLine("* " + Strings.Program.NewFilesHeader + ":"); foreach (KeyValuePair <Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files) { if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.AddedFile) { Console.WriteLine(x.Value); } } Console.WriteLine(); Console.WriteLine("* " + Strings.Program.ModifiedFilesHeader + ":"); foreach (KeyValuePair <Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files) { if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.UpdatedFile) { Console.WriteLine(x.Value); } } } Console.WriteLine(); Console.WriteLine("* " + Strings.Program.ControlFilesHeader + ":"); foreach (KeyValuePair <Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files) { if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.ControlFile) { Console.WriteLine(x.Value); } } } else if (source.Trim().ToLower() == "collection-status") { cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } List <Duplicati.Library.Main.ManifestEntry> entries = Duplicati.Library.Main.Interface.ParseFileList(cargs[0], options); Console.WriteLine(Strings.Program.CollectionStatusHeader.Replace("\\t", "\t"), entries.Count); foreach (Duplicati.Library.Main.ManifestEntry m in entries) { Console.WriteLine(); long size = Math.Max(m.Fileentry.Size, 0); foreach (KeyValuePair <Duplicati.Library.Main.SignatureEntry, Duplicati.Library.Main.ContentEntry> x in m.Volumes) { size += Math.Max(x.Key.Fileentry.Size, 0) + Math.Max(x.Value.Fileentry.Size, 0); } Console.WriteLine(Strings.Program.CollectionStatusLineFull.Replace("\\t", "\t"), m.Time.ToString(), m.Volumes.Count, Library.Utility.Utility.FormatSizeString(size)); foreach (Duplicati.Library.Main.ManifestEntry mi in m.Incrementals) { size = Math.Max(mi.Fileentry.Size, 0); foreach (KeyValuePair <Duplicati.Library.Main.SignatureEntry, Duplicati.Library.Main.ContentEntry> x in mi.Volumes) { size += Math.Max(x.Key.Fileentry.Size, 0) + Math.Max(x.Value.Fileentry.Size, 0); } Console.WriteLine(Strings.Program.CollectionStatusLineInc.Replace("\\t", "\t"), mi.Time.ToString(), mi.Volumes.Count, Library.Utility.Utility.FormatSizeString(size)); } } } else if (source.Trim().ToLower() == "delete-all-but-n-full" || source.Trim().ToLower() == "delete-all-but-n") { int n = 0; if (!int.TryParse(target, out n) || n < 0) { Console.WriteLine(string.Format(Strings.Program.IntegerParseError, target)); return; } options["delete-all-but-n-full"] = n.ToString(); cargs.RemoveAt(0); cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } if (source.Trim().ToLower() == "delete-all-but-n") { Console.WriteLine(Duplicati.Library.Main.Interface.DeleteAllButN(cargs[0], options)); } else { Console.WriteLine(Duplicati.Library.Main.Interface.DeleteAllButNFull(cargs[0], options)); } } else if (source.Trim().ToLower() == "delete-older-than") { try { Duplicati.Library.Utility.Timeparser.ParseTimeSpan(target); } catch (Exception ex) { Console.WriteLine(string.Format(Strings.Program.TimeParseError, target, ex.Message)); return; } options["delete-older-than"] = target; cargs.RemoveAt(0); cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } Console.WriteLine(Duplicati.Library.Main.Interface.DeleteOlderThan(cargs[0], options)); } else if (source.Trim().ToLower() == "cleanup") { cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } Console.WriteLine(Duplicati.Library.Main.Interface.Cleanup(cargs[0], options)); } else if (source.Trim().ToLower() == "create-folder") { cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } Duplicati.Library.Main.Interface.CreateFolder(cargs[0], options); Console.WriteLine(string.Format(Strings.Program.FolderCreatedMessage, cargs[0])); } else if (source.Trim().ToLower() == "find-last-version") { cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } List <KeyValuePair <string, DateTime> > results = Duplicati.Library.Main.Interface.FindLastFileVersion(cargs[0], options); Console.WriteLine(Strings.Program.FindLastVersionHeader.Replace("\\t", "\t")); foreach (KeyValuePair <string, DateTime> k in results) { Console.WriteLine(string.Format(Strings.Program.FindLastVersionEntry.Replace("\\t", "\t"), k.Value.Ticks == 0 ? Strings.Program.FileEntryNotFound : k.Value.ToLocalTime().ToString("yyyyMMdd hhmmss"), k.Key)); } } else if (source.Trim().ToLower() == "verify") { cargs.RemoveAt(0); if (cargs.Count != 1) { PrintWrongNumberOfArguments(cargs, 1); return; } List <KeyValuePair <Duplicati.Library.Main.BackupEntryBase, Exception> > results = Duplicati.Library.Main.Interface.VerifyBackup(cargs[0], options); int manifests = 0; int signatures = 0; int contentfiles = 0; int errors = 0; foreach (KeyValuePair <Duplicati.Library.Main.BackupEntryBase, Exception> x in results) { if (x.Key is Duplicati.Library.Main.ManifestEntry) { manifests++; } else if (x.Key is Duplicati.Library.Main.SignatureEntry) { signatures++; } else if (x.Key is Duplicati.Library.Main.ContentEntry) { contentfiles++; } if (x.Value != null) { errors++; } } Console.WriteLine(string.Format(Strings.Program.VerificationCompleted, manifests, signatures, contentfiles, errors)); if (errors > 0) { Console.WriteLine(); Console.WriteLine(Strings.Program.VerificationErrorHeader); Console.WriteLine(); foreach (KeyValuePair <Duplicati.Library.Main.BackupEntryBase, Exception> x in results) { if (x.Value != null) { Console.WriteLine(string.Format("{0}: {1}", x.Key.Filename, x.Value.Message)); } } } } else if (source.IndexOf("://") > 0 || options.ContainsKey("restore")) { if (cargs.Count != 2) { PrintWrongNumberOfArguments(cargs, 2); return; } Console.WriteLine(Duplicati.Library.Main.Interface.Restore(source, target.Split(System.IO.Path.PathSeparator), options)); } else { if (cargs.Count != 2) { PrintWrongNumberOfArguments(cargs, 2); return; } Console.WriteLine(Duplicati.Library.Main.Interface.Backup(source.Split(System.IO.Path.PathSeparator), target, options)); } } catch (Exception ex) { while (ex is System.Reflection.TargetInvocationException && ex.InnerException != null) { ex = ex.InnerException; } if (!(ex is Library.Interface.CancelException)) { if (!string.IsNullOrEmpty(ex.Message)) { Console.Error.WriteLine(); Console.Error.WriteLine(ex.Message); } } else { Console.Error.WriteLine(Strings.Program.UnhandledException, ex.ToString()); while (ex.InnerException != null) { ex = ex.InnerException; Console.Error.WriteLine(); Console.Error.WriteLine(Strings.Program.UnhandledInnerException, ex.ToString()); } } } }