diff --git a/.travis.yml b/.travis.yml index bb59178ae..8a18829db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,7 @@ jobs: - env: CATEGORY=Purge - env: CATEGORY=Serialization - env: CATEGORY=Utility + - env: CATEGORY=UriUtility - env: CATEGORY=GUI addons: @@ -66,40 +67,4 @@ before_install: - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- script: -# build duplicati - - ls -la "${TRAVIS_BUILD_DIR}"/packages/ - - echo "travis_fold:start:build_duplicati" - - msbuild /p:Configuration=Release Duplicati.sln - - echo "travis_fold:end:build_duplicati" - - cp -r ./Duplicati/Server/webroot ./Duplicati/GUI/Duplicati.GUI.TrayIcon/bin/Release/webroot -# download and extract testdata - - echo "travis_fold:start:download_extract_testdata" - - ls -la . - - if [ ! -d ~/tmp ]; then mkdir ~/tmp; fi - - if [ ! -d ~/download ]; then mkdir ~/download; fi - - if [ ! -d ~/download/svn ]; then mkdir ~/download/svn; fi - - if [ ! -d ~/download/bulk ]; then mkdir ~/download/bulk; fi - - if [ ! -f ~/download/svn/DSMCBE.zip ] && ([ "$CATEGORY" == "SVNData" ] || [ "$CATEGORY" == "SVNDataLong" ]); then wget --progress=dot:giga "https://s3.amazonaws.com/duplicati-test-file-hosting/DSMCBE.zip" -O ~/download/svn/DSMCBE.zip; fi - - if [ ! -f ~/download/bulk/data.zip ] && ([ "$CATEGORY" == "BulkNormal" ] || [ "$CATEGORY" == "BulkNoSize" ]); then wget --progress=dot:giga "https://s3.amazonaws.com/duplicati-test-file-hosting/data.zip" -O ~/download/bulk/data.zip; fi - - ls -la . - - mkdir ~/testdata - - mkdir ~/testdata/data - - mkdir ~/testdata/DSMCBE - - if [ "$CATEGORY" == "SVNData" ] || [ "$CATEGORY" == "SVNDataLong" ]; then unzip -q ~/download/svn/DSMCBE.zip -d ~/testdata/; fi - - if [ "$CATEGORY" == "BulkNormal" ] || [ "$CATEGORY" == "BulkNoSize" ]; then unzip -q ~/download/bulk/data.zip -d ~/testdata/; fi - - chown -R travis ~/testdata/ - - chmod -R 755 ~/testdata - - ls -la ~/testdata - - ls -la ~/testdata/DSMCBE - - ls -la ~/testdata/data - - echo "travis_fold:end:download_extract_testdata" -# run unit tests - - echo "travis_fold:start:unit_test" - - if [[ "$CATEGORY" != "GUI" ]]; then mono ./testrunner/NUnit.ConsoleRunner.3.5.0/tools/nunit3-console.exe ./Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll --where:cat==$CATEGORY --workers=1; fi - - echo "travis_fold:end:unit_test" -# start server and run gui tests - - echo "travis_fold:start:gui_unit_test" - - mono ./Duplicati/GUI/Duplicati.GUI.TrayIcon/bin/Release/Duplicati.Server.exe & - - if [[ "$CATEGORY" == "GUI" ]]; then python guiTests/guiTest.py; fi - - echo "travis_fold:end:gui_unit_test" - + - ./build.sh ${TRAVIS_BUILD_DIR} ${CATEGORY} diff --git a/Duplicati/CommandLine/BackendTool/Program.cs b/Duplicati/CommandLine/BackendTool/Program.cs index 0b177c656..85e52cc36 100644 --- a/Duplicati/CommandLine/BackendTool/Program.cs +++ b/Duplicati/CommandLine/BackendTool/Program.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; using Duplicati.Library.Interface; namespace Duplicati.CommandLine.BackendTool diff --git a/Duplicati/CommandLine/Commands.cs b/Duplicati/CommandLine/Commands.cs index fe0d473c0..eb9222646 100644 --- a/Duplicati/CommandLine/Commands.cs +++ b/Duplicati/CommandLine/Commands.cs @@ -27,12 +27,12 @@ namespace Duplicati.CommandLine private class PeriodicOutput : IDisposable { public event Action WriteOutput; - - private System.Threading.ManualResetEvent m_readyEvent; - private System.Threading.ManualResetEvent m_finishEvent; - private ConsoleOutput m_output; + + private readonly System.Threading.ManualResetEvent m_readyEvent; + private readonly System.Threading.ManualResetEvent m_finishEvent; + private readonly ConsoleOutput m_output; private System.Threading.Thread m_thread; - private TimeSpan m_updateFrequency; + private readonly TimeSpan m_updateFrequency; public PeriodicOutput(ConsoleOutput messageSink, TimeSpan updateFrequency) { @@ -133,11 +133,7 @@ namespace Duplicati.CommandLine public static int Help(TextWriter outwriter, Action setup, List args, Dictionary options, Library.Utility.IFilter filter) { - if (args.Count < 1) - Duplicati.CommandLine.Help.PrintUsage(outwriter, "help", options); - else - Duplicati.CommandLine.Help.PrintUsage(outwriter, args[0], options); - + Duplicati.CommandLine.Help.PrintUsage(outwriter, args.Count > 1? args[1] : "help", options); return 0; } diff --git a/Duplicati/CommandLine/Help.cs b/Duplicati/CommandLine/Help.cs index 463be36f4..6edd12220 100644 --- a/Duplicati/CommandLine/Help.cs +++ b/Duplicati/CommandLine/Help.cs @@ -409,10 +409,10 @@ namespace Duplicati.CommandLine string c = s; - string leadingSpaces = ""; + StringBuilder leadingSpaces = new StringBuilder(); while (c.Length > 0 && c.StartsWith(" ", StringComparison.Ordinal)) { - leadingSpaces += " "; + leadingSpaces.Append(" "); c = c.Remove(0, 1); } @@ -434,7 +434,7 @@ namespace Duplicati.CommandLine if (extraIndent) { extraIndent = false; - leadingSpaces += " "; + leadingSpaces.Append(" "); } } } @@ -442,13 +442,15 @@ namespace Duplicati.CommandLine private class Matcher { - Dictionary args = new Dictionary(StringComparer.OrdinalIgnoreCase); + readonly Dictionary args = new Dictionary(StringComparer.OrdinalIgnoreCase); public Matcher() { - List> foundArgs = new List>(); - foundArgs.Add(new Library.Main.Options(new Dictionary()).SupportedCommands); - foundArgs.Add(Program.SupportedOptions); + List> foundArgs = new List> + { + new Library.Main.Options(new Dictionary()).SupportedCommands, + Program.SupportedOptions + }; foreach (Duplicati.Library.Interface.IBackend backend in Library.DynamicLoader.BackendLoader.Backends) if (backend.SupportedCommands != null) diff --git a/Duplicati/CommandLine/Program.cs b/Duplicati/CommandLine/Program.cs index dc22314e1..b6d0c5eaa 100644 --- a/Duplicati/CommandLine/Program.cs +++ b/Duplicati/CommandLine/Program.cs @@ -29,7 +29,7 @@ namespace Duplicati.CommandLine { public class Program { - private static string LOGTAG = Library.Logging.Log.LogTagFromType(); + private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType(); public static bool FROM_COMMANDLINE = false; @@ -163,34 +163,22 @@ namespace Duplicati.CommandLine } // Probe for "help" to avoid extra processing - bool isHelp = cargs.Count == 0 || (cargs.Count >= 1 && string.Equals(cargs[0], "help", StringComparison.OrdinalIgnoreCase)); - - if (isHelp) + if (cargs.Count == 0 || (string.Equals(cargs[0], "help", StringComparison.OrdinalIgnoreCase))) { return Commands.Help(outwriter, setup, cargs, options, filter); } - if ((options.ContainsKey("parameters-file") && !string.IsNullOrEmpty("parameters-file")) || (options.ContainsKey("parameter-file") && !string.IsNullOrEmpty("parameter-file")) || (options.ContainsKey("parameterfile") && !string.IsNullOrEmpty("parameterfile"))) + // try and parse all parameter file aliases + foreach (string parameterOption in new []{ "parameters-file", "parameters-file", "parameterfile"} ) { - string filename; - if (options.ContainsKey("parameters-file") && !string.IsNullOrEmpty("parameters-file")) + if (options.ContainsKey(parameterOption) && !string.IsNullOrEmpty(options[parameterOption])) { - filename = options["parameters-file"]; - options.Remove("parameters-file"); - } - else if (options.ContainsKey("parameter-file") && !string.IsNullOrEmpty("parameter-file")) - { - filename = options["parameter-file"]; - options.Remove("parameter-file"); - } - else - { - filename = options["parameterfile"]; - options.Remove("parameterfile"); - } - - if (!ReadOptionsFromFile(outwriter, filename, ref filter, cargs, options)) - return 100; + string filename = options[parameterOption]; + options.Remove(parameterOption); + if (!ReadOptionsFromFile(outwriter, filename, ref filter, cargs, options)) + return 100; + break; + } } if (!options.ContainsKey("passphrase")) @@ -204,7 +192,7 @@ namespace Duplicati.CommandLine if (!options.ContainsKey("auth-username")) if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("AUTH_USERNAME"))) options["auth-username"] = System.Environment.GetEnvironmentVariable("AUTH_USERNAME"); - + var showDeletionErrors = verboseErrors; Duplicati.Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => { diff --git a/Duplicati/CommandLine/RecoveryTool/Properties/AssemblyInfo.cs b/Duplicati/CommandLine/RecoveryTool/Properties/AssemblyInfo.cs index 0c797de64..24b0dacd1 100644 --- a/Duplicati/CommandLine/RecoveryTool/Properties/AssemblyInfo.cs +++ b/Duplicati/CommandLine/RecoveryTool/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2015, The Duplicati Team +// 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 diff --git a/Duplicati/CommandLine/RecoveryTool/Restore.cs b/Duplicati/CommandLine/RecoveryTool/Restore.cs index ef5f9fb9d..36147959b 100644 --- a/Duplicati/CommandLine/RecoveryTool/Restore.cs +++ b/Duplicati/CommandLine/RecoveryTool/Restore.cs @@ -268,14 +268,14 @@ namespace Duplicati.CommandLine.RecoveryTool { private const int LOOKUP_TABLE_SIZE = 2048; - private CompressedFileMRUCache m_cache; - private int m_hashsize; - private int m_blocksize; + private readonly CompressedFileMRUCache m_cache; + private readonly int m_hashsize; + private readonly int m_blocksize; private Stream m_indexfile; - private List m_lookup = new List(); - private List m_offsets = new List(); - private byte[] m_linebuf = new byte[128]; - private byte[] m_newline = Environment.NewLine.Select(x => (byte)x).ToArray(); + private readonly List m_lookup = new List(); + private readonly List m_offsets = new List(); + private readonly byte[] m_linebuf = new byte[128]; + private readonly byte[] m_newline = Environment.NewLine.Select(x => (byte)x).ToArray(); public HashLookupHelper(string indexfile, CompressedFileMRUCache cache, int blocksize, int hashsize) { @@ -432,7 +432,7 @@ namespace Duplicati.CommandLine.RecoveryTool private Dictionary m_lookup = new Dictionary(); private Dictionary m_streams = new Dictionary(); private List m_mru = new List(); - private Dictionary m_options; + private readonly Dictionary m_options; private const int MAX_OPEN_ARCHIVES = 20; diff --git a/Duplicati/CommandLine/Strings.cs b/Duplicati/CommandLine/Strings.cs index 72b98f287..5db8812c5 100644 --- a/Duplicati/CommandLine/Strings.cs +++ b/Duplicati/CommandLine/Strings.cs @@ -22,9 +22,9 @@ namespace Duplicati.CommandLine.Strings { public static string ParametersFileOptionShort { get { return LC.L(@"Path to a file with parameters"); } } public static string UnhandledException(string message) { return LC.L(@"An error occured: {0}", message); } public static string UnhandledInnerException(string message) { return LC.L(@"The inner error message is: {0}", message); } - public static string IncludeLong { get { return LC.L(@"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]. Filter groups (which encapsulate a built-in set of well-known files and folders) can be specified by using curly braces, i.e. {Applications}."); } } + public static string IncludeLong { get { return LC.L(@"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]. Filter groups (which encapsulate a built-in set of well-known files and folders) can be specified by using curly braces, i.e. {{Applications}}."); } } public static string IncludeShort { get { return LC.L(@"Include files"); } } - public static string ExcludeLong { get { return LC.L(@"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]. Filter groups (which encapsulate a built-in set of well-known files and folders) can be specified by using curly braces, i.e. {TemporaryFiles}."); } } + public static string ExcludeLong { get { return LC.L(@"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]. Filter groups (which encapsulate a built-in set of well-known files and folders) can be specified by using curly braces, i.e. {{TemporaryFiles}}."); } } public static string ExcludeShort { get { return LC.L(@"Exclude files"); } } public static string ControlFilesOptionShort { get { return LC.L(@"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."); } } public static string ControlFilesOptionLong { get { return LC.L(@"Use control files"); } } diff --git a/Duplicati/CommandLine/help.txt b/Duplicati/CommandLine/help.txt index d4976e082..bc8b703f2 100644 --- a/Duplicati/CommandLine/help.txt +++ b/Duplicati/CommandLine/help.txt @@ -292,8 +292,10 @@ Duplicati provides information for debugging and logging purposes. By default, a If something needs to be retried (e.g. upload failed) this will cause an entry in the log file. --log-file = The path to the log file e.g. "D:\duplicati\log.txt". - --log-level = Profiling | Information | Warning | Error - Specifies the log level. + --log-file-log-level = Profiling | Verbose | Information | Warning | Error + Specifies the log level to use for the log file. + --console-log-level = Profiling | Verbose | Information | Warning | Error + Specifies the log level to use for the console log. # ============================================================================= diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/CocoaRunner.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/CocoaRunner.cs index c00a776fd..fbab341e0 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/CocoaRunner.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/CocoaRunner.cs @@ -28,8 +28,8 @@ namespace Duplicati.GUI.TrayIcon { private class MenuItemWrapper : Duplicati.GUI.TrayIcon.IMenuItem { - private NSMenuItem m_item; - private Action m_callback; + private readonly NSMenuItem m_item; + private readonly Action m_callback; public NSMenuItem MenuItem { get { return m_item; } } @@ -93,11 +93,11 @@ namespace Duplicati.GUI.TrayIcon private static readonly string ICON_ERROR = ICON_PATH + "normal-error.png"; private NSStatusItem m_statusItem; - private Dictionary m_images = new Dictionary(); + private readonly Dictionary m_images = new Dictionary(); private NSApplication m_app; // We need to keep the items around, otherwise the GC will destroy them and crash the app - private List m_keeper = new List(); + private readonly List m_keeper = new List(); public override void Init(string[] args) { diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HostedInstanceKeeper.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HostedInstanceKeeper.cs index 8d348213c..4260da6fa 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HostedInstanceKeeper.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HostedInstanceKeeper.cs @@ -10,7 +10,7 @@ namespace Duplicati.GUI.TrayIcon /// internal class HostedInstanceKeeper : IDisposable { - private System.Threading.Thread m_runner; + private readonly System.Threading.Thread m_runner; private System.Exception m_runnerException = null; public event Action InstanceShutdown; diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs index 3b9a509c9..e7e0ff7c4 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs @@ -38,10 +38,10 @@ namespace Duplicati.GUI.TrayIcon } } - private string m_apiUri; - private string m_baseUri; + private readonly string m_apiUri; + private readonly string m_baseUri; private string m_password; - private bool m_saltedpassword; + private readonly bool m_saltedpassword; private string m_authtoken; private string m_xsrftoken; private static readonly System.Text.Encoding ENCODING = System.Text.Encoding.GetEncoding("utf-8"); @@ -59,7 +59,7 @@ namespace Duplicati.GUI.TrayIcon private volatile bool m_shutdown = false; private volatile System.Threading.Thread m_requestThread; private volatile System.Threading.Thread m_pollThread; - private System.Threading.AutoResetEvent m_waitLock; + private readonly System.Threading.AutoResetEvent m_waitLock; private readonly Dictionary m_updateRequest; private readonly Dictionary m_options; @@ -69,7 +69,7 @@ namespace Duplicati.GUI.TrayIcon public IServerStatus Status { get { return m_status; } } private readonly object m_lock = new object(); - private Queue m_workQueue = new Queue(); + private readonly Queue m_workQueue = new Queue(); public HttpServerConnection(Uri server, string password, bool saltedpassword, Program.PasswordSource passwordSource, Dictionary options) { diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs index 958606ba3..e81bcb2af 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs @@ -29,7 +29,7 @@ namespace Duplicati.GUI.TrayIcon private class MenuItemWrapper : Duplicati.GUI.TrayIcon.IMenuItem { - private RumpsRunner m_parent; + private readonly RumpsRunner m_parent; private string m_text; private MenuIcons m_icon; @@ -115,7 +115,7 @@ namespace Duplicati.GUI.TrayIcon private static readonly string ICON_RUNNING = ICON_PATH + "normal-running.png"; private static readonly string ICON_ERROR = ICON_PATH + "normal-error.png"; - private Dictionary m_images = new Dictionary(); + private readonly Dictionary m_images = new Dictionary(); private System.Diagnostics.Process m_rumpsProcess; diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs index 4b4788e81..36c5b0a36 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs @@ -134,7 +134,7 @@ namespace Duplicati.GUI.TrayIcon public virtual IBrowserWindow ShowUrlInWindow(string url) { //Fallback is to just show the window in a browser - Duplicati.Library.Utility.UrlUtillity.OpenURL(url, Program.BrowserCommand); + Duplicati.Library.Utility.UrlUtility.OpenURL(url, Program.BrowserCommand); return null; } diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/WinFormsRunner.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/WinFormsRunner.cs index 8881a8337..1eadd5e0f 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/WinFormsRunner.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/WinFormsRunner.cs @@ -27,8 +27,8 @@ namespace Duplicati.GUI.TrayIcon.Windows { private class MenuItemWrapper : IMenuItem { - private ToolStripItem m_menu; - private Action m_callback; + private readonly ToolStripItem m_menu; + private readonly Action m_callback; public ToolStripItem MenuItem { get { return m_menu; } } diff --git a/Duplicati/Library/AutoUpdater/SignatureReadingStream.cs b/Duplicati/Library/AutoUpdater/SignatureReadingStream.cs index 6e23c05eb..7211a2a24 100644 --- a/Duplicati/Library/AutoUpdater/SignatureReadingStream.cs +++ b/Duplicati/Library/AutoUpdater/SignatureReadingStream.cs @@ -30,7 +30,7 @@ namespace Duplicati.Library.AutoUpdater /// /// The stream to read from /// - private System.IO.Stream m_stream; + private readonly System.IO.Stream m_stream; protected SignatureReadingStream() { diff --git a/Duplicati/Library/AutoUpdater/UpdaterManager.cs b/Duplicati/Library/AutoUpdater/UpdaterManager.cs index dddee284f..fdd406cf1 100644 --- a/Duplicati/Library/AutoUpdater/UpdaterManager.cs +++ b/Duplicati/Library/AutoUpdater/UpdaterManager.cs @@ -616,7 +616,7 @@ namespace Duplicati.Library.AutoUpdater if (string.IsNullOrWhiteSpace(relpath)) continue; - if (IgnoreWebrootFolder && relpath.StartsWith("webroot", Library.Utility.Utility.ClientFilenameStringComparision)) + if (IgnoreWebrootFolder && relpath.StartsWith("webroot", Library.Utility.Utility.ClientFilenameStringComparison)) continue; FileEntry fe; @@ -624,7 +624,7 @@ namespace Duplicati.Library.AutoUpdater { var ignore = false; foreach (var c in ignores) - if (ignore = relpath.StartsWith(c, Library.Utility.Utility.ClientFilenameStringComparision)) + if (ignore = relpath.StartsWith(c, Library.Utility.Utility.ClientFilenameStringComparison)) break; if (ignore) @@ -654,7 +654,7 @@ namespace Duplicati.Library.AutoUpdater var filteredpaths = paths .Where(p => !string.IsNullOrWhiteSpace(p.Key) && !p.Key.EndsWith("/", StringComparison.Ordinal)) - .Where(p => !IgnoreWebrootFolder || !p.Key.StartsWith("webroot", Library.Utility.Utility.ClientFilenameStringComparision)) + .Where(p => !IgnoreWebrootFolder || !p.Key.StartsWith("webroot", Library.Utility.Utility.ClientFilenameStringComparison)) .Select(p => p.Key) .ToList(); diff --git a/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs b/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs index 6b695950a..79070b162 100644 --- a/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs +++ b/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs @@ -45,14 +45,14 @@ namespace Duplicati.Library.Backend.AmazonCloudDrive private EndpointInfo m_endPointInfo; private ResourceModel m_curdir; - private string m_path; - private string[] m_labels; + private readonly string m_path; + private readonly string[] m_labels; - private OAuthHelper m_oauth; + private readonly OAuthHelper m_oauth; private Dictionary m_filecache; - private string m_userid; + private readonly string m_userid; private DateTime m_waitUntil; - private TimeSpan m_delayTimeSpan; + private readonly TimeSpan m_delayTimeSpan; private enum RemoteOperation { diff --git a/Duplicati/Library/Backend/Backblaze/B2.cs b/Duplicati/Library/Backend/Backblaze/B2.cs index 471eb1e53..d295eeeda 100644 --- a/Duplicati/Library/Backend/Backblaze/B2.cs +++ b/Duplicati/Library/Backend/Backblaze/B2.cs @@ -35,12 +35,12 @@ namespace Duplicati.Library.Backend.Backblaze private const int DEFAULT_PAGE_SIZE = 500; - private string m_bucketname; - private string m_prefix; - private string m_urlencodedprefix; - private string m_bucketType; - private int m_pagesize; - private B2AuthHelper m_helper; + private readonly string m_bucketname; + private readonly string m_prefix; + private readonly string m_urlencodedprefix; + private readonly string m_bucketType; + private readonly int m_pagesize; + private readonly B2AuthHelper m_helper; private UploadUrlResponse m_uploadUrl; private Dictionary> m_filecache; diff --git a/Duplicati/Library/Backend/Box/BoxBackend.cs b/Duplicati/Library/Backend/Box/BoxBackend.cs index fe347427f..f1ece85ee 100644 --- a/Duplicati/Library/Backend/Box/BoxBackend.cs +++ b/Duplicati/Library/Backend/Box/BoxBackend.cs @@ -35,9 +35,9 @@ namespace Duplicati.Library.Backend.Box private const int PAGE_SIZE = 200; - private BoxHelper m_oauth; - private string m_path; - private bool m_deleteFromTrash; + private readonly BoxHelper m_oauth; + private readonly string m_path; + private readonly bool m_deleteFromTrash; private string m_currentfolder; private Dictionary m_filecache; diff --git a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs index ad1d553db..dfffe4a8f 100644 --- a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs +++ b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs @@ -39,13 +39,13 @@ namespace Duplicati.Library.Backend private const int ITEM_LIST_LIMIT = 1000; - private string m_username; - private string m_password; - private string m_path; + private readonly string m_username; + private readonly string m_password; + private readonly string m_path; private string m_storageUrl = null; private string m_authToken = null; - private string m_authUrl; + private readonly string m_authUrl; private readonly byte[] m_copybuffer = new byte[Duplicati.Library.Utility.Utility.DEFAULT_BUFFER_SIZE]; diff --git a/Duplicati/Library/Backend/Dropbox/Dropbox.cs b/Duplicati/Library/Backend/Dropbox/Dropbox.cs index 4374bd8a3..fc77efbf2 100644 --- a/Duplicati/Library/Backend/Dropbox/Dropbox.cs +++ b/Duplicati/Library/Backend/Dropbox/Dropbox.cs @@ -10,8 +10,9 @@ namespace Duplicati.Library.Backend private const string AUTHID_OPTION = "authid"; private const int MAX_FILE_LIST = 10000; - private string m_path,m_accesToken; - private DropboxHelper dbx; + private readonly string m_accesToken; + private readonly string m_path; + private readonly DropboxHelper dbx; public Dropbox() { diff --git a/Duplicati/Library/Backend/File/FileBackend.cs b/Duplicati/Library/Backend/File/FileBackend.cs index 5d10a05fa..64bd0f837 100644 --- a/Duplicati/Library/Backend/File/FileBackend.cs +++ b/Duplicati/Library/Backend/File/FileBackend.cs @@ -32,12 +32,12 @@ namespace Duplicati.Library.Backend private const string OPTION_MOVE_FILE = "use-move-for-put"; private const string OPTION_FORCE_REAUTH = "force-smb-authentication"; - private string m_path; + private readonly string m_path; private string m_username; private string m_password; - private bool m_moveFile; + private readonly bool m_moveFile; private bool m_hasAutenticated; - private bool m_forceReauth; + private readonly bool m_forceReauth; private readonly byte[] m_copybuffer = new byte[Duplicati.Library.Utility.Utility.DEFAULT_BUFFER_SIZE]; diff --git a/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj b/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj index 817a57af5..070b4832a 100644 --- a/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj +++ b/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj @@ -41,6 +41,7 @@ + diff --git a/Duplicati/Library/Backend/GoogleServices/GCSConfig.cs b/Duplicati/Library/Backend/GoogleServices/GCSConfig.cs index d51a730ed..ffbdd1ef9 100644 --- a/Duplicati/Library/Backend/GoogleServices/GCSConfig.cs +++ b/Duplicati/Library/Backend/GoogleServices/GCSConfig.cs @@ -14,15 +14,69 @@ // 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 Duplicati.Library.Interface; using System.Collections.Generic; using System.Linq; +using System; +using Duplicati.Library.Interface; +using System.Collections.Generic; +using System.Linq; namespace Duplicati.Library.Backend.GoogleServices { public class GCSConfig : IWebModule - { private const ConfigType DEFAULT_CONFIG_TYPE = ConfigType.Locations; private static readonly string DEFAULT_CONFIG_TYPE_STR = Enum.GetName(typeof(ConfigType), DEFAULT_CONFIG_TYPE); private const string KEY_CONFIGTYPE = "gcs-config"; public enum ConfigType { Locations, StorageClasses } + { + private const ConfigType DEFAULT_CONFIG_TYPE = ConfigType.Locations; + private static readonly string DEFAULT_CONFIG_TYPE_STR = Enum.GetName(typeof(ConfigType), DEFAULT_CONFIG_TYPE); + private const string KEY_CONFIGTYPE = "gcs-config"; + + public enum ConfigType + { + Locations, + StorageClasses + } public GCSConfig() { } - #region IWebModule implementation public System.Collections.Generic.IDictionary Execute(System.Collections.Generic.IDictionary options) { string k; options.TryGetValue(KEY_CONFIGTYPE, out k); if (string.IsNullOrWhiteSpace(k)) k = DEFAULT_CONFIG_TYPE_STR; ConfigType ct; if (!Enum.TryParse(k, true, out ct)) ct = DEFAULT_CONFIG_TYPE; switch (ct) { case ConfigType.StorageClasses: return GoogleCloudStorage.GoogleCloudStorage.KNOWN_GCS_STORAGE_CLASSES.ToDictionary((x) => x.Key, (y) => y.Value); default: return GoogleCloudStorage.GoogleCloudStorage.KNOWN_GCS_LOCATIONS.ToDictionary((x) => x.Key, (y) => y.Value); } } public string Key { get { return "gcs-getconfig"; } } public string DisplayName { get { return "Google Cloud Storage configuration module"; } } public string Description { get { return "Exposes Google Cloud Storage configuration as a web module"; } } public System.Collections.Generic.IList SupportedCommands { get { return new List(new ICommandLineArgument[] { new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, "The config to get", "Provides different config values", DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) }); } } #endregion } + + #region IWebModule implementation + + public System.Collections.Generic.IDictionary Execute(System.Collections.Generic.IDictionary options) + { + string k; + options.TryGetValue(KEY_CONFIGTYPE, out k); + if (string.IsNullOrWhiteSpace(k)) + k = DEFAULT_CONFIG_TYPE_STR; + + ConfigType ct; + if (!Enum.TryParse(k, true, out ct)) + ct = DEFAULT_CONFIG_TYPE; + + switch (ct) + { + case ConfigType.StorageClasses: + return GoogleCloudStorage.GoogleCloudStorage.KNOWN_GCS_STORAGE_CLASSES.ToDictionary((x) => x.Key, (y) => y.Value); + default: + return GoogleCloudStorage.GoogleCloudStorage.KNOWN_GCS_LOCATIONS.ToDictionary((x) => x.Key, (y) => y.Value); + } + } + + public string Key { get { return "gcs-getconfig"; } } + + public string DisplayName { get { return "Google Cloud Storage configuration module"; } } + + public string Description { get { return "Exposes Google Cloud Storage configuration as a web module"; } } + + + public System.Collections.Generic.IList SupportedCommands + { + get + { + return new List(new ICommandLineArgument[] { + new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, "The config to get", "Provides different config values", DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) + + }); + } + } + + #endregion + } } diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs index b426eab5b..8aecae59f 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs @@ -18,7 +18,6 @@ using System; using Duplicati.Library.Interface; using System.Collections.Generic; using System.Net; -using System.Web; using Duplicati.Library.Utility; using Newtonsoft.Json; using System.Text; @@ -62,13 +61,13 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage new KeyValuePair("Nearline", "NEARLINE"), }; - private string m_bucket; - private string m_prefix; - private string m_project; - private OAuthHelper m_oauth; + private readonly string m_bucket; + private readonly string m_prefix; + private readonly string m_project; + private readonly OAuthHelper m_oauth; - private string m_location; - private string m_storage_class; + private readonly string m_location; + private readonly string m_storage_class; public GoogleCloudStorage() { } @@ -97,9 +96,9 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage m_oauth = new OAuthHelper(authid, this.ProtocolKey); m_oauth.AutoAuthHeader = true; - } - - + } + + private class ListBucketResponse { public string kind { get; set; } @@ -205,7 +204,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage if (string.IsNullOrEmpty(m_project)) throw new UserInformationException(Strings.GoogleCloudStorage.ProjectIDMissingError(PROJECT_OPTION), "GoogleCloudStorageMissingProjectID"); - var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new CreateBucketRequest() { + var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new CreateBucketRequest + { name = m_bucket, location = m_location, storageClass = m_storage_class @@ -220,7 +220,7 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage var areq = new AsyncHttpRequest(req); - using(var rs = areq.GetRequestStream()) + using (var rs = areq.GetRequestStream()) rs.Write(data, 0, data.Length); m_oauth.ReadJSONResponse(areq); @@ -246,8 +246,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage foreach (KeyValuePair s in KNOWN_GCS_LOCATIONS) locations.AppendLine(string.Format("{0}: {1}", s.Key, s.Value)); foreach (KeyValuePair s in KNOWN_GCS_STORAGE_CLASSES) - storageClasses.AppendLine(string.Format("{0}: {1}", s.Key, s.Value)); - + storageClasses.AppendLine(string.Format("{0}: {1}", s.Key, s.Value)); + return new List(new ICommandLineArgument[] { new CommandLineArgument(LOCATION_OPTION, CommandLineArgument.ArgumentType.String, Strings.GoogleCloudStorage.LocationDescriptionShort, Strings.GoogleCloudStorage.LocationDescriptionLong(locations.ToString())), new CommandLineArgument(STORAGECLASS_OPTION, CommandLineArgument.ArgumentType.String, Strings.GoogleCloudStorage.StorageclassDescriptionShort, Strings.GoogleCloudStorage.StorageclassDescriptionLong(locations.ToString())), @@ -276,8 +276,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage var res = GoogleCommon.ChunckedUploadWithResume(m_oauth, item, url, stream); if (res == null) - throw new Exception(string.Format("Upload succeeded, but no data was returned")); - + throw new Exception("Upload succeeded, but no data was returned"); + } public void Get(string remotename, System.IO.Stream stream) @@ -288,8 +288,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage var req = m_oauth.CreateRequest(url); var areq = new AsyncHttpRequest(req); - using(var resp = areq.GetResponse()) - using(var rs = areq.GetResponseStream()) + using (var resp = areq.GetResponse()) + using (var rs = areq.GetResponseStream()) Library.Utility.Utility.CopyStream(rs, stream); } catch (WebException wex) @@ -304,7 +304,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage public void Rename(string oldname, string newname) { - var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new BucketResourceItem() { + var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new BucketResourceItem + { name = m_prefix + newname, })); @@ -315,7 +316,7 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage req.ContentType = "application/json; charset=UTF-8"; var areq = new AsyncHttpRequest(req); - using(var rs = areq.GetRequestStream()) + using (var rs = areq.GetRequestStream()) rs.Write(data, 0, data.Length); m_oauth.ReadJSONResponse(req); @@ -323,8 +324,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage #region IDisposable implementation public void Dispose() - { - + { + } #endregion } diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleCommon.cs b/Duplicati/Library/Backend/GoogleServices/GoogleCommon.cs index be58a5e5d..a0d002ba0 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleCommon.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleCommon.cs @@ -14,12 +14,226 @@ // 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 Duplicati.Library.Utility; using System.Net; using Newtonsoft.Json; +using System; +using Duplicati.Library.Utility; +using System.Net; +using Newtonsoft.Json; namespace Duplicati.Library.Backend.GoogleServices { internal static class GoogleCommon - { /// /// The size of upload chunks /// private const long UPLOAD_CHUNK_SIZE = 1024 * 1024 * 10; /// /// Helper method that queries a resumeable upload uri for progress /// /// The upload range. /// The Oauth instance /// The resumeable uploaduri /// The length of the entire stream private static long QueryUploadRange(OAuthHelper oauth, string uploaduri, long streamlength, out T response) where T : class { response = null; var req = oauth.CreateRequest(uploaduri); req.Method = "PUT"; req.ContentLength = 0; req.Headers["Content-Range"] = string.Format("bytes */{0}", streamlength); var areq = new AsyncHttpRequest(req); using(var resp = oauth.GetResponseWithoutException(areq)) { var code = (int)resp.StatusCode; // If the upload is completed, we get 201 or 200 if (code >= 200 && code <= 299) { response = oauth.ReadJSONResponse(resp); if (response == null) throw new Exception(string.Format("Upload succeeded, but no data was returned, status code: {0}", code)); return streamlength; } if (code == 308) { // A lack of a Range header is undocumented, // but seems to occur when no data has reached the server: // https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3884 if (resp.Headers["Range"] == null) return 0; else return long.Parse(resp.Headers["Range"].Split(new char[] { '-' })[1]) + 1; } else throw new WebException(string.Format("Unexpected status code: {0}", code), null, WebExceptionStatus.ServerProtocolViolation, resp); } } /// /// Uploads the requestdata as JSON and starts a resumeable upload session, then uploads the stream in chunks /// /// Serialized result of the last request. /// The Oauth instance. /// The data to submit as JSON metadata. /// The URL to register the upload session against. /// The stream with content data to upload. /// The HTTP Method. /// The type of data to upload as metadata. /// The type of data returned from the upload. public static TResponse ChunckedUploadWithResume(OAuthHelper oauth, TRequest requestdata, string url, System.IO.Stream stream, string method = "POST") where TRequest : class where TResponse : class { var data = requestdata == null ? null : System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(requestdata)); var req = oauth.CreateRequest(url); req.Method = method; req.ContentLength = data == null ? 0 : data.Length; if (data != null) req.ContentType = "application/json; charset=UTF-8"; req.Headers["X-Upload-Content-Type"] = "application/octet-stream"; req.Headers["X-Upload-Content-Length"] = stream.Length.ToString(); var areq = new AsyncHttpRequest(req); if (data != null) using(var rs = areq.GetRequestStream()) rs.Write(data, 0, data.Length); string uploaduri; using(var resp = (HttpWebResponse)areq.GetResponse()) { if (resp.StatusCode != HttpStatusCode.OK || string.IsNullOrWhiteSpace(resp.Headers["Location"])) throw new WebException("Failed to start upload session", null, WebExceptionStatus.UnknownError, resp); uploaduri = resp.Headers["Location"]; } return ChunkedUpload(oauth, uploaduri, stream); } /// /// Helper method that performs a chunked upload, and queries for http status after each chunk /// /// The response item /// The Oauth instance /// The resumeable uploaduri /// The stream with data to upload. /// The type of data in the response. private static T ChunkedUpload(OAuthHelper oauth, string uploaduri, System.IO.Stream stream) where T : class { var queryRange = false; var retries = 0; var offset = 0L; var buffer = new byte[Library.Utility.Utility.DEFAULT_BUFFER_SIZE]; // Repeatedly try uploading until all retries are done while(true) { try { if (queryRange) { T re; offset = GoogleCommon.QueryUploadRange(oauth, uploaduri, stream.Length, out re); queryRange = false; if (re != null) return re; } //Seek into the right place if (stream.Position != offset) stream.Position = offset; var req = oauth.CreateRequest(uploaduri); req.Method = "PUT"; req.ContentType = "application/octet-stream"; var chunkSize = Math.Min(UPLOAD_CHUNK_SIZE, stream.Length - offset); req.ContentLength = chunkSize; req.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", offset, offset + chunkSize - 1, stream.Length);; // Upload the remaining data var areq = new AsyncHttpRequest(req); using(var rs = areq.GetRequestStream()) { var remaining = chunkSize; while(remaining > 0) { var n = stream.Read(buffer, 0, (int)Math.Min(remaining, Library.Utility.Utility.DEFAULT_BUFFER_SIZE)); rs.Write(buffer, 0, n); remaining -= n; } } // Check the response using(var resp = oauth.GetResponseWithoutException(areq)) { var code = (int)resp.StatusCode; if (code == 308 && resp.Headers["Range"] != null) { offset = long.Parse(resp.Headers["Range"].Split(new char[] {'-'})[1]) + 1; retries = 0; } else if (code >= 200 && code <= 299) { offset += chunkSize; if (offset != stream.Length) throw new Exception(string.Format("Upload succeeded prematurely. Uploaded: {0}, total size: {1}", offset, stream.Length)); //Verify that the response is also valid var res = oauth.ReadJSONResponse(resp); if (res == null) throw new Exception(string.Format("Upload succeeded, but no data was returned, status code: {0}", code)); return res; } else { throw new WebException(string.Format("Unexpected status code: {0}", code), null, WebExceptionStatus.ServerProtocolViolation, resp); } } } catch (Exception ex) { var retry = false; // If we get a 5xx error, or some network issue, we retry if (ex is WebException && ((WebException)ex).Response is HttpWebResponse) { var code = (int)((HttpWebResponse)((WebException)ex).Response).StatusCode; retry = code >= 500 && code <= 599; } else if (ex is System.Net.Sockets.SocketException || ex is System.IO.IOException || ex.InnerException is System.Net.Sockets.SocketException || ex.InnerException is System.IO.IOException) { retry = true; } // Retry with exponential backoff if (retry && retries < 5) { System.Threading.Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, retries))); retries++; // Ask server where we left off queryRange = true; } else throw; } } } + { + /// + /// The size of upload chunks + /// + private const long UPLOAD_CHUNK_SIZE = 1024 * 1024 * 10; + + /// + /// Helper method that queries a resumeable upload uri for progress + /// + /// The upload range. + /// The Oauth instance + /// The resumeable uploaduri + /// The length of the entire stream + private static long QueryUploadRange(OAuthHelper oauth, string uploaduri, long streamlength, out T response) + where T : class + { + response = null; + var req = oauth.CreateRequest(uploaduri); + req.Method = "PUT"; + req.ContentLength = 0; + req.Headers["Content-Range"] = string.Format("bytes */{0}", streamlength); + + var areq = new AsyncHttpRequest(req); + using(var resp = oauth.GetResponseWithoutException(areq)) + { + var code = (int)resp.StatusCode; + + // If the upload is completed, we get 201 or 200 + if (code >= 200 && code <= 299) + { + response = oauth.ReadJSONResponse(resp); + if (response == null) + throw new Exception(string.Format("Upload succeeded, but no data was returned, status code: {0}", code)); + + return streamlength; + } + + if (code == 308) + { + // A lack of a Range header is undocumented, + // but seems to occur when no data has reached the server: + // https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3884 + + if (resp.Headers["Range"] == null) + return 0; + else + return long.Parse(resp.Headers["Range"].Split(new char[] { '-' })[1]) + 1; + } + else + throw new WebException(string.Format("Unexpected status code: {0}", code), null, WebExceptionStatus.ServerProtocolViolation, resp); + } + } + + /// + /// Uploads the requestdata as JSON and starts a resumeable upload session, then uploads the stream in chunks + /// + /// Serialized result of the last request. + /// The Oauth instance. + /// The data to submit as JSON metadata. + /// The URL to register the upload session against. + /// The stream with content data to upload. + /// The HTTP Method. + /// The type of data to upload as metadata. + /// The type of data returned from the upload. + public static TResponse ChunckedUploadWithResume(OAuthHelper oauth, TRequest requestdata, string url, System.IO.Stream stream, string method = "POST") + where TRequest : class + where TResponse : class + { + var data = requestdata == null ? null : System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(requestdata)); + + var req = oauth.CreateRequest(url); + req.Method = method; + req.ContentLength = data == null ? 0 : data.Length; + + if (data != null) + req.ContentType = "application/json; charset=UTF-8"; + + req.Headers["X-Upload-Content-Type"] = "application/octet-stream"; + req.Headers["X-Upload-Content-Length"] = stream.Length.ToString(); + + var areq = new AsyncHttpRequest(req); + if (data != null) + using(var rs = areq.GetRequestStream()) + rs.Write(data, 0, data.Length); + + string uploaduri; + using(var resp = (HttpWebResponse)areq.GetResponse()) + { + if (resp.StatusCode != HttpStatusCode.OK || string.IsNullOrWhiteSpace(resp.Headers["Location"])) + throw new WebException("Failed to start upload session", null, WebExceptionStatus.UnknownError, resp); + + uploaduri = resp.Headers["Location"]; + } + + return ChunkedUpload(oauth, uploaduri, stream); + } + + /// + /// Helper method that performs a chunked upload, and queries for http status after each chunk + /// + /// The response item + /// The Oauth instance + /// The resumeable uploaduri + /// The stream with data to upload. + /// The type of data in the response. + private static T ChunkedUpload(OAuthHelper oauth, string uploaduri, System.IO.Stream stream) + where T : class + { + var queryRange = false; + var retries = 0; + var offset = 0L; + var buffer = new byte[Library.Utility.Utility.DEFAULT_BUFFER_SIZE]; + + // Repeatedly try uploading until all retries are done + while(true) + { + try + { + if (queryRange) + { + T re; + offset = GoogleCommon.QueryUploadRange(oauth, uploaduri, stream.Length, out re); + queryRange = false; + + if (re != null) + return re; + } + + //Seek into the right place + if (stream.Position != offset) + stream.Position = offset; + + var req = oauth.CreateRequest(uploaduri); + req.Method = "PUT"; + req.ContentType = "application/octet-stream"; + + var chunkSize = Math.Min(UPLOAD_CHUNK_SIZE, stream.Length - offset); + + req.ContentLength = chunkSize; + req.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", offset, offset + chunkSize - 1, stream.Length);; + + // Upload the remaining data + var areq = new AsyncHttpRequest(req); + using(var rs = areq.GetRequestStream()) + { + var remaining = chunkSize; + while(remaining > 0) + { + var n = stream.Read(buffer, 0, (int)Math.Min(remaining, Library.Utility.Utility.DEFAULT_BUFFER_SIZE)); + rs.Write(buffer, 0, n); + remaining -= n; + } + } + + // Check the response + using(var resp = oauth.GetResponseWithoutException(areq)) + { + var code = (int)resp.StatusCode; + + if (code == 308 && resp.Headers["Range"] != null) + { + offset = long.Parse(resp.Headers["Range"].Split(new char[] {'-'})[1]) + 1; + retries = 0; + } + else if (code >= 200 && code <= 299) + { + offset += chunkSize; + if (offset != stream.Length) + throw new Exception(string.Format("Upload succeeded prematurely. Uploaded: {0}, total size: {1}", offset, stream.Length)); + + //Verify that the response is also valid + var res = oauth.ReadJSONResponse(resp); + if (res == null) + throw new Exception(string.Format("Upload succeeded, but no data was returned, status code: {0}", code)); + + return res; + } + else + { + throw new WebException(string.Format("Unexpected status code: {0}", code), null, WebExceptionStatus.ServerProtocolViolation, resp); + } + } + } + catch (Exception ex) + { + var retry = false; + + // If we get a 5xx error, or some network issue, we retry + if (ex is WebException && ((WebException)ex).Response is HttpWebResponse) + { + var code = (int)((HttpWebResponse)((WebException)ex).Response).StatusCode; + retry = code >= 500 && code <= 599; + } + else if (ex is System.Net.Sockets.SocketException || ex is System.IO.IOException || ex.InnerException is System.Net.Sockets.SocketException || ex.InnerException is System.IO.IOException) + { + retry = true; + } + + // Retry with exponential backoff + if (retry && retries < 5) + { + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, retries))); + retries++; + + // Ask server where we left off + queryRange = true; + } + else + throw; + } + } + } } } diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs index fe846c529..9cf16cb1c 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs @@ -15,14 +15,18 @@ // 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.Collections.Specialized; using System.Linq; +using System.Net; + +using Newtonsoft.Json; + +using Duplicati.Library.Backend.GoogleServices; using Duplicati.Library.Interface; using Duplicati.Library.Utility; -using System.Collections.Generic; -using System.Net; -using Newtonsoft.Json; -using Duplicati.Library.Backend.GoogleServices; - +using System.Text; + namespace Duplicati.Library.Backend.GoogleDrive { public class GoogleDrive : IBackend, IStreamingBackend, IQuotaEnabledBackend, IRenameEnabledBackend @@ -31,15 +35,13 @@ namespace Duplicati.Library.Backend.GoogleDrive private const string DISABLE_TEAMDRIVE_OPTION = "googledrive-disable-teamdrive"; private const string FOLDER_MIMETYPE = "application/vnd.google-apps.folder"; - private const string DRIVE_API_UPLOAD_URL = "https://www.googleapis.com/upload/drive/v2"; - private const string DRIVE_API_URL = "https://www.googleapis.com/drive/v2"; + + private readonly string m_path; + private readonly bool m_useTeamDrive = true; - private string m_path; - private bool m_useTeamDrive = true; - - private OAuthHelper m_oauth; + private readonly OAuthHelper m_oauth; private string m_currentFolderId; - private Dictionary m_filecache; + private readonly Dictionary m_filecache; public GoogleDrive() { @@ -66,26 +68,30 @@ namespace Duplicati.Library.Backend.GoogleDrive private string GetFolderId(string path, bool autocreate = false) { - var curparent = GetAboutInfo().rootFolderId; - var curdisplay = "/"; + var curparent = GetAboutInfo().rootFolderId; + var curdisplay = new StringBuilder("/"); - foreach(var p in path.Split(new char[] {'/'}, StringSplitOptions.RemoveEmptyEntries)) + foreach (var p in path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)) { - var res = ListFolder(curparent, true, p).ToArray(); - - if (res.Length == 0) - { - if (!autocreate) - throw new FolderMissingException(); - - curparent = CreateFolder(p, curparent).id; - } - else if (res.Length > 1) - throw new UserInformationException(Strings.GoogleDrive.MultipleEntries(p, curdisplay), "GoogleDriveMultipleEntries"); - else - curparent = res[0].id; - - curdisplay += p + "/"; + var res = ListFolder(curparent, true, p).ToArray(); + + if (res.Length == 0) + { + if (!autocreate) + throw new FolderMissingException(); + + curparent = CreateFolder(p, curparent).id; + } + else if (res.Length > 1) + { + throw new UserInformationException(Strings.GoogleDrive.MultipleEntries(p, curdisplay.ToString()), "GoogleDriveMultipleEntries"); + } + else + { + curparent = res[0].id; + } + + curdisplay.Append(p).Append("/"); } return curparent; @@ -110,17 +116,17 @@ namespace Duplicati.Library.Backend.GoogleDrive if (entries != null) return entries; - var list = ListFolder(CurrentFolderId, false, remotename).ToArray(); + entries = ListFolder(CurrentFolderId, false, remotename).ToArray(); - if (list == null || list.Length == 0) + if (entries == null || entries.Length == 0) { if (throwMissingException) throw new FileMissingException(); else return null; } - - return m_filecache[remotename] = list; + + return m_filecache[remotename] = entries; } private static string EscapeTitleEntries(string title) @@ -141,27 +147,32 @@ namespace Duplicati.Library.Backend.GoogleDrive GoogleDriveFolderItem[] files; m_filecache.TryGetValue(remotename, out files); - string fileid = null; + string fileId = null; if (files != null) { if (files.Length == 1) - fileid = files[0].id; + fileId = files[0].id; else Delete(remotename); } - var isUpdate = !string.IsNullOrWhiteSpace(fileid); + var isUpdate = !string.IsNullOrWhiteSpace(fileId); + + var values = new NameValueCollection { + { WebApi.GoogleDrive.QueryParam.UploadType, + WebApi.GoogleDrive.QueryValue.Resumable } }; var url = isUpdate ? - string.Format("{0}/files/{1}?uploadType=resumable", DRIVE_API_UPLOAD_URL, Library.Utility.Uri.UrlPathEncode(fileid)) : - string.Format("{0}/files?uploadType=resumable", DRIVE_API_UPLOAD_URL); + WebApi.GoogleDrive.FileUploadUrl(Library.Utility.Uri.UrlPathEncode(fileId), values) : + WebApi.GoogleDrive.FileUploadUrl(values); - var item = new GoogleDriveFolderItem() { + var item = new GoogleDriveFolderItem + { title = remotename, description = remotename, mimeType = "application/octet-stream", labels = new GoogleDriveFolderItemLabels { hidden = true }, - parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference() { id = CurrentFolderId } } + parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference { id = CurrentFolderId } } }; var res = GoogleCommon.ChunckedUploadWithResume(m_oauth, item, url, stream, isUpdate ? "PUT" : "POST"); @@ -172,7 +183,6 @@ namespace Duplicati.Library.Backend.GoogleDrive m_filecache.Clear(); throw; } - } public void Get(string remotename, System.IO.Stream stream) @@ -181,12 +191,15 @@ namespace Duplicati.Library.Backend.GoogleDrive if (m_filecache.Count == 0) foreach (var file in List()) { /* Enumerate the full listing */ } - var fileid = GetFileEntries(remotename).OrderByDescending(x => x.createdDate).First().id; + var fileId = GetFileEntries(remotename).OrderByDescending(x => x.createdDate).First().id; - var req = m_oauth.CreateRequest(string.Format("{0}/files/{1}?alt=media{2}", DRIVE_API_URL, fileid, m_useTeamDrive ? "&supportsTeamDrives=true" : string.Empty)); + var url = WebApi.GoogleDrive.FileQueryUrl(fileId, new NameValueCollection{ + { WebApi.GoogleDrive.QueryParam.Alt, WebApi.GoogleDrive.QueryValue.Media } + }); + var req = m_oauth.CreateRequest(url); var areq = new AsyncHttpRequest(req); - using(var resp = (HttpWebResponse)areq.GetResponse()) - using(var rs = areq.GetResponseStream()) + using (var resp = (HttpWebResponse)areq.GetResponse()) + using (var rs = areq.GetResponseStream()) Duplicati.Library.Utility.Utility.CopyStream(rs, stream); } @@ -247,7 +260,7 @@ namespace Duplicati.Library.Backend.GoogleDrive } } } - + public void Put(string remotename, string filename) { using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) @@ -264,11 +277,11 @@ namespace Duplicati.Library.Backend.GoogleDrive { try { - foreach(var fileid in from n in GetFileEntries(remotename) select n.id) + foreach (var fileid in from n in GetFileEntries(remotename) select n.id) { - var url = string.Format("{0}/files/{1}{2}", DRIVE_API_URL, Library.Utility.Uri.UrlPathEncode(fileid), m_useTeamDrive ? "?supportsTeamDrives=true" : string.Empty); - - m_oauth.GetJSONData(url, x => { + var url = WebApi.GoogleDrive.FileQueryUrl(Library.Utility.Uri.UrlPathEncode(fileid), SupportsTeamDriveParam()); + m_oauth.GetJSONData(url, x => + { x.Method = "DELETE"; }); } @@ -310,15 +323,16 @@ namespace Duplicati.Library.Backend.GoogleDrive } } - public System.Collections.Generic.IList SupportedCommands - { - get { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(AUTHID_OPTION, CommandLineArgument.ArgumentType.Password, Strings.GoogleDrive.AuthidShort, Strings.GoogleDrive.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("googledrive"))), - new CommandLineArgument(DISABLE_TEAMDRIVE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.GoogleDrive.DisableTeamDriveShort, Strings.GoogleDrive.DisableTeamDriveLong), + public System.Collections.Generic.IList SupportedCommands => new List(new ICommandLineArgument[] { + new CommandLineArgument(AUTHID_OPTION, + CommandLineArgument.ArgumentType.Password, + Strings.GoogleDrive.AuthidShort, + Strings.GoogleDrive.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("googledrive"))), + new CommandLineArgument(DISABLE_TEAMDRIVE_OPTION, + CommandLineArgument.ArgumentType.Boolean, + Strings.GoogleDrive.DisableTeamDriveShort, + Strings.GoogleDrive.DisableTeamDriveLong), }); - } - } public string Description { @@ -348,8 +362,8 @@ namespace Duplicati.Library.Backend.GoogleDrive } public string[] DNSName - { - get { return new string[] { new System.Uri(DRIVE_API_URL).Host, new System.Uri(DRIVE_API_UPLOAD_URL).Host }; } + { + get { return new string[] { new System.Uri(WebApi.GoogleDrive.Url.DRIVE).Host, new System.Uri(WebApi.GoogleDrive.Url.UPLOAD).Host }; } } #endregion @@ -365,21 +379,22 @@ namespace Duplicati.Library.Backend.GoogleDrive var newfile = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(files[0])); newfile.title = newname; - newfile.parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference() { id = CurrentFolderId } }; + newfile.parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference { id = CurrentFolderId } }; - var url = string.Format("{0}/files/{1}", DRIVE_API_UPLOAD_URL, Library.Utility.Uri.UrlPathEncode(files[0].id)); + var url = WebApi.GoogleDrive.FileQueryUrl(Library.Utility.Uri.UrlPathEncode(files[0].id)); var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(newfile)); - var nf = m_oauth.GetJSONData(url, x => { + var nf = m_oauth.GetJSONData(url, x => + { x.Method = "PUT"; x.ContentLength = data.Length; x.ContentType = "application/json; charset=UTF-8"; - }, x => { - - using(var rs = x.GetRequestStream()) + }, x => + { + using (var rs = x.GetRequestStream()) rs.Write(data, 0, data.Length); }); - + m_filecache[newname] = new GoogleDriveFolderItem[] { nf }; m_filecache.Remove(oldname); } @@ -397,9 +412,24 @@ namespace Duplicati.Library.Backend.GoogleDrive public void Dispose() { + } + + #endregion + + private NameValueCollection SupportsTeamDriveParam() + { + return m_useTeamDrive ? new NameValueCollection { + { WebApi.GoogleDrive.QueryParam.SupportsTeamDrive, + WebApi.GoogleDrive.QueryValue.True } + } : null; } - #endregion + private NameValueCollection IncludeTeamDriveParam() + { + return m_useTeamDrive ? new NameValueCollection { + { WebApi.GoogleDrive.QueryParam.IncludeTeamDrive, + WebApi.GoogleDrive.QueryValue.True } } : null; + } private class GoogleDriveParentReference { @@ -470,40 +500,52 @@ namespace Duplicati.Library.Backend.GoogleDrive private IEnumerable ListFolder(string parentfolder, bool? onlyFolders = null, string name = null) { - var p = new string[] { + var fileQuery = new string[] { string.IsNullOrEmpty(name) ? null : string.Format("title = '{0}'", EscapeTitleEntries(name)), onlyFolders == null ? null : string.Format("mimeType {0}= '{1}'", onlyFolders.Value ? "" : "!", FOLDER_MIMETYPE), string.Format("'{0}' in parents", EscapeTitleEntries(parentfolder)) }; - var url = string.Format("{0}/files?q={1}{2}", DRIVE_API_URL, Library.Utility.Uri.UrlEncode(string.Join(" and ", p.Where(x => x != null))), m_useTeamDrive ? "&supportsTeamDrives=true&includeTeamDriveItems=true" : string.Empty); - var token = string.Empty; + var queryParams = new NameValueCollection + { + {WebApi.GoogleDrive.QueryParam.File, + Library.Utility.Uri.UrlEncode(string.Join(" and ", fileQuery.Where(x => x != null)))}, + }; + queryParams.Add(SupportsTeamDriveParam()); + queryParams.Add(IncludeTeamDriveParam()); - do + while (true) { - var res = m_oauth.GetJSONData(url + (string.IsNullOrWhiteSpace(token) ? "" : "&pageToken=" + Library.Utility.Uri.UrlEncode(token))); + var url = WebApi.GoogleDrive.FileQueryUrl(queryParams); + var res = m_oauth.GetJSONData(url); foreach (var n in res.items) yield return n; - token = res.nextPageToken; - } while (!string.IsNullOrWhiteSpace(token)); + var token = res.nextPageToken; + if (string.IsNullOrWhiteSpace(token)) + break; + + queryParams.Set(WebApi.GoogleDrive.QueryParam.PageToken, token); + } } private GoogleDriveAboutResponse GetAboutInfo() - { - var url = string.Format("{0}/about", DRIVE_API_URL); + { + var url = Library.Utility.Uri.UriBuilder(WebApi.GoogleDrive.Url.DRIVE, WebApi.GoogleDrive.Path.About); return m_oauth.GetJSONData(url); } private GoogleDriveFolderItem CreateFolder(string name, string parent) { - var url = string.Format("{0}/files{1}", DRIVE_API_URL, m_useTeamDrive ? "?supportsTeamDrives=true" : string.Empty); - var folder = new GoogleDriveFolderItem() { + var url = WebApi.GoogleDrive.FileQueryUrl(SupportsTeamDriveParam()); + + var folder = new GoogleDriveFolderItem() + { title = name, description = name, mimeType = FOLDER_MIMETYPE, labels = new GoogleDriveFolderItemLabels { hidden = true }, - parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference() { id = parent } } + parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference { id = parent } } }; var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(folder)); @@ -514,13 +556,11 @@ namespace Duplicati.Library.Backend.GoogleDrive x.ContentType = "application/json; charset=UTF-8"; x.ContentLength = data.Length; - }, req => { - - using(var rs = req.GetRequestStream()) + }, req => + { + using (var rs = req.GetRequestStream()) rs.Write(data, 0, data.Length); - }); - } } } diff --git a/Duplicati/Library/Backend/GoogleServices/WebApi.cs b/Duplicati/Library/Backend/GoogleServices/WebApi.cs new file mode 100644 index 000000000..0c53eeb63 --- /dev/null +++ b/Duplicati/Library/Backend/GoogleServices/WebApi.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2018, The Duplicati Team +// http://www.duplicati.com, info@duplicati.com +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +using System.Collections.Specialized; + +namespace Duplicati.Library.Backend.WebApi +{ + static class GoogleDrive + { + public static class Url + { + public const string DRIVE = "https://www.googleapis.com/drive/v2"; + public const string UPLOAD = "https://www.googleapis.com/upload/drive/v2"; + } + + public static class Path + { + public const string File = "files"; + public const string About = "about"; + } + + public static class QueryParam + { + public const string File = "q"; + public const string SupportsTeamDrive = "supportsTeamDrives"; + public const string IncludeTeamDrive = "includeTeamDriveItems"; + public const string PageToken = "pageToken"; + public const string UploadType = "uploadType"; + public const string Alt = "alt"; + } + + public static class QueryValue + { + public const string True = "true"; + public const string Resumable = "resumable"; + public const string Media = "media"; + } + + public static string FileQueryUrl(NameValueCollection values) + { + return Library.Utility.Uri.UriBuilder(Url.DRIVE, Path.File, values); + } + + public static string FileQueryUrl(string fileId, NameValueCollection values = null) + { + return Library.Utility.Uri.UriBuilder(Url.DRIVE, Library.Utility.Uri.ConcatPaths(Path.File, fileId), values); + } + + public static string FileUploadUrl(string fileId, NameValueCollection values) + { + return Library.Utility.Uri.UriBuilder(Url.UPLOAD, Library.Utility.Uri.ConcatPaths(Path.File, fileId), values); + } + + public static string FileUploadUrl(NameValueCollection values) + { + return Library.Utility.Uri.UriBuilder(Url.UPLOAD, Path.File, values); + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/HubiC/HubiCBackend.cs b/Duplicati/Library/Backend/HubiC/HubiCBackend.cs index ec604c063..da6942e63 100644 --- a/Duplicati/Library/Backend/HubiC/HubiCBackend.cs +++ b/Duplicati/Library/Backend/HubiC/HubiCBackend.cs @@ -39,7 +39,7 @@ namespace Duplicati.Library.Backend.HubiC private class OpenStackHelper : OpenStackStorage { - private OAuthHelper m_helper; + private readonly OAuthHelper m_helper; private HubiCAuthResponse m_token; public OpenStackHelper(string authid, string url) diff --git a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs index 39640de6f..3e2013f12 100644 --- a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs +++ b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs @@ -44,7 +44,7 @@ namespace Duplicati.Library.Backend private readonly string m_url_device; private readonly string m_url; private readonly string m_url_upload; - private System.Net.NetworkCredential m_userInfo; + private readonly System.Net.NetworkCredential m_userInfo; private readonly byte[] m_copybuffer = new byte[Duplicati.Library.Utility.Utility.DEFAULT_BUFFER_SIZE]; public Jottacloud() diff --git a/Duplicati/Library/Backend/Mega/MegaBackend.cs b/Duplicati/Library/Backend/Mega/MegaBackend.cs index 21a72e24b..f60d9f7e5 100644 --- a/Duplicati/Library/Backend/Mega/MegaBackend.cs +++ b/Duplicati/Library/Backend/Mega/MegaBackend.cs @@ -24,11 +24,11 @@ namespace Duplicati.Library.Backend.Mega { public class MegaBackend: IBackend, IStreamingBackend { - private string m_username = null; - private string m_password = null; + private readonly string m_username = null; + private readonly string m_password = null; private Dictionary> m_filecache; private INode m_currentFolder = null; - private string m_prefix = null; + private readonly string m_prefix = null; private MegaApiClient m_client; diff --git a/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs b/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs index 0b82f4c00..53be6521a 100644 --- a/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs +++ b/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs @@ -140,9 +140,9 @@ namespace Duplicati.Library /// The deserialized JSON data. /// The remote URL /// A callback method that can be used to customize the request, e.g. by setting the method, content-type and headers. - /// A callback method that can be used to submit data into the body of the request. + /// A callback method that can be used to submit data into the body of the request. /// The type of data to return. - public virtual T GetJSONData(string url, Action setup = null, Action setupreq = null) + public virtual T GetJSONData(string url, Action setup = null, Action setupbodyreq = null) { var req = CreateRequest(url); @@ -151,8 +151,8 @@ namespace Duplicati.Library var areq = new AsyncHttpRequest(req); - if (setupreq != null) - setupreq(areq); + if (setupbodyreq != null) + setupbodyreq(areq); return ReadJSONResponse(areq); } diff --git a/Duplicati/Library/Backend/OneDrive/OneDrive.cs b/Duplicati/Library/Backend/OneDrive/OneDrive.cs index 18bd0fb21..4435861e9 100644 --- a/Duplicati/Library/Backend/OneDrive/OneDrive.cs +++ b/Duplicati/Library/Backend/OneDrive/OneDrive.cs @@ -28,14 +28,14 @@ namespace Duplicati.Library.Backend 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; + private readonly string m_rootfolder; + private readonly string m_prefix; private string m_userid; private WLID_FolderItem m_folderid; - private OAuthHelper m_oauth; + private readonly OAuthHelper m_oauth; - private Dictionary m_fileidCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary m_fileidCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly byte[] m_copybuffer = new byte[Duplicati.Library.Utility.Utility.DEFAULT_BUFFER_SIZE]; diff --git a/Duplicati/Library/Backend/OpenStack/OpenStackConfig.cs b/Duplicati/Library/Backend/OpenStack/OpenStackConfig.cs index a8ed5c5f7..340039826 100644 --- a/Duplicati/Library/Backend/OpenStack/OpenStackConfig.cs +++ b/Duplicati/Library/Backend/OpenStack/OpenStackConfig.cs @@ -14,18 +14,65 @@ // 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 Duplicati.Library.Interface; using System.Collections.Generic; using System.Linq; +using System; +using Duplicati.Library.Interface; +using System.Collections.Generic; +using System.Linq; namespace Duplicati.Library.Backend.OpenStack { public class SwiftConfig : IWebModule - { private const ConfigType DEFAULT_CONFIG_TYPE = ConfigType.Providers; private static readonly string DEFAULT_CONFIG_TYPE_STR = Enum.GetName(typeof(ConfigType), DEFAULT_CONFIG_TYPE); private const string KEY_CONFIGTYPE = "openstack-config"; public enum ConfigType { Providers, - Versions, } + { + private const ConfigType DEFAULT_CONFIG_TYPE = ConfigType.Providers; + private static readonly string DEFAULT_CONFIG_TYPE_STR = Enum.GetName(typeof(ConfigType), DEFAULT_CONFIG_TYPE); + private const string KEY_CONFIGTYPE = "openstack-config"; + + public enum ConfigType + { + Providers, + Versions, + } public SwiftConfig() { - } public System.Collections.Generic.IDictionary Execute(System.Collections.Generic.IDictionary options) { string k; options.TryGetValue(KEY_CONFIGTYPE, out k); if (string.IsNullOrWhiteSpace(k)) k = DEFAULT_CONFIG_TYPE_STR; ConfigType ct; if (!Enum.TryParse(k, true, out ct)) ct = DEFAULT_CONFIG_TYPE; switch (ct) { + } + + public System.Collections.Generic.IDictionary Execute(System.Collections.Generic.IDictionary options) + { + string k; + options.TryGetValue(KEY_CONFIGTYPE, out k); + if (string.IsNullOrWhiteSpace(k)) + k = DEFAULT_CONFIG_TYPE_STR; + + ConfigType ct; + if (!Enum.TryParse(k, true, out ct)) + ct = DEFAULT_CONFIG_TYPE; + + switch (ct) + { case ConfigType.Versions: - return OpenStack.OpenStackStorage.OPENSTACK_VERSIONS.ToDictionary((x) => x.Key, (y) => y.Value); default: return OpenStack.OpenStackStorage.KNOWN_OPENSTACK_PROVIDERS.ToDictionary((x) => x.Key, (y) => y.Value); } } public string Key { get { return "openstack-getconfig"; } } public string DisplayName { get { return "OpenStack configuration module"; } } public string Description { get { return "Exposes OpenStack configuration as a web module"; } } public System.Collections.Generic.IList SupportedCommands { get { return new List(new ICommandLineArgument[] { new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, "The config to get", "Provides different config values", DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) }); } } + return OpenStack.OpenStackStorage.OPENSTACK_VERSIONS.ToDictionary((x) => x.Key, (y) => y.Value); + default: + return OpenStack.OpenStackStorage.KNOWN_OPENSTACK_PROVIDERS.ToDictionary((x) => x.Key, (y) => y.Value); + } + } + + public string Key { get { return "openstack-getconfig"; } } + + public string DisplayName { get { return "OpenStack configuration module"; } } + + public string Description { get { return "Exposes OpenStack configuration as a web module"; } } + + + public System.Collections.Generic.IList SupportedCommands + { + get + { + return new List(new ICommandLineArgument[] { + new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, "The config to get", "Provides different config values", DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) + + }); + } + } } } diff --git a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs index e8915a629..952091942 100644 --- a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs +++ b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs @@ -64,6 +64,7 @@ namespace Duplicati.Library.Backend.OpenStack new KeyValuePair("Rackspace UK", "https://lon.identity.api.rackspacecloud.com/v2.0"), new KeyValuePair("OVH Cloud Storage", "https://auth.cloud.ovh.net/v2.0"), new KeyValuePair("Selectel Cloud Storage", "https://auth.selcdn.ru"), + new KeyValuePair("Memset Cloud Storage", "https://auth.storage.memset.com"), }; public static readonly KeyValuePair[] OPENSTACK_VERSIONS = { diff --git a/Duplicati/Library/Backend/OpenStack/Strings.cs b/Duplicati/Library/Backend/OpenStack/Strings.cs index 692083b5f..a4c8a5adb 100644 --- a/Duplicati/Library/Backend/OpenStack/Strings.cs +++ b/Duplicati/Library/Backend/OpenStack/Strings.cs @@ -13,15 +13,33 @@ // // 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 Duplicati.Library.Localization.Short; +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend.Strings { - internal static class OpenStack { public static string Description { get { return LC.L(@"This backend can read and write data to Swift (OpenStack Object Storage). Supported format is ""openstack://container/folder""."); } } public static string DisplayName { get { return LC.L(@"OpenStack Simple Storage"); } } public static string MissingOptionError(string optionname) { return LC.L(@"Missing required option: {0}", optionname); } public static string PasswordOptionLong(string tenantnameoption) { return LC.L(@"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", tenantnameoption); } public static string PasswordOptionShort { get { return LC.L(@"Supplies the password used to connect to the server"); } } public static string DomainnameOptionLong { get { return LC.L(@"The domain name of the user used to connect to the server."); } } + internal static class OpenStack { + public static string Description { get { return LC.L(@"This backend can read and write data to Swift (OpenStack Object Storage). Supported format is ""openstack://container/folder""."); } } + public static string DisplayName { get { return LC.L(@"OpenStack Simple Storage"); } } + public static string MissingOptionError(string optionname) { return LC.L(@"Missing required option: {0}", optionname); } + public static string PasswordOptionLong(string tenantnameoption) { return LC.L(@"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", tenantnameoption); } + public static string PasswordOptionShort { get { return LC.L(@"Supplies the password used to connect to the server"); } } + public static string DomainnameOptionLong { get { return LC.L(@"The domain name of the user used to connect to the server."); } } public static string DomainnameOptionShort { get { return LC.L(@"Supplies the domain used to connect to the server"); } } public static string UsernameOptionLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } public static string UsernameOptionShort { get { return LC.L(@"Supplies the username used to connect to the server"); } } - public static string TenantnameOptionLong { get { return LC.L(@"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."); } } public static string TenantnameOptionShort { get { return LC.L(@"Supplies the Tenant Name used to connect to the server"); } } public static string ApikeyOptionLong { get { return LC.L(@"The API key can be used to connect without supplying a password and tenant ID with some providers."); } } public static string ApikeyOptionShort { get { return LC.L(@"Supplies the API key used to connect to the server"); } } public static string AuthuriOptionLong(string providers) { return LC.L(@"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}", System.Environment.NewLine, providers); } public static string AuthuriOptionShort { get { return LC.L(@"Supplies the authentication URL"); } } public static string VersionOptionLong { get { return LC.L(@"The keystone API version to use, valid values are 'v2' and 'v3'."); } } + public static string TenantnameOptionLong { get { return LC.L(@"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."); } } + public static string TenantnameOptionShort { get { return LC.L(@"Supplies the Tenant Name used to connect to the server"); } } + public static string ApikeyOptionLong { get { return LC.L(@"The API key can be used to connect without supplying a password and tenant ID with some providers."); } } + public static string ApikeyOptionShort { get { return LC.L(@"Supplies the API key used to connect to the server"); } } + public static string AuthuriOptionLong(string providers) { return LC.L(@"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}", System.Environment.NewLine, providers); } + public static string AuthuriOptionShort { get { return LC.L(@"Supplies the authentication URL"); } } + public static string VersionOptionLong { get { return LC.L(@"The keystone API version to use, valid values are 'v2' and 'v3'."); } } public static string VersionOptionShort { get { return LC.L(@"The keystone API version to use"); } } - public static string RegionOptionLong { get { return LC.L(@"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."); } } public static string RegionOptionShort { get { return LC.L(@"Supplies the region used for creating a container"); } } } } + public static string RegionOptionLong { get { return LC.L(@"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."); } } + public static string RegionOptionShort { get { return LC.L(@"Supplies the region used for creating a container"); } } + + } +} diff --git a/Duplicati/Library/Backend/Rclone/Rclone.cs b/Duplicati/Library/Backend/Rclone/Rclone.cs index 7b1355137..b16b48a0f 100644 --- a/Duplicati/Library/Backend/Rclone/Rclone.cs +++ b/Duplicati/Library/Backend/Rclone/Rclone.cs @@ -1,316 +1,319 @@ -#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; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; -using System.Diagnostics; -using Duplicati.Library.Interface; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Globalization; -using System.IO; - -namespace Duplicati.Library.Backend -{ - public class Rclone : IBackend - { - private const string OPTION_LOCAL_REPO = "rclone-local-repository"; - private const string OPTION_REMOTE_REPO = "rclone-remote-repository"; - private const string OPTION_REMOTE_PATH = "rclone-remote-path"; - private const string OPTION_RCLONE = "rclone-option"; - private const string OPTION_RCLONE_EXECUTABLE = "rclone-executable"; - private const string RCLONE_ERROR_DIRECTORY_NOT_FOUND = "directory not found"; - private const string RCLONE_ERROR_CONFIG_NOT_FOUND = "didn't find section in config file"; - - private string local_repo; - private string remote_repo; - private string remote_path; - private string opt_rclone; - private string rclone_executable; - - public Rclone() - { - - } - - public Rclone(string url, Dictionary options) - { - var uri = new Utility.Uri(url); - - remote_repo = uri.Host; - remote_path = uri.Path; - - - local_repo = "local"; - opt_rclone = ""; - rclone_executable = "rclone"; - /*should check here if program is installed */ - - if (options.ContainsKey(OPTION_LOCAL_REPO)) - local_repo = options[OPTION_LOCAL_REPO]; - if (options.ContainsKey(OPTION_REMOTE_REPO)) - remote_repo = options[OPTION_REMOTE_REPO]; - if (options.ContainsKey(OPTION_REMOTE_PATH)) - remote_path = options[OPTION_REMOTE_PATH]; - if (options.ContainsKey(OPTION_RCLONE)) - opt_rclone = options[OPTION_RCLONE]; - if (options.ContainsKey(OPTION_RCLONE_EXECUTABLE)) - rclone_executable = options[OPTION_RCLONE_EXECUTABLE]; -#if DEBUG - Console.WriteLine(string.Format("Constructor {0}: {1}:{2} {3}", local_repo, remote_repo, remote_path, opt_rclone)); -#endif - } - -#region IBackendInterface Members - - public string DisplayName - { - get { return Strings.Rclone.DisplayName; } - } - - public string ProtocolKey - { - get { return "rclone"; } - } - - public bool SupportsStreaming - { - get { return false; } - } - - private string RcloneCommandExecuter(String command, String arguments) - { - StringBuilder outputBuilder = new StringBuilder(); - StringBuilder errorBuilder = new StringBuilder(); - Process process; - - ProcessStartInfo psi = new ProcessStartInfo - { - Arguments = String.Format("{0} {1}", arguments, opt_rclone), - CreateNoWindow = true, - FileName = command, - RedirectStandardError = true, - RedirectStandardInput = true, - RedirectStandardOutput = true, - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden - }; - -#if DEBUG - Console.Error.WriteLine(String.Format("command executing: {0} {1}", psi.FileName, psi.Arguments)); -#endif - process = new Process - { - StartInfo = psi, - // enable raising events because Process does not raise events by default - EnableRaisingEvents = true - }; - // attach the event handler for OutputDataReceived before starting the process - process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler - ( - delegate (object sender, System.Diagnostics.DataReceivedEventArgs e) - { - if (!String.IsNullOrEmpty(e.Data)) - { -#if DEBUG - // Console.Error.WriteLine(String.Format("output {0}", e.Data)); -#endif - // append the new data to the data already read-in - outputBuilder.Append(e.Data); - }; - } - ); - - process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler - ( - delegate (object sender, System.Diagnostics.DataReceivedEventArgs e) - { - - if (!String.IsNullOrEmpty(e.Data)) - { -#if DEBUG - Console.Error.WriteLine(String.Format("error {0}", e.Data)); -#endif - errorBuilder.Append(e.Data); - } - } - ); - - // start the process - // then begin asynchronously reading the output - // then wait for the process to exit - // then cancel asynchronously reading the output - process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - process.WaitForExit(); - process.CancelOutputRead(); - process.CancelErrorRead(); - - if (errorBuilder.ToString().Contains(RCLONE_ERROR_DIRECTORY_NOT_FOUND)) - { - throw new FolderMissingException(errorBuilder.ToString()); - } - - if (errorBuilder.ToString().Contains(RCLONE_ERROR_CONFIG_NOT_FOUND)) - { - throw new Exception(String.Format("Missing config file? {0}", errorBuilder.ToString())); - } - - if (errorBuilder.Length > 0) { - throw new Exception(errorBuilder.ToString()); - } - Console.Error.WriteLine(errorBuilder.ToString()); - return outputBuilder.ToString(); - } - - - public IEnumerable List() - { - JArray files; - String str_result; - - try - { - - str_result = RcloneCommandExecuter(rclone_executable, String.Format("lsjson {0}:{1}", remote_repo, remote_path)); - // this will give an error if the executable does not exist. - } - - catch (FolderMissingException ex) - { - throw new FolderMissingException(ex); - } - - using (JsonReader jsonReader = new JsonTextReader(new StringReader(str_result))) - { - //no date parsing by JArray needed, will be parsed later - jsonReader.DateParseHandling = DateParseHandling.None; - var array = JArray.Load(jsonReader); - - foreach (JObject item in array) - { -#if DEBUG - Console.Error.WriteLine(item.ToString()); -#endif - FileEntry fe = new FileEntry( - item.GetValue("Name").Value(), - item.GetValue("Size").Value(), - DateTime.Parse(item.GetValue("ModTime").Value()), - DateTime.Parse(item.GetValue("ModTime").Value()) - ) - { - IsFolder = item.GetValue("IsDir").Value() - }; - yield return fe; - } - } - } - - public void Put(string remotename, string filename) - { - try - { - RcloneCommandExecuter(rclone_executable, String.Format("copyto {0}:{1} {2}:{3}/{4}", local_repo, filename, remote_repo, remote_path, remotename)); - } - catch (FolderMissingException ex) - { - throw new FileMissingException(ex); - } - } - - public void Get(string remotename, string filename) - { - try - { - RcloneCommandExecuter(rclone_executable, String.Format("copyto {2}:{3}/{4} {0}:{1}", local_repo, filename, remote_repo, remote_path, remotename)); - } - catch (FolderMissingException ex) { - throw new FileMissingException(ex); - } - } - - public void Delete(string remotename) - { - //this will actually delete the folder if remotename is a folder... - // Will give a "directory not found" error if the file does not exist, need to change that to a missing file exception - try - { - RcloneCommandExecuter(rclone_executable, String.Format("delete {0}:{1}/{2}", remote_repo, remote_path, remotename)); - } - catch (FolderMissingException ex) { - throw new FileMissingException(ex); - } - } - - public IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(OPTION_LOCAL_REPO, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneLocalRepoShort, Strings.Rclone.RcloneLocalRepoLong, "local"), - new CommandLineArgument(OPTION_REMOTE_REPO, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneRemoteRepoShort, Strings.Rclone.RcloneRemoteRepoLong, "remote"), - new CommandLineArgument(OPTION_REMOTE_PATH, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneRemotePathShort, Strings.Rclone.RcloneRemotePathLong, "backup"), - new CommandLineArgument(OPTION_RCLONE, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneOptionRcloneShort, Strings.Rclone.RcloneOptionRcloneLong, ""), - new CommandLineArgument(OPTION_RCLONE_EXECUTABLE, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneExecutableShort, Strings.Rclone.RcloneExecutableLong, "rclone") - }); - - } - } - - public string Description - { - get - { - return Strings.Rclone.Description; - } +#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; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Diagnostics; +using Duplicati.Library.Interface; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Globalization; +using System.IO; + +namespace Duplicati.Library.Backend +{ + public class Rclone : IBackend + { + private const string OPTION_LOCAL_REPO = "rclone-local-repository"; + private const string OPTION_REMOTE_REPO = "rclone-remote-repository"; + private const string OPTION_REMOTE_PATH = "rclone-remote-path"; + private const string OPTION_RCLONE = "rclone-option"; + private const string OPTION_RCLONE_EXECUTABLE = "rclone-executable"; + private const string RCLONE_ERROR_DIRECTORY_NOT_FOUND = "directory not found"; + private const string RCLONE_ERROR_CONFIG_NOT_FOUND = "didn't find section in config file"; + + private readonly string local_repo; + private readonly string remote_repo; + private readonly string remote_path; + private readonly string opt_rclone; + private readonly string rclone_executable; + + public Rclone() + { + } - public string[] DNSName - { - get { return new string[] { remote_repo }; } + public Rclone(string url, Dictionary options) + { + var uri = new Utility.Uri(url); + + remote_repo = uri.Host; + remote_path = uri.Path; + + + local_repo = "local"; + opt_rclone = ""; + rclone_executable = "rclone"; + /*should check here if program is installed */ + + if (options.ContainsKey(OPTION_LOCAL_REPO)) + local_repo = options[OPTION_LOCAL_REPO]; + if (options.ContainsKey(OPTION_REMOTE_REPO)) + remote_repo = options[OPTION_REMOTE_REPO]; + if (options.ContainsKey(OPTION_REMOTE_PATH)) + remote_path = options[OPTION_REMOTE_PATH]; + if (options.ContainsKey(OPTION_RCLONE)) + opt_rclone = options[OPTION_RCLONE]; + if (options.ContainsKey(OPTION_RCLONE_EXECUTABLE)) + rclone_executable = options[OPTION_RCLONE_EXECUTABLE]; +#if DEBUG + Console.WriteLine(string.Format("Constructor {0}: {1}:{2} {3}", local_repo, remote_repo, remote_path, opt_rclone)); +#endif } - public void Test() - { - this.TestList(); - } - - public void CreateFolder() - { - RcloneCommandExecuter(rclone_executable, String.Format("mkdir {0}:{1}", remote_repo, remote_path)); - } - -#endregion - -#region IDisposable Members - - public void Dispose() - { - - } - -#endregion - - - - } -} +#region IBackendInterface Members + + public string DisplayName + { + get { return Strings.Rclone.DisplayName; } + } + + public string ProtocolKey + { + get { return "rclone"; } + } + + public bool SupportsStreaming + { + get { return false; } + } + + private string RcloneCommandExecuter(String command, String arguments) + { + StringBuilder outputBuilder = new StringBuilder(); + StringBuilder errorBuilder = new StringBuilder(); + Process process; + + ProcessStartInfo psi = new ProcessStartInfo + { + Arguments = String.Format("{0} {1}", arguments, opt_rclone), + CreateNoWindow = true, + FileName = command, + RedirectStandardError = true, + RedirectStandardInput = true, + RedirectStandardOutput = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden + }; + +#if DEBUG + Console.Error.WriteLine(String.Format("command executing: {0} {1}", psi.FileName, psi.Arguments)); +#endif + process = new Process + { + StartInfo = psi, + // enable raising events because Process does not raise events by default + EnableRaisingEvents = true + }; + // attach the event handler for OutputDataReceived before starting the process + process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler + ( + delegate (object sender, System.Diagnostics.DataReceivedEventArgs e) + { + if (!String.IsNullOrEmpty(e.Data)) + { +#if DEBUG + // Console.Error.WriteLine(String.Format("output {0}", e.Data)); +#endif + // append the new data to the data already read-in + outputBuilder.Append(e.Data); + }; + } + ); + + process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler + ( + delegate (object sender, System.Diagnostics.DataReceivedEventArgs e) + { + + if (!String.IsNullOrEmpty(e.Data)) + { +#if DEBUG + Console.Error.WriteLine(String.Format("error {0}", e.Data)); +#endif + errorBuilder.Append(e.Data); + } + } + ); + + // start the process + // then begin asynchronously reading the output + // then wait for the process to exit + // then cancel asynchronously reading the output + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + process.CancelOutputRead(); + process.CancelErrorRead(); + + if (errorBuilder.ToString().Contains(RCLONE_ERROR_DIRECTORY_NOT_FOUND)) + { + throw new FolderMissingException(errorBuilder.ToString()); + } + + if (errorBuilder.ToString().Contains(RCLONE_ERROR_CONFIG_NOT_FOUND)) + { + throw new Exception(String.Format("Missing config file? {0}", errorBuilder.ToString())); + } + + if (errorBuilder.Length > 0) { + throw new Exception(errorBuilder.ToString()); + } + Console.Error.WriteLine(errorBuilder.ToString()); + return outputBuilder.ToString(); + } + + + public IEnumerable List() + { + String str_result; + + try + { + + str_result = RcloneCommandExecuter(rclone_executable, String.Format("lsjson {0}:{1}", remote_repo, remote_path)); + // this will give an error if the executable does not exist. + } + + catch (FolderMissingException ex) + { + throw new FolderMissingException(ex); + } + + using (JsonReader jsonReader = new JsonTextReader(new StringReader(str_result))) + { + //no date parsing by JArray needed, will be parsed later + jsonReader.DateParseHandling = DateParseHandling.None; + var array = JArray.Load(jsonReader); + + foreach (JObject item in array) + { +#if DEBUG + Console.Error.WriteLine(item.ToString()); +#endif + FileEntry fe = new FileEntry( + item.GetValue("Name").Value(), + item.GetValue("Size").Value(), + DateTime.Parse(item.GetValue("ModTime").Value()), + DateTime.Parse(item.GetValue("ModTime").Value()) + ) + { + IsFolder = item.GetValue("IsDir").Value() + }; + yield return fe; + } + } + } + + public void Put(string remotename, string filename) + { + try + { + RcloneCommandExecuter(rclone_executable, String.Format("copyto {0}:{1} {2}:{3}/{4}", local_repo, filename, remote_repo, remote_path, remotename)); + } + catch (FolderMissingException ex) + { + throw new FileMissingException(ex); + } + } + + public void Get(string remotename, string filename) + { + try + { + RcloneCommandExecuter(rclone_executable, String.Format("copyto {2}:{3}/{4} {0}:{1}", local_repo, filename, remote_repo, remote_path, remotename)); + } + catch (FolderMissingException ex) { + throw new FileMissingException(ex); + } + } + + public void Delete(string remotename) + { + //this will actually delete the folder if remotename is a folder... + // Will give a "directory not found" error if the file does not exist, need to change that to a missing file exception + try + { + RcloneCommandExecuter(rclone_executable, String.Format("delete {0}:{1}/{2}", remote_repo, remote_path, remotename)); + } + catch (FolderMissingException ex) { + throw new FileMissingException(ex); + } + } + + public IList SupportedCommands + { + get + { + return new List(new ICommandLineArgument[] { + new CommandLineArgument(OPTION_LOCAL_REPO, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneLocalRepoShort, Strings.Rclone.RcloneLocalRepoLong, "local"), + new CommandLineArgument(OPTION_REMOTE_REPO, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneRemoteRepoShort, Strings.Rclone.RcloneRemoteRepoLong, "remote"), + new CommandLineArgument(OPTION_REMOTE_PATH, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneRemotePathShort, Strings.Rclone.RcloneRemotePathLong, "backup"), + new CommandLineArgument(OPTION_RCLONE, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneOptionRcloneShort, Strings.Rclone.RcloneOptionRcloneLong, ""), + new CommandLineArgument(OPTION_RCLONE_EXECUTABLE, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneExecutableShort, Strings.Rclone.RcloneExecutableLong, "rclone") + }); + + } + } + + public string Description + { + get + { + return Strings.Rclone.Description; + } + } + + + + public string[] DNSName + { + get { return new string[] { remote_repo }; } + } + + + + public void Test() + { + this.TestList(); + } + + public void CreateFolder() + { + RcloneCommandExecuter(rclone_executable, String.Format("mkdir {0}:{1}", remote_repo, remote_path)); + } + +#endregion + +#region IDisposable Members + + public void Dispose() + { + + } + +#endregion + + + + } +} diff --git a/Duplicati/Library/Backend/S3/S3Backend.cs b/Duplicati/Library/Backend/S3/S3Backend.cs index c5de25661..91313ffc7 100644 --- a/Duplicati/Library/Backend/S3/S3Backend.cs +++ b/Duplicati/Library/Backend/S3/S3Backend.cs @@ -126,8 +126,8 @@ namespace Duplicati.Library.Backend } } - private string m_bucket; - private string m_prefix; + private readonly string m_bucket; + private readonly string m_prefix; public const string DEFAULT_S3_HOST = "s3.amazonaws.com"; public const string S3_EU_REGION_NAME = "eu-west-1"; diff --git a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs index 7c5407e94..8c180f9f3 100644 --- a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs +++ b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs @@ -37,18 +37,18 @@ namespace Duplicati.Library.Backend public const string SSH_TIMEOUT_OPTION = "ssh-operation-timeout"; public const string SSH_KEEPALIVE_OPTION = "ssh-keepalive"; - Dictionary m_options; + readonly Dictionary m_options; - private string m_server; - private string m_path; - private string m_username; - private string m_password; - private string m_fingerprint; - private bool m_fingerprintallowall; - private TimeSpan m_operationtimeout; - private TimeSpan m_keepaliveinterval; + private readonly string m_server; + private readonly string m_path; + private readonly string m_username; + private readonly string m_password; + private readonly string m_fingerprint; + private readonly bool m_fingerprintallowall; + private readonly TimeSpan m_operationtimeout; + private readonly TimeSpan m_keepaliveinterval; - private int m_port = 22; + private readonly int m_port = 22; private SftpClient m_con; diff --git a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs index f38792b91..cef7ba6dd 100644 --- a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs +++ b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs @@ -54,15 +54,15 @@ namespace Duplicati.Library.Backend #region [Variables and constants declarations] /// Auth-stripped HTTPS-URI as passed to constructor. - private Utility.Uri m_orgUrl; + private readonly Utility.Uri m_orgUrl; /// Server relative path to backup folder. - private string m_serverRelPath; + private readonly string m_serverRelPath; /// User's credentials to create client context private System.Net.ICredentials m_userInfo; /// Flag indicating to move files to recycler on deletion. - private bool m_deleteToRecycler = false; + private readonly bool m_deleteToRecycler = false; /// Flag indicating to use UploadBinaryDirect. - private bool m_useBinaryDirectMode = false; + private readonly bool m_useBinaryDirectMode = false; /// URL to SharePoint web. Will be determined from m_orgUri on first use. private string m_spWebUrl; @@ -70,10 +70,10 @@ namespace Duplicati.Library.Backend private SP.ClientContext m_spContext; /// The chunk size for uploading files. - private int m_fileChunkSize = 4 << 20; // Default: 4MB + private readonly int m_fileChunkSize = 4 << 20; // Default: 4MB /// The chunk size for uploading files. - private int m_useContextTimeoutMs = -1; // default: do not touch original setting + private readonly int m_useContextTimeoutMs = -1; // default: do not touch original setting #endregion @@ -666,12 +666,14 @@ namespace Duplicati.Library.Backend string[] folderNames = m_serverRelPath.Substring(0, m_serverRelPath.Length - 1).Split('/'); folderNames = Array.ConvertAll(folderNames, fold => System.Net.WebUtility.UrlDecode(fold)); var spfolders = new SP.Folder[folderNames.Length]; - string folderRelPath = ""; + StringBuilder relativePathBuilder = new StringBuilder(); int fi = 0; for (; fi < folderNames.Length; fi++) { - folderRelPath += System.Web.HttpUtility.UrlPathEncode(folderNames[fi]) + "/"; - if (fi < pathLengthToWeb) continue; + relativePathBuilder.Append(System.Web.HttpUtility.UrlPathEncode(folderNames[fi])).Append("/"); + if (fi < pathLengthToWeb) continue; + + string folderRelPath = relativePathBuilder.ToString(); var folder = ctx.Web.GetFolderByServerRelativeUrl(folderRelPath); spfolders[fi] = folder; ctx.Load(folder, f => f.Exists); diff --git a/Duplicati/Library/Backend/Sia/Sia.cs b/Duplicati/Library/Backend/Sia/Sia.cs index c573de452..e788cb209 100644 --- a/Duplicati/Library/Backend/Sia/Sia.cs +++ b/Duplicati/Library/Backend/Sia/Sia.cs @@ -15,11 +15,11 @@ namespace Duplicati.Library.Backend.Sia private const string SIA_TARGETPATH = "sia-targetpath"; private const string SIA_REDUNDANCY = "sia-redundancy"; - private string m_apihost; - private int m_apiport; - private string m_targetpath; - private float m_redundancy; - private System.Net.NetworkCredential m_user; + private readonly string m_apihost; + private readonly int m_apiport; + private readonly string m_targetpath; + private readonly float m_redundancy; + private readonly System.Net.NetworkCredential m_user; public Sia() { diff --git a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs index ede3695d1..14c52a010 100644 --- a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs +++ b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs @@ -29,8 +29,8 @@ namespace Duplicati.Library.Backend { public class TahoeBackend : IBackend, IStreamingBackend { - private string m_url; - private bool m_useSSL = false; + private readonly string m_url; + private readonly bool m_useSSL = false; private readonly byte[] m_copybuffer = new byte[Duplicati.Library.Utility.Utility.DEFAULT_BUFFER_SIZE]; private class TahoeEl diff --git a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs index 6ca21011d..c94307cc7 100644 --- a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs +++ b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs @@ -26,18 +26,18 @@ namespace Duplicati.Library.Backend { public class WEBDAV : IBackend, IStreamingBackend { - private System.Net.NetworkCredential m_userInfo; - private string m_url; - private string m_path; - private string m_sanitizedUrl; - private string m_reverseProtocolUrl; - private string m_rawurl; - private string m_rawurlPort; - private string m_dnsName; - private bool m_useIntegratedAuthentication = false; - private bool m_forceDigestAuthentication = false; - private bool m_useSSL = false; - private string m_debugPropfindFile = null; + private readonly System.Net.NetworkCredential m_userInfo; + private readonly string m_url; + private readonly string m_path; + private readonly string m_sanitizedUrl; + private readonly string m_reverseProtocolUrl; + private readonly string m_rawurl; + private readonly string m_rawurlPort; + private readonly string m_dnsName; + private readonly bool m_useIntegratedAuthentication = false; + private readonly bool m_forceDigestAuthentication = false; + private readonly bool m_useSSL = false; + private readonly string m_debugPropfindFile = null; private readonly byte[] m_copybuffer = new byte[Duplicati.Library.Utility.Utility.DEFAULT_BUFFER_SIZE]; /// diff --git a/Duplicati/Library/Compression/FileArchiveZip.cs b/Duplicati/Library/Compression/FileArchiveZip.cs index 33c78e77c..e41c174a3 100644 --- a/Duplicati/Library/Compression/FileArchiveZip.cs +++ b/Duplicati/Library/Compression/FileArchiveZip.cs @@ -92,7 +92,7 @@ namespace Duplicati.Library.Compression /// /// This property indicates reading or writing access mode of the file archive. /// - ArchiveMode m_mode; + readonly ArchiveMode m_mode; /// /// Gets the number of bytes expected to be written after the stream is disposed @@ -126,17 +126,17 @@ namespace Duplicati.Library.Compression /// /// The compression level applied when the hint does not indicate incompressible /// - private SharpCompress.Compressors.Deflate.CompressionLevel m_defaultCompressionLevel; + private readonly SharpCompress.Compressors.Deflate.CompressionLevel m_defaultCompressionLevel; /// /// The compression level applied when the hint does not indicate incompressible /// - private CompressionType m_compressionType; + private readonly CompressionType m_compressionType; /// /// A flag indicating if zip64 is in use /// - private bool m_usingZip64; + private readonly bool m_usingZip64; /// /// Default constructor, used to read file extension and supported commands @@ -306,9 +306,9 @@ namespace Duplicati.Library.Compression var q = m_entryDict.Values.AsEnumerable(); if (!string.IsNullOrEmpty(prefix)) q = q.Where(x => - x.Key.StartsWith(prefix, Duplicati.Library.Utility.Utility.ClientFilenameStringComparision) + x.Key.StartsWith(prefix, Duplicati.Library.Utility.Utility.ClientFilenameStringComparison) || - x.Key.Replace('\\', '/').StartsWith(prefix, Duplicati.Library.Utility.Utility.ClientFilenameStringComparision) + x.Key.Replace('\\', '/').StartsWith(prefix, Duplicati.Library.Utility.Utility.ClientFilenameStringComparison) ); return q.Select(x => new KeyValuePair(x.Key, x.Size)).ToArray(); diff --git a/Duplicati/Library/Compression/SevenZipCompression.cs b/Duplicati/Library/Compression/SevenZipCompression.cs index 6d35b2311..dd2fafb16 100644 --- a/Duplicati/Library/Compression/SevenZipCompression.cs +++ b/Duplicati/Library/Compression/SevenZipCompression.cs @@ -174,7 +174,7 @@ namespace Duplicati.Library.Compression if (m_reader == null) throw new InvalidOperationException(Strings.SevenZipCompression.NoReaderError); - StringComparison sc = Library.Utility.Utility.ClientFilenameStringComparision; + StringComparison sc = Library.Utility.Utility.ClientFilenameStringComparison; return from n in m_reader.GetFiles(m_archive) @@ -222,8 +222,8 @@ namespace Duplicati.Library.Compression private sealed class WriterEntry : ManagedLzma.LZMA.Master.SevenZip.IArchiveWriterEntry { - private string mName; - private DateTime? mTimestamp; + private readonly string mName; + private readonly DateTime? mTimestamp; internal WriterEntry(string name, DateTime? timestamp) { diff --git a/Duplicati/Library/DynamicLoader/BackendLoader.cs b/Duplicati/Library/DynamicLoader/BackendLoader.cs index a00bf7937..a7f36476c 100644 --- a/Duplicati/Library/DynamicLoader/BackendLoader.cs +++ b/Duplicati/Library/DynamicLoader/BackendLoader.cs @@ -138,10 +138,10 @@ namespace Duplicati.Library.DynamicLoader /// /// The static instance used to access backend information /// - private static BackendLoaderSub _backendLoader = new BackendLoaderSub(); + private static readonly BackendLoaderSub _backendLoader = new BackendLoaderSub(); #region Public static API - + /// /// Gets a list of loaded backends, the instances can be used to extract interface information, not used to interact with the backend. /// diff --git a/Duplicati/Library/DynamicLoader/CompressionLoader.cs b/Duplicati/Library/DynamicLoader/CompressionLoader.cs index 3426f09cd..0e4345426 100644 --- a/Duplicati/Library/DynamicLoader/CompressionLoader.cs +++ b/Duplicati/Library/DynamicLoader/CompressionLoader.cs @@ -103,7 +103,7 @@ namespace Duplicati.Library.DynamicLoader /// /// The static instance used to access compression module information /// - private static CompressionLoaderSub _compressionLoader = new CompressionLoaderSub(); + private static readonly CompressionLoaderSub _compressionLoader = new CompressionLoaderSub(); #region Public static API diff --git a/Duplicati/Library/DynamicLoader/EncryptionLoader.cs b/Duplicati/Library/DynamicLoader/EncryptionLoader.cs index 822336ad3..09cd4f2f9 100644 --- a/Duplicati/Library/DynamicLoader/EncryptionLoader.cs +++ b/Duplicati/Library/DynamicLoader/EncryptionLoader.cs @@ -104,7 +104,7 @@ namespace Duplicati.Library.DynamicLoader /// /// The static instance used to access encryption module information /// - private static EncryptionLoaderSub _encryptionLoader = new EncryptionLoaderSub(); + private static readonly EncryptionLoaderSub _encryptionLoader = new EncryptionLoaderSub(); #region Public static API diff --git a/Duplicati/Library/Encryption/GPGEncryption.cs b/Duplicati/Library/Encryption/GPGEncryption.cs index 03d1ca5db..3b4ac86d8 100644 --- a/Duplicati/Library/Encryption/GPGEncryption.cs +++ b/Duplicati/Library/Encryption/GPGEncryption.cs @@ -94,17 +94,17 @@ namespace Duplicati.Library.Encryption /// /// The PGP program to use, should be with absolute path /// - private string m_programpath = "gpg"; + private readonly string m_programpath = "gpg"; /// /// Commandline switches for encryption /// - private string m_encryption_args; + private readonly string m_encryption_args; /// /// Commandline switches for decryption /// - private string m_decryption_args; + private readonly string m_decryption_args; /// /// The key used for cryptographic operations diff --git a/Duplicati/Library/Localization/LocalizationContext.cs b/Duplicati/Library/Localization/LocalizationContext.cs index 3554ccdcb..b788de186 100644 --- a/Duplicati/Library/Localization/LocalizationContext.cs +++ b/Duplicati/Library/Localization/LocalizationContext.cs @@ -25,7 +25,7 @@ namespace Duplicati.Library.Localization /// /// The previous context /// - private object m_prev; + private readonly object m_prev; /// /// Flag to prevent double dispose diff --git a/Duplicati/Library/Localization/LocalizationService.cs b/Duplicati/Library/Localization/LocalizationService.cs index 1e2750bd0..a68fbd542 100644 --- a/Duplicati/Library/Localization/LocalizationService.cs +++ b/Duplicati/Library/Localization/LocalizationService.cs @@ -110,7 +110,7 @@ namespace Duplicati.Library.Localization /// public static ILocalizationService Get(CultureInfo ci) { - if (ci == CultureInfo.InvariantCulture) + if (ci.Equals(CultureInfo.InvariantCulture)) return InvariantService; ILocalizationService service; diff --git a/Duplicati/Library/Localization/MoLocalizationService.cs b/Duplicati/Library/Localization/MoLocalizationService.cs index 38667a471..d014893e9 100644 --- a/Duplicati/Library/Localization/MoLocalizationService.cs +++ b/Duplicati/Library/Localization/MoLocalizationService.cs @@ -33,7 +33,7 @@ namespace Duplicati.Library.Localization /// /// The catalog containing the translations /// - private ICatalog catalog = new Catalog(); + private readonly ICatalog catalog = new Catalog(); /// /// The environment variable used to locate MO files diff --git a/Duplicati/Library/Localization/Short.cs b/Duplicati/Library/Localization/Short.cs index 1b5dfebd3..2056780dc 100644 --- a/Duplicati/Library/Localization/Short.cs +++ b/Duplicati/Library/Localization/Short.cs @@ -27,7 +27,7 @@ namespace Duplicati.Library.Localization.Short /// /// The instance for translation /// - private static ILocalizationService LS = LocalizationService.Invariant; + private static readonly ILocalizationService LS = LocalizationService.Invariant; /// /// Localizes the string similar to how string.Format works @@ -172,7 +172,7 @@ namespace Duplicati.Library.Localization.Short /// /// The instance for translation /// - private static ILocalizationService LS = LocalizationService.Installed; + private static readonly ILocalizationService LS = LocalizationService.Installed; /// /// Localizes the string similar to how string.Format works diff --git a/Duplicati/Library/Logging/Timer.cs b/Duplicati/Library/Logging/Timer.cs index 65ceb5ac7..7ae58072b 100644 --- a/Duplicati/Library/Logging/Timer.cs +++ b/Duplicati/Library/Logging/Timer.cs @@ -33,7 +33,7 @@ namespace Duplicati.Library.Logging /// private static readonly string LOGTAG = "Timer"; - private DateTime m_begin; + private readonly DateTime m_begin; private string m_operation; private readonly string m_logtag; private readonly string m_logid; diff --git a/Duplicati/Library/Main/AsyncDownloader.cs b/Duplicati/Library/Main/AsyncDownloader.cs index 30977e265..52900326e 100644 --- a/Duplicati/Library/Main/AsyncDownloader.cs +++ b/Duplicati/Library/Main/AsyncDownloader.cs @@ -17,7 +17,7 @@ namespace Duplicati.Library.Main { private class AsyncDownloadedFile : IAsyncDownloadedFile { - private Exception m_exception; + private readonly Exception m_exception; private Library.Utility.TempFile m_file; public string Name { get; private set; } @@ -53,9 +53,9 @@ namespace Duplicati.Library.Main } } - private IList m_volumes; + private readonly IList m_volumes; private BackendManager.IDownloadWaitHandle m_handle; - private BackendManager m_backend; + private readonly BackendManager m_backend; private int m_index; private AsyncDownloadedFile m_current; @@ -129,8 +129,8 @@ namespace Duplicati.Library.Main } } - private IList m_volumes; - private BackendManager m_backend; + private readonly IList m_volumes; + private readonly BackendManager m_backend; public AsyncDownloader(IList volumes, BackendManager backend) { diff --git a/Duplicati/Library/Main/BackendManager.cs b/Duplicati/Library/Main/BackendManager.cs index 19c42d5ed..64f302fc4 100644 --- a/Duplicati/Library/Main/BackendManager.cs +++ b/Duplicati/Library/Main/BackendManager.cs @@ -126,7 +126,7 @@ namespace Duplicati.Library.Main /// /// The event that is signaled once the operation is complete or has failed /// - private System.Threading.ManualResetEvent DoneEvent; + private readonly System.Threading.ManualResetEvent DoneEvent; public FileEntryItem(OperationType operation, string remotefilename, Tuple indexfile = null) { @@ -245,10 +245,10 @@ namespace Duplicati.Library.Main private class DatabaseCollector { private readonly object m_dbqueuelock = new object(); - private LocalDatabase m_database; - private System.Threading.Thread m_callerThread; + private readonly LocalDatabase m_database; + private readonly System.Threading.Thread m_callerThread; private List m_dbqueue; - private IBackendWriter m_stats; + private readonly IBackendWriter m_stats; private interface IDbEntry { } @@ -349,15 +349,15 @@ namespace Duplicati.Library.Main } private readonly BlockingQueue m_queue; - private Options m_options; + private readonly Options m_options; private volatile Exception m_lastException; - private Library.Interface.IEncryption m_encryption; + private readonly Library.Interface.IEncryption m_encryption; private readonly object m_encryptionLock = new object(); private Library.Interface.IBackend m_backend; - private string m_backendurl; - private IBackendWriter m_statwriter; + private readonly string m_backendurl; + private readonly IBackendWriter m_statwriter; private System.Threading.Thread m_thread; - private BasicResults m_taskControl; + private readonly BasicResults m_taskControl; private readonly DatabaseCollector m_db; // Cache these diff --git a/Duplicati/Library/Main/Controller.cs b/Duplicati/Library/Main/Controller.cs index 9b1859a0f..2bd5c803c 100644 --- a/Duplicati/Library/Main/Controller.cs +++ b/Duplicati/Library/Main/Controller.cs @@ -40,7 +40,7 @@ namespace Duplicati.Library.Main /// /// The parsed type-safe version of the commandline options /// - private Options m_options; + private readonly Options m_options; /// /// The destination for all output messages during execution /// @@ -59,7 +59,7 @@ namespace Duplicati.Library.Main /// /// Holds various keys that need to be reset after running the task /// - private Dictionary m_resetKeys = new Dictionary(); + private readonly Dictionary m_resetKeys = new Dictionary(); /// /// The thread priority to reset to @@ -222,7 +222,7 @@ namespace Duplicati.Library.Main //Sanity check for multiple inclusions of the same folder for (int i = 0; i < sources.Count; i++) for (int j = 0; j < sources.Count; j++) - if (i != j && sources[i].StartsWith(sources[j], Library.Utility.Utility.ClientFilenameStringComparision) && sources[i].EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), Library.Utility.Utility.ClientFilenameStringComparision)) + if (i != j && sources[i].StartsWith(sources[j], Library.Utility.Utility.ClientFilenameStringComparison) && sources[i].EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), Library.Utility.Utility.ClientFilenameStringComparison)) { if (filter != null) { diff --git a/Duplicati/Library/Main/ControllerMultiLogTarget.cs b/Duplicati/Library/Main/ControllerMultiLogTarget.cs index 28a0f645f..e5a91a854 100644 --- a/Duplicati/Library/Main/ControllerMultiLogTarget.cs +++ b/Duplicati/Library/Main/ControllerMultiLogTarget.cs @@ -28,7 +28,7 @@ namespace Duplicati.Library.Main /// /// The list of log targets to handle /// - private List> m_targets = new List>(); + private readonly List> m_targets = new List>(); /// /// Initializes a new instance of the class. diff --git a/Duplicati/Library/Main/Database/HashLookupHelper.cs b/Duplicati/Library/Main/Database/HashLookupHelper.cs index a1edba9e1..679781a4e 100644 --- a/Duplicati/Library/Main/Database/HashLookupHelper.cs +++ b/Duplicati/Library/Main/Database/HashLookupHelper.cs @@ -28,7 +28,7 @@ namespace Duplicati.Library.Main /// /// The lookup table /// - private SortedList[] m_lookup; + private readonly SortedList[] m_lookup; /// /// The number of entries in the table /// diff --git a/Duplicati/Library/Main/Database/LocalDatabase.cs b/Duplicati/Library/Main/Database/LocalDatabase.cs index 10c63710f..6219b5a50 100644 --- a/Duplicati/Library/Main/Database/LocalDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDatabase.cs @@ -211,7 +211,7 @@ namespace Duplicati.Library.Main.Database public Tuple GetFilelistWhereClause(DateTime time, long[] versions, IEnumerable> filesetslist = null, bool singleTimeMatch = false) { var filesets = (filesetslist ?? this.FilesetTimes).ToArray(); - string query = ""; + StringBuilder query = new StringBuilder(); var args = new List(); if (time.Ticks > 0 || (versions != null && versions.Length > 0)) { @@ -220,45 +220,44 @@ namespace Duplicati.Library.Main.Database { if (time.Kind == DateTimeKind.Unspecified) throw new Exception("Invalid DateTime given, must be either local or UTC"); - - query += singleTimeMatch ? @" ""Timestamp"" = ?" : @" ""Timestamp"" <= ?"; + + query.Append(singleTimeMatch ? @" ""Timestamp"" = ?" : @" ""Timestamp"" <= ?"); // Make sure the resolution is the same (i.e. no milliseconds) args.Add(NormalizeDateTimeToEpochSeconds(time)); hasTime = true; } - + if (versions != null && versions.Length > 0) { - var qs = ""; + StringBuilder qs = new StringBuilder(); foreach (var v in versions) { if (v >= 0 && v < filesets.Length) { args.Add(filesets[v].Key); - qs += "?,"; + qs.Append("?,"); } else Logging.Log.WriteWarningMessage(LOGTAG, "SkipInvalidVersion", null, "Skipping invalid version: {0}", v); } - + if (qs.Length > 0) { - qs = qs.Substring(0, qs.Length - 1); - if (hasTime) - query += " OR "; - - query += @" ""ID"" IN (" + qs + ")"; + query.Append(" OR "); + + query.Append(@" ""ID"" IN (" + qs.ToString(0, qs.Length - 1) + ")"); } } - - if (!string.IsNullOrEmpty(query)) - query = " WHERE " + query; + if (query.Length > 0) + { + query.Insert(0, " WHERE "); + } } - return new Tuple(query, args.ToArray()); + return new Tuple(query.ToString(), args.ToArray()); } public long GetRemoteVolumeID(string file, System.Data.IDbTransaction transaction = null) @@ -562,8 +561,8 @@ namespace Duplicati.Library.Main.Database protected class TemporaryTransactionWrapper : IDisposable { - private System.Data.IDbTransaction m_parent; - private bool m_isTemporary; + private readonly System.Data.IDbTransaction m_parent; + private readonly bool m_isTemporary; public TemporaryTransactionWrapper(System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) { @@ -605,7 +604,7 @@ namespace Duplicati.Library.Main.Database private class LocalFileEntry : ILocalFileEntry { - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public LocalFileEntry(System.Data.IDataReader reader) { m_reader = reader; @@ -850,8 +849,8 @@ ON { private class BlocklistHashEnumerator : IEnumerator { - private System.Data.IDataReader m_reader; - private BlocklistHashEnumerable m_parent; + private readonly System.Data.IDataReader m_reader; + private readonly BlocklistHashEnumerable m_parent; private string m_path = null; private bool m_first = true; private string m_current = null; @@ -913,7 +912,7 @@ ON } } - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public BlocklistHashEnumerable(System.Data.IDataReader reader) { @@ -1143,8 +1142,8 @@ ORDER BY public class FilteredFilenameTable : IDisposable { public string Tablename { get; private set; } - private System.Data.IDbConnection m_connection; - + private readonly System.Data.IDbConnection m_connection; + public FilteredFilenameTable(System.Data.IDbConnection connection, Library.Utility.IFilter filter, System.Data.IDbTransaction transaction) { m_connection = connection; diff --git a/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs b/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs index 3f958a87a..fe5ec4287 100644 --- a/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs @@ -69,12 +69,7 @@ namespace Duplicati.Library.Main.Database { cmd.Transaction = transaction; - var q = ""; - foreach(var n in toDelete) - if (q.Length == 0) - q = "?"; - else - q += ",?"; + string q = String.Join(",", Enumerable.Repeat("?", toDelete.Length)); //First we remove unwanted entries var deleted = cmd.ExecuteNonQuery(@"DELETE FROM ""Fileset"" WHERE ""Timestamp"" IN (" + q + @") ", toDelete.Select(x => NormalizeDateTimeToEpochSeconds(x)).Cast().ToArray()); @@ -175,20 +170,20 @@ namespace Duplicati.Library.Main.Database private class CompactReport : ICompactReport { - private IEnumerable m_report; - private IEnumerable m_cleandelete; - private IEnumerable m_wastevolumes; - private IEnumerable m_smallvolumes; + private readonly IEnumerable m_report; + private readonly IEnumerable m_cleandelete; + private readonly IEnumerable m_wastevolumes; + private readonly IEnumerable m_smallvolumes; + + private readonly long m_deletablevolumes; + private readonly long m_wastedspace; + private readonly long m_smallspace; + private readonly long m_fullsize; + private readonly long m_smallvolumecount; - private long m_deletablevolumes; - private long m_wastedspace; - private long m_smallspace; - private long m_fullsize; - private long m_smallvolumecount; - - private long m_wastethreshold; - private long m_volsize; - private long m_maxsmallfilecount; + private readonly long m_wastethreshold; + private readonly long m_volsize; + private readonly long m_maxsmallfilecount; public CompactReport(long volsize, long wastethreshold, long smallfilesize, long maxsmallfilecount, IEnumerable report) { diff --git a/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs b/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs index e46ffc345..00222df26 100644 --- a/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs @@ -89,7 +89,7 @@ namespace Duplicati.Library.Main.Database private class StorageHelper : IStorageHelper { - private System.Data.IDbConnection m_connection; + private readonly System.Data.IDbConnection m_connection; private System.Data.IDbTransaction m_transaction; private System.Data.IDbCommand m_insertPreviousElementCommand; diff --git a/Duplicati/Library/Main/Database/LocalListDatabase.cs b/Duplicati/Library/Main/Database/LocalListDatabase.cs index 0bcaf42b6..033646e2a 100644 --- a/Duplicati/Library/Main/Database/LocalListDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListDatabase.cs @@ -56,9 +56,9 @@ namespace Duplicati.Library.Main.Database private class FileSets : IFileSets { - private System.Data.IDbConnection m_connection; + private readonly System.Data.IDbConnection m_connection; private string m_tablename; - private KeyValuePair[] m_filesets; + private readonly KeyValuePair[] m_filesets; public FileSets(LocalListDatabase owner, DateTime time, long[] versions) { @@ -94,7 +94,7 @@ namespace Duplicati.Library.Main.Database private class Fileversion : IFileversion { - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public string Path { get; private set; } public bool More { get; private set; } diff --git a/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs b/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs index eb1850a54..042feb818 100644 --- a/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs @@ -74,7 +74,7 @@ namespace Duplicati.Library.Main.Database private readonly System.Data.IDbConnection m_connection; private readonly System.Data.IDbTransaction m_transaction; private readonly string m_tablename; - private LocalPurgeDatabase m_parentdb; + private readonly LocalPurgeDatabase m_parentdb; public long ParentID { get; private set; } public long RemovedFileCount { get; private set; } diff --git a/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs b/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs index f2a05f5b9..09b7e7623 100644 --- a/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs @@ -49,23 +49,23 @@ namespace Duplicati.Library.Main.Database } } - private System.Data.IDbCommand m_insertFileCommand; - private System.Data.IDbCommand m_insertFilesetEntryCommand; - private System.Data.IDbCommand m_insertMetadatasetCommand; - private System.Data.IDbCommand m_insertBlocksetCommand; - private System.Data.IDbCommand m_insertBlocklistHashCommand; - private System.Data.IDbCommand m_updateBlockVolumeCommand; - private System.Data.IDbCommand m_insertBlockset; - private System.Data.IDbCommand m_insertSmallBlockset; - private System.Data.IDbCommand m_findBlocksetCommand; - private System.Data.IDbCommand m_findMetadatasetCommand; - private System.Data.IDbCommand m_findFilesetCommand; - private System.Data.IDbCommand m_findblocklisthashCommand; - private System.Data.IDbCommand m_findHashBlockCommand; - private System.Data.IDbCommand m_insertBlockCommand; - private System.Data.IDbCommand m_insertDuplicateBlockCommand; + private readonly System.Data.IDbCommand m_insertFileCommand; + private readonly System.Data.IDbCommand m_insertFilesetEntryCommand; + private readonly System.Data.IDbCommand m_insertMetadatasetCommand; + private readonly System.Data.IDbCommand m_insertBlocksetCommand; + private readonly System.Data.IDbCommand m_insertBlocklistHashCommand; + private readonly System.Data.IDbCommand m_updateBlockVolumeCommand; + private readonly System.Data.IDbCommand m_insertBlockset; + private readonly System.Data.IDbCommand m_insertSmallBlockset; + private readonly System.Data.IDbCommand m_findBlocksetCommand; + private readonly System.Data.IDbCommand m_findMetadatasetCommand; + private readonly System.Data.IDbCommand m_findFilesetCommand; + private readonly System.Data.IDbCommand m_findblocklisthashCommand; + private readonly System.Data.IDbCommand m_findHashBlockCommand; + private readonly System.Data.IDbCommand m_insertBlockCommand; + private readonly System.Data.IDbCommand m_insertDuplicateBlockCommand; - private PathLookupHelper m_filesetLookup; + private readonly PathLookupHelper m_filesetLookup; private string m_tempblocklist; private string m_tempsmalllist; diff --git a/Duplicati/Library/Main/Database/LocalRepairDatabase.cs b/Duplicati/Library/Main/Database/LocalRepairDatabase.cs index 5725b7cc9..b7fe07dd2 100644 --- a/Duplicati/Library/Main/Database/LocalRepairDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRepairDatabase.cs @@ -71,8 +71,8 @@ namespace Duplicati.Library.Main.Database } } - - private System.Data.IDataReader m_rd; + + private readonly System.Data.IDataReader m_rd; public bool Done { get; private set; } public BlockWithSources(System.Data.IDataReader rd) @@ -135,11 +135,11 @@ namespace Duplicati.Library.Main.Database private class MissingBlockList : IMissingBlockList { - private System.Data.IDbConnection m_connection; - private TemporaryTransactionWrapper m_transaction; + private readonly System.Data.IDbConnection m_connection; + private readonly TemporaryTransactionWrapper m_transaction; private System.Data.IDbCommand m_insertCommand; private string m_tablename; - private string m_volumename; + private readonly string m_volumename; public MissingBlockList(string volumename, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) { diff --git a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs index f1737f08f..ec4cf3192 100644 --- a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs @@ -536,7 +536,7 @@ namespace Duplicati.Library.Main.Database private class ExistingFile : IExistingFile { - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public ExistingFile(System.Data.IDataReader rd) { m_reader = rd; HasMore = true; } @@ -549,7 +549,7 @@ namespace Duplicati.Library.Main.Database private class ExistingFileBlock : IExistingFileBlock { - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public ExistingFileBlock(System.Data.IDataReader rd) { m_reader = rd; } @@ -606,7 +606,7 @@ namespace Duplicati.Library.Main.Database { private class BlockSource : IBlockSource { - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public BlockSource(System.Data.IDataReader rd) { m_reader = rd; } public string Path { get { return m_reader.ConvertValueToString(6); } } @@ -615,7 +615,7 @@ namespace Duplicati.Library.Main.Database public bool IsMetadata { get { return false; } } } - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public BlockDescriptor(System.Data.IDataReader rd) { m_reader = rd; HasMore = true; } private string TargetPath { get { return m_reader.ConvertValueToString(0); } } @@ -645,7 +645,7 @@ namespace Duplicati.Library.Main.Database } } - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public LocalBlockSource(System.Data.IDataReader rd) { m_reader = rd; HasMore = true; } public string TargetPath { get { return m_reader.ConvertValueToString(0); } } @@ -756,12 +756,12 @@ namespace Duplicati.Library.Main.Database private class FilesAndMetadata : IFilesAndMetadata { - private string m_tmptable; - private string m_filetablename; - private string m_blocktablename; - private long m_blocksize; + private readonly string m_tmptable; + private readonly string m_filetablename; + private readonly string m_blocktablename; + private readonly long m_blocksize; - private System.Data.IDbConnection m_connection; + private readonly System.Data.IDbConnection m_connection; public FilesAndMetadata(System.Data.IDbConnection connection, string filetablename, string blocktablename, long blocksize, BlockVolumeReader curvolume) { @@ -805,7 +805,7 @@ namespace Duplicati.Library.Main.Database { private class PatchBlock : IPatchBlock { - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public PatchBlock(System.Data.IDataReader rd) { m_reader = rd; } public long Offset { get { return m_reader.ConvertValueToInt64(2); } } @@ -813,7 +813,7 @@ namespace Duplicati.Library.Main.Database public string Key { get { return m_reader.ConvertValueToString(4); } } } - private System.Data.IDataReader m_reader; + private readonly System.Data.IDataReader m_reader; public VolumePatch(System.Data.IDataReader rd) { m_reader = rd; HasMore = true; } public string Path { get { return m_reader.ConvertValueToString(0); } } @@ -1017,8 +1017,8 @@ namespace Duplicati.Library.Main.Database private System.Data.IDbCommand m_statUpdateCommand; private bool m_hasUpdates = false; - private string m_blocktablename; - private string m_filetablename; + private readonly string m_blocktablename; + private readonly string m_filetablename; public System.Data.IDbTransaction Transaction { get { return m_insertblockCommand.Transaction; } } @@ -1212,17 +1212,17 @@ namespace Duplicati.Library.Main.Database { private class BlockEntry : IBlockEntry { - private System.Data.IDataReader m_rd; - private long m_blocksize; + private readonly System.Data.IDataReader m_rd; + private readonly long m_blocksize; public BlockEntry(System.Data.IDataReader rd, long blocksize) { m_rd = rd; m_blocksize = blocksize; } public long Offset { get { return m_rd.GetInt64(3) * m_blocksize; } } public long Index { get { return m_rd.GetInt64(3); } } public long Size { get { return m_rd.GetInt64(5); } } public string Hash { get { return m_rd.GetString(4); } } } - - private System.Data.IDataReader m_rd; - private long m_blocksize; + + private readonly System.Data.IDataReader m_rd; + private readonly long m_blocksize; public FastSource(System.Data.IDataReader rd, long blocksize) { m_rd = rd; m_blocksize = blocksize; MoreData = true; } public bool MoreData { get; private set; } public string TargetPath { get { return m_rd.GetValue(0).ToString(); } } diff --git a/Duplicati/Library/Main/Database/PathLookupHelper.cs b/Duplicati/Library/Main/Database/PathLookupHelper.cs index 8146691b3..2b1809a8a 100644 --- a/Duplicati/Library/Main/Database/PathLookupHelper.cs +++ b/Duplicati/Library/Main/Database/PathLookupHelper.cs @@ -63,7 +63,7 @@ namespace Duplicati.Library.Main.Database var hotIx = Math.Min(m_lookup.Count, paths.Length) - 1; while (hotIx >= 0) { - if (path.StartsWith(m_lookup[hotIx].Key, Duplicati.Library.Utility.Utility.ClientFilenameStringComparision)) + if (path.StartsWith(m_lookup[hotIx].Key, Duplicati.Library.Utility.Utility.ClientFilenameStringComparison)) { c = m_lookup[hotIx].Value; diff --git a/Duplicati/Library/Main/DatabaseLocator.cs b/Duplicati/Library/Main/DatabaseLocator.cs index 5b82bfde8..8c3653e29 100644 --- a/Duplicati/Library/Main/DatabaseLocator.cs +++ b/Duplicati/Library/Main/DatabaseLocator.cs @@ -202,12 +202,13 @@ namespace Duplicati.Library.Main public static string GenerateRandomName() { - var backupname = ""; var rnd = new Random(); - for(var i = 0; i < 10; i++) - backupname += (char)rnd.Next('A', 'Z' + 1); - - return backupname; + + System.Text.StringBuilder backupName = new System.Text.StringBuilder(); + for (var i = 0; i < 10; i++) + backupName.Append(rnd.Next('A', 'Z' + 1)); + + return backupName.ToString(); } public static bool IsDatabasePathInUse(string path) @@ -220,7 +221,7 @@ namespace Duplicati.Library.Main if (!System.IO.File.Exists(file)) return false; - return Newtonsoft.Json.JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(file, System.Text.Encoding.UTF8)).Any(x => string.Equals(path, x.Databasepath, Library.Utility.Utility.ClientFilenameStringComparision)); + return Newtonsoft.Json.JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(file, System.Text.Encoding.UTF8)).Any(x => string.Equals(path, x.Databasepath, Library.Utility.Utility.ClientFilenameStringComparison)); } } } diff --git a/Duplicati/Library/Main/Operation/Backup/BackendUploader.cs b/Duplicati/Library/Main/Operation/Backup/BackendUploader.cs index 70b4a9c54..8347d6f9e 100644 --- a/Duplicati/Library/Main/Operation/Backup/BackendUploader.cs +++ b/Duplicati/Library/Main/Operation/Backup/BackendUploader.cs @@ -30,7 +30,7 @@ namespace Duplicati.Library.Main.Operation.Backup internal class FlushRequest : IUploadRequest { public Task LastWriteSizeAync { get { return m_tcs.Task; } } - private TaskCompletionSource m_tcs = new TaskCompletionSource(); + private readonly TaskCompletionSource m_tcs = new TaskCompletionSource(); public void SetFlushed(long size) { m_tcs.TrySetResult(size); diff --git a/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs b/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs index 21d7b21b5..f521afde8 100644 --- a/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs +++ b/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs @@ -33,7 +33,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// internal class BackupDatabase : DatabaseCommon { - private LocalBackupDatabase m_database; + private readonly LocalBackupDatabase m_database; public class FileEntryData { diff --git a/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs b/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs index 207d5379c..a2f3bea7d 100644 --- a/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs +++ b/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs @@ -26,7 +26,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// internal class BackupStatsCollector : StatsCollector { - private BackupResults m_res; + private readonly BackupResults m_res; public BackupStatsCollector(BackupResults res) : base(res.BackendWriter) diff --git a/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs b/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs index 5dc14ae79..745adccb2 100644 --- a/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs +++ b/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs @@ -143,7 +143,7 @@ namespace Duplicati.Library.Main.Operation.Backup var isDirectory = s[s.Length - 1] == System.IO.Path.DirectorySeparatorChar; if (isDirectory) { - while (pathstack.Count > 0 && !s.StartsWith(pathstack.Peek().Path, Library.Utility.Utility.ClientFilenameStringComparision)) + while (pathstack.Count > 0 && !s.StartsWith(pathstack.Peek().Path, Library.Utility.Utility.ClientFilenameStringComparison)) { var e = pathstack.Pop(); if (e.AnyEntries || pathstack.Count == 0) @@ -158,7 +158,7 @@ namespace Duplicati.Library.Main.Operation.Backup Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingEmptyFolder", "Excluding empty folder {0}", e.Path); } - if (pathstack.Count == 0 || s.StartsWith(pathstack.Peek().Path, Library.Utility.Utility.ClientFilenameStringComparision)) + if (pathstack.Count == 0 || s.StartsWith(pathstack.Peek().Path, Library.Utility.Utility.ClientFilenameStringComparison)) { pathstack.Push(new DirectoryStackEntry() { Path = s }); continue; diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs index 3c5468716..4ee61bbc4 100644 --- a/Duplicati/Library/Main/Operation/BackupHandler.cs +++ b/Duplicati/Library/Main/Operation/BackupHandler.cs @@ -48,7 +48,7 @@ namespace Duplicati.Library.Main.Operation private static readonly string LOGTAG = Logging.Log.LogTagFromType(); private readonly Options m_options; - private string m_backendurl; + private readonly string m_backendurl; private LocalBackupDatabase m_database; private System.Data.IDbTransaction m_transaction; @@ -56,7 +56,7 @@ namespace Duplicati.Library.Main.Operation private Library.Utility.IFilter m_filter; private Library.Utility.IFilter m_sourceFilter; - private BackupResults m_result; + private readonly BackupResults m_result; public BackupHandler(string backendurl, Options options, BackupResults results) { diff --git a/Duplicati/Library/Main/Operation/Common/BackendHandler.cs b/Duplicati/Library/Main/Operation/Common/BackendHandler.cs index b5deb6edb..b8fe1b55f 100644 --- a/Duplicati/Library/Main/Operation/Common/BackendHandler.cs +++ b/Duplicati/Library/Main/Operation/Common/BackendHandler.cs @@ -156,13 +156,13 @@ namespace Duplicati.Library.Main.Operation.Common } - private DatabaseCommon m_database; + private readonly DatabaseCommon m_database; private IBackend m_backend; - private Options m_options; - private string m_backendurl; + private readonly Options m_options; + private readonly string m_backendurl; private bool m_uploadSuccess; - private StatsCollector m_stats; - private ITaskReader m_taskreader; + private readonly StatsCollector m_stats; + private readonly ITaskReader m_taskreader; public BackendHandler(Options options, string backendUrl, DatabaseCommon database, StatsCollector stats, ITaskReader taskreader) : base() diff --git a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs index 4f88627e8..7d0ba3ece 100644 --- a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs +++ b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs @@ -29,8 +29,8 @@ namespace Duplicati.Library.Main.Operation /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); private string m_targetpath; - private Options m_options; - private CreateLogDatabaseResults m_result; + private readonly Options m_options; + private readonly CreateLogDatabaseResults m_result; public CreateBugReportHandler(string targetpath, Options options, CreateLogDatabaseResults result) { diff --git a/Duplicati/Library/Main/Operation/DeleteHandler.cs b/Duplicati/Library/Main/Operation/DeleteHandler.cs index dacc9b5fd..be88a8d50 100644 --- a/Duplicati/Library/Main/Operation/DeleteHandler.cs +++ b/Duplicati/Library/Main/Operation/DeleteHandler.cs @@ -34,7 +34,7 @@ namespace Duplicati.Library.Main.Operation /// private static readonly string LOGTAG_RETENTION = LOGTAG + ":RetentionPolicy"; - private DeleteResults m_result; + private readonly DeleteResults m_result; protected string m_backendurl; protected Options m_options; diff --git a/Duplicati/Library/Main/Operation/ListAffected.cs b/Duplicati/Library/Main/Operation/ListAffected.cs index f5bc643aa..3c9c9e3bd 100644 --- a/Duplicati/Library/Main/Operation/ListAffected.cs +++ b/Duplicati/Library/Main/Operation/ListAffected.cs @@ -24,8 +24,8 @@ namespace Duplicati.Library.Main.Operation { internal class ListAffected { - private Options m_options; - private ListAffectedResults m_result; + private readonly Options m_options; + private readonly ListAffectedResults m_result; public ListAffected(Options options, ListAffectedResults result) { diff --git a/Duplicati/Library/Main/Operation/ListChangesHandler.cs b/Duplicati/Library/Main/Operation/ListChangesHandler.cs index 66b5d50ea..c9b6ed52c 100644 --- a/Duplicati/Library/Main/Operation/ListChangesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListChangesHandler.cs @@ -29,9 +29,9 @@ namespace Duplicati.Library.Main.Operation /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ListChangesHandler)); - private string m_backendurl; - private Options m_options; - private ListChangesResults m_result; + private readonly string m_backendurl; + private readonly Options m_options; + private readonly ListChangesResults m_result; public ListChangesHandler(string backend, Options options, ListChangesResults result) { diff --git a/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs b/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs index 39747456b..8b89bb707 100644 --- a/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs @@ -23,9 +23,9 @@ namespace Duplicati.Library.Main.Operation { internal class ListControlFilesHandler { - private Options m_options; - private string m_backendurl; - private ListResults m_result; + private readonly Options m_options; + private readonly string m_backendurl; + private readonly ListResults m_result; public ListControlFilesHandler(string backendurl, Options options, ListResults result) { diff --git a/Duplicati/Library/Main/Operation/ListFilesHandler.cs b/Duplicati/Library/Main/Operation/ListFilesHandler.cs index 10e91fba6..3f20d524a 100644 --- a/Duplicati/Library/Main/Operation/ListFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFilesHandler.cs @@ -13,9 +13,9 @@ namespace Duplicati.Library.Main.Operation /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private string m_backendurl; - private Options m_options; - private ListResults m_result; + private readonly string m_backendurl; + private readonly Options m_options; + private readonly ListResults m_result; public ListFilesHandler(string backend, Options options, ListResults result) { diff --git a/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs b/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs index ba4b7949a..86b925f89 100644 --- a/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs +++ b/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs @@ -14,9 +14,9 @@ namespace Duplicati.Library.Main.Operation /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private string m_backendurl; - private Options m_options; - private RecreateDatabaseResults m_result; + private readonly string m_backendurl; + private readonly Options m_options; + private readonly RecreateDatabaseResults m_result; public delegate IEnumerable> NumberedFilterFilelistDelegate(IEnumerable filelist); public delegate void BlockVolumePostProcessor(string volumename,BlockVolumeReader reader); diff --git a/Duplicati/Library/Main/Operation/RepairHandler.cs b/Duplicati/Library/Main/Operation/RepairHandler.cs index 4f6e66849..147158298 100644 --- a/Duplicati/Library/Main/Operation/RepairHandler.cs +++ b/Duplicati/Library/Main/Operation/RepairHandler.cs @@ -14,9 +14,9 @@ namespace Duplicati.Library.Main.Operation /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private string m_backendurl; - private Options m_options; - private RepairResults m_result; + private readonly string m_backendurl; + private readonly Options m_options; + private readonly RepairResults m_result; public RepairHandler(string backend, Options options, RepairResults result) { diff --git a/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs b/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs index 00d0b4d75..1845e2fd3 100644 --- a/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs @@ -7,9 +7,9 @@ namespace Duplicati.Library.Main.Operation { internal class RestoreControlFilesHandler { - private Options m_options; - private string m_backendurl; - private RestoreControlFilesResults m_result; + private readonly Options m_options; + private readonly string m_backendurl; + private readonly RestoreControlFilesResults m_result; public RestoreControlFilesHandler(string backendurl, Options options, RestoreControlFilesResults result) { diff --git a/Duplicati/Library/Main/Operation/RestoreHandler.cs b/Duplicati/Library/Main/Operation/RestoreHandler.cs index 341e117d5..d8a507a1f 100644 --- a/Duplicati/Library/Main/Operation/RestoreHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreHandler.cs @@ -15,10 +15,10 @@ namespace Duplicati.Library.Main.Operation /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private string m_backendurl; - private Options m_options; + private readonly string m_backendurl; + private readonly Options m_options; private byte[] m_blockbuffer; - private RestoreResults m_result; + private readonly RestoreResults m_result; private static readonly Snapshots.ISystemIO m_systemIO = Duplicati.Library.Utility.Utility.IsClientLinux ? (Snapshots.ISystemIO)new Snapshots.SystemIOLinux() : (Snapshots.ISystemIO)new Snapshots.SystemIOWindows(); private static readonly string DIRSEP = System.IO.Path.DirectorySeparatorChar.ToString(); diff --git a/Duplicati/Library/Main/Operation/TestHandler.cs b/Duplicati/Library/Main/Operation/TestHandler.cs index fb36b1784..a879b003d 100644 --- a/Duplicati/Library/Main/Operation/TestHandler.cs +++ b/Duplicati/Library/Main/Operation/TestHandler.cs @@ -31,8 +31,8 @@ namespace Duplicati.Library.Main.Operation private static readonly string LOGTAG = Logging.Log.LogTagFromType(); private readonly Options m_options; - private string m_backendurl; - private TestResults m_results; + private readonly string m_backendurl; + private readonly TestResults m_results; public TestHandler(string backendurl, Options options, TestResults results) { diff --git a/Duplicati/Library/Main/Operation/VacuumHandler.cs b/Duplicati/Library/Main/Operation/VacuumHandler.cs index 454366667..51f3eefa9 100644 --- a/Duplicati/Library/Main/Operation/VacuumHandler.cs +++ b/Duplicati/Library/Main/Operation/VacuumHandler.cs @@ -8,8 +8,8 @@ namespace Duplicati.Library.Main.Operation { internal class VacuumHandler { - private Options m_options; - private VacuumResult m_result; + private readonly Options m_options; + private readonly VacuumResult m_result; public VacuumHandler(Options options, VacuumResult result) { diff --git a/Duplicati/Library/Main/Options.cs b/Duplicati/Library/Main/Options.cs index edcf162f0..f6d23310b 100644 --- a/Duplicati/Library/Main/Options.cs +++ b/Duplicati/Library/Main/Options.cs @@ -20,7 +20,6 @@ using System; using System.Linq; using System.Collections.Generic; -using System.Text; using Duplicati.Library.Interface; using Duplicati.Library.Utility; @@ -739,8 +738,8 @@ namespace Duplicati.Library.Main { if (!m_options.ContainsKey("tempdir") || string.IsNullOrEmpty(m_options["tempdir"])) return System.IO.Path.GetTempPath(); - else - return m_options["tempdir"]; + + return m_options["tempdir"]; } } diff --git a/Duplicati/Library/Main/ResultClasses.cs b/Duplicati/Library/Main/ResultClasses.cs index 200d726df..5e78a23d8 100644 --- a/Duplicati/Library/Main/ResultClasses.cs +++ b/Duplicati/Library/Main/ResultClasses.cs @@ -202,7 +202,7 @@ namespace Duplicati.Library.Main protected Queue m_dbqueue; private TaskControlState m_controlState = TaskControlState.Run; - private System.Threading.ManualResetEvent m_pauseEvent = new System.Threading.ManualResetEvent(true); + private readonly System.Threading.ManualResetEvent m_pauseEvent = new System.Threading.ManualResetEvent(true); public virtual ParsedResultType ParsedResult { @@ -863,7 +863,7 @@ namespace Duplicati.Library.Main public override OperationMode MainOperation { get { return OperationMode.Test; } } public IEnumerable>>> Verifications { get { return m_verifications; } } - private List>>> m_verifications = new List>>>(); + private readonly List>>> m_verifications = new List>>>(); public KeyValuePair>> AddResult(string volume, IEnumerable> changes) { diff --git a/Duplicati/Library/Main/Volumes/FilesetVolumeReader.cs b/Duplicati/Library/Main/Volumes/FilesetVolumeReader.cs index 483d35981..044d8dacd 100644 --- a/Duplicati/Library/Main/Volumes/FilesetVolumeReader.cs +++ b/Duplicati/Library/Main/Volumes/FilesetVolumeReader.cs @@ -20,7 +20,7 @@ namespace Duplicati.Library.Main.Volumes { private class BlocklistHashEnumerator : IEnumerator { - private JsonReader m_reader; + private readonly JsonReader m_reader; private string m_current; private bool m_firstReset = true; private bool m_done = false; @@ -73,7 +73,7 @@ namespace Duplicati.Library.Main.Volumes } } - private JsonReader m_reader; + private readonly JsonReader m_reader; public BlocklistHashEnumerable(JsonReader reader) { m_reader = reader; @@ -103,7 +103,7 @@ namespace Duplicati.Library.Main.Volumes public long Blocksize { get; private set; } public IEnumerable BlocklistHashes { get; private set; } public IEnumerable MetaBlocklistHashes { get; private set; } - private JsonReader m_reader; + private readonly JsonReader m_reader; public FileEntry(JsonReader reader) { @@ -221,7 +221,7 @@ namespace Duplicati.Library.Main.Volumes } } - private ICompression m_compression; + private readonly ICompression m_compression; private FileEntry m_current; private StreamReader m_stream; private JsonReader m_reader; @@ -302,7 +302,7 @@ namespace Duplicati.Library.Main.Volumes } } - private ICompression m_compression; + private readonly ICompression m_compression; public FileEntryEnumerable(ICompression compression) { m_compression = compression; @@ -323,7 +323,7 @@ namespace Duplicati.Library.Main.Volumes { private class ControlFileEnumerator : IEnumerator> { - private ICompression m_compression; + private readonly ICompression m_compression; private string[] m_files; private long m_index; private KeyValuePair? m_current; @@ -365,7 +365,7 @@ namespace Duplicati.Library.Main.Volumes } } - private ICompression m_compression; + private readonly ICompression m_compression; public ControlFileEnumerable(ICompression compression) { m_compression = compression; diff --git a/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs b/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs index 7fb76a585..b63574b6f 100644 --- a/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs +++ b/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs @@ -11,7 +11,7 @@ namespace Duplicati.Library.Main.Volumes public class FilesetVolumeWriter : VolumeWriterBase { private StreamWriter m_streamwriter; - private JsonWriter m_writer; + private readonly JsonWriter m_writer; private long m_filecount; private long m_foldercount; diff --git a/Duplicati/Library/Main/Volumes/IndexVolumeReader.cs b/Duplicati/Library/Main/Volumes/IndexVolumeReader.cs index acbe5f0a7..bff7e5e47 100644 --- a/Duplicati/Library/Main/Volumes/IndexVolumeReader.cs +++ b/Duplicati/Library/Main/Volumes/IndexVolumeReader.cs @@ -28,7 +28,7 @@ namespace Duplicati.Library.Main.Volumes private class IndexBlockVolumeEnumerable : IEnumerable { - private ICompression m_compression; + private readonly ICompression m_compression; public IndexBlockVolumeEnumerable(ICompression compression) { @@ -41,8 +41,8 @@ namespace Duplicati.Library.Main.Volumes { private class BlockEnumerator : IEnumerator> { - private ICompression m_compression; - private string m_filename; + private readonly ICompression m_compression; + private readonly string m_filename; private KeyValuePair? m_current; private System.IO.StreamReader m_stream; private JsonReader m_reader; @@ -139,8 +139,8 @@ namespace Duplicati.Library.Main.Volumes } } - private ICompression m_compression; - private string m_filename; + private readonly ICompression m_compression; + private readonly string m_filename; private BlockEnumerator m_enumerator; public BlockEnumerable(ICompression compression, string filename) @@ -173,8 +173,8 @@ namespace Duplicati.Library.Main.Volumes private class IndexBlockVolume : IIndexBlockVolume { - private ICompression m_compression; - private string m_filename; + private readonly ICompression m_compression; + private readonly string m_filename; private long? m_length; private string m_hash; private BlockEnumerable m_blocks; @@ -231,7 +231,7 @@ namespace Duplicati.Library.Main.Volumes } } - private ICompression m_compression; + private readonly ICompression m_compression; private IndexBlockVolume m_current; private string[] m_files; private long m_index; @@ -284,8 +284,8 @@ namespace Duplicati.Library.Main.Volumes private class IndexBlocklistEnumerable : IEnumerable { - private ICompression m_compression; - private long m_hashsize; + private readonly ICompression m_compression; + private readonly long m_hashsize; public IndexBlocklistEnumerable(ICompression compression, long hashsize) { @@ -297,10 +297,10 @@ namespace Duplicati.Library.Main.Volumes { private class IndexBlocklist : IIndexBlocklist { - private ICompression m_compression; - private string m_filename; - private long m_size; - private long m_hashsize; + private readonly ICompression m_compression; + private readonly string m_filename; + private readonly long m_size; + private readonly long m_hashsize; public IndexBlocklist(ICompression compression, string filename, long size, long hashsize) { @@ -333,11 +333,11 @@ namespace Duplicati.Library.Main.Volumes } } - private ICompression m_compression; + private readonly ICompression m_compression; private long m_index; private KeyValuePair[] m_files; private IndexBlocklist m_current; - private long m_hashsize; + private readonly long m_hashsize; public IndexBlocklistEnumerator(ICompression compression, long hashsize) { @@ -386,7 +386,7 @@ namespace Duplicati.Library.Main.Volumes System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } - private long m_hashsize; + private readonly long m_hashsize; public IndexVolumeReader(ICompression compression, Options options, long hashsize) : base(compression, options) diff --git a/Duplicati/Library/Snapshots/DefineDosDevice.cs b/Duplicati/Library/Snapshots/DefineDosDevice.cs index 762d7512a..135d44946 100644 --- a/Duplicati/Library/Snapshots/DefineDosDevice.cs +++ b/Duplicati/Library/Snapshots/DefineDosDevice.cs @@ -59,11 +59,11 @@ namespace Duplicati.Library.Snapshots /// /// The path that is mapped to the drive /// - private string m_targetPath; + private readonly string m_targetPath; /// /// A value indicating if the shell should be notified of changes /// - private bool m_shellBroadcast; + private readonly bool m_shellBroadcast; /// /// Gets the drive that this mapping represents diff --git a/Duplicati/Library/Snapshots/HyperVUtility.cs b/Duplicati/Library/Snapshots/HyperVUtility.cs index ea6bcb416..4858d94f3 100644 --- a/Duplicati/Library/Snapshots/HyperVUtility.cs +++ b/Duplicati/Library/Snapshots/HyperVUtility.cs @@ -89,7 +89,7 @@ namespace Duplicati.Library.Snapshots /// Enumerated Hyper-V guests /// public List Guests { get { return m_Guests; } } - private List m_Guests; + private readonly List m_Guests; public HyperVUtility() { diff --git a/Duplicati/Library/Snapshots/MSSQLUtility.cs b/Duplicati/Library/Snapshots/MSSQLUtility.cs index 0f8f03e54..4cfb6bc7d 100644 --- a/Duplicati/Library/Snapshots/MSSQLUtility.cs +++ b/Duplicati/Library/Snapshots/MSSQLUtility.cs @@ -79,7 +79,7 @@ namespace Duplicati.Library.Snapshots /// Enumerated MS SQL DBs /// public List DBs { get { return m_DBs; } } - private List m_DBs; + private readonly List m_DBs; public MSSQLUtility() { diff --git a/Duplicati/Library/Snapshots/USNJournal.cs b/Duplicati/Library/Snapshots/USNJournal.cs index 1c360187c..52251e179 100644 --- a/Duplicati/Library/Snapshots/USNJournal.cs +++ b/Duplicati/Library/Snapshots/USNJournal.cs @@ -153,7 +153,7 @@ namespace Duplicati.Library.Snapshots { if (r.UsnRecord.Usn >= minUsn && (reason == ChangeReason.Any || (MapChangeReason(r.UsnRecord.Reason) & reason) != 0) - && (r.FullPath.Equals(sourceFileOrFolder, Utility.Utility.ClientFilenameStringComparision) + && (r.FullPath.Equals(sourceFileOrFolder, Utility.Utility.ClientFilenameStringComparison) || isFolder && Utility.Utility.IsPathBelowFolder(r.FullPath, sourceFileOrFolder))) { yield return Tuple.Create(r.FullPath, @@ -242,7 +242,7 @@ namespace Duplicati.Library.Snapshots { private readonly IReadOnlyCollection m_entryData; private readonly IntPtr m_bufferPointer; - private GCHandle m_bufferHandle; + private readonly GCHandle m_bufferHandle; private long m_offset; public RecordEnumeratorImpl(IReadOnlyCollection entryData) diff --git a/Duplicati/Library/Snapshots/UsnJournalService.cs b/Duplicati/Library/Snapshots/UsnJournalService.cs index f91a12806..638698832 100644 --- a/Duplicati/Library/Snapshots/UsnJournalService.cs +++ b/Duplicati/Library/Snapshots/UsnJournalService.cs @@ -387,7 +387,7 @@ namespace Duplicati.Library.Snapshots foreach (var folder in volumeData.Folders) { - if (path.Equals(folder, Utility.Utility.ClientFilenameStringComparision)) + if (path.Equals(folder, Utility.Utility.ClientFilenameStringComparison)) return true; // do not append from previous set, already scanned if (Utility.Utility.IsPathBelowFolder(path, folder)) diff --git a/Duplicati/Library/Snapshots/WindowsSnapshot.cs b/Duplicati/Library/Snapshots/WindowsSnapshot.cs index b0bad5d90..3f47322de 100644 --- a/Duplicati/Library/Snapshots/WindowsSnapshot.cs +++ b/Duplicati/Library/Snapshots/WindowsSnapshot.cs @@ -392,7 +392,7 @@ namespace Duplicati.Library.Snapshots foreach (var kvp in m_volumeReverseMap) { - if (snapshotPath.StartsWith(kvp.Key, Utility.Utility.ClientFilenameStringComparision)) + if (snapshotPath.StartsWith(kvp.Key, Utility.Utility.ClientFilenameStringComparison)) return Path.Combine(kvp.Value, snapshotPath.Substring(kvp.Key.Length)); } diff --git a/Duplicati/Library/Utility/AsyncHttpRequest.cs b/Duplicati/Library/Utility/AsyncHttpRequest.cs index d8eff7bfb..f515f7b90 100644 --- a/Duplicati/Library/Utility/AsyncHttpRequest.cs +++ b/Duplicati/Library/Utility/AsyncHttpRequest.cs @@ -17,7 +17,7 @@ namespace Duplicati.Library.Utility /// /// The method being wrapped /// - private WebRequest m_request; + private readonly WebRequest m_request; /// /// The current internal state of the object /// @@ -37,7 +37,7 @@ namespace Duplicati.Library.Utility /// /// The activity timeout value /// - private int m_activity_timeout = (int)TimeSpan.FromSeconds(30).TotalMilliseconds; + private readonly int m_activity_timeout = (int)TimeSpan.FromSeconds(30).TotalMilliseconds; /// /// List of valid states @@ -186,13 +186,13 @@ namespace Duplicati.Library.Utility /// private class AsyncWrapper { - private IAsyncResult m_async = null; + private readonly IAsyncResult m_async = null; private Stream m_stream = null; private WebResponse m_response = null; - private AsyncHttpRequest m_owner; + private readonly AsyncHttpRequest m_owner; private Exception m_exception = null; - private ManualResetEvent m_event = new ManualResetEvent(false); - private bool m_isRequest; + private readonly ManualResetEvent m_event = new ManualResetEvent(false); + private readonly bool m_isRequest; private bool m_timedout = false; public AsyncWrapper(AsyncHttpRequest owner, bool isRequest) diff --git a/Duplicati/Library/Utility/BlockingQueue.cs b/Duplicati/Library/Utility/BlockingQueue.cs index 5a057f489..2e8c63d3c 100644 --- a/Duplicati/Library/Utility/BlockingQueue.cs +++ b/Duplicati/Library/Utility/BlockingQueue.cs @@ -56,8 +56,8 @@ namespace Duplicati.Library.Utility private class BlockingQueueEnumerator : IEnumerator { - private BlockingQueue m_queue; - private bool m_first = true; + private readonly BlockingQueue m_queue; + private readonly bool m_first = true; private T m_current; public BlockingQueueEnumerator(BlockingQueue queue) @@ -119,7 +119,7 @@ namespace Duplicati.Library.Utility /// /// The queue storing the elements produced /// - private Queue m_queue = new Queue(); + private readonly Queue m_queue = new Queue(); /// /// A flag indicating if the queue is now empty forever /// @@ -127,15 +127,15 @@ namespace Duplicati.Library.Utility /// /// The event that signals that items have been produced (consumers wait for this) /// - private System.Threading.ManualResetEvent m_itemsProduced = new System.Threading.ManualResetEvent(false); + private readonly System.Threading.ManualResetEvent m_itemsProduced = new System.Threading.ManualResetEvent(false); /// /// The event that signals that items have been consumed (producers wait for this) /// - private System.Threading.ManualResetEvent m_itemsConsumed = new System.Threading.ManualResetEvent(false); + private readonly System.Threading.ManualResetEvent m_itemsConsumed = new System.Threading.ManualResetEvent(false); /// /// The maximum capacity of the queue, the producers will be blocked if more elements are put into the queue /// - private long m_maxCapacity = 100; + private readonly long m_maxCapacity = 100; /// /// Initializes a new instance of the class with the default capacity diff --git a/Duplicati/Library/Utility/DirectStreamLink.cs b/Duplicati/Library/Utility/DirectStreamLink.cs index 2142f5f74..43c06839b 100644 --- a/Duplicati/Library/Utility/DirectStreamLink.cs +++ b/Duplicati/Library/Utility/DirectStreamLink.cs @@ -44,9 +44,9 @@ namespace Duplicati.Library.Utility private readonly object m_lock = new object(); /// An event to wake reader. - private ManualResetEventSlim m_signalDataAvailable = new ManualResetEventSlim(false); + private readonly ManualResetEventSlim m_signalDataAvailable = new ManualResetEventSlim(false); /// An event to wake writer. - private ManualResetEventSlim m_signalBufferAvailable = new ManualResetEventSlim(true); + private readonly ManualResetEventSlim m_signalBufferAvailable = new ManualResetEventSlim(true); /// Track closes and enable threadsafe self dispose. private int m_autoDisposeCounter = 2; @@ -60,10 +60,10 @@ namespace Duplicati.Library.Utility private volatile bool m_readerClosed = false; /// The buffer for piping. - private byte[] m_buf; + private readonly byte[] m_buf; /// A stream to pass writes to. For stream stacking. - private Stream m_passWriteThrough; + private readonly Stream m_passWriteThrough; /// Allows to set a length for SubStreams Length property. private long m_knownLength = -1; @@ -78,14 +78,14 @@ namespace Duplicati.Library.Utility /// Forces to wait until reader has consumed all bytes from buffer /// on a Flush operation. Set via constructor. /// - private bool m_blockOnFlush = true; + private readonly bool m_blockOnFlush = true; /// /// Forces to wait until reader has closed its stream /// on writer's Close operation. This may be used to /// synchronize worker threads. Set via constructor. /// - private bool m_blockOnClose = true; + private readonly bool m_blockOnClose = true; /// The helper stream for reader from pipe. private LinkedReaderStream m_readerStream; diff --git a/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj b/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj index 2536ca9d1..f3b04d18d 100644 --- a/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj +++ b/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj @@ -61,7 +61,7 @@ - + diff --git a/Duplicati/Library/Utility/FileBackedList.cs b/Duplicati/Library/Utility/FileBackedList.cs index 293e74cd5..e3ccbdb1b 100644 --- a/Duplicati/Library/Utility/FileBackedList.cs +++ b/Duplicati/Library/Utility/FileBackedList.cs @@ -33,11 +33,11 @@ namespace Duplicati.Library.Utility private class StreamEnumerator : IEnumerator, System.Collections.IEnumerator { private Stream m_stream; - private Func m_deserialize; + private readonly Func m_deserialize; private long m_position; private readonly byte[] m_sizebuffer; - private long m_expectedCount; - private FileBackedList m_parent; + private readonly long m_expectedCount; + private readonly FileBackedList m_parent; private T m_current; public StreamEnumerator(Stream stream, Func deserialize, FileBackedList parent) diff --git a/Duplicati/Library/Utility/FilterCollector.cs b/Duplicati/Library/Utility/FilterCollector.cs index 3abd70710..8c21f900d 100644 --- a/Duplicati/Library/Utility/FilterCollector.cs +++ b/Duplicati/Library/Utility/FilterCollector.cs @@ -22,36 +22,32 @@ namespace Duplicati.Library.Utility { public class FilterCollector { - private List m_filters = new List(); - private Library.Utility.IFilter Filter - { - get - { + private readonly List m_filters = new List(); + private Library.Utility.IFilter Filter + { + get + { if (m_filters.Count == 0) return new Library.Utility.FilterExpression(); else if (m_filters.Count == 1) return m_filters[0]; - else - return m_filters.Aggregate((a,b) => Library.Utility.JoinedFilterExpression.Join(a, b)); + else + return m_filters.Aggregate(Library.Utility.JoinedFilterExpression.Join); } } private Dictionary DoExtractOptions(List args, Func callbackHandler = null) { - return Library.Utility.CommandLineParser.ExtractOptions(args, (key, value) => { - if (key.Equals("include", StringComparison.OrdinalIgnoreCase)) + return Library.Utility.CommandLineParser.ExtractOptions(args, (key, value) => + { + if (!string.IsNullOrEmpty(value)) { - if (!string.IsNullOrEmpty(value)) + bool include = key.Equals("include", StringComparison.OrdinalIgnoreCase); + bool exclude = key.Equals("exclude", StringComparison.OrdinalIgnoreCase); + + if (include || exclude) { - m_filters.Add(new Library.Utility.FilterExpression(Library.Utility.Utility.ExpandEnvironmentVariables(value), true)); - return false; - } - } - else if (key.Equals("exclude", StringComparison.OrdinalIgnoreCase)) - { - if (!string.IsNullOrEmpty(value)) - { - m_filters.Add(new Library.Utility.FilterExpression(Library.Utility.Utility.ExpandEnvironmentVariables(value), false)); + m_filters.Add(new Library.Utility.FilterExpression(Library.Utility.Utility.ExpandEnvironmentVariables(value), include)); return false; } } @@ -61,8 +57,8 @@ namespace Duplicati.Library.Utility return true; }); - } - + } + public static Tuple, Library.Utility.IFilter> ExtractOptions(List args, Func callbackHandler = null) { var fc = new FilterCollector(); diff --git a/Duplicati/Library/Utility/FilterExpression.cs b/Duplicati/Library/Utility/FilterExpression.cs index 8a3b7ee84..9a5e11318 100644 --- a/Duplicati/Library/Utility/FilterExpression.cs +++ b/Duplicati/Library/Utility/FilterExpression.cs @@ -264,7 +264,7 @@ namespace Duplicati.Library.Utility switch (this.Type) { case FilterType.Simple: - return string.Equals(this.Filter, path, Library.Utility.Utility.ClientFilenameStringComparision); + return string.Equals(this.Filter, path, Library.Utility.Utility.ClientFilenameStringComparison); case FilterType.Wildcard: return IsWildcardMatch(!Utility.IsFSCaseSensitive ? path.ToUpper() : path, this.Filter); case FilterType.Regexp: @@ -420,11 +420,11 @@ namespace Duplicati.Library.Utility private static List Compact(IEnumerable items) { var r = new List(); - string combined = null; + System.Text.StringBuilder combined = new System.Text.StringBuilder(); bool first = false; foreach (var f in items) { - if (combined == null) + if (combined.Length == 0) { // Note that even though group filters may include regexes, we don't want to merge them together and compact them, // since that would make their names much more difficult to interpret on the command line. @@ -432,7 +432,7 @@ namespace Duplicati.Library.Utility r.Add(f); else if (f.Type != FilterType.Empty) { - combined = f.Regexp.ToString(); + combined.Append(f.Regexp.ToString()); first = true; } } @@ -440,25 +440,25 @@ namespace Duplicati.Library.Utility { if (f.Type == FilterType.Simple || f.Type == FilterType.Wildcard || f.Type == FilterType.Group) { - r.Add(new FilterEntry("[" + combined + "]")); + r.Add(new FilterEntry("[" + combined.Append("]"))); r.Add(f); - combined = null; + combined.Clear(); } else if (f.Type != FilterType.Empty) { if (first) { - combined = "(" + combined + ")"; + combined.Insert(0, "(").Append(")"); first = false; } - combined += "|(" + f.Regexp + ")"; + combined.Append("|(" + f.Regexp + ")"); } } } - - if (combined != null) - r.Add(new FilterEntry("[" + combined + "]")); + + if (combined.Length > 0) + r.Add(new FilterEntry("[" + combined.Append("]"))); return r; } diff --git a/Duplicati/Library/Utility/FilterGroups.cs b/Duplicati/Library/Utility/FilterGroups.cs index e9e05a887..0176eff38 100644 --- a/Duplicati/Library/Utility/FilterGroups.cs +++ b/Duplicati/Library/Utility/FilterGroups.cs @@ -268,7 +268,7 @@ namespace Duplicati.Library.Utility if (n.Length > 2 && char.IsLetter(n[0]) && n[1] == ':' && wildcardRootWindowsPaths.Contains(n.Substring(2))) continue; - else if (prev != null && prev.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && n.StartsWith(prev, Utility.ClientFilenameStringComparision)) + else if (prev != null && prev.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && n.StartsWith(prev, Utility.ClientFilenameStringComparison)) continue; else yield return prev = n; diff --git a/Duplicati/Library/Utility/ProgressReportingStream.cs b/Duplicati/Library/Utility/ProgressReportingStream.cs index 5acdaface..061c3a01c 100644 --- a/Duplicati/Library/Utility/ProgressReportingStream.cs +++ b/Duplicati/Library/Utility/ProgressReportingStream.cs @@ -28,7 +28,7 @@ namespace Duplicati.Library.Utility /// public class ProgressReportingStream : OverrideableStream { - private Action m_progress; + private readonly Action m_progress; private long m_streamOffset; public ProgressReportingStream(System.IO.Stream basestream, long expectedSize, Action progress) diff --git a/Duplicati/Library/Utility/SslCertificateValidator.cs b/Duplicati/Library/Utility/SslCertificateValidator.cs index 9b579adee..658d731aa 100644 --- a/Duplicati/Library/Utility/SslCertificateValidator.cs +++ b/Duplicati/Library/Utility/SslCertificateValidator.cs @@ -30,8 +30,8 @@ namespace Duplicati.Library.Utility [Serializable] public class InvalidCertificateException : Exception { - private string m_certificate = null; - private SslPolicyErrors m_errors = SslPolicyErrors.None; + private readonly string m_certificate = null; + private readonly SslPolicyErrors m_errors = SslPolicyErrors.None; public string Certificate { get { return m_certificate; } } public SslPolicyErrors SslError { get { return m_errors; } } @@ -50,8 +50,8 @@ namespace Duplicati.Library.Utility m_validHashes = validHashes; } - private bool m_acceptAll = false; - private string[] m_validHashes = null; + private readonly bool m_acceptAll = false; + private readonly string[] m_validHashes = null; private Exception m_uncastException = null; public bool ValidateServerCertficate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors) diff --git a/Duplicati/Library/Utility/Uri.cs b/Duplicati/Library/Utility/Uri.cs index 7e42f025c..cbdaa7a1c 100644 --- a/Duplicati/Library/Utility/Uri.cs +++ b/Duplicati/Library/Utility/Uri.cs @@ -17,6 +17,8 @@ // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Collections.Specialized; +using System.Linq; +using System.Text; using System.Web; namespace Duplicati.Library.Utility @@ -62,18 +64,18 @@ namespace Duplicati.Library.Utility /// /// The password, if any /// - public readonly string Password; - + public readonly string Password; + /// /// The original URI. /// - public readonly string OriginalUri; - + public readonly string OriginalUri; + /// /// Cache for the query parameters. /// - private NameValueCollection m_queryParams; - + private NameValueCollection m_queryParams; + /// /// Gets the paramters in the query string /// @@ -88,12 +90,12 @@ namespace Duplicati.Library.Utility m_queryParams = new NameValueCollection(); else m_queryParams = ParseQueryString(Query); - } - + } + return m_queryParams; } - } - + } + /// /// Gets the host and path. /// @@ -109,8 +111,8 @@ namespace Duplicati.Library.Utility else return Host + (Path == null ? "" : "/" + Path); } - } - + } + /// /// Gets the path and query. /// @@ -119,10 +121,10 @@ namespace Duplicati.Library.Utility { get { - return (Path ?? "") + (Query == null ? "" : "?" + Query); + return (Path ?? "") + (Query == null ? "" : "?" + Query); } - } - + } + /// /// Initializes a new instance of the struct. /// @@ -130,8 +132,8 @@ namespace Duplicati.Library.Utility public Uri(string url) { if (string.IsNullOrEmpty(url)) - throw new ArgumentNullException(nameof(url)); - + throw new ArgumentNullException(nameof(url)); + m_queryParams = null; this.OriginalUri = url; @@ -159,8 +161,8 @@ namespace Duplicati.Library.Utility { } throw new ArgumentException(Strings.Uri.UriParseError(url), nameof(url)); - } - + } + this.Scheme = m.Groups["scheme"].Value; var h = m.Groups["hostname"].Success ? m.Groups["hostname"].Value : ""; @@ -185,8 +187,8 @@ namespace Duplicati.Library.Utility this.Port = int.Parse(m.Groups["port"].Value); else this.Port = -1; - } - + } + /// /// Constructs a free-form URI from components /// @@ -208,13 +210,13 @@ namespace Duplicati.Library.Utility Password = password; Port = port; OriginalUri = AsString(scheme, host, path, query, username, password, port); - } - + } + /// /// Returns a that represents the current . /// /// A that represents the current . - public override string ToString () + public override string ToString() { return AsString(Scheme, Host, Path, Query, Username, Password, Port); } @@ -226,8 +228,8 @@ namespace Duplicati.Library.Utility { if (string.IsNullOrEmpty(Host)) throw new ArgumentException(Strings.Uri.NoHostname(OriginalUri)); - } - + } + /// /// Constructs an url-like string from components. /// @@ -243,21 +245,21 @@ namespace Duplicati.Library.Utility { var s = scheme + "://"; if (!string.IsNullOrEmpty(username) || !string.IsNullOrEmpty(password)) - { - + { + s += UrlEncode(username ?? ""); s += ":"; s += UrlEncode(password ?? ""); s += "@"; - } - + } + if (!string.IsNullOrEmpty(host)) { s += host; if (port != -1) s += ":" + port.ToString(); - } - + } + if (!string.IsNullOrEmpty(path)) { if (!string.IsNullOrEmpty(host) && !path.StartsWith("/", StringComparison.Ordinal)) @@ -267,9 +269,9 @@ namespace Duplicati.Library.Utility if (!string.IsNullOrEmpty(query)) s += "?" + query; - return s; - } - + return s; + } + /// /// Creates a new instance with another scheme /// @@ -278,8 +280,8 @@ namespace Duplicati.Library.Utility public Uri SetScheme(string scheme) { return new Uri(scheme, Host, Path, Query, Username, Password, Port); - } - + } + /// /// Creates a new instance with another host /// @@ -288,8 +290,8 @@ namespace Duplicati.Library.Utility public Uri SetHost(string host) { return new Uri(Scheme, host, Path, Query, Username, Password, Port); - } - + } + /// /// Creates a new instance with another path /// @@ -298,8 +300,8 @@ namespace Duplicati.Library.Utility public Uri SetPath(string path) { return new Uri(Scheme, Host, path, Query, Username, Password, Port); - } - + } + /// /// Creates a new instance with another query /// @@ -319,8 +321,8 @@ namespace Duplicati.Library.Utility public Uri SetCredentials(string username, string password) { return new Uri(Scheme, Host, Path, Query, username, password, Port); - } - + } + /// /// Creates a new instance with another port /// @@ -329,13 +331,13 @@ namespace Duplicati.Library.Utility public Uri SetPort(int port) { return new Uri(Scheme, Host, Path, Query, Username, Password, port); - } - + } + /// /// The regular expression that matches %20 type values in a querystring /// - private static System.Text.RegularExpressions.Regex RE_ESCAPECHAR = new System.Text.RegularExpressions.Regex(@"[^0-9a-zA-Z\-_]", System.Text.RegularExpressions.RegexOptions.Compiled); - + private static System.Text.RegularExpressions.Regex RE_ESCAPECHAR = new System.Text.RegularExpressions.Regex(@"[^0-9a-zA-Z\-_]", System.Text.RegularExpressions.RegexOptions.Compiled); + /// /// Encodes a URL, like System.Web.HttpUtility.UrlEncode /// @@ -345,44 +347,45 @@ namespace Duplicati.Library.Utility public static string UrlPathEncode(string value, System.Text.Encoding encoding = null) { return UrlEncode(value, encoding, "%20"); - } - + } + /// /// Encodes a URL, like System.Web.HttpUtility.UrlEncode /// /// The encoded URL /// The URL fragment to encode /// The encoding to use - public static string UrlEncode(string value, System.Text.Encoding encoding = null, string spacevalue = "+") + public static string UrlEncode(string value, System.Text.Encoding encoding = null, string spacevalue = "+") { if (value == null) - throw new ArgumentNullException(nameof(value)); - + throw new ArgumentNullException(nameof(value)); + encoding = encoding ?? System.Text.Encoding.UTF8; var encoder = encoding.GetEncoder(); - var inbuf = new char[1]; + var inbuf = new char[1]; var outbuf = new byte[4]; - return RE_ESCAPECHAR.Replace(value, (m) => { + return RE_ESCAPECHAR.Replace(value, (m) => + { if (m.Value == " ") - return spacevalue; - - inbuf[0] = m.Value[0]; - - try + return spacevalue; + + inbuf[0] = m.Value[0]; + + try { var len = encoder.GetBytes(inbuf, 0, 1, outbuf, 0, true); return "%" + BitConverter.ToString(outbuf, 0, len).Replace("-", "%"); } catch { - } - - //Fallback + } + + //Fallback return m.Value; - }); - + }); + } /// @@ -399,15 +402,16 @@ namespace Duplicati.Library.Utility public static string UrlDecode(string value, System.Text.Encoding encoding = null) { if (value == null) - throw new ArgumentNullException(nameof(value)); - - encoding = encoding ?? System.Text.Encoding.UTF8; - + throw new ArgumentNullException(nameof(value)); + + encoding = encoding ?? System.Text.Encoding.UTF8; + var decoder = encoding.GetDecoder(); var inbuf = new byte[8]; var outbuf = new char[8]; - return RE_NUMBER.Replace(value, (m) => { + return RE_NUMBER.Replace(value, (m) => + { if (m.Value == "+") return " "; @@ -416,24 +420,24 @@ namespace Duplicati.Library.Utility var hex = m.Groups["number"].Value; var bytelen = hex.Length / 2; Utility.HexStringAsByteArray(hex, inbuf); - var c = decoder.GetChars(inbuf, 0, bytelen, outbuf, 0); + var c = decoder.GetChars(inbuf, 0, bytelen, outbuf, 0); return new string(outbuf, 0, c); } catch { - } - - //Fallback + } + + //Fallback return m.Value; - }); - - } - + }); + + } + /// /// The regular expression that matches a=b type values in a querystring /// - private static System.Text.RegularExpressions.Regex RE_URLPARAM = new System.Text.RegularExpressions.Regex(@"(?[^\=\&]+)(\=(?[^\&]*))?", System.Text.RegularExpressions.RegexOptions.Compiled); - + private static System.Text.RegularExpressions.Regex RE_URLPARAM = new System.Text.RegularExpressions.Regex(@"(?[^\=\&]+)(\=(?[^\&]*))?", System.Text.RegularExpressions.RegexOptions.Compiled); + /// /// Parses the query string. /// This is a duplicate of the System.Web.HttpUtility.ParseQueryString that does not work well on Mono @@ -447,14 +451,104 @@ namespace Duplicati.Library.Utility if (query.StartsWith("?", StringComparison.Ordinal)) query = query.Substring(1); if (string.IsNullOrEmpty(query)) - return new NameValueCollection(StringComparer.OrdinalIgnoreCase); - + return new NameValueCollection(StringComparer.OrdinalIgnoreCase); + var result = new NameValueCollection(StringComparer.OrdinalIgnoreCase); - foreach(System.Text.RegularExpressions.Match m in RE_URLPARAM.Matches(query)) - result.Add(UrlDecode(m.Groups["key"].Value), UrlDecode(m.Groups["value"].Success ? m.Groups["value"].Value : "")); - - return result; - } + foreach (System.Text.RegularExpressions.Match m in RE_URLPARAM.Matches(query)) + result.Add(UrlDecode(m.Groups["key"].Value), UrlDecode(m.Groups["value"].Success ? m.Groups["value"].Value : "")); + + return result; + } + + /// + /// Build the querystring to be used in a URL + /// + /// The generated querystring + /// A collection of name value pairs to be translated into a query string + /// The delimiter to separate key value pairs in the query string + public static string BuildUriQuery(NameValueCollection query, string delimiter) + { + + if (query == null) + throw new ArgumentNullException(nameof(query)); + + StringBuilder builder = new StringBuilder(); + foreach (var key in query.Cast().Where(key => !string.IsNullOrEmpty(query[key]))) + { + builder.Append(builder.Length == 0 ? string.Empty : delimiter) + .Append(key) + .Append("=") + .Append(query[key]); + } + + return builder.ToString(); + } + + /// + /// Build the querystring to be used in a URL + /// + /// The generated querystring + /// A collection of name value pairs to be translated into a query string that is + /// ampsersand delimited. + public static string BuildUriQuery(NameValueCollection query) + { + return BuildUriQuery(query, "&"); + } + + /// + /// Builds a URL together using a base URL, a path and a query. + /// + /// The built together URL. + /// Base URL, containing schema, host, port. + /// Base path. + /// A collection of name value pairs to be translated into a query string. + public static string UriBuilder(string url, string path, NameValueCollection query) + { + var builder = new UriBuilder(url) + { + Path = ConcatPaths(ExtractPath(url), path), + Query = query != null ? BuildUriQuery(query) : null + }; + return builder.Uri.AbsoluteUri; + } + + /// + /// Concats paths of URIs. + /// + /// The concatenated paths. + /// Path1. + /// Path2. + public static string ConcatPaths(string path1, string path2) + { + if (string.IsNullOrEmpty(path2)) + { + return path1; + } + + return path1.TrimEnd('/') + '/' + path2; + } + + /// + /// Grab path part of a URI. + /// At the moment, simple implementation does not remove fragments. + /// + /// The path. + /// URL. + public static string ExtractPath(string url) + { + return (new Uri(url)).Path; + } + + /// + /// Builds a URL together using a base URL and path. + /// + /// The built together URL. + /// Base URL, containing schema, host, port. + /// Base path. + public static string UriBuilder(string url, string path) + { + return UriBuilder(url, path, null); + } } } diff --git a/Duplicati/Library/Utility/UrlUtillity.cs b/Duplicati/Library/Utility/UrlUtility.cs similarity index 97% rename from Duplicati/Library/Utility/UrlUtillity.cs rename to Duplicati/Library/Utility/UrlUtility.cs index 84469e019..8e2c87a36 100644 --- a/Duplicati/Library/Utility/UrlUtillity.cs +++ b/Duplicati/Library/Utility/UrlUtility.cs @@ -23,7 +23,7 @@ using System.Text; namespace Duplicati.Library.Utility { - public static class UrlUtillity + public static class UrlUtility { /// /// The file path to the system browser selected diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index bcc3bb611..9e6147989 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -1,4 +1,3 @@ -#region Disclaimer / License // Copyright (C) 2015, The Duplicati Team // http://www.duplicati.com, info@duplicati.com // @@ -15,21 +14,13 @@ // 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 -// -using System.Text.RegularExpressions; -using System.Linq; -using System.Threading.Tasks; - - -#endregion - using System; -using System.Text.RegularExpressions; -using System.Linq; using System.Collections.Generic; using System.IO; -using System.Text; +using System.Linq; using System.Threading.Tasks; +using System.Text; +using System.Text.RegularExpressions; namespace Duplicati.Library.Utility { @@ -416,8 +407,8 @@ namespace Duplicati.Library.Utility public static bool IsPathBelowFolder(string fileOrFolderPath, string parentFolder) { var sanitizedParentFolder = AppendDirSeparator(parentFolder); - return fileOrFolderPath.StartsWith(sanitizedParentFolder, ClientFilenameStringComparision) && - !fileOrFolderPath.Equals(sanitizedParentFolder, ClientFilenameStringComparision); + return fileOrFolderPath.StartsWith(sanitizedParentFolder, ClientFilenameStringComparison) && + !fileOrFolderPath.Equals(sanitizedParentFolder, ClientFilenameStringComparison); } /// @@ -1066,7 +1057,7 @@ namespace Duplicati.Library.Utility /// /// Gets the string comparision that matches the client filesystems case sensitivity /// - public static StringComparison ClientFilenameStringComparision => IsFSCaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase; + public static StringComparison ClientFilenameStringComparison => IsFSCaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase; /// /// Searches the system paths for the file specified diff --git a/Duplicati/Library/Utility/WorkerThread.cs b/Duplicati/Library/Utility/WorkerThread.cs index cff4219fc..f7e9d3f7e 100644 --- a/Duplicati/Library/Utility/WorkerThread.cs +++ b/Duplicati/Library/Utility/WorkerThread.cs @@ -37,7 +37,7 @@ namespace Duplicati.Library.Utility /// /// The wait event /// - private AutoResetEvent m_event; + private readonly AutoResetEvent m_event; /// /// The internal list of tasks to perform /// @@ -63,7 +63,7 @@ namespace Duplicati.Library.Utility /// /// A callback that performs the actual work on the item /// - private Action m_delegate; + private readonly Action m_delegate; /// /// An event that is raised when the runner state changes diff --git a/Duplicati/License/LicenseReader.cs b/Duplicati/License/LicenseReader.cs index bd2d6ca6f..01ad385bf 100644 --- a/Duplicati/License/LicenseReader.cs +++ b/Duplicati/License/LicenseReader.cs @@ -31,16 +31,16 @@ namespace Duplicati.License /// /// The regular expression used to find url files /// - private static System.Text.RegularExpressions.Regex URL_FILENAME = new System.Text.RegularExpressions.Regex("(homepage.txt)|(download.txt)", System.Text.RegularExpressions.RegexOptions.IgnoreCase); + private static readonly System.Text.RegularExpressions.Regex URL_FILENAME = new System.Text.RegularExpressions.Regex("(homepage.txt)|(download.txt)", System.Text.RegularExpressions.RegexOptions.IgnoreCase); /// /// The regular expression used to find license files /// - private static System.Text.RegularExpressions.Regex LICENSE_FILENAME = new System.Text.RegularExpressions.Regex("license.txt", System.Text.RegularExpressions.RegexOptions.IgnoreCase); + private static readonly System.Text.RegularExpressions.Regex LICENSE_FILENAME = new System.Text.RegularExpressions.Regex("license.txt", System.Text.RegularExpressions.RegexOptions.IgnoreCase); /// /// The regular expression used to find licensedata files /// - private static System.Text.RegularExpressions.Regex LICENSEDATA_FILENAME = new System.Text.RegularExpressions.Regex("licensedata.json", System.Text.RegularExpressions.RegexOptions.IgnoreCase); - + private static readonly System.Text.RegularExpressions.Regex LICENSEDATA_FILENAME = new System.Text.RegularExpressions.Regex("licensedata.json", System.Text.RegularExpressions.RegexOptions.IgnoreCase); + /// /// Reads all license files in the given base folder /// diff --git a/Duplicati/Server/Database/Connection.cs b/Duplicati/Server/Database/Connection.cs index fbefec2e9..f2347f20a 100644 --- a/Duplicati/Server/Database/Connection.cs +++ b/Duplicati/Server/Database/Connection.cs @@ -25,12 +25,12 @@ namespace Duplicati.Server.Database { public class Connection : IDisposable { - private System.Data.IDbConnection m_connection; + private readonly System.Data.IDbConnection m_connection; private System.Data.IDbCommand m_errorcmd; public readonly object m_lock = new object(); public const int ANY_BACKUP_ID = -1; public const int SERVER_SETTINGS_ID = -2; - private Dictionary m_temporaryBackups = new Dictionary(); + private readonly Dictionary m_temporaryBackups = new Dictionary(); public Connection(System.Data.IDbConnection connection) { diff --git a/Duplicati/Server/Database/ServerSettings.cs b/Duplicati/Server/Database/ServerSettings.cs index bafb09355..738430b50 100644 --- a/Duplicati/Server/Database/ServerSettings.cs +++ b/Duplicati/Server/Database/ServerSettings.cs @@ -53,9 +53,9 @@ namespace Duplicati.Server.Database public const string USAGE_REPORTER_LEVEL = "usage-reporter-level"; public const string HAS_ASKED_FOR_PASSWORD_PROTECTION = "has-asked-for-password-protection"; } - - private Dictionary m_values; - private Database.Connection m_connection; + + private readonly Dictionary m_values; + private readonly Database.Connection m_connection; private Library.AutoUpdater.UpdateInfo m_latestUpdate; internal ServerSettings(Connection con) diff --git a/Duplicati/Server/EventPollNotify.cs b/Duplicati/Server/EventPollNotify.cs index 83e853252..015e05af5 100644 --- a/Duplicati/Server/EventPollNotify.cs +++ b/Duplicati/Server/EventPollNotify.cs @@ -21,7 +21,7 @@ namespace Duplicati.Server /// /// The list of subscribed waiting threads /// - private Queue m_waitQueue = new Queue(); + private readonly Queue m_waitQueue = new Queue(); /// /// An eventhandler for subscribing to event updates without blocking diff --git a/Duplicati/Server/LiveControls.cs b/Duplicati/Server/LiveControls.cs index cfb835e3f..f6aa8a1dd 100644 --- a/Duplicati/Server/LiveControls.cs +++ b/Duplicati/Server/LiveControls.cs @@ -158,7 +158,7 @@ namespace Duplicati.Server /// /// The timer that is activated after a pause period. /// - private System.Threading.Timer m_waitTimer; + private readonly System.Threading.Timer m_waitTimer; /// /// The time that the current pause is expected to expire diff --git a/Duplicati/Server/LogWriteHandler.cs b/Duplicati/Server/LogWriteHandler.cs index 44033ab6b..bbd949250 100644 --- a/Duplicati/Server/LogWriteHandler.cs +++ b/Duplicati/Server/LogWriteHandler.cs @@ -226,7 +226,7 @@ namespace Duplicati.Server public int Size { get { return m_buffer.Length; } } } - private DateTime[] m_timeouts; + private readonly DateTime[] m_timeouts; private readonly object m_lock = new object(); private volatile bool m_anytimeouts = false; private RingBuffer m_buffer; diff --git a/Duplicati/Server/Runner.cs b/Duplicati/Server/Runner.cs index ff1625e6e..ef75909ec 100644 --- a/Duplicati/Server/Runner.cs +++ b/Duplicati/Server/Runner.cs @@ -261,7 +261,7 @@ namespace Duplicati.Server #endregion } - private ProgressState m_state; + private readonly ProgressState m_state; private Duplicati.Library.Main.IBackendProgress m_backendProgress; private Duplicati.Library.Main.IOperationProgress m_operationProgress; private readonly object m_lock = new object(); @@ -589,7 +589,7 @@ namespace Duplicati.Server var r = controller.CreateLogDatabase(tf); var tempid = Program.DataConnection.RegisterTempFile("create-bug-report", r.TargetPath, DateTime.Now.AddDays(3)); - if (string.Equals(tf, r.TargetPath, Library.Utility.Utility.ClientFilenameStringComparision)) + if (string.Equals(tf, r.TargetPath, Library.Utility.Utility.ClientFilenameStringComparison)) tf.Protected = true; Program.DataConnection.RegisterNotification( diff --git a/Duplicati/Server/Scheduler.cs b/Duplicati/Server/Scheduler.cs index fb01df458..2254fd89e 100644 --- a/Duplicati/Server/Scheduler.cs +++ b/Duplicati/Server/Scheduler.cs @@ -39,7 +39,7 @@ namespace Duplicati.Server /// /// The thread that runs the scheduler /// - private Thread m_thread; + private readonly Thread m_thread; /// /// A termination flag /// @@ -47,11 +47,11 @@ namespace Duplicati.Server /// /// The worker thread that is invoked to do work /// - private WorkerThread m_worker; + private readonly WorkerThread m_worker; /// /// The wait event /// - private AutoResetEvent m_event; + private readonly AutoResetEvent m_event; /// /// The data syncronization lock /// @@ -70,7 +70,7 @@ namespace Duplicati.Server /// /// List of update tasks, used to set the timestamp on the schedule once completed /// - private Dictionary> m_updateTasks; + private readonly Dictionary> m_updateTasks; /// /// Constructs a new scheduler @@ -307,7 +307,7 @@ namespace Duplicati.Server } Server.Runner.IRunnerData lastJob = jobsToRun.LastOrDefault(); - if (lastJob != null && lastJob != null) + if (lastJob != null) lock(m_lock) m_updateTasks[lastJob] = new Tuple(sc, start, DateTime.UtcNow); diff --git a/Duplicati/Server/SingleInstance.cs b/Duplicati/Server/SingleInstance.cs index 9559c3328..cebf31069 100644 --- a/Duplicati/Server/SingleInstance.cs +++ b/Duplicati/Server/SingleInstance.cs @@ -100,12 +100,12 @@ namespace Duplicati.Server /// /// The folder where control files are placed /// - private string m_controldir; - + private readonly string m_controldir; + /// /// The full path to the locking file /// - private string m_lockfilename; + private readonly string m_lockfilename; /// /// The watcher that allows interprocess communication diff --git a/Duplicati/Server/UpdatePollThread.cs b/Duplicati/Server/UpdatePollThread.cs index f913a60a6..8ae50ada9 100644 --- a/Duplicati/Server/UpdatePollThread.cs +++ b/Duplicati/Server/UpdatePollThread.cs @@ -27,12 +27,12 @@ namespace Duplicati.Server /// public class UpdatePollThread { - private Thread m_thread; + private readonly Thread m_thread; private volatile bool m_terminated = false; private volatile bool m_download = false; private volatile bool m_forceCheck = false; private readonly object m_lock = new object(); - private AutoResetEvent m_waitSignal; + private readonly AutoResetEvent m_waitSignal; private double m_downloadProgress; public bool IsUpdateRequested { get; private set; } = false; diff --git a/Duplicati/Server/WebServer/AuthenticationHandler.cs b/Duplicati/Server/WebServer/AuthenticationHandler.cs index ccc232444..214acbf63 100644 --- a/Duplicati/Server/WebServer/AuthenticationHandler.cs +++ b/Duplicati/Server/WebServer/AuthenticationHandler.cs @@ -41,11 +41,11 @@ namespace Duplicati.Server.WebServer private const int XSRF_TIMEOUT_MINUTES = 10; private const int AUTH_TIMEOUT_MINUTES = 10; - private ConcurrentDictionary m_activeTokens = new ConcurrentDictionary(); - private ConcurrentDictionary> m_activeNonces = new ConcurrentDictionary>(); - private ConcurrentDictionary m_activexsrf = new ConcurrentDictionary(); + private readonly ConcurrentDictionary m_activeTokens = new ConcurrentDictionary(); + private readonly ConcurrentDictionary> m_activeNonces = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary m_activexsrf = new ConcurrentDictionary(); - System.Security.Cryptography.RandomNumberGenerator m_prng = System.Security.Cryptography.RNGCryptoServiceProvider.Create(); + readonly System.Security.Cryptography.RandomNumberGenerator m_prng = System.Security.Cryptography.RNGCryptoServiceProvider.Create(); private string FindXSRFToken(HttpServer.IHttpRequest request) { diff --git a/Duplicati/Server/WebServer/BodyWriter.cs b/Duplicati/Server/WebServer/BodyWriter.cs index 6868a1673..e02deec7d 100644 --- a/Duplicati/Server/WebServer/BodyWriter.cs +++ b/Duplicati/Server/WebServer/BodyWriter.cs @@ -22,8 +22,8 @@ namespace Duplicati.Server.WebServer { public class BodyWriter : System.IO.StreamWriter, IDisposable { - private HttpServer.IHttpResponse m_resp; - private string m_jsonp; + private readonly HttpServer.IHttpResponse m_resp; + private readonly string m_jsonp; private static object SUCCESS_RESPONSE = new { Status = "OK" }; // We override the format provider so all JSON output uses US format diff --git a/Duplicati/Server/WebServer/IndexHtmlHandler.cs b/Duplicati/Server/WebServer/IndexHtmlHandler.cs index 73c7dac14..6d3e4e53b 100644 --- a/Duplicati/Server/WebServer/IndexHtmlHandler.cs +++ b/Duplicati/Server/WebServer/IndexHtmlHandler.cs @@ -25,7 +25,7 @@ namespace Duplicati.Server.WebServer { internal class IndexHtmlHandler : HttpModule { - private string m_webroot; + private readonly string m_webroot; private static readonly string[] ForbiddenChars = new string[] {"\\", "..", ":"}.Union(from n in System.IO.Path.GetInvalidPathChars() select n.ToString()).Distinct().ToArray(); private static readonly string DirSep = System.IO.Path.DirectorySeparatorChar.ToString(); diff --git a/Duplicati/Server/WebServer/RESTMethods/Captcha.cs b/Duplicati/Server/WebServer/RESTMethods/Captcha.cs index bd7ad5d5d..de64085c0 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Captcha.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Captcha.cs @@ -39,7 +39,7 @@ namespace Duplicati.Server.WebServer.RESTMethods } private static readonly object m_lock = new object(); - private static Dictionary m_captchas = new Dictionary(); + private static readonly Dictionary m_captchas = new Dictionary(); public static bool SolvedCaptcha(string token, string target, string answer) { diff --git a/Duplicati/Server/WebServer/RESTMethods/Help.cs b/Duplicati/Server/WebServer/RESTMethods/Help.cs index 616ada9be..e96a03c18 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Help.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Help.cs @@ -14,11 +14,88 @@ // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -using System; using System.Linq; using System.Text; using Newtonsoft.Json; +using System; +using System.Linq; +using System.Text; +using Newtonsoft.Json; namespace Duplicati.Server.WebServer.RESTMethods { public class Help : IRESTMethodGET - { public void GET(string key, RequestInfo info) { var sb = new StringBuilder(); if (string.IsNullOrWhiteSpace(key)) { foreach(var m in RESTHandler.Modules.Keys.OrderBy(x => x)) { var mod = RESTHandler.Modules[m]; if (mod == this) continue; var desc = mod.GetType().Name; if (mod is IRESTMethodDocumented) desc = ((IRESTMethodDocumented)mod).Description; sb.AppendFormat(ITEM_TEMPLATE, RESTHandler.API_URI_PATH, m, mod.GetType().Name, desc); } var data = Encoding.UTF8.GetBytes(string.Format(TEMPLATE, "API Information", "", sb.ToString())); info.Response.ContentType = "text/html"; info.Response.ContentLength = data.Length; info.Response.Body.Write(data, 0, data.Length); info.Response.Send(); } else { IRESTMethod m; RESTHandler.Modules.TryGetValue(key, out m); if (m == null) { info.Response.Status = System.Net.HttpStatusCode.NotFound; info.Response.Reason = "Module not found"; } else { var desc = ""; if (m is IRESTMethodDocumented) { var doc = m as IRESTMethodDocumented; desc = doc.Description; foreach(var t in doc.Types) sb.AppendFormat(METHOD_TEMPLATE, t.Key, JsonConvert.SerializeObject(t.Value)); //TODO: Format the type } var data = Encoding.UTF8.GetBytes(string.Format(TEMPLATE, m.GetType().Name, desc, sb.ToString())); info.Response.ContentType = "text/html"; info.Response.ContentLength = data.Length; info.Response.Body.Write(data, 0, data.Length); info.Response.Send(); } } } private const string TEMPLATE = @" {0}

{0}

{1}

    {2}
"; private const string ITEM_TEMPLATE = @"
  • {2}: {3}
  • "; private const string METHOD_TEMPLATE = @" {0}:
    {1}
    "; } + { + public void GET(string key, RequestInfo info) + { + var sb = new StringBuilder(); + if (string.IsNullOrWhiteSpace(key)) + { + foreach(var m in RESTHandler.Modules.Keys.OrderBy(x => x)) + { + var mod = RESTHandler.Modules[m]; + if (mod == this) + continue; + + var desc = mod.GetType().Name; + if (mod is IRESTMethodDocumented) + desc = ((IRESTMethodDocumented)mod).Description; + sb.AppendFormat(ITEM_TEMPLATE, RESTHandler.API_URI_PATH, m, mod.GetType().Name, desc); + } + + + var data = Encoding.UTF8.GetBytes(string.Format(TEMPLATE, "API Information", "", sb.ToString())); + + info.Response.ContentType = "text/html"; + info.Response.ContentLength = data.Length; + info.Response.Body.Write(data, 0, data.Length); + info.Response.Send(); + } + else + { + IRESTMethod m; + RESTHandler.Modules.TryGetValue(key, out m); + if (m == null) + { + info.Response.Status = System.Net.HttpStatusCode.NotFound; + info.Response.Reason = "Module not found"; + } + else + { + var desc = ""; + if (m is IRESTMethodDocumented) + { + var doc = m as IRESTMethodDocumented; + desc = doc.Description; + foreach(var t in doc.Types) + sb.AppendFormat(METHOD_TEMPLATE, t.Key, JsonConvert.SerializeObject(t.Value)); //TODO: Format the type + } + + var data = Encoding.UTF8.GetBytes(string.Format(TEMPLATE, m.GetType().Name, desc, sb.ToString())); + + info.Response.ContentType = "text/html"; + info.Response.ContentLength = data.Length; + info.Response.Body.Write(data, 0, data.Length); + info.Response.Send(); + + } + } + } + + private const string TEMPLATE = @" +{0} + +

    {0}

    +

    {1}

    +
      +{2} +
    + +"; + private const string ITEM_TEMPLATE = @" +
  • {2}: {3}
  • +"; + + private const string METHOD_TEMPLATE = @" +{0}:
    {1}
    +"; + } } diff --git a/Duplicati/Server/WebServer/RESTMethods/IRESTMethod.cs b/Duplicati/Server/WebServer/RESTMethods/IRESTMethod.cs index ae2647b86..024da32b0 100644 --- a/Duplicati/Server/WebServer/RESTMethods/IRESTMethod.cs +++ b/Duplicati/Server/WebServer/RESTMethods/IRESTMethod.cs @@ -14,11 +14,43 @@ // 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; +using System.Collections.Generic; namespace Duplicati.Server.WebServer.RESTMethods { public interface IRESTMethod - { } public interface IRESTMethodDocumented { string Description { get; } IEnumerable> Types { get; } } public interface IRESTMethodGET : IRESTMethod { void GET(string key, RequestInfo info); } - public interface IRESTMethodPUT : IRESTMethod { void PUT(string key, RequestInfo info); } public interface IRESTMethodPOST : IRESTMethod { void POST(string key, RequestInfo info); } public interface IRESTMethodDELETE : IRESTMethod { void DELETE(string key, RequestInfo info); } public interface IRESTMethodPATCH : IRESTMethod { void PATCH(string key, RequestInfo info); } } + { + } + + public interface IRESTMethodDocumented + { + string Description { get; } + IEnumerable> Types { get; } + } + + public interface IRESTMethodGET : IRESTMethod + { + void GET(string key, RequestInfo info); + } + public interface IRESTMethodPUT : IRESTMethod + { + void PUT(string key, RequestInfo info); + } + + public interface IRESTMethodPOST : IRESTMethod + { + void POST(string key, RequestInfo info); + } + + public interface IRESTMethodDELETE : IRESTMethod + { + void DELETE(string key, RequestInfo info); + } + + public interface IRESTMethodPATCH : IRESTMethod + { + void PATCH(string key, RequestInfo info); + } +} diff --git a/Duplicati/Server/WebServer/RESTMethods/Notification.cs b/Duplicati/Server/WebServer/RESTMethods/Notification.cs index e5043aa8e..7abcca55e 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Notification.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Notification.cs @@ -14,12 +14,47 @@ // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -using System; using System.Linq; +using System; +using System.Linq; namespace Duplicati.Server.WebServer.RESTMethods { public class Notification : IRESTMethodGET, IRESTMethodDELETE { - public void GET(string key, RequestInfo info) { long id; if (!long.TryParse(key, out id)) { info.ReportClientError("Invalid ID", System.Net.HttpStatusCode.BadRequest); return; } var el = Program.DataConnection.GetNotifications().Where(x => x.ID == id).FirstOrDefault(); if (el == null) info.ReportClientError("No such notification", System.Net.HttpStatusCode.NotFound); else info.OutputOK(el); } public void DELETE(string key, RequestInfo info) { long id; if (!long.TryParse(key, out id)) { info.ReportClientError("Invalid ID", System.Net.HttpStatusCode.BadRequest); return; } var el = Program.DataConnection.GetNotifications().Where(x => x.ID == id).FirstOrDefault(); if (el == null) info.ReportClientError("No such notification", System.Net.HttpStatusCode.NotFound); else { Program.DataConnection.DismissNotification(id); info.OutputOK(); } } } + public void GET(string key, RequestInfo info) + { + long id; + if (!long.TryParse(key, out id)) + { + info.ReportClientError("Invalid ID", System.Net.HttpStatusCode.BadRequest); + return; + } + + var el = Program.DataConnection.GetNotifications().Where(x => x.ID == id).FirstOrDefault(); + if (el == null) + info.ReportClientError("No such notification", System.Net.HttpStatusCode.NotFound); + else + info.OutputOK(el); + } + + public void DELETE(string key, RequestInfo info) + { + long id; + if (!long.TryParse(key, out id)) + { + info.ReportClientError("Invalid ID", System.Net.HttpStatusCode.BadRequest); + return; + } + + var el = Program.DataConnection.GetNotifications().Where(x => x.ID == id).FirstOrDefault(); + if (el == null) + info.ReportClientError("No such notification", System.Net.HttpStatusCode.NotFound); + else + { + Program.DataConnection.DismissNotification(id); + info.OutputOK(); + } + } + } } diff --git a/Duplicati/Server/WebServer/RESTMethods/Notifications.cs b/Duplicati/Server/WebServer/RESTMethods/Notifications.cs index 0a934d64c..cbc76032d 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Notifications.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Notifications.cs @@ -19,7 +19,11 @@ using System; namespace Duplicati.Server.WebServer.RESTMethods { public class Notifications : IRESTMethodGET - { public void GET(string key, RequestInfo info) { info.OutputOK(Program.DataConnection.GetNotifications()); } + { + public void GET(string key, RequestInfo info) + { + info.OutputOK(Program.DataConnection.GetNotifications()); + } } } diff --git a/Duplicati/Server/WebServer/RESTMethods/ProgressState.cs b/Duplicati/Server/WebServer/RESTMethods/ProgressState.cs index ebe06090b..4a757df2e 100644 --- a/Duplicati/Server/WebServer/RESTMethods/ProgressState.cs +++ b/Duplicati/Server/WebServer/RESTMethods/ProgressState.cs @@ -14,12 +14,32 @@ // 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; +using System.Collections.Generic; namespace Duplicati.Server.WebServer.RESTMethods { public class ProgressState : IRESTMethodGET, IRESTMethodDocumented { - public void GET(string key, RequestInfo info) { if (Program.GenerateProgressState == null) info.ReportClientError("No active backup", System.Net.HttpStatusCode.NotFound); else info.OutputOK(Program.GenerateProgressState()); } public string Description { get { return "Return the progress of the currently running operation."; } } public IEnumerable> Types { get { return new KeyValuePair[] { new KeyValuePair(HttpServer.Method.Get, typeof(Serialization.Interface.IProgressEventData)) }; } } } + public void GET(string key, RequestInfo info) + { + if (Program.GenerateProgressState == null) + info.ReportClientError("No active backup", System.Net.HttpStatusCode.NotFound); + else + info.OutputOK(Program.GenerateProgressState()); + } + + public string Description { get { return "Return the progress of the currently running operation."; } } + + public IEnumerable> Types + { + get + { + return new KeyValuePair[] { + new KeyValuePair(HttpServer.Method.Get, typeof(Serialization.Interface.IProgressEventData)) + }; + } + } + } } diff --git a/Duplicati/Server/WebServer/RESTMethods/RequestInfo.cs b/Duplicati/Server/WebServer/RESTMethods/RequestInfo.cs index 50d10a24c..13820d0ed 100644 --- a/Duplicati/Server/WebServer/RESTMethods/RequestInfo.cs +++ b/Duplicati/Server/WebServer/RESTMethods/RequestInfo.cs @@ -19,9 +19,91 @@ using System; namespace Duplicati.Server.WebServer.RESTMethods { public class RequestInfo : IDisposable - { public HttpServer.IHttpRequest Request { get; private set; } public HttpServer.IHttpResponse Response { get; private set; } public HttpServer.Sessions.IHttpSession Session { get; private set; } public BodyWriter BodyWriter { get; private set; } + { + public HttpServer.IHttpRequest Request { get; private set; } + public HttpServer.IHttpResponse Response { get; private set; } + public HttpServer.Sessions.IHttpSession Session { get; private set; } + public BodyWriter BodyWriter { get; private set; } public RequestInfo(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session) - { Request = request; Response = response; Session = session; BodyWriter = new BodyWriter(response, request); - } public void ReportServerError(string message, System.Net.HttpStatusCode code = System.Net.HttpStatusCode.InternalServerError) { Response.Status = code; Response.Reason = message; BodyWriter.WriteJsonObject(new { Error = message }); } public void ReportClientError(string message, System.Net.HttpStatusCode code = System.Net.HttpStatusCode.BadRequest) { ReportServerError(message, code); } public bool LongPollCheck(EventPollNotify poller, ref long id, out bool isError) { HttpServer.HttpInput input = Request.Method.ToUpper() == "POST" ? Request.Form : Request.QueryString; if (Library.Utility.Utility.ParseBool(input["longpoll"].Value, false)) { long lastEventId; if (!long.TryParse(input["lasteventid"].Value, out lastEventId)) { ReportClientError("When activating long poll, the request must include the last event id", System.Net.HttpStatusCode.BadRequest); isError = true; return false; } TimeSpan ts; try { ts = Library.Utility.Timeparser.ParseTimeSpan(input["duration"].Value); } catch (Exception ex) { ReportClientError("Invalid duration: " + ex.Message, System.Net.HttpStatusCode.BadRequest); isError = true; return false; } if (ts <= TimeSpan.FromSeconds(10) || ts.TotalMilliseconds > int.MaxValue) { ReportClientError("Invalid duration, must be at least 10 seconds, and less than " + int.MaxValue + " milliseconds", System.Net.HttpStatusCode.BadRequest); isError = true; return false; } isError = false; id = poller.Wait(lastEventId, (int)ts.TotalMilliseconds); return true; } isError = false; return false; } public void OutputOK(object item = null) { BodyWriter.OutputOK(item); } public void OutputError(object item = null, System.Net.HttpStatusCode code = System.Net.HttpStatusCode.InternalServerError, string reason = null) { Response.Status = code; Response.Reason = reason ?? "Error"; BodyWriter.WriteJsonObject(item); } public void Dispose() { if (BodyWriter != null) { var bw = BodyWriter; BodyWriter = null; bw.Dispose(); } } } + { + Request = request; + Response = response; + Session = session; + BodyWriter = new BodyWriter(response, request); + } + + public void ReportServerError(string message, System.Net.HttpStatusCode code = System.Net.HttpStatusCode.InternalServerError) + { + Response.Status = code; + Response.Reason = message; + + BodyWriter.WriteJsonObject(new { Error = message }); + } + + public void ReportClientError(string message, System.Net.HttpStatusCode code = System.Net.HttpStatusCode.BadRequest) + { + ReportServerError(message, code); + } + + public bool LongPollCheck(EventPollNotify poller, ref long id, out bool isError) + { + HttpServer.HttpInput input = Request.Method.ToUpper() == "POST" ? Request.Form : Request.QueryString; + if (Library.Utility.Utility.ParseBool(input["longpoll"].Value, false)) + { + long lastEventId; + if (!long.TryParse(input["lasteventid"].Value, out lastEventId)) + { + ReportClientError("When activating long poll, the request must include the last event id", System.Net.HttpStatusCode.BadRequest); + isError = true; + return false; + } + + TimeSpan ts; + try { ts = Library.Utility.Timeparser.ParseTimeSpan(input["duration"].Value); } + catch (Exception ex) + { + ReportClientError("Invalid duration: " + ex.Message, System.Net.HttpStatusCode.BadRequest); + isError = true; + return false; + } + + if (ts <= TimeSpan.FromSeconds(10) || ts.TotalMilliseconds > int.MaxValue) + { + ReportClientError("Invalid duration, must be at least 10 seconds, and less than " + int.MaxValue + " milliseconds", System.Net.HttpStatusCode.BadRequest); + isError = true; + return false; + } + + isError = false; + id = poller.Wait(lastEventId, (int)ts.TotalMilliseconds); + return true; + } + + isError = false; + return false; + } + + public void OutputOK(object item = null) + { + BodyWriter.OutputOK(item); + } + + public void OutputError(object item = null, System.Net.HttpStatusCode code = System.Net.HttpStatusCode.InternalServerError, string reason = null) + { + Response.Status = code; + Response.Reason = reason ?? "Error"; + BodyWriter.WriteJsonObject(item); + } + + public void Dispose() + { + if (BodyWriter != null) + { + var bw = BodyWriter; + BodyWriter = null; + bw.Dispose(); + } + } + } } diff --git a/Duplicati/Server/WebServer/RESTMethods/SystemWideSettings.cs b/Duplicati/Server/WebServer/RESTMethods/SystemWideSettings.cs index 27d813110..853192ce3 100644 --- a/Duplicati/Server/WebServer/RESTMethods/SystemWideSettings.cs +++ b/Duplicati/Server/WebServer/RESTMethods/SystemWideSettings.cs @@ -14,12 +14,31 @@ // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -using System; using System.Collections.Generic; using Duplicati.Library.Interface; +using System; +using System.Collections.Generic; +using Duplicati.Library.Interface; namespace Duplicati.Server.WebServer.RESTMethods { public class SystemWideSettings : IRESTMethodGET, IRESTMethodDocumented { - public void GET(string key, RequestInfo info) { info.OutputOK(new Duplicati.Library.Main.Options(new Dictionary()).SupportedCommands); } public string Description { get { return "Return a list of settings that can be applied to all backups on a system-wide basis"; } } public IEnumerable> Types { get { return new KeyValuePair[] { new KeyValuePair(HttpServer.Method.Get, typeof(ICommandLineArgument[])) }; } } } + public void GET(string key, RequestInfo info) + { + info.OutputOK(new Duplicati.Library.Main.Options(new Dictionary()).SupportedCommands); + } + + public string Description { get { return "Return a list of settings that can be applied to all backups on a system-wide basis"; } } + + public IEnumerable> Types + { + get + { + return new KeyValuePair[] { + new KeyValuePair(HttpServer.Method.Get, typeof(ICommandLineArgument[])) + }; + } + } + + } } diff --git a/Duplicati/Server/WebServer/RESTMethods/Tags.cs b/Duplicati/Server/WebServer/RESTMethods/Tags.cs index 856a9de45..6bf19054a 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Tags.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Tags.cs @@ -14,11 +14,40 @@ // 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; +using System; +using System.Collections.Generic; +using System.Linq; namespace Duplicati.Server.WebServer.RESTMethods { public class Tags : IRESTMethodGET, IRESTMethodDocumented - { public void GET(string key, RequestInfo info) { var r = from n in Serializable.ServerSettings.CompressionModules .Union(Serializable.ServerSettings.EncryptionModules) .Union(Serializable.ServerSettings.BackendModules) .Union(Serializable.ServerSettings.GenericModules) select n.Key.ToLower(); // Append all known tags r = r.Union(from n in Program.DataConnection.Backups select n.Tags into p from x in p select x.ToLower()); info.OutputOK(r); } public string Description { get { return "Gets the list of tags"; } } public IEnumerable> Types { get { return new KeyValuePair[] { new KeyValuePair(HttpServer.Method.Get, typeof(string[])), }; } } } + { + public void GET(string key, RequestInfo info) + { + var r = + from n in + Serializable.ServerSettings.CompressionModules + .Union(Serializable.ServerSettings.EncryptionModules) + .Union(Serializable.ServerSettings.BackendModules) + .Union(Serializable.ServerSettings.GenericModules) + select n.Key.ToLower(); + + // Append all known tags + r = r.Union(from n in Program.DataConnection.Backups select n.Tags into p from x in p select x.ToLower()); + info.OutputOK(r); + } + + public string Description { get { return "Gets the list of tags"; } } + + public IEnumerable> Types + { + get + { + return new KeyValuePair[] { + new KeyValuePair(HttpServer.Method.Get, typeof(string[])), + }; + } + } + } } diff --git a/Duplicati/Server/WebServer/RESTMethods/Tasks.cs b/Duplicati/Server/WebServer/RESTMethods/Tasks.cs index 90e5f6d91..0309a794a 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Tasks.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Tasks.cs @@ -14,11 +14,23 @@ // 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; +using System.Collections.Generic; namespace Duplicati.Server.WebServer.RESTMethods { public class Tasks : IRESTMethodGET - { public void GET(string key, RequestInfo info) { var cur = Program.WorkThread.CurrentTask; var n = Program.WorkThread.CurrentTasks; if (cur != null) n.Insert(0, cur); info.OutputOK(n); } } + { + public void GET(string key, RequestInfo info) + { + var cur = Program.WorkThread.CurrentTask; + var n = Program.WorkThread.CurrentTasks; + + if (cur != null) + n.Insert(0, cur); + + info.OutputOK(n); + } + } } diff --git a/Duplicati/Server/WebServer/RESTMethods/UISettings.cs b/Duplicati/Server/WebServer/RESTMethods/UISettings.cs index e760a8b17..2f498a6b9 100644 --- a/Duplicati/Server/WebServer/RESTMethods/UISettings.cs +++ b/Duplicati/Server/WebServer/RESTMethods/UISettings.cs @@ -14,49 +14,64 @@ // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -using System; using System.Collections.Generic; using Duplicati.Server.Serialization; using System.IO; +using System; +using System.Collections.Generic; +using Duplicati.Server.Serialization; +using System.IO; namespace Duplicati.Server.WebServer.RESTMethods { public class UISettings : IRESTMethodGET, IRESTMethodPOST, IRESTMethodPATCH - { public void GET(string key, RequestInfo info) { if (string.IsNullOrWhiteSpace(key)) { info.OutputOK(Program.DataConnection.GetUISettingsSchemes()); } else { info.OutputOK(Program.DataConnection.GetUISettings(key)); } } - - public void POST(string key, RequestInfo info) - { - PATCH(key, info); - } - - public void PATCH(string key, RequestInfo info) - { - if (string.IsNullOrWhiteSpace(key)) - { - info.ReportClientError("Scheme is missing", System.Net.HttpStatusCode.BadRequest); - return; - } - - IDictionary data; - try - { - data = Serializer.Deserialize>(new StreamReader(info.Request.Body)); - } - catch (Exception ex) - { - info.ReportClientError(string.Format("Unable to parse settings object: {0}", ex.Message), System.Net.HttpStatusCode.BadRequest); - return; - } - - if (data == null) - { - info.ReportClientError(string.Format("Unable to parse settings object"), System.Net.HttpStatusCode.BadRequest); - return; - } - - if (info.Request.Method == "POST") - Program.DataConnection.SetUISettings(key, data); + { + public void GET(string key, RequestInfo info) + { + if (string.IsNullOrWhiteSpace(key)) + { + info.OutputOK(Program.DataConnection.GetUISettingsSchemes()); + } else - Program.DataConnection.UpdateUISettings(key, data); - info.OutputOK(); - } - } + { + info.OutputOK(Program.DataConnection.GetUISettings(key)); + } + } + + public void POST(string key, RequestInfo info) + { + PATCH(key, info); + } + + public void PATCH(string key, RequestInfo info) + { + if (string.IsNullOrWhiteSpace(key)) + { + info.ReportClientError("Scheme is missing", System.Net.HttpStatusCode.BadRequest); + return; + } + + IDictionary data; + try + { + data = Serializer.Deserialize>(new StreamReader(info.Request.Body)); + } + catch (Exception ex) + { + info.ReportClientError(string.Format("Unable to parse settings object: {0}", ex.Message), System.Net.HttpStatusCode.BadRequest); + return; + } + + if (data == null) + { + info.ReportClientError(string.Format("Unable to parse settings object"), System.Net.HttpStatusCode.BadRequest); + return; + } + + if (info.Request.Method == "POST") + Program.DataConnection.SetUISettings(key, data); + else + Program.DataConnection.UpdateUISettings(key, data); + info.OutputOK(); + } + + } } diff --git a/Duplicati/Server/WebServer/RESTMethods/Updates.cs b/Duplicati/Server/WebServer/RESTMethods/Updates.cs index e882ad211..8e72449a2 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Updates.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Updates.cs @@ -19,7 +19,38 @@ using System; namespace Duplicati.Server.WebServer.RESTMethods { public class Updates : IRESTMethodPOST - { public void POST(string key, RequestInfo info) { switch ((key ?? "").ToLowerInvariant()) { case "check": Program.UpdatePoller.CheckNow(); info.OutputOK(); return; case "install": Program.UpdatePoller.InstallUpdate(); info.OutputOK(); return; case "activate": if (Program.WorkThread.CurrentTask != null || Program.WorkThread.CurrentTasks.Count != 0) { info.ReportServerError("Cannot activate update while task is running or scheduled"); } else { Program.UpdatePoller.ActivateUpdate(); info.OutputOK(); } return; default: info.ReportClientError("No such action", System.Net.HttpStatusCode.NotFound); return; } } + { + public void POST(string key, RequestInfo info) + { + switch ((key ?? "").ToLowerInvariant()) + { + case "check": + Program.UpdatePoller.CheckNow(); + info.OutputOK(); + return; + + case "install": + Program.UpdatePoller.InstallUpdate(); + info.OutputOK(); + return; + + case "activate": + if (Program.WorkThread.CurrentTask != null || Program.WorkThread.CurrentTasks.Count != 0) + { + info.ReportServerError("Cannot activate update while task is running or scheduled"); + } + else + { + Program.UpdatePoller.ActivateUpdate(); + info.OutputOK(); + } + return; + + default: + info.ReportClientError("No such action", System.Net.HttpStatusCode.NotFound); + return; + } + } } } diff --git a/Duplicati/Server/WebServer/RESTMethods/WebModules.cs b/Duplicati/Server/WebServer/RESTMethods/WebModules.cs index e65b98335..6205bd8b2 100644 --- a/Duplicati/Server/WebServer/RESTMethods/WebModules.cs +++ b/Duplicati/Server/WebServer/RESTMethods/WebModules.cs @@ -14,14 +14,16 @@ // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -using System; using System.Linq; +using System; +using System.Linq; namespace Duplicati.Server.WebServer.RESTMethods { public class WebModules : IRESTMethodGET { public void GET(string key, RequestInfo info) - { info.OutputOK(Duplicati.Library.DynamicLoader.WebLoader.Modules); + { + info.OutputOK(Duplicati.Library.DynamicLoader.WebLoader.Modules); } } } diff --git a/Duplicati/Server/WebServer/Server.cs b/Duplicati/Server/WebServer/Server.cs index 5351482b9..285f70e52 100644 --- a/Duplicati/Server/WebServer/Server.cs +++ b/Duplicati/Server/WebServer/Server.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using HttpServer.HttpModules; -using System.IO; -using Duplicati.Server.Serialization; using System.Security.Cryptography.X509Certificates; namespace Duplicati.Server.WebServer @@ -59,7 +56,7 @@ namespace Duplicati.Server.WebServer /// /// The single webserver instance /// - private HttpServer.HttpServer m_server; + private readonly HttpServer.HttpServer m_server; /// /// The webserver listening port @@ -249,9 +246,9 @@ namespace Duplicati.Server.WebServer if (!string.IsNullOrWhiteSpace(userroot) && ( - userroot.StartsWith(Library.Utility.Utility.AppendDirSeparator(System.Reflection.Assembly.GetExecutingAssembly().Location), Library.Utility.Utility.ClientFilenameStringComparision) + userroot.StartsWith(Library.Utility.Utility.AppendDirSeparator(System.Reflection.Assembly.GetExecutingAssembly().Location), Library.Utility.Utility.ClientFilenameStringComparison) || - userroot.StartsWith(Library.Utility.Utility.AppendDirSeparator(Program.StartupPath), Library.Utility.Utility.ClientFilenameStringComparision) + userroot.StartsWith(Library.Utility.Utility.AppendDirSeparator(Program.StartupPath), Library.Utility.Utility.ClientFilenameStringComparison) ) ) #endif diff --git a/Duplicati/Service/Runner.cs b/Duplicati/Service/Runner.cs index c45bb458b..556f56103 100644 --- a/Duplicati/Service/Runner.cs +++ b/Duplicati/Service/Runner.cs @@ -22,13 +22,13 @@ namespace Duplicati.Service { public class Runner : IDisposable { - private System.Threading.Thread m_thread; + private readonly System.Threading.Thread m_thread; private volatile bool m_terminate = false; private volatile bool m_softstop = false; private System.Diagnostics.Process m_process; - private Action m_onStartedAction; - private Action m_onStoppedAction; - private Action m_reportMessage; + private readonly Action m_onStartedAction; + private readonly Action m_onStoppedAction; + private readonly Action m_reportMessage; private readonly object m_writelock = new object(); private readonly string[] m_cmdargs; diff --git a/Duplicati/UnitTest/BasicSetupHelper.cs b/Duplicati/UnitTest/BasicSetupHelper.cs index 4dad6bc42..93170ce3d 100644 --- a/Duplicati/UnitTest/BasicSetupHelper.cs +++ b/Duplicati/UnitTest/BasicSetupHelper.cs @@ -18,7 +18,6 @@ using System; using NUnit.Framework; using System.IO; using System.Collections.Generic; -using System.Linq; namespace Duplicati.UnitTest { @@ -29,7 +28,7 @@ namespace Duplicati.UnitTest /// protected static readonly string BASEFOLDER = string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("UNITTEST_BASEFOLDER")) - ? Path.Combine(Library.Utility.Utility.HOME_PATH, "testdata") + ? Path.Combine(Library.Utility.Utility.HOME_PATH, "duplicati_testdata") : Environment.GetEnvironmentVariable("UNITTEST_BASEFOLDER"); /// diff --git a/Duplicati/UnitTest/CommandLineOperationsTests.cs b/Duplicati/UnitTest/CommandLineOperationsTests.cs index a3e0f3cb8..8b5783734 100644 --- a/Duplicati/UnitTest/CommandLineOperationsTests.cs +++ b/Duplicati/UnitTest/CommandLineOperationsTests.cs @@ -19,7 +19,6 @@ using NUnit.Framework; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; namespace Duplicati.UnitTest { diff --git a/Duplicati/UnitTest/Duplicati.UnitTest.csproj b/Duplicati/UnitTest/Duplicati.UnitTest.csproj index da8f73258..fabf1506f 100644 --- a/Duplicati/UnitTest/Duplicati.UnitTest.csproj +++ b/Duplicati/UnitTest/Duplicati.UnitTest.csproj @@ -50,6 +50,7 @@ + @@ -173,4 +174,4 @@ - \ No newline at end of file + diff --git a/Duplicati/UnitTest/GeneralBlackBoxTesting.cs b/Duplicati/UnitTest/GeneralBlackBoxTesting.cs index 043c133cb..eeb3c2a87 100644 --- a/Duplicati/UnitTest/GeneralBlackBoxTesting.cs +++ b/Duplicati/UnitTest/GeneralBlackBoxTesting.cs @@ -28,7 +28,7 @@ namespace Duplicati.UnitTest private static readonly string SOURCE_FOLDERS = Path.Combine( string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("UNITTEST_BASEFOLDER")) - ? Path.Combine(Library.Utility.Utility.HOME_PATH, "testdata") + ? Path.Combine(Library.Utility.Utility.HOME_PATH, "duplicati_testdata") : Environment.GetEnvironmentVariable("UNITTEST_BASEFOLDER") , "DSMCBE"); diff --git a/Duplicati/UnitTest/RandomErrorBackend.cs b/Duplicati/UnitTest/RandomErrorBackend.cs index d714f7920..ad9a05e88 100644 --- a/Duplicati/UnitTest/RandomErrorBackend.cs +++ b/Duplicati/UnitTest/RandomErrorBackend.cs @@ -14,22 +14,133 @@ // 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 Duplicati.Library.Interface; using System.Collections.Generic; using System.IO; +using System; +using Duplicati.Library.Interface; +using System.Collections.Generic; +using System.IO; namespace Duplicati.UnitTest { public class RandomErrorBackend : IBackend, IStreamingBackend - { static RandomErrorBackend() { WrappedBackend = "file"; } private static Random random = new Random(42); public static string WrappedBackend { get; set; } private IStreamingBackend m_backend; + { + static RandomErrorBackend() { WrappedBackend = "file"; } + + private static Random random = new Random(42); + + public static string WrappedBackend { get; set; } + + private IStreamingBackend m_backend; public RandomErrorBackend() { - } public RandomErrorBackend(string url, Dictionary options) { var u = new Library.Utility.Uri(url).SetScheme(WrappedBackend).ToString(); m_backend = (IStreamingBackend)Library.DynamicLoader.BackendLoader.GetBackend(u, options); } private void ThrowErrorRandom() { if (random.NextDouble() > 0.90) throw new Exception("Random upload failure"); } - #region IStreamingBackend implementation public void Put(string remotename, Stream stream) { var uploadError = random.NextDouble() > 0.9; using(var f = new Library.Utility.ProgressReportingStream(stream, stream.Length, x => { if (uploadError && stream.Position > stream.Length / 2) throw new Exception("Random upload failure"); })) m_backend.Put(remotename, f); ThrowErrorRandom(); } public void Get(string remotename, Stream stream) { ThrowErrorRandom(); m_backend.Get(remotename, stream); ThrowErrorRandom(); } #endregion #region IBackend implementation public IEnumerable List() { return m_backend.List(); } public void Put(string remotename, string filename) { ThrowErrorRandom(); m_backend.Put(remotename, filename); ThrowErrorRandom(); } public void Get(string remotename, string filename) { ThrowErrorRandom(); m_backend.Get(remotename, filename); ThrowErrorRandom(); } public void Delete(string remotename) { ThrowErrorRandom(); m_backend.Delete(remotename); ThrowErrorRandom(); } public void Test() { m_backend.Test(); } public void CreateFolder() { m_backend.CreateFolder(); } + } + + public RandomErrorBackend(string url, Dictionary options) + { + var u = new Library.Utility.Uri(url).SetScheme(WrappedBackend).ToString(); + m_backend = (IStreamingBackend)Library.DynamicLoader.BackendLoader.GetBackend(u, options); + } + + private void ThrowErrorRandom() + { + if (random.NextDouble() > 0.90) + throw new Exception("Random upload failure"); + } + #region IStreamingBackend implementation + public void Put(string remotename, Stream stream) + { + var uploadError = random.NextDouble() > 0.9; + + using(var f = new Library.Utility.ProgressReportingStream(stream, stream.Length, x => { if (uploadError && stream.Position > stream.Length / 2) throw new Exception("Random upload failure"); })) + m_backend.Put(remotename, f); + ThrowErrorRandom(); + } + public void Get(string remotename, Stream stream) + { + ThrowErrorRandom(); + m_backend.Get(remotename, stream); + ThrowErrorRandom(); + } + #endregion + + #region IBackend implementation + public IEnumerable List() + { + return m_backend.List(); + } + public void Put(string remotename, string filename) + { + ThrowErrorRandom(); + m_backend.Put(remotename, filename); + ThrowErrorRandom(); + } + public void Get(string remotename, string filename) + { + ThrowErrorRandom(); + m_backend.Get(remotename, filename); + ThrowErrorRandom(); + } + public void Delete(string remotename) + { + ThrowErrorRandom(); + m_backend.Delete(remotename); + ThrowErrorRandom(); + } + public void Test() + { + m_backend.Test(); + } + public void CreateFolder() + { + m_backend.CreateFolder(); + } public string[] DNSName { get { return m_backend.DNSName; } - } public string DisplayName { get { return "Random Error Backend"; } } public string ProtocolKey { get { return "randomerror"; } } public IList SupportedCommands { get { if (m_backend == null) try { return Duplicati.Library.DynamicLoader.BackendLoader.GetSupportedCommands(WrappedBackend + "://"); } catch { } return m_backend.SupportedCommands; } } public string Description { get { return "A testing backend that randomly fails"; } } #endregion #region IDisposable implementation public void Dispose() { if (m_backend != null) try { m_backend.Dispose(); } finally { m_backend = null; } } #endregion } + } + public string DisplayName + { + get + { + return "Random Error Backend"; + } + } + public string ProtocolKey + { + get + { + return "randomerror"; + } + } + public IList SupportedCommands + { + get + { + if (m_backend == null) + try { return Duplicati.Library.DynamicLoader.BackendLoader.GetSupportedCommands(WrappedBackend + "://"); } + catch { } + + return m_backend.SupportedCommands; + } + } + public string Description + { + get + { + return "A testing backend that randomly fails"; + } + } + #endregion + #region IDisposable implementation + public void Dispose() + { + if (m_backend != null) + try { m_backend.Dispose(); } + finally { m_backend = null; } + } + #endregion + } } diff --git a/Duplicati/UnitTest/SVNCheckoutsTest.cs b/Duplicati/UnitTest/SVNCheckoutsTest.cs index 1dfad2608..c4de10f9d 100644 --- a/Duplicati/UnitTest/SVNCheckoutsTest.cs +++ b/Duplicati/UnitTest/SVNCheckoutsTest.cs @@ -302,7 +302,7 @@ namespace Duplicati.UnitTest { string f = testfiles[j]; - if (!f.StartsWith(usingFHWithRestore ? fhtempsource : folders[i], Utility.ClientFilenameStringComparision)) + if (!f.StartsWith(usingFHWithRestore ? fhtempsource : folders[i], Utility.ClientFilenameStringComparison)) throw new Exception(string.Format("Unexpected file found: {0}, path is not a subfolder for {1}", f, folders[i])); f = f.Substring(Utility.AppendDirSeparator(usingFHWithRestore ? fhtempsource : folders[i]).Length); diff --git a/Duplicati/UnitTest/TestUtils.cs b/Duplicati/UnitTest/TestUtils.cs index 273147882..ae0fd1eac 100644 --- a/Duplicati/UnitTest/TestUtils.cs +++ b/Duplicati/UnitTest/TestUtils.cs @@ -133,7 +133,7 @@ namespace Duplicati.UnitTest /// The string to find private static int IndexOf(List lst, string m) { - StringComparison sc = Duplicati.Library.Utility.Utility.ClientFilenameStringComparision; + StringComparison sc = Duplicati.Library.Utility.Utility.ClientFilenameStringComparison; for(int i = 0; i < lst.Count; i++) if (lst[i].Equals(m, sc)) return i; diff --git a/Duplicati/UnitTest/UriUtilityTests.cs b/Duplicati/UnitTest/UriUtilityTests.cs new file mode 100644 index 000000000..84d43c8a3 --- /dev/null +++ b/Duplicati/UnitTest/UriUtilityTests.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2018, The Duplicati Team +// http://www.duplicati.com, info@duplicati.com +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +using NUnit.Framework; +using System.Collections.Specialized; + +namespace Duplicati.UnitTest +{ + public class UriUtilityTests + { + [Test] + [Category("UriUtility")] + public static void TestBuildUriQuery() + { + var query = new NameValueCollection { { "a", "b" } }; + var queryUrl = Library.Utility.Uri.BuildUriQuery(query); + Assert.AreEqual("a=b", queryUrl); + query.Add(new NameValueCollection { { "c", "d" } }); + queryUrl = Library.Utility.Uri.BuildUriQuery(query); + Assert.AreEqual("a=b&c=d", queryUrl); + } + + [Test] + [Category("UriUtility")] + public static void TestUrlBuilder() + { + var baseUrl = "http://localhost"; + var path = "files"; + var query = new NameValueCollection { { "a", "b" }, { "c", "d" } }; + var url = Library.Utility.Uri.UriBuilder(baseUrl, path, query); + Assert.AreEqual(baseUrl + "/" + path + "?a=b&c=d", url); + } + + [Test] + [Category("UriUtility")] + public static void TestExtractPath() + { + var url = "http://localhost/a/b"; + var path = Library.Utility.Uri.ExtractPath(url); + Assert.AreEqual("a/b", path); + } + + + [Test] + [Category("UriUtility")] + public static void TestConcatPaths() + { + var path1 = "/a"; + var path2 = "b/"; + Assert.AreEqual("/a/b/", Library.Utility.Uri.ConcatPaths(path1, path2)); + Assert.AreEqual("/a", Library.Utility.Uri.ConcatPaths(path1, null)); + Assert.AreEqual("/b/", Library.Utility.Uri.ConcatPaths(string.Empty, path2)); + } + } +} diff --git a/Duplicati/WindowsService/ServiceControl.cs b/Duplicati/WindowsService/ServiceControl.cs index f2f96b204..281bc7306 100644 --- a/Duplicati/WindowsService/ServiceControl.cs +++ b/Duplicati/WindowsService/ServiceControl.cs @@ -16,7 +16,7 @@ namespace Duplicati.WindowsService public const string DISPLAY_NAME = "Duplicati service"; public const string DESCRIPTION = "Runs Duplicati as a service"; - private System.Diagnostics.EventLog m_eventLog; + private readonly System.Diagnostics.EventLog m_eventLog; private readonly object m_lock = new object(); private Runner m_runner = null; diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..318675f7f --- /dev/null +++ b/build.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +quit_on_error() { + echo "Error on line $1, stopping build." + exit 1 +} + +list_dir() { + echo "listing directory $1 :" + ls -al $1 +} + +trap 'quit_on_error $LINENO' ERR + +TRAVIS_BUILD_DIR=$1 +CATEGORY=$2 +TESTUSER=travis + +echo "Build script starting with parameters TRAVIS_BUILD_DIR=$TRAVIS_BUILD_DIR and CATEGORY=$CATEGORY" + +# build duplicati + +list_dir "${TRAVIS_BUILD_DIR}"/packages/ +echo "travis_fold:start:build_duplicati" +msbuild /p:Configuration=Release Duplicati.sln +cp -r ./Duplicati/Server/webroot ./Duplicati/GUI/Duplicati.GUI.TrayIcon/bin/Release/webroot +echo "travis_fold:end:build_duplicati" + +# download and extract testdata +echo "travis_fold:start:download_extract_testdata" +list_dir . + +if [ ! -d ~/tmp ]; then mkdir ~/tmp; fi +if [ ! -d ~/download ]; then mkdir ~/download; fi +if [ ! -d ~/download/svn ]; then mkdir ~/download/svn; fi +if [ ! -d ~/download/bulk ]; then mkdir ~/download/bulk; fi + +if [ "$CATEGORY" == "SVNData" ] || [ "$CATEGORY" == "SVNDataLong" ]; then + # test if zip file exists and contains no errors + unzip -t ~/download/svn/DSMCBE.zip &> /dev/null || \ + wget --progress=dot:giga "https://s3.amazonaws.com/duplicati-test-file-hosting/DSMCBE.zip" -O ~/download/svn/DSMCBE.zip + list_dir ~/download/svn +fi + +if [ "$CATEGORY" == "BulkNormal" ] || [ "$CATEGORY" == "BulkNoSize" ]; then + # test if zip file exists and contains no errors + unzip -t ~/download/bulk/data.zip &> /dev/null || \ + wget --progress=dot:giga "https://s3.amazonaws.com/duplicati-test-file-hosting/data.zip" -O ~/download/bulk/data.zip + list_dir ~/download/bulk +fi + +rm -rf ~/duplicati_testdata && mkdir ~/duplicati_testdata + +if [ "$CATEGORY" == "SVNData" ] || [ "$CATEGORY" == "SVNDataLong" ]; then + mkdir ~/duplicati_testdata/DSMCBE + unzip -q ~/download/svn/DSMCBE.zip -d ~/duplicati_testdata/ + list_dir ~/duplicati_testdata/DSMCBE +fi + +if [ "$CATEGORY" == "BulkNormal" ] || [ "$CATEGORY" == "BulkNoSize" ]; then + mkdir ~/duplicati_testdata/data + unzip -q ~/download/bulk/data.zip -d ~/duplicati_testdata/ + list_dir ~/duplicati_testdata/data +fi + +chown -R $TESTUSER ~/duplicati_testdata/ +chmod -R 755 ~/duplicati_testdata +echo "travis_fold:end:download_extract_testdata" + +# run unit tests +echo "travis_fold:start:unit_test" +if [[ "$CATEGORY" != "GUI" ]]; then + mono ./testrunner/NUnit.ConsoleRunner.3.5.0/tools/nunit3-console.exe \ + ./Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll --where:cat==$CATEGORY --workers=1 +fi +echo "travis_fold:end:unit_test" + +# start server and run gui tests +echo "travis_fold:start:gui_unit_test" +mono ./Duplicati/GUI/Duplicati.GUI.TrayIcon/bin/Release/Duplicati.Server.exe & +if [[ "$CATEGORY" == "GUI" ]]; then + python guiTests/guiTest.py +fi +echo "travis_fold:end:gui_unit_test" diff --git a/thirdparty/DnsLite/DnsLite.cs b/thirdparty/DnsLite/DnsLite.cs index 0654fa684..75f84f831 100644 --- a/thirdparty/DnsLite/DnsLite.cs +++ b/thirdparty/DnsLite/DnsLite.cs @@ -27,7 +27,7 @@ namespace DnsLib private string name; private ArrayList dnsServers; private static int DNS_PORT = 53; - Encoding ASCII = Encoding.ASCII; + readonly Encoding ASCII = Encoding.ASCII; public DnsLite() { id = DateTime.Now.Millisecond * 60;