Simplified the CLI and moved actual commands into a separate file

This commit is contained in:
Kenneth Skovhede
2013-05-02 20:57:39 +02:00
parent cba82103a6
commit 7ac9ce698a
5 changed files with 434 additions and 400 deletions
+375
View File
@@ -0,0 +1,375 @@
// Copyright (C) 2011, Kenneth Skovhede
// http://www.hexad.dk, opensource@hexad.dk
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
namespace Duplicati.CommandLine
{
public static class Commands
{
public static int PurgeSignatureCache(List<string> args, Dictionary<string, string> options)
{
Library.Main.Interface.PurgeSignatureCache(options);
return 0;
}
public static int Help(List<string> args, Dictionary<string, string> options)
{
if (args.Count < 1)
Duplicati.CommandLine.Help.PrintUsage("help", options);
else
Duplicati.CommandLine.Help.PrintUsage(args[0], options);
return 0;
}
public static int List(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
Console.WriteLine(string.Join(Environment.NewLine, Duplicati.Library.Main.Interface.List(args[0], options)));
return 0;
}
public static int ListCurrentFiles(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
Console.WriteLine(string.Join(Environment.NewLine, new List<string>(Duplicati.Library.Main.Interface.ListCurrentFiles(args[0], options)).ToArray()));
return 0;
}
public static int ListSourceFolders(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
Console.WriteLine(string.Join(Environment.NewLine, Duplicati.Library.Main.Interface.ListSourceFolders(args[0], options) ?? new string[0]));
return 0;
}
public static int ListActualSignatureFiles(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
List<KeyValuePair<Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string>> files = Duplicati.Library.Main.Interface.ListActualSignatureFiles(args[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);
Console.WriteLine();
Console.WriteLine("* " + Strings.Program.SymlinksHeader + ":");
foreach (KeyValuePair<Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files)
if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.Symlink)
Console.WriteLine(x.Value);
return 0;
}
public static int CollectionStatus(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
List<Duplicati.Library.Main.ManifestEntry> entries = Duplicati.Library.Main.Interface.ParseFileList(args[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));
}
}
return 0;
}
public static int DeleteAllButN(List<string> args, Dictionary<string, string> options)
{
int n = 0;
if (!int.TryParse(args[0], out n) || n < 0)
{
Console.WriteLine(string.Format(Strings.Program.IntegerParseError, args[0]));
return 200;
}
options["delete-all-but-n-full"] = n.ToString();
args.RemoveAt(0);
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
Console.WriteLine(Duplicati.Library.Main.Interface.DeleteAllButN(args[0], options));
return 0;
}
public static int DeleteAllButNFull(List<string> args, Dictionary<string, string> options)
{
int n = 0;
if (!int.TryParse(args[0], out n) || n < 0)
{
Console.WriteLine(string.Format(Strings.Program.IntegerParseError, args[0]));
return 200;
}
options["delete-all-but-n-full"] = n.ToString();
args.RemoveAt(0);
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
Console.WriteLine(Duplicati.Library.Main.Interface.DeleteAllButNFull(args[0], options));
return 0;
}
public static int DeleteOlderThan(List<string> args, Dictionary<string, string> options)
{
try
{
Duplicati.Library.Utility.Timeparser.ParseTimeSpan(args[0]);
}
catch (Exception ex)
{
Console.WriteLine(string.Format(Strings.Program.TimeParseError, args[0], ex.Message));
return 200;
}
options["delete-older-than"] = args[0];
args.RemoveAt(0);
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
Console.WriteLine(Duplicati.Library.Main.Interface.DeleteOlderThan(args[0], options));
return 0;
}
public static int Cleanup(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
Console.WriteLine(Duplicati.Library.Main.Interface.Cleanup(args[0], options));
return 0;
}
public static int CreateFolder(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
Duplicati.Library.Main.Interface.CreateFolder(args[0], options);
Console.WriteLine(string.Format(Strings.Program.FolderCreatedMessage, args[0]));
return 0;
}
public static int FindLastVersion(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
List<KeyValuePair<string, DateTime>> results = Duplicati.Library.Main.Interface.FindLastFileVersion(args[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));
return 0;
}
public static int Verify(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 1)
return PrintWrongNumberOfArguments(args, 1);
List<KeyValuePair<Duplicati.Library.Main.BackupEntryBase, Exception>> results = Duplicati.Library.Main.Interface.VerifyBackup(args[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));
}
return 0;
}
public static int Restore(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 2)
return PrintWrongNumberOfArguments(args, 2);
Console.WriteLine(Duplicati.Library.Main.Interface.Restore(args[0], args[1].Split(System.IO.Path.PathSeparator), options));
return 0;
}
public static int Backup(List<string> args, Dictionary<string, string> options)
{
if (args.Count != 2)
return PrintWrongNumberOfArguments(args, 2);
string result = Duplicati.Library.Main.Interface.Backup(args[0].Split(System.IO.Path.PathSeparator), args[1], options);
Console.WriteLine(result);
Dictionary<string, string> tmp = ParseDuplicatiOutput(result);
//Interrupted = 50
if (tmp.ContainsKey("PartialBackup"))
return 50;
//Completed with warnings = 2
if (tmp.ContainsKey("NumberOfWarnings"))
return 2;
//Success, but no upload = 1
if (tmp.ContainsKey("BytesUploaded"))
{
long s;
if (long.TryParse(tmp["BytesUploaded"], out s) && s == 0)
return 1;
}
return 0;
}
private static Dictionary<string, string> ParseDuplicatiOutput(string output)
{
System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex("(?<key>[^\\:]+)\\:(?<value>[^\\n]*)", System.Text.RegularExpressions.RegexOptions.Singleline);
Dictionary<string, string> res = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
foreach (System.Text.RegularExpressions.Match m in re.Matches(output))
res[m.Groups["key"].Value.Trim()] = m.Groups["value"].Value.Trim();
return res;
}
private static int PrintWrongNumberOfArguments(List<string> args, int expected)
{
Console.WriteLine(Strings.Program.WrongNumberOfCommandsError_v2, args.Count, expected, args.Count == 0 ? "" : "\"" + string.Join("\", \"", args.ToArray()) + "\"");
return 200;
}
public static int PrintInvalidCommand(List<string> args)
{
Console.WriteLine(Strings.Program.InvalidCommandError, args.Count == 0 ? "" : args[0]);
return 200;
}
}
}
@@ -70,6 +70,7 @@
<DependentUpon>Program.resx</DependentUpon>
</Compile>
<Compile Include="UnitTest.cs" />
<Compile Include="Commands.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj">
+37 -396
View File
@@ -116,53 +116,8 @@ namespace Duplicati.CommandLine
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 0;
}
}
if (cargs.Count < 2 || cargs[0].Trim().Equals("help", StringComparison.InvariantCultureIgnoreCase))
{
if (cargs.Count == 1 && !cargs[0].Trim().Equals("help", StringComparison.InvariantCultureIgnoreCase) )
{
PrintWrongNumberOfArguments(cargs, 2);
return 200;
}
if (cargs.Count < 2)
Help.PrintUsage("help", options);
else
Help.PrintUsage(cargs[1], options);
return 0;
}
string source = cargs[0];
string target = cargs[1];
bool operationSpecified = false;
if (source.Trim().ToLower() == "restore" && cargs.Count == 3)
{
source = target;
target = cargs[2];
options["restore"] = null;
cargs.RemoveAt(0);
operationSpecified = true;
}
else if (source.Trim().ToLower() == "backup" && cargs.Count == 3)
{
source = target;
target = cargs[2];
cargs.RemoveAt(0);
operationSpecified = true;
}
string command = cargs[0];
cargs.RemoveAt(0);
if (!options.ContainsKey("passphrase"))
if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("PASSPHRASE")))
@@ -176,319 +131,34 @@ namespace Duplicati.CommandLine
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(Environment.NewLine, Duplicati.Library.Main.Interface.List(target, options)));
else if (source.Trim().ToLower() == "list-current-files")
var knownCommands = new Dictionary<string, Func<List<string>, Dictionary<string, string>, int>>(StringComparer.InvariantCultureIgnoreCase);
knownCommands["purge-signature-cache"] = Commands.PurgeSignatureCache;
knownCommands["help"] = Commands.Help;
knownCommands["list"] = Commands.List;
knownCommands["list-current-files"] = Commands.ListCurrentFiles;
knownCommands["list-source-folders"] = Commands.ListSourceFolders;
knownCommands["list-actual-signature-files"] = Commands.ListActualSignatureFiles;
knownCommands["collection-status"] = Commands.CollectionStatus;
knownCommands["delete-all-but-n-full"] = Commands.DeleteAllButNFull;
knownCommands["delete-all-but-n"] = Commands.DeleteAllButN;
knownCommands["delete-older-than"] = Commands.DeleteOlderThan;
knownCommands["cleanup"] = Commands.Cleanup;
knownCommands["create-folder"] = Commands.CreateFolder;
knownCommands["find-last-version"] = Commands.FindLastVersion;
knownCommands["verify"] = Commands.Verify;
knownCommands["restore"] = Commands.Restore;
knownCommands["backup"] = Commands.Backup;
if (knownCommands.ContainsKey(command))
{
cargs.RemoveAt(0);
if (cargs.Count != 1)
{
PrintWrongNumberOfArguments(cargs, 1);
return 200;
}
Console.WriteLine(string.Join(Environment.NewLine, 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 200;
}
Console.WriteLine(string.Join(Environment.NewLine, 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 200;
}
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);
Console.WriteLine();
Console.WriteLine("* " + Strings.Program.SymlinksHeader + ":");
foreach (KeyValuePair<Duplicati.Library.Main.RSync.RSyncDir.PatchFileType, string> x in files)
if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.Symlink)
Console.WriteLine(x.Value);
}
else if (source.Trim().ToLower() == "collection-status")
{
cargs.RemoveAt(0);
if (cargs.Count != 1)
{
PrintWrongNumberOfArguments(cargs, 1);
return 200;
}
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 200;
}
options["delete-all-but-n-full"] = n.ToString();
cargs.RemoveAt(0);
cargs.RemoveAt(0);
if (cargs.Count != 1)
{
PrintWrongNumberOfArguments(cargs, 1);
return 200;
}
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 200;
}
options["delete-older-than"] = target;
cargs.RemoveAt(0);
cargs.RemoveAt(0);
if (cargs.Count != 1)
{
PrintWrongNumberOfArguments(cargs, 1);
return 200;
}
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 200;
}
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 200;
}
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 200;
}
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 200;
}
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 200;
}
Console.WriteLine(Duplicati.Library.Main.Interface.Restore(source, target.Split(System.IO.Path.PathSeparator), options));
return knownCommands[command](cargs, options);
}
else
{
if (cargs.Count != 2)
{
PrintWrongNumberOfArguments(cargs, 2);
return 200;
}
//Assume file:// if no url fragment is found, but only if "backup" is specified
if (!target.Contains("://") && !operationSpecified)
{
Console.WriteLine(Strings.Program.MissingURISchemeError, "file://", target);
return 200;
}
string result = Duplicati.Library.Main.Interface.Backup(source.Split(System.IO.Path.PathSeparator), target, options);
Console.WriteLine(result);
Dictionary<string, string> tmp = ParseDuplicatiOutput(result);
//Interrupted = 50
if (tmp.ContainsKey("PartialBackup"))
return 50;
//Completed with warnings = 2
if (tmp.ContainsKey("NumberOfWarnings"))
return 2;
//Success, but no upload = 1
if (tmp.ContainsKey("BytesUploaded"))
{
long s;
if (long.TryParse(tmp["BytesUploaded"], out s) && s == 0)
return 1;
}
Commands.PrintInvalidCommand(cargs);
return 200;
}
//Normal operation = 0
return 0;
}
catch (Exception ex)
{
@@ -520,20 +190,6 @@ namespace Duplicati.CommandLine
}
}
public static Dictionary<string, string> ParseDuplicatiOutput(string output)
{
System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex("(?<key>[^\\:]+)\\:(?<value>[^\\n]*)", System.Text.RegularExpressions.RegexOptions.Singleline);
Dictionary<string, string> res = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
foreach (System.Text.RegularExpressions.Match m in re.Matches(output))
res[m.Groups["key"].Value.Trim()] = m.Groups["value"].Value.Trim();
return res;
}
private static void PrintWrongNumberOfArguments(List<string> args, int expected)
{
Console.WriteLine(Strings.Program.WrongNumberOfCommandsError_v2, args.Count, expected, args.Count == 0 ? "" : "\"" + string.Join("\", \"", args.ToArray()) + "\"");
}
public static IList<Library.Interface.ICommandLineArgument> SupportedCommands
{
@@ -561,7 +217,7 @@ namespace Duplicati.CommandLine
filter = newfilter;
Dictionary<string, string> opt = Library.Utility.CommandLineParser.ExtractOptions(fargs);
String newsource = null, newtarget = null;
string newsource = null, newtarget = null;
foreach (KeyValuePair<String, String> keyvalue in opt)
{
switch (keyvalue.Key.ToLower())
@@ -584,13 +240,11 @@ namespace Duplicati.CommandLine
// Note: this block is faily complex due to the way parameters are handled by the rest of the
// procedure. It could likely be much simpler and versatile (allow --source and --target on
// restore or other actions) with some refactoring of the parameters decision tree
if (!String.IsNullOrEmpty(newsource) || !String.IsNullOrEmpty(newtarget))
if (!string.IsNullOrEmpty(newsource) || !string.IsNullOrEmpty(newtarget))
{
int offset = cargs.Count > 0 && (cargs[0] == "backup" || cargs[0] == "restore") ? 1 : 0;
bool isrestore = cargs.Count > 0 && cargs[0] == "restore"
|| cargs.Count >= 2 && cargs[0].Contains("://");
bool isrestore = cargs.Count > 0 && cargs[0] == "restore";
if (cargs.Count == 0 + offset)
if (cargs.Count == 1 || cargs.Count == 0)
{
// if either is empty loading will fail later, so we don't really care.
if (!String.IsNullOrEmpty(newsource)) cargs.Add(newsource);
@@ -598,28 +252,15 @@ namespace Duplicati.CommandLine
}
else
{
bool isurl = cargs[offset].Contains("://");
bool isdir = !isurl && cargs[offset].IndexOfAny(new char[] { '/', '\\', ':' }) >= 0;
if (offset > 0 || isdir || isurl)
if (isrestore)
{
if (cargs.Count == 1 + offset)
{
if (isrestore ^ isurl)
{
if (!String.IsNullOrEmpty(newtarget)) cargs[offset] = newtarget;
if (!String.IsNullOrEmpty(newsource)) cargs.Insert(offset, newsource);
}
else
{
if (!String.IsNullOrEmpty(newtarget)) cargs[offset] = newsource;
if (!String.IsNullOrEmpty(newsource)) cargs.Add(newtarget);
}
}
else
{
if (!String.IsNullOrEmpty(newsource)) cargs[offset] = newsource;
if (!String.IsNullOrEmpty(newtarget)) cargs[offset + 1] = newtarget;
}
if (!String.IsNullOrEmpty(newtarget)) cargs[1] = newtarget;
if (!String.IsNullOrEmpty(newsource)) cargs.Insert(1, newsource);
}
else
{
if (!String.IsNullOrEmpty(newtarget)) cargs[1] = newsource;
if (!String.IsNullOrEmpty(newsource)) cargs.Add(newtarget);
}
}
}
+17 -4
View File
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.296
// Runtime Version:4.0.30319.1
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -90,7 +90,7 @@ namespace Duplicati.CommandLine.Strings {
}
/// <summary>
/// Looks up a localized string similar to Inc\t{0}\t{1}\t{2}.
/// Looks up a localized string similar to Inc\t{0}\t{1}\t{2}.
/// </summary>
internal static string CollectionStatusLineInc {
get {
@@ -215,6 +215,16 @@ namespace Duplicati.CommandLine.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Command not supported: {0}
///{1}.
/// </summary>
internal static string InvalidCommandError {
get {
return ResourceManager.GetString("InvalidCommandError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The source or target parameter must be a URL, try using &quot;{0}{1}&quot;.
/// </summary>
@@ -292,7 +302,7 @@ namespace Duplicati.CommandLine.Strings {
/// Using this system, Duplicati can detect if you want to backup or restore.
/// The cleanup and delete commands do not delete files, unless the --force option is specified, so you may examine what files are affected, before actually deleting the files.
/// The cleanup command should not be used unless a backup was interrupted and has left partial files. Duplicati will inform you if this happens.
/// The delete command can be used to remove bac [rest of string was truncated]&quot;;.
/// The delete command can be used to remove backup sets when newer backups are present..
/// </summary>
internal static string ProgramUsageBackend {
get {
@@ -347,7 +357,10 @@ namespace Duplicati.CommandLine.Strings {
/// Duplicati uses a &quot;first-touch&quot; filter where the first rule that matches a file determines if the file is included or excluded. Internally Duplicati uses regular expression filters, but supports filters in the form of filename globbing. The order of the commandline arguments also determine what order they are applied in. An example:
/// --include=*.txt --exclude=*\Thumbs.db --include=*
///
/// Even though the last filter includes everything, no [rest of string was truncated]&quot;;.
/// Even though the last filter includes everything, no files named &quot;Thumbs.db&quot; are included because they match the exclude rule before the include rule. Paths are evaluated in full, and as paths that are relative to folder being backed up, but including a leading slash. An example:
///
///{0}
/// If a folder is excluded, files in that folder are always excluded, even if there are filters that include files in that folder. If a folder is included with a wildcard at the end, all files are included, if the folder is included without a wildcard, files may be excluded or included with extra rules..
/// </summary>
internal static string ProgramUsageFilters {
get {
@@ -337,4 +337,8 @@ Errors: {3}</value>
<value>Found {0} commands but expected {1}, commands:
{2}</value>
</data>
<data name="InvalidCommandError" xml:space="preserve">
<value>Command not supported: {0}
{1}</value>
</data>
</root>