@@ -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
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 & 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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-11
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+403
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user