Merge pull request #39 from duplicati/master

upstream
This commit is contained in:
Max
2018-03-24 10:59:28 +01:00
committed by GitHub
221 changed files with 5520 additions and 3012 deletions
+9 -9
View File
@@ -94,12 +94,12 @@ namespace Duplicati.CommandLine.BackendTool
using(var backend = Library.DynamicLoader.BackendLoader.GetBackend(args[1], options))
{
if (backend == null)
throw new UserInformationException("Backend not supported");
throw new UserInformationException("Backend not supported", "InvalidBackend");
if (command == "list")
{
if (args.Count != 2)
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)));
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)), "BackendToolTooManyArguments");
Console.WriteLine(string.Format("{0}\t{1}\t{2}\t{3}", "Name", "Dir/File", "LastChange", "Size"));
foreach(var e in backend.List())
@@ -110,7 +110,7 @@ namespace Duplicati.CommandLine.BackendTool
else if (command == "create")
{
if (args.Count != 2)
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)));
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)), "BackendToolTooManyArguments");
backend.CreateFolder();
@@ -119,7 +119,7 @@ namespace Duplicati.CommandLine.BackendTool
else if (command == "delete")
{
if (args.Count < 3)
throw new UserInformationException("DELETE requires a filename argument");
throw new UserInformationException("DELETE requires a filename argument", "BackendToolDeleteRequiresAnArgument");
if (args.Count > 3)
throw new Exception(string.Format("too many arguments: {0}", string.Join(",", args)));
backend.Delete(Path.GetFileName(args[2]));
@@ -129,11 +129,11 @@ namespace Duplicati.CommandLine.BackendTool
else if (command == "get")
{
if (args.Count < 3)
throw new UserInformationException("GET requires a filename argument");
throw new UserInformationException("GET requires a filename argument", "BackendToolGetRequiresAnArgument");
if (args.Count > 3)
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)));
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)), "BackendToolTooManyArguments");
if (File.Exists(args[2]))
throw new UserInformationException("File already exists, not overwriting!");
throw new UserInformationException("File already exists, not overwriting!", "BackendToolFileAlreadyExists");
backend.Get(Path.GetFileName(args[2]), args[2]);
return 0;
@@ -141,9 +141,9 @@ namespace Duplicati.CommandLine.BackendTool
else if (command == "put")
{
if (args.Count < 3)
throw new UserInformationException("PUT requires a filename argument");
throw new UserInformationException("PUT requires a filename argument","BackendToolPutRequiresAndArgument");
if (args.Count > 3)
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)));
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)), "BackendToolTooManyArguments");
backend.Put(Path.GetFileName(args[2]), args[2]);
+141 -125
View File
@@ -143,7 +143,7 @@ namespace Duplicati.CommandLine
public static int Affected(TextWriter outwriter, Action<Duplicati.Library.Main.Controller> setup, List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter)
{
var fullresult = Duplicati.Library.Utility.Utility.ParseBoolOption(options, "verbose") || Duplicati.Library.Utility.Utility.ParseBoolOption(options, "full-result");
var fullresult = Duplicati.Library.Utility.Utility.ParseBoolOption(options, "full-result");
var backend = args[0];
args.RemoveAt(0);
@@ -172,7 +172,8 @@ namespace Duplicati.CommandLine
options["no-encryption"] = "true";
}
using(var i = new Library.Main.Controller(backend, options, new ConsoleOutput(outwriter, options)))
using(var console = new ConsoleOutput(outwriter, options))
using(var i = new Library.Main.Controller(backend, options, console))
{
setup(i);
i.ListAffected(args, res =>
@@ -242,7 +243,8 @@ namespace Duplicati.CommandLine
if (Duplicati.Library.Utility.Utility.ParseBoolOption(options, "list-sets-only"))
filter = new Duplicati.Library.Utility.FilterExpression();
using(var i = new Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using(var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
var backend = args[0];
@@ -391,7 +393,8 @@ namespace Duplicati.CommandLine
return 200;
}
using(var i = new Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using(var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
args.RemoveAt(0);
@@ -422,7 +425,8 @@ namespace Duplicati.CommandLine
if (args.Count != 1)
return PrintWrongNumberOfArguments(outwriter, args, 1);
using (var i = new Duplicati.Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Duplicati.Library.Main.Controller(args[0], options, console))
{
setup(i);
i.Repair(filter);
@@ -452,11 +456,10 @@ namespace Duplicati.CommandLine
if (args[ix].IndexOfAny(new char[] { '*', '?' }) < 0 && !args[ix].StartsWith("[", StringComparison.Ordinal) && args[ix].EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
args[ix] += "*";
var output = new ConsoleOutput(outwriter, options);
output.MessageEvent(string.Format("Restore started at {0}", DateTime.Now));
using(var output = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(backend, options, output))
{
output.MessageEvent(string.Format("Restore started at {0}", DateTime.Now));
setup(i);
if (controlFiles)
{
@@ -517,7 +520,7 @@ namespace Duplicati.CommandLine
output.MessageEvent("***********************************************");
}
if (output.VerboseOutput)
if (output.FullResults)
Library.Utility.Utility.PrintSerializeObject(res, outwriter);
if (res.Warnings.Count() > 0)
@@ -537,110 +540,116 @@ namespace Duplicati.CommandLine
var backend = args[0];
args.RemoveAt(0);
var dirs = args.ToArray();
var output = new ConsoleOutput(outwriter, options);
Library.Interface.IBackupResults result;
using(var periodicOutput = new PeriodicOutput(output, TimeSpan.FromSeconds(5)))
using (var output = new ConsoleOutput(outwriter, options))
{
output.MessageEvent(string.Format("Backup started at {0}", DateTime.Now));
output.PhaseChanged += (phase, previousPhase) =>
using (var periodicOutput = new PeriodicOutput(output, TimeSpan.FromSeconds(5)))
{
if (previousPhase == Duplicati.Library.Main.OperationPhase.Backup_PostBackupTest)
output.MessageEvent("Remote backup verification completed");
switch (phase)
if ((new Duplicati.Library.Main.Options(options)).DisableOnBattery && (Duplicati.Library.Utility.Power.PowerSupply.GetSource() == Duplicati.Library.Utility.Power.PowerSupply.Source.Battery))
{
case Duplicati.Library.Main.OperationPhase.Backup_ProcessingFiles:
output.MessageEvent("Scanning local files ...");
periodicOutput.SetReady();
break;
case Duplicati.Library.Main.OperationPhase.Backup_Finalize:
periodicOutput.SetFinished();
break;
case Duplicati.Library.Main.OperationPhase.Backup_PreBackupVerify:
output.MessageEvent("Checking remote backup ...");
break;
case Duplicati.Library.Main.OperationPhase.Backup_PostBackupVerify:
output.MessageEvent("Checking remote backup ...");
break;
case Duplicati.Library.Main.OperationPhase.Backup_PostBackupTest:
output.MessageEvent("Verifying remote backup ...");
break;
case Duplicati.Library.Main.OperationPhase.Backup_Compact:
output.MessageEvent("Compacting remote backup ...");
break;
output.MessageEvent("The \"disable-on-battery\" option only affects scheduled backups and is ignored by backups run manually or from the command line.");
}
};
periodicOutput.WriteOutput += (progress, files, size, counting) => {
output.MessageEvent(string.Format(" {0} files need to be examined ({1}){2}", files, Library.Utility.Utility.FormatSizeString(size), counting ? " (still counting)" : ""));
};
using (var i = new Library.Main.Controller(backend, options, output))
{
setup(i);
result = i.Backup(dirs, filter);
output.MessageEvent(string.Format("Backup started at {0}", DateTime.Now));
output.PhaseChanged += (phase, previousPhase) =>
{
if (previousPhase == Duplicati.Library.Main.OperationPhase.Backup_PostBackupTest)
output.MessageEvent("Remote backup verification completed");
switch (phase)
{
case Duplicati.Library.Main.OperationPhase.Backup_ProcessingFiles:
output.MessageEvent("Scanning local files ...");
periodicOutput.SetReady();
break;
case Duplicati.Library.Main.OperationPhase.Backup_Finalize:
periodicOutput.SetFinished();
break;
case Duplicati.Library.Main.OperationPhase.Backup_PreBackupVerify:
output.MessageEvent("Checking remote backup ...");
break;
case Duplicati.Library.Main.OperationPhase.Backup_PostBackupVerify:
output.MessageEvent("Checking remote backup ...");
break;
case Duplicati.Library.Main.OperationPhase.Backup_PostBackupTest:
output.MessageEvent("Verifying remote backup ...");
break;
case Duplicati.Library.Main.OperationPhase.Backup_Compact:
output.MessageEvent("Compacting remote backup ...");
break;
}
};
periodicOutput.WriteOutput += (progress, files, size, counting) =>
{
output.MessageEvent(string.Format(" {0} files need to be examined ({1}){2}", files, Library.Utility.Utility.FormatSizeString(size), counting ? " (still counting)" : ""));
};
using (var i = new Library.Main.Controller(backend, options, output))
{
setup(i);
result = i.Backup(dirs, filter);
}
}
if (output.FullResults)
{
Library.Utility.Utility.PrintSerializeObject(result, outwriter);
outwriter.WriteLine();
}
var parsedStats = result.BackendStatistics as Duplicati.Library.Interface.IParsedBackendStatistics;
output.MessageEvent(string.Format(" Duration of backup: {0:hh\\:mm\\:ss}", result.Duration));
if (parsedStats != null)
{
if (parsedStats.KnownFileCount > 0)
{
output.MessageEvent(string.Format(" Remote files: {0}", parsedStats.KnownFileCount));
output.MessageEvent(string.Format(" Remote size: {0}", Library.Utility.Utility.FormatSizeString(parsedStats.KnownFileSize)));
}
if (parsedStats.TotalQuotaSpace >= 0)
{
output.MessageEvent(string.Format(" Total remote quota: {0}", Library.Utility.Utility.FormatSizeString(parsedStats.TotalQuotaSpace)));
}
if (parsedStats.FreeQuotaSpace >= 0)
{
output.MessageEvent(string.Format(" Available remote quota: {0}", Library.Utility.Utility.FormatSizeString(parsedStats.FreeQuotaSpace)));
}
}
output.MessageEvent(string.Format(" Files added: {0}", result.AddedFiles));
output.MessageEvent(string.Format(" Files deleted: {0}", result.DeletedFiles));
output.MessageEvent(string.Format(" Files changed: {0}", result.ModifiedFiles));
output.MessageEvent(string.Format(" Data uploaded: {0}", Library.Utility.Utility.FormatSizeString(result.BackendStatistics.BytesUploaded)));
output.MessageEvent(string.Format(" Data downloaded: {0}", Library.Utility.Utility.FormatSizeString(result.BackendStatistics.BytesDownloaded)));
if (result.ExaminedFiles == 0 && (filter != null || !filter.Empty))
output.MessageEvent("No files were processed. If this was not intentional you may want to use the \"test-filters\" command");
output.MessageEvent("Backup completed successfully!");
//Interrupted = 50
if (result.PartialBackup)
return 50;
//Completed with errors = 3
if (result.ParsedResult == Library.Interface.ParsedResultType.Error)
return 3;
//Completed with warnings = 2
if (result.ParsedResult == Library.Interface.ParsedResultType.Warning)
return 2;
//Success, but no upload = 1
if (result.BackendStatistics.BytesUploaded == 0)
return 1;
return 0;
}
if (output.VerboseOutput)
{
Library.Utility.Utility.PrintSerializeObject(result, outwriter);
outwriter.WriteLine();
}
var parsedStats = result.BackendStatistics as Duplicati.Library.Interface.IParsedBackendStatistics;
output.MessageEvent(string.Format(" Duration of backup: {0:hh\\:mm\\:ss}", result.Duration));
if (parsedStats != null)
{
if (parsedStats.KnownFileCount > 0)
{
output.MessageEvent(string.Format(" Remote files: {0}", parsedStats.KnownFileCount));
output.MessageEvent(string.Format(" Remote size: {0}", Library.Utility.Utility.FormatSizeString(parsedStats.KnownFileSize)));
}
if (parsedStats.TotalQuotaSpace >= 0)
{
output.MessageEvent(string.Format(" Total remote quota: {0}", Library.Utility.Utility.FormatSizeString(parsedStats.TotalQuotaSpace)));
}
if (parsedStats.FreeQuotaSpace >= 0)
{
output.MessageEvent(string.Format(" Available remote quota: {0}", Library.Utility.Utility.FormatSizeString(parsedStats.FreeQuotaSpace)));
}
}
output.MessageEvent(string.Format(" Files added: {0}", result.AddedFiles));
output.MessageEvent(string.Format(" Files deleted: {0}", result.DeletedFiles));
output.MessageEvent(string.Format(" Files changed: {0}", result.ModifiedFiles));
output.MessageEvent(string.Format(" Data uploaded: {0}", Library.Utility.Utility.FormatSizeString(result.BackendStatistics.BytesUploaded)));
output.MessageEvent(string.Format(" Data downloaded: {0}", Library.Utility.Utility.FormatSizeString(result.BackendStatistics.BytesDownloaded)));
if (result.ExaminedFiles == 0 && (filter != null || !filter.Empty))
output.MessageEvent("No files were processed. If this was not intentional you may want to use the \"test-filters\" command");
output.MessageEvent("Backup completed successfully!");
//Interrupted = 50
if (result.PartialBackup)
return 50;
//Completed with errors = 3
if (result.ParsedResult == Library.Interface.ParsedResultType.Error)
return 3;
//Completed with warnings = 2
if (result.ParsedResult == Library.Interface.ParsedResultType.Warning)
return 2;
//Success, but no upload = 1
if (result.BackendStatistics.BytesUploaded == 0)
return 1;
return 0;
}
public static int Compact(TextWriter outwriter, Action<Duplicati.Library.Main.Controller> setup, List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter)
@@ -648,7 +657,8 @@ namespace Duplicati.CommandLine
if (args.Count != 1)
return PrintWrongNumberOfArguments(outwriter, args, 1);
using (var i = new Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using(var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
i.Compact();
@@ -659,7 +669,7 @@ namespace Duplicati.CommandLine
public static int Test(TextWriter outwriter, Action<Duplicati.Library.Main.Controller> setup, List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter)
{
var verbose = Library.Utility.Utility.ParseBoolOption(options, "verbose") || Duplicati.Library.Utility.Utility.ParseBoolOption(options, "full-result");
var fullResults = Duplicati.Library.Utility.Utility.ParseBoolOption(options, "full-result");
if (args.Count != 1 && args.Count != 2)
return PrintWrongNumberOfArguments(outwriter, args, 1);
@@ -673,7 +683,8 @@ namespace Duplicati.CommandLine
}
Library.Interface.ITestResults result;
using (var i = new Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
result = i.Test(tests);
@@ -700,7 +711,7 @@ namespace Duplicati.CommandLine
var changecount = n.Value.Count();
if (changecount == 0)
{
if (verbose)
if (fullResults)
Console.WriteLine("{0}: No errors", n.Key);
}
else
@@ -711,7 +722,7 @@ namespace Duplicati.CommandLine
{
count++;
Console.WriteLine("\t{0}: {1}", c.Key, c.Value);
if (!verbose && count == 10 && changecount > 10)
if (!fullResults && count == 10 && changecount > 10)
{
Console.WriteLine("\t... and {0} more", changecount - count);
break;
@@ -764,7 +775,8 @@ namespace Duplicati.CommandLine
else if (args.Count == 1)
args.Add("report");
using (var i = new Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
i.CreateLogDatabase(args[1]);
@@ -782,7 +794,7 @@ namespace Duplicati.CommandLine
public static int ListChanges(TextWriter outwriter, Action<Duplicati.Library.Main.Controller> setup, List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter)
{
var fullresult = Duplicati.Library.Utility.Utility.ParseBoolOption(options, "verbose") || Duplicati.Library.Utility.Utility.ParseBoolOption(options, "full-result");
var fullresult = Duplicati.Library.Utility.Utility.ParseBoolOption(options, "full-result");
if (args.Count < 1)
return PrintWrongNumberOfArguments(outwriter, args, 1);
@@ -907,7 +919,8 @@ namespace Duplicati.CommandLine
outwriter.WriteLine("Size of backup {0}: {1}", result.CompareVersionIndex, Library.Utility.Utility.FormatSizeString(result.CurrentSize));
};
using (var i = new Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
if (args.Count == 2)
@@ -927,9 +940,8 @@ namespace Duplicati.CommandLine
return 200;
}
options["verbose"] = "true";
using(var i = new Library.Main.Controller("dummy://", options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using(var i = new Library.Main.Controller("dummy://", options, console))
{
setup(i);
var result = i.TestFilter(args.ToArray(), filter);
@@ -948,7 +960,8 @@ namespace Duplicati.CommandLine
return 200;
}
using (var i = new Library.Main.Controller("dummy://", options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller("dummy://", options, console))
{
setup(i);
foreach (var line in i.SystemInfo().Lines)
@@ -986,7 +999,8 @@ namespace Duplicati.CommandLine
}
using (var i = new Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
i.PurgeFiles(filter);
@@ -1000,11 +1014,11 @@ namespace Duplicati.CommandLine
if (args.Count != 1)
return PrintWrongNumberOfArguments(outwriter, args, 1);
var con = new ConsoleOutput(outwriter, options);
var previd = -1L;
var outputcount = 0L;
var verbose = Duplicati.Library.Utility.Utility.ParseBoolOption(options, "verbose") || Duplicati.Library.Utility.Utility.ParseBoolOption(options, "full-result");
var fullresult = Duplicati.Library.Utility.Utility.ParseBoolOption(options, "full-result");
using (var con = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(args[0], options, con))
{
setup(i);
@@ -1019,7 +1033,7 @@ namespace Duplicati.CommandLine
con.MessageEvent(string.Format("\t{0} ({1})", path, Library.Utility.Utility.FormatSizeString(size)));
outputcount++;
if (outputcount >= 5 && !verbose && count != outputcount)
if (outputcount >= 5 && !fullresult && count != outputcount)
{
con.MessageEvent(string.Format("\t ... and {0} more, (use --{1} to list all)", count - outputcount, "full-result"));
return false;
@@ -1038,7 +1052,8 @@ namespace Duplicati.CommandLine
if (args.Count != 1)
return PrintWrongNumberOfArguments(outwriter, args, 1);
using (var i = new Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
var res = i.PurgeBrokenFiles(filter);
@@ -1055,7 +1070,8 @@ namespace Duplicati.CommandLine
return 200;
}
using (var i = new Library.Main.Controller("dummy://", options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller("dummy://", options, console))
{
setup(i);
foreach (var l in i.SendMail().Lines)
@@ -1074,8 +1090,8 @@ namespace Duplicati.CommandLine
if (args.Count != 1)
return PrintWrongNumberOfArguments(outwriter, args, 1);
using (
var controller = new Library.Main.Controller(args[0], options, new ConsoleOutput(outwriter, options)))
using (var console = new ConsoleOutput(outwriter, options))
using (var controller = new Library.Main.Controller(args[0], options, console))
{
setup(controller);
controller.Vacuum();
+93
View File
@@ -0,0 +1,93 @@
// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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.IO;
using Duplicati.Library.Logging;
using Duplicati.Library.Utility;
namespace Duplicati.CommandLine
{
/// <summary>
/// Log output handler for the console
/// </summary>
public class ConsoleLogTarget : Library.Logging.ILogDestination
{
/// <summary>
/// The stdout stream
/// </summary>
private readonly TextWriter m_stdout;
/// <summary>
/// The stderr stream
/// </summary>
private readonly TextWriter m_stderr;
/// <summary>
/// The default log level
/// </summary>
private readonly LogMessageType m_level;
/// <summary>
/// The filter, if any
/// </summary>
private readonly IFilter m_filter;
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.CommandLine.ConsoleLogTarget"/> class.
/// </summary>
/// <param name="stdout">The stdout stream.</param>
/// <param name="stderr">The stderr stream.</param>
/// <param name="level">The minimum log level to consider.</param>
/// <param name="logfilter">The log filter, if any.</param>
public ConsoleLogTarget(TextWriter stdout, TextWriter stderr, LogMessageType level, string logfilter)
{
m_stdout = stdout;
m_stderr = stderr;
m_level = level;
m_filter = Library.Main.Options.ParseLogFilter(logfilter);
}
/// <summary>
/// Writes the message to stdout or stderr.
/// </summary>
/// <param name="entry">The entry to write.</param>
public void WriteMessage(LogEntry entry)
{
var found = m_filter.Matches(entry.Tag, out var result, out var match);
// If there is a filter match, use that
if (found)
{
if (!result)
return;
if (entry.Level == LogMessageType.Error)
m_stderr.WriteLine(entry.ToString());
else
m_stdout.WriteLine(entry.ToString());
}
else
{
// Otherwise, filter by log-level
if (entry.Level < m_level)
return;
}
if (entry.Level == LogMessageType.Error)
m_stderr.WriteLine(entry.FormattedMessage);
else
m_stdout.WriteLine(entry.FormattedMessage);
}
}
}
+29 -35
View File
@@ -22,21 +22,21 @@ using Duplicati.Library.Main;
namespace Duplicati.CommandLine
{
public class ConsoleOutput : Library.Main.IMessageSink
public class ConsoleOutput : Library.Main.IMessageSink, IDisposable
{
private object m_lock = new object();
private readonly object m_lock = new object();
public bool QuietConsole { get; private set; }
public bool VerboseOutput { get; private set; }
public bool VerboseErrors { get; private set; }
public TextWriter Output { get; private set; }
public bool FullResults { get; private set; }
public ConsoleOutput(TextWriter output, Dictionary<string, string> options)
{
this.Output = output;
this.QuietConsole = Library.Utility.Utility.ParseBoolOption(options, "quiet-console");
this.VerboseOutput = Library.Utility.Utility.ParseBoolOption(options, "verbose");
this.VerboseErrors = Library.Utility.Utility.ParseBoolOption(options, "debug-output");
this.FullResults = Library.Utility.Utility.ParseBoolOption(options, "full-results");
}
#region IMessageSink implementation
@@ -92,43 +92,37 @@ namespace Duplicati.CommandLine
}
}
}
public void VerboseEvent(string message, object[] args)
public void WriteMessage(Library.Logging.LogEntry entry)
{
if (VerboseOutput)
lock(m_lock)
Output.WriteLine(message, args);
if (QuietConsole)
return;
lock (m_lock)
{
if (entry.Exception != null)
Output.WriteLine("{0} => {1}", entry.FormattedMessage, VerboseErrors ? entry.Exception.ToString() : entry.Exception.Message);
else if (entry.Level == Library.Logging.LogMessageType.DryRun)
Output.WriteLine("[Dryrun]: {0}", entry.FormattedMessage);
else
Output.WriteLine(entry.FormattedMessage);
}
}
public void MessageEvent(string message)
{
if (!QuietConsole)
lock(m_lock)
Output.WriteLine(message);
if (QuietConsole)
return;
lock (m_lock)
Output.WriteLine(message);
}
public void RetryEvent(string message, Exception ex)
public void Dispose()
{
if (!QuietConsole)
lock(m_lock)
Output.WriteLine(ex == null ? message : string.Format("{0} => {1}", message, VerboseErrors ? ex.ToString() : ex.Message));
}
public void WarningEvent(string message, Exception ex)
{
if (!QuietConsole)
lock(m_lock)
Output.WriteLine(ex == null ? message : string.Format("{0} => {1}", message, VerboseErrors ? ex.ToString() : ex.Message));
}
public void ErrorEvent(string message, Exception ex)
{
if (!QuietConsole)
lock(m_lock)
Output.WriteLine(ex == null ? message : string.Format("{0} => {1}", message, VerboseErrors ? ex.ToString() : ex.Message));
}
public void DryrunEvent(string message)
{
if (!QuietConsole)
lock(m_lock)
Output.WriteLine(string.Format("[Dryrun]: {0}", message));
}
#endregion
}
@@ -54,6 +54,7 @@
<Compile Include="Commands.cs" />
<Compile Include="ConsoleOutput.cs" />
<Compile Include="Strings.cs" />
<Compile Include="ConsoleLogTarget.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Library\Backend\AzureBlob\Duplicati.Library.Backend.AzureBlob.csproj">
+12 -4
View File
@@ -29,6 +29,8 @@ namespace Duplicati.CommandLine
{
public class Program
{
private static string LOGTAG = Library.Logging.Log.LogTagFromType<Program>();
public static bool FROM_COMMANDLINE = false;
/// <summary>
@@ -125,7 +127,6 @@ namespace Duplicati.CommandLine
var filter = tmpparsed.Item2;
verboseErrors = Library.Utility.Utility.ParseBoolOption(options, "debug-output");
verbose = Library.Utility.Utility.ParseBoolOption(options, "verbose");
if (cargs.Count == 1 && string.Equals(cargs[0], "changelog", StringComparison.OrdinalIgnoreCase))
{
@@ -284,6 +285,13 @@ namespace Duplicati.CommandLine
}
}
/*if (ex is Duplicati.Library.Interface.UserInformationException)
{
errwriter.WriteLine(Strings.Program.YouMayGetMoreHelpHere(
Library.Logging.Log.CreateHelpLink(null, null, ((Library.Interface.UserInformationException)(ex)).HelpID, true)
));
}*/
//Error = 100
return 100;
}
@@ -351,7 +359,7 @@ namespace Duplicati.CommandLine
// If the user specifies parameters-file, all filters must be in the file.
// Allowing to specify some filters on the command line could result in wrong filter ordering
if (!filter.Empty && !newfilter.Empty)
throw new Duplicati.Library.Interface.UserInformationException(Strings.Program.FiltersCannotBeUsedWithFileError2);
throw new Duplicati.Library.Interface.UserInformationException(Strings.Program.FiltersCannotBeUsedWithFileError2, "FiltersCannotBeUsedOnCommandLineAndInParameterFile");
if (!newfilter.Empty)
filter = newfilter;
@@ -378,8 +386,8 @@ namespace Duplicati.CommandLine
if (cargs.Count >= 1 && cargs[0].Equals("backup", StringComparison.OrdinalIgnoreCase))
cargs.AddRange(newsource);
else if (newsource.Count > 0 && Library.Utility.Utility.ParseBoolOption(options, "verbose"))
outwriter.WriteLine(Strings.Program.SkippingSourceArgumentsOnNonBackupOperation);
else if (newsource.Count > 0)
Library.Logging.Log.WriteVerboseMessage(LOGTAG, "NotUsingBackupSources", Strings.Program.SkippingSourceArgumentsOnNonBackupOperation);
return true;
}
+1 -1
View File
@@ -91,7 +91,7 @@ namespace Duplicati.CommandLine.RecoveryTool
{
var items = ParseListFiles(folder);
if (index < 0 || index >= items.Length)
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Valid range for version is 0 to {0}", items.Length - 1));
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Valid range for version is 0 to {0}", items.Length - 1), "RecoveryToolInvalidVersion");
return items[index].Value;
}
@@ -115,7 +115,7 @@ namespace Duplicati.CommandLine.RecoveryTool
// If the user specifies parameters-file, all filters must be in the file.
// Allowing to specify some filters on the command line could result in wrong filter ordering
if (!filter.Empty && !newfilter.Empty)
throw new Duplicati.Library.Interface.UserInformationException("Filters cannot be specified on the commandline if filters are also present in the parameter file");
throw new Duplicati.Library.Interface.UserInformationException("Filters cannot be specified on the commandline if filters are also present in the parameter file", "RecoveryToolFiltersOnCommandLineAndInParameterFile");
if (!newfilter.Empty)
filter = newfilter;
@@ -94,9 +94,9 @@ namespace Duplicati.CommandLine.RecoveryTool
var filehasher = string.IsNullOrWhiteSpace(filehash_str) ? null : Library.Utility.HashAlgorithmHelper.Create(filehash_str);
if (blockhasher == null)
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Block hash algorithm not valid: {0}", blockhash_str));
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Block hash algorithm not valid: {0}", blockhash_str), "BlockHashAlgorithmNotSupported");
if (filehasher == null)
throw new Duplicati.Library.Interface.UserInformationException(string.Format("File hash algorithm not valid: {0}", filehash_str));
throw new Duplicati.Library.Interface.UserInformationException(string.Format("File hash algorithm not valid: {0}", filehash_str), "FileHashAlgorithmNotSupported");
var hashesprblock = blocksize / (blockhasher.HashSize / 8);
+1
View File
@@ -31,5 +31,6 @@ namespace Duplicati.CommandLine.Strings {
public static string QuietConsoleOptionLong { get { return LC.L(@"If this option is set, progress reports and other messages that would normally go to the console will be redirected to the log."); } }
public static string QuietConsoleOptionShort { get { return LC.L(@"Disable console output"); } }
public static string SkippingSourceArgumentsOnNonBackupOperation { get { return @"The --source argument was specified in the parameter file, but the current operation is not a backup operation, so the argument is ignored"; } }
public static string YouMayGetMoreHelpHere(string url) { return LC.L(@"This link may provide additional information: {0}", url); }
}
}
@@ -73,10 +73,10 @@
<HintPath>..\..\..\thirdparty\appindicator-sharp\appindicator-sharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="gdk-sharp">
<Reference Include="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
<HintPath>..\..\..\thirdparty\appindicator-sharp\gdk-sharp.dll</HintPath>
</Reference>
<Reference Include="gtk-sharp">
<Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
<HintPath>..\..\..\thirdparty\appindicator-sharp\gtk-sharp.dll</HintPath>
</Reference>
<Reference Include="glib-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
@@ -49,7 +49,7 @@ namespace Duplicati.GUI.TrayIcon
if (m_runnerException != null)
throw m_runnerException;
else
throw new Duplicati.Library.Interface.UserInformationException("Hosted server startup timed out");
throw new Duplicati.Library.Interface.UserInformationException("Hosted server startup timed out", "HostedStartupError");
}
if (m_runnerException != null)
@@ -62,15 +62,15 @@ namespace Duplicati.GUI.TrayIcon
private readonly Dictionary<string, string> m_updateRequest;
private readonly Dictionary<string, string> m_options;
private readonly bool m_dbPasswordSourceDatabase;
private string m_TrayIconHeaderValue => m_dbPasswordSourceDatabase ? "database" : "user";
private readonly Program.PasswordSource m_passwordSource;
private string m_TrayIconHeaderValue => (m_passwordSource == Program.PasswordSource.Database) ? "database" : "user";
public IServerStatus Status { get { return m_status; } }
private object m_lock = new object();
private readonly object m_lock = new object();
private Queue<BackgroundRequest> m_workQueue = new Queue<BackgroundRequest>();
public HttpServerConnection(Uri server, string password, bool saltedpassword, bool dbPasswordSourceDatabase, Dictionary<string, string> options)
public HttpServerConnection(Uri server, string password, bool saltedpassword, Program.PasswordSource passwordSource, Dictionary<string, string> options)
{
m_baseUri = server.ToString();
if (!m_baseUri.EndsWith("/", StringComparison.Ordinal))
@@ -83,7 +83,7 @@ namespace Duplicati.GUI.TrayIcon
m_password = password;
m_saltedpassword = saltedpassword;
m_options = options;
m_dbPasswordSourceDatabase = dbPasswordSourceDatabase;
m_passwordSource = passwordSource;
m_updateRequest = new Dictionary<string, string>();
m_updateRequest["longpoll"] = "false";
@@ -364,15 +364,25 @@ namespace Duplicati.GUI.TrayIcon
wex.Status == System.Net.WebExceptionStatus.ProtocolError &&
httpex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (m_dbPasswordSourceDatabase)
//Can survive if server password is changed via web ui
switch (m_passwordSource)
{
Program.databaseConnection.ApplicationSettings.ReloadSettings();
//Can survive if server password is changed via web ui
if (Program.databaseConnection.ApplicationSettings.WebserverPasswordTrayIcon != m_password)
m_password = Program.databaseConnection.ApplicationSettings.WebserverPasswordTrayIcon;
else
hasTriedPassword = true;
case Program.PasswordSource.Database:
Program.databaseConnection.ApplicationSettings.ReloadSettings();
if (Program.databaseConnection.ApplicationSettings.WebserverPasswordTrayIcon != m_password)
m_password = Program.databaseConnection.ApplicationSettings.WebserverPasswordTrayIcon;
else
hasTriedPassword = true;
break;
case Program.PasswordSource.HostedServer:
if (Server.Program.DataConnection.ApplicationSettings.WebserverPassword != m_password)
m_password = Server.Program.DataConnection.ApplicationSettings.WebserverPassword;
else
hasTriedPassword = true;
break;
default:
throw new ArgumentOutOfRangeException();
}
m_authtoken = GetAuthToken();
+30 -14
View File
@@ -8,6 +8,12 @@ namespace Duplicati.GUI.TrayIcon
{
public static class Program
{
public enum PasswordSource
{
Database,
HostedServer
}
public static HttpServerConnection Connection;
private const string TOOLKIT_OPTION = "toolkit";
@@ -150,28 +156,30 @@ namespace Duplicati.GUI.TrayIcon
openui = Duplicati.Server.Program.IsFirstRun || Duplicati.Server.Program.ServerPortChanged;
password = Duplicati.Server.Program.DataConnection.ApplicationSettings.WebserverPassword;
saltedpassword = true;
serverURL = (new UriBuilder(serverURL) { Port = Duplicati.Server.Program.ServerPort }).Uri;
// Tell the hosted server it was started by the TrayIcon
Duplicati.Server.Program.Origin = "Tray icon";
var cert = Duplicati.Server.Program.DataConnection.ApplicationSettings.ServerSSLCertificate;
var scheme = "http";
if (cert != null && cert.HasPrivateKey)
scheme = "https";
serverURL = (new UriBuilder(serverURL)
{
Port = Duplicati.Server.Program.ServerPort,
Scheme = scheme
}).Uri;
}
if (Library.Utility.Utility.ParseBoolOption(options, NOHOSTEDSERVER_OPTION) && Library.Utility.Utility.ParseBoolOption(options, READCONFIGFROMDB_OPTION))
databaseConnection = Server.Program.GetDatabaseConnection(options);
string pwd;
if (options.TryGetValue("webserver-password", out pwd))
{
password = pwd;
saltedpassword = false;
}
if (databaseConnection != null)
{
password = databaseConnection.ApplicationSettings.WebserverPasswordTrayIcon;
saltedpassword = false;
}
if (databaseConnection != null)
{
var cert = databaseConnection.ApplicationSettings.ServerSSLCertificate;
var scheme = "http";
@@ -185,6 +193,14 @@ namespace Duplicati.GUI.TrayIcon
}).Uri;
}
string pwd;
if (options.TryGetValue("webserver-password", out pwd))
{
password = pwd;
saltedpassword = false;
}
string url;
if (options.TryGetValue(HOSTURL_OPTION, out url))
@@ -200,7 +216,7 @@ namespace Duplicati.GUI.TrayIcon
{
System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
using (Connection = new HttpServerConnection(serverURL, password, saltedpassword, databaseConnection != null, options))
using (Connection = new HttpServerConnection(serverURL, password, saltedpassword, databaseConnection != null ? PasswordSource.Database : PasswordSource.HostedServer, options))
{
using (var tk = RunTrayIcon(toolkit))
{
@@ -280,7 +296,7 @@ namespace Duplicati.GUI.TrayIcon
else if (toolkit == TOOLKIT_RUMPS)
return GetRumpsRunnerInstance();
else
throw new UserInformationException(string.Format("The selected toolkit '{0}' is invalid", toolkit));
throw new UserInformationException(string.Format("The selected toolkit '{0}' is invalid", toolkit), "TrayIconInvalidToolKit");
}
//We keep these in functions to avoid attempting to load the instance,
@@ -361,11 +361,11 @@ namespace Duplicati.Library.Backend.AlternativeFTP
return;
}
throw new UserInformationException(Strings.ListVerifySizeFailure(remotename, fileEntry.Size, streamLen));
throw new UserInformationException(Strings.ListVerifySizeFailure(remotename, fileEntry.Size, streamLen), "AftpListVerifySizeFailure");
}
}
throw new UserInformationException(Strings.ListVerifyFailure(remotename, fileEntries.Select(n => n.Name)));
throw new UserInformationException(Strings.ListVerifyFailure(remotename, fileEntries.Select(n => n.Name)), "AftpListVerifySizeFailure");
}
}
catch (FtpCommandException ex)
@@ -450,9 +450,9 @@ namespace Duplicati.Library.Backend.AlternativeFTP
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new Uri(_url).Host }; }
}
private static System.IO.Stream StringToStream(string str)
@@ -224,7 +224,7 @@ namespace Duplicati.Library.Backend.AmazonCloudDrive
m_waitUntil = DateTime.Now + m_delayTimeSpan;
}
else if (self != null && self.Count > 1)
throw new UserInformationException(Strings.AmzCD.MultipleEntries(p, "/" + string.Join("/", curpath)));
throw new UserInformationException(Strings.AmzCD.MultipleEntries(p, "/" + string.Join("/", curpath)), "AmzCDMultipleEntries");
else
parent = self.Data.First();
}
@@ -458,9 +458,27 @@ namespace Duplicati.Library.Backend.AmazonCloudDrive
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get
{
var contentUrl = string.Empty;
var metdataUrl = string.Empty;
if (m_endPointInfo != null)
{
if (!string.IsNullOrWhiteSpace(m_endPointInfo.ContentUrl))
contentUrl = new Uri(m_endPointInfo.ContentUrl).Host;
if (!string.IsNullOrWhiteSpace(m_endPointInfo.MetadataUrl))
metdataUrl = new Uri(m_endPointInfo.MetadataUrl).Host;
}
return new string[] {
new Uri(CLOUDRIVE_MASTER_URL).Host,
contentUrl,
metdataUrl
};
}
}
#endregion
@@ -57,11 +57,11 @@ namespace Duplicati.Library.Backend.AzureBlob
if (string.IsNullOrWhiteSpace(storageAccountName))
{
throw new UserInformationException(Strings.AzureBlobBackend.NoStorageAccountName);
throw new UserInformationException(Strings.AzureBlobBackend.NoStorageAccountName, "AzureNoAccountName");
}
if (string.IsNullOrWhiteSpace(accessKey))
{
throw new UserInformationException(Strings.AzureBlobBackend.NoAccessKey);
throw new UserInformationException(Strings.AzureBlobBackend.NoAccessKey, "AzureNoAccessKey");
}
_azureBlob = new AzureBlobWrapper(storageAccountName, accessKey, containerName);
@@ -159,9 +159,9 @@ namespace Duplicati.Library.Backend.AzureBlob
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return _azureBlob.DnsNames; }
}
public void Test()
@@ -36,6 +36,29 @@ namespace Duplicati.Library.Backend.AzureBlob
private readonly string _containerName;
private readonly CloudBlobContainer _container;
public string[] DnsNames
{
get
{
var lst = new List<string>();
if (_container != null)
{
if (_container.Uri != null)
lst.Add(_container.Uri.Host);
if (_container.StorageUri != null)
{
if (_container.StorageUri.PrimaryUri != null)
lst.Add(_container.StorageUri.PrimaryUri.Host);
if (_container.StorageUri.SecondaryUri != null)
lst.Add(_container.StorageUri.SecondaryUri.Host);
}
}
return lst.ToArray();
}
}
public AzureBlobWrapper(string accountName, string accessKey, string containerName)
{
_containerName = containerName;
+5 -5
View File
@@ -88,9 +88,9 @@ namespace Duplicati.Library.Backend.Backblaze
accountKey = uri.Password;
if (string.IsNullOrEmpty(accountId))
throw new UserInformationException(Strings.B2.NoB2UserIDError);
throw new UserInformationException(Strings.B2.NoB2UserIDError, "B2MissingUserID");
if (string.IsNullOrEmpty(accountKey))
throw new UserInformationException(Strings.B2.NoB2KeyError);
throw new UserInformationException(Strings.B2.NoB2KeyError, "B2MissingKey");
m_helper = new B2AuthHelper(accountId, accountKey);
@@ -100,7 +100,7 @@ namespace Duplicati.Library.Backend.Backblaze
int.TryParse(options[B2_PAGESIZE_OPTION], out m_pagesize);
if (m_pagesize <= 0)
throw new UserInformationException(Strings.B2.InvalidPageSizeError(B2_PAGESIZE_OPTION, options[B2_PAGESIZE_OPTION]));
throw new UserInformationException(Strings.B2.InvalidPageSizeError(B2_PAGESIZE_OPTION, options[B2_PAGESIZE_OPTION]), "B2InvalidPageSize");
}
}
@@ -422,9 +422,9 @@ namespace Duplicati.Library.Backend.Backblaze
get { return Strings.B2.Description; }
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new System.Uri(B2AuthHelper.AUTH_URL).Host, m_helper?.APIDnsName, m_helper?.DownloadDnsName} ; }
}
public void Dispose()
@@ -27,7 +27,7 @@ namespace Duplicati.Library.Backend.Backblaze
private readonly string m_credentials;
private AuthResponse m_config;
private DateTime m_configExpires;
private const string AUTH_URL = "https://api.backblazeb2.com/b2api/v1/b2_authorize_account";
internal const string AUTH_URL = "https://api.backblazeb2.com/b2api/v1/b2_authorize_account";
public B2AuthHelper(string userid, string password)
: base()
@@ -54,6 +54,26 @@ namespace Duplicati.Library.Backend.Backblaze
return url;
}
public string APIDnsName
{
get
{
if (m_config == null || string.IsNullOrWhiteSpace(m_config.APIUrl))
return null;
return new System.Uri(m_config.APIUrl).Host;
}
}
public string DownloadDnsName
{
get
{
if (m_config == null || string.IsNullOrWhiteSpace(m_config.DownloadUrl))
return null;
return new System.Uri(m_config.DownloadUrl).Host;
}
}
private AuthResponse Config
{
get
+2 -2
View File
@@ -337,9 +337,9 @@ namespace Duplicati.Library.Backend.Box
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new Uri(BOX_API_URL).Host, new Uri(BOX_UPLOAD_URL).Host }; }
}
#endregion
@@ -73,9 +73,9 @@ namespace Duplicati.Library.Backend
m_password = uri.Password;
if (string.IsNullOrEmpty(m_username))
throw new UserInformationException(Strings.CloudFiles.NoUserIDError);
throw new UserInformationException(Strings.CloudFiles.NoUserIDError, "CloudFilesNoUserID");
if (string.IsNullOrEmpty(m_password))
throw new UserInformationException(Strings.CloudFiles.NoAPIKeyError);
throw new UserInformationException(Strings.CloudFiles.NoAPIKeyError, "CloudFilesNoApiKey");
//Fallback to the previous format
if (url.Contains(DUMMY_HOSTNAME))
@@ -290,9 +290,9 @@ namespace Duplicati.Library.Backend
get { return true; }
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new Uri(m_authUrl).Host, string.IsNullOrWhiteSpace(m_storageUrl) ? null : new Uri(m_storageUrl).Host }; }
}
public void Get(string remotename, System.IO.Stream stream)
+2 -2
View File
@@ -136,9 +136,9 @@ namespace Duplicati.Library.Backend
public string Description { get { return Strings.Dropbox.Description; } }
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new Uri(DropboxHelper.API_URL).Host, new Uri(DropboxHelper.CONTENT_API_URL).Host }; }
}
public void Test()
@@ -11,8 +11,8 @@ namespace Duplicati.Library.Backend
{
public class DropboxHelper : OAuthHelper
{
private const string API_URL = "https://api.dropboxapi.com/2";
private const string CONTENT_API_URL = "https://content.dropboxapi.com/2";
internal const string API_URL = "https://api.dropboxapi.com/2";
internal const string CONTENT_API_URL = "https://content.dropboxapi.com/2";
private const int DROPBOX_MAX_CHUNK_UPLOAD = 10 * 1024 * 1024; // 10 MB max upload
private const string API_ARG_HEADER = "DROPBOX-API-arg";
+2 -2
View File
@@ -355,9 +355,9 @@ namespace Duplicati.Library.Backend
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new Uri(m_url).Host }; }
}
public void Test()
@@ -113,7 +113,7 @@ namespace Duplicati.Library.Backend
}
if (m_path == null)
throw new UserInformationException(Strings.FileBackend.NoDestinationWithMarkerFileError(markerfile, paths.ToArray()));
throw new UserInformationException(Strings.FileBackend.NoDestinationWithMarkerFileError(markerfile, paths.ToArray()), "NoDestinationWithMarker");
}
m_moveFile = Utility.Utility.ParseBoolOption(options, OPTION_MOVE_FILE);
@@ -351,7 +351,7 @@ namespace Duplicati.Library.Backend
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
}
@@ -93,7 +93,7 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
options.TryGetValue(STORAGECLASS_OPTION, out m_storage_class);
if (string.IsNullOrEmpty(authid))
throw new UserInformationException(Strings.GoogleCloudStorage.MissingAuthID(AUTHID_OPTION));
throw new UserInformationException(Strings.GoogleCloudStorage.MissingAuthID(AUTHID_OPTION), "GoogleCloudStorageMissingAuthID");
m_oauth = new OAuthHelper(authid, this.ProtocolKey);
m_oauth.AutoAuthHeader = true;
@@ -203,7 +203,7 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
public void CreateFolder()
{
if (string.IsNullOrEmpty(m_project))
throw new UserInformationException(Strings.GoogleCloudStorage.ProjectIDMissingError(PROJECT_OPTION));
throw new UserInformationException(Strings.GoogleCloudStorage.ProjectIDMissingError(PROJECT_OPTION), "GoogleCloudStorageMissingProjectID");
var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new CreateBucketRequest() {
name = m_bucket,
@@ -261,9 +261,9 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
get { return Strings.GoogleCloudStorage.Description; }
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new System.Uri(UPLOAD_API_URL).Host, new System.Uri(API_URL).Host }; }
}
#endregion
@@ -81,7 +81,7 @@ namespace Duplicati.Library.Backend.GoogleDrive
curparent = CreateFolder(p, curparent).id;
}
else if (res.Length > 1)
throw new UserInformationException(Strings.GoogleDrive.MultipleEntries(p, curdisplay));
throw new UserInformationException(Strings.GoogleDrive.MultipleEntries(p, curdisplay), "GoogleDriveMultipleEntries");
else
curparent = res[0].id;
@@ -347,9 +347,9 @@ namespace Duplicati.Library.Backend.GoogleDrive
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new System.Uri(DRIVE_API_URL).Host, new System.Uri(DRIVE_API_UPLOAD_URL).Host }; }
}
#endregion
@@ -361,7 +361,7 @@ namespace Duplicati.Library.Backend.GoogleDrive
{
var files = GetFileEntries(oldname, true);
if (files.Length > 1)
throw new UserInformationException(string.Format(Strings.GoogleDrive.MultipleEntries(oldname, m_path)));
throw new UserInformationException(string.Format(Strings.GoogleDrive.MultipleEntries(oldname, m_path)), "GoogleDriveMultipleEntries");
var newfile = JsonConvert.DeserializeObject<GoogleDriveFolderItem>(JsonConvert.SerializeObject(files[0]));
newfile.title = newname;
@@ -28,7 +28,7 @@ namespace Duplicati.Library.Backend.HubiC
private const string HUBIC_API_URL = "https://api.hubic.com/1.0/";
private const string HUBIC_API_CREDENTIAL_URL = HUBIC_API_URL + "account/credentials";
private OpenStackStorage m_openstack;
private OpenStackHelper m_openstack;
private class HubiCAuthResponse
{
@@ -77,6 +77,17 @@ namespace Duplicati.Library.Backend.HubiC
{
get { return AuthToken.endpoint; }
}
public string EndPointDnsName
{
get
{
if (m_token == null || string.IsNullOrWhiteSpace(m_token.endpoint))
return null;
return new Uri(m_token.endpoint).Host;
}
}
}
public HubiCBackend()
@@ -171,9 +182,9 @@ namespace Duplicati.Library.Backend.HubiC
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new Uri(HUBIC_API_URL).Host, m_openstack.EndPointDnsName }; }
}
#endregion
@@ -89,7 +89,7 @@ namespace Duplicati.Library.Backend
{
// Check that it is not set to one of the special built-in mount points that we definitely cannot make use of.
if (Array.FindIndex(JFS_BUILTIN_ILLEGAL_MOUNT_POINTS, x => x.Equals(m_mountPoint, StringComparison.OrdinalIgnoreCase)) != -1)
throw new UserInformationException(Strings.Jottacloud.IllegalMountPoint);
throw new UserInformationException(Strings.Jottacloud.IllegalMountPoint, "JottaIllegalMountPoint");
// Check if it is one of the legal built-in mount points.
// What to do if it is not is open for discussion: The JFS API supports creation of custom mount points not only
// for custom (backup) devices, but also for the built-in device. But this will not be visible via the official
@@ -99,7 +99,7 @@ namespace Duplicati.Library.Backend
if (i != -1)
m_mountPoint = JFS_BUILTIN_MOUNT_POINTS[i]; // Ensure correct casing (doesn't seem to matter, but in theory it could).
else
throw new UserInformationException(Strings.Jottacloud.IllegalMountPoint); // User defined mount points on built-in device currently not allowed.
throw new UserInformationException(Strings.Jottacloud.IllegalMountPoint, "JottaIllegalMountPoint"); // User defined mount points on built-in device currently not allowed.
}
}
else
@@ -114,7 +114,7 @@ namespace Duplicati.Library.Backend
var u = new Utility.Uri(url);
m_path = u.HostAndPath; // Host and path of "jottacloud://folder/subfolder" is "folder/subfolder", so the actual folder path within the mount point.
if (string.IsNullOrEmpty(m_path)) // Require a folder. Actually it is possible to store files directly on the root level of the mount point, but that does not seem to be a good option.
throw new UserInformationException(Strings.Jottacloud.NoPathError);
throw new UserInformationException(Strings.Jottacloud.NoPathError, "JottaNoPath");
if (!m_path.EndsWith("/", StringComparison.Ordinal))
m_path += "/";
if (!string.IsNullOrEmpty(u.Username))
@@ -137,9 +137,9 @@ namespace Duplicati.Library.Backend
}
}
if (m_userInfo == null || string.IsNullOrEmpty(m_userInfo.UserName))
throw new UserInformationException(Strings.Jottacloud.NoUsernameError);
throw new UserInformationException(Strings.Jottacloud.NoUsernameError, "JottaNoUsername");
if (m_userInfo == null || string.IsNullOrEmpty(m_userInfo.Password))
throw new UserInformationException(Strings.Jottacloud.NoPasswordError);
throw new UserInformationException(Strings.Jottacloud.NoPasswordError, "JottaNoPassword");
if (m_userInfo != null) // Bugfix, see http://connect.microsoft.com/VisualStudio/feedback/details/695227/networkcredential-default-constructor-leaves-domain-null-leading-to-null-object-reference-exceptions-in-framework-code
m_userInfo.Domain = "";
m_url_device = JFS_ROOT + "/" + m_userInfo.UserName + "/" + m_device;
@@ -318,9 +318,9 @@ namespace Duplicati.Library.Backend
get { return true; }
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new Uri(JFS_ROOT).Host, new Uri(JFS_ROOT_UPLOAD).Host }; }
}
public void Get(string remotename, System.IO.Stream stream)
@@ -66,9 +66,9 @@ namespace Duplicati.Library.Backend.Mega
m_password = uri.Password;
if (string.IsNullOrEmpty(m_username))
throw new UserInformationException(Strings.MegaBackend.NoUsernameError);
throw new UserInformationException(Strings.MegaBackend.NoUsernameError, "MegaNoUsername");
if (string.IsNullOrEmpty(m_password))
throw new UserInformationException(Strings.MegaBackend.NoPasswordError);
throw new UserInformationException(Strings.MegaBackend.NoPasswordError, "MegaNoPassword");
m_prefix = uri.HostAndPath ?? "";
}
@@ -260,7 +260,7 @@ namespace Duplicati.Library.Backend.Mega
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
}
@@ -92,7 +92,7 @@ namespace Duplicati.Library
OAuthLoginUrl = OAUTH_LOGIN_URL(servicename);
if (string.IsNullOrEmpty(authid))
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.MissingAuthID(OAuthLoginUrl));
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.MissingAuthID(OAuthLoginUrl), "MissingAuthID");
}
public T GetTokenResponse<T>()
@@ -150,9 +150,9 @@ namespace Duplicati.Library
if (resp.StatusCode == HttpStatusCode.ServiceUnavailable)
{
if (msg == resp.StatusDescription)
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.OverQuotaError);
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.OverQuotaError, "OAuthOverQuotaError");
else
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.AuthorizationFailure(msg, OAuthLoginUrl), ex);
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.AuthorizationFailure(msg, OAuthLoginUrl), "OAuthLoginError", ex);
}
//Fail faster on client errors
@@ -161,7 +161,7 @@ namespace Duplicati.Library
}
if (retries >= (clienterror ? 1 : 5))
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.AuthorizationFailure(msg, OAuthLoginUrl), ex);
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.AuthorizationFailure(msg, OAuthLoginUrl), "OAuthLoginError", ex);
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, retries)));
retries++;
@@ -175,15 +175,15 @@ namespace Duplicati.Library
public void ThrowOverQuotaError()
{
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.OverQuotaError);
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.OverQuotaError, "OAuthOverQuotaError");
}
public void ThrowAuthException(string msg, Exception ex)
{
if (ex == null)
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.AuthorizationFailure(msg, OAuthLoginUrl));
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.AuthorizationFailure(msg, OAuthLoginUrl), "OAuthLoginError");
else
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.AuthorizationFailure(msg, OAuthLoginUrl), ex);
throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.AuthorizationFailure(msg, OAuthLoginUrl), "OAuthLoginError", ex);
}
@@ -500,9 +500,9 @@ namespace Duplicati.Library.Backend
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new Uri(WLID_SERVER).Host, new Uri(ONEDRIVE_SERVICE_URL).Host, string.IsNullOrWhiteSpace(m_userid) ? null : string.Format("cid-{0}.users.storage.live.com", m_userid) }; }
}
#endregion
@@ -203,16 +203,16 @@ namespace Duplicati.Library.Backend.OpenStack
options.TryGetValue(REGION_OPTION, out m_region);
if (string.IsNullOrWhiteSpace(m_username))
throw new UserInformationException(Strings.OpenStack.MissingOptionError(USERNAME_OPTION));
throw new UserInformationException(Strings.OpenStack.MissingOptionError(USERNAME_OPTION), "OpenStackMissingUsername");
if (string.IsNullOrWhiteSpace(m_authUri))
throw new UserInformationException(Strings.OpenStack.MissingOptionError(AUTHURI_OPTION));
throw new UserInformationException(Strings.OpenStack.MissingOptionError(AUTHURI_OPTION), "OpenStackMissingAuthUri");
if (string.IsNullOrWhiteSpace(m_apikey))
{
if (string.IsNullOrWhiteSpace(m_password))
throw new UserInformationException(Strings.OpenStack.MissingOptionError(PASSWORD_OPTION));
throw new UserInformationException(Strings.OpenStack.MissingOptionError(PASSWORD_OPTION), "OpenStackMissingPassword");
if (string.IsNullOrWhiteSpace(m_tenantName))
throw new UserInformationException(Strings.OpenStack.MissingOptionError(TENANTNAME_OPTION));
throw new UserInformationException(Strings.OpenStack.MissingOptionError(TENANTNAME_OPTION), "OpenStackMissingTenantName");
}
m_helper = new WebHelper(this);
@@ -423,9 +423,15 @@ namespace Duplicati.Library.Backend.OpenStack
}
}
public string DNSName
public virtual string[] DNSName
{
get { return null; }
get
{
return new string[] {
new System.Uri(m_authUri).Host,
string.IsNullOrWhiteSpace(m_simplestorageendpoint) ? null : new System.Uri(m_simplestorageendpoint).Host
};
}
}
#endregion
+2 -2
View File
@@ -284,9 +284,9 @@ namespace Duplicati.Library.Backend
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { remote_repo }; }
}
public void Test()
+6 -6
View File
@@ -169,9 +169,9 @@ namespace Duplicati.Library.Backend
awsKey = uri.Password;
if (string.IsNullOrEmpty(awsID))
throw new UserInformationException(Strings.S3Backend.NoAMZUserIDError);
throw new UserInformationException(Strings.S3Backend.NoAMZUserIDError, "S3NoAmzUserID");
if (string.IsNullOrEmpty(awsKey))
throw new UserInformationException(Strings.S3Backend.NoAMZKeyError);
throw new UserInformationException(Strings.S3Backend.NoAMZKeyError, "S3NoAmzKey");
bool euBuckets = Utility.Utility.ParseBoolOption(options, EU_BUCKETS_OPTION);
bool useRRS = Utility.Utility.ParseBoolOption(options, RRS_OPTION);
@@ -181,7 +181,7 @@ namespace Duplicati.Library.Backend
options.TryGetValue(LOCATION_OPTION, out locationConstraint);
if (!string.IsNullOrEmpty(locationConstraint) && euBuckets)
throw new UserInformationException(Strings.S3Backend.OptionsAreMutuallyExclusiveError(LOCATION_OPTION, EU_BUCKETS_OPTION));
throw new UserInformationException(Strings.S3Backend.OptionsAreMutuallyExclusiveError(LOCATION_OPTION, EU_BUCKETS_OPTION), "S3CannotMixLocationAndEuOptions");
if (euBuckets)
locationConstraint = S3_EU_REGION_NAME;
@@ -240,7 +240,7 @@ namespace Duplicati.Library.Backend
m_prefix = m_prefix.Substring(1);
}
else
throw new UserInformationException(Strings.S3Backend.UnableToDecodeBucketnameError(url));
throw new UserInformationException(Strings.S3Backend.UnableToDecodeBucketnameError(url), "S3CannotDecodeBucketName");
}
try { Console.Error.WriteLine(Strings.S3Backend.DeprecatedUrlFormat("s3://" + m_bucket + "/" + m_prefix)); }
@@ -474,9 +474,9 @@ namespace Duplicati.Library.Backend
get { return m_wrapper; }
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { m_wrapper.DNSHost }; }
}
private string GetFullKey(string name)
+1 -1
View File
@@ -138,7 +138,7 @@ namespace Duplicati.Library.Backend
private string GeneratePolicyDoc(string path)
{
if (string.IsNullOrWhiteSpace(path))
throw new ArgumentNullException("path");
throw new ArgumentNullException(nameof(path));
path = path.Trim().Trim('/').Trim();
@@ -38,6 +38,8 @@ namespace Duplicati.Library.Backend
protected string m_storageClass;
protected AmazonS3Client m_client;
public readonly string DNSHost;
public S3Wrapper(string awsID, string awsKey, string locationConstraint, string servername, string storageClass, bool useSSL, Dictionary<string, string> options)
{
var cfg = new AmazonS3Config();
@@ -73,6 +75,7 @@ namespace Duplicati.Library.Backend
m_locationConstraint = locationConstraint;
m_storageClass = storageClass;
DNSHost = string.IsNullOrWhiteSpace(cfg.ServiceURL) ? null : new Uri(cfg.ServiceURL).Host;
}
public void AddBucket(string bucketName)
@@ -204,7 +204,7 @@ namespace Duplicati.Library.Backend
}
else
{
throw new UserInformationException(string.Format("Unsupported key type: {0}", keytype));
throw new UserInformationException(string.Format("Unsupported key type: {0}", keytype), "SSHUnsupportedKey");
}
}
public string Key { get { return "ssh-keygen"; } }
@@ -355,7 +355,7 @@ namespace Duplicati.Library.Backend
}
catch (Exception ex)
{
throw new UserInformationException(string.Format("Failed to parse the keyfile, check the key format and passphrase. Error message was {0}", ex.Message), ex);
throw new UserInformationException(string.Format("Failed to parse the keyfile, check the key format and passphrase. Error message was {0}", ex.Message), "SSHFailedToParseKeyFile", ex);
}
}
@@ -369,9 +369,9 @@ namespace Duplicati.Library.Backend
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { m_server }; }
}
}
}
@@ -115,9 +115,9 @@ namespace Duplicati.Library.Backend
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { m_orgUrl.Host, string.IsNullOrWhiteSpace(m_spWebUrl) ? null : new Utility.Uri(m_spWebUrl).Host }; }
}
#endregion
@@ -417,7 +417,7 @@ namespace Duplicati.Library.Backend
public CustomWebRequestExecutorFactory(WebRequestExecutorFactory parent)
{
if (parent == null)
throw new ArgumentNullException("parent");
throw new ArgumentNullException(nameof(parent));
m_parent = parent;
}
+2 -2
View File
@@ -440,9 +440,9 @@ namespace Duplicati.Library.Backend.Sia
}
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new System.Uri(m_apihost).Host }; }
}
#endregion
@@ -100,7 +100,7 @@ namespace Duplicati.Library.Backend
u.RequireHost();
if (!u.Path.StartsWith("uri/URI:DIR2:", StringComparison.Ordinal) && !u.Path.StartsWith("uri/URI%3ADIR2%3A", StringComparison.Ordinal))
throw new UserInformationException(Strings.TahoeBackend.UnrecognizedUriError);
throw new UserInformationException(Strings.TahoeBackend.UnrecognizedUriError, "TahoeInvalidUri");
m_useSSL = Utility.Utility.ParseBoolOption(options, "use-ssl");
@@ -254,9 +254,9 @@ namespace Duplicati.Library.Backend
get { return Strings.TahoeBackend.Description; }
}
public string DNSName
public string[] DNSName
{
get { return null; }
get { return new string[] { new Uri(m_url).Host }; }
}
#endregion
+3 -3
View File
@@ -140,7 +140,7 @@ namespace Duplicati.Library.Backend
throw new Interface.FolderMissingException(Strings.WEBDAV.MissingFolderError(m_path, wex.Message), wex);
if (wex.Response as System.Net.HttpWebResponse != null && (wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
throw new UserInformationException(Strings.WEBDAV.MethodNotAllowedError((wex.Response as System.Net.HttpWebResponse).StatusCode), wex);
throw new UserInformationException(Strings.WEBDAV.MethodNotAllowedError((wex.Response as System.Net.HttpWebResponse).StatusCode), "WebdavMethodNotAllowed", wex);
throw;
}
@@ -313,9 +313,9 @@ namespace Duplicati.Library.Backend
get { return Strings.WEBDAV.Description; }
}
public string DNSName
public string[] DNSName
{
get { return m_dnsName; }
get { return new string[] { m_dnsName }; }
}
public void Test()
@@ -33,10 +33,15 @@ namespace Duplicati.Library.Compression
{
/// <summary>
/// An abstraction of a zip archive as a FileArchive, based on SharpCompress.
/// Please note, duplicati does not require both Read & Write access at the same time so this has not been implemented.
/// Please note, duplicati does not require both Read &amp; Write access at the same time so this has not been implemented.
/// </summary>
public class FileArchiveZip : ICompression
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<FileArchiveZip>();
private const string CannotReadWhileWriting = "Cannot read while writing";
private const string CannotWriteWhileReading = "Cannot write while reading";
@@ -353,7 +358,7 @@ namespace Duplicati.Library.Compression
if (m_using_reader)
throw;
Logging.Log.WriteMessage("Zip archive appears to have a broken Central Record Header, switching to stream mode", Logging.LogMessageType.Warning, ex);
Logging.Log.WriteWarningMessage(LOGTAG, "BrokenCentralHeaderFallback", ex, "Zip archive appears to have a broken Central Record Header, switching to stream mode");
SwitchToReader();
var d = new Dictionary<string, IEntry>(Duplicati.Library.Utility.Utility.ClientFilenameStringComparer);
@@ -377,7 +382,7 @@ namespace Duplicati.Library.Compression
if (d.Count < 2)
throw;
Logging.Log.WriteMessage(string.Format("Zip archive appears to have broken records, returning the {0} records that could be recovered", d.Count), Logging.LogMessageType.Warning, ex2);
Logging.Log.WriteWarningMessage(LOGTAG, "BrokenCentralHeader", ex2, "Zip archive appears to have broken records, returning the {0} records that could be recovered", d.Count);
}
m_entryDict = d;
+2 -2
View File
@@ -16,8 +16,8 @@ namespace Duplicati.Library.Compression.Strings {
public static string NoWriterError { get { return LC.L(@"Archive not opened for writing"); } }
public static string NoReaderError { get { return LC.L(@"Archive not opened for reading"); } }
public static string FileNotFoundError { get { return LC.L(@"The given file is not part of this archive"); } }
public static string Description { get { return LC.L(@"7z Archive with LZMA2 support."); } }
public static string DisplayName { get { return LC.L(@"7z Archive"); } }
public static string Description { get { return LC.L(@"*Experimental*: 7z Archive with LZMA2 support."); } }
public static string DisplayName { get { return LC.L(@"Experimental - 7z Archive"); } }
public static string ThreadcountLong { get { return LC.L(@"The number of threads used in LZMA 2 compression. Defaults to the number of processor cores."); } }
public static string ThreadcountShort { get { return LC.L(@"Number of threads used in compression"); } }
public static string CompressionlevelLong { get { return LC.L(@"This option controls the compression level used. A setting of zero gives no compression, and a setting of 9 gives maximum compression."); } }
@@ -160,7 +160,7 @@ namespace Duplicati.Library.DynamicLoader
public static IList<ICommandLineArgument> GetSupportedCommands(string url)
{
if (string.IsNullOrEmpty(url))
throw new ArgumentNullException("url");
throw new ArgumentNullException(nameof(url));
return _backendLoader.GetSupportedCommands(url);
}
@@ -57,13 +57,14 @@ namespace Duplicati.Library.DynamicLoader
/// Instanciates a specific compression module, given the file extension and options
/// </summary>
/// <param name="fileExtension">The file extension to create the instance for</param>
/// <param name="filename">The filename used to compress/decompress contents</param>
/// <param name="stream">The stream of the file used to compress/decompress contents</param>
/// <param name="mode">The mode for compression/decompression</param>
/// <param name="options">The options to pass to the instance constructor</param>
/// <returns>The instanciated encryption module or null if the file extension is not supported</returns>
public ICompression GetModule(string fileExtension, Stream stream, ArchiveMode mode, Dictionary<string, string> options)
{
if (string.IsNullOrEmpty(fileExtension))
throw new ArgumentNullException("fileExtension");
throw new ArgumentNullException(nameof(fileExtension));
LoadInterfaces();
@@ -84,7 +85,7 @@ namespace Duplicati.Library.DynamicLoader
public IList<ICommandLineArgument> GetSupportedCommands(string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException("key");
throw new ArgumentNullException(nameof(key));
LoadInterfaces();
@@ -131,7 +132,7 @@ namespace Duplicati.Library.DynamicLoader
/// </summary>
/// <param name="fileextension">The file extension to create the instance for</param>
/// <param name="stream">The stream of the file used to compress/decompress contents</param>
/// <param name="writing">True is the file opened for writing</param>
/// <param name="mode">The mode for compression/decompression</param>
/// <param name="options">The options to pass to the instance constructor</param>
/// <returns>The instanciated compression module or null if the file extension is not supported</returns>
public static ICompression GetModule(string fileextension, Stream stream, ArchiveMode mode, Dictionary<string, string> options)
@@ -29,10 +29,15 @@ namespace Duplicati.Library.DynamicLoader
/// <typeparam name="T">The interface that the class loads</typeparam>
internal abstract class DynamicLoader<T> where T : class
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<DynamicLoader<T>>();
/// <summary>
/// A lock used to guarantee threadsafe access to the interface lookup table
/// </summary>
protected object m_lock = new object();
protected readonly object m_lock = new object();
/// <summary>
/// A cached list of interfaces
@@ -130,14 +135,15 @@ namespace Duplicati.Library.DynamicLoader
}
catch (Exception ex)
{
Duplicati.Library.Logging.Log.WriteMessage(Strings.DynamicLoader.DynamicTypeLoadError(t.FullName, s, ex.ToString()), Duplicati.Library.Logging.LogMessageType.Warning);
Duplicati.Library.Logging.Log.WriteWarningMessage(LOGTAG, "SoftError", ex, Strings.DynamicLoader.DynamicTypeLoadError(t.FullName, s, ex.Message));
}
}
}
catch // (Exception ex)
{ // Since this is locating the assemblies that have the proper interface, it isn't an error to not.
catch(Exception ex)
{
// Since this is locating the assemblies that have the proper interface, it isn't an error to not.
// This was loading the log with errors about additional DLL's that are not plugins and do not have manifests.
// Duplicati.Library.Logging.Log.WriteMessage(string.Format(Strings.DynamicLoader.DynamicAssemblyLoadError, s, ex.ToString()), Duplicati.Library.Logging.LogMessageType.Warning);
Duplicati.Library.Logging.Log.WriteExplicitMessage(LOGTAG, "HardError", ex, Strings.DynamicLoader.DynamicAssemblyLoadError(s, ex.Message));
}
}
@@ -62,10 +62,10 @@ namespace Duplicati.Library.DynamicLoader
public IEncryption GetModule(string fileExtension, string passphrase, Dictionary<string, string> options)
{
if (string.IsNullOrEmpty(fileExtension))
throw new ArgumentNullException("fileExtension");
throw new ArgumentNullException(nameof(fileExtension));
if (string.IsNullOrEmpty(passphrase))
throw new ArgumentNullException("passphrase");
throw new ArgumentNullException(nameof(passphrase));
LoadInterfaces();
@@ -86,7 +86,7 @@ namespace Duplicati.Library.DynamicLoader
public IList<ICommandLineArgument> GetSupportedCommands(string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException("key");
throw new ArgumentNullException(nameof(key));
LoadInterfaces();
+25 -18
View File
@@ -33,14 +33,21 @@ namespace Duplicati.Library.Interface
[Serializable]
public class UserInformationException : Exception
{
public UserInformationException(string message)
/// <summary>
/// The help ID for the exception
/// </summary>
public readonly string HelpID;
public UserInformationException(string message, string helpId)
: base(message)
{
HelpID = helpId;
}
public UserInformationException(string message, Exception innerException)
public UserInformationException(string message, string helpId, Exception innerException)
: base(message, innerException)
{
HelpID = helpId;
}
}
@@ -51,19 +58,19 @@ namespace Duplicati.Library.Interface
public class FolderMissingException : UserInformationException
{
public FolderMissingException()
: base(Strings.Common.FolderMissingError)
: base(Strings.Common.FolderMissingError, "FolderMissing")
{ }
public FolderMissingException(string message)
: base(message)
: base(message, "FolderMissing")
{ }
public FolderMissingException(Exception innerException)
: base(Strings.Common.FolderMissingError, innerException)
: base(Strings.Common.FolderMissingError, "FolderMissing", innerException)
{ }
public FolderMissingException(string message, Exception innerException)
: base(message, innerException)
: base(message, "FolderMissing", innerException)
{ }
}
@@ -74,19 +81,19 @@ namespace Duplicati.Library.Interface
public class FileMissingException : UserInformationException
{
public FileMissingException()
: base(LC.L("The requested file does not exist"))
: base(LC.L("The requested file does not exist"), "FileMissing")
{ }
public FileMissingException(string message)
: base(message)
: base(message, "FileMissing")
{ }
public FileMissingException(Exception innerException)
: base(LC.L("The requested file does not exist"), innerException)
: base(LC.L("The requested file does not exist"), "FileMissing", innerException)
{ }
public FileMissingException(string message, Exception innerException)
: base(message, innerException)
: base(message, "FileMissing", innerException)
{ }
}
@@ -97,19 +104,19 @@ namespace Duplicati.Library.Interface
public class FolderAreadyExistedException : UserInformationException
{
public FolderAreadyExistedException()
: base(Strings.Common.FolderAlreadyExistsError)
: base(Strings.Common.FolderAlreadyExistsError, "FolderAlreadyExists")
{ }
public FolderAreadyExistedException(string message)
: base(message)
: base(message, "FolderAlreadyExists")
{ }
public FolderAreadyExistedException(Exception innerException)
: base(Strings.Common.FolderAlreadyExistsError, innerException)
: base(Strings.Common.FolderAlreadyExistsError, "FolderAlreadyExists", innerException)
{ }
public FolderAreadyExistedException(string message, Exception innerException)
: base(message, innerException)
: base(message, "FolderAlreadyExists", innerException)
{ }
}
@@ -120,19 +127,19 @@ namespace Duplicati.Library.Interface
public class CancelException : UserInformationException
{
public CancelException()
: base(Strings.Common.CancelExceptionError)
: base(Strings.Common.CancelExceptionError, "Cancelled")
{ }
public CancelException(string message)
: base(message)
: base(message, "Cancelled")
{ }
public CancelException(Exception innerException)
: base(Strings.Common.CancelExceptionError, innerException)
: base(Strings.Common.CancelExceptionError, "Cancelled", innerException)
{ }
public CancelException(string message, Exception innerException)
: base(message, innerException)
: base(message, "Cancelled", innerException)
{ }
}
}
+2 -2
View File
@@ -82,9 +82,9 @@ namespace Duplicati.Library.Interface
string Description { get; }
/// <summary>
/// The DNS name used to resolve the IP address for this backend
/// The DNS names used to resolve the IP addresses for this backend
/// </summary>
string DNSName { get; }
string[] DNSName { get; }
/// <summary>
/// The purpose of this method is to test the connection to the remote backend.
@@ -39,12 +39,17 @@
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="ILog.cs" />
<Compile Include="Log.cs" />
<Compile Include="ILogDestination.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StreamLog.cs" />
<Compile Include="StreamLogDestination.cs" />
<Compile Include="Timer.cs" />
<Compile Include="LogScope.cs" />
<Compile Include="Log.cs" />
<Compile Include="ILogFilter.cs" />
<Compile Include="LogTagFilter.cs" />
<Compile Include="LogEntry.cs" />
<Compile Include="LambdaSupport.cs" />
<Compile Include="RepeatingLogScope.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@@ -26,14 +26,12 @@ namespace Duplicati.Library.Logging
/// <summary>
/// Interface for a loggin destination
/// </summary>
public interface ILog
public interface ILogDestination
{
/// <summary>
/// The function called when a message is logged
/// </summary>
/// <param name="message">The message logged</param>
/// <param name="type">The type of message logged</param>
/// <param name="exception">An exception, may be null</param>
void WriteMessage(string message, LogMessageType type, Exception exception);
/// <param name="entry">The message to log</param>
void WriteMessage(LogEntry entry);
}
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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;
namespace Duplicati.Library.Logging
{
/// <summary>
/// An interface for filtering log messages
/// </summary>
public interface ILogFilter
{
/// <summary>
/// A method called to determine if a given message should be filtered or not
/// </summary>
/// <returns><c>true</c> if the message is included; <c>false</c> otherwise</returns>
/// <param name="entry">The entry to examine</param>
bool Accepts(LogEntry entry);
}
}
@@ -0,0 +1,78 @@
// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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;
namespace Duplicati.Library.Logging
{
/// <summary>
/// A log destinations that invokes a method
/// </summary>
internal class FunctionLogDestination : ILogDestination
{
/// <summary>
/// The method to call for logging
/// </summary>
private readonly Action<LogEntry> m_entry;
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Logging.FunctionLogDestination"/> class.
/// </summary>
/// <param name="entry">The method to call for logging.</param>
public FunctionLogDestination(Action<LogEntry> entry)
{
m_entry = entry ?? throw new ArgumentNullException(nameof(entry));
}
/// <summary>
/// Writes the message by invoking the handler.
/// </summary>
/// <param name="entry">The entry to log.</param>
public void WriteMessage(LogEntry entry)
{
m_entry.Invoke(entry);
}
}
/// <summary>
/// A log filter that wraps a callback method
/// </summary>
internal class FunctionFilter : ILogFilter
{
/// <summary>
/// The method to call for filtering
/// </summary>
private readonly Func<LogEntry, bool> m_handler;
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Logging.FunctionFilter"/> class.
/// </summary>
/// <param name="handler">The method to call for filtering.</param>
public FunctionFilter(Func<LogEntry, bool> handler)
{
m_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
/// <summary>
/// Determines if the element is accepted or nor
/// </summary>
/// <returns>A value indicating if the value is accepted or not.</returns>
/// <param name="entry">The log entry to filter.</param>
public bool Accepts(LogEntry entry)
{
return m_handler(entry);
}
}
}
+252 -63
View File
@@ -1,4 +1,4 @@
#region Disclaimer / License
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
@@ -28,15 +28,31 @@ namespace Duplicati.Library.Logging
/// </summary>
public enum LogMessageType
{
/// <summary>
/// The message should only be shown if it is explicitly requested
/// </summary>
ExplicitOnly,
/// <summary>
/// The message is a profiling message
/// </summary>
Profiling,
/// <summary>
/// Messags that are normally not wanted for display
/// </summary>
Verbose,
/// <summary>
/// The message is from a retry
/// </summary>
Retry,
/// <summary>
/// The message is informative but does not indicate problems
/// </summary>
Information,
/// <summary>
/// The message is from dry-run output
/// </summary>
DryRun,
/// <summary>
/// The message is a warning, meaning that later errors may be releated to this message
/// </summary>
Warning,
@@ -59,7 +75,7 @@ namespace Duplicati.Library.Logging
/// <summary>
/// The root scope
/// </summary>
private static readonly LogScope m_root = new LogScope(null, LogMessageType.Error, null);
private static readonly LogScope m_root = new LogScope(null, new LogTagFilter(LogMessageType.Error, null, null), null);
/// <summary>
/// The stored log instances
@@ -69,26 +85,219 @@ namespace Duplicati.Library.Logging
/// <summary>
/// Static lock object to provide thread safe logging
/// </summary>
private static object m_lock = new object();
/// <summary>
/// Flag used to block recursion
/// </summary>
private static bool m_is_active = false;
private static readonly object m_lock = new object();
/// <summary>
/// Gets the lock instance used to protect the logging calls
/// </summary>
public static object Lock { get { return m_lock; } }
/// <summary>
/// Gets a log tag taht reflects the type
/// </summary>
/// <returns>The log-tag for the type.</returns>
/// <typeparam name="T">The type to get the tag for.</typeparam>
public static string LogTagFromType<T>()
{
return LogTagFromType(typeof(T));
}
/// <summary>
/// Gets a log tag taht reflects the type
/// </summary>
/// <returns>The log-tag for the type.</returns>
/// <param name="t">The type to get the tag for.</param>
public static string LogTagFromType(Type t)
{
return t.Namespace + "." + t.Name;
}
/// <summary>
/// Gets a common list of items to include in the help-url
/// </summary>
/// <returns>The basic help url items.</returns>
/// <param name="fromCommandLine">A flag indicating if the link is for commandline</param>
private static List<string> GetBasicHelpUrlItems(bool fromCommandLine)
{
var items = new List<string>();
items.Add("version=" + System.Uri.EscapeDataString(typeof(Log).Assembly.GetName().Version.ToString()));
items.Add("cli=" + (fromCommandLine ? "t" : "f"));
// TOOD: Add OS type? mono version? install id? app-name?
return items;
}
/// <summary>
/// Encodes a list of query string values and appends them to the help url
/// </summary>
/// <returns>The help URL.</returns>
/// <param name="items">The items to include.</param>
private static string EncodeHelpUrl(IEnumerable<string> items)
{
var query = string.Join("&", items);
if (!string.IsNullOrWhiteSpace(query))
query = "?" + query;
return "https://help.duplicati.com/" + query;
}
/// <summary>
/// Creates a link to look up further information on a particular message
/// </summary>
/// <returns>The help link.</returns>
/// <param name="id">The id to use.</param>
/// <param name="fromCommandLine">A flag indicating if the link is for commandline</param>
public static string CreateHelpLink(string id, bool fromCommandLine)
{
var items = GetBasicHelpUrlItems(fromCommandLine);
if (!string.IsNullOrWhiteSpace(id))
items.Add("id=" + System.Uri.EscapeDataString(id));
return EncodeHelpUrl(items);
}
/// <summary>
/// Creates a link to look up further information on a particular message
/// </summary>
/// <returns>The help link.</returns>
/// <param name="tag">The log tag, if any.</param>
/// <param name="messageid">The message id, if any.</param>
/// <param name="exceptionid">The exception id, if any.</param>
/// <param name="fromCommandLine">A flag indicating if the link is for commandline</param>
public static string CreateHelpLink(string tag, string messageid, string exceptionid, bool fromCommandLine)
{
var items = GetBasicHelpUrlItems(fromCommandLine);
if (!string.IsNullOrWhiteSpace(tag))
items.Add("tag=" + System.Uri.EscapeDataString(tag));
if (!string.IsNullOrWhiteSpace(messageid))
items.Add("msgid=" + System.Uri.EscapeDataString(messageid));
if (!string.IsNullOrWhiteSpace(exceptionid))
items.Add("exid=" + System.Uri.EscapeDataString(exceptionid));
return EncodeHelpUrl(items);
}
/// <summary>
/// Writes an explicit message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
/// <param name="arguments">The message format arguments</param>
public static void WriteExplicitMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.ExplicitOnly, tag, id, null, message, arguments);
}
/// <summary>
/// Writes an explicit message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
/// <param name="ex">The exception to log</param>
/// <param name="arguments">The message format arguments</param>
public static void WriteExplicitMessage(string tag, string id, Exception ex, string message, params object[] arguments)
{
WriteMessage(LogMessageType.ExplicitOnly, tag, id, ex, message, arguments);
}
/// <summary>
/// Writes a verbose message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
/// <param name="arguments">The message format arguments</param>
public static void WriteVerboseMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Verbose, tag, id, null, message, arguments);
}
/// <summary>
/// Writes a profiling message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
/// <param name="arguments">The message format arguments</param>
public static void WriteProfilingMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Profiling, tag, id, null, message, arguments);
}
/// <summary>
/// Writes a dry-run message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
/// <param name="arguments">The message format arguments</param>
public static void WriteDryrunMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.DryRun, tag, id, null, message, arguments);
}
/// <summary>
/// Writes a retry message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
/// <param name="ex">The exception to attach</param>
/// <param name="arguments">The message format arguments</param>
public static void WriteRetryMessage(string tag, string id, Exception ex, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Retry, tag, id, ex, message, arguments);
}
/// <summary>
/// Writes an information message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
/// <param name="arguments">The message format arguments</param>
public static void WriteInformationMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Information, tag, id, null, message, arguments);
}
/// <summary>
/// Writes a warning message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
/// <param name="ex">The exception to attach</param>
/// <param name="arguments">The message format arguments</param>
public static void WriteWarningMessage(string tag, string id, Exception ex, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Warning, tag, id, ex, message, arguments);
}
/// <summary>
/// Writes an error message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
/// <param name="arguments">The message format arguments</param>
public static void WriteErrorMessage(string tag, string id, Exception ex, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Error, tag, id, ex, message, arguments);
}
/// <summary>
/// Writes a message to the current log destination
/// </summary>
/// <param name="message">The message to write</param>
/// <param name="type">The type of the message</param>
public static void WriteMessage(string message, LogMessageType type)
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
public static void WriteMessage(LogMessageType type, string tag, string id, string message, params object[] arguments)
{
WriteMessage(message, type, null);
WriteMessage(type, tag, id, null, message, arguments);
}
/// <summary>
@@ -97,67 +306,23 @@ namespace Duplicati.Library.Logging
/// <param name="message">The message to write</param>
/// <param name="type">The type of the message</param>
/// <param name="ex">An exception value</param>
public static void WriteMessage(string message, LogMessageType type, Exception ex)
/// <param name="tag">The tag-type for this message</param>
/// <param name="id">The message id</param>
public static void WriteMessage(LogMessageType type, string tag, string id, Exception ex, string message, params object[] arguments)
{
var msg = new LogEntry(message, arguments, type, type + "-" + tag + "-" + id, id, ex);
lock (m_lock)
{
if (m_is_active)
return;
try
{
m_is_active = true;
var cs = CurrentScope;
while (cs != null)
{
if (cs.Log != null && type >= cs.LogLevel)
cs.Log.WriteMessage(message, type, ex);
cs = cs.Parent;
}
}
finally
{
m_is_active = false;
}
}
}
/// <summary>
/// Gets or sets the level of log messages to write to the CurrentLog
/// </summary>
public static LogMessageType LogLevel
{
get
{
var cs = CurrentScope;
var ll = (int)cs.LogLevel;
while (cs != null)
{
ll = Math.Min(ll, (int)cs.LogLevel);
cs.WriteMessage(msg);
cs = cs.Parent;
}
return (LogMessageType)ll;
}
set
{
CurrentScope.LogLevel = value;
}
}
/// <summary>
/// Gets or sets the current log destination
/// </summary>
public static ILog CurrentLog
{
get
{
return CurrentScope.Log;
}
set
{
CurrentScope.Log = value;
}
}
/// <summary>
/// Starts a new scope, that can be closed by disposing the returned instance
@@ -165,16 +330,40 @@ namespace Duplicati.Library.Logging
/// <returns>The new scope.</returns>
public static IDisposable StartScope()
{
return StartScope((ILog)null);
return StartScope((ILogDestination)null, null);
}
/// <summary>
/// Starts a new scope, that can be stopped by disposing the returned instance
/// </summary>
/// <param name="log">The log target</param>
/// <param name="level">The log level</param>
/// <returns>The new scope.</returns>
public static IDisposable StartScope(ILog log)
public static IDisposable StartScope(ILogDestination log, LogMessageType level)
{
return new LogScope(log, LogLevel, CurrentScope);
return StartScope(log, new LogTagFilter(level, null, null));
}
/// <summary>
/// Starts a new scope, that can be stopped by disposing the returned instance
/// </summary>
/// <param name="log">The log target</param>
/// <param name="filter">The log filter</param>
/// <returns>The new scope.</returns>
public static IDisposable StartScope(ILogDestination log, ILogFilter filter = null)
{
return new LogScope(log, filter, CurrentScope);
}
/// <summary>
/// Starts a new scope, that can be stopped by disposing the returned instance
/// </summary>
/// <param name="log">The log target</param>
/// <param name="filter">The log filter</param>
/// <returns>The new scope.</returns>
public static IDisposable StartScope(Action<LogEntry> log, Func<LogEntry, bool> filter = null)
{
return new LogScope(new FunctionLogDestination(log), filter == null ? null : new FunctionFilter(filter), CurrentScope);
}
/// <summary>
+174
View File
@@ -0,0 +1,174 @@
// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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;
using System.Linq;
namespace Duplicati.Library.Logging
{
/// <summary>
/// Instance of a log entry
/// </summary>
public class LogEntry
{
/// <summary>
/// The time when the entry was logged
/// </summary>
public DateTime When;
/// <summary>
/// The message that was logged
/// </summary>
public readonly string Message;
/// <summary>
/// The string format arguments, if any
/// </summary>
public readonly object[] Arguments;
/// <summary>
/// The message level
/// </summary>
public LogMessageType Level;
/// <summary>
/// The tag for the message
/// </summary>
public readonly string Tag;
/// <summary>
/// The message help-id
/// </summary>
public readonly string Id;
/// <summary>
/// An optional exception
/// </summary>
public readonly Exception Exception;
/// <summary>
/// The log context data, if any
/// </summary>
private Dictionary<string, string> m_logContext;
/// <summary>
/// Gets or sets the <see cref="T:Duplicati.Library.Logging.LogEntry"/> with the specified value.
/// </summary>
/// <param name="value">The key to use.</param>
public string this[string key]
{
get
{
if (m_logContext == null)
return null;
m_logContext.TryGetValue(key, out var s);
return s;
}
set
{
if (m_logContext == null)
m_logContext = new Dictionary<string, string>();
m_logContext[key] = value;
}
}
/// <summary>
/// Gets the keys in this log context
/// </summary>
/// <value>The context keys.</value>
public IEnumerable<string> ContextKeys
{
get
{
if (m_logContext == null)
return new string[0];
return m_logContext.Keys;
}
}
/// <summary>
/// Gets the message, formatted with arguments
/// </summary>
/// <value>The formatted message.</value>
public string FormattedMessage
{
get
{
if (Arguments == null || Arguments.Length == 0)
return Message;
try
{
return string.Format(Message, Arguments);
}
catch
{
// Try no to crash ...
return string.Format("Error while formating: \"{0}\" with arguments: [{1}]", Message, string.Join(", ", Arguments.Select(x => x == null ? "(null)" : x.ToString())));
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Logging.LogEntry"/> class.
/// </summary>
/// <param name="message">The message to use.</param>
/// <param name="arguments">An optional set of arguments.</param>
/// <param name="level">The log level.</param>
/// <param name="tag">The tag to use.</param>
/// <param name="id">The message ID.</param>
/// <param name="exception">An optional exception.</param>
public LogEntry(string message, object[] arguments, LogMessageType level, string tag, string id, Exception exception)
{
When = DateTime.Now;
Message = message;
Arguments = arguments;
Level = level;
Tag = tag;
Id = id;
Exception = exception;
}
/// <summary>
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:Duplicati.Library.Logging.LogEntry"/>.
/// </summary>
/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:Duplicati.Library.Logging.LogEntry"/>.</returns>
public override string ToString()
{
return string.Format("{0:yyyy-MM-dd HH:mm:ss zz} - [{1}]: {2}", When.ToLocalTime(), Tag, FormattedMessage);
}
/// <summary>
/// Returns the item as a string optionally with exception details
/// </summary>
/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:Duplicati.Library.Logging.LogEntry"/>.</returns>
/// <param name="withExceptionDetails">If set to <c>true</c> the result has expanded exception details.</param>
public string AsString(bool withExceptionDetails)
{
return this.ToString() +
((withExceptionDetails && Exception != null)
? Environment.NewLine + Exception.ToString()
: string.Empty);
}
}
}
+20 -6
View File
@@ -15,6 +15,8 @@
// 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.Linq;
namespace Duplicati.Library.Logging
{
/// <summary>
@@ -30,12 +32,12 @@ namespace Duplicati.Library.Logging
/// <summary>
/// The log instance assigned to this scope
/// </summary>
public ILog Log;
private readonly ILogDestination m_log;
/// <summary>
/// The current log level
/// The log filter
/// </summary>
public LogMessageType LogLevel;
private readonly ILogFilter m_filter;
/// <summary>
/// The log scope parent
@@ -51,16 +53,28 @@ namespace Duplicati.Library.Logging
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Logging.LogWrapper"/> class.
/// </summary>
/// <param name="self">The log instance to wrap.</param>
public LogScope(ILog self, LogMessageType level, LogScope parent)
/// <param name="filter">The log filter to use</param>
public LogScope(ILogDestination self, ILogFilter filter, LogScope parent)
{
Log = self;
LogLevel = level;
Parent = parent;
m_log = self;
m_filter = filter;
if (parent != null)
Logging.Log.StartScope(this);
}
/// <summary>
/// The function called when a message is logged
/// </summary>
/// <param name="entry">The log entry</param>
public void WriteMessage(LogEntry entry)
{
if (m_log != null && (m_filter == null || m_filter.Accepts(entry)))
m_log.WriteMessage(entry);
}
/// <summary>
/// Releases all resource used by the <see cref="T:Duplicati.Library.Logging.LogScope"/> object.
/// </summary>
+67
View File
@@ -0,0 +1,67 @@
// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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.Linq;
namespace Duplicati.Library.Logging
{
/// <summary>
/// Implements a filter that removes log messages based on tags
/// </summary>
public class LogTagFilter : ILogFilter
{
/// <summary>
/// The tag prefixes we exclude
/// </summary>
private readonly string[] m_excludeTags;
/// <summary>
/// The tag prefixes we include
/// </summary>
private readonly string[] m_includeTags;
/// <summary>
/// The minimum log level to consider
/// </summary>
private readonly LogMessageType m_logLevel;
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Logging.LogTagFilter"/> class.
/// </summary>
public LogTagFilter(LogMessageType logLevel, string[] excludeTags, string[] includeTags)
{
m_logLevel = logLevel;
m_excludeTags = excludeTags ?? new string[0];
m_includeTags = includeTags ?? new string[0];
}
/// <summary>
/// A method called to determine if a given message should be filtered or not
/// </summary>
/// <returns><c>true</c> if the message is included; <c>false</c> otherwise</returns>
/// <param name="entry">The entry to examine</param>
public bool Accepts(LogEntry entry)
{
// Skip all explicitly excluded
if (m_excludeTags.Any(x => entry.Tag.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
return false;
// Include all explicitly included or under threshold
return entry.Level >= m_logLevel || m_includeTags.Any(x => entry.Tag.StartsWith(x, StringComparison.OrdinalIgnoreCase));
}
}
}
@@ -0,0 +1,119 @@
// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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.Threading.Tasks;
namespace Duplicati.Library.Logging
{
/// <summary>
/// Creates a log-scope that repeats the last emitted message, if there is no log activity
/// </summary>
public class RepeatingLogScope : ILogDestination, IDisposable
{
/// <summary>
/// A string that is written as the message to the log
/// </summary>
private const string STILL_RUNNING = "...";
/// <summary>
/// The scope that we dispose when we are done
/// </summary>
private readonly IDisposable m_scope;
/// <summary>
/// The max time to wait before repeating the message (in ticks)
/// </summary>
private readonly long m_maxIdleTime = TimeSpan.FromSeconds(10).Ticks;
/// <summary>
/// The time the last message was written, in ticks
/// </summary>
private long m_lastWritten;
/// <summary>
/// A flag to keep track of when this instance is disposed
/// </summary>
private bool m_completed = false;
/// <summary>
/// The last log entry seen
/// </summary>
private LogEntry m_lastEntry;
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Logging.RepeatingLogScope"/> class.
/// </summary>
public RepeatingLogScope()
{
m_scope = Log.StartScope(this);
Task.Run(() => RepeatedRunner());
}
/// <summary>
/// The repeating run task
/// </summary>
private async void RepeatedRunner()
{
var remainingTime = m_maxIdleTime;
while (!m_completed)
{
await Task.Delay(new TimeSpan(Math.Max(TimeSpan.FromMilliseconds(500).Ticks, remainingTime)));
if (m_completed)
return;
if (m_lastEntry == null)
continue;
var now = DateTime.Now.Ticks;
remainingTime = (m_lastWritten + m_maxIdleTime) - now;
if (remainingTime < 0)
{
Logging.Log.WriteMessage(
m_lastEntry.Level,
m_lastEntry.Tag,
m_lastEntry.Id,
m_lastEntry.Exception,
STILL_RUNNING,
m_lastEntry.Arguments
);
remainingTime = m_maxIdleTime;
}
}
}
/// <summary>
/// Releases all resource used by the <see cref="T:Duplicati.Library.Logging.RepeatingLogScope"/> object.
/// </summary>
/// <remarks>Call <see cref="Dispose"/> when you are finished using the
/// <see cref="T:Duplicati.Library.Logging.RepeatingLogScope"/>. The <see cref="Dispose"/> method leaves the
/// <see cref="T:Duplicati.Library.Logging.RepeatingLogScope"/> in an unusable state. After calling
/// <see cref="Dispose"/>, you must release all references to the
/// <see cref="T:Duplicati.Library.Logging.RepeatingLogScope"/> so the garbage collector can reclaim the memory
/// that the <see cref="T:Duplicati.Library.Logging.RepeatingLogScope"/> was occupying.</remarks>
public void Dispose()
{
m_completed = true;
m_scope.Dispose();
}
/// <summary>
/// Records the message and updates the timestamp
/// </summary>
/// <param name="entry">The entry that is being written.</param>
public void WriteMessage(LogEntry entry)
{
m_lastWritten = DateTime.Now.Ticks;
m_lastEntry = entry;
}
}
}
@@ -26,15 +26,18 @@ namespace Duplicati.Library.Logging
/// <summary>
/// Writes log messages to a stream
/// </summary>
public class StreamLog : ILog, IDisposable
public class StreamLogDestination : ILogDestination, IDisposable
{
/// <summary>
/// The stream to log to
/// </summary>
private System.IO.StreamWriter m_stream;
/// <summary>
/// Constructs a new log destination, writing to the supplied stream
/// </summary>
/// <param name="stream">The stream to write log messages into</param>
public StreamLog(System.IO.Stream stream)
public StreamLogDestination(System.IO.Stream stream)
{
m_stream = new System.IO.StreamWriter(stream);
m_stream.AutoFlush = true;
@@ -44,7 +47,7 @@ namespace Duplicati.Library.Logging
/// Constructs a new log destination, writing to the supplied file
/// </summary>
/// <param name="filename">The file to write to</param>
public StreamLog(string filename)
public StreamLogDestination(string filename)
: this(new System.IO.FileStream(filename, System.IO.FileMode.Append, System.IO.FileAccess.Write))
{
}
@@ -54,17 +57,18 @@ namespace Duplicati.Library.Logging
/// <summary>
/// The function called when a message is logged
/// </summary>
/// <param name="message">The message logged</param>
/// <param name="type">The type of message logged</param>
/// <param name="exception">An exception, may be null</param>
public virtual void WriteMessage(string message, LogMessageType type, Exception exception)
/// <param name="entry">The entry to write</param>
public virtual void WriteMessage(LogEntry entry)
{
m_stream.WriteLine("{0:u} - {1}: {2}", DateTime.UtcNow.ToString("u"), type, message);
if (exception != null)
/*m_stream.WriteLine("{0:u} - {1}: {2}", entry.When.ToLocalTime(), entry.Level, entry.FormattedMessage);
if (entry.Exception != null)
{
m_stream.WriteLine(exception);
m_stream.WriteLine(entry.Exception);
m_stream.WriteLine();
}
}*/
m_stream.WriteLine(entry.AsString(true));
}
#endregion
+14 -6
View File
@@ -28,20 +28,29 @@ namespace Duplicati.Library.Logging
/// </summary>
public class Timer : IDisposable
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = "Timer";
private DateTime m_begin;
private string m_operation;
private readonly string m_logtag;
private readonly string m_logid;
/// <summary>
/// Constructs a new timer, and starts timing
/// </summary>
/// <param name="operation">The name of the operation being performed</param>
public Timer(string operation)
/// <param name="logtag">The log tag addition to use</param>
/// <param name="logid">The ID for this log message</param>
public Timer(string logtag, string logid, string operation)
{
m_operation = operation;
m_begin = DateTime.Now;
if (Log.LogLevel == LogMessageType.Profiling)
Log.WriteMessage(string.Format("Starting - {0}", m_operation), LogMessageType.Profiling);
m_logtag = logtag;
m_logid = logid;
Log.WriteProfilingMessage(LOGTAG + ".Begin-" + m_logtag, m_logid, "Starting - {0}", m_operation);
}
#region IDisposable Members
@@ -54,8 +63,7 @@ namespace Duplicati.Library.Logging
if (m_operation == null)
return;
if (Log.LogLevel == LogMessageType.Profiling)
Log.WriteMessage(string.Format("{0} took {1:hh\\:mm\\:ss\\.fff}", m_operation, DateTime.Now - m_begin), LogMessageType.Profiling);
Log.WriteProfilingMessage(LOGTAG + ".Finished-" + m_logtag, m_logid, "{0} took {1:d\\:hh\\:mm\\:ss\\.fff}", m_operation, DateTime.Now - m_begin);
m_operation = null;
}
+33 -26
View File
@@ -12,6 +12,11 @@ namespace Duplicati.Library.Main
{
internal class BackendManager : IDisposable
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<BackendManager>();
public const string VOLUME_HASH = "SHA256";
/// <summary>
@@ -209,7 +214,7 @@ namespace Duplicati.Library.Main
{
if (this.LocalTempfile != null)
try { this.LocalTempfile.Dispose(); }
catch (Exception ex) { stat.AddWarning(string.Format("Failed to dispose temporary file: {0}", this.LocalTempfile), ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DeleteTemporaryFileError", ex, "Failed to dispose temporary file: {0}", this.LocalTempfile); }
finally { this.LocalTempfile = null; }
}
@@ -239,7 +244,7 @@ namespace Duplicati.Library.Main
private class DatabaseCollector
{
private object m_dbqueuelock = new object();
private readonly object m_dbqueuelock = new object();
private LocalDatabase m_database;
private System.Threading.Thread m_callerThread;
private List<IDbEntry> m_dbqueue;
@@ -333,7 +338,7 @@ namespace Duplicati.Library.Main
else if (e is DbRename)
db.RenameRemoteFile(((DbRename)e).Oldname, ((DbRename)e).Newname, transaction);
else if (e != null)
m_stats.AddError(string.Format("Queue had element of type: {0}, {1}", e.GetType(), e), null);
Logging.Log.WriteErrorMessage(LOGTAG, "InvalidQueueElement", null, "Queue had element of type: {0}, {1}", e.GetType(), e);
// Finally remove volumes from DB.
if (volsRemoved.Count > 0)
@@ -384,14 +389,14 @@ namespace Duplicati.Library.Main
try { shortname = new Library.Utility.Uri(shortname).Scheme; }
catch { }
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Backend not supported: {0}", shortname));
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Backend not supported: {0}", shortname), "BackendNotSupported");
}
if (!m_options.NoEncryption)
{
m_encryption = DynamicLoader.EncryptionLoader.GetModule(m_options.EncryptionModule, m_options.Passphrase, m_options.RawOptions);
if (m_encryption == null)
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Encryption method not supported: {0}", m_options.EncryptionModule));
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Encryption method not supported: {0}", m_options.EncryptionModule), "EncryptionMethodNotSupported");
}
if (m_taskControl != null)
@@ -472,7 +477,7 @@ namespace Duplicati.Library.Main
if (m_backend == null)
throw new Exception("Backend failed to re-load");
using (new Logging.Timer(string.Format("RemoteOperation{0}", item.Operation)))
using (new Logging.Timer(LOGTAG, string.Format("RemoteOperation{0}", item.Operation), string.Format("RemoteOperation{0}", item.Operation)))
switch (item.Operation)
{
case OperationType.Put:
@@ -508,7 +513,7 @@ namespace Duplicati.Library.Main
{
retries++;
lastException = ex;
m_statwriter.AddRetryAttempt(string.Format("Operation {0} with file {1} attempt {2} of {3} failed with message: {4}", item.Operation, item.RemoteFilename, retries, m_numberofretries, ex.Message), ex);
Logging.Log.WriteRetryMessage(LOGTAG, $"Retry{item.Operation}", ex, "Operation {0} with file {1} attempt {2} of {3} failed with message: {4}", item.Operation, item.RemoteFilename, retries, m_numberofretries, ex.Message);
// If the thread is aborted, we exit here
if (ex is System.Threading.ThreadAbortException)
@@ -526,8 +531,10 @@ namespace Duplicati.Library.Main
{
try
{
if (!string.IsNullOrWhiteSpace(m_backend.DNSName))
System.Net.Dns.GetHostEntry(m_backend.DNSName);
var names = m_backend.DNSName ?? new string[0];
foreach(var name in names)
if (!string.IsNullOrWhiteSpace(name))
System.Net.Dns.GetHostEntry(name);
}
catch
{
@@ -548,7 +555,7 @@ namespace Duplicati.Library.Main
}
catch (Exception dex)
{
m_statwriter.AddWarning(string.Format("Failed to create folder: {0}", ex.Message), dex);
Logging.Log.WriteWarningMessage(LOGTAG, "FolderCreateError", dex, "Failed to create folder: {0}", ex.Message);
}
}
@@ -559,7 +566,7 @@ namespace Duplicati.Library.Main
if (!recovered)
{
try { m_backend.Dispose(); }
catch (Exception dex) { m_statwriter.AddWarning(LC.L("Failed to dispose backend instance: {0}", ex.Message), dex); }
catch (Exception dex) { Logging.Log.WriteWarningMessage(LOGTAG, "BackendDisposeError", dex, "Failed to dispose backend instance: {0}", ex.Message); }
m_backend = null;
@@ -582,18 +589,18 @@ namespace Duplicati.Library.Main
if (lastException != null && !(lastException is Duplicati.Library.Interface.FileMissingException) && item.Operation == OperationType.Delete)
{
m_statwriter.AddMessage(LC.L("Failed to delete file {0}, testing if file exists", item.RemoteFilename));
Logging.Log.WriteInformationMessage(LOGTAG, "DeleteFileFailed", LC.L("Failed to delete file {0}, testing if file exists", item.RemoteFilename));
try
{
if (!m_backend.List().Select(x => x.Name).Contains(item.RemoteFilename))
{
lastException = null;
m_statwriter.AddMessage(LC.L("Recovered from problem with attempting to delete non-existing file {0}", item.RemoteFilename));
Logging.Log.WriteInformationMessage(LOGTAG, "DeleteFileFailureRecovered", LC.L("Recovered from problem with attempting to delete non-existing file {0}", item.RemoteFilename));
}
}
catch (Exception ex)
{
m_statwriter.AddWarning(LC.L("Failed to recover from error deleting file {0}", item.RemoteFilename), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "DeleteFileFailure", ex, LC.L("Failed to recover from error deleting file {0}", item.RemoteFilename), ex);
}
}
@@ -633,7 +640,7 @@ namespace Duplicati.Library.Main
m_statwriter.SendEvent(item.BackendActionType, BackendEventType.Rename, oldname, item.Size);
m_statwriter.SendEvent(item.BackendActionType, BackendEventType.Rename, newname, item.Size);
m_statwriter.AddMessage(string.Format("Renaming \"{0}\" to \"{1}\"", oldname, newname));
Logging.Log.WriteInformationMessage(LOGTAG, "RenameRemoteTargetFile", "Renaming \"{0}\" to \"{1}\"", oldname, newname);
m_db.LogDbRename(oldname, newname);
item.RemoteFilename = newname;
@@ -735,7 +742,7 @@ namespace Duplicati.Library.Main
m_backend.Put(item.RemoteFilename, item.LocalFilename);
var duration = DateTime.Now - begin;
Logging.Log.WriteMessage(string.Format("Uploaded {0} in {1}, {2}/s", Library.Utility.Utility.FormatSizeString(item.Size), duration, Library.Utility.Utility.FormatSizeString((long)(item.Size / duration.TotalSeconds))), Duplicati.Library.Logging.LogMessageType.Profiling);
Logging.Log.WriteProfilingMessage(LOGTAG, "UploadSpeed", "Uploaded {0} in {1}, {2}/s", Library.Utility.Utility.FormatSizeString(item.Size), duration, Library.Utility.Utility.FormatSizeString((long)(item.Size / duration.TotalSeconds)));
if (!item.NotTrackedInDb)
m_db.LogDbUpdate(item.RemoteFilename, RemoteVolumeState.Uploaded, item.Size, item.Hash);
@@ -821,7 +828,7 @@ namespace Duplicati.Library.Main
{
using (var ss = new ShaderStream(nextTierWriter, false))
{
using (var ts = new ThrottledStream(ss, m_options.MaxUploadPrSecond, m_options.MaxDownloadPrSecond))
using (var ts = new ThrottledStream(ss, m_options.MaxDownloadPrSecond, m_options.MaxUploadPrSecond))
using (var pgs = new Library.Utility.ProgressReportingStream(ts, item.Size, pg => HandleProgress(ts, pg)))
{
taskHasher.Start(); // We do not start tasks earlier to be sure the input always gets closed.
@@ -906,7 +913,7 @@ namespace Duplicati.Library.Main
using (var hs = GetFileHasherStream(fs, System.Security.Cryptography.CryptoStreamMode.Write, out getFileHash))
using (var ss = new ShaderStream(hs, true))
{
using (var ts = new ThrottledStream(ss, m_options.MaxUploadPrSecond, m_options.MaxDownloadPrSecond))
using (var ts = new ThrottledStream(ss, m_options.MaxDownloadPrSecond, m_options.MaxUploadPrSecond))
using (var pgs = new Library.Utility.ProgressReportingStream(ts, item.Size, pg => HandleProgress(ts, pg)))
{ ((Library.Interface.IStreamingBackend)m_backend).Get(item.RemoteFilename, pgs); }
ss.Flush();
@@ -979,20 +986,20 @@ namespace Duplicati.Library.Main
// Check if the file is encrypted with something else
if (DynamicLoader.EncryptionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase))
{
m_statwriter.AddVerboseMessage("Filename extension \"{0}\" does not match encryption module \"{1}\", using matching encryption module", ext, m_options.EncryptionModule);
Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", using matching encryption module", ext, m_options.EncryptionModule);
useDecrypter = DynamicLoader.EncryptionLoader.GetModule(ext, m_options.Passphrase, m_options.RawOptions);
useDecrypter = useDecrypter ?? m_encryption;
}
// Check if the file is not encrypted
else if (DynamicLoader.CompressionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase))
{
m_statwriter.AddVerboseMessage("Filename extension \"{0}\" does not match encryption module \"{1}\", guessing that it is not encrypted", ext, m_options.EncryptionModule);
Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", guessing that it is not encrypted", ext, m_options.EncryptionModule);
useDecrypter = null;
}
// Fallback, lets see what happens...
else
{
m_statwriter.AddVerboseMessage("Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use specified encryption module as no others match", ext, m_options.EncryptionModule);
Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use specified encryption module as no others match", ext, m_options.EncryptionModule);
}
}
}
@@ -1011,9 +1018,9 @@ namespace Duplicati.Library.Main
tmpfile = coreDoGetPiping(item, useDecrypter, out dataSizeDownloaded, out fileHash);
var duration = DateTime.Now - begin;
Logging.Log.WriteMessage(string.Format("Downloaded {3}{0} in {1}, {2}/s", Library.Utility.Utility.FormatSizeString(dataSizeDownloaded),
Logging.Log.WriteProfilingMessage(LOGTAG, "DownloadSpeed", "Downloaded {3}{0} in {1}, {2}/s", Library.Utility.Utility.FormatSizeString(dataSizeDownloaded),
duration, Library.Utility.Utility.FormatSizeString((long)(dataSizeDownloaded / duration.TotalSeconds)),
useDecrypter == null ? "" : "and decrypted "), Duplicati.Library.Logging.LogMessageType.Profiling);
useDecrypter == null ? "" : "and decrypted ");
m_db.LogDbOperation("get", item.RemoteFilename, JsonConvert.SerializeObject(new { Size = dataSizeDownloaded, Hash = fileHash }));
m_statwriter.SendEvent(BackendActionType.Get, BackendEventType.Completed, item.RemoteFilename, dataSizeDownloaded);
@@ -1098,7 +1105,7 @@ namespace Duplicati.Library.Main
if (isFileMissingException || (wr != null && wr.StatusCode == System.Net.HttpStatusCode.NotFound))
{
m_statwriter.AddWarning(LC.L("Delete operation failed for {0} with FileNotFound, listing contents", item.RemoteFilename), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "DeleteRemoteFileFailed", ex, LC.L("Delete operation failed for {0} with FileNotFound, listing contents", item.RemoteFilename));
bool success = false;
try
@@ -1111,7 +1118,7 @@ namespace Duplicati.Library.Main
if (success)
{
m_statwriter.AddMessage(LC.L("Listing indicates file {0} is deleted correctly", item.RemoteFilename));
Logging.Log.WriteInformationMessage(LOGTAG, "DeleteRemoteFileSuccess", LC.L("Listing indicates file {0} is deleted correctly", item.RemoteFilename));
return;
}
@@ -1501,7 +1508,7 @@ namespace Duplicati.Library.Main
}
try { m_db.FlushDbMessages(true); }
catch (Exception ex) { m_statwriter.AddError(string.Format("Backend Shutdown error: {0}", ex.Message), ex); }
catch (Exception ex) { Logging.Log.WriteErrorMessage(LOGTAG, "ShutdownError", ex, "Backend Shutdown error: {0}", ex.Message); }
}
}
}
+2 -2
View File
@@ -15,10 +15,10 @@ namespace Duplicati.Library.Main
public Blockprocessor(Stream stream, byte[] buffer)
{
if (stream == null)
throw new ArgumentNullException("stream");
throw new ArgumentNullException(nameof(stream));
if (buffer == null)
throw new ArgumentNullException("buffer");
throw new ArgumentNullException(nameof(buffer));
m_stream = stream;
m_buffer = buffer;
+84 -157
View File
@@ -29,6 +29,10 @@ namespace Duplicati.Library.Main
{
public class Controller : IDisposable
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<Controller>();
/// <summary>
/// The backend url
/// </summary>
@@ -42,16 +46,6 @@ namespace Duplicati.Library.Main
/// </summary>
private IMessageSink m_messageSink;
/// <summary>
/// The stream log, if any
/// </summary>
private Logging.StreamLog m_logfile = null;
/// <summary>
/// The logging filescope
/// </summary>
private IDisposable m_logfilescope = null;
/// <summary>
/// The current executing task
/// </summary>
@@ -88,78 +82,9 @@ namespace Duplicati.Library.Main
private bool m_doResetLocale;
/// <summary>
/// The caffeinate process runner
/// The multi-controller log target
/// </summary>
private System.Diagnostics.Process m_caffeinate;
/// <summary>
/// This gets called whenever execution of an operation is started or stopped; it currently handles the AllowSleep option
/// </summary>
/// <param name="isRunning">Flag indicating execution state</param>
private void OperationRunning(bool isRunning)
{
if (m_options != null && !m_options.AllowSleep)
{
if (Duplicati.Library.Utility.Utility.IsClientWindows)
{
try
{
Win32.SetThreadExecutionState(Win32.EXECUTION_STATE.ES_CONTINUOUS | (isRunning ? Win32.EXECUTION_STATE.ES_SYSTEM_REQUIRED : 0));
}
catch (Exception ex)
{
Logging.Log.WriteMessage("Failed to set sleep prevention", Logging.LogMessageType.Warning, ex);
}
}
else if (Duplicati.Library.Utility.Utility.IsClientOSX)
{
if (isRunning)
{
try
{
if (m_caffeinate == null)
{
// -s prevents sleep on AC, -i prevents sleep generally
var psi = new System.Diagnostics.ProcessStartInfo("caffeinate", "-s");
psi.RedirectStandardInput = true;
psi.UseShellExecute = false;
m_caffeinate = System.Diagnostics.Process.Start(psi);
}
}
catch (Exception ex)
{
Logging.Log.WriteMessage("Failed to set sleep prevention", Logging.LogMessageType.Warning, ex);
}
}
else
{
try
{
if (m_caffeinate != null && !m_caffeinate.HasExited)
{
// Send CTRL+C
m_caffeinate.StandardInput.Write("\x3");
m_caffeinate.StandardInput.Flush();
m_caffeinate.WaitForExit(500);
if (!m_caffeinate.HasExited)
{
m_caffeinate.Kill();
m_caffeinate.WaitForExit(500);
if (!m_caffeinate.HasExited)
throw new Exception("Failed to kill the caffeinate process");
}
}
}
catch (Exception ex)
{
Logging.Log.WriteMessage("Failed to unset sleep prevention", Logging.LogMessageType.Warning, ex);
}
}
}
}
}
private ControllerMultiLogTarget m_logTarget;
/// <summary>
/// Constructs a new interface for performing backup and restore operations
@@ -191,10 +116,10 @@ namespace Duplicati.Library.Main
/// </summary>
/// <returns>The expanded and filtered sources.</returns>
/// <param name="inputsources">The paths to use.</param>
public string[] ExpandInputSources(string[] inputsources, IFilter filter, ILogWriter log)
public string[] ExpandInputSources(string[] inputsources, IFilter filter)
{
if (inputsources == null || inputsources.Length == 0)
throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.NoSourceFoldersError);
throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.NoSourceFoldersError, "NoSourceFolders");
var sources = new List<string>(inputsources.Length);
@@ -216,7 +141,7 @@ namespace Duplicati.Library.Main
foreach (System.IO.DriveInfo drive in drives)
{
string expandedSource = drive.Name[0] + sourcePath;
log.AddVerboseMessage(@"Adding source path ""{0}"" due to wildcard source path ""{1}""", expandedSource, inputsources[i]);
Logging.Log.WriteVerboseMessage(LOGTAG, "AddingSourcePathFromWildcard", @"Adding source path ""{0}"" due to wildcard source path ""{1}""", expandedSource, inputsources[i]);
expandedSources.Add(expandedSource);
}
}
@@ -233,21 +158,21 @@ namespace Duplicati.Library.Main
if (!string.IsNullOrEmpty(driveLetter))
{
string expandedSource = driveLetter + inputsources[i].Substring(@"\\?\Volume{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}".Length);
log.AddVerboseMessage(@"Adding source path ""{0}"" in place of volume guid source path ""{1}""", expandedSource, inputsources[i]);
Logging.Log.WriteVerboseMessage(LOGTAG, "AddingSourceFromGuid", @"Adding source path ""{0}"" in place of volume guid source path ""{1}""", expandedSource, inputsources[i]);
expandedSources.Add(expandedSource);
}
else
{
// If we aren't allow to have missing sources, throw an exception indicating we couldn't find a drive where this volume is mounted
if (!m_options.AllowMissingSource)
throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.SourceVolumeNameNotFoundError(inputsources[i], volumeGuid));
throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.SourceVolumeNameNotFoundError(inputsources[i], volumeGuid), "MissingSourceFolder");
}
}
else
{
// If we aren't allow to have missing sources, throw an exception indicating we couldn't find this volume
if (!m_options.AllowMissingSource)
throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.SourceVolumeNameInvalidError(inputsources[i]));
throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.SourceVolumeNameInvalidError(inputsources[i]), "SourceVolumeNameInvalid");
}
}
else
@@ -266,7 +191,7 @@ namespace Duplicati.Library.Main
catch (Exception ex)
{
// Note that we use the original source (with the *) in the error
throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.InvalidPathError(expandedSource, ex.Message), ex);
throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.InvalidPathError(expandedSource, ex.Message), "InputSourceInvalid", ex);
}
var fi = new System.IO.FileInfo(source);
@@ -292,7 +217,7 @@ namespace Duplicati.Library.Main
sources = Library.Utility.Utility.GetUniqueItems(sources, Library.Utility.Utility.ClientFilenameStringComparer, out pathDuplicates).OrderBy(a => a).ToList();
foreach (var pathDuplicate in pathDuplicates)
log.AddVerboseMessage(string.Format("Removing duplicate source: {0}", pathDuplicate));
Logging.Log.WriteVerboseMessage(LOGTAG, "RemoveDuplicateSource", "Removing duplicate source: {0}", pathDuplicate);
//Sanity check for multiple inclusions of the same folder
for (int i = 0; i < sources.Count; i++)
@@ -309,14 +234,14 @@ namespace Duplicati.Library.Main
// If there are no excludes, there is no need to keep the folder as a filter
if (excludes)
{
log.AddVerboseMessage("Removing source \"{0}\" because it is a subfolder of \"{1}\", and using it as an include filter", sources[i], sources[j]);
Logging.Log.WriteVerboseMessage(LOGTAG, "RemovingSubfolderSource", "Removing source \"{0}\" because it is a subfolder of \"{1}\", and using it as an include filter", sources[i], sources[j]);
filter = Library.Utility.JoinedFilterExpression.Join(new FilterExpression(sources[i]), filter);
}
else
log.AddVerboseMessage("Removing source \"{0}\" because it is a subfolder or subfile of \"{1}\"", sources[i], sources[j]);
Logging.Log.WriteVerboseMessage(LOGTAG, "RemovingSubfolderSource", "Removing source \"{0}\" because it is a subfolder or subfile of \"{1}\"", sources[i], sources[j]);
}
else
log.AddVerboseMessage("Removing source \"{0}\" because it is a subfolder or subfile of \"{1}\"", sources[i], sources[j]);
Logging.Log.WriteVerboseMessage(LOGTAG, "RemovingSubfolderSource", "Removing source \"{0}\" because it is a subfolder or subfile of \"{1}\"", sources[i], sources[j]);
sources.RemoveAt(i);
i--;
@@ -335,7 +260,7 @@ namespace Duplicati.Library.Main
return RunAction(new BackupResults(), ref inputsources, ref filter, (result) => {
using (var h = new Operation.BackupHandler(m_backend, m_options, result))
h.Run(ExpandInputSources(inputsources, filter, result), filter);
h.Run(ExpandInputSources(inputsources, filter), filter);
Library.UsageReporter.Reporter.Report("BACKUP_FILECOUNT", result.ExaminedFiles);
Library.UsageReporter.Reporter.Report("BACKUP_FILESIZE", result.SizeOfExaminedFiles);
@@ -436,7 +361,7 @@ namespace Duplicati.Library.Main
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to delete remote file: {0}", list[i].File.Name), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "DeleteFilesetError", ex, "Failed to delete remote file: {0}", list[i].File.Name);
}
result.OperationProgressUpdater.UpdateProgress((float)i / list.Count);
}
@@ -511,13 +436,18 @@ namespace Duplicati.Library.Main
public Library.Interface.ITestFilterResults TestFilter(string[] paths, Library.Utility.IFilter filter = null)
{
m_options.RawOptions["verbose"] = "true";
m_options.RawOptions["dry-run"] = "true";
m_options.RawOptions["dbpath"] = "INVALID!";
return RunAction(new TestFilterResults(), ref paths, ref filter, (result) => {
new Operation.TestFilterHandler(m_options, result).Run(ExpandInputSources(paths, filter, result), filter);
});
// Redirect all messages from the filter to the message sink
var filtertag = Logging.Log.LogTagFromType<Operation.BackupHandler.FilterHandler>();
using (Logging.Log.StartScope(m_messageSink.WriteMessage, x => x.Tag.Contains(filtertag)))
{
return RunAction(new TestFilterResults(), ref paths, ref filter, (result) =>
{
new Operation.TestFilterHandler(m_options, result).Run(ExpandInputSources(paths, filter), filter);
});
}
}
public Library.Interface.ISystemInfoResults SystemInfo()
@@ -560,23 +490,25 @@ namespace Duplicati.Library.Main
if (string.IsNullOrWhiteSpace(targetmail))
throw new Exception(string.Format("No email specified, please use --{0}", "send-mail-to"));
if (m_options.Loglevel == Logging.LogMessageType.Error)
m_options.RawOptions["log-level"] = Logging.LogMessageType.Warning.ToString();
m_options.RawOptions["disable-module"] = string.Join(
",",
DynamicLoader.GenericLoader.Modules
.Where(m =>
!(m is Library.Interface.IConnectionModule) && m.GetType().FullName != "Duplicati.Library.Modules.Builtin.SendMail"
!(m is Modules.Builtin.SendMail)
)
.Select(x => x.Key)
);
return RunAction(new SendMailResults(), result =>
/// Forward all messages from the email module to the message sink
var filtertag = Logging.Log.LogTagFromType<Modules.Builtin.SendMail>();
using (Logging.Log.StartScope(m_messageSink.WriteMessage, x => x.Tag.Contains(filtertag)))
{
result.Lines = new string[0];
System.Threading.Thread.Sleep(5);
});
return RunAction(new SendMailResults(), result =>
{
result.Lines = new string[0];
System.Threading.Thread.Sleep(5);
});
}
}
public Library.Interface.IVacuumResults Vacuum()
@@ -587,7 +519,7 @@ namespace Duplicati.Library.Main
}
private T RunAction<T>(T result, Action<T> method)
where T : ISetCommonOptions, ITaskControl, Logging.ILog
where T : ISetCommonOptions, ITaskControl, Logging.ILogDestination
{
var tmp = new string[0];
IFilter tempfilter = null;
@@ -595,33 +527,39 @@ namespace Duplicati.Library.Main
}
private T RunAction<T>(T result, ref string[] paths, Action<T> method)
where T : ISetCommonOptions, ITaskControl, Logging.ILog
where T : ISetCommonOptions, ITaskControl, Logging.ILogDestination
{
IFilter tempfilter = null;
return RunAction<T>(result, ref paths, ref tempfilter, method);
}
private T RunAction<T>(T result, ref IFilter filter, Action<T> method)
where T : ISetCommonOptions, ITaskControl, Logging.ILog
where T : ISetCommonOptions, ITaskControl, Logging.ILogDestination
{
var tmp = new string[0];
return RunAction<T>(result, ref tmp, ref filter, method);
}
private T RunAction<T>(T result, ref string[] paths, ref IFilter filter, Action<T> method)
where T : ISetCommonOptions, ITaskControl, Logging.ILog
where T : ISetCommonOptions, ITaskControl, Logging.ILogDestination
{
using (Logging.Log.StartScope(result))
m_logTarget = new ControllerMultiLogTarget(result, Logging.LogMessageType.Information, null);
using (Logging.Log.StartScope(m_logTarget, null))
{
m_logTarget.AddTarget(m_messageSink, m_options.ConsoleLoglevel, m_options.ConsoleLogFilter);
result.MessageSink = m_messageSink;
try
{
m_currentTask = result;
m_currentTaskThread = System.Threading.Thread.CurrentThread;
SetupCommonOptions(result, ref paths, ref filter);
Logging.Log.WriteInformationMessage(LOGTAG, "StartingOperation", Strings.Controller.StartingOperationMessage(m_options.MainAction));
result.WriteLogMessageDirect(Strings.Controller.StartingOperationMessage(m_options.MainAction), Logging.LogMessageType.Information, null);
using (new Logging.Timer(string.Format("Running {0}", result.MainOperation)))
using (new ProcessController(m_options))
using (new Logging.Timer(LOGTAG, string.Format("Run{0}", result.MainOperation), string.Format("Running {0}", result.MainOperation)))
using (new Logging.RepeatingLogScope())
method(result);
if (result.EndTime.Ticks == 0)
@@ -630,7 +568,7 @@ namespace Duplicati.Library.Main
OnOperationComplete(result);
result.WriteLogMessageDirect(Strings.Controller.CompletedOperationMessage(m_options.MainAction), Logging.LogMessageType.Information, null);
Logging.Log.WriteInformationMessage(LOGTAG, "CompletedOperation", Strings.Controller.CompletedOperationMessage(m_options.MainAction));
return result;
}
@@ -643,7 +581,7 @@ namespace Duplicati.Library.Main
OnOperationComplete(ex);
result.WriteLogMessageDirect(Strings.Controller.FailedOperationMessage(m_options.MainAction, ex.Message), Logging.LogMessageType.Error, ex);
Logging.Log.WriteErrorMessage(LOGTAG, "FailedOperation", ex, Strings.Controller.FailedOperationMessage(m_options.MainAction, ex.Message));
throw;
}
@@ -682,15 +620,14 @@ namespace Duplicati.Library.Main
foreach (KeyValuePair<bool, Library.Interface.IGenericModule> mx in m_options.LoadedModules)
if (mx.Key && mx.Value is Duplicati.Library.Interface.IGenericCallbackModule)
try { ((Duplicati.Library.Interface.IGenericCallbackModule)mx.Value).OnFinish(result); }
catch (Exception ex) { Logging.Log.WriteMessage(string.Format("OnFinish callback {0} failed: {1}", mx.Key, ex.Message), Duplicati.Library.Logging.LogMessageType.Warning, ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, $"OnFinishError{mx.Key}", ex, "OnFinish callback {0} failed: {1}", mx.Key, ex.Message); }
foreach (KeyValuePair<bool, Library.Interface.IGenericModule> mx in m_options.LoadedModules)
if (mx.Key && mx.Value is IDisposable)
try { ((IDisposable)mx.Value).Dispose(); }
catch (Exception ex) { Logging.Log.WriteMessage(string.Format("Dispose for {0} failed: {1}", mx.Key, ex.Message), Duplicati.Library.Logging.LogMessageType.Warning, ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, $"DisposeError{mx.Key}", ex, "Dispose for {0} failed: {1}", mx.Key, ex.Message); }
m_options.LoadedModules.Clear();
OperationRunning(false);
}
if (m_resetPriority != null)
@@ -724,24 +661,16 @@ namespace Duplicati.Library.Main
}
}
if (m_logfilescope != null)
if (m_logTarget != null)
{
m_logfilescope.Dispose();
m_logfilescope = null;
m_logTarget.Dispose();
m_logTarget = null;
}
if (m_logfile != null)
{
m_logfile.Dispose();
m_logfile = null;
}
}
private void SetupCommonOptions(ISetCommonOptions result, ref string[] paths, ref IFilter filter)
{
m_options.MainAction = result.MainOperation;
result.MessageSink = m_messageSink;
switch (m_options.MainAction)
{
@@ -815,22 +744,20 @@ namespace Duplicati.Library.Main
}
m_options.RawOptions.Remove("filter"); // "--filter" is not a supported command line option
OperationRunning(true);
if (m_options.HasLoglevel)
Library.Logging.Log.LogLevel = m_options.Loglevel;
if (!string.IsNullOrEmpty(m_options.Logfile))
{
var path = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(m_options.Logfile));
if (!System.IO.Directory.Exists(path))
System.IO.Directory.CreateDirectory(path);
m_logfilescope = Logging.Log.StartScope(m_logfile = new Library.Logging.StreamLog(m_options.Logfile));
m_logTarget.AddTarget(
new Library.Logging.StreamLogDestination(m_options.Logfile),
m_options.LogFileLoglevel,
m_options.LogFileLogFilter
);
}
result.VerboseErrors = m_options.DebugOutput;
result.VerboseOutput = m_options.Verbose;
if (m_options.HasTempDir)
{
@@ -861,7 +788,7 @@ namespace Duplicati.Library.Main
}
catch (Exception ex) // or only: MissingMethodException
{
Library.Logging.Log.WriteMessage(Strings.Controller.FailedForceLocaleError(ex.Message), Logging.LogMessageType.Warning);
Library.Logging.Log.WriteWarningMessage(LOGTAG, "LocaleChangeError", ex, Strings.Controller.FailedForceLocaleError(ex.Message));
m_doResetLocale = false;
m_resetLocale = m_resetLocaleUI = null;
}
@@ -876,15 +803,14 @@ namespace Duplicati.Library.Main
if (string.IsNullOrEmpty(m_options.Dbpath))
m_options.Dbpath = DatabaseLocator.GetDatabasePath(m_backend, m_options);
ValidateOptions(result);
ValidateOptions();
}
/// <summary>
/// This function will examine all options passed on the commandline, and test for unsupported or deprecated values.
/// Any errors will be logged into the statistics module.
/// </summary>
/// <param name="log">The log instance</param>
private void ValidateOptions(ILogWriter log)
private void ValidateOptions()
{
// Check if only one of the retention options is set
var selectedRetentionOptions = new List<String>();
@@ -907,33 +833,30 @@ namespace Duplicati.Library.Main
if (selectedRetentionOptions.Count() > 1)
{
throw new Interface.UserInformationException(string.Format("Setting multiple retention options ({0}) is not permitted",
String.Join(", ", selectedRetentionOptions.Select(x => "--" + x))));
String.Join(", ", selectedRetentionOptions.Select(x => "--" + x))), "MultipleRetentionOptionsNotSupported");
}
// Check Prefix
if (!string.IsNullOrWhiteSpace(m_options.Prefix) && m_options.Prefix.Contains("-"))
throw new Interface.UserInformationException("The prefix cannot contain hyphens (-)");
throw new Interface.UserInformationException("The prefix cannot contain hyphens (-)", "PrefixCannotContainHyphens");
//Check validity of retention-policy option value
try
{
foreach (var configEntry in m_options.RetentionPolicy)
{
if (configEntry.Value >= configEntry.Key)
if (!configEntry.IsKeepAllVersions() && !configEntry.IsUnlimtedTimeframe() &&
configEntry.Interval >= configEntry.Timeframe)
{
throw new Interface.UserInformationException("A time frame cannot be smaller than its interval");
throw new Interface.UserInformationException("An interval cannot be bigger than the timeframe it is in", "IntervalCannotBeBiggerThanTimeFrame");
}
}
}
catch (Exception e) // simply reading the option value might also result in an exception due to incorrect formatting
{
throw new Interface.UserInformationException(string.Format("An error occoured while processing the value of --{0}", "retention-policy"), e);
throw new Interface.UserInformationException(string.Format("An error occoured while processing the value of --{0}", "retention-policy"), "RetentionPolicyParseError", e);
}
//No point in going through with this if we can't report
if (log == null)
return;
//Keep a list of all supplied options
Dictionary<string, string> ropts = new Dictionary<string, string>(m_options.RawOptions);
@@ -980,7 +903,7 @@ namespace Duplicati.Library.Main
foreach (Library.Interface.ICommandLineArgument a in l)
{
if (supportedOptions.ContainsKey(a.Name) && Array.IndexOf(Options.KnownDuplicates, a.Name.ToLower()) < 0)
log.AddWarning(Strings.Controller.DuplicateOptionNameWarning(a.Name), null);
Logging.Log.WriteWarningMessage(LOGTAG, "DuplicateOption", null, Strings.Controller.DuplicateOptionNameWarning(a.Name));
supportedOptions[a.Name] = a;
@@ -988,7 +911,7 @@ namespace Duplicati.Library.Main
foreach (string s in a.Aliases)
{
if (supportedOptions.ContainsKey(s) && Array.IndexOf(Options.KnownDuplicates, s.ToLower()) < 0)
log.AddWarning(Strings.Controller.DuplicateOptionNameWarning(s), null);
Logging.Log.WriteWarningMessage(LOGTAG, "DuplicateOption", null, Strings.Controller.DuplicateOptionNameWarning(s));
supportedOptions[s] = a;
}
@@ -1007,7 +930,7 @@ namespace Duplicati.Library.Main
if (a.Name != s)
optname += " (" + s + ")";
log.AddWarning(Strings.Controller.DeprecatedOptionUsedWarning(optname, a.DeprecationMessage), null);
Logging.Log.WriteWarningMessage(LOGTAG, "DeprecatedOption", null, Strings.Controller.DeprecatedOptionUsedWarning(optname, a.DeprecationMessage), null);
}
}
@@ -1018,9 +941,9 @@ namespace Duplicati.Library.Main
foreach (string s in ropts.Keys)
if (!supportedOptions.ContainsKey(s))
if (disabledModuleOptions.ContainsKey(s))
log.AddWarning(Strings.Controller.UnsupportedOptionDisabledModuleWarning(s, disabledModuleOptions[s]), null);
Logging.Log.WriteWarningMessage(LOGTAG, "UnsupportedDisabledModule", null, Strings.Controller.UnsupportedOptionDisabledModuleWarning(s, disabledModuleOptions[s]), null);
else
log.AddWarning(Strings.Controller.UnsupportedOptionWarning(s), null);
Logging.Log.WriteWarningMessage(LOGTAG, "UnsupportedOption", null, Strings.Controller.UnsupportedOptionWarning(s), null);
//Look at the value supplied for each argument and see if is valid according to its type
foreach (string s in ropts.Keys)
@@ -1030,10 +953,14 @@ namespace Duplicati.Library.Main
{
string validationMessage = ValidateOptionValue(arg, s, ropts[s]);
if (validationMessage != null)
log.AddWarning(validationMessage, null);
Logging.Log.WriteWarningMessage(LOGTAG, "OptionValidationError", null, validationMessage);
}
}
// For now, warn not to use 7z
if (string.Equals(m_options.CompressionModule, "7z", StringComparison.OrdinalIgnoreCase))
Logging.Log.WriteWarningMessage(LOGTAG, "7zModuleHasIssues", null, "The 7z compression module has known issues and should only be used for experimental purposes");
//TODO: Based on the action, see if all options are relevant
}
@@ -0,0 +1,102 @@
// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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;
using Duplicati.Library.Logging;
namespace Duplicati.Library.Main
{
/// <summary>
/// The log target handler for the controller, which sends to multiple log targets
/// </summary>
public class ControllerMultiLogTarget : Logging.ILogDestination, IDisposable
{
/// <summary>
/// The list of log targets to handle
/// </summary>
private List<Tuple<ILogDestination, LogMessageType, Library.Utility.IFilter>> m_targets = new List<Tuple<ILogDestination, LogMessageType, Library.Utility.IFilter>>();
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Main.ControllerMultiLogTarget"/> class.
/// </summary>
/// <param name="target">The log target.</param>
/// <param name="loglevel">The minimum log level to consider</param>
/// <param name="filter">The log filter.</param>
public ControllerMultiLogTarget(ILogDestination target, Logging.LogMessageType loglevel, Library.Utility.IFilter filter)
{
AddTarget(target, loglevel, filter);
}
/// <summary>
/// Adds the target to the list.
/// </summary>
/// <param name="target">The log target.</param>
/// <param name="loglevel">The minimum log level to consider</param>
/// <param name="filter">The log filter.</param>
public void AddTarget(ILogDestination target, LogMessageType loglevel, Library.Utility.IFilter filter)
{
if (target == null)
return;
m_targets.Add(new Tuple<ILogDestination, LogMessageType, Library.Utility.IFilter>(target, loglevel, filter ?? new Library.Utility.FilterExpression()));
}
/// <summary>
/// Releases all resource used by the <see cref="T:Duplicati.Library.Main.ControllerMultiLogTarget"/> object.
/// </summary>
/// <remarks>Call <see cref="Dispose"/> when you are finished using the
/// <see cref="T:Duplicati.Library.Main.ControllerMultiLogTarget"/>. The <see cref="Dispose"/> method leaves the
/// <see cref="T:Duplicati.Library.Main.ControllerMultiLogTarget"/> in an unusable state. After calling
/// <see cref="Dispose"/>, you must release all references to the
/// <see cref="T:Duplicati.Library.Main.ControllerMultiLogTarget"/> so the garbage collector can reclaim the
/// memory that the <see cref="T:Duplicati.Library.Main.ControllerMultiLogTarget"/> was occupying.</remarks>
public void Dispose()
{
foreach (var m in m_targets)
(m.Item1 as IDisposable)?.Dispose();
m_targets.Clear();
}
/// <summary>
/// Writes the message to all the destinations.
/// </summary>
/// <param name="entry">Entry.</param>
public void WriteMessage(LogEntry entry)
{
foreach (var e in m_targets)
{
var found = e.Item3.Matches(entry.Tag, out var result, out var match);
// If there is a filter match, use that
if (found)
{
if (!result)
continue;
}
else
{
// Otherwise, filter by log-level
if (entry.Level < e.Item2)
continue;
}
// If we get here, write the message
e.Item1.WriteMessage(entry);
}
}
}
}
@@ -8,6 +8,11 @@ namespace Duplicati.Library.Main.Database
{
public static class ExtensionMethods
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ExtensionMethods));
public static void AddParameters(this System.Data.IDbCommand self, int count)
{
for(var i = 0; i < count; i++)
@@ -45,7 +50,7 @@ namespace Duplicati.Library.Main.Database
self.AddParameter(n);
}
using(new Logging.Timer(string.Format("ExecuteNonQuery: {0}", self.CommandText)))
using(new Logging.Timer(LOGTAG, "ExecuteNonQuery", string.Format("ExecuteNonQuery: {0}", self.CommandText)))
return self.ExecuteNonQuery();
}
@@ -61,7 +66,7 @@ namespace Duplicati.Library.Main.Database
self.AddParameter(n);
}
using(new Logging.Timer(string.Format("ExecuteScalar: {0}", self.CommandText)))
using(new Logging.Timer(LOGTAG, "ExecuteScalar", string.Format("ExecuteScalar: {0}", self.CommandText)))
return self.ExecuteScalar();
}
@@ -87,7 +92,7 @@ namespace Duplicati.Library.Main.Database
self.AddParameter(n);
}
using(new Logging.Timer(string.Format("ExecuteScalarInt64: {0}", self.CommandText)))
using(new Logging.Timer(LOGTAG, "ExecuteScalarInt64", string.Format("ExecuteScalarInt64: {0}", self.CommandText)))
using(var rd = self.ExecuteReader())
if (rd.Read())
return ConvertValueToInt64(rd, 0, defaultvalue);
@@ -107,7 +112,7 @@ namespace Duplicati.Library.Main.Database
self.AddParameter(n);
}
using(new Logging.Timer(string.Format("ExecuteReader: {0}", self.CommandText)))
using(new Logging.Timer(LOGTAG, "ExcuteReader", string.Format("ExecuteReader: {0}", self.CommandText)))
return self.ExecuteReader();
}
@@ -35,7 +35,7 @@ namespace Duplicati.Library.Main
private readonly ulong m_entries;
/// <summary>
/// Initializes a new instance of the <see cref="Duplicati.Library.Main.HashLookupHelper"/> class.
/// Initializes a new instance of the <see cref="Duplicati.Library.Main.HashLookupHelper{T}"/> class.
/// </summary>
/// <param name="maxmemory">The maximum amount of bytes to use for the lookup table</param>
public HashLookupHelper (ulong maxmemory)
@@ -9,6 +9,11 @@ namespace Duplicati.Library.Main.Database
{
internal class LocalBackupDatabase : LocalDatabase
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<LocalBackupDatabase>();
private class PathEntryKeeper
{
public DateTime Lastmodified;
@@ -202,7 +207,7 @@ namespace Duplicati.Library.Main.Database
@" AND ""F"".""ID"" = ""E"".""BlocksetID"" ";
if (m_pathLookup != null)
using(new Logging.Timer("Build path lastmodified lookup table"))
using(new Logging.Timer(LOGTAG, "BuildLastModified", "Build path lastmodified lookup table"))
using (var rd = cmd.ExecuteReader(string.Format(@" SELECT ""FileID"", ""Lastmodified"", ""Length"", ""Path"", ""Metahash"", ""Metasize"" FROM ({0}) WHERE ""BlocksetID"" >= 0 ", scantableDefinition)))
while (rd.Read())
{
@@ -218,7 +223,7 @@ namespace Duplicati.Library.Main.Database
if (m_pathLookup != null)
try
{
using(new Logging.Timer("Build path lookup table"))
using(new Logging.Timer(LOGTAG, "BuildPathTable", "Build path lookup table"))
using (var rd = cmd.ExecuteReader(string.Format(@" SELECT ""Path"", ""BlocksetID"", ""MetadataID"", ""ID"" FROM ""File"" ")))
while (rd.Read())
{
@@ -265,8 +270,8 @@ namespace Duplicati.Library.Main.Database
}
catch (Exception ex)
{
Logging.Log.WriteMessage(string.Format("Failed to create block cache, this could mean you have hash collisions in your table, the hash that failed is {0}. Error message: {1}.", failedhash, ex.Message), Logging.LogMessageType.Warning);
Logging.Log.WriteMessage(string.Format("Disabling block cache due to error"), Logging.LogMessageType.Warning);
Logging.Log.WriteWarningMessage(LOGTAG, "BlockCacheFailure", ex, "Failed to create block cache, this could mean you have hash collisions in your table, the hash that failed is {0}. Error message: {1}.", failedhash, ex.Message);
Logging.Log.WriteWarningMessage(LOGTAG, "BlockCacheFailure", null, "Disabling block cache due to error");
}
}
}
@@ -285,7 +290,7 @@ namespace Duplicati.Library.Main.Database
if (exsize == size)
return false;
Logging.Log.WriteMessage(string.Format("Found hash collision on {0}, sizes {1} vs {2}. Disabling cache from now on.", key, size, exsize), Logging.LogMessageType.Warning);
Logging.Log.WriteWarningMessage(LOGTAG, "HashCollisionsFound", null, "Found hash collision on {0}, sizes {1} vs {2}. Disabling cache from now on.", key, size, exsize);
m_blockCache = null;
}
@@ -366,14 +371,14 @@ namespace Duplicati.Library.Main.Database
var c = m_insertblocksetentryCommand.ExecuteNonQuery();
if (c != 1)
{
m_result.AddError(string.Format("Checking errors, related to #1400. Unexpected result count: {0}, expected {1}, hash: {2}, size: {3}, blocksetid: {4}, ix: {5}, fullhash: {6}, fullsize: {7}", c, 1, h, exsize, blocksetid, ix, filehash, size), null);
Logging.Log.WriteErrorMessage(LOGTAG, "CheckingErrorsForIssue1400", null, "Checking errors, related to #1400. Unexpected result count: {0}, expected {1}, hash: {2}, size: {3}, blocksetid: {4}, ix: {5}, fullhash: {6}, fullsize: {7}", c, 1, h, exsize, blocksetid, ix, filehash, size);
using (var cmd = m_connection.CreateCommand(tr.Parent))
{
var bid = cmd.ExecuteScalarInt64(@"SELECT ""ID"" FROM ""Block"" WHERE ""Hash"" = ?", -1, h);
if (bid == -1)
throw new Exception(string.Format("Could not find any blocks with the given hash: {0}", h));
foreach(var rd in cmd.ExecuteReaderEnumerable(@"SELECT ""Size"" FROM ""Block"" WHERE ""Hash"" = ?", h))
m_result.AddError(string.Format("Found block with ID {0} and hash {1} and size {2}", bid, h, rd.ConvertValueToInt64(0, -1)), null);
Logging.Log.WriteErrorMessage(LOGTAG, "FoundIssue1400Error", null, "Found block with ID {0} and hash {1} and size {2}", bid, h, rd.ConvertValueToInt64(0, -1));
}
throw new Exception(string.Format("Unexpected result count: {0}, expected {1}, check log for more messages", c, 1));
@@ -648,9 +653,9 @@ namespace Duplicati.Library.Main.Database
finally
{
try { cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"";", tmpName1)); }
catch (Exception ex) { m_result.AddWarning("Dispose temp table error", ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DisposeError", ex, "Dispose temp table error"); }
try { cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"";", tmpName2)); }
catch (Exception ex) { m_result.AddWarning("Dispose temp table error", ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DisposeError", ex, "Dispose temp table error"); }
}
}
}
@@ -21,6 +21,11 @@ namespace Duplicati.Library.Main.Database
{
internal class LocalBugReportDatabase : LocalDatabase
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalBugReportDatabase));
public LocalBugReportDatabase(string path)
: base(path, "BugReportCreate", false)
{
@@ -59,7 +64,7 @@ namespace Duplicati.Library.Main.Database
cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", tablename));
using(new Logging.Timer("CommitUpdateBugReport"))
using(new Logging.Timer(LOGTAG, "CommitUpdateBugReport", "CommitUpdateBugReport"))
tr.Commit();
cmd.Transaction = null;
@@ -8,6 +8,11 @@ namespace Duplicati.Library.Main.Database
{
internal class LocalDatabase : IDisposable
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalDatabase));
protected readonly System.Data.IDbConnection m_connection;
protected readonly long m_operationid = -1;
@@ -233,7 +238,7 @@ namespace Duplicati.Library.Main.Database
qs += "?,";
}
else
m_result.AddWarning(string.Format("Skipping invalid version: {0}", v), null);
Logging.Log.WriteWarningMessage(LOGTAG, "SkipInvalidVersion", null, "Skipping invalid version: {0}", v);
if (qs.Length > 0)
@@ -518,9 +523,9 @@ namespace Duplicati.Library.Main.Database
res.Add(rd.ConvertValueToInt64(0));
if (res.Count == 0)
throw new Duplicati.Library.Interface.UserInformationException("No backup at the specified date");
throw new Duplicati.Library.Interface.UserInformationException("No backup at the specified date", "NoBackupAtDate");
else
m_result.AddWarning(string.Format("Restore time or version did not match any existing backups, selecting newest backup"), null);
Logging.Log.WriteWarningMessage(LOGTAG, "RestoreTimeNoMatch", null, "Restore time or version did not match any existing backups, selecting newest backup");
}
return res;
@@ -1069,7 +1074,7 @@ ORDER BY
var metablocklisthash = rd.GetValue(7).ToString();
if (path == lastpath)
m_result.AddWarning(string.Format("Duplicate path detected: {0}!", path), null);
Logging.Log.WriteWarningMessage(LOGTAG, "DuplicatePathFound", null, "Duplicate path detected: {0}!", path);
lastpath = path;
@@ -1392,9 +1397,6 @@ ORDER BY
(prop, item) =>
!typeof(IBackendProgressUpdater).IsAssignableFrom(prop.PropertyType) &&
!typeof(IMessageSink).IsAssignableFrom(prop.PropertyType) &&
!typeof(ILogWriter).IsAssignableFrom(prop.PropertyType) &&
prop.Name != "VerboseOutput" &&
prop.Name != "VerboseErrors" &&
!(prop.Name == "MainOperation" && item is BackendWriter) &&
!(prop.Name == "EndTime" && item is BackendWriter) &&
!(prop.Name == "Duration" && item is BackendWriter) &&
@@ -25,6 +25,11 @@ namespace Duplicati.Library.Main.Database
{
internal class LocalDeleteDatabase : LocalDatabase
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<LocalDeleteDatabase>();
private System.Data.IDbCommand m_moveBlockToNewVolumeCommand;
public LocalDeleteDatabase(string path, string operation)
@@ -164,7 +169,7 @@ namespace Duplicati.Library.Main.Database
IEnumerable<string> CompactableVolumes { get; }
bool ShouldReclaim { get; }
bool ShouldCompact { get; }
void ReportCompactData(ILogWriter log);
void ReportCompactData();
}
private class CompactReport : ICompactReport
@@ -204,26 +209,23 @@ namespace Duplicati.Library.Main.Database
m_smallvolumecount = m_smallvolumes.Count();
}
public void ReportCompactData(ILogWriter log)
public void ReportCompactData()
{
var wastepercentage = ((m_wastedspace / (float)m_fullsize) * 100);
if (log.VerboseOutput)
{
log.AddVerboseMessage(string.Format("Found {0} fully deletable volume(s)", m_deletablevolumes));
log.AddVerboseMessage(string.Format("Found {0} small volumes(s) with a total size of {1}", m_smallvolumes.Count(), Library.Utility.Utility.FormatSizeString(m_smallspace)));
log.AddVerboseMessage(string.Format("Found {0} volume(s) with a total of {1:F2}% wasted space ({2} of {3})", m_wastevolumes.Count(), wastepercentage, Library.Utility.Utility.FormatSizeString(m_wastedspace), Library.Utility.Utility.FormatSizeString(m_fullsize)));
}
Logging.Log.WriteVerboseMessage(LOGTAG, "FullyDeletableCount", "Found {0} fully deletable volume(s)", m_deletablevolumes);
Logging.Log.WriteVerboseMessage(LOGTAG, "SmallVolumeCount", "Found {0} small volumes(s) with a total size of {1}", m_smallvolumes.Count(), Library.Utility.Utility.FormatSizeString(m_smallspace));
Logging.Log.WriteVerboseMessage(LOGTAG, "WastedSpaceVolumes", "Found {0} volume(s) with a total of {1:F2}% wasted space ({2} of {3})", m_wastevolumes.Count(), wastepercentage, Library.Utility.Utility.FormatSizeString(m_wastedspace), Library.Utility.Utility.FormatSizeString(m_fullsize));
if (m_deletablevolumes > 0)
log.AddMessage(string.Format("Compacting because there are {0} fully deletable volume(s)", m_deletablevolumes));
Logging.Log.WriteInformationMessage(LOGTAG, "CompactReason", "Compacting because there are {0} fully deletable volume(s)", m_deletablevolumes);
else if (wastepercentage >= m_wastethreshold && m_wastevolumes.Count() >= 2)
log.AddMessage(string.Format("Compacting because there is {0:F2}% wasted space and the limit is {1}%", wastepercentage, m_wastethreshold));
Logging.Log.WriteInformationMessage(LOGTAG, "CompactReason", "Compacting because there is {0:F2}% wasted space and the limit is {1}%", wastepercentage, m_wastethreshold);
else if (m_smallspace > m_volsize)
log.AddMessage(string.Format("Compacting because there are {0} in small volumes and the volume size is {1}", Library.Utility.Utility.FormatSizeString(m_smallspace), Library.Utility.Utility.FormatSizeString(m_volsize)));
Logging.Log.WriteInformationMessage(LOGTAG, "CompactReason", "Compacting because there are {0} in small volumes and the volume size is {1}", Library.Utility.Utility.FormatSizeString(m_smallspace), Library.Utility.Utility.FormatSizeString(m_volsize));
else if (m_smallvolumecount > m_maxsmallfilecount)
log.AddMessage(string.Format("Compacting because there are {0} small volumes and the maximum is {1}", m_smallvolumecount, m_maxsmallfilecount));
Logging.Log.WriteInformationMessage(LOGTAG, "CompactReason", "Compacting because there are {0} small volumes and the maximum is {1}", m_smallvolumecount, m_maxsmallfilecount);
else
log.AddMessage("Compacting not required");
Logging.Log.WriteInformationMessage(LOGTAG, "CompactReason", "Compacting not required");
}
public bool ShouldReclaim
@@ -159,7 +159,7 @@ namespace Duplicati.Library.Main.Database
RemovedFileSize = cmd.ExecuteScalarInt64(string.Format(@"SELECT SUM(""C"".""Length"") FROM ""{0}"" A, ""File"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" ", m_tablename), 0);
var filesetcount = cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FilesetID"" = " + ParentID), 0);
if (filesetcount == RemovedFileCount)
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Refusing to purge {0} files from fileset with ID {1}, as that would remove the entire fileset.\nTo delete a fileset, use the \"delete\" command.", RemovedFileCount, ParentID));
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Refusing to purge {0} files from fileset with ID {1}, as that would remove the entire fileset.\nTo delete a fileset, use the \"delete\" command.", RemovedFileCount, ParentID), "PurgeWouldRemoveEntireFileset");
}
}
@@ -8,6 +8,11 @@ namespace Duplicati.Library.Main.Database
{
internal partial class LocalRecreateDatabase : LocalRestoreDatabase
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalRecreateDatabase));
private class PathEntryKeeper
{
private SortedList<KeyValuePair<long, long>, long> m_versions;
@@ -274,7 +279,7 @@ namespace Duplicati.Library.Main.Database
}
catch (Exception ex)
{
m_result.AddError("Blockset insert failed, comitting temporary tables for trace purposes", ex);
Logging.Log.WriteErrorMessage(LOGTAG, "BlocksetInsertFailed", ex, "Blockset insert failed, comitting temporary tables for trace purposes");
using (var fixcmd = m_connection.CreateCommand())
{
@@ -408,7 +413,7 @@ namespace Duplicati.Library.Main.Database
}
if (c != expectedblocklisthashes)
m_result.AddWarning(string.Format("Mismatching number of blocklist hashes detected on blockset {2}. Expected {0} blocklist hashes, but found {1}", expectedblocklisthashes, c, blocksetid), null);
Logging.Log.WriteWarningMessage(LOGTAG, "MismatchInBlocklistHashCount", null, "Mismatching number of blocklist hashes detected on blockset {2}. Expected {0} blocklist hashes, but found {1}", expectedblocklisthashes, c, blocksetid);
return blocksetid;
}
@@ -23,6 +23,11 @@ namespace Duplicati.Library.Main.Database
{
internal class LocalRepairDatabase : LocalDatabase
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalRepairDatabase));
public LocalRepairDatabase(string path)
: base(path, "Repair", true)
{
@@ -250,7 +255,7 @@ namespace Duplicati.Library.Main.Database
var x = cmd.ExecuteScalarInt64(sql_count, 0);
if (x > 1)
{
m_result.AddMessage("Found duplicate metadatahashes, repairing");
Logging.Log.WriteInformationMessage(LOGTAG, "DuplicateMetadataHashes", "Found duplicate metadatahashes, repairing");
var tablename = "TmpFile-" + Guid.NewGuid().ToString("N");
@@ -288,9 +293,9 @@ namespace Duplicati.Library.Main.Database
cmd.CommandText = sql_count;
x = cmd.ExecuteScalarInt64(0);
if (x > 1)
throw new Duplicati.Library.Interface.UserInformationException("Repair failed, there are still duplicate metadatahashes!");
throw new Duplicati.Library.Interface.UserInformationException("Repair failed, there are still duplicate metadatahashes!", "DuplicateHashesRepairFailed");
m_result.AddMessage("Duplicate metadatahashes repaired succesfully");
Logging.Log.WriteInformationMessage(LOGTAG, "DuplicateMetadataHashesFixed", "Duplicate metadatahashes repaired succesfully");
tr.Commit();
}
}
@@ -306,7 +311,7 @@ namespace Duplicati.Library.Main.Database
var x = cmd.ExecuteScalarInt64(sql_count, 0);
if (x > 0)
{
m_result.AddMessage("Found duplicate file entries, repairing");
Logging.Log.WriteInformationMessage(LOGTAG, "DuplicateFileEntries", "Found duplicate file entries, repairing");
var sql = @"SELECT ""ID"", ""Path"", ""BlocksetID"", ""MetadataID"", ""Entries"" FROM (
SELECT MIN(""ID"") AS ""ID"", ""Path"", ""BlocksetID"", ""MetadataID"", COUNT(*) as ""Entries"" FROM ""File"" GROUP BY ""Path"", ""BlocksetID"", ""MetadataID"")
@@ -323,9 +328,9 @@ namespace Duplicati.Library.Main.Database
cmd.CommandText = sql_count;
x = cmd.ExecuteScalarInt64(0);
if (x > 1)
throw new Duplicati.Library.Interface.UserInformationException("Repair failed, there are still duplicate file entries!");
throw new Duplicati.Library.Interface.UserInformationException("Repair failed, there are still duplicate file entries!", "DuplicateFilesRepairFailed");
m_result.AddMessage("Duplicate file entries repaired succesfully");
Logging.Log.WriteInformationMessage(LOGTAG, "DuplicateFileEntriesFixed", "Duplicate file entries repaired succesfully");
tr.Commit();
}
}
@@ -350,7 +355,7 @@ namespace Duplicati.Library.Main.Database
var itemswithnoblocklisthash = cmd.ExecuteScalarInt64(countsql, 0);
if (itemswithnoblocklisthash != 0)
{
m_result.AddMessage(string.Format("Found {0} missing blocklisthash entries, repairing", itemswithnoblocklisthash));
Logging.Log.WriteInformationMessage(LOGTAG, "MissingBlocklistHashes", "Found {0} missing blocklisthash entries, repairing", itemswithnoblocklisthash);
using(var c2 = m_connection.CreateCommand(tr))
using(var c3 = m_connection.CreateCommand(tr))
using(var c4 = m_connection.CreateCommand(tr))
@@ -429,9 +434,9 @@ namespace Duplicati.Library.Main.Database
itemswithnoblocklisthash = cmd.ExecuteScalarInt64(countsql, 0);
if (itemswithnoblocklisthash != 0)
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Failed to repair, after repair {0} blocklisthashes were missing", itemswithnoblocklisthash));
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Failed to repair, after repair {0} blocklisthashes were missing", itemswithnoblocklisthash), "MissingBlocklistHashesRepairFailed");
m_result.AddMessage("Missing blocklisthashes repaired succesfully");
Logging.Log.WriteInformationMessage(LOGTAG, "MissingBlocklisthashesRepaired", "Missing blocklisthashes repaired succesfully");
tr.Commit();
}
}
@@ -450,7 +455,7 @@ namespace Duplicati.Library.Main.Database
var x = cmd.ExecuteScalarInt64(sql_count, 0);
if (x > 0)
{
m_result.AddMessage("Found duplicate blocklisthash entries, repairing");
Logging.Log.WriteInformationMessage(LOGTAG, "DuplicateBlocklistHashes", "Found duplicate blocklisthash entries, repairing");
var unique_count = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM (SELECT DISTINCT ""BlocksetID"", ""Index"" FROM ""BlocklistHash"")", 0);
@@ -474,7 +479,7 @@ namespace Duplicati.Library.Main.Database
var real_count = cmd.ExecuteScalarInt64(@"SELECT Count(*) FROM ""BlocklistHash""", 0);
if (real_count != unique_count)
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Failed to repair, result should have been {0} blocklist hashes, but result was {1} blocklist hashes", unique_count, real_count));
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Failed to repair, result should have been {0} blocklist hashes, but result was {1} blocklist hashes", unique_count, real_count), "DuplicateBlocklistHashesRepairFailed");
try
{
@@ -482,10 +487,10 @@ namespace Duplicati.Library.Main.Database
}
catch(Exception ex)
{
throw new Duplicati.Library.Interface.UserInformationException("Repaired blocklisthashes, but the database was broken afterwards, rolled back changes", ex);
throw new Duplicati.Library.Interface.UserInformationException("Repaired blocklisthashes, but the database was broken afterwards, rolled back changes", "DuplicateBlocklistHashesRepairFailed", ex);
}
m_result.AddMessage("Duplicate blocklisthashes repaired succesfully");
Logging.Log.WriteInformationMessage(LOGTAG, "DuplicateBlocklistHashesRepaired", "Duplicate blocklisthashes repaired succesfully");
tr.Commit();
}
}
@@ -8,6 +8,11 @@ namespace Duplicati.Library.Main.Database
{
internal partial class LocalRestoreDatabase : LocalDatabase
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalRestoreDatabase));
protected string m_temptabsetguid = Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray());
protected string m_tempfiletable;
protected string m_tempblocktable;
@@ -173,7 +178,7 @@ namespace Duplicati.Library.Main.Database
}
public Tuple<long, long> PrepareRestoreFilelist(DateTime restoretime, long[] versions, Library.Utility.IFilter filter, ILogWriter log)
public Tuple<long, long> PrepareRestoreFilelist(DateTime restoretime, long[] versions, Library.Utility.IFilter filter)
{
var guid = Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray());
@@ -195,7 +200,7 @@ namespace Duplicati.Library.Main.Database
.Select(pair => pair.index + 1)
.FirstOrDefault() - 1;
log.AddMessage(string.Format("Searching backup {0} ({1}) ...", ix, m_restoreTime));
Logging.Log.WriteInformationMessage(LOGTAG, "SearchingBackup", "Searching backup {0} ({1}) ...", ix, m_restoreTime);
cmd.Parameters.Clear();
@@ -251,13 +256,13 @@ namespace Duplicati.Library.Main.Database
sb.AppendLine(rd.GetValue(0).ToString());
var actualrestoretime = ParseFromEpochSeconds(cmd.ExecuteScalarInt64(@"SELECT ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" = ?", 0, filesetId));
log.AddWarning(string.Format("{0} File(s) were not found in list of files for backup at {1}, will not be restored: {2}", p.Length - c, actualrestoretime.ToLocalTime(), sb), null);
Logging.Log.WriteWarningMessage(LOGTAG, "FilesNotFoundInBackupList", null, "{0} File(s) were not found in list of files for backup at {1}, will not be restored: {2}", p.Length - c, actualrestoretime.ToLocalTime(), sb);
cmd.Parameters.Clear();
}
cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", m_filenamestable));
using(new Logging.Timer("CommitPrepareFileset"))
using(new Logging.Timer(LOGTAG, "CommitPrepareFileset", "CommitPrepareFileset"))
tr.Commit();
}
}
@@ -305,7 +310,7 @@ namespace Duplicati.Library.Main.Database
if (filecount > 0)
{
log.AddVerboseMessage("Needs to restore {0} files ({1})", filecount, Library.Utility.Utility.FormatSizeString(filesize));
Logging.Log.WriteVerboseMessage(LOGTAG, "RestoreTargetFileCount", "Needs to restore {0} files ({1})", filecount, Library.Utility.Utility.FormatSizeString(filesize));
return new Tuple<long, long>(filecount, filesize);
}
}
@@ -440,7 +445,7 @@ namespace Duplicati.Library.Main.Database
}
}
public void FindMissingBlocks(ILogWriter log, bool skipMetadata)
public void FindMissingBlocks(bool skipMetadata)
{
using(var cmd = m_connection.CreateCommand())
{
@@ -454,11 +459,8 @@ namespace Duplicati.Library.Main.Database
p2 = cmd.ExecuteNonQuery();
}
if (log.VerboseOutput)
{
var size = cmd.ExecuteScalarInt64(string.Format(@"SELECT SUM(""Size"") FROM ""{0}"" ", m_tempblocktable), 0);
log.AddVerboseMessage("Restore list contains {0} blocks with a total size of {1}", p1 + p2, Library.Utility.Utility.FormatSizeString(size));
}
var size = cmd.ExecuteScalarInt64(string.Format(@"SELECT SUM(""Size"") FROM ""{0}"" ", m_tempblocktable), 0);
Logging.Log.WriteVerboseMessage(LOGTAG, "RestoreSourceSize", "Restore list contains {0} blocks with a total size of {1}", p1 + p2, Library.Utility.Utility.FormatSizeString(size));
}
}
@@ -947,7 +949,7 @@ namespace Duplicati.Library.Main.Database
cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_tempfiletable);
cmd.ExecuteNonQuery();
}
catch(Exception ex) { if (m_result != null) m_result.AddWarning(string.Format("Cleanup error: {0}", ex.Message), ex); }
catch(Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); }
finally { m_tempfiletable = null; }
if (m_tempblocktable != null)
@@ -956,7 +958,7 @@ namespace Duplicati.Library.Main.Database
cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_tempblocktable);
cmd.ExecuteNonQuery();
}
catch(Exception ex) { if (m_result != null) m_result.AddWarning(string.Format("Cleanup error: {0}", ex.Message), ex); }
catch(Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); }
finally { m_tempblocktable = null; }
@@ -966,7 +968,7 @@ namespace Duplicati.Library.Main.Database
cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_fileprogtable);
cmd.ExecuteNonQuery();
}
catch (Exception ex) { if (m_result != null) m_result.AddWarning(string.Format("Cleanup error: {0}", ex.Message), ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); }
finally { m_fileprogtable = null; }
if (m_totalprogtable != null)
@@ -975,7 +977,7 @@ namespace Duplicati.Library.Main.Database
cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_totalprogtable);
cmd.ExecuteNonQuery();
}
catch (Exception ex) { if (m_result != null) m_result.AddWarning(string.Format("Cleanup error: {0}", ex.Message), ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); }
finally { m_totalprogtable = null; }
if (m_filesnewlydonetable != null)
@@ -984,7 +986,7 @@ namespace Duplicati.Library.Main.Database
cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_filesnewlydonetable);
cmd.ExecuteNonQuery();
}
catch (Exception ex) { if (m_result != null) m_result.AddWarning(string.Format("Cleanup error: {0}", ex.Message), ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); }
finally { m_filesnewlydonetable = null; }
}
@@ -996,7 +998,7 @@ namespace Duplicati.Library.Main.Database
void SetAllBlocksMissing(long targetfileid);
void SetAllBlocksRestored(long targetfileid, bool includeMetadata);
void SetFileDataVerified(long targetfileid);
void Commit(ILogWriter log);
void Commit();
void UpdateProcessed(IOperationProgressUpdater writer);
System.Data.IDbTransaction Transaction { get; }
}
@@ -1132,12 +1134,12 @@ namespace Duplicati.Library.Main.Database
}
public void Commit(ILogWriter log)
public void Commit()
{
var tr = m_insertblockCommand.Transaction;
m_insertblockCommand.Dispose();
m_insertblockCommand = null;
using (new Logging.Timer("CommitBlockMarker"))
using (new Logging.Timer(LOGTAG, "CommitBlockMarker", "CommitBlockMarker"))
tr.Commit();
tr.Dispose();
}
@@ -30,7 +30,7 @@ namespace Duplicati.Library.Main.Database
private readonly List<KeyValuePair<string, FolderEntry>> m_lookup;
/// <summary>
/// Initializes a new instance of the <see cref="Duplicati.Library.Main.Database.PathLookupHelper`1"/> class.
/// Initializes a new instance of the <see cref="Duplicati.Library.Main.Database.PathLookupHelper{T}"/> class.
/// </summary>
/// <param name="useHotPath">If set to <c>true</c> use hotpath optimization</param>
public PathLookupHelper(bool useHotPath = true)
+3 -3
View File
@@ -134,7 +134,7 @@ namespace Duplicati.Library.Main
select n).ToList();
if (matches.Count > 1)
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Multiple sources found for: {0}", backend));
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Multiple sources found for: {0}", backend), "MultipleLocalDatabaseSourcesFound");
// Re-select
if (matches.Count == 0 && anyUsername && string.IsNullOrEmpty(username))
@@ -149,7 +149,7 @@ namespace Duplicati.Library.Main
select n).ToList();
if (matches.Count > 1)
throw new Duplicati.Library.Interface.UserInformationException(String.Format("Multiple sources found for \"{0}\", try supplying --{1}", backend, "auth-username"));
throw new Duplicati.Library.Interface.UserInformationException(String.Format("Multiple sources found for \"{0}\", try supplying --{1}", backend, "auth-username"), "MultipleLocalDatabaseSourcesFound");
}
if (matches.Count == 0 && !autoCreate)
@@ -172,7 +172,7 @@ namespace Duplicati.Library.Main
newpath = System.IO.Path.Combine(folder, GenerateRandomName());
if (System.IO.File.Exists(newpath))
throw new Duplicati.Library.Interface.UserInformationException("Unable to find a unique name for the database, please use --dbpath");
throw new Duplicati.Library.Interface.UserInformationException("Unable to find a unique name for the database, please use --dbpath", "CannotCreateRandomName");
//Create a new one, add it to the list, and save it
configs.Add(new BackendEntry() {
@@ -111,6 +111,8 @@
<Compile Include="Database\LocalListBrokenFilesDatabase.cs" />
<Compile Include="Operation\ListBrokenFilesHandler.cs" />
<Compile Include="Operation\PurgeBrokenFilesHandler.cs" />
<Compile Include="ControllerMultiLogTarget.cs" />
<Compile Include="ProcessController.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Utility\Duplicati.Library.Utility.csproj">
@@ -149,6 +151,10 @@
<Project>{BB014EA5-CE2C-4444-8D30-38983A0E8553}</Project>
<Name>Duplicati.Library.UsageReporter</Name>
</ProjectReference>
<ProjectReference Include="..\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj">
<Project>{52826615-7964-47FE-B4B3-1B2DBDF605B9}</Project>
<Name>Duplicati.Library.Modules.Builtin</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
+103 -108
View File
@@ -12,6 +12,11 @@ namespace Duplicati.Library.Main.Operation
{
internal class BackupHandler : IDisposable
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<BackupHandler>();
private readonly Options m_options;
private string m_backendurl;
@@ -57,24 +62,24 @@ namespace Duplicati.Library.Main.Operation
m_symlinkPolicy = m_options.SymlinkPolicy;
if (options.AllowPassphraseChange)
throw new UserInformationException(Strings.Common.PassphraseChangeUnsupported);
throw new UserInformationException(Strings.Common.PassphraseChangeUnsupported, "PassphraseChangeUnsupported");
}
public class FilterHandler
{
private static readonly string FILTER_LOGTAG = Logging.Log.LogTagFromType<FilterHandler>();
private Snapshots.ISnapshotService m_snapshot;
private FileAttributes m_attributeFilter;
private Duplicati.Library.Utility.IFilter m_enumeratefilter;
private Duplicati.Library.Utility.IFilter m_emitfilter;
private Options.SymlinkStrategy m_symlinkPolicy;
private Options.HardlinkStrategy m_hardlinkPolicy;
private ILogWriter m_logWriter;
private Dictionary<string, string> m_hardlinkmap;
private Duplicati.Library.Utility.IFilter m_sourcefilter;
private Queue<string> m_mixinqueue;
public FilterHandler(Snapshots.ISnapshotService snapshot, FileAttributes attributeFilter, Duplicati.Library.Utility.IFilter sourcefilter, Duplicati.Library.Utility.IFilter filter, Options.SymlinkStrategy symlinkPolicy, Options.HardlinkStrategy hardlinkPolicy, ILogWriter logWriter)
public FilterHandler(Snapshots.ISnapshotService snapshot, FileAttributes attributeFilter, Duplicati.Library.Utility.IFilter sourcefilter, Duplicati.Library.Utility.IFilter filter, Options.SymlinkStrategy symlinkPolicy, Options.HardlinkStrategy hardlinkPolicy)
{
m_snapshot = snapshot;
m_attributeFilter = attributeFilter;
@@ -82,7 +87,6 @@ namespace Duplicati.Library.Main.Operation
m_emitfilter = filter;
m_symlinkPolicy = symlinkPolicy;
m_hardlinkPolicy = hardlinkPolicy;
m_logWriter = logWriter;
m_hardlinkmap = new Dictionary<string, string>();
m_mixinqueue = new Queue<string>();
@@ -99,8 +103,7 @@ namespace Duplicati.Library.Main.Operation
public void ReportError(string rootpath, string path, Exception ex)
{
if (m_logWriter != null)
m_logWriter.AddWarning(string.Format("Error reported while accessing file: {0}", path), ex);
Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "FileAccessError", ex, "Error reported while accessing file: {0}", path);
}
public bool AttributeFilter(string rootpath, string path, FileAttributes attributes)
@@ -109,15 +112,13 @@ namespace Duplicati.Library.Main.Operation
{
if (m_snapshot.IsBlockDevice(path))
{
if (m_logWriter != null)
m_logWriter.AddVerboseMessage("Excluding block device: {0}", path);
Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingBlockDevice", "Excluding block device: {0}", path);
return false;
}
}
catch (Exception ex)
{
if (m_logWriter != null)
m_logWriter.AddWarning(string.Format("Failed to process path: {0}", path), ex);
Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingError", ex, "Failed to process path: {0}", path);
return false;
}
@@ -125,8 +126,7 @@ namespace Duplicati.Library.Main.Operation
bool sourcematches;
if (m_sourcefilter.Matches(path, out sourcematches, out sourcematch) && sourcematches)
{
if (m_logWriter != null)
m_logWriter.AddVerboseMessage("Including source path: {0}", path);
Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "IncludingSourcePath", "Including source path: {0}", path);
return true;
}
@@ -140,8 +140,7 @@ namespace Duplicati.Library.Main.Operation
{
if (m_hardlinkPolicy == Options.HardlinkStrategy.None)
{
if (m_logWriter != null)
m_logWriter.AddVerboseMessage("Excluding hardlink: {0} ({1})", path, id);
Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingHardlinkByPolicy", "Excluding hardlink: {0} ({1})", path, id);
return false;
}
else if (m_hardlinkPolicy == Options.HardlinkStrategy.First)
@@ -149,8 +148,7 @@ namespace Duplicati.Library.Main.Operation
string prevPath;
if (m_hardlinkmap.TryGetValue(id, out prevPath))
{
if (m_logWriter != null)
m_logWriter.AddVerboseMessage("Excluding hardlink ({1}) for: {0}, previous hardlink: {2}", path, id, prevPath);
Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingDuplicateHardlink", "Excluding hardlink ({1}) for: {0}, previous hardlink: {2}", path, id, prevPath);
return false;
}
else
@@ -162,44 +160,38 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
if (m_logWriter != null)
m_logWriter.AddWarning(string.Format("Failed to process path: {0}", path), ex);
Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingError", ex, "Failed to process path: {0}", path);
return false;
}
}
if ((m_attributeFilter & attributes) != 0)
{
if (m_logWriter != null)
m_logWriter.AddVerboseMessage("Excluding path due to attribute filter: {0}", path);
Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingPathFromAttributes", "Excluding path due to attribute filter: {0}", path);
return false;
}
Library.Utility.IFilter match;
if (!Library.Utility.FilterExpression.Matches(m_enumeratefilter, path, out match))
{
if (m_logWriter != null)
m_logWriter.AddVerboseMessage("Excluding path due to filter: {0} => {1}", path, match == null ? "null" : match.ToString());
Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingPathFromFilter", "Excluding path due to filter: {0} => {1}", path, match == null ? "null" : match.ToString());
return false;
}
else if (match != null)
{
if (m_logWriter != null)
m_logWriter.AddVerboseMessage("Including path due to filter: {0} => {1}", path, match.ToString());
Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "IncludingPathFromFilter", "Including path due to filter: {0} => {1}", path, match.ToString());
}
var isSymlink = m_snapshot.IsSymlink(path, attributes);
if (isSymlink && m_symlinkPolicy == Options.SymlinkStrategy.Ignore)
{
if (m_logWriter != null)
m_logWriter.AddVerboseMessage("Excluding symlink: {0}", path);
Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludeSymlink", "Excluding symlink: {0}", path);
return false;
}
if (isSymlink && m_symlinkPolicy == Options.SymlinkStrategy.Store)
{
if (m_logWriter != null)
m_logWriter.AddVerboseMessage("Storing symlink: {0}", path);
Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "StoreSymlink", "Storing symlink: {0}", path);
m_mixinqueue.Enqueue(path);
return false;
@@ -231,7 +223,7 @@ namespace Duplicati.Library.Main.Operation
foreach(var s in list.Where(x => {
var fa = FileAttributes.Normal;
try { fa = m_snapshot.GetAttributes(x); }
catch { }
catch(Exception ex) { Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", x, ex.Message); }
return AttributeFilter(null, x, fa);
}))
@@ -248,7 +240,7 @@ namespace Duplicati.Library.Main.Operation
}
public static Snapshots.ISnapshotService GetSnapshot(string[] sources, Options options, ILogWriter log)
public static Snapshots.ISnapshotService GetSnapshot(string[] sources, Options options)
{
try
{
@@ -260,7 +252,7 @@ namespace Duplicati.Library.Main.Operation
if (options.SnapShotStrategy == Options.OptimizationStrategy.Required)
throw;
else if (options.SnapShotStrategy == Options.OptimizationStrategy.On)
log.AddWarning(Strings.Common.SnapshotFailedError(ex.ToString()), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "SnapshotFailed", ex, Strings.Common.SnapshotFailedError(ex.ToString()));
}
return Library.Utility.Utility.IsClientLinux ?
@@ -271,17 +263,18 @@ namespace Duplicati.Library.Main.Operation
private void CountFilesThread(object state)
{
var COUNTER_LOGTAG = LOGTAG + ".Counter";
var snapshot = (Snapshots.ISnapshotService)state;
var updater = m_result.OperationProgressUpdater;
var count = 0L;
var size = 0L;
var followSymlinks = m_options.SymlinkPolicy != Duplicati.Library.Main.Options.SymlinkStrategy.Follow;
foreach(var path in new FilterHandler(snapshot, m_attributeFilter, m_sourceFilter, m_filter, m_symlinkPolicy, m_options.HardlinkPolicy, null).EnumerateFilesAndFolders())
foreach(var path in new FilterHandler(snapshot, m_attributeFilter, m_sourceFilter, m_filter, m_symlinkPolicy, m_options.HardlinkPolicy).EnumerateFilesAndFolders())
{
var fa = FileAttributes.Normal;
try { fa = snapshot.GetAttributes(path); }
catch { }
catch (Exception ex) { Logging.Log.WriteVerboseMessage(COUNTER_LOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", path, ex.Message); }
if (followSymlinks && snapshot.IsSymlink(path, fa))
continue;
@@ -291,7 +284,7 @@ namespace Duplicati.Library.Main.Operation
count++;
try { size += snapshot.GetFileSize(path); }
catch { }
catch (Exception ex) { Logging.Log.WriteVerboseMessage(COUNTER_LOGTAG, "SizeReadFailed", "Failed to read length of file {0}: {1}", path, ex.Message); }
m_result.OperationProgressUpdater.UpdatefileCount(count, size, false);
}
@@ -305,7 +298,7 @@ namespace Duplicati.Library.Main.Operation
var incompleteFilesets = m_database.GetIncompleteFilesets(null).OrderBy(x => x.Value).ToList();
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PreviousBackupFinalize);
m_result.AddMessage(string.Format("Uploading filelist from previous interrupted backup"));
Logging.Log.WriteInformationMessage(LOGTAG, "PreviousBackupFilelistUpload", "Uploading filelist from previous interrupted backup");
using(var trn = m_database.BeginTransaction())
{
var incompleteSet = incompleteFilesets.Last();
@@ -353,13 +346,13 @@ namespace Duplicati.Library.Main.Operation
if (m_options.Dryrun)
{
m_result.AddDryrunMessage(string.Format("Would upload fileset: {0}, size: {1}", fsw.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(fsw.LocalFilename).Length)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadFileset", "Would upload fileset: {0}, size: {1}", fsw.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(fsw.LocalFilename).Length));
}
else
{
m_database.UpdateRemoteVolume(fsw.RemoteFilename, RemoteVolumeState.Uploading, -1, null, trn);
using(new Logging.Timer("CommitUpdateFilelistVolume"))
using(new Logging.Timer(LOGTAG, "CommitUpdateFilelistVolume", "CommitUpdateFilelistVolume"))
trn.Commit();
backend.Put(fsw);
@@ -384,7 +377,7 @@ namespace Duplicati.Library.Main.Operation
foreach (var blockfile in m_database.GetMissingIndexFiles())
{
m_result.AddMessage(string.Format("Re-creating missing index file for {0}", blockfile));
Logging.Log.WriteInformationMessage(LOGTAG, "RecreateMissingIndexFile", "Re-creating missing index file for {0}", blockfile);
var w = new IndexVolumeWriter(m_options);
w.VolumeID = m_database.RegisterRemoteVolume(w.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, null);
@@ -406,7 +399,7 @@ namespace Duplicati.Library.Main.Operation
m_database.AddIndexBlockLink(w.VolumeID, volumeid, null);
if (m_options.Dryrun)
m_result.AddDryrunMessage(string.Format("would upload new index file {0}, with size {1}, previous size {2}", w.RemoteFilename, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(w.LocalFilename).Length), Library.Utility.Utility.FormatSizeString(w.Filesize)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUpdateIndexFile", "Would upload new index file {0}, with size {1}, previous size {2}", w.RemoteFilename, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(w.LocalFilename).Length), Library.Utility.Utility.FormatSizeString(w.Filesize));
else
{
m_database.UpdateRemoteVolume(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null, null);
@@ -419,7 +412,7 @@ namespace Duplicati.Library.Main.Operation
private void PreBackupVerify(BackendManager backend, string protectedfile)
{
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PreBackupVerify);
using(new Logging.Timer("PreBackupVerify"))
using(new Logging.Timer(LOGTAG, "PreBackupVerify", "PreBackupVerify"))
{
try
{
@@ -435,11 +428,11 @@ namespace Duplicati.Library.Main.Operation
{
if (m_options.AutoCleanup)
{
m_result.AddWarning("Backend verification failed, attempting automatic cleanup", ex);
Logging.Log.WriteWarningMessage(LOGTAG, "BackendVerifyFailedAttemptingCleanup", ex, "Backend verification failed, attempting automatic cleanup");
m_result.RepairResults = new RepairResults(m_result);
new RepairHandler(backend.BackendUrl, m_options, (RepairResults)m_result.RepairResults).Run();
m_result.AddMessage("Backend cleanup finished, retrying verification");
Logging.Log.WriteInformationMessage(LOGTAG, "BackendCleanupFinished", "Backend cleanup finished, retrying verification");
FilelistProcessor.VerifyRemoteList(backend, m_options, m_database, m_result.BackendWriter);
}
else
@@ -450,20 +443,20 @@ namespace Duplicati.Library.Main.Operation
private void RunMainOperation(Snapshots.ISnapshotService snapshot, BackendManager backend)
{
var filterhandler = new FilterHandler(snapshot, m_attributeFilter, m_sourceFilter, m_filter, m_symlinkPolicy, m_options.HardlinkPolicy, m_result);
var filterhandler = new FilterHandler(snapshot, m_attributeFilter, m_sourceFilter, m_filter, m_symlinkPolicy, m_options.HardlinkPolicy);
using(new Logging.Timer("BackupMainOperation"))
using(new Logging.Timer(LOGTAG, "BackupMainOperation", "BackupMainOperation"))
{
if (m_options.ChangedFilelist != null && m_options.ChangedFilelist.Length >= 1)
{
m_result.AddVerboseMessage("Processing supplied change list instead of enumerating filesystem");
Logging.Log.WriteVerboseMessage(LOGTAG, "UsingChangeList", "Processing supplied change list instead of enumerating filesystem");
m_result.OperationProgressUpdater.UpdatefileCount(m_options.ChangedFilelist.Length, 0, true);
foreach(var p in filterhandler.Mixin(m_options.ChangedFilelist))
{
if (m_result.TaskControlRendevouz() == TaskControlState.Stop)
{
m_result.AddMessage("Stopping backup operation on request");
Logging.Log.WriteInformationMessage(LOGTAG, "StopBackup", "Stopping backup operation on request");
break;
}
@@ -473,7 +466,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
m_result.AddWarning(string.Format("Failed to process element: {0}, message: {1}", p, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "FailedToProcessEntry", ex, "Failed to process element: {0}, message: {1}", p, ex.Message);
}
}
@@ -485,13 +478,13 @@ namespace Duplicati.Library.Main.Operation
{
if (m_result.TaskControlRendevouz() == TaskControlState.Stop)
{
m_result.AddMessage("Stopping backup operation on request");
Logging.Log.WriteInformationMessage(LOGTAG, "StopBackup", "Stopping backup operation on request");
break;
}
var fa = FileAttributes.Normal;
try { fa = snapshot.GetAttributes(path); }
catch { }
catch (Exception ex) { Logging.Log.WriteVerboseMessage(LOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", path, ex.Message); }
this.HandleFilesystemEntry(snapshot, backend, path, fa);
}
@@ -506,7 +499,7 @@ namespace Duplicati.Library.Main.Operation
{
var lastVolumeSize = -1L;
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Finalize);
using(new Logging.Timer("FinalizeRemoteVolumes"))
using(new Logging.Timer(LOGTAG, "FinalizeRemoteVolumes", "FinalizeRemoteVolumes"))
{
if (m_blockvolume != null && m_blockvolume.SourceSize > 0)
{
@@ -514,13 +507,13 @@ namespace Duplicati.Library.Main.Operation
if (m_options.Dryrun)
{
m_result.AddDryrunMessage(string.Format("Would upload block volume: {0}, size: {1}", m_blockvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(m_blockvolume.LocalFilename).Length)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadBlock", "Would upload block volume: {0}, size: {1}", m_blockvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(m_blockvolume.LocalFilename).Length));
if (m_indexvolume != null)
{
m_blockvolume.Close();
UpdateIndexVolume();
m_indexvolume.FinishVolume(Library.Utility.Utility.CalculateHash(m_blockvolume.LocalFilename), new FileInfo(m_blockvolume.LocalFilename).Length);
m_result.AddDryrunMessage(string.Format("Would upload index volume: {0}, size: {1}", m_indexvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(m_indexvolume.LocalFilename).Length)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadIndex", "Would upload index volume: {0}, size: {1}", m_indexvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(m_indexvolume.LocalFilename).Length));
}
m_blockvolume.Dispose();
@@ -534,13 +527,13 @@ namespace Duplicati.Library.Main.Operation
m_blockvolume.Close();
UpdateIndexVolume();
using(new Logging.Timer("CommitUpdateRemoteVolume"))
using(new Logging.Timer(LOGTAG, "CommitUpdateRemoteVolume", "CommitUpdateRemoteVolume"))
m_transaction.Commit();
m_transaction = m_database.BeginTransaction();
backend.Put(m_blockvolume, m_indexvolume);
using(new Logging.Timer("CommitUpdateRemoteVolume"))
using(new Logging.Timer(LOGTAG, "CommitUpdateRemoteVolume", "CommitUpdateRemoteVolume"))
m_transaction.Commit();
m_transaction = m_database.BeginTransaction();
@@ -563,7 +556,7 @@ namespace Duplicati.Library.Main.Operation
//Changes in the filelist triggers a filelist upload
if (m_options.UploadUnchangedBackups || changeCount > 0)
{
using(new Logging.Timer("Uploading a new fileset"))
using(new Logging.Timer(LOGTAG, "UploadNewFileset", "Uploading a new fileset"))
{
if (!string.IsNullOrEmpty(m_options.ControlFiles))
foreach(var p in m_options.ControlFiles.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries))
@@ -573,18 +566,18 @@ namespace Duplicati.Library.Main.Operation
filesetvolume.Close();
if (m_options.Dryrun)
m_result.AddDryrunMessage(string.Format("Would upload fileset volume: {0}, size: {1}", filesetvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(filesetvolume.LocalFilename).Length)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadFileset", "Would upload fileset volume: {0}, size: {1}", filesetvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(filesetvolume.LocalFilename).Length));
else
{
m_database.UpdateRemoteVolume(filesetvolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null, m_transaction);
using(new Logging.Timer("CommitUpdateRemoteVolume"))
using(new Logging.Timer(LOGTAG, "CommitUpdateRemoteVolume", "CommitUpdateRemoteVolume"))
m_transaction.Commit();
m_transaction = m_database.BeginTransaction();
backend.Put(filesetvolume);
using(new Logging.Timer("CommitUpdateRemoteVolume"))
using(new Logging.Timer(LOGTAG, "CommitUpdateRemoteVolume", "CommitUpdateRemoteVolume"))
m_transaction.Commit();
m_transaction = m_database.BeginTransaction();
@@ -593,7 +586,7 @@ namespace Duplicati.Library.Main.Operation
}
else
{
m_result.AddVerboseMessage("removing temp files, as no data needs to be uploaded");
Logging.Log.WriteVerboseMessage(LOGTAG, "RemovingLeftoverTempFile", "removing temp files, as no data needs to be uploaded");
m_database.RemoveRemoteVolume(filesetvolume.RemoteFilename, m_transaction);
}
}
@@ -624,7 +617,7 @@ namespace Duplicati.Library.Main.Operation
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PostBackupVerify);
using(var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, m_database))
{
using(new Logging.Timer("AfterBackupVerify"))
using(new Logging.Timer(LOGTAG, "AfterBackupVerify", "AfterBackupVerify"))
FilelistProcessor.VerifyRemoteList(backend, m_options, m_database, m_result.BackendWriter);
backend.WaitForComplete(m_database, null);
}
@@ -689,13 +682,13 @@ namespace Duplicati.Library.Main.Operation
var probe_path = m_database.GetFirstPath();
if (probe_path != null && Duplicati.Library.Utility.Utility.GuessDirSeparator(probe_path) != System.IO.Path.DirectorySeparatorChar.ToString())
throw new UserInformationException(string.Format("The backup contains files that belong to another operating system. Proceeding with a backup would cause the database to contain paths from two different operation systems, which is not supported. To proceed without losing remote data, delete all filesets and make sure the --{0} option is set, then run the backup again to re-use the existing data on the remote store.", "no-auto-compact"));
throw new UserInformationException(string.Format("The backup contains files that belong to another operating system. Proceeding with a backup would cause the database to contain paths from two different operation systems, which is not supported. To proceed without losing remote data, delete all filesets and make sure the --{0} option is set, then run the backup again to re-use the existing data on the remote store.", "no-auto-compact"), "CrossOsDatabaseReuseNotSupported");
if (m_database.PartiallyRecreated)
throw new UserInformationException("The database was only partially recreated. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.");
throw new UserInformationException("The database was only partially recreated. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsPartiallyRecreated");
if (m_database.RepairInProgress)
throw new UserInformationException("The database was attempted repaired, but the repair did not complete. This database may be incomplete and the backup process cannot continue. You may delete the local database and attempt to repair it again.");
throw new UserInformationException("The database was attempted repaired, but the repair did not complete. This database may be incomplete and the backup process cannot continue. You may delete the local database and attempt to repair it again.", "DatabaseRepairInProgress");
m_blocksize = m_options.Blocksize;
m_maxmetadatasize = (m_blocksize / (long)m_options.BlockhashSize) * m_blocksize;
@@ -707,14 +700,14 @@ namespace Duplicati.Library.Main.Operation
m_filehasher = Library.Utility.HashAlgorithmHelper.Create(m_options.FileHashAlgorithm);
if (m_blockhasher == null)
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm), "BlockHashAlgoithmNotSupported");
if (m_filehasher == null)
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.FileHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.FileHashAlgorithm), "FileHashAlgoithmNotSupported");
if (!m_blockhasher.CanReuseTransform)
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.BlockHashAlgorithm), "BlockHashAlgoithmNotSupported");
if (!m_filehasher.CanReuseTransform)
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.FileHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.FileHashAlgorithm), "FileHashAlgoithmNotSupported");
m_database.VerifyConsistency(null, m_options.Blocksize, m_options.BlockhashSize, false);
// If there is no filter, we set an empty filter to simplify the code
@@ -730,7 +723,7 @@ namespace Duplicati.Library.Main.Operation
using(var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, m_database))
using(var filesetvolume = new FilesetVolumeWriter(m_options, m_database.OperationTimestamp))
{
using(var snapshot = GetSnapshot(sources, m_options, m_result))
using(var snapshot = GetSnapshot(sources, m_options))
{
// Start parallel scan, or use the database
if (m_options.ChangedFilelist == null || m_options.ChangedFilelist.Length < 1)
@@ -778,22 +771,22 @@ namespace Duplicati.Library.Main.Operation
// Remove the protected file
if (syntbase.State == RemoteVolumeState.Uploading)
{
m_result.AddMessage(string.Format("removing incomplete remote file listed as {0}: {1}", syntbase.State, syntbase.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "RemoveIncompleteFilelist", "Removing incomplete remote file listed as {0}: {1}", syntbase.State, syntbase.Name);
backend.Delete(syntbase.Name, syntbase.Size);
}
else if (syntbase.State == RemoteVolumeState.Temporary)
{
m_result.AddMessage(string.Format("removing file listed as {0}: {1}", syntbase.State, syntbase.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "RemoveTemporaryFile", "Removing file listed as {0}: {1}", syntbase.State, syntbase.Name);
m_database.RemoveRemoteVolume(syntbase.Name);
}
}
else if (syntbase.Name == null || syntbase.State != RemoteVolumeState.Uploaded)
m_result.AddWarning(string.Format("Expected there to be a temporary fileset for synthetic filelist ({0}, {1}), but none was found?", lasttempfileid, lasttempfilelist), null);
Logging.Log.WriteWarningMessage(LOGTAG, "MissingTemporaryFilelist", null, "Expected there to be a temporary fileset for synthetic filelist ({0}, {1}), but none was found?", lasttempfileid, lasttempfilelist);
}
var prevfileset = m_database.FilesetTimes.FirstOrDefault();
if (prevfileset.Value.ToUniversalTime() > m_database.OperationTimestamp.ToUniversalTime())
throw new UserInformationException(string.Format("The previous backup has time {0}, but this backup has time {1}. Something is wrong with the clock.", prevfileset.Value.ToLocalTime(), m_database.OperationTimestamp.ToLocalTime()));
throw new UserInformationException(string.Format("The previous backup has time {0}, but this backup has time {1}. Something is wrong with the clock.", prevfileset.Value.ToLocalTime(), m_database.OperationTimestamp.ToLocalTime()), "FilesetIntervalClockIssue");
m_lastfilesetid = prevfileset.Value.Ticks == 0 ? -1 : prevfileset.Key;
@@ -823,21 +816,21 @@ namespace Duplicati.Library.Main.Operation
var lastVolumeSize = FinalizeRemoteVolumes(backend);
using(new Logging.Timer("UpdateChangeStatistics"))
using(new Logging.Timer(LOGTAG, "UpdateChangeStatistics", "UpdateChangeStatistics"))
m_database.UpdateChangeStatistics(m_result);
using(new Logging.Timer("VerifyConsistency"))
using(new Logging.Timer(LOGTAG, "VerifyConsistency", "VerifyConsistency"))
m_database.VerifyConsistency(m_transaction, m_options.Blocksize, m_options.BlockhashSize, false);
UploadRealFileList(backend, filesetvolume);
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_WaitForUpload);
using(new Logging.Timer("Async backend wait"))
using(new Logging.Timer(LOGTAG, "AsyncBackendWait", "Async backend wait"))
backend.WaitForEmpty(m_database, m_transaction);
if (m_result.TaskControlRendevouz() != TaskControlState.Stop)
CompactIfRequired(backend, lastVolumeSize);
using (new Logging.Timer("Async backend wait"))
using (new Logging.Timer(LOGTAG, "AsyncBackendWait", "Async backend wait"))
backend.WaitForComplete(m_database, m_transaction);
if (m_options.UploadVerificationFile)
@@ -853,7 +846,7 @@ namespace Duplicati.Library.Main.Operation
}
else
{
using(new Logging.Timer("CommitFinalizingBackup"))
using(new Logging.Timer(LOGTAG, "CommitFinalizingBackupe", "CommitFinalizingBackup"))
m_transaction.Commit();
m_transaction = null;
@@ -879,7 +872,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
m_result.AddError("Fatal error", ex);
Logging.Log.WriteErrorMessage(LOGTAG, "FatalError", ex, "Fatal error");
throw;
}
finally
@@ -889,18 +882,19 @@ namespace Duplicati.Library.Main.Operation
parallelScanner.Abort();
parallelScanner.Join(500);
if (parallelScanner.IsAlive)
m_result.AddWarning("Failed to terminate filecounter thread", null);
Logging.Log.WriteWarningMessage(LOGTAG, "FileCounterTerminateFailed", null, "Failed to terminate filecounter thread");
}
if (m_transaction != null)
try { m_transaction.Rollback(); }
catch (Exception ex) { m_result.AddError(string.Format("Rollback error: {0}", ex.Message), ex); }
catch (Exception ex) { Logging.Log.WriteErrorMessage(LOGTAG, "RollbackError", ex, "Rollback error: {0}", ex.Message); }
}
}
}
private Dictionary<string, string> GenerateMetadata(Snapshots.ISnapshotService snapshot, string path, System.IO.FileAttributes attributes)
{
string METALOGTAG = LOGTAG + ".Metadata";
try
{
Dictionary<string, string> metadata;
@@ -922,7 +916,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
m_result.AddWarning(string.Format("Failed to read timestamp on \"{0}\"", path), ex);
Logging.Log.WriteWarningMessage(METALOGTAG, "TimestampReadFailed", ex, "Failed to read timestamp on \"{0}\"", path);
}
}
@@ -934,7 +928,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
m_result.AddWarning(string.Format("Failed to read timestamp on \"{0}\"", path), ex);
Logging.Log.WriteWarningMessage(METALOGTAG, "TimestampReadFailed", ex, "Failed to read timestamp on \"{0}\"", path);
}
}
}
@@ -947,13 +941,15 @@ namespace Duplicati.Library.Main.Operation
}
catch(Exception ex)
{
m_result.AddWarning(string.Format("Failed to process metadata for \"{0}\", storing empty metadata", path), ex);
Logging.Log.WriteWarningMessage(METALOGTAG, "MetadataProcessFailed", ex, "Failed to process metadata for \"{0}\", storing empty metadata", path);
return new Dictionary<string, string>();
}
}
private bool HandleFilesystemEntry(Snapshots.ISnapshotService snapshot, BackendManager backend, string path, System.IO.FileAttributes attributes)
{
string FILELOGTAG = LOGTAG + ".FileEntry";
// If we lost the connection, there is no point in keeping on processing
if (backend.HasDied)
throw backend.LastException;
@@ -975,7 +971,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
m_result.AddWarning(string.Format("Failed to read timestamp on \"{0}\"", path), ex);
Logging.Log.WriteWarningMessage(FILELOGTAG, "TimestampReadFailed", ex, "Failed to read timestamp on \"{0}\"", path);
}
if ((attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
@@ -989,7 +985,7 @@ namespace Duplicati.Library.Main.Operation
{
if (m_options.SymlinkPolicy == Options.SymlinkStrategy.Ignore)
{
m_result.AddVerboseMessage("Ignoring symlink {0}", path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "IgnoreSymlink", "Ignoring symlink {0}", path);
return false;
}
@@ -1005,14 +1001,14 @@ namespace Duplicati.Library.Main.Operation
var metahash = Utility.WrapMetadata(metadata, m_options);
AddSymlinkToOutput(backend, path, DateTime.UtcNow, metahash);
m_result.AddVerboseMessage("Stored symlink {0}", path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "StoreSymlink", "Stored symlink {0}", path);
//Do not recurse symlinks
return false;
}
}
else
{
m_result.AddVerboseMessage("Treating empty symlink as regular path {0}", path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "FollowingEmptySymlink", "Treating empty symlink as regular path {0}", path);
}
}
@@ -1029,7 +1025,7 @@ namespace Duplicati.Library.Main.Operation
metahash = EMPTY_METADATA;
}
m_result.AddVerboseMessage("Adding directory {0}", path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "AddDirectory", "Adding directory {0}", path);
AddFolderToOutput(backend, path, lastwrite, metahash);
return true;
}
@@ -1047,7 +1043,7 @@ namespace Duplicati.Library.Main.Operation
long filestatsize = -1;
try { filestatsize = snapshot.GetFileSize(path); }
catch { }
catch (Exception ex) { Logging.Log.WriteVerboseMessage(FILELOGTAG, "SizeReadFailed", "Failed to read length of file {0}: {1}", path, ex.Message); }
IMetahash metahashandsize = m_options.StoreMetadata ? Utility.WrapMetadata(GenerateMetadata(snapshot, path, attributes), m_options) : EMPTY_METADATA;
@@ -1070,7 +1066,7 @@ namespace Duplicati.Library.Main.Operation
if ((oldId < 0 || m_options.DisableFiletimeCheck || timestampChanged || filesizeChanged || metadatachanged) && !tooLargeFile)
{
m_result.AddVerboseMessage("Checking file for changes {0}, new: {1}, timestamp changed: {2}, size changed: {3}, metadatachanged: {4}, {5} vs {6}", path, oldId <= 0, timestampChanged, filesizeChanged, metadatachanged, lastwrite, oldModified);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "CheckFileForChanges", "Checking file for changes {0}, new: {1}, timestamp changed: {2}, size changed: {3}, metadatachanged: {4}, {5} vs {6}", path, oldId <= 0, timestampChanged, filesizeChanged, metadatachanged, lastwrite, oldModified);
m_result.OpenedFiles++;
@@ -1085,7 +1081,7 @@ namespace Duplicati.Library.Main.Operation
using (var fs = snapshot.OpenRead(path))
{
try { m_result.OperationProgressUpdater.StartFile(path, fs.Length); }
catch (Exception ex) { m_result.AddWarning(string.Format("Failed to read file length for file {0}", path), ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(FILELOGTAG, "FileLengthFailure", ex, "Failed to read file length for file {0}", path); }
if ((filesize = ProcessStream(fs, hint, backend, blocklisthashes, hashcollector, false)) < 0)
return false;
@@ -1097,16 +1093,16 @@ namespace Duplicati.Library.Main.Operation
if (oldHash != filekey)
{
if (oldHash == null)
m_result.AddVerboseMessage("New file {0}", path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "NewFile", "New file {0}", path);
else
m_result.AddVerboseMessage("File has changed {0}", path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "ChangedFile", "File has changed {0}", path);
if (oldId < 0)
{
m_result.AddedFiles++;
m_result.SizeOfAddedFiles += filesize;
if (m_options.Dryrun)
m_result.AddDryrunMessage(string.Format("Would add new file {0}, size {1}", path, Library.Utility.Utility.FormatSizeString(filesize)));
Logging.Log.WriteVerboseMessage(FILELOGTAG, "WoudlAddNewFile", "Would add new file {0}, size {1}", path, Library.Utility.Utility.FormatSizeString(filesize));
}
else
{
@@ -1114,7 +1110,7 @@ namespace Duplicati.Library.Main.Operation
m_result.SizeOfModifiedFiles += filesize;
if (m_options.Dryrun)
m_result.AddDryrunMessage(string.Format("Would add changed file {0}, size {1}", path, Library.Utility.Utility.FormatSizeString(filesize)));
Logging.Log.WriteVerboseMessage(FILELOGTAG, "WoudlAddChangedFile", "Would add changed file {0}, size {1}", path, Library.Utility.Utility.FormatSizeString(filesize));
}
AddFileToOutput(backend, path, filesize, lastwrite, metahashandsize, hashcollector, filekey, blocklisthashes);
@@ -1122,7 +1118,7 @@ namespace Duplicati.Library.Main.Operation
}
else if (metadatachanged)
{
m_result.AddVerboseMessage("File has only metadata changes {0}", path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "FileMetadataChanged", "File has only metadata changes {0}", path);
AddFileToOutput(backend, path, filesize, lastwrite, metahashandsize, hashcollector, filekey, blocklisthashes);
changed = true;
}
@@ -1130,17 +1126,16 @@ namespace Duplicati.Library.Main.Operation
{
// When we write the file to output, update the last modified time
oldModified = lastwrite;
m_result.AddVerboseMessage("File has not changed {0}", path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "NoFileChanges", "File has not changed {0}", path);
}
}
}
else
{
if (tooLargeFile)
m_result.AddVerboseMessage("Excluding file because the size {0} exceeds limit ({1}): {2}", Library.Utility.Utility.FormatSizeString(filestatsize), Library.Utility.Utility.FormatSizeString(m_options.SkipFilesLargerThan), path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "ExcludeLargeFile", "Excluding file because the size {0} exceeds limit ({1}): {2}", Library.Utility.Utility.FormatSizeString(filestatsize), Library.Utility.Utility.FormatSizeString(m_options.SkipFilesLargerThan), path);
else
m_result.AddVerboseMessage("Skipped checking file, because timestamp was not updated {0}", path);
Logging.Log.WriteVerboseMessage(FILELOGTAG, "SkipCheckNoTimestampChange", "Skipped checking file, because timestamp was not updated {0}", path);
}
// If the file was not previously found, we cannot add it
@@ -1156,7 +1151,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
m_result.AddWarning(string.Format("Failed to process path: {0}", path), ex);
Logging.Log.WriteWarningMessage(FILELOGTAG, "PathProcessingFailed", ex, "Failed to process path: {0}", path);
m_result.FilesWithError++;
}
@@ -1272,13 +1267,13 @@ namespace Duplicati.Library.Main.Operation
if (m_options.Dryrun)
{
m_blockvolume.Close();
m_result.AddDryrunMessage(string.Format("Would upload block volume: {0}, size: {1}", m_blockvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(m_blockvolume.LocalFilename).Length)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadBlock", "Would upload block volume: {0}, size: {1}", m_blockvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(m_blockvolume.LocalFilename).Length));
if (m_indexvolume != null)
{
UpdateIndexVolume();
m_indexvolume.FinishVolume(Library.Utility.Utility.CalculateHash(m_blockvolume.LocalFilename), new FileInfo(m_blockvolume.LocalFilename).Length);
m_result.AddDryrunMessage(string.Format("Would upload index volume: {0}, size: {1}", m_indexvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(m_indexvolume.LocalFilename).Length)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadIndex", "Would upload index volume: {0}, size: {1}", m_indexvolume.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(m_indexvolume.LocalFilename).Length));
m_indexvolume.Dispose();
m_indexvolume = null;
}
@@ -1297,7 +1292,7 @@ namespace Duplicati.Library.Main.Operation
backend.FlushDbMessages(m_database, m_transaction);
m_backendLogFlushTimer = DateTime.Now.Add(FLUSH_TIMESPAN);
using(new Logging.Timer("CommitAddBlockToOutputFlush"))
using(new Logging.Timer(LOGTAG, "CommitAddBlockToOutputFlush", "CommitAddBlockToOutputFlush"))
m_transaction.Commit();
m_transaction = m_database.BeginTransaction();
@@ -1305,7 +1300,7 @@ namespace Duplicati.Library.Main.Operation
m_blockvolume = null;
m_indexvolume = null;
using(new Logging.Timer("CommitAddBlockToOutputFlush"))
using(new Logging.Timer(LOGTAG, "CommitAddBlockToOutputFlush", "CommitAddBlockToOutputFlush"))
m_transaction.Commit();
m_transaction = m_database.BeginTransaction();
@@ -1335,7 +1330,7 @@ namespace Duplicati.Library.Main.Operation
// This could be done such that an extra query is made if the metadata
// spans multiple blocklist hashes, as it is not expected to be common
m_result.AddWarning(string.Format("Metadata size is {0}, but the largest accepted size is {1}, recording empty metadata", meta.Blob.Length, m_maxmetadatasize), null);
Logging.Log.WriteWarningMessage(LOGTAG, "TooLargeMetadata", null, "Metadata size is {0}, but the largest accepted size is {1}, recording empty metadata", meta.Blob.Length, m_maxmetadatasize);
meta = EMPTY_METADATA;
}
@@ -1412,14 +1407,14 @@ namespace Duplicati.Library.Main.Operation
if (m_blockvolume != null)
{
try { m_blockvolume.Dispose(); }
catch (Exception ex) { m_result.AddError("Failed disposing block volume", ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "BlockDisposeFailed", ex, "Failed disposing block volume", ex); }
finally { m_blockvolume = null; }
}
if (m_indexvolume != null)
{
try { m_indexvolume.Dispose(); }
catch (Exception ex) { m_result.AddError("Failed disposing index volume", ex); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "IndexDisposeFailed", ex, "Failed disposing index volume", ex); }
finally { m_indexvolume = null; }
}
@@ -26,6 +26,10 @@ namespace Duplicati.Library.Main.Operation
{
internal class CompactHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<CompactHandler>();
protected string m_backendurl;
protected Options m_options;
protected CompactResults m_result;
@@ -58,7 +62,7 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.Dryrun)
{
using(new Logging.Timer("CommitCompact"))
using(new Logging.Timer(LOGTAG, "CommitCompact", "CommitCompact"))
tr.Commit();
if (changed)
{
@@ -86,7 +90,7 @@ namespace Duplicati.Library.Main.Operation
internal bool DoCompact(LocalDeleteDatabase db, bool hasVerifiedBackend, ref System.Data.IDbTransaction transaction, BackendManager sharedBackend)
{
var report = db.GetCompactReport(m_options.VolumeSize, m_options.Threshold, m_options.SmallFileSize, m_options.SmallFileMaxCount, transaction);
report.ReportCompactData(m_result);
report.ReportCompactData();
if (report.ShouldReclaim || report.ShouldCompact)
{
@@ -177,7 +181,7 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.Dryrun)
backend.Put(newvol, newvolindex);
else
m_result.AddDryrunMessage(string.Format("Would upload generated blockset of size {0}", Library.Utility.Utility.FormatSizeString(newvol.Filesize)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadGeneratedBlockset", "Would upload generated blockset of size {0}", Library.Utility.Utility.FormatSizeString(newvol.Filesize));
newvol = new BlockVolumeWriter(m_options);
@@ -217,7 +221,7 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.Dryrun)
backend.Put(newvol, newvolindex);
else
m_result.AddDryrunMessage(string.Format("Would upload generated blockset of size {0}", Library.Utility.Utility.FormatSizeString(newvol.Filesize)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadGeneratedBlockset", "Would upload generated blockset of size {0}", Library.Utility.Utility.FormatSizeString(newvol.Filesize));
}
else
{
@@ -248,23 +252,23 @@ namespace Duplicati.Library.Main.Operation
if (m_result.Dryrun)
{
if (downloadedVolumes.Count == 0)
m_result.AddDryrunMessage(string.Format("Would delete {0} files, which would reduce storage by {1}", m_result.DeletedFileCount, Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize)));
Logging.Log.WriteDryrunMessage(LOGTAG, "CompactResults", "Would delete {0} files, which would reduce storage by {1}", m_result.DeletedFileCount, Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize));
else
m_result.AddDryrunMessage(string.Format("Would download {0} file(s) with a total size of {1}, delete {2} file(s) with a total size of {3}, and compact to {4} file(s) with a size of {5}, which would reduce storage by {6} file(s) and {7}",
Logging.Log.WriteDryrunMessage(LOGTAG, "CompactResults", "Would download {0} file(s) with a total size of {1}, delete {2} file(s) with a total size of {3}, and compact to {4} file(s) with a size of {5}, which would reduce storage by {6} file(s) and {7}",
m_result.DownloadedFileCount,
Library.Utility.Utility.FormatSizeString(m_result.DownloadedFileSize),
m_result.DeletedFileCount,
Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize), m_result.UploadedFileCount,
Library.Utility.Utility.FormatSizeString(m_result.UploadedFileSize),
m_result.DeletedFileCount - m_result.UploadedFileCount,
Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize - m_result.UploadedFileSize)));
Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize - m_result.UploadedFileSize));
}
else
{
if (m_result.DownloadedFileCount == 0)
m_result.AddMessage(string.Format("Deleted {0} files, which reduced storage by {1}", m_result.DeletedFileCount, Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize)));
Logging.Log.WriteInformationMessage(LOGTAG, "CompactResults", "Deleted {0} files, which reduced storage by {1}", m_result.DeletedFileCount, Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize));
else
m_result.AddMessage(string.Format("Downloaded {0} file(s) with a total size of {1}, deleted {2} file(s) with a total size of {3}, and compacted to {4} file(s) with a size of {5}, which reduced storage by {6} file(s) and {7}",
Logging.Log.WriteInformationMessage(LOGTAG, "CompactResults", "Downloaded {0} file(s) with a total size of {1}, deleted {2} file(s) with a total size of {3}, and compacted to {4} file(s) with a size of {5}, which reduced storage by {6} file(s) and {7}",
m_result.DownloadedFileCount,
Library.Utility.Utility.FormatSizeString(downloadSize),
m_result.DeletedFileCount,
@@ -272,7 +276,7 @@ namespace Duplicati.Library.Main.Operation
m_result.UploadedFileCount,
Library.Utility.Utility.FormatSizeString(m_result.UploadedFileSize),
m_result.DeletedFileCount - m_result.UploadedFileCount,
Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize - m_result.UploadedFileSize)));
Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize - m_result.UploadedFileSize));
}
backend.WaitForComplete(db, transaction);
@@ -314,7 +318,7 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.Dryrun)
backend.Delete(f.Name, f.Size);
else
m_result.AddDryrunMessage(string.Format("Would delete remote file: {0}, size: {1}", f.Name, Library.Utility.Utility.FormatSizeString(f.Size)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteRemoteFile", "Would delete remote file: {0}, size: {1}", f.Name, Library.Utility.Utility.FormatSizeString(f.Size));
yield return new KeyValuePair<string, long>(f.Name, f.Size);
}
@@ -24,6 +24,10 @@ namespace Duplicati.Library.Main.Operation
{
internal class CreateBugReportHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<CreateBugReportHandler>();
private string m_targetpath;
private Options m_options;
private CreateLogDatabaseResults m_result;
@@ -44,15 +48,15 @@ namespace Duplicati.Library.Main.Operation
m_targetpath = m_targetpath + "." + module;
if (System.IO.File.Exists(m_targetpath))
throw new UserInformationException(string.Format("Output file already exists, not overwriting: {0}", m_targetpath));
throw new UserInformationException(string.Format("Output file already exists, not overwriting: {0}", m_targetpath), "BugReportTargetAlreadyExists");
if (!System.IO.File.Exists(m_options.Dbpath))
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "BugReportSourceDatabaseNotFound");
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.BugReport_Running);
m_result.OperationProgressUpdater.UpdateProgress(0);
m_result.AddMessage("Scrubbing filenames from database, this may take a while, please wait");
Logging.Log.WriteInformationMessage(LOGTAG, "ScrubbingFilenames", "Scrubbing filenames from database, this may take a while, please wait");
using (var tmp = new Library.Utility.TempFile())
{
@@ -25,6 +25,15 @@ namespace Duplicati.Library.Main.Operation
{
internal class DeleteHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<DeleteHandler>();
/// <summary>
/// The tag used for logging retention policy messages
/// </summary>
private static readonly string LOGTAG_RETENTION = LOGTAG + ":RetentionPolicy";
private DeleteResults m_result;
protected string m_backendurl;
protected Options m_options;
@@ -39,7 +48,7 @@ namespace Duplicati.Library.Main.Operation
public void Run()
{
if (!System.IO.File.Exists(m_options.Dbpath))
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseFileMissing");
using(var db = new Database.LocalDeleteDatabase(m_options.Dbpath, "Delete"))
{
@@ -54,7 +63,7 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.Dryrun)
{
using(new Logging.Timer("CommitDelete"))
using(new Logging.Timer(LOGTAG, "CommitDelete", "CommitDelete"))
tr.Commit();
db.WriteResults();
@@ -89,12 +98,12 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.AllowFullRemoval && sets.Length == toDelete.Length)
{
m_result.AddMessage(string.Format("Preventing removal of last fileset, use --{0} to allow removal ...", "allow-full-removal"));
Logging.Log.WriteInformationMessage(LOGTAG, "PreventingLastFilesetRemoval", "Preventing removal of last fileset, use --{0} to allow removal ...", "allow-full-removal");
toDelete = toDelete.Skip(1).ToArray();
}
if (toDelete != null && toDelete.Length > 0)
m_result.AddMessage(string.Format("Deleting {0} remote fileset(s) ...", toDelete.Length));
Logging.Log.WriteInformationMessage(LOGTAG, "DeleteRemoteFileset", "Deleting {0} remote fileset(s) ...", toDelete.Length);
var lst = db.DropFilesetsFromTable(toDelete, transaction).ToArray();
foreach(var f in lst)
@@ -117,7 +126,7 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.Dryrun)
backend.Delete(f.Key, f.Value);
else
m_result.AddDryrunMessage(string.Format("Would delete remote fileset: {0}", f.Key));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteRemoteFileset", "Would delete remote fileset: {0}", f.Key);
}
if (sharedManager == null)
@@ -129,20 +138,20 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.Dryrun)
{
if (count == 0)
m_result.AddMessage("No remote filesets were deleted");
Logging.Log.WriteInformationMessage(LOGTAG, "DeleteResults", "No remote filesets were deleted");
else
m_result.AddMessage(string.Format("Deleted {0} remote fileset(s)", count));
Logging.Log.WriteInformationMessage(LOGTAG, "DeleteResults", "Deleted {0} remote fileset(s)", count);
}
else
{
if (count == 0)
m_result.AddDryrunMessage("No remote filesets would be deleted");
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteResults", "No remote filesets would be deleted");
else
m_result.AddDryrunMessage(string.Format("{0} remote fileset(s) would be deleted", count));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteResults", "{0} remote fileset(s) would be deleted", count);
if (count > 0 && m_options.Dryrun)
m_result.AddDryrunMessage("Remove --dry-run to actually delete files");
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteHelp", "Remove --dry-run to actually delete files");
}
if (!m_options.NoAutoCompact && (forceCompact || (toDelete != null && toDelete.Length > 0)))
@@ -214,13 +223,13 @@ namespace Duplicati.Library.Main.Operation
private List<DateTime> ApplyRetentionPolicy(DateTime[] backups)
{
// Any work to do?
Dictionary<TimeSpan, TimeSpan> retentionPolicyOptionValue = m_options.RetentionPolicy;
if (retentionPolicyOptionValue.Count == 0 || backups.Length == 0)
var retentionPolicyOptionValues = m_options.RetentionPolicy;
if (retentionPolicyOptionValues.Count == 0 || backups.Length == 0)
{
return new List<DateTime>(); // don't delete any backups
}
Logging.Log.WriteMessage("[Retention Policy]: Start checking if backups can be removed", Logging.LogMessageType.Information);
Logging.Log.WriteInformationMessage(LOGTAG_RETENTION, "StartCheck", "Start checking if backups can be removed");
// Work with a copy to not modify the enumeration that the caller passed
List<DateTime> clonedBackupList = new List<DateTime>(backups);
@@ -234,25 +243,23 @@ namespace Duplicati.Library.Main.Operation
clonedBackupList.RemoveAt(0);
var deleteMostRecentBackup = m_options.AllowFullRemoval;
Logging.Log.WriteMessage(string.Format("[Retention Policy]: Time frames and intervals pairs: {0}",
string.Join(", ", retentionPolicyOptionValue.Select(x => x.Key + " / " + x.Value))), Logging.LogMessageType.Information);
Logging.Log.WriteInformationMessage(LOGTAG_RETENTION, "FramesAndIntervals", "Time frames and intervals pairs: {0}",
string.Join(", ", retentionPolicyOptionValues));
Logging.Log.WriteMessage(string.Format("[Retention Policy]: Backups to consider: {0}",
string.Join(", ", clonedBackupList)), Logging.LogMessageType.Information);
Logging.Log.WriteInformationMessage(LOGTAG_RETENTION, "BackupList", "Backups to consider: {0}",
string.Join(", ", clonedBackupList));
// Collect all potential backups in each time frame and thin out according to the specified interval,
// starting with the oldest backup in that time frame.
// The order in which the time frames values are checked has to be from the smallest to the largest.
List<DateTime> backupsToDelete = new List<DateTime>();
var now = DateTime.Now;
foreach (var singleRetentionPolicyOptionValue in retentionPolicyOptionValue.OrderBy(x => x.Key))
foreach (var singleRetentionPolicyOptionValue in retentionPolicyOptionValues.OrderBy(x => x.Timeframe))
{
var period = singleRetentionPolicyOptionValue.Key;
var interval = singleRetentionPolicyOptionValue.Value;
// The timeframe in the retention policy option is only a timespan which has to be applied to the current DateTime to get the actual lower bound
DateTime timeFrame = (singleRetentionPolicyOptionValue.IsUnlimtedTimeframe()) ? DateTime.MinValue : (now - singleRetentionPolicyOptionValue.Timeframe);
DateTime timeFrame = (period > TimeSpan.Zero) ? (now - period) : DateTime.MinValue; // period equal or below 0 means "biggest time frame possible"
Logging.Log.WriteMessage(string.Format("[Retention Policy]: Next time frame and interval pair: {0} / {1}", timeFrame, interval), Logging.LogMessageType.Profiling);
Logging.Log.WriteProfilingMessage(LOGTAG_RETENTION, "NextTimeAndFrame", "Next time frame and interval pair: {0}", singleRetentionPolicyOptionValue.ToString());
List<DateTime> backupsInTimeFrame = new List<DateTime>();
while (clonedBackupList.Count > 0 && clonedBackupList[0] >= timeFrame)
@@ -261,8 +268,8 @@ namespace Duplicati.Library.Main.Operation
clonedBackupList.RemoveAt(0); // remove from here to not handle the same backup in two time frames
}
Logging.Log.WriteMessage(string.Format("[Retention Policy]: Backups in this time frame: {0}",
string.Join(", ", backupsInTimeFrame)), Logging.LogMessageType.Profiling);
Logging.Log.WriteProfilingMessage(LOGTAG_RETENTION, "BackupsInFrame", "Backups in this time frame: {0}",
string.Join(", ", backupsInTimeFrame));
// Run through backups in this time frame
DateTime? lastKept = null;
@@ -271,14 +278,14 @@ namespace Duplicati.Library.Main.Operation
// Keep this backup if
// - no backup has yet been added to the time frame (keeps at least the oldest backup in a time frame)
// - difference between last added backup and this backup is bigger than the specified interval
if (lastKept == null || (backup - lastKept.Value) >= interval)
if (lastKept == null || singleRetentionPolicyOptionValue.IsKeepAllVersions() || (backup - lastKept.Value) >= singleRetentionPolicyOptionValue.Interval)
{
Logging.Log.WriteMessage(string.Format("[Retention Policy]: Keeping backup: {0}", backup), Logging.LogMessageType.Profiling);
Logging.Log.WriteProfilingMessage(LOGTAG_RETENTION, "KeepBackups", string.Format("Keeping backup: {0}", backup), Logging.LogMessageType.Profiling);
lastKept = backup;
}
else
{
Logging.Log.WriteMessage(string.Format("[Retention Policy]: Deleting backup: {0}", backup), Logging.LogMessageType.Profiling);
Logging.Log.WriteProfilingMessage(LOGTAG_RETENTION, "DeletingBackups", "Deleting backup: {0}", backup);
backupsToDelete.Add(backup);
}
}
@@ -289,19 +296,19 @@ namespace Duplicati.Library.Main.Operation
// Delete all remaining backups
backupsToDelete.AddRange(clonedBackupList);
Logging.Log.WriteMessage(string.Format("[Retention Policy]: Backups outside of all time frames and thus getting deleted: {0}",
string.Join(", ", clonedBackupList)), Logging.LogMessageType.Information);
Logging.Log.WriteInformationMessage(LOGTAG_RETENTION, "BackupsToDelete", "Backups outside of all time frames and thus getting deleted: {0}",
string.Join(", ", clonedBackupList));
// Delete most recent backup if allow-full-removal is set and the most current backup is outside of any time frame
if (deleteMostRecentBackup)
{
backupsToDelete.Add(mostRecentBackup);
Logging.Log.WriteMessage(string.Format("[Retention Policy]: Deleting most recent backup: {0}",
mostRecentBackup), Logging.LogMessageType.Information);
Logging.Log.WriteInformationMessage(LOGTAG_RETENTION, "DeleteMostRecent", "Deleting most recent backup: {0}",
mostRecentBackup);
}
Logging.Log.WriteMessage(string.Format("[Retention Policy]: All backups to delete: {0}",
string.Join(", ", backupsToDelete.OrderByDescending(x => x))), Logging.LogMessageType.Information);
Logging.Log.WriteInformationMessage(LOGTAG_RETENTION, "AllBackupsToDelete", "All backups to delete: {0}",
string.Join(", ", backupsToDelete.OrderByDescending(x => x)));
return backupsToDelete;
}
@@ -24,6 +24,11 @@ namespace Duplicati.Library.Main.Operation
{
internal static class FilelistProcessor
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(FilelistProcessor));
/// <summary>
/// Helper method that verifies uploaded volumes and updates their state in the database.
/// Throws an error if there are issues with the remote storage
@@ -46,20 +51,20 @@ namespace Duplicati.Library.Main.Operation
case RemoteVolumeState.Temporary:
case RemoteVolumeState.Deleting:
case RemoteVolumeState.Uploading:
log.AddMessage(string.Format("removing remote file listed as {0}: {1}", i.State, i.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "RemovingStaleFile", "Removing remote file listed as {0}: {1}", i.State, i.Name);
try
{
backend.Delete(i.Name, i.Size, true);
}
catch (Exception ex)
{
log.AddWarning(string.Format("Failed to erase file {0}, treating as deleted: {1}", i.Name, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "DeleteFileFailed", ex, "Failed to erase file {0}, treating as deleted: {1}", i.Name, ex.Message);
}
break;
default:
log.AddWarning(string.Format("unknown state for remote file listed as {0}: {1}", i.State, i.Name), null);
Logging.Log.WriteWarningMessage(LOGTAG, "UnknownFileState", null, "Unknown state for remote file listed as {0}: {1}", i.State, i.Name);
break;
}
@@ -84,21 +89,21 @@ namespace Duplicati.Library.Main.Operation
foreach(var n in tp.ExtraVolumes)
{
log.AddWarning(string.Format("Extra unknown file: {0}", n.File.Name), null);
Logging.Log.WriteWarningMessage(LOGTAG, "ExtraUnknownFile", null, "Extra unknown file: {0}", n.File.Name);
extraCount++;
}
foreach(var n in tp.MissingVolumes)
{
log.AddWarning(string.Format("Missing file: {0}", n.Name), null);
Logging.Log.WriteWarningMessage(LOGTAG, "MissingFile", null, "Missing file: {0}", n.Name);
missingCount++;
}
if (extraCount > 0)
{
var s = string.Format("Found {0} remote files that are not recorded in local storage, please run repair", extraCount);
log.AddError(s, null);
throw new Duplicati.Library.Interface.UserInformationException(s);
Logging.Log.WriteErrorMessage(LOGTAG, "ExtraRemoteFiles", null, s);
throw new Duplicati.Library.Interface.UserInformationException(s, "ExtraRemoteFiles");
}
ISet<string> doubles;
@@ -107,8 +112,8 @@ namespace Duplicati.Library.Main.Operation
if (doubles.Count > 0)
{
var s = string.Format("Found remote files reported as duplicates, either the backend module is broken or you need to manually remove the extra copies.\nThe following files were found multiple times: {0}", string.Join(", ", doubles));
log.AddError(s, null);
throw new Duplicati.Library.Interface.UserInformationException(s);
Logging.Log.WriteErrorMessage(LOGTAG, "DuplicateRemoteFiles", null, s);
throw new Duplicati.Library.Interface.UserInformationException(s, "DuplicateRemoteFiles");
}
if (missingCount > 0)
@@ -119,8 +124,8 @@ namespace Duplicati.Library.Main.Operation
else
s = string.Format("Found {0} files that are missing from the remote storage, please run repair", missingCount);
log.AddError(s, null);
throw new Duplicati.Library.Interface.UserInformationException(s);
Logging.Log.WriteErrorMessage(LOGTAG, "MissingRemoteFiles", null, s);
throw new Duplicati.Library.Interface.UserInformationException(s, "MissingRemoteFiles");
}
}
@@ -166,7 +171,7 @@ namespace Duplicati.Library.Main.Operation
if (options.Dryrun)
{
result.AddDryrunMessage(string.Format("Would upload verification file: {0}, size: {1}", remotename, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(tempfile).Length)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadVerificationFile", "Would upload verification file: {0}, size: {1}", remotename, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(tempfile).Length));
}
else
{
@@ -235,7 +240,7 @@ namespace Duplicati.Library.Main.Operation
if (!log.ReportedQuotaError && quota.FreeQuotaSpace == 0)
{
log.ReportedQuotaError = true;
log.AddError(string.Format("Backend quota has been exceeded: Using {0} of {1} ({2} available)", Library.Utility.Utility.FormatSizeString(knownFileSize), Library.Utility.Utility.FormatSizeString(quota.TotalQuotaSpace), Library.Utility.Utility.FormatSizeString(quota.FreeQuotaSpace)), null);
Logging.Log.WriteErrorMessage(LOGTAG, "BackendQuotaExceeded", null, "Backend quota has been exceeded: Using {0} of {1} ({2} available)", Library.Utility.Utility.FormatSizeString(knownFileSize), Library.Utility.Utility.FormatSizeString(quota.TotalQuotaSpace), Library.Utility.Utility.FormatSizeString(quota.FreeQuotaSpace));
}
else if (!log.ReportedQuotaWarning && !log.ReportedQuotaError && quota.FreeQuotaSpace >= 0) // Negative value means the backend didn't return the quota info
{
@@ -244,7 +249,7 @@ namespace Duplicati.Library.Main.Operation
if (quota.FreeQuotaSpace < warningThreshold * knownFileSize)
{
log.ReportedQuotaWarning = true;
log.AddWarning(string.Format("Backend quota is close to being exceeded: Using {0} of {1} ({2} available)", Library.Utility.Utility.FormatSizeString(knownFileSize), Library.Utility.Utility.FormatSizeString(quota.TotalQuotaSpace), Library.Utility.Utility.FormatSizeString(quota.FreeQuotaSpace)), null);
Logging.Log.WriteWarningMessage(LOGTAG, "BackendQuotaNear" , null, "Backend quota is close to being exceeded: Using {0} of {1} ({2} available)", Library.Utility.Utility.FormatSizeString(knownFileSize), Library.Utility.Utility.FormatSizeString(quota.TotalQuotaSpace), Library.Utility.Utility.FormatSizeString(quota.FreeQuotaSpace));
}
}
}
@@ -280,7 +285,7 @@ namespace Duplicati.Library.Main.Operation
{
case RemoteVolumeState.Deleted:
if (remoteFound)
log.AddMessage(string.Format("ignoring remote file listed as {0}: {1}", i.State, i.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "IgnoreRemoteDeletedFile", "ignoring remote file listed as {0}: {1}", i.State, i.Name);
break;
@@ -288,24 +293,24 @@ namespace Duplicati.Library.Main.Operation
case RemoteVolumeState.Deleting:
if (remoteFound)
{
log.AddMessage(string.Format("removing remote file listed as {0}: {1}", i.State, i.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "RemoveUnwantedRemoteFile", "removing remote file listed as {0}: {1}", i.State, i.Name);
backend.Delete(i.Name, i.Size, true);
}
else
{
if (i.deleteGracePeriod > DateTime.UtcNow)
{
log.AddMessage(string.Format("keeping delete request for {0} until {1}", i.Name, i.deleteGracePeriod.ToLocalTime()));
Logging.Log.WriteInformationMessage(LOGTAG, "KeepDeleteRequest", "keeping delete request for {0} until {1}", i.Name, i.deleteGracePeriod.ToLocalTime());
}
else
{
if (string.Equals(i.Name, protectedfile) && i.State == RemoteVolumeState.Temporary)
{
log.AddMessage(string.Format("keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name);
}
else
{
log.AddMessage(string.Format("removing file listed as {0}: {1}", i.State, i.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "RemoteUnwantedMissingFile", "removing file listed as {0}: {1}", i.State, i.Name);
cleanupRemovedRemoteVolumes.Add(i.Name);
}
}
@@ -314,7 +319,7 @@ namespace Duplicati.Library.Main.Operation
case RemoteVolumeState.Uploading:
if (remoteFound && correctSize && r.File.Size >= 0)
{
log.AddMessage(string.Format("promoting uploaded complete file from {0} to {2}: {1}", i.State, i.Name, RemoteVolumeState.Uploaded));
Logging.Log.WriteInformationMessage(LOGTAG, "PromotingCompleteFile", "promoting uploaded complete file from {0} to {2}: {1}", i.State, i.Name, RemoteVolumeState.Uploaded);
database.UpdateRemoteVolume(i.Name, RemoteVolumeState.Uploaded, i.Size, i.Hash);
}
else if (!remoteFound)
@@ -322,12 +327,12 @@ namespace Duplicati.Library.Main.Operation
if (string.Equals(i.Name, protectedfile))
{
log.AddMessage(string.Format("keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name);
database.UpdateRemoteVolume(i.Name, RemoteVolumeState.Temporary, i.Size, i.Hash, false, new TimeSpan(0), null);
}
else
{
log.AddMessage(string.Format("scheduling missing file for deletion, currently listed as {0}: {1}", i.State, i.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "SchedulingMissingFileForDelete", "scheduling missing file for deletion, currently listed as {0}: {1}", i.State, i.Name);
cleanupRemovedRemoteVolumes.Add(i.Name);
database.UpdateRemoteVolume(i.Name, RemoteVolumeState.Deleting, i.Size, i.Hash, false, TimeSpan.FromHours(2), null);
}
@@ -336,11 +341,11 @@ namespace Duplicati.Library.Main.Operation
{
if (string.Equals(i.Name, protectedfile))
{
log.AddMessage(string.Format("keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name);
}
else
{
log.AddMessage(string.Format("removing incomplete remote file listed as {0}: {1}", i.State, i.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "Remove incomplete file", "removing incomplete remote file listed as {0}: {1}", i.State, i.Name);
backend.Delete(i.Name, i.Size, true);
}
}
@@ -365,7 +370,7 @@ namespace Duplicati.Library.Main.Operation
break;
default:
log.AddWarning(string.Format("unknown state for remote file listed as {0}: {1}", i.State, i.Name), null);
Logging.Log.WriteWarningMessage(LOGTAG, "UnknownFileState", null, "unknown state for remote file listed as {0}: {1}", i.State, i.Name);
break;
}
@@ -376,7 +381,7 @@ namespace Duplicati.Library.Main.Operation
database.RemoveRemoteVolumes(cleanupRemovedRemoteVolumes, null);
foreach(var i in missingHash)
log.AddWarning(string.Format("remote file {1} is listed as {0} with size {2} but should be {3}, please verify the sha256 hash \"{4}\"", i.Item2.State, i.Item2.Name, i.Item1, i.Item2.Size, i.Item2.Hash), null);
Logging.Log.WriteWarningMessage(LOGTAG, "MissingRemoteHash", null, "remote file {1} is listed as {0} with size {2} but should be {3}, please verify the sha256 hash \"{4}\"", i.Item2.State, i.Item2.Name, i.Item1, i.Item2.Size, i.Item2.Hash);
return new RemoteAnalysisResult() {
ParsedVolumes = remotelist,
@@ -36,7 +36,7 @@ namespace Duplicati.Library.Main.Operation
public void Run(List<string> args, Action<Duplicati.Library.Interface.IListAffectedResults> callback = null)
{
if (!System.IO.File.Exists(m_options.Dbpath))
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist");
using(var db = new Database.LocalListAffectedDatabase(m_options.Dbpath))
{
@@ -24,6 +24,10 @@ namespace Duplicati.Library.Main.Operation
{
internal class ListBrokenFilesHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ListBrokenFilesHandler));
protected string m_backendurl;
protected Options m_options;
protected ListBrokenFilesResults m_result;
@@ -38,7 +42,7 @@ namespace Duplicati.Library.Main.Operation
public void Run(Library.Utility.IFilter filter, Func<long, DateTime, long, string, long, bool> callbackhandler = null)
{
if (!System.IO.File.Exists(m_options.Dbpath))
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist");
using (var db = new Database.LocalListBrokenFilesDatabase(m_options.Dbpath))
using (var tr = db.BeginTransaction())
@@ -53,20 +57,20 @@ namespace Duplicati.Library.Main.Operation
if (brokensets.Length == 0)
{
if (db.RepairInProgress)
throw new UserInformationException("Cannot continue because the database is marked as being under repair, but does not have broken files.");
throw new UserInformationException("Cannot continue because the database is marked as being under repair, but does not have broken files.", "CannotListOnDatabaseInRepair");
result.AddMessage("No broken filesets found in database, checking for missing remote files");
Logging.Log.WriteInformationMessage(LOGTAG, "NoBrokenFilesetsInDatabase", "No broken filesets found in database, checking for missing remote files");
using (var backend = new BackendManager(backendurl, options, result.BackendWriter, db))
{
var remotestate = FilelistProcessor.RemoteListAnalysis(backend, options, db, result.BackendWriter, null);
if (!remotestate.ParsedVolumes.Any())
throw new UserInformationException("No remote volumes were found, refusing purge");
throw new UserInformationException("No remote volumes were found, refusing purge", "CannotPurgeWithNoRemoteVolumes");
missing = remotestate.MissingVolumes.ToList();
if (missing.Count == 0)
{
result.AddMessage("Skipping operation because no files were found to be missing, and no filesets were recorded as broken.");
Logging.Log.WriteInformationMessage(LOGTAG, "NoMissingFilesFound", "Skipping operation because no files were found to be missing, and no filesets were recorded as broken.");
return null;
}
@@ -74,7 +78,7 @@ namespace Duplicati.Library.Main.Operation
foreach (var f in missing)
db.UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, transaction);
result.AddMessage(string.Format("Marked {0} remote files for deletion", missing.Count));
Logging.Log.WriteInformationMessage(LOGTAG, "MarkedRemoteFilesForDeletion", "Marked {0} remote files for deletion", missing.Count);
// Drop all content from tables
db.RemoveMissingBlocks(missing.Select(x => x.Name), transaction);
@@ -88,10 +92,10 @@ namespace Duplicati.Library.Main.Operation
private void DoRun(Database.LocalListBrokenFilesDatabase db, System.Data.IDbTransaction transaction, Library.Utility.IFilter filter, Func<long, DateTime, long, string, long, bool> callbackhandler)
{
if (filter != null && !filter.Empty)
throw new UserInformationException("Filters are not supported for this operation");
throw new UserInformationException("Filters are not supported for this operation", "FiltersAreNotSupportedForListBrokenFiles");
if (db.PartiallyRecreated)
throw new UserInformationException("The command does not work on partially recreated databases");
throw new UserInformationException("The command does not work on partially recreated databases", "ListBrokenFilesDoesNotWorkOnPartialDatabase");
List<Database.RemoteVolumeEntry> missing;
var brokensets = GetBrokenFilesetsFromRemote(m_backendurl, m_result, db, transaction, m_options, out missing);
@@ -101,7 +105,7 @@ namespace Duplicati.Library.Main.Operation
if (brokensets.Length == 0)
{
m_result.BrokenFiles = new Tuple<long, DateTime, IEnumerable<Tuple<string, long>>>[0];
m_result.AddMessage("No broken filesets found");
Logging.Log.WriteInformationMessage(LOGTAG, "NoMissingFilesFound", "No broken filesets found");
return;
}
@@ -24,6 +24,11 @@ namespace Duplicati.Library.Main.Operation
{
internal class ListChangesHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ListChangesHandler));
private string m_backendurl;
private Options m_options;
private ListChangesResults m_result;
@@ -80,7 +85,7 @@ namespace Duplicati.Library.Main.Operation
{
var dbtimes = db.FilesetTimes.ToList();
if (dbtimes.Count < 2)
throw new UserInformationException(string.Format("Need at least two backups to show differences, database contains {0} backups", dbtimes.Count));
throw new UserInformationException(string.Format("Need at least two backups to show differences, database contains {0} backups", dbtimes.Count), "NeedTwoBackupsToStartDiff");
long baseVersionId;
long compareVersionId;
@@ -95,7 +100,7 @@ namespace Duplicati.Library.Main.Operation
}
else
{
m_result.AddMessage("No local database, accessing remote store");
Logging.Log.WriteInformationMessage(LOGTAG, "NoLocalDatabase", "No local database, accessing remote store");
var parsedlist = (from n in backend.List()
let p = Volumes.VolumeBase.ParseFilename(n)
@@ -105,7 +110,7 @@ namespace Duplicati.Library.Main.Operation
var numberedList = parsedlist.Zip(Enumerable.Range(0, parsedlist.Length), (a, b) => new Tuple<long, DateTime, Volumes.IParsedVolume>(b, a.Time, a)).ToList();
if (numberedList.Count < 2)
throw new UserInformationException(string.Format("Need at least two backups to show differences, database contains {0} backups", numberedList.Count));
throw new UserInformationException(string.Format("Need at least two backups to show differences, database contains {0} backups", numberedList.Count), "NeedTwoBackupsToStartDiff");
Volumes.IParsedVolume baseFile;
Volumes.IParsedVolume compareFile;
@@ -150,7 +155,7 @@ namespace Duplicati.Library.Main.Operation
var changes = storageKeeper.CreateChangeCountReport();
var sizes = storageKeeper.CreateChangeSizeReport();
var lst = (m_options.Verbose || m_options.FullResult || callback != null) ?
var lst = (m_options.FullResult || callback != null) ?
(from n in storageKeeper.CreateChangedFileReport()
select n) : null;
@@ -8,6 +8,11 @@ namespace Duplicati.Library.Main.Operation
{
internal class ListFilesHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<ListFilesHandler>();
private string m_backendurl;
private Options m_options;
private ListResults m_result;
@@ -67,13 +72,13 @@ namespace Duplicati.Library.Main.Operation
}
}
m_result.AddMessage("No local database, accessing remote store");
Logging.Log.WriteInformationMessage(LOGTAG, "NoLocalDatabase", "No local database, accessing remote store");
//TODO: Add prefix and foldercontents
if (m_options.ListFolderContents)
throw new UserInformationException("Listing folder contents is not supported without a local database, consider using the \"repair\" option to rebuild the database.");
throw new UserInformationException("Listing folder contents is not supported without a local database, consider using the \"repair\" option to rebuild the database.", "FolderContentListingRequiresLocalDatabase");
else if (m_options.ListPrefixOnly)
throw new UserInformationException("Listing prefixes is not supported without a local database, consider using the \"repair\" option to rebuild the database.");
throw new UserInformationException("Listing prefixes is not supported without a local database, consider using the \"repair\" option to rebuild the database.", "PrefixListingRequiresLocalDatabase");
// Otherwise, grab info from remote location
using (var tmpdb = new Library.Utility.TempFile())
@@ -84,7 +89,7 @@ namespace Duplicati.Library.Main.Operation
var filteredList = ParseAndFilterFilesets(backend.List(), m_options);
if (filteredList.Count == 0)
throw new UserInformationException("No filesets found on remote target");
throw new UserInformationException("No filesets found on remote target", "EmptyRemoteFolder");
var numberSeq = CreateResultSequence(filteredList);
if (filter.Empty)
@@ -23,6 +23,10 @@ namespace Duplicati.Library.Main.Operation
{
internal class PurgeBrokenFilesHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(PurgeBrokenFilesHandler));
protected string m_backendurl;
protected Options m_options;
protected PurgeBrokenFilesResults m_result;
@@ -37,10 +41,10 @@ namespace Duplicati.Library.Main.Operation
public void Run(Library.Utility.IFilter filter)
{
if (!System.IO.File.Exists(m_options.Dbpath))
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist");
if (filter != null && !filter.Empty)
throw new UserInformationException("Filters are not supported for this operation");
throw new UserInformationException("Filters are not supported for this operation", "FiltersNotAllowedOnPurgeBrokenFiles");
List<Database.RemoteVolumeEntry> missing = null;
@@ -48,7 +52,7 @@ namespace Duplicati.Library.Main.Operation
using (var tr = db.BeginTransaction())
{
if (db.PartiallyRecreated)
throw new UserInformationException("The command does not work on partially recreated databases");
throw new UserInformationException("The command does not work on partially recreated databases", "CannotPurgeOnPartialDatabase");
var sets = ListBrokenFilesHandler.GetBrokenFilesetsFromRemote(m_backendurl, m_result, db, tr, m_options, out missing);
if (sets == null)
@@ -57,14 +61,14 @@ namespace Duplicati.Library.Main.Operation
if (sets.Length == 0)
{
if (missing == null)
m_result.AddMessage("Found no broken filesets");
Logging.Log.WriteInformationMessage(LOGTAG, "NoBrokenFilesets", "Found no broken filesets");
else if (missing.Count == 0)
m_result.AddMessage("Found no broken filesets and no missing remote files");
Logging.Log.WriteInformationMessage(LOGTAG, "NoBrokenFilesetsOrMissingFiles", "Found no broken filesets and no missing remote files");
else
throw new UserInformationException(string.Format("Found no broken filesets, but {0} missing remote files", sets.Length));
throw new UserInformationException(string.Format("Found no broken filesets, but {0} missing remote files", sets.Length), "NoBrokenSetsButMissingRemoteFiles");
}
m_result.AddMessage(string.Format("Found {0} broken filesets with {1} affected files, purging files", sets.Length, sets.Sum(x => x.Item3)));
Logging.Log.WriteInformationMessage(LOGTAG, "FoundBrokenFilesets", "Found {0} broken filesets with {1} affected files, purging files", sets.Length, sets.Sum(x => x.Item3));
var pgoffset = 0.0f;
var pgspan = 0.95f / sets.Length;
@@ -86,9 +90,9 @@ namespace Duplicati.Library.Main.Operation
if (fully_emptied.Length != 0)
{
if (fully_emptied.Length == 1)
m_result.AddMessage(string.Format("Removing entire fileset {1} as all {0} file(s) are broken", fully_emptied.First().Timestamp, fully_emptied.First().RemoveCount));
Logging.Log.WriteInformationMessage(LOGTAG, "RemovingFilesets", "Removing entire fileset {1} as all {0} file(s) are broken", fully_emptied.First().Timestamp, fully_emptied.First().RemoveCount);
else
m_result.AddMessage(string.Format("Removing {0} filesets where all file(s) are broken: {1}", fully_emptied.Length, string.Join(", ", fully_emptied.Select(x => x.Timestamp.ToLocalTime().ToString()))));
Logging.Log.WriteInformationMessage(LOGTAG, "RemovingFilesets", "Removing {0} filesets where all file(s) are broken: {1}", fully_emptied.Length, string.Join(", ", fully_emptied.Select(x => x.Timestamp.ToLocalTime().ToString())));
m_result.DeleteResults = new DeleteResults(m_result);
using (var rmdb = new Database.LocalDeleteDatabase(db))
@@ -106,7 +110,7 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.Dryrun)
{
using (new Logging.Timer("CommitDelete"))
using (new Logging.Timer(LOGTAG, "CommitDelete", "CommitDelete"))
deltr.Commit();
rmdb.WriteResults();
@@ -133,7 +137,7 @@ namespace Duplicati.Library.Main.Operation
foreach (var bs in to_purge)
{
m_result.AddMessage(string.Format("Purging {0} file(s) from fileset {1}", bs.RemoveCount, bs.Timestamp.ToLocalTime()));
Logging.Log.WriteInformationMessage(LOGTAG, "PurgingFiles", "Purging {0} file(s) from fileset {1}", bs.RemoveCount, bs.Timestamp.ToLocalTime());
var opts = new Options(new Dictionary<string, string>(m_options.RawOptions));
using (var pgdb = new Database.LocalPurgeDatabase(db))
@@ -174,7 +178,7 @@ namespace Duplicati.Library.Main.Operation
{
foreach (var f in missing)
if (m_options.Dryrun)
m_result.AddDryrunMessage(string.Format("Would delete remote file: {0}, size: {1}", f.Name, Library.Utility.Utility.FormatSizeString(f.Size)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteRemoteFile", "Would delete remote file: {0}, size: {1}", f.Name, Library.Utility.Utility.FormatSizeString(f.Size));
else
backend.Delete(f.Name, f.Size);
}
@@ -182,9 +186,9 @@ namespace Duplicati.Library.Main.Operation
if (!m_options.Dryrun && db.RepairInProgress)
{
m_result.AddMessage("Database was previously marked as in-progress, checking if it is valid after purging files");
Logging.Log.WriteInformationMessage(LOGTAG, "ValidatingDatabase", "Database was previously marked as in-progress, checking if it is valid after purging files");
db.VerifyConsistency(null, m_options.Blocksize, m_options.BlockhashSize, true);
m_result.AddMessage("Purge completed, and consistency checks completed, marking database as complete");
Logging.Log.WriteInformationMessage(LOGTAG, "UpdatingDatabase", "Purge completed, and consistency checks completed, marking database as complete");
db.RepairInProgress = false;
}
@@ -22,6 +22,10 @@ namespace Duplicati.Library.Main.Operation
{
internal class PurgeFilesHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<PurgeFilesHandler>();
protected string m_backendurl;
protected Options m_options;
protected PurgeFilesResults m_result;
@@ -36,10 +40,10 @@ namespace Duplicati.Library.Main.Operation
public void Run(Library.Utility.IFilter filter)
{
if (filter == null || filter.Empty)
throw new UserInformationException("Cannot purge with an empty filter, as that would cause all files to be removed.\nTo remove an entire backup set, use the \"delete\" command.");
throw new UserInformationException("Cannot purge with an empty filter, as that would cause all files to be removed.\nTo remove an entire backup set, use the \"delete\" command.", "EmptyFilterPurgeNotAllowed");
if (!System.IO.File.Exists(m_options.Dbpath))
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist");
using (var db = new Database.LocalPurgeDatabase(m_options.Dbpath))
DoRun(db, filter, null, 0, 1);
@@ -53,25 +57,25 @@ namespace Duplicati.Library.Main.Operation
private void DoRun(Database.LocalPurgeDatabase db, Library.Utility.IFilter filter, Action<System.Data.IDbCommand, long, string> filtercommand, float pgoffset, float pgspan)
{
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.PurgeFiles_Begin);
m_result.AddMessage("Starting purge operation");
Logging.Log.WriteInformationMessage(LOGTAG, "StartingPurge", "Starting purge operation");
var doCompactStep = !m_options.NoAutoCompact && filtercommand == null;
using (var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, db))
{
if (db.PartiallyRecreated)
throw new UserInformationException("The purge command does not work on partially recreated databases");
throw new UserInformationException("The purge command does not work on partially recreated databases", "PurgeNotAllowedOnPartialDatabase");
if (db.RepairInProgress && filtercommand == null)
throw new UserInformationException(string.Format("The purge command does not work on an incomplete database, try the {0} operation.", "purge-broken-files"));
throw new UserInformationException(string.Format("The purge command does not work on an incomplete database, try the {0} operation.", "purge-broken-files"), "PurgeNotAllowedOnIncompleteDatabase");
var versions = db.GetFilesetIDs(m_options.Time, m_options.Version).ToArray();
if (versions.Length <= 0)
throw new UserInformationException("No filesets matched the supplied time or versions");
throw new UserInformationException("No filesets matched the supplied time or versions", "NoFilesetFoundForTimeOrVersion");
var orphans = db.CountOrphanFiles(null);
if (orphans != 0)
throw new UserInformationException(string.Format("Unable to start the purge process as there are {0} orphan file(s)", orphans));
throw new UserInformationException(string.Format("Unable to start the purge process as there are {0} orphan file(s)", orphans), "CannotPurgeWithOrphans");
Utility.UpdateOptionsFromDb(db, m_options);
Utility.VerifyParameters(db, m_options);
@@ -137,7 +141,7 @@ namespace Duplicati.Library.Main.Operation
if (tempset.RemovedFileCount == 0)
{
m_result.AddMessage(string.Format("Not writing a new fileset for {0} as it was not changed", prevfilename));
Logging.Log.WriteInformationMessage(LOGTAG, "NotWritingNewFileset", "Not writing a new fileset for {0} as it was not changed", prevfilename);
currentprogress += versionprogress;
tr.Rollback();
continue;
@@ -150,7 +154,7 @@ namespace Duplicati.Library.Main.Operation
var newids = tempset.ConvertToPermanentFileset(vol.RemoteFilename, ts);
vol.VolumeID = newids.Item1;
m_result.AddMessage(string.Format("Replacing fileset {0} with {1} which has with {2} fewer file(s) ({3} reduction)", prevfilename, vol.RemoteFilename, tempset.RemovedFileCount, Library.Utility.Utility.FormatSizeString(tempset.RemovedFileSize)));
Logging.Log.WriteInformationMessage(LOGTAG, "ReplacingFileset", "Replacing fileset {0} with {1} which has with {2} fewer file(s) ({3} reduction)", prevfilename, vol.RemoteFilename, tempset.RemovedFileCount, Library.Utility.Utility.FormatSizeString(tempset.RemovedFileSize));
db.WriteFileset(vol, tr, newids.Item2);
@@ -161,30 +165,28 @@ namespace Duplicati.Library.Main.Operation
currentprogress += (versionprogress / 2);
m_result.OperationProgressUpdater.UpdateProgress(currentprogress);
if (m_options.Dryrun || m_options.Verbose || Logging.Log.LogLevel == Logging.LogMessageType.Profiling)
if (m_options.Dryrun || m_options.FullResult)
{
foreach (var fe in tempset.ListAllDeletedFiles())
{
var msg = string.Format(" Purging file {0} ({1})", fe.Key, Library.Utility.Utility.FormatSizeString(fe.Value));
if (Logging.Log.LogLevel == Logging.LogMessageType.Profiling)
Logging.Log.WriteMessage(msg, Logging.LogMessageType.Profiling);
Logging.Log.WriteProfilingMessage(LOGTAG, "PurgeFile", msg);
Logging.Log.WriteVerboseMessage(LOGTAG, "PurgeFile", msg);
if (m_options.Dryrun)
m_result.AddDryrunMessage(msg);
else if (m_options.Verbose)
m_result.AddVerboseMessage(msg);
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPurgeFile", msg);
}
if (m_options.Dryrun)
m_result.AddDryrunMessage("Writing files to remote storage");
else if (m_options.Verbose)
m_result.AddVerboseMessage("Writing files to remote storage");
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldWriteRemoteFiles", "Would write files to remote storage");
Logging.Log.WriteVerboseMessage(LOGTAG, "WritingRemoteFiles", "Writing files to remote storage");
}
if (m_options.Dryrun)
{
m_result.AddDryrunMessage(string.Format("Would upload file {0} ({1}) and delete file {2}, removing {3} files", vol.RemoteFilename, Library.Utility.Utility.FormatSizeString(vol.Filesize), prevfilename, tempset.RemovedFileCount));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadAndDelete", "Would upload file {0} ({1}) and delete file {2}, removing {3} files", vol.RemoteFilename, Library.Utility.Utility.FormatSizeString(vol.Filesize), prevfilename, tempset.RemovedFileCount);
tr.Rollback();
}
else
@@ -212,7 +214,7 @@ namespace Duplicati.Library.Main.Operation
{
if (m_result.RewrittenFileLists == 0)
{
m_result.AddMessage("Skipping compacting as no new volumes were written");
Logging.Log.WriteInformationMessage(LOGTAG, "SkippingCompacting", "Skipping compacting as no new volumes were written");
}
else
{
@@ -9,6 +9,11 @@ namespace Duplicati.Library.Main.Operation
{
internal class RecreateDatabaseHandler : IDisposable
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<RecreateDatabaseHandler>();
private string m_backendurl;
private Options m_options;
private RecreateDatabaseResults m_result;
@@ -33,7 +38,7 @@ namespace Duplicati.Library.Main.Operation
public void Run(string path, Library.Utility.IFilter filter = null, NumberedFilterFilelistDelegate filelistfilter = null, BlockVolumePostProcessor blockprocessor = null)
{
if (System.IO.File.Exists(path))
throw new UserInformationException(string.Format("Cannot recreate database because file already exists: {0}", path));
throw new UserInformationException(string.Format("Cannot recreate database because file already exists: {0}", path), "RecreateTargetDatabaseExists");
using(var db = new LocalDatabase(path, "Recreate", true))
{
@@ -52,14 +57,14 @@ namespace Duplicati.Library.Main.Operation
public void RunUpdate(Library.Utility.IFilter filter = null, NumberedFilterFilelistDelegate filelistfilter = null, BlockVolumePostProcessor blockprocessor = null)
{
if (!m_options.RepairOnlyPaths)
throw new UserInformationException(string.Format("Can only update with paths, try setting {0}", "--repair-only-paths"));
throw new UserInformationException(string.Format("Can only update with paths, try setting {0}", "--repair-only-paths"), "RepairUpdateRequiresPathsOnly");
using(var db = new LocalDatabase(m_options.Dbpath, "Recreate", true))
{
m_result.SetDatabase(db);
if (db.FindMatchingFilesets(m_options.Time, m_options.Version).Any())
throw new UserInformationException(string.Format("The version(s) being updated to, already exists"));
throw new UserInformationException(string.Format("The version(s) being updated to, already exists"), "UpdateVersionAlreadyExists");
// Mark as incomplete
db.PartiallyRecreated = true;
@@ -106,7 +111,7 @@ namespace Duplicati.Library.Main.Operation
if (remotefiles.Length == 0)
{
if (rawlist.Count == 0)
throw new UserInformationException("No files were found at the remote location, perhaps the target url is incorrect?");
throw new UserInformationException("No files were found at the remote location, perhaps the target url is incorrect?", "EmptyRemoteLocation");
else
{
var tmp =
@@ -118,11 +123,11 @@ namespace Duplicati.Library.Main.Operation
var types = tmp.Distinct().ToArray();
if (tmp.Length == 0)
throw new UserInformationException(string.Format("Found {0} files at the remote storage, but none that could be parsed", rawlist.Count));
throw new UserInformationException(string.Format("Found {0} files at the remote storage, but none that could be parsed", rawlist.Count), "EmptyRemoteLocation");
else if (types.Length == 1)
throw new UserInformationException(string.Format("Found {0} parse-able files with the prefix {1}, did you forget to set the backup prefix?", tmp.Length, types[0]));
throw new UserInformationException(string.Format("Found {0} parse-able files with the prefix {1}, did you forget to set the backup prefix?", tmp.Length, types[0]), "EmptyRemoteLocationWithPrefix");
else
throw new UserInformationException(string.Format("Found {0} parse-able files (of {1} files) with different prefixes: {2}, did you forget to set the backup prefix?", tmp.Length, rawlist.Count, string.Join(", ", types)));
throw new UserInformationException(string.Format("Found {0} parse-able files (of {1} files) with different prefixes: {2}, did you forget to set the backup prefix?", tmp.Length, rawlist.Count, string.Join(", ", types)), "EmptyRemoteLocationWithPrefix");
}
}
@@ -135,13 +140,13 @@ namespace Duplicati.Library.Main.Operation
select n;
if (filelists.Count() <= 0)
throw new UserInformationException(string.Format("No filelists found on the remote destination"));
throw new UserInformationException(string.Format("No filelists found on the remote destination"), "EmptyRemoteLocation");
if (filelistfilter != null)
filelists = filelistfilter(filelists).Select(x => x.Value).ToArray();
if (filelists.Count() <= 0)
throw new UserInformationException(string.Format("No filelists"));
throw new UserInformationException(string.Format("No filelists"), "NoMatchingRemoteFilelists");
// If we are updating, all files should be accounted for
foreach(var fl in remotefiles)
@@ -159,7 +164,7 @@ namespace Duplicati.Library.Main.Operation
using(var tr = restoredb.BeginTransaction())
{
var filelistWork = (from n in filelists orderby n.Time select new RemoteVolume(n.File) as IRemoteVolume).ToList();
m_result.AddMessage(string.Format("Rebuild database started, downloading {0} filelists", filelistWork.Count));
Logging.Log.WriteInformationMessage(LOGTAG, "RebuildStarted", "Rebuild database started, downloading {0} filelists", filelistWork.Count);
var progress = 0;
@@ -244,7 +249,7 @@ namespace Duplicati.Library.Main.Operation
else if (m_options.BlockHashAlgorithm == m_options.FileHashAlgorithm)
restoredb.AddSmallBlocksetLink(fe.Hash, fe.Hash, fe.Size, tr);
else
m_result.AddWarning(string.Format("No block hash found for file: {0}", fe.Path), null);
Logging.Log.WriteWarningMessage(LOGTAG, "MissingBlockHash", null, "No block hash found for file: {0}", fe.Path);
}
break;
@@ -253,7 +258,7 @@ namespace Duplicati.Library.Main.Operation
restoredb.AddSymlinkEntry(filesetid, fe.Path, fe.Time, metadataid, tr);
break;
default:
m_result.AddWarning(string.Format("Skipping file-entry with unknown type {0}: {1} ", fe.Type, fe.Path), null);
Logging.Log.WriteWarningMessage(LOGTAG, "SkippingUnknownFileEntry", null, "Skipping file-entry with unknown type {0}: {1} ", fe.Type, fe.Path);
break;
}
@@ -264,19 +269,19 @@ namespace Duplicati.Library.Main.Operation
else if (m_options.BlockHashAlgorithm == m_options.FileHashAlgorithm)
restoredb.AddSmallBlocksetLink(fe.Metahash, fe.Metahash, fe.Metasize, tr);
else
m_result.AddWarning(string.Format("No block hash found for file metadata: {0}", fe.Path), null);
Logging.Log.WriteWarningMessage(LOGTAG, "MissingMetadataBlockHash", null, "No block hash found for file metadata: {0}", fe.Path);
}
}
catch (Exception ex)
{
m_result.AddWarning(string.Format("Failed to process file-entry: {0}", fe.Path), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "FileEntryProcessingFailed", ex, "Failed to process file-entry: {0}", fe.Path);
}
}
}
}
catch (Exception ex)
{
m_result.AddWarning(string.Format("Failed to process file: {0}", entry.Name), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "FileProcessingFailed", ex, "Failed to process file: {0}", entry.Name);
if (ex is System.Threading.ThreadAbortException)
{
m_result.EndTime = DateTime.UtcNow;
@@ -294,7 +299,7 @@ namespace Duplicati.Library.Main.Operation
if (!updating)
Utility.VerifyParameters(restoredb, m_options, tr);
using(new Logging.Timer("CommitUpdateFilesetFromRemote"))
using(new Logging.Timer(LOGTAG, "CommitUpdateFilesetFromRemote", "CommitUpdateFilesetFromRemote"))
tr.Commit();
}
@@ -302,7 +307,7 @@ namespace Duplicati.Library.Main.Operation
{
var hashalg = Library.Utility.HashAlgorithmHelper.Create(m_options.BlockHashAlgorithm);
if (hashalg == null)
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm), "BlockHashAlgorithmNotSupported");
var hashsize = hashalg.HashSize / 8;
//Grab all index files, and update the block table
@@ -313,7 +318,7 @@ namespace Duplicati.Library.Main.Operation
where n.FileType == RemoteVolumeType.Index
select new RemoteVolume(n.File) as IRemoteVolume).ToList();
m_result.AddMessage(string.Format("Filelists restored, downloading {0} index files", indexfiles.Count));
Logging.Log.WriteInformationMessage(LOGTAG, "FilelistsRestored", "Filelists restored, downloading {0} index files", indexfiles.Count);
var progress = 0;
@@ -352,7 +357,7 @@ namespace Duplicati.Library.Main.Operation
var p = VolumeBase.ParseFilename(filename);
if (p == null)
throw new Exception(string.Format("Unable to parse filename: {0}", filename));
m_result.AddError(string.Format("Remote file referenced as {0}, but not found in list, registering a missing remote file", filename), null);
Logging.Log.WriteErrorMessage(LOGTAG, "MissingFileDetected", null, "Remote file referenced as {0}, but not found in list, registering a missing remote file", filename);
volumeID = restoredb.RegisterRemoteVolume(filename, p.FileType, RemoteVolumeState.Verified, tr);
}
@@ -373,7 +378,7 @@ namespace Duplicati.Library.Main.Operation
catch (Exception ex)
{
//Not fatal
m_result.AddWarning(string.Format("Failed to process index file: {0}", sf.Name), ex);
Logging.Log.WriteErrorMessage(LOGTAG, "IndexFileProcessingFailed", ex, "Failed to process index file: {0}", sf.Name);
if (ex is System.Threading.ThreadAbortException)
{
m_result.EndTime = DateTime.UtcNow;
@@ -381,7 +386,7 @@ namespace Duplicati.Library.Main.Operation
}
}
using(new Logging.Timer("CommitRecreatedDb"))
using(new Logging.Timer(LOGTAG, "CommitRecreateDb", "CommitRecreatedDb"))
tr.Commit();
// TODO: In some cases, we can avoid downloading all index files,
@@ -399,25 +404,21 @@ namespace Duplicati.Library.Main.Operation
var lst = restoredb.GetMissingBlockListVolumes(i, m_options.Blocksize, hashsize).ToList();
if (lst.Count > 0)
{
var fullist = ": " + string.Join(", ", lst.Select(x => x.Name));
switch (i)
{
case 0:
if (m_options.Verbose)
m_result.AddVerboseMessage("Processing required {0} blocklist volumes: {1}", lst.Count, string.Join(", ", lst.Select(x => x.Name)));
else
m_result.AddMessage(string.Format("Processing required {0} blocklist volumes", lst.Count));
Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingRequiredBlocklistVolumes", "Processing required {0} blocklist volumes{1}", lst.Count, fullist);
Logging.Log.WriteInformationMessage(LOGTAG, "ProcessingRequiredBlocklistVolumes", "Processing required {0} blocklist volumes{1}", lst.Count, m_options.FullResult ? fullist : string.Empty);
break;
case 1:
if (m_options.Verbose)
m_result.AddVerboseMessage("Probing {0} candidate blocklist volumes: {1}", lst.Count, string.Join(", ", lst.Select(x => x.Name)));
else
m_result.AddMessage(string.Format("Probing {0} candidate blocklist volumes", lst.Count));
Logging.Log.WriteVerboseMessage(LOGTAG, "ProbingCandicateBlocklistVolumes", "Probing {0} candidate blocklist volumes{1}", lst.Count, fullist);
Logging.Log.WriteInformationMessage(LOGTAG, "ProbingCandicateBlocklistVolumes", "Probing {0} candidate blocklist volumes{1}", lst.Count, m_options.FullResult ? fullist : string.Empty);
break;
default:
if (m_options.Verbose)
m_result.AddVerboseMessage("Processing all of the {0} volumes for blocklists: {1}", lst.Count, string.Join(", ", lst.Select(x => x.Name)));
else
m_result.AddMessage(string.Format("Processing all of the {0} volumes for blocklists", lst.Count));
Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingAllBlocklistVolumes", "Processing all of the {0} volumes for blocklists{1}", lst.Count, fullist);
Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingAllBlocklistVolumes", "Processing all of the {0} volumes for blocklists{1}", lst.Count, m_options.FullResult ? fullist : string.Empty);
break;
}
}
@@ -453,7 +454,7 @@ namespace Duplicati.Library.Main.Operation
// Update tables so we know if we are done
restoredb.FindMissingBlocklistHashes(hashsize, m_options.Blocksize, tr);
using(new Logging.Timer("CommitRestoredBlocklist"))
using(new Logging.Timer(LOGTAG, "CommitRestoredBlocklist", "CommitRestoredBlocklist"))
tr.Commit();
//At this point we can patch files with data from the block volume
@@ -467,11 +468,11 @@ namespace Duplicati.Library.Main.Operation
if (m_options.RepairOnlyPaths)
{
m_result.AddMessage("Recreate/path-update completed, not running consistency checks");
Logging.Log.WriteInformationMessage(LOGTAG, "RecreateOrUpdateOnly", "Recreate/path-update completed, not running consistency checks");
}
else
{
m_result.AddMessage("Recreate completed, verifying the database consistency");
Logging.Log.WriteInformationMessage(LOGTAG, "RecreateCompletedCheckingDatabase", "Recreate completed, verifying the database consistency");
//All done, we must verify that we have all blocklist fully intact
// if this fails, the db will not be deleted, so it can be used,
@@ -482,12 +483,12 @@ namespace Duplicati.Library.Main.Operation
{
var broken = lbfdb.GetBrokenFilesets(new DateTime(0), null, null).Count();
if (broken != 0)
throw new UserInformationException(string.Format("Recreated database has missing blocks and {0} broken filelists. Consider using \"{1}\" and \"{2}\" to purge broken data from the remote store and the database.", broken, "list-broken-files", "purge-broken-files"));
throw new UserInformationException(string.Format("Recreated database has missing blocks and {0} broken filelists. Consider using \"{1}\" and \"{2}\" to purge broken data from the remote store and the database.", broken, "list-broken-files", "purge-broken-files"), "DatabaseIsBrokenConsiderPurge");
}
restoredb.VerifyConsistency(null, m_options.Blocksize, m_options.BlockhashSize, true);
m_result.AddMessage("Recreate completed, and consistency checks completed, marking database as complete");
Logging.Log.WriteInformationMessage(LOGTAG, "RecreateCompleted", "Recreate completed, and consistency checks completed, marking database as complete");
restoredb.RepairInProgress = false;
}
@@ -514,7 +515,7 @@ namespace Duplicati.Library.Main.Operation
var tvid = restoredb.GetRemoteVolumeID(testfilename);
if (tvid >= 0)
{
m_result.AddWarning(string.Format("Unable to find volume {0}, but mapping to matching file {1}", filename, testfilename), null);
Logging.Log.WriteWarningMessage(LOGTAG, "RewritingFilenameMapping", null, "Unable to find volume {0}, but mapping to matching file {1}", filename, testfilename);
filename = testfilename;
return tvid;
}
@@ -10,6 +10,10 @@ namespace Duplicati.Library.Main.Operation
{
internal class RepairHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<RepairHandler>();
private string m_backendurl;
private Options m_options;
private RepairResults m_result;
@@ -21,7 +25,7 @@ namespace Duplicati.Library.Main.Operation
m_result = result;
if (options.AllowPassphraseChange)
throw new UserInformationException(Strings.Common.PassphraseChangeUnsupported);
throw new UserInformationException(Strings.Common.PassphraseChangeUnsupported, "PassphraseChangeUnsupported");
}
public void Run(Library.Utility.IFilter filter = null)
@@ -42,14 +46,14 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
m_result.AddWarning(string.Format("Failed to read local db {0}, error: {1}", m_options.Dbpath, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "FailedToReadLocalDatabase", ex, "Failed to read local db {0}, error: {1}", m_options.Dbpath, ex.Message);
}
if (knownRemotes <= 0)
{
if (m_options.Dryrun)
{
m_result.AddDryrunMessage("Performing dryrun recreate");
Logging.Log.WriteDryrunMessage(LOGTAG, "PerformingDryrunRecreate", "Performing dryrun recreate");
}
else
{
@@ -58,7 +62,7 @@ namespace Duplicati.Library.Main.Operation
while (System.IO.File.Exists(baseName) && i++ < 1000)
baseName = System.IO.Path.ChangeExtension(m_options.Dbpath, "backup-" + i.ToString());
m_result.AddMessage(string.Format("Renaming existing db from {0} to {1}", m_options.Dbpath, baseName));
Logging.Log.WriteInformationMessage(LOGTAG, "RenamingDatabase", "Renaming existing db from {0} to {1}", m_options.Dbpath, baseName);
System.IO.File.Move(m_options.Dbpath, baseName);
}
@@ -78,7 +82,7 @@ namespace Duplicati.Library.Main.Operation
public void RunRepairLocal(Library.Utility.IFilter filter = null)
{
m_result.RecreateDatabaseResults = new RecreateDatabaseResults(m_result);
using(new Logging.Timer("Recreate database for repair"))
using(new Logging.Timer(LOGTAG, "RecreateDbForRepair", "Recreate database for repair"))
using(var f = m_options.Dryrun ? new Library.Utility.TempFile() : null)
{
if (f != null && System.IO.File.Exists(f))
@@ -94,7 +98,7 @@ namespace Duplicati.Library.Main.Operation
public void RunRepairRemote()
{
if (!System.IO.File.Exists(m_options.Dbpath))
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "RepairDatabaseFileDoesNotExist");
m_result.OperationProgressUpdater.UpdateProgress(0);
@@ -106,10 +110,10 @@ namespace Duplicati.Library.Main.Operation
Utility.VerifyParameters(db, m_options);
if (db.PartiallyRecreated)
throw new UserInformationException("The database was only partially recreated. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.");
throw new UserInformationException("The database was only partially recreated. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsPartiallyRecreated");
if (db.RepairInProgress)
throw new UserInformationException("The database was attempted repaired, but the repair did not complete. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.");
throw new UserInformationException("The database was attempted repaired, but the repair did not complete. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsInRepairState");
var tp = FilelistProcessor.RemoteListAnalysis(backend, m_options, db, m_result.BackendWriter, null);
var buffer = new byte[m_options.Blocksize];
@@ -117,9 +121,9 @@ namespace Duplicati.Library.Main.Operation
var hashsize = blockhasher.HashSize / 8;
if (blockhasher == null)
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm), "BlockHashAlgorithmNotSupported");
if (!blockhasher.CanReuseTransform)
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.BlockHashAlgorithm), "BlockHashAlgorithmNotSupported");
var progress = 0;
var targetProgess = tp.ExtraVolumes.Count() + tp.MissingVolumes.Count() + tp.VerificationRequiredVolumes.Count();
@@ -129,13 +133,13 @@ namespace Duplicati.Library.Main.Operation
if (tp.ParsedVolumes.Count() == 0 && tp.OtherVolumes.Count() > 0)
{
if (tp.BackupPrefixes.Length == 1)
throw new UserInformationException(string.Format("Found no backup files with prefix {0}, but files with prefix {1}, did you forget to set the backup prefix?", m_options.Prefix, tp.BackupPrefixes[0]));
throw new UserInformationException(string.Format("Found no backup files with prefix {0}, but files with prefix {1}, did you forget to set the backup prefix?", m_options.Prefix, tp.BackupPrefixes[0]), "RemoteFolderEmptyWithPrefix");
else
throw new UserInformationException(string.Format("Found no backup files with prefix {0}, but files with prefixes {1}, did you forget to set the backup prefix?", m_options.Prefix, string.Join(", ", tp.BackupPrefixes)));
throw new UserInformationException(string.Format("Found no backup files with prefix {0}, but files with prefixes {1}, did you forget to set the backup prefix?", m_options.Prefix, string.Join(", ", tp.BackupPrefixes)), "RemoteFolderEmptyWithPrefix");
}
else if (tp.ParsedVolumes.Count() == 0 && tp.ExtraVolumes.Count() > 0)
{
throw new UserInformationException(string.Format("No files were missing, but {0} remote files were, found, did you mean to run recreate-database?", tp.ExtraVolumes.Count()));
throw new UserInformationException(string.Format("No files were missing, but {0} remote files were, found, did you mean to run recreate-database?", tp.ExtraVolumes.Count()), "NoRemoteFilesMissing");
}
}
@@ -162,21 +166,21 @@ namespace Duplicati.Library.Main.Operation
KeyValuePair<string, IEnumerable<KeyValuePair<Duplicati.Library.Interface.TestEntryStatus, string>>> res;
using (var tf = backend.GetWithInfo(n.Name, out size, out hash))
res = TestHandler.TestVolumeInternals(testdb, n, tf, m_options, m_result, 1);
res = TestHandler.TestVolumeInternals(testdb, n, tf, m_options, 1);
if (res.Value.Any())
throw new Exception(string.Format("Remote verification failure: {0}", res.Value.First()));
if (!m_options.Dryrun)
{
m_result.AddMessage(string.Format("Sucessfully captured hash for {0}, updating database", n.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "CapturedRemoteFileHash", "Sucessfully captured hash for {0}, updating database", n.Name);
db.UpdateRemoteVolume(n.Name, RemoteVolumeState.Verified, size, hash);
}
}
catch (Exception ex)
{
m_result.AddError(string.Format("Failed to perform verification for file: {0}, please run verify; message: {1}", n.Name, ex.Message), ex);
Logging.Log.WriteErrorMessage(LOGTAG, "RemoteFileVerificationError", ex, "Failed to perform verification for file: {0}, please run verify; message: {1}", n.Name, ex.Message);
if (ex is System.Threading.ThreadAbortException)
throw;
}
@@ -235,14 +239,14 @@ namespace Duplicati.Library.Main.Operation
}
// All checks fine, we accept the new index file
m_result.AddMessage(string.Format("Accepting new index file {0}", n.File.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "AcceptNewIndexFile", "Accepting new index file {0}", n.File.Name);
db.RegisterRemoteVolume(n.File.Name, RemoteVolumeType.Index, size, RemoteVolumeState.Uploading);
db.UpdateRemoteVolume(n.File.Name, RemoteVolumeState.Verified, size, hash);
continue;
}
catch (Exception rex)
{
m_result.AddError(string.Format("Failed to accept new index file: {0}, message: {1}", n.File.Name, rex.Message), rex);
Logging.Log.WriteErrorMessage(LOGTAG, "FailedNewIndexFile", rex, "Failed to accept new index file: {0}, message: {1}", n.File.Name, rex.Message);
}
}
@@ -252,11 +256,11 @@ namespace Duplicati.Library.Main.Operation
backend.Delete(n.File.Name, n.File.Size);
}
else
m_result.AddDryrunMessage(string.Format("would delete file {0}", n.File.Name));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteFile", "would delete file {0}", n.File.Name);
}
catch (Exception ex)
{
m_result.AddError(string.Format("Failed to perform cleanup for extra file: {0}, message: {1}", n.File.Name, ex.Message), ex);
Logging.Log.WriteErrorMessage(LOGTAG, "FailedExtraFileCleanup", ex, "Failed to perform cleanup for extra file: {0}, message: {1}", n.File.Name, ex.Message);
if (ex is System.Threading.ThreadAbortException)
throw;
}
@@ -287,7 +291,7 @@ namespace Duplicati.Library.Main.Operation
w.Close();
if (m_options.Dryrun)
m_result.AddDryrunMessage(string.Format("would re-upload fileset {0}, with size {1}, previous size {2}", n.Name, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(w.LocalFilename).Length), Library.Utility.Utility.FormatSizeString(n.Size)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldReUploadFileset", "would re-upload fileset {0}, with size {1}, previous size {2}", n.Name, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(w.LocalFilename).Length), Library.Utility.Utility.FormatSizeString(n.Size));
else
{
db.UpdateRemoteVolume(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null, null);
@@ -326,7 +330,7 @@ namespace Duplicati.Library.Main.Operation
w.Close();
if (m_options.Dryrun)
m_result.AddDryrunMessage(string.Format("would re-upload index file {0}, with size {1}, previous size {2}", n.Name, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(w.LocalFilename).Length), Library.Utility.Utility.FormatSizeString(n.Size)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldReUploadIndexFile", "would re-upload index file {0}, with size {1}, previous size {2}", n.Name, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(w.LocalFilename).Length), Library.Utility.Utility.FormatSizeString(n.Size));
else
{
db.UpdateRemoteVolume(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null, null);
@@ -372,7 +376,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
m_result.AddError(string.Format("Failed to access file: {0}", file), ex);
Logging.Log.WriteErrorMessage(LOGTAG, "FileAccessError", ex, "Failed to access file: {0}", file);
}
}
}
@@ -391,7 +395,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
m_result.AddError(string.Format("Failed to access remote file: {0}", vol.Name), ex);
Logging.Log.WriteErrorMessage(LOGTAG, "RemoteFileAccessError", ex, "Failed to access remote file: {0}", vol.Name);
}
}
@@ -399,27 +403,27 @@ namespace Duplicati.Library.Main.Operation
var missingBlocks = mbl.GetMissingBlocks().Count();
if (missingBlocks > 0)
{
m_result.AddMessage(string.Format("Repair cannot acquire {0} required blocks for volume {1}, which are required by the following filesets: ", missingBlocks, n.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "RepairMissingBlocks", "Repair cannot acquire {0} required blocks for volume {1}, which are required by the following filesets: ", missingBlocks, n.Name);
foreach(var f in mbl.GetFilesetsUsingMissingBlocks())
m_result.AddMessage(f.Name);
Logging.Log.WriteInformationMessage(LOGTAG, "AffectedFilesetName", f.Name);
var recoverymsg = string.Format("If you want to continue working with the database, you can use the \"{0}\" and \"{1}\" commands to purge the missing data from the database and the remote storage.", "list-broken-files", "purge-broken-files");
if (!m_options.Dryrun)
{
m_result.AddMessage("This may be fixed by deleting the filesets and running repair again");
Logging.Log.WriteInformationMessage(LOGTAG, "RecoverySuggestion", "This may be fixed by deleting the filesets and running repair again");
throw new UserInformationException(string.Format("Repair not possible, missing {0} blocks.\n" + recoverymsg, missingBlocks));
throw new UserInformationException(string.Format("Repair not possible, missing {0} blocks.\n" + recoverymsg, missingBlocks), "RepairIsNotPossible");
}
else
{
m_result.AddMessage(recoverymsg);
Logging.Log.WriteInformationMessage(LOGTAG, "RecoverySuggestion", recoverymsg);
}
}
else
{
if (m_options.Dryrun)
m_result.AddDryrunMessage(string.Format("would re-upload block file {0}, with size {1}, previous size {2}", n.Name, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(w.LocalFilename).Length), Library.Utility.Utility.FormatSizeString(n.Size)));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldReUploadBlockFile", "would re-upload block file {0}, with size {1}, previous size {2}", n.Name, Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(w.LocalFilename).Length), Library.Utility.Utility.FormatSizeString(n.Size));
else
{
db.UpdateRemoteVolume(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null, null);
@@ -436,7 +440,7 @@ namespace Duplicati.Library.Main.Operation
catch { }
finally { newEntry = null; }
m_result.AddError(string.Format("Failed to perform cleanup for missing file: {0}, message: {1}", n.Name, ex.Message), ex);
Logging.Log.WriteErrorMessage(LOGTAG, "CleanupMissingFileError", ex, "Failed to perform cleanup for missing file: {0}, message: {1}", n.Name, ex.Message);
if (ex is System.Threading.ThreadAbortException)
throw;
@@ -445,7 +449,7 @@ namespace Duplicati.Library.Main.Operation
}
else
{
m_result.AddMessage("Destination and database are synchronized, not making any changes");
Logging.Log.WriteInformationMessage(LOGTAG, "DatabaseIsSynchronized", "Destination and database are synchronized, not making any changes");
}
m_result.OperationProgressUpdater.UpdateProgress(1);
@@ -457,7 +461,7 @@ namespace Duplicati.Library.Main.Operation
public void RunRepairCommon()
{
if (!System.IO.File.Exists(m_options.Dbpath))
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist");
m_result.OperationProgressUpdater.UpdateProgress(0);
@@ -468,7 +472,7 @@ namespace Duplicati.Library.Main.Operation
Utility.UpdateOptionsFromDb(db, m_options);
if (db.RepairInProgress || db.PartiallyRecreated)
m_result.AddWarning("The database is marked as \"in-progress\" and may be incomplete.", null);
Logging.Log.WriteWarningMessage(LOGTAG, "InProgressDatabase", null, "The database is marked as \"in-progress\" and may be incomplete.");
db.FixDuplicateMetahash();
db.FixDuplicateFileentries();
@@ -10,6 +10,11 @@ namespace Duplicati.Library.Main.Operation
{
internal class RestoreHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<RestoreHandler>();
private string m_backendurl;
private Options m_options;
private byte[] m_blockbuffer;
@@ -33,7 +38,7 @@ namespace Duplicati.Library.Main.Operation
{
var tmp = VolumeBase.ParseFilename(filename);
if (tmp == null)
throw new UserInformationException(string.Format("Unable to parse filename to valid entry: {0}", filename));
throw new UserInformationException(string.Format("Unable to parse filename to valid entry: {0}", filename), "FailedToParseRemoteName");
return tmp.CompressionModule;
}
@@ -95,7 +100,7 @@ namespace Duplicati.Library.Main.Operation
}
m_result.AddMessage("No local database, building a temporary database");
Logging.Log.WriteInformationMessage(LOGTAG, "NoLocalDatabase", "No local database, building a temporary database");
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_RecreateDatabase);
using(var tmpdb = new Library.Utility.TempFile())
@@ -127,14 +132,14 @@ namespace Duplicati.Library.Main.Operation
blockhasher = Library.Utility.HashAlgorithmHelper.Create(m_options.BlockHashAlgorithm);
filehasher = Library.Utility.HashAlgorithmHelper.Create(m_options.FileHashAlgorithm);
if (blockhasher == null)
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm), "BlockHashAlgorithmNotSupported");
if (!blockhasher.CanReuseTransform)
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.BlockHashAlgorithm), "BlockHashAlgorithmNotSupported");
if (filehasher == null)
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.FileHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.FileHashAlgorithm), "FileHashAlgorithmNotSupported");
if (!filehasher.CanReuseTransform)
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.FileHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.FileHashAlgorithm), "FileHashAlgorithmNotSupported");
// Don't run this again
first = false;
@@ -171,7 +176,7 @@ namespace Duplicati.Library.Main.Operation
// and this will reduce the need for multiple downloads of the same volume
// TODO: This will need some work to preserve the missing block list for use with --fh-dryrun
m_result.RecreateDatabaseResults = new RecreateDatabaseResults(m_result);
using(new Logging.Timer("Recreate temporary database for restore"))
using(new Logging.Timer(LOGTAG, "RecreateTempDbForRestore", "Recreate temporary database for restore"))
new RecreateDatabaseHandler(m_backendurl, m_options, (RecreateDatabaseResults)m_result.RecreateDatabaseResults)
.DoRun(database, false, filter, filelistfilter, /*localpatcher*/null);
@@ -202,20 +207,21 @@ namespace Duplicati.Library.Main.Operation
foreach(var restorelist in volumekeeper.FilesWithMissingBlocks)
{
var targetpath = restorelist.Path;
result.AddVerboseMessage("Patching file with remote data: {0}", targetpath);
if (options.Dryrun)
{
result.AddDryrunMessage(string.Format("Would patch file with remote data: {0}", targetpath));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchFile", "Would patch file with remote data: {0}", targetpath);
}
else
{
Logging.Log.WriteVerboseMessage(LOGTAG, "PatchingFile", "Patching file with remote data: {0}", targetpath);
try
{
var folderpath = m_systemIO.PathGetDirectoryName(targetpath);
if (!options.Dryrun && !m_systemIO.DirectoryExists(folderpath))
{
result.AddWarning(string.Format("Creating missing folder {0} for file {1}", folderpath, targetpath), null);
Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for file {1}", folderpath, targetpath);
m_systemIO.DirectoryCreate(folderpath);
}
@@ -236,7 +242,7 @@ namespace Duplicati.Library.Main.Operation
if (targetblock.Key == key)
valid = true;
else
result.AddWarning(string.Format("Invalid block detected for {0}, expected hash: {1}, actual hash: {2}", targetpath, targetblock.Key, key), null);
Logging.Log.WriteWarningMessage(LOGTAG, "InvalidBlock", null, "Invalid block detected for {0}, expected hash: {1}, actual hash: {2}", targetpath, targetblock.Key, key);
}
if (valid)
@@ -247,7 +253,7 @@ namespace Duplicati.Library.Main.Operation
}
else
{
result.AddWarning(string.Format("Block with hash {0} should have size {1} but has size {2}", targetblock.Key, targetblock.Size, size), null);
Logging.Log.WriteWarningMessage(LOGTAG, "WrongBlockSize", null, "Block with hash {0} should have size {1} but has size {2}", targetblock.Key, targetblock.Size, size);
}
}
@@ -256,7 +262,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to patch file: \"{0}\", message: {1}, message: {1}", targetpath, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "PatchFailed", ex, "Failed to patch file: \"{0}\", message: {1}, message: {1}", targetpath, ex.Message);
}
}
}
@@ -266,7 +272,7 @@ namespace Duplicati.Library.Main.Operation
foreach(var restoremetadata in volumekeeper.MetadataWithMissingBlocks)
{
var targetpath = restoremetadata.Path;
result.AddVerboseMessage("Recording metadata from remote data: {0}", targetpath);
Logging.Log.WriteVerboseMessage(LOGTAG, "RecordingMetadata", "Recording metadata from remote data: {0}", targetpath);
try
{
@@ -291,12 +297,12 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to record metadata for file: \"{0}\", message: {1}", targetpath, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "MetatdataRecordFailed", ex, "Failed to record metadata for file: \"{0}\", message: {1}", targetpath, ex.Message);
}
}
}
blockmarker.UpdateProcessed(result.OperationProgressUpdater);
blockmarker.Commit(result);
blockmarker.Commit();
}
}
@@ -305,20 +311,20 @@ namespace Duplicati.Library.Main.Operation
foreach(var metainfo in metadatastorage.Records)
{
var targetpath = metainfo.Key;
result.AddVerboseMessage("Patching metadata with remote data: {0}", targetpath);
if (options.Dryrun)
{
result.AddDryrunMessage(string.Format("Would patch metadata with remote data: {0}", targetpath));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchMetadata", "Would patch metadata with remote data: {0}", targetpath);
}
else
{
Logging.Log.WriteVerboseMessage(LOGTAG, "PatchingMetadata", "Patching metadata with remote data: {0}", targetpath);
try
{
var folderpath = m_systemIO.PathGetDirectoryName(targetpath);
if (!options.Dryrun && !m_systemIO.DirectoryExists(folderpath))
{
result.AddWarning(string.Format("Creating missing folder {0} for target {1}", folderpath, targetpath), null);
Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for target {1}", folderpath, targetpath);
m_systemIO.DirectoryCreate(folderpath);
}
@@ -326,7 +332,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to apply metadata to file: \"{0}\", message: {1}", targetpath, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "MetadataWriteFailed", ex, "Failed to apply metadata to file: \"{0}\", message: {1}", targetpath, ex.Message);
}
}
}
@@ -348,14 +354,14 @@ namespace Duplicati.Library.Main.Operation
var blockhasher = Library.Utility.HashAlgorithmHelper.Create(m_options.BlockHashAlgorithm);
var filehasher = Library.Utility.HashAlgorithmHelper.Create(m_options.FileHashAlgorithm);
if (blockhasher == null)
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.BlockHashAlgorithm), "BlockHashAlgorithmNotSupported");
if (!blockhasher.CanReuseTransform)
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.BlockHashAlgorithm), "BlockHashAlgorithmNotSupported");
if (filehasher == null)
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.FileHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(m_options.FileHashAlgorithm), "FileHashAlgorithmNotSupported");
if (!filehasher.CanReuseTransform)
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.FileHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(m_options.FileHashAlgorithm), "FileHashAlgorithmNotSupported");
if (!m_options.NoBackendverification)
{
@@ -365,21 +371,21 @@ namespace Duplicati.Library.Main.Operation
//Figure out what files are to be patched, and what blocks are needed
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateFileList);
using(new Logging.Timer("PrepareBlockList"))
using(new Logging.Timer(LOGTAG, "PrepareBlockList", "PrepareBlockList"))
PrepareBlockAndFileList(database, m_options, filter, result);
//Make the entire output setup
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateTargetFolders);
using(new Logging.Timer("CreateDirectory"))
using(new Logging.Timer(LOGTAG, "CreateDirectory", "CreateDirectory"))
CreateDirectoryStructure(database, m_options, result);
//If we are patching an existing target folder, do not touch stuff that is already updated
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_ScanForExistingFiles);
using(new Logging.Timer("ScanForexistingTargetBlocks"))
using(new Logging.Timer(LOGTAG, "ScanForExistingTargetBlocks", "ScanForExistingTargetBlocks"))
ScanForExistingTargetBlocks(database, m_blockbuffer, blockhasher, filehasher, m_options, result);
//Look for existing blocks in the original source files only
using(new Logging.Timer("ScanForExistingSourceBlocksFast"))
using(new Logging.Timer(LOGTAG, "ScanForExistingSourceBlocksFast", "ScanForExistingSourceBlocksFast"))
if (!m_options.NoLocalBlocks && !string.IsNullOrEmpty(m_options.Restorepath))
{
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_ScanForLocalBlocks);
@@ -396,7 +402,7 @@ namespace Duplicati.Library.Main.Operation
if (m_options.PatchWithLocalBlocks)
{
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PatchWithLocalBlocks);
using(new Logging.Timer("PatchWithLocalBlocks"))
using(new Logging.Timer(LOGTAG, "PatchWithLocalBlocks", "PatchWithLocalBlocks"))
ScanForExistingSourceBlocks(database, m_options, m_blockbuffer, blockhasher, result, metadatastorage);
}
@@ -408,12 +414,12 @@ namespace Duplicati.Library.Main.Operation
// Fill BLOCKS with remote sources
List<IRemoteVolume> volumes;
using (new Logging.Timer("GetMissingVolumes"))
using (new Logging.Timer(LOGTAG, "GetMissingVolumes", "GetMissingVolumes"))
volumes = database.GetMissingVolumes().ToList();
if (volumes.Count > 0)
{
m_result.AddMessage(string.Format("{0} remote files are required to restore", volumes.Count));
Logging.Log.WriteInformationMessage(LOGTAG, "RemoteFileCount", "{0} remote files are required to restore", volumes.Count);
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_DownloadingRemoteFiles);
}
@@ -434,7 +440,7 @@ namespace Duplicati.Library.Main.Operation
catch (Exception ex)
{
brokenFiles.Add(blockvolume.Name);
result.AddError(string.Format("Failed to patch with remote file: \"{0}\", message: {1}", blockvolume.Name, ex.Message), ex);
Logging.Log.WriteErrorMessage(LOGTAG, "PatchingFailed", ex, "Failed to patch with remote file: \"{0}\", message: {1}", blockvolume.Name, ex.Message);
if (ex is System.Threading.ThreadAbortException)
throw;
}
@@ -459,7 +465,7 @@ namespace Duplicati.Library.Main.Operation
if (m_options.PerformRestoredFileVerification)
{
// After all blocks in the files are restored, verify the file hash
using(new Logging.Timer("RestoreVerification"))
using(new Logging.Timer(LOGTAG, "RestoreVerification", "RestoreVerification"))
foreach(var file in database.GetFilesToRestore(true))
{
try
@@ -470,7 +476,7 @@ namespace Duplicati.Library.Main.Operation
return;
}
result.AddVerboseMessage("Testing restored file integrity: {0}", file.Path);
Logging.Log.WriteVerboseMessage(LOGTAG, "TestFileIntegrity", "Testing restored file integrity: {0}", file.Path);
string key;
long size;
@@ -488,7 +494,7 @@ namespace Duplicati.Library.Main.Operation
catch (Exception ex)
{
fileErrors++;
result.AddWarning(ex.Message, ex);
Logging.Log.WriteErrorMessage(LOGTAG, "RestoreFileFailed", ex, ex.Message);
if (ex is System.Threading.ThreadAbortException)
throw;
}
@@ -496,9 +502,9 @@ namespace Duplicati.Library.Main.Operation
}
if (fileErrors > 0 && brokenFiles.Count > 0)
m_result.AddMessage(string.Format("Failed to restore {0} files, additionally the following files failed to download, which may be the cause:{1}{2}", fileErrors, Environment.NewLine, string.Join(Environment.NewLine, brokenFiles)));
Logging.Log.WriteInformationMessage(LOGTAG, "RestoreFailures", "Failed to restore {0} files, additionally the following files failed to download, which may be the cause:{1}{2}", fileErrors, Environment.NewLine, string.Join(Environment.NewLine, brokenFiles));
else if (fileErrors > 0)
m_result.AddMessage(string.Format("Failed to restore {0} files", fileErrors));
Logging.Log.WriteInformationMessage(LOGTAG, "RestoreFailures", "Failed to restore {0} files", fileErrors);
// Drop the temp tables
database.DropRestoreTable();
@@ -576,7 +582,7 @@ namespace Duplicati.Library.Main.Operation
var folderpath = m_systemIO.PathGetDirectoryName(targetpath);
if (!options.Dryrun && !m_systemIO.DirectoryExists(folderpath))
{
result.AddWarning(string.Format("Creating missing folder {0} for file {1}", folderpath, targetpath), null);
Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for file {1}", folderpath, targetpath);
m_systemIO.DirectoryCreate(folderpath);
}
@@ -619,7 +625,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}", targetpath, sourcepath, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}", targetpath, sourcepath, ex.Message);
if (ex is System.Threading.ThreadAbortException)
throw;
}
@@ -635,27 +641,27 @@ namespace Duplicati.Library.Main.Operation
}
else
{
result.AddVerboseMessage("Local source file not found: {0}", sourcepath);
Logging.Log.WriteVerboseMessage(LOGTAG, "LocalSourceMissing", "Local source file not found: {0}", sourcepath);
}
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to patch file: \"{0}\" with local data, message: {1}", targetpath, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with local data, message: {1}", targetpath, ex.Message);
if (ex is System.Threading.ThreadAbortException)
throw;
}
if (patched)
result.AddVerboseMessage("Target file is patched with some local data: {0}", targetpath);
Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is patched with some local data: {0}", targetpath);
else
result.AddVerboseMessage("Target file is not patched any local data: {0}", targetpath);
Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is not patched any local data: {0}", targetpath);
if (patched && options.Dryrun)
result.AddDryrunMessage(string.Format("Would patch file with local data: {0}", targetpath));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchWithLocal", "Would patch file with local data: {0}", targetpath);
}
blockmarker.UpdateProcessed(result.OperationProgressUpdater);
blockmarker.Commit(result);
blockmarker.Commit();
}
}
@@ -678,7 +684,7 @@ namespace Duplicati.Library.Main.Operation
var folderpath = m_systemIO.PathGetDirectoryName(targetpath);
if (!options.Dryrun && !m_systemIO.DirectoryExists(folderpath))
{
result.AddWarning(string.Format("Creating missing folder {0} for file {1}", folderpath, targetpath), null);
Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for file {1}", folderpath, targetpath);
m_systemIO.DirectoryCreate(folderpath);
}
@@ -734,7 +740,7 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}", targetpath, source.Path, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}", targetpath, source.Path, ex.Message);
if (ex is System.Threading.ThreadAbortException)
throw;
}
@@ -746,20 +752,20 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to patch file: \"{0}\" with local data, message: {1}", targetpath, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with local data, message: {1}", targetpath, ex.Message);
}
if (patched)
result.AddVerboseMessage("Target file is patched with some local data: {0}", targetpath);
Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is patched with some local data: {0}", targetpath);
else
result.AddVerboseMessage("Target file is not patched any local data: {0}", targetpath);
Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is not patched any local data: {0}", targetpath);
if (patched && options.Dryrun)
result.AddDryrunMessage(string.Format("Would patch file with local data: {0}", targetpath));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchWithLocal", string.Format("Would patch file with local data: {0}", targetpath));
}
blockmarker.UpdateProcessed(result.OperationProgressUpdater);
blockmarker.Commit(result);
blockmarker.Commit();
}
}
@@ -767,13 +773,13 @@ namespace Duplicati.Library.Main.Operation
{
// Create a temporary table FILES by selecting the files from fileset that matches a specific operation id
// Delete all entries from the temp table that are excluded by the filter(s)
using(new Logging.Timer("PrepareRestoreFileList"))
using(new Logging.Timer(LOGTAG, "PrepareRestoreFileList", "PrepareRestoreFileList"))
{
var c = database.PrepareRestoreFilelist(options.Time, options.Version, filter, result);
var c = database.PrepareRestoreFilelist(options.Time, options.Version, filter);
result.OperationProgressUpdater.UpdatefileCount(c.Item1, c.Item2, true);
}
using(new Logging.Timer("SetTargetPaths"))
using(new Logging.Timer(LOGTAG, "SetTargetPaths", "SetTargetPaths"))
if (!string.IsNullOrEmpty(options.Restorepath))
{
// Find the largest common prefix
@@ -781,7 +787,7 @@ namespace Duplicati.Library.Main.Operation
if (options.DontCompressRestorePaths)
largest_prefix = "";
result.AddVerboseMessage("Mapping restore path prefix to \"{0}\" to \"{1}\"", largest_prefix, Library.Utility.Utility.AppendDirSeparator(options.Restorepath));
Logging.Log.WriteVerboseMessage(LOGTAG, "MappingRestorePath", "Mapping restore path prefix to \"{0}\" to \"{1}\"", largest_prefix, Library.Utility.Utility.AppendDirSeparator(options.Restorepath));
// Set the target paths, special care with C:\ and /
database.SetTargetPaths(largest_prefix, Library.Utility.Utility.AppendDirSeparator(options.Restorepath));
@@ -792,11 +798,11 @@ namespace Duplicati.Library.Main.Operation
}
// Create a temporary table BLOCKS that lists all blocks that needs to be recovered
using(new Logging.Timer("FindMissingBlocks"))
database.FindMissingBlocks(result, options.SkipMetadata);
using(new Logging.Timer(LOGTAG, "FindMissingBlocks", "FindMissingBlocks"))
database.FindMissingBlocks(options.SkipMetadata);
// Create temporary tables and triggers that automatically track progress
using (new Logging.Timer("CreateProgressTracker"))
using (new Logging.Timer(LOGTAG, "CreateProgressTracker", "CreateProgressTracker"))
database.CreateProgressTracker(false);
}
@@ -807,11 +813,10 @@ namespace Duplicati.Library.Main.Operation
if (!string.IsNullOrEmpty(options.Restorepath))
if (!m_systemIO.DirectoryExists(options.Restorepath))
{
if (options.Verbose)
result.AddVerboseMessage("Creating folder: {0}", options.Restorepath);
Logging.Log.WriteVerboseMessage(LOGTAG, "CreateFolder", "Creating folder: {0}", options.Restorepath);
if (options.Dryrun)
result.AddDryrunMessage(string.Format("Would create folder: {0}", options.Restorepath));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldCreateFolder", "Would create folder: {0}", options.Restorepath);
else
m_systemIO.DirectoryCreate(options.Restorepath);
}
@@ -827,18 +832,17 @@ namespace Duplicati.Library.Main.Operation
{
result.FoldersRestored++;
if (options.Verbose)
result.AddVerboseMessage("Creating folder: {0}", folder);
Logging.Log.WriteVerboseMessage(LOGTAG, "CreateFolder", "Creating folder: {0}", folder);
if (options.Dryrun)
result.AddDryrunMessage(string.Format("Would create folder: {0}", folder));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldCreateFolder", "Would create folder: {0}", folder);
else
m_systemIO.DirectoryCreate(folder);
}
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to create folder: \"{0}\", message: {1}", folder, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "FolderCreateFailed", ex, "Failed to create folder: \"{0}\", message: {1}", folder, ex.Message);
}
}
}
@@ -874,11 +878,12 @@ namespace Duplicati.Library.Main.Operation
var currentAttr = m_systemIO.GetFileAttributes(targetpath);
if ((currentAttr & System.IO.FileAttributes.ReadOnly) != 0) // clear readonly attribute
{
if (options.Dryrun) result.AddDryrunMessage(string.Format("Would reset read-only attribute on file: {0}", targetpath));
if (options.Dryrun)
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldResetReadOnlyAttribyte", "Would reset read-only attribute on file: {0}", targetpath);
else m_systemIO.SetFileAttributes(targetpath, currentAttr & ~System.IO.FileAttributes.ReadOnly);
}
if (options.Dryrun)
result.AddDryrunMessage(string.Format("Would truncate file '{0}' to length of {1:N0} bytes", targetpath, targetfilelength));
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldTruncateFile", "Would truncate file '{0}' to length of {1:N0} bytes", targetpath, targetfilelength);
else
{
using (var file = m_systemIO.FileOpenWrite(targetpath))
@@ -952,7 +957,8 @@ namespace Duplicati.Library.Main.Operation
var currentAttr = m_systemIO.GetFileAttributes(targetpath);
if ((currentAttr & System.IO.FileAttributes.ReadOnly) != 0)
{
if (options.Dryrun) result.AddDryrunMessage(string.Format("Would reset read-only attribute on file: {0}", targetpath));
if (options.Dryrun)
Logging.Log.WriteDryrunMessage(LOGTAG, "WouldResetReadOnlyAttribyte", "Would reset read-only attribute on file: {0}", targetpath);
else m_systemIO.SetFileAttributes(targetpath, currentAttr & ~System.IO.FileAttributes.ReadOnly);
}
}
@@ -961,7 +967,7 @@ namespace Duplicati.Library.Main.Operation
{
//TODO: Check metadata to trigger rename? If metadata changed, it will still be restored for the file in-place.
blockmarker.SetFileDataVerified(targetfileid);
result.AddVerboseMessage("Target file exists{1} and is correct version: {0}", targetpath, wasTruncated ? " (but was truncated)" : "");
Logging.Log.WriteVerboseMessage(LOGTAG, "TargetExistsInCorrectVersion", "Target file exists{1} and is correct version: {0}", targetpath, wasTruncated ? " (but was truncated)" : "");
rename = false;
}
else if (rename)
@@ -981,14 +987,14 @@ namespace Duplicati.Library.Main.Operation
}
catch (Exception ex)
{
result.AddWarning(string.Format("Failed to read target file: \"{0}\", message: {1}", targetpath, ex.Message), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "TargetFileReadError", ex, "Failed to read target file: \"{0}\", message: {1}", targetpath, ex.Message);
if (ex is System.Threading.ThreadAbortException)
throw;
}
}
else
{
result.AddVerboseMessage("Target file does not exist: {0}", targetpath);
Logging.Log.WriteVerboseMessage(LOGTAG, "MissingTargetFile", "Target file does not exist: {0}", targetpath);
rename = false;
}
@@ -1026,21 +1032,21 @@ namespace Duplicati.Library.Main.Operation
}
catch(Exception ex)
{
result.AddWarning(string.Format("Failed to read candidate restore target {0}", tr), ex);
Logging.Log.WriteWarningMessage(LOGTAG, "FailedToReadRestoreTarget", ex, "Failed to read candidate restore target {0}", tr);
}
tr = newname + " (" + (c++).ToString() + ")" + ext;
}
newname = tr;
result.AddVerboseMessage("Target file exists and will be restored to: {0}", newname);
Logging.Log.WriteVerboseMessage(LOGTAG, "TargetFileRetargeted", "Target file exists and will be restored to: {0}", newname);
database.UpdateTargetPath(targetfileid, newname);
}
}
blockmarker.UpdateProcessed(result.OperationProgressUpdater);
blockmarker.Commit(result);
blockmarker.Commit();
}
}
}
@@ -22,6 +22,10 @@ namespace Duplicati.Library.Main.Operation
{
public class RestoreHandlerMetadataStorage : IDisposable
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<RestoreHandlerMetadataStorage>();
private TempFile m_temp;
private FileStream m_stream;
private long m_entries;
@@ -81,9 +85,9 @@ namespace Duplicati.Library.Main.Operation
var bf = BitConverter.GetBytes(0L);
var buf = new byte[8 * 1024];
Logging.Log.WriteMessage(string.Format("The metadata storage file has {0} entries and takes up {1}", m_entries, Library.Utility.Utility.FormatSizeString(m_stream.Length)), Duplicati.Library.Logging.LogMessageType.Profiling);
Logging.Log.WriteProfilingMessage(LOGTAG, "MetadataStorageSize", "The metadata storage file has {0} entries and takes up {1}", m_entries, Library.Utility.Utility.FormatSizeString(m_stream.Length));
using(new Logging.Timer("Read metadata from file"))
using(new Logging.Timer(LOGTAG, "ReadMetadata", "Read metadata from file"))
for(var e = 0L; e < m_entries; e++)
{
m_stream.Position = pos;
@@ -116,12 +120,12 @@ namespace Duplicati.Library.Main.Operation
{
if (m_stream != null)
try { m_stream.Dispose(); }
catch { }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DisposeError", ex, "Failed to dispose stream"); }
finally { m_stream = null; }
if (m_temp != null)
try { m_temp.Dispose(); }
catch { }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DisposeError", ex, "Failed to temp file stream"); }
finally { m_temp = null; }
}
@@ -23,6 +23,11 @@ namespace Duplicati.Library.Main.Operation
{
internal class TestFilterHandler : IDisposable
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<TestFilterHandler>();
private readonly Options m_options;
private TestFilterResults m_result;
@@ -37,21 +42,21 @@ namespace Duplicati.Library.Main.Operation
var storeSymlinks = m_options.SymlinkPolicy == Duplicati.Library.Main.Options.SymlinkStrategy.Store;
var sourcefilter = new Library.Utility.FilterExpression(sources, true);
using(var snapshot = BackupHandler.GetSnapshot(sources, m_options, m_result))
using(var snapshot = BackupHandler.GetSnapshot(sources, m_options))
{
foreach(var path in new BackupHandler.FilterHandler(snapshot, m_options.FileAttributeFilter, sourcefilter, filter, m_options.SymlinkPolicy, m_options.HardlinkPolicy, m_result).EnumerateFilesAndFolders())
foreach(var path in new BackupHandler.FilterHandler(snapshot, m_options.FileAttributeFilter, sourcefilter, filter, m_options.SymlinkPolicy, m_options.HardlinkPolicy).EnumerateFilesAndFolders())
{
var fa = FileAttributes.Normal;
try { fa = snapshot.GetAttributes(path); }
catch { }
catch (Exception ex) { Logging.Log.WriteVerboseMessage(LOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", path, ex.Message); }
if (storeSymlinks && snapshot.IsSymlink(path, fa))
{
m_result.AddVerboseMessage("Storing symlink: {0}", path);
Logging.Log.WriteVerboseMessage(LOGTAG, "StoreSymlink", "Storing symlink: {0}", path);
}
else if ((fa & FileAttributes.Directory) == FileAttributes.Directory)
{
m_result.AddVerboseMessage("Including folder: {0}", path);
Logging.Log.WriteVerboseMessage(LOGTAG, "AddDirectory", "Including folder {0}", path);
}
else
{
@@ -63,15 +68,16 @@ namespace Duplicati.Library.Main.Operation
size = snapshot.GetFileSize(path);
m_result.FileSize += size;
}
catch
{
catch (Exception ex)
{
Logging.Log.WriteVerboseMessage(LOGTAG, "SizeReadFailed", "Failed to read length of file {0}: {1}", path, ex.Message);
}
if (m_options.SkipFilesLargerThan == long.MaxValue || m_options.SkipFilesLargerThan == 0 || size < m_options.SkipFilesLargerThan)
m_result.AddVerboseMessage("Including file: {0} ({1})", path, size < 0 ? "unknown" : Duplicati.Library.Utility.Utility.FormatSizeString(size));
Logging.Log.WriteVerboseMessage(LOGTAG, "IncludeFile", "Including file: {0} ({1})", path, size < 0 ? "unknown" : Duplicati.Library.Utility.Utility.FormatSizeString(size));
else
m_result.AddVerboseMessage("Excluding file due to size: {0} ({1})", path, size < 0 ? "unknown" : Duplicati.Library.Utility.Utility.FormatSizeString(size));
Logging.Log.WriteVerboseMessage(LOGTAG, "ExcludeLargeFile", "Excluding file due to size: {0} ({1})", path, size < 0 ? "unknown" : Duplicati.Library.Utility.Utility.FormatSizeString(size));
}
}
+19 -14
View File
@@ -25,6 +25,11 @@ namespace Duplicati.Library.Main.Operation
{
internal class TestHandler
{
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<TestHandler>();
private readonly Options m_options;
private string m_backendurl;
private TestResults m_results;
@@ -39,7 +44,7 @@ namespace Duplicati.Library.Main.Operation
public void Run(long samples)
{
if (!System.IO.File.Exists(m_options.Dbpath))
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist");
using(var db = new LocalTestDatabase(m_options.Dbpath))
using(var backend = new BackendManager(m_backendurl, m_options, m_results.BackendWriter, db))
@@ -82,7 +87,7 @@ namespace Duplicati.Library.Main.Operation
KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>> res;
using(var tf = vol.TempFile)
res = TestVolumeInternals(db, vol, tf, m_options, m_results, m_options.FullBlockVerification ? 1.0 : 0.2);
res = TestVolumeInternals(db, vol, tf, m_options, m_options.FullBlockVerification ? 1.0 : 0.2);
m_results.AddResult(res.Key, res.Value);
if (!string.IsNullOrWhiteSpace(vol.Hash) && vol.Size > 0)
@@ -100,11 +105,11 @@ namespace Duplicati.Library.Main.Operation
{
if (m_options.Dryrun)
{
m_results.AddDryrunMessage(string.Format("Sucessfully captured hash and size for {0}, would update database", vol.Name));
Logging.Log.WriteDryrunMessage(LOGTAG, "CaptureHashAndSize", "Sucessfully captured hash and size for {0}, would update database", vol.Name);
}
else
{
m_results.AddMessage(string.Format("Sucessfully captured hash and size for {0}, updating database", vol.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "CaptureHashAndSize", "Sucessfully captured hash and size for {0}, updating database", vol.Name);
db.UpdateRemoteVolume(vol.Name, RemoteVolumeState.Verified, vol.Size, vol.Hash);
}
}
@@ -117,7 +122,7 @@ namespace Duplicati.Library.Main.Operation
catch (Exception ex)
{
m_results.AddResult(vol.Name, new KeyValuePair<Duplicati.Library.Interface.TestEntryStatus, string>[] { new KeyValuePair<Duplicati.Library.Interface.TestEntryStatus, string>(Duplicati.Library.Interface.TestEntryStatus.Error, ex.Message) });
m_results.AddError(string.Format("Failed to process file {0}", vol.Name), ex);
Logging.Log.WriteErrorMessage(LOGTAG, "RemoteFileProcessingFailed", ex, "Failed to process file {0}", vol.Name);
if (ex is System.Threading.ThreadAbortException)
{
m_results.EndTime = DateTime.UtcNow;
@@ -143,13 +148,13 @@ namespace Duplicati.Library.Main.Operation
if (f.Size <= 0 || string.IsNullOrWhiteSpace(f.Hash))
{
m_results.AddMessage(string.Format("No hash or size recorded for {0}, performing full verification", f.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "MissingRemoteHash", "No hash or size recorded for {0}, performing full verification", f.Name);
KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>> res;
string hash;
long size;
using (var tf = backend.GetWithInfo(f.Name, out size, out hash))
res = TestVolumeInternals(db, f, tf, m_options, m_results, 1);
res = TestVolumeInternals(db, f, tf, m_options, 1);
m_results.AddResult(res.Key, res.Value);
if (!string.IsNullOrWhiteSpace(hash) && size > 0)
@@ -158,11 +163,11 @@ namespace Duplicati.Library.Main.Operation
{
if (m_options.Dryrun)
{
m_results.AddDryrunMessage(string.Format("Sucessfully captured hash and size for {0}, would update database", f.Name));
Logging.Log.WriteDryrunMessage(LOGTAG, "CapturedHashAndSize", "Sucessfully captured hash and size for {0}, would update database", f.Name);
}
else
{
m_results.AddMessage(string.Format("Sucessfully captured hash and size for {0}, updating database", f.Name));
Logging.Log.WriteInformationMessage(LOGTAG, "CapturedHashAndSize", "Sucessfully captured hash and size for {0}, updating database", f.Name);
db.UpdateRemoteVolume(f.Name, RemoteVolumeState.Verified, size, hash);
}
}
@@ -177,7 +182,7 @@ namespace Duplicati.Library.Main.Operation
catch (Exception ex)
{
m_results.AddResult(f.Name, new KeyValuePair<Duplicati.Library.Interface.TestEntryStatus, string>[] { new KeyValuePair<Duplicati.Library.Interface.TestEntryStatus, string>(Duplicati.Library.Interface.TestEntryStatus.Error, ex.Message) });
m_results.AddError(string.Format("Failed to process file {0}", f.Name), ex);
Logging.Log.WriteErrorMessage(LOGTAG, "FailedToProcessFile", ex, "Failed to process file {0}", f.Name);
if (ex is System.Threading.ThreadAbortException)
{
m_results.EndTime = DateTime.UtcNow;
@@ -196,14 +201,14 @@ namespace Duplicati.Library.Main.Operation
/// <param name="vol">The remote volume being examined</param>
/// <param name="tf">The path to the downloaded copy of the file</param>
/// <param name="sample_percent">A value between 0 and 1 that indicates how many blocks are tested in a dblock file</param>
public static KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>> TestVolumeInternals(LocalTestDatabase db, IRemoteVolume vol, string tf, Options options, ILogWriter log, double sample_percent)
public static KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>> TestVolumeInternals(LocalTestDatabase db, IRemoteVolume vol, string tf, Options options, double sample_percent)
{
var blockhasher = Library.Utility.HashAlgorithmHelper.Create(options.BlockHashAlgorithm);
if (blockhasher == null)
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidHashAlgorithm(options.BlockHashAlgorithm), "BlockHashAlgorithmInvalid");
if (!blockhasher.CanReuseTransform)
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(options.BlockHashAlgorithm));
throw new UserInformationException(Strings.Common.InvalidCryptoSystem(options.BlockHashAlgorithm), "BlockHashAlgorithmInvalid");
var hashsize = blockhasher.HashSize / 8;
var parsedInfo = Volumes.VolumeBase.ParseFilename(vol.Name);
@@ -285,7 +290,7 @@ namespace Duplicati.Library.Main.Operation
}
}
log.AddWarning(string.Format("Unexpected file type {0} for {1}", parsedInfo.FileType, vol.Name), null);
Logging.Log.WriteWarningMessage(LOGTAG, "UnexpectedFileType", null, "Unexpected file type {0} for {1}", parsedInfo.FileType, vol.Name);
return new KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>>(vol.Name, null);
}
}
+225 -42
View File
@@ -188,7 +188,7 @@ namespace Duplicati.Library.Main
/// <summary>
/// Lock that protects the options collection
/// </summary>
protected object m_lock = new object();
protected readonly object m_lock = new object();
protected Dictionary<string, string> m_options;
@@ -257,7 +257,8 @@ namespace Duplicati.Library.Main
"compression-extension-file",
"full-remote-verification",
"disable-synthetic-filelist",
"disable-file-scanner"
"disable-file-scanner",
"disable-on-battery"
};
}
}
@@ -280,7 +281,8 @@ namespace Duplicati.Library.Main
"max-upload-pr-second",
"max-download-pr-second",
"no-connection-reuse",
"allow-sleep"
"allow-sleep",
"use-background-io-priority"
};
}
}
@@ -310,7 +312,10 @@ namespace Duplicati.Library.Main
"debug-output",
"debug-retry-errors",
"log-file",
"log-level",
"log-file-log-level",
"log-file-log-filter",
"console-log-level",
"console-log-filter",
};
}
}
@@ -460,11 +465,18 @@ namespace Duplicati.Library.Main
new CommandLineArgument("debug-output", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DebugoutputShort, Strings.Options.DebugoutputLong, "false"),
new CommandLineArgument("debug-retry-errors", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DebugretryerrorsShort, Strings.Options.DebugretryerrorsLong, "false"),
new CommandLineArgument("log-file", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Options.LogfileShort, Strings.Options.LogfileShort),
new CommandLineArgument("log-level", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration, Strings.Options.LoglevelShort, Strings.Options.LoglevelLong, "Warning", null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))),
new CommandLineArgument("log-file", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Options.LogfileShort, Strings.Options.LogfileLong),
new CommandLineArgument("log-file-log-level", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration, Strings.Options.LogfileloglevelShort, Strings.Options.LogfileloglevelShort, "Warning", null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))),
new CommandLineArgument("log-file-log-filter", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Options.LogfilelogfiltersShort, Strings.Options.LogfilelogfiltersLong(System.IO.Path.PathSeparator.ToString()), null),
new CommandLineArgument("console-log-level", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration, Strings.Options.ConsoleloglevelShort, Strings.Options.ConsoleloglevelShort, "Warning", null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))),
new CommandLineArgument("console-log-filter", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Options.ConsolelogfiltersShort, Strings.Options.ConsolelogfiltersLong(System.IO.Path.PathSeparator.ToString()), null),
new CommandLineArgument("log-level", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration, Strings.Options.LoglevelShort, Strings.Options.LoglevelLong, "Warning", null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType)), Strings.Options.LogLevelDeprecated("log-file-log-level", "console-log-level")),
new CommandLineArgument("list-verify-uploads", CommandLineArgument.ArgumentType.Boolean, Strings.Options.ListverifyuploadsShort, Strings.Options.ListverifyuploadsShort, "false"),
new CommandLineArgument("allow-sleep", CommandLineArgument.ArgumentType.Boolean, Strings.Options.AllowsleepShort, Strings.Options.AllowsleepLong, "false"),
new CommandLineArgument("use-background-io-priority", CommandLineArgument.ArgumentType.Boolean, Strings.Options.UsebackgroundiopriorityShort, Strings.Options.UsebackgroundiopriorityLong, "false"),
new CommandLineArgument("no-connection-reuse", CommandLineArgument.ArgumentType.Boolean, Strings.Options.NoconnectionreuseShort, Strings.Options.NoconnectionreuseLong, "false"),
new CommandLineArgument("quota-size", CommandLineArgument.ArgumentType.Size, Strings.Options.QuotasizeShort, Strings.Options.QuotasizeLong),
@@ -478,7 +490,7 @@ namespace Duplicati.Library.Main
new CommandLineArgument("backup-name", CommandLineArgument.ArgumentType.String, Strings.Options.BackupnameShort, Strings.Options.BackupnameLong, DefaultBackupName),
new CommandLineArgument("compression-extension-file", CommandLineArgument.ArgumentType.Path, Strings.Options.CompressionextensionfileShort, Strings.Options.CompressionextensionfileLong(DEFAULT_COMPRESSED_EXTENSION_FILE), DEFAULT_COMPRESSED_EXTENSION_FILE),
new CommandLineArgument("verbose", CommandLineArgument.ArgumentType.Boolean, Strings.Options.VerboseShort, Strings.Options.VerboseLong, "false"),
new CommandLineArgument("verbose", CommandLineArgument.ArgumentType.Boolean, Strings.Options.VerboseShort, Strings.Options.VerboseLong, "false", null, null, Strings.Options.VerboseDeprecated),
new CommandLineArgument("full-result", CommandLineArgument.ArgumentType.Boolean, Strings.Options.FullresultShort, Strings.Options.FullresultLong, "false"),
new CommandLineArgument("overwrite", CommandLineArgument.ArgumentType.Boolean, Strings.Options.OverwriteShort, Strings.Options.OverwriteLong, "false"),
@@ -532,6 +544,7 @@ namespace Duplicati.Library.Main
new CommandLineArgument("auto-vacuum", CommandLineArgument.ArgumentType.Boolean, Strings.Options.AutoVacuumShort, Strings.Options.AutoVacuumLong, "false"),
new CommandLineArgument("disable-file-scanner", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DisablefilescannerShort, Strings.Options.DisablefilescannerLong, "false"),
new CommandLineArgument("disable-on-battery", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DisableOnBatteryShort, Strings.Options.DisableOnBatteryLong, "false"),
});
return lst;
@@ -816,10 +829,10 @@ namespace Duplicati.Library.Main
/// <summary>
/// Gets the time frames and intervals for the retention policy
/// </summary>
public Dictionary<TimeSpan, TimeSpan> RetentionPolicy
public List<RetentionPolicyValue> RetentionPolicy
{
get {
var retentionPolicyConfig = new Dictionary<TimeSpan, TimeSpan>();
var retentionPolicyConfig = new List<RetentionPolicyValue>();
string v;
m_options.TryGetValue("retention-policy", out v);
@@ -831,11 +844,7 @@ namespace Duplicati.Library.Main
foreach (var periodIntervalString in periodIntervalStrings)
{
var periodInterval = periodIntervalString.Split(':');
var period = Library.Utility.Timeparser.ParseTimeSpan(periodInterval[0]);
var interval = Library.Utility.Timeparser.ParseTimeSpan(periodInterval[1]);
retentionPolicyConfig.Add(period, interval);
retentionPolicyConfig.Add(RetentionPolicyValue.CreateFromString(periodIntervalString));
}
return retentionPolicyConfig;
@@ -937,7 +946,7 @@ namespace Duplicati.Library.Main
{
int x = int.Parse(m_options["number-of-retries"]);
if (x < 0)
throw new UserInformationException("Invalid count for number-of-retries");
throw new UserInformationException("Invalid count for number-of-retries", "NumberOfRetriesInvalid");
return x;
}
@@ -950,10 +959,15 @@ namespace Duplicati.Library.Main
public bool SynchronousUpload { get { return Library.Utility.Utility.ParseBoolOption(m_options, "synchronous-upload"); } }
/// <summary>
/// A value indicating if system is allowed to enter sleep power states during backup/restore ops (win32 only)
/// A value indicating if system is allowed to enter sleep power states during backup/restore
/// </summary>
public bool AllowSleep { get { return GetBool("allow-sleep"); } }
/// <summary>
/// A value indicating if system should use the low-priority IO during backup/restore
/// </summary>
public bool UseBackgroundIOPriority { get { return GetBool("use-background-io-priority"); } }
/// <summary>
/// A value indicating if use of the streaming interface is disallowed
/// </summary>
@@ -1212,34 +1226,19 @@ namespace Duplicati.Library.Main
}
/// <summary>
/// Gets a value indicating if the log level has been set
/// Gets the log-file detail level
/// </summary>
public bool HasLoglevel
public Duplicati.Library.Logging.LogMessageType LogFileLoglevel
{
get
{
string value;
if (!m_options.TryGetValue("log-level", out value))
if (!m_options.TryGetValue("log-file-log-level", out value))
value = null;
foreach (string s in Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType)))
if (s.Equals(value, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
}
/// <summary>
/// Gets the log detail level
/// </summary>
public Duplicati.Library.Logging.LogMessageType Loglevel
{
get
{
string value;
if (!m_options.TryGetValue("log-level", out value))
value = null;
if (string.IsNullOrWhiteSpace(value))
if (!m_options.TryGetValue("log-level", out value))
value = null;
foreach (string s in Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType)))
if (s.Equals(value, StringComparison.OrdinalIgnoreCase))
@@ -1249,6 +1248,102 @@ namespace Duplicati.Library.Main
}
}
/// <summary>
/// Helper method to support filters with either a + or - prefix
/// </summary>
/// <returns>The IFilter instance.</returns>
/// <param name="msg">The filter string to parse.</param>
private static IFilter StringToIFilter(string msg)
{
if (string.IsNullOrWhiteSpace(msg))
return new FilterExpression();
if (msg[0] == '+')
return new FilterExpression(msg.Substring(1), true);
if (msg[0] == '-')
return new FilterExpression(msg.Substring(1), false);
return new FilterExpression(msg, true);
}
/// <summary>
/// Parses a log filter string, and returns the filter instance
/// </summary>
/// <returns>The log filter.</returns>
/// <param name="value">The filter string to parse.</param>
public static IFilter ParseLogFilter(string value)
{
if (string.IsNullOrWhiteSpace(value))
return new FilterExpression();
return value
.Split(new char[] { System.IO.Path.PathSeparator, ':', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(StringToIFilter)
.Aggregate(FilterExpression.Combine);
}
/// <summary>
/// Parses a log level string
/// </summary>
/// <returns>The log level enumeration value.</returns>
/// <param name="value">The string value to parse.</param>
/// <param name="backupvalue">An optional fallback parsing value.</param>
public static Logging.LogMessageType ParseLogLevel(string value, string backupvalue)
{
value = string.IsNullOrWhiteSpace(value) ? backupvalue : value;
foreach (string s in Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType)))
if (s.Equals(value, StringComparison.OrdinalIgnoreCase))
return (Duplicati.Library.Logging.LogMessageType)Enum.Parse(typeof(Duplicati.Library.Logging.LogMessageType), s);
return Duplicati.Library.Logging.LogMessageType.Warning;
}
/// <summary>
/// Gets the filter used for log-file messages.
/// </summary>
/// <value>The log file filter.</value>
public IFilter LogFileLogFilter
{
get
{
m_options.TryGetValue("log-file-log-filter", out var value);
return ParseLogFilter(value);
}
}
/// <summary>
/// Gets the filter used for console messages.
/// </summary>
/// <value>The log file filter.</value>
public IFilter ConsoleLogFilter
{
get
{
m_options.TryGetValue("console-log-filter", out var value);
return ParseLogFilter(value);
}
}
/// <summary>
/// Gets the console log detail level
/// </summary>
public Duplicati.Library.Logging.LogMessageType ConsoleLoglevel
{
get
{
string value;
if (!m_options.TryGetValue("console-log-level", out value))
value = null;
if (string.IsNullOrWhiteSpace(value))
if (!m_options.TryGetValue("log-level", out value))
value = null;
foreach (string s in Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType)))
if (s.Equals(value, StringComparison.OrdinalIgnoreCase))
return (Duplicati.Library.Logging.LogMessageType)Enum.Parse(typeof(Duplicati.Library.Logging.LogMessageType), s);
return Duplicati.Library.Logging.LogMessageType.Warning;
}
}
/// <summary>
/// Gets the attribute filter used to exclude files and folders.
/// </summary>
@@ -1282,11 +1377,6 @@ namespace Duplicati.Library.Main
/// </summary>
public bool NoConnectionReuse { get { return GetBool("no-connection-reuse"); } }
/// <summary>
/// A value indicating if the output should be verbose
/// </summary>
public bool Verbose { get { return GetBool("verbose"); } }
/// <summary>
/// A value indicating if the returned value should not be truncated
/// </summary>
@@ -1802,6 +1892,15 @@ namespace Duplicati.Library.Main
get { return Library.Utility.Utility.ParseBoolOption(m_options, "disable-file-scanner"); }
}
/// <summary>
/// Gets a flag indicating whether the backup should be disabled when on battery power.
/// </summary>
/// <value><c>true</c> if the backup should be disabled when on battery power; otherwise, <c>false</c>.</value>
public bool DisableOnBattery
{
get { return Library.Utility.Utility.ParseBoolOption(m_options, "disable-on-battery"); }
}
/// <summary>
/// Gets the threshold for when log data should be cleaned
/// </summary>
@@ -1878,5 +1977,89 @@ namespace Duplicati.Library.Main
return Library.Utility.Utility.ParseBoolOption(m_options, name);
}
/// <summary>
/// Class for handling a single RententionPolicy timeframe-interval-pair
/// </summary>
public class RetentionPolicyValue
{
public readonly TimeSpan Timeframe;
public readonly TimeSpan Interval;
public RetentionPolicyValue(TimeSpan timeframe, TimeSpan interval)
{
if (timeframe < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(timeframe), string.Format("The timeframe cannot be negative: '{0}'", timeframe));
}
if (interval < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(interval), string.Format("The interval cannot be negative: '{0}'", interval));
}
this.Timeframe = timeframe;
this.Interval = interval;
}
/// <summary>
/// Returns whether this is an unlimited timeframe or not
/// </summary>
/// <returns></returns>
public Boolean IsUnlimtedTimeframe()
{
// Timeframes equal or bigger than the maximum TimeSpan effectivly represent an unlimited timeframe
return Timeframe >= TimeSpan.MaxValue;
}
/// <summary>
/// Returns whether all versions in this timeframe should be kept or not
/// </summary>
/// <returns></returns>
public Boolean IsKeepAllVersions()
{
/// Intervals between two versions that are equal or smaller than zero effectivly result in
/// all versions in that timeframe being kept.
return Interval <= TimeSpan.Zero;
}
public override string ToString()
{
return (IsUnlimtedTimeframe() ? "Unlimited" : Timeframe.ToString()) + " / " + (IsKeepAllVersions() ? "Keep all" : Interval.ToString());
}
/// <summary>
/// Parses a string representation of a timeframe-interval-pair and returns a RentionPolicyValue object
/// </summary>
/// <returns></returns>
public static RetentionPolicyValue CreateFromString(string rententionPolicyValueString)
{
var periodInterval = rententionPolicyValueString.Split(':');
TimeSpan timeframe;
// Timeframe "U" (= Unlimited) means: For unlimted time keep one version every X interval.
// So the timeframe has to span the maximum time possible.
if (String.Equals(periodInterval[0], "U", StringComparison.OrdinalIgnoreCase))
{
timeframe = TimeSpan.MaxValue;
}
else
{
timeframe = Library.Utility.Timeparser.ParseTimeSpan(periodInterval[0]);
}
TimeSpan interval;
// Interval "U" (= Unlimited) means: For period X keep all versions.
// So the interval between two versions has to be zero.
if (String.Equals(periodInterval[1], "U", StringComparison.OrdinalIgnoreCase))
{
interval = TimeSpan.Zero;
}
else
{
interval = Library.Utility.Timeparser.ParseTimeSpan(periodInterval[1]);
}
return new RetentionPolicyValue(timeframe, interval);
}
}
}
}
+403
View File
@@ -0,0 +1,403 @@
// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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.Linq;
using Duplicati.Library.Utility;
namespace Duplicati.Library.Main
{
/// <summary>
/// This class provides various process control tasks,
/// such as preventing sleep and setting the IO priority of
/// the running process
/// </summary>
public class ProcessController : IDisposable
{
/// <summary>
/// The log tag to use
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<ProcessController>();
/// <summary>
/// A flag used to control the stop invocation
/// </summary>
private bool m_disposed = true;
/// <summary>
/// A flag indicating if the sleep prevention has been started
/// </summary>
private bool m_runningSleepPrevention;
/// <summary>
/// A flag indicating if the background IO priority has been started
/// </summary>
private bool m_hasEnabledBackgroundIOPriority;
/// <summary>
/// The caffeinate process runner
/// </summary>
private System.Diagnostics.Process m_caffeinate;
/// <summary>
/// The nice level to restore the process to
/// </summary>
private int m_originalNiceLevel;
/// <summary>
/// The nice class to restore the process to
/// </summary>
private int m_originalNiceClass;
/// <summary>
/// The priority class to restore the process to
/// </summary>
private Win32.IO_PRIORITY_HINT m_originalWinPriorityClass;
/// <summary>
/// A flag indicating if the Windows background mode is started
/// </summary>
private bool m_hasStartedBackgroundMode = false;
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Main.ProcessController"/> class.
/// </summary>
/// <param name="options">The options to use.</param>
public ProcessController(Options options)
{
if (options == null)
return;
try
{
Start(options);
m_disposed = false;
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "ProcessControllerStartError", ex, "Failed to start the process controller: {0}", ex.Message);
}
}
/// <summary>
/// Starts the sleep prevention
/// </summary>
private void StartSleepPrevention()
{
if (Duplicati.Library.Utility.Utility.IsClientWindows)
{
try
{
Win32.SetThreadExecutionState(Win32.EXECUTION_STATE.ES_CONTINUOUS | Win32.EXECUTION_STATE.ES_SYSTEM_REQUIRED);
m_runningSleepPrevention = true;
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "SleepPrevetionError", ex, "Failed to set sleep prevention");
}
}
else if (Duplicati.Library.Utility.Utility.IsClientOSX)
{
try
{
// -s prevents sleep on AC, -i prevents sleep generally
var psi = new System.Diagnostics.ProcessStartInfo("caffeinate", "-s");
psi.RedirectStandardInput = true;
psi.UseShellExecute = false;
m_caffeinate = System.Diagnostics.Process.Start(psi);
m_runningSleepPrevention = true;
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "SleepPreventionError", ex, "Failed to set sleep prevention");
}
}
else
{
}
}
/// <summary>
/// Activates the process bacground IO priority
/// </summary>
private void ActivateBackgroundIOPriority()
{
var pid = System.Diagnostics.Process.GetCurrentProcess().Id;
if (Duplicati.Library.Utility.Utility.IsClientWindows)
{
var handle = System.Diagnostics.Process.GetCurrentProcess().Handle;
try
{
var mode = Win32.IO_PRIORITY_HINT.IoPriorityLow;
var res = Win32.NtQueryInformationProcess(handle, Win32.PROCESS_INFORMATION_CLASS.ProcessIoPriority, ref mode, sizeof(Win32.IO_PRIORITY_HINT), IntPtr.Zero);
if (res != 0)
throw new Library.Interface.UserInformationException($"Failed to read process priority {res:x}", "BackgroundPriorityEnableError", new System.ComponentModel.Win32Exception());
m_originalWinPriorityClass = mode;
mode = Win32.IO_PRIORITY_HINT.IoPriorityVeryLow;
res = Win32.NtSetInformationProcess(handle, Win32.PROCESS_INFORMATION_CLASS.ProcessIoPriority, ref mode, sizeof(Win32.IO_PRIORITY_HINT));
if (res != 0)
throw new Library.Interface.UserInformationException($"Failed to set process priority {res:x}", "BackgroundPriorityEnableError", new System.ComponentModel.Win32Exception());
m_hasEnabledBackgroundIOPriority = true;
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "BackgroundPriorityError", ex, "Failed to set background IO priority");
}
try
{
if (!Win32.SetPriorityClass(handle, Win32.PROCESS_PRIORITY_CLASS.PROCESS_MODE_BACKGROUND_BEGIN))
throw new Library.Interface.UserInformationException($"Failed to start process background mode", "BackgroundPriorityEnableError", new System.ComponentModel.Win32Exception());
m_hasStartedBackgroundMode = true;
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "BackgroundPriorityError", ex, "Failed to set start background processing mode");
}
}
else
{
if (Duplicati.Library.Utility.Utility.IsClientOSX)
{
var data = RunProcessAndGetResult("ps", $"-onice -p {pid}");
m_originalNiceLevel = int.Parse(data.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Last());
data = RunProcessAndGetResult("renice", $"20 {pid}");
m_hasEnabledBackgroundIOPriority = true;
}
else
{
var data = RunProcessAndGetResult("ionice", $"-p {pid}");
var results = data.Split(new char[] { ':', ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
var ioclass = results[0];
if (string.Equals(ioclass, "idle", StringComparison.OrdinalIgnoreCase))
{
m_originalNiceClass = 3;
m_originalNiceLevel = -1;
}
else if (string.Equals(ioclass, "none", StringComparison.OrdinalIgnoreCase))
{
m_originalNiceClass = 0;
m_originalNiceLevel = int.Parse(results.Last());
}
else if (string.Equals(ioclass, "best-effort", StringComparison.OrdinalIgnoreCase))
{
m_originalNiceClass = 2;
m_originalNiceLevel = int.Parse(results.Last());
}
else if (string.Equals(ioclass, "realtime", StringComparison.OrdinalIgnoreCase))
{
m_originalNiceClass = 1;
m_originalNiceLevel = int.Parse(results.Last());
}
else
throw new Library.Interface.UserInformationException($"Unable to parse priority class {ioclass}", "UnableToParseIONicePriorityClass");
data = RunProcessAndGetResult("ionice", $"-c 3 -p {pid}");
m_hasEnabledBackgroundIOPriority = true;
}
}
}
/// <summary>
/// Starts the process controller
/// </summary>
/// <param name="options">The options to use</param>
private void Start(Options options)
{
if (!options.AllowSleep)
StartSleepPrevention();
if (options.UseBackgroundIOPriority)
ActivateBackgroundIOPriority();
}
/// <summary>
/// Stops the sleep prevention, if it was enabled
/// </summary>
private void StopSleepPrevention()
{
if (Duplicati.Library.Utility.Utility.IsClientWindows)
{
try
{
if (m_runningSleepPrevention)
{
m_runningSleepPrevention = false;
Win32.SetThreadExecutionState(Win32.EXECUTION_STATE.ES_CONTINUOUS);
}
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "SleepPrevetionError", ex, "Failed to set sleep prevention");
}
}
else if (Duplicati.Library.Utility.Utility.IsClientOSX)
{
try
{
m_runningSleepPrevention = false;
if (m_caffeinate != null && !m_caffeinate.HasExited)
{
// Send CTRL+C
m_caffeinate.StandardInput.Write("\x3");
m_caffeinate.StandardInput.Flush();
m_caffeinate.WaitForExit(500);
if (!m_caffeinate.HasExited)
{
m_caffeinate.Kill();
m_caffeinate.WaitForExit(500);
if (!m_caffeinate.HasExited)
throw new Exception("Failed to kill the caffeinate process");
}
}
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "SleepPreventionDisableError", ex, "Failed to unset sleep prevention");
}
}
}
/// <summary>
/// Deactivates the background IO Priority, if set.
/// </summary>
private void DeactivateBackgroundIOPriority()
{
if (Duplicati.Library.Utility.Utility.IsClientWindows)
{
try
{
if (m_hasStartedBackgroundMode)
{
m_hasStartedBackgroundMode = false;
var handle = System.Diagnostics.Process.GetCurrentProcess().Handle;
if (!Win32.SetPriorityClass(handle, Win32.PROCESS_PRIORITY_CLASS.PROCESS_MODE_BACKGROUND_END))
throw new Library.Interface.UserInformationException($"Failed to stop process background mode", "BackgroundPriorityEnableError", new System.ComponentModel.Win32Exception());
}
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "BackgroundPriorityError", ex, "Failed to stop start background processing mode");
}
try
{
if (m_hasEnabledBackgroundIOPriority)
{
m_hasEnabledBackgroundIOPriority = false;
var handle = System.Diagnostics.Process.GetCurrentProcess().Handle;
var mode = m_originalWinPriorityClass;
var res = Win32.NtSetInformationProcess(handle, Win32.PROCESS_INFORMATION_CLASS.ProcessIoPriority, ref mode, sizeof(Win32.IO_PRIORITY_HINT));
if (res != 0)
Logging.Log.WriteWarningMessage(LOGTAG, "BackgroundPriorityDisableError", new System.ComponentModel.Win32Exception(), "Failed to reset background IO priority, status code {0}", res);
}
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "BackgroundPriorityError", ex, "Failed to reset background IO priority");
}
}
else
{
if (m_hasEnabledBackgroundIOPriority)
{
m_hasEnabledBackgroundIOPriority = false;
var pid = System.Diagnostics.Process.GetCurrentProcess().Id;
if (Duplicati.Library.Utility.Utility.IsClientOSX)
{
var data = RunProcessAndGetResult($"renice", $"{m_originalNiceLevel} {pid}");
}
else
{
string data;
if (m_originalNiceLevel < 0)
data = RunProcessAndGetResult($"ionice", $"-c {m_originalNiceClass} -p {pid}");
else
data = RunProcessAndGetResult($"ionice", $"-c {m_originalNiceClass} -n {m_originalNiceLevel} -p {pid}");
}
}
}
}
/// <summary>
/// Stops the process controller
/// </summary>
private void Stop()
{
StopSleepPrevention();
DeactivateBackgroundIOPriority();
}
/// <summary>
/// Runs a process and returns the stdout data
/// </summary>
/// <returns>The stdout data.</returns>
/// <param name="filename">The executable to invoke.</param>
/// <param name="arguments">The commandline arguments.</param>
private static string RunProcessAndGetResult(string filename, string arguments)
{
var psi = new System.Diagnostics.ProcessStartInfo(filename, arguments);
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
Logging.Log.WriteExplicitMessage(LOGTAG, "RunningCommand", null, "Running: {0} {1}", filename, arguments);
var pi = System.Diagnostics.Process.Start(psi);
pi.WaitForExit(5000);
if (pi.HasExited)
return pi.StandardOutput.ReadToEnd().Trim();
pi.Kill();
throw new Library.Interface.UserInformationException($"The process {filename} with arguments {arguments} failed to stop", "LaunchProcessFailed");
}
/// <summary>
/// Releases all resource used by the <see cref="T:Duplicati.Library.Main.ProcessController"/> object.
/// </summary>
/// <remarks>Call <see cref="Dispose"/> when you are finished using the
/// <see cref="T:Duplicati.Library.Main.ProcessController"/>. The <see cref="Dispose"/> method leaves the
/// <see cref="T:Duplicati.Library.Main.ProcessController"/> in an unusable state. After calling
/// <see cref="Dispose"/>, you must release all references to the
/// <see cref="T:Duplicati.Library.Main.ProcessController"/> so the garbage collector can reclaim the memory
/// that the <see cref="T:Duplicati.Library.Main.ProcessController"/> was occupying.</remarks>
public void Dispose()
{
if (!m_disposed)
{
m_disposed = true;
try
{
Stop();
}
catch(Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "ProcessControllerStopError", ex, "Failed to stop the process controller: {0}", ex.Message);
}
}
}
}
}
+32 -60
View File
@@ -1,38 +1,40 @@
// Copyright (C) 2013, Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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
// Copyright (C) 2013, Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// 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.Linq;
using Duplicati.Library.Logging;
namespace Duplicati.Library.Main
{
/// <summary>
/// Interface for recieving messages from the Duplicati operations
/// </summary>
public interface IMessageSink
{
public interface IMessageSink : Logging.ILogDestination
{
/// <summary>
/// Handles an event from the backend
/// </summary>
/// <param name="action">The backend action.</param>
/// <param name="type">The event type.</param>
/// <param name="path">The target path.</param>
/// <param name="size">The size of the element.</param>
void BackendEvent(BackendActionType action, BackendEventType type, string path, long size);
void VerboseEvent(string message, object[] args);
void MessageEvent(string message);
void RetryEvent(string message, Exception ex);
void WarningEvent(string message, Exception ex);
void ErrorEvent(string message, Exception ex);
void DryrunEvent(string message);
/// <summary>
/// Sets the backend progress update object
/// </summary>
@@ -104,40 +106,10 @@ namespace Duplicati.Library.Main
s.BackendEvent(action, type, path, size);
}
public void DryrunEvent(string message)
public void WriteMessage(LogEntry entry)
{
foreach (var s in m_sinks)
s.DryrunEvent(message);
}
public void ErrorEvent(string message, Exception ex)
{
foreach (var s in m_sinks)
s.ErrorEvent(message, ex);
}
public void MessageEvent(string message)
{
foreach (var s in m_sinks)
s.MessageEvent(message);
}
public void RetryEvent(string message, Exception ex)
{
foreach (var s in m_sinks)
s.RetryEvent(message, ex);
}
public void VerboseEvent(string message, object[] args)
{
foreach (var s in m_sinks)
s.VerboseEvent(message, args);
}
public void WarningEvent(string message, Exception ex)
{
foreach (var s in m_sinks)
s.WarningEvent(message, ex);
foreach (var s in m_sinks)
s.WriteMessage(entry);
}
}

Some files were not shown because too many files have changed in this diff Show More