Merge branch 'master' of https://github.com/jonmikelv/duplicati
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile />
|
||||
<UseMSBuildEngine>false</UseMSBuildEngine>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Duplicati.CommandLine.BackendTester
|
||||
var p = Library.Utility.Utility.ExpandEnvironmentVariables(_args[0]);
|
||||
if (System.IO.File.Exists(p))
|
||||
_args = (from x in System.IO.File.ReadLines(p)
|
||||
where !string.IsNullOrWhiteSpace(x) && !x.Trim().StartsWith("#")
|
||||
where !string.IsNullOrWhiteSpace(x) && !x.Trim().StartsWith("#", StringComparison.Ordinal)
|
||||
select x.Trim()
|
||||
).ToArray();
|
||||
}
|
||||
@@ -121,7 +121,7 @@ namespace Duplicati.CommandLine.BackendTester
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Unittest failed: " + ex.ToString());
|
||||
Console.WriteLine("Unittest failed: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ namespace Duplicati.CommandLine.BackendTester
|
||||
}
|
||||
|
||||
if (e != null)
|
||||
Console.WriteLine("failed\n*** Error: {0}", e.ToString());
|
||||
Console.WriteLine("failed\n*** Error: {0}", e);
|
||||
else
|
||||
Console.WriteLine("done");
|
||||
|
||||
@@ -350,7 +350,7 @@ namespace Duplicati.CommandLine.BackendTester
|
||||
try { backend.Delete(tx.remotefilename); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("*** Failed to delete file {0}, message: {1}", tx.remotefilename, ex.ToString());
|
||||
Console.WriteLine("*** Failed to delete file {0}, message: {1}", tx.remotefilename, ex);
|
||||
}
|
||||
|
||||
curlist = backend.List();
|
||||
@@ -397,11 +397,11 @@ namespace Duplicati.CommandLine.BackendTester
|
||||
|
||||
if (e != null)
|
||||
{
|
||||
Console.WriteLine("Failed to upload file {0}, error message: {1}, remote name: {2}", i, e.ToString(), remotefilename);
|
||||
Console.WriteLine("Failed to upload file {0}, error message: {1}, remote name: {2}", i, e, remotefilename);
|
||||
while (e.InnerException != null)
|
||||
{
|
||||
e = e.InnerException;
|
||||
Console.WriteLine(string.Format(" Inner exception: {0}", e.ToString()));
|
||||
Console.WriteLine(string.Format(" Inner exception: {0}", e));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile />
|
||||
<UseMSBuildEngine>false</UseMSBuildEngine>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Duplicati.CommandLine.BackendTool
|
||||
{
|
||||
Console.WriteLine("Command failed: " + ex.Message);
|
||||
if (debugoutput || !(ex is UserInformationException))
|
||||
Console.WriteLine(ex.ToString());
|
||||
Console.WriteLine(ex);
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ namespace Duplicati.CommandLine
|
||||
options["version"] = v.ToString();
|
||||
}
|
||||
}
|
||||
else if (args[0].IndexOfAny(new char[] { '*', '?' }) < 0 && !args[0].StartsWith("["))
|
||||
else if (args[0].IndexOfAny(new char[] { '*', '?' }) < 0 && !args[0].StartsWith("[", StringComparison.Ordinal))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -277,7 +277,7 @@ namespace Duplicati.CommandLine
|
||||
|
||||
// Prefix all filenames with "*/" so we search all folders
|
||||
for(var ix = 0; ix < args.Count; ix++)
|
||||
if (args[ix].IndexOfAny(new char[] { '*', '?', System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar }) < 0 && !args[ix].StartsWith("["))
|
||||
if (args[ix].IndexOfAny(new char[] { '*', '?', System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar }) < 0 && !args[ix].StartsWith("[", StringComparison.Ordinal))
|
||||
args[ix] = "*" + System.IO.Path.DirectorySeparatorChar.ToString() + args[ix];
|
||||
|
||||
// Support for not adding the --auth-username if possible
|
||||
@@ -360,7 +360,7 @@ namespace Duplicati.CommandLine
|
||||
var f = res.Filesets.First();
|
||||
outwriter.WriteLine("Listing contents {0} ({1}):", f.Version, f.Time);
|
||||
foreach(var e in res.Files)
|
||||
outwriter.WriteLine("{0} {1}", e.Path, e.Path.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()) ? "" : "(" + Library.Utility.Utility.FormatSizeString(e.Sizes.First()) + ")");
|
||||
outwriter.WriteLine("{0} {1}", e.Path, e.Path.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) ? "" : "(" + Library.Utility.Utility.FormatSizeString(e.Sizes.First()) + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -444,12 +444,12 @@ namespace Duplicati.CommandLine
|
||||
|
||||
// Prefix all filenames with "*/" so we search all folders
|
||||
for (var ix = 0; ix < args.Count; ix++)
|
||||
if (args[ix].IndexOfAny(new char[] { '*', '?', System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar }) < 0 && !args[ix].StartsWith("["))
|
||||
if (args[ix].IndexOfAny(new char[] { '*', '?', System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar }) < 0 && !args[ix].StartsWith("[", StringComparison.Ordinal))
|
||||
args[ix] = "*" + System.IO.Path.DirectorySeparatorChar.ToString() + args[ix];
|
||||
|
||||
// suffix all folders with "*" so we restore all contents in the folder
|
||||
for (var ix = 0; ix < args.Count; ix++)
|
||||
if (args[ix].IndexOfAny(new char[] { '*', '?' }) < 0 && !args[ix].StartsWith("[") && args[ix].EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()))
|
||||
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);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile />
|
||||
<UseMSBuildEngine>false</UseMSBuildEngine>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace Duplicati.CommandLine
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach(var line in sr.ReadToEnd().Split(new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.None))
|
||||
{
|
||||
if (line.Trim().StartsWith("#"))
|
||||
if (line.Trim().StartsWith("#", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
if (line.Trim().StartsWith(">"))
|
||||
if (line.Trim().StartsWith(">", StringComparison.Ordinal))
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
@@ -410,13 +410,13 @@ namespace Duplicati.CommandLine
|
||||
string c = s;
|
||||
|
||||
string leadingSpaces = "";
|
||||
while (c.Length > 0 && c.StartsWith(" "))
|
||||
while (c.Length > 0 && c.StartsWith(" ", StringComparison.Ordinal))
|
||||
{
|
||||
leadingSpaces += " ";
|
||||
c = c.Remove(0, 1);
|
||||
}
|
||||
|
||||
bool extraIndent = c.StartsWith("--");
|
||||
bool extraIndent = c.StartsWith("--", StringComparison.Ordinal);
|
||||
|
||||
while (c.Length > 0)
|
||||
{
|
||||
@@ -424,7 +424,7 @@ namespace Duplicati.CommandLine
|
||||
len -= leadingSpaces.Length;
|
||||
if (len < c.Length)
|
||||
{
|
||||
int ix = c.LastIndexOf(" ", len);
|
||||
int ix = c.LastIndexOf(" ", len, StringComparison.Ordinal);
|
||||
if (ix > 0)
|
||||
len = ix;
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ namespace Duplicati.CommandLine
|
||||
else if (!(ex is Library.Interface.CancelException))
|
||||
{
|
||||
errwriter.WriteLine();
|
||||
errwriter.WriteLine(ex.ToString());
|
||||
errwriter.WriteLine(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine(" error: {0}", ex.ToString());
|
||||
Console.WriteLine(" error: {0}", ex);
|
||||
errors++;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<AssemblyOriginatorKeyFile>Duplicati.snk</AssemblyOriginatorKeyFile>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<UseMSBuildEngine>false</UseMSBuildEngine>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
ixfile = Path.GetFullPath(ixfile);
|
||||
if (!File.Exists(ixfile))
|
||||
{
|
||||
using(File.Create(ixfile))
|
||||
using (File.Create(ixfile))
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
var errors = 0;
|
||||
var totalblocks = 0L;
|
||||
var files = 0;
|
||||
foreach(var file in Directory.EnumerateFiles(folder))
|
||||
foreach (var file in Directory.EnumerateFiles(folder))
|
||||
{
|
||||
Console.Write("{0}: {1}", i, file);
|
||||
|
||||
@@ -95,11 +95,12 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
var filekey = Path.GetFileName(file);
|
||||
|
||||
var blocks = 0;
|
||||
using(var cp = Library.DynamicLoader.CompressionLoader.GetModule(p.CompressionModule, file, options))
|
||||
using(var tf = new Library.Utility.TempFile())
|
||||
using (var stream = new System.IO.FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var cp = Library.DynamicLoader.CompressionLoader.GetModule(p.CompressionModule, stream, Library.Interface.ArchiveMode.Read, options))
|
||||
using (var tf = new Library.Utility.TempFile())
|
||||
{
|
||||
using(var sw = new StreamWriter(tf))
|
||||
foreach(var f in cp.ListFiles(null))
|
||||
using (var sw = new StreamWriter(tf))
|
||||
foreach (var f in cp.ListFiles(null))
|
||||
{
|
||||
sw.WriteLine("{0}, {1}", Library.Utility.Utility.Base64UrlToBase64Plain(f), filekey);
|
||||
blocks++;
|
||||
@@ -121,9 +122,9 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
Console.WriteLine(" done!");
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(" error: {0}", ex.ToString());
|
||||
Console.WriteLine(" error: {0}", ex);
|
||||
errors++;
|
||||
}
|
||||
|
||||
@@ -142,7 +143,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
try
|
||||
{
|
||||
// If the file can fit into memory, this is MUCH faster
|
||||
using(var tfout = new Library.Utility.TempFile())
|
||||
using (var tfout = new Library.Utility.TempFile())
|
||||
{
|
||||
var data = File.ReadAllLines(filein);
|
||||
Array.Sort(data, StringComparer.Ordinal);
|
||||
@@ -156,8 +157,8 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
{
|
||||
}
|
||||
|
||||
using(var tfin = new Library.Utility.TempFile())
|
||||
using(var tfout = new Library.Utility.TempFile())
|
||||
using (var tfin = new Library.Utility.TempFile())
|
||||
using (var tfout = new Library.Utility.TempFile())
|
||||
{
|
||||
long swaps;
|
||||
|
||||
@@ -167,15 +168,15 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
{
|
||||
swaps = 0L;
|
||||
|
||||
using(var sw = new System.IO.StreamWriter(tfout))
|
||||
using(var sr = new System.IO.StreamReader(tfin))
|
||||
using (var sw = new System.IO.StreamWriter(tfout))
|
||||
using (var sr = new System.IO.StreamReader(tfin))
|
||||
{
|
||||
var c1 = sr.ReadLine();
|
||||
var c2 = sr.ReadLine();
|
||||
|
||||
while (c1 != null || c2 != null)
|
||||
{
|
||||
if (c1 != null && c1.StartsWith("a"))
|
||||
if (c1 != null && c1.StartsWith("a", StringComparison.Ordinal))
|
||||
Console.Write("");
|
||||
var cmp = StringComparer.Ordinal.Compare(c1, c2);
|
||||
|
||||
@@ -198,7 +199,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
File.Copy(tfout, tfin, true);
|
||||
|
||||
} while(swaps > 0);
|
||||
} while (swaps > 0);
|
||||
|
||||
File.Copy(tfout, fileout, true);
|
||||
}
|
||||
@@ -206,16 +207,16 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
private static void MergeFiles(string file1, string file2, string fileout)
|
||||
{
|
||||
using(var tf = new Library.Utility.TempFile())
|
||||
using (var tf = new Library.Utility.TempFile())
|
||||
{
|
||||
using(var sw = new System.IO.StreamWriter(tf))
|
||||
using(var sr1 = new System.IO.StreamReader(file1))
|
||||
using(var sr2 = new System.IO.StreamReader(file2))
|
||||
using (var sw = new System.IO.StreamWriter(tf))
|
||||
using (var sr1 = new System.IO.StreamReader(file1))
|
||||
using (var sr2 = new System.IO.StreamReader(file2))
|
||||
{
|
||||
var c1 = sr1.ReadLine();
|
||||
var c2 = sr2.ReadLine();
|
||||
|
||||
while(c1 != null || c2 != null)
|
||||
while (c1 != null || c2 != null)
|
||||
{
|
||||
var cmp = StringComparer.Ordinal.Compare(c1, c2);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
if (args.Count == 2)
|
||||
{
|
||||
var times = ParseListFiles(folder);
|
||||
foreach(var v in times.Zip(Enumerable.Range(0, times.Length), (a,b) => new KeyValuePair<int, DateTime>(b, a.Key)))
|
||||
foreach (var v in times.Zip(Enumerable.Range(0, times.Length), (a, b) => new KeyValuePair<int, DateTime>(b, a.Key)))
|
||||
Console.WriteLine("{0}: {1}", v.Key, v.Value.ToLocalTime());
|
||||
}
|
||||
else
|
||||
@@ -55,7 +55,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
Library.Main.Volumes.VolumeReaderBase.UpdateOptionsFromManifest(p.CompressionModule, file, new Duplicati.Library.Main.Options(options));
|
||||
|
||||
foreach(var f in EnumerateFilesInDList(file, filter, options))
|
||||
foreach (var f in EnumerateFilesInDList(file, filter, options))
|
||||
Console.WriteLine("{0} ({1})", f.Path, Library.Utility.Utility.FormatSizeString(f.Size));
|
||||
}
|
||||
|
||||
@@ -65,9 +65,10 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
public static IEnumerable<Duplicati.Library.Main.Volumes.IFileEntry> EnumerateFilesInDList(string file, Duplicati.Library.Utility.IFilter filter, Dictionary<string, string> options)
|
||||
{
|
||||
var p = Library.Main.Volumes.VolumeBase.ParseFilename(file);
|
||||
using(var cm = Library.DynamicLoader.CompressionLoader.GetModule(p.CompressionModule, file, options))
|
||||
using(var filesetreader = new Library.Main.Volumes.FilesetVolumeReader(cm, new Duplicati.Library.Main.Options(options)))
|
||||
foreach(var f in filesetreader.Files)
|
||||
using (var fs = new System.IO.FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var cm = Library.DynamicLoader.CompressionLoader.GetModule(p.CompressionModule, fs, Library.Interface.ArchiveMode.Read, options))
|
||||
using (var filesetreader = new Library.Main.Volumes.FilesetVolumeReader(cm, new Duplicati.Library.Main.Options(options)))
|
||||
foreach (var f in filesetreader.Files)
|
||||
{
|
||||
if (f.Type != Duplicati.Library.Main.FilelistEntryType.File)
|
||||
continue;
|
||||
@@ -98,9 +99,9 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
var parsedtime = Library.Utility.Timeparser.ParseTimeInterval(time, DateTime.Now, true);
|
||||
var el = (
|
||||
from n in ParseListFiles(folder)
|
||||
let diff = Math.Abs((n.Key - parsedtime).TotalSeconds)
|
||||
orderby diff
|
||||
select n).First();
|
||||
let diff = Math.Abs((n.Key - parsedtime).TotalSeconds)
|
||||
orderby diff
|
||||
select n).First();
|
||||
|
||||
Console.WriteLine("Selected time {0}", el.Key);
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
if (ex is Duplicati.Library.Interface.UserInformationException)
|
||||
Console.WriteLine(ex.Message);
|
||||
else
|
||||
Console.WriteLine("Program crashed: {0}{1}", Environment.NewLine, ex.ToString());
|
||||
Console.WriteLine("Program crashed: {0}{1}", Environment.NewLine, ex);
|
||||
return 200;
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
cargs.AddRange(
|
||||
from c in fargs
|
||||
where !string.IsNullOrWhiteSpace(c) && !c.StartsWith("#") && !c.StartsWith("!") && !c.StartsWith("REM ", StringComparison.OrdinalIgnoreCase)
|
||||
where !string.IsNullOrWhiteSpace(c) && !c.StartsWith("#", StringComparison.Ordinal) && !c.StartsWith("!", StringComparison.Ordinal) && !c.StartsWith("REM ", StringComparison.OrdinalIgnoreCase)
|
||||
select c
|
||||
);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Duplicati.Library.Main;
|
||||
using Duplicati.Library.Main.Volumes;
|
||||
using Duplicati.Library.Utility;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Duplicati.Library.Interface;
|
||||
|
||||
namespace Duplicati.CommandLine.RecoveryTool
|
||||
{
|
||||
@@ -130,7 +131,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
{
|
||||
Console.WriteLine(" - cannot detect compression type");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!reencrypt && File.Exists(localFileTarget)) || (reencrypt && File.Exists(localFileTarget + "." + localFileSourceEncryption)))
|
||||
{
|
||||
@@ -181,14 +182,15 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
File.Move(localFileSource, localFileSource + ".same");
|
||||
localFileSource = localFileSource + ".same";
|
||||
}
|
||||
|
||||
using (var cmOld = Library.DynamicLoader.CompressionLoader.GetModule(remoteFile.CompressionModule, localFileSource, options))
|
||||
using (var cmNew = Library.DynamicLoader.CompressionLoader.GetModule(target_compr_module, localFileTarget, options))
|
||||
using (var localFileSourceStream = new System.IO.FileStream(localFileSource, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var cmOld = Library.DynamicLoader.CompressionLoader.GetModule(remoteFile.CompressionModule, localFileSourceStream, ArchiveMode.Read, options))
|
||||
using (var localFileTargetStream = new FileStream(localFileTarget, FileMode.Create, FileAccess.Write, FileShare.Delete))
|
||||
using (var cmNew = Library.DynamicLoader.CompressionLoader.GetModule(target_compr_module, localFileTargetStream, ArchiveMode.Write, options))
|
||||
foreach (var cmfile in cmOld.ListFiles(""))
|
||||
{
|
||||
string cmfileNew = cmfile;
|
||||
var cmFileVolume = VolumeBase.ParseFilename(cmfileNew);
|
||||
|
||||
|
||||
if (remoteFile.FileType == RemoteVolumeType.Index && cmFileVolume != null && cmFileVolume.FileType == RemoteVolumeType.Blocks)
|
||||
{
|
||||
// Correct inner filename extension to target compression type
|
||||
@@ -205,7 +207,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
JToken token = JObject.Parse(textJSON);
|
||||
var fileInfoBlocks = new FileInfo(Path.Combine(targetfolder, cmfileNew.Replace("vol/", "")));
|
||||
var filehasher = HashAlgorithmHelper.Create(m_Options.FileHashAlgorithm);
|
||||
|
||||
|
||||
using (var fileStream = fileInfoBlocks.Open(FileMode.Open))
|
||||
{
|
||||
fileStream.Position = 0;
|
||||
@@ -216,7 +218,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
token["volumesize"] = fileInfoBlocks.Length;
|
||||
textJSON = token.ToString();
|
||||
}
|
||||
|
||||
|
||||
using (var sourceStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(textJSON)))
|
||||
using (var cs = cmNew.CreateFile(cmfileNew, Library.Interface.CompressionHint.Compressible, cmOld.GetLastWriteTime(cmfile)))
|
||||
Library.Utility.Utility.CopyStream(sourceStream, cs);
|
||||
@@ -228,12 +230,12 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
Library.Utility.Utility.CopyStream(sourceStream, cs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File.Delete(localFileSource);
|
||||
destinationFileInfo = new FileInfo(localFileTarget);
|
||||
destinationFileInfo.LastWriteTime = originLastWriteTime;
|
||||
}
|
||||
|
||||
|
||||
if (reencrypt && remoteFile.EncryptionModule != null)
|
||||
{
|
||||
Console.Write(" reencrypting ...");
|
||||
@@ -247,7 +249,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
destinationFileInfo = new FileInfo(localFileTarget);
|
||||
destinationFileInfo.LastWriteTime = originLastWriteTime;
|
||||
}
|
||||
|
||||
|
||||
if (reupload)
|
||||
{
|
||||
Console.Write(" reuploading ...");
|
||||
@@ -261,7 +263,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(" error: {0}", ex.ToString());
|
||||
Console.WriteLine(" error: {0}", ex);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
var hashesprblock = blocksize / (blockhasher.HashSize / 8);
|
||||
|
||||
using(var mru = new CompressedFileMRUCache(options))
|
||||
using (var mru = new CompressedFileMRUCache(options))
|
||||
{
|
||||
Console.WriteLine("Building lookup table for file hashes");
|
||||
var lookup = new HashLookupHelper(ixfile, mru, (int)blocksize, blockhasher.HashSize / 8);
|
||||
@@ -111,8 +111,8 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(targetpath))
|
||||
Console.WriteLine("Computing restore path");
|
||||
|
||||
foreach(var f in List.EnumerateFilesInDList(filelist, filter, options))
|
||||
|
||||
foreach (var f in List.EnumerateFilesInDList(filelist, filter, options))
|
||||
{
|
||||
if (largestprefix == null)
|
||||
{
|
||||
@@ -124,7 +124,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
var parts = f.Path.Split(new char[] { Path.DirectorySeparatorChar });
|
||||
|
||||
var ni = 0;
|
||||
for(; ni < Math.Min(parts.Length, largestprefixparts.Length); ni++)
|
||||
for (; ni < Math.Min(parts.Length, largestprefixparts.Length); ni++)
|
||||
if (!Library.Utility.Utility.ClientFilenameStringComparer.Equals(parts[ni], largestprefixparts[ni]))
|
||||
break;
|
||||
|
||||
@@ -155,19 +155,19 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
var i = 0L;
|
||||
var errors = 0L;
|
||||
foreach(var f in List.EnumerateFilesInDList(filelist, filter, options))
|
||||
foreach (var f in List.EnumerateFilesInDList(filelist, filter, options))
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetfile = MapToRestorePath(f.Path, largestprefix, targetpath);
|
||||
if (!Directory.Exists(Path.GetDirectoryName(targetfile)))
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetfile));
|
||||
|
||||
|
||||
Console.Write("{0}: {1} ({2})", i, targetfile, Library.Utility.Utility.FormatSizeString(f.Size));
|
||||
|
||||
using(var tf = new Library.Utility.TempFile())
|
||||
using (var tf = new Library.Utility.TempFile())
|
||||
{
|
||||
using(var sw = File.OpenWrite(tf))
|
||||
using (var sw = File.OpenWrite(tf))
|
||||
{
|
||||
if (f.BlocklistHashes == null)
|
||||
{
|
||||
@@ -176,7 +176,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
else
|
||||
{
|
||||
var blhi = 0L;
|
||||
foreach(var blh in f.BlocklistHashes)
|
||||
foreach (var blh in f.BlocklistHashes)
|
||||
{
|
||||
Console.Write(" {0}", blhi);
|
||||
var blockhashoffset = blhi * hashesprblock * blocksize;
|
||||
@@ -184,16 +184,16 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
try
|
||||
{
|
||||
var bi = 0;
|
||||
foreach(var h in lookup.ReadBlocklistHashes(blh))
|
||||
foreach (var h in lookup.ReadBlocklistHashes(blh))
|
||||
{
|
||||
try
|
||||
{
|
||||
sw.Position = blockhashoffset + (bi * blocksize);
|
||||
lookup.WriteHash(sw, h);
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Failed to read hash: {0}{1}{2}", h, Environment.NewLine, ex.ToString());
|
||||
Console.WriteLine("Failed to read hash: {0}{1}{2}", h, Environment.NewLine, ex);
|
||||
}
|
||||
|
||||
bi++;
|
||||
@@ -201,16 +201,16 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Failed to read Blocklist hash: {0}{1}{2}", blh, Environment.NewLine, ex.ToString());
|
||||
Console.WriteLine("Failed to read Blocklist hash: {0}{1}{2}", blh, Environment.NewLine, ex);
|
||||
}
|
||||
|
||||
blhi++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string fh;
|
||||
using(var fs = File.OpenRead(tf))
|
||||
using (var fs = File.OpenRead(tf))
|
||||
fh = Convert.ToBase64String(filehasher.ComputeHash(fs));
|
||||
|
||||
if (fh == f.Hash)
|
||||
@@ -233,7 +233,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(" error: {0}", ex.ToString());
|
||||
Console.WriteLine(" error: {0}", ex);
|
||||
errors++;
|
||||
}
|
||||
i++;
|
||||
@@ -288,7 +288,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
var hashes = 0L;
|
||||
string prev = null;
|
||||
m_indexfile.Position = 0;
|
||||
foreach(var n in AllFileLines())
|
||||
foreach (var n in AllFileLines())
|
||||
{
|
||||
if (n.Key != prev)
|
||||
{
|
||||
@@ -308,7 +308,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
var prevoff = 0L;
|
||||
var hc = 0L;
|
||||
m_indexfile.Position = 0;
|
||||
foreach(var n in AllFileLines())
|
||||
foreach (var n in AllFileLines())
|
||||
{
|
||||
if (n.Key != prev)
|
||||
{
|
||||
@@ -331,9 +331,9 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
var max = m_indexfile.Read(m_linebuf, 0, m_linebuf.Length);
|
||||
if (max == 0)
|
||||
return null;
|
||||
|
||||
|
||||
var lfi = 0;
|
||||
for(int i = 0; i < max; i++)
|
||||
for (int i = 0; i < max; i++)
|
||||
if (m_linebuf[i] == m_newline[lfi])
|
||||
{
|
||||
if (lfi == m_newline.Length - 1)
|
||||
@@ -352,12 +352,12 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
lfi = 0;
|
||||
}
|
||||
|
||||
throw new Exception(string.Format("Unexpected long line starting at offset {0}, read {1} bytes without a newline", p, m_linebuf.Length));
|
||||
throw new Exception(string.Format("Unexpected long line starting at offset {0}, read {1} bytes without a newline", p, m_linebuf.Length));
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> AllFileLines()
|
||||
{
|
||||
while(true)
|
||||
while (true)
|
||||
{
|
||||
var str = ReadNextLine();
|
||||
if (str == null)
|
||||
@@ -366,7 +366,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
if (str.Length == 0)
|
||||
continue;
|
||||
|
||||
var ix = str.IndexOf(", ");
|
||||
var ix = str.IndexOf(", ", StringComparison.Ordinal);
|
||||
if (ix < 0)
|
||||
Console.WriteLine("Failed to parse line starting at offset {0} in index file, string: {1}", m_indexfile.Position - str.Length - m_newline.Length, str);
|
||||
yield return new KeyValuePair<string, string>(str.Substring(0, ix), str.Substring(ix + 2));
|
||||
@@ -376,7 +376,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
public IEnumerable<string> ReadBlocklistHashes(string hash)
|
||||
{
|
||||
var bytes = ReadHash(hash);
|
||||
for(var i = 0; i < bytes.Length; i += m_hashsize)
|
||||
for (var i = 0; i < bytes.Length; i += m_hashsize)
|
||||
yield return Convert.ToBase64String(bytes, i, m_hashsize);
|
||||
}
|
||||
|
||||
@@ -392,10 +392,10 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
m_indexfile.Position = m_offsets[ix];
|
||||
|
||||
foreach(var v in AllFileLines())
|
||||
foreach (var v in AllFileLines())
|
||||
{
|
||||
if (v.Key == hash)
|
||||
using(var fs = m_cache.ReadBlock(v.Value, hash))
|
||||
using (var fs = m_cache.ReadBlock(v.Value, hash))
|
||||
{
|
||||
var buf = new byte[m_blocksize];
|
||||
var l = fs.Read(buf, 0, buf.Length);
|
||||
@@ -403,8 +403,8 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (StringComparer.Ordinal.Compare(hash, v.Key) < 0)
|
||||
break;
|
||||
}
|
||||
@@ -430,6 +430,7 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
private class CompressedFileMRUCache : IDisposable
|
||||
{
|
||||
private Dictionary<string, Library.Interface.ICompression> m_lookup = new Dictionary<string, Duplicati.Library.Interface.ICompression>();
|
||||
private Dictionary<string, Stream> m_streams = new Dictionary<string, Stream>();
|
||||
private List<string> m_mru = new List<string>();
|
||||
private Dictionary<string, string> m_options;
|
||||
|
||||
@@ -443,12 +444,18 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
public Stream ReadBlock(string filename, string hash)
|
||||
{
|
||||
Library.Interface.ICompression cf;
|
||||
if (!m_lookup.TryGetValue(filename, out cf))
|
||||
Stream stream;
|
||||
if (!m_lookup.TryGetValue(filename, out cf) || !m_streams.TryGetValue(filename, out stream))
|
||||
{
|
||||
cf = Library.DynamicLoader.CompressionLoader.GetModule(Path.GetExtension(filename).Trim('.'), filename, m_options);
|
||||
stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete);
|
||||
cf = Library.DynamicLoader.CompressionLoader.GetModule(Path.GetExtension(filename).Trim('.'), stream, Library.Interface.ArchiveMode.Read, m_options);
|
||||
if (cf == null)
|
||||
{
|
||||
stream.Dispose();
|
||||
throw new Exception(string.Format("Unable to decompress {0}, no such compression module {1}", filename, Path.GetExtension(filename).Trim('.')));
|
||||
}
|
||||
m_lookup[filename] = cf;
|
||||
m_streams[filename] = stream;
|
||||
}
|
||||
|
||||
if (m_mru.Count > 0 && m_mru[0] != filename)
|
||||
@@ -462,6 +469,8 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
var f = m_mru.Last();
|
||||
m_lookup[f].Dispose();
|
||||
m_lookup.Remove(f);
|
||||
m_streams[f].Dispose();
|
||||
m_streams.Remove(f);
|
||||
m_mru.Remove(f);
|
||||
}
|
||||
|
||||
@@ -472,11 +481,16 @@ namespace Duplicati.CommandLine.RecoveryTool
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach(var v in m_lookup.Values)
|
||||
foreach (var v in m_lookup.Values)
|
||||
try { v.Dispose(); }
|
||||
catch { }
|
||||
|
||||
foreach (var v in m_streams.Values)
|
||||
try { v.Dispose(); }
|
||||
catch { }
|
||||
|
||||
m_lookup = null;
|
||||
m_streams = null;
|
||||
m_mru = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,8 @@ Usage: delete <storage-URL> [<options>]
|
||||
|
||||
Marks old data deleted and removes outdated dlist files. A backup is deleted when it is older than <keep-time> or when there are more newer versions than <keep-versions>. Data is considered old, when it is not required from any existing backup anymore.
|
||||
|
||||
--dry-run
|
||||
Performs the operation, but does not write changes to the local database or the remote storage
|
||||
--keep-time=<time>
|
||||
Marks data outdated that is older than <time>.
|
||||
--keep-versions=<int>
|
||||
@@ -269,7 +271,7 @@ Usage: test-filters <source-path> [<options>]
|
||||
|
||||
Usage: affected <storage-URL> <remote-filename> [<remote-filenames>] [<options>]
|
||||
|
||||
Returns a report explaining what backup sets and files are affected by a remote file. You can use this option to see what source files are affected if one or more remote files are damaged or deleted.
|
||||
Returns a report explaining what backup sets and files are affected by a remote file. You can use this option to see what source files are affected if one or more remote files are damaged or deleted. Notes that this command requires a local database to be present, if the database is not found automatically, you can set --dbpath to point to the database to use.
|
||||
|
||||
# =============================================================================
|
||||
> duplicati.commandline.exe help vacuum
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile />
|
||||
<UseMSBuildEngine>false</UseMSBuildEngine>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Duplicati.GUI.TrayIcon
|
||||
public HttpServerConnection(Uri server, string password, bool saltedpassword, bool dbPasswordSourceDatabase, Dictionary<string, string> options)
|
||||
{
|
||||
m_baseUri = server.ToString();
|
||||
if (!m_baseUri.EndsWith("/"))
|
||||
if (!m_baseUri.EndsWith("/", StringComparison.Ordinal))
|
||||
m_baseUri += "/";
|
||||
|
||||
m_apiUri = m_baseUri + "api/v1";
|
||||
@@ -225,7 +225,7 @@ namespace Duplicati.GUI.TrayIcon
|
||||
var req = (System.Net.HttpWebRequest) System.Net.WebRequest.Create(m_baseUri + LOGIN_SCRIPT);
|
||||
req.Method = "POST";
|
||||
req.UserAgent = "Duplicati TrayIcon Monitor, v" +
|
||||
System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
req.Headers.Add(TRAYICONPASSWORDSOURCE_HEADER, m_TrayIconHeaderValue);
|
||||
req.ContentType = "application/x-www-form-urlencoded";
|
||||
|
||||
@@ -252,7 +252,7 @@ namespace Duplicati.GUI.TrayIcon
|
||||
(System.Net.HttpWebRequest) System.Net.WebRequest.Create(m_baseUri + LOGIN_SCRIPT);
|
||||
req.Method = "POST";
|
||||
req.UserAgent = "Duplicati TrayIcon Monitor, v" +
|
||||
System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
req.Headers.Add(TRAYICONPASSWORDSOURCE_HEADER, m_TrayIconHeaderValue);
|
||||
req.ContentType = "application/x-www-form-urlencoded";
|
||||
if (req.CookieContainer == null)
|
||||
@@ -313,7 +313,7 @@ namespace Duplicati.GUI.TrayIcon
|
||||
(System.Net.HttpWebRequest) System.Net.WebRequest.Create(m_baseUri + STATUS_WINDOW);
|
||||
req.Method = "GET";
|
||||
req.UserAgent = "Duplicati TrayIcon Monitor, v" +
|
||||
System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
req.Headers.Add(TRAYICONPASSWORDSOURCE_HEADER, m_TrayIconHeaderValue);
|
||||
if (req.CookieContainer == null)
|
||||
req.CookieContainer = new System.Net.CookieContainer();
|
||||
@@ -404,7 +404,7 @@ namespace Duplicati.GUI.TrayIcon
|
||||
if (m_xsrftoken != null)
|
||||
req.Headers.Add(XSRF_HEADER, m_xsrftoken);
|
||||
req.UserAgent = "Duplicati TrayIcon Monitor, v" +
|
||||
System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
req.Headers.Add(TRAYICONPASSWORDSOURCE_HEADER, m_TrayIconHeaderValue);
|
||||
if (req.CookieContainer == null)
|
||||
req.CookieContainer = new System.Net.CookieContainer();
|
||||
|
||||
@@ -250,15 +250,15 @@ namespace Duplicati.GUI.TrayIcon
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
System.Diagnostics.Trace.WriteLine("Request error: " + ex.ToString());
|
||||
Console.WriteLine("Request error: " + ex.ToString());
|
||||
System.Diagnostics.Trace.WriteLine("Request error: " + ex);
|
||||
Console.WriteLine("Request error: " + ex);
|
||||
|
||||
reSpawn++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Trace.WriteLine("Unexpected error: " + ex.ToString());
|
||||
Console.WriteLine("Unexpected error: " + ex.ToString());
|
||||
System.Diagnostics.Trace.WriteLine("Unexpected error: " + ex);
|
||||
Console.WriteLine("Unexpected error: " + ex);
|
||||
return;
|
||||
}
|
||||
} while (reSpawn < 3);
|
||||
|
||||
@@ -177,8 +177,8 @@ namespace Duplicati.GUI.TrayIcon
|
||||
m_toRumps = ch.AsWriteOnly();
|
||||
|
||||
WriteChannel(m_rumpsProcess.StandardInput, ch.AsReadOnly());
|
||||
ReadChannel(m_rumpsProcess.StandardOutput);
|
||||
ReadChannel(m_rumpsProcess.StandardError);
|
||||
var standardOutputTask = ReadChannel(m_rumpsProcess.StandardOutput);
|
||||
var standardErrorTask = ReadChannel(m_rumpsProcess.StandardError);
|
||||
|
||||
m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "background"}));
|
||||
//m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "setappicon", Image = GetIcon(m_lastIcon)}));
|
||||
@@ -243,7 +243,7 @@ namespace Duplicati.GUI.TrayIcon
|
||||
menu.Callback();
|
||||
}
|
||||
}
|
||||
else if (!line.StartsWith("info") && !string.IsNullOrWhiteSpace(line))
|
||||
else if (!line.StartsWith("info", StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
Console.WriteLine("Unexpected message: {0}", line);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Duplicati.Library.Interface;
|
||||
|
||||
namespace Duplicati.Library.AutoUpdater
|
||||
{
|
||||
@@ -47,8 +48,8 @@ namespace Duplicati.Library.AutoUpdater
|
||||
|
||||
public static readonly string INSTALLDIR;
|
||||
|
||||
private static readonly string INSTALLED_BASE_DIR =
|
||||
string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(string.Format(BASEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME)))
|
||||
private static readonly string INSTALLED_BASE_DIR =
|
||||
string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(string.Format(BASEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME)))
|
||||
? System.IO.Path.GetDirectoryName(Duplicati.Library.Utility.Utility.getEntryAssembly().Location)
|
||||
: Library.Utility.Utility.ExpandEnvironmentVariables(System.Environment.GetEnvironmentVariable(string.Format(BASEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME)));
|
||||
|
||||
@@ -87,7 +88,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
/// <summary>
|
||||
/// Gets the last version found from an update
|
||||
/// </summary>
|
||||
public static UpdateInfo LastUpdateCheckVersion { get; private set; }
|
||||
public static UpdateInfo LastUpdateCheckVersion { get; private set; }
|
||||
|
||||
static UpdaterManager()
|
||||
{
|
||||
@@ -118,7 +119,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
var programfiles = System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
|
||||
// The user can override updates by having a local updates folder
|
||||
var overrides = new List<string>(new string [] {
|
||||
var overrides = new List<string>(new string[] {
|
||||
System.IO.Path.Combine(InstalledBaseDir, "updates"),
|
||||
});
|
||||
|
||||
@@ -131,7 +132,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
{
|
||||
if (Library.Utility.Utility.IsClientOSX)
|
||||
overrides.Add(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support", APPNAME, "updates"));
|
||||
|
||||
|
||||
overrides.Add(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), APPNAME, "updates"));
|
||||
}
|
||||
|
||||
@@ -148,7 +149,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
var attempts = new List<string>();
|
||||
|
||||
// We do not want to install anything in the basedir, if the application is installed in "ProgramFiles"
|
||||
if (!string.IsNullOrWhiteSpace(programfiles) && !InstalledBaseDir.StartsWith(Library.Utility.Utility.AppendDirSeparator(programfiles)))
|
||||
if (!string.IsNullOrWhiteSpace(programfiles) && !InstalledBaseDir.StartsWith(Library.Utility.Utility.AppendDirSeparator(programfiles), StringComparison.Ordinal))
|
||||
attempts.Add(System.IO.Path.Combine(InstalledBaseDir, "updates"));
|
||||
|
||||
if (Library.Utility.Utility.IsClientOSX)
|
||||
@@ -173,7 +174,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
installdir = p;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(installdir))
|
||||
foreach (var p in attempts)
|
||||
if (!string.IsNullOrWhiteSpace(p) && TestDirectoryIsWriteable(p))
|
||||
@@ -181,7 +182,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
installdir = p;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
INSTALLDIR = installdir;
|
||||
}
|
||||
else
|
||||
@@ -222,7 +223,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
Displayname = string.IsNullOrWhiteSpace(Duplicati.License.VersionNumbers.TAG) ? "Current" : Duplicati.License.VersionNumbers.TAG,
|
||||
Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(),
|
||||
ReleaseTime = new DateTime(0),
|
||||
ReleaseType =
|
||||
ReleaseType =
|
||||
#if DEBUG
|
||||
"Debug"
|
||||
#else
|
||||
@@ -257,13 +258,13 @@ namespace Duplicati.Library.AutoUpdater
|
||||
{
|
||||
var selfversion = TryParseVersion(SelfVersion.Version);
|
||||
|
||||
m_hasUpdateInstalled =
|
||||
m_hasUpdateInstalled =
|
||||
(from n in FindInstalledVersions()
|
||||
let nversion = TryParseVersion(n.Value.Version)
|
||||
let newerVersion = selfversion < nversion
|
||||
where newerVersion && VerifyUnpackedFolder(n.Key, n.Value)
|
||||
orderby nversion descending
|
||||
select n)
|
||||
let nversion = TryParseVersion(n.Value.Version)
|
||||
let newerVersion = selfversion < nversion
|
||||
where newerVersion && VerifyUnpackedFolder(n.Key, n.Value)
|
||||
orderby nversion descending
|
||||
select n)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
@@ -278,11 +279,11 @@ namespace Duplicati.Library.AutoUpdater
|
||||
|
||||
if (!System.IO.Directory.Exists(probe))
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(probe);
|
||||
if (probe != path)
|
||||
System.IO.Directory.Delete(probe);
|
||||
System.IO.Directory.Delete(probe);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -296,20 +297,20 @@ namespace Duplicati.Library.AutoUpdater
|
||||
public static string InstallID
|
||||
{
|
||||
get
|
||||
{
|
||||
try { return System.IO.File.ReadAllText(System.IO.Path.Combine(INSTALLDIR, INSTALL_FILE)).Replace('\r', '\n').Split(new char[] { '\n' }).FirstOrDefault().Trim() ?? ""; }
|
||||
{
|
||||
try { return System.IO.File.ReadAllText(System.IO.Path.Combine(INSTALLDIR, INSTALL_FILE)).Replace('\r', '\n').Split(new char[] { '\n' }).FirstOrDefault().Trim() ?? ""; }
|
||||
catch { }
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static UpdateInfo CheckForUpdate(ReleaseType channel = ReleaseType.Unknown)
|
||||
{
|
||||
if (channel == ReleaseType.Unknown)
|
||||
channel = AutoUpdateSettings.DefaultUpdateChannel;
|
||||
|
||||
foreach(var rawurl in MANIFEST_URLS)
|
||||
foreach (var rawurl in MANIFEST_URLS)
|
||||
{
|
||||
var url = rawurl;
|
||||
|
||||
@@ -332,17 +333,17 @@ namespace Duplicati.Library.AutoUpdater
|
||||
|
||||
try
|
||||
{
|
||||
using(var tmpfile = new Library.Utility.TempFile())
|
||||
using (var tmpfile = new Library.Utility.TempFile())
|
||||
{
|
||||
System.Net.WebClient wc = new System.Net.WebClient();
|
||||
wc.Headers.Add(System.Net.HttpRequestHeader.UserAgent, string.Format("{0} v{1}{2}", APPNAME, SelfVersion.Version, string.IsNullOrWhiteSpace(InstallID) ? "" : " -" + InstallID));
|
||||
wc.Headers.Add("X-Install-ID", InstallID);
|
||||
wc.DownloadFile(url, tmpfile);
|
||||
|
||||
using(var fs = System.IO.File.OpenRead(tmpfile))
|
||||
using(var ss = new SignatureReadingStream(fs, SIGN_KEY))
|
||||
using(var tr = new System.IO.StreamReader(ss))
|
||||
using(var jr = new Newtonsoft.Json.JsonTextReader(tr))
|
||||
using (var fs = System.IO.File.OpenRead(tmpfile))
|
||||
using (var ss = new SignatureReadingStream(fs, SIGN_KEY))
|
||||
using (var tr = new System.IO.StreamReader(ss))
|
||||
using (var jr = new Newtonsoft.Json.JsonTextReader(tr))
|
||||
{
|
||||
var update = new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr);
|
||||
|
||||
@@ -384,10 +385,10 @@ namespace Duplicati.Library.AutoUpdater
|
||||
{
|
||||
try
|
||||
{
|
||||
using(var fs = System.IO.File.OpenRead(manifest))
|
||||
using(var ss = new SignatureReadingStream(fs, SIGN_KEY))
|
||||
using(var tr = new System.IO.StreamReader(ss))
|
||||
using(var jr = new Newtonsoft.Json.JsonTextReader(tr))
|
||||
using (var fs = System.IO.File.OpenRead(manifest))
|
||||
using (var ss = new SignatureReadingStream(fs, SIGN_KEY))
|
||||
using (var tr = new System.IO.StreamReader(ss))
|
||||
using (var jr = new Newtonsoft.Json.JsonTextReader(tr))
|
||||
return new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -404,7 +405,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
{
|
||||
var res = new List<KeyValuePair<string, UpdateInfo>>();
|
||||
if (INSTALLDIR != null)
|
||||
foreach(var folder in System.IO.Directory.GetDirectories(INSTALLDIR))
|
||||
foreach (var folder in System.IO.Directory.GetDirectories(INSTALLDIR))
|
||||
{
|
||||
var r = ReadInstalledManifest(folder);
|
||||
if (r != null)
|
||||
@@ -429,20 +430,20 @@ namespace Duplicati.Library.AutoUpdater
|
||||
var packagepath = new Library.Utility.Uri(updates[0]).Path;
|
||||
var packagename = packagepath.Split('/').Last();
|
||||
|
||||
foreach(var alt_url in AutoUpdateSettings.URLs.Reverse())
|
||||
foreach (var alt_url in AutoUpdateSettings.URLs.Reverse())
|
||||
{
|
||||
var alt_uri = new Library.Utility.Uri(alt_url);
|
||||
var path_components = alt_uri.Path.Split('/');
|
||||
var path = string.Join("/", path_components.Take(path_components.Count() - 1).Union(new string[] { packagename}));
|
||||
var path = string.Join("/", path_components.Take(path_components.Count() - 1).Union(new string[] { packagename }));
|
||||
|
||||
var new_path = alt_uri.SetPath(path);
|
||||
updates.Insert(0, new_path.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
using(var tempfile = new Library.Utility.TempFile())
|
||||
using (var tempfile = new Library.Utility.TempFileStream())
|
||||
{
|
||||
foreach(var url in updates)
|
||||
foreach (var url in updates)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -455,36 +456,35 @@ namespace Duplicati.Library.AutoUpdater
|
||||
wreq.Headers.Add("X-Install-ID", InstallID);
|
||||
|
||||
var areq = new Duplicati.Library.Utility.AsyncHttpRequest(wreq);
|
||||
using(var resp = areq.GetResponse())
|
||||
using(var rss = areq.GetResponseStream())
|
||||
using(var pgs = new Duplicati.Library.Utility.ProgressReportingStream(rss, version.CompressedSize, cb))
|
||||
using(var fs = System.IO.File.Open(tempfile, System.IO.FileMode.Create))
|
||||
Duplicati.Library.Utility.Utility.CopyStream(pgs, fs);
|
||||
using (var resp = areq.GetResponse())
|
||||
using (var rss = areq.GetResponseStream())
|
||||
using (var pgs = new Duplicati.Library.Utility.ProgressReportingStream(rss, version.CompressedSize, cb))
|
||||
{
|
||||
Duplicati.Library.Utility.Utility.CopyStream(pgs, tempfile);
|
||||
}
|
||||
|
||||
var sha256 = System.Security.Cryptography.SHA256.Create();
|
||||
var md5 = System.Security.Cryptography.MD5.Create();
|
||||
var md5 = System.Security.Cryptography.MD5.Create();
|
||||
|
||||
using(var s = System.IO.File.OpenRead(tempfile))
|
||||
{
|
||||
if (s.Length != version.CompressedSize)
|
||||
throw new Exception(string.Format("Invalid file size {0}, expected {1} for {2}", s.Length, version.CompressedSize, url));
|
||||
|
||||
var sha256hash = Convert.ToBase64String(sha256.ComputeHash(s));
|
||||
if (sha256hash != version.SHA256)
|
||||
throw new Exception(string.Format("Damaged or corrupted file, sha256 mismatch for {0}", url));
|
||||
}
|
||||
if (tempfile.Length != version.CompressedSize)
|
||||
throw new Exception(string.Format("Invalid file size {0}, expected {1} for {2}", tempfile.Length, version.CompressedSize, url));
|
||||
|
||||
using(var s = System.IO.File.OpenRead(tempfile))
|
||||
tempfile.Position = 0;
|
||||
var sha256hash = Convert.ToBase64String(sha256.ComputeHash(tempfile));
|
||||
if (sha256hash != version.SHA256)
|
||||
throw new Exception(string.Format("Damaged or corrupted file, sha256 mismatch for {0}", url));
|
||||
|
||||
|
||||
tempfile.Position = 0;
|
||||
var md5hash = Convert.ToBase64String(md5.ComputeHash(tempfile));
|
||||
if (md5hash != version.MD5)
|
||||
throw new Exception(string.Format("Damaged or corrupted file, md5 mismatch for {0}", url));
|
||||
|
||||
tempfile.Position = 0;
|
||||
using (var tempfolder = new Duplicati.Library.Utility.TempFolder())
|
||||
using (ICompression zip = new Duplicati.Library.Compression.FileArchiveZip(tempfile, ArchiveMode.Read, new Dictionary<string, string>()))
|
||||
{
|
||||
var md5hash = Convert.ToBase64String(md5.ComputeHash(s));
|
||||
if (md5hash != version.MD5)
|
||||
throw new Exception(string.Format("Damaged or corrupted file, md5 mismatch for {0}", url));
|
||||
}
|
||||
|
||||
using(var tempfolder = new Duplicati.Library.Utility.TempFolder())
|
||||
using(var zip = new Duplicati.Library.Compression.FileArchiveZip(tempfile, new Dictionary<string, string>()))
|
||||
{
|
||||
foreach(var file in zip.ListFilesWithSize(""))
|
||||
foreach (var file in zip.ListFilesWithSize(""))
|
||||
{
|
||||
if (System.IO.Path.IsPathRooted(file.Key) || file.Key.Trim().StartsWith("..", StringComparison.OrdinalIgnoreCase))
|
||||
throw new Exception(string.Format("Out-of-place file path detected: {0}", file.Key));
|
||||
@@ -494,8 +494,8 @@ namespace Duplicati.Library.AutoUpdater
|
||||
if (!System.IO.Directory.Exists(targetfolder))
|
||||
System.IO.Directory.CreateDirectory(targetfolder);
|
||||
|
||||
using(var zs = zip.OpenRead(file.Key))
|
||||
using(var fs = System.IO.File.Create(targetpath))
|
||||
using (var zs = zip.OpenRead(file.Key))
|
||||
using (var fs = System.IO.File.Create(targetpath))
|
||||
zs.CopyTo(fs);
|
||||
}
|
||||
|
||||
@@ -505,7 +505,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
var targetfolder = System.IO.Path.Combine(INSTALLDIR, versionstring);
|
||||
if (System.IO.Directory.Exists(targetfolder))
|
||||
System.IO.Directory.Delete(targetfolder, true);
|
||||
|
||||
|
||||
System.IO.Directory.CreateDirectory(targetfolder);
|
||||
|
||||
var tempfolderpath = Duplicati.Library.Utility.Utility.AppendDirSeparator(tempfolder);
|
||||
@@ -514,14 +514,14 @@ namespace Duplicati.Library.AutoUpdater
|
||||
// Would be nice, but does not work :(
|
||||
//System.IO.Directory.Move(tempfolder, targetfolder);
|
||||
|
||||
foreach(var e in Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(tempfolder))
|
||||
foreach (var e in Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(tempfolder))
|
||||
{
|
||||
var relpath = e.Substring(tempfolderlength);
|
||||
if (string.IsNullOrWhiteSpace(relpath))
|
||||
continue;
|
||||
|
||||
var fullpath = System.IO.Path.Combine(targetfolder, relpath);
|
||||
if (relpath.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()))
|
||||
if (relpath.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
|
||||
System.IO.Directory.CreateDirectory(fullpath);
|
||||
else
|
||||
System.IO.File.Copy(e, fullpath);
|
||||
@@ -530,16 +530,16 @@ namespace Duplicati.Library.AutoUpdater
|
||||
// Verification will kick in when we list the installed updates
|
||||
//VerifyUnpackedFolder(targetfolder, version);
|
||||
System.IO.File.WriteAllText(System.IO.Path.Combine(INSTALLDIR, CURRENT_FILE), versionstring);
|
||||
|
||||
|
||||
m_hasUpdateInstalled = null;
|
||||
|
||||
var obsolete = (from n in FindInstalledVersions()
|
||||
where n.Value.Version != version.Version && n.Value.Version != SelfVersion.Version
|
||||
let x = TryParseVersion(n.Value.Version)
|
||||
orderby x descending
|
||||
select n).Skip(1).ToArray();
|
||||
where n.Value.Version != version.Version && n.Value.Version != SelfVersion.Version
|
||||
let x = TryParseVersion(n.Value.Version)
|
||||
orderby x descending
|
||||
select n).Skip(1).ToArray();
|
||||
|
||||
foreach(var f in obsolete)
|
||||
foreach (var f in obsolete)
|
||||
try { System.IO.Directory.Delete(f.Key, true); }
|
||||
catch { }
|
||||
|
||||
@@ -551,7 +551,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (OnError != null)
|
||||
OnError(ex);
|
||||
@@ -572,11 +572,11 @@ namespace Duplicati.Library.AutoUpdater
|
||||
var sha256 = System.Security.Cryptography.SHA256.Create();
|
||||
var md5 = System.Security.Cryptography.MD5.Create();
|
||||
|
||||
using(var fs = System.IO.File.OpenRead(System.IO.Path.Combine(folder, UPDATE_MANIFEST_FILENAME)))
|
||||
using (var fs = System.IO.File.OpenRead(System.IO.Path.Combine(folder, UPDATE_MANIFEST_FILENAME)))
|
||||
{
|
||||
using(var ss = new SignatureReadingStream(fs, SIGN_KEY))
|
||||
using(var tr = new System.IO.StreamReader(ss))
|
||||
using(var jr = new Newtonsoft.Json.JsonTextReader(tr))
|
||||
using (var ss = new SignatureReadingStream(fs, SIGN_KEY))
|
||||
using (var tr = new System.IO.StreamReader(ss))
|
||||
using (var jr = new Newtonsoft.Json.JsonTextReader(tr))
|
||||
update = new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr);
|
||||
|
||||
sha256.Initialize();
|
||||
@@ -607,7 +607,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
folder = Library.Utility.Utility.AppendDirSeparator(folder);
|
||||
var baselen = folder.Length;
|
||||
|
||||
foreach(var file in Library.Utility.Utility.EnumerateFileSystemEntries(folder))
|
||||
foreach (var file in Library.Utility.Utility.EnumerateFileSystemEntries(folder))
|
||||
{
|
||||
var relpath = file.Substring(baselen);
|
||||
if (string.IsNullOrWhiteSpace(relpath))
|
||||
@@ -620,7 +620,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
if (!paths.TryGetValue(relpath, out fe))
|
||||
{
|
||||
var ignore = false;
|
||||
foreach(var c in ignores)
|
||||
foreach (var c in ignores)
|
||||
if (ignore = relpath.StartsWith(c, Library.Utility.Utility.ClientFilenameStringComparision))
|
||||
break;
|
||||
|
||||
@@ -638,7 +638,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
sha256.Initialize();
|
||||
md5.Initialize();
|
||||
|
||||
using(var fs = System.IO.File.OpenRead(file))
|
||||
using (var fs = System.IO.File.OpenRead(file))
|
||||
{
|
||||
if (Convert.ToBase64String(sha256.ComputeHash(fs)) != fe.SHA256)
|
||||
throw new Exception(string.Format("Invalid sha256 hash for file: {0}", file));
|
||||
@@ -690,11 +690,11 @@ namespace Duplicati.Library.AutoUpdater
|
||||
|
||||
var manifestpath = manifest ?? System.IO.Path.Combine(inputfolder, UPDATE_MANIFEST_FILENAME);
|
||||
|
||||
using(var s = System.IO.File.OpenRead(manifestpath))
|
||||
using(var sr = new System.IO.StreamReader(s))
|
||||
using(var jr = new Newtonsoft.Json.JsonTextReader(sr))
|
||||
using (var s = System.IO.File.OpenRead(manifestpath))
|
||||
using (var sr = new System.IO.StreamReader(s))
|
||||
using (var jr = new Newtonsoft.Json.JsonTextReader(sr))
|
||||
remoteManifest = new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr);
|
||||
|
||||
|
||||
if (remoteManifest.Files == null)
|
||||
remoteManifest.Files = new FileEntry[0];
|
||||
|
||||
@@ -702,8 +702,8 @@ namespace Duplicati.Library.AutoUpdater
|
||||
remoteManifest.ReleaseTime = DateTime.UtcNow;
|
||||
|
||||
var ignoreFiles = (from n in remoteManifest.Files
|
||||
where n.Ignore
|
||||
select n).ToArray();
|
||||
where n.Ignore
|
||||
select n).ToArray();
|
||||
|
||||
var ignoreMap = ignoreFiles.ToDictionary(k => k.Path, k => "", Duplicati.Library.Utility.Utility.ClientFilenameStringComparer);
|
||||
|
||||
@@ -724,60 +724,73 @@ namespace Duplicati.Library.AutoUpdater
|
||||
var md5 = System.Security.Cryptography.MD5.Create();
|
||||
var sha256 = System.Security.Cryptography.SHA256.Create();
|
||||
|
||||
Func<string, string> computeMD5 = (path) =>
|
||||
Func<Stream, string> computeStreamMD5 = (stream) =>
|
||||
{
|
||||
md5.Initialize();
|
||||
using(var fs = System.IO.File.OpenRead(path))
|
||||
return Convert.ToBase64String(md5.ComputeHash(fs));
|
||||
return Convert.ToBase64String(md5.ComputeHash(stream));
|
||||
};
|
||||
|
||||
Func<Stream, string> computeStreamSHA256 = (stream) =>
|
||||
{
|
||||
sha256.Initialize();
|
||||
return Convert.ToBase64String(sha256.ComputeHash(stream));
|
||||
};
|
||||
|
||||
Func<string, string> computeMD5 = (path) =>
|
||||
{
|
||||
using (Stream fs = System.IO.File.OpenRead(path))
|
||||
return computeStreamMD5(fs);
|
||||
};
|
||||
|
||||
Func<string, string> computeSHA256 = (path) =>
|
||||
{
|
||||
sha256.Initialize();
|
||||
using(var fs = System.IO.File.OpenRead(path))
|
||||
return Convert.ToBase64String(sha256.ComputeHash(fs));
|
||||
using (Stream fs = System.IO.File.OpenRead(path))
|
||||
return computeStreamSHA256(fs);
|
||||
};
|
||||
|
||||
// Build a zip
|
||||
using (var archive_temp = new Duplicati.Library.Utility.TempFile())
|
||||
using (var archive_temp_file = new Duplicati.Library.Utility.TempFile())
|
||||
{
|
||||
using (var zipfile = new Duplicati.Library.Compression.FileArchiveZip(archive_temp, new Dictionary<string, string>()))
|
||||
using (var archive_temp = new Duplicati.Library.Utility.TempFileStream(archive_temp_file))
|
||||
{
|
||||
Func<string, string, bool> addToArchive = (path, relpath) =>
|
||||
using (ICompression zipfile = new Duplicati.Library.Compression.FileArchiveZip(archive_temp, ArchiveMode.Write, new Dictionary<string, string>()))
|
||||
{
|
||||
if (ignoreMap.ContainsKey(relpath))
|
||||
return false;
|
||||
|
||||
if (path.EndsWith(dirsep))
|
||||
return true;
|
||||
|
||||
using (var source = System.IO.File.OpenRead(path))
|
||||
using (var target = zipfile.CreateFile(relpath,
|
||||
Duplicati.Library.Interface.CompressionHint.Compressible,
|
||||
System.IO.File.GetLastAccessTimeUtc(path)))
|
||||
Func<string, string, bool> addToArchive = (path, relpath) =>
|
||||
{
|
||||
source.CopyTo(target);
|
||||
remoteManifest.UncompressedSize += source.Length;
|
||||
}
|
||||
if (ignoreMap.ContainsKey(relpath))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Build the update manifest
|
||||
localManifest.Files =
|
||||
(from fse in Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(inputfolder)
|
||||
let relpath = fse.Substring(baselen)
|
||||
where addToArchive(fse, relpath)
|
||||
select new FileEntry() {
|
||||
Path = relpath,
|
||||
LastWriteTime = System.IO.File.GetLastAccessTimeUtc(fse),
|
||||
MD5 = fse.EndsWith(dirsep) ? null : computeMD5(fse),
|
||||
SHA256 = fse.EndsWith(dirsep) ? null : computeSHA256(fse)
|
||||
})
|
||||
.Union(ignoreFiles).ToArray();
|
||||
if (path.EndsWith(dirsep, StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
using (var source = System.IO.File.OpenRead(path))
|
||||
using (var target = zipfile.CreateFile(relpath,
|
||||
Duplicati.Library.Interface.CompressionHint.Compressible,
|
||||
System.IO.File.GetLastAccessTimeUtc(path)))
|
||||
{
|
||||
source.CopyTo(target);
|
||||
remoteManifest.UncompressedSize += source.Length;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Build the update manifest
|
||||
localManifest.Files =
|
||||
(from fse in Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(inputfolder)
|
||||
let relpath = fse.Substring(baselen)
|
||||
where addToArchive(fse, relpath)
|
||||
select new FileEntry()
|
||||
{
|
||||
Path = relpath,
|
||||
LastWriteTime = System.IO.File.GetLastAccessTimeUtc(fse),
|
||||
MD5 = fse.EndsWith(dirsep, StringComparison.Ordinal) ? null : computeMD5(fse),
|
||||
SHA256 = fse.EndsWith(dirsep, StringComparison.Ordinal) ? null : computeSHA256(fse)
|
||||
})
|
||||
.Union(ignoreFiles).ToArray();
|
||||
|
||||
// Write a signed manifest with the files
|
||||
|
||||
// Write a signed manifest with the files
|
||||
|
||||
using (var ms = new System.IO.MemoryStream())
|
||||
using (var sw = new System.IO.StreamWriter(ms))
|
||||
{
|
||||
@@ -788,26 +801,26 @@ namespace Duplicati.Library.AutoUpdater
|
||||
{
|
||||
SignatureReadingStream.CreateSignedStream(ms, ms2, key);
|
||||
ms2.Position = 0;
|
||||
using (var sigfile = zipfile.CreateFile(UPDATE_MANIFEST_FILENAME,
|
||||
using (var sigfile = zipfile.CreateFile(UPDATE_MANIFEST_FILENAME,
|
||||
Duplicati.Library.Interface.CompressionHint.Compressible,
|
||||
DateTime.UtcNow))
|
||||
ms2.CopyTo(sigfile);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
archive_temp.Position = 0;
|
||||
remoteManifest.CompressedSize = archive_temp.Length;
|
||||
remoteManifest.MD5 = computeStreamMD5(archive_temp);
|
||||
remoteManifest.SHA256 = computeStreamSHA256(archive_temp);
|
||||
}
|
||||
|
||||
remoteManifest.CompressedSize = new System.IO.FileInfo(archive_temp).Length;
|
||||
remoteManifest.MD5 = computeMD5(archive_temp);
|
||||
remoteManifest.SHA256 = computeSHA256(archive_temp);
|
||||
|
||||
System.IO.File.Move(archive_temp, System.IO.Path.Combine(outputfolder, "package.zip"));
|
||||
|
||||
System.IO.File.Move(archive_temp_file, System.IO.Path.Combine(outputfolder, "package.zip"));
|
||||
}
|
||||
|
||||
// Write a signed manifest for upload
|
||||
|
||||
using(var tf = new Duplicati.Library.Utility.TempFile())
|
||||
using (var tf = new Duplicati.Library.Utility.TempFile())
|
||||
{
|
||||
using (var ms = new System.IO.MemoryStream())
|
||||
using (var sw = new System.IO.StreamWriter(ms))
|
||||
@@ -984,7 +997,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Crash! {0}{1}", Environment.NewLine, tex.ToString());
|
||||
Console.WriteLine("Crash! {0}{1}", Environment.NewLine, tex);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -997,7 +1010,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
string.Format("{0}-crashlog.txt", AutoUpdateSettings.AppName)
|
||||
);
|
||||
|
||||
System.IO.File.WriteAllText(report_file, tex.ToString());
|
||||
System.IO.File.WriteAllText(report_file, tex.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -1070,9 +1083,8 @@ namespace Duplicati.Library.AutoUpdater
|
||||
return r;
|
||||
}
|
||||
|
||||
var args = Environment.CommandLine;
|
||||
var app = Environment.GetCommandLineArgs().First();
|
||||
args = args.Substring(app.Length);
|
||||
var args = Library.Utility.Utility.WrapAsCommandLine(Environment.GetCommandLineArgs().Skip(1), false);
|
||||
|
||||
if (!Path.IsPathRooted(app))
|
||||
app = Path.Combine(InstalledBaseDir, app);
|
||||
@@ -1091,7 +1103,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
ErrorDialog = false,
|
||||
ErrorDialog = false,
|
||||
};
|
||||
pi.EnvironmentVariables.Clear();
|
||||
|
||||
@@ -1201,7 +1213,7 @@ namespace Duplicati.Library.AutoUpdater
|
||||
|
||||
try { AppDomain.Unload(domain); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
{
|
||||
Console.WriteLine("Appdomain unload error: {0}", ex);
|
||||
}
|
||||
|
||||
@@ -1216,13 +1228,12 @@ namespace Duplicati.Library.AutoUpdater
|
||||
|
||||
try
|
||||
{
|
||||
var args = Environment.CommandLine;
|
||||
var app = Environment.GetCommandLineArgs().First();
|
||||
args = args.Substring(app.Length);
|
||||
var args = Library.Utility.Utility.WrapAsCommandLine(Environment.GetCommandLineArgs().Skip(1), false);
|
||||
|
||||
if (!System.IO.Path.IsPathRooted(app))
|
||||
app = System.IO.Path.Combine(InstalledBaseDir, app);
|
||||
|
||||
|
||||
|
||||
// Re-launch but give the OS a little time to fully unload all open handles, etc.
|
||||
var si = new System.Diagnostics.ProcessStartInfo(app, args);
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
_userInfo.Domain = "";
|
||||
|
||||
_url = u.SetScheme("ftp").SetQuery(null).SetCredentials(null, null).ToString();
|
||||
if (!_url.EndsWith("/"))
|
||||
if (!_url.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
_url += "/";
|
||||
}
|
||||
@@ -215,7 +215,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
|
||||
// Get the remote path
|
||||
var url = new Uri(this._url);
|
||||
remotePath = "/" + (url.AbsolutePath.EndsWith("/") ? url.AbsolutePath.Substring(0, url.AbsolutePath.Length - 1) : url.AbsolutePath);
|
||||
remotePath = "/" + (url.AbsolutePath.EndsWith("/", StringComparison.Ordinal) ? url.AbsolutePath.Substring(0, url.AbsolutePath.Length - 1) : url.AbsolutePath);
|
||||
|
||||
if (!string.IsNullOrEmpty(filename))
|
||||
{
|
||||
@@ -354,7 +354,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
|
||||
foreach (var fileEntry in fileEntries)
|
||||
{
|
||||
if (fileEntry.Name.Equals(remotename) || fileEntry.Name.EndsWith("/" + remotename) || fileEntry.Name.EndsWith("\\" + remotename))
|
||||
if (fileEntry.Name.Equals(remotename) || fileEntry.Name.EndsWith("/" + remotename, StringComparison.Ordinal) || fileEntry.Name.EndsWith("\\" + remotename, StringComparison.Ordinal))
|
||||
{
|
||||
if (fileEntry.Size < 0 || streamLen < 0 || fileEntry.Size == streamLen)
|
||||
{
|
||||
@@ -522,7 +522,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
var url = new Uri(_url);
|
||||
|
||||
// Get the remote path
|
||||
var remotePath = url.AbsolutePath.EndsWith("/") ? url.AbsolutePath.Substring(0, url.AbsolutePath.Length - 1) : url.AbsolutePath;
|
||||
var remotePath = url.AbsolutePath.EndsWith("/", StringComparison.Ordinal) ? url.AbsolutePath.Substring(0, url.AbsolutePath.Length - 1) : url.AbsolutePath;
|
||||
|
||||
// Try to create the directory
|
||||
client.CreateDirectory(remotePath, true);
|
||||
@@ -561,7 +561,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
ftpClient.ValidateCertificate += HandleValidateCertificate;
|
||||
|
||||
// Get the remote path
|
||||
var remotePath = uri.AbsolutePath.EndsWith("/") ? uri.AbsolutePath.Substring(0, uri.AbsolutePath.Length - 1) : uri.AbsolutePath;
|
||||
var remotePath = uri.AbsolutePath.EndsWith("/", StringComparison.Ordinal) ? uri.AbsolutePath.Substring(0, uri.AbsolutePath.Length - 1) : uri.AbsolutePath;
|
||||
ftpClient.SetWorkingDirectory(remotePath);
|
||||
|
||||
this.Client = ftpClient;
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Duplicati.Library.Backend.AmazonCloudDrive
|
||||
var uri = new Utility.Uri(url);
|
||||
|
||||
m_path = uri.HostAndPath;
|
||||
if (!m_path.EndsWith("/"))
|
||||
if (!m_path.EndsWith("/", StringComparison.Ordinal))
|
||||
m_path += "/";
|
||||
|
||||
string authid = null;
|
||||
@@ -152,7 +152,7 @@ namespace Duplicati.Library.Backend.AmazonCloudDrive
|
||||
{
|
||||
if (m_endPointInfo == null)
|
||||
RefreshMetadataAndContentUrl();
|
||||
while (m_endPointInfo.ContentUrl.EndsWith("/"))
|
||||
while (m_endPointInfo.ContentUrl.EndsWith("/", StringComparison.Ordinal))
|
||||
m_endPointInfo.ContentUrl = m_endPointInfo.ContentUrl.Substring(0, m_endPointInfo.ContentUrl.Length - 1);
|
||||
return m_endPointInfo.ContentUrl;
|
||||
}
|
||||
@@ -164,7 +164,7 @@ namespace Duplicati.Library.Backend.AmazonCloudDrive
|
||||
{
|
||||
if (m_endPointInfo == null)
|
||||
RefreshMetadataAndContentUrl();
|
||||
while (m_endPointInfo.MetadataUrl.EndsWith("/"))
|
||||
while (m_endPointInfo.MetadataUrl.EndsWith("/", StringComparison.Ordinal))
|
||||
m_endPointInfo.MetadataUrl = m_endPointInfo.MetadataUrl.Substring(0, m_endPointInfo.MetadataUrl.Length - 1);
|
||||
return m_endPointInfo.MetadataUrl;
|
||||
}
|
||||
|
||||
@@ -57,11 +57,11 @@ namespace Duplicati.Library.Backend.Backblaze
|
||||
|
||||
m_bucketname = uri.Host;
|
||||
m_prefix = "/" + uri.Path;
|
||||
if (!m_prefix.EndsWith("/"))
|
||||
if (!m_prefix.EndsWith("/", StringComparison.Ordinal))
|
||||
m_prefix += "/";
|
||||
|
||||
// For B2 we do not use a leading slash
|
||||
while(m_prefix.StartsWith("/"))
|
||||
while(m_prefix.StartsWith("/", StringComparison.Ordinal))
|
||||
m_prefix = m_prefix.Substring(1);
|
||||
|
||||
m_urlencodedprefix = string.Join("/", m_prefix.Split(new [] { '/' }).Select(x => Library.Utility.Uri.UrlPathEncode(x)));
|
||||
@@ -323,7 +323,7 @@ namespace Duplicati.Library.Backend.Backblaze
|
||||
|
||||
foreach(var f in resp.Files)
|
||||
{
|
||||
if (!f.FileName.StartsWith(m_prefix))
|
||||
if (!f.FileName.StartsWith(m_prefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var name = f.FileName.Substring(m_prefix.Length);
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Duplicati.Library.Backend.Backblaze
|
||||
|
||||
private string DropTrailingSlashes(string url)
|
||||
{
|
||||
while(url.EndsWith("/"))
|
||||
while(url.EndsWith("/", StringComparison.Ordinal))
|
||||
url = url.Substring(0, url.Length - 1);
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Duplicati.Library.Backend.Box
|
||||
var uri = new Utility.Uri(url);
|
||||
|
||||
m_path = uri.HostAndPath;
|
||||
if (!m_path.EndsWith("/"))
|
||||
if (!m_path.EndsWith("/", StringComparison.Ordinal))
|
||||
m_path += "/";
|
||||
|
||||
string authid = null;
|
||||
|
||||
@@ -84,10 +84,10 @@ namespace Duplicati.Library.Backend
|
||||
|
||||
if (!string.IsNullOrEmpty(u.UserInfo))
|
||||
{
|
||||
if (u.UserInfo.IndexOf(":") >= 0)
|
||||
if (u.UserInfo.IndexOf(":", StringComparison.Ordinal) >= 0)
|
||||
{
|
||||
m_username = u.UserInfo.Substring(0, u.UserInfo.IndexOf(":"));
|
||||
m_password = u.UserInfo.Substring(u.UserInfo.IndexOf(":") + 1);
|
||||
m_username = u.UserInfo.Substring(0, u.UserInfo.IndexOf(":", StringComparison.Ordinal));
|
||||
m_password = u.UserInfo.Substring(u.UserInfo.IndexOf(":", StringComparison.Ordinal) + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -108,9 +108,9 @@ namespace Duplicati.Library.Backend
|
||||
m_path = uri.HostAndPath;
|
||||
}
|
||||
|
||||
if (m_path.EndsWith("/"))
|
||||
if (m_path.EndsWith("/", StringComparison.Ordinal))
|
||||
m_path = m_path.Substring(0, m_path.Length - 1);
|
||||
if (!m_path.StartsWith("/"))
|
||||
if (!m_path.StartsWith("/", StringComparison.Ordinal))
|
||||
m_path = "/" + m_path;
|
||||
|
||||
if (!options.TryGetValue("cloudfiles-authentication-url", out m_authUrl))
|
||||
@@ -416,7 +416,7 @@ namespace Duplicati.Library.Backend
|
||||
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(m_storageUrl + UrlEncode(m_path + remotename) + query);
|
||||
req.Headers.Add("X-Auth-Token", UrlEncode(m_authToken));
|
||||
|
||||
req.UserAgent = "Duplicati CloudFiles Backend v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
req.UserAgent = "Duplicati CloudFiles Backend v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
req.KeepAlive = false;
|
||||
req.PreAuthenticate = true;
|
||||
req.AllowWriteStreamBuffering = false;
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace Duplicati.Library.Backend
|
||||
var uri = new Utility.Uri(url);
|
||||
|
||||
m_path = Library.Utility.Uri.UrlDecode(uri.HostAndPath);
|
||||
if (m_path.Length != 0 && !m_path.StartsWith("/"))
|
||||
if (m_path.Length != 0 && !m_path.StartsWith("/", StringComparison.Ordinal))
|
||||
m_path = "/" + m_path;
|
||||
|
||||
if (m_path.EndsWith("/"))
|
||||
if (m_path.EndsWith("/", StringComparison.Ordinal))
|
||||
m_path = m_path.Substring(0, m_path.Length - 1);
|
||||
|
||||
if (options.ContainsKey(AUTHID_OPTION))
|
||||
@@ -117,7 +117,7 @@ namespace Duplicati.Library.Backend
|
||||
string path = String.Format("{0}/{1}", m_path, remotename);
|
||||
dbx.Delete(path);
|
||||
}
|
||||
catch (DropboxException de)
|
||||
catch (DropboxException)
|
||||
{
|
||||
// we can catch some events here and convert them to Duplicati exceptions
|
||||
throw;
|
||||
@@ -163,7 +163,7 @@ namespace Duplicati.Library.Backend
|
||||
string path = string.Format("{0}/{1}", m_path, remotename);
|
||||
dbx.UploadFile(path, stream);
|
||||
}
|
||||
catch (DropboxException de)
|
||||
catch (DropboxException)
|
||||
{
|
||||
// we can catch some events here and convert them to Duplicati exceptions
|
||||
throw;
|
||||
@@ -177,7 +177,7 @@ namespace Duplicati.Library.Backend
|
||||
string path = string.Format("{0}/{1}", m_path, remotename);
|
||||
dbx.DownloadFile(path, stream);
|
||||
}
|
||||
catch (DropboxException de)
|
||||
catch (DropboxException)
|
||||
{
|
||||
// we can catch some events here and convert them to Duplicati exceptions
|
||||
throw;
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Duplicati.Library.Backend
|
||||
m_userInfo.Domain = "";
|
||||
|
||||
m_url = u.SetScheme("ftp").SetQuery(null).SetCredentials(null, null).ToString();
|
||||
if (!m_url.EndsWith("/"))
|
||||
if (!m_url.EndsWith("/", StringComparison.Ordinal))
|
||||
m_url += "/";
|
||||
|
||||
m_useSSL = Utility.Utility.ParseBoolOption(options, "use-ssl");
|
||||
@@ -278,7 +278,7 @@ namespace Duplicati.Library.Backend
|
||||
{
|
||||
IEnumerable<IFileEntry> files = List(remotename);
|
||||
foreach(IFileEntry fe in files)
|
||||
if (fe.Name.Equals(remotename) || fe.Name.EndsWith("/" + remotename) || fe.Name.EndsWith("\\" + remotename))
|
||||
if (fe.Name.Equals(remotename) || fe.Name.EndsWith("/" + remotename, StringComparison.Ordinal) || fe.Name.EndsWith("\\" + remotename, StringComparison.Ordinal))
|
||||
{
|
||||
if (fe.Size < 0 || streamLen < 0 || fe.Size == streamLen)
|
||||
return;
|
||||
@@ -390,7 +390,7 @@ namespace Duplicati.Library.Backend
|
||||
private System.Net.FtpWebRequest CreateRequest(string remotename, bool createFolder)
|
||||
{
|
||||
string url = m_url;
|
||||
if (createFolder && url.EndsWith("/"))
|
||||
if (createFolder && url.EndsWith("/", StringComparison.Ordinal))
|
||||
url = url.Substring(0, url.Length - 1);
|
||||
|
||||
System.Net.FtpWebRequest req = (System.Net.FtpWebRequest)System.Net.FtpWebRequest.Create(url + remotename);
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Duplicati.Library.Backend
|
||||
|
||||
for (int i = 0; i < paths.Count; i++)
|
||||
{
|
||||
if (paths[i].StartsWith("*:"))
|
||||
if (paths[i].StartsWith("*:", StringComparison.Ordinal))
|
||||
{
|
||||
string rpl_path = paths[i].Substring(1);
|
||||
paths.RemoveAt(i);
|
||||
@@ -304,7 +304,7 @@ namespace Duplicati.Library.Backend
|
||||
//TODO: Can trick this with symlinks, where the symlink is on one mounted volume,
|
||||
// and the actual storage is on another
|
||||
foreach (System.IO.DriveInfo di in System.IO.DriveInfo.GetDrives())
|
||||
if (path.StartsWith(Utility.Utility.AppendDirSeparator(di.Name)) && di.Name.Length > root.Length)
|
||||
if (path.StartsWith(Utility.Utility.AppendDirSeparator(di.Name), StringComparison.Ordinal) && di.Name.Length > root.Length)
|
||||
root = di.Name;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -95,13 +95,13 @@ namespace Duplicati.Library.Backend
|
||||
//Strip it down from \\server\share\folder1\folder2\filename.extension to
|
||||
// \\server\share
|
||||
string minpath = path;
|
||||
if (!minpath.StartsWith("\\\\"))
|
||||
if (!minpath.StartsWith("\\\\", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
int first = minpath.IndexOf("\\", 2);
|
||||
int first = minpath.IndexOf("\\", 2, StringComparison.Ordinal);
|
||||
if (first <= 0)
|
||||
return false;
|
||||
int next = minpath.IndexOf("\\", first + 1);
|
||||
int next = minpath.IndexOf("\\", first + 1, StringComparison.Ordinal);
|
||||
if (next >= 0)
|
||||
minpath = minpath.Substring(0, next);
|
||||
|
||||
|
||||
@@ -79,11 +79,11 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
|
||||
|
||||
m_bucket = uri.Host;
|
||||
m_prefix = "/" + uri.Path;
|
||||
if (!m_prefix.EndsWith("/"))
|
||||
if (!m_prefix.EndsWith("/", StringComparison.Ordinal))
|
||||
m_prefix += "/";
|
||||
|
||||
// For GCS we do not use a leading slash
|
||||
if (m_prefix.StartsWith("/"))
|
||||
if (m_prefix.StartsWith("/", StringComparison.Ordinal))
|
||||
m_prefix = m_prefix.Substring(1);
|
||||
|
||||
string authid;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Duplicati.Library.Backend.GoogleDrive
|
||||
var uri = new Utility.Uri(url);
|
||||
|
||||
m_path = uri.HostAndPath;
|
||||
if (!m_path.EndsWith("/"))
|
||||
if (!m_path.EndsWith("/", StringComparison.Ordinal))
|
||||
m_path += "/";
|
||||
|
||||
string authid = null;
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Duplicati.Library.Backend
|
||||
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);
|
||||
if (!m_path.EndsWith("/"))
|
||||
if (!m_path.EndsWith("/", StringComparison.Ordinal))
|
||||
m_path += "/";
|
||||
if (!string.IsNullOrEmpty(u.Username))
|
||||
{
|
||||
@@ -300,7 +300,7 @@ namespace Duplicati.Library.Backend
|
||||
req.Credentials = m_userInfo;
|
||||
req.PreAuthenticate = true; // We need this under Mono for some reason, and it appears some servers require this as well
|
||||
req.KeepAlive = false;
|
||||
req.UserAgent = "Duplicati Jottacloud Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
req.UserAgent = "Duplicati Jottacloud Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
req.Headers.Add("x-jftp-version", API_VERSION);
|
||||
return req;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Duplicati.Library
|
||||
{
|
||||
public class JSONWebHelper
|
||||
{
|
||||
public static readonly string USER_AGENT = string.Format("Duplicati v{0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
||||
public static readonly string USER_AGENT = string.Format("Duplicati v{0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
|
||||
private readonly string m_user_agent;
|
||||
public string OAuthLoginUrl { get; protected set; }
|
||||
public string UserAgent { get { return m_user_agent; } }
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Duplicati.Library.Backend
|
||||
private const long BITS_FILE_SIZE_LIMIT = 1024 * 1024 * 15;
|
||||
private const long BITS_CHUNK_SIZE = 1024 * 1024 * 10;
|
||||
|
||||
private static readonly string USER_AGENT = string.Format("Duplicati v{0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
||||
private static readonly string USER_AGENT = string.Format("Duplicati v{0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
|
||||
|
||||
private string m_rootfolder;
|
||||
private string m_prefix;
|
||||
@@ -42,7 +42,7 @@ namespace Duplicati.Library.Backend
|
||||
|
||||
m_rootfolder = uri.Host;
|
||||
m_prefix = "/" + uri.Path;
|
||||
if (!m_prefix.EndsWith("/"))
|
||||
if (!m_prefix.EndsWith("/", StringComparison.Ordinal))
|
||||
m_prefix += "/";
|
||||
|
||||
string authid = null;
|
||||
|
||||
@@ -188,11 +188,11 @@ namespace Duplicati.Library.Backend.OpenStack
|
||||
|
||||
m_container = uri.Host;
|
||||
m_prefix = "/" + uri.Path;
|
||||
if (!m_prefix.EndsWith("/"))
|
||||
if (!m_prefix.EndsWith("/", StringComparison.Ordinal))
|
||||
m_prefix += "/";
|
||||
|
||||
// For OpenStack we do not use a leading slash
|
||||
if (m_prefix.StartsWith("/"))
|
||||
if (m_prefix.StartsWith("/", StringComparison.Ordinal))
|
||||
m_prefix = m_prefix.Substring(1);
|
||||
|
||||
options.TryGetValue(USERNAME_OPTION, out m_username);
|
||||
@@ -232,7 +232,7 @@ namespace Duplicati.Library.Backend.OpenStack
|
||||
private string JoinUrls(string uri, string fragment)
|
||||
{
|
||||
fragment = fragment ?? "";
|
||||
return uri + (uri.EndsWith("/") ? "" : "/") + (fragment.StartsWith("/") ? fragment.Substring(1) : fragment);
|
||||
return uri + (uri.EndsWith("/", StringComparison.Ordinal) ? "" : "/") + (fragment.StartsWith("/", StringComparison.Ordinal) ? fragment.Substring(1) : fragment);
|
||||
}
|
||||
private string JoinUrls(string uri, string fragment1, string fragment2)
|
||||
{
|
||||
@@ -338,7 +338,7 @@ namespace Duplicati.Library.Backend.OpenStack
|
||||
foreach (var n in items)
|
||||
{
|
||||
var name = n.name;
|
||||
if (name.StartsWith(m_prefix))
|
||||
if (name.StartsWith(m_prefix, StringComparison.Ordinal))
|
||||
name = name.Substring(m_prefix.Length);
|
||||
|
||||
if (n.bytes == null)
|
||||
|
||||
@@ -218,25 +218,25 @@ namespace Duplicati.Library.Backend
|
||||
{
|
||||
m_bucket = Library.Utility.Uri.UrlDecode(u.PathAndQuery);
|
||||
|
||||
if (m_bucket.StartsWith("/"))
|
||||
if (m_bucket.StartsWith("/", StringComparison.Ordinal))
|
||||
m_bucket = m_bucket.Substring(1);
|
||||
|
||||
if (m_bucket.Contains("/"))
|
||||
{
|
||||
m_prefix = m_bucket.Substring(m_bucket.IndexOf("/") + 1);
|
||||
m_bucket = m_bucket.Substring(0, m_bucket.IndexOf("/"));
|
||||
m_prefix = m_bucket.Substring(m_bucket.IndexOf("/", StringComparison.Ordinal) + 1);
|
||||
m_bucket = m_bucket.Substring(0, m_bucket.IndexOf("/", StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Subdomain type lookup
|
||||
if (host.ToLower().EndsWith("." + s3host))
|
||||
if (host.ToLower().EndsWith("." + s3host, StringComparison.Ordinal))
|
||||
{
|
||||
m_bucket = host.Substring(0, host.Length - ("." + s3host).Length);
|
||||
host = s3host;
|
||||
m_prefix = Library.Utility.Uri.UrlDecode(u.PathAndQuery);
|
||||
|
||||
if (m_prefix.StartsWith("/"))
|
||||
if (m_prefix.StartsWith("/", StringComparison.Ordinal))
|
||||
m_prefix = m_prefix.Substring(1);
|
||||
}
|
||||
else
|
||||
@@ -255,7 +255,7 @@ namespace Duplicati.Library.Backend
|
||||
|
||||
m_options = options;
|
||||
m_prefix = m_prefix.Trim();
|
||||
if (m_prefix.Length != 0 && !m_prefix.EndsWith("/"))
|
||||
if (m_prefix.Length != 0 && !m_prefix.EndsWith("/", StringComparison.Ordinal))
|
||||
m_prefix += "/";
|
||||
|
||||
// Auto-disable dns lookup for non AWS configurations
|
||||
@@ -315,7 +315,7 @@ namespace Duplicati.Library.Backend
|
||||
((FileEntry)file).Name = file.Name.Substring(m_prefix.Length);
|
||||
|
||||
//Fix for a bug in Duplicati 1.0 beta 3 and earlier, where filenames are incorrectly prefixed with a slash
|
||||
if (file.Name.StartsWith("/") && !m_prefix.StartsWith("/"))
|
||||
if (file.Name.StartsWith("/", StringComparison.Ordinal) && !m_prefix.StartsWith("/", StringComparison.Ordinal))
|
||||
((FileEntry)file).Name = file.Name.Substring(1);
|
||||
|
||||
yield return file;
|
||||
@@ -362,7 +362,7 @@ namespace Duplicati.Library.Backend
|
||||
//This is a fix for the S3 backend prior to beta 3, where the filenames had a slash prefixed
|
||||
try
|
||||
{
|
||||
if (!remotename.StartsWith("/"))
|
||||
if (!remotename.StartsWith("/", StringComparison.Ordinal))
|
||||
Connection.GetFileStream(m_bucket, GetFullKey("/" + remotename), output);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace Duplicati.Library.Backend
|
||||
}
|
||||
|
||||
var keys = authorized_keys == null ? new string[0] : authorized_keys.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var cleaned_keys = keys.Select(x => x.Trim()).Where(x => x.Length > 0 && !x.StartsWith("#"));
|
||||
var cleaned_keys = keys.Select(x => x.Trim()).Where(x => x.Length > 0 && !x.StartsWith("#", StringComparison.Ordinal));
|
||||
|
||||
// Does the key already exist?
|
||||
if (cleaned_keys.Where(x =>
|
||||
|
||||
@@ -78,10 +78,10 @@ namespace Duplicati.Library.Backend
|
||||
|
||||
m_path = uri.Path;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(m_path) && !m_path.EndsWith("/"))
|
||||
if (!string.IsNullOrWhiteSpace(m_path) && !m_path.EndsWith("/", StringComparison.Ordinal))
|
||||
m_path += "/";
|
||||
|
||||
if (!m_path.StartsWith("/"))
|
||||
if (!m_path.StartsWith("/", StringComparison.Ordinal))
|
||||
m_path = "/" + m_path;
|
||||
|
||||
m_server = uri.Host;
|
||||
@@ -114,7 +114,7 @@ namespace Duplicati.Library.Backend
|
||||
CreateConnection();
|
||||
//Bugfix, some SSH servers do not like a trailing slash
|
||||
string p = m_path;
|
||||
if (p.EndsWith("/"))
|
||||
if (p.EndsWith("/", StringComparison.Ordinal))
|
||||
p.Substring(0, p.Length - 1);
|
||||
m_con.CreateDirectory(p);
|
||||
}
|
||||
@@ -297,7 +297,7 @@ namespace Duplicati.Library.Backend
|
||||
|
||||
string working_dir = m_con.WorkingDirectory;
|
||||
|
||||
if (!working_dir.EndsWith("/"))
|
||||
if (!working_dir.EndsWith("/", StringComparison.Ordinal))
|
||||
working_dir += "/";
|
||||
|
||||
if (working_dir == path)
|
||||
@@ -322,33 +322,40 @@ namespace Duplicati.Library.Backend
|
||||
|
||||
foreach (Renci.SshNet.Sftp.SftpFile ls in m_con.ListDirectory(path))
|
||||
if (ls.Name.ToString() != "." && ls.Name.ToString() != "..")
|
||||
yield return new FileEntry(ls.Name.ToString(), ls.Length, ls.LastAccessTime, ls.LastWriteTime) { IsFolder = ls.Attributes.IsDirectory };
|
||||
yield return new FileEntry(ls.Name, ls.Length, ls.LastAccessTime, ls.LastWriteTime) { IsFolder = ls.Attributes.IsDirectory };
|
||||
}
|
||||
|
||||
public static Renci.SshNet.PrivateKeyFile ValidateKeyFile(string filename, string password)
|
||||
{
|
||||
if (filename.StartsWith(KEYFILE_URI, StringComparison.OrdinalIgnoreCase))
|
||||
try
|
||||
{
|
||||
using (var ms = new System.IO.MemoryStream())
|
||||
using (var sr = new System.IO.StreamWriter(ms))
|
||||
if (filename.StartsWith(KEYFILE_URI, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sr.Write(Duplicati.Library.Utility.Uri.UrlDecode(filename.Substring(KEYFILE_URI.Length)));
|
||||
sr.Flush();
|
||||
using (var ms = new System.IO.MemoryStream())
|
||||
using (var sr = new System.IO.StreamWriter(ms))
|
||||
{
|
||||
sr.Write(Duplicati.Library.Utility.Uri.UrlDecode(filename.Substring(KEYFILE_URI.Length)));
|
||||
sr.Flush();
|
||||
|
||||
ms.Position = 0;
|
||||
ms.Position = 0;
|
||||
|
||||
if (String.IsNullOrEmpty(password))
|
||||
return new Renci.SshNet.PrivateKeyFile(ms);
|
||||
else
|
||||
return new Renci.SshNet.PrivateKeyFile(ms, password);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (String.IsNullOrEmpty(password))
|
||||
return new Renci.SshNet.PrivateKeyFile(ms);
|
||||
return new Renci.SshNet.PrivateKeyFile(filename);
|
||||
else
|
||||
return new Renci.SshNet.PrivateKeyFile(ms, password);
|
||||
return new Renci.SshNet.PrivateKeyFile(filename, password);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (String.IsNullOrEmpty(password))
|
||||
return new Renci.SshNet.PrivateKeyFile(filename);
|
||||
else
|
||||
return new Renci.SshNet.PrivateKeyFile(filename, password);
|
||||
throw new UserInformationException(string.Format("Failed to parse the keyfile, check the key format and passphrase. Error message was {0}", ex.Message), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,9 +162,9 @@ namespace Duplicati.Library.Backend
|
||||
m_spWebUrl = null;
|
||||
|
||||
m_serverRelPath = u.Path;
|
||||
if (!m_serverRelPath.StartsWith("/"))
|
||||
if (!m_serverRelPath.StartsWith("/", StringComparison.Ordinal))
|
||||
m_serverRelPath = "/" + m_serverRelPath;
|
||||
if (!m_serverRelPath.EndsWith("/"))
|
||||
if (!m_serverRelPath.EndsWith("/", StringComparison.Ordinal))
|
||||
m_serverRelPath += "/";
|
||||
// remove marker for SP-Web
|
||||
m_serverRelPath = m_serverRelPath.Replace("//", "/");
|
||||
@@ -290,7 +290,7 @@ namespace Duplicati.Library.Backend
|
||||
retCtx = null;
|
||||
|
||||
string path = orgUrl.Path;
|
||||
int webIndicatorPos = path.IndexOf("//");
|
||||
int webIndicatorPos = path.IndexOf("//", StringComparison.Ordinal);
|
||||
|
||||
// if a hint is supplied, we will of course use this first.
|
||||
if (webIndicatorPos >= 0)
|
||||
@@ -364,7 +364,6 @@ namespace Duplicati.Library.Backend
|
||||
/// We have to check for the exceptions thrown to know about file /folder existence.
|
||||
/// Why the funny guys at MS provided an .Exists field stays a mystery...
|
||||
/// </summary>
|
||||
/// <param name="fileNameInfo"> the filename </param>
|
||||
private void wrappedExecuteQueryOnConext(SP.ClientContext ctx, string serverRelPathInfo, bool isFolder)
|
||||
{
|
||||
try { ctx.ExecuteQuery(); }
|
||||
|
||||
@@ -46,9 +46,9 @@ namespace Duplicati.Library.Backend.Sia
|
||||
}
|
||||
while(m_targetpath.Contains("//"))
|
||||
m_targetpath = m_targetpath.Replace("//","/");
|
||||
while (m_targetpath.StartsWith("/"))
|
||||
while (m_targetpath.StartsWith("/", StringComparison.Ordinal))
|
||||
m_targetpath = m_targetpath.Substring(1);
|
||||
while (m_targetpath.EndsWith("/"))
|
||||
while (m_targetpath.EndsWith("/", StringComparison.Ordinal))
|
||||
m_targetpath = m_targetpath.Remove(m_targetpath.Length - 1);
|
||||
|
||||
if (m_targetpath.Length == 0)
|
||||
@@ -73,7 +73,7 @@ namespace Duplicati.Library.Backend.Sia
|
||||
}
|
||||
|
||||
req.KeepAlive = false;
|
||||
req.UserAgent = string.Format("Sia-Agent (Duplicati SIA client {0})", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
||||
req.UserAgent = string.Format("Sia-Agent (Duplicati SIA client {0})", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
|
||||
|
||||
return req;
|
||||
}
|
||||
@@ -294,7 +294,7 @@ namespace Duplicati.Library.Backend.Sia
|
||||
{
|
||||
// Sia returns a complete file list, but we're only interested in files that are
|
||||
// in our target path
|
||||
if (f.Siapath.StartsWith(m_targetpath))
|
||||
if (f.Siapath.StartsWith(m_targetpath, StringComparison.Ordinal))
|
||||
{
|
||||
FileEntry fe = new FileEntry(f.Siapath.Substring(m_targetpath.Length + 1));
|
||||
fe.Size = f.Filesize;
|
||||
|
||||
@@ -99,13 +99,13 @@ namespace Duplicati.Library.Backend
|
||||
var u = new Utility.Uri(url);
|
||||
u.RequireHost();
|
||||
|
||||
if (!u.Path.StartsWith("uri/URI:DIR2:") && !u.Path.StartsWith("uri/URI%3ADIR2%3A"))
|
||||
if (!u.Path.StartsWith("uri/URI:DIR2:", StringComparison.Ordinal) && !u.Path.StartsWith("uri/URI%3ADIR2%3A", StringComparison.Ordinal))
|
||||
throw new UserInformationException(Strings.TahoeBackend.UnrecognizedUriError);
|
||||
|
||||
m_useSSL = Utility.Utility.ParseBoolOption(options, "use-ssl");
|
||||
|
||||
m_url = u.SetScheme(m_useSSL ? "https" : "http").SetQuery(null).SetCredentials(null, null).ToString();
|
||||
if (!m_url.EndsWith("/"))
|
||||
if (!m_url.EndsWith("/", StringComparison.Ordinal))
|
||||
m_url += "/";
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace Duplicati.Library.Backend
|
||||
System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(m_url + (Library.Utility.Uri.UrlEncode(remotename).Replace("+", "%20")) + (string.IsNullOrEmpty(queryparams) || queryparams.Trim().Length == 0 ? "" : "?" + queryparams));
|
||||
|
||||
req.KeepAlive = false;
|
||||
req.UserAgent = "Duplicati Tahoe-LAFS Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
req.UserAgent = "Duplicati Tahoe-LAFS Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
@@ -91,13 +91,13 @@ namespace Duplicati.Library.Backend
|
||||
m_useSSL = Utility.Utility.ParseBoolOption(options, "use-ssl");
|
||||
|
||||
m_url = u.SetScheme(m_useSSL ? "https" : "http").SetCredentials(null, null).SetQuery(null).ToString();
|
||||
if (!m_url.EndsWith("/"))
|
||||
if (!m_url.EndsWith("/", StringComparison.Ordinal))
|
||||
m_url += "/";
|
||||
|
||||
m_path = u.Path;
|
||||
if (!m_path.StartsWith("/"))
|
||||
if (!m_path.StartsWith("/", StringComparison.Ordinal))
|
||||
m_path = "/" + m_path;
|
||||
if (!m_path.EndsWith("/"))
|
||||
if (!m_path.EndsWith("/", StringComparison.Ordinal))
|
||||
m_path += "/";
|
||||
|
||||
m_path = Library.Utility.Uri.UrlDecode(m_path);
|
||||
@@ -195,19 +195,19 @@ namespace Duplicati.Library.Backend
|
||||
|
||||
//TODO: This list is getting ridiculous, should change to regexps
|
||||
|
||||
if (name.StartsWith(m_url))
|
||||
if (name.StartsWith(m_url, StringComparison.Ordinal))
|
||||
cmp_path = m_url;
|
||||
else if (name.StartsWith(m_rawurl))
|
||||
else if (name.StartsWith(m_rawurl, StringComparison.Ordinal))
|
||||
cmp_path = m_rawurl;
|
||||
else if (name.StartsWith(m_rawurlPort))
|
||||
else if (name.StartsWith(m_rawurlPort, StringComparison.Ordinal))
|
||||
cmp_path = m_rawurlPort;
|
||||
else if (name.StartsWith(m_path))
|
||||
else if (name.StartsWith(m_path, StringComparison.Ordinal))
|
||||
cmp_path = m_path;
|
||||
else if (name.StartsWith("/" + m_path))
|
||||
else if (name.StartsWith("/" + m_path, StringComparison.Ordinal))
|
||||
cmp_path = "/" + m_path;
|
||||
else if (name.StartsWith(m_sanitizedUrl))
|
||||
else if (name.StartsWith(m_sanitizedUrl, StringComparison.Ordinal))
|
||||
cmp_path = m_sanitizedUrl;
|
||||
else if (name.StartsWith(m_reverseProtocolUrl))
|
||||
else if (name.StartsWith(m_reverseProtocolUrl, StringComparison.Ordinal))
|
||||
cmp_path = m_reverseProtocolUrl;
|
||||
else
|
||||
continue;
|
||||
@@ -360,7 +360,7 @@ namespace Duplicati.Library.Backend
|
||||
}
|
||||
|
||||
req.KeepAlive = false;
|
||||
req.UserAgent = "Duplicati WEBDAV Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
req.UserAgent = "Duplicati WEBDAV Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ using Duplicati.Library.Interface;
|
||||
using SharpCompress.Common;
|
||||
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Writers;
|
||||
using SharpCompress.Writers.Zip;
|
||||
using SharpCompress.Readers;
|
||||
@@ -34,10 +33,13 @@ 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
|
||||
{
|
||||
private const string CannotReadWhileWriting = "Cannot read while writing";
|
||||
private const string CannotWriteWhileReading = "Cannot write while reading";
|
||||
|
||||
/// <summary>
|
||||
/// The commandline option for toggling the compression level
|
||||
/// </summary>
|
||||
@@ -51,16 +53,16 @@ namespace Duplicati.Library.Compression
|
||||
/// <summary>
|
||||
/// The commandline option for toggling the compression method
|
||||
/// </summary>
|
||||
private const string COMPRESSION_METHOD_OPTION = "zip-compression-method";
|
||||
/// <summary>
|
||||
/// The commandline option for toggling the zip64 support
|
||||
/// </summary>
|
||||
private const string COMPRESSION_ZIP64_OPTION = "zip-compression-zip64";
|
||||
|
||||
/// <summary>
|
||||
/// The default compression level
|
||||
/// </summary>
|
||||
private const SharpCompress.Compressors.Deflate.CompressionLevel DEFAULT_COMPRESSION_LEVEL = SharpCompress.Compressors.Deflate.CompressionLevel.Level9;
|
||||
private const string COMPRESSION_METHOD_OPTION = "zip-compression-method";
|
||||
/// <summary>
|
||||
/// The commandline option for toggling the zip64 support
|
||||
/// </summary>
|
||||
private const string COMPRESSION_ZIP64_OPTION = "zip-compression-zip64";
|
||||
|
||||
/// <summary>
|
||||
/// The default compression level
|
||||
/// </summary>
|
||||
private const SharpCompress.Compressors.Deflate.CompressionLevel DEFAULT_COMPRESSION_LEVEL = SharpCompress.Compressors.Deflate.CompressionLevel.Level9;
|
||||
|
||||
/// <summary>
|
||||
/// The default compression method
|
||||
@@ -83,9 +85,9 @@ namespace Duplicati.Library.Compression
|
||||
private const int CENTRAL_HEADER_ENTRY_SIZE_ZIP64_EXTRA = 2 + 2 + 8 + 8 + 8 + 4;
|
||||
|
||||
/// <summary>
|
||||
/// This property indicates that this current instance should write to a file
|
||||
/// This property indicates reading or writing access mode of the file archive.
|
||||
/// </summary>
|
||||
private bool m_isWriting;
|
||||
ArchiveMode m_mode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes expected to be written after the stream is disposed
|
||||
@@ -100,12 +102,12 @@ namespace Duplicati.Library.Compression
|
||||
/// The stream used to either read or write
|
||||
/// </summary>
|
||||
private Stream m_stream;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Lookup table for faster access to entries based on their name.
|
||||
/// </summary>
|
||||
private Dictionary<string, IEntry> m_entryDict;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The writer instance used when creating archives
|
||||
/// </summary>
|
||||
@@ -119,18 +121,13 @@ namespace Duplicati.Library.Compression
|
||||
/// <summary>
|
||||
/// The compression level applied when the hint does not indicate incompressible
|
||||
/// </summary>
|
||||
private SharpCompress.Compressors.Deflate.CompressionLevel m_defaultCompressionLevel;
|
||||
private SharpCompress.Compressors.Deflate.CompressionLevel m_defaultCompressionLevel;
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// The compression level applied when the hint does not indicate incompressible
|
||||
/// </summary>
|
||||
private CompressionType m_compressionType;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the file being read
|
||||
/// </summary>
|
||||
private string m_filename;
|
||||
|
||||
/// <summary>
|
||||
/// A flag indicating if zip64 is in use
|
||||
/// </summary>
|
||||
@@ -141,14 +138,15 @@ namespace Duplicati.Library.Compression
|
||||
/// </summary>
|
||||
public FileArchiveZip() { }
|
||||
|
||||
public IArchive Archive
|
||||
private IArchive Archive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_stream == null)
|
||||
m_stream = new System.IO.FileStream(m_filename, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
if (m_archive == null)
|
||||
{
|
||||
m_stream.Position = 0;
|
||||
m_archive = ArchiveFactory.Open(m_stream);
|
||||
}
|
||||
return m_archive;
|
||||
}
|
||||
}
|
||||
@@ -168,19 +166,16 @@ namespace Duplicati.Library.Compression
|
||||
|
||||
public Stream GetStreamFromReader(IEntry entry)
|
||||
{
|
||||
Stream fs = null;
|
||||
SharpCompress.Readers.Zip.ZipReader rd = null;
|
||||
|
||||
try
|
||||
{
|
||||
fs = new System.IO.FileStream(m_filename, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
rd = SharpCompress.Readers.Zip.ZipReader.Open(fs);
|
||||
rd = SharpCompress.Readers.Zip.ZipReader.Open(m_stream);
|
||||
|
||||
while (rd.MoveToNextEntry())
|
||||
if (entry.Key == rd.Entry.Key)
|
||||
return new StreamWrapper(rd.OpenEntryStream(), stream => {
|
||||
rd.Dispose();
|
||||
fs.Dispose();
|
||||
});
|
||||
|
||||
throw new Exception(string.Format("Stream not found: {0}", entry.Key));
|
||||
@@ -189,9 +184,7 @@ namespace Duplicati.Library.Compression
|
||||
{
|
||||
if (rd != null)
|
||||
rd.Dispose();
|
||||
if (fs != null)
|
||||
fs.Dispose();
|
||||
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -199,22 +192,18 @@ namespace Duplicati.Library.Compression
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new zip instance.
|
||||
/// If the file exists and has a non-zero length we read it,
|
||||
/// otherwise we create a new archive.
|
||||
/// Access mode is specified by mode parameter.
|
||||
/// Note that stream would not be disposed by FileArchiveZip instance so
|
||||
/// you may reuse it and have to dispose it yourself.
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the file to read or write</param>
|
||||
/// <param name="stream">The stream to read or write depending access mode</param>
|
||||
/// <param name="mode">The archive acces mode</param>
|
||||
/// <param name="options">The options passed on the commandline</param>
|
||||
public FileArchiveZip(string filename, Dictionary<string, string> options)
|
||||
public FileArchiveZip(Stream stream, ArchiveMode mode, IDictionary<string, string> options)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename) && filename.Trim().Length == 0)
|
||||
throw new ArgumentException("Invalid filename.", nameof(filename));
|
||||
|
||||
if (File.Exists(filename) && new FileInfo(filename).Length > 0)
|
||||
{
|
||||
m_isWriting = false;
|
||||
m_filename = filename;
|
||||
}
|
||||
else
|
||||
m_stream = stream;
|
||||
m_mode = mode;
|
||||
if (mode == ArchiveMode.Write)
|
||||
{
|
||||
var compression = new ZipWriterOptions(CompressionType.Deflate);
|
||||
|
||||
@@ -241,8 +230,6 @@ namespace Duplicati.Library.Compression
|
||||
m_defaultCompressionLevel = compression.DeflateCompressionLevel;
|
||||
m_compressionType = compression.CompressionType;
|
||||
|
||||
m_isWriting = true;
|
||||
m_stream = new System.IO.FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
m_writer = WriterFactory.Open(m_stream, ArchiveType.Zip, compression);
|
||||
|
||||
//Size of endheader, taken from SharpCompress ZipWriter
|
||||
@@ -270,26 +257,26 @@ namespace Duplicati.Library.Compression
|
||||
public IList<ICommandLineArgument> SupportedCommands
|
||||
{
|
||||
get
|
||||
{
|
||||
// This is the cross between these two:
|
||||
// https://github.com/adamhathcock/sharpcompress/blob/master/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs
|
||||
// https://github.com/adamhathcock/sharpcompress/blob/master/src/SharpCompress/Common/CompressionType.cs
|
||||
|
||||
var methods = new[]
|
||||
{
|
||||
// This is the cross between these two:
|
||||
// https://github.com/adamhathcock/sharpcompress/blob/master/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs
|
||||
// https://github.com/adamhathcock/sharpcompress/blob/master/src/SharpCompress/Common/CompressionType.cs
|
||||
|
||||
var methods = new[]
|
||||
{
|
||||
CompressionType.None.ToString(),
|
||||
CompressionType.Deflate.ToString(),
|
||||
CompressionType.BZip2.ToString(),
|
||||
CompressionType.LZMA.ToString(),
|
||||
CompressionType.PPMd.ToString(),
|
||||
CompressionType.None.ToString(),
|
||||
CompressionType.Deflate.ToString(),
|
||||
CompressionType.BZip2.ToString(),
|
||||
CompressionType.LZMA.ToString(),
|
||||
CompressionType.PPMd.ToString(),
|
||||
};
|
||||
|
||||
return new List<ICommandLineArgument>(new ICommandLineArgument[] {
|
||||
new CommandLineArgument(COMPRESSION_LEVEL_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.FileArchiveZip.CompressionlevelShort, Strings.FileArchiveZip.CompressionlevelLong, DEFAULT_COMPRESSION_LEVEL.ToString(), null, new string[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}),
|
||||
new CommandLineArgument(COMPRESSION_LEVEL_OPTION_ALIAS, CommandLineArgument.ArgumentType.Enumeration, Strings.FileArchiveZip.CompressionlevelShort, Strings.FileArchiveZip.CompressionlevelLong, DEFAULT_COMPRESSION_LEVEL.ToString(), null, new string[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}, Strings.FileArchiveZip.CompressionlevelDeprecated(COMPRESSION_LEVEL_OPTION)),
|
||||
new CommandLineArgument(COMPRESSION_METHOD_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.FileArchiveZip.CompressionmethodShort, Strings.FileArchiveZip.CompressionmethodLong(COMPRESSION_LEVEL_OPTION), DEFAULT_COMPRESSION_METHOD.ToString(), null, methods),
|
||||
new CommandLineArgument(COMPRESSION_ZIP64_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.FileArchiveZip.Compressionzip64Short, Strings.FileArchiveZip.Compressionzip64Long, DEFAULT_ZIP64.ToString())
|
||||
});
|
||||
new CommandLineArgument(COMPRESSION_ZIP64_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.FileArchiveZip.Compressionzip64Short, Strings.FileArchiveZip.Compressionzip64Long, DEFAULT_ZIP64.ToString())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,8 +316,8 @@ namespace Duplicati.Library.Compression
|
||||
/// <returns>A stream with the file contents</returns>
|
||||
public Stream OpenRead(string file)
|
||||
{
|
||||
if (m_isWriting)
|
||||
throw new InvalidOperationException("Cannot read while writing");
|
||||
if (m_mode != ArchiveMode.Read)
|
||||
throw new InvalidOperationException(CannotReadWhileWriting);
|
||||
|
||||
var ze = GetEntry(file);
|
||||
if (ze == null)
|
||||
@@ -373,8 +360,7 @@ namespace Duplicati.Library.Compression
|
||||
|
||||
try
|
||||
{
|
||||
using (var fs = new System.IO.FileStream(m_filename, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var rd = SharpCompress.Readers.Zip.ZipReader.Open(fs, new ReaderOptions() { LookForHeader = false }))
|
||||
using (var rd = SharpCompress.Readers.Zip.ZipReader.Open(m_stream, new ReaderOptions() { LookForHeader = false }))
|
||||
while (rd.MoveToNextEntry())
|
||||
{
|
||||
d[rd.Entry.Key] = rd.Entry;
|
||||
@@ -390,10 +376,10 @@ namespace Duplicati.Library.Compression
|
||||
// If we have zero files, or just a manifest, don't bother
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
m_entryDict = d;
|
||||
}
|
||||
}
|
||||
@@ -406,8 +392,8 @@ namespace Duplicati.Library.Compression
|
||||
/// <returns>The ZipEntry for the file or null if no such file was found</returns>
|
||||
private IEntry GetEntry(string file)
|
||||
{
|
||||
if (m_isWriting)
|
||||
throw new InvalidOperationException("Cannot read while writing");
|
||||
if (m_mode != ArchiveMode.Read)
|
||||
throw new InvalidOperationException(CannotReadWhileWriting);
|
||||
|
||||
LoadEntryTable();
|
||||
|
||||
@@ -429,13 +415,13 @@ namespace Duplicati.Library.Compression
|
||||
/// <returns>A writeable stream for the file contents</returns>
|
||||
public virtual Stream CreateFile(string file, CompressionHint hint, DateTime lastWrite)
|
||||
{
|
||||
if (!m_isWriting)
|
||||
throw new InvalidOperationException("Cannot write while reading");
|
||||
if (m_mode != ArchiveMode.Write)
|
||||
throw new InvalidOperationException(CannotWriteWhileReading);
|
||||
|
||||
m_flushBufferSize += CENTRAL_HEADER_ENTRY_SIZE + System.Text.Encoding.UTF8.GetByteCount(file);
|
||||
if (m_usingZip64)
|
||||
m_flushBufferSize += CENTRAL_HEADER_ENTRY_SIZE_ZIP64_EXTRA;
|
||||
|
||||
|
||||
return ((ZipWriter)m_writer).WriteToStream(file, new ZipWriterEntryOptions()
|
||||
{
|
||||
DeflateCompressionLevel = hint == CompressionHint.Noncompressible ? SharpCompress.Compressors.Deflate.CompressionLevel.None : m_defaultCompressionLevel,
|
||||
@@ -452,8 +438,8 @@ namespace Duplicati.Library.Compression
|
||||
/// <returns>True if the file exists, false otherwise</returns>
|
||||
public bool FileExists(string file)
|
||||
{
|
||||
if (m_isWriting)
|
||||
throw new InvalidOperationException("Cannot read while writing");
|
||||
if (m_mode != ArchiveMode.Read)
|
||||
throw new InvalidOperationException(CannotReadWhileWriting);
|
||||
|
||||
return GetEntry(file) != null;
|
||||
}
|
||||
@@ -465,7 +451,7 @@ namespace Duplicati.Library.Compression
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_isWriting ? m_stream.Length : Archive.TotalSize;
|
||||
return m_mode == ArchiveMode.Write ? m_stream.Length : Archive.TotalSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,11 +459,11 @@ namespace Duplicati.Library.Compression
|
||||
/// The size of the current unflushed buffer
|
||||
/// </summary>
|
||||
public long FlushBufferSize
|
||||
{
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_flushBufferSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -514,8 +500,6 @@ namespace Duplicati.Library.Compression
|
||||
m_writer.Dispose();
|
||||
m_writer = null;
|
||||
|
||||
if (m_stream != null)
|
||||
m_stream.Dispose();
|
||||
m_stream = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Duplicati.Library.Interface;
|
||||
using Duplicati.Library.Utility;
|
||||
|
||||
namespace Duplicati.Library.Compression
|
||||
{
|
||||
public class SevenZipCompression: ICompressionHinting
|
||||
public class SevenZipCompression : ICompressionHinting
|
||||
{
|
||||
// next file starts a new stream if the previous stream is larger than this
|
||||
private const int kStreamThreshold = 1 << 20; // 1 MB
|
||||
|
||||
private FileStream m_file;
|
||||
private Stream m_stream;
|
||||
private ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter m_writer;
|
||||
private ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.PlainEncoder m_copyEncoder;
|
||||
private ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.ThreadedEncoder m_lzma2Encoder;
|
||||
@@ -33,9 +33,9 @@ namespace Duplicati.Library.Compression
|
||||
get { return m_lowOverheadMode; }
|
||||
set
|
||||
{
|
||||
if(m_lowOverheadMode != value)
|
||||
if (m_lowOverheadMode != value)
|
||||
{
|
||||
if(m_lzma2Encoder != null)
|
||||
if (m_lzma2Encoder != null)
|
||||
throw new NotSupportedException("Cannot change LowOverheadMode after writing has started.");
|
||||
|
||||
m_lowOverheadMode = value;
|
||||
@@ -50,29 +50,46 @@ namespace Duplicati.Library.Compression
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new zip instance.
|
||||
/// If the file exists and has a non-zero length we read it,
|
||||
/// otherwise we create a new archive.
|
||||
/// Access mode is specified by mode parameter.
|
||||
/// Note that stream would not be disposed by FileArchiveZip instance so
|
||||
/// you may reuse it and have to dispose it yourself.
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the file to read or write</param>
|
||||
/// <param name="stream">The stream to read or write depending access mode</param>
|
||||
/// <param name="mode">The archive acces mode</param>
|
||||
/// <param name="options">The options passed on the commandline</param>
|
||||
public SevenZipCompression(string filename, Dictionary<string, string> options)
|
||||
public SevenZipCompression(Stream stream, ArchiveMode mode, IDictionary<string, string> options)
|
||||
{
|
||||
InitializeCompression(options);
|
||||
// Preventing the stream being closed by ArchiveWriter so it could be reused.
|
||||
// Required for using with MemoryStream.
|
||||
m_stream = new ShaderStream(stream, true);
|
||||
if (mode == ArchiveMode.Write)
|
||||
InitializeArchiveWriter();
|
||||
else
|
||||
{
|
||||
stream.Position = 0;
|
||||
InitializeArchiveReader();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeCompression(IDictionary<string, string> options)
|
||||
{
|
||||
m_threadCount = DEFAULT_THREAD_COUNT;
|
||||
|
||||
string threadCountSetting;
|
||||
int threadCountValue;
|
||||
if(options != null
|
||||
if (options != null
|
||||
&& options.TryGetValue(THREAD_COUNT_OPTION, out threadCountSetting)
|
||||
&& Int32.TryParse(threadCountSetting, out threadCountValue)
|
||||
&& threadCountValue > 0)
|
||||
{
|
||||
// arbitrary limit to avoid stupid mistakes
|
||||
if(threadCountValue > MAX_THREAD_COUNT)
|
||||
if (threadCountValue > MAX_THREAD_COUNT)
|
||||
threadCountValue = MAX_THREAD_COUNT;
|
||||
|
||||
m_threadCount = threadCountValue;
|
||||
}
|
||||
|
||||
|
||||
string cplvl;
|
||||
int tmplvl;
|
||||
if (options.TryGetValue(COMPRESSION_LEVEL_OPTION, out cplvl) && int.TryParse(cplvl, out tmplvl))
|
||||
@@ -85,67 +102,59 @@ namespace Duplicati.Library.Compression
|
||||
|
||||
m_encoderProps.mLzmaProps.mLevel = tmplvl;
|
||||
m_encoderProps.mLzmaProps.mAlgo = Library.Utility.Utility.ParseBoolOption(options, COMPRESSION_FASTALGO_OPTION) ? 0 : 1;
|
||||
|
||||
var file = new FileInfo(filename);
|
||||
if(file.Exists && file.Length > 0)
|
||||
InitializeArchiveReader(filename);
|
||||
else
|
||||
InitializeArchiveWriter(filename);
|
||||
}
|
||||
|
||||
private void InitializeArchiveReader(string file)
|
||||
private void InitializeArchiveReader()
|
||||
{
|
||||
m_file = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete);
|
||||
m_archive = new master._7zip.Legacy.CArchiveDatabaseEx();
|
||||
m_reader = new master._7zip.Legacy.ArchiveReader();
|
||||
m_reader.Open(m_file);
|
||||
m_reader.Open(m_stream);
|
||||
m_reader.ReadDatabase(m_archive, null);
|
||||
m_archive.Fill();
|
||||
}
|
||||
|
||||
private void InitializeArchiveWriter(string file)
|
||||
private void InitializeArchiveWriter()
|
||||
{
|
||||
m_file = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Delete);
|
||||
m_writer = new ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter(m_file);
|
||||
m_writer = new ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter(m_stream);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(m_archive != null)
|
||||
if (m_archive != null)
|
||||
m_archive = null;
|
||||
|
||||
if(m_reader != null)
|
||||
if (m_reader != null)
|
||||
try { m_reader.Close(); }
|
||||
finally { m_reader = null; }
|
||||
|
||||
if(m_writer != null)
|
||||
if (m_writer != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(m_copyEncoder != null)
|
||||
if (m_copyEncoder != null)
|
||||
try
|
||||
{
|
||||
m_writer.ConnectEncoder(m_copyEncoder);
|
||||
m_copyEncoder.Dispose();
|
||||
}
|
||||
finally { m_copyEncoder = null; }
|
||||
|
||||
if(m_lzma2Encoder != null)
|
||||
|
||||
if (m_lzma2Encoder != null)
|
||||
try
|
||||
{
|
||||
m_writer.ConnectEncoder(m_lzma2Encoder);
|
||||
m_lzma2Encoder.Dispose();
|
||||
}
|
||||
finally { m_lzma2Encoder = null; }
|
||||
|
||||
|
||||
m_writer.WriteFinalHeader();
|
||||
}
|
||||
finally { m_writer = null; }
|
||||
}
|
||||
|
||||
if(m_file != null)
|
||||
try { m_file.Dispose(); }
|
||||
finally { m_file = null; }
|
||||
if (m_stream != null)
|
||||
try { m_stream.Dispose(); }
|
||||
finally { m_stream = null; }
|
||||
}
|
||||
|
||||
#region ICompression - Reader Methods
|
||||
@@ -162,26 +171,26 @@ namespace Duplicati.Library.Compression
|
||||
|
||||
public IEnumerable<KeyValuePair<string, long>> ListFilesWithSize(string prefix)
|
||||
{
|
||||
if(m_reader == null)
|
||||
if (m_reader == null)
|
||||
throw new InvalidOperationException(Strings.SevenZipCompression.NoReaderError);
|
||||
|
||||
StringComparison sc = Library.Utility.Utility.ClientFilenameStringComparision;
|
||||
|
||||
return
|
||||
return
|
||||
from n in m_reader.GetFiles(m_archive)
|
||||
where
|
||||
!n.IsDir &&
|
||||
(prefix == null || n.Name.StartsWith(prefix, sc))
|
||||
select new KeyValuePair<string, long>(n.Name, n.Size);
|
||||
where
|
||||
!n.IsDir &&
|
||||
(prefix == null || n.Name.StartsWith(prefix, sc))
|
||||
select new KeyValuePair<string, long>(n.Name, n.Size);
|
||||
}
|
||||
|
||||
|
||||
public DateTime GetLastWriteTime(string file)
|
||||
{
|
||||
var item = m_reader.GetFiles(m_archive).FirstOrDefault(x => x.Name == file);
|
||||
if(item == null)
|
||||
if (item == null)
|
||||
throw new FileNotFoundException(Strings.SevenZipCompression.FileNotFoundError, file);
|
||||
|
||||
if(!item.MTime.HasValue)
|
||||
if (!item.MTime.HasValue)
|
||||
return new DateTime(0);
|
||||
|
||||
return item.MTime.Value;
|
||||
@@ -189,14 +198,14 @@ namespace Duplicati.Library.Compression
|
||||
|
||||
public Stream OpenRead(string file)
|
||||
{
|
||||
if(string.IsNullOrEmpty(file))
|
||||
throw new ArgumentNullException("file");
|
||||
if (string.IsNullOrEmpty(file))
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
|
||||
if(m_reader == null)
|
||||
if (m_reader == null)
|
||||
throw new InvalidOperationException(Strings.SevenZipCompression.NoReaderError);
|
||||
|
||||
var item = m_reader.GetFiles(m_archive).FirstOrDefault(x => x.Name == file);
|
||||
if(item == null)
|
||||
if (item == null)
|
||||
throw new FileNotFoundException(Strings.SevenZipCompression.FileNotFoundError, file);
|
||||
|
||||
return m_reader.OpenStream(m_archive, m_reader.GetFileIndex(m_archive, item), null);
|
||||
@@ -211,7 +220,7 @@ namespace Duplicati.Library.Compression
|
||||
m_writer.ConnectEncoder(m_lzma2Encoder);
|
||||
}
|
||||
|
||||
private sealed class WriterEntry: ManagedLzma.LZMA.Master.SevenZip.IArchiveWriterEntry
|
||||
private sealed class WriterEntry : ManagedLzma.LZMA.Master.SevenZip.IArchiveWriterEntry
|
||||
{
|
||||
private string mName;
|
||||
private DateTime? mTimestamp;
|
||||
@@ -254,19 +263,19 @@ namespace Duplicati.Library.Compression
|
||||
|
||||
public Stream CreateFile(string file, CompressionHint hint, DateTime lastWrite)
|
||||
{
|
||||
if(string.IsNullOrEmpty(file))
|
||||
throw new ArgumentNullException("file");
|
||||
if (string.IsNullOrEmpty(file))
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
|
||||
if(m_writer == null)
|
||||
if (m_writer == null)
|
||||
throw new InvalidOperationException(Strings.SevenZipCompression.NoWriterError);
|
||||
|
||||
var entry = new WriterEntry(file, lastWrite);
|
||||
|
||||
if(hint != CompressionHint.Noncompressible)
|
||||
if (hint != CompressionHint.Noncompressible)
|
||||
{
|
||||
if(m_lzma2Encoder == null)
|
||||
if (m_lzma2Encoder == null)
|
||||
{
|
||||
if(m_lowOverheadMode)
|
||||
if (m_lowOverheadMode)
|
||||
m_lzma2Encoder = new ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.LzmaEncoder();
|
||||
else
|
||||
m_lzma2Encoder = new ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.Lzma2Encoder(m_threadCount, m_encoderProps);
|
||||
@@ -279,13 +288,13 @@ namespace Duplicati.Library.Compression
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_copyEncoder == null)
|
||||
if (m_copyEncoder == null)
|
||||
m_copyEncoder = new ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.PlainEncoder();
|
||||
|
||||
if(m_lzma2Encoder != null && m_lzma2Encoder == m_writer.CurrentEncoder)
|
||||
if (m_lzma2Encoder != null && m_lzma2Encoder == m_writer.CurrentEncoder)
|
||||
m_lzma2Encoder.SetOutputThreshold(kStreamThreshold); // rearm threshold so we can switch back
|
||||
|
||||
if(m_writer.CurrentEncoder != m_copyEncoder)
|
||||
if (m_writer.CurrentEncoder != m_copyEncoder)
|
||||
m_writer.ConnectEncoder(m_copyEncoder);
|
||||
|
||||
return m_copyEncoder.BeginWriteFile(entry);
|
||||
@@ -296,10 +305,10 @@ namespace Duplicati.Library.Compression
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_writer != null)
|
||||
if (m_writer != null)
|
||||
return m_writer.WrittenSize;
|
||||
else if(m_reader != null)
|
||||
return m_file.Length;
|
||||
else if (m_reader != null)
|
||||
return m_stream.Length;
|
||||
else
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
@@ -309,17 +318,17 @@ namespace Duplicati.Library.Compression
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_writer != null)
|
||||
if (m_writer != null)
|
||||
{
|
||||
long remaining = m_writer.CurrentSizeLimit - m_writer.WrittenSize;
|
||||
if(m_copyEncoder != null && m_copyEncoder != m_writer.CurrentEncoder)
|
||||
if (m_copyEncoder != null && m_copyEncoder != m_writer.CurrentEncoder)
|
||||
remaining += m_copyEncoder.UpperBound;
|
||||
if(m_lzma2Encoder != null && m_lzma2Encoder != m_writer.CurrentEncoder)
|
||||
if (m_lzma2Encoder != null && m_lzma2Encoder != m_writer.CurrentEncoder)
|
||||
remaining += m_lzma2Encoder.UpperBound;
|
||||
return remaining;
|
||||
}
|
||||
else if(m_reader != null)
|
||||
return m_file.Length;
|
||||
else if (m_reader != null)
|
||||
return m_stream.Length;
|
||||
else
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
@@ -392,12 +401,12 @@ namespace Duplicati.Library.Compression
|
||||
DEFAULT_THREAD_COUNT.ToString()),
|
||||
|
||||
new CommandLineArgument(
|
||||
COMPRESSION_LEVEL_OPTION,
|
||||
CommandLineArgument.ArgumentType.Enumeration,
|
||||
Strings.SevenZipCompression.CompressionlevelShort,
|
||||
COMPRESSION_LEVEL_OPTION,
|
||||
CommandLineArgument.ArgumentType.Enumeration,
|
||||
Strings.SevenZipCompression.CompressionlevelShort,
|
||||
Strings.SevenZipCompression.CompressionlevelLong,
|
||||
DEFAULT_COMPRESSION_LEVEL.ToString(),
|
||||
null,
|
||||
DEFAULT_COMPRESSION_LEVEL.ToString(),
|
||||
null,
|
||||
new string[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}),
|
||||
|
||||
new CommandLineArgument(
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Duplicati.Library.DynamicLoader
|
||||
{
|
||||
if (m_interfaces.ContainsKey(uri.Scheme))
|
||||
return (IBackend)Activator.CreateInstance(m_interfaces[uri.Scheme].GetType(), url, newOpts);
|
||||
else if (uri.Scheme.EndsWith("s"))
|
||||
else if (uri.Scheme.EndsWith("s", StringComparison.Ordinal))
|
||||
{
|
||||
var tmpscheme = uri.Scheme.Substring(0, uri.Scheme.Length - 1);
|
||||
if (m_interfaces.ContainsKey(tmpscheme))
|
||||
@@ -123,7 +123,7 @@ namespace Duplicati.Library.DynamicLoader
|
||||
IBackend b;
|
||||
if (m_interfaces.TryGetValue(uri.Scheme, out b) && b != null)
|
||||
return b.SupportedCommands;
|
||||
else if (uri.Scheme.EndsWith("s"))
|
||||
else if (uri.Scheme.EndsWith("s", StringComparison.Ordinal))
|
||||
{
|
||||
var tmpscheme = uri.Scheme.Substring(0, uri.Scheme.Length - 1);
|
||||
if (m_interfaces.ContainsKey(tmpscheme))
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#endregion
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Duplicati.Library.Interface;
|
||||
|
||||
@@ -59,17 +60,17 @@ namespace Duplicati.Library.DynamicLoader
|
||||
/// <param name="filename">The filename used to compress/decompress contents</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, string filename, Dictionary<string, string> options)
|
||||
public ICompression GetModule(string fileExtension, Stream stream, ArchiveMode mode, Dictionary<string, string> options)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileExtension))
|
||||
throw new ArgumentNullException("fileExtension");
|
||||
|
||||
LoadInterfaces();
|
||||
|
||||
|
||||
lock (m_lock)
|
||||
{
|
||||
if (m_interfaces.ContainsKey(fileExtension))
|
||||
return (ICompression)Activator.CreateInstance(m_interfaces[fileExtension].GetType(), filename, options);
|
||||
return (ICompression)Activator.CreateInstance(m_interfaces[fileExtension].GetType(), stream, mode, options);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@@ -125,17 +126,17 @@ namespace Duplicati.Library.DynamicLoader
|
||||
return _compressionLoader.GetSupportedCommands(key);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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 of the file used to compress/decompress contents</param>
|
||||
/// <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="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, string filename, Dictionary<string, string> options)
|
||||
public static ICompression GetModule(string fileextension, Stream stream, ArchiveMode mode, Dictionary<string, string> options)
|
||||
{
|
||||
return _compressionLoader.GetModule(fileextension, filename, options);
|
||||
return _compressionLoader.GetModule(fileextension, stream, mode, options);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Duplicati.Library.DynamicLoader
|
||||
/// <summary>
|
||||
/// Instanciates a specific encryption module, given the file extension and options
|
||||
/// </summary>
|
||||
/// <param name="fileExtension">The file extension to create the instance for</param>
|
||||
/// <param name="fileextension">The file extension to create the instance for</param>
|
||||
/// <param name="passphrase">The passphrase used to encrypt contents</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>
|
||||
|
||||
@@ -67,7 +67,6 @@ namespace Duplicati.Library.Encryption
|
||||
/// <summary>
|
||||
/// Constructs a new AES encryption/decyption instance
|
||||
/// </summary>
|
||||
/// <param name="key">The key used for encryption. The key gets stretched through SHA hashing to fit the key size requirements</param>
|
||||
public AESEncryption(string passphrase, Dictionary<string, string> options)
|
||||
{
|
||||
if(string.IsNullOrEmpty(passphrase))
|
||||
|
||||
@@ -222,7 +222,6 @@ namespace Duplicati.Library.Encryption
|
||||
/// </summary>
|
||||
/// <param name="args">The commandline arguments</param>
|
||||
/// <param name="input">The input stream</param>
|
||||
/// <param name="output">The output stream</param>
|
||||
private System.IO.Stream Execute(string args, System.IO.Stream input, bool encrypt)
|
||||
{
|
||||
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
|
||||
|
||||
@@ -221,7 +221,6 @@ namespace Duplicati.Library.Interface
|
||||
/// <param name="longDescription">The arguments long description</param>
|
||||
/// <param name="defaultValue">The default value of the argumen</param>
|
||||
/// <param name="aliases">A list of aliases for the command</param>
|
||||
/// <param name="values">A list of valid values for the command</param>
|
||||
public CommandLineArgument(string name, ArgumentType type, string shortDescription, string longDescription, string defaultValue, string[] aliases)
|
||||
: this(name, type, shortDescription, longDescription, defaultValue)
|
||||
{
|
||||
|
||||
@@ -19,10 +19,26 @@
|
||||
#endregion
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Duplicati.Library.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the possible modes for compression and decompression
|
||||
/// </summary>
|
||||
public enum ArchiveMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates compression i.e. write archive mode, which means that the stream must be writeable
|
||||
/// </summary>
|
||||
Write,
|
||||
/// <summary>
|
||||
/// Indicates decompression i.e. read archive mode, which means that the stream must be readable
|
||||
/// </summary>
|
||||
Read
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value that is given to the compressor as a hint
|
||||
/// to how compressible the file is
|
||||
@@ -43,9 +59,8 @@ namespace Duplicati.Library.Interface
|
||||
/// Indicates that the files is incompressible
|
||||
/// </summary>
|
||||
Noncompressible
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An interface for passing additional hints to the compressor
|
||||
/// about the expected contents of the volume
|
||||
@@ -60,18 +75,7 @@ namespace Duplicati.Library.Interface
|
||||
bool LowOverheadMode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface for accessing files in an archive, such as a folder or compressed file.
|
||||
/// All modules that implements compression must implement this interface.
|
||||
/// The classes that implements this interface MUST also
|
||||
/// implement a default constructor and a construtor that
|
||||
/// has the signature new(string file, Dictionary<string, string> options).
|
||||
/// The default constructor is used to construct an instance
|
||||
/// so the DisplayName and other values can be read.
|
||||
/// The other constructor is used to do the actual work.
|
||||
/// The input file may not exist or have zero length, in which case it should be created.
|
||||
/// </summary>
|
||||
public interface ICompression : IDisposable
|
||||
public interface IArchiveReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all files in the archive, matching the prefix, if any.
|
||||
@@ -86,35 +90,14 @@ namespace Duplicati.Library.Interface
|
||||
/// <param name="prefix">An optional prefix for limiting the files returned</param>
|
||||
/// <returns>All files in the archive, matching the prefix, if any</returns>
|
||||
IEnumerable<KeyValuePair<string, long>> ListFilesWithSize(string prefix);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a stream with data from the given file
|
||||
/// Returns a stream with data from the given file in archive.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to read the data from</param>
|
||||
/// <returns>A stream with data from the given file</returns>
|
||||
System.IO.Stream OpenRead(string file);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a file in the archive
|
||||
/// </summary>
|
||||
/// <param name="file">The file to create</param>
|
||||
/// <param name="hint">A hint to the compressor as to how compressible the file data is</param>
|
||||
/// <param name="lastWrite">The time the file was last written</param>
|
||||
/// <returns>A stream with the data to write into the file</returns>
|
||||
System.IO.Stream CreateFile(string file, CompressionHint hint, DateTime lastWrite);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if the specified file exists
|
||||
/// </summary>
|
||||
/// <param name="file">The name of the file to examine</param>
|
||||
/// <returns>True if the file exists, false otherwise</returns>
|
||||
bool FileExists(string file);
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the archive
|
||||
/// </summary>
|
||||
long Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last modification time for the file
|
||||
/// </summary>
|
||||
@@ -122,10 +105,48 @@ namespace Duplicati.Library.Interface
|
||||
/// <returns>The timestamp on the file</returns>
|
||||
DateTime GetLastWriteTime(string file);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if the specified file exists
|
||||
/// </summary>
|
||||
/// <param name="file">The name of the file to examine</param>
|
||||
/// <returns>True if the file exists, false otherwise</returns>
|
||||
bool FileExists(string file);
|
||||
}
|
||||
|
||||
public interface IArchiveWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a file in the archive.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to create in the archive</param>
|
||||
/// <param name="hint">A hint to the compressor as to how compressible the file data is</param>
|
||||
/// <param name="lastWrite">The time the file was last written</param>
|
||||
/// <returns>A stream with the data to write into the file</returns>
|
||||
System.IO.Stream CreateFile(string file, CompressionHint hint, DateTime lastWrite);
|
||||
|
||||
/// <summary>
|
||||
/// The size in bytes of the buffer that will be written when flushed
|
||||
/// </summary>
|
||||
long FlushBufferSize { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface for accessing files in an archive, such as a folder or compressed file.
|
||||
/// All modules that implements compression must implement this interface.
|
||||
/// The classes that implements this interface MUST also
|
||||
/// implement a default constructor and a construtor that
|
||||
/// has the signature new(string file, Dictionary<string, string> options).
|
||||
/// The default constructor is used to construct an instance
|
||||
/// so the DisplayName and other values can be read.
|
||||
/// The other constructor is used to do the actual work.
|
||||
/// The input file may not exist or have zero length, in which case it should be created.
|
||||
/// </summary>
|
||||
public interface ICompression : IDisposable, IArchiveReader, IArchiveWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The total size of the archive.
|
||||
/// </summary>
|
||||
long Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The extension that the compression implementation adds to the filename
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace Duplicati.Library.Interface
|
||||
/// <summary>
|
||||
/// Called when the operation finishes
|
||||
/// </summary>
|
||||
/// <param name="operationname">The full name of the operation</param>
|
||||
/// <param name="result">The result object, if this derives from an exception, the operation failed</param>
|
||||
void OnFinish(object result);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Duplicati.Library.Logging
|
||||
m_stream.WriteLine("{0:u} - {1}: {2}", DateTime.UtcNow.ToString("u"), type, message);
|
||||
if (exception != null)
|
||||
{
|
||||
m_stream.WriteLine(exception.ToString());
|
||||
m_stream.WriteLine(exception);
|
||||
m_stream.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Duplicati.Library.Main
|
||||
internal class BackendManager : IDisposable
|
||||
{
|
||||
public const string VOLUME_HASH = "SHA256";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Class to represent hash failures
|
||||
/// </summary>
|
||||
@@ -38,7 +38,7 @@ namespace Duplicati.Library.Main
|
||||
/// <param name="innerException">The exception that caused this exception</param>
|
||||
public HashMismatchException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
|
||||
|
||||
private enum OperationType
|
||||
{
|
||||
Get,
|
||||
@@ -162,20 +162,20 @@ namespace Duplicati.Library.Main
|
||||
this.WaitForComplete();
|
||||
if (Exception != null)
|
||||
throw Exception;
|
||||
|
||||
|
||||
return (TempFile)this.Result;
|
||||
}
|
||||
|
||||
TempFile IDownloadWaitHandle.Wait(out string hash, out long size)
|
||||
{
|
||||
this.WaitForComplete();
|
||||
|
||||
|
||||
if (Exception != null)
|
||||
throw Exception;
|
||||
|
||||
|
||||
hash = this.Hash;
|
||||
size = this.Size;
|
||||
|
||||
|
||||
return (TempFile)this.Result;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace Duplicati.Library.Main
|
||||
this.Encrypted = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool UpdateHashAndSize(Options options)
|
||||
{
|
||||
if (Hash == null || Size < 0)
|
||||
@@ -204,7 +204,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void DeleteLocalFile(IBackendWriter stat)
|
||||
{
|
||||
if (this.LocalTempfile != null)
|
||||
@@ -212,9 +212,9 @@ namespace Duplicati.Library.Main
|
||||
catch (Exception ex) { stat.AddWarning(string.Format("Failed to dispose temporary file: {0}", this.LocalTempfile), ex); }
|
||||
finally { this.LocalTempfile = null; }
|
||||
}
|
||||
|
||||
|
||||
public BackendActionType BackendActionType
|
||||
{
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (this.Operation)
|
||||
@@ -234,56 +234,7 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary> A small utility stream that allows to keep streams open and counts the bytes sent through. </summary>
|
||||
private class ShaderStream : System.IO.Stream
|
||||
{
|
||||
private readonly System.IO.Stream m_baseStream;
|
||||
private readonly bool m_keepBaseOpen;
|
||||
private long m_read = 0;
|
||||
private long m_written = 0;
|
||||
|
||||
public ShaderStream(System.IO.Stream baseStream, bool keepBaseOpen)
|
||||
{
|
||||
if (baseStream == null) throw new ArgumentNullException("baseStream");
|
||||
this.m_baseStream = baseStream;
|
||||
this.m_keepBaseOpen = keepBaseOpen;
|
||||
}
|
||||
|
||||
public long TotalBytesRead { get { return m_read; } }
|
||||
public long TotalBytesWritten { get { return m_written; } }
|
||||
|
||||
public override bool CanRead { get { return m_baseStream.CanRead; } }
|
||||
public override bool CanSeek { get { return m_baseStream.CanSeek; } }
|
||||
public override bool CanWrite { get { return m_baseStream.CanWrite; } }
|
||||
public override long Length { get { return m_baseStream.Length; } }
|
||||
public override long Position
|
||||
{
|
||||
get { return m_baseStream.Position; }
|
||||
set { m_baseStream.Position = value; }
|
||||
}
|
||||
public override void Flush() { m_baseStream.Flush(); }
|
||||
public override long Seek(long offset, System.IO.SeekOrigin origin) { return m_baseStream.Seek(offset, origin); }
|
||||
public override void SetLength(long value) { m_baseStream.SetLength(value); }
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int r = m_baseStream.Read(buffer, offset, count);
|
||||
m_read += r;
|
||||
return r;
|
||||
}
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
m_baseStream.Write(buffer, offset, count);
|
||||
m_written += count;
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && !m_keepBaseOpen)
|
||||
m_baseStream.Close();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
private class DatabaseCollector
|
||||
@@ -293,16 +244,16 @@ namespace Duplicati.Library.Main
|
||||
private System.Threading.Thread m_callerThread;
|
||||
private List<IDbEntry> m_dbqueue;
|
||||
private IBackendWriter m_stats;
|
||||
|
||||
|
||||
private interface IDbEntry { }
|
||||
|
||||
|
||||
private class DbOperation : IDbEntry
|
||||
{
|
||||
public string Action;
|
||||
public string File;
|
||||
public string Result;
|
||||
}
|
||||
|
||||
|
||||
private class DbUpdate : IDbEntry
|
||||
{
|
||||
public string Remotename;
|
||||
@@ -310,53 +261,53 @@ namespace Duplicati.Library.Main
|
||||
public long Size;
|
||||
public string Hash;
|
||||
}
|
||||
|
||||
|
||||
private class DbRename : IDbEntry
|
||||
{
|
||||
public string Oldname;
|
||||
public string Newname;
|
||||
}
|
||||
|
||||
|
||||
public DatabaseCollector(LocalDatabase database, IBackendWriter stats)
|
||||
{
|
||||
m_database = database;
|
||||
m_stats = stats;
|
||||
m_dbqueue = new List<IDbEntry>();
|
||||
m_dbqueue = new List<IDbEntry>();
|
||||
if (m_database != null)
|
||||
m_callerThread = System.Threading.Thread.CurrentThread;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void LogDbOperation(string action, string file, string result)
|
||||
{
|
||||
lock(m_dbqueuelock)
|
||||
lock (m_dbqueuelock)
|
||||
m_dbqueue.Add(new DbOperation() { Action = action, File = file, Result = result });
|
||||
}
|
||||
|
||||
|
||||
public void LogDbUpdate(string remotename, RemoteVolumeState state, long size, string hash)
|
||||
{
|
||||
lock(m_dbqueuelock)
|
||||
lock (m_dbqueuelock)
|
||||
m_dbqueue.Add(new DbUpdate() { Remotename = remotename, State = state, Size = size, Hash = hash });
|
||||
}
|
||||
|
||||
public void LogDbRename(string oldname, string newname)
|
||||
{
|
||||
lock(m_dbqueuelock)
|
||||
lock (m_dbqueuelock)
|
||||
m_dbqueue.Add(new DbRename() { Oldname = oldname, Newname = newname });
|
||||
}
|
||||
|
||||
|
||||
public bool FlushDbMessages(bool checkThread = false)
|
||||
{
|
||||
if (m_database != null && (checkThread == false || m_callerThread == System.Threading.Thread.CurrentThread))
|
||||
return FlushDbMessages(m_database, null);
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public bool FlushDbMessages(LocalDatabase db, System.Data.IDbTransaction transaction)
|
||||
{
|
||||
List<IDbEntry> entries;
|
||||
lock(m_dbqueuelock)
|
||||
lock (m_dbqueuelock)
|
||||
if (m_dbqueue.Count == 0)
|
||||
return false;
|
||||
else
|
||||
@@ -369,7 +320,7 @@ namespace Duplicati.Library.Main
|
||||
HashSet<string> volsRemoved = new HashSet<string>();
|
||||
|
||||
//As we replace the list, we can now freely access the elements without locking
|
||||
foreach(var e in entries)
|
||||
foreach (var e in entries)
|
||||
if (e is DbOperation)
|
||||
db.LogRemoteOperation(((DbOperation)e).Action, ((DbOperation)e).File, ((DbOperation)e).Result, transaction);
|
||||
else if (e is DbUpdate && ((DbUpdate)e).State == RemoteVolumeState.Deleted)
|
||||
@@ -382,7 +333,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.ToString()), null);
|
||||
m_stats.AddError(string.Format("Queue had element of type: {0}, {1}", e.GetType(), e), null);
|
||||
|
||||
// Finally remove volumes from DB.
|
||||
if (volsRemoved.Count > 0)
|
||||
@@ -407,9 +358,9 @@ namespace Duplicati.Library.Main
|
||||
// Cache these
|
||||
private readonly int m_numberofretries;
|
||||
private readonly TimeSpan m_retrydelay;
|
||||
|
||||
|
||||
public string BackendUrl { get { return m_backendurl; } }
|
||||
|
||||
|
||||
public bool HasDied { get { return m_lastException != null; } }
|
||||
public Exception LastException { get { return m_lastException; } }
|
||||
|
||||
@@ -444,7 +395,7 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
|
||||
if (m_taskControl != null)
|
||||
m_taskControl.StateChangedEvent += (state) => {
|
||||
m_taskControl.StateChangedEvent += (state) => {
|
||||
if (state == TaskControlState.Abort)
|
||||
m_thread.Abort();
|
||||
};
|
||||
@@ -509,7 +460,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
if (m_taskControl != null)
|
||||
m_taskControl.TaskControlRendevouz();
|
||||
|
||||
|
||||
if (m_options.NoConnectionReuse && m_backend != null)
|
||||
{
|
||||
m_backend.Dispose();
|
||||
@@ -521,7 +472,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(string.Format("RemoteOperation{0}", item.Operation)))
|
||||
switch (item.Operation)
|
||||
{
|
||||
case OperationType.Put:
|
||||
@@ -558,7 +509,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);
|
||||
|
||||
|
||||
// If the thread is aborted, we exit here
|
||||
if (ex is System.Threading.ThreadAbortException)
|
||||
{
|
||||
@@ -567,33 +518,33 @@ namespace Duplicati.Library.Main
|
||||
item.SignalComplete();
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
m_statwriter.SendEvent(item.BackendActionType, retries < m_numberofretries ? BackendEventType.Retrying : BackendEventType.Failed, item.RemoteFilename, item.Size);
|
||||
|
||||
bool recovered = false;
|
||||
if (!uploadSuccess && ex is Duplicati.Library.Interface.FolderMissingException && m_options.AutocreateFolders)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
// If we successfully create the folder, we can re-use the connection
|
||||
m_backend.CreateFolder();
|
||||
m_backend.CreateFolder();
|
||||
recovered = true;
|
||||
}
|
||||
catch(Exception dex)
|
||||
{
|
||||
m_statwriter.AddWarning(string.Format("Failed to create folder: {0}", ex.Message), dex);
|
||||
catch (Exception dex)
|
||||
{
|
||||
m_statwriter.AddWarning(string.Format("Failed to create folder: {0}", ex.Message), dex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// To work around the Apache WEBDAV issue, we rename the file here
|
||||
if (item.Operation == OperationType.Put && retries < m_numberofretries && !item.NotTrackedInDb)
|
||||
RenameFileAfterError(item);
|
||||
|
||||
|
||||
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) { m_statwriter.AddWarning(LC.L("Failed to dispose backend instance: {0}", ex.Message), dex); }
|
||||
|
||||
m_backend = null;
|
||||
|
||||
if (retries < m_numberofretries && m_retrydelay.Ticks != 0)
|
||||
@@ -603,13 +554,13 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
if (m_taskControl != null && m_taskControl.IsAbortRequested())
|
||||
break;
|
||||
|
||||
|
||||
System.Threading.Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} while (retries < m_numberofretries);
|
||||
|
||||
@@ -624,7 +575,7 @@ namespace Duplicati.Library.Main
|
||||
m_statwriter.AddMessage(LC.L("Recovered from problem with attempting to delete non-existing file {0}", item.RemoteFilename));
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
m_statwriter.AddWarning(LC.L("Failed to recover from error deleting file {0}", item.RemoteFilename), ex);
|
||||
}
|
||||
@@ -635,17 +586,17 @@ namespace Duplicati.Library.Main
|
||||
item.Exception = lastException;
|
||||
if (item.Operation == OperationType.Put)
|
||||
item.DeleteLocalFile(m_statwriter);
|
||||
|
||||
|
||||
if (item.ExceptionKillsHandler)
|
||||
{
|
||||
m_lastException = lastException;
|
||||
|
||||
//TODO: If there are temp files in the queue, we must delete them
|
||||
m_queue.SetCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
item.SignalComplete();
|
||||
}
|
||||
}
|
||||
@@ -663,13 +614,13 @@ namespace Duplicati.Library.Main
|
||||
var time = p.Time.Ticks == 0 ? p.Time : p.Time.AddSeconds(1);
|
||||
var newname = VolumeBase.GenerateFilename(p.FileType, p.Prefix, guid, time, p.CompressionModule, p.EncryptionModule);
|
||||
var oldname = item.RemoteFilename;
|
||||
|
||||
|
||||
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));
|
||||
m_db.LogDbRename(oldname, newname);
|
||||
item.RemoteFilename = newname;
|
||||
|
||||
|
||||
// If there is an index file attached to the block file,
|
||||
// it references the block filename, so we create a new index file
|
||||
// which is a copy of the current, but with the new name
|
||||
@@ -681,13 +632,13 @@ namespace Duplicati.Library.Main
|
||||
item.Indexfile.Item1.Close();
|
||||
item.IndexfileUpdated = true;
|
||||
}
|
||||
|
||||
|
||||
IndexVolumeWriter wr = null;
|
||||
try
|
||||
{
|
||||
var hashsize = HashAlgorithmHelper.Create(m_options.BlockHashAlgorithm).HashSize / 8;
|
||||
wr = new IndexVolumeWriter(m_options);
|
||||
using(var rd = new IndexVolumeReader(p.CompressionModule, item.Indexfile.Item2.LocalFilename, m_options, hashsize))
|
||||
using (var rd = new IndexVolumeReader(p.CompressionModule, item.Indexfile.Item2.LocalFilename, m_options, hashsize))
|
||||
wr.CopyFrom(rd, x => x == oldname ? newname : x);
|
||||
item.Indexfile.Item1.Dispose();
|
||||
item.Indexfile = new Tuple<IndexVolumeWriter, FileEntryItem>(wr, item.Indexfile.Item2);
|
||||
@@ -701,7 +652,7 @@ namespace Duplicati.Library.Main
|
||||
try { wr.Dispose(); }
|
||||
catch { }
|
||||
finally { wr = null; }
|
||||
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -739,9 +690,9 @@ namespace Duplicati.Library.Main
|
||||
private void DoPut(FileEntryItem item)
|
||||
{
|
||||
if (m_encryption != null)
|
||||
lock(m_encryptionLock)
|
||||
lock (m_encryptionLock)
|
||||
item.Encrypt(m_encryption, m_statwriter);
|
||||
|
||||
|
||||
if (item.UpdateHashAndSize(m_options) && !item.NotTrackedInDb)
|
||||
m_db.LogDbUpdate(item.RemoteFilename, RemoteVolumeState.Uploading, item.Size, item.Hash);
|
||||
|
||||
@@ -750,7 +701,7 @@ namespace Duplicati.Library.Main
|
||||
item.Indexfile.Item1.FinishVolume(item.Hash, item.Size);
|
||||
item.Indexfile.Item1.Close();
|
||||
item.IndexfileUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_db.LogDbOperation("put", item.RemoteFilename, JsonConvert.SerializeObject(new { Size = item.Size, Hash = item.Hash }));
|
||||
m_statwriter.SendEvent(BackendActionType.Put, BackendEventType.Started, item.RemoteFilename, item.Size);
|
||||
@@ -772,7 +723,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
if (!item.NotTrackedInDb)
|
||||
m_db.LogDbUpdate(item.RemoteFilename, RemoteVolumeState.Uploaded, item.Size, item.Hash);
|
||||
|
||||
|
||||
m_statwriter.SendEvent(BackendActionType.Put, BackendEventType.Completed, item.RemoteFilename, item.Size);
|
||||
|
||||
if (m_options.ListVerifyUploads)
|
||||
@@ -1079,7 +1030,7 @@ namespace Duplicati.Library.Main
|
||||
item.Result = tmpfile;
|
||||
tmpfile = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -1147,7 +1098,7 @@ namespace Duplicati.Library.Main
|
||||
m_statwriter.AddMessage(LC.L("Listing indicates file {0} is deleted correctly", item.RemoteFilename));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
result = ex.ToString();
|
||||
@@ -1157,11 +1108,11 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
m_db.LogDbOperation("delete", item.RemoteFilename, result);
|
||||
}
|
||||
|
||||
|
||||
m_db.LogDbUpdate(item.RemoteFilename, RemoteVolumeState.Deleted, -1, null);
|
||||
m_statwriter.SendEvent(BackendActionType.Delete, BackendEventType.Completed, item.RemoteFilename, item.Size);
|
||||
}
|
||||
|
||||
|
||||
private void DoCreateFolder(FileEntryItem item)
|
||||
{
|
||||
m_statwriter.SendEvent(BackendActionType.CreateFolder, BackendEventType.Started, null, -1);
|
||||
@@ -1170,7 +1121,7 @@ namespace Duplicati.Library.Main
|
||||
try
|
||||
{
|
||||
m_backend.CreateFolder();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result = ex.ToString();
|
||||
@@ -1180,15 +1131,15 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
m_db.LogDbOperation("createfolder", item.RemoteFilename, result);
|
||||
}
|
||||
|
||||
|
||||
m_statwriter.SendEvent(BackendActionType.CreateFolder, BackendEventType.Completed, null, -1);
|
||||
}
|
||||
|
||||
|
||||
public void PutUnencrypted(string remotename, string localpath)
|
||||
{
|
||||
if (m_lastException != null)
|
||||
throw m_lastException;
|
||||
|
||||
|
||||
var req = new FileEntryItem(OperationType.Put, remotename, null);
|
||||
req.SetLocalfilename(localpath);
|
||||
req.Encrypted = true; //Prevent encryption
|
||||
@@ -1208,7 +1159,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
m_statwriter.BackendProgressUpdater.SetBlocking(false);
|
||||
}
|
||||
|
||||
|
||||
if (m_lastException != null)
|
||||
throw m_lastException;
|
||||
}
|
||||
@@ -1217,23 +1168,23 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
if (m_lastException != null)
|
||||
throw m_lastException;
|
||||
|
||||
|
||||
item.Close();
|
||||
var req = new FileEntryItem(OperationType.Put, item.RemoteFilename, null);
|
||||
req.LocalTempfile = item.TempFile;
|
||||
|
||||
|
||||
if (m_lastException != null)
|
||||
throw m_lastException;
|
||||
|
||||
FileEntryItem req2 = null;
|
||||
|
||||
|
||||
// As the network link is the bottleneck,
|
||||
// we encrypt the dblock volume before the
|
||||
// upload is enqueued (i.e. on the worker thread)
|
||||
if (m_encryption != null)
|
||||
lock (m_encryptionLock)
|
||||
req.Encrypt(m_encryption, m_statwriter);
|
||||
|
||||
|
||||
req.UpdateHashAndSize(m_options);
|
||||
m_db.LogDbUpdate(item.RemoteFilename, RemoteVolumeState.Uploading, req.Size, req.Hash);
|
||||
|
||||
@@ -1270,7 +1221,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
m_statwriter.BackendProgressUpdater.SetBlocking(false);
|
||||
}
|
||||
|
||||
|
||||
if (m_lastException != null)
|
||||
throw m_lastException;
|
||||
}
|
||||
@@ -1344,12 +1295,12 @@ namespace Duplicati.Library.Main
|
||||
else
|
||||
throw new InvalidOperationException("GetAsync called after backend is shut down");
|
||||
}
|
||||
|
||||
|
||||
public void GetForTesting(string remotename, long size, string hash)
|
||||
{
|
||||
if (m_lastException != null)
|
||||
throw m_lastException;
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(hash))
|
||||
throw new InvalidOperationException("Cannot test a file without the hash");
|
||||
|
||||
@@ -1372,7 +1323,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
if (m_lastException != null)
|
||||
throw m_lastException;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<Library.Interface.IFileEntry> List()
|
||||
{
|
||||
@@ -1477,7 +1428,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
if (m_lastException != null)
|
||||
throw m_lastException;
|
||||
|
||||
|
||||
m_db.LogDbUpdate(remotename, RemoteVolumeState.Deleting, size, null);
|
||||
var req = new FileEntryItem(OperationType.Delete, remotename, size, null);
|
||||
try
|
||||
@@ -1498,12 +1449,12 @@ namespace Duplicati.Library.Main
|
||||
if (m_lastException != null)
|
||||
throw m_lastException;
|
||||
}
|
||||
|
||||
|
||||
public bool FlushDbMessages(LocalDatabase database, System.Data.IDbTransaction transaction)
|
||||
{
|
||||
return m_db.FlushDbMessages(database, transaction);
|
||||
}
|
||||
|
||||
|
||||
public bool FlushDbMessages()
|
||||
{
|
||||
return m_db.FlushDbMessages(false);
|
||||
@@ -1513,7 +1464,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
if (m_queue != null && !m_queue.Completed)
|
||||
m_queue.SetCompleted();
|
||||
|
||||
|
||||
if (m_thread != null)
|
||||
{
|
||||
if (!m_thread.Join(TimeSpan.FromSeconds(10)))
|
||||
@@ -1524,7 +1475,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
m_thread = null;
|
||||
}
|
||||
|
||||
|
||||
//TODO: We cannot null this, because it will be recreated
|
||||
//Should we wait for queue completion or abort immediately?
|
||||
if (m_backend != null)
|
||||
@@ -1532,7 +1483,7 @@ namespace Duplicati.Library.Main
|
||||
m_backend.Dispose();
|
||||
m_backend = null;
|
||||
}
|
||||
|
||||
|
||||
try { m_db.FlushDbMessages(true); }
|
||||
catch (Exception ex) { m_statwriter.AddError(string.Format("Backend Shutdown error: {0}", ex.Message), ex); }
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
List<string> expandedSources = new List<string>();
|
||||
|
||||
if (Library.Utility.Utility.IsClientWindows && (inputsources[i].StartsWith("*:") || inputsources[i].StartsWith("?:")))
|
||||
if (Library.Utility.Utility.IsClientWindows && (inputsources[i].StartsWith("*:", StringComparison.Ordinal) || inputsources[i].StartsWith("?:", StringComparison.Ordinal)))
|
||||
{
|
||||
// *: drive paths are only supported on Windows clients
|
||||
// Lazily load the drive info
|
||||
|
||||
@@ -275,7 +275,6 @@ namespace Duplicati.Library.Main.Database
|
||||
/// Adds a block to the local database, returning a value indicating if the value presents a new block
|
||||
/// </summary>
|
||||
/// <param name="key">The block key</param>
|
||||
/// <param name="archivename">The name of the archive that holds the data</param>
|
||||
/// <returns>True if the block should be added to the current output</returns>
|
||||
public bool AddBlock (string key, long size, long volumeid, System.Data.IDbTransaction transaction = null)
|
||||
{
|
||||
@@ -319,8 +318,6 @@ namespace Duplicati.Library.Main.Database
|
||||
/// </summary>
|
||||
/// <param name="filehash">The hash of the blockset</param>
|
||||
/// <param name="size">The size of the blockset</param>
|
||||
/// <param name="fragmentoffset">The fragmentoffset for the last block</param>
|
||||
/// <param name="fragmenthash">The hash of the fragment</param>
|
||||
/// <param name="hashes">The list of hashes</param>
|
||||
/// <param name="blocksetid">The id of the blockset, new or old</param>
|
||||
/// <returns>True if the blockset was created, false otherwise</returns>
|
||||
@@ -396,7 +393,7 @@ namespace Duplicati.Library.Main.Database
|
||||
/// <summary>
|
||||
/// Adds a metadata set to the database, and returns a value indicating if the record was new
|
||||
/// </summary>
|
||||
/// <param name="hash">The metadata hash</param>
|
||||
/// <param name="filehash">The metadata hash</param>
|
||||
/// <param name="metadataid">The id of the metadata set</param>
|
||||
/// <returns>True if the set was added to the database, false otherwise</returns>
|
||||
public bool AddMetadataset(string filehash, long size, int blocksize, IEnumerable<string> blockhashes, IEnumerable<string> blocklisthashes, out long metadataid, System.Data.IDbTransaction transaction = null)
|
||||
@@ -436,7 +433,6 @@ namespace Duplicati.Library.Main.Database
|
||||
/// <param name="blocksetID">The ID of the hashkey for the file</param>
|
||||
/// <param name="metadataID">The ID for the metadata</param>
|
||||
/// <param name="transaction">The transaction to use for insertion, or null for no transaction</param>
|
||||
/// <param name="operationId">The operationId to use, or -1 to use the current operation</param>
|
||||
public void AddFile(string filename, DateTime lastmodified, long blocksetID, long metadataID, System.Data.IDbTransaction transaction)
|
||||
{
|
||||
var fileidobj = -1L;
|
||||
|
||||
@@ -63,8 +63,6 @@ namespace Duplicati.Library.Main.Database
|
||||
/// <summary>
|
||||
/// Creates a new database instance and starts a new operation
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the database</param>
|
||||
/// <param name="operation">The name of the operation</param>
|
||||
public LocalDatabase(LocalDatabase db)
|
||||
: this(db.m_connection)
|
||||
{
|
||||
@@ -77,7 +75,6 @@ namespace Duplicati.Library.Main.Database
|
||||
/// <summary>
|
||||
/// Creates a new database instance and starts a new operation
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the database</param>
|
||||
/// <param name="operation">The name of the operation</param>
|
||||
public LocalDatabase(System.Data.IDbConnection connection, string operation)
|
||||
: this(connection)
|
||||
@@ -1198,7 +1195,7 @@ ORDER BY
|
||||
{
|
||||
cmd.Transaction = tr.Parent;
|
||||
cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL)", Tablename));
|
||||
cmd.ExecuteNonQuery(string.Format(@"INSERT INTO ""{0}"" SELECT DISTINCT ""Path"" FROM ""File"" WHERE " + sb.ToString(), Tablename), args.ToArray());
|
||||
cmd.ExecuteNonQuery(string.Format(@"INSERT INTO ""{0}"" SELECT DISTINCT ""Path"" FROM ""File"" WHERE " + sb, Tablename), args.ToArray());
|
||||
tr.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,6 @@ namespace Duplicati.Library.Main.Database
|
||||
/// <summary>
|
||||
/// Builds a lookup table to enable faster response to block queries
|
||||
/// </summary>
|
||||
/// <param name="volumename">The name of the volume to prepare for</param>
|
||||
public IBlockQuery CreateBlockQueryHelper(Options options, System.Data.IDbTransaction transaction)
|
||||
{
|
||||
return new BlockQuery(m_connection, options, transaction);
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace Duplicati.Library.Main.Database
|
||||
while (rd.Read())
|
||||
{
|
||||
var s = rd.GetString(0);
|
||||
if (!s.StartsWith(prefix))
|
||||
if (!s.StartsWith(prefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var dirsep = Duplicati.Library.Utility.Utility.GuessDirSeparator(s);
|
||||
@@ -266,8 +266,8 @@ namespace Duplicati.Library.Main.Database
|
||||
//Then we select the matching results
|
||||
var filesets = string.Format(@"SELECT ""FilesetID"", ""Timestamp"" FROM ""{0}"" ORDER BY ""Timestamp"" DESC", m_tablename);
|
||||
var cartesianPathFileset = string.Format(@"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{0}"" A, (" + filesets + @") B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC", tbname, m_tablename);
|
||||
|
||||
var filesWithSizes = @"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" ";
|
||||
|
||||
var filesWithSizes = string.Format(@"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND FilesetEntry.""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{0}"") ", m_tablename);
|
||||
var query = @"SELECT ""C"".""Path"", ""D"".""Length"", ""C"".""FilesetID"" FROM (" + cartesianPathFileset + @") C LEFT OUTER JOIN (" + filesWithSizes + @") D ON ""C"".""FilesetID"" = ""D"".""FilesetID"" AND ""C"".""Path"" = ""D"".""Path""";
|
||||
|
||||
cmd.AddParameter(pathprefix, "1");
|
||||
@@ -318,7 +318,7 @@ namespace Duplicati.Library.Main.Database
|
||||
//Then we select the matching results
|
||||
var filesets = string.Format(@"SELECT ""FilesetID"", ""Timestamp"" FROM ""{0}"" ORDER BY ""Timestamp"" DESC", m_tablename);
|
||||
var cartesianPathFileset = string.Format(@"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{0}"" A, (" + filesets + @") B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC", tmpnames.Tablename, m_tablename);
|
||||
var filesWithSizes = @"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" ";
|
||||
var filesWithSizes = string.Format(@"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND FilesetEntry.""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{0}"") ", m_tablename);
|
||||
var query = @"SELECT ""C"".""Path"", ""D"".""Length"", ""C"".""FilesetID"" FROM (" + cartesianPathFileset + @") C LEFT OUTER JOIN (" + filesWithSizes + @") D ON ""C"".""FilesetID"" = ""D"".""FilesetID"" AND ""C"".""Path"" = ""D"".""Path""";
|
||||
using(var rd = cmd.ExecuteReader(query))
|
||||
if(rd.Read())
|
||||
@@ -368,7 +368,7 @@ namespace Duplicati.Library.Main.Database
|
||||
for(var i = 0; i < m_filesets.Length; i++)
|
||||
dict[m_filesets[i].Key] = i;
|
||||
|
||||
var summation = @"SELECT ""A"".""FilesetID"" AS ""FilesetID"", COUNT(*) AS ""FileCount"", SUM(""C"".""Length"") AS ""FileSizes"" FROM ""FilesetEntry"" A, ""File"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" GROUP BY ""A"".""FilesetID"" ";
|
||||
var summation = string.Format(@"SELECT ""A"".""FilesetID"" AS ""FilesetID"", COUNT(*) AS ""FileCount"", SUM(""C"".""Length"") AS ""FileSizes"" FROM ""FilesetEntry"" A, ""File"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" AND ""A"".""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{0}"") GROUP BY ""A"".""FilesetID"" ",m_tablename);
|
||||
|
||||
using(var cmd = m_connection.CreateCommand())
|
||||
using (var rd = cmd.ExecuteReader(string.Format(@"SELECT DISTINCT ""A"".""FilesetID"", ""B"".""FileCount"", ""B"".""FileSizes"" FROM ""{0}"" A LEFT OUTER JOIN ( " + summation + @" ) B ON ""A"".""FilesetID"" = ""B"".""FilesetID"" ORDER BY ""A"".""Timestamp"" DESC ", m_tablename)))
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace Duplicati.Library.Main.Database
|
||||
{
|
||||
m_fileprogtable = null;
|
||||
m_totalprogtable = null;
|
||||
Console.WriteLine(ex.ToString());
|
||||
Console.WriteLine(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,6 @@ namespace Duplicati.Library.Main
|
||||
return false;
|
||||
|
||||
var file = System.IO.Path.Combine(folder, "dbconfig.json");
|
||||
List<BackendEntry> configs;
|
||||
if (!System.IO.File.Exists(file))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1339,9 +1339,6 @@ namespace Duplicati.Library.Main.Operation
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the file to record</param>
|
||||
/// <param name="lastModified">The value of the lastModified timestamp</param>
|
||||
/// <param name="hashlist">The list of hashes that make up the file</param>
|
||||
/// <param name="size">The size of the file</param>
|
||||
/// <param name="fragmentoffset">The offset into a fragment block where the last few bytes are stored</param>
|
||||
/// <param name="metadata">A lookup table with various metadata values describing the file</param>
|
||||
private void AddFolderToOutput(BackendManager backend, string filename, DateTime lastModified, IMetahash metadata)
|
||||
{
|
||||
@@ -1354,9 +1351,6 @@ namespace Duplicati.Library.Main.Operation
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the file to record</param>
|
||||
/// <param name="lastModified">The value of the lastModified timestamp</param>
|
||||
/// <param name="hashlist">The list of hashes that make up the file</param>
|
||||
/// <param name="size">The size of the file</param>
|
||||
/// <param name="fragmentoffset">The offset into a fragment block where the last few bytes are stored</param>
|
||||
/// <param name="metadata">A lookup table with various metadata values describing the file</param>
|
||||
private void AddSymlinkToOutput(BackendManager backend, string filename, DateTime lastModified, IMetahash metadata)
|
||||
{
|
||||
@@ -1368,10 +1362,9 @@ namespace Duplicati.Library.Main.Operation
|
||||
/// Adds a file to the output,
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the file to record</param>
|
||||
/// <param name="lastModified">The value of the lastModified timestamp</param>
|
||||
/// <param name="lastmodified">The value of the lastModified timestamp</param>
|
||||
/// <param name="hashlist">The list of hashes that make up the file</param>
|
||||
/// <param name="size">The size of the file</param>
|
||||
/// <param name="fragmentoffset">The offset into a fragment block where the last few bytes are stored</param>
|
||||
/// <param name="metadata">A lookup table with various metadata values describing the file</param>
|
||||
private void AddFileToOutput(BackendManager backend, string filename, long size, DateTime lastmodified, IMetahash metadata, IEnumerable<string> hashlist, string filehash, IEnumerable<string> blocklisthashes)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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.Interface;
|
||||
using Duplicati.Library.Main.Database;
|
||||
|
||||
@@ -33,7 +34,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
m_options = options;
|
||||
m_result = result;
|
||||
}
|
||||
|
||||
|
||||
public void Run()
|
||||
{
|
||||
var ext = System.IO.Path.GetExtension(m_targetpath);
|
||||
@@ -41,22 +42,22 @@ namespace Duplicati.Library.Main.Operation
|
||||
|
||||
if (ext == "" || string.Compare(ext, 1, module, 0, module.Length, StringComparison.OrdinalIgnoreCase) != 0)
|
||||
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));
|
||||
|
||||
|
||||
if (!System.IO.File.Exists(m_options.Dbpath))
|
||||
throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath));
|
||||
|
||||
|
||||
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");
|
||||
|
||||
using(var tmp = new Library.Utility.TempFile())
|
||||
using (var tmp = new Library.Utility.TempFile())
|
||||
{
|
||||
System.IO.File.Copy(m_options.Dbpath, tmp, true);
|
||||
using(var db = new LocalBugReportDatabase(tmp))
|
||||
using (var db = new LocalBugReportDatabase(tmp))
|
||||
{
|
||||
m_result.SetDatabase(db);
|
||||
db.Fix();
|
||||
@@ -65,20 +66,21 @@ namespace Duplicati.Library.Main.Operation
|
||||
db.Vacuum();
|
||||
}
|
||||
}
|
||||
|
||||
using(var cm = DynamicLoader.CompressionLoader.GetModule(module, m_targetpath, m_options.RawOptions))
|
||||
|
||||
using (var stream = new System.IO.FileStream(m_targetpath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
using (ICompression cm = DynamicLoader.CompressionLoader.GetModule(module, stream, Interface.ArchiveMode.Write, m_options.RawOptions))
|
||||
{
|
||||
using(var cs = cm.CreateFile("log-database.sqlite", Duplicati.Library.Interface.CompressionHint.Compressible, DateTime.UtcNow))
|
||||
using(var fs = System.IO.File.Open(tmp, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite))
|
||||
using (var cs = cm.CreateFile("log-database.sqlite", Duplicati.Library.Interface.CompressionHint.Compressible, DateTime.UtcNow))
|
||||
using (var fs = System.IO.File.Open(tmp, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite))
|
||||
Library.Utility.Utility.CopyStream(fs, cs);
|
||||
|
||||
using(var cs = new System.IO.StreamWriter(cm.CreateFile("system-info.txt", Duplicati.Library.Interface.CompressionHint.Compressible, DateTime.UtcNow)))
|
||||
foreach(var line in SystemInfoHandler.GetSystemInfo())
|
||||
|
||||
using (var cs = new System.IO.StreamWriter(cm.CreateFile("system-info.txt", Duplicati.Library.Interface.CompressionHint.Compressible, DateTime.UtcNow)))
|
||||
foreach (var line in SystemInfoHandler.GetSystemInfo())
|
||||
cs.WriteLine(line);
|
||||
}
|
||||
|
||||
m_result.TargetPath = m_targetpath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Duplicati.Library.Interface;
|
||||
|
||||
namespace Duplicati.Library.Main.Operation
|
||||
{
|
||||
internal class ListAffected
|
||||
@@ -34,6 +35,9 @@ 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));
|
||||
|
||||
using(var db = new Database.LocalListAffectedDatabase(m_options.Dbpath))
|
||||
{
|
||||
m_result.SetDatabase(db);
|
||||
|
||||
@@ -523,7 +523,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
if (dryrun)
|
||||
return;
|
||||
|
||||
var isDirTarget = path.EndsWith(DIRSEP);
|
||||
var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal);
|
||||
var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path;
|
||||
|
||||
// Make the symlink first, otherwise we cannot apply metadata to it
|
||||
@@ -996,7 +996,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
{
|
||||
//Select a new filename
|
||||
var ext = m_systemIO.PathGetExtension(targetpath) ?? "";
|
||||
if (!string.IsNullOrEmpty(ext) && !ext.StartsWith("."))
|
||||
if (!string.IsNullOrEmpty(ext) && !ext.StartsWith(".", StringComparison.Ordinal))
|
||||
ext = "." + ext;
|
||||
|
||||
// First we try with a simple date append, assuming that there are not many conflicts there
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
yield return string.Format("Autoupdate urls: {0}", string.Join(";", Duplicati.Library.AutoUpdater.AutoUpdateSettings.URLs));
|
||||
yield return string.Format("Update folder: {0}", Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLDIR);
|
||||
yield return string.Format("Base install folder: {0}", Duplicati.Library.AutoUpdater.UpdaterManager.InstalledBaseDir);
|
||||
yield return string.Format("Version name: \"{0}\" ({1})", Duplicati.Library.AutoUpdater.UpdaterManager.SelfVersion.Displayname, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version, System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));
|
||||
yield return string.Format("Version name: \"{0}\" ({1})", Duplicati.Library.AutoUpdater.UpdaterManager.SelfVersion.Displayname, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
|
||||
yield return string.Format("Current Version folder {0}", System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));
|
||||
|
||||
yield return string.Format("OS: {0}", Environment.OSVersion);
|
||||
|
||||
@@ -34,11 +34,11 @@ namespace Duplicati.Library.Main
|
||||
void AddDryrunMessage(string message);
|
||||
void WriteLogMessageDirect(string message, LogMessageType type, Exception ex);
|
||||
}
|
||||
|
||||
|
||||
internal interface IBackendWriter : ILogWriter
|
||||
{
|
||||
void AddRetryAttempt(string message, Exception ex);
|
||||
|
||||
|
||||
long UnknownFileSize { set; }
|
||||
long UnknownFileCount { set; }
|
||||
long KnownFileCount { set; }
|
||||
@@ -57,32 +57,32 @@ namespace Duplicati.Library.Main
|
||||
/// <param name="path">Path to the resource</param>
|
||||
/// <param name="size">Size of the file or progress</param>
|
||||
void SendEvent(BackendActionType action, BackendEventType type, string path, long size);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backend progress updater.
|
||||
/// </summary>
|
||||
/// <value>The backend progress updater.</value>
|
||||
IBackendProgressUpdater BackendProgressUpdater { get; }
|
||||
}
|
||||
|
||||
|
||||
internal interface ISetCommonOptions : ILogWriter
|
||||
{
|
||||
bool VerboseOutput { set; }
|
||||
new bool VerboseOutput { set; }
|
||||
bool VerboseErrors { set; }
|
||||
|
||||
|
||||
DateTime EndTime { get; set; }
|
||||
DateTime BeginTime { set; }
|
||||
|
||||
|
||||
void SetDatabase(LocalDatabase db);
|
||||
|
||||
|
||||
OperationMode MainOperation { get; }
|
||||
IMessageSink MessageSink { set; }
|
||||
IMessageSink MessageSink { set; }
|
||||
}
|
||||
|
||||
|
||||
internal class BackendWriter : BasicResults, IBackendWriter, IBackendStatstics, IParsedBackendStatistics
|
||||
{
|
||||
public BackendWriter(BasicResults p) : base(p) { }
|
||||
|
||||
|
||||
protected long m_remoteCalls = 0;
|
||||
protected long m_bytesUploaded = 0;
|
||||
protected long m_bytesDownloaded = 0;
|
||||
@@ -91,22 +91,22 @@ namespace Duplicati.Library.Main
|
||||
protected long m_filesDeleted = 0;
|
||||
protected long m_foldersCreated = 0;
|
||||
protected long m_retryAttemptCount = 0;
|
||||
|
||||
|
||||
public void AddNumberOfRemoteCalls(long count)
|
||||
{
|
||||
System.Threading.Interlocked.Add(ref m_remoteCalls, count);
|
||||
}
|
||||
|
||||
|
||||
public void AddBytesUploaded(long count)
|
||||
{
|
||||
System.Threading.Interlocked.Add(ref m_bytesUploaded, count);
|
||||
}
|
||||
|
||||
|
||||
public void AddBytesDownloaded(long count)
|
||||
{
|
||||
System.Threading.Interlocked.Add(ref m_bytesDownloaded, count);
|
||||
}
|
||||
|
||||
|
||||
public long RemoteCalls { get { return m_remoteCalls; } }
|
||||
public long BytesUploaded { get { return m_bytesUploaded; } }
|
||||
public long BytesDownloaded { get { return m_bytesDownloaded; } }
|
||||
@@ -125,9 +125,9 @@ namespace Duplicati.Library.Main
|
||||
public long TotalQuotaSpace { get; set; }
|
||||
public long FreeQuotaSpace { get; set; }
|
||||
public long AssignedQuotaSpace { get; set; }
|
||||
|
||||
|
||||
public override OperationMode MainOperation { get { return m_parent.MainOperation; } }
|
||||
|
||||
|
||||
public void SendEvent(BackendActionType action, BackendEventType type, string path, long size)
|
||||
{
|
||||
if (type == BackendEventType.Started)
|
||||
@@ -160,13 +160,13 @@ namespace Duplicati.Library.Main
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
base.AddBackendEvent(action, type, path, size);
|
||||
}
|
||||
|
||||
IBackendProgressUpdater IBackendWriter.BackendProgressUpdater { get { return base.BackendProgressUpdater; } }
|
||||
|
||||
IBackendProgressUpdater IBackendWriter.BackendProgressUpdater { get { return base.BackendProgressUpdater; } }
|
||||
}
|
||||
|
||||
|
||||
public interface ITaskControl
|
||||
{
|
||||
void Pause();
|
||||
@@ -174,7 +174,7 @@ namespace Duplicati.Library.Main
|
||||
void Stop();
|
||||
void Abort();
|
||||
}
|
||||
|
||||
|
||||
internal enum TaskControlState
|
||||
{
|
||||
Run,
|
||||
@@ -182,7 +182,7 @@ namespace Duplicati.Library.Main
|
||||
Stop,
|
||||
Abort
|
||||
}
|
||||
|
||||
|
||||
internal abstract class BasicResults : IBasicResults, ILogWriter, ISetCommonOptions, ITaskControl, Logging.ILog
|
||||
{
|
||||
protected class DbMessage
|
||||
@@ -190,7 +190,7 @@ namespace Duplicati.Library.Main
|
||||
public readonly string Type;
|
||||
public readonly string Message;
|
||||
public readonly Exception Exception;
|
||||
|
||||
|
||||
public DbMessage(string type, string message, Exception ex)
|
||||
{
|
||||
this.Type = type;
|
||||
@@ -198,28 +198,28 @@ namespace Duplicati.Library.Main
|
||||
this.Exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected LocalDatabase m_db;
|
||||
protected readonly BasicResults m_parent;
|
||||
protected System.Threading.Thread m_callerThread;
|
||||
protected readonly object m_lock = new object();
|
||||
protected Queue<DbMessage> m_dbqueue;
|
||||
|
||||
|
||||
private TaskControlState m_controlState = TaskControlState.Run;
|
||||
private System.Threading.ManualResetEvent m_pauseEvent = new System.Threading.ManualResetEvent(true);
|
||||
|
||||
|
||||
private bool m_verboseOutput;
|
||||
private bool m_verboseErrors;
|
||||
|
||||
|
||||
public bool VerboseOutput
|
||||
{
|
||||
{
|
||||
get
|
||||
{
|
||||
{
|
||||
if (m_parent != null)
|
||||
return m_parent.VerboseOutput;
|
||||
else
|
||||
return m_verboseOutput;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (m_parent != null)
|
||||
@@ -228,15 +228,16 @@ namespace Duplicati.Library.Main
|
||||
m_verboseOutput = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool VerboseErrors
|
||||
{
|
||||
{
|
||||
get
|
||||
{
|
||||
{
|
||||
if (m_parent != null)
|
||||
return m_parent.VerboseErrors;
|
||||
else
|
||||
return m_verboseErrors;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (m_parent != null)
|
||||
@@ -257,24 +258,24 @@ namespace Duplicati.Library.Main
|
||||
else
|
||||
return ParsedResultType.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime EndTime { get; set; }
|
||||
public DateTime BeginTime { get; set; }
|
||||
public TimeSpan Duration { get { return EndTime.Ticks == 0 ? new TimeSpan(0) : EndTime - BeginTime; } }
|
||||
|
||||
|
||||
public abstract OperationMode MainOperation { get; }
|
||||
|
||||
|
||||
protected Library.Utility.FileBackedStringList m_messages;
|
||||
protected Library.Utility.FileBackedStringList m_warnings;
|
||||
protected Library.Utility.FileBackedStringList m_errors;
|
||||
protected Library.Utility.FileBackedStringList m_retryAttempts;
|
||||
|
||||
|
||||
protected IMessageSink m_messageSink;
|
||||
public IMessageSink MessageSink
|
||||
{
|
||||
get { return m_messageSink; }
|
||||
set
|
||||
set
|
||||
{
|
||||
m_messageSink = value;
|
||||
if (value != null)
|
||||
@@ -284,7 +285,7 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected internal IOperationProgressUpdaterAndReporter m_operationProgressUpdater;
|
||||
internal IOperationProgressUpdaterAndReporter OperationProgressUpdater
|
||||
{
|
||||
@@ -296,7 +297,7 @@ namespace Duplicati.Library.Main
|
||||
return m_operationProgressUpdater;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected internal IBackendProgressUpdaterAndReporter m_backendProgressUpdater;
|
||||
internal IBackendProgressUpdaterAndReporter BackendProgressUpdater
|
||||
{
|
||||
@@ -308,7 +309,7 @@ namespace Duplicati.Library.Main
|
||||
return m_backendProgressUpdater;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetDatabase(LocalDatabase db)
|
||||
{
|
||||
if (m_parent != null)
|
||||
@@ -317,7 +318,7 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
else
|
||||
{
|
||||
lock(m_lock)
|
||||
lock (m_lock)
|
||||
{
|
||||
m_db = db;
|
||||
if (m_db != null)
|
||||
@@ -325,14 +326,14 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void FlushLog()
|
||||
{
|
||||
if (m_parent != null)
|
||||
m_parent.FlushLog();
|
||||
else
|
||||
{
|
||||
lock(m_lock)
|
||||
lock (m_lock)
|
||||
{
|
||||
while (m_dbqueue.Count > 0)
|
||||
{
|
||||
@@ -342,7 +343,7 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void LogDbMessage(string type, string message, Exception ex)
|
||||
{
|
||||
if (System.Threading.Thread.CurrentThread != m_callerThread)
|
||||
@@ -366,7 +367,7 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
else
|
||||
{
|
||||
lock(Logging.Log.Lock)
|
||||
lock (Logging.Log.Lock)
|
||||
{
|
||||
if (m_is_reporting)
|
||||
return;
|
||||
@@ -388,7 +389,7 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void AddDryrunMessage(string message)
|
||||
@@ -397,7 +398,7 @@ namespace Duplicati.Library.Main
|
||||
m_parent.AddDryrunMessage(message);
|
||||
else
|
||||
{
|
||||
lock(Logging.Log.Lock)
|
||||
lock (Logging.Log.Lock)
|
||||
{
|
||||
if (m_is_reporting)
|
||||
return;
|
||||
@@ -451,12 +452,12 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
|
||||
public void AddMessage(string message)
|
||||
{
|
||||
{
|
||||
if (m_parent != null)
|
||||
m_parent.AddMessage(message);
|
||||
else
|
||||
{
|
||||
lock(Logging.Log.Lock)
|
||||
lock (Logging.Log.Lock)
|
||||
{
|
||||
if (m_is_reporting)
|
||||
return;
|
||||
@@ -471,7 +472,7 @@ namespace Duplicati.Library.Main
|
||||
if (MessageSink != null)
|
||||
MessageSink.MessageEvent(message);
|
||||
|
||||
lock(m_lock)
|
||||
lock (m_lock)
|
||||
if (m_db != null && !m_db.IsDisposed)
|
||||
LogDbMessage("Message", message, null);
|
||||
}
|
||||
@@ -489,7 +490,7 @@ namespace Duplicati.Library.Main
|
||||
m_parent.AddWarning(message, ex);
|
||||
else
|
||||
{
|
||||
lock(Logging.Log.Lock)
|
||||
lock (Logging.Log.Lock)
|
||||
{
|
||||
if (m_is_reporting)
|
||||
return;
|
||||
@@ -505,7 +506,7 @@ namespace Duplicati.Library.Main
|
||||
if (MessageSink != null)
|
||||
MessageSink.WarningEvent(message, ex);
|
||||
|
||||
lock(m_lock)
|
||||
lock (m_lock)
|
||||
if (m_db != null && !m_db.IsDisposed)
|
||||
LogDbMessage("Warning", message, ex);
|
||||
}
|
||||
@@ -523,7 +524,7 @@ namespace Duplicati.Library.Main
|
||||
m_parent.AddRetryAttempt(message, ex);
|
||||
else
|
||||
{
|
||||
lock(Logging.Log.Lock)
|
||||
lock (Logging.Log.Lock)
|
||||
{
|
||||
if (m_is_reporting)
|
||||
return;
|
||||
@@ -539,7 +540,7 @@ namespace Duplicati.Library.Main
|
||||
if (MessageSink != null)
|
||||
MessageSink.RetryEvent(message, ex);
|
||||
|
||||
lock(m_lock)
|
||||
lock (m_lock)
|
||||
if (m_db != null && !m_db.IsDisposed)
|
||||
LogDbMessage("Retry", message, ex);
|
||||
}
|
||||
@@ -557,7 +558,7 @@ namespace Duplicati.Library.Main
|
||||
m_parent.AddError(message, ex);
|
||||
else
|
||||
{
|
||||
lock(Logging.Log.Lock)
|
||||
lock (Logging.Log.Lock)
|
||||
{
|
||||
if (m_is_reporting)
|
||||
return;
|
||||
@@ -573,7 +574,7 @@ namespace Duplicati.Library.Main
|
||||
if (MessageSink != null)
|
||||
MessageSink.ErrorEvent(message, ex);
|
||||
|
||||
lock(m_lock)
|
||||
lock (m_lock)
|
||||
if (m_db != null && !m_db.IsDisposed)
|
||||
LogDbMessage("Error", message, ex);
|
||||
}
|
||||
@@ -584,14 +585,14 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<string> Messages { get { return m_messages; } }
|
||||
public IEnumerable<string> Warnings { get { return m_warnings; } }
|
||||
public IEnumerable<string> Errors { get { return m_errors; } }
|
||||
|
||||
public BasicResults()
|
||||
{
|
||||
this.BeginTime = DateTime.UtcNow;
|
||||
|
||||
public BasicResults()
|
||||
{
|
||||
this.BeginTime = DateTime.UtcNow;
|
||||
this.m_parent = null;
|
||||
m_messages = new Library.Utility.FileBackedStringList();
|
||||
m_warnings = new Library.Utility.FileBackedStringList();
|
||||
@@ -605,27 +606,27 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
|
||||
public BasicResults(BasicResults p)
|
||||
{
|
||||
this.BeginTime = DateTime.UtcNow;
|
||||
{
|
||||
this.BeginTime = DateTime.UtcNow;
|
||||
this.m_parent = p;
|
||||
}
|
||||
|
||||
|
||||
protected IBackendStatstics m_backendStatistics;
|
||||
public IBackendStatstics BackendStatistics
|
||||
{
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.m_parent != null)
|
||||
return this.m_parent.BackendStatistics;
|
||||
|
||||
|
||||
return m_backendStatistics;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IBackendWriter BackendWriter { get { return (IBackendWriter)this.BackendStatistics; } }
|
||||
|
||||
|
||||
public event Action<TaskControlState> StateChangedEvent;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Request that this task pauses.
|
||||
/// </summary>
|
||||
@@ -646,7 +647,7 @@ namespace Duplicati.Library.Main
|
||||
StateChangedEvent(m_controlState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Request that this task resumes.
|
||||
/// </summary>
|
||||
@@ -667,11 +668,11 @@ namespace Duplicati.Library.Main
|
||||
StateChangedEvent(m_controlState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Request that this task stops.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
public void Stop()
|
||||
{
|
||||
if (m_parent != null)
|
||||
m_parent.Stop();
|
||||
@@ -688,7 +689,7 @@ namespace Duplicati.Library.Main
|
||||
StateChangedEvent(m_controlState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Request that this task aborts.
|
||||
/// </summary>
|
||||
@@ -708,7 +709,7 @@ namespace Duplicati.Library.Main
|
||||
StateChangedEvent(m_controlState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that the current running task can call to obtain the current state
|
||||
/// </summary>
|
||||
@@ -752,7 +753,7 @@ namespace Duplicati.Library.Main
|
||||
public override string ToString()
|
||||
{
|
||||
return Library.Utility.Utility.PrintSerializeObject(
|
||||
this,
|
||||
this,
|
||||
filter: (prop, item) =>
|
||||
!typeof(IBackendProgressUpdater).IsAssignableFrom(prop.PropertyType) &&
|
||||
!typeof(IMessageSink).IsAssignableFrom(prop.PropertyType) &&
|
||||
@@ -855,11 +856,11 @@ namespace Duplicati.Library.Main
|
||||
else if ((Warnings != null && Warnings.Any()) || PartialBackup)
|
||||
return ParsedResultType.Warning;
|
||||
else
|
||||
return ParsedResultType.Success;
|
||||
return ParsedResultType.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class RestoreResults : BasicResults, Library.Interface.IRestoreResults
|
||||
{
|
||||
public long FilesRestored { get; internal set; }
|
||||
@@ -870,9 +871,9 @@ namespace Duplicati.Library.Main
|
||||
public long FilesDeleted { get; internal set; }
|
||||
public long FoldersDeleted { get; internal set; }
|
||||
public long SymlinksDeleted { get; internal set; }
|
||||
|
||||
|
||||
public override OperationMode MainOperation { get { return OperationMode.Restore; } }
|
||||
|
||||
|
||||
public IRecreateDatabaseResults RecreateDatabaseResults { get; internal set; }
|
||||
|
||||
public override ParsedResultType ParsedResult
|
||||
@@ -914,7 +915,7 @@ namespace Duplicati.Library.Main
|
||||
this.FileSizes = fileSizes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class ListResults : BasicResults, Duplicati.Library.Interface.IListResults
|
||||
{
|
||||
private IEnumerable<Duplicati.Library.Interface.IListResultFileset> m_filesets;
|
||||
@@ -926,10 +927,10 @@ namespace Duplicati.Library.Main
|
||||
m_filesets = filesets;
|
||||
m_files = files;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<Duplicati.Library.Interface.IListResultFileset> Filesets { get { return m_filesets; } }
|
||||
public IEnumerable<Duplicati.Library.Interface.IListResultFile> Files { get { return m_files; } }
|
||||
|
||||
|
||||
public override OperationMode MainOperation { get { return OperationMode.List; } }
|
||||
}
|
||||
|
||||
@@ -947,7 +948,7 @@ namespace Duplicati.Library.Main
|
||||
m_logs = logs;
|
||||
m_volumes = volumes;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<Duplicati.Library.Interface.IListResultFileset> Filesets { get { return m_filesets; } }
|
||||
public IEnumerable<Duplicati.Library.Interface.IListResultFile> Files { get { return m_files; } }
|
||||
public IEnumerable<Duplicati.Library.Interface.IListResultRemoteLog> LogMessages { get { return m_logs; } }
|
||||
@@ -960,23 +961,23 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
public IEnumerable<Tuple<long, DateTime>> DeletedSets { get; private set; }
|
||||
public bool Dryrun { get; private set; }
|
||||
|
||||
|
||||
public void SetResults(IEnumerable<Tuple<long, DateTime>> deletedSets, bool dryrun)
|
||||
{
|
||||
EndTime = DateTime.UtcNow;
|
||||
DeletedSets = deletedSets;
|
||||
Dryrun = dryrun;
|
||||
}
|
||||
|
||||
|
||||
public override OperationMode MainOperation { get { return OperationMode.Delete; } }
|
||||
|
||||
public DeleteResults() : base() { }
|
||||
public DeleteResults(BasicResults p) : base(p) { }
|
||||
|
||||
|
||||
private ICompactResults m_compactResults;
|
||||
|
||||
public ICompactResults CompactResults
|
||||
{
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_parent != null && m_parent is BackupResults)
|
||||
@@ -988,33 +989,33 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
if (m_parent != null && m_parent is BackupResults)
|
||||
((BackupResults)m_parent).CompactResults = value;
|
||||
|
||||
|
||||
m_compactResults = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class RecreateDatabaseResults : BasicResults, Library.Interface.IRecreateDatabaseResults
|
||||
{
|
||||
public override OperationMode MainOperation { get { return OperationMode.Repair; } }
|
||||
|
||||
|
||||
public RecreateDatabaseResults() : base() { }
|
||||
public RecreateDatabaseResults(BasicResults p) : base(p) { }
|
||||
}
|
||||
}
|
||||
|
||||
internal class CreateLogDatabaseResults : BasicResults, Library.Interface.ICreateLogDatabaseResults
|
||||
{
|
||||
public override OperationMode MainOperation { get { return OperationMode.CreateLogDb; } }
|
||||
public string TargetPath { get; internal set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class RestoreControlFilesResults : BasicResults, Library.Interface.IRestoreControlFilesResults
|
||||
{
|
||||
public IEnumerable<string> Files { get; private set; }
|
||||
|
||||
|
||||
public override OperationMode MainOperation { get { return OperationMode.RestoreControlfiles; } }
|
||||
public void SetResult(IEnumerable<string> files) { this.Files = files; }
|
||||
}
|
||||
}
|
||||
|
||||
internal class ListRemoteResults : BasicResults, Library.Interface.IListRemoteResults
|
||||
{
|
||||
@@ -1027,12 +1028,12 @@ namespace Duplicati.Library.Main
|
||||
internal class RepairResults : BasicResults, Library.Interface.IRepairResults
|
||||
{
|
||||
public override OperationMode MainOperation { get { return OperationMode.Repair; } }
|
||||
|
||||
|
||||
public RepairResults() : base() { }
|
||||
public RepairResults(BasicResults p) : base(p) { }
|
||||
public Library.Interface.IRecreateDatabaseResults RecreateDatabaseResults { get; internal set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class CompactResults : BasicResults, Library.Interface.ICompactResults
|
||||
{
|
||||
public long DeletedFileCount { get; internal set; }
|
||||
@@ -1044,22 +1045,22 @@ namespace Duplicati.Library.Main
|
||||
public bool Dryrun { get; internal set; }
|
||||
|
||||
public override OperationMode MainOperation { get { return OperationMode.Compact; } }
|
||||
|
||||
|
||||
public CompactResults() : base() { }
|
||||
public CompactResults(BasicResults p) : base(p) { }
|
||||
}
|
||||
}
|
||||
|
||||
internal class ListChangesResults : BasicResults, Library.Interface.IListChangesResults
|
||||
{
|
||||
public override OperationMode MainOperation { get { return OperationMode.ListChanges; } }
|
||||
|
||||
|
||||
public DateTime BaseVersionTimestamp { get; internal set; }
|
||||
public DateTime CompareVersionTimestamp { get; internal set; }
|
||||
public long BaseVersionIndex { get; internal set; }
|
||||
public long CompareVersionIndex { get; internal set; }
|
||||
|
||||
public IEnumerable<Tuple<ListChangesChangeType, ListChangesElementType, string>> ChangeDetails { get; internal set; }
|
||||
|
||||
|
||||
public IEnumerable<Tuple<ListChangesChangeType, ListChangesElementType, string>> ChangeDetails { get; internal set; }
|
||||
|
||||
public long AddedFolders { get; internal set; }
|
||||
public long AddedSymlinks { get; internal set; }
|
||||
public long AddedFiles { get; internal set; }
|
||||
@@ -1071,13 +1072,13 @@ namespace Duplicati.Library.Main
|
||||
public long ModifiedFolders { get; internal set; }
|
||||
public long ModifiedSymlinks { get; internal set; }
|
||||
public long ModifiedFiles { get; internal set; }
|
||||
|
||||
|
||||
public long PreviousSize { get; internal set; }
|
||||
public long CurrentSize { get; internal set; }
|
||||
|
||||
public long AddedSize { get; internal set; }
|
||||
public long DeletedSize { get; internal set; }
|
||||
|
||||
|
||||
public void SetResult(
|
||||
DateTime baseVersionTime, long baseVersionIndex, DateTime compareVersionTime, long compareVersionIndex,
|
||||
long addedFolders, long addedSymlinks, long addedFiles,
|
||||
@@ -1091,38 +1092,38 @@ namespace Duplicati.Library.Main
|
||||
this.BaseVersionIndex = baseVersionIndex;
|
||||
this.CompareVersionTimestamp = compareVersionTime;
|
||||
this.CompareVersionIndex = compareVersionIndex;
|
||||
|
||||
this.AddedFolders = addedFolders;
|
||||
|
||||
this.AddedFolders = addedFolders;
|
||||
this.AddedSymlinks = addedSymlinks;
|
||||
this.AddedFiles = addedFiles;
|
||||
|
||||
|
||||
this.DeletedFolders = deletedFolders;
|
||||
this.DeletedSymlinks = deletedSymlinks;
|
||||
this.DeletedFiles = deletedFiles;
|
||||
|
||||
|
||||
this.ModifiedFolders = modifiedFolders;
|
||||
this.ModifiedSymlinks = modifiedSymlinks;
|
||||
this.ModifiedFiles = modifiedFiles;
|
||||
|
||||
|
||||
this.AddedSize = addedSize;
|
||||
this.DeletedSize = deletedSize;
|
||||
|
||||
|
||||
this.PreviousSize = previousSize;
|
||||
this.CurrentSize = currentSize;
|
||||
|
||||
|
||||
this.ChangeDetails = changeDetails;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class TestResults : BasicResults, ITestResults
|
||||
{
|
||||
public TestResults() : base() { }
|
||||
public TestResults(BasicResults p) : base(p) { }
|
||||
|
||||
|
||||
public override OperationMode MainOperation { get { return OperationMode.Test; } }
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>>> Verifications { get { return m_verifications; } }
|
||||
private List<KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>>> m_verifications = new List<KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>>>();
|
||||
|
||||
|
||||
public KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>> AddResult(string volume, IEnumerable<KeyValuePair<TestEntryStatus, string>> changes)
|
||||
{
|
||||
var res = new KeyValuePair<string, IEnumerable<KeyValuePair<TestEntryStatus, string>>>(volume, changes);
|
||||
@@ -1130,7 +1131,7 @@ namespace Duplicati.Library.Main
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class TestFilterResults : BasicResults, ITestFilterResults
|
||||
{
|
||||
public long FileCount { get; set; }
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Duplicati.Library.Main.Strings
|
||||
public static string DisablemoduleShort { get { return LC.L(@"Disabled one or more modules"); } }
|
||||
public static string EnablemoduleLong { get { return LC.L(@"Supply one or more module names, separated by commas to load them"); } }
|
||||
public static string EnablemoduleShort { get { return LC.L(@"Enables one or more modules"); } }
|
||||
public static string SnapshotpolicyLong { get { return LC.L(@"This setting controls the usage of snapshots, which allows Duplicati to backup files that are locked by other programs. If this is set to ""off"", Duplicati will not attempt to create a disk snapshot. Setting this to ""auto"" makes Duplicati attempt to create a snapshot, and fail silently if that was not allowed or supported. A setting of ""on"" will also make Duplicati attempt to create a snapshot, but will produce a warning message in the log if it fails. Setting it to ""required"" will make Duplicati abort the backup if the snapshot creation fails. On windows this uses the Volume Shadow Copy Services (VSS) and requires administrative privileges. On Linux this uses Logical Volume Management (LVM) and requires root privileges."); } }
|
||||
public static string SnapshotpolicyLong { get { return LC.L(@"This setting controls the usage of snapshots, which allows Duplicati to backup files that are locked by other programs. If this is set to ""off"", Duplicati will not attempt to create a disk snapshot. Setting this to ""auto"" makes Duplicati attempt to create a snapshot, and fail silently if that was not allowed or supported (note that the OS may still log system warnings). A setting of ""on"" will also make Duplicati attempt to create a snapshot, but will produce a warning message in the log if it fails. Setting it to ""required"" will make Duplicati abort the backup if the snapshot creation fails. On windows this uses the Volume Shadow Copy Services (VSS) and requires administrative privileges. On Linux this uses Logical Volume Management (LVM) and requires root privileges."); } }
|
||||
public static string SnapshotpolicyShort { get { return LC.L(@"Controls the use of disk snapshots"); } }
|
||||
public static string AsynchronousuploadfolderLong { get { return LC.L(@"The pre-generated volumes will be placed into the temporary folder by default, this option can set a different folder for placing the temporary volumes, despite the name, this also works for synchronous runs"); } }
|
||||
public static string AsynchronousuploadfolderShort { get { return LC.L(@"The path where ready volumes are placed until uploaded"); } }
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Duplicati.Library.Interface;
|
||||
using Duplicati.Library.Interface;
|
||||
using Duplicati.Library.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Duplicati.Library.Main.Volumes
|
||||
@@ -12,18 +11,37 @@ namespace Duplicati.Library.Main.Volumes
|
||||
{
|
||||
protected bool m_disposeCompression = false;
|
||||
protected ICompression m_compression;
|
||||
protected Stream m_stream;
|
||||
|
||||
private static ICompression LoadCompressor(string compressor, string file, Options options)
|
||||
private static ICompression LoadCompressor(string compressor, Stream stream, Options options)
|
||||
{
|
||||
var tmp = DynamicLoader.CompressionLoader.GetModule(compressor, file, options.RawOptions);
|
||||
var tmp = DynamicLoader.CompressionLoader.GetModule(compressor, stream, Interface.ArchiveMode.Read, options.RawOptions);
|
||||
if (tmp == null)
|
||||
throw new Exception(string.Format("Unable to create {0} decompressor on file {1}", compressor, file));
|
||||
{
|
||||
var name = "[stream]";
|
||||
if (stream is FileStream)
|
||||
name = ((FileStream)stream).Name;
|
||||
if (stream is TempFileStream)
|
||||
name = ((TempFileStream)stream).Name;
|
||||
|
||||
throw new Exception(string.Format("Unable to create {0} decompressor on file {1}", compressor, name));
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
public VolumeReaderBase(string compressor, string file, Options options)
|
||||
: this(LoadCompressor(compressor, file, options), options)
|
||||
private static ICompression LoadCompressor(string compressor, string file, Options options, out Stream stream)
|
||||
{
|
||||
stream = new System.IO.FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return LoadCompressor(compressor, stream, options);
|
||||
}
|
||||
|
||||
public VolumeReaderBase(string compressor, string file, Options options)
|
||||
: base(options)
|
||||
{
|
||||
m_compression = LoadCompressor(compressor, file, options, out m_stream);
|
||||
ReadManifests(options);
|
||||
|
||||
m_disposeCompression = true;
|
||||
}
|
||||
|
||||
@@ -31,13 +49,18 @@ namespace Duplicati.Library.Main.Volumes
|
||||
: base(options)
|
||||
{
|
||||
m_compression = compression;
|
||||
ReadManifests(options);
|
||||
}
|
||||
|
||||
private void ReadManifests(Options options)
|
||||
{
|
||||
if (!options.DontReadManifests)
|
||||
{
|
||||
using (var s = m_compression.OpenRead(MANIFEST_FILENAME))
|
||||
{
|
||||
if (s == null)
|
||||
throw new InvalidManifestException("No manifest file found in volume");
|
||||
|
||||
|
||||
using (var fs = new StreamReader(s, ENCODING))
|
||||
ManifestData.VerifyManifest(fs.ReadToEnd(), m_blocksize, options.BlockHashAlgorithm, options.FileHashAlgorithm);
|
||||
}
|
||||
@@ -46,7 +69,8 @@ namespace Duplicati.Library.Main.Volumes
|
||||
|
||||
public static void UpdateOptionsFromManifest(string compressor, string file, Options options)
|
||||
{
|
||||
using(var c = LoadCompressor(compressor, file, options))
|
||||
using (var stream = new System.IO.FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var c = LoadCompressor(compressor, stream, options))
|
||||
using (var s = c.OpenRead(MANIFEST_FILENAME))
|
||||
using (var fs = new StreamReader(s, ENCODING))
|
||||
{
|
||||
@@ -75,17 +99,20 @@ namespace Duplicati.Library.Main.Volumes
|
||||
{
|
||||
m_compression.Dispose();
|
||||
m_compression = null;
|
||||
|
||||
m_stream?.Dispose();
|
||||
m_stream = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ReadBlocklist(ICompression compression, string filename, long hashsize)
|
||||
{
|
||||
var buffer = new byte[hashsize];
|
||||
using(var fs = compression.OpenRead(filename))
|
||||
using (var fs = compression.OpenRead(filename))
|
||||
{
|
||||
int s;
|
||||
while ((s = Library.Utility.Utility.ForceStreamRead(fs, buffer, buffer.Length)) != 0)
|
||||
{
|
||||
{
|
||||
if (s != buffer.Length)
|
||||
throw new InvalidDataException("Premature End-of-stream encountered while reading blocklist hashes");
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Duplicati.Library.Interface;
|
||||
using Duplicati.Library.Utility;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Duplicati.Library.Interface;
|
||||
|
||||
namespace Duplicati.Library.Main.Volumes
|
||||
{
|
||||
@@ -11,6 +9,7 @@ namespace Duplicati.Library.Main.Volumes
|
||||
{
|
||||
protected ICompression m_compression;
|
||||
protected Library.Utility.TempFile m_localfile;
|
||||
protected Stream m_localFileStream;
|
||||
protected string m_volumename;
|
||||
public string LocalFilename { get { return m_localfile; } }
|
||||
public string RemoteFilename { get { return m_volumename; } }
|
||||
@@ -19,7 +18,7 @@ namespace Duplicati.Library.Main.Volumes
|
||||
public abstract RemoteVolumeType FileType { get; }
|
||||
|
||||
public long VolumeID { get; set; }
|
||||
|
||||
|
||||
public void SetRemoteFilename(string name)
|
||||
{
|
||||
m_volumename = name;
|
||||
@@ -29,15 +28,15 @@ namespace Duplicati.Library.Main.Volumes
|
||||
: this(options, DateTime.UtcNow)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public static string GenerateGuid(Options options)
|
||||
{
|
||||
var s = Guid.NewGuid().ToString("N");
|
||||
|
||||
|
||||
//We can choose shorter GUIDs here
|
||||
|
||||
|
||||
return s;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void ResetRemoteFilename(Options options, DateTime timestamp)
|
||||
@@ -54,14 +53,16 @@ namespace Duplicati.Library.Main.Volumes
|
||||
m_localfile = new Library.Utility.TempFile();
|
||||
|
||||
ResetRemoteFilename(options, timestamp);
|
||||
m_compression = DynamicLoader.CompressionLoader.GetModule(options.CompressionModule, m_localfile, options.RawOptions);
|
||||
|
||||
if(m_compression == null)
|
||||
|
||||
m_localFileStream = new System.IO.FileStream(m_localfile, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
m_compression = DynamicLoader.CompressionLoader.GetModule(options.CompressionModule, m_localFileStream, ArchiveMode.Write, options.RawOptions);
|
||||
|
||||
if (m_compression == null)
|
||||
throw new UserInformationException(string.Format("Unsupported compression module: {0}", options.CompressionModule));
|
||||
|
||||
|
||||
if ((this is IndexVolumeWriter || this is FilesetVolumeWriter) && m_compression is Library.Interface.ICompressionHinting)
|
||||
((Library.Interface.ICompressionHinting)m_compression).LowOverheadMode = true;
|
||||
|
||||
|
||||
AddManifestfile();
|
||||
}
|
||||
|
||||
@@ -77,6 +78,10 @@ namespace Duplicati.Library.Main.Volumes
|
||||
try { m_compression.Dispose(); }
|
||||
finally { m_compression = null; }
|
||||
|
||||
if (m_localFileStream != null)
|
||||
try { m_localFileStream.Dispose(); }
|
||||
finally { m_localFileStream = null; }
|
||||
|
||||
if (m_localfile != null)
|
||||
try { m_localfile.Dispose(); }
|
||||
finally { m_localfile = null; }
|
||||
@@ -89,16 +94,13 @@ namespace Duplicati.Library.Main.Volumes
|
||||
if (m_compression != null)
|
||||
try { m_compression.Dispose(); }
|
||||
finally { m_compression = null; }
|
||||
|
||||
if (m_localFileStream != null)
|
||||
try { m_localFileStream.Dispose(); }
|
||||
finally { m_localFileStream = null; }
|
||||
}
|
||||
|
||||
public long Filesize { get { return m_compression.Size + m_compression.FlushBufferSize; } }
|
||||
|
||||
public void SetTempFile(Library.Utility.TempFile tmpfile)
|
||||
{
|
||||
if (m_localfile != null)
|
||||
m_localfile.Dispose();
|
||||
|
||||
m_localfile = tmpfile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +101,9 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
{
|
||||
var filters = filter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
filtersInclude = filters.Where(x => x.StartsWith("+") && Regex.IsMatch(x, m_HyperVPathGuidRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
filtersInclude = filters.Where(x => x.StartsWith("+", StringComparison.Ordinal) && Regex.IsMatch(x, m_HyperVPathGuidRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
.Select(x => Regex.Match(x.Substring(1), m_HyperVPathGuidRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Groups[1].Value).ToList();
|
||||
filtersExclude = filters.Where(x => x.StartsWith("-") && Regex.IsMatch(x, m_HyperVPathGuidRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
filtersExclude = filters.Where(x => x.StartsWith("-", StringComparison.Ordinal) && Regex.IsMatch(x, m_HyperVPathGuidRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
.Select(x => Regex.Match(x.Substring(1), m_HyperVPathGuidRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Groups[1].Value).ToList();
|
||||
|
||||
var remainingfilters = filters.Where(x => !Regex.IsMatch(x, m_HyperVPathGuidRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)).ToArray();
|
||||
@@ -186,7 +186,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
|
||||
var pathsForBackup = new List<string>(paths);
|
||||
var filterhandler = new Utility.FilterExpression(
|
||||
filter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries).Where(x => x.StartsWith("-")).Select(x => x.Substring(1)).ToList());
|
||||
filter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries).Where(x => x.StartsWith("-", StringComparison.Ordinal)).Select(x => x.Substring(1)).ToList());
|
||||
|
||||
foreach (var guestForBackup in guestsForBackup)
|
||||
foreach (var pathForBackup in guestForBackup.DataPaths)
|
||||
|
||||
@@ -101,9 +101,9 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
{
|
||||
var filters = filter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
filtersInclude = filters.Where(x => x.StartsWith("+") && Regex.IsMatch(x, m_MSSQLPathDBRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
filtersInclude = filters.Where(x => x.StartsWith("+", StringComparison.Ordinal) && Regex.IsMatch(x, m_MSSQLPathDBRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
.Select(x => Regex.Match(x.Substring(1), m_MSSQLPathDBRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Groups[1].Value).ToList();
|
||||
filtersExclude = filters.Where(x => x.StartsWith("-") && Regex.IsMatch(x, m_MSSQLPathDBRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
filtersExclude = filters.Where(x => x.StartsWith("-", StringComparison.Ordinal) && Regex.IsMatch(x, m_MSSQLPathDBRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
.Select(x => Regex.Match(x.Substring(1), m_MSSQLPathDBRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Groups[1].Value).ToList();
|
||||
|
||||
var remainingfilters = filters.Where(x => !Regex.IsMatch(x, m_MSSQLPathDBRegExp, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)).ToArray();
|
||||
@@ -183,7 +183,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
|
||||
var pathsForBackup = new List<string>(paths);
|
||||
var filterhandler = new Utility.FilterExpression(
|
||||
filter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries).Where(x => x.StartsWith("-")).Select(x => x.Substring(1)).ToList());
|
||||
filter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries).Where(x => x.StartsWith("-", StringComparison.Ordinal)).Select(x => x.Substring(1)).ToList());
|
||||
|
||||
foreach (var dbForBackup in dbsForBackup)
|
||||
foreach (var pathForBackup in dbForBackup.DataPaths)
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
sw.WriteLine("{0}: {1}", key, value);
|
||||
}
|
||||
else
|
||||
sw.WriteLine(c.ToString());
|
||||
sw.WriteLine(c);
|
||||
}
|
||||
}
|
||||
else if (result.GetType().IsArray)
|
||||
@@ -164,7 +164,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
sw.WriteLine("{0}: {1}", key, value);
|
||||
}
|
||||
else
|
||||
sw.WriteLine(c.ToString());
|
||||
sw.WriteLine(c);
|
||||
}
|
||||
}
|
||||
else if (result is Exception)
|
||||
@@ -172,7 +172,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
//No localization, must be parseable by script
|
||||
Exception e = (Exception)result;
|
||||
sw.WriteLine("Failed: {0}", e.Message);
|
||||
sw.WriteLine("Details: {0}", e.ToString());
|
||||
sw.WriteLine("Details: {0}", e);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -252,7 +252,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
foreach(string rawline in stdout.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
string line = rawline.Trim();
|
||||
if (!line.StartsWith("--"))
|
||||
if (!line.StartsWith("--", StringComparison.Ordinal))
|
||||
continue; //Ingore anything that does not start with --
|
||||
|
||||
line = line.Substring(2);
|
||||
@@ -273,7 +273,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
key = line.Substring(0, lix).Trim();
|
||||
value = line.Substring(lix + 1).Trim();
|
||||
|
||||
if (value.Length >= 2 && value.StartsWith("\"") && value.EndsWith("\""))
|
||||
if (value.Length >= 2 && value.StartsWith("\"", StringComparison.Ordinal) && value.EndsWith("\"", StringComparison.Ordinal))
|
||||
value = value.Substring(1, value.Length - 2);
|
||||
}
|
||||
|
||||
|
||||
@@ -310,7 +310,7 @@ namespace Duplicati.Library.Modules.Builtin
|
||||
};
|
||||
|
||||
agsXMPP.XmppElementHandler loginErroHandler = (sender, e) => {
|
||||
ex = new Exception(string.Format("Failed to log in: {0}", e.ToString()));
|
||||
ex = new Exception(string.Format("Failed to log in: {0}", e));
|
||||
waitEvent.Set();
|
||||
};
|
||||
|
||||
|
||||
@@ -152,11 +152,11 @@ namespace Duplicati.Library.SQLiteHelper
|
||||
{
|
||||
//The resource name will be "Duplicati.GUI.Database_schema.1.Sample upgrade.sql"
|
||||
//The number indicates the version that will be upgraded to
|
||||
if (s.StartsWith(prefix) && !s.Equals(prefix + SCHEMA_NAME))
|
||||
if (s.StartsWith(prefix, StringComparison.Ordinal) && !s.Equals(prefix + SCHEMA_NAME))
|
||||
{
|
||||
try
|
||||
{
|
||||
string version = s.Substring(prefix.Length, s.IndexOf(".", prefix.Length + 1) - prefix.Length);
|
||||
string version = s.Substring(prefix.Length, s.IndexOf(".", prefix.Length + 1, StringComparison.Ordinal) - prefix.Length);
|
||||
int fileversion = int.Parse(version);
|
||||
|
||||
string prev;
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace Duplicati.Library.SQLiteHelper
|
||||
if (!Duplicati.Library.Utility.Utility.IsMono)
|
||||
{
|
||||
//If we run with MS.Net we can use the mixed mode assemblies
|
||||
if (Library.Utility.Utility.Is64BitProcess)
|
||||
if (Environment.Is64BitProcess)
|
||||
{
|
||||
if (System.IO.File.Exists(System.IO.Path.Combine(System.IO.Path.Combine(basePath, "win64"), filename)))
|
||||
assemblyPath = System.IO.Path.Combine(basePath, "win64");
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Duplicati.Library.Snapshots
|
||||
List<char> drives = new List<char>("DEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray());
|
||||
foreach (DriveInfo di in DriveInfo.GetDrives())
|
||||
{
|
||||
if ((di.RootDirectory.FullName.Length == 2 && di.RootDirectory.FullName[1] == ':') || ((di.RootDirectory.FullName.Length == 3 && di.RootDirectory.FullName.EndsWith(":\\"))))
|
||||
if ((di.RootDirectory.FullName.Length == 2 && di.RootDirectory.FullName[1] == ':') || ((di.RootDirectory.FullName.Length == 3 && di.RootDirectory.FullName.EndsWith(":\\", StringComparison.Ordinal))))
|
||||
{
|
||||
int i = drives.IndexOf(di.RootDirectory.FullName[0]);
|
||||
if (i >= 0)
|
||||
@@ -110,10 +110,10 @@ namespace Duplicati.Library.Snapshots
|
||||
drive = drives[0].ToString() + ':';
|
||||
}
|
||||
|
||||
while (drive.EndsWith("\\"))
|
||||
while (drive.EndsWith("\\", StringComparison.Ordinal))
|
||||
drive = drive.Substring(0, drive.Length - 1);
|
||||
|
||||
if (!drive.EndsWith(":"))
|
||||
if (!drive.EndsWith(":", StringComparison.Ordinal))
|
||||
throw new ArgumentException("The drive specification must end with a colon.", nameof(drive));
|
||||
|
||||
Win32API.DDD_Flags flags = 0;
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// Gets a unique hardlink target ID
|
||||
/// </summary>
|
||||
/// <returns>The hardlink ID</returns>
|
||||
/// <param name="file">The file or folder to examine</param>
|
||||
/// <param name="path">The file or folder to examine</param>
|
||||
string HardlinkTargetID(string path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// <returns>The local path</returns>
|
||||
public string ConvertToLocalPath(string path)
|
||||
{
|
||||
if (!path.StartsWith(m_mountPoint))
|
||||
if (!path.StartsWith(m_mountPoint, StringComparison.Ordinal))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
return m_tmpDir + path.Substring(m_mountPoint.Length);
|
||||
@@ -114,7 +114,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// <returns>The snapshot path</returns>
|
||||
public string ConvertToSnapshotPath(string path)
|
||||
{
|
||||
if (!path.StartsWith(m_tmpDir))
|
||||
if (!path.StartsWith(m_tmpDir, StringComparison.Ordinal))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
return m_mountPoint + path.Substring(m_tmpDir.Length);
|
||||
@@ -261,7 +261,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// <summary>
|
||||
/// Constructs a new snapshot module using LVM
|
||||
/// </summary>
|
||||
/// <param name="folders">The list of folders to create snapshots for</param>
|
||||
/// <param name="sources">The list of folders to create snapshots for</param>
|
||||
/// <param name="options">A set of commandline options</param>
|
||||
public LinuxSnapshot(string[] sources, Dictionary<string, string> options)
|
||||
{
|
||||
@@ -342,7 +342,7 @@ namespace Duplicati.Library.Snapshots
|
||||
KeyValuePair<string, SnapShot>? best = null;
|
||||
|
||||
foreach (KeyValuePair<string, SnapShot> s in m_entries)
|
||||
if (name.StartsWith(s.Key) && (best == null || s.Key.Length > best.Value.Key.Length))
|
||||
if (name.StartsWith(s.Key, StringComparison.Ordinal) && (best == null || s.Key.Length > best.Value.Key.Length))
|
||||
best = s;
|
||||
|
||||
if (best == null)
|
||||
@@ -494,7 +494,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// Gets a unique hardlink target ID
|
||||
/// </summary>
|
||||
/// <returns>The hardlink ID</returns>
|
||||
/// <param name="file">The file or folder to examine</param>
|
||||
/// <param name="path">The file or folder to examine</param>
|
||||
public string HardlinkTargetID(string path)
|
||||
{
|
||||
var local = ConvertToSnapshotPath(FindSnapShotByLocalPath(path), path);
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// <summary>
|
||||
/// Constructs a new backup snapshot, using all the required disks
|
||||
/// </summary>
|
||||
/// <param name="sourcepaths">The folders that are about to be backed up</param>
|
||||
/// <param name="sources">The folders that are about to be backed up</param>
|
||||
public NoSnapshot(string[] sources)
|
||||
: this(sources, new Dictionary<string, string>())
|
||||
{
|
||||
@@ -49,7 +49,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// <summary>
|
||||
/// Constructs a new backup snapshot, using all the required disks
|
||||
/// </summary>
|
||||
/// <param name="folders">The folders that are about to be backed up</param>
|
||||
/// <param name="sources">The folders that are about to be backed up</param>
|
||||
/// <param name="options">A set of system options</param>
|
||||
public NoSnapshot(string[] sources, Dictionary<string, string> options)
|
||||
{
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// Gets a unique hardlink target ID
|
||||
/// </summary>
|
||||
/// <returns>The hardlink ID</returns>
|
||||
/// <param name="file">The file or folder to examine</param>
|
||||
/// <param name="path">The file or folder to examine</param>
|
||||
public override string HardlinkTargetID(string path)
|
||||
{
|
||||
path = NormalizePath(path);
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// Gets a unique hardlink target ID
|
||||
/// </summary>
|
||||
/// <returns>The hardlink ID</returns>
|
||||
/// <param name="file">The file or folder to examine</param>
|
||||
/// <param name="path">The file or folder to examine</param>
|
||||
public override string HardlinkTargetID(string path)
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -31,21 +31,21 @@ namespace Duplicati.Library.Snapshots
|
||||
|
||||
for (int i = 0; i < args.Count; i++)
|
||||
{
|
||||
if (args[i].StartsWith("--"))
|
||||
if (args[i].StartsWith("--", StringComparison.Ordinal))
|
||||
{
|
||||
string key = null;
|
||||
string value = null;
|
||||
if (args[i].IndexOf("=") > 0)
|
||||
if (args[i].IndexOf("=", StringComparison.Ordinal) > 0)
|
||||
{
|
||||
key = args[i].Substring(0, args[i].IndexOf("="));
|
||||
value = args[i].Substring(args[i].IndexOf("=") + 1);
|
||||
key = args[i].Substring(0, args[i].IndexOf("=", StringComparison.Ordinal));
|
||||
value = args[i].Substring(args[i].IndexOf("=", StringComparison.Ordinal) + 1);
|
||||
}
|
||||
else
|
||||
key = args[i];
|
||||
|
||||
//Skip the leading --
|
||||
key = key.Substring(2).ToLower();
|
||||
if (!string.IsNullOrEmpty(value) && value.Length > 1 && value.StartsWith("\"") && value.EndsWith("\""))
|
||||
if (!string.IsNullOrEmpty(value) && value.Length > 1 && value.StartsWith("\"", StringComparison.Ordinal) && value.EndsWith("\"", StringComparison.Ordinal))
|
||||
value = value.Substring(1, value.Length - 2);
|
||||
|
||||
//Last argument overwrites the current
|
||||
@@ -151,7 +151,7 @@ Where <test-folder> is the folder where files will be locked/created etc");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(string.Format("The file {0} was locked even through snapshot, message: {1}", filename, ex.ToString()));
|
||||
Console.WriteLine(string.Format("The file {0} was locked even through snapshot, message: {1}", filename, ex));
|
||||
Console.WriteLine("* Test failed");
|
||||
return;
|
||||
}
|
||||
@@ -163,7 +163,7 @@ Where <test-folder> is the folder where files will be locked/created etc");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(string.Format("The snapshot tester failed: {0}", ex.ToString()));
|
||||
Console.WriteLine(string.Format("The snapshot tester failed: {0}", ex));
|
||||
Console.WriteLine("* Test failed");
|
||||
}
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace Duplicati.Library.Snapshots
|
||||
|
||||
var f = NoSnapshot.NormalizePath(file);
|
||||
|
||||
foreach(var x in data.Where(x => x.Key.StartsWith("unix-ext:")).Select(x => new KeyValuePair<string, byte[]>(x.Key.Substring("unix-ext:".Length), Convert.FromBase64String(x.Value))))
|
||||
foreach(var x in data.Where(x => x.Key.StartsWith("unix-ext:", StringComparison.Ordinal)).Select(x => new KeyValuePair<string, byte[]>(x.Key.Substring("unix-ext:".Length), Convert.FromBase64String(x.Value))))
|
||||
UnixSupport.File.SetExtendedAttribute(f, x.Key, x.Value);
|
||||
|
||||
if (restorePermissions && data.ContainsKey("unix:uid-gid-perm"))
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Duplicati.Library.Snapshots
|
||||
|
||||
public static bool IsPathTooLong(string path)
|
||||
{
|
||||
if (path.StartsWith(UNCPREFIX) || path.StartsWith(UNCPREFIX_SERVER) || path.Length > 260)
|
||||
if (path.StartsWith(UNCPREFIX, StringComparison.Ordinal) || path.StartsWith(UNCPREFIX_SERVER, StringComparison.Ordinal) || path.Length > 260)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -41,13 +41,13 @@ namespace Duplicati.Library.Snapshots
|
||||
|
||||
public static string PrefixWithUNC(string path)
|
||||
{
|
||||
if (path.StartsWith(UNCPREFIX_SERVER))
|
||||
if (path.StartsWith(UNCPREFIX_SERVER, StringComparison.Ordinal))
|
||||
return path;
|
||||
|
||||
if (path.StartsWith(UNCPREFIX))
|
||||
if (path.StartsWith(UNCPREFIX, StringComparison.Ordinal))
|
||||
return path;
|
||||
|
||||
if (path.StartsWith(PATHPREFIX_SERVER))
|
||||
if (path.StartsWith(PATHPREFIX_SERVER, StringComparison.Ordinal))
|
||||
return UNCPREFIX_SERVER + path.Remove(0, PATHPREFIX_SERVER.Length);
|
||||
|
||||
return UNCPREFIX + path;
|
||||
@@ -55,7 +55,7 @@ namespace Duplicati.Library.Snapshots
|
||||
|
||||
public static string StripUNCPrefix(string path)
|
||||
{
|
||||
if (path.StartsWith(UNCPREFIX))
|
||||
if (path.StartsWith(UNCPREFIX, StringComparison.Ordinal))
|
||||
return path.Substring(UNCPREFIX.Length);
|
||||
else
|
||||
return path;
|
||||
@@ -510,7 +510,7 @@ namespace Duplicati.Library.Snapshots
|
||||
|
||||
public Dictionary<string, string> GetMetadata(string path, bool isSymlink, bool followSymlink)
|
||||
{
|
||||
var isDirTarget = path.EndsWith(DIRSEP);
|
||||
var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal);
|
||||
var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path;
|
||||
var dict = new Dictionary<string, string>();
|
||||
|
||||
@@ -532,7 +532,7 @@ namespace Duplicati.Library.Snapshots
|
||||
|
||||
public void SetMetadata(string path, Dictionary<string, string> data, bool restorePermissions)
|
||||
{
|
||||
var isDirTarget = path.EndsWith(DIRSEP);
|
||||
var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal);
|
||||
var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path;
|
||||
|
||||
System.Security.AccessControl.FileSystemSecurity rules;
|
||||
|
||||
@@ -526,13 +526,13 @@ namespace Duplicati.Library.Snapshots
|
||||
/// Sends the dwIoControlCode to the device specified by hDevice.
|
||||
/// </summary>
|
||||
/// <param name="hDevice">Safe handle to the device </param>
|
||||
/// <param name="dwIoControlCode">Device IO Control Code to send</param>
|
||||
/// <param name="lpInBuffer">Input buffer if required</param>
|
||||
/// <param name="IoControlCode">Device IO Control Code to send</param>
|
||||
/// <param name="InBuffer">Input buffer if required</param>
|
||||
/// <param name="nInBufferSize">Size of input buffer</param>
|
||||
/// <param name="lpOutBuffer">Output buffer if required</param>
|
||||
/// <param name="OutBuffer">Output buffer if required</param>
|
||||
/// <param name="nOutBufferSize">Size of output buffer</param>
|
||||
/// <param name="lpBytesReturned">Number of bytes returned in output buffer</param>
|
||||
/// <param name="lpOverlapped">IntPtr to an 'OVERLAPPED' structure</param>
|
||||
/// <param name="pBytesReturned">Number of bytes returned in output buffer</param>
|
||||
/// <param name="overlapped">IntPtr to an 'OVERLAPPED' structure</param>
|
||||
/// <returns>'true' if successful, otherwise 'false'</returns>
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern bool DeviceIoControl(
|
||||
@@ -550,13 +550,13 @@ namespace Duplicati.Library.Snapshots
|
||||
/// Sends the dwIoControlCode to the device specified by hDevice.
|
||||
/// </summary>
|
||||
/// <param name="hDevice">Safe handle to the device </param>
|
||||
/// <param name="dwIoControlCode">Device IO Control Code to send</param>
|
||||
/// <param name="lpInBuffer">Input buffer if required</param>
|
||||
/// <param name="IoControlCode">Device IO Control Code to send</param>
|
||||
/// <param name="InBuffer">Input buffer if required</param>
|
||||
/// <param name="nInBufferSize">Size of input buffer</param>
|
||||
/// <param name="lpOutBuffer">Output buffer if required</param>
|
||||
/// <param name="OutBuffer">Output buffer if required</param>
|
||||
/// <param name="nOutBufferSize">Size of output buffer</param>
|
||||
/// <param name="lpBytesReturned">Number of bytes returned in output buffer</param>
|
||||
/// <param name="lpOverlapped">IntPtr to an 'OVERLAPPED' structure</param>
|
||||
/// <param name="pBytesReturned">Number of bytes returned in output buffer</param>
|
||||
/// <param name="overlapped">IntPtr to an 'OVERLAPPED' structure</param>
|
||||
/// <returns>'true' if successful, otherwise 'false'</returns>
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern bool DeviceIoControl(
|
||||
@@ -574,13 +574,13 @@ namespace Duplicati.Library.Snapshots
|
||||
/// Sends the dwIoControlCode to the device specified by hDevice.
|
||||
/// </summary>
|
||||
/// <param name="hDevice">Safe handle to the device </param>
|
||||
/// <param name="dwIoControlCode">Device IO Control Code to send</param>
|
||||
/// <param name="lpInBuffer">Input buffer if required</param>
|
||||
/// <param name="IoControlCode">Device IO Control Code to send</param>
|
||||
/// <param name="InBuffer">Input buffer if required</param>
|
||||
/// <param name="nInBufferSize">Size of input buffer</param>
|
||||
/// <param name="lpOutBuffer">Output buffer if required</param>
|
||||
/// <param name="OutBuffer">Output buffer if required</param>
|
||||
/// <param name="nOutBufferSize">Size of output buffer</param>
|
||||
/// <param name="lpBytesReturned">Number of bytes returned in output buffer</param>
|
||||
/// <param name="lpOverlapped">IntPtr to an 'OVERLAPPED' structure</param>
|
||||
/// <param name="pBytesReturned">Number of bytes returned in output buffer</param>
|
||||
/// <param name="overlapped">IntPtr to an 'OVERLAPPED' structure</param>
|
||||
/// <returns>'true' if successful, otherwise 'false'</returns>
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern bool DeviceIoControl(
|
||||
|
||||
@@ -281,7 +281,7 @@ namespace Duplicati.Library.Snapshots
|
||||
|
||||
localPath = localPath.Replace(root, String.Empty);
|
||||
|
||||
if (!volumePath.EndsWith(SLASH) && !localPath.StartsWith(SLASH))
|
||||
if (!volumePath.EndsWith(SLASH, StringComparison.Ordinal) && !localPath.StartsWith(SLASH, StringComparison.Ordinal))
|
||||
localPath = localPath.Insert(0, SLASH);
|
||||
localPath = localPath.Insert(0, volumePath);
|
||||
|
||||
|
||||
@@ -78,9 +78,9 @@ namespace Duplicati.Library.UsageReporter
|
||||
if (m != null)
|
||||
{
|
||||
var lines = m.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var product = lines.Where(x => x.Trim().StartsWith("ProductName:")).Select(x => x.Trim().Substring("ProductName:".Length).Trim()).FirstOrDefault();
|
||||
var version = lines.Where(x => x.Trim().StartsWith("ProductVersion:")).Select(x => x.Trim().Substring("ProductVersion:".Length).Trim()).FirstOrDefault();
|
||||
var build = lines.Where(x => x.Trim().StartsWith("BuildVersion:")).Select(x => x.Trim().Substring("BuildVersion:".Length).Trim()).FirstOrDefault();
|
||||
var product = lines.Where(x => x.Trim().StartsWith("ProductName:", StringComparison.Ordinal)).Select(x => x.Trim().Substring("ProductName:".Length).Trim()).FirstOrDefault();
|
||||
var version = lines.Where(x => x.Trim().StartsWith("ProductVersion:", StringComparison.Ordinal)).Select(x => x.Trim().Substring("ProductVersion:".Length).Trim()).FirstOrDefault();
|
||||
var build = lines.Where(x => x.Trim().StartsWith("BuildVersion:", StringComparison.Ordinal)).Select(x => x.Trim().Substring("BuildVersion:".Length).Trim()).FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(product))
|
||||
return string.Format("{0} {1} {2}", product, version, build);
|
||||
}
|
||||
@@ -145,7 +145,7 @@ namespace Duplicati.Library.UsageReporter
|
||||
if (m != null)
|
||||
{
|
||||
var lines = m.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var line = lines.Where(x => x.Trim().StartsWith("Description:")).Select(x => x.Trim().Substring("Description:".Length).Trim()).FirstOrDefault();
|
||||
var line = lines.Where(x => x.Trim().StartsWith("Description:", StringComparison.Ordinal)).Select(x => x.Trim().Substring("Description:".Length).Trim()).FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
return line;
|
||||
}
|
||||
@@ -154,7 +154,6 @@ namespace Duplicati.Library.UsageReporter
|
||||
return RunProgramAndReadOutput("uname", "-srvmpio");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,21 +41,21 @@ namespace Duplicati.Library.Utility
|
||||
|
||||
for (int i = 0; i < args.Count; i++)
|
||||
{
|
||||
if (args[i].StartsWith("--"))
|
||||
if (args[i].StartsWith("--", StringComparison.Ordinal))
|
||||
{
|
||||
string key = null;
|
||||
string value = null;
|
||||
if (args[i].IndexOf("=") > 0)
|
||||
if (args[i].IndexOf("=", StringComparison.Ordinal) > 0)
|
||||
{
|
||||
key = args[i].Substring(0, args[i].IndexOf("="));
|
||||
value = args[i].Substring(args[i].IndexOf("=") + 1);
|
||||
key = args[i].Substring(0, args[i].IndexOf("=", StringComparison.Ordinal));
|
||||
value = args[i].Substring(args[i].IndexOf("=", StringComparison.Ordinal) + 1);
|
||||
}
|
||||
else
|
||||
key = args[i];
|
||||
|
||||
//Skip the leading --
|
||||
key = key.Substring(2).ToLower();
|
||||
if (!string.IsNullOrEmpty(value) && value.Length > 1 && value.StartsWith("\"") && value.EndsWith("\""))
|
||||
if (!string.IsNullOrEmpty(value) && value.Length > 1 && value.StartsWith("\"", StringComparison.Ordinal) && value.EndsWith("\"", StringComparison.Ordinal))
|
||||
value = value.Substring(1, value.Length - 2);
|
||||
|
||||
//Last argument overwrites the current
|
||||
|
||||
@@ -1,417 +1,421 @@
|
||||
// Copyright (C) 2015, 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.Utility
|
||||
{
|
||||
[Flags]
|
||||
public enum DefaultFilterSet
|
||||
{
|
||||
None = 0,
|
||||
Windows = 1,
|
||||
OSX = 2,
|
||||
Linux = 4,
|
||||
All = Windows | OSX | Linux,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class defines a set of common filters for files that don't typically need to be backed up.
|
||||
/// These filters are largely based on the filters described here:
|
||||
/// https://superuser.com/questions/443890/what-files-file-types-and-folders-to-exclude-from-user-folder-backup
|
||||
/// </summary>
|
||||
public static class DefaultFilters
|
||||
{
|
||||
/// <summary>
|
||||
/// Case insensitive mapping from strings to filter sets
|
||||
/// /// </summary>
|
||||
private static readonly Dictionary<string, DefaultFilterSet> defaultFilterSetMapping =
|
||||
((DefaultFilterSet[])Enum.GetValues(typeof(DefaultFilterSet)))
|
||||
.ToDictionary(val => val.ToString(), val => val, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Filters common for all operating systems
|
||||
/// </summary>
|
||||
public static IEnumerable<string> Common
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultFilters.CreateCommonFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters for Windows
|
||||
/// </summary>
|
||||
public static IEnumerable<string> Windows
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultFilters.CreateWindowsFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters for OSX
|
||||
/// </summary>
|
||||
public static IEnumerable<string> OSX
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultFilters.CreateOSXFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters for Linux
|
||||
/// </summary>
|
||||
public static IEnumerable<string> Linux
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultFilters.CreateLinuxFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filters indicated by the given filter sets
|
||||
/// </summary>
|
||||
/// <param name="options">Filter set options</param>
|
||||
/// <returns>Default filters</returns>
|
||||
public static IEnumerable<FilterExpression> GetFilters(string options)
|
||||
{
|
||||
bool anyOptions = false;
|
||||
DefaultFilterSet filterSets = DefaultFilterSet.None;
|
||||
foreach (string option in options.Split(new[] { ',', '|', ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
anyOptions = true;
|
||||
DefaultFilterSet filter;
|
||||
if (DefaultFilters.defaultFilterSetMapping.TryGetValue(option, out filter))
|
||||
{
|
||||
filterSets |= filter;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(Strings.Filters.UnknownDefaultFilterSet(option));
|
||||
}
|
||||
}
|
||||
|
||||
// If no filter sets are specified, we use the default for the platform we're on
|
||||
if (!anyOptions)
|
||||
{
|
||||
if (Utility.IsClientWindows)
|
||||
{
|
||||
filterSets |= DefaultFilterSet.Windows;
|
||||
}
|
||||
|
||||
if (Utility.IsClientOSX)
|
||||
{
|
||||
filterSets |= DefaultFilterSet.OSX;
|
||||
}
|
||||
|
||||
if (Utility.IsClientLinux)
|
||||
{
|
||||
filterSets |= DefaultFilterSet.Linux;
|
||||
}
|
||||
}
|
||||
|
||||
if (filterSets == DefaultFilterSet.None)
|
||||
{
|
||||
return Enumerable.Empty<FilterExpression>();
|
||||
}
|
||||
|
||||
IEnumerable<string> filters = DefaultFilters.Common;
|
||||
|
||||
if ((filterSets & DefaultFilterSet.Windows) == DefaultFilterSet.Windows)
|
||||
{
|
||||
filters = filters.Concat(DefaultFilters.Windows);
|
||||
}
|
||||
|
||||
if ((filterSets & DefaultFilterSet.OSX) == DefaultFilterSet.OSX)
|
||||
{
|
||||
filters = filters.Concat(DefaultFilters.OSX);
|
||||
}
|
||||
|
||||
if ((filterSets & DefaultFilterSet.Linux) == DefaultFilterSet.Linux)
|
||||
{
|
||||
filters = filters.Concat(DefaultFilters.Linux);
|
||||
}
|
||||
|
||||
// Filter down to distinct filters, then convert them to filter expressions
|
||||
return filters.Distinct(Utility.ClientFilenameStringComparer).Select(filter => new FilterExpression(filter, false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates common filters
|
||||
/// </summary>
|
||||
/// <returns>Common filters</returns>
|
||||
private static string[] CreateCommonFilters()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Google/Chrome/*cache*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Google/Chrome/Safe Browsing*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/iPhoto Library/iPod Photo Cache/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Mozilla/Firefox/*cache*"),
|
||||
DefaultFilters.CreateRegexFilter(@".*/(cookies|permissions).sqllite(-.{3})?"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Windows filters
|
||||
/// </summary>
|
||||
/// <returns>Windows filters</returns>
|
||||
private static string[] CreateWindowsFilters()
|
||||
{
|
||||
var filters = new[]
|
||||
{
|
||||
DefaultFilters.CreateWildcardFilter(@"*.rbf"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*.tmp"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*.tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/$RECYCLE.BIN/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Apple Computer/Mobile Sync/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Local/Microsoft/Windows Store/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Local/Packages/*"), // https://superuser.com/questions/490925/explain-windows-8-windows-store-appdata-packages-and-what-to-backup
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Local/Temp*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/LocalLow/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Temp*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Application Data/Apple Computer/Mobile Sync/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Application Data/Application Data*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Cookies/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Google/Chrome/User Data/Default/Cookies"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Google/Chrome/User Data/Default/Cookies-journal"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/I386*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Internet Explorer/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Local Settings/History/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Local Settings/Temp*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/LocalService/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft*/RecoveryStore*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft*/Windows/*.edb"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft*/Windows/*.log"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft*/Windows/Cookies*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/MSOCache*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/NetHood/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/NetworkService/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/NTUSER*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/ntuser.dat*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/PrintHood/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Recent/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/RECYCLER/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Safari/Library/Caches/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/SendTo/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Start Menu/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Temporary Internet Files/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Thumbs.db"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*UsrClass.dat"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*UsrClass.dat.LOG"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/autoexec.bat"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Config.Msi*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/hiberfil.sys"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/pagefile.sys"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Program Files (x86)/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Program Files/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/ProgramData/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/swapfile.sys"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/System Volume Information/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Windows.old/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Windows/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Windows/Installer*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Windows/Temp*"),
|
||||
};
|
||||
|
||||
var extra = GetWindowsRegistryFilters();
|
||||
if (extra == null || extra.Length == 0)
|
||||
return filters;
|
||||
|
||||
return filters.Union(extra).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates OSX filters
|
||||
/// </summary>
|
||||
/// <returns>OSX filters</returns>
|
||||
private static string[] CreateOSXFilters()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
DefaultFilters.CreateWildcardFilter(@"*.fseventsd*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*.hotfiles.btree*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*.Spotlight-*/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*.Trash*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Application Support/Google/Chrome/Default/Cookies"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Application Support/Google/Chrome/Default/Cookies-journal"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/backups.backupdb/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/iP* Software Updates/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/iPhoto Library/iPod Photo Cache*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/iTunes/Album Artwork/Cache/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Application Support/SyncServices/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Caches/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Calendars/*/Info.plist"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Calendars/Calendar Cache"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Cookies/com.apple.appstore.plist"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Cookies/Cookies.binarycookies"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Logs/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Mail/*/Info.plist"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Mail/AvailableFeeds/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Mail/Envelope Index"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Mirrors/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/PubSub/Database/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/PubSub/Downloads/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/PubSub/Feeds/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Safari/HistoryIndex.sk"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Safari/Icons.db"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Safari/WebpageIcons.db"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Saved Application State/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/lost+found/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft User Data/Entourage Temp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Network Trash Folder/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Trash/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/VM Storage"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*Mobile.*Backups/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/.vol/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/afs/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/automount/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/bin/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/cores/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Desktop DB"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Desktop DF"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/dev/.*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/etc/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/mach.sym"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/mach_kernel"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/net/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Network/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Network/Servers*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Previous Systems*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/Network/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/automount/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/db/dhcpclient/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/db/fseventsd/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/folders/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/run/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/spool/postfix/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/vm/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/sbin/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/sw/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/System/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/System/Library/Extensions/Caches/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Users/Shared/SC Info*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/usr/*"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Linux filters
|
||||
/// </summary>
|
||||
/// <returns>Linux filters</returns>
|
||||
private static string[] CreateLinuxFilters()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
DefaultFilters.CreateWildcardFilter(@"*/.config/google-chrome/Default/Cookies"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/.config/google-chrome/Default/Cookies-journal"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/lost+found/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*~"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/bin/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/boot/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/dev/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/etc/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/initrd/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/lib/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/opt/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/proc/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/sbin/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/selinux/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/srv/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/sys/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/usr/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/var/*"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a wildcard filter
|
||||
/// </summary>
|
||||
/// <param name="filter">Filter text</param>
|
||||
/// <returns>Wildcard filter</returns>
|
||||
private static string CreateWildcardFilter(string filter)
|
||||
{
|
||||
// Create a filter with the given name.
|
||||
// However, in order to match paths correctly, the directory separators need to be normalized to match the system default.
|
||||
return filter.Replace(System.IO.Path.AltDirectorySeparatorChar, System.IO.Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Regex filter
|
||||
/// </summary>
|
||||
/// <param name="filter">Filter text</param>
|
||||
/// <returns>Regex filter</returns>
|
||||
private static string CreateRegexFilter(string filter)
|
||||
{
|
||||
// Create a filter with the given name.
|
||||
// However, in order to match paths correctly, the directory separators need to be normalized to match the system default.
|
||||
string escapedAlt = System.Text.RegularExpressions.Regex.Escape(System.IO.Path.AltDirectorySeparatorChar.ToString());
|
||||
string escaped = System.Text.RegularExpressions.Regex.Escape(System.IO.Path.DirectorySeparatorChar.ToString());
|
||||
return "[" + filter.Replace(escapedAlt, escaped) + "]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper method for getting a list of exclude paths from the Windows registry.
|
||||
/// This method guards the call to <see cref="GetWindowsRegistryFiltersInternal"/> to avoid loader errors.
|
||||
/// </summary>
|
||||
/// <returns>The list of paths to exclude.</returns>
|
||||
private static string[] GetWindowsRegistryFilters()
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetWindowsRegistryFiltersInternal();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return new string[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that reads the Windows registry and finds paths to exclude.
|
||||
/// This method should not be called directly as that could cause loader errors on Mono.
|
||||
/// </summary>
|
||||
/// <returns>The list of paths to exclude.</returns>
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
|
||||
private static string[] GetWindowsRegistryFiltersInternal()
|
||||
{
|
||||
var rk = Microsoft.Win32.RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, Microsoft.Win32.RegistryView.Default);
|
||||
if (rk == null)
|
||||
return new string[0];
|
||||
|
||||
var sk = rk.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\BackupRestore\FilesNotToBackup");
|
||||
if (sk == null)
|
||||
return new string[0];
|
||||
|
||||
// Each value in this key is a potential path
|
||||
return sk.GetValueNames()
|
||||
.Where(x => x != null)
|
||||
// Copyright (C) 2015, 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.Utility
|
||||
{
|
||||
[Flags]
|
||||
public enum DefaultFilterSet
|
||||
{
|
||||
None = 0,
|
||||
Windows = 1,
|
||||
OSX = 2,
|
||||
Linux = 4,
|
||||
All = Windows | OSX | Linux,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class defines a set of common filters for files that don't typically need to be backed up.
|
||||
/// These filters are largely based on the filters described here:
|
||||
/// https://superuser.com/questions/443890/what-files-file-types-and-folders-to-exclude-from-user-folder-backup
|
||||
/// </summary>
|
||||
public static class DefaultFilters
|
||||
{
|
||||
/// <summary>
|
||||
/// Case insensitive mapping from strings to filter sets
|
||||
/// /// </summary>
|
||||
private static readonly Dictionary<string, DefaultFilterSet> defaultFilterSetMapping =
|
||||
((DefaultFilterSet[])Enum.GetValues(typeof(DefaultFilterSet)))
|
||||
.ToDictionary(val => val.ToString(), val => val, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Filters common for all operating systems
|
||||
/// </summary>
|
||||
public static IEnumerable<string> Common
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultFilters.CreateCommonFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters for Windows
|
||||
/// </summary>
|
||||
public static IEnumerable<string> Windows
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultFilters.CreateWindowsFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters for OSX
|
||||
/// </summary>
|
||||
public static IEnumerable<string> OSX
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultFilters.CreateOSXFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters for Linux
|
||||
/// </summary>
|
||||
public static IEnumerable<string> Linux
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultFilters.CreateLinuxFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filters indicated by the given filter sets
|
||||
/// </summary>
|
||||
/// <param name="options">Filter set options</param>
|
||||
/// <returns>Default filters</returns>
|
||||
public static IEnumerable<FilterExpression> GetFilters(string options)
|
||||
{
|
||||
bool anyOptions = false;
|
||||
DefaultFilterSet filterSets = DefaultFilterSet.None;
|
||||
foreach (string option in options.Split(new[] { ',', '|', ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
anyOptions = true;
|
||||
DefaultFilterSet filter;
|
||||
if (DefaultFilters.defaultFilterSetMapping.TryGetValue(option, out filter))
|
||||
{
|
||||
filterSets |= filter;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(Strings.Filters.UnknownDefaultFilterSet(option));
|
||||
}
|
||||
}
|
||||
|
||||
// If no filter sets are specified, we use the default for the platform we're on
|
||||
if (!anyOptions)
|
||||
{
|
||||
if (Utility.IsClientWindows)
|
||||
{
|
||||
filterSets |= DefaultFilterSet.Windows;
|
||||
}
|
||||
|
||||
if (Utility.IsClientOSX)
|
||||
{
|
||||
filterSets |= DefaultFilterSet.OSX;
|
||||
}
|
||||
|
||||
if (Utility.IsClientLinux)
|
||||
{
|
||||
filterSets |= DefaultFilterSet.Linux;
|
||||
}
|
||||
}
|
||||
|
||||
if (filterSets == DefaultFilterSet.None)
|
||||
{
|
||||
return Enumerable.Empty<FilterExpression>();
|
||||
}
|
||||
|
||||
IEnumerable<string> filters = DefaultFilters.Common;
|
||||
|
||||
if ((filterSets & DefaultFilterSet.Windows) == DefaultFilterSet.Windows)
|
||||
{
|
||||
filters = filters.Concat(DefaultFilters.Windows);
|
||||
}
|
||||
|
||||
if ((filterSets & DefaultFilterSet.OSX) == DefaultFilterSet.OSX)
|
||||
{
|
||||
filters = filters.Concat(DefaultFilters.OSX);
|
||||
}
|
||||
|
||||
if ((filterSets & DefaultFilterSet.Linux) == DefaultFilterSet.Linux)
|
||||
{
|
||||
filters = filters.Concat(DefaultFilters.Linux);
|
||||
}
|
||||
|
||||
// Filter down to distinct filters, then convert them to filter expressions
|
||||
return filters.Distinct(Utility.ClientFilenameStringComparer).Select(filter => new FilterExpression(filter, false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates common filters
|
||||
/// </summary>
|
||||
/// <returns>Common filters</returns>
|
||||
private static string[] CreateCommonFilters()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Google/Chrome/*cache*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Google/Chrome/Safe Browsing*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/iPhoto Library/iPod Photo Cache/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Mozilla/Firefox/*cache*"),
|
||||
DefaultFilters.CreateRegexFilter(@".*/(cookies|permissions).sqllite(-.{3})?"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Windows filters
|
||||
/// </summary>
|
||||
/// <returns>Windows filters</returns>
|
||||
private static string[] CreateWindowsFilters()
|
||||
{
|
||||
var filters = new[]
|
||||
{
|
||||
DefaultFilters.CreateWildcardFilter(@"*/config.msi/*.rbf"), // https://github.com/duplicati/duplicati/issues/2886
|
||||
DefaultFilters.CreateWildcardFilter(@"*.tmp"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*.tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/$RECYCLE.BIN/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Apple Computer/Mobile Sync/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Local/Microsoft/Windows Store/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Local/Packages/*"), // https://superuser.com/questions/490925/explain-windows-8-windows-store-appdata-packages-and-what-to-backup
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Local/Temp*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/LocalLow/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/AppData/Temp*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Application Data/Apple Computer/Mobile Sync/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Application Data/Application Data*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Cookies/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Google/Chrome/User Data/Default/Cookies"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Google/Chrome/User Data/Default/Cookies-journal"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/I386*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Internet Explorer/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Local Settings/History/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Local Settings/Temp*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/LocalService/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft*/RecoveryStore*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft*/Windows/*.edb"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft*/Windows/*.log"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft*/Windows/Cookies*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/MSOCache*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/NetHood/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/NetworkService/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/NTUSER*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/ntuser.dat*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/PrintHood/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Recent/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/RECYCLER/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Safari/Library/Caches/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/SendTo/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Start Menu/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Temporary Internet Files/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Thumbs.db"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*UsrClass.dat"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*UsrClass.dat.LOG"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/autoexec.bat"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Config.Msi*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/hiberfil.sys"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/pagefile.sys"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Program Files (x86)/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Program Files/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/ProgramData/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/swapfile.sys"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/System Volume Information/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Windows.old/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Windows/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Windows/Installer*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"?:/Windows/Temp*"),
|
||||
};
|
||||
|
||||
var extra = GetWindowsRegistryFilters();
|
||||
if (extra == null || extra.Length == 0)
|
||||
return filters;
|
||||
|
||||
return filters.Union(extra).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates OSX filters
|
||||
/// </summary>
|
||||
/// <returns>OSX filters</returns>
|
||||
private static string[] CreateOSXFilters()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
DefaultFilters.CreateWildcardFilter(@"*.fseventsd*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*.hotfiles.btree*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*.Spotlight-*/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*.Trash*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Application Support/Google/Chrome/Default/Cookies"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Application Support/Google/Chrome/Default/Cookies-journal"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/backups.backupdb/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/iP* Software Updates/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/iPhoto Library/iPod Photo Cache*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/iTunes/Album Artwork/Cache/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Application Support/SyncServices/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Caches/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Calendars/*/Info.plist"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Calendars/Calendar Cache"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Cookies/com.apple.appstore.plist"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Cookies/Cookies.binarycookies"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Logs/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Mail/*/Info.plist"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Mail/AvailableFeeds/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Mail/Envelope Index"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Mirrors/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/PubSub/Database/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/PubSub/Downloads/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/PubSub/Feeds/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Safari/HistoryIndex.sk"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Safari/Icons.db"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Safari/WebpageIcons.db"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Library/Saved Application State/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/lost+found/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Microsoft User Data/Entourage Temp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Network Trash Folder/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/Trash/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/VM Storage"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*Mobile.*Backups/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/.vol/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/afs/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/automount/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/bin/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/cores/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Desktop DB"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Desktop DF"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/dev/.*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/etc/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/mach.sym"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/mach_kernel"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/net/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Network/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Network/Servers*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Previous Systems*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/Network/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/automount/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/db/dhcpclient/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/db/fseventsd/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/folders/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/run/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/spool/postfix/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/private/var/vm/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/sbin/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/sw/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/System/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/System/Library/Extensions/Caches/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/Users/Shared/SC Info*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/usr/*"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Linux filters
|
||||
/// </summary>
|
||||
/// <returns>Linux filters</returns>
|
||||
private static string[] CreateLinuxFilters()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
DefaultFilters.CreateWildcardFilter(@"*/.config/google-chrome/Default/Cookies"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/.config/google-chrome/Default/Cookies-journal"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*/lost+found/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"*~"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/bin/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/boot/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/dev/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/etc/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/initrd/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/lib/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/opt/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/proc/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/sbin/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/selinux/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/srv/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/sys/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/tmp/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/usr/*"),
|
||||
DefaultFilters.CreateWildcardFilter(@"/var/*"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a wildcard filter
|
||||
/// </summary>
|
||||
/// <param name="filter">Filter text</param>
|
||||
/// <returns>Wildcard filter</returns>
|
||||
private static string CreateWildcardFilter(string filter)
|
||||
{
|
||||
// Create a filter with the given name.
|
||||
// However, in order to match paths correctly, the directory separators need to be normalized to match the system default.
|
||||
return filter.Replace(System.IO.Path.AltDirectorySeparatorChar, System.IO.Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Regex filter
|
||||
/// </summary>
|
||||
/// <param name="filter">Filter text</param>
|
||||
/// <returns>Regex filter</returns>
|
||||
private static string CreateRegexFilter(string filter)
|
||||
{
|
||||
// Create a filter with the given name.
|
||||
// However, in order to match paths correctly, the directory separators need to be normalized to match the system default.
|
||||
string escapedAlt = System.Text.RegularExpressions.Regex.Escape(System.IO.Path.AltDirectorySeparatorChar.ToString());
|
||||
string escaped = System.Text.RegularExpressions.Regex.Escape(System.IO.Path.DirectorySeparatorChar.ToString());
|
||||
return "[" + filter.Replace(escapedAlt, escaped) + "]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper method for getting a list of exclude paths from the Windows registry.
|
||||
/// This method guards the call to <see cref="GetWindowsRegistryFiltersInternal"/> to avoid loader errors.
|
||||
/// </summary>
|
||||
/// <returns>The list of paths to exclude.</returns>
|
||||
private static string[] GetWindowsRegistryFilters()
|
||||
{
|
||||
if (Utility.IsClientWindows)
|
||||
{
|
||||
// One Windows, filters may also be stored in the registry
|
||||
try
|
||||
{
|
||||
return GetWindowsRegistryFiltersInternal();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that reads the Windows registry and finds paths to exclude.
|
||||
/// This method should not be called directly as that could cause loader errors on Mono.
|
||||
/// </summary>
|
||||
/// <returns>The list of paths to exclude.</returns>
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
|
||||
private static string[] GetWindowsRegistryFiltersInternal()
|
||||
{
|
||||
var rk = Microsoft.Win32.RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, Microsoft.Win32.RegistryView.Default);
|
||||
if (rk == null)
|
||||
return new string[0];
|
||||
|
||||
var sk = rk.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\BackupRestore\FilesNotToBackup");
|
||||
if (sk == null)
|
||||
return new string[0];
|
||||
|
||||
// Each value in this key is a potential path
|
||||
return sk.GetValueNames()
|
||||
.Where(x => x != null)
|
||||
.SelectMany(x =>
|
||||
{
|
||||
var v = sk.GetValue(x);
|
||||
@@ -421,15 +425,15 @@ namespace Duplicati.Library.Utility
|
||||
return (string[])v;
|
||||
else
|
||||
return new string[0];
|
||||
})
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Select(x => Environment.ExpandEnvironmentVariables(x))
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Where(x => x.IndexOfAny(System.IO.Path.GetInvalidPathChars()) < 0)
|
||||
.Where(x => System.IO.Path.IsPathRooted(x))
|
||||
.Select(x => x.EndsWith(" /s", StringComparison.OrdinalIgnoreCase) ? x.Substring(0, x.Length - 3).TrimEnd() : x)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Select(x => Environment.ExpandEnvironmentVariables(x))
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Where(x => x.IndexOfAny(System.IO.Path.GetInvalidPathChars()) < 0)
|
||||
.Where(x => System.IO.Path.IsPathRooted(x))
|
||||
.Select(x => x.EndsWith(" /s", StringComparison.OrdinalIgnoreCase) ? x.Substring(0, x.Length - 3).TrimEnd() : x)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
<Compile Include="OverrideableStream.cs" />
|
||||
<Compile Include="ProgressReportingStream.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ShaderStream.cs" />
|
||||
<Compile Include="Sizeparser.cs" />
|
||||
<Compile Include="SslCertificateValidator.cs" />
|
||||
<Compile Include="TempFile.cs" />
|
||||
@@ -102,7 +103,5 @@
|
||||
<Name>Duplicati.Library.Localization</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Power\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<ItemGroup />
|
||||
</Project>
|
||||
@@ -49,10 +49,22 @@ namespace Duplicati.Library.Utility
|
||||
/// </summary>
|
||||
public class FilterExpression : IFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of a filter entry
|
||||
/// </summary>
|
||||
private struct FilterEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the filter
|
||||
/// </summary>
|
||||
public readonly FilterType Type;
|
||||
/// <summary>
|
||||
/// The filter string
|
||||
/// </summary>
|
||||
public readonly string Filter;
|
||||
/// <summary>
|
||||
/// The regular expression version of the filter
|
||||
/// </summary>
|
||||
public readonly System.Text.RegularExpressions.Regex Regexp;
|
||||
|
||||
/// <summary>
|
||||
@@ -64,12 +76,18 @@ namespace Duplicati.Library.Utility
|
||||
/// </summary>
|
||||
private const char MULTIPLE_WILDCARD = '*';
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The regular expression flags
|
||||
/// </summary>
|
||||
private static readonly System.Text.RegularExpressions.RegexOptions REGEXP_OPTIONS =
|
||||
System.Text.RegularExpressions.RegexOptions.Compiled |
|
||||
System.Text.RegularExpressions.RegexOptions.ExplicitCapture |
|
||||
(Library.Utility.Utility.IsFSCaseSensitive ? System.Text.RegularExpressions.RegexOptions.None : System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Utility.FilterExpression.FilterEntry"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="filter">The filter string to use.</param>
|
||||
public FilterEntry(string filter)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
@@ -78,7 +96,7 @@ namespace Duplicati.Library.Utility
|
||||
this.Filter = null;
|
||||
this.Regexp = null;
|
||||
}
|
||||
if (filter.StartsWith("[") && filter.EndsWith("]"))
|
||||
else if (filter.StartsWith("[", StringComparison.Ordinal) && filter.EndsWith("]", StringComparison.Ordinal))
|
||||
{
|
||||
this.Type = FilterType.Regexp;
|
||||
this.Filter = filter.Substring(1, filter.Length - 2);
|
||||
@@ -255,7 +273,6 @@ namespace Duplicati.Library.Utility
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Duplicati.Library.Utility.FilterExpression"/> instance, representing an empty filter.
|
||||
/// </summary>
|
||||
/// <param name="filter">The filter string that represents the filter</param>
|
||||
public FilterExpression()
|
||||
: this((IEnumerable<string>)null, true)
|
||||
{
|
||||
@@ -302,7 +319,7 @@ namespace Duplicati.Library.Utility
|
||||
if (string.IsNullOrWhiteSpace(filter))
|
||||
return null;
|
||||
|
||||
if (filter.Length < 2 || (filter.StartsWith("[") && filter.EndsWith("]")))
|
||||
if (filter.Length < 2 || (filter.StartsWith("[", StringComparison.Ordinal) && filter.EndsWith("]", StringComparison.Ordinal)))
|
||||
return new string[] { filter };
|
||||
else
|
||||
return filter.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -340,7 +357,7 @@ namespace Duplicati.Library.Utility
|
||||
first = false;
|
||||
}
|
||||
|
||||
combined += "|(" + f.Regexp.ToString() + ")";
|
||||
combined += "|(" + f.Regexp + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,9 +601,9 @@ namespace Duplicati.Library.Utility
|
||||
foreach(var n in filters)
|
||||
{
|
||||
bool include;
|
||||
if (n.StartsWith("+"))
|
||||
if (n.StartsWith("+", StringComparison.Ordinal))
|
||||
include = true;
|
||||
else if (n.StartsWith("-"))
|
||||
else if (n.StartsWith("-", StringComparison.Ordinal))
|
||||
include = false;
|
||||
else
|
||||
continue;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Duplicati.Library.Utility
|
||||
else if (this.Second.Empty)
|
||||
return this.First.ToString();
|
||||
else
|
||||
return "(" + this.First.ToString() + ") || (" + this.Second.ToString() + ")";
|
||||
return "(" + this.First + ") || (" + this.Second + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#region Disclaimer / License
|
||||
// Copyright (C) 2015, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
#endregion
|
||||
using System;
|
||||
|
||||
namespace Duplicati.Library.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// A small utility stream that allows to keep streams open and counts the bytes sent through. </summary>
|
||||
/// </summary>
|
||||
public class ShaderStream : System.IO.Stream
|
||||
{
|
||||
private readonly System.IO.Stream m_baseStream;
|
||||
private readonly bool m_keepBaseOpen;
|
||||
private long m_read = 0;
|
||||
private long m_written = 0;
|
||||
|
||||
public ShaderStream(System.IO.Stream baseStream, bool keepBaseOpen)
|
||||
{
|
||||
if (baseStream == null)
|
||||
throw new ArgumentNullException(nameof(baseStream));
|
||||
this.m_baseStream = baseStream;
|
||||
this.m_keepBaseOpen = keepBaseOpen;
|
||||
}
|
||||
|
||||
public long TotalBytesRead { get { return m_read; } }
|
||||
public long TotalBytesWritten { get { return m_written; } }
|
||||
|
||||
public override bool CanRead { get { return m_baseStream.CanRead; } }
|
||||
public override bool CanSeek { get { return m_baseStream.CanSeek; } }
|
||||
public override bool CanWrite { get { return m_baseStream.CanWrite; } }
|
||||
public override long Length { get { return m_baseStream.Length; } }
|
||||
public override long Position
|
||||
{
|
||||
get { return m_baseStream.Position; }
|
||||
set { m_baseStream.Position = value; }
|
||||
}
|
||||
public override void Flush() { m_baseStream.Flush(); }
|
||||
public override long Seek(long offset, System.IO.SeekOrigin origin) { return m_baseStream.Seek(offset, origin); }
|
||||
public override void SetLength(long value) { m_baseStream.SetLength(value); }
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int r = m_baseStream.Read(buffer, offset, count);
|
||||
m_read += r;
|
||||
return r;
|
||||
}
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
m_baseStream.Write(buffer, offset, count);
|
||||
m_written += count;
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && !m_keepBaseOpen)
|
||||
m_baseStream.Close();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace Duplicati.Library.Utility
|
||||
|
||||
size = size.ToLower().Trim();
|
||||
|
||||
if (size.EndsWith("tb") || size.EndsWith("gb") || size.EndsWith("mb") || size.EndsWith("kb") || size.EndsWith("b"))
|
||||
if (size.EndsWith("tb", StringComparison.Ordinal) || size.EndsWith("gb", StringComparison.Ordinal) || size.EndsWith("mb", StringComparison.Ordinal) || size.EndsWith("kb", StringComparison.Ordinal) || size.EndsWith("b", StringComparison.Ordinal))
|
||||
return ParseSize(size);
|
||||
else
|
||||
return ParseSize(size + " " + defaultSuffix);
|
||||
@@ -49,27 +49,27 @@ namespace Duplicati.Library.Utility
|
||||
|
||||
long factor = 1;
|
||||
|
||||
if (size.EndsWith("tb"))
|
||||
if (size.EndsWith("tb", StringComparison.Ordinal))
|
||||
{
|
||||
factor = 1024L * 1024 * 1024 * 1024;
|
||||
size = size.Substring(0, size.Length - 2).Trim();
|
||||
}
|
||||
else if (size.EndsWith("gb"))
|
||||
else if (size.EndsWith("gb", StringComparison.Ordinal))
|
||||
{
|
||||
factor = 1024 * 1024 * 1024;
|
||||
size = size.Substring(0, size.Length - 2).Trim();
|
||||
}
|
||||
else if (size.EndsWith("mb"))
|
||||
else if (size.EndsWith("mb", StringComparison.Ordinal))
|
||||
{
|
||||
factor = 1024 * 1024;
|
||||
size = size.Substring(0, size.Length - 2).Trim();
|
||||
}
|
||||
else if (size.EndsWith("kb"))
|
||||
else if (size.EndsWith("kb", StringComparison.Ordinal))
|
||||
{
|
||||
factor = 1024;
|
||||
size = size.Substring(0, size.Length - 2).Trim();
|
||||
}
|
||||
else if (size.EndsWith("b"))
|
||||
else if (size.EndsWith("b", StringComparison.Ordinal))
|
||||
size = size.Substring(0, size.Length - 1).Trim();
|
||||
|
||||
long r;
|
||||
|
||||
@@ -72,13 +72,17 @@ namespace Duplicati.Library.Utility
|
||||
m_fileTrace.Add(s, st);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
private static string GenerateUniqueName()
|
||||
{
|
||||
return APPLICATION_PREFIX + Guid.NewGuid().ToString();
|
||||
}
|
||||
#endif
|
||||
/// <summary>
|
||||
/// The name of the temp file
|
||||
/// </summary>
|
||||
public string Name => m_path;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all temporary files found in the current tempdir, that matches the application prefix
|
||||
|
||||
@@ -37,6 +37,11 @@ namespace Duplicati.Library.Utility
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full name of the temp file.
|
||||
/// </summary>
|
||||
public string Name => m_file?.Name;
|
||||
|
||||
public TempFileStream(string file)
|
||||
: this(TempFile.WrapExistingFile(file))
|
||||
{
|
||||
@@ -44,6 +49,8 @@ namespace Duplicati.Library.Utility
|
||||
|
||||
public TempFileStream(TempFile file)
|
||||
{
|
||||
if (file == null)
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
m_file = file;
|
||||
m_stream = System.IO.File.Open(file, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
|
||||
}
|
||||
|
||||
@@ -92,8 +92,6 @@ namespace Duplicati.Library.Utility
|
||||
/// Creates a throttle around a stream.
|
||||
/// </summary>
|
||||
/// <param name="basestream">The stream to throttle</param>
|
||||
/// <param name="readspeed">The maximum number of bytes pr. second to read. Specify a number less than 1 to allow unlimited speed.</param>
|
||||
/// <param name="writespeed">The maximum number of bytes pr. second to write. Specify a number less than 1 to allow unlimited speed.</param>
|
||||
public ThrottledStream(Stream basestream, Action<ThrottledStream> updateLimits)
|
||||
: base(basestream)
|
||||
{
|
||||
@@ -217,11 +215,6 @@ namespace Duplicati.Library.Utility
|
||||
/// <summary>
|
||||
/// Calculates the speed, and inserts appropriate delays
|
||||
/// </summary>
|
||||
/// <param name="read">True if the operation is read, false otherwise</param>
|
||||
/// <param name="buffer">The data buffer</param>
|
||||
/// <param name="offset">The offset into the buffer</param>
|
||||
/// <param name="count">The number of bytes to read or write</param>
|
||||
/// <returns>The number of bytes processed while delaying</returns>
|
||||
private void DelayIfRequired(ref long limit, int count, ref DateTime last_sample, ref long last_count, ref double current_speed)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user