From 9c1e55f2413bc625bf5b6533fd1e662886172776 Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sat, 21 Oct 2017 09:13:26 -0700 Subject: [PATCH 01/40] Expose field that represents users's home directory. We will be removing support for tilde expansion, and this field can be used instead of ~ internally in the code. --- Duplicati/Library/Utility/Utility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 3a2443488..12d42bf21 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -974,7 +974,7 @@ namespace Duplicati.Library.Utility /// /// The path to the users home directory /// - private static readonly string HOME_PATH = Environment.GetFolderPath(IsClientLinux ? Environment.SpecialFolder.Personal : Environment.SpecialFolder.UserProfile); + public static readonly string HOME_PATH = Environment.GetFolderPath(IsClientLinux ? Environment.SpecialFolder.Personal : Environment.SpecialFolder.UserProfile); /// /// Expands environment variables, including the tilde character From c9aa6cf5fb05e2e562b5bc500fcd3ef44ae08c76 Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sat, 21 Oct 2017 09:21:07 -0700 Subject: [PATCH 02/40] Avoid performing tilde expansion. The expansion being performed was often incorrect, as it's possible for the tilde character to appear as part of the path where it should not be expanded to the user's home directory. Properly supporting tilde expansion is complicated and requires additional study. Instead of supporting it only partially, we have decided to simplify things and just drop tilde expansion for now. See the discussion in issue #2619, as well as issues #2325 and #2555. --- Duplicati/Library/Utility/Utility.cs | 10 ++++------ Duplicati/UnitTest/BasicSetupHelper.cs | 2 +- Duplicati/UnitTest/GeneralBlackBoxTesting.cs | 2 +- Duplicati/UnitTest/SVNCheckoutsTest.cs | 5 ----- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 12d42bf21..78da0a201 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -977,13 +977,13 @@ namespace Duplicati.Library.Utility public static readonly string HOME_PATH = Environment.GetFolderPath(IsClientLinux ? Environment.SpecialFolder.Personal : Environment.SpecialFolder.UserProfile); /// - /// Expands environment variables, including the tilde character + /// Expands environment variables. /// /// The expanded string. /// The string to expand. public static string ExpandEnvironmentVariables(string str) { - return Environment.ExpandEnvironmentVariables(str.Replace("~", HOME_PATH)); + return Environment.ExpandEnvironmentVariables(str); } /// @@ -997,7 +997,7 @@ namespace Duplicati.Library.Utility private static readonly Regex ENVIRONMENT_VARIABLE_MATCHER_LINUX = new Regex(@"\$(?\w+)|(\{(?[^\}]+)\})"); /// - /// Expands environment variables, including the tilde character, in a RegExp safe format + /// Expands environment variables in a RegExp safe format /// /// The expanded string. /// The string to expand. @@ -1012,9 +1012,7 @@ namespace Duplicati.Library.Utility // TODO: Should we switch to using the native format, instead of following the Windows scheme? //IsClientLinux ? ENVIRONMENT_VARIABLE_MATCHER_LINUX : ENVIRONMENT_VARIABLE_MATCHER_WINDOWS - ENVIRONMENT_VARIABLE_MATCHER_WINDOWS - .Replace(str.Replace("~", Regex.Escape(HOME_PATH)), (m) => - Regex.Escape(lookup(m.Groups["name"].Value))); + ENVIRONMENT_VARIABLE_MATCHER_WINDOWS.Replace(str, (m) => Regex.Escape(lookup(m.Groups["name"].Value))); } /// diff --git a/Duplicati/UnitTest/BasicSetupHelper.cs b/Duplicati/UnitTest/BasicSetupHelper.cs index ce045a3da..f039f2cb8 100644 --- a/Duplicati/UnitTest/BasicSetupHelper.cs +++ b/Duplicati/UnitTest/BasicSetupHelper.cs @@ -29,7 +29,7 @@ namespace Duplicati.UnitTest /// protected static readonly string BASEFOLDER = string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("UNITTEST_BASEFOLDER")) - ? Library.Utility.Utility.ExpandEnvironmentVariables(Path.Combine("~", "testdata")) + ? Path.Combine(Library.Utility.Utility.HOME_PATH, "testdata") : Environment.GetEnvironmentVariable("UNITTEST_BASEFOLDER"); /// diff --git a/Duplicati/UnitTest/GeneralBlackBoxTesting.cs b/Duplicati/UnitTest/GeneralBlackBoxTesting.cs index 9143d128f..fb9e508ec 100644 --- a/Duplicati/UnitTest/GeneralBlackBoxTesting.cs +++ b/Duplicati/UnitTest/GeneralBlackBoxTesting.cs @@ -29,7 +29,7 @@ namespace Duplicati.UnitTest private static readonly string SOURCE_FOLDERS = Path.Combine( string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("UNITTEST_BASEFOLDER")) - ? Library.Utility.Utility.ExpandEnvironmentVariables(Path.Combine("~", "testdata")) + ? Path.Combine(Library.Utility.Utility.HOME_PATH, "testdata") : Environment.GetEnvironmentVariable("UNITTEST_BASEFOLDER") , "DSMCBE"); diff --git a/Duplicati/UnitTest/SVNCheckoutsTest.cs b/Duplicati/UnitTest/SVNCheckoutsTest.cs index 74107abf4..3416c09e0 100644 --- a/Duplicati/UnitTest/SVNCheckoutsTest.cs +++ b/Duplicati/UnitTest/SVNCheckoutsTest.cs @@ -99,11 +99,6 @@ namespace Duplicati.UnitTest where !string.IsNullOrWhiteSpace(x) select Library.Utility.Utility.ExpandEnvironmentVariables(x)).ToArray(); - //Expand the tilde to home folder on Linux/OSX - if (Utility.IsClientLinux) - folders = (from x in folders - select x.Replace("~", Environment.GetFolderPath(Environment.SpecialFolder.Personal))).ToArray(); - foreach (var f in folders) foreach (var n in f.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) if (!System.IO.Directory.Exists(n)) From b1ad31516d6f0b16e60b3abb5912a6d912845cb8 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Thu, 9 Nov 2017 17:40:06 +0100 Subject: [PATCH 03/40] Added guards to prevent throttle parsing errors to bring down the server on startup. --- Duplicati/Server/LiveControls.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Duplicati/Server/LiveControls.cs b/Duplicati/Server/LiveControls.cs index 827651f5f..32e600ee7 100644 --- a/Duplicati/Server/LiveControls.cs +++ b/Duplicati/Server/LiveControls.cs @@ -185,9 +185,24 @@ namespace Duplicati.Server m_priority = settings.ThreadPriorityOverride; if (!string.IsNullOrEmpty(settings.DownloadSpeedLimit)) - m_downloadLimit = Library.Utility.Sizeparser.ParseSize(settings.DownloadSpeedLimit, "kb"); + try + { + m_downloadLimit = Library.Utility.Sizeparser.ParseSize(settings.DownloadSpeedLimit, "kb"); + } + catch (Exception ex) + { + Library.Logging.Log.WriteMessage(string.Format("Failed to parse download limit: {0}", settings.DownloadSpeedLimit), Library.Logging.LogMessageType.Error, ex); + } + if (!string.IsNullOrEmpty(settings.UploadSpeedLimit)) - m_uploadLimit = Library.Utility.Sizeparser.ParseSize(settings.UploadSpeedLimit, "kb"); + try + { + m_uploadLimit = Library.Utility.Sizeparser.ParseSize(settings.UploadSpeedLimit, "kb"); + } + catch (Exception ex) + { + Library.Logging.Log.WriteMessage(string.Format("Failed to parse upload limit: {0}", settings.UploadSpeedLimit), Library.Logging.LogMessageType.Error, ex); + } try { From f4511d0bbb8fb266e2dfeef3050ca4be40d19b77 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Thu, 9 Nov 2017 17:59:36 +0100 Subject: [PATCH 04/40] Updated the `affected` command to check if the local database is found, and to return an error if not. Also updated the help text to indicate that a database is required. --- Duplicati/CommandLine/help.txt | 2 +- Duplicati/Library/Main/Operation/ListAffected.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Duplicati/CommandLine/help.txt b/Duplicati/CommandLine/help.txt index cab82daf6..27b2d8395 100644 --- a/Duplicati/CommandLine/help.txt +++ b/Duplicati/CommandLine/help.txt @@ -269,7 +269,7 @@ Usage: test-filters [] Usage: affected [] [] - 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 diff --git a/Duplicati/Library/Main/Operation/ListAffected.cs b/Duplicati/Library/Main/Operation/ListAffected.cs index b5a491269..aee7c2bec 100644 --- a/Duplicati/Library/Main/Operation/ListAffected.cs +++ b/Duplicati/Library/Main/Operation/ListAffected.cs @@ -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 args, Action 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); From 1ce4ccd61fcfed4635225f36157b488aeb8cc2a3 Mon Sep 17 00:00:00 2001 From: Roberto Ribeiro Date: Wed, 15 Nov 2017 15:16:25 -0200 Subject: [PATCH 05/40] Changed subqueries to improve folder browsing in restore --- Duplicati/Library/Main/Database/LocalListDatabase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Duplicati/Library/Main/Database/LocalListDatabase.cs b/Duplicati/Library/Main/Database/LocalListDatabase.cs index 9c9072ce5..59f38d4c4 100644 --- a/Duplicati/Library/Main/Database/LocalListDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListDatabase.cs @@ -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))) From ae1f69b5e871ab2a7071b49273da80c3039969e0 Mon Sep 17 00:00:00 2001 From: Tyler Gill Date: Wed, 15 Nov 2017 20:56:24 -0800 Subject: [PATCH 06/40] Only try to load Windows registry filters on Windows. The reason for this is that Windows filters may be used on other systems (e.g., to ignore Windows files when backing up a Windows drive) --- Duplicati/Library/Utility/DefaultFilters.cs | 856 ++++++++++---------- 1 file changed, 430 insertions(+), 426 deletions(-) diff --git a/Duplicati/Library/Utility/DefaultFilters.cs b/Duplicati/Library/Utility/DefaultFilters.cs index b88a7be8b..6306b8883 100644 --- a/Duplicati/Library/Utility/DefaultFilters.cs +++ b/Duplicati/Library/Utility/DefaultFilters.cs @@ -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, - } - - /// - /// 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 - /// - public static class DefaultFilters - { - /// - /// Case insensitive mapping from strings to filter sets - /// /// - private static readonly Dictionary defaultFilterSetMapping = - ((DefaultFilterSet[])Enum.GetValues(typeof(DefaultFilterSet))) - .ToDictionary(val => val.ToString(), val => val, StringComparer.OrdinalIgnoreCase); - - /// - /// Filters common for all operating systems - /// - public static IEnumerable Common - { - get - { - return DefaultFilters.CreateCommonFilters(); - } - } - - /// - /// Filters for Windows - /// - public static IEnumerable Windows - { - get - { - return DefaultFilters.CreateWindowsFilters(); - } - } - - /// - /// Filters for OSX - /// - public static IEnumerable OSX - { - get - { - return DefaultFilters.CreateOSXFilters(); - } - } - - /// - /// Filters for Linux - /// - public static IEnumerable Linux - { - get - { - return DefaultFilters.CreateLinuxFilters(); - } - } - - /// - /// Gets the filters indicated by the given filter sets - /// - /// Filter set options - /// Default filters - public static IEnumerable 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(); - } - - IEnumerable 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)); - } - - /// - /// Creates common filters - /// - /// Common filters - 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})?"), - }; - } - - /// - /// Creates Windows filters - /// - /// Windows filters - 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(); - } - - /// - /// Creates OSX filters - /// - /// OSX filters - 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/*"), - }; - } - - /// - /// Creates Linux filters - /// - /// Linux filters - 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/*"), - }; - } - - /// - /// Creates a wildcard filter - /// - /// Filter text - /// Wildcard filter - 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); - } - - /// - /// Creates a Regex filter - /// - /// Filter text - /// Regex filter - 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) + "]"; - } - - /// - /// Wrapper method for getting a list of exclude paths from the Windows registry. - /// This method guards the call to to avoid loader errors. - /// - /// The list of paths to exclude. - private static string[] GetWindowsRegistryFilters() - { - try - { - return GetWindowsRegistryFiltersInternal(); - } - catch - { - } - - return new string[0]; - } - - /// - /// 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. - /// - /// The list of paths to exclude. - [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, + } + + /// + /// 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 + /// + public static class DefaultFilters + { + /// + /// Case insensitive mapping from strings to filter sets + /// /// + private static readonly Dictionary defaultFilterSetMapping = + ((DefaultFilterSet[])Enum.GetValues(typeof(DefaultFilterSet))) + .ToDictionary(val => val.ToString(), val => val, StringComparer.OrdinalIgnoreCase); + + /// + /// Filters common for all operating systems + /// + public static IEnumerable Common + { + get + { + return DefaultFilters.CreateCommonFilters(); + } + } + + /// + /// Filters for Windows + /// + public static IEnumerable Windows + { + get + { + return DefaultFilters.CreateWindowsFilters(); + } + } + + /// + /// Filters for OSX + /// + public static IEnumerable OSX + { + get + { + return DefaultFilters.CreateOSXFilters(); + } + } + + /// + /// Filters for Linux + /// + public static IEnumerable Linux + { + get + { + return DefaultFilters.CreateLinuxFilters(); + } + } + + /// + /// Gets the filters indicated by the given filter sets + /// + /// Filter set options + /// Default filters + public static IEnumerable 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(); + } + + IEnumerable 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)); + } + + /// + /// Creates common filters + /// + /// Common filters + 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})?"), + }; + } + + /// + /// Creates Windows filters + /// + /// Windows filters + 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(); + } + + /// + /// Creates OSX filters + /// + /// OSX filters + 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/*"), + }; + } + + /// + /// Creates Linux filters + /// + /// Linux filters + 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/*"), + }; + } + + /// + /// Creates a wildcard filter + /// + /// Filter text + /// Wildcard filter + 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); + } + + /// + /// Creates a Regex filter + /// + /// Filter text + /// Regex filter + 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) + "]"; + } + + /// + /// Wrapper method for getting a list of exclude paths from the Windows registry. + /// This method guards the call to to avoid loader errors. + /// + /// The list of paths to exclude. + private static string[] GetWindowsRegistryFilters() + { + if (Utility.IsClientWindows) + { + // One Windows, filters may also be stored in the registry + try + { + return GetWindowsRegistryFiltersInternal(); + } + catch + { + } + } + + return null; + } + + /// + /// 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. + /// + /// The list of paths to exclude. + [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(); + } + + } +} From c3fe4bbe515bac6a85c8e560484ce0b9626246e0 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Fri, 17 Nov 2017 13:04:27 +0100 Subject: [PATCH 07/40] Updated the translation files to match the source and uploaded to Transifex --- Localizations/duplicati/localization.pot | 3203 +++++++++-------- .../webroot/localization_webroot.pot | 283 +- 2 files changed, 1780 insertions(+), 1706 deletions(-) diff --git a/Localizations/duplicati/localization.pot b/Localizations/duplicati/localization.pot index 33414d66d..546e433e7 100644 --- a/Localizations/duplicati/localization.pot +++ b/Localizations/duplicati/localization.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-17 22:31+0200\n" +"POT-Creation-Date: 2017-11-17 13:00+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,301 +17,690 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: CommandLine/Program.cs:301 -msgid "Toggle automatic updates" +#: Server/Strings.cs:7 +msgid "Another instance is running, and was notified" msgstr "" -#: CommandLine/Program.cs:301 -msgid "" -"Set this option if you prefer to have the commandline version automatically " -"update" -msgstr "" - -#: CommandLine/Strings.cs:4 -#, csharp-format -msgid "The command {0} needs at least one of the following options set: {1}" -msgstr "" - -#: CommandLine/Strings.cs:5 +#: Server/Strings.cs:8 #, csharp-format msgid "" -"Found {0} commands but expected {1}, commands: \n" -"{2}" +"Failed to create, open or upgrade the database.\n" +"Error message: {0}" msgstr "" -#: CommandLine/Strings.cs:7 +#: Server/Strings.cs:10 +msgid "Displays this help" +msgstr "" + +#: Server/Strings.cs:11 +msgid "" +"Supported commandline arguments:\n" +"\n" +msgstr "" + +#: Server/Strings.cs:14 #, csharp-format -msgid "Command not supported: {0}" +msgid "--{0}: {1}" msgstr "" -#: CommandLine/Strings.cs:8 -msgid "No filesets matched the criteria" +#: Server/Strings.cs:15 +msgid "Outputs log information to the file given" msgstr "" -#: CommandLine/Strings.cs:9 -msgid "The following filesets would be deleted:" +#: Server/Strings.cs:16 +msgid "Determines the amount of information written in the log file" msgstr "" -#: CommandLine/Strings.cs:10 -msgid "These filesets were deleted:" +#: Server/Strings.cs:17 +msgid "" +"Activates portable mode where the database is placed below the program " +"executable" msgstr "" -#: CommandLine/Strings.cs:11 -msgid "Supported backends:" -msgstr "" - -#: CommandLine/Strings.cs:12 -msgid "Supported compression modules:" -msgstr "" - -#: CommandLine/Strings.cs:13 -msgid "Supported encryption modules:" -msgstr "" - -#: CommandLine/Strings.cs:14 -msgid "Supported options:" -msgstr "" - -#: CommandLine/Strings.cs:15 -msgid "Module is loaded automatically, use --disable-module to prevent this" -msgstr "" - -#: CommandLine/Strings.cs:16 -msgid "Module is not loaded automatically, use --enable-module to load it" -msgstr "" - -#: CommandLine/Strings.cs:17 -msgid "Supported generic modules:" -msgstr "" - -#: CommandLine/Strings.cs:18 +#: Server/Strings.cs:18 #, csharp-format -msgid "Unable to read the parameters file \"{0}\", reason: {1}" +msgid "A serious error occurred in Duplicati: {0}" msgstr "" -#: CommandLine/Strings.cs:19 +#: Server/Strings.cs:19 #, csharp-format msgid "" -"Filters cannot be specified on the commandline if filters are also present " -"in the parameter file. Use the special --{0}, --{1}, or --{2} options to " -"specify filters inside the parameter file. Each filter must be prefixed with " -"either a + or a -, and multiple filters must be joined with {3}" +"Unable to start up, perhaps another process is already running?\n" +"Error message: {0}" msgstr "" -#: CommandLine/Strings.cs:20 +#: Server/Strings.cs:21 +msgid "Disables database encryption" +msgstr "" + +#: Server/Strings.cs:22 +#, csharp-format +msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" +msgstr "" + +#: Server/Strings.cs:23 +msgid "" +"The path to the folder where the static files for the webserver is present. " +"The folder must be located beneath the installation folder" +msgstr "" + +#: Server/Strings.cs:24 +msgid "" +"The port the webserver listens on. Multiple values may be supplied with a " +"comma in between." +msgstr "" + +#: Server/Strings.cs:25 +msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL. " +"Only RSA/DSA keys are supported." +msgstr "" + +#: Server/Strings.cs:26 +msgid "The password for decryption of certificate PKCS #12 file." +msgstr "" + +#: Server/Strings.cs:27 +msgid "" +"The interface the webserver listens on. The special values \"*\" and \"any\" " +"means any interface. The special value \"loopback\" means the loopback " +"adapter." +msgstr "" + +#: Server/Strings.cs:28 +msgid "" +"The password required to access the webserver. This option is saved so you " +"do not need to set it on each run. Setting an empty value disables the " +"password." +msgstr "" + +#: Server/Strings.cs:29 +msgid "Enables the ping-pong responder" +msgstr "" + +#: Server/Strings.cs:30 +msgid "" +"When running as a server, the service daemon must verify that the process is " +"responding. If this option is enabled, the server reads stdin and writes a " +"reply to each line read" +msgstr "" + +#: Server/Strings.cs:31 Library/Main/Strings.cs:218 +msgid "Clean up old log data" +msgstr "" + +#: Server/Strings.cs:32 Library/Main/Strings.cs:219 +msgid "Set the time after which log data will be purged from the database." +msgstr "" + +#: Server/Strings.cs:33 +msgid "Sets the folder where settings are stored" +msgstr "" + +#: Server/Strings.cs:34 #, csharp-format msgid "" -"The option --{0} was supplied, but it is reserved for internal use and may " -"not be set on the commandline" +"Duplicati needs to store a small database with all settings. Use this option " +"to choose where the settings are stored. This option can also be set with " +"the environment variable {0}." msgstr "" -#: CommandLine/Strings.cs:21 +#: Server/Strings.cs:35 +msgid "Sets the database encryption key" +msgstr "" + +#: Server/Strings.cs:36 #, csharp-format msgid "" -"This option can be used to store some or all of the options given to the " -"commandline client. The file must be a plain text file, UTF-8 encoding is " -"preferred. Each line in the file should be of the format --option=value. The " -"special options --{0} and --{1} can be used to override the localpath and " -"the remote destination uri, respectively. The options in this file take " -"precedence over the options provided on the commandline. You cannot specify " -"filters in both the file and on the commandline. Instead, you can use the " -"special --{2}, --{3}, or --{4} options to specify filters inside the " -"parameter file. Each filter must be prefixed with either a + or a -, and " -"multiple filters must be joined with {5} " +"This option sets the encryption key used to scramble the local settings " +"database. This option can also be set with the environment variable {0}. Use " +"the option --{1} to disable the database scrambling." msgstr "" -#: CommandLine/Strings.cs:22 -msgid "Path to a file with parameters" -msgstr "" - -#: CommandLine/Strings.cs:23 +#: Server/Strings.cs:39 #, csharp-format -msgid "An error occured: {0}" +msgid "" +"Unable to find a valid date, given the start date {0}, the repetition " +"interval {1} and the allowed days {2}" msgstr "" -#: CommandLine/Strings.cs:24 +#: Server/Strings.cs:44 #, csharp-format -msgid "The inner error message is: {0}" +msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: CommandLine/Strings.cs:25 +#: Server/Strings.cs:45 +#, csharp-format msgid "" -"Include files that match this filter. The special character * means any " -"number of character, and the special character ? means any single character, " -"use *.txt to include all files with a txt extension. Regular expressions are " -"also supported and can be supplied by using hard braces, i.e. [.*\\.txt]." +"Unable to create SSL certificate using provided parameters. Exception " +"detail: {0}" msgstr "" -#: CommandLine/Strings.cs:26 -msgid "Include files" +#: Server/Strings.cs:46 +#, csharp-format +msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" -#: CommandLine/Strings.cs:27 +#: Library/Encryption/Strings.cs:4 msgid "" -"Exclude files that match this filter. The special character * means any " -"number of character, and the special character ? means any single character, " -"use *.txt to exclude all files with a txt extension. Regular expressions are " -"also supported and can be supplied by using hard braces, i.e. [.*\\.txt]." +"This module encrypts all files in the same way that AESCrypt does, using 256 " +"bit AES encryption." msgstr "" -#: CommandLine/Strings.cs:28 -msgid "Exclude files" +#: Library/Encryption/Strings.cs:5 +msgid "AES-256 encryption, built in" msgstr "" -#: CommandLine/Strings.cs:29 +#: Library/Encryption/Strings.cs:6 +msgid "Empty passphrase not allowed" +msgstr "" + +#: Library/Encryption/Strings.cs:7 msgid "" -"If this option is used with a backup operation, it is interpreted as a list " -"of files to add to the filesets. When used with list or restore, it will " -"list or restore the control files instead of the normal files." +"Use this option to set the thread level allowed for AES crypt operations. " +"Valid values are 0 (uses default), or from 1 (no multithreading) to 4 (max. " +"multithreading)" msgstr "" -#: CommandLine/Strings.cs:30 -msgid "Use control files" +#: Library/Encryption/Strings.cs:8 +msgid "Set thread level utilized for crypting (0-4)" msgstr "" -#: CommandLine/Strings.cs:31 +#: Library/Encryption/Strings.cs:11 +#, csharp-format +msgid "Failed to decrypt data (invalid passphrase?): {0}" +msgstr "" + +#: Library/Encryption/Strings.cs:14 msgid "" -"If this option is set, progress reports and other messages that would " -"normally go to the console will be redirected to the log." +"The GPG encryption module uses the GNU Privacy Guard program to encrypt and " +"decrypt files. It requires that the gpg executable is available on the " +"system. On Windows it is assumed that this is in the default installation " +"folder under program files, under Linux and OSX it is assumed that the " +"program is available via the PATH environment variable. It is possible to " +"supply the path to GPG using the --gpg-program-path switch." msgstr "" -#: CommandLine/Strings.cs:32 -msgid "Disable console output" +#: Library/Encryption/Strings.cs:15 +msgid "GNU Privacy Guard, external" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:9 +#: Library/Encryption/Strings.cs:16 msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" or " -"\"aftp://username:password@hostname/folder\"" +"Use this switch to specify any extra options to GPG. You cannot specify the " +"--passphrase-fd option here. The --decrypt option is always specified." msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:10 -#: Library/Backend/AzureBlob/Strings.cs:13 -#: Library/Backend/Backblaze/Strings.cs:9 -#: Library/Backend/CloudFiles/Strings.cs:6 Library/Backend/File/Strings.cs:9 -#: Library/Backend/FTP/Strings.cs:12 Library/Backend/Jottacloud/Strings.cs:15 -#: Library/Backend/Mega/Strings.cs:5 Library/Backend/S3/Strings.cs:9 -#: Library/Backend/SharePoint/Strings.cs:8 Library/Backend/SSHv2/Strings.cs:23 -#: Library/Backend/WEBDAV/Strings.cs:7 +#: Library/Encryption/Strings.cs:17 +msgid "Extra GPG commandline options for decryption" +msgstr "" + +#: Library/Encryption/Strings.cs:18 +msgid "" +"The GPG encryption/decryption will use the --armor option for GPG to protect " +"the files with armor. Specify this switch to remove the --armor option." +msgstr "" + +#: Library/Encryption/Strings.cs:19 +msgid "Don't use GPG Armor" +msgstr "" + +#: Library/Encryption/Strings.cs:20 +msgid "" +"Use this switch to specify any extra options to GPG. You cannot specify the " +"--passphrase-fd option here. The --encrypt option is always specified." +msgstr "" + +#: Library/Encryption/Strings.cs:21 +msgid "Extra GPG commandline options for encryption" +msgstr "" + +#: Library/Encryption/Strings.cs:22 +#, csharp-format +msgid "Failed to execute GPG at \"\"{0}\" {1}\": {2}" +msgstr "" + +#: Library/Encryption/Strings.cs:23 +msgid "" +"The path to the GNU Privacy Guard program. If not supplied, Duplicati will " +"assume that the program \"gpg\" is available in the system path." +msgstr "" + +#: Library/Encryption/Strings.cs:24 +msgid "The path to GnuPG" +msgstr "" + +#: Library/Encryption/Strings.cs:25 +#, csharp-format +msgid "" +"This option has non-standard handling, please use the --{0} option instead." +msgstr "" + +#: Library/Encryption/Strings.cs:26 +msgid "" +"Use this option to supply the --armor option to GPG. The files will be " +"larger but can be sent as pure text files." +msgstr "" + +#: Library/Encryption/Strings.cs:27 +msgid "Use GPG Armor" +msgstr "" + +#: Library/Encryption/Strings.cs:28 +msgid "Overrides the GPG command supplied for decryption" +msgstr "" + +#: Library/Encryption/Strings.cs:29 +msgid "The GPG decryption command" +msgstr "" + +#: Library/Encryption/Strings.cs:30 +#, csharp-format +msgid "" +"Overrides the default GPG encryption command \"{0}\", normal usage is to " +"request asymetric encryption with the setting {1}" +msgstr "" + +#: Library/Encryption/Strings.cs:31 +msgid "The GPG encryption command" +msgstr "" + +#: Library/Encryption/Strings.cs:34 +#, csharp-format +msgid "Decryption failed: {0}" +msgstr "" + +#: Library/Encryption/Strings.cs:35 +msgid "Failure while invoking GnuPG, program won't flush output" +msgstr "" + +#: Library/Encryption/Strings.cs:36 +msgid "Failure while invoking GnuPG, program won't terminate" +msgstr "" + +#: Library/Interface/Strings.cs:4 +msgid "aliases" +msgstr "" + +#: Library/Interface/Strings.cs:5 +msgid "default value" +msgstr "" + +#: Library/Interface/Strings.cs:6 +msgid "[DEPRECATED]" +msgstr "" + +#: Library/Interface/Strings.cs:7 +msgid "values" +msgstr "" + +#: Library/Interface/Strings.cs:10 +msgid "Boolean" +msgstr "" + +#: Library/Interface/Strings.cs:11 +msgid "Enumeration" +msgstr "" + +#: Library/Interface/Strings.cs:12 +msgid "Flags" +msgstr "" + +#: Library/Interface/Strings.cs:13 +msgid "Integer" +msgstr "" + +#: Library/Interface/Strings.cs:14 +msgid "Path" +msgstr "" + +#: Library/Interface/Strings.cs:15 +msgid "Size" +msgstr "" + +#: Library/Interface/Strings.cs:16 +msgid "String" +msgstr "" + +#: Library/Interface/Strings.cs:17 +msgid "Timespan" +msgstr "" + +#: Library/Interface/Strings.cs:18 +msgid "Unknown" +msgstr "" + +#: Library/Interface/Strings.cs:21 +#, csharp-format +msgid "" +"The configuration for the backend is not valid, it is missing the {0} field" +msgstr "" + +#: Library/Interface/Strings.cs:22 +msgid "Do you want to test the connection?" +msgstr "" + +#: Library/Interface/Strings.cs:23 +#, csharp-format +msgid "Connection Failed: {0}" +msgstr "" + +#: Library/Interface/Strings.cs:24 +msgid "Connection succeeded!" +msgstr "" + +#: Library/Interface/Strings.cs:25 +msgid "" +"You have not entered a path. This will store all backups in the default " +"directory. Is this what you want?" +msgstr "" + +#: Library/Interface/Strings.cs:26 +msgid "You must enter a password" +msgstr "" + +#: Library/Interface/Strings.cs:27 +msgid "" +"You have not entered a password.\n" +"Proceed without a password?" +msgstr "" + +#: Library/Interface/Strings.cs:29 +msgid "You must enter the name of the server" +msgstr "" + +#: Library/Interface/Strings.cs:30 +msgid "You must enter a username" +msgstr "" + +#: Library/Interface/Strings.cs:31 +msgid "" +"You have not entered a username.\n" +"This is fine if the server allows anonymous uploads, but likely a username " +"is required\n" +"Proceed without a username?" +msgstr "" + +#: Library/Interface/Strings.cs:34 +msgid "" +"The connection succeeded but another backup was found in the destination " +"folder. It is possible to configure Duplicati to store multiple backups in " +"the same folder, but it is not recommended.\n" +"\n" +"Do you want to use the selected folder?" +msgstr "" + +#: Library/Interface/Strings.cs:37 +msgid "The folder cannot be created because it already exists" +msgstr "" + +#: Library/Interface/Strings.cs:38 +msgid "Folder created!" +msgstr "" + +#: Library/Interface/Strings.cs:39 +msgid "The requested folder does not exist" +msgstr "" + +#: Library/Interface/Strings.cs:40 +#, csharp-format +msgid "The server name \"{0}\" is not valid" +msgstr "" + +#: Library/Interface/Strings.cs:41 +msgid "Cancelled" +msgstr "" + +#: Library/Interface/CustomExceptions.cs:77 +#: Library/Interface/CustomExceptions.cs:85 +msgid "The requested file does not exist" +msgstr "" + +#: Library/Snapshots/Strings.cs:4 +#, csharp-format +msgid "" +"The external command failed to start.\n" +"Error message: {0}\n" +"Command: {1} {2}" +msgstr "" + +#: Library/Snapshots/Strings.cs:7 +#, csharp-format +msgid "" +"The external command failed to complete within the set time limit: {0} {1}" +msgstr "" + +#: Library/Snapshots/Strings.cs:8 +#, csharp-format +msgid "Unable to match local path {0} with any snapshot path: {1}" +msgstr "" + +#: Library/Snapshots/Strings.cs:9 +#, csharp-format +msgid "" +"Script returned successfully, but the temporary folder {0} does not exist: " +"{1}" +msgstr "" + +#: Library/Snapshots/Strings.cs:10 +#, csharp-format +msgid "" +"Script returned successfully, but the temporary folder {0} still exist: {1}" +msgstr "" + +#: Library/Snapshots/Strings.cs:11 +#, csharp-format +msgid "The script returned exit code {0}, but {1} was expected: {2}" +msgstr "" + +#: Library/Snapshots/Strings.cs:12 +#, csharp-format +msgid "" +"Script returned successfully, but the output was missing the {0} parameter: " +"{1}" +msgstr "" + +#: Library/Snapshots/Strings.cs:15 +msgid "Unexpected empty response while enumerating" +msgstr "" + +#: Library/Snapshots/Strings.cs:16 +msgid "USN is not supported on Linux" +msgstr "" + +#: Library/Snapshots/Strings.cs:17 +msgid "" +"The number of files returned by USN was zero. This is likely an error. To " +"remedy this, USN has been disabled." +msgstr "" + +#: Library/Snapshots/Strings.cs:20 +msgid "Calling process does not have the backup privilege" +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 +msgid "" +"This backend can read and write data to Swift (OpenStack Object Storage). " +"Supported format is \"openstack://container/folder\"." +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 +msgid "OpenStack Simple Storage" +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 +#, csharp-format +msgid "Missing required option: {0}" +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 +#, csharp-format msgid "" "The password used to connect to the server. This may also be supplied as the " -"environment variable \"AUTH_PASSWORD\"." +"environment variable \"AUTH_PASSWORD\". If the password is supplied, --{0} " +"must also be set" msgstr "" +#: Library/Backend/OpenStack/Strings.cs:20 Library/Backend/FTP/Strings.cs:13 +#: Library/Backend/CloudFiles/Strings.cs:7 Library/Backend/S3/Strings.cs:10 #: Library/Backend/AlternativeFTP/Strings.cs:11 -#: Library/Backend/AzureBlob/Strings.cs:14 +#: Library/Backend/SSHv2/Strings.cs:24 Library/Backend/File/Strings.cs:10 #: Library/Backend/Backblaze/Strings.cs:10 -#: Library/Backend/CloudFiles/Strings.cs:7 Library/Backend/File/Strings.cs:10 -#: Library/Backend/FTP/Strings.cs:13 Library/Backend/Jottacloud/Strings.cs:14 -#: Library/Backend/Mega/Strings.cs:6 Library/Backend/OpenStack/Strings.cs:20 -#: Library/Backend/S3/Strings.cs:10 Library/Backend/SharePoint/Strings.cs:9 -#: Library/Backend/SSHv2/Strings.cs:24 Library/Backend/WEBDAV/Strings.cs:8 +#: Library/Backend/AzureBlob/Strings.cs:14 +#: Library/Backend/Jottacloud/Strings.cs:14 Library/Backend/Mega/Strings.cs:6 +#: Library/Backend/SharePoint/Strings.cs:9 Library/Backend/WEBDAV/Strings.cs:8 msgid "Supplies the password used to connect to the server" msgstr "" +#: Library/Backend/OpenStack/Strings.cs:20 Library/Backend/FTP/Strings.cs:14 +#: Library/Backend/CloudFiles/Strings.cs:8 Library/Backend/S3/Strings.cs:11 #: Library/Backend/AlternativeFTP/Strings.cs:12 -#: Library/Backend/AzureBlob/Strings.cs:15 +#: Library/Backend/SSHv2/Strings.cs:25 Library/Backend/File/Strings.cs:11 #: Library/Backend/Backblaze/Strings.cs:11 -#: Library/Backend/CloudFiles/Strings.cs:8 Library/Backend/File/Strings.cs:11 -#: Library/Backend/FTP/Strings.cs:14 Library/Backend/Jottacloud/Strings.cs:13 -#: Library/Backend/Mega/Strings.cs:7 Library/Backend/OpenStack/Strings.cs:20 -#: Library/Backend/S3/Strings.cs:11 Library/Backend/SharePoint/Strings.cs:10 -#: Library/Backend/SSHv2/Strings.cs:25 Library/Backend/WEBDAV/Strings.cs:9 +#: Library/Backend/AzureBlob/Strings.cs:15 +#: Library/Backend/Jottacloud/Strings.cs:13 Library/Backend/Mega/Strings.cs:7 +#: Library/Backend/SharePoint/Strings.cs:10 Library/Backend/WEBDAV/Strings.cs:9 msgid "" "The username used to connect to the server. This may also be supplied as the " "environment variable \"AUTH_USERNAME\"." msgstr "" +#: Library/Backend/OpenStack/Strings.cs:20 Library/Backend/FTP/Strings.cs:15 +#: Library/Backend/CloudFiles/Strings.cs:9 Library/Backend/S3/Strings.cs:12 #: Library/Backend/AlternativeFTP/Strings.cs:13 -#: Library/Backend/AzureBlob/Strings.cs:16 +#: Library/Backend/SSHv2/Strings.cs:26 Library/Backend/File/Strings.cs:12 #: Library/Backend/Backblaze/Strings.cs:12 -#: Library/Backend/CloudFiles/Strings.cs:9 Library/Backend/File/Strings.cs:12 -#: Library/Backend/FTP/Strings.cs:15 Library/Backend/Jottacloud/Strings.cs:12 -#: Library/Backend/Mega/Strings.cs:8 Library/Backend/OpenStack/Strings.cs:20 -#: Library/Backend/S3/Strings.cs:12 Library/Backend/SharePoint/Strings.cs:11 -#: Library/Backend/SSHv2/Strings.cs:26 Library/Backend/WEBDAV/Strings.cs:10 +#: Library/Backend/AzureBlob/Strings.cs:16 +#: Library/Backend/Jottacloud/Strings.cs:12 Library/Backend/Mega/Strings.cs:8 +#: Library/Backend/SharePoint/Strings.cs:11 +#: Library/Backend/WEBDAV/Strings.cs:10 msgid "Supplies the username used to connect to the server" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:14 -msgid "Alternative FTP" +#: Library/Backend/OpenStack/Strings.cs:20 +msgid "" +"The Tenant Name is commonly the paying user account name. This option must " +"be supplied when authenticating with a password, but is not required when " +"using an API key." msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:15 +#: Library/Backend/OpenStack/Strings.cs:20 +msgid "Supplies the Tenant Name used to connect to the server" +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 +msgid "" +"The API key can be used to connect without supplying a password and tenant " +"ID with some providers." +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 +msgid "Supplies the API key used to connect to the server" +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 #, csharp-format -msgid "The folder {0} was not found. Message: {1}" +msgid "" +"The authentication URL is used to authenticate the user and find the storage " +"service. The URL commonly ends with \"/v2.0\". Known providers are: {0}{1}" +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 +msgid "Supplies the authentication URL" +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 +msgid "" +"This option is only used when creating a container, and is used to indicate " +"where the container should be placed. Consult your provider for a list of " +"valid regions, or leave empty for the default region." +msgstr "" + +#: Library/Backend/OpenStack/Strings.cs:20 +msgid "Supplies the region used for creating a container" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:7 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"ftp://hostname/folder\" or \"ftp://username:password@hostname/" +"folder\"" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:8 +msgid "" +"If this flag is set, the FTP connection is made in active mode. Even if the " +"\"ftp-passive\" flag is also set, the connection will be made in active mode" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:9 Library/Backend/FTP/Strings.cs:11 +msgid "Toggles the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:10 +msgid "" +"If this flag is set, the FTP connection is made in passive mode, which works " +"better with some firewalls. If the \"ftp-regular\" flag is also set, this " +"flag is ignored" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:12 Library/Backend/CloudFiles/Strings.cs:6 +#: Library/Backend/S3/Strings.cs:9 Library/Backend/AlternativeFTP/Strings.cs:10 +#: Library/Backend/SSHv2/Strings.cs:23 Library/Backend/File/Strings.cs:9 +#: Library/Backend/Backblaze/Strings.cs:9 +#: Library/Backend/AzureBlob/Strings.cs:13 +#: Library/Backend/Jottacloud/Strings.cs:15 Library/Backend/Mega/Strings.cs:5 +#: Library/Backend/SharePoint/Strings.cs:8 Library/Backend/WEBDAV/Strings.cs:7 +msgid "" +"The password used to connect to the server. This may also be supplied as the " +"environment variable \"AUTH_PASSWORD\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:16 +msgid "" +"Use this flag to communicate using Secure Socket Layer (SSL) over ftp (ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:17 +msgid "Instructs Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:18 +msgid "FTP" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:19 Library/Backend/WEBDAV/Strings.cs:15 +#: Library/Backend/TahoeLAFS/Strings.cs:8 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:16 #: Library/Backend/FTP/Strings.cs:20 +#: Library/Backend/AlternativeFTP/Strings.cs:16 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards, the file listing " "returned {1}" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:17 #: Library/Backend/FTP/Strings.cs:21 +#: Library/Backend/AlternativeFTP/Strings.cs:17 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " "to be {2}" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:18 #: Library/Backend/FTP/Strings.cs:22 +#: Library/Backend/AlternativeFTP/Strings.cs:18 msgid "Disable upload verification" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:19 +#: Library/Backend/FTP/Strings.cs:23 msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:20 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:21 -msgid "Configure the FTP data connection type" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:22 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected " -"option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:23 -msgid "Configure the FTP encryption mode" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:24 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:25 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:26 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:27 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:28 -#, csharp-format -msgid "Error writing file: {0}" +"To protect against network failures, every upload will be attempted " +"verified. Use this option to disable this verification to make the upload " +"faster but less reliable." msgstr "" #: Library/Backend/AmazonCloudDrive/Strings.cs:23 @@ -321,21 +710,20 @@ msgid "" msgstr "" #: Library/Backend/AmazonCloudDrive/Strings.cs:24 -#: Library/Backend/Box/Strings.cs:24 Library/Backend/Dropbox/Strings.cs:24 #: Library/Backend/GoogleServices/Strings.cs:26 #: Library/Backend/GoogleServices/Strings.cs:43 -#: Library/Backend/HubiC/Strings.cs:24 Library/Backend/OAuthHelper/Strings.cs:9 -#: Library/Backend/OneDrive/Strings.cs:11 +#: Library/Backend/Box/Strings.cs:24 Library/Backend/OneDrive/Strings.cs:11 +#: Library/Backend/HubiC/Strings.cs:24 Library/Backend/Dropbox/Strings.cs:24 +#: Library/Backend/OAuthHelper/Strings.cs:9 msgid "The authorization code" msgstr "" #: Library/Backend/AmazonCloudDrive/Strings.cs:25 -#: Library/Backend/Box/Strings.cs:25 Library/Backend/Dropbox/Strings.cs:25 #: Library/Backend/GoogleServices/Strings.cs:27 #: Library/Backend/GoogleServices/Strings.cs:44 -#: Library/Backend/HubiC/Strings.cs:25 +#: Library/Backend/Box/Strings.cs:25 Library/Backend/OneDrive/Strings.cs:12 +#: Library/Backend/HubiC/Strings.cs:25 Library/Backend/Dropbox/Strings.cs:25 #: Library/Backend/OAuthHelper/Strings.cs:10 -#: Library/Backend/OneDrive/Strings.cs:12 #, csharp-format msgid "The authorization token retrieved from {0}" msgstr "" @@ -345,10 +733,10 @@ msgid "Amazon Cloud Drive" msgstr "" #: Library/Backend/AmazonCloudDrive/Strings.cs:27 -#: Library/Backend/Box/Strings.cs:23 #: Library/Backend/GoogleServices/Strings.cs:24 #: Library/Backend/GoogleServices/Strings.cs:46 -#: Library/Backend/HubiC/Strings.cs:23 Library/Backend/OAuthHelper/Strings.cs:6 +#: Library/Backend/Box/Strings.cs:23 Library/Backend/HubiC/Strings.cs:23 +#: Library/Backend/OAuthHelper/Strings.cs:6 #, csharp-format msgid "You need an AuthID, you can get it from: {0}" msgstr "" @@ -375,121 +763,73 @@ msgstr "" msgid "Amazon Cloud drive needs a small delay for results to stay consistent." msgstr "" -#: Library/Backend/AzureBlob/Strings.cs:4 -msgid "All files will be written to the container specified" -msgstr "" - -#: Library/Backend/AzureBlob/Strings.cs:5 -msgid "The name of the storage container " -msgstr "" - -#: Library/Backend/AzureBlob/Strings.cs:6 -msgid "Azure blob" -msgstr "" - -#: Library/Backend/AzureBlob/Strings.cs:7 -msgid "No Azure storage account name given" -msgstr "" - -#: Library/Backend/AzureBlob/Strings.cs:8 +#: Library/Backend/GoogleServices/Strings.cs:22 msgid "" -"The Azure storage account name which can be obtained by clicking the " -"\"Manage Access Keys\" button on the storage account dashboard" +"This backend can read and write data to Google Cloud Storage. Supported " +"format is \"googlecloudstore://bucket/folder\"." msgstr "" -#: Library/Backend/AzureBlob/Strings.cs:9 -msgid "The storage account name" +#: Library/Backend/GoogleServices/Strings.cs:23 +msgid "Google Cloud Storage" msgstr "" -#: Library/Backend/AzureBlob/Strings.cs:10 +#: Library/Backend/GoogleServices/Strings.cs:25 +#, csharp-format +msgid "You must supply a project ID with --{0} for creating a bucket" +msgstr "" + +#: Library/Backend/GoogleServices/Strings.cs:29 +#, csharp-format msgid "" -"The Azure access key which can be obtained by clicking the \"Manage Access " -"Keys\" button on the storage account dashboard" +"This option is only used when creating new buckets. Use this option to " +"change what region the data is stored in. Charges vary with bucket location. " +"Known bucket locations:\n" +"{0}" msgstr "" -#: Library/Backend/AzureBlob/Strings.cs:11 -msgid "The access key" +#: Library/Backend/GoogleServices/Strings.cs:31 +msgid "Specifies location option for creating a bucket" msgstr "" -#: Library/Backend/AzureBlob/Strings.cs:12 -msgid "No Azure access key given" -msgstr "" - -#: Library/Backend/AzureBlob/Strings.cs:17 +#: Library/Backend/GoogleServices/Strings.cs:32 +#, csharp-format msgid "" -"This backend can read and write data to Azure blob storage. Allowed formats " -"are: \"azure://bucketname\"" +"This option is only used when creating new buckets. Use this option to " +"change what storage type the bucket has. Charges and functionality vary with " +"bucket storage class. Known storage classes:\n" +"{0}" msgstr "" -#: Library/Backend/Backblaze/Strings.cs:4 +#: Library/Backend/GoogleServices/Strings.cs:34 +msgid "Specifies storage class for creating a bucket" +msgstr "" + +#: Library/Backend/GoogleServices/Strings.cs:35 +msgid "Specifies project for creating a bucket" +msgstr "" + +#: Library/Backend/GoogleServices/Strings.cs:36 msgid "" -"The \"B2 Cloud Storage Application Key\" can be obtained after logging into " -"your Backblaze account, this can also be supplied through the \"auth-password" -"\" property" +"This option is only used when creating new buckets. Use this option to " +"supply the project ID that the bucket is attached to. The project determines " +"where usage charges are applied" msgstr "" -#: Library/Backend/Backblaze/Strings.cs:5 -msgid "The \"B2 Cloud Storage Application Key\"" -msgstr "" - -#: Library/Backend/Backblaze/Strings.cs:6 +#: Library/Backend/GoogleServices/Strings.cs:41 +#, csharp-format msgid "" -"The \"B2 Cloud Storage Account ID\" can be obtained after logging into your " -"Backblaze account, this can also be supplied through the \"auth-username\" " -"property" +"The account access has been blocked by Google, please visit this URL and " +"unlock it: {0}" msgstr "" -#: Library/Backend/Backblaze/Strings.cs:7 -msgid "The \"B2 Cloud Storage Account ID\"" -msgstr "" - -#: Library/Backend/Backblaze/Strings.cs:8 -msgid "B2 Cloud Storage" -msgstr "" - -#: Library/Backend/Backblaze/Strings.cs:13 -msgid "No \"B2 Cloud Storage Application Key\" given" -msgstr "" - -#: Library/Backend/Backblaze/Strings.cs:14 -msgid "No \"B2 Cloud Storage Account ID\" given" -msgstr "" - -#: Library/Backend/Backblaze/Strings.cs:15 +#: Library/Backend/GoogleServices/Strings.cs:42 msgid "" -"This backend can read and write data to the Backblaze B2 Cloud Storage. " -"Allowed formats are: \"b2://bucketname/prefix\"" +"This backend can read and write data to Google Drive. Supported format is " +"\"googledrive://folder/subfolder\"." msgstr "" -#: Library/Backend/Backblaze/Strings.cs:16 -msgid "" -"By default, a private bucket is created. Use this option to set the bucket " -"type. Refer to the B2 documentation for allowed types " -msgstr "" - -#: Library/Backend/Backblaze/Strings.cs:17 -msgid "The bucket type used when creating a bucket" -msgstr "" - -#: Library/Backend/Box/Strings.cs:21 -msgid "" -"This backend can read and write data to Box.com. Supported format is \"box://" -"folder/subfolder\"." -msgstr "" - -#: Library/Backend/Box/Strings.cs:22 -msgid "Box.com" -msgstr "" - -#: Library/Backend/Box/Strings.cs:26 -msgid "Force delete files" -msgstr "" - -#: Library/Backend/Box/Strings.cs:27 -msgid "" -"After deleting a file, it may end up in the trash folder where it will be " -"deleted after a grace period. Use this command to force immediate removal of " -"delete files." +#: Library/Backend/GoogleServices/Strings.cs:45 +msgid "Google Drive" msgstr "" #: Library/Backend/CloudFiles/Strings.cs:4 @@ -567,406 +907,6 @@ msgstr "" msgid "Unexpected CloudFiles response, perhaps the API has changed?" msgstr "" -#: Library/Backend/Dropbox/Strings.cs:22 -msgid "" -"This backend can read and write data to Dropbox. Supported format is " -"\"dropbox://folder/subfolder\"." -msgstr "" - -#: Library/Backend/Dropbox/Strings.cs:23 -msgid "Dropbox" -msgstr "" - -#: Library/Backend/File/Strings.cs:4 -#, csharp-format -msgid "" -"This option only works when the --{0} option is also specified. If there are " -"alternate paths specified, this option indicates the name of a marker file " -"that must be present in the folder. This can be used to handle situations " -"where an external drive changes drive letter or mount point. By ensuring " -"that a certain file exists, it is possible to prevent writing data to an " -"unwanted external drive. The contents of the file are never examined, only " -"file existence." -msgstr "" - -#: Library/Backend/File/Strings.cs:5 -msgid "Look for a file in the destination folder" -msgstr "" - -#: Library/Backend/File/Strings.cs:6 -#, csharp-format -msgid "" -"This option allows multiple targets to be specified. The primary target path " -"is placed before the list of paths supplied with this option. Before " -"starting the backup, each folder in the list is checked for existence and " -"optionally the presence of the marker file supplied by --{0}. The first " -"existing path that optionally contains the marker file is then used as the " -"destination. Multiple destinations are separated with a \"{1}\". On Windows, " -"the path may be a UNC path, and the drive letter may be substituted with an " -"asterisk (*), eg.: \"*:\\backup\", which will examine all drive letters. If " -"a username and password is supplied, the same credentials are used for all " -"destinations." -msgstr "" - -#: Library/Backend/File/Strings.cs:7 -msgid "A list of secondary target paths" -msgstr "" - -#: Library/Backend/File/Strings.cs:8 -msgid "" -"This backend can read and write data to an file based backend. Allowed " -"formats are \"file://hostname/folder\" or \"file://username:" -"password@hostname/folder\". You may supply UNC paths (eg: \"file://\\\\server" -"\\folder\") or local paths (eg: (win) \"file://c:\\folder\", (linux) " -"\"file:///usr/pub/files\")" -msgstr "" - -#: Library/Backend/File/Strings.cs:13 -msgid "Local folder or drive" -msgstr "" - -#: Library/Backend/File/Strings.cs:14 -#, csharp-format -msgid "The folder {0} does not exist" -msgstr "" - -#: Library/Backend/File/Strings.cs:15 -#, csharp-format -msgid "" -"The marker file \"{0}\" was not found in any of the examined destinations: " -"{1}" -msgstr "" - -#: Library/Backend/File/Strings.cs:16 -msgid "" -"When storing the file, the standard operation is to copy the file and delete " -"the original. This sequence ensures that the operation can be retried if " -"something goes wrong. Activating this option may cause the retry operation " -"to fail. This option has no effect unless the --disable-streaming-transfers " -"options is activated." -msgstr "" - -#: Library/Backend/File/Strings.cs:17 -msgid "Move the file instead of copying it" -msgstr "" - -#: Library/Backend/File/Strings.cs:18 -msgid "Force authentication against remote share" -msgstr "" - -#: Library/Backend/File/Strings.cs:19 -msgid "" -"If this option is set, any existing authentication against the remote share " -"is dropped before attempting to authenticate" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:7 -msgid "" -"This backend can read and write data to an FTP based backend. Allowed " -"formats are \"ftp://hostname/folder\" or \"ftp://username:password@hostname/" -"folder\"" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:8 -msgid "" -"If this flag is set, the FTP connection is made in active mode. Even if the " -"\"ftp-passive\" flag is also set, the connection will be made in active mode" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:9 Library/Backend/FTP/Strings.cs:11 -msgid "Toggles the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:10 -msgid "" -"If this flag is set, the FTP connection is made in passive mode, which works " -"better with some firewalls. If the \"ftp-regular\" flag is also set, this " -"flag is ignored" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:16 -msgid "" -"Use this flag to communicate using Secure Socket Layer (SSL) over ftp (ftps)." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:17 -msgid "Instructs Duplicati to use an SSL (ftps) connection" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:18 -msgid "FTP" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:19 Library/Backend/TahoeLAFS/Strings.cs:8 -#: Library/Backend/WEBDAV/Strings.cs:15 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:23 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:22 -msgid "" -"This backend can read and write data to Google Cloud Storage. Supported " -"format is \"googlecloudstore://bucket/folder\"." -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:23 -msgid "Google Cloud Storage" -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:25 -#, csharp-format -msgid "You must supply a project ID with --{0} for creating a bucket" -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:29 -#, csharp-format -msgid "" -"This option is only used when creating new buckets. Use this option to " -"change what region the data is stored in. Charges vary with bucket location. " -"Known bucket locations:\n" -"{0}" -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:31 -msgid "Specifies location option for creating a bucket" -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:32 -#, csharp-format -msgid "" -"This option is only used when creating new buckets. Use this option to " -"change what storage type the bucket has. Charges and functionality vary with " -"bucket storage class. Known storage classes:\n" -"{0}" -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:34 -msgid "Specifies storage class for creating a bucket" -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:35 -msgid "Specifies project for creating a bucket" -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:36 -msgid "" -"This option is only used when creating new buckets. Use this option to " -"supply the project ID that the bucket is attached to. The project determines " -"where usage charges are applied" -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:41 -#, csharp-format -msgid "" -"The account access has been blocked by Google, please visit this URL and " -"unlock it: {0}" -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:42 -msgid "" -"This backend can read and write data to Google Drive. Supported format is " -"\"googledrive://folder/subfolder\"." -msgstr "" - -#: Library/Backend/GoogleServices/Strings.cs:45 -msgid "Google Drive" -msgstr "" - -#: Library/Backend/HubiC/Strings.cs:21 -msgid "" -"This backend can read and write data to HubiC. Supported format is \"hubic://" -"container/folder\"." -msgstr "" - -#: Library/Backend/HubiC/Strings.cs:22 -msgid "HubiC" -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:5 -msgid "Jottacloud" -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:6 -msgid "" -"This backend can read and write data to Jottacloud using it's REST protocol. " -"Allowed format is \"jottacloud://folder/subfolder\"." -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:7 Library/Backend/Mega/Strings.cs:10 -msgid "No username given" -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:8 Library/Backend/Mega/Strings.cs:9 -msgid "No password given" -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:9 Library/Backend/Mega/Strings.cs:11 -msgid "No path given, cannot upload files to the root folder" -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:10 -msgid "Illegal mount point given." -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:16 -msgid "Supplies the backup device to use" -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:17 -#, csharp-format -msgid "" -"The backup device to use. Will be created if not already exists. You can " -"manage your devices from the backup panel in the Jottacloud web interface. " -"When you specify a custom device you should also specify the mount point to " -"use on this device with the \"{0}\" option." -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:18 -msgid "Supplies the mount point to use on the server" -msgstr "" - -#: Library/Backend/Jottacloud/Strings.cs:19 -#, csharp-format -msgid "" -"The mount point to use on the server. The default is \"Archive\" for using " -"the built-in archive mount point. Set this option to \"Sync\" to use the " -"built-in synchronization mount point instead, or if you have specified a " -"custom device with option \"{0}\" you are free to name the mount point as " -"you like." -msgstr "" - -#: Library/Backend/Mega/Strings.cs:4 -msgid "mega.nz" -msgstr "" - -#: Library/Backend/Mega/Strings.cs:12 -msgid "" -"This backend can read and write data to Mega.co.nz. Allowed formats are: " -"\"mega://folder/subfolder\"" -msgstr "" - -#: Library/Backend/OAuthHelper/Strings.cs:7 -#, csharp-format -msgid "" -"Failed to authorize using the OAuth service: {0}. If the problem persists, " -"try generating a new authid token from: {1}" -msgstr "" - -#: Library/Backend/OAuthHelper/Strings.cs:8 -#: Library/Backend/OneDrive/Strings.cs:7 -#, csharp-format -msgid "Unexpected error code: {0} - {1}" -msgstr "" - -#: Library/Backend/OAuthHelper/Strings.cs:11 -msgid "The OAuth service is currently over quota, try again in a few hours" -msgstr "" - -#: Library/Backend/OneDrive/Strings.cs:5 -#, csharp-format -msgid "" -"Failed to authorize using the WLID service: {0}. If the problem persists, " -"try generating a new authid token from: {1}" -msgstr "" - -#: Library/Backend/OneDrive/Strings.cs:6 -msgid "Autocreated folder" -msgstr "" - -#: Library/Backend/OneDrive/Strings.cs:8 -#, csharp-format -msgid "Missing the folder: {0}" -msgstr "" - -#: Library/Backend/OneDrive/Strings.cs:9 Library/Compression/Strings.cs:13 -#, csharp-format -msgid "File not found: {0}" -msgstr "" - -#: Library/Backend/OneDrive/Strings.cs:10 -msgid "Microsoft OneDrive" -msgstr "" - -#: Library/Backend/OneDrive/Strings.cs:13 -#, csharp-format -msgid "" -"Stores files on Microsoft OneDrive. Usage of this backend requires that you " -"agree to the terms in {0} ({1}) and {2} ({3})" -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -msgid "" -"This backend can read and write data to Swift (OpenStack Object Storage). " -"Supported format is \"openstack://container/folder\"." -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -msgid "OpenStack Simple Storage" -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -#, csharp-format -msgid "Missing required option: {0}" -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -#, csharp-format -msgid "" -"The password used to connect to the server. This may also be supplied as the " -"environment variable \"AUTH_PASSWORD\". If the password is supplied, --{0} " -"must also be set" -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -msgid "" -"The Tenant Name is commonly the paying user account name. This option must " -"be supplied when authenticating with a password, but is not required when " -"using an API key." -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -msgid "Supplies the Tenant Name used to connect to the server" -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -msgid "" -"The API key can be used to connect without supplying a password and tenant " -"ID with some providers." -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -msgid "Supplies the API key used to connect to the server" -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -#, csharp-format -msgid "" -"The authentication URL is used to authenticate the user and find the storage " -"service. The URL commonly ends with \"/v2.0\". Known providers are: {0}{1}" -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -msgid "Supplies the authentication URL" -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -msgid "" -"This option is only used when creating a container, and is used to indicate " -"where the container should be placed. Consult your provider for a list of " -"valid regions, or leave empty for the default region." -msgstr "" - -#: Library/Backend/OpenStack/Strings.cs:20 -msgid "Supplies the region used for creating a container" -msgstr "" - #: Library/Backend/S3/Strings.cs:4 msgid "" "The AWS \"Secret Access Key\" can be obtained after logging into your AWS " @@ -1097,8 +1037,8 @@ msgid "" "connections." msgstr "" -#: Library/Backend/S3/Strings.cs:34 Library/Backend/TahoeLAFS/Strings.cs:6 -#: Library/Backend/WEBDAV/Strings.cs:20 +#: Library/Backend/S3/Strings.cs:34 Library/Backend/WEBDAV/Strings.cs:20 +#: Library/Backend/TahoeLAFS/Strings.cs:6 msgid "Instructs Duplicati to use an SSL (https) connection" msgstr "" @@ -1112,139 +1052,70 @@ msgstr "" msgid "Specify storage class" msgstr "" -#: Library/Backend/SharePoint/Strings.cs:6 -msgid "Microsoft SharePoint" -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:7 +#: Library/Backend/AlternativeFTP/Strings.cs:9 msgid "" -"Supports connections to a SharePoint server (including OneDrive for " -"Business). Allowed formats are \"mssp://tennant.sharepoint.com/PathToWeb//" -"BaseDocLibrary/subfolder\" or \"mssp://username:password@tennant.sharepoint." -"com/PathToWeb//BaseDocLibrary/subfolder\". Use a double slash '//' in the " -"path to denote the web from the documents library." +"This backend can read and write data to an FTP based backend using an " +"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" or " +"\"aftp://username:password@hostname/folder\"" msgstr "" -#: Library/Backend/SharePoint/Strings.cs:12 -#: Library/Backend/WEBDAV/Strings.cs:11 -msgid "" -"If the server and client both supports integrated authentication, this " -"option enables that authentication method. This is likely only available " -"with windows servers and clients." +#: Library/Backend/AlternativeFTP/Strings.cs:14 +msgid "Alternative FTP" msgstr "" -#: Library/Backend/SharePoint/Strings.cs:13 -#: Library/Backend/WEBDAV/Strings.cs:12 -msgid "Use windows integrated authentication to connect to the server" -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:14 -msgid "" -"Use this option to have files moved to the recycle bin folder instead of " -"removing them permanently when compacting or deleting backups." -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:15 -msgid "Move deleted files to the recycle bin" -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:17 -msgid "" -"Use this option to upload files to SharePoint as a whole with BinaryDirect " -"mode. This is the most efficient way of uploading, but can cause non-" -"recoverable timeouts under certain conditions. Use this option only with " -"very fast and stable internet connections." -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:18 -msgid "Upload files using binary direct mode." -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:20 -msgid "" -"Use this option to specify a custom value for timeouts of web operation when " -"communicating with SharePoint Server. Recommended value is 180s." -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:21 -msgid "Set timeout for SharePoint web operations." -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:23 -msgid "" -"Use this option to specify the size of each chunk when uploading to " -"SharePoint Server. Recommended value is 4MB." -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:24 -msgid "Set block size for chunked uploads to SharePoint." -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:26 +#: Library/Backend/AlternativeFTP/Strings.cs:15 #, csharp-format -msgid "Element with path '{0}' not found on host '{1}'." +msgid "The folder {0} was not found. Message: {1}" msgstr "" -#: Library/Backend/SharePoint/Strings.cs:27 +#: Library/Backend/AlternativeFTP/Strings.cs:19 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/AlternativeFTP/Strings.cs:20 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/AlternativeFTP/Strings.cs:21 +msgid "Configure the FTP data connection type" +msgstr "" + +#: Library/Backend/AlternativeFTP/Strings.cs:22 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected " +"option." +msgstr "" + +#: Library/Backend/AlternativeFTP/Strings.cs:23 +msgid "Configure the FTP encryption mode" +msgstr "" + +#: Library/Backend/AlternativeFTP/Strings.cs:24 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/AlternativeFTP/Strings.cs:25 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" + +#: Library/Backend/AlternativeFTP/Strings.cs:26 #, csharp-format -msgid "" -"No SharePoint web could be logged in to at path '{0}'. Maybe wrong " -"credentials. Or try using '//' in path to separate web from folder path." +msgid "Error on deleting file: {0}" msgstr "" -#: Library/Backend/SharePoint/Strings.cs:28 -msgid "" -"Everything seemed alright, but then web title could not be read to test " -"connection. Something's wrong." +#: Library/Backend/AlternativeFTP/Strings.cs:27 +#, csharp-format +msgid "Error reading file: {0}" msgstr "" -#: Library/Backend/SharePoint/Strings.cs:33 -msgid "Microsoft OneDrive for Business" -msgstr "" - -#: Library/Backend/SharePoint/Strings.cs:34 -msgid "" -"Supports connections to Microsoft OneDrive for Business. Allowed formats are " -"\"od4b://tennant.sharepoint.com/personal/username_domain/Documents/subfolder" -"\" or \"od4b://username:password@tennant.sharepoint.com/personal/" -"username_domain/Documents/folder\". You can use a double slash '//' in the " -"path to denote the base path from the documents folder." -msgstr "" - -#: Library/Backend/Sia/Strings.cs:6 -msgid "Sia Decentralized Cloud" -msgstr "" - -#: Library/Backend/Sia/Strings.cs:7 -msgid "This backend can read and write data to Sia." -msgstr "" - -#: Library/Backend/Sia/Strings.cs:8 -msgid "Sia address" -msgstr "" - -#: Library/Backend/Sia/Strings.cs:9 -msgid "Sia address, ie 127.0.0.1:9980" -msgstr "" - -#: Library/Backend/Sia/Strings.cs:10 -msgid "Backup path" -msgstr "" - -#: Library/Backend/Sia/Strings.cs:11 -msgid "Target path, ie /backup" -msgstr "" - -#: Library/Backend/Sia/Strings.cs:12 Library/Backend/Sia/Strings.cs:13 -msgid "Sia password" -msgstr "" - -#: Library/Backend/Sia/Strings.cs:14 -msgid "3" -msgstr "" - -#: Library/Backend/Sia/Strings.cs:15 -msgid "Minimum value is 3." +#: Library/Backend/AlternativeFTP/Strings.cs:28 +#, csharp-format +msgid "Error writing file: {0}" msgstr "" #: Library/Backend/SSHv2/Strings.cs:4 @@ -1400,24 +1271,478 @@ msgid "" "(NOT SECURE) for testing!" msgstr "" -#: Library/Backend/TahoeLAFS/Strings.cs:4 +#: Library/Backend/Box/Strings.cs:21 msgid "" -"This backend can read and write data to a Tahoe-LAFS based backend. Allowed " -"format is \"tahoe://hostname:port/uri/$DIRCAP\"." +"This backend can read and write data to Box.com. Supported format is \"box://" +"folder/subfolder\"." msgstr "" -#: Library/Backend/TahoeLAFS/Strings.cs:5 Library/Backend/WEBDAV/Strings.cs:19 +#: Library/Backend/Box/Strings.cs:22 +msgid "Box.com" +msgstr "" + +#: Library/Backend/Box/Strings.cs:26 +msgid "Force delete files" +msgstr "" + +#: Library/Backend/Box/Strings.cs:27 msgid "" -"Use this flag to communicate using Secure Socket Layer (SSL) over http " -"(https)." +"After deleting a file, it may end up in the trash folder where it will be " +"deleted after a grace period. Use this command to force immediate removal of " +"delete files." msgstr "" -#: Library/Backend/TahoeLAFS/Strings.cs:7 -msgid "Tahoe-LAFS" +#: Library/Backend/File/Strings.cs:4 +#, csharp-format +msgid "" +"This option only works when the --{0} option is also specified. If there are " +"alternate paths specified, this option indicates the name of a marker file " +"that must be present in the folder. This can be used to handle situations " +"where an external drive changes drive letter or mount point. By ensuring " +"that a certain file exists, it is possible to prevent writing data to an " +"unwanted external drive. The contents of the file are never examined, only " +"file existence." msgstr "" -#: Library/Backend/TahoeLAFS/Strings.cs:9 -msgid "Unsupported URL format, must start with \"uri/URI:DIR2:\"" +#: Library/Backend/File/Strings.cs:5 +msgid "Look for a file in the destination folder" +msgstr "" + +#: Library/Backend/File/Strings.cs:6 +#, csharp-format +msgid "" +"This option allows multiple targets to be specified. The primary target path " +"is placed before the list of paths supplied with this option. Before " +"starting the backup, each folder in the list is checked for existence and " +"optionally the presence of the marker file supplied by --{0}. The first " +"existing path that optionally contains the marker file is then used as the " +"destination. Multiple destinations are separated with a \"{1}\". On Windows, " +"the path may be a UNC path, and the drive letter may be substituted with an " +"asterisk (*), eg.: \"*:\\backup\", which will examine all drive letters. If " +"a username and password is supplied, the same credentials are used for all " +"destinations." +msgstr "" + +#: Library/Backend/File/Strings.cs:7 +msgid "A list of secondary target paths" +msgstr "" + +#: Library/Backend/File/Strings.cs:8 +msgid "" +"This backend can read and write data to an file based backend. Allowed " +"formats are \"file://hostname/folder\" or \"file://username:" +"password@hostname/folder\". You may supply UNC paths (eg: \"file://\\\\server" +"\\folder\") or local paths (eg: (win) \"file://c:\\folder\", (linux) " +"\"file:///usr/pub/files\")" +msgstr "" + +#: Library/Backend/File/Strings.cs:13 +msgid "Local folder or drive" +msgstr "" + +#: Library/Backend/File/Strings.cs:14 +#, csharp-format +msgid "The folder {0} does not exist" +msgstr "" + +#: Library/Backend/File/Strings.cs:15 +#, csharp-format +msgid "" +"The marker file \"{0}\" was not found in any of the examined destinations: " +"{1}" +msgstr "" + +#: Library/Backend/File/Strings.cs:16 +msgid "" +"When storing the file, the standard operation is to copy the file and delete " +"the original. This sequence ensures that the operation can be retried if " +"something goes wrong. Activating this option may cause the retry operation " +"to fail. This option has no effect unless the --disable-streaming-transfers " +"options is activated." +msgstr "" + +#: Library/Backend/File/Strings.cs:17 +msgid "Move the file instead of copying it" +msgstr "" + +#: Library/Backend/File/Strings.cs:18 +msgid "Force authentication against remote share" +msgstr "" + +#: Library/Backend/File/Strings.cs:19 +msgid "" +"If this option is set, any existing authentication against the remote share " +"is dropped before attempting to authenticate" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:4 +msgid "" +"The \"B2 Cloud Storage Application Key\" can be obtained after logging into " +"your Backblaze account, this can also be supplied through the \"auth-password" +"\" property" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:5 +msgid "The \"B2 Cloud Storage Application Key\"" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:6 +msgid "" +"The \"B2 Cloud Storage Account ID\" can be obtained after logging into your " +"Backblaze account, this can also be supplied through the \"auth-username\" " +"property" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:7 +msgid "The \"B2 Cloud Storage Account ID\"" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:8 +msgid "B2 Cloud Storage" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:13 +msgid "No \"B2 Cloud Storage Application Key\" given" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:14 +msgid "No \"B2 Cloud Storage Account ID\" given" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:15 +msgid "" +"This backend can read and write data to the Backblaze B2 Cloud Storage. " +"Allowed formats are: \"b2://bucketname/prefix\"" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:16 +msgid "" +"By default, a private bucket is created. Use this option to set the bucket " +"type. Refer to the B2 documentation for allowed types " +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:17 +msgid "The bucket type used when creating a bucket" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:18 +msgid "" +"Use this option to set the page size for listing contents of B2 buckets. A " +"lower number means less data, but can increase the number of Class C " +"transaction on B2. Suggested values are between 100 and 1000" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:19 +msgid "The size of file-listing pages" +msgstr "" + +#: Library/Backend/Backblaze/Strings.cs:20 +#, csharp-format +msgid "" +"The setting \"{0}\" is invalid for \"{1}\", it must be an integer larger " +"than zero" +msgstr "" + +#: Library/Backend/Sia/Strings.cs:6 +msgid "Sia Decentralized Cloud" +msgstr "" + +#: Library/Backend/Sia/Strings.cs:7 +msgid "This backend can read and write data to Sia." +msgstr "" + +#: Library/Backend/Sia/Strings.cs:8 +msgid "Sia address" +msgstr "" + +#: Library/Backend/Sia/Strings.cs:9 +msgid "Sia address, ie 127.0.0.1:9980" +msgstr "" + +#: Library/Backend/Sia/Strings.cs:10 +msgid "Backup path" +msgstr "" + +#: Library/Backend/Sia/Strings.cs:11 +msgid "Target path, ie /backup" +msgstr "" + +#: Library/Backend/Sia/Strings.cs:12 Library/Backend/Sia/Strings.cs:13 +msgid "Sia password" +msgstr "" + +#: Library/Backend/Sia/Strings.cs:14 +msgid "3" +msgstr "" + +#: Library/Backend/Sia/Strings.cs:15 +msgid "Minimum value is 3." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:5 +#, csharp-format +msgid "" +"Failed to authorize using the WLID service: {0}. If the problem persists, " +"try generating a new authid token from: {1}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:6 +msgid "Autocreated folder" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:7 +#: Library/Backend/OAuthHelper/Strings.cs:8 +#, csharp-format +msgid "Unexpected error code: {0} - {1}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:8 +#, csharp-format +msgid "Missing the folder: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:9 Library/Compression/Strings.cs:13 +#, csharp-format +msgid "File not found: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:10 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:13 +#, csharp-format +msgid "" +"Stores files on Microsoft OneDrive. Usage of this backend requires that you " +"agree to the terms in {0} ({1}) and {2} ({3})" +msgstr "" + +#: Library/Backend/HubiC/Strings.cs:21 +msgid "" +"This backend can read and write data to HubiC. Supported format is \"hubic://" +"container/folder\"." +msgstr "" + +#: Library/Backend/HubiC/Strings.cs:22 +msgid "HubiC" +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:4 +msgid "All files will be written to the container specified" +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:5 +msgid "The name of the storage container " +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:6 +msgid "Azure blob" +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:7 +msgid "No Azure storage account name given" +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:8 +msgid "" +"The Azure storage account name which can be obtained by clicking the " +"\"Manage Access Keys\" button on the storage account dashboard" +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:9 +msgid "The storage account name" +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:10 +msgid "" +"The Azure access key which can be obtained by clicking the \"Manage Access " +"Keys\" button on the storage account dashboard" +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:11 +msgid "The access key" +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:12 +msgid "No Azure access key given" +msgstr "" + +#: Library/Backend/AzureBlob/Strings.cs:17 +msgid "" +"This backend can read and write data to Azure blob storage. Allowed formats " +"are: \"azure://bucketname\"" +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:5 +msgid "Jottacloud" +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:6 +msgid "" +"This backend can read and write data to Jottacloud using it's REST protocol. " +"Allowed format is \"jottacloud://folder/subfolder\"." +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:7 Library/Backend/Mega/Strings.cs:10 +msgid "No username given" +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:8 Library/Backend/Mega/Strings.cs:9 +msgid "No password given" +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:9 Library/Backend/Mega/Strings.cs:11 +msgid "No path given, cannot upload files to the root folder" +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:10 +msgid "Illegal mount point given." +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:16 +msgid "Supplies the backup device to use" +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:17 +#, csharp-format +msgid "" +"The backup device to use. Will be created if not already exists. You can " +"manage your devices from the backup panel in the Jottacloud web interface. " +"When you specify a custom device you should also specify the mount point to " +"use on this device with the \"{0}\" option." +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:18 +msgid "Supplies the mount point to use on the server" +msgstr "" + +#: Library/Backend/Jottacloud/Strings.cs:19 +#, csharp-format +msgid "" +"The mount point to use on the server. The default is \"Archive\" for using " +"the built-in archive mount point. Set this option to \"Sync\" to use the " +"built-in synchronization mount point instead, or if you have specified a " +"custom device with option \"{0}\" you are free to name the mount point as " +"you like." +msgstr "" + +#: Library/Backend/Mega/Strings.cs:4 +msgid "mega.nz" +msgstr "" + +#: Library/Backend/Mega/Strings.cs:12 +msgid "" +"This backend can read and write data to Mega.co.nz. Allowed formats are: " +"\"mega://folder/subfolder\"" +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:6 +msgid "Microsoft SharePoint" +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:7 +msgid "" +"Supports connections to a SharePoint server (including OneDrive for " +"Business). Allowed formats are \"mssp://tennant.sharepoint.com/PathToWeb//" +"BaseDocLibrary/subfolder\" or \"mssp://username:password@tennant.sharepoint." +"com/PathToWeb//BaseDocLibrary/subfolder\". Use a double slash '//' in the " +"path to denote the web from the documents library." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:12 +#: Library/Backend/WEBDAV/Strings.cs:11 +msgid "" +"If the server and client both supports integrated authentication, this " +"option enables that authentication method. This is likely only available " +"with windows servers and clients." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:13 +#: Library/Backend/WEBDAV/Strings.cs:12 +msgid "Use windows integrated authentication to connect to the server" +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:14 +msgid "" +"Use this option to have files moved to the recycle bin folder instead of " +"removing them permanently when compacting or deleting backups." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:15 +msgid "Move deleted files to the recycle bin" +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:17 +msgid "" +"Use this option to upload files to SharePoint as a whole with BinaryDirect " +"mode. This is the most efficient way of uploading, but can cause non-" +"recoverable timeouts under certain conditions. Use this option only with " +"very fast and stable internet connections." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:18 +msgid "Upload files using binary direct mode." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:20 +msgid "" +"Use this option to specify a custom value for timeouts of web operation when " +"communicating with SharePoint Server. Recommended value is 180s." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:21 +msgid "Set timeout for SharePoint web operations." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:23 +msgid "" +"Use this option to specify the size of each chunk when uploading to " +"SharePoint Server. Recommended value is 4MB." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:24 +msgid "Set block size for chunked uploads to SharePoint." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:26 +#, csharp-format +msgid "Element with path '{0}' not found on host '{1}'." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:27 +#, csharp-format +msgid "" +"No SharePoint web could be logged in to at path '{0}'. Maybe wrong " +"credentials. Or try using '//' in path to separate web from folder path." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:28 +msgid "" +"Everything seemed alright, but then web title could not be read to test " +"connection. Something's wrong." +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:33 +msgid "Microsoft OneDrive for Business" +msgstr "" + +#: Library/Backend/SharePoint/Strings.cs:34 +msgid "" +"Supports connections to Microsoft OneDrive for Business. Allowed formats are " +"\"od4b://tennant.sharepoint.com/personal/username_domain/Documents/subfolder" +"\" or \"od4b://username:password@tennant.sharepoint.com/personal/" +"username_domain/Documents/folder\". You can use a double slash '//' in the " +"path to denote the base path from the documents folder." +msgstr "" + +#: Library/Backend/Dropbox/Strings.cs:22 +msgid "" +"This backend can read and write data to Dropbox. Supported format is " +"\"dropbox://folder/subfolder\"." +msgstr "" + +#: Library/Backend/Dropbox/Strings.cs:23 +msgid "Dropbox" msgstr "" #: Library/Backend/WEBDAV/Strings.cs:4 @@ -1463,6 +1788,12 @@ msgid "" "Error message: {3}" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:19 Library/Backend/TahoeLAFS/Strings.cs:5 +msgid "" +"Use this flag to communicate using Secure Socket Layer (SSL) over http " +"(https)." +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:21 msgid "" "To aid in debugging issues, it is possible to set a path to a file that will " @@ -1473,6 +1804,41 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/TahoeLAFS/Strings.cs:4 +msgid "" +"This backend can read and write data to a Tahoe-LAFS based backend. Allowed " +"format is \"tahoe://hostname:port/uri/$DIRCAP\"." +msgstr "" + +#: Library/Backend/TahoeLAFS/Strings.cs:7 +msgid "Tahoe-LAFS" +msgstr "" + +#: Library/Backend/TahoeLAFS/Strings.cs:9 +msgid "Unsupported URL format, must start with \"uri/URI:DIR2:\"" +msgstr "" + +#: Library/Backend/OAuthHelper/Strings.cs:7 +#, csharp-format +msgid "" +"Failed to authorize using the OAuth service: {0}. If the problem persists, " +"try generating a new authid token from: {1}" +msgstr "" + +#: Library/Backend/OAuthHelper/Strings.cs:11 +msgid "The OAuth service is currently over quota, try again in a few hours" +msgstr "" + +#: Library/DynamicLoader/Strings.cs:4 +#, csharp-format +msgid "Failed to load assembly {0}, error message: {1}" +msgstr "" + +#: Library/DynamicLoader/Strings.cs:5 +#, csharp-format +msgid "Failed to load process type {0} assembly {1}, error message: {2}" +msgstr "" + #: Library/Compression/Strings.cs:4 #, csharp-format msgid "Please use the {0} option instead" @@ -1565,319 +1931,36 @@ msgstr "" msgid "Sets the 7z fast algorithm usage" msgstr "" -#: Library/DynamicLoader/Strings.cs:4 +#: Library/SQLiteHelper/Strings.cs:4 +msgid "backup" +msgstr "" + +#: Library/SQLiteHelper/Strings.cs:5 #, csharp-format -msgid "Failed to load assembly {0}, error message: {1}" +msgid "Unable to determine database format: {0}" msgstr "" -#: Library/DynamicLoader/Strings.cs:5 -#, csharp-format -msgid "Failed to load process type {0} assembly {1}, error message: {2}" -msgstr "" - -#: Library/Encryption/Strings.cs:4 -msgid "" -"This module encrypts all files in the same way that AESCrypt does, using 256 " -"bit AES encryption." -msgstr "" - -#: Library/Encryption/Strings.cs:5 -msgid "AES-256 encryption, built in" -msgstr "" - -#: Library/Encryption/Strings.cs:6 -msgid "Empty passphrase not allowed" -msgstr "" - -#: Library/Encryption/Strings.cs:7 -msgid "" -"Use this option to set the thread level allowed for AES crypt operations. " -"Valid values are 0 (uses default), or from 1 (no multithreading) to 4 (max. " -"multithreading)" -msgstr "" - -#: Library/Encryption/Strings.cs:8 -msgid "Set thread level utilized for crypting (0-4)" -msgstr "" - -#: Library/Encryption/Strings.cs:11 -#, csharp-format -msgid "Failed to decrypt data (invalid passphrase?): {0}" -msgstr "" - -#: Library/Encryption/Strings.cs:14 -msgid "" -"The GPG encryption module uses the GNU Privacy Guard program to encrypt and " -"decrypt files. It requires that the gpg executable is available on the " -"system. On Windows it is assumed that this is in the default installation " -"folder under program files, under Linux and OSX it is assumed that the " -"program is available via the PATH environment variable. It is possible to " -"supply the path to GPG using the --gpg-program-path switch." -msgstr "" - -#: Library/Encryption/Strings.cs:15 -msgid "GNU Privacy Guard, external" -msgstr "" - -#: Library/Encryption/Strings.cs:16 -msgid "" -"Use this switch to specify any extra options to GPG. You cannot specify the " -"--passphrase-fd option here. The --decrypt option is always specified." -msgstr "" - -#: Library/Encryption/Strings.cs:17 -msgid "Extra GPG commandline options for decryption" -msgstr "" - -#: Library/Encryption/Strings.cs:18 -msgid "" -"The GPG encryption/decryption will use the --armor option for GPG to protect " -"the files with armor. Specify this switch to remove the --armor option." -msgstr "" - -#: Library/Encryption/Strings.cs:19 -msgid "Don't use GPG Armor" -msgstr "" - -#: Library/Encryption/Strings.cs:20 -msgid "" -"Use this switch to specify any extra options to GPG. You cannot specify the " -"--passphrase-fd option here. The --encrypt option is always specified." -msgstr "" - -#: Library/Encryption/Strings.cs:21 -msgid "Extra GPG commandline options for encryption" -msgstr "" - -#: Library/Encryption/Strings.cs:22 -#, csharp-format -msgid "Failed to execute GPG at \"\"{0}\" {1}\": {2}" -msgstr "" - -#: Library/Encryption/Strings.cs:23 -msgid "" -"The path to the GNU Privacy Guard program. If not supplied, Duplicati will " -"assume that the program \"gpg\" is available in the system path." -msgstr "" - -#: Library/Encryption/Strings.cs:24 -msgid "The path to GnuPG" -msgstr "" - -#: Library/Encryption/Strings.cs:25 +#: Library/SQLiteHelper/Strings.cs:6 #, csharp-format msgid "" -"This option has non-standard handling, please use the --{0} option instead." -msgstr "" - -#: Library/Encryption/Strings.cs:26 -msgid "" -"Use this option to supply the --armor option to GPG. The files will be " -"larger but can be sent as pure text files." -msgstr "" - -#: Library/Encryption/Strings.cs:27 -msgid "Use GPG Armor" -msgstr "" - -#: Library/Encryption/Strings.cs:28 -msgid "Overrides the GPG command supplied for decryption" -msgstr "" - -#: Library/Encryption/Strings.cs:29 -msgid "The GPG decryption command" -msgstr "" - -#: Library/Encryption/Strings.cs:30 -#, csharp-format -msgid "" -"Overrides the default GPG encryption command \"{0}\", normal usage is to " -"request asymetric encryption with the setting {1}" -msgstr "" - -#: Library/Encryption/Strings.cs:31 -msgid "The GPG encryption command" -msgstr "" - -#: Library/Encryption/Strings.cs:34 -#, csharp-format -msgid "Decryption failed: {0}" -msgstr "" - -#: Library/Encryption/Strings.cs:35 -msgid "Failure while invoking GnuPG, program won't flush output" -msgstr "" - -#: Library/Encryption/Strings.cs:36 -msgid "Failure while invoking GnuPG, program won't terminate" -msgstr "" - -#: Library/Interface/CustomExceptions.cs:77 -#: Library/Interface/CustomExceptions.cs:85 -msgid "The requested file does not exist" -msgstr "" - -#: Library/Interface/Strings.cs:4 -msgid "aliases" -msgstr "" - -#: Library/Interface/Strings.cs:5 -msgid "default value" -msgstr "" - -#: Library/Interface/Strings.cs:6 -msgid "[DEPRECATED]" -msgstr "" - -#: Library/Interface/Strings.cs:7 -msgid "values" -msgstr "" - -#: Library/Interface/Strings.cs:10 -msgid "Boolean" -msgstr "" - -#: Library/Interface/Strings.cs:11 -msgid "Enumeration" -msgstr "" - -#: Library/Interface/Strings.cs:12 -msgid "Flags" -msgstr "" - -#: Library/Interface/Strings.cs:13 -msgid "Integer" -msgstr "" - -#: Library/Interface/Strings.cs:14 -msgid "Path" -msgstr "" - -#: Library/Interface/Strings.cs:15 -msgid "Size" -msgstr "" - -#: Library/Interface/Strings.cs:16 -msgid "String" -msgstr "" - -#: Library/Interface/Strings.cs:17 -msgid "Timespan" -msgstr "" - -#: Library/Interface/Strings.cs:18 -msgid "Unknown" -msgstr "" - -#: Library/Interface/Strings.cs:21 -#, csharp-format -msgid "" -"The configuration for the backend is not valid, it is missing the {0} field" -msgstr "" - -#: Library/Interface/Strings.cs:22 -msgid "Do you want to test the connection?" -msgstr "" - -#: Library/Interface/Strings.cs:23 -#, csharp-format -msgid "Connection Failed: {0}" -msgstr "" - -#: Library/Interface/Strings.cs:24 -msgid "Connection succeeded!" -msgstr "" - -#: Library/Interface/Strings.cs:25 -msgid "" -"You have not entered a path. This will store all backups in the default " -"directory. Is this what you want?" -msgstr "" - -#: Library/Interface/Strings.cs:26 -msgid "You must enter a password" -msgstr "" - -#: Library/Interface/Strings.cs:27 -msgid "" -"You have not entered a password.\n" -"Proceed without a password?" -msgstr "" - -#: Library/Interface/Strings.cs:29 -msgid "You must enter the name of the server" -msgstr "" - -#: Library/Interface/Strings.cs:30 -msgid "You must enter a username" -msgstr "" - -#: Library/Interface/Strings.cs:31 -msgid "" -"You have not entered a username.\n" -"This is fine if the server allows anonymous uploads, but likely a username " -"is required\n" -"Proceed without a username?" -msgstr "" - -#: Library/Interface/Strings.cs:34 -msgid "" -"The connection succeeded but another backup was found in the destination " -"folder. It is possible to configure Duplicati to store multiple backups in " -"the same folder, but it is not recommended.\n" "\n" -"Do you want to use the selected folder?" +"The database has version {0} but the largest supported version is {1}.\n" +"\n" +"This is likely caused by upgrading to a newer version and then downgrading.\n" +"If this is the case, there is likely a backup file of the previous database " +"version in the folder {2}." msgstr "" -#: Library/Interface/Strings.cs:37 -msgid "The folder cannot be created because it already exists" +#: Library/SQLiteHelper/Strings.cs:11 +msgid "Unknown table layout detected" msgstr "" -#: Library/Interface/Strings.cs:38 -msgid "Folder created!" -msgstr "" - -#: Library/Interface/Strings.cs:39 -msgid "The requested folder does not exist" -msgstr "" - -#: Library/Interface/Strings.cs:40 +#: Library/SQLiteHelper/Strings.cs:12 #, csharp-format -msgid "The server name \"{0}\" is not valid" -msgstr "" - -#: Library/Interface/Strings.cs:41 -msgid "Cancelled" -msgstr "" - -#: Library/Main/BackendManager.cs:588 -#, csharp-format -msgid "Failed to dispose backend instance: {0}" -msgstr "" - -#: Library/Main/BackendManager.cs:611 -#, csharp-format -msgid "Failed to delete file {0}, testing if file exists" -msgstr "" - -#: Library/Main/BackendManager.cs:617 -#, csharp-format -msgid "Recovered from problem with attempting to delete non-existing file {0}" -msgstr "" - -#: Library/Main/BackendManager.cs:622 -#, csharp-format -msgid "Failed to recover from error deleting file {0}" -msgstr "" - -#: Library/Main/BackendManager.cs:1122 -#, csharp-format -msgid "Delete operation failed for {0} with FileNotFound, listing contents" -msgstr "" - -#: Library/Main/BackendManager.cs:1135 -#, csharp-format -msgid "Listing indicates file {0} is deleted correctly" +msgid "" +"Failed to execute SQL: {0}\n" +"Error: {1}\n" +"Database is NOT upgraded." msgstr "" #: Library/Main/Database/ExtensionMethods.cs:48 @@ -1905,137 +1988,141 @@ msgstr "" msgid "{0} records" msgstr "" -#: Library/Main/Strings.cs:7 +#: Library/Main/Strings.cs:8 #, csharp-format msgid "Hash mismatch on file \"{0}\", recorded hash: {1}, actual hash {2}" msgstr "" -#: Library/Main/Strings.cs:8 +#: Library/Main/Strings.cs:9 #, csharp-format msgid "" "The file {0} was downloaded and had size {1} but the size was expected to be " "{2}" msgstr "" -#: Library/Main/Strings.cs:9 +#: Library/Main/Strings.cs:10 #, csharp-format msgid "The option {0} is deprecated: {1}" msgstr "" -#: Library/Main/Strings.cs:10 +#: Library/Main/Strings.cs:11 #, csharp-format msgid "" "The option --{0} exists more than once, please report this to the developers" msgstr "" -#: Library/Main/Strings.cs:11 +#: Library/Main/Strings.cs:12 msgid "No source folders specified for backup" msgstr "" -#: Library/Main/Strings.cs:12 +#: Library/Main/Strings.cs:13 #, csharp-format msgid "The source folder {0} does not exist, aborting backup" msgstr "" -#: Library/Main/Strings.cs:13 +#: Library/Main/Strings.cs:14 #, csharp-format msgid "" "The value \"{1}\" supplied to --{0} does not parse into a valid boolean, " "this will be treated as if it was set to \"true\"" msgstr "" -#: Library/Main/Strings.cs:14 +#: Library/Main/Strings.cs:15 #, csharp-format msgid "" "The option --{0} does not support the value \"{1}\", supported values are: " "{2}" msgstr "" -#: Library/Main/Strings.cs:15 +#: Library/Main/Strings.cs:16 #, csharp-format msgid "" "The option --{0} does not support the value \"{1}\", supported flag values " "are: {2}" msgstr "" -#: Library/Main/Strings.cs:16 +#: Library/Main/Strings.cs:17 #, csharp-format msgid "The value \"{1}\" supplied to --{0} does not represent a valid integer" msgstr "" -#: Library/Main/Strings.cs:17 +#: Library/Main/Strings.cs:18 #, csharp-format msgid "" "The option --{0} is not supported because the module {1} is not currently " "loaded" msgstr "" -#: Library/Main/Strings.cs:18 +#: Library/Main/Strings.cs:19 #, csharp-format msgid "The supplied option --{0} is not supported and will be ignored" msgstr "" -#: Library/Main/Strings.cs:19 +#: Library/Main/Strings.cs:20 #, csharp-format msgid "The value \"{1}\" supplied to --{0} does not represent a valid path" msgstr "" -#: Library/Main/Strings.cs:20 +#: Library/Main/Strings.cs:21 #, csharp-format msgid "The value \"{1}\" supplied to --{0} does not represent a valid size" msgstr "" -#: Library/Main/Strings.cs:21 +#: Library/Main/Strings.cs:22 #, csharp-format msgid "The value \"{1}\" supplied to --{0} does not represent a valid time" msgstr "" -#: Library/Main/Strings.cs:22 +#: Library/Main/Strings.cs:23 #, csharp-format msgid "The operation {0} has started" msgstr "" -#: Library/Main/Strings.cs:23 +#: Library/Main/Strings.cs:24 #, csharp-format msgid "The operation {0} has completed" msgstr "" -#: Library/Main/Strings.cs:24 +#: Library/Main/Strings.cs:25 #, csharp-format msgid "The operation {0} has failed with error: {1}" msgstr "" -#: Library/Main/Strings.cs:25 +#: Library/Main/Strings.cs:26 #, csharp-format msgid "Invalid path: \"{0}\" ({1})" msgstr "" -#: Library/Main/Strings.cs:26 +#: Library/Main/Strings.cs:27 #, csharp-format msgid "" "Failed to apply 'force-locale' setting. Please try to update .NET-Framework. " "Exception was: \"{0}\" " msgstr "" -#: Library/Main/Strings.cs:27 +#: Library/Main/Strings.cs:28 #, csharp-format -msgid "" -"The source folder {0} uses a wildcard drive, which is only supported on " -"Windows, aborting backup" +msgid "The source {0} uses an invalid volume name, aborting backup" msgstr "" -#: Library/Main/Strings.cs:32 +#: Library/Main/Strings.cs:29 +#, csharp-format +msgid "" +"The source {0} is on volume {1}, which could not be found, aborting backup" +msgstr "" + +#: Library/Main/Strings.cs:34 msgid "" "If a backup is interrupted there will likely be partial files present on the " "backend. Using this flag, Duplicati will automatically remove such files " "when encountered." msgstr "" -#: Library/Main/Strings.cs:33 +#: Library/Main/Strings.cs:35 msgid "A flag indicating that Duplicati should remove unused files" msgstr "" -#: Library/Main/Strings.cs:34 +#: Library/Main/Strings.cs:36 msgid "" "A string used to prefix the filenames of the remote volumes, can be used to " "store multiple backups in the same remote folder. The prefix cannot contain " @@ -2043,11 +2130,11 @@ msgid "" "storage." msgstr "" -#: Library/Main/Strings.cs:35 +#: Library/Main/Strings.cs:37 msgid "Remote filename prefix" msgstr "" -#: Library/Main/Strings.cs:36 +#: Library/Main/Strings.cs:38 msgid "" "The operating system keeps track of the last time a file was written. Using " "this information, Duplicati can quickly determine if the file has been " @@ -2055,179 +2142,179 @@ msgid "" "Duplicati won't work correctly unless this flag is set." msgstr "" -#: Library/Main/Strings.cs:37 +#: Library/Main/Strings.cs:39 msgid "Disable checks based on file time" msgstr "" -#: Library/Main/Strings.cs:38 +#: Library/Main/Strings.cs:40 msgid "" "By default, files will be restored in the source folders, use this option to " "restore to another folder" msgstr "" -#: Library/Main/Strings.cs:39 +#: Library/Main/Strings.cs:41 msgid "Restore to another folder" msgstr "" -#: Library/Main/Strings.cs:40 +#: Library/Main/Strings.cs:42 msgid "Toggles system sleep mode" msgstr "" -#: Library/Main/Strings.cs:41 +#: Library/Main/Strings.cs:43 msgid "" "Allow system to enter sleep power modes for inactivity during backup/restore " "operations (Windows/OSX only)" msgstr "" -#: Library/Main/Strings.cs:42 +#: Library/Main/Strings.cs:44 msgid "" "By setting this value you can limit how much bandwidth Duplicati consumes " "for downloads. Setting this limit can make the backups take longer, but will " "make Duplicati less intrusive." msgstr "" -#: Library/Main/Strings.cs:43 +#: Library/Main/Strings.cs:45 msgid "Max number of kilobytes to download pr. second" msgstr "" -#: Library/Main/Strings.cs:44 +#: Library/Main/Strings.cs:46 msgid "" "By setting this value you can limit how much bandwidth Duplicati consumes " "for uploads. Setting this limit can make the backups take longer, but will " "make Duplicati less intrusive." msgstr "" -#: Library/Main/Strings.cs:45 +#: Library/Main/Strings.cs:47 msgid "Max number of kilobytes to upload pr. second" msgstr "" -#: Library/Main/Strings.cs:46 +#: Library/Main/Strings.cs:48 msgid "" "If you store the backups on a local disk, and prefer that they are kept " "unencrypted, you can turn of encryption completely by using this switch." msgstr "" -#: Library/Main/Strings.cs:47 +#: Library/Main/Strings.cs:49 msgid "Disable encryption" msgstr "" -#: Library/Main/Strings.cs:48 +#: Library/Main/Strings.cs:50 msgid "" "If an upload or download fails, Duplicati will retry a number of times " "before failing. Use this to handle unstable network connections better." msgstr "" -#: Library/Main/Strings.cs:49 +#: Library/Main/Strings.cs:51 msgid "Number of times to retry a failed transmission" msgstr "" -#: Library/Main/Strings.cs:50 +#: Library/Main/Strings.cs:52 msgid "" "Supply a passphrase that Duplicati will use to encrypt the backup volumes, " "making them unreadable without the passphrase. This variable can also be " "supplied through the environment variable PASSPHRASE." msgstr "" -#: Library/Main/Strings.cs:51 +#: Library/Main/Strings.cs:53 msgid "Passphrase used to encrypt backups" msgstr "" -#: Library/Main/Strings.cs:52 +#: Library/Main/Strings.cs:54 msgid "" "By default, Duplicati will list and restore files from the most recent " "backup, use this option to select another item. You may use relative times, " "like \"-2M\" for a backup from two months ago." msgstr "" -#: Library/Main/Strings.cs:53 +#: Library/Main/Strings.cs:55 msgid "The time to list/restore files" msgstr "" -#: Library/Main/Strings.cs:54 +#: Library/Main/Strings.cs:56 msgid "" "By default, Duplicati will list and restore files from the most recent " "backup, use this option to select another item. You may enter multiple " "values separated with comma, and ranges using -, e.g. \"0,2-4,7\" ." msgstr "" -#: Library/Main/Strings.cs:55 +#: Library/Main/Strings.cs:57 msgid "The version to list/restore files" msgstr "" -#: Library/Main/Strings.cs:56 +#: Library/Main/Strings.cs:58 msgid "" "When searching for files, only the most recent backup is searched. Use this " "option to show all previous versions too." msgstr "" -#: Library/Main/Strings.cs:57 -msgid "Show all versions" -msgstr "" - -#: Library/Main/Strings.cs:58 -msgid "" -"When searching for files, all matching files are returned. Use this option " -"to return only the largest common prefix path." -msgstr "" - #: Library/Main/Strings.cs:59 -msgid "Show largest prefix" +msgid "Show all versions" msgstr "" #: Library/Main/Strings.cs:60 msgid "" "When searching for files, all matching files are returned. Use this option " -"to return only the entries found in the folder specified as filter." +"to return only the largest common prefix path." msgstr "" #: Library/Main/Strings.cs:61 -msgid "Show folder contents" +msgid "Show largest prefix" msgstr "" #: Library/Main/Strings.cs:62 msgid "" +"When searching for files, all matching files are returned. Use this option " +"to return only the entries found in the folder specified as filter." +msgstr "" + +#: Library/Main/Strings.cs:63 +msgid "Show folder contents" +msgstr "" + +#: Library/Main/Strings.cs:64 +msgid "" "After a failed transmission, Duplicati will wait a short period before " "attempting again. This is useful if the network drops out occasionally " "during transmissions." msgstr "" -#: Library/Main/Strings.cs:63 +#: Library/Main/Strings.cs:65 msgid "Time to wait between retries" msgstr "" -#: Library/Main/Strings.cs:64 +#: Library/Main/Strings.cs:66 msgid "Use this option to attach extra files to the newly uploaded filelists." msgstr "" -#: Library/Main/Strings.cs:65 +#: Library/Main/Strings.cs:67 msgid "Set control files" msgstr "" -#: Library/Main/Strings.cs:66 +#: Library/Main/Strings.cs:68 msgid "" "If the hash for the volume does not match, Duplicati will refuse to use the " "backup. Supply this flag to allow Duplicati to proceed anyway." msgstr "" -#: Library/Main/Strings.cs:67 +#: Library/Main/Strings.cs:69 msgid "Set this flag to skip hash checks" msgstr "" -#: Library/Main/Strings.cs:68 +#: Library/Main/Strings.cs:70 msgid "" "This option allows you to exclude files that are larger than the given " "value. Use this to prevent backups becoming extremely large." msgstr "" -#: Library/Main/Strings.cs:69 +#: Library/Main/Strings.cs:71 msgid "Limit the size of files being backed up" msgstr "" -#: Library/Main/Strings.cs:70 +#: Library/Main/Strings.cs:72 msgid "Temporary storage folder" msgstr "" -#: Library/Main/Strings.cs:71 +#: Library/Main/Strings.cs:73 msgid "" "Duplicati will use the system default temporary folder. This option can be " "used to supply an alternative folder for temporary storage. Note that SQLite " @@ -2236,49 +2323,49 @@ msgid "" "folder for both Duplicati and SQLite." msgstr "" -#: Library/Main/Strings.cs:72 +#: Library/Main/Strings.cs:74 msgid "" "Selects another thread priority for the process. Use this to set Duplicati " "to be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:73 +#: Library/Main/Strings.cs:75 msgid "Thread priority" msgstr "" -#: Library/Main/Strings.cs:74 +#: Library/Main/Strings.cs:76 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual file" msgstr "" -#: Library/Main/Strings.cs:75 +#: Library/Main/Strings.cs:77 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:76 +#: Library/Main/Strings.cs:78 msgid "" "Enabling this option will disallow usage of the streaming interface, which " "means that transfer progress bars will not show, and bandwidth throttle " "settings will be ignored." msgstr "" -#: Library/Main/Strings.cs:77 +#: Library/Main/Strings.cs:79 msgid "Disables use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:78 +#: Library/Main/Strings.cs:80 msgid "" "This option will make sure the contents of the manifest file are not read. " "This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:79 +#: Library/Main/Strings.cs:81 msgid "An option that prevents verifying the manifests" msgstr "" -#: Library/Main/Strings.cs:80 +#: Library/Main/Strings.cs:82 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -2286,11 +2373,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:81 +#: Library/Main/Strings.cs:83 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:82 +#: Library/Main/Strings.cs:84 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a " "module to use for encryption. This is only applied when creating new " @@ -2298,27 +2385,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:83 +#: Library/Main/Strings.cs:85 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:84 +#: Library/Main/Strings.cs:86 msgid "Supply one or more module names, separated by commas to unload them" msgstr "" -#: Library/Main/Strings.cs:85 +#: Library/Main/Strings.cs:87 msgid "Disabled one or more modules" msgstr "" -#: Library/Main/Strings.cs:86 +#: Library/Main/Strings.cs:88 msgid "Supply one or more module names, separated by commas to load them" msgstr "" -#: Library/Main/Strings.cs:87 +#: Library/Main/Strings.cs:89 msgid "Enables one or more modules" msgstr "" -#: Library/Main/Strings.cs:88 +#: Library/Main/Strings.cs:90 msgid "" "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\", " @@ -2332,67 +2419,67 @@ msgid "" "Logical Volume Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:89 +#: Library/Main/Strings.cs:91 msgid "Controls the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:90 +#: Library/Main/Strings.cs:92 msgid "" "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" msgstr "" -#: Library/Main/Strings.cs:91 +#: Library/Main/Strings.cs:93 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:92 +#: Library/Main/Strings.cs:94 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can " "be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the limit" msgstr "" -#: Library/Main/Strings.cs:93 +#: Library/Main/Strings.cs:95 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:94 +#: Library/Main/Strings.cs:96 msgid "" "Activating this option will make some error messages more verbose, which may " "help you track down a particular issue" msgstr "" -#: Library/Main/Strings.cs:95 +#: Library/Main/Strings.cs:97 msgid "Enables debugging output" msgstr "" -#: Library/Main/Strings.cs:96 +#: Library/Main/Strings.cs:98 msgid "Log internal information" msgstr "" -#: Library/Main/Strings.cs:97 +#: Library/Main/Strings.cs:99 msgid "" "Specifies the amount of log information to write into the file specified by " "--log-file" msgstr "" -#: Library/Main/Strings.cs:98 +#: Library/Main/Strings.cs:100 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:101 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:102 msgid "Disables automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -2401,12 +2488,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:104 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:105 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to \"off" @@ -2419,11 +2506,11 @@ msgid "" "privileges." msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:106 msgid "Controls the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:107 msgid "" "If USN is enabled the USN numbers are used to find all changed files since " "last backup. Use this option to disable the use of USN numbers, which will " @@ -2432,11 +2519,11 @@ msgid "" "If USN is not enabled, this option has no effect." msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:108 msgid "Disables changelist by USN numbers" msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:109 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -2449,26 +2536,26 @@ msgid "" "strict time checking" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:110 msgid "Deactivates tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:111 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:112 msgid "" "Duplicati will upload files while scanning the disk and producing volumes, " "which usually makes the backup faster. Use this flag to turn the behavior " "off, so that Duplicati will wait for each volume to complete." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:113 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:114 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -2476,47 +2563,60 @@ msgid "" "on a seperate connection" msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:115 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:116 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:117 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:118 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty" msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:119 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:120 msgid "" "This value can be used to set a known upper limit on the amount of space a " "backend has. If the backend reports the size itself, this value is ignored" msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:121 msgid "A reported maximum storage" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:122 +#, csharp-format +msgid "" +"Exclude files that match the given filter sets. Which default filter sets " +"should be used. Valid sets are \"{0}\", \"{1}\", \"{2}\", and \"{3}\". If " +"this parameter is set with no value, the set for the current operating " +"system will be used." +msgstr "" + +#: Library/Main/Strings.cs:123 +msgid "Default filter sets" +msgstr "" + +#: Library/Main/Strings.cs:124 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:125 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -2527,11 +2627,11 @@ msgid "" "included and restore as normal files." msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:126 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:127 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -2541,11 +2641,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:128 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:129 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -2553,7 +2653,7 @@ msgid "" "are: {0}" msgstr "" -#: Library/Main/Strings.cs:126 +#: Library/Main/Strings.cs:130 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -2561,21 +2661,21 @@ msgid "" "file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:131 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:128 +#: Library/Main/Strings.cs:132 msgid "" "A display name that is attached to this backup. Can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:133 msgid "Name of the backup" msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:134 #, csharp-format msgid "" "This property can be used to point to a text file where each line contains a " @@ -2587,22 +2687,22 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:135 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:132 Library/Main/Strings.cs:142 -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:136 Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:155 msgid "" "A fragment of memory is used to reduce database lookups. You should not " "change this value unless you get warnings in the log." msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:137 msgid "Memory used by the block hash" msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:138 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -2610,93 +2710,93 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:139 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:140 msgid "" "This option can be used to limit the scan to only files that are known to " "have changed. This is usually only activated in combination with a " "filesystem watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:141 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:142 msgid "Path to the file containing the local cache of the remote file database" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:143 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:144 #, csharp-format msgid "" "This option can be used to supply a list of deleted files. This option will " "be ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:145 msgid "List of deleted files" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:147 msgid "Memory used by the file hash" msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:148 msgid "" "This option can be used to reduce the memory footprint by not keeping paths " "and modification timestamps in memory" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:149 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:150 msgid "" "This option can be used to increase speed in exchange for extra memory use." msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:151 msgid "Store an in-memory block cache" msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:152 msgid "" "Stores metadata, such as file timestamps and attributes. This increases the " "required storage space as well as the processing time." msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:153 msgid "Enables storing file metadata" msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:154 msgid "This option is no longer used as metadata is now stored by default" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:156 msgid "Memory used by the metadata hash" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:157 msgid "" "If this flag is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly " "in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:158 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:159 msgid "" "The index files are used to limit the need for downloading dblock files when " "there is no local database present. The more information is recorded in the " @@ -2705,11 +2805,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:160 msgid "Determines usage of index files" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:161 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -2717,43 +2817,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:162 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:163 msgid "" "This option can be used to experiment with different settings and observe " "the outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:164 msgid "Does not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:165 msgid "" "This is a very advanced option! This option can be used to select a block " "hash algorithm with smaller or larger hash size, for performance or storage " "space reasons." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:166 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:167 msgid "" "This is a very advanced option! This option can be used to select a file " "hash algorithm with smaller or larger hash size, for performance or storage " "space reasons." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:168 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:169 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -2761,11 +2861,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:170 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:171 msgid "" "When examining the size of a volume in consideration for compacting, a small " "tolerance value is used, by default 20 percent of the volume size. This " @@ -2773,114 +2873,128 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:172 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:173 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:174 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:175 msgid "" "Enable this option to look into other files on this machine to find existing " "blocks. This is a fairly slow operation but can limit the size of downloads." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:176 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:177 msgid "Disables the local database" msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:178 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:179 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:180 msgid "" "Use this option to set number of versions to keep, supply -1 to keep all " "versions" msgstr "" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:181 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:182 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:179 +#: Library/Main/Strings.cs:183 +msgid "Reduce number of versions by deleting old intermediate backups" +msgstr "" + +#: Library/Main/Strings.cs:184 +msgid "" +"Use this option to reduce the number of versions that are kept with " +"increasing version age by deleting most of the old backups. The expected " +"format is a comma seperated list of collon sperated time frame and interval " +"pairs. For example the value \"7D:0s,3M:1D,10Y:2M\" means \"For 7 day keep " +"all backups, for 3 months keep one backup per day and for 10 years one " +"backup every 2nd month" +msgstr "" + +#: Library/Main/Strings.cs:185 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:180 +#: Library/Main/Strings.cs:186 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:181 +#: Library/Main/Strings.cs:187 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:188 msgid "" "Use this option to overwrite target files when restoring, if this option is " "not set the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:189 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:190 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:191 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:192 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:193 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:194 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " "of all the remote files and can be used to verify the integrity of the files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:195 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:196 #, csharp-format msgid "" "After a backup is completed, some files are selected for verification on the " @@ -2888,11 +3002,11 @@ msgid "" "0 or the option --{0} is set, no remote files are verified" msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:197 msgid "Activates in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:198 #, csharp-format msgid "" "After a backup is completed, some files are selected for verification on the " @@ -2903,109 +3017,101 @@ msgid "" "performed directly." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:199 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:200 msgid "" "Use this size to control how many bytes a read from a file before processing" msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:201 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:202 msgid "" "Use this option to allow the passphrase to change, note that this option is " "not permitted for a backup or repair operation" msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:203 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:204 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:206 msgid "Don't store metadata" msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:207 msgid "" "Use this option to disable the storage of metadata, such as file timestamps. " "Disabling metadata storage will speed up the backup and restore operations, " "but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:202 +#: Library/Main/Strings.cs:208 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:209 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:210 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:211 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check " "and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:212 msgid "Activate caches" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:213 msgid "Activate in-memory caches, which are now off by default" msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:214 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:215 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:216 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:217 msgid "" "Use this option to increase verification by checking the hash of blocks read " "from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:212 Server/Strings.cs:31 -msgid "Clean up old log data" -msgstr "" - -#: Library/Main/Strings.cs:213 Server/Strings.cs:32 -msgid "Set the time after which log data will be purged from the database." -msgstr "" - -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:220 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:221 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3013,11 +3119,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:222 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:223 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages " @@ -3025,40 +3131,40 @@ msgid "" "blank string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:224 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to disable multithreaded handling of up- and downloads, that " "can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:226 msgid "Perform backup of Hyper-V machines (Windows only)" msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:227 msgid "" "Use this option to specify the IDs of machines to include in the backup. " "Specify multiple machine IDs with a semicolon separator. (You can use this " "Powershell command to get ID 'Get-VM | ft VMName, ID')" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:228 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:229 msgid "Disables synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:230 msgid "" "This flag instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3066,15 +3172,15 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:231 msgid "Checks only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:232 msgid "Disables path compresion on restore" msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:233 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3082,22 +3188,22 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:234 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:235 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " "this flag to disable that protection, such that all filesets can be deleted." msgstr "" -#: Library/Main/Strings.cs:230 +#: Library/Main/Strings.cs:236 msgid "Allow automatic rebuilding of local database to save space." msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:237 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3107,27 +3213,57 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:242 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:243 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:244 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:245 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" +#: Library/Main/BackendManager.cs:595 +#, csharp-format +msgid "Failed to dispose backend instance: {0}" +msgstr "" + +#: Library/Main/BackendManager.cs:618 +#, csharp-format +msgid "Failed to delete file {0}, testing if file exists" +msgstr "" + +#: Library/Main/BackendManager.cs:624 +#, csharp-format +msgid "Recovered from problem with attempting to delete non-existing file {0}" +msgstr "" + +#: Library/Main/BackendManager.cs:629 +#, csharp-format +msgid "Failed to recover from error deleting file {0}" +msgstr "" + +#: Library/Main/BackendManager.cs:1134 +#, csharp-format +msgid "Delete operation failed for {0} with FileNotFound, listing contents" +msgstr "" + +#: Library/Main/BackendManager.cs:1147 +#, csharp-format +msgid "Listing indicates file {0} is deleted correctly" +msgstr "" + #: Library/Modules/Builtin/Strings.cs:7 msgid "Confirm encryption passphrase" msgstr "" @@ -3658,100 +3794,6 @@ msgstr "" msgid "Failed to send http message: {0}" msgstr "" -#: Library/Snapshots/Strings.cs:4 -#, csharp-format -msgid "" -"The external command failed to start.\n" -"Error message: {0}\n" -"Command: {1} {2}" -msgstr "" - -#: Library/Snapshots/Strings.cs:7 -#, csharp-format -msgid "" -"The external command failed to complete within the set time limit: {0} {1}" -msgstr "" - -#: Library/Snapshots/Strings.cs:8 -#, csharp-format -msgid "Unable to match local path {0} with any snapshot path: {1}" -msgstr "" - -#: Library/Snapshots/Strings.cs:9 -#, csharp-format -msgid "" -"Script returned successfully, but the temporary folder {0} does not exist: " -"{1}" -msgstr "" - -#: Library/Snapshots/Strings.cs:10 -#, csharp-format -msgid "" -"Script returned successfully, but the temporary folder {0} still exist: {1}" -msgstr "" - -#: Library/Snapshots/Strings.cs:11 -#, csharp-format -msgid "The script returned exit code {0}, but {1} was expected: {2}" -msgstr "" - -#: Library/Snapshots/Strings.cs:12 -#, csharp-format -msgid "" -"Script returned successfully, but the output was missing the {0} parameter: " -"{1}" -msgstr "" - -#: Library/Snapshots/Strings.cs:15 -msgid "Unexpected empty response while enumerating" -msgstr "" - -#: Library/Snapshots/Strings.cs:16 -msgid "USN is not supported on Linux" -msgstr "" - -#: Library/Snapshots/Strings.cs:17 -msgid "" -"The number of files returned by USN was zero. This is likely an error. To " -"remedy this, USN has been disabled." -msgstr "" - -#: Library/Snapshots/Strings.cs:20 -msgid "Calling process does not have the backup privilege" -msgstr "" - -#: Library/SQLiteHelper/Strings.cs:4 -msgid "backup" -msgstr "" - -#: Library/SQLiteHelper/Strings.cs:5 -#, csharp-format -msgid "Unable to determine database format: {0}" -msgstr "" - -#: Library/SQLiteHelper/Strings.cs:6 -#, csharp-format -msgid "" -"\n" -"The database has version {0} but the largest supported version is {1}.\n" -"\n" -"This is likely caused by upgrading to a newer version and then downgrading.\n" -"If this is the case, there is likely a backup file of the previous database " -"version in the folder {2}." -msgstr "" - -#: Library/SQLiteHelper/Strings.cs:11 -msgid "Unknown table layout detected" -msgstr "" - -#: Library/SQLiteHelper/Strings.cs:12 -#, csharp-format -msgid "" -"Failed to execute SQL: {0}\n" -"Error: {1}\n" -"Database is NOT upgraded." -msgstr "" - #: Library/Utility/Strings.cs:7 #, csharp-format msgid "Invalid size value: {0}" @@ -3850,158 +3892,169 @@ msgstr "" msgid "Cannot read and write on the same stream" msgstr "" -#: Server/Strings.cs:7 -msgid "Another instance is running, and was notified" +#: Library/Utility/Strings.cs:39 +#, csharp-format +msgid "Unknown default filter set: {0}" msgstr "" -#: Server/Strings.cs:8 +#: CommandLine/Strings.cs:4 +#, csharp-format +msgid "The command {0} needs at least one of the following options set: {1}" +msgstr "" + +#: CommandLine/Strings.cs:5 #, csharp-format msgid "" -"Failed to create, open or upgrade the database.\n" -"Error message: {0}" +"Found {0} commands but expected {1}, commands: \n" +"{2}" msgstr "" -#: Server/Strings.cs:10 -msgid "Displays this help" -msgstr "" - -#: Server/Strings.cs:11 -msgid "" -"Supported commandline arguments:\n" -"\n" -msgstr "" - -#: Server/Strings.cs:14 +#: CommandLine/Strings.cs:7 #, csharp-format -msgid "--{0}: {1}" +msgid "Command not supported: {0}" msgstr "" -#: Server/Strings.cs:15 -msgid "Outputs log information to the file given" +#: CommandLine/Strings.cs:8 +msgid "No filesets matched the criteria" msgstr "" -#: Server/Strings.cs:16 -msgid "Determines the amount of information written in the log file" +#: CommandLine/Strings.cs:9 +msgid "The following filesets would be deleted:" msgstr "" -#: Server/Strings.cs:17 -msgid "" -"Activates portable mode where the database is placed below the program " -"executable" +#: CommandLine/Strings.cs:10 +msgid "These filesets were deleted:" msgstr "" -#: Server/Strings.cs:18 +#: CommandLine/Strings.cs:11 +msgid "Supported backends:" +msgstr "" + +#: CommandLine/Strings.cs:12 +msgid "Supported compression modules:" +msgstr "" + +#: CommandLine/Strings.cs:13 +msgid "Supported encryption modules:" +msgstr "" + +#: CommandLine/Strings.cs:14 +msgid "Supported options:" +msgstr "" + +#: CommandLine/Strings.cs:15 +msgid "Module is loaded automatically, use --disable-module to prevent this" +msgstr "" + +#: CommandLine/Strings.cs:16 +msgid "Module is not loaded automatically, use --enable-module to load it" +msgstr "" + +#: CommandLine/Strings.cs:17 +msgid "Supported generic modules:" +msgstr "" + +#: CommandLine/Strings.cs:18 #, csharp-format -msgid "A serious error occurred in Duplicati: {0}" +msgid "Unable to read the parameters file \"{0}\", reason: {1}" msgstr "" -#: Server/Strings.cs:19 +#: CommandLine/Strings.cs:19 #, csharp-format msgid "" -"Unable to start up, perhaps another process is already running?\n" -"Error message: {0}" +"Filters cannot be specified on the commandline if filters are also present " +"in the parameter file. Use the special --{0}, --{1}, or --{2} options to " +"specify filters inside the parameter file. Each filter must be prefixed with " +"either a + or a -, and multiple filters must be joined with {3}" msgstr "" -#: Server/Strings.cs:21 -msgid "Disables database encryption" -msgstr "" - -#: Server/Strings.cs:22 -#, csharp-format -msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" -msgstr "" - -#: Server/Strings.cs:23 -msgid "" -"The path to the folder where the static files for the webserver is present. " -"The folder must be located beneath the installation folder" -msgstr "" - -#: Server/Strings.cs:24 -msgid "" -"The port the webserver listens on. Multiple values may be supplied with a " -"comma in between." -msgstr "" - -#: Server/Strings.cs:25 -msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." -msgstr "" - -#: Server/Strings.cs:26 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "" - -#: Server/Strings.cs:27 -msgid "" -"The interface the webserver listens on. The special values \"*\" and \"any\" " -"means any interface. The special value \"loopback\" means the loopback " -"adapter." -msgstr "" - -#: Server/Strings.cs:28 -msgid "" -"The password required to access the webserver. This option is saved so you " -"do not need to set it on each run. Setting an empty value disables the " -"password." -msgstr "" - -#: Server/Strings.cs:29 -msgid "Enables the ping-pong responder" -msgstr "" - -#: Server/Strings.cs:30 -msgid "" -"When running as a server, the service daemon must verify that the process is " -"responding. If this option is enabled, the server reads stdin and writes a " -"reply to each line read" -msgstr "" - -#: Server/Strings.cs:33 -msgid "Sets the folder where settings are stored" -msgstr "" - -#: Server/Strings.cs:34 +#: CommandLine/Strings.cs:20 #, csharp-format msgid "" -"Duplicati needs to store a small database with all settings. Use this option " -"to choose where the settings are stored. This option can also be set with " -"the environment variable {0}." +"The option --{0} was supplied, but it is reserved for internal use and may " +"not be set on the commandline" msgstr "" -#: Server/Strings.cs:35 -msgid "Sets the database encryption key" -msgstr "" - -#: Server/Strings.cs:36 +#: CommandLine/Strings.cs:21 #, csharp-format msgid "" -"This option sets the encryption key used to scramble the local settings " -"database. This option can also be set with the environment variable {0}. Use " -"the option --{1} to disable the database scrambling." +"This option can be used to store some or all of the options given to the " +"commandline client. The file must be a plain text file, UTF-8 encoding is " +"preferred. Each line in the file should be of the format --option=value. The " +"special options --{0} and --{1} can be used to override the localpath and " +"the remote destination uri, respectively. The options in this file take " +"precedence over the options provided on the commandline. You cannot specify " +"filters in both the file and on the commandline. Instead, you can use the " +"special --{2}, --{3}, or --{4} options to specify filters inside the " +"parameter file. Each filter must be prefixed with either a + or a -, and " +"multiple filters must be joined with {5} " msgstr "" -#: Server/Strings.cs:39 +#: CommandLine/Strings.cs:22 +msgid "Path to a file with parameters" +msgstr "" + +#: CommandLine/Strings.cs:23 #, csharp-format +msgid "An error occured: {0}" +msgstr "" + +#: CommandLine/Strings.cs:24 +#, csharp-format +msgid "The inner error message is: {0}" +msgstr "" + +#: CommandLine/Strings.cs:25 msgid "" -"Unable to find a valid date, given the start date {0}, the repetition " -"interval {1} and the allowed days {2}" +"Include files that match this filter. The special character * means any " +"number of character, and the special character ? means any single character, " +"use *.txt to include all files with a txt extension. Regular expressions are " +"also supported and can be supplied by using hard braces, i.e. [.*\\.txt]." msgstr "" -#: Server/Strings.cs:44 -#, csharp-format -msgid "Server has started and is listening on {0}, port {1}" +#: CommandLine/Strings.cs:26 +msgid "Include files" msgstr "" -#: Server/Strings.cs:45 -#, csharp-format +#: CommandLine/Strings.cs:27 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"Exclude files that match this filter. The special character * means any " +"number of character, and the special character ? means any single character, " +"use *.txt to exclude all files with a txt extension. Regular expressions are " +"also supported and can be supplied by using hard braces, i.e. [.*\\.txt]." msgstr "" -#: Server/Strings.cs:46 -#, csharp-format -msgid "Unable to open a socket for listening, tried ports: {0}" +#: CommandLine/Strings.cs:28 +msgid "Exclude files" +msgstr "" + +#: CommandLine/Strings.cs:29 +msgid "" +"If this option is used with a backup operation, it is interpreted as a list " +"of files to add to the filesets. When used with list or restore, it will " +"list or restore the control files instead of the normal files." +msgstr "" + +#: CommandLine/Strings.cs:30 +msgid "Use control files" +msgstr "" + +#: CommandLine/Strings.cs:31 +msgid "" +"If this option is set, progress reports and other messages that would " +"normally go to the console will be redirected to the log." +msgstr "" + +#: CommandLine/Strings.cs:32 +msgid "Disable console output" +msgstr "" + +#: CommandLine/Program.cs:302 +msgid "Toggle automatic updates" +msgstr "" + +#: CommandLine/Program.cs:302 +msgid "" +"Set this option if you prefer to have the commandline version automatically " +"update" msgstr "" diff --git a/Localizations/webroot/localization_webroot.pot b/Localizations/webroot/localization_webroot.pot index c881620fe..89519edb6 100644 --- a/Localizations/webroot/localization_webroot.pot +++ b/Localizations/webroot/localization_webroot.pot @@ -108,7 +108,7 @@ msgstr "" msgid "Advanced Options" msgstr "" -#: templates/addoredit.html:334 +#: templates/addoredit.html:348 #: templates/edituri.html:28 msgid "Advanced options" msgstr "" @@ -117,6 +117,10 @@ msgstr "" msgid "Advanced:" msgstr "" +#: scripts/controllers/EditBackupController.js:22 +msgid "All" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:423 msgid "All Hyper-V Machines" msgstr "" @@ -125,7 +129,7 @@ msgstr "" msgid "All Microsoft SQL Databases" msgstr "" -#: templates/settings.html:115 +#: templates/settings.html:116 msgid "All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information." msgstr "" @@ -133,7 +137,7 @@ msgstr "" msgid "Allow remote access (requires restart)" msgstr "" -#: templates/addoredit.html:268 +#: templates/addoredit.html:282 msgid "Allowed days" msgstr "" @@ -147,7 +151,7 @@ msgid "" "Are you sure you want the database to point to an existing file?" msgstr "" -#: scripts/controllers/EditBackupController.js:399 +#: scripts/controllers/EditBackupController.js:472 msgid "" "An existing local database for the storage has been found.\n" "Re-using the database will allow the command-line and server instances to work on the same remote storage.\n" @@ -155,7 +159,7 @@ msgid "" " Do you wish to use the existing database?" msgstr "" -#: templates/settings.html:103 +#: templates/settings.html:104 msgid "Anonymous usage reports" msgstr "" @@ -181,11 +185,11 @@ msgstr "" msgid "Authentication username" msgstr "" -#: scripts/controllers/EditBackupController.js:308 +#: scripts/controllers/EditBackupController.js:381 msgid "Autogenerated passphrase" msgstr "" -#: templates/addoredit.html:248 +#: templates/addoredit.html:262 msgid "Automatically run backups." msgstr "" @@ -230,7 +234,7 @@ msgstr "" msgid "Backup:" msgstr "" -#: templates/settings.html:80 +#: templates/settings.html:81 msgid "Beta" msgstr "" @@ -290,19 +294,19 @@ msgstr "" msgid "Busy ..." msgstr "" -#: templates/settings.html:90 +#: templates/settings.html:91 msgid "Canary" msgstr "" #: scripts/controllers/AppController.js:44 #: scripts/controllers/AppController.js:59 -#: scripts/controllers/EditBackupController.js:293 -#: scripts/controllers/EditBackupController.js:308 -#: scripts/controllers/EditBackupController.js:342 -#: scripts/controllers/EditBackupController.js:351 -#: scripts/controllers/EditBackupController.js:378 -#: scripts/controllers/EditBackupController.js:399 -#: scripts/controllers/EditBackupController.js:73 +#: scripts/controllers/EditBackupController.js:366 +#: scripts/controllers/EditBackupController.js:381 +#: scripts/controllers/EditBackupController.js:415 +#: scripts/controllers/EditBackupController.js:424 +#: scripts/controllers/EditBackupController.js:451 +#: scripts/controllers/EditBackupController.js:472 +#: scripts/controllers/EditBackupController.js:82 #: scripts/controllers/LocalDatabaseController.js:72 #: scripts/controllers/LocalDatabaseController.js:88 #: scripts/controllers/RestoreDirectController.js:24 @@ -316,7 +320,7 @@ msgstr "" #: scripts/services/EditUriBuiltins.js:719 #: templates/delete.html:54 #: templates/export.html:26 -#: templates/settings.html:145 +#: templates/settings.html:146 #: templates/waitarea.html:19 msgid "Cancel" msgstr "" @@ -456,7 +460,7 @@ msgstr "" msgid "Continue" msgstr "" -#: scripts/controllers/EditBackupController.js:378 +#: scripts/controllers/EditBackupController.js:451 msgid "Continue without encryption" msgstr "" @@ -481,7 +485,7 @@ msgstr "" msgid "Counting ({{files}} files found, {{size}})" msgstr "" -#: templates/settings.html:110 +#: templates/settings.html:111 msgid "Crashes only" msgstr "" @@ -556,7 +560,7 @@ msgid "Database ..." msgstr "" #: scripts/services/AppUtils.js:90 -#: templates/addoredit.html:314 +#: templates/addoredit.html:328 msgid "Days" msgstr "" @@ -564,11 +568,15 @@ msgstr "" msgid "Default" msgstr "" -#: templates/settings.html:67 +#: templates/settings.html:68 msgid "Default ({{channelname}})" msgstr "" -#: templates/settings.html:130 +#: templates/addoredit.html:201 +msgid "Default Filters" +msgstr "" + +#: templates/settings.html:131 msgid "Default options" msgstr "" @@ -635,7 +643,7 @@ msgstr "" msgid "Dismiss" msgstr "" -#: templates/settings.html:49 +#: templates/settings.html:50 msgid "Display and color theme" msgstr "" @@ -662,16 +670,16 @@ msgstr "" msgid "Donate with PayPal" msgstr "" -#: templates/settings.html:57 -#: templates/settings.html:59 +#: templates/settings.html:58 +#: templates/settings.html:60 msgid "Donation messages" msgstr "" -#: templates/settings.html:61 +#: templates/settings.html:62 msgid "Donation messages are hidden, click to show" msgstr "" -#: templates/settings.html:60 +#: templates/settings.html:61 msgid "Donation messages are visible, click to hide" msgstr "" @@ -723,16 +731,16 @@ msgid "Edit ..." msgstr "" #: templates/addoredit.html:170 -#: templates/addoredit.html:345 +#: templates/addoredit.html:359 #: templates/edituri.html:39 -#: templates/settings.html:134 +#: templates/settings.html:135 msgid "Edit as list" msgstr "" #: templates/addoredit.html:173 -#: templates/addoredit.html:348 +#: templates/addoredit.html:362 #: templates/edituri.html:42 -#: templates/settings.html:140 +#: templates/settings.html:141 msgid "Edit as text" msgstr "" @@ -747,7 +755,7 @@ msgstr "" msgid "Encryption" msgstr "" -#: scripts/controllers/EditBackupController.js:351 +#: scripts/controllers/EditBackupController.js:424 msgid "Encryption changed" msgstr "" @@ -755,7 +763,7 @@ msgstr "" msgid "Encryption modules:" msgstr "" -#: scripts/controllers/EditBackupController.js:73 +#: scripts/controllers/EditBackupController.js:82 #: scripts/controllers/RestoreDirectController.js:24 msgid "Enter URL" msgstr "" @@ -834,11 +842,11 @@ msgstr "" msgid "Error!" msgstr "" -#: templates/settings.html:109 +#: templates/settings.html:110 msgid "Errors and crashes" msgstr "" -#: templates/addoredit.html:204 +#: templates/addoredit.html:218 msgid "Exclude" msgstr "" @@ -874,7 +882,7 @@ msgstr "" msgid "Existing file found" msgstr "" -#: templates/settings.html:85 +#: templates/settings.html:86 msgid "Experimental" msgstr "" @@ -939,7 +947,7 @@ msgstr "" msgid "Failed to import:" msgstr "" -#: scripts/controllers/EditBackupController.js:632 +#: scripts/controllers/EditBackupController.js:737 msgid "Failed to read backup defaults:" msgstr "" @@ -960,7 +968,7 @@ msgstr "" msgid "File" msgstr "" -#: templates/addoredit.html:227 +#: templates/addoredit.html:241 msgid "Files larger than:" msgstr "" @@ -1021,7 +1029,7 @@ msgstr "" msgid "General backup settings" msgstr "" -#: templates/addoredit.html:295 +#: templates/addoredit.html:309 msgid "General options" msgstr "" @@ -1037,7 +1045,7 @@ msgstr "" msgid "Getting file versions ..." msgstr "" -#: scripts/controllers/EditBackupController.js:17 +#: scripts/controllers/EditBackupController.js:26 msgid "Hidden files" msgstr "" @@ -1057,7 +1065,7 @@ msgstr "" #: scripts/services/AppUtils.js:89 #: scripts/services/AppUtils.js:99 -#: templates/settings.html:33 +#: templates/settings.html:34 msgid "Hours" msgstr "" @@ -1083,7 +1091,7 @@ msgstr "" msgid "ID:" msgstr "" -#: templates/addoredit.html:251 +#: templates/addoredit.html:265 msgid "If a date was missed, the job will run as soon as possible." msgstr "" @@ -1148,7 +1156,7 @@ msgstr "" msgid "Importing ..." msgstr "" -#: scripts/controllers/EditBackupController.js:141 +#: scripts/controllers/EditBackupController.js:150 msgid "Include a file?" msgstr "" @@ -1164,7 +1172,7 @@ msgstr "" msgid "Incorrect answer, try again" msgstr "" -#: templates/settings.html:91 +#: templates/settings.html:92 msgid "Individual builds for developers only." msgstr "" @@ -1185,8 +1193,8 @@ msgstr "" msgid "Invalid characters in path" msgstr "" -#: scripts/controllers/EditBackupController.js:250 -#: scripts/controllers/EditBackupController.js:257 +#: scripts/controllers/EditBackupController.js:323 +#: scripts/controllers/EditBackupController.js:330 msgid "Invalid retention time" msgstr "" @@ -1204,11 +1212,11 @@ msgstr "" msgid "KByte/s" msgstr "" -#: templates/addoredit.html:303 +#: templates/addoredit.html:317 msgid "Keep this number of backups" msgstr "" -#: templates/settings.html:39 +#: templates/settings.html:40 msgid "Language in user interface" msgstr "" @@ -1228,6 +1236,10 @@ msgstr "" msgid "Libraries" msgstr "" +#: scripts/controllers/EditBackupController.js:21 +msgid "Linux" +msgstr "" + #: scripts/controllers/RestoreDirectController.js:64 msgid "Listing backup dates ..." msgstr "" @@ -1333,7 +1345,7 @@ msgstr "" #: index.html:152 #: templates/addoredit.html:123 #: templates/addoredit.html:165 -#: templates/addoredit.html:340 +#: templates/addoredit.html:354 #: templates/addoredit.html:91 #: templates/edituri.html:34 #: templates/restoredirect.html:34 @@ -1358,19 +1370,19 @@ msgstr "" #: scripts/services/AppUtils.js:88 #: scripts/services/AppUtils.js:98 -#: templates/settings.html:32 +#: templates/settings.html:33 msgid "Minutes" msgstr "" -#: scripts/controllers/EditBackupController.js:217 +#: scripts/controllers/EditBackupController.js:290 msgid "Missing name" msgstr "" -#: scripts/controllers/EditBackupController.js:225 +#: scripts/controllers/EditBackupController.js:298 msgid "Missing passphrase" msgstr "" -#: scripts/controllers/EditBackupController.js:238 +#: scripts/controllers/EditBackupController.js:311 msgid "Missing sources" msgstr "" @@ -1379,7 +1391,7 @@ msgid "Mon" msgstr "" #: scripts/services/AppUtils.js:92 -#: templates/addoredit.html:316 +#: templates/addoredit.html:330 msgid "Months" msgstr "" @@ -1426,8 +1438,8 @@ msgid "" msgstr "" #: templates/addoredit.html:109 -#: templates/addoredit.html:236 -#: templates/addoredit.html:286 +#: templates/addoredit.html:250 +#: templates/addoredit.html:300 #: templates/addoredit.html:78 #: templates/addwizard.html:28 #: templates/restoredirect.html:52 @@ -1447,14 +1459,14 @@ msgstr "" msgid "Next task:" msgstr "" -#: templates/addoredit.html:254 +#: templates/addoredit.html:268 msgid "Next time" msgstr "" #: scripts/controllers/DeleteController.js:77 -#: scripts/controllers/EditBackupController.js:131 -#: scripts/controllers/EditBackupController.js:141 -#: scripts/controllers/EditBackupController.js:399 +#: scripts/controllers/EditBackupController.js:140 +#: scripts/controllers/EditBackupController.js:150 +#: scripts/controllers/EditBackupController.js:472 #: scripts/controllers/HomeController.js:7 #: scripts/controllers/LocalDatabaseController.js:28 #: scripts/controllers/LocalDatabaseController.js:72 @@ -1481,7 +1493,7 @@ msgstr "" msgid "No editor found for the "{{backend}}" storage type" msgstr "" -#: scripts/controllers/EditBackupController.js:378 +#: scripts/controllers/EditBackupController.js:451 #: templates/addoredit.html:45 msgid "No encryption" msgstr "" @@ -1506,18 +1518,18 @@ msgstr "" msgid "No, my machine has only a single account" msgstr "" -#: scripts/controllers/EditBackupController.js:231 +#: scripts/controllers/EditBackupController.js:304 msgid "Non-matching passphrase" msgstr "" -#: templates/settings.html:111 +#: templates/settings.html:112 msgid "None / disabled" msgstr "" #: scripts/controllers/AppController.js:44 #: scripts/controllers/AppController.js:59 -#: scripts/controllers/EditBackupController.js:73 -#: scripts/controllers/EditBackupController.js:81 +#: scripts/controllers/EditBackupController.js:82 +#: scripts/controllers/EditBackupController.js:90 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 @@ -1526,10 +1538,14 @@ msgstr "" #: scripts/services/DialogService.js:58 #: scripts/services/EditUriBuiltins.js:121 #: templates/restore.html:147 -#: templates/settings.html:146 +#: templates/settings.html:147 msgid "OK" msgstr "" +#: scripts/controllers/EditBackupController.js:20 +msgid "OSX" +msgstr "" + #: templates/backends/openstack.html:7 msgid "OpenStack AuthURI" msgstr "" @@ -1557,12 +1573,12 @@ msgstr "" #: templates/addoredit.html:28 #: templates/edituri.html:51 -#: templates/settings.html:133 -#: templates/settings.html:139 +#: templates/settings.html:134 +#: templates/settings.html:140 msgid "Options" msgstr "" -#: templates/settings.html:131 +#: templates/settings.html:132 msgid "Options added here are applied to all backups, but can be overridden in each individual backup" msgstr "" @@ -1588,11 +1604,11 @@ msgstr "" msgid "Passphrase (if encrypted)" msgstr "" -#: scripts/controllers/EditBackupController.js:342 +#: scripts/controllers/EditBackupController.js:415 msgid "Passphrase changed" msgstr "" -#: scripts/controllers/EditBackupController.js:231 +#: scripts/controllers/EditBackupController.js:304 msgid "Passphrases are not matching" msgstr "" @@ -1609,7 +1625,7 @@ msgstr "" msgid "Password" msgstr "" -#: scripts/controllers/EditBackupController.js:27 +#: scripts/controllers/EditBackupController.js:36 msgid "Passwords do not match" msgstr "" @@ -1617,7 +1633,7 @@ msgstr "" msgid "Patching files with local blocks ..." msgstr "" -#: scripts/controllers/EditBackupController.js:131 +#: scripts/controllers/EditBackupController.js:140 msgid "Path not found" msgstr "" @@ -1660,9 +1676,9 @@ msgid "Port" msgstr "" #: templates/addoredit.html:110 -#: templates/addoredit.html:237 -#: templates/addoredit.html:287 -#: templates/addoredit.html:367 +#: templates/addoredit.html:251 +#: templates/addoredit.html:301 +#: templates/addoredit.html:381 #: templates/restoredirect.html:81 msgid "Previous" msgstr "" @@ -1695,7 +1711,7 @@ msgstr "" msgid "Registering temporary backup ..." msgstr "" -#: scripts/controllers/EditBackupController.js:116 +#: scripts/controllers/EditBackupController.js:125 msgid "Relative paths not allowed" msgstr "" @@ -1784,7 +1800,7 @@ msgstr "" msgid "Resume" msgstr "" -#: templates/addoredit.html:259 +#: templates/addoredit.html:273 msgid "Run again every" msgstr "" @@ -1813,7 +1829,7 @@ msgstr "" msgid "S3 Compatible" msgstr "" -#: templates/settings.html:68 +#: templates/settings.html:69 msgid "Same as the base install version: {{channelname}}" msgstr "" @@ -1821,7 +1837,7 @@ msgstr "" msgid "Sat" msgstr "" -#: templates/addoredit.html:366 +#: templates/addoredit.html:380 #: templates/localdatabase.html:32 msgid "Save" msgstr "" @@ -1846,7 +1862,7 @@ msgstr "" msgid "Scanning for local blocks ..." msgstr "" -#: templates/addoredit.html:245 +#: templates/addoredit.html:259 #: templates/addoredit.html:27 msgid "Schedule" msgstr "" @@ -1860,7 +1876,7 @@ msgid "Search for files" msgstr "" #: scripts/services/AppUtils.js:97 -#: templates/settings.html:31 +#: templates/settings.html:32 msgid "Seconds" msgstr "" @@ -1968,7 +1984,7 @@ msgstr "" msgid "Source:" msgstr "" -#: templates/settings.html:86 +#: templates/settings.html:87 msgid "Specific builds for developers only." msgstr "" @@ -2031,7 +2047,7 @@ msgstr "" msgid "Stored" msgstr "" -#: scripts/controllers/EditBackupController.js:31 +#: scripts/controllers/EditBackupController.js:40 msgid "Strong" msgstr "" @@ -2048,11 +2064,11 @@ msgstr "" msgid "Symbolic link" msgstr "" -#: templates/settings.html:106 +#: templates/settings.html:107 msgid "System default ({{levelname}})" msgstr "" -#: scripts/controllers/EditBackupController.js:18 +#: scripts/controllers/EditBackupController.js:27 msgid "System files" msgstr "" @@ -2080,7 +2096,7 @@ msgstr "" msgid "Task is running" msgstr "" -#: scripts/controllers/EditBackupController.js:19 +#: scripts/controllers/EditBackupController.js:28 msgid "Temporary files" msgstr "" @@ -2121,11 +2137,11 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} ..." msgstr "" -#: templates/settings.html:53 +#: templates/settings.html:54 msgid "The dark theme (by Michal)" msgstr "" -#: templates/settings.html:52 +#: templates/settings.html:53 msgid "The default blue on white theme (by Alex)" msgstr "" @@ -2142,18 +2158,18 @@ msgid "" "Do you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?" msgstr "" -#: scripts/controllers/EditBackupController.js:131 +#: scripts/controllers/EditBackupController.js:140 msgid "The path does not appear to exist, do you want to add it anyway?" msgstr "" -#: scripts/controllers/EditBackupController.js:141 +#: scripts/controllers/EditBackupController.js:150 msgid "" "The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n" "\n" "Do you want to include the specified file?" msgstr "" -#: scripts/controllers/EditBackupController.js:116 +#: scripts/controllers/EditBackupController.js:125 msgid "The path must be an absolute path, i.e. it must start with a forward slash '/'" msgstr "" @@ -2234,7 +2250,7 @@ msgstr "" msgid "Trust server certificate?" msgstr "" -#: templates/settings.html:81 +#: templates/settings.html:82 msgid "Try out the new features we are working on. Don't use with important data." msgstr "" @@ -2254,7 +2270,7 @@ msgstr "" msgid "Until resumed" msgstr "" -#: templates/settings.html:64 +#: templates/settings.html:65 msgid "Update channel" msgstr "" @@ -2266,7 +2282,7 @@ msgstr "" msgid "Updating with existing database" msgstr "" -#: templates/addoredit.html:297 +#: templates/addoredit.html:311 msgid "Upload volume size" msgstr "" @@ -2274,15 +2290,15 @@ msgstr "" msgid "Uploading verification file ..." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:114 msgid "Usage reports help us improve the user experience and evaluate impact of new features. We use them to generate public usage statistics" msgstr "" -#: templates/settings.html:101 +#: templates/settings.html:102 msgid "Usage statistics" msgstr "" -#: templates/settings.html:107 +#: templates/settings.html:108 msgid "Usage statistics, warnings, errors, and crashes" msgstr "" @@ -2291,15 +2307,15 @@ msgstr "" msgid "Use SSL" msgstr "" -#: scripts/controllers/EditBackupController.js:399 +#: scripts/controllers/EditBackupController.js:472 msgid "Use existing database?" msgstr "" -#: scripts/controllers/EditBackupController.js:293 +#: scripts/controllers/EditBackupController.js:366 msgid "Use weak passphrase" msgstr "" -#: scripts/controllers/EditBackupController.js:28 +#: scripts/controllers/EditBackupController.js:37 msgid "Useless" msgstr "" @@ -2311,7 +2327,7 @@ msgstr "" msgid "User has too many permissions" msgstr "" -#: templates/settings.html:37 +#: templates/settings.html:38 msgid "User interface settings" msgstr "" @@ -2365,11 +2381,11 @@ msgstr "" msgid "Verifying restored files ..." msgstr "" -#: scripts/controllers/EditBackupController.js:32 +#: scripts/controllers/EditBackupController.js:41 msgid "Very strong" msgstr "" -#: scripts/controllers/EditBackupController.js:29 +#: scripts/controllers/EditBackupController.js:38 msgid "Very weak" msgstr "" @@ -2397,7 +2413,7 @@ msgstr "" msgid "Waiting for upload ..." msgstr "" -#: templates/settings.html:108 +#: templates/settings.html:109 msgid "Warnings, errors and crashes" msgstr "" @@ -2405,11 +2421,11 @@ msgstr "" msgid "We recommend that you encrypt all backups stored outside your system" msgstr "" -#: scripts/controllers/EditBackupController.js:30 +#: scripts/controllers/EditBackupController.js:39 msgid "Weak" msgstr "" -#: scripts/controllers/EditBackupController.js:293 +#: scripts/controllers/EditBackupController.js:366 msgid "Weak passphrase" msgstr "" @@ -2418,7 +2434,7 @@ msgid "Wed" msgstr "" #: scripts/services/AppUtils.js:91 -#: templates/addoredit.html:315 +#: templates/addoredit.html:329 msgid "Weeks" msgstr "" @@ -2430,16 +2446,20 @@ msgstr "" msgid "Where do you want to restore the files to?" msgstr "" +#: scripts/controllers/EditBackupController.js:19 +msgid "Windows" +msgstr "" + #: scripts/services/AppUtils.js:93 -#: templates/addoredit.html:317 +#: templates/addoredit.html:331 msgid "Years" msgstr "" #: scripts/controllers/AppController.js:172 #: scripts/controllers/DeleteController.js:77 -#: scripts/controllers/EditBackupController.js:131 -#: scripts/controllers/EditBackupController.js:141 -#: scripts/controllers/EditBackupController.js:399 +#: scripts/controllers/EditBackupController.js:140 +#: scripts/controllers/EditBackupController.js:150 +#: scripts/controllers/EditBackupController.js:472 #: scripts/controllers/HomeController.js:7 #: scripts/controllers/LocalDatabaseController.js:28 #: scripts/controllers/LocalDatabaseController.js:72 @@ -2455,15 +2475,15 @@ msgstr "" msgid "Yes" msgstr "" -#: scripts/controllers/EditBackupController.js:308 +#: scripts/controllers/EditBackupController.js:381 msgid "Yes, I have stored the passphrase safely" msgstr "" -#: scripts/controllers/EditBackupController.js:351 +#: scripts/controllers/EditBackupController.js:424 msgid "Yes, I'm brave!" msgstr "" -#: scripts/controllers/EditBackupController.js:342 +#: scripts/controllers/EditBackupController.js:415 msgid "Yes, please break my backup!" msgstr "" @@ -2495,15 +2515,15 @@ msgstr "" msgid "You can stop the task immediately, or allow the process to continue its current file and the stop." msgstr "" -#: scripts/controllers/EditBackupController.js:351 +#: scripts/controllers/EditBackupController.js:424 msgid "You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead" msgstr "" -#: scripts/controllers/EditBackupController.js:342 +#: scripts/controllers/EditBackupController.js:415 msgid "You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead." msgstr "" -#: scripts/controllers/EditBackupController.js:378 +#: scripts/controllers/EditBackupController.js:451 msgid "You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server." msgstr "" @@ -2511,23 +2531,23 @@ msgstr "" msgid "You have chosen to restore to a new location, but not entered one" msgstr "" -#: scripts/controllers/EditBackupController.js:308 -msgid "You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you loose the passphrase." +#: scripts/controllers/EditBackupController.js:381 +msgid "You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase." msgstr "" -#: scripts/controllers/EditBackupController.js:238 +#: scripts/controllers/EditBackupController.js:311 msgid "You must choose at least one source folder" msgstr "" -#: scripts/controllers/EditBackupController.js:217 +#: scripts/controllers/EditBackupController.js:290 msgid "You must enter a name for the backup" msgstr "" -#: scripts/controllers/EditBackupController.js:225 +#: scripts/controllers/EditBackupController.js:298 msgid "You must enter a passphrase or disable encryption" msgstr "" -#: scripts/controllers/EditBackupController.js:257 +#: scripts/controllers/EditBackupController.js:330 msgid "You must enter a positive number of backups to keep" msgstr "" @@ -2535,7 +2555,7 @@ msgstr "" msgid "You must enter a tenant name if you do not provide an API Key" msgstr "" -#: scripts/controllers/EditBackupController.js:250 +#: scripts/controllers/EditBackupController.js:323 msgid "You must enter a valid duration for the time to keep backups" msgstr "" @@ -2556,6 +2576,7 @@ msgid "You must fill in the server name or address" msgstr "" #: scripts/services/EditUriBackendConfig.js:106 +#: scripts/services/EditUriBackendConfig.js:115 msgid "You must fill in the username" msgstr "" @@ -2579,11 +2600,11 @@ msgstr "" msgid "Your files and folders have been restored successfully." msgstr "" -#: scripts/controllers/EditBackupController.js:293 +#: scripts/controllers/EditBackupController.js:366 msgid "Your passphrase is easy to guess. Consider changing passphrase." msgstr "" -#: templates/addoredit.html:307 +#: templates/addoredit.html:321 msgid "a specific number" msgstr "" @@ -2600,8 +2621,8 @@ msgstr "" msgid "byte/s" msgstr "" -#: templates/addoredit.html:262 -#: templates/addoredit.html:318 +#: templates/addoredit.html:276 +#: templates/addoredit.html:332 #: templates/advancedoptionseditor.html:28 #: templates/advancedoptionseditor.html:35 msgid "custom" @@ -2613,11 +2634,11 @@ msgstr "" msgid "resume now" msgstr "" -#: templates/addoredit.html:305 +#: templates/addoredit.html:319 msgid "unlimited" msgstr "" -#: templates/addoredit.html:306 +#: templates/addoredit.html:320 msgid "until they are older than" msgstr "" From c8865b6c3086d471830becd3312dde29db3bd8bb Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Fri, 17 Nov 2017 13:08:20 +0100 Subject: [PATCH 08/40] Added an example init script for use on systems without systemd. --- Installer/debian/init-script-example.sh | 80 +++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Installer/debian/init-script-example.sh diff --git a/Installer/debian/init-script-example.sh b/Installer/debian/init-script-example.sh new file mode 100644 index 000000000..c60427965 --- /dev/null +++ b/Installer/debian/init-script-example.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# +### BEGIN INIT INFO +# Provides: duplicati +# Required-Start: +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 1 +# Short-Description: Duplicati +### END INIT INFO + +DAEMON=/usr/bin/duplicati-server +DAEMON_PROCESS=DuplicatiServer +NAME=duplicati +PIDFILE=/var/run/$NAME.pid +DESC="Duplicati backup service" +DEFAULT=/etc/default/$NAME + +# Use LSB +. /lib/lsb/init-functions + +# If we don't have the basics, don't bother +test -x $DAEMON || exit 0 +test -f $DEFAULT && . $DEFAULT + +running_pid() +{ + # Check if a given process pid's cmdline matches a given name + pid=$1 + name=$2 + [ -z "$pid" ] && return 1 + [ ! -d /proc/$pid ] && return 1 + cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1` + # Is this the expected child? + [ "$cmd" != "$name" ] && return 1 + return 0 +} + +running() +{ +# Check if the process is running looking at /proc +# (works for all users) + # No pidfile, probably no daemon present + [ ! -f "$PIDFILE" ] && return 1 + # Obtain the pid and check it against the binary name + pid=`cat $PIDFILE` + running_pid $pid $DAEMON_PROCESS || return 1 + return 0 +} + +case "$1" in + start) + log_daemon_msg "Starting $DESC" "$NAME" + if running; then + log_progress_msg "already running" + log_end_msg 0 + exit 0 + fi + rm -f $PIDFILE + start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_OPTS + log_end_msg $? + ;; + stop) + log_daemon_msg "Stopping $DESC" "$NAME" + start-stop-daemon --stop --pidfile $PIDFILE + log_end_msg $? + ;; + restart|force-reload) + $0 stop + sleep 2 + $0 start + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 \ No newline at end of file From 6cc70c7ae538715cd1bb84d2ee51f243220e9952 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Sun, 19 Nov 2017 17:28:51 +0100 Subject: [PATCH 09/40] Added a friendly error message if the SSH key fails to parse/load. This fixes #2856 --- .../Library/Backend/SSHv2/SSHv2Backend.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs index 1131c521f..415fcd6fe 100644 --- a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs +++ b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs @@ -327,28 +327,35 @@ namespace Duplicati.Library.Backend 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); } } From 4fd9e417037d9c158733f39fa2a96367d9242e9d Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Sun, 19 Nov 2017 17:48:39 +0100 Subject: [PATCH 10/40] Added some documentation strings and a missing "else" that was causing a crash. This fixes #2858 --- Duplicati/Library/Utility/FilterExpression.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Duplicati/Library/Utility/FilterExpression.cs b/Duplicati/Library/Utility/FilterExpression.cs index 7064dc21d..35feb0f15 100644 --- a/Duplicati/Library/Utility/FilterExpression.cs +++ b/Duplicati/Library/Utility/FilterExpression.cs @@ -49,10 +49,22 @@ namespace Duplicati.Library.Utility /// public class FilterExpression : IFilter { + /// + /// Implementation of a filter entry + /// private struct FilterEntry { + /// + /// The type of the filter + /// public readonly FilterType Type; + /// + /// The filter string + /// public readonly string Filter; + /// + /// The regular expression version of the filter + /// public readonly System.Text.RegularExpressions.Regex Regexp; /// @@ -64,12 +76,18 @@ namespace Duplicati.Library.Utility /// private const char MULTIPLE_WILDCARD = '*'; - + /// + /// The regular expression flags + /// 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); - + + /// + /// Initializes a new instance of the struct. + /// + /// The filter string to use. 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); From e1a0ca06d5ee4b93ce314f64b279cdd068fc3d54 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Tue, 21 Nov 2017 13:52:56 +0100 Subject: [PATCH 11/40] Change .rbf filter to only exclude `.rbf` files in the `config.msi` subfolder. This fixes #2886 --- Duplicati/Library/Utility/DefaultFilters.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Library/Utility/DefaultFilters.cs b/Duplicati/Library/Utility/DefaultFilters.cs index 6306b8883..7c04ac378 100644 --- a/Duplicati/Library/Utility/DefaultFilters.cs +++ b/Duplicati/Library/Utility/DefaultFilters.cs @@ -180,7 +180,7 @@ namespace Duplicati.Library.Utility { var filters = new[] { - DefaultFilters.CreateWildcardFilter(@"*.rbf"), + DefaultFilters.CreateWildcardFilter(@"*/config.msi/*.rbf"), // https://github.com/duplicati/duplicati/issues/2886 DefaultFilters.CreateWildcardFilter(@"*.tmp"), DefaultFilters.CreateWildcardFilter(@"*.tmp/*"), DefaultFilters.CreateWildcardFilter(@"*/$RECYCLE.BIN/*"), From 6564220df024ec4fae7ff2ff74c4cc619b2f31a1 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Tue, 21 Nov 2017 23:41:02 +0100 Subject: [PATCH 12/40] Implemented a utility method for escaping commandline arguments --- Duplicati/Library/Utility/Utility.cs | 69 ++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 01a34ccf1..0ae450d8d 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -1496,5 +1496,74 @@ namespace Duplicati.Library.Utility } } } + + /// + /// The regular expression matching all know non-quoted commandline characters + /// + private static readonly Regex COMMANDLINE_SAFE = new Regex(@"[A-Za-z0-9\-_/:\.]*"); + /// + /// Special characters that needs to be escaped on Linux + /// + private static readonly Regex COMMANDLINE_ESCAPED_LINUX = new Regex(@"[""|$|`|\\|!]"); + + /// + /// Wraps a single argument in quotes suitable for the passing on the commandline + /// + /// The wrapped commandline element. + /// The argument to wrap. + /// A flag indicating if environment variables are allowed to be expanded + public static string WrapCommandLineElement(string arg, bool allowEnvExpansion) + { + if (string.IsNullOrWhiteSpace(arg)) + return arg; + + if (!Library.Utility.Utility.IsClientWindows) + { + // We could consider using single quotes that prevents all expansions + //if (!allowEnvExpansion) + // return "'" + arg.Replace("'", "\\'") + "'"; + + // Linux is using backslash to escape, except for ! + arg = COMMANDLINE_ESCAPED_LINUX.Replace(arg, (match) => + { + if (match.Value == "!") + return "\"'!'\""; + + if (match.Value == "$" && allowEnvExpansion) + return match.Value; + + return "\\" + match.Value; + }); + } + else + { + // Windows needs only needs " replaced with "", + // but is prone to %var% expansion when used in + // immediate mode (i.e. from command prompt) + + // TODO: I have not found a way to avoid escaping %varname%, + // and sadly it expands only if the variable exists + // making it even rarer and harder to diagnose when + // it happens + arg = arg.Replace("\"", "\"\""); + } + + // Check that all characters are in the safe set + if (COMMANDLINE_SAFE.Match(arg).Length != arg.Length) + return "\"" + arg + "\""; + else + return arg; + } + + /// + /// Wrap a set of commandline arguments suitable for the commandline + /// + /// A commandline string. + /// The arguments to create into a commandline. + /// A flag indicating if environment variables are allowed to be expanded + public static string WrapAsCommandLine(IEnumerable args, bool allowEnvExpansion = false) + { + return string.Join(" ", args.Select(x => WrapCommandLineElement(x, allowEnvExpansion))); + } } } From 514a90b11f8569c87e3c9a5c5acb1a249fddc4d4 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Tue, 21 Nov 2017 23:41:31 +0100 Subject: [PATCH 13/40] Updated the GUI commandline generation to use the new methods --- Duplicati/Server/Runner.cs | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/Duplicati/Server/Runner.cs b/Duplicati/Server/Runner.cs index fd09be390..2cf755d44 100644 --- a/Duplicati/Server/Runner.cs +++ b/Duplicati/Server/Runner.cs @@ -361,8 +361,6 @@ namespace Duplicati.Server where !string.IsNullOrWhiteSpace(p) select p).ToArray(); - var cmd = new System.Text.StringBuilder(); - var exe = System.IO.Path.Combine( Library.AutoUpdater.UpdaterManager.InstalledBaseDir, @@ -371,42 +369,26 @@ namespace Duplicati.Server ) ); - Func commandLineEscapeValue = x => - { - if (string.IsNullOrWhiteSpace(x)) - return x; - - if (x.EndsWith("\\", StringComparison.Ordinal)) - x += "\\"; - - x = x.Replace("\"", Library.Utility.Utility.IsClientWindows ? "\"\"" : "\\\""); - - return "\"" + x + "\""; - }; - - exe = commandLineEscapeValue(exe); - + var cmd = new System.Text.StringBuilder(); if (Library.Utility.Utility.IsMono) - exe = "mono " + exe; - + cmd.Append("mono "); - cmd.Append(exe); - cmd.Append(" backup"); - cmd.Append(" "); - cmd.Append(commandLineEscapeValue(backup.TargetURL)); - cmd.Append(" "); - cmd.Append(string.Join(" ", sources.Select(x => commandLineEscapeValue(x)))); + cmd.Append(Library.Utility.Utility.WrapAsCommandLine(new string[] { exe, "backup", backup.TargetURL }, false)); + cmd.Append(" "); + cmd.Append(Library.Utility.Utility.WrapAsCommandLine(sources, true)); + + // TODO: We should check each option to see if it is a path, and allow expansion on that foreach(var opt in options) - cmd.AppendFormat(" --{0}={1}", opt.Key, commandLineEscapeValue(opt.Value)); + cmd.AppendFormat(" --{0}={1}", opt.Key, Library.Utility.Utility.WrapCommandLineElement(opt.Value, false)); if (cf != null) foreach(var f in cf) - cmd.AppendFormat(" --{0}={1}", f.Include ? "include" : "exclude", commandLineEscapeValue(f.Expression)); + cmd.AppendFormat(" --{0}={1}", f.Include ? "include" : "exclude", Library.Utility.Utility.WrapCommandLineElement(f.Expression, true)); if (bf != null) foreach(var f in bf) - cmd.AppendFormat(" --{0}={1}", f.Include ? "include" : "exclude", commandLineEscapeValue(f.Expression)); + cmd.AppendFormat(" --{0}={1}", f.Include ? "include" : "exclude", Library.Utility.Utility.WrapCommandLineElement(f.Expression, true)); return cmd.ToString(); } From b42ab42e57ac8683422fcfd8f0c96a548caa0a19 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Tue, 21 Nov 2017 23:42:48 +0100 Subject: [PATCH 14/40] Fixed the updater to use the argument escaping code from utilities. This fixes #2891 --- Duplicati/Library/AutoUpdater/UpdaterManager.cs | 6 ++---- Duplicati/Library/Utility/Utility.cs | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Duplicati/Library/AutoUpdater/UpdaterManager.cs b/Duplicati/Library/AutoUpdater/UpdaterManager.cs index db19e68aa..a1cc4fd1e 100644 --- a/Duplicati/Library/AutoUpdater/UpdaterManager.cs +++ b/Duplicati/Library/AutoUpdater/UpdaterManager.cs @@ -1070,9 +1070,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); @@ -1216,9 +1215,8 @@ 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); diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 0ae450d8d..815f64497 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -1540,6 +1540,8 @@ namespace Duplicati.Library.Utility // Windows needs only needs " replaced with "", // but is prone to %var% expansion when used in // immediate mode (i.e. from command prompt) + // Fortunately it does not expand when processes + // are started from within .Net // TODO: I have not found a way to avoid escaping %varname%, // and sadly it expands only if the variable exists From 6f5709e0d35a9e5bcc6379c265ae50627fba5b82 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Wed, 22 Nov 2017 18:01:16 +0100 Subject: [PATCH 15/40] Version bump to v2.0.2.13-2.0.2.13_canary_2017-11-22 You can download this build from: Binaries: https://updates.duplicati.com/canary/duplicati-2.0.2.13_canary_2017-11-22.zip Signature file: https://updates.duplicati.com/canary/duplicati-2.0.2.13_canary_2017-11-22.zip.sig ASCII signature file: https://updates.duplicati.com/canary/duplicati-2.0.2.13_canary_2017-11-22.zip.sig.asc MD5: 9b957fac42d0811d82a1f2443a7bbc02 SHA1: 8686c21ab30706f8d9e95e197a2bf4b38ba2d8cb SHA256: 746340cdfe2261317f1dca7a4a47194486e5efaf5958b9879790a4abb044120d --- Updates/build_version.txt | 2 +- changelog.txt | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Updates/build_version.txt b/Updates/build_version.txt index 48082f72f..b1bd38b62 100644 --- a/Updates/build_version.txt +++ b/Updates/build_version.txt @@ -1 +1 @@ -12 +13 diff --git a/changelog.txt b/changelog.txt index 0a1d18a38..cd7bbde4b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,24 @@ +2017-11-22 - 2.0.2.13_canary_2017-11-22 +========== +* Fixed an issue with AutoUpdater semi-ignoring webroot +* Fixed an issue where source folders would be omitted from the backup if a file with the same leading name was also added +* Switched to default "keep forever", thanks @ItEndsWithTens +* Fixed a startup crash caused by invalid throttle values +* Added a number of new languages, thanks to all translators! +* Timestamps in the log are now reported in local time and include a UTC timestamp for easy script parsing +* Added Windows registry exclusion list to the default exclude filters +* Added .dv files to compression exclusion list, thanks @jesjimher +* Fixed some spelling in the UI, thanks @davegold24 and @JonMikeIV +* Fixed logic of the `affected` command to not return empty results if the local database was not found +* Fixed a case where the "Resume" button did not appear clickable, thanks @JonMikeIV +* Improved query time for browsing folders during restore, thanks @RobSauro +* Commands are now sorted alphabetically, thanks @JonMikeIV +* Improved an error message when the SSH key failed to parse +* Removed tilde expansion to fix multiple issues caused by unexpected expansio, thanks @warwickmm +* Fixed .rbf files to only be excluded if they are part of the `config.msi` folder +* Improved the commandline export to show commandlines that are correctly escaped for the shell +* Fixed a case where the AutoUpdater would fail to send the correct commandline to the updated process + 2017-10-20 - 2.0.2.12_canary_2017-10-20 ========== * Fixed not reporting "Finished" until the backup is actually finished, thanks @warwickmm From 8810e0130d3857cadc9269367f0e80f6434b44af Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sun, 26 Nov 2017 10:53:14 -0800 Subject: [PATCH 16/40] Make string comparisons use ordinal (binary) sort rules. These string comparisons should not be culture-aware. --- .../CommandLine/BackendTester/Program.cs | 2 +- Duplicati/CommandLine/Commands.cs | 10 +++++----- Duplicati/CommandLine/Help.cs | 10 +++++----- Duplicati/CommandLine/RecoveryTool/Index.cs | 2 +- Duplicati/CommandLine/RecoveryTool/Program.cs | 2 +- Duplicati/CommandLine/RecoveryTool/Restore.cs | 2 +- .../HttpServerConnection.cs | 2 +- .../GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs | 2 +- .../Library/AutoUpdater/UpdaterManager.cs | 10 +++++----- .../AlternativeFTP/AlternativeFTPBackend.cs | 10 +++++----- .../Library/Backend/AmazonCloudDrive/AmzCD.cs | 6 +++--- Duplicati/Library/Backend/Backblaze/B2.cs | 6 +++--- .../Library/Backend/Backblaze/B2AuthHelper.cs | 2 +- Duplicati/Library/Backend/Box/BoxBackend.cs | 2 +- .../Library/Backend/CloudFiles/CloudFiles.cs | 10 +++++----- Duplicati/Library/Backend/Dropbox/Dropbox.cs | 4 ++-- Duplicati/Library/Backend/FTP/FTPBackend.cs | 6 +++--- Duplicati/Library/Backend/File/FileBackend.cs | 4 ++-- Duplicati/Library/Backend/File/Win32.cs | 6 +++--- .../GoogleServices/GoogleCloudStorage.cs | 4 ++-- .../Backend/GoogleServices/GoogleDrive.cs | 2 +- .../Library/Backend/Jottacloud/Jottacloud.cs | 2 +- .../Library/Backend/OneDrive/OneDrive.cs | 2 +- .../Backend/OpenStack/OpenStackStorage.cs | 8 ++++---- Duplicati/Library/Backend/S3/S3Backend.cs | 16 +++++++-------- .../Library/Backend/SSHv2/KeyUploader.cs | 2 +- .../Library/Backend/SSHv2/SSHv2Backend.cs | 8 ++++---- .../Backend/SharePoint/SharePointBackend.cs | 6 +++--- Duplicati/Library/Backend/Sia/Sia.cs | 6 +++--- .../Library/Backend/TahoeLAFS/TahoeBackend.cs | 4 ++-- Duplicati/Library/Backend/WEBDAV/WEBDAV.cs | 20 +++++++++---------- .../Library/DynamicLoader/BackendLoader.cs | 4 ++-- Duplicati/Library/Main/Controller.cs | 2 +- .../Main/Database/LocalListDatabase.cs | 2 +- .../Library/Main/Operation/RestoreHandler.cs | 4 ++-- .../Library/Modules/Builtin/HyperVOptions.cs | 6 +++--- .../Library/Modules/Builtin/MSSQLOptions.cs | 6 +++--- .../Library/Modules/Builtin/RunScript.cs | 4 ++-- .../Library/SQLiteHelper/DatabaseUpgrader.cs | 4 ++-- .../Library/Snapshots/DefineDosDevice.cs | 6 +++--- Duplicati/Library/Snapshots/LinuxSnapshot.cs | 6 +++--- Duplicati/Library/Snapshots/Program.cs | 10 +++++----- Duplicati/Library/Snapshots/SystemIOLinux.cs | 2 +- .../Library/Snapshots/SystemIOWindows.cs | 14 ++++++------- .../Library/Snapshots/WindowsSnapshot.cs | 2 +- .../Library/UsageReporter/OSInfoHelper.cs | 8 ++++---- .../Library/Utility/CommandLineParser.cs | 10 +++++----- Duplicati/Library/Utility/FilterExpression.cs | 6 +++--- Duplicati/Library/Utility/Sizeparser.cs | 12 +++++------ Duplicati/Library/Utility/Uri.cs | 8 ++++---- Duplicati/Library/Utility/UrlUtillity.cs | 4 ++-- Duplicati/Library/Utility/Utility.cs | 4 ++-- Duplicati/License/LicenseEntry.cs | 2 +- Duplicati/Server/Database/Backup.cs | 2 +- Duplicati/Server/Database/Connection.cs | 4 ++-- Duplicati/Server/Database/ServerSettings.cs | 2 +- Duplicati/Server/Runner.cs | 8 ++++---- Duplicati/Server/Serializable/ServerStatus.cs | 2 +- Duplicati/Server/SpecialFolders.cs | 8 ++++---- .../Server/WebServer/AuthenticationHandler.cs | 2 +- .../Server/WebServer/IndexHtmlHandler.cs | 4 ++-- .../WebServer/RESTMethods/Filesystem.cs | 6 +++--- Duplicati/UnitTest/SVNCheckoutsTest.cs | 6 +++--- 63 files changed, 174 insertions(+), 174 deletions(-) diff --git a/Duplicati/CommandLine/BackendTester/Program.cs b/Duplicati/CommandLine/BackendTester/Program.cs index 47a578d3e..30b757790 100644 --- a/Duplicati/CommandLine/BackendTester/Program.cs +++ b/Duplicati/CommandLine/BackendTester/Program.cs @@ -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(); } diff --git a/Duplicati/CommandLine/Commands.cs b/Duplicati/CommandLine/Commands.cs index ed248d367..30c3fe662 100644 --- a/Duplicati/CommandLine/Commands.cs +++ b/Duplicati/CommandLine/Commands.cs @@ -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); diff --git a/Duplicati/CommandLine/Help.cs b/Duplicati/CommandLine/Help.cs index c088eb0c7..88350d6cb 100644 --- a/Duplicati/CommandLine/Help.cs +++ b/Duplicati/CommandLine/Help.cs @@ -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; } diff --git a/Duplicati/CommandLine/RecoveryTool/Index.cs b/Duplicati/CommandLine/RecoveryTool/Index.cs index 6b3eb1540..95812a191 100644 --- a/Duplicati/CommandLine/RecoveryTool/Index.cs +++ b/Duplicati/CommandLine/RecoveryTool/Index.cs @@ -175,7 +175,7 @@ namespace Duplicati.CommandLine.RecoveryTool 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); diff --git a/Duplicati/CommandLine/RecoveryTool/Program.cs b/Duplicati/CommandLine/RecoveryTool/Program.cs index e37eda13e..938613dec 100644 --- a/Duplicati/CommandLine/RecoveryTool/Program.cs +++ b/Duplicati/CommandLine/RecoveryTool/Program.cs @@ -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 ); diff --git a/Duplicati/CommandLine/RecoveryTool/Restore.cs b/Duplicati/CommandLine/RecoveryTool/Restore.cs index 8a357c941..213c7be51 100644 --- a/Duplicati/CommandLine/RecoveryTool/Restore.cs +++ b/Duplicati/CommandLine/RecoveryTool/Restore.cs @@ -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(str.Substring(0, ix), str.Substring(ix + 2)); diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs index 97c9ff33a..7695a98a6 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs @@ -73,7 +73,7 @@ namespace Duplicati.GUI.TrayIcon public HttpServerConnection(Uri server, string password, bool saltedpassword, bool dbPasswordSourceDatabase, Dictionary options) { m_baseUri = server.ToString(); - if (!m_baseUri.EndsWith("/")) + if (!m_baseUri.EndsWith("/", StringComparison.Ordinal)) m_baseUri += "/"; m_apiUri = m_baseUri + "api/v1"; diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs index 68a9b7be5..1b3426749 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs @@ -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); } diff --git a/Duplicati/Library/AutoUpdater/UpdaterManager.cs b/Duplicati/Library/AutoUpdater/UpdaterManager.cs index a1cc4fd1e..0ad8e3455 100644 --- a/Duplicati/Library/AutoUpdater/UpdaterManager.cs +++ b/Duplicati/Library/AutoUpdater/UpdaterManager.cs @@ -148,7 +148,7 @@ namespace Duplicati.Library.AutoUpdater var attempts = new List(); // 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) @@ -521,7 +521,7 @@ namespace Duplicati.Library.AutoUpdater 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); @@ -748,7 +748,7 @@ namespace Duplicati.Library.AutoUpdater if (ignoreMap.ContainsKey(relpath)) return false; - if (path.EndsWith(dirsep)) + if (path.EndsWith(dirsep, StringComparison.Ordinal)) return true; using (var source = System.IO.File.OpenRead(path)) @@ -771,8 +771,8 @@ namespace Duplicati.Library.AutoUpdater 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) + MD5 = fse.EndsWith(dirsep, StringComparison.Ordinal) ? null : computeMD5(fse), + SHA256 = fse.EndsWith(dirsep, StringComparison.Ordinal) ? null : computeSHA256(fse) }) .Union(ignoreFiles).ToArray(); diff --git a/Duplicati/Library/Backend/AlternativeFTP/AlternativeFTPBackend.cs b/Duplicati/Library/Backend/AlternativeFTP/AlternativeFTPBackend.cs index 032854043..b6543a6e1 100644 --- a/Duplicati/Library/Backend/AlternativeFTP/AlternativeFTPBackend.cs +++ b/Duplicati/Library/Backend/AlternativeFTP/AlternativeFTPBackend.cs @@ -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; diff --git a/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs b/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs index b68319333..520d9a06c 100644 --- a/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs +++ b/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs @@ -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; } diff --git a/Duplicati/Library/Backend/Backblaze/B2.cs b/Duplicati/Library/Backend/Backblaze/B2.cs index 5b8838eab..35ec87393 100644 --- a/Duplicati/Library/Backend/Backblaze/B2.cs +++ b/Duplicati/Library/Backend/Backblaze/B2.cs @@ -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); diff --git a/Duplicati/Library/Backend/Backblaze/B2AuthHelper.cs b/Duplicati/Library/Backend/Backblaze/B2AuthHelper.cs index 8adef2e89..55197519c 100644 --- a/Duplicati/Library/Backend/Backblaze/B2AuthHelper.cs +++ b/Duplicati/Library/Backend/Backblaze/B2AuthHelper.cs @@ -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; } diff --git a/Duplicati/Library/Backend/Box/BoxBackend.cs b/Duplicati/Library/Backend/Box/BoxBackend.cs index 124a51fe7..02c6d2cca 100644 --- a/Duplicati/Library/Backend/Box/BoxBackend.cs +++ b/Duplicati/Library/Backend/Box/BoxBackend.cs @@ -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; diff --git a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs index 29cbc2ba1..25c3a1395 100644 --- a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs +++ b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs @@ -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)) diff --git a/Duplicati/Library/Backend/Dropbox/Dropbox.cs b/Duplicati/Library/Backend/Dropbox/Dropbox.cs index 289ec4f34..bf252d1e6 100644 --- a/Duplicati/Library/Backend/Dropbox/Dropbox.cs +++ b/Duplicati/Library/Backend/Dropbox/Dropbox.cs @@ -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)) diff --git a/Duplicati/Library/Backend/FTP/FTPBackend.cs b/Duplicati/Library/Backend/FTP/FTPBackend.cs index 76bda999b..e09bd165e 100644 --- a/Duplicati/Library/Backend/FTP/FTPBackend.cs +++ b/Duplicati/Library/Backend/FTP/FTPBackend.cs @@ -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 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); diff --git a/Duplicati/Library/Backend/File/FileBackend.cs b/Duplicati/Library/Backend/File/FileBackend.cs index 18774c176..343234e13 100644 --- a/Duplicati/Library/Backend/File/FileBackend.cs +++ b/Duplicati/Library/Backend/File/FileBackend.cs @@ -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 diff --git a/Duplicati/Library/Backend/File/Win32.cs b/Duplicati/Library/Backend/File/Win32.cs index 2bc99b6d8..a0c69b672 100644 --- a/Duplicati/Library/Backend/File/Win32.cs +++ b/Duplicati/Library/Backend/File/Win32.cs @@ -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); diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs index 1ac463469..1d8bc335c 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs @@ -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; diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs index feec7f4e8..d71db7813 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs @@ -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; diff --git a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs index ba1bbd0d5..b39e1e774 100644 --- a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs +++ b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs @@ -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)) { diff --git a/Duplicati/Library/Backend/OneDrive/OneDrive.cs b/Duplicati/Library/Backend/OneDrive/OneDrive.cs index f93f6102d..5fc707435 100644 --- a/Duplicati/Library/Backend/OneDrive/OneDrive.cs +++ b/Duplicati/Library/Backend/OneDrive/OneDrive.cs @@ -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; diff --git a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs index fa801bd77..ed4f88cba 100644 --- a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs +++ b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs @@ -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) diff --git a/Duplicati/Library/Backend/S3/S3Backend.cs b/Duplicati/Library/Backend/S3/S3Backend.cs index c287fe491..4d3e9ddf6 100644 --- a/Duplicati/Library/Backend/S3/S3Backend.cs +++ b/Duplicati/Library/Backend/S3/S3Backend.cs @@ -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; } diff --git a/Duplicati/Library/Backend/SSHv2/KeyUploader.cs b/Duplicati/Library/Backend/SSHv2/KeyUploader.cs index fd4f6464c..7f72ce4dd 100644 --- a/Duplicati/Library/Backend/SSHv2/KeyUploader.cs +++ b/Duplicati/Library/Backend/SSHv2/KeyUploader.cs @@ -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 => diff --git a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs index 415fcd6fe..2175068eb 100644 --- a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs +++ b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs @@ -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) diff --git a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs index 9d2bd0e77..3c6342488 100644 --- a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs +++ b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs @@ -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) diff --git a/Duplicati/Library/Backend/Sia/Sia.cs b/Duplicati/Library/Backend/Sia/Sia.cs index 636c2dc63..7c8b7adc7 100644 --- a/Duplicati/Library/Backend/Sia/Sia.cs +++ b/Duplicati/Library/Backend/Sia/Sia.cs @@ -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) @@ -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; diff --git a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs index 2c78d77a9..786049f37 100644 --- a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs +++ b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs @@ -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 += "/"; } diff --git a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs index d35815f16..1ca7f9b56 100644 --- a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs +++ b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs @@ -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; diff --git a/Duplicati/Library/DynamicLoader/BackendLoader.cs b/Duplicati/Library/DynamicLoader/BackendLoader.cs index fab99a87d..78632b3a2 100644 --- a/Duplicati/Library/DynamicLoader/BackendLoader.cs +++ b/Duplicati/Library/DynamicLoader/BackendLoader.cs @@ -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)) diff --git a/Duplicati/Library/Main/Controller.cs b/Duplicati/Library/Main/Controller.cs index 66d64a9a0..e89cc3f3c 100644 --- a/Duplicati/Library/Main/Controller.cs +++ b/Duplicati/Library/Main/Controller.cs @@ -205,7 +205,7 @@ namespace Duplicati.Library.Main { List expandedSources = new List(); - 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 diff --git a/Duplicati/Library/Main/Database/LocalListDatabase.cs b/Duplicati/Library/Main/Database/LocalListDatabase.cs index 59f38d4c4..0bcaf42b6 100644 --- a/Duplicati/Library/Main/Database/LocalListDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListDatabase.cs @@ -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); diff --git a/Duplicati/Library/Main/Operation/RestoreHandler.cs b/Duplicati/Library/Main/Operation/RestoreHandler.cs index 1d02eb6ed..724fcc76f 100644 --- a/Duplicati/Library/Main/Operation/RestoreHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreHandler.cs @@ -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 diff --git a/Duplicati/Library/Modules/Builtin/HyperVOptions.cs b/Duplicati/Library/Modules/Builtin/HyperVOptions.cs index 81938bf9b..d33ebdbbf 100644 --- a/Duplicati/Library/Modules/Builtin/HyperVOptions.cs +++ b/Duplicati/Library/Modules/Builtin/HyperVOptions.cs @@ -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(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) diff --git a/Duplicati/Library/Modules/Builtin/MSSQLOptions.cs b/Duplicati/Library/Modules/Builtin/MSSQLOptions.cs index 3c8bcbf3c..4c3c42136 100644 --- a/Duplicati/Library/Modules/Builtin/MSSQLOptions.cs +++ b/Duplicati/Library/Modules/Builtin/MSSQLOptions.cs @@ -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(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) diff --git a/Duplicati/Library/Modules/Builtin/RunScript.cs b/Duplicati/Library/Modules/Builtin/RunScript.cs index 6f9f69cf6..f294b47f7 100644 --- a/Duplicati/Library/Modules/Builtin/RunScript.cs +++ b/Duplicati/Library/Modules/Builtin/RunScript.cs @@ -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); } diff --git a/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs b/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs index f3a9a987a..994d7ed95 100644 --- a/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs +++ b/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs @@ -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; diff --git a/Duplicati/Library/Snapshots/DefineDosDevice.cs b/Duplicati/Library/Snapshots/DefineDosDevice.cs index adc45c668..762d7512a 100644 --- a/Duplicati/Library/Snapshots/DefineDosDevice.cs +++ b/Duplicati/Library/Snapshots/DefineDosDevice.cs @@ -97,7 +97,7 @@ namespace Duplicati.Library.Snapshots List drives = new List("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; diff --git a/Duplicati/Library/Snapshots/LinuxSnapshot.cs b/Duplicati/Library/Snapshots/LinuxSnapshot.cs index ff5ff1375..a82b64748 100644 --- a/Duplicati/Library/Snapshots/LinuxSnapshot.cs +++ b/Duplicati/Library/Snapshots/LinuxSnapshot.cs @@ -101,7 +101,7 @@ namespace Duplicati.Library.Snapshots /// The local path 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 /// The snapshot path 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); @@ -342,7 +342,7 @@ namespace Duplicati.Library.Snapshots KeyValuePair? best = null; foreach (KeyValuePair 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) diff --git a/Duplicati/Library/Snapshots/Program.cs b/Duplicati/Library/Snapshots/Program.cs index d7dab0da1..07e4fbbe0 100644 --- a/Duplicati/Library/Snapshots/Program.cs +++ b/Duplicati/Library/Snapshots/Program.cs @@ -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 diff --git a/Duplicati/Library/Snapshots/SystemIOLinux.cs b/Duplicati/Library/Snapshots/SystemIOLinux.cs index 2e05cabc9..f97462c66 100644 --- a/Duplicati/Library/Snapshots/SystemIOLinux.cs +++ b/Duplicati/Library/Snapshots/SystemIOLinux.cs @@ -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(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(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")) diff --git a/Duplicati/Library/Snapshots/SystemIOWindows.cs b/Duplicati/Library/Snapshots/SystemIOWindows.cs index 943611f8e..aba8c26de 100644 --- a/Duplicati/Library/Snapshots/SystemIOWindows.cs +++ b/Duplicati/Library/Snapshots/SystemIOWindows.cs @@ -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 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(); @@ -532,7 +532,7 @@ namespace Duplicati.Library.Snapshots public void SetMetadata(string path, Dictionary 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; diff --git a/Duplicati/Library/Snapshots/WindowsSnapshot.cs b/Duplicati/Library/Snapshots/WindowsSnapshot.cs index f8f7b4ca1..14104c9c5 100644 --- a/Duplicati/Library/Snapshots/WindowsSnapshot.cs +++ b/Duplicati/Library/Snapshots/WindowsSnapshot.cs @@ -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); diff --git a/Duplicati/Library/UsageReporter/OSInfoHelper.cs b/Duplicati/Library/UsageReporter/OSInfoHelper.cs index 8246300b9..e394e2f1d 100644 --- a/Duplicati/Library/UsageReporter/OSInfoHelper.cs +++ b/Duplicati/Library/UsageReporter/OSInfoHelper.cs @@ -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; } diff --git a/Duplicati/Library/Utility/CommandLineParser.cs b/Duplicati/Library/Utility/CommandLineParser.cs index 4ddeddbb4..de539f994 100644 --- a/Duplicati/Library/Utility/CommandLineParser.cs +++ b/Duplicati/Library/Utility/CommandLineParser.cs @@ -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 diff --git a/Duplicati/Library/Utility/FilterExpression.cs b/Duplicati/Library/Utility/FilterExpression.cs index 35feb0f15..81bd915d5 100644 --- a/Duplicati/Library/Utility/FilterExpression.cs +++ b/Duplicati/Library/Utility/FilterExpression.cs @@ -320,7 +320,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); @@ -602,9 +602,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; diff --git a/Duplicati/Library/Utility/Sizeparser.cs b/Duplicati/Library/Utility/Sizeparser.cs index 409def46a..46a6d8671 100644 --- a/Duplicati/Library/Utility/Sizeparser.cs +++ b/Duplicati/Library/Utility/Sizeparser.cs @@ -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; diff --git a/Duplicati/Library/Utility/Uri.cs b/Duplicati/Library/Utility/Uri.cs index 5f39e5db1..8b5a6baf3 100644 --- a/Duplicati/Library/Utility/Uri.cs +++ b/Duplicati/Library/Utility/Uri.cs @@ -165,11 +165,11 @@ namespace Duplicati.Library.Utility var h = m.Groups["hostname"].Success ? m.Groups["hostname"].Value : ""; var p = m.Groups["path"].Success ? m.Groups["path"].Value : ""; - if (m.Groups["hostname"].Success && p.StartsWith("/")) + if (m.Groups["hostname"].Success && p.StartsWith("/", StringComparison.Ordinal)) p = p.Substring(1); // file://c:\test support - if (h.Length == 1 && p.StartsWith(":")) + if (h.Length == 1 && p.StartsWith(":", StringComparison.Ordinal)) { h = h + p; p = ""; @@ -260,7 +260,7 @@ namespace Duplicati.Library.Utility if (!string.IsNullOrEmpty(path)) { - if (!string.IsNullOrEmpty(host) && !path.StartsWith("/")) + if (!string.IsNullOrEmpty(host) && !path.StartsWith("/", StringComparison.Ordinal)) s += "/"; s += path; } @@ -444,7 +444,7 @@ namespace Duplicati.Library.Utility { if (query == null) throw new ArgumentNullException("query"); - if (query.StartsWith("?")) + if (query.StartsWith("?", StringComparison.Ordinal)) query = query.Substring(1); if (string.IsNullOrEmpty(query)) return new NameValueCollection(StringComparer.OrdinalIgnoreCase); diff --git a/Duplicati/Library/Utility/UrlUtillity.cs b/Duplicati/Library/Utility/UrlUtillity.cs index 5753bb1d6..84469e019 100644 --- a/Duplicati/Library/Utility/UrlUtillity.cs +++ b/Duplicati/Library/Utility/UrlUtillity.cs @@ -47,7 +47,7 @@ namespace Duplicati.Library.Utility /// The url to open, must start with http:// or https:// public static void OpenURL(string url, string browserprogram = null) { - if (!url.StartsWith("http://") && !url.StartsWith("https://")) + if (!url.StartsWith("http://", StringComparison.Ordinal) && !url.StartsWith("https://", StringComparison.Ordinal)) throw new Exception("Malformed URL"); if (string.IsNullOrWhiteSpace(browserprogram)) @@ -107,7 +107,7 @@ namespace Duplicati.Library.Utility try { - if (!url.StartsWith("http://") && !url.StartsWith("https://")) + if (!url.StartsWith("http://", StringComparison.Ordinal) && !url.StartsWith("https://", StringComparison.Ordinal)) throw new Exception("Malformed URL"); if (string.IsNullOrEmpty(browserprogram)) diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 815f64497..6ae8b8d53 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -170,7 +170,7 @@ namespace Duplicati.Library.Utility /// A list of the full filenames public static IEnumerable EnumerateFiles(string basepath, IFilter filter) { - return EnumerateFileSystemEntries(basepath, filter).Where(x => !x.EndsWith(DirectorySeparatorString)); + return EnumerateFileSystemEntries(basepath, filter).Where(x => !x.EndsWith(DirectorySeparatorString, StringComparison.Ordinal)); } /// @@ -182,7 +182,7 @@ namespace Duplicati.Library.Utility /// A list of the full paths public static IEnumerable EnumerateFolders(string basepath, IFilter filter) { - return EnumerateFileSystemEntries(basepath, filter).Where(x => x.EndsWith(DirectorySeparatorString)); + return EnumerateFileSystemEntries(basepath, filter).Where(x => x.EndsWith(DirectorySeparatorString, StringComparison.Ordinal)); } /// diff --git a/Duplicati/License/LicenseEntry.cs b/Duplicati/License/LicenseEntry.cs index d89bca6e9..dc3e87c43 100644 --- a/Duplicati/License/LicenseEntry.cs +++ b/Duplicati/License/LicenseEntry.cs @@ -57,7 +57,7 @@ namespace Duplicati.License if (!string.IsNullOrEmpty(urlfile) && System.IO.File.Exists(urlfile)) Url = System.IO.File.ReadAllText(urlfile).Trim(); License = System.IO.File.ReadAllText(licensefile); - if (License.IndexOf("\r\n") < 0) + if (License.IndexOf("\r\n", StringComparison.Ordinal) < 0) License = License.Replace("\n", "\r\n").Replace("\r", "\r\n"); if (Environment.NewLine != "\r\n") License = License.Replace("\r\n", Environment.NewLine); diff --git a/Duplicati/Server/Database/Backup.cs b/Duplicati/Server/Database/Backup.cs index a45113880..3317421b2 100644 --- a/Duplicati/Server/Database/Backup.cs +++ b/Duplicati/Server/Database/Backup.cs @@ -92,7 +92,7 @@ namespace Duplicati.Server.Database /// /// Gets a value indicating if this instance is not persisted to the database /// - public bool IsTemporary { get { return ID == null ? false : ID.IndexOf("-") > 0; } } + public bool IsTemporary { get { return ID == null ? false : ID.IndexOf("-", StringComparison.Ordinal) > 0; } } } } diff --git a/Duplicati/Server/Database/Connection.cs b/Duplicati/Server/Database/Connection.cs index 4e4de0c8a..f57457ff2 100644 --- a/Duplicati/Server/Database/Connection.cs +++ b/Duplicati/Server/Database/Connection.cs @@ -248,7 +248,7 @@ namespace Duplicati.Server.Database if (tags == null || tags.Length == 0) return new long[0]; - if (tags.Length == 1 && tags[0].StartsWith("ID=")) + if (tags.Length == 1 && tags[0].StartsWith("ID=", StringComparison.Ordinal)) return new long[] { long.Parse(tags[0].Substring("ID=".Length)) }; lock(m_lock) @@ -510,7 +510,7 @@ namespace Duplicati.Server.Database @"INSERT INTO ""Backup"" (""Name"", ""Tags"", ""TargetURL"", ""DBPath"") VALUES (?,?,?,?)", (n) => { - if (n.TargetURL.IndexOf(Duplicati.Server.WebServer.Server.PASSWORD_PLACEHOLDER) >= 0) + if (n.TargetURL.IndexOf(Duplicati.Server.WebServer.Server.PASSWORD_PLACEHOLDER, StringComparison.Ordinal) >= 0) throw new Exception("Attempted to save a backup with the password placeholder"); if (update && long.Parse(n.ID) <= 0) throw new Exception("Invalid update, cannot update application settings through update method"); diff --git a/Duplicati/Server/Database/ServerSettings.cs b/Duplicati/Server/Database/ServerSettings.cs index 8104bb175..2ba5d3716 100644 --- a/Duplicati/Server/Database/ServerSettings.cs +++ b/Duplicati/Server/Database/ServerSettings.cs @@ -89,7 +89,7 @@ namespace Duplicati.Server.Database m_values.Clear(); foreach(var k in newsettings) - if (!clearExisting && newsettings[k.Key] == null && k.Key.StartsWith("--")) + if (!clearExisting && newsettings[k.Key] == null && k.Key.StartsWith("--", StringComparison.Ordinal)) m_values.Remove(k.Key); else m_values[k.Key] = newsettings[k.Key]; diff --git a/Duplicati/Server/Runner.cs b/Duplicati/Server/Runner.cs index 2cf755d44..45fc863c7 100644 --- a/Duplicati/Server/Runner.cs +++ b/Duplicati/Server/Runner.cs @@ -833,12 +833,12 @@ namespace Duplicati.Server // Apply normal options foreach(var o in backup.Settings) - if (!o.Name.StartsWith("--") && TestIfOptionApplies(backup, mode, o.Filter)) + if (!o.Name.StartsWith("--", StringComparison.Ordinal) && TestIfOptionApplies(backup, mode, o.Filter)) options[o.Name] = o.Value; // Apply override options foreach(var o in backup.Settings) - if (o.Name.StartsWith("--") && TestIfOptionApplies(backup, mode, o.Filter)) + if (o.Name.StartsWith("--", StringComparison.Ordinal) && TestIfOptionApplies(backup, mode, o.Filter)) options[o.Name.Substring(2)] = o.Value; @@ -855,8 +855,8 @@ namespace Duplicati.Server { var nf = (from n in f2 - let exp = - n.Expression.StartsWith("[") && n.Expression.EndsWith("]") + let exp = + n.Expression.StartsWith("[", StringComparison.Ordinal) && n.Expression.EndsWith("]", StringComparison.Ordinal) ? SpecialFolders.ExpandEnvironmentVariablesRegexp(n.Expression) : SpecialFolders.ExpandEnvironmentVariables(n.Expression) orderby n.Order diff --git a/Duplicati/Server/Serializable/ServerStatus.cs b/Duplicati/Server/Serializable/ServerStatus.cs index 1804dc87f..378225270 100644 --- a/Duplicati/Server/Serializable/ServerStatus.cs +++ b/Duplicati/Server/Serializable/ServerStatus.cs @@ -84,7 +84,7 @@ namespace Duplicati.Server.Serializable return ( from n in Program.Scheduler.Schedule let backupid = (from t in n.Value.Tags - where t != null && t.StartsWith("ID=") + where t != null && t.StartsWith("ID=", StringComparison.Ordinal) select t.Substring("ID=".Length)).FirstOrDefault() where !string.IsNullOrWhiteSpace(backupid) select new Tuple(backupid, n.Key) diff --git a/Duplicati/Server/SpecialFolders.cs b/Duplicati/Server/SpecialFolders.cs index fc7b31c9f..504f2be05 100644 --- a/Duplicati/Server/SpecialFolders.cs +++ b/Duplicati/Server/SpecialFolders.cs @@ -30,7 +30,7 @@ namespace Duplicati.Server public static string ExpandEnvironmentVariables(string path) { foreach(var n in Nodes) - if (path.StartsWith(n.id)) + if (path.StartsWith(n.id, StringComparison.Ordinal)) path = path.Replace(n.id, n.resolvedpath); return Library.Utility.Utility.ExpandEnvironmentVariables(path); } @@ -162,7 +162,7 @@ namespace Duplicati.Server try { var nx = x; - if (nx.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) + if (nx.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) nx = nx.Substring(0, nx.Length - 1); var n = systemIO.PathGetFileName(nx); if (!string.IsNullOrWhiteSpace(n)) @@ -172,8 +172,8 @@ namespace Duplicati.Server { } - if (x.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()) && x.Length > 1) - return new KeyValuePair(x, x.Substring(0, x.Length - 1).Substring(x.Substring(0, x.Length - 1).LastIndexOf("/") + 1)); + if (x.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && x.Length > 1) + return new KeyValuePair(x, x.Substring(0, x.Length - 1).Substring(x.Substring(0, x.Length - 1).LastIndexOf("/", StringComparison.Ordinal) + 1)); else return new KeyValuePair(x, x); diff --git a/Duplicati/Server/WebServer/AuthenticationHandler.cs b/Duplicati/Server/WebServer/AuthenticationHandler.cs index bd8339e4d..908d6ae6f 100644 --- a/Duplicati/Server/WebServer/AuthenticationHandler.cs +++ b/Duplicati/Server/WebServer/AuthenticationHandler.cs @@ -250,7 +250,7 @@ namespace Duplicati.Server.WebServer var expires = DateTime.UtcNow.AddHours(1); m_prng.GetBytes(buf); var token = Duplicati.Library.Utility.Utility.Base64UrlEncode(buf); - while (token.Length > 0 && token.EndsWith("=")) + while (token.Length > 0 && token.EndsWith("=", StringComparison.Ordinal)) token = token.Substring(0, token.Length - 1); m_activeTokens.AddOrUpdate(token, key => expires, (key, existingValue) => diff --git a/Duplicati/Server/WebServer/IndexHtmlHandler.cs b/Duplicati/Server/WebServer/IndexHtmlHandler.cs index 35dfee9ac..bc03c4c09 100644 --- a/Duplicati/Server/WebServer/IndexHtmlHandler.cs +++ b/Duplicati/Server/WebServer/IndexHtmlHandler.cs @@ -40,7 +40,7 @@ namespace Duplicati.Server.WebServer if (System.IO.Directory.Exists(path) && (System.IO.File.Exists(html) || System.IO.File.Exists(htm))) { - if (!request.Uri.AbsolutePath.EndsWith("/")) + if (!request.Uri.AbsolutePath.EndsWith("/", StringComparison.Ordinal)) { response.Redirect(request.Uri.AbsolutePath + "/"); return true; @@ -68,7 +68,7 @@ namespace Duplicati.Server.WebServer if (ForbiddenChars.Where(x => uri.AbsolutePath.Contains(x)).Any()) throw new BadRequestException("Illegal path"); var uripath = Uri.UnescapeDataString(uri.AbsolutePath); - while(uripath.Length > 0 && (uripath.StartsWith("/") || uripath.StartsWith(DirSep))) + while(uripath.Length > 0 && (uripath.StartsWith("/", StringComparison.Ordinal) || uripath.StartsWith(DirSep, StringComparison.Ordinal))) uripath = uripath.Substring(1); return System.IO.Path.Combine(m_webroot, uripath.Replace('/', System.IO.Path.DirectorySeparatorChar)); } diff --git a/Duplicati/Server/WebServer/RESTMethods/Filesystem.cs b/Duplicati/Server/WebServer/RESTMethods/Filesystem.cs index dd44dfe3a..f93f72013 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Filesystem.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Filesystem.cs @@ -48,9 +48,9 @@ namespace Duplicati.Server.WebServer.RESTMethods string specialpath = null; string specialtoken = null; - if (path.StartsWith("%")) + if (path.StartsWith("%", StringComparison.Ordinal)) { - var ix = path.IndexOf("%", 1); + var ix = path.IndexOf("%", 1, StringComparison.Ordinal); if (ix > 0) { var tk = path.Substring(0, ix + 1); @@ -65,7 +65,7 @@ namespace Duplicati.Server.WebServer.RESTMethods path = SpecialFolders.ExpandEnvironmentVariables(path); - if (Duplicati.Library.Utility.Utility.IsClientLinux && !path.StartsWith("/")) + if (Duplicati.Library.Utility.Utility.IsClientLinux && !path.StartsWith("/", StringComparison.Ordinal)) { info.ReportClientError("The path parameter must start with a forward-slash"); return; diff --git a/Duplicati/UnitTest/SVNCheckoutsTest.cs b/Duplicati/UnitTest/SVNCheckoutsTest.cs index 3416c09e0..9cc77c7f5 100644 --- a/Duplicati/UnitTest/SVNCheckoutsTest.cs +++ b/Duplicati/UnitTest/SVNCheckoutsTest.cs @@ -269,7 +269,7 @@ namespace Duplicati.UnitTest //Remove all folders from list for (int j = 0; j < sourcefiles.Count; j++) - if (sourcefiles[j].EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) + if (sourcefiles[j].EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { sourcefiles.RemoveAt(j); j--; @@ -361,7 +361,7 @@ namespace Duplicati.UnitTest continue; if (s == logfilename) continue; - if (s.StartsWith(Utility.AppendDirSeparator(tf))) + if (s.StartsWith(Utility.AppendDirSeparator(tf), StringComparison.Ordinal)) continue; Log.WriteMessage(string.Format("Found left-over temp file: {0}", s.Substring(tempdir.Length)), LogMessageType.Warning); @@ -375,7 +375,7 @@ namespace Duplicati.UnitTest } foreach (string s in Utility.EnumerateFolders(tempdir)) - if (!s.StartsWith(Utility.AppendDirSeparator(tf)) && Utility.AppendDirSeparator(s) != Utility.AppendDirSeparator(tf) && Utility.AppendDirSeparator(s) != Utility.AppendDirSeparator(tempdir)) + if (!s.StartsWith(Utility.AppendDirSeparator(tf), StringComparison.Ordinal) && Utility.AppendDirSeparator(s) != Utility.AppendDirSeparator(tf) && Utility.AppendDirSeparator(s) != Utility.AppendDirSeparator(tempdir)) { Log.WriteMessage(string.Format("Found left-over temp folder: {0}", s.Substring(tempdir.Length)), LogMessageType.Warning); BasicSetupHelper.ProgressWriteLine("Found left-over temp folder: {0}", s.Substring(tempdir.Length)); From 5fb1958cebf8eb7a12e52ed3d7e9c7838f5b3f3c Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sun, 26 Nov 2017 15:24:53 -0800 Subject: [PATCH 17/40] Remove redundant calls to ToString. --- Duplicati/CommandLine/BackendTester/Program.cs | 10 +++++----- Duplicati/CommandLine/BackendTool/Program.cs | 2 +- Duplicati/CommandLine/Program.cs | 2 +- Duplicati/CommandLine/RecoveryTool/Download.cs | 2 +- Duplicati/CommandLine/RecoveryTool/Index.cs | 2 +- Duplicati/CommandLine/RecoveryTool/Program.cs | 2 +- Duplicati/CommandLine/RecoveryTool/Recompress.cs | 2 +- Duplicati/CommandLine/RecoveryTool/Restore.cs | 6 +++--- .../GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs | 8 ++++---- Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs | 8 ++++---- Duplicati/Library/AutoUpdater/UpdaterManager.cs | 2 +- Duplicati/Library/Backend/CloudFiles/CloudFiles.cs | 2 +- Duplicati/Library/Backend/Jottacloud/Jottacloud.cs | 2 +- Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs | 2 +- Duplicati/Library/Backend/OneDrive/OneDrive.cs | 2 +- Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs | 2 +- Duplicati/Library/Backend/Sia/Sia.cs | 2 +- Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs | 2 +- Duplicati/Library/Backend/WEBDAV/WEBDAV.cs | 2 +- Duplicati/Library/Logging/StreamLog.cs | 2 +- Duplicati/Library/Main/BackendManager.cs | 2 +- Duplicati/Library/Main/Database/LocalDatabase.cs | 2 +- .../Library/Main/Database/LocalRestoreDatabase.cs | 2 +- Duplicati/Library/Modules/Builtin/RunScript.cs | 6 +++--- Duplicati/Library/Modules/Builtin/SendJabberMessage.cs | 2 +- Duplicati/Library/Snapshots/Program.cs | 4 ++-- Duplicati/Library/Utility/FilterExpression.cs | 2 +- Duplicati/Library/Utility/JoinedFilterExpression.cs | 2 +- Duplicati/Server/Database/Connection.cs | 8 ++++---- Duplicati/Server/Program.cs | 4 ++-- Duplicati/Server/WebServer/RESTHandler.cs | 2 +- Duplicati/Server/WebServer/RESTMethods/CommandLine.cs | 2 +- Duplicati/UnitTest/SVNCheckoutsTest.cs | 2 +- 33 files changed, 52 insertions(+), 52 deletions(-) diff --git a/Duplicati/CommandLine/BackendTester/Program.cs b/Duplicati/CommandLine/BackendTester/Program.cs index 47a578d3e..fc0e3a679 100644 --- a/Duplicati/CommandLine/BackendTester/Program.cs +++ b/Duplicati/CommandLine/BackendTester/Program.cs @@ -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 diff --git a/Duplicati/CommandLine/BackendTool/Program.cs b/Duplicati/CommandLine/BackendTool/Program.cs index 2f8054575..ca25f403b 100644 --- a/Duplicati/CommandLine/BackendTool/Program.cs +++ b/Duplicati/CommandLine/BackendTool/Program.cs @@ -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; } } diff --git a/Duplicati/CommandLine/Program.cs b/Duplicati/CommandLine/Program.cs index 3d6239d9e..a9ce8e319 100644 --- a/Duplicati/CommandLine/Program.cs +++ b/Duplicati/CommandLine/Program.cs @@ -270,7 +270,7 @@ namespace Duplicati.CommandLine else if (!(ex is Library.Interface.CancelException)) { errwriter.WriteLine(); - errwriter.WriteLine(ex.ToString()); + errwriter.WriteLine(ex); } else { diff --git a/Duplicati/CommandLine/RecoveryTool/Download.cs b/Duplicati/CommandLine/RecoveryTool/Download.cs index 2a22a36ac..28998f7c1 100644 --- a/Duplicati/CommandLine/RecoveryTool/Download.cs +++ b/Duplicati/CommandLine/RecoveryTool/Download.cs @@ -122,7 +122,7 @@ namespace Duplicati.CommandLine.RecoveryTool } catch(Exception ex) { - Console.WriteLine(" error: {0}", ex.ToString()); + Console.WriteLine(" error: {0}", ex); errors++; } diff --git a/Duplicati/CommandLine/RecoveryTool/Index.cs b/Duplicati/CommandLine/RecoveryTool/Index.cs index 6b3eb1540..1594b0d00 100644 --- a/Duplicati/CommandLine/RecoveryTool/Index.cs +++ b/Duplicati/CommandLine/RecoveryTool/Index.cs @@ -123,7 +123,7 @@ namespace Duplicati.CommandLine.RecoveryTool } catch(Exception ex) { - Console.WriteLine(" error: {0}", ex.ToString()); + Console.WriteLine(" error: {0}", ex); errors++; } diff --git a/Duplicati/CommandLine/RecoveryTool/Program.cs b/Duplicati/CommandLine/RecoveryTool/Program.cs index e37eda13e..25936598a 100644 --- a/Duplicati/CommandLine/RecoveryTool/Program.cs +++ b/Duplicati/CommandLine/RecoveryTool/Program.cs @@ -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; } } diff --git a/Duplicati/CommandLine/RecoveryTool/Recompress.cs b/Duplicati/CommandLine/RecoveryTool/Recompress.cs index 21dddd731..ddd414957 100644 --- a/Duplicati/CommandLine/RecoveryTool/Recompress.cs +++ b/Duplicati/CommandLine/RecoveryTool/Recompress.cs @@ -261,7 +261,7 @@ namespace Duplicati.CommandLine.RecoveryTool } catch (Exception ex) { - Console.WriteLine(" error: {0}", ex.ToString()); + Console.WriteLine(" error: {0}", ex); errors++; } } diff --git a/Duplicati/CommandLine/RecoveryTool/Restore.cs b/Duplicati/CommandLine/RecoveryTool/Restore.cs index 8a357c941..3ccf05fdf 100644 --- a/Duplicati/CommandLine/RecoveryTool/Restore.cs +++ b/Duplicati/CommandLine/RecoveryTool/Restore.cs @@ -193,7 +193,7 @@ namespace Duplicati.CommandLine.RecoveryTool } 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,7 +201,7 @@ 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++; @@ -233,7 +233,7 @@ namespace Duplicati.CommandLine.RecoveryTool } catch (Exception ex) { - Console.WriteLine(" error: {0}", ex.ToString()); + Console.WriteLine(" error: {0}", ex); errors++; } i++; diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs index 97c9ff33a..b892702ac 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs @@ -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(); diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs index 79324e237..6566bb4cb 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs @@ -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); diff --git a/Duplicati/Library/AutoUpdater/UpdaterManager.cs b/Duplicati/Library/AutoUpdater/UpdaterManager.cs index a1cc4fd1e..fdd9c17fc 100644 --- a/Duplicati/Library/AutoUpdater/UpdaterManager.cs +++ b/Duplicati/Library/AutoUpdater/UpdaterManager.cs @@ -984,7 +984,7 @@ namespace Duplicati.Library.AutoUpdater { try { - Console.WriteLine("Crash! {0}{1}", Environment.NewLine, tex.ToString()); + Console.WriteLine("Crash! {0}{1}", Environment.NewLine, tex); } catch { diff --git a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs index 29cbc2ba1..0eb430a24 100644 --- a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs +++ b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs @@ -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; diff --git a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs index ba1bbd0d5..c54643d36 100644 --- a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs +++ b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs @@ -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; } diff --git a/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs b/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs index 426ede3dc..3547a8a05 100644 --- a/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs +++ b/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs @@ -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; } } diff --git a/Duplicati/Library/Backend/OneDrive/OneDrive.cs b/Duplicati/Library/Backend/OneDrive/OneDrive.cs index f93f6102d..4fe41ca65 100644 --- a/Duplicati/Library/Backend/OneDrive/OneDrive.cs +++ b/Duplicati/Library/Backend/OneDrive/OneDrive.cs @@ -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; diff --git a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs index 415fcd6fe..8e7bd5eb5 100644 --- a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs +++ b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs @@ -322,7 +322,7 @@ 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) diff --git a/Duplicati/Library/Backend/Sia/Sia.cs b/Duplicati/Library/Backend/Sia/Sia.cs index 636c2dc63..de28298c1 100644 --- a/Duplicati/Library/Backend/Sia/Sia.cs +++ b/Duplicati/Library/Backend/Sia/Sia.cs @@ -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; } diff --git a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs index 2c78d77a9..a5906291d 100644 --- a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs +++ b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs @@ -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; } diff --git a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs index d35815f16..bf20fe33f 100644 --- a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs +++ b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs @@ -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; } diff --git a/Duplicati/Library/Logging/StreamLog.cs b/Duplicati/Library/Logging/StreamLog.cs index 314fa0a68..eb3137406 100644 --- a/Duplicati/Library/Logging/StreamLog.cs +++ b/Duplicati/Library/Logging/StreamLog.cs @@ -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(); } } diff --git a/Duplicati/Library/Main/BackendManager.cs b/Duplicati/Library/Main/BackendManager.cs index 82edd14e2..5c2e72cab 100644 --- a/Duplicati/Library/Main/BackendManager.cs +++ b/Duplicati/Library/Main/BackendManager.cs @@ -382,7 +382,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) diff --git a/Duplicati/Library/Main/Database/LocalDatabase.cs b/Duplicati/Library/Main/Database/LocalDatabase.cs index 35da7c843..840d1e11c 100644 --- a/Duplicati/Library/Main/Database/LocalDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDatabase.cs @@ -1198,7 +1198,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(); } } diff --git a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs index 41f034efa..61a217450 100644 --- a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs @@ -165,7 +165,7 @@ namespace Duplicati.Library.Main.Database { m_fileprogtable = null; m_totalprogtable = null; - Console.WriteLine(ex.ToString()); + Console.WriteLine(ex); throw; } } diff --git a/Duplicati/Library/Modules/Builtin/RunScript.cs b/Duplicati/Library/Modules/Builtin/RunScript.cs index 6f9f69cf6..c29a0a4f8 100644 --- a/Duplicati/Library/Modules/Builtin/RunScript.cs +++ b/Duplicati/Library/Modules/Builtin/RunScript.cs @@ -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 { diff --git a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs index e57d3bbf3..586b40c00 100644 --- a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs @@ -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(); }; diff --git a/Duplicati/Library/Snapshots/Program.cs b/Duplicati/Library/Snapshots/Program.cs index d7dab0da1..c6946c7fd 100644 --- a/Duplicati/Library/Snapshots/Program.cs +++ b/Duplicati/Library/Snapshots/Program.cs @@ -151,7 +151,7 @@ Where 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 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"); } diff --git a/Duplicati/Library/Utility/FilterExpression.cs b/Duplicati/Library/Utility/FilterExpression.cs index 35feb0f15..2742798db 100644 --- a/Duplicati/Library/Utility/FilterExpression.cs +++ b/Duplicati/Library/Utility/FilterExpression.cs @@ -358,7 +358,7 @@ namespace Duplicati.Library.Utility first = false; } - combined += "|(" + f.Regexp.ToString() + ")"; + combined += "|(" + f.Regexp + ")"; } } diff --git a/Duplicati/Library/Utility/JoinedFilterExpression.cs b/Duplicati/Library/Utility/JoinedFilterExpression.cs index 9ffaf8b4b..882183077 100644 --- a/Duplicati/Library/Utility/JoinedFilterExpression.cs +++ b/Duplicati/Library/Utility/JoinedFilterExpression.cs @@ -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 + ")"; } } } diff --git a/Duplicati/Server/Database/Connection.cs b/Duplicati/Server/Database/Connection.cs index 4e4de0c8a..bccc2a2ed 100644 --- a/Duplicati/Server/Database/Connection.cs +++ b/Duplicati/Server/Database/Connection.cs @@ -266,8 +266,8 @@ namespace Duplicati.Server.Database p.Value = t; cmd.Parameters.Add(p); } - - cmd.CommandText = @"SELECT ""ID"" FROM ""Backup"" WHERE " + sb.ToString(); + + cmd.CommandText = @"SELECT ""ID"" FROM ""Backup"" WHERE " + sb; return Read(cmd, (rd) => ConvertToInt64(rd, 0)).ToArray(); } @@ -347,8 +347,8 @@ namespace Duplicati.Server.Database p.Value = t; cmd.Parameters.Add(p); } - - cmd.CommandText = @"SELECT ""ID"" FROM ""Schedule"" WHERE " + sb.ToString(); + + cmd.CommandText = @"SELECT ""ID"" FROM ""Schedule"" WHERE " + sb; return Read(cmd, (rd) => ConvertToInt64(rd, 0)).ToArray(); } diff --git a/Duplicati/Server/Program.cs b/Duplicati/Server/Program.cs index 28d116882..a803d7144 100644 --- a/Duplicati/Server/Program.cs +++ b/Duplicati/Server/Program.cs @@ -765,8 +765,8 @@ namespace Duplicati.Server new Duplicati.Library.Interface.CommandLineArgument("log-level", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LoglevelCommandDescription, Strings.Program.LoglevelCommandDescription, "Warning", null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))), new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_WEBROOT, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverWebrootDescription, Strings.Program.WebserverWebrootDescription, Duplicati.Server.WebServer.Server.DEFAULT_OPTION_WEBROOT), new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_PORT, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverPortDescription, Strings.Program.WebserverPortDescription, Duplicati.Server.WebServer.Server.DEFAULT_OPTION_PORT.ToString()), - new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILE, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificateFileDescription, Strings.Program.WebserverCertificateFileDescription, Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILE.ToString()), - new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILEPASSWORD, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificatePasswordDescription, Strings.Program.WebserverCertificatePasswordDescription, Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILEPASSWORD.ToString()), + new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILE, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificateFileDescription, Strings.Program.WebserverCertificateFileDescription, Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILE), + new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILEPASSWORD, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificatePasswordDescription, Strings.Program.WebserverCertificatePasswordDescription, Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILEPASSWORD), new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_INTERFACE, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverInterfaceDescription, Strings.Program.WebserverInterfaceDescription, Duplicati.Server.WebServer.Server.DEFAULT_OPTION_INTERFACE), new Duplicati.Library.Interface.CommandLineArgument("webservice-password", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Password, Strings.Program.WebserverPasswordDescription, Strings.Program.WebserverPasswordDescription), new Duplicati.Library.Interface.CommandLineArgument("ping-pong-keepalive", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.PingpongkeepaliveShort, Strings.Program.PingpongkeepaliveLong), diff --git a/Duplicati/Server/WebServer/RESTHandler.cs b/Duplicati/Server/WebServer/RESTHandler.cs index f006a2711..5c4901572 100644 --- a/Duplicati/Server/WebServer/RESTHandler.cs +++ b/Duplicati/Server/WebServer/RESTHandler.cs @@ -188,7 +188,7 @@ namespace Duplicati.Server.WebServer catch(Exception ex) { Program.DataConnection.LogError("", string.Format("Request for {0} gave error", info.Request.Uri), ex); - Console.WriteLine(ex.ToString()); + Console.WriteLine(ex); try { diff --git a/Duplicati/Server/WebServer/RESTMethods/CommandLine.cs b/Duplicati/Server/WebServer/RESTMethods/CommandLine.cs index 72ccc6bd0..addeed592 100644 --- a/Duplicati/Server/WebServer/RESTMethods/CommandLine.cs +++ b/Duplicati/Server/WebServer/RESTMethods/CommandLine.cs @@ -62,7 +62,7 @@ namespace Duplicati.Server.WebServer.RESTMethods if (m_sb.Length != 0) { - m_target.Log.Add(m_sb.ToString() + value); + m_target.Log.Add(m_sb + value); m_sb.Length = 0; m_newlinechars = 0; } diff --git a/Duplicati/UnitTest/SVNCheckoutsTest.cs b/Duplicati/UnitTest/SVNCheckoutsTest.cs index 3416c09e0..571afccef 100644 --- a/Duplicati/UnitTest/SVNCheckoutsTest.cs +++ b/Duplicati/UnitTest/SVNCheckoutsTest.cs @@ -240,7 +240,7 @@ namespace Duplicati.UnitTest StringBuilder sb = new StringBuilder(); sb.AppendLine("Entry count: " + entries.Count.ToString()); sb.Append(string.Format("Found {0} filelists but there were {1} source folders", entries.Count, folders.Length)); - throw new Exception("Filename parsing problem, or corrupt storage: " + sb.ToString()); + throw new Exception("Filename parsing problem, or corrupt storage: " + sb); } if (!skipfullrestore || !skippartialrestore) From 46e8065219ca03259d09f128d8953bbaeef10f01 Mon Sep 17 00:00:00 2001 From: tooomm Date: Fri, 1 Dec 2017 23:11:52 +0100 Subject: [PATCH 18/40] fix contributor gallery link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bc771c1a..6cd8b1c36 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ If you are looking for an issue to fix, try looking at one of the [minor change] Thank you to all our existing contributors: - + ## Backers From 1c7391399dc75fb1b3ab75b4bafd414eb0c1bf28 Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sat, 9 Dec 2017 18:05:27 -0800 Subject: [PATCH 19/40] Remove XML documentation for unmatched parameters. --- Duplicati/Library/Backend/SharePoint/SharePointBackend.cs | 1 - Duplicati/Library/Encryption/AESEncryption.cs | 1 - Duplicati/Library/Encryption/GPGEncryption.cs | 1 - Duplicati/Library/Interface/CommandLineArgument.cs | 1 - Duplicati/Library/Interface/IGenericCallbackModule.cs | 1 - Duplicati/Library/Main/Database/LocalBackupDatabase.cs | 4 ---- Duplicati/Library/Main/Database/LocalDatabase.cs | 3 --- Duplicati/Library/Main/Database/LocalDeleteDatabase.cs | 1 - Duplicati/Library/Main/Operation/BackupHandler.cs | 7 ------- Duplicati/Library/Utility/FilterExpression.cs | 1 - Duplicati/Library/Utility/ThrottledStream.cs | 6 ------ Duplicati/Library/Utility/Utility.cs | 1 - Duplicati/Server/LiveControls.cs | 2 -- Duplicati/Server/Scheduler.cs | 2 -- 14 files changed, 32 deletions(-) diff --git a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs index 3c6342488..221fe2488 100644 --- a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs +++ b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs @@ -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... /// - /// the filename private void wrappedExecuteQueryOnConext(SP.ClientContext ctx, string serverRelPathInfo, bool isFolder) { try { ctx.ExecuteQuery(); } diff --git a/Duplicati/Library/Encryption/AESEncryption.cs b/Duplicati/Library/Encryption/AESEncryption.cs index d342e35c1..ba0bd9fcf 100644 --- a/Duplicati/Library/Encryption/AESEncryption.cs +++ b/Duplicati/Library/Encryption/AESEncryption.cs @@ -67,7 +67,6 @@ namespace Duplicati.Library.Encryption /// /// Constructs a new AES encryption/decyption instance /// - /// The key used for encryption. The key gets stretched through SHA hashing to fit the key size requirements public AESEncryption(string passphrase, Dictionary options) { if(string.IsNullOrEmpty(passphrase)) diff --git a/Duplicati/Library/Encryption/GPGEncryption.cs b/Duplicati/Library/Encryption/GPGEncryption.cs index ac1bfa597..03d1ca5db 100644 --- a/Duplicati/Library/Encryption/GPGEncryption.cs +++ b/Duplicati/Library/Encryption/GPGEncryption.cs @@ -222,7 +222,6 @@ namespace Duplicati.Library.Encryption /// /// The commandline arguments /// The input stream - /// The output stream private System.IO.Stream Execute(string args, System.IO.Stream input, bool encrypt) { System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(); diff --git a/Duplicati/Library/Interface/CommandLineArgument.cs b/Duplicati/Library/Interface/CommandLineArgument.cs index 7931019ba..fad82bf6b 100644 --- a/Duplicati/Library/Interface/CommandLineArgument.cs +++ b/Duplicati/Library/Interface/CommandLineArgument.cs @@ -221,7 +221,6 @@ namespace Duplicati.Library.Interface /// The arguments long description /// The default value of the argumen /// A list of aliases for the command - /// A list of valid values for the command public CommandLineArgument(string name, ArgumentType type, string shortDescription, string longDescription, string defaultValue, string[] aliases) : this(name, type, shortDescription, longDescription, defaultValue) { diff --git a/Duplicati/Library/Interface/IGenericCallbackModule.cs b/Duplicati/Library/Interface/IGenericCallbackModule.cs index dcdf6690d..aba4010f8 100644 --- a/Duplicati/Library/Interface/IGenericCallbackModule.cs +++ b/Duplicati/Library/Interface/IGenericCallbackModule.cs @@ -32,7 +32,6 @@ namespace Duplicati.Library.Interface /// /// Called when the operation finishes /// - /// The full name of the operation /// The result object, if this derives from an exception, the operation failed void OnFinish(object result); } diff --git a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs index d4d7bea62..0a49feb05 100644 --- a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs @@ -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 /// /// The block key - /// The name of the archive that holds the data /// True if the block should be added to the current output public bool AddBlock (string key, long size, long volumeid, System.Data.IDbTransaction transaction = null) { @@ -319,8 +318,6 @@ namespace Duplicati.Library.Main.Database /// /// The hash of the blockset /// The size of the blockset - /// The fragmentoffset for the last block - /// The hash of the fragment /// The list of hashes /// The id of the blockset, new or old /// True if the blockset was created, false otherwise @@ -436,7 +433,6 @@ namespace Duplicati.Library.Main.Database /// The ID of the hashkey for the file /// The ID for the metadata /// The transaction to use for insertion, or null for no transaction - /// The operationId to use, or -1 to use the current operation public void AddFile(string filename, DateTime lastmodified, long blocksetID, long metadataID, System.Data.IDbTransaction transaction) { var fileidobj = -1L; diff --git a/Duplicati/Library/Main/Database/LocalDatabase.cs b/Duplicati/Library/Main/Database/LocalDatabase.cs index 840d1e11c..6ffe4bd35 100644 --- a/Duplicati/Library/Main/Database/LocalDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDatabase.cs @@ -63,8 +63,6 @@ namespace Duplicati.Library.Main.Database /// /// Creates a new database instance and starts a new operation /// - /// The path to the database - /// The name of the operation public LocalDatabase(LocalDatabase db) : this(db.m_connection) { @@ -77,7 +75,6 @@ namespace Duplicati.Library.Main.Database /// /// Creates a new database instance and starts a new operation /// - /// The path to the database /// The name of the operation public LocalDatabase(System.Data.IDbConnection connection, string operation) : this(connection) diff --git a/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs b/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs index c2d179e80..28f4f5b4c 100644 --- a/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs @@ -304,7 +304,6 @@ namespace Duplicati.Library.Main.Database /// /// Builds a lookup table to enable faster response to block queries /// - /// The name of the volume to prepare for public IBlockQuery CreateBlockQueryHelper(Options options, System.Data.IDbTransaction transaction) { return new BlockQuery(m_connection, options, transaction); diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs index 226a9f5c5..670a05e26 100644 --- a/Duplicati/Library/Main/Operation/BackupHandler.cs +++ b/Duplicati/Library/Main/Operation/BackupHandler.cs @@ -1339,9 +1339,6 @@ namespace Duplicati.Library.Main.Operation /// /// The name of the file to record /// The value of the lastModified timestamp - /// The list of hashes that make up the file - /// The size of the file - /// The offset into a fragment block where the last few bytes are stored /// A lookup table with various metadata values describing the file private void AddFolderToOutput(BackendManager backend, string filename, DateTime lastModified, IMetahash metadata) { @@ -1354,9 +1351,6 @@ namespace Duplicati.Library.Main.Operation /// /// The name of the file to record /// The value of the lastModified timestamp - /// The list of hashes that make up the file - /// The size of the file - /// The offset into a fragment block where the last few bytes are stored /// A lookup table with various metadata values describing the file private void AddSymlinkToOutput(BackendManager backend, string filename, DateTime lastModified, IMetahash metadata) { @@ -1371,7 +1365,6 @@ namespace Duplicati.Library.Main.Operation /// The value of the lastModified timestamp /// The list of hashes that make up the file /// The size of the file - /// The offset into a fragment block where the last few bytes are stored /// A lookup table with various metadata values describing the file private void AddFileToOutput(BackendManager backend, string filename, long size, DateTime lastmodified, IMetahash metadata, IEnumerable hashlist, string filehash, IEnumerable blocklisthashes) { diff --git a/Duplicati/Library/Utility/FilterExpression.cs b/Duplicati/Library/Utility/FilterExpression.cs index 10736471e..a9ab54409 100644 --- a/Duplicati/Library/Utility/FilterExpression.cs +++ b/Duplicati/Library/Utility/FilterExpression.cs @@ -273,7 +273,6 @@ namespace Duplicati.Library.Utility /// /// Creates a new instance, representing an empty filter. /// - /// The filter string that represents the filter public FilterExpression() : this((IEnumerable)null, true) { diff --git a/Duplicati/Library/Utility/ThrottledStream.cs b/Duplicati/Library/Utility/ThrottledStream.cs index 7da95e996..a7345cae2 100644 --- a/Duplicati/Library/Utility/ThrottledStream.cs +++ b/Duplicati/Library/Utility/ThrottledStream.cs @@ -92,8 +92,6 @@ namespace Duplicati.Library.Utility /// Creates a throttle around a stream. /// /// The stream to throttle - /// The maximum number of bytes pr. second to read. Specify a number less than 1 to allow unlimited speed. - /// The maximum number of bytes pr. second to write. Specify a number less than 1 to allow unlimited speed. public ThrottledStream(Stream basestream, Action updateLimits) : base(basestream) { @@ -217,10 +215,6 @@ namespace Duplicati.Library.Utility /// /// Calculates the speed, and inserts appropriate delays /// - /// True if the operation is read, false otherwise - /// The data buffer - /// The offset into the buffer - /// The number of bytes to read or write /// The number of bytes processed while delaying private void DelayIfRequired(ref long limit, int count, ref DateTime last_sample, ref long last_count, ref double current_speed) { diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 6ae8b8d53..6486f43a2 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -132,7 +132,6 @@ namespace Duplicati.Library.Utility /// The search is recursive. /// /// The folder to look in - /// The filter to apply. /// A list of the full filenames public static IEnumerable EnumerateFiles(string basepath) { diff --git a/Duplicati/Server/LiveControls.cs b/Duplicati/Server/LiveControls.cs index 32e600ee7..cafbfa6de 100644 --- a/Duplicati/Server/LiveControls.cs +++ b/Duplicati/Server/LiveControls.cs @@ -163,7 +163,6 @@ namespace Duplicati.Server /// /// Constructs a new instance of the LiveControl /// - /// The duration that the backups should be initially suspended public LiveControls(Database.ServerSettings settings) { m_state = LiveControlState.Running; @@ -216,7 +215,6 @@ namespace Duplicati.Server /// Event that occurs when the timeout duration is exceeded /// /// The sender of the event - /// An unused event argument private void m_waitTimer_Tick(object sender) { lock (m_lock) diff --git a/Duplicati/Server/Scheduler.cs b/Duplicati/Server/Scheduler.cs index 5db794ba7..bc3912810 100644 --- a/Duplicati/Server/Scheduler.cs +++ b/Duplicati/Server/Scheduler.cs @@ -73,9 +73,7 @@ namespace Duplicati.Server /// /// Constructs a new scheduler /// - /// The database connection /// The worker thread - /// The database lock object public Scheduler(WorkerThread worker) { m_thread = new Thread(new ThreadStart(Runner)); From ab489d9a2856279d98e516691261dade502b18e8 Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sat, 9 Dec 2017 18:11:34 -0800 Subject: [PATCH 20/40] Make parameter names in XML documentation match code. --- .../DynamicLoader/CompressionLoader.cs | 2 +- .../Library/DynamicLoader/EncryptionLoader.cs | 2 +- .../Main/Database/LocalBackupDatabase.cs | 2 +- .../Library/Main/Operation/BackupHandler.cs | 2 +- .../Library/Snapshots/ISnapshotService.cs | 2 +- Duplicati/Library/Snapshots/LinuxSnapshot.cs | 4 +-- Duplicati/Library/Snapshots/NoSnapshot.cs | 4 +-- .../Library/Snapshots/NoSnapshotLinux.cs | 2 +- .../Library/Snapshots/NoSnapshotWindows.cs | 2 +- Duplicati/Library/Snapshots/Win32USN.cs | 30 +++++++++---------- Duplicati/Library/Utility/Utility.cs | 2 +- 11 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Duplicati/Library/DynamicLoader/CompressionLoader.cs b/Duplicati/Library/DynamicLoader/CompressionLoader.cs index 70db8bf1b..085e80a4f 100644 --- a/Duplicati/Library/DynamicLoader/CompressionLoader.cs +++ b/Duplicati/Library/DynamicLoader/CompressionLoader.cs @@ -129,7 +129,7 @@ namespace Duplicati.Library.DynamicLoader /// /// Instanciates a specific compression module, given the file extension and options /// - /// The file extension to create the instance for + /// The file extension to create the instance for /// The filename of the file used to compress/decompress contents /// The options to pass to the instance constructor /// The instanciated compression module or null if the file extension is not supported diff --git a/Duplicati/Library/DynamicLoader/EncryptionLoader.cs b/Duplicati/Library/DynamicLoader/EncryptionLoader.cs index cd6e387c9..dacf9087d 100644 --- a/Duplicati/Library/DynamicLoader/EncryptionLoader.cs +++ b/Duplicati/Library/DynamicLoader/EncryptionLoader.cs @@ -132,7 +132,7 @@ namespace Duplicati.Library.DynamicLoader /// /// Instanciates a specific encryption module, given the file extension and options /// - /// The file extension to create the instance for + /// The file extension to create the instance for /// The passphrase used to encrypt contents /// The options to pass to the instance constructor /// The instanciated encryption module or null if the file extension is not supported diff --git a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs index 0a49feb05..45450e2a5 100644 --- a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs @@ -393,7 +393,7 @@ namespace Duplicati.Library.Main.Database /// /// Adds a metadata set to the database, and returns a value indicating if the record was new /// - /// The metadata hash + /// The metadata hash /// The id of the metadata set /// True if the set was added to the database, false otherwise public bool AddMetadataset(string filehash, long size, int blocksize, IEnumerable blockhashes, IEnumerable blocklisthashes, out long metadataid, System.Data.IDbTransaction transaction = null) diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs index 670a05e26..1bf05ab9b 100644 --- a/Duplicati/Library/Main/Operation/BackupHandler.cs +++ b/Duplicati/Library/Main/Operation/BackupHandler.cs @@ -1362,7 +1362,7 @@ namespace Duplicati.Library.Main.Operation /// Adds a file to the output, /// /// The name of the file to record - /// The value of the lastModified timestamp + /// The value of the lastModified timestamp /// The list of hashes that make up the file /// The size of the file /// A lookup table with various metadata values describing the file diff --git a/Duplicati/Library/Snapshots/ISnapshotService.cs b/Duplicati/Library/Snapshots/ISnapshotService.cs index 4e8d5c20c..db43b7d47 100644 --- a/Duplicati/Library/Snapshots/ISnapshotService.cs +++ b/Duplicati/Library/Snapshots/ISnapshotService.cs @@ -97,7 +97,7 @@ namespace Duplicati.Library.Snapshots /// Gets a unique hardlink target ID /// /// The hardlink ID - /// The file or folder to examine + /// The file or folder to examine string HardlinkTargetID(string path); } } diff --git a/Duplicati/Library/Snapshots/LinuxSnapshot.cs b/Duplicati/Library/Snapshots/LinuxSnapshot.cs index a82b64748..714ea00ef 100644 --- a/Duplicati/Library/Snapshots/LinuxSnapshot.cs +++ b/Duplicati/Library/Snapshots/LinuxSnapshot.cs @@ -261,7 +261,7 @@ namespace Duplicati.Library.Snapshots /// /// Constructs a new snapshot module using LVM /// - /// The list of folders to create snapshots for + /// The list of folders to create snapshots for /// A set of commandline options public LinuxSnapshot(string[] sources, Dictionary options) { @@ -494,7 +494,7 @@ namespace Duplicati.Library.Snapshots /// Gets a unique hardlink target ID /// /// The hardlink ID - /// The file or folder to examine + /// The file or folder to examine public string HardlinkTargetID(string path) { var local = ConvertToSnapshotPath(FindSnapShotByLocalPath(path), path); diff --git a/Duplicati/Library/Snapshots/NoSnapshot.cs b/Duplicati/Library/Snapshots/NoSnapshot.cs index 7c4f7c5de..994018154 100644 --- a/Duplicati/Library/Snapshots/NoSnapshot.cs +++ b/Duplicati/Library/Snapshots/NoSnapshot.cs @@ -40,7 +40,7 @@ namespace Duplicati.Library.Snapshots /// /// Constructs a new backup snapshot, using all the required disks /// - /// The folders that are about to be backed up + /// The folders that are about to be backed up public NoSnapshot(string[] sources) : this(sources, new Dictionary()) { @@ -49,7 +49,7 @@ namespace Duplicati.Library.Snapshots /// /// Constructs a new backup snapshot, using all the required disks /// - /// The folders that are about to be backed up + /// The folders that are about to be backed up /// A set of system options public NoSnapshot(string[] sources, Dictionary options) { diff --git a/Duplicati/Library/Snapshots/NoSnapshotLinux.cs b/Duplicati/Library/Snapshots/NoSnapshotLinux.cs index a6d18c3b7..b4e8dc79a 100644 --- a/Duplicati/Library/Snapshots/NoSnapshotLinux.cs +++ b/Duplicati/Library/Snapshots/NoSnapshotLinux.cs @@ -83,7 +83,7 @@ namespace Duplicati.Library.Snapshots /// Gets a unique hardlink target ID /// /// The hardlink ID - /// The file or folder to examine + /// The file or folder to examine public override string HardlinkTargetID(string path) { path = NormalizePath(path); diff --git a/Duplicati/Library/Snapshots/NoSnapshotWindows.cs b/Duplicati/Library/Snapshots/NoSnapshotWindows.cs index e708042ac..3aa2fa3e1 100644 --- a/Duplicati/Library/Snapshots/NoSnapshotWindows.cs +++ b/Duplicati/Library/Snapshots/NoSnapshotWindows.cs @@ -167,7 +167,7 @@ namespace Duplicati.Library.Snapshots /// Gets a unique hardlink target ID /// /// The hardlink ID - /// The file or folder to examine + /// The file or folder to examine public override string HardlinkTargetID(string path) { return null; diff --git a/Duplicati/Library/Snapshots/Win32USN.cs b/Duplicati/Library/Snapshots/Win32USN.cs index e9ff53a6d..19479e0fb 100644 --- a/Duplicati/Library/Snapshots/Win32USN.cs +++ b/Duplicati/Library/Snapshots/Win32USN.cs @@ -526,13 +526,13 @@ namespace Duplicati.Library.Snapshots /// Sends the dwIoControlCode to the device specified by hDevice. /// /// Safe handle to the device - /// Device IO Control Code to send - /// Input buffer if required + /// Device IO Control Code to send + /// Input buffer if required /// Size of input buffer - /// Output buffer if required + /// Output buffer if required /// Size of output buffer - /// Number of bytes returned in output buffer - /// IntPtr to an 'OVERLAPPED' structure + /// Number of bytes returned in output buffer + /// IntPtr to an 'OVERLAPPED' structure /// 'true' if successful, otherwise 'false' [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. /// /// Safe handle to the device - /// Device IO Control Code to send - /// Input buffer if required + /// Device IO Control Code to send + /// Input buffer if required /// Size of input buffer - /// Output buffer if required + /// Output buffer if required /// Size of output buffer - /// Number of bytes returned in output buffer - /// IntPtr to an 'OVERLAPPED' structure + /// Number of bytes returned in output buffer + /// IntPtr to an 'OVERLAPPED' structure /// 'true' if successful, otherwise 'false' [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. /// /// Safe handle to the device - /// Device IO Control Code to send - /// Input buffer if required + /// Device IO Control Code to send + /// Input buffer if required /// Size of input buffer - /// Output buffer if required + /// Output buffer if required /// Size of output buffer - /// Number of bytes returned in output buffer - /// IntPtr to an 'OVERLAPPED' structure + /// Number of bytes returned in output buffer + /// IntPtr to an 'OVERLAPPED' structure /// 'true' if successful, otherwise 'false' [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool DeviceIoControl( diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 6486f43a2..7b021c433 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -546,7 +546,7 @@ namespace Duplicati.Library.Utility /// /// Calculates the hash of a given stream, and returns the results as an base64 encoded string /// - /// The stream to calculate the hash for + /// The stream to calculate the hash for /// The base64 encoded hash public static string CalculateHash(System.IO.Stream stream) { From aac3bc5d87305c32998a1fd619e9b47f4a50feab Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sat, 9 Dec 2017 18:14:03 -0800 Subject: [PATCH 21/40] Remove invalid XML documentation element for void method. --- Duplicati/Library/Utility/ThrottledStream.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Duplicati/Library/Utility/ThrottledStream.cs b/Duplicati/Library/Utility/ThrottledStream.cs index a7345cae2..a82669aa6 100644 --- a/Duplicati/Library/Utility/ThrottledStream.cs +++ b/Duplicati/Library/Utility/ThrottledStream.cs @@ -215,7 +215,6 @@ namespace Duplicati.Library.Utility /// /// Calculates the speed, and inserts appropriate delays /// - /// The number of bytes processed while delaying private void DelayIfRequired(ref long limit, int count, ref DateTime last_sample, ref long last_count, ref double current_speed) { var now = DateTime.Now; From 5ecd3815923e2e750bfd010eacc7a85c5e5979b5 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Mon, 11 Dec 2017 00:23:53 +0100 Subject: [PATCH 22/40] Update Strings.cs Updated the help text for the snapshot policy --- Duplicati/Library/Main/Strings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Library/Main/Strings.cs b/Duplicati/Library/Main/Strings.cs index 39b0f27ad..7f774ea91 100644 --- a/Duplicati/Library/Main/Strings.cs +++ b/Duplicati/Library/Main/Strings.cs @@ -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"); } } From c6f88377db78062a1e0c49384563a2890b6a4519 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Mon, 11 Dec 2017 00:24:16 +0100 Subject: [PATCH 23/40] Update Strings.cs Updated the help text for the snapshot policy From 7cf04ea5d0fa42b66b062c195500aa412a8ba8aa Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sat, 16 Dec 2017 09:42:45 -0800 Subject: [PATCH 24/40] Remove unused format string argument. The location of the assembly is already being reported as the "Current Version folder". --- Duplicati/Library/Main/Operation/SystemInfoHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Library/Main/Operation/SystemInfoHandler.cs b/Duplicati/Library/Main/Operation/SystemInfoHandler.cs index 2eb04da8e..56fd95bbd 100644 --- a/Duplicati/Library/Main/Operation/SystemInfoHandler.cs +++ b/Duplicati/Library/Main/Operation/SystemInfoHandler.cs @@ -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); From 9b9d9b129757030245e93e601a21a7d19bea2b2b Mon Sep 17 00:00:00 2001 From: vnau Date: Sun, 17 Dec 2017 01:34:21 +0700 Subject: [PATCH 25/40] Fixed compiler warnings. --- .../GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs | 4 +- Duplicati/Library/Backend/Dropbox/Dropbox.cs | 6 +- Duplicati/Library/Main/DatabaseLocator.cs | 1 - Duplicati/Library/Main/ResultClasses.cs | 243 +++++++++--------- .../Library/UsageReporter/OSInfoHelper.cs | 1 - .../SynologyAuthenticationHandler.cs | 4 +- 6 files changed, 129 insertions(+), 130 deletions(-) diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs index 1b3426749..958606ba3 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs @@ -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)})); diff --git a/Duplicati/Library/Backend/Dropbox/Dropbox.cs b/Duplicati/Library/Backend/Dropbox/Dropbox.cs index bf252d1e6..281395868 100644 --- a/Duplicati/Library/Backend/Dropbox/Dropbox.cs +++ b/Duplicati/Library/Backend/Dropbox/Dropbox.cs @@ -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; diff --git a/Duplicati/Library/Main/DatabaseLocator.cs b/Duplicati/Library/Main/DatabaseLocator.cs index c2d54616f..1d38c22a4 100644 --- a/Duplicati/Library/Main/DatabaseLocator.cs +++ b/Duplicati/Library/Main/DatabaseLocator.cs @@ -217,7 +217,6 @@ namespace Duplicati.Library.Main return false; var file = System.IO.Path.Combine(folder, "dbconfig.json"); - List configs; if (!System.IO.File.Exists(file)) return false; diff --git a/Duplicati/Library/Main/ResultClasses.cs b/Duplicati/Library/Main/ResultClasses.cs index 97d7eed70..5e4f1a830 100644 --- a/Duplicati/Library/Main/ResultClasses.cs +++ b/Duplicati/Library/Main/ResultClasses.cs @@ -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 /// Path to the resource /// Size of the file or progress void SendEvent(BackendActionType action, BackendEventType type, string path, long size); - + /// /// Gets the backend progress updater. /// /// The backend progress updater. 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 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; @@ -423,7 +424,7 @@ namespace Duplicati.Library.Main m_parent.AddVerboseMessage(message, args); else { - lock(Logging.Log.Lock) + lock (Logging.Log.Lock) { if (m_is_reporting) return; @@ -445,12 +446,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; @@ -465,7 +466,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); } @@ -483,7 +484,7 @@ namespace Duplicati.Library.Main m_parent.AddWarning(message, ex); else { - lock(Logging.Log.Lock) + lock (Logging.Log.Lock) { if (m_is_reporting) return; @@ -499,7 +500,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); } @@ -517,7 +518,7 @@ namespace Duplicati.Library.Main m_parent.AddRetryAttempt(message, ex); else { - lock(Logging.Log.Lock) + lock (Logging.Log.Lock) { if (m_is_reporting) return; @@ -533,7 +534,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); } @@ -551,7 +552,7 @@ namespace Duplicati.Library.Main m_parent.AddError(message, ex); else { - lock(Logging.Log.Lock) + lock (Logging.Log.Lock) { if (m_is_reporting) return; @@ -567,7 +568,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); } @@ -578,14 +579,14 @@ namespace Duplicati.Library.Main } } } - + public IEnumerable Messages { get { return m_messages; } } public IEnumerable Warnings { get { return m_warnings; } } public IEnumerable 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(); @@ -599,27 +600,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 StateChangedEvent; - + /// /// Request that this task pauses. /// @@ -640,7 +641,7 @@ namespace Duplicati.Library.Main StateChangedEvent(m_controlState); } } - + /// /// Request that this task resumes. /// @@ -661,11 +662,11 @@ namespace Duplicati.Library.Main StateChangedEvent(m_controlState); } } - + /// /// Request that this task stops. /// - public void Stop() + public void Stop() { if (m_parent != null) m_parent.Stop(); @@ -682,7 +683,7 @@ namespace Duplicati.Library.Main StateChangedEvent(m_controlState); } } - + /// /// Request that this task aborts. /// @@ -702,7 +703,7 @@ namespace Duplicati.Library.Main StateChangedEvent(m_controlState); } } - + /// /// Helper method that the current running task can call to obtain the current state /// @@ -746,7 +747,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) && @@ -849,11 +850,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; } @@ -864,9 +865,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 @@ -908,7 +909,7 @@ namespace Duplicati.Library.Main this.FileSizes = fileSizes; } } - + internal class ListResults : BasicResults, Duplicati.Library.Interface.IListResults { private IEnumerable m_filesets; @@ -920,10 +921,10 @@ namespace Duplicati.Library.Main m_filesets = filesets; m_files = files; } - + public IEnumerable Filesets { get { return m_filesets; } } public IEnumerable Files { get { return m_files; } } - + public override OperationMode MainOperation { get { return OperationMode.List; } } } @@ -941,7 +942,7 @@ namespace Duplicati.Library.Main m_logs = logs; m_volumes = volumes; } - + public IEnumerable Filesets { get { return m_filesets; } } public IEnumerable Files { get { return m_files; } } public IEnumerable LogMessages { get { return m_logs; } } @@ -954,23 +955,23 @@ namespace Duplicati.Library.Main { public IEnumerable> DeletedSets { get; private set; } public bool Dryrun { get; private set; } - + public void SetResults(IEnumerable> 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) @@ -982,33 +983,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 Files { get; private set; } - + public override OperationMode MainOperation { get { return OperationMode.RestoreControlfiles; } } public void SetResult(IEnumerable files) { this.Files = files; } - } + } internal class ListRemoteResults : BasicResults, Library.Interface.IListRemoteResults { @@ -1021,12 +1022,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; } @@ -1038,22 +1039,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> ChangeDetails { get; internal set; } - + + public IEnumerable> ChangeDetails { get; internal set; } + public long AddedFolders { get; internal set; } public long AddedSymlinks { get; internal set; } public long AddedFiles { get; internal set; } @@ -1065,13 +1066,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, @@ -1085,38 +1086,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>>> Verifications { get { return m_verifications; } } private List>>> m_verifications = new List>>>(); - + public KeyValuePair>> AddResult(string volume, IEnumerable> changes) { var res = new KeyValuePair>>(volume, changes); @@ -1124,7 +1125,7 @@ namespace Duplicati.Library.Main return res; } } - + internal class TestFilterResults : BasicResults, ITestFilterResults { public long FileCount { get; set; } diff --git a/Duplicati/Library/UsageReporter/OSInfoHelper.cs b/Duplicati/Library/UsageReporter/OSInfoHelper.cs index e394e2f1d..fa89fd42e 100644 --- a/Duplicati/Library/UsageReporter/OSInfoHelper.cs +++ b/Duplicati/Library/UsageReporter/OSInfoHelper.cs @@ -154,7 +154,6 @@ namespace Duplicati.Library.UsageReporter return RunProgramAndReadOutput("uname", "-srvmpio"); } - return null; } } } diff --git a/Duplicati/Server/WebServer/SynologyAuthenticationHandler.cs b/Duplicati/Server/WebServer/SynologyAuthenticationHandler.cs index 37c49f084..f8c7d960d 100644 --- a/Duplicati/Server/WebServer/SynologyAuthenticationHandler.cs +++ b/Duplicati/Server/WebServer/SynologyAuthenticationHandler.cs @@ -154,7 +154,7 @@ namespace Duplicati.Server.WebServer else throw new Exception("Unable to get XSRF token"); } - catch (Exception ex) + catch (Exception) { response.Status = System.Net.HttpStatusCode.InternalServerError; response.Reason = "The system is incorrectly configured"; @@ -175,7 +175,7 @@ namespace Duplicati.Server.WebServer { username = ShellExec(AUTH_CGI, shell: false, exitcode: 0, env: tmpenv).Result; } - catch (Exception ex) + catch (Exception) { response.Status = System.Net.HttpStatusCode.InternalServerError; response.Reason = "The system is incorrectly configured"; From 01ec66cdc56e95890457305391cbba3e68be0a72 Mon Sep 17 00:00:00 2001 From: vnau Date: Sun, 17 Dec 2017 01:50:17 +0700 Subject: [PATCH 26/40] Removed AutoGenerateBindingRedirects compile warnings. --- .../BackendTester/Duplicati.CommandLine.BackendTester.csproj | 1 + .../BackendTool/Duplicati.CommandLine.BackendTool.csproj | 1 + Duplicati/CommandLine/Duplicati.CommandLine.csproj | 1 + .../RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj | 1 + .../GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj | 1 + Duplicati/Server/Duplicati.Server.csproj | 1 + 6 files changed, 6 insertions(+) diff --git a/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj b/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj index f1960f082..c3f4f7e19 100644 --- a/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj +++ b/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj @@ -18,6 +18,7 @@ v4.5 false + true true diff --git a/Duplicati/CommandLine/BackendTool/Duplicati.CommandLine.BackendTool.csproj b/Duplicati/CommandLine/BackendTool/Duplicati.CommandLine.BackendTool.csproj index 089ff1686..e92114664 100644 --- a/Duplicati/CommandLine/BackendTool/Duplicati.CommandLine.BackendTool.csproj +++ b/Duplicati/CommandLine/BackendTool/Duplicati.CommandLine.BackendTool.csproj @@ -12,6 +12,7 @@ v4.5 false + true true diff --git a/Duplicati/CommandLine/Duplicati.CommandLine.csproj b/Duplicati/CommandLine/Duplicati.CommandLine.csproj index a873c0467..b14f49867 100644 --- a/Duplicati/CommandLine/Duplicati.CommandLine.csproj +++ b/Duplicati/CommandLine/Duplicati.CommandLine.csproj @@ -19,6 +19,7 @@ v4.5 false + true true diff --git a/Duplicati/CommandLine/RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj b/Duplicati/CommandLine/RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj index 892ed39f2..5b3bcc943 100644 --- a/Duplicati/CommandLine/RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj +++ b/Duplicati/CommandLine/RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj @@ -11,6 +11,7 @@ Duplicati.snk v4.5 false + true true diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj index 2c1366d95..f07637060 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj @@ -28,6 +28,7 @@ v4.5 false + true bin\Debug diff --git a/Duplicati/Server/Duplicati.Server.csproj b/Duplicati/Server/Duplicati.Server.csproj index bc5c3a49f..2328b2599 100644 --- a/Duplicati/Server/Duplicati.Server.csproj +++ b/Duplicati/Server/Duplicati.Server.csproj @@ -13,6 +13,7 @@ v4.5 false + true true From 788a1db877844b0460073d33d24e4357e1974a6b Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sun, 17 Dec 2017 14:05:19 -0800 Subject: [PATCH 27/40] Add overload for method that parses string into bool. This is useful for cases where the default value is expensive to compute. By specifying the default via a delegate, it is not computed unless it is needed. --- Duplicati/Library/Utility/Utility.cs | 29 ++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 7b021c433..a3ffdf143 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -637,15 +637,17 @@ namespace Duplicati.Library.Utility } /// - /// Parses a string into a boolean value + /// Parses a string into a boolean value. /// - /// The value to parse - /// The default value, in case the string is not a valid boolean value - /// The parsed value or the default value - public static bool ParseBool(string value, bool @default) + /// The value to parse. + /// A delegate that returns the default value if is not a valid boolean value. + /// The parsed value, or the value returned by . + public static bool ParseBool(string value, Func defaultFunc) { - if (value == null) - value = ""; + if (String.IsNullOrWhiteSpace(value)) + { + return defaultFunc(); + } switch (value.Trim().ToLower()) { @@ -660,10 +662,21 @@ namespace Duplicati.Library.Utility case "no": return false; default: - return @default; + return defaultFunc(); } } + /// + /// Parses a string into a boolean value. + /// + /// The value to parse. + /// The default value, in case is not a valid boolean value. + /// The parsed value, or the default value. + public static bool ParseBool(string value, bool @default) + { + return Utility.ParseBool(value, () => @default); + } + /// /// Parses an option from the option set, using the convention that if the option is set, it is true unless it parses to false, and false otherwise /// From faad6de7bab4d4dd11373c053442a68cb67d399f Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sun, 17 Dec 2017 14:32:21 -0800 Subject: [PATCH 28/40] Add tests for parsing string into bool. --- Duplicati/UnitTest/UtilityTests.cs | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Duplicati/UnitTest/UtilityTests.cs b/Duplicati/UnitTest/UtilityTests.cs index 18ddbd7ee..208ee933f 100644 --- a/Duplicati/UnitTest/UtilityTests.cs +++ b/Duplicati/UnitTest/UtilityTests.cs @@ -57,5 +57,42 @@ namespace Duplicati.UnitTest Assert.IsNotNull(actualUniqueItems); Assert.IsNotNull(actualDuplicateItems); } + + [Test] + [Category("Utility")] + public void ParseBool() + { + string[] expectTrue = { "1", "on", "true", "yes" }; + string[] expectFalse = { "0", "off", "false", "no" }; + string[] expectDefault = { null, "", "maybe" }; + Func returnsTrue = () => true; + Func returnsFalse = () => false; + + foreach (string value in expectTrue) + { + string message = $"{value} should be parsed to true."; + + Assert.IsTrue(Utility.ParseBool(value, false), message); + Assert.IsTrue(Utility.ParseBool(value.ToUpper(), false), message); + Assert.IsTrue(Utility.ParseBool($" {value} ", false), message); + } + + foreach (string value in expectFalse) + { + string message = $"{value} should be parsed to false."; + + Assert.IsFalse(Utility.ParseBool(value, true), message); + Assert.IsFalse(Utility.ParseBool(value.ToUpper(), true), message); + Assert.IsFalse(Utility.ParseBool($" {value} ", true), message); + } + + foreach (string value in expectDefault) + { + Assert.IsTrue(Utility.ParseBool(value, true)); + Assert.IsTrue(Utility.ParseBool(value, returnsTrue)); + Assert.IsFalse(Utility.ParseBool(value, false)); + Assert.IsFalse(Utility.ParseBool(value, returnsFalse)); + } + } } } From bbebcdf3c9021e5db5843f597278f13f4490564c Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sun, 17 Dec 2017 14:11:16 -0800 Subject: [PATCH 29/40] Use method to parse string into bool to reduce duplication. --- Duplicati/Library/Utility/Utility.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index a3ffdf143..9f0036eaa 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -839,18 +839,11 @@ namespace Duplicati.Library.Utility { var str = Environment.GetEnvironmentVariable("FILESYSTEM_CASE_SENSITIVE"); - if (!string.IsNullOrWhiteSpace(str)) - { - str = str.Trim(); - if (new[] { "yes", "1", "on", "true" }.Contains(str, StringComparer.OrdinalIgnoreCase)) - return true; - if (new[] { "no", "0", "off", "false" }.Contains(str, StringComparer.OrdinalIgnoreCase)) - return false; - } - - //TODO: This should probably be determined by filesystem rather than OS, + // TODO: This should probably be determined by filesystem rather than OS, // OSX can actually have the disks formated as Case Sensitive, but insensitive is default - return IsClientLinux && !IsClientOSX; + Func defaultReply = () => Utility.IsClientLinux && !Utility.IsClientOSX; + + return Utility.ParseBool(str, defaultReply); } } From 839c8d9f2794be9c50b9b4ca901fddaf1bc2bf6e Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sun, 17 Dec 2017 14:15:43 -0800 Subject: [PATCH 30/40] Use method to parse string into bool to reduce duplication. --- Duplicati/UnitTest/BasicSetupHelper.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Duplicati/UnitTest/BasicSetupHelper.cs b/Duplicati/UnitTest/BasicSetupHelper.cs index f039f2cb8..8636df956 100644 --- a/Duplicati/UnitTest/BasicSetupHelper.cs +++ b/Duplicati/UnitTest/BasicSetupHelper.cs @@ -60,9 +60,7 @@ namespace Duplicati.UnitTest /// this can be used to diagnose errors on a CI build instance by setting /// the environment variable DEBUG_OUTPUT=1 and running the job /// - public static readonly bool DEBUG_OUTPUT = - new[] { "1", "true", "on", "yes" } - .Contains(Environment.GetEnvironmentVariable("DEBUG_OUTPUT") ?? "", StringComparer.OrdinalIgnoreCase); + public static readonly bool DEBUG_OUTPUT = Library.Utility.Utility.ParseBool(Environment.GetEnvironmentVariable("DEBUG_OUTPUT"), false); /// /// Writes a message to TestContext.Progress and Console.Out From 486ea11fbdaf3c7fc61d5b05093b03fe9211ab28 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Wed, 20 Dec 2017 22:45:44 +0100 Subject: [PATCH 31/40] Fixed a case where the commandline arguments were not correctly escaped. This fixes #2907 --- Duplicati/Library/Utility/Utility.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 815f64497..aac14fd6a 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -1548,6 +1548,10 @@ namespace Duplicati.Library.Utility // making it even rarer and harder to diagnose when // it happens arg = arg.Replace("\"", "\"\""); + + // Also fix the case where the argument ends with a slash + if (arg[arg.Length - 1] == '\\') + arg += "\\"; } // Check that all characters are in the safe set From 180bb16296809d8915014a88620c6d81d73472e8 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Fri, 22 Dec 2017 10:48:42 +0100 Subject: [PATCH 32/40] Version bump to v2.0.2.14-2.0.2.14_canary_2017-12-22 You can download this build from: Binaries: https://updates.duplicati.com/canary/duplicati-2.0.2.14_canary_2017-12-22.zip Signature file: https://updates.duplicati.com/canary/duplicati-2.0.2.14_canary_2017-12-22.zip.sig ASCII signature file: https://updates.duplicati.com/canary/duplicati-2.0.2.14_canary_2017-12-22.zip.sig.asc MD5: 8614b551d8669691165c74d8b78c2d38 SHA1: a7c6ffc6954bbb695ad6e9a5c42aa5e2df32462d SHA256: 19b799ab758f5967baea0a3bd7a4427ff6b97d05647e48f21fd5cf7b37faea3c --- Updates/build_version.txt | 2 +- changelog.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Updates/build_version.txt b/Updates/build_version.txt index b1bd38b62..8351c1939 100644 --- a/Updates/build_version.txt +++ b/Updates/build_version.txt @@ -1 +1 @@ -13 +14 diff --git a/changelog.txt b/changelog.txt index cd7bbde4b..67a1deac5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +2017-12-22 - 2.0.2.14_canary_2017-12-22 +========== +* Multiple code quality fixes, thansk @warwickmm +* Fixed a case where commandline arguments ending with a backslash were not correctly escaped on Windows + 2017-11-22 - 2.0.2.13_canary_2017-11-22 ========== * Fixed an issue with AutoUpdater semi-ignoring webroot From 62a7901aca6a7303a1492bdd3f9a7ba3b8998c77 Mon Sep 17 00:00:00 2001 From: vnau Date: Mon, 25 Dec 2017 04:12:19 +0700 Subject: [PATCH 33/40] Compression modules refactored to work with streams instead of filenames; Added unit test to check compression reversibility --- Duplicati/CommandLine/RecoveryTool/Index.cs | 37 +-- Duplicati/CommandLine/RecoveryTool/List.cs | 17 +- .../CommandLine/RecoveryTool/Recompress.cs | 22 +- Duplicati/CommandLine/RecoveryTool/Restore.cs | 69 +++-- .../Library/AutoUpdater/UpdaterManager.cs | 289 +++++++++--------- .../Library/Compression/FileArchiveZip.cs | 142 ++++----- .../Compression/SevenZipCompression.cs | 149 ++++----- Duplicati/Library/Compression/packages.config | 2 +- .../DynamicLoader/CompressionLoader.cs | 15 +- Duplicati/Library/Interface/ICompression.cs | 95 +++--- Duplicati/Library/Main/BackendManager.cs | 213 +++++-------- .../Main/Operation/CreateBugReportHandler.cs | 30 +- .../Library/Main/Volumes/VolumeReaderBase.cs | 55 +++- .../Library/Main/Volumes/VolumeWriterBase.cs | 46 +-- .../Utility/Duplicati.Library.Utility.csproj | 7 +- Duplicati/Library/Utility/ShaderStream.cs | 73 +++++ Duplicati/Library/Utility/TempFile.cs | 6 +- Duplicati/Library/Utility/TempFileStream.cs | 7 +- Duplicati/UnitTest/CompressionTests.cs | 82 +++-- 19 files changed, 753 insertions(+), 603 deletions(-) create mode 100644 Duplicati/Library/Utility/ShaderStream.cs diff --git a/Duplicati/CommandLine/RecoveryTool/Index.cs b/Duplicati/CommandLine/RecoveryTool/Index.cs index 614cffe2e..b09413e60 100644 --- a/Duplicati/CommandLine/RecoveryTool/Index.cs +++ b/Duplicati/CommandLine/RecoveryTool/Index.cs @@ -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,7 +122,7 @@ namespace Duplicati.CommandLine.RecoveryTool Console.WriteLine(" done!"); } } - catch(Exception ex) + catch (Exception ex) { 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,8 +168,8 @@ 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(); @@ -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); diff --git a/Duplicati/CommandLine/RecoveryTool/List.cs b/Duplicati/CommandLine/RecoveryTool/List.cs index d14640f14..fb3cc7408 100644 --- a/Duplicati/CommandLine/RecoveryTool/List.cs +++ b/Duplicati/CommandLine/RecoveryTool/List.cs @@ -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(b, a.Key))) + foreach (var v in times.Zip(Enumerable.Range(0, times.Length), (a, b) => new KeyValuePair(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 EnumerateFilesInDList(string file, Duplicati.Library.Utility.IFilter filter, Dictionary 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); diff --git a/Duplicati/CommandLine/RecoveryTool/Recompress.cs b/Duplicati/CommandLine/RecoveryTool/Recompress.cs index ddd414957..af7b51508 100644 --- a/Duplicati/CommandLine/RecoveryTool/Recompress.cs +++ b/Duplicati/CommandLine/RecoveryTool/Recompress.cs @@ -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 ..."); diff --git a/Duplicati/CommandLine/RecoveryTool/Restore.cs b/Duplicati/CommandLine/RecoveryTool/Restore.cs index 9d71e5b27..f53c56afb 100644 --- a/Duplicati/CommandLine/RecoveryTool/Restore.cs +++ b/Duplicati/CommandLine/RecoveryTool/Restore.cs @@ -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,14 +184,14 @@ 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); } @@ -208,9 +208,9 @@ namespace Duplicati.CommandLine.RecoveryTool } } } - + string fh; - using(var fs = File.OpenRead(tf)) + using (var fs = File.OpenRead(tf)) fh = Convert.ToBase64String(filehasher.ComputeHash(fs)); if (fh == f.Hash) @@ -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> AllFileLines() { - while(true) + while (true) { var str = ReadNextLine(); if (str == null) @@ -376,7 +376,7 @@ namespace Duplicati.CommandLine.RecoveryTool public IEnumerable 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 m_lookup = new Dictionary(); + private Dictionary m_streams = new Dictionary(); private List m_mru = new List(); private Dictionary m_options; @@ -443,12 +444,13 @@ 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); - if (cf == null) - throw new Exception(string.Format("Unable to decompress {0}, no such compression module {1}", filename, Path.GetExtension(filename).Trim('.'))); - m_lookup[filename] = cf; + 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); + m_lookup[filename] = cf ?? throw new Exception(string.Format("Unable to decompress {0}, no such compression module {1}", filename, Path.GetExtension(filename).Trim('.'))); + m_streams[filename] = stream; } if (m_mru.Count > 0 && m_mru[0] != filename) @@ -462,6 +464,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 +476,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; } diff --git a/Duplicati/Library/AutoUpdater/UpdaterManager.cs b/Duplicati/Library/AutoUpdater/UpdaterManager.cs index 61cf973fb..5f555f9b4 100644 --- a/Duplicati/Library/AutoUpdater/UpdaterManager.cs +++ b/Duplicati/Library/AutoUpdater/UpdaterManager.cs @@ -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 /// /// Gets the last version found from an update /// - 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(new string [] { + var overrides = new List(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")); } @@ -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(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(jr); } catch (Exception ex) @@ -404,7 +405,7 @@ namespace Duplicati.Library.AutoUpdater { var res = new List>(); 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())) { - 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())) - { - 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,7 +514,7 @@ 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)) @@ -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(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(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,75 @@ namespace Duplicati.Library.AutoUpdater var md5 = System.Security.Cryptography.MD5.Create(); var sha256 = System.Security.Cryptography.SHA256.Create(); - Func computeMD5 = (path) => + string computeMD5(string path) { - md5.Initialize(); - using(var fs = System.IO.File.OpenRead(path)) - return Convert.ToBase64String(md5.ComputeHash(fs)); + using (var fs = System.IO.File.OpenRead(path)) + return computeStreamMD5(fs); }; - Func computeSHA256 = (path) => + string computeSHA256(string 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()) + string computeStreamMD5(Stream stream) { - using (var zipfile = new Duplicati.Library.Compression.FileArchiveZip(archive_temp, new Dictionary())) + md5.Initialize(); + return Convert.ToBase64String(md5.ComputeHash(stream)); + }; + + string computeStreamSHA256(Stream stream) + { + sha256.Initialize(); + return Convert.ToBase64String(sha256.ComputeHash(stream)); + }; + + + // Build a zip + using (var archive_temp_file = new Duplicati.Library.Utility.TempFile()) + { + using (var archive_temp = new Duplicati.Library.Utility.TempFileStream(archive_temp_file)) { - Func addToArchive = (path, relpath) => + using (ICompression zipfile = new Duplicati.Library.Compression.FileArchiveZip(archive_temp, ArchiveMode.Write, new Dictionary())) { - if (ignoreMap.ContainsKey(relpath)) - return false; - - 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))) + Func 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, StringComparison.Ordinal) ? null : computeMD5(fse), - SHA256 = fse.EndsWith(dirsep, StringComparison.Ordinal) ? 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 +803,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)) @@ -997,7 +1012,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 { @@ -1090,7 +1105,7 @@ namespace Duplicati.Library.AutoUpdater RedirectStandardError = true, RedirectStandardInput = true, RedirectStandardOutput = true, - ErrorDialog = false, + ErrorDialog = false, }; pi.EnvironmentVariables.Clear(); @@ -1200,7 +1215,7 @@ namespace Duplicati.Library.AutoUpdater try { AppDomain.Unload(domain); } catch (Exception ex) - { + { Console.WriteLine("Appdomain unload error: {0}", ex); } @@ -1220,7 +1235,7 @@ namespace Duplicati.Library.AutoUpdater 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); diff --git a/Duplicati/Library/Compression/FileArchiveZip.cs b/Duplicati/Library/Compression/FileArchiveZip.cs index 4f1dc9c07..4ce923bf4 100644 --- a/Duplicati/Library/Compression/FileArchiveZip.cs +++ b/Duplicati/Library/Compression/FileArchiveZip.cs @@ -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 { /// /// 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. /// public class FileArchiveZip : ICompression { + private const string CannotReadWhileWriting = "Cannot read while writing"; + private const string CannotWriteWhileReading = "Cannot write while reading"; + /// /// The commandline option for toggling the compression level /// @@ -51,16 +53,16 @@ namespace Duplicati.Library.Compression /// /// The commandline option for toggling the compression method /// - private const string COMPRESSION_METHOD_OPTION = "zip-compression-method"; - /// - /// The commandline option for toggling the zip64 support - /// - private const string COMPRESSION_ZIP64_OPTION = "zip-compression-zip64"; - - /// - /// The default compression level - /// - private const SharpCompress.Compressors.Deflate.CompressionLevel DEFAULT_COMPRESSION_LEVEL = SharpCompress.Compressors.Deflate.CompressionLevel.Level9; + private const string COMPRESSION_METHOD_OPTION = "zip-compression-method"; + /// + /// The commandline option for toggling the zip64 support + /// + private const string COMPRESSION_ZIP64_OPTION = "zip-compression-zip64"; + + /// + /// The default compression level + /// + private const SharpCompress.Compressors.Deflate.CompressionLevel DEFAULT_COMPRESSION_LEVEL = SharpCompress.Compressors.Deflate.CompressionLevel.Level9; /// /// 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; /// - /// This property indicates that this current instance should write to a file + /// This property indicates reading or writing access mode of the file archive. /// - private bool m_isWriting; + ArchiveMode m_mode; /// /// 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 /// private Stream m_stream; - + /// /// Lookup table for faster access to entries based on their name. /// private Dictionary m_entryDict; - + /// /// The writer instance used when creating archives /// @@ -119,18 +121,13 @@ namespace Duplicati.Library.Compression /// /// The compression level applied when the hint does not indicate incompressible /// - private SharpCompress.Compressors.Deflate.CompressionLevel m_defaultCompressionLevel; + private SharpCompress.Compressors.Deflate.CompressionLevel m_defaultCompressionLevel; - /// + /// /// The compression level applied when the hint does not indicate incompressible /// private CompressionType m_compressionType; - /// - /// The name of the file being read - /// - private string m_filename; - /// /// A flag indicating if zip64 is in use /// @@ -141,14 +138,15 @@ namespace Duplicati.Library.Compression /// 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 /// /// 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. /// - /// The name of the file to read or write + /// The stream to read or write depending access mode + /// The archive acces mode /// The options passed on the commandline - public FileArchiveZip(string filename, Dictionary options) + public FileArchiveZip(Stream stream, ArchiveMode mode, IDictionary 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 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(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 /// A stream with the file contents 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 /// The ZipEntry for the file or null if no such file was found 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 /// A writeable stream for the file contents 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 /// True if the file exists, false otherwise 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 /// 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; } diff --git a/Duplicati/Library/Compression/SevenZipCompression.cs b/Duplicati/Library/Compression/SevenZipCompression.cs index 1622c9701..6d35b2311 100644 --- a/Duplicati/Library/Compression/SevenZipCompression.cs +++ b/Duplicati/Library/Compression/SevenZipCompression.cs @@ -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 /// /// 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. /// - /// The name of the file to read or write + /// The stream to read or write depending access mode + /// The archive acces mode /// The options passed on the commandline - public SevenZipCompression(string filename, Dictionary options) + public SevenZipCompression(Stream stream, ArchiveMode mode, IDictionary 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 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> 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(n.Name, n.Size); + where + !n.IsDir && + (prefix == null || n.Name.StartsWith(prefix, sc)) + select new KeyValuePair(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( diff --git a/Duplicati/Library/Compression/packages.config b/Duplicati/Library/Compression/packages.config index 2c846c9a5..6b2e4259c 100644 --- a/Duplicati/Library/Compression/packages.config +++ b/Duplicati/Library/Compression/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/Duplicati/Library/DynamicLoader/CompressionLoader.cs b/Duplicati/Library/DynamicLoader/CompressionLoader.cs index 085e80a4f..293132f65 100644 --- a/Duplicati/Library/DynamicLoader/CompressionLoader.cs +++ b/Duplicati/Library/DynamicLoader/CompressionLoader.cs @@ -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 /// The filename used to compress/decompress contents /// The options to pass to the instance constructor /// The instanciated encryption module or null if the file extension is not supported - public ICompression GetModule(string fileExtension, string filename, Dictionary options) + public ICompression GetModule(string fileExtension, Stream stream, ArchiveMode mode, Dictionary 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); } - /// /// Instanciates a specific compression module, given the file extension and options /// /// The file extension to create the instance for - /// The filename of the file used to compress/decompress contents + /// The stream of the file used to compress/decompress contents + /// True is the file opened for writing /// The options to pass to the instance constructor /// The instanciated compression module or null if the file extension is not supported - public static ICompression GetModule(string fileextension, string filename, Dictionary options) + public static ICompression GetModule(string fileextension, Stream stream, ArchiveMode mode, Dictionary options) { - return _compressionLoader.GetModule(fileextension, filename, options); + return _compressionLoader.GetModule(fileextension, stream, mode, options); } #endregion diff --git a/Duplicati/Library/Interface/ICompression.cs b/Duplicati/Library/Interface/ICompression.cs index a0ee13ee6..6b6cec997 100644 --- a/Duplicati/Library/Interface/ICompression.cs +++ b/Duplicati/Library/Interface/ICompression.cs @@ -19,10 +19,26 @@ #endregion using System; using System.Collections.Generic; +using System.IO; using System.Text; namespace Duplicati.Library.Interface { + /// + /// Enumerates the possible modes for compression and decompression + /// + public enum ArchiveMode + { + /// + /// Indicates compression i.e. write archive mode, which means that the stream must be writeable + /// + Write, + /// + /// Indicates decompression i.e. read archive mode, which means that the stream must be readable + /// + Read + } + /// /// 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 /// Noncompressible - } - + /// /// 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; } } - /// - /// 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. - /// - public interface ICompression : IDisposable + public interface IArchiveReader { /// /// Returns all files in the archive, matching the prefix, if any. @@ -86,35 +90,14 @@ namespace Duplicati.Library.Interface /// An optional prefix for limiting the files returned /// All files in the archive, matching the prefix, if any IEnumerable> ListFilesWithSize(string prefix); - + /// - /// Returns a stream with data from the given file + /// Returns a stream with data from the given file in archive. /// /// The file to read the data from /// A stream with data from the given file System.IO.Stream OpenRead(string file); - /// - /// Creates a file in the archive - /// - /// The file to create - /// A hint to the compressor as to how compressible the file data is - /// The time the file was last written - /// A stream with the data to write into the file - System.IO.Stream CreateFile(string file, CompressionHint hint, DateTime lastWrite); - - /// - /// Returns a value indicating if the specified file exists - /// - /// The name of the file to examine - /// True if the file exists, false otherwise - bool FileExists(string file); - - /// - /// The total size of the archive - /// - long Size { get; } - /// /// Returns the last modification time for the file /// @@ -122,10 +105,48 @@ namespace Duplicati.Library.Interface /// The timestamp on the file DateTime GetLastWriteTime(string file); + /// + /// Returns a value indicating if the specified file exists + /// + /// The name of the file to examine + /// True if the file exists, false otherwise + bool FileExists(string file); + } + + public interface IArchiveWriter + { + /// + /// Creates a file in the archive. + /// + /// The file to create in the archive + /// A hint to the compressor as to how compressible the file data is + /// The time the file was last written + /// A stream with the data to write into the file + System.IO.Stream CreateFile(string file, CompressionHint hint, DateTime lastWrite); + /// /// The size in bytes of the buffer that will be written when flushed /// long FlushBufferSize { get; } + } + + /// + /// 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. + /// + public interface ICompression : IDisposable, IArchiveReader, IArchiveWriter + { + /// + /// The total size of the archive. + /// + long Size { get; } /// /// The extension that the compression implementation adds to the filename diff --git a/Duplicati/Library/Main/BackendManager.cs b/Duplicati/Library/Main/BackendManager.cs index 5c2e72cab..15969fbcd 100644 --- a/Duplicati/Library/Main/BackendManager.cs +++ b/Duplicati/Library/Main/BackendManager.cs @@ -13,7 +13,7 @@ namespace Duplicati.Library.Main internal class BackendManager : IDisposable { public const string VOLUME_HASH = "SHA256"; - + /// /// Class to represent hash failures /// @@ -38,7 +38,7 @@ namespace Duplicati.Library.Main /// The exception that caused this exception 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 } } } - - } - /// A small utility stream that allows to keep streams open and counts the bytes sent through. - 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 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(); + m_dbqueue = new List(); 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 entries; - lock(m_dbqueuelock) + lock (m_dbqueuelock) if (m_dbqueue.Count == 0) return false; else @@ -369,7 +320,7 @@ namespace Duplicati.Library.Main HashSet volsRemoved = new HashSet(); //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) @@ -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(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 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); } } diff --git a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs index 066d0d4d4..5586f99df 100644 --- a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs +++ b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs @@ -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; - } + } } } } diff --git a/Duplicati/Library/Main/Volumes/VolumeReaderBase.cs b/Duplicati/Library/Main/Volumes/VolumeReaderBase.cs index 6db4c63a1..c9da33b77 100644 --- a/Duplicati/Library/Main/Volumes/VolumeReaderBase.cs +++ b/Duplicati/Library/Main/Volumes/VolumeReaderBase.cs @@ -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 fs) + name = fs.Name; + if (stream is TempFileStream tfs) + name = tfs.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 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"); diff --git a/Duplicati/Library/Main/Volumes/VolumeWriterBase.cs b/Duplicati/Library/Main/Volumes/VolumeWriterBase.cs index d3b14404f..be78eda4d 100644 --- a/Duplicati/Library/Main/Volumes/VolumeWriterBase.cs +++ b/Duplicati/Library/Main/Volumes/VolumeWriterBase.cs @@ -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; - } } } diff --git a/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj b/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj index 07ba1b083..e9a9509d5 100644 --- a/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj +++ b/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj @@ -51,6 +51,7 @@ + @@ -102,7 +103,5 @@ Duplicati.Library.Localization - - - - + + \ No newline at end of file diff --git a/Duplicati/Library/Utility/ShaderStream.cs b/Duplicati/Library/Utility/ShaderStream.cs new file mode 100644 index 000000000..d06ba3b6c --- /dev/null +++ b/Duplicati/Library/Utility/ShaderStream.cs @@ -0,0 +1,73 @@ +#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 +{ + /// + /// A small utility stream that allows to keep streams open and counts the bytes sent through. + /// + 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) + { + this.m_baseStream = baseStream ?? throw new ArgumentNullException(nameof(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); + } + } +} diff --git a/Duplicati/Library/Utility/TempFile.cs b/Duplicati/Library/Utility/TempFile.cs index 21e645ca2..29832eae3 100644 --- a/Duplicati/Library/Utility/TempFile.cs +++ b/Duplicati/Library/Utility/TempFile.cs @@ -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 + /// + /// The name of the temp file + /// + public string Name => m_path; /// /// Gets all temporary files found in the current tempdir, that matches the application prefix diff --git a/Duplicati/Library/Utility/TempFileStream.cs b/Duplicati/Library/Utility/TempFileStream.cs index 532cce82f..a773c5b7a 100644 --- a/Duplicati/Library/Utility/TempFileStream.cs +++ b/Duplicati/Library/Utility/TempFileStream.cs @@ -37,6 +37,11 @@ namespace Duplicati.Library.Utility { } + /// + /// Full name of the temp file. + /// + public string Name => m_file?.Name; + public TempFileStream(string file) : this(TempFile.WrapExistingFile(file)) { @@ -44,7 +49,7 @@ namespace Duplicati.Library.Utility public TempFileStream(TempFile file) { - m_file = file; + m_file = file ?? throw new ArgumentNullException(nameof(file)); m_stream = System.IO.File.Open(file, FileMode.Create, FileAccess.ReadWrite, FileShare.None); } diff --git a/Duplicati/UnitTest/CompressionTests.cs b/Duplicati/UnitTest/CompressionTests.cs index 7ed414d9e..f317ac766 100644 --- a/Duplicati/UnitTest/CompressionTests.cs +++ b/Duplicati/UnitTest/CompressionTests.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using NUnit.Framework; namespace Duplicati.UnitTest @@ -25,44 +26,87 @@ namespace Duplicati.UnitTest [Category("Compression")] public class CompressionTests { - [Test] - public void TestZipCompressionHints() - { - TestCompressionHints("zip"); - } - - [Test] - public void Test7zCompressionHints() - { - TestCompressionHints("7z"); - } - + [TestCase("zip")] + [TestCase("7z")] public void TestCompressionHints(string module) { const int TESTSIZE = 1024 * 1024; - using (var tf0 = new Library.Utility.TempFile()) - using (var tf1 = new Library.Utility.TempFile()) + using (var tf0 = new MemoryStream()) + using (var tf1 = new MemoryStream()) { var opts = new Dictionary(); opts["zip-compression-level"] = "9"; - using (var z0 = Library.DynamicLoader.CompressionLoader.GetModule(module, tf0, opts)) + using (var z0 = Library.DynamicLoader.CompressionLoader.GetModule(module, tf0, Library.Interface.ArchiveMode.Write, opts)) using (var fs0 = z0.CreateFile("sample", Library.Interface.CompressionHint.Noncompressible, DateTime.Now)) fs0.Write(new byte[TESTSIZE], 0, TESTSIZE); - using (var z1 = Library.DynamicLoader.CompressionLoader.GetModule(module, tf1, opts)) + using (var z1 = Library.DynamicLoader.CompressionLoader.GetModule(module, tf1, Library.Interface.ArchiveMode.Write, opts)) using (var fs1 = z1.CreateFile("sample", Library.Interface.CompressionHint.Compressible, DateTime.Now)) fs1.Write(new byte[TESTSIZE], 0, TESTSIZE); - if (new FileInfo(tf0).Length < TESTSIZE) + if (tf0.Length < TESTSIZE) throw new Exception("Compression hint non-compressible is not honored"); - if (new FileInfo(tf1).Length > new FileInfo(tf0).Length * 0.25) + if (tf1.Length > tf0.Length * 0.25) throw new Exception("Compression is not applied"); } } - + + /// + /// Test compression reversibility + /// + /// + [TestCase("zip")] + [TestCase("7z")] + public void TestCompressionReversibility(string module) + { + const int TESTSIZE = 1024 * 1024; + var testset1 = Enumerable.Range(0, TESTSIZE).Select(i => (byte)((i * 22695477) % 257)); + var testset2 = Enumerable.Range(0, TESTSIZE).Select(i => (byte)((i * 48271) % 257)); + + using (var stream = new MemoryStream()) + { + var opts = new Dictionary(); + opts["zip-compression-level"] = "9"; + + // Compress test streams + using (var z0 = Library.DynamicLoader.CompressionLoader.GetModule(module, stream, Library.Interface.ArchiveMode.Write, opts)) + { + // Add two files to the archive + using (var fs1 = z0.CreateFile("sample1", Library.Interface.CompressionHint.Compressible, DateTime.Now)) + fs1.Write(testset1.ToArray(), 0, TESTSIZE); + + using (var fs1 = z0.CreateFile("sample2", Library.Interface.CompressionHint.Compressible, DateTime.Now)) + fs1.Write(testset2.ToArray(), 0, TESTSIZE); + } + + Console.WriteLine("Compression rate for module {0}: {1:0.00}%", module, 100.0 * stream.Length / (TESTSIZE * 2)); + // Decompress + using (var z0 = Library.DynamicLoader.CompressionLoader.GetModule(module, stream, Library.Interface.ArchiveMode.Read, opts)) + { + // Get files list + var files = z0.ListFiles(null); + + // Read second file + using (var fd = z0.OpenRead(files[1])) + { + bool match = testset2.All(b => b == fd.ReadByte()) && fd.ReadByte() == -1; + if (!match) + throw new Exception("Decompressed file sample2 contents do not match the source file."); + } + + // Read first file + using (var fd = z0.OpenRead(files[0])) + { + bool match = testset1.All(b => b == fd.ReadByte()) && fd.ReadByte() == -1; + if (!match) + throw new Exception("Decompressed file sample1 contents do not match the source file."); + } + } + } + } } } From 4812382aaf9b441c2a1ccbf981ff7546ef16dfdb Mon Sep 17 00:00:00 2001 From: vnau Date: Mon, 25 Dec 2017 04:19:10 +0700 Subject: [PATCH 34/40] SharpCompress rolled back to version 0.18.2 --- Duplicati/Library/Compression/packages.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Library/Compression/packages.config b/Duplicati/Library/Compression/packages.config index 6b2e4259c..2c846c9a5 100644 --- a/Duplicati/Library/Compression/packages.config +++ b/Duplicati/Library/Compression/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file From e78c3f8650d8673d2a7d45bfc75615f9c7312e4f Mon Sep 17 00:00:00 2001 From: vnau Date: Mon, 25 Dec 2017 22:59:17 +0700 Subject: [PATCH 35/40] Removed throw expressions to avoid Microsoft compiler bug. --- Duplicati/CommandLine/RecoveryTool/Restore.cs | 7 ++++++- Duplicati/Library/Utility/ShaderStream.cs | 4 +++- Duplicati/Library/Utility/TempFileStream.cs | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Duplicati/CommandLine/RecoveryTool/Restore.cs b/Duplicati/CommandLine/RecoveryTool/Restore.cs index f53c56afb..686de93d1 100644 --- a/Duplicati/CommandLine/RecoveryTool/Restore.cs +++ b/Duplicati/CommandLine/RecoveryTool/Restore.cs @@ -449,7 +449,12 @@ namespace Duplicati.CommandLine.RecoveryTool { 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); - m_lookup[filename] = cf ?? throw new Exception(string.Format("Unable to decompress {0}, no such compression module {1}", filename, Path.GetExtension(filename).Trim('.'))); + 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; } diff --git a/Duplicati/Library/Utility/ShaderStream.cs b/Duplicati/Library/Utility/ShaderStream.cs index d06ba3b6c..7fc970ca0 100644 --- a/Duplicati/Library/Utility/ShaderStream.cs +++ b/Duplicati/Library/Utility/ShaderStream.cs @@ -33,7 +33,9 @@ namespace Duplicati.Library.Utility public ShaderStream(System.IO.Stream baseStream, bool keepBaseOpen) { - this.m_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); + if (baseStream == null) + throw new ArgumentNullException(nameof(baseStream)); + this.m_baseStream = baseStream; this.m_keepBaseOpen = keepBaseOpen; } diff --git a/Duplicati/Library/Utility/TempFileStream.cs b/Duplicati/Library/Utility/TempFileStream.cs index a773c5b7a..9f814d62e 100644 --- a/Duplicati/Library/Utility/TempFileStream.cs +++ b/Duplicati/Library/Utility/TempFileStream.cs @@ -49,7 +49,9 @@ namespace Duplicati.Library.Utility public TempFileStream(TempFile file) { - m_file = file ?? throw new ArgumentNullException(nameof(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); } From 2250f5fda18629a55ba9882e26b97ee25a616a2d Mon Sep 17 00:00:00 2001 From: vnau Date: Mon, 25 Dec 2017 23:13:08 +0700 Subject: [PATCH 36/40] Fix for AppVeyor. --- .../Library/AutoUpdater/UpdaterManager.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/Duplicati/Library/AutoUpdater/UpdaterManager.cs b/Duplicati/Library/AutoUpdater/UpdaterManager.cs index 5f555f9b4..5a18f77e4 100644 --- a/Duplicati/Library/AutoUpdater/UpdaterManager.cs +++ b/Duplicati/Library/AutoUpdater/UpdaterManager.cs @@ -724,31 +724,29 @@ namespace Duplicati.Library.AutoUpdater var md5 = System.Security.Cryptography.MD5.Create(); var sha256 = System.Security.Cryptography.SHA256.Create(); - string computeMD5(string path) - { - using (var fs = System.IO.File.OpenRead(path)) - return computeStreamMD5(fs); - }; - - string computeSHA256(string path) - { - sha256.Initialize(); - using (Stream fs = System.IO.File.OpenRead(path)) - return computeStreamSHA256(fs); - }; - - string computeStreamMD5(Stream stream) + Func computeStreamMD5 = (stream) => { md5.Initialize(); return Convert.ToBase64String(md5.ComputeHash(stream)); }; - string computeStreamSHA256(Stream stream) + Func computeStreamSHA256 = (stream) => { sha256.Initialize(); return Convert.ToBase64String(sha256.ComputeHash(stream)); }; + Func computeMD5 = (path) => + { + using (Stream fs = System.IO.File.OpenRead(path)) + return computeStreamMD5(fs); + }; + + Func computeSHA256 = (path) => + { + using (Stream fs = System.IO.File.OpenRead(path)) + return computeStreamSHA256(fs); + }; // Build a zip using (var archive_temp_file = new Duplicati.Library.Utility.TempFile()) From 1c6711873703fd446af69e9949f1edaf1d6dd937 Mon Sep 17 00:00:00 2001 From: vnau Date: Mon, 25 Dec 2017 23:51:10 +0700 Subject: [PATCH 37/40] Type pattern conditions removed to compile with AppVeyor. --- Duplicati/Library/Main/Volumes/VolumeReaderBase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Duplicati/Library/Main/Volumes/VolumeReaderBase.cs b/Duplicati/Library/Main/Volumes/VolumeReaderBase.cs index c9da33b77..e2f0218fc 100644 --- a/Duplicati/Library/Main/Volumes/VolumeReaderBase.cs +++ b/Duplicati/Library/Main/Volumes/VolumeReaderBase.cs @@ -19,10 +19,10 @@ namespace Duplicati.Library.Main.Volumes if (tmp == null) { var name = "[stream]"; - if (stream is FileStream fs) - name = fs.Name; - if (stream is TempFileStream tfs) - name = tfs.Name; + 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)); } From 7d0e617d0a600be9c652f1200b6933fc43722e5f Mon Sep 17 00:00:00 2001 From: mr-flibble Date: Fri, 29 Dec 2017 10:24:15 +0100 Subject: [PATCH 38/40] Update help.txt Added --dry-run to delete command --- Duplicati/CommandLine/help.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Duplicati/CommandLine/help.txt b/Duplicati/CommandLine/help.txt index 27b2d8395..fb489aadc 100644 --- a/Duplicati/CommandLine/help.txt +++ b/Duplicati/CommandLine/help.txt @@ -138,6 +138,8 @@ Usage: delete [] Marks old data deleted and removes outdated dlist files. A backup is deleted when it is older than or when there are more newer versions than . 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= public static long DEFAULT_BUFFER_SIZE => SystemContextSettings.Buffersize; - /// - /// A value indicating if the current process is running in 64bit mode - /// - public static readonly bool Is64BitProcess = IntPtr.Size == 8; - /// /// Gets the hash algorithm used for calculating a hash /// From 8ac6affd2a171710b3172e646a99f3fadd7ae3d7 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Wed, 3 Jan 2018 13:43:56 +0100 Subject: [PATCH 40/40] Version bump to v2.0.2.15-2.0.2.15_experimental_2018-01-03 You can download this build from: Binaries: https://updates.duplicati.com/experimental/duplicati-2.0.2.15_experimental_2018-01-03.zip Signature file: https://updates.duplicati.com/experimental/duplicati-2.0.2.15_experimental_2018-01-03.zip.sig ASCII signature file: https://updates.duplicati.com/experimental/duplicati-2.0.2.15_experimental_2018-01-03.zip.sig.asc MD5: ff053fe98fee1f3ba305ad12949b4c5e SHA1: 14d1729ba6ac5742f94b91c47dd6779d2a43489a SHA256: a906bd79b18fc9e7b33712018aaaea17e4e64cbf657a3c2232577b98828a5423 --- Updates/build_version.txt | 2 +- changelog.txt | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Updates/build_version.txt b/Updates/build_version.txt index 8351c1939..60d3b2f4a 100644 --- a/Updates/build_version.txt +++ b/Updates/build_version.txt @@ -1 +1 @@ -14 +15 diff --git a/changelog.txt b/changelog.txt index 67a1deac5..ae1e57fab 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,21 @@ +2018-01-03 - 2.0.2.15_experimental_2018-01-03 +========== +This is the same as build 2.0.2.14 canary + +Overview of major and breaking changes since 2.0.1.73 experimental: +* Fixed not running the slow VACUUM command after each operation +* Improved S3 to work better with non AWS hosts +* Updated backend support libraries to the latest versions: Azure, AWS, SSH, SharpCompress, Mega, AlphaFS +* Fixed a problem with SSL certificates periodically failing +* Added a Sia backend +* Added a number of default excludes +* Updated the hashing library to use faster native libraries where available +* Added a new retention policy which allows backup versions to decrease over time +* Updated the auto-updater, such that new installs will spawn an extra process instead of using the AppDomain method as previously done. +* Faster browsing of files to restore +* Removed automatic tilde expansions as it created numerous problems +* Multiple new languages added to the UI + 2017-12-22 - 2.0.2.14_canary_2017-12-22 ========== * Multiple code quality fixes, thansk @warwickmm