Fixed issue #14, and added a more readable output than what was avalible in beta 3.
Added commandline action to purge the signature cache. Duplicati now removes files from the signature cache when they are deleted from the backend. git-svn-id: https://duplicati.googlecode.com/svn/trunk@130 59da171f-624f-0410-aa54-27559c288bec
This commit is contained in:
@@ -57,7 +57,16 @@ namespace Duplicati.CommandLine
|
||||
}
|
||||
#endif
|
||||
|
||||
//TODO: Print usage window
|
||||
if (cargs.Count == 1)
|
||||
{
|
||||
switch (cargs[0].Trim().ToLower())
|
||||
{
|
||||
case "purge-signature-cache":
|
||||
Library.Main.Interface.PurgeSignatureCache(options);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cargs.Count < 2)
|
||||
{
|
||||
PrintUsage(true);
|
||||
@@ -256,6 +265,9 @@ namespace Duplicati.CommandLine
|
||||
lines.Add(" List content files (backed up files):");
|
||||
lines.Add(" Duplicati.CommandLine list-current-files [options] <backend>");
|
||||
lines.Add("");
|
||||
lines.Add(" Purge signature cache:");
|
||||
lines.Add(" Duplicati.CommandLine purge-signature-cache [options]");
|
||||
lines.Add("");
|
||||
lines.Add(" Delete old backups:");
|
||||
lines.Add(" Duplicati.CommandLine delete-all-but-n-full <number of full backups to keep> [options] <backend>");
|
||||
lines.Add(" Duplicati.CommandLine delete-older-than <max allowed age> [options] <backend>");
|
||||
@@ -268,15 +280,14 @@ namespace Duplicati.CommandLine
|
||||
lines.Add(" The delete commands can be used to remove backup sets when newer backups are present.");
|
||||
lines.Add("");
|
||||
lines.Add("Option types:");
|
||||
lines.Add("");
|
||||
lines.Add(" The following option types are avalible:");
|
||||
lines.Add(" Integer: a numerical value");
|
||||
lines.Add(" Boolean: a truth value, --force and --force=true are equivalent. --force=false is the oposite");
|
||||
lines.Add(" Timespan: a time in the special time format");
|
||||
lines.Add(" Size: a size like 5mb or 200kb");
|
||||
lines.Add(" Enumeration: any of the listed values");
|
||||
lines.Add(" Path: the path to a folder or file");
|
||||
lines.Add(" String: any other type");
|
||||
lines.Add(" Integer: a numerical value");
|
||||
lines.Add(" Boolean: a truth value, --force and --force=true are equivalent. --force=false is the oposite");
|
||||
lines.Add(" Timespan: a time in the special time format");
|
||||
lines.Add(" Size: a size like 5mb or 200kb");
|
||||
lines.Add(" Enumeration: any of the listed values");
|
||||
lines.Add(" Path: the path to a folder or file");
|
||||
lines.Add(" String: any other type");
|
||||
lines.Add("");
|
||||
lines.Add("Times:");
|
||||
lines.Add(" Duplicati uses the time system from duplicity, where times may be presented as:");
|
||||
@@ -285,6 +296,26 @@ namespace Duplicati.CommandLine
|
||||
lines.Add(" 3: a string like \"2009-03-26T08:30:00+01:00\"");
|
||||
lines.Add(" 4: an interval string, using Y, M, W, D, h, m, s for Year, Month, Week, Day, hour, minute or second, eg: \"1M4D\" for one month and four days, or \"5m\" for five minutes.");
|
||||
lines.Add("");
|
||||
lines.Add("Filters:");
|
||||
lines.Add(" Duplicati uses filters to include and exclude files.");
|
||||
lines.Add(" Duplicati uses a \"first-touch\" filter where the first rule that matches a file determines if the file is included or excluded. Internally Duplciati 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:");
|
||||
lines.Add(" --include=*.txt --exclude=*\\Thumbs.db --include=*");
|
||||
lines.Add("");
|
||||
lines.Add(" Even though the last filter includes everything, no files named \"Thumbs.db\" are included because they match the exclude rule before the include rule. Paths are evaluated as paths that are releative to folder being backed up, but including a leading slash. An example:");
|
||||
if (System.Environment.OSVersion.Platform == PlatformID.Unix || System.Environment.OSVersion.Platform == PlatformID.MacOSX)
|
||||
{
|
||||
lines.Add(" Duplicati.CommandLine /home/user/ ftp://host/folder --exclude=/file.txt");
|
||||
lines.Add("");
|
||||
lines.Add(" In this example the file \"/home/user/file.txt\" is excluded.");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add(" Duplicati.CommandLine C:\\Documents\\Files ftp://host/folder --exclude=\\file.txt");
|
||||
lines.Add("");
|
||||
lines.Add(" In this example the file \"C:\\Documents\\Files\\file.txt\" is excluded.");
|
||||
}
|
||||
lines.Add("");
|
||||
lines.Add("");
|
||||
lines.Add("Duplicati options:");
|
||||
Library.Main.Options opt = new Library.Main.Options(new Dictionary<string, string>());
|
||||
foreach (Library.Backend.ICommandLineArgument arg in opt.SupportedCommands)
|
||||
@@ -296,7 +327,7 @@ namespace Duplicati.CommandLine
|
||||
{
|
||||
lines.Add(back.DisplayName + " (" + back.ProtocolKey + "):");
|
||||
lines.Add(" " + back.Description);
|
||||
lines.Add(" supported options:");
|
||||
lines.Add(" Supported options:");
|
||||
foreach (Library.Backend.ICommandLineArgument arg in back.SupportedCommands)
|
||||
PrintArgument(lines, arg);
|
||||
|
||||
@@ -325,25 +356,31 @@ namespace Duplicati.CommandLine
|
||||
{
|
||||
int len = Math.Min(Console.WindowWidth - 2, leadingSpaces.Length + c.Length);
|
||||
len -= leadingSpaces.Length;
|
||||
Console.WriteLine(leadingSpaces + c.Substring(0, len));
|
||||
if (len < c.Length)
|
||||
{
|
||||
int ix = c.LastIndexOf(" ", len);
|
||||
if (ix > 0)
|
||||
len = ix;
|
||||
}
|
||||
|
||||
Console.WriteLine(leadingSpaces + c.Substring(0, len).Trim());
|
||||
c = c.Remove(0, len);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void PrintArgument(List<string> lines, Duplicati.Library.Backend.ICommandLineArgument arg)
|
||||
{
|
||||
lines.Add(" --" + arg.Name + " (" + arg.Type.ToString()+ "): " + arg.ShortDescription);
|
||||
lines.Add(" " + arg.LongDescription);
|
||||
lines.Add(" " + arg.LongDescription);
|
||||
if (arg.Aliases != null && arg.Aliases.Length > 0)
|
||||
lines.Add(" aliases: --" + string.Join(", --", arg.Aliases));
|
||||
lines.Add(" * aliases: --" + string.Join(", --", arg.Aliases));
|
||||
|
||||
if (arg.ValidValues != null && arg.ValidValues.Length > 0)
|
||||
lines.Add(" values: " + string.Join(", ", arg.ValidValues));
|
||||
lines.Add(" * values: " + string.Join(", ", arg.ValidValues));
|
||||
|
||||
if (!string.IsNullOrEmpty(arg.DefaultValue))
|
||||
lines.Add(" default value: " + arg.DefaultValue);
|
||||
lines.Add(" * default value: " + arg.DefaultValue);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="AESEncryption.cs" />
|
||||
<Compile Include="CryptoStreamWrapper.cs" />
|
||||
<Compile Include="EncryptedBackendWrapper.cs" />
|
||||
<Compile Include="EncryptionBase.cs" />
|
||||
<Compile Include="GPGStreamWrapper.cs" />
|
||||
<Compile Include="IEncryption.cs" />
|
||||
|
||||
@@ -30,11 +30,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
private Backend.IBackend m_backend;
|
||||
private CommunicationStatistics m_statistics;
|
||||
private Dictionary<string, string> m_options;
|
||||
|
||||
private int m_retries;
|
||||
private TimeSpan m_retrydelay;
|
||||
private bool m_asyncOperation;
|
||||
private Options m_options;
|
||||
|
||||
//These keep track of async operations
|
||||
private Queue<string[]> m_pendingOperations;
|
||||
@@ -48,23 +44,13 @@ namespace Duplicati.Library.Main
|
||||
|
||||
public event RSync.RSyncDir.ProgressEventDelegate ProgressEvent;
|
||||
|
||||
public BackendWrapper(CommunicationStatistics statistics, Backend.IBackend backend, Dictionary<string, string> options)
|
||||
public BackendWrapper(CommunicationStatistics statistics, Backend.IBackend backend, Options options)
|
||||
{
|
||||
m_statistics = statistics;
|
||||
m_backend = backend;
|
||||
m_retries = 5;
|
||||
m_options = options;
|
||||
|
||||
if (options.ContainsKey("number-of-retries"))
|
||||
int.TryParse(options["number-of-retries"], out m_retries);
|
||||
|
||||
m_retrydelay = new TimeSpan(TimeSpan.TicksPerSecond * 10);
|
||||
if (options.ContainsKey("retry-delay"))
|
||||
try { Core.Timeparser.ParseTimeSpan(options["retry-delay"]); }
|
||||
catch { }
|
||||
|
||||
m_asyncOperation = options.ContainsKey("asynchronous-upload");
|
||||
if (m_asyncOperation)
|
||||
if (options.AsynchronousUpload)
|
||||
{
|
||||
//If we are using async operations, the entire class is actually threadsafe,
|
||||
//utilizing a common exclusive lock on all operations. But the implementation does
|
||||
@@ -99,7 +85,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
public void Put(string remotename, string filename)
|
||||
{
|
||||
if (!m_asyncOperation)
|
||||
if (!m_options.AsynchronousUpload)
|
||||
PutInternal(remotename, filename);
|
||||
else
|
||||
{
|
||||
@@ -160,7 +146,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
|
||||
//If the code is not async, just invoke it
|
||||
if (!m_asyncOperation)
|
||||
if (!m_options.AsynchronousUpload)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -212,7 +198,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
private List<Duplicati.Library.Backend.FileEntry> ListInternal()
|
||||
{
|
||||
int retries = m_retries;
|
||||
int retries = m_options.NumberOfRetries;
|
||||
Exception lastEx = null;
|
||||
|
||||
do
|
||||
@@ -228,8 +214,8 @@ namespace Duplicati.Library.Main
|
||||
m_statistics.LogError(ex.Message);
|
||||
|
||||
retries--;
|
||||
if (retries > 0 && m_retrydelay.Ticks > 0)
|
||||
System.Threading.Thread.Sleep(m_retrydelay);
|
||||
if (retries > 0 && m_options.RetryDelay.Ticks > 0)
|
||||
System.Threading.Thread.Sleep(m_options.RetryDelay);
|
||||
}
|
||||
} while (retries > 0);
|
||||
|
||||
@@ -238,7 +224,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
private void DeleteInternal(string remotename)
|
||||
{
|
||||
int retries = m_retries;
|
||||
int retries = m_options.NumberOfRetries;
|
||||
Exception lastEx = null;
|
||||
|
||||
do
|
||||
@@ -255,8 +241,8 @@ namespace Duplicati.Library.Main
|
||||
m_statistics.LogError(ex.Message);
|
||||
|
||||
retries--;
|
||||
if (retries > 0 && m_retrydelay.Ticks > 0)
|
||||
System.Threading.Thread.Sleep(m_retrydelay);
|
||||
if (retries > 0 && m_options.RetryDelay.Ticks > 0)
|
||||
System.Threading.Thread.Sleep(m_options.RetryDelay);
|
||||
}
|
||||
} while (lastEx != null && retries > 0);
|
||||
|
||||
@@ -266,7 +252,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
private void GetInternal(string remotename, string filename)
|
||||
{
|
||||
int retries = m_retries;
|
||||
int retries = m_options.NumberOfRetries;
|
||||
Exception lastEx = null;
|
||||
m_statusmessage = "Downloading: " + remotename;
|
||||
|
||||
@@ -277,17 +263,17 @@ namespace Duplicati.Library.Main
|
||||
m_statistics.NumberOfRemoteCalls++;
|
||||
if (!this.SupportsStreaming)
|
||||
{
|
||||
if (!m_asyncOperation && ProgressEvent != null)
|
||||
if (!m_options.AsynchronousUpload && ProgressEvent != null)
|
||||
ProgressEvent(50, m_statusmessage);
|
||||
m_backend.Get(remotename, filename);
|
||||
if (!m_asyncOperation && ProgressEvent != null)
|
||||
if (!m_options.AsynchronousUpload && ProgressEvent != null)
|
||||
ProgressEvent(100, m_statusmessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: How can we guess the remote file size for progress reporting?
|
||||
using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
|
||||
((Backend.IStreamingBackend)m_backend).Get(remotename, ThrottleStream(fs, m_options));
|
||||
((Backend.IStreamingBackend)m_backend).Get(remotename, new Core.ThrottledStream(fs, m_options.MaxUploadPrSecond, m_options.MaxDownloadPrSecond));
|
||||
}
|
||||
|
||||
lastEx = null;
|
||||
@@ -298,8 +284,8 @@ namespace Duplicati.Library.Main
|
||||
m_statistics.LogError(ex.Message);
|
||||
|
||||
retries--;
|
||||
if (retries > 0 && m_retrydelay.Ticks > 0)
|
||||
System.Threading.Thread.Sleep(m_retrydelay);
|
||||
if (retries > 0 && m_options.RetryDelay.Ticks > 0)
|
||||
System.Threading.Thread.Sleep(m_options.RetryDelay);
|
||||
}
|
||||
} while (lastEx != null && retries > 0);
|
||||
|
||||
@@ -315,7 +301,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
try
|
||||
{
|
||||
int retries = m_retries;
|
||||
int retries = m_options.NumberOfRetries;
|
||||
bool success = false;
|
||||
|
||||
do
|
||||
@@ -325,10 +311,10 @@ namespace Duplicati.Library.Main
|
||||
m_statistics.NumberOfRemoteCalls++;
|
||||
if (!this.SupportsStreaming)
|
||||
{
|
||||
if (!m_asyncOperation && ProgressEvent != null)
|
||||
if (!m_options.AsynchronousUpload && ProgressEvent != null)
|
||||
ProgressEvent(50, m_statusmessage);
|
||||
m_backend.Put(remotename, filename);
|
||||
if (!m_asyncOperation && ProgressEvent != null)
|
||||
if (!m_options.AsynchronousUpload && ProgressEvent != null)
|
||||
ProgressEvent(50, m_statusmessage);
|
||||
}
|
||||
else
|
||||
@@ -340,9 +326,9 @@ namespace Duplicati.Library.Main
|
||||
using (Core.ProgressReportingStream pgs = new Duplicati.Library.Core.ProgressReportingStream(fs, fs.Length))
|
||||
{
|
||||
l = pgs.Length;
|
||||
if (!m_asyncOperation)
|
||||
if (!m_options.AsynchronousUpload)
|
||||
pgs.Progress += new Duplicati.Library.Core.ProgressReportingStream.ProgressDelegate(pgs_Progress);
|
||||
((Backend.IStreamingBackend)m_backend).Put(remotename, ThrottleStream(pgs, m_options));
|
||||
((Backend.IStreamingBackend)m_backend).Put(remotename, new Core.ThrottledStream(pgs, m_options.MaxUploadPrSecond, m_options.MaxDownloadPrSecond));
|
||||
}
|
||||
|
||||
TimeSpan duration = DateTime.Now - begin;
|
||||
@@ -366,8 +352,8 @@ namespace Duplicati.Library.Main
|
||||
m_statistics.LogError(ex.Message);
|
||||
|
||||
retries--;
|
||||
if (retries > 0 && m_retrydelay.Ticks > 0)
|
||||
System.Threading.Thread.Sleep(m_retrydelay);
|
||||
if (retries > 0 && m_options.RetryDelay.Ticks > 0)
|
||||
System.Threading.Thread.Sleep(m_options.RetryDelay);
|
||||
}
|
||||
} while (!success && retries > 0);
|
||||
|
||||
@@ -433,21 +419,6 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Core.ThrottledStream ThrottleStream(System.IO.Stream basestream, Dictionary<string, string> options)
|
||||
{
|
||||
return new Core.ThrottledStream(basestream,
|
||||
options.ContainsKey("max-upload-pr-second") ? Core.Sizeparser.ParseSize(options["max-upload-pr-second"], "kb") : 0,
|
||||
options.ContainsKey("max-download-pr-second") ? Core.Sizeparser.ParseSize(options["max-download-pr-second"], "kb") : 0);
|
||||
}
|
||||
|
||||
private Core.ThrottledStream ThrottleStream(System.IO.Stream basestream, string upspeed, string downspeed)
|
||||
{
|
||||
return new Core.ThrottledStream(basestream,
|
||||
upspeed == null ? 0 : Core.Sizeparser.ParseSize(upspeed, "kb"),
|
||||
downspeed == null ? 0 : Core.Sizeparser.ParseSize(downspeed, "kb"));
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
<Compile Include="BackendWrapper.cs" />
|
||||
<Compile Include="BackupEntry.cs" />
|
||||
<Compile Include="CommunicationStatistics.cs" />
|
||||
<Compile Include="EncryptedBackendWrapper.cs" />
|
||||
<Compile Include="FilenameStrategy.cs" />
|
||||
<Compile Include="Interface.cs" />
|
||||
<Compile Include="Options.cs" />
|
||||
|
||||
+18
-21
@@ -21,7 +21,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Duplicati.Library.Encryption
|
||||
namespace Duplicati.Library.Main
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that wraps a backend and transparently encrypts and decrypts data to and from the backend
|
||||
@@ -29,29 +29,29 @@ namespace Duplicati.Library.Encryption
|
||||
public class EncryptedBackendWrapper : Backend.IStreamingBackend
|
||||
{
|
||||
private Backend.IBackend m_realbackend;
|
||||
private IEncryption m_encryptionEngine;
|
||||
private Library.Encryption.IEncryption m_encryptionEngine;
|
||||
|
||||
public EncryptedBackendWrapper(string target, Dictionary<string, string> options)
|
||||
: this(Backend.BackendLoader.GetBackend(target, options), options)
|
||||
public EncryptedBackendWrapper(string target, Options options)
|
||||
: this(Backend.BackendLoader.GetBackend(target, options.RawOptions), options)
|
||||
{
|
||||
}
|
||||
|
||||
public EncryptedBackendWrapper(Backend.IBackend backend, Dictionary<string, string> options)
|
||||
public EncryptedBackendWrapper(Backend.IBackend backend, Options options)
|
||||
{
|
||||
if (!options.ContainsKey("passphrase") || string.IsNullOrEmpty(options["passphrase"]))
|
||||
if (string.IsNullOrEmpty(options.Passphrase))
|
||||
throw new Exception("No passphrase set");
|
||||
|
||||
string passphrase = options["passphrase"];
|
||||
string passphrase = options.Passphrase;
|
||||
|
||||
m_realbackend = backend;
|
||||
if (options.ContainsKey("gpg-encryption"))
|
||||
if (options.GPGEncryption)
|
||||
{
|
||||
if (options.ContainsKey("gpg-program-path"))
|
||||
GPGEncryption.PGP_PROGRAM = options["gpg-program-path"];
|
||||
m_encryptionEngine = new GPGEncryption(passphrase, options.ContainsKey("sign-key") ? options["sign-key"] : null);
|
||||
if (!string.IsNullOrEmpty(options.GPGPath))
|
||||
Library.Encryption.GPGEncryption.PGP_PROGRAM = options.GPGPath;
|
||||
m_encryptionEngine = new Library.Encryption.GPGEncryption(options.Passphrase, options.GPGSignKey);
|
||||
}
|
||||
else
|
||||
m_encryptionEngine = new AESEncryption(passphrase);
|
||||
m_encryptionEngine = new Library.Encryption.AESEncryption(options.Passphrase);
|
||||
}
|
||||
|
||||
#region IStreamingBackend Members
|
||||
@@ -90,7 +90,7 @@ namespace Duplicati.Library.Encryption
|
||||
|
||||
public void Put(string remotename, string filename)
|
||||
{
|
||||
remotename += (m_encryptionEngine is AESEncryption ? ".aes" : ".gpg");
|
||||
remotename += (m_encryptionEngine is Library.Encryption.AESEncryption ? ".aes" : ".gpg");
|
||||
using (Core.TempFile tf = new Duplicati.Library.Core.TempFile())
|
||||
{
|
||||
m_encryptionEngine.Encrypt(filename, tf);
|
||||
@@ -143,25 +143,22 @@ namespace Duplicati.Library.Encryption
|
||||
|
||||
#endregion
|
||||
|
||||
public static Backend.IBackend WrapWithEncryption(Backend.IBackend backend, Dictionary<string, string> options)
|
||||
public static Backend.IBackend WrapWithEncryption(Backend.IBackend backend, Options options)
|
||||
{
|
||||
if (options.ContainsKey("no-encryption"))
|
||||
if (options.NoEncryption)
|
||||
return backend;
|
||||
else
|
||||
return new EncryptedBackendWrapper(backend, options);
|
||||
}
|
||||
|
||||
public static Backend.IBackend WrapWithEncryption(Backend.IBackend backend, string extension, Dictionary<string, string> options)
|
||||
public static Backend.IBackend WrapWithEncryption(Backend.IBackend backend, string extension, Options options)
|
||||
{
|
||||
if (string.IsNullOrEmpty(extension))
|
||||
return backend;
|
||||
else if (extension == "aes")
|
||||
{
|
||||
if (options.ContainsKey("gpg-encryption"))
|
||||
options.Remove("gpg-encryption");
|
||||
}
|
||||
options.GPGEncryption = false;
|
||||
else if (extension == "gpg")
|
||||
options["gpg-encryption"] = "";
|
||||
options.GPGEncryption = true;
|
||||
else
|
||||
throw new Exception("Unsupported encryption extension");
|
||||
|
||||
@@ -43,6 +43,11 @@ namespace Duplicati.Library.Main
|
||||
private double m_incrementalFraction = 0.15;
|
||||
private double m_progress = 0.0;
|
||||
|
||||
/// <summary>
|
||||
/// The filenamestrategy for the signature cache
|
||||
/// </summary>
|
||||
FilenameStrategy m_cacheFilenameStrategy = new FilenameStrategy("dpl", "_", true);
|
||||
|
||||
public event OperationProgressEvent OperationStarted;
|
||||
public event OperationProgressEvent OperationCompleted;
|
||||
public event OperationProgressEvent OperationProgress;
|
||||
@@ -72,16 +77,15 @@ namespace Duplicati.Library.Main
|
||||
OperationProgress(this, DuplicatiOperation.Backup, -1, -1, "Loading remote filelist");
|
||||
|
||||
FilenameStrategy fns = new FilenameStrategy(m_options);
|
||||
FilenameStrategy cacheFilenameStrategy = new FilenameStrategy("dpl", "_", true);
|
||||
|
||||
bool full = m_options.Full;
|
||||
|
||||
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.RawOptions);
|
||||
backend = new BackendWrapper(bs, backend, m_options);
|
||||
((BackendWrapper)backend).ProgressEvent += new Duplicati.Library.Main.RSync.RSyncDir.ProgressEventDelegate(BackupTransfer_ProgressEvent);
|
||||
backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, m_options.RawOptions);
|
||||
backend = EncryptedBackendWrapper.WrapWithEncryption(backend, m_options);
|
||||
|
||||
List<BackupEntry> orphans = m_options.AutoCleanup ? new List<BackupEntry>() : null;
|
||||
List <BackupEntry > prev = ParseFileList(backend, orphans);
|
||||
@@ -192,7 +196,7 @@ namespace Duplicati.Library.Main
|
||||
//For now it works, because the manifest does not contain this entry anyway
|
||||
//Any problems would only occur when using disabled signature checks
|
||||
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)));
|
||||
System.IO.File.Copy(signaturefile, System.IO.Path.Combine(m_options.SignatureCachePath, m_cacheFilenameStrategy.GenerateFilename(BackupEntry.EntryType.Signature, full, backuptime, vol + 1)));
|
||||
|
||||
backend.Put(fns.GenerateFilename(BackupEntry.EntryType.Signature, full, backuptime, vol + 1) + ".zip", signaturefile);
|
||||
|
||||
@@ -247,27 +251,45 @@ namespace Duplicati.Library.Main
|
||||
return bs.ToString();
|
||||
}
|
||||
|
||||
private void DeleteSignatureCacheCopy(BackupEntry entry)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(m_options.SignatureCachePath))
|
||||
{
|
||||
string cacheFilename = m_cacheFilenameStrategy.GenerateFilename(entry.Type, entry.IsFull, entry.Time, entry.VolumeNumber);
|
||||
if (System.IO.File.Exists(cacheFilename))
|
||||
try { System.IO.File.Delete(cacheFilename); }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteOrphans(Backend.IBackend backend, List<BackupEntry> orphans)
|
||||
{
|
||||
//TODO: Delete from SignatureCache as well
|
||||
|
||||
foreach (BackupEntry be in orphans)
|
||||
{
|
||||
Logging.Log.WriteMessage("Removing leftover file: " + be.Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.Force)
|
||||
{
|
||||
backend.Delete(be.Filename);
|
||||
DeleteSignatureCacheCopy(be);
|
||||
}
|
||||
|
||||
foreach (BackupEntry bex in be.SignatureFile)
|
||||
{
|
||||
Logging.Log.WriteMessage("Removing leftover file: " + bex.Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.Force)
|
||||
{
|
||||
backend.Delete(bex.Filename);
|
||||
DeleteSignatureCacheCopy(bex);
|
||||
}
|
||||
}
|
||||
foreach (BackupEntry bex in be.ContentVolumes)
|
||||
{
|
||||
Logging.Log.WriteMessage("Removing leftover file: " + bex.Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.Force)
|
||||
{
|
||||
backend.Delete(bex.Filename);
|
||||
DeleteSignatureCacheCopy(bex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,11 +340,11 @@ namespace Duplicati.Library.Main
|
||||
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.RawOptions);
|
||||
backend = new BackendWrapper(rs, backend, m_options);
|
||||
|
||||
BackupEntry bestFit = FindBestMatch(ParseFileList(backend), m_options.RestoreTime);
|
||||
if (bestFit.EncryptionMode != null)
|
||||
backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options.RawOptions);
|
||||
backend = EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options);
|
||||
|
||||
List<BackupEntry> entries = new List<BackupEntry>();
|
||||
entries.Add(bestFit);
|
||||
@@ -425,7 +447,7 @@ namespace Duplicati.Library.Main
|
||||
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.RawOptions);
|
||||
backend = new BackendWrapper(rs, backend, m_options);
|
||||
|
||||
List<BackupEntry> attempts = ParseFileList(backend);
|
||||
|
||||
@@ -444,7 +466,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
Backend.IBackend realbackend = backend;
|
||||
if (be.EncryptionMode != null)
|
||||
realbackend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, be.EncryptionMode, m_options.RawOptions);
|
||||
realbackend = EncryptedBackendWrapper.WrapWithEncryption(backend, be.EncryptionMode, m_options);
|
||||
|
||||
if (be.SignatureFile.Count > 0)
|
||||
using(Core.TempFile z = new Duplicati.Library.Core.TempFile())
|
||||
@@ -600,7 +622,6 @@ namespace Duplicati.Library.Main
|
||||
|
||||
private string RemoveBackupSets(Backend.IBackend backend, List<BackupEntry> entries)
|
||||
{
|
||||
//TODO: Delete from SignatureCache as well
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
foreach (BackupEntry be in entries)
|
||||
@@ -611,12 +632,19 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
//Delete manifest
|
||||
backend.Delete(be.Filename);
|
||||
DeleteSignatureCacheCopy(be);
|
||||
|
||||
foreach (BackupEntry bex in be.ContentVolumes)
|
||||
{
|
||||
backend.Delete(bex.Filename);
|
||||
DeleteSignatureCacheCopy(bex);
|
||||
}
|
||||
|
||||
foreach (BackupEntry bex in be.SignatureFile)
|
||||
{
|
||||
backend.Delete(bex.Filename);
|
||||
DeleteSignatureCacheCopy(bex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +737,10 @@ namespace Duplicati.Library.Main
|
||||
anyRemoved = true;
|
||||
Logging.Log.WriteMessage("Removing partial file: " + be.SignatureFile[i].Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.Force)
|
||||
{
|
||||
backend.Delete(be.SignatureFile[i].Filename);
|
||||
DeleteSignatureCacheCopy(be.SignatureFile[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = count - 1; i < be.ContentVolumes.Count; i++)
|
||||
@@ -717,7 +748,10 @@ namespace Duplicati.Library.Main
|
||||
anyRemoved = true;
|
||||
Logging.Log.WriteMessage("Removing partial file: " + be.SignatureFile[i].Filename, Duplicati.Library.Logging.LogMessageType.Information);
|
||||
if (m_options.Force)
|
||||
{
|
||||
backend.Delete(be.ContentVolumes[i].Filename);
|
||||
DeleteSignatureCacheCopy(be.ContentVolumes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -866,14 +900,14 @@ namespace Duplicati.Library.Main
|
||||
|
||||
if (backend == null)
|
||||
throw new Exception("Unable to find backend for target: " + m_backend);
|
||||
backend = new BackendWrapper(rs, backend, m_options.RawOptions);
|
||||
backend = new BackendWrapper(rs, backend, m_options);
|
||||
|
||||
if (OperationStarted != null)
|
||||
OperationStarted(this, DuplicatiOperation.List, 0, -1, "Started");
|
||||
|
||||
BackupEntry bestFit = FindBestMatch(ParseFileList(backend), m_options.RestoreTime);
|
||||
if (bestFit.EncryptionMode != null)
|
||||
backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options.RawOptions);
|
||||
backend = EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options);
|
||||
|
||||
List<string> res;
|
||||
|
||||
@@ -941,7 +975,6 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
List<Core.IFileArchive> patches = new List<Core.IFileArchive>();
|
||||
|
||||
FilenameStrategy cacheFilenameStrategy = new FilenameStrategy("dpl", "_", true);
|
||||
using (new Logging.Timer("Reading incremental data"))
|
||||
{
|
||||
if (OperationProgress != null)
|
||||
@@ -1028,7 +1061,7 @@ namespace Duplicati.Library.Main
|
||||
string cachefilename = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(m_options.SignatureCachePath))
|
||||
cachefilename = System.IO.Path.Combine(m_options.SignatureCachePath, cacheFilenameStrategy.GenerateFilename(bes.Type, bes.IsFull, bes.Time, bes.VolumeNumber));
|
||||
cachefilename = System.IO.Path.Combine(m_options.SignatureCachePath, m_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])
|
||||
@@ -1059,6 +1092,15 @@ namespace Duplicati.Library.Main
|
||||
return patches;
|
||||
}
|
||||
|
||||
public void PurgeSignatureCache()
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_options.SignatureCachePath))
|
||||
throw new Exception("Signature cache path was not given as an argument");
|
||||
else
|
||||
System.IO.Directory.Delete(m_options.SignatureCachePath, true);
|
||||
}
|
||||
|
||||
|
||||
//Static helpers
|
||||
|
||||
public static string[] List(string target, Dictionary<string, string> options)
|
||||
@@ -1115,6 +1157,15 @@ namespace Duplicati.Library.Main
|
||||
return i.RestoreControlFiles(target);
|
||||
}
|
||||
|
||||
public static void PurgeSignatureCache(Dictionary<string, string> options)
|
||||
{
|
||||
Options opt = new Options(options);
|
||||
if (string.IsNullOrEmpty(opt.SignatureCachePath))
|
||||
throw new Exception("Signature cache path was not given as an argument");
|
||||
else
|
||||
System.IO.Directory.Delete(opt.SignatureCachePath, true);
|
||||
}
|
||||
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
|
||||
@@ -60,18 +60,23 @@ namespace Duplicati.Library.Main
|
||||
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("include", Backend.CommandLineArgument.ArgumentType.String, "Include files", "Include files that match this filter. The filter is a \"file-globbing\" filter, much like commandline options. You can use \"*.txt\" to include all text files."),
|
||||
new Backend.CommandLineArgument("exclude", Backend.CommandLineArgument.ArgumentType.String, "Exclude files", "Include files that match this filter. The filter is a \"file-globbing\" filter, much like commandline options. You can use \"*.txt\" to exclude all text files."),
|
||||
new Backend.CommandLineArgument("include-regexp", Backend.CommandLineArgument.ArgumentType.String, "Include files regular expression", "Include files that match this filter. The filter is a regular expression filter. You can use \"(.*)\\.txt\" to include all text files."),
|
||||
new Backend.CommandLineArgument("exclude-regexp", Backend.CommandLineArgument.ArgumentType.String, "Exclude files regular expression", "Include files that match this filter. The filter is a regular expression filter. You can use \"(.*)\\.txt\" to exclude all text files."),
|
||||
|
||||
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("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.", "gpg"),
|
||||
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."),
|
||||
|
||||
new Backend.CommandLineArgument("number-of-retries", Backend.CommandLineArgument.ArgumentType.Integer, "Number of times to retry a failed transmission", "If an upload or download fails, Duplicati will retry a number of times before failing. Use this to handle unstable network connections better.", "5"),
|
||||
new Backend.CommandLineArgument("retry-delay", Backend.CommandLineArgument.ArgumentType.Timespan, "Time to wait between retries", "After a failed transmission, Duplicati will wait a short period before attempting again. This is usefull if the network drops out occasionally during transmissions.", "10s"),
|
||||
new Backend.CommandLineArgument("asynchronous-upload", Backend.CommandLineArgument.ArgumentType.Boolean, "Transmit files on a seperate thread", "By supplying this option, Duplicati will transmit files, while building volumes. This can shorten the time it takes to perform a backup, but requires more diskspace.", "false"),
|
||||
|
||||
new Backend.CommandLineArgument("max-upload-pr-second", Backend.CommandLineArgument.ArgumentType.Size, "Max number of bytes to upload pr. second", "By setting this value you can limit how much bandwidth Duplicati consumes for uploads. Setting this limit can make the backups take longer, but will make Duplicati less intrusive."),
|
||||
new Backend.CommandLineArgument("max-download-pr-second", Backend.CommandLineArgument.ArgumentType.Size, "Max number of bytes to download pr. second", "By setting this value you can limit how much bandwidth Duplicati consumes for downloads. Setting this limit can make the backups take longer, but will make Duplicati less intrusive.", "false"),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -268,7 +273,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_options.ContainsKey("filter"))
|
||||
if (m_options.ContainsKey("filter") && !string.IsNullOrEmpty(m_options["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>>());
|
||||
@@ -287,7 +292,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("remove-all-but-n-full"))
|
||||
if (!m_options.ContainsKey("remove-all-but-n-full") || string.IsNullOrEmpty(m_options["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"]);
|
||||
@@ -312,6 +317,131 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the encryption passphrase
|
||||
/// </summary>
|
||||
public string Passphrase
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("passphrase") || string.IsNullOrEmpty(m_options["passphrase"]))
|
||||
return null;
|
||||
else
|
||||
return m_options["passphrase"];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets GnuPG program path
|
||||
/// </summary>
|
||||
public string GPGPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("gpg-program-path") || string.IsNullOrEmpty(m_options["gpg-program-path"]))
|
||||
return "gpg";
|
||||
else
|
||||
return m_options["gpg-program-path"];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GPG sign key
|
||||
/// </summary>
|
||||
public string GPGSignKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("sign-key") || string.IsNullOrEmpty(m_options["sign-key"]))
|
||||
return null;
|
||||
else
|
||||
return m_options["sign-key"];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating if backups are not encrypted
|
||||
/// </summary>
|
||||
public bool NoEncryption { get { return GetBool("no-encryption"); } }
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating if GPG encryption is used
|
||||
/// </summary>
|
||||
public bool GPGEncryption
|
||||
{
|
||||
get { return GetBool("gpg-encryption"); }
|
||||
set { m_options["gpg-encryption"] = value.ToString(); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of time to retry transmission if it fails
|
||||
/// </summary>
|
||||
public int NumberOfRetries
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("number-of-retries") || string.IsNullOrEmpty(m_options["number-of-retries"]))
|
||||
return 5;
|
||||
else
|
||||
{
|
||||
int x = int.Parse(m_options["number-of-retries"]);
|
||||
if (x < 0)
|
||||
throw new Exception("Invalid count for number-of-retries");
|
||||
|
||||
return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating if backups are transmitted on a seperate thread
|
||||
/// </summary>
|
||||
public bool AsynchronousUpload { get { return GetBool("asynchronous-upload"); } }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timelimit for removal
|
||||
/// </summary>
|
||||
public TimeSpan RetryDelay
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("retry-delay") || string.IsNullOrEmpty(m_options["retry-delay"]))
|
||||
return new TimeSpan(TimeSpan.TicksPerSecond * 10);
|
||||
else
|
||||
return Core.Timeparser.ParseTimeSpan(m_options["retry-delay"]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the max upload speed in bytes pr. second
|
||||
/// </summary>
|
||||
public long MaxUploadPrSecond
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("max-upload-pr-second") || string.IsNullOrEmpty(m_options["max-upload-pr-second"]))
|
||||
return 0;
|
||||
else
|
||||
return Core.Sizeparser.ParseSize(m_options["max-upload-pr-second"], "kb");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the max download speed in bytes pr. second
|
||||
/// </summary>
|
||||
public long MaxDownloadPrSecond
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_options.ContainsKey("max-download-pr-second") || string.IsNullOrEmpty(m_options["max-download-pr-second"]))
|
||||
return 0;
|
||||
else
|
||||
return Core.Sizeparser.ParseSize(m_options["max-download-pr-second"], "kb");
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetBool(string name)
|
||||
{
|
||||
if (!m_options.ContainsKey(name))
|
||||
|
||||
Reference in New Issue
Block a user