Improved the usage dialog greatly, and added some missing options.
Still needs to describe the filter system. git-svn-id: https://duplicati.googlecode.com/svn/trunk@129 59da171f-624f-0410-aa54-27559c288bec
This commit is contained in:
@@ -305,7 +305,30 @@ namespace Duplicati.CommandLine
|
||||
lines.Add("");
|
||||
|
||||
foreach (string s in lines)
|
||||
Console.WriteLine(s);
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
{
|
||||
Console.WriteLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
string c = s;
|
||||
|
||||
string leadingSpaces = "";
|
||||
while (c.Length > 0 && c.StartsWith(" "))
|
||||
{
|
||||
leadingSpaces += " ";
|
||||
c = c.Remove(0, 1);
|
||||
}
|
||||
|
||||
while (c.Length > 0)
|
||||
{
|
||||
int len = Math.Min(Console.WindowWidth - 2, leadingSpaces.Length + c.Length);
|
||||
len -= leadingSpaces.Length;
|
||||
Console.WriteLine(leadingSpaces + c.Substring(0, len));
|
||||
c = c.Remove(0, len);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -315,6 +338,13 @@ namespace Duplicati.CommandLine
|
||||
lines.Add(" " + arg.LongDescription);
|
||||
if (arg.Aliases != null && arg.Aliases.Length > 0)
|
||||
lines.Add(" aliases: --" + string.Join(", --", arg.Aliases));
|
||||
|
||||
if (arg.ValidValues != null && arg.ValidValues.Length > 0)
|
||||
lines.Add(" values: " + string.Join(", ", arg.ValidValues));
|
||||
|
||||
if (!string.IsNullOrEmpty(arg.DefaultValue))
|
||||
lines.Add(" default value: " + arg.DefaultValue);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,14 @@ namespace Duplicati.Library.Backend
|
||||
set { m_longDescription = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default value for the parameter
|
||||
/// </summary>
|
||||
public string DefaultValue
|
||||
{
|
||||
get { return m_defaultValue; }
|
||||
set { m_defaultValue = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CommandLineArgument instance
|
||||
|
||||
@@ -28,5 +28,6 @@ namespace Duplicati.Library.Backend
|
||||
string ShortDescription { get; set; }
|
||||
CommandLineArgument.ArgumentType Type { get; set; }
|
||||
string[] ValidValues { get; set; }
|
||||
string DefaultValue { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace Duplicati.Library.Backend
|
||||
{
|
||||
get
|
||||
{
|
||||
return "This backend can read and write data to an FTP based backend.\nAllowed formats are \"ftp://hostname/folder\" or \"ftp://username:password@hostname/folder\"";
|
||||
return "This backend can read and write data to an FTP based backend. Allowed formats are \"ftp://hostname/folder\" or \"ftp://username:password@hostname/folder\"";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace Duplicati.Library.Backend
|
||||
{
|
||||
get
|
||||
{
|
||||
return "This backend can read and write data to an file based backend.\nAllowed formats are \"file://hostname/folder\" or \"file://username:password@hostname/folder\"\nYou may supply UNC paths (eg: \"file://\\\\server\\folder\") or local paths (eg: (win) \"file://c:\\folder\", (linux) \"file:///usr/pub/files\").";
|
||||
return "This backend can read and write data to an file based backend. Allowed formats are \"file://hostname/folder\" or \"file://username:password@hostname/folder\". You may supply UNC paths (eg: \"file://\\\\server\\folder\") or local paths (eg: (win) \"file://c:\\folder\", (linux) \"file:///usr/pub/files\").";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ namespace Duplicati.Library.Backend
|
||||
{
|
||||
get
|
||||
{
|
||||
return "This backend can read and write data to an Amazon S3 based backend.\nAllowed formats are \"s3://bucketname/prefix\" or \"s3://aws_id:aws_key@bucketname/prefix\".\nNote that if you AWS ID or Key contains either \":\", \"/\" or \"@\" you cannot use the second form, but must supply them through arguments.";
|
||||
return "This backend can read and write data to an Amazon S3 based backend. Allowed formats are \"s3://bucketname/prefix\" or \"s3://aws_id:aws_key@bucketname/prefix\". Note that if you AWS ID or Key contains either \":\", \"/\" or \"@\" you cannot use the second form, but must supply them through arguments.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace Duplicati.Library.Backend
|
||||
{
|
||||
get
|
||||
{
|
||||
return "This backend can read and write data to an SSH based backend, using SCP and SFTP.\nAllowed formats are \"ssh://hostname/folder\" or \"ssh://username:password@hostname/folder\"\nNOTE: This backend does not support throttling uploads or downloads.";
|
||||
return "This backend can read and write data to an SSH based backend, using SCP and SFTP. Allowed formats are \"ssh://hostname/folder\" or \"ssh://username:password@hostname/folder\". NOTE: This backend does not support throttling uploads or downloads, and requires that sftp and scp are installed (using putty for windows).";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<ProjectGuid>{BAE27510-8B5D-44B2-B33E-372A98908041}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Duplicati.Library.Backend.WEBDAV</RootNamespace>
|
||||
<RootNamespace>Duplicati.Library.Backend</RootNamespace>
|
||||
<AssemblyName>Duplicati.Library.Backend.WEBDAV</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace Duplicati.Library.Backend
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return "Supports connections to a WEBDAV enabled web server"; }
|
||||
get { return "Supports connections to a WEBDAV enabled web server, using the HTTP protocol. Allowed formats are \"webdav://hostname/folder\" or \"webdav://username:password@hostname/folder\"."; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -47,12 +47,8 @@ namespace Duplicati.Library.Main
|
||||
m_filenameRegExp = new Regex(@"(?<prefix>" + Regex.Escape(m_prefix) + @")\-(?<inc>(full|inc))\-(?<type>(content|signature|manifest))\.(?<time>\d{4}\-\d{2}\-\d{2}.\d{2}" + Regex.Escape(m_timeSeperator) + @"\d{2}" + Regex.Escape(m_timeSeperator) + @"\d{2}(?<timezone>([\+\-]\d{2}" + Regex.Escape(m_timeSeperator) + @"\d{2})|Z)?)\.(?<volumegroup>vol(?<volumenumber>\d+)\.)?(?<extension>.+)");
|
||||
}
|
||||
|
||||
public FilenameStrategy(Dictionary<string, string> options)
|
||||
: this(
|
||||
options.ContainsKey("backup-prefix") ? options["backup-prefix"] : (options.ContainsKey("short-filenames") ? "dpl" : "duplicati"),
|
||||
options.ContainsKey("time-separator") ? options["time-separator"] : ":",
|
||||
options.ContainsKey("short-filenames")
|
||||
)
|
||||
public FilenameStrategy(Options options)
|
||||
: this(options.BackupPrefix, options.TimeSeperatorChar, options.UseShortFilenames)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ namespace Duplicati.Library.Main
|
||||
public class Interface : IDisposable
|
||||
{
|
||||
private string m_backend;
|
||||
private Dictionary<string, string> m_options;
|
||||
//private Dictionary<string, string> m_options;
|
||||
private Options m_options;
|
||||
|
||||
//The amount of progressbar allocated for reading incremental data
|
||||
private double m_incrementalFraction = 0.15;
|
||||
@@ -50,14 +51,14 @@ namespace Duplicati.Library.Main
|
||||
public Interface(string backend, Dictionary<string, string> options)
|
||||
{
|
||||
m_backend = backend;
|
||||
m_options = options;
|
||||
m_options = new Options(options);
|
||||
}
|
||||
|
||||
public string Backup(string source)
|
||||
{
|
||||
BackupStatistics bs = new BackupStatistics();
|
||||
|
||||
SetupCommonOptions(m_options);
|
||||
SetupCommonOptions();
|
||||
Backend.IBackend backend = null;
|
||||
|
||||
using (new Logging.Timer("Backup from " + source + " to " + m_backend))
|
||||
@@ -72,24 +73,17 @@ namespace Duplicati.Library.Main
|
||||
|
||||
FilenameStrategy fns = new FilenameStrategy(m_options);
|
||||
FilenameStrategy cacheFilenameStrategy = new FilenameStrategy("dpl", "_", true);
|
||||
|
||||
Core.FilenameFilter filter = new Duplicati.Library.Core.FilenameFilter(m_options);
|
||||
bool full = m_options.ContainsKey("full");
|
||||
|
||||
int volumesize = (int)Core.Sizeparser.ParseSize(m_options.ContainsKey("volsize") ? m_options["volsize"] : "5", "mb");
|
||||
volumesize = Math.Max(1024 * 1024, volumesize);
|
||||
bool full = m_options.Full;
|
||||
|
||||
long totalmax = m_options.ContainsKey("totalsize") ? Core.Sizeparser.ParseSize(m_options["totalsize"], "mb") : long.MaxValue;
|
||||
totalmax = Math.Max(volumesize, totalmax);
|
||||
|
||||
backend = Backend.BackendLoader.GetBackend(m_backend, m_options);
|
||||
backend = Backend.BackendLoader.GetBackend(m_backend, m_options.RawOptions);
|
||||
if (backend == null)
|
||||
throw new Exception("Unable to find backend for m_backend: " + m_backend);
|
||||
backend = new BackendWrapper(bs, backend, m_options);
|
||||
backend = new BackendWrapper(bs, backend, m_options.RawOptions);
|
||||
((BackendWrapper)backend).ProgressEvent += new Duplicati.Library.Main.RSync.RSyncDir.ProgressEventDelegate(BackupTransfer_ProgressEvent);
|
||||
backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, m_options);
|
||||
backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, m_options.RawOptions);
|
||||
|
||||
List<BackupEntry> orphans = m_options.ContainsKey("auto-cleanup") ? new List<BackupEntry>() : null;
|
||||
List<BackupEntry> orphans = m_options.AutoCleanup ? new List<BackupEntry>() : null;
|
||||
List <BackupEntry > prev = ParseFileList(backend, orphans);
|
||||
|
||||
m_progress = 0.0;
|
||||
@@ -99,16 +93,16 @@ namespace Duplicati.Library.Main
|
||||
|
||||
if (prev.Count == 0)
|
||||
full = true;
|
||||
|
||||
if (!full && m_options.ContainsKey("full-if-older-than"))
|
||||
full = DateTime.Now > Core.Timeparser.ParseTimeInterval(m_options["full-if-older-than"], prev[prev.Count - 1].Time);
|
||||
|
||||
if (!full)
|
||||
full = DateTime.Now > m_options.FullIfOlderThan(prev[prev.Count - 1].Time);
|
||||
|
||||
List<string> controlfiles = new List<string>();
|
||||
if (m_options.ContainsKey("signature-control-files"))
|
||||
controlfiles.AddRange(m_options["signature-control-files"].Split(System.IO.Path.PathSeparator));
|
||||
if (!string.IsNullOrEmpty(m_options.SignatureControlFiles))
|
||||
controlfiles.AddRange(m_options.SignatureControlFiles.Split(System.IO.Path.PathSeparator));
|
||||
|
||||
if (m_options.ContainsKey("signature-cache-path") && !System.IO.Directory.Exists(m_options["signature-cache-path"]))
|
||||
System.IO.Directory.CreateDirectory(m_options["signature-cache-path"]);
|
||||
if (!string.IsNullOrEmpty(m_options.SignatureCachePath) && !System.IO.Directory.Exists(m_options.SignatureCachePath))
|
||||
System.IO.Directory.CreateDirectory(m_options.SignatureCachePath);
|
||||
|
||||
using (Core.TempFolder tempfolder = new Duplicati.Library.Core.TempFolder())
|
||||
{
|
||||
@@ -126,7 +120,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
DateTime backuptime = DateTime.Now;
|
||||
|
||||
using (RSync.RSyncDir dir = new Duplicati.Library.Main.RSync.RSyncDir(source, bs, filter, patches))
|
||||
using (RSync.RSyncDir dir = new Duplicati.Library.Main.RSync.RSyncDir(source, bs, m_options.Filter, patches))
|
||||
{
|
||||
if (OperationProgress != null)
|
||||
{
|
||||
@@ -134,7 +128,7 @@ namespace Duplicati.Library.Main
|
||||
dir.ProgressEvent += new Duplicati.Library.Main.RSync.RSyncDir.ProgressEventDelegate(BackupRSyncDir_ProgressEvent);
|
||||
}
|
||||
|
||||
dir.DisableFiletimeCheck = m_options.ContainsKey("disable-filetime-check");
|
||||
dir.DisableFiletimeCheck = m_options.DisableFiletimeCheck;
|
||||
using (new Logging.Timer("Initiating multipass"))
|
||||
dir.InitiateMultiPassDiff(full);
|
||||
|
||||
@@ -145,7 +139,7 @@ namespace Duplicati.Library.Main
|
||||
List<string> signaturehashes = new List<string>();
|
||||
|
||||
bool done = false;
|
||||
while (!done && totalsize < totalmax)
|
||||
while (!done && totalsize < m_options.MaxSize)
|
||||
{
|
||||
using (new Logging.Timer("Multipass " + (vol + 1).ToString()))
|
||||
{
|
||||
@@ -172,12 +166,12 @@ namespace Duplicati.Library.Main
|
||||
//Only add control files to the very first volume
|
||||
controlfiles.Clear();
|
||||
|
||||
done = dir.MakeMultiPassDiff(signaturearchive, contentarchive, Math.Min(volumesize, totalmax - totalsize));
|
||||
done = dir.MakeMultiPassDiff(signaturearchive, contentarchive, Math.Min(m_options.VolumeSize, m_options.MaxSize - totalsize));
|
||||
totalsize += new System.IO.FileInfo(contentfile).Length;
|
||||
|
||||
totalsize += signaturearchive.Size;
|
||||
|
||||
if (totalsize >= totalmax)
|
||||
if (totalsize >= m_options.MaxSize)
|
||||
dir.FinalizeMultiPass(signaturearchive, contentarchive);
|
||||
}
|
||||
|
||||
@@ -197,8 +191,8 @@ namespace Duplicati.Library.Main
|
||||
//TODO: put this in the backend wrapper, so it can be delayed properly
|
||||
//For now it works, because the manifest does not contain this entry anyway
|
||||
//Any problems would only occur when using disabled signature checks
|
||||
if (m_options.ContainsKey("signature-cache-path"))
|
||||
System.IO.File.Copy(signaturefile, System.IO.Path.Combine(m_options["signature-cache-path"], cacheFilenameStrategy.GenerateFilename(BackupEntry.EntryType.Signature, full, backuptime, vol + 1)));
|
||||
if (!string.IsNullOrEmpty(m_options.SignatureCachePath))
|
||||
System.IO.File.Copy(signaturefile, System.IO.Path.Combine(m_options.SignatureCachePath, cacheFilenameStrategy.GenerateFilename(BackupEntry.EntryType.Signature, full, backuptime, vol + 1)));
|
||||
|
||||
backend.Put(fns.GenerateFilename(BackupEntry.EntryType.Signature, full, backuptime, vol + 1) + ".zip", signaturefile);
|
||||
|
||||
@@ -232,12 +226,9 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
|
||||
vol++;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -263,24 +254,24 @@ namespace Duplicati.Library.Main
|
||||
foreach (BackupEntry be in orphans)
|
||||
{
|
||||
Logging.Log.WriteMessage("Removing leftover file: " + be.Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.ContainsKey("force"))
|
||||
if (m_options.Force)
|
||||
backend.Delete(be.Filename);
|
||||
|
||||
foreach (BackupEntry bex in be.SignatureFile)
|
||||
{
|
||||
Logging.Log.WriteMessage("Removing leftover file: " + bex.Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.ContainsKey("force"))
|
||||
if (m_options.Force)
|
||||
backend.Delete(bex.Filename);
|
||||
}
|
||||
foreach (BackupEntry bex in be.ContentVolumes)
|
||||
{
|
||||
Logging.Log.WriteMessage("Removing leftover file: " + bex.Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.ContainsKey("force"))
|
||||
if (m_options.Force)
|
||||
backend.Delete(bex.Filename);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_options.ContainsKey("force") && orphans.Count > 0)
|
||||
if (!m_options.Force && orphans.Count > 0)
|
||||
Logging.Log.WriteMessage("No files removed, specify --force to remove files.", Duplicati.Library.Logging.LogMessageType.Information);
|
||||
|
||||
}
|
||||
@@ -300,7 +291,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
public string Restore(string target)
|
||||
{
|
||||
SetupCommonOptions(m_options);
|
||||
SetupCommonOptions();
|
||||
RestoreStatistics rs = new RestoreStatistics();
|
||||
|
||||
Backend.IBackend backend = null;
|
||||
@@ -312,28 +303,26 @@ namespace Duplicati.Library.Main
|
||||
if (OperationStarted != null)
|
||||
OperationStarted(this, DuplicatiOperation.Restore, -1, -1, "Started");
|
||||
|
||||
string specificfile = m_options.ContainsKey("file-to-restore") ? m_options["file-to-restore"] : "";
|
||||
string specifictime = m_options.ContainsKey("restore-time") ? m_options["restore-time"] : "now";
|
||||
Core.FilenameFilter filter = new Duplicati.Library.Core.FilenameFilter(m_options);
|
||||
Core.FilenameFilter filter = m_options.Filter;
|
||||
|
||||
//Filter is prefered, if both file and filter is specified
|
||||
if (!m_options.ContainsKey("filter") && !string.IsNullOrEmpty(specificfile))
|
||||
if (!m_options.HasFilter && !string.IsNullOrEmpty(m_options.FileToRestore))
|
||||
{
|
||||
List<Core.IFilenameFilter> list = new List<Duplicati.Library.Core.IFilenameFilter>();
|
||||
list.Add(new Core.FilelistFilter(true, specificfile.Split(System.IO.Path.PathSeparator)));
|
||||
list.Add(new Core.FilelistFilter(true, m_options.FileToRestore.Split(System.IO.Path.PathSeparator)));
|
||||
list.Add(new Core.RegularExpressionFilter(false, ".*"));
|
||||
|
||||
filter = new Duplicati.Library.Core.FilenameFilter(list);
|
||||
}
|
||||
|
||||
backend = Backend.BackendLoader.GetBackend(m_backend, m_options);
|
||||
backend = Backend.BackendLoader.GetBackend(m_backend, m_options.RawOptions);
|
||||
if (backend == null)
|
||||
throw new Exception("Unable to find backend for target: " + m_backend);
|
||||
backend = new BackendWrapper(rs, backend, m_options);
|
||||
backend = new BackendWrapper(rs, backend, m_options.RawOptions);
|
||||
|
||||
BackupEntry bestFit = FindBestMatch(ParseFileList(backend), specifictime);
|
||||
BackupEntry bestFit = FindBestMatch(ParseFileList(backend), m_options.RestoreTime);
|
||||
if (bestFit.EncryptionMode != null)
|
||||
backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options);
|
||||
backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options.RawOptions);
|
||||
|
||||
List<BackupEntry> entries = new List<BackupEntry>();
|
||||
entries.Add(bestFit);
|
||||
@@ -345,7 +334,7 @@ namespace Duplicati.Library.Main
|
||||
foreach (BackupEntry be in entries)
|
||||
{
|
||||
List<string> contentHashes = null;
|
||||
if (!m_options.ContainsKey("skip-file-hash-checks"))
|
||||
if (!m_options.SkipFileHashChecks)
|
||||
{
|
||||
if (OperationProgress != null)
|
||||
OperationProgress(this, DuplicatiOperation.Backup, 0, -1, "Reading manifest file: " + be.Filename);
|
||||
@@ -421,7 +410,7 @@ namespace Duplicati.Library.Main
|
||||
/// <returns>A restore report</returns>
|
||||
public string RestoreControlFiles(string target)
|
||||
{
|
||||
SetupCommonOptions(m_options);
|
||||
SetupCommonOptions();
|
||||
RestoreStatistics rs = new RestoreStatistics();
|
||||
|
||||
Backend.IBackend backend = null;
|
||||
@@ -433,10 +422,10 @@ namespace Duplicati.Library.Main
|
||||
if (OperationStarted != null)
|
||||
OperationStarted(this, DuplicatiOperation.Restore, 0, -1, "Started");
|
||||
|
||||
backend = Backend.BackendLoader.GetBackend(m_backend, m_options);
|
||||
backend = Backend.BackendLoader.GetBackend(m_backend, m_options.RawOptions);
|
||||
if (backend == null)
|
||||
throw new Exception("Unable to find backend for target: " + m_backend);
|
||||
backend = new BackendWrapper(rs, backend, m_options);
|
||||
backend = new BackendWrapper(rs, backend, m_options.RawOptions);
|
||||
|
||||
List<BackupEntry> attempts = ParseFileList(backend);
|
||||
|
||||
@@ -455,7 +444,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
Backend.IBackend realbackend = backend;
|
||||
if (be.EncryptionMode != null)
|
||||
realbackend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, be.EncryptionMode, m_options);
|
||||
realbackend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, be.EncryptionMode, m_options.RawOptions);
|
||||
|
||||
if (be.SignatureFile.Count > 0)
|
||||
using(Core.TempFile z = new Duplicati.Library.Core.TempFile())
|
||||
@@ -506,13 +495,9 @@ namespace Duplicati.Library.Main
|
||||
|
||||
public string RemoveAllButNFull()
|
||||
{
|
||||
if (!m_options.ContainsKey("remove-all-but-n-full"))
|
||||
throw new Exception("No count given for \"Remove All But N Full\"");
|
||||
int x = m_options.RemoveAllButNFull;
|
||||
|
||||
int x = int.Parse(m_options["remove-all-but-n-full"]);
|
||||
if (x < 0)
|
||||
throw new Exception("Invalid count for remove-all-but-n-full");
|
||||
Backend.IBackend backend = Backend.BackendLoader.GetBackend(m_backend, m_options);
|
||||
Backend.IBackend backend = Backend.BackendLoader.GetBackend(m_backend, m_options.RawOptions);
|
||||
if (backend == null)
|
||||
throw new Exception("Unable to find backend for target: " + m_backend);
|
||||
|
||||
@@ -551,10 +536,9 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (!m_options.ContainsKey("remove-older-than"))
|
||||
throw new Exception("No count given for \"Remove Older Than\"");
|
||||
DateTime expires = m_options.RemoveOlderThan;
|
||||
|
||||
Backend.IBackend backend = Backend.BackendLoader.GetBackend(m_backend, m_options);
|
||||
Backend.IBackend backend = Backend.BackendLoader.GetBackend(m_backend, m_options.RawOptions);
|
||||
if (backend == null)
|
||||
throw new Exception("Unable to find backend for target: " + m_backend);
|
||||
|
||||
@@ -563,10 +547,8 @@ namespace Duplicati.Library.Main
|
||||
if (OperationStarted != null)
|
||||
OperationStarted(this, DuplicatiOperation.Remove, 0, -1, "Started");
|
||||
|
||||
string duration = m_options["remove-older-than"];
|
||||
List<BackupEntry> entries = ParseFileList(backend);
|
||||
|
||||
DateTime expires = Core.Timeparser.ParseTimeInterval(duration, DateTime.Now, true);
|
||||
|
||||
List<BackupEntry> toremove = new List<BackupEntry>();
|
||||
|
||||
@@ -625,7 +607,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
sb.AppendLine("Deleting backup at " + be.Time.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
|
||||
if (m_options.ContainsKey("force"))
|
||||
if (m_options.Force)
|
||||
{
|
||||
//Delete manifest
|
||||
backend.Delete(be.Filename);
|
||||
@@ -638,7 +620,7 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_options.ContainsKey("force") && entries.Count > 0)
|
||||
if (!m_options.Force && entries.Count > 0)
|
||||
sb.AppendLine("Files are not deleted, use the --force command to actually remove files");
|
||||
|
||||
return sb.ToString();
|
||||
@@ -647,10 +629,10 @@ namespace Duplicati.Library.Main
|
||||
|
||||
public string[] List()
|
||||
{
|
||||
SetupCommonOptions(m_options);
|
||||
SetupCommonOptions();
|
||||
|
||||
List<string> res = new List<string>();
|
||||
Duplicati.Library.Backend.IBackend i = new Duplicati.Library.Backend.BackendLoader(m_backend, m_options);
|
||||
Duplicati.Library.Backend.IBackend i = new Duplicati.Library.Backend.BackendLoader(m_backend, m_options.RawOptions);
|
||||
|
||||
if (i == null)
|
||||
throw new Exception("Unable to find backend for target: " + m_backend);
|
||||
@@ -673,7 +655,7 @@ namespace Duplicati.Library.Main
|
||||
if (OperationStarted != null)
|
||||
OperationStarted(this, DuplicatiOperation.List, 0, -1, "Started");
|
||||
|
||||
Backend.IBackend obackend = Backend.BackendLoader.GetBackend(m_backend, m_options);
|
||||
Backend.IBackend obackend = Backend.BackendLoader.GetBackend(m_backend, m_options.RawOptions);
|
||||
if (obackend == null)
|
||||
throw new Exception("Unable to find backend for target: " + m_backend);
|
||||
using (obackend)
|
||||
@@ -691,7 +673,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
bool anyRemoved = false;
|
||||
|
||||
Backend.IBackend backend = Backend.BackendLoader.GetBackend(m_backend, m_options);
|
||||
Backend.IBackend backend = Backend.BackendLoader.GetBackend(m_backend, m_options.RawOptions);
|
||||
if (backend == null)
|
||||
throw new Exception("Unable to find backend for target: " + m_backend);
|
||||
using (backend)
|
||||
@@ -726,7 +708,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
anyRemoved = true;
|
||||
Logging.Log.WriteMessage("Removing partial file: " + be.SignatureFile[i].Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.ContainsKey("force"))
|
||||
if (m_options.Force)
|
||||
backend.Delete(be.SignatureFile[i].Filename);
|
||||
}
|
||||
|
||||
@@ -734,14 +716,14 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
anyRemoved = true;
|
||||
Logging.Log.WriteMessage("Removing partial file: " + be.SignatureFile[i].Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.ContainsKey("force"))
|
||||
if (m_options.Force)
|
||||
backend.Delete(be.ContentVolumes[i].Filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_options.ContainsKey("force") && anyRemoved)
|
||||
if (!m_options.Force && anyRemoved)
|
||||
Logging.Log.WriteMessage("No files removed, specify --force to remove files.", Duplicati.Library.Logging.LogMessageType.Information);
|
||||
|
||||
return ""; //TODO: Write a message here?
|
||||
@@ -754,7 +736,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
private List<BackupEntry> ParseFileList(Duplicati.Library.Backend.IBackend backend, List<BackupEntry> orphans)
|
||||
{
|
||||
SetupCommonOptions(m_options);
|
||||
SetupCommonOptions();
|
||||
|
||||
using (new Logging.Timer("Getting and sorting filelist from " + backend.DisplayName))
|
||||
{
|
||||
@@ -875,24 +857,23 @@ namespace Duplicati.Library.Main
|
||||
|
||||
public IList<string> ListContent()
|
||||
{
|
||||
SetupCommonOptions(m_options);
|
||||
SetupCommonOptions();
|
||||
RestoreStatistics rs = new RestoreStatistics();
|
||||
|
||||
Duplicati.Library.Backend.IBackend backend = new Duplicati.Library.Backend.BackendLoader(m_backend, m_options);
|
||||
Duplicati.Library.Backend.IBackend backend = new Duplicati.Library.Backend.BackendLoader(m_backend, m_options.RawOptions);
|
||||
|
||||
Core.FilenameFilter filter = new Duplicati.Library.Core.FilenameFilter(m_options);
|
||||
Core.FilenameFilter filter = m_options.Filter;
|
||||
|
||||
if (backend == null)
|
||||
throw new Exception("Unable to find backend for target: " + m_backend);
|
||||
backend = new BackendWrapper(rs, backend, m_options);
|
||||
backend = new BackendWrapper(rs, backend, m_options.RawOptions);
|
||||
|
||||
if (OperationStarted != null)
|
||||
OperationStarted(this, DuplicatiOperation.List, 0, -1, "Started");
|
||||
|
||||
string specifictime = m_options.ContainsKey("restore-time") ? m_options["restore-time"] : "now";
|
||||
BackupEntry bestFit = FindBestMatch(ParseFileList(backend), specifictime);
|
||||
BackupEntry bestFit = FindBestMatch(ParseFileList(backend), m_options.RestoreTime);
|
||||
if (bestFit.EncryptionMode != null)
|
||||
backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options);
|
||||
backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options.RawOptions);
|
||||
|
||||
List<string> res;
|
||||
|
||||
@@ -920,11 +901,14 @@ namespace Duplicati.Library.Main
|
||||
if (string.IsNullOrEmpty(specifictime))
|
||||
specifictime = "now";
|
||||
|
||||
return FindBestMatch(backups, Core.Timeparser.ParseTimeInterval(specifictime, DateTime.Now));
|
||||
}
|
||||
|
||||
private BackupEntry FindBestMatch(List<BackupEntry> backups, DateTime timelimit)
|
||||
{
|
||||
if (backups.Count == 0)
|
||||
throw new Exception("No backups found at remote location");
|
||||
|
||||
DateTime timelimit = Core.Timeparser.ParseTimeInterval(specifictime, DateTime.Now);
|
||||
|
||||
BackupEntry bestFit = backups[0];
|
||||
List<BackupEntry> additions = new List<BackupEntry>();
|
||||
foreach (BackupEntry be in backups)
|
||||
@@ -944,12 +928,13 @@ namespace Duplicati.Library.Main
|
||||
return bestFit;
|
||||
}
|
||||
|
||||
private void SetupCommonOptions(Dictionary<string, string> options)
|
||||
private void SetupCommonOptions()
|
||||
{
|
||||
if (options.ContainsKey("tempdir"))
|
||||
Core.TempFolder.SystemTempPath = options["tempdir"];
|
||||
if (options.ContainsKey("thread-priority"))
|
||||
System.Threading.Thread.CurrentThread.Priority = Core.Utility.ParsePriority(options["thread-priority"]);
|
||||
if (!string.IsNullOrEmpty(m_options.TempDir))
|
||||
Core.TempFolder.SystemTempPath = m_options.TempDir;
|
||||
|
||||
if (!string.IsNullOrEmpty(m_options.ThreadPriority))
|
||||
System.Threading.Thread.CurrentThread.Priority = Core.Utility.ParsePriority(m_options.ThreadPriority);
|
||||
}
|
||||
|
||||
private List<Core.IFileArchive> FindPatches(Backend.IBackend backend, List<BackupEntry> entries, List<BackupEntry> orphans, string tempfolder)
|
||||
@@ -992,7 +977,7 @@ namespace Duplicati.Library.Main
|
||||
m_progress += unitCost;
|
||||
|
||||
List<string> signatureHashes = null;
|
||||
if (!m_options.ContainsKey("skip-file-hash-checks"))
|
||||
if (!m_options.SkipFileHashChecks)
|
||||
{
|
||||
if (OperationProgress != null)
|
||||
OperationProgress(this, DuplicatiOperation.Backup, (int)(m_progress * 100), -1, "Reading manifest file: " + be.Time.ToShortDateString() + " " + be.Time.ToShortTimeString());
|
||||
@@ -1042,8 +1027,8 @@ namespace Duplicati.Library.Main
|
||||
|
||||
string cachefilename = null;
|
||||
|
||||
if (m_options.ContainsKey("signature-cache-path"))
|
||||
cachefilename = System.IO.Path.Combine(m_options["signature-cache-path"], cacheFilenameStrategy.GenerateFilename(bes.Type, bes.IsFull, bes.Time, bes.VolumeNumber));
|
||||
if (!string.IsNullOrEmpty(m_options.SignatureCachePath))
|
||||
cachefilename = System.IO.Path.Combine(m_options.SignatureCachePath, cacheFilenameStrategy.GenerateFilename(bes.Type, bes.IsFull, bes.Time, bes.VolumeNumber));
|
||||
|
||||
if (cachefilename != null && System.IO.File.Exists(cachefilename))
|
||||
if (signatureHashes != null && Core.Utility.CalculateHash(cachefilename) == signatureHashes[bes.VolumeNumber - 1])
|
||||
|
||||
@@ -25,13 +25,15 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
public class Options
|
||||
{
|
||||
Dictionary<string, string> m_options;
|
||||
private Dictionary<string, string> m_options;
|
||||
|
||||
public Options(Dictionary<string, string> options)
|
||||
{
|
||||
m_options = options;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> RawOptions { get { return m_options; } }
|
||||
|
||||
public IList<Backend.ICommandLineArgument> SupportedCommands
|
||||
{
|
||||
get
|
||||
@@ -51,8 +53,26 @@ namespace Duplicati.Library.Main
|
||||
|
||||
new Backend.CommandLineArgument("disable-filetime-check", Backend.CommandLineArgument.ArgumentType.String, "Disable checks based on file time", "The operating system keeps track of the last time a file was written. Using this information, Duplicati can quickly determine if the file has been modified. If some application deliberately modifies this information, Duplicati won't work correctly unless this flag is set."),
|
||||
new Backend.CommandLineArgument("force", Backend.CommandLineArgument.ArgumentType.String, "Force the removal of files", "When deleting old files, Duplicati will only write out what files are supposed to be deleted. Specify the \"force\" option to actually remove them."),
|
||||
});
|
||||
new Backend.CommandLineArgument("tempdir", Backend.CommandLineArgument.ArgumentType.Path, "Temporary storage folder", "Duplicati will use the system default temporary folder. This option can be used to supply an alternative folder for temporary storage."),
|
||||
new Backend.CommandLineArgument("thread-priority", Backend.CommandLineArgument.ArgumentType.Enumeration, "Thread priority", "Selects another thread priority for the process. Use this to set Duplicati to be more or less CPU intensive.", "normal", null, new string[] {"high", "abovenormal", "normal", "belownormal", "low", "idle" }),
|
||||
|
||||
new Backend.CommandLineArgument("backup-prefix", Backend.CommandLineArgument.ArgumentType.String, "Backup volume filename prefix", "Any string used to prefix the filenames generated by Duplicati.", "duplicati"),
|
||||
new Backend.CommandLineArgument("time-separator", Backend.CommandLineArgument.ArgumentType.String, "Backup volume filename timeseperator", "Per default, Duplicati will use the colon \":\" character to seperate the time fields in the filename. However, on some filesystem, notably windows, this character is not allowed. Use this option to use another character."),
|
||||
new Backend.CommandLineArgument("short-filenames", Backend.CommandLineArgument.ArgumentType.Boolean, "Use short filenames", "If the filesystem does not support the long filenames that Duplicati uses, this switch will change the time to be a much more compact, but hard to read format. It will also make the default prefix \"dpl\"."),
|
||||
|
||||
/*new Backend.CommandLineArgument("include", Backend.CommandLineArgument.ArgumentType.String, "Backup volume filename timeseperator", "Per default, Duplicati will use the colon \":\" character to seperate the time fields in the filename. However, on some filesystem, notably windows, this character is not allowed. Use this option to use another character."),
|
||||
new Backend.CommandLineArgument("exclude", Backend.CommandLineArgument.ArgumentType.String, "Backup volume filename timeseperator", "Per default, Duplicati will use the colon \":\" character to seperate the time fields in the filename. However, on some filesystem, notably windows, this character is not allowed. Use this option to use another character."),
|
||||
new Backend.CommandLineArgument("include-regexp", Backend.CommandLineArgument.ArgumentType.String, "Backup volume filename timeseperator", "Per default, Duplicati will use the colon \":\" character to seperate the time fields in the filename. However, on some filesystem, notably windows, this character is not allowed. Use this option to use another character."),
|
||||
new Backend.CommandLineArgument("exclude-regexp", Backend.CommandLineArgument.ArgumentType.String, "Backup volume filename timeseperator", "Per default, Duplicati will use the colon \":\" character to seperate the time fields in the filename. However, on some filesystem, notably windows, this character is not allowed. Use this option to use another character."),*/
|
||||
|
||||
new Backend.CommandLineArgument("passphrase", Backend.CommandLineArgument.ArgumentType.String, "Passphrase used to encrypt backups", "Supply a passphrase that Duplicati will use to encrypt the backup volumes, making the unreadable without the passphrase."),
|
||||
new Backend.CommandLineArgument("gpg-encryption", Backend.CommandLineArgument.ArgumentType.Boolean, "Use GnuPG for encryption", "By default, Duplicati will use the AES encryption algorithm to encrypt the backup volumes, setting this flag makes Duplicati use the GNU Privacy Guard instead. GnuPG must be installed on the machine for this to work."),
|
||||
new Backend.CommandLineArgument("gpg-program-path", Backend.CommandLineArgument.ArgumentType.Path, "The path to GnuPG", "The path to the GNU Privacy Guard program. If not supplied, Duplicati will assume that the program \"gpg\" is avalible in the system path."),
|
||||
new Backend.CommandLineArgument("sign-key", Backend.CommandLineArgument.ArgumentType.String, "Sign key for GnuPG", "The GNU Privacy Guard can optionally sign volumes with a special key. This feature is not currently active in Duplicati."),
|
||||
new Backend.CommandLineArgument("no-encryption", Backend.CommandLineArgument.ArgumentType.Boolean, "Disable encryption", "If you store the backups on a local disk, and prefer that they are kept unencrypted, you can turn of encryption completely by using this switch."),
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +92,7 @@ namespace Duplicati.Library.Main
|
||||
if (m_options.ContainsKey("volsize"))
|
||||
volsize = m_options["volsize"];
|
||||
|
||||
return Core.Sizeparser.ParseSize(volsize, "mb");
|
||||
return Math.Max(1024 * 1024, Core.Sizeparser.ParseSize(volsize, "mb"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +106,7 @@ namespace Duplicati.Library.Main
|
||||
if (!m_options.ContainsKey("totalsize") || string.IsNullOrEmpty(m_options["totalsize"]))
|
||||
return long.MaxValue;
|
||||
else
|
||||
return Core.Sizeparser.ParseSize(m_options["totalsize"]);
|
||||
return Math.Max(VolumeSize, Core.Sizeparser.ParseSize(m_options["totalsize"]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +199,119 @@ namespace Duplicati.Library.Main
|
||||
/// </summary>
|
||||
public bool Force { get { return GetBool("force"); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder where temporary files are stored
|
||||
/// </summary>
|
||||
public string TempDir
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("tempdir") || string.IsNullOrEmpty(m_options["tempdir"]))
|
||||
return null;
|
||||
else
|
||||
return m_options["tempdir"];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process priority
|
||||
/// </summary>
|
||||
public string ThreadPriority
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("thread-priority") || string.IsNullOrEmpty(m_options["thread-priority"]))
|
||||
return null;
|
||||
else
|
||||
return m_options["thread-priority"];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating if file deletes are forced
|
||||
/// </summary>
|
||||
public bool UseShortFilenames { get { return GetBool("short-filenames"); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backup prefix
|
||||
/// </summary>
|
||||
public string BackupPrefix
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("backup-prefix") || string.IsNullOrEmpty(m_options["backup-prefix"]))
|
||||
return this.UseShortFilenames ? "dpl" : "duplicati";
|
||||
else
|
||||
return m_options["backup-prefix"];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process priority
|
||||
/// </summary>
|
||||
public string TimeSeperatorChar
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("time-separator") || string.IsNullOrEmpty(m_options["time-separator"]))
|
||||
return ":";
|
||||
else
|
||||
return m_options["time-separator"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filter used to include or exclude files
|
||||
/// </summary>
|
||||
public Core.FilenameFilter Filter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_options.ContainsKey("filter"))
|
||||
return new Duplicati.Library.Core.FilenameFilter(Core.FilenameFilter.DecodeFilter(m_options["filter"]));
|
||||
else
|
||||
return new Duplicati.Library.Core.FilenameFilter(new List<KeyValuePair<bool, string>>());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indiciating if a filter is specified
|
||||
/// </summary>
|
||||
public bool HasFilter { get { return m_options.ContainsKey("filter"); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of old backups to keep
|
||||
/// </summary>
|
||||
public int RemoveAllButNFull
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("remove-all-but-n-full"))
|
||||
throw new Exception("No count given for \"Remove All But N Full\"");
|
||||
|
||||
int x = int.Parse(m_options["remove-all-but-n-full"]);
|
||||
if (x < 0)
|
||||
throw new Exception("Invalid count for remove-all-but-n-full");
|
||||
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timelimit for removal
|
||||
/// </summary>
|
||||
public DateTime RemoveOlderThan
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("remove-older-than"))
|
||||
throw new Exception("No count given for \"Remove Older Than\"");
|
||||
|
||||
return Core.Timeparser.ParseTimeInterval(m_options["remove-older-than"], DateTime.Now, true);
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetBool(string name)
|
||||
{
|
||||
if (!m_options.ContainsKey(name))
|
||||
|
||||
Reference in New Issue
Block a user