diff --git a/Duplicati/Library/Main/Options.cs b/Duplicati/Library/Main/Options.cs
index 9cd02b6c9..3bb038d54 100644
--- a/Duplicati/Library/Main/Options.cs
+++ b/Duplicati/Library/Main/Options.cs
@@ -19,6 +19,8 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+#nullable enable
+
using System;
using System.Linq;
using System.Collections.Generic;
@@ -28,6 +30,7 @@ using System.Globalization;
using System.Threading;
using Duplicati.Library.Utility.Options;
using Duplicati.Library.SQLiteHelper;
+using System.Diagnostics.CodeAnalysis;
namespace Duplicati.Library.Main
{
@@ -81,6 +84,31 @@ namespace Duplicati.Library.Main
///
private const string DEFAULT_READ_WRITE_TIMEOUT = "30s";
+ ///
+ /// The default asynchronous upload limit
+ ///
+ private const int DEFAULT_ASYNCHRONOUS_UPLOAD_LIMIT = 4;
+
+ ///
+ /// The default number of concurrent uploads
+ ///
+ private const int DEFAULT_ASYNCHRONOUS_CONCURRENT_UPLOAD_LIMIT = 4;
+
+ ///
+ /// The default retry delay
+ ///
+ private const string DEFAULT_RETRY_DELAY = "10s";
+
+ ///
+ /// The default number of retries
+ ///
+ private const int DEFAULT_NUMBER_OF_RETRIES = 5;
+
+ ///
+ /// The default number of backup test samples
+ ///
+ private const long DEFAULT_BACKUP_TEST_SAMPLES = 1;
+
///
/// The default number of compressor instances
///
@@ -256,34 +284,28 @@ namespace Duplicati.Library.Main
///
protected readonly object m_lock = new object();
- protected readonly Dictionary m_options;
+ protected readonly Dictionary m_options;
protected readonly List> m_loadedModules = new List>();
///
/// Lookup table for compression hints
///
- private Dictionary m_compressionHints;
+ private Dictionary? m_compressionHints;
- public Options(Dictionary options)
+ public Options(Dictionary options)
{
m_options = options;
}
- public Dictionary RawOptions { get { return m_options; } }
+ public Dictionary RawOptions => m_options;
///
/// Returns a list of strings that are not supported on the commandline as options, but used internally
///
- public static string[] InternalOptions
- {
- get
- {
- return new string[] {
+ public static string[] InternalOptions => [
"main-action"
- };
- }
- }
+ ];
///
/// Returns a list of options that are intentionally duplicate
@@ -297,13 +319,7 @@ namespace Duplicati.Library.Main
///
/// A default backup name
///
- public static string DefaultBackupName
- {
- get
- {
- return System.IO.Path.GetFileNameWithoutExtension(Library.Utility.Utility.getEntryAssembly().Location);
- }
- }
+ public static string DefaultBackupName => System.IO.Path.GetFileNameWithoutExtension(Library.Utility.Utility.getEntryAssembly().Location);
///
/// Gets all supported commands
@@ -339,13 +355,13 @@ namespace Duplicati.Library.Main
new CommandLineArgument("passphrase", CommandLineArgument.ArgumentType.Password, Strings.Options.PassphraseShort, Strings.Options.PassphraseLong),
new CommandLineArgument("no-encryption", CommandLineArgument.ArgumentType.Boolean, Strings.Options.NoencryptionShort, Strings.Options.NoencryptionLong, "false"),
- new CommandLineArgument("number-of-retries", CommandLineArgument.ArgumentType.Integer, Strings.Options.NumberofretriesShort, Strings.Options.NumberofretriesLong, "5"),
- new CommandLineArgument("retry-delay", CommandLineArgument.ArgumentType.Timespan, Strings.Options.RetrydelayShort, Strings.Options.RetrydelayLong, "10s"),
+ new CommandLineArgument("number-of-retries", CommandLineArgument.ArgumentType.Integer, Strings.Options.NumberofretriesShort, Strings.Options.NumberofretriesLong, DEFAULT_NUMBER_OF_RETRIES.ToString()),
+ new CommandLineArgument("retry-delay", CommandLineArgument.ArgumentType.Timespan, Strings.Options.RetrydelayShort, Strings.Options.RetrydelayLong, DEFAULT_RETRY_DELAY),
new CommandLineArgument("retry-with-exponential-backoff", CommandLineArgument.ArgumentType.Boolean, Strings.Options.RetrywithexponentialbackoffShort, Strings.Options.RetrywithexponentialbackoffLong, "false"),
new CommandLineArgument("synchronous-upload", CommandLineArgument.ArgumentType.Boolean, Strings.Options.SynchronousuploadShort, Strings.Options.SynchronousuploadLong, "false"),
- new CommandLineArgument("asynchronous-upload-limit", CommandLineArgument.ArgumentType.Integer, Strings.Options.AsynchronousuploadlimitShort, Strings.Options.AsynchronousuploadlimitLong, "4"),
- new CommandLineArgument("asynchronous-concurrent-upload-limit", CommandLineArgument.ArgumentType.Integer, Strings.Options.AsynchronousconcurrentuploadlimitShort, Strings.Options.AsynchronousconcurrentuploadlimitLong, "4"),
+ new CommandLineArgument("asynchronous-upload-limit", CommandLineArgument.ArgumentType.Integer, Strings.Options.AsynchronousuploadlimitShort, Strings.Options.AsynchronousuploadlimitLong, DEFAULT_ASYNCHRONOUS_UPLOAD_LIMIT.ToString()),
+ new CommandLineArgument("asynchronous-concurrent-upload-limit", CommandLineArgument.ArgumentType.Integer, Strings.Options.AsynchronousconcurrentuploadlimitShort, Strings.Options.AsynchronousconcurrentuploadlimitLong, DEFAULT_ASYNCHRONOUS_CONCURRENT_UPLOAD_LIMIT.ToString()),
new CommandLineArgument("asynchronous-upload-folder", CommandLineArgument.ArgumentType.Path, Strings.Options.AsynchronousuploadfolderShort, Strings.Options.AsynchronousuploadfolderLong, System.IO.Path.GetTempPath()),
new CommandLineArgument("disable-streaming-transfers", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DisableStreamingShort, Strings.Options.DisableStreamingLong, "false"),
@@ -425,7 +441,7 @@ namespace Duplicati.Library.Main
new CommandLineArgument("threshold", CommandLineArgument.ArgumentType.Integer, Strings.Options.ThresholdShort, Strings.Options.ThresholdLong, DEFAULT_THRESHOLD.ToString()),
new CommandLineArgument("index-file-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.IndexfilepolicyShort, Strings.Options.IndexfilepolicyLong, IndexFileStrategy.Full.ToString(), null, Enum.GetNames(typeof(IndexFileStrategy))),
new CommandLineArgument("no-backend-verification", CommandLineArgument.ArgumentType.Boolean, Strings.Options.NobackendverificationShort, Strings.Options.NobackendverificationLong, "false"),
- new CommandLineArgument("backup-test-samples", CommandLineArgument.ArgumentType.Integer, Strings.Options.BackendtestsamplesShort, Strings.Options.BackendtestsamplesLong("no-backend-verification"), "1"),
+ new CommandLineArgument("backup-test-samples", CommandLineArgument.ArgumentType.Integer, Strings.Options.BackendtestsamplesShort, Strings.Options.BackendtestsamplesLong("no-backend-verification"), DEFAULT_BACKUP_TEST_SAMPLES.ToString()),
new CommandLineArgument("backup-test-percentage", CommandLineArgument.ArgumentType.Decimal, Strings.Options.BackendtestpercentageShort, Strings.Options.BackendtestpercentageLong, "0.1"),
new CommandLineArgument("full-remote-verification", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.FullremoteverificationShort, Strings.Options.FullremoteverificationLong("no-backend-verification"), Enum.GetName(typeof(RemoteTestStrategy), RemoteTestStrategy.False), null, Enum.GetNames(typeof(RemoteTestStrategy))),
@@ -504,7 +520,13 @@ namespace Duplicati.Library.Main
///
public OperationMode MainAction
{
- get { return (OperationMode)Enum.Parse(typeof(OperationMode), m_options["main-action"]); }
+ get
+ {
+ var value = m_options.GetValueOrDefault("main-action");
+ return string.IsNullOrEmpty(value)
+ ? ((OperationMode)(-1))
+ : (OperationMode)Enum.Parse(typeof(OperationMode), value);
+ }
set { m_options["main-action"] = value.ToString(); }
}
@@ -515,11 +537,7 @@ namespace Duplicati.Library.Main
{
get
{
- string volsize;
- m_options.TryGetValue("dblock-size", out volsize);
- if (string.IsNullOrEmpty(volsize))
- volsize = DEFAULT_VOLUME_SIZE;
-
+ var volsize = GetString("dblock-size", DEFAULT_VOLUME_SIZE);
#if DEBUG
return Math.Max(1024 * 10, Library.Utility.Sizeparser.ParseSize(volsize, "mb"));
#else
@@ -535,46 +553,38 @@ namespace Duplicati.Library.Main
{
get
{
- if (!m_options.ContainsKey("skip-files-larger-than") || string.IsNullOrEmpty(m_options["skip-files-larger-than"]))
- return long.MaxValue;
- else
- return Library.Utility.Sizeparser.ParseSize(m_options["skip-files-larger-than"], "mb");
+ var value = m_options.GetValueOrDefault("skip-files-larger-than");
+ return string.IsNullOrWhiteSpace(value)
+ ? long.MaxValue
+ : Library.Utility.Sizeparser.ParseSize(value, "mb");
}
}
///
/// A value indicating if orphan files are deleted automatically
///
- public bool AutoCleanup { get { return GetBool("auto-cleanup"); } }
+ public bool AutoCleanup => GetBool("auto-cleanup");
///
/// A value indicating if we are running in unittest mode
///
- public bool UnittestMode { get { return GetBool("unittest-mode"); } }
+ public bool UnittestMode => GetBool("unittest-mode");
///
/// Gets a list of files to add to the signature volumes
///
- public string ControlFiles
- {
- get
- {
- string v;
- m_options.TryGetValue("control-files", out v);
- return v;
- }
- }
+ public string? ControlFiles => m_options.GetValueOrDefault("control-files");
///
/// A value indicating if file hash checks are skipped
///
- public bool SkipFileHashChecks { get { return GetBool("skip-file-hash-checks"); } }
+ public bool SkipFileHashChecks => GetBool("skip-file-hash-checks");
///
/// A value indicating if the manifest files are not read
///
- public bool DontReadManifests { get { return GetBool("dont-read-manifests"); } }
+ public bool DontReadManifests => GetBool("dont-read-manifests");
///
/// Gets the backup that should be restored
@@ -583,22 +593,21 @@ namespace Duplicati.Library.Main
{
get
{
- if (!m_options.ContainsKey("time") || string.IsNullOrEmpty(m_options["time"]))
- return new DateTime(0, DateTimeKind.Utc);
- else
- return Library.Utility.Timeparser.ParseTimeInterval(m_options["time"], DateTime.Now);
+ var value = m_options.GetValueOrDefault("time");
+ return string.IsNullOrEmpty(value)
+ ? new DateTime(0, DateTimeKind.Utc)
+ : Library.Utility.Timeparser.ParseTimeInterval(value, DateTime.Now);
}
}
///
/// Gets the versions the restore or list operation is limited to
///
- public long[] Version
+ public long[]? Version
{
get
{
- string v;
- m_options.TryGetValue("version", out v);
+ m_options.TryGetValue("version", out var v);
if (string.IsNullOrEmpty(v))
return null;
@@ -625,32 +634,32 @@ namespace Duplicati.Library.Main
///
/// A value indicating if all versions are listed
///
- public bool AllVersions { get { return GetBool("all-versions"); } }
+ public bool AllVersions => GetBool("all-versions");
///
/// A value indicating if only the largest common prefix is returned
///
- public bool ListPrefixOnly { get { return GetBool("list-prefix-only"); } }
+ public bool ListPrefixOnly => GetBool("list-prefix-only");
///
/// A value indicating if only folder contents are returned
///
- public bool ListFolderContents { get { return GetBool("list-folder-contents"); } }
+ public bool ListFolderContents => GetBool("list-folder-contents");
///
/// A value indicating that only filesets are returned
///
- public bool ListSetsOnly { get { return GetBool("list-sets-only"); } }
+ public bool ListSetsOnly => GetBool("list-sets-only");
///
/// A value indicating if file time checks are skipped
///
- public bool DisableFiletimeCheck { get { return GetBool("disable-filetime-check"); } }
+ public bool DisableFiletimeCheck => GetBool("disable-filetime-check");
///
/// A value indicating if file time checks are skipped
///
- public bool CheckFiletimeOnly { get { return GetBool("check-filetime-only"); } }
+ public bool CheckFiletimeOnly => GetBool("check-filetime-only");
///
/// A value indicating if USN numbers are used to get list of changed files
@@ -660,33 +669,22 @@ namespace Duplicati.Library.Main
///
/// A value indicating if time tolerance is disabled
///
- public bool DisableTimeTolerance { get { return GetBool("disable-time-tolerance"); } }
+ public bool DisableTimeTolerance => GetBool("disable-time-tolerance");
///
/// Gets a value indicating whether a temporary folder has been specified
///
- public bool HasTempDir { get { return m_options.ContainsKey("tempdir") && !string.IsNullOrEmpty(m_options["tempdir"]); } }
+ public bool HasTempDir => m_options.ContainsKey("tempdir") && !string.IsNullOrEmpty(m_options["tempdir"]);
///
/// Gets the folder where temporary files are stored
///
- public string TempDir
- {
- get
- {
- if (!m_options.ContainsKey("tempdir") || string.IsNullOrEmpty(m_options["tempdir"]))
- {
- return Duplicati.Library.Utility.TempFolder.SystemTempPath;
- }
-
- return m_options["tempdir"];
- }
- }
+ public string TempDir => GetString("tempdir", TempFolder.SystemTempPath);
///
/// Gets a value indicating whether the user has forced the locale
///
- public bool HasForcedLocale { get { return m_options.ContainsKey("force-locale"); } }
+ public bool HasForcedLocale => m_options.ContainsKey("force-locale");
///
/// Gets the forced locale for the current user
@@ -709,39 +707,17 @@ namespace Duplicati.Library.Main
///
/// A value indicating if missing folders should be created automatically
///
- public bool AutocreateFolders { get { return !GetBool("disable-autocreate-folder"); } }
+ public bool AutocreateFolders => !GetBool("disable-autocreate-folder");
///
/// Gets the backup prefix
///
- public string Prefix
- {
- get
- {
- string v;
- m_options.TryGetValue("prefix", out v);
- if (!string.IsNullOrEmpty(v))
- return v;
-
- return "duplicati";
- }
- }
+ public string Prefix => GetString("prefix", "duplicati");
///
/// Gets the number of old backups to keep
///
- public int KeepVersions
- {
- get
- {
- string v;
- m_options.TryGetValue("keep-versions", out v);
- if (string.IsNullOrEmpty(v))
- return DEFAULT_KEEP_VERSIONS;
-
- return Math.Max(0, int.Parse(v));
- }
- }
+ public int KeepVersions => Math.Max(0, GetInt("keep-versions", DEFAULT_KEEP_VERSIONS));
///
/// Gets the timelimit for removal
@@ -750,13 +726,12 @@ namespace Duplicati.Library.Main
{
get
{
- string v;
- m_options.TryGetValue("keep-time", out v);
+ m_options.TryGetValue("keep-time", out var v);
if (string.IsNullOrEmpty(v))
return new DateTime(0);
- TimeSpan tolerance =
+ var tolerance =
this.DisableTimeTolerance ?
TimeSpan.FromSeconds(0) :
TimeSpan.FromSeconds(Math.Min(Library.Utility.Timeparser.ParseTimeSpan(v).TotalSeconds / 100, 60.0 * 60.0));
@@ -774,19 +749,14 @@ namespace Duplicati.Library.Main
{
var retentionPolicyConfig = new List();
- string v;
- m_options.TryGetValue("retention-policy", out v);
+ m_options.TryGetValue("retention-policy", out var v);
if (string.IsNullOrEmpty(v))
- {
return retentionPolicyConfig;
- }
var periodIntervalStrings = v.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var periodIntervalString in periodIntervalStrings)
- {
retentionPolicyConfig.Add(RetentionPolicyValue.CreateFromString(periodIntervalString));
- }
return retentionPolicyConfig;
}
@@ -795,95 +765,47 @@ namespace Duplicati.Library.Main
///
/// Gets the encryption passphrase
///
- public string Passphrase
- {
- get
- {
- if (!m_options.ContainsKey("passphrase") || string.IsNullOrEmpty(m_options["passphrase"]))
- return null;
- else
- return m_options["passphrase"];
- }
- }
+ public string? Passphrase => GetString("passphrase", null);
///
/// A value indicating if backups are not encrypted
///
- public bool NoEncryption { get { return GetBool("no-encryption"); } }
+ public bool NoEncryption => GetBool("no-encryption");
///
/// Gets the module used for encryption
///
- public string EncryptionModule
- {
- get
- {
- //Disabled?
- if (NoEncryption)
- return null;
-
- //Specified?
- if (m_options.ContainsKey("encryption-module"))
- return m_options["encryption-module"];
-
- return "aes";
- }
- }
+ public string? EncryptionModule => NoEncryption ? null : GetString("encryption-module", "aes");
///
/// Gets the module used for compression
///
- public string CompressionModule
- {
- get
- {
- if (m_options.ContainsKey("compression-module"))
- return m_options["compression-module"];
- else
- return "zip";
- }
- }
-
+ public string CompressionModule => GetString("compression-module", "zip");
///
/// Gets the number of time to retry transmission if it fails
///
- public int NumberOfRetries
- {
- get
- {
- if (!m_options.ContainsKey("number-of-retries") || string.IsNullOrEmpty(m_options["number-of-retries"]))
- return 5;
- else
- {
- int x = int.Parse(m_options["number-of-retries"]);
- if (x < 0)
- throw new UserInformationException("Invalid count for number-of-retries", "NumberOfRetriesInvalid");
-
- return x;
- }
- }
- }
+ public int NumberOfRetries => Math.Max(0, GetInt("number-of-retries", DEFAULT_NUMBER_OF_RETRIES));
///
/// A value indicating if backups are transmitted on a separate thread
///
- public bool SynchronousUpload { get { return Library.Utility.Utility.ParseBoolOption(m_options, "synchronous-upload"); } }
+ public bool SynchronousUpload => GetBool("synchronous-upload");
///
/// A value indicating if system is allowed to enter sleep power states during backup/restore
///
- public bool AllowSleep { get { return GetBool("allow-sleep"); } }
+ public bool AllowSleep => GetBool("allow-sleep");
///
/// A value indicating if system should use the low-priority IO during backup/restore
///
- public bool UseBackgroundIOPriority { get { return GetBool("use-background-io-priority"); } }
+ public bool UseBackgroundIOPriority => GetBool("use-background-io-priority");
///
/// A value indicating if use of the streaming interface is disallowed
///
- public bool DisableStreamingTransfers { get { return GetBool("disable-streaming-transfers"); } }
+ public bool DisableStreamingTransfers => GetBool("disable-streaming-transfers");
///
/// The maximum time to allow inactivity before a connection is closed.
@@ -893,39 +815,20 @@ namespace Duplicati.Library.Main
{
get
{
- var v = m_options.GetValueOrDefault("read-write-timeout");
- if (string.IsNullOrWhiteSpace(v))
- v = DEFAULT_READ_WRITE_TIMEOUT;
-
- var res = Library.Utility.Timeparser.ParseTimeSpan(v);
- if (res.Ticks <= 0)
- return Timeout.Infinite;
-
- return (int)res.TotalMilliseconds;
+ var ts = Library.Utility.Utility.ParseTimespanOption(m_options, "read-write-timeout", DEFAULT_READ_WRITE_TIMEOUT);
+ return ts.Ticks <= 0 ? Timeout.Infinite : (int)ts.TotalMilliseconds;
}
}
///
/// Gets the delay period to retry uploads
///
- public TimeSpan RetryDelay
- {
- get
- {
- if (!m_options.ContainsKey("retry-delay") || string.IsNullOrEmpty(m_options["retry-delay"]))
- return new TimeSpan(TimeSpan.TicksPerSecond * 10);
- else
- return Library.Utility.Timeparser.ParseTimeSpan(m_options["retry-delay"]);
- }
- }
+ public TimeSpan RetryDelay => Library.Utility.Utility.ParseTimespanOption(m_options, "retry-delay", DEFAULT_RETRY_DELAY);
///
/// Gets whether exponential backoff is enabled
///
- public Boolean RetryWithExponentialBackoff
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "retry-with-exponential-backoff"); }
- }
+ public bool RetryWithExponentialBackoff => GetBool("retry-with-exponential-backoff");
///
/// Gets the max upload speed in bytes pr. second
@@ -935,22 +838,12 @@ namespace Duplicati.Library.Main
get
{
lock (m_lock)
- {
- string v;
- m_options.TryGetValue("throttle-upload", out v);
- if (string.IsNullOrEmpty(v))
- return 0;
- else
- return Library.Utility.Sizeparser.ParseSize(v, "kb");
- }
+ return GetSize("throttle-upload", "kb", "0b");
}
set
{
lock (m_lock)
- if (value <= 0)
- m_options["throttle-upload"] = "";
- else
- m_options["throttle-upload"] = value.ToString() + "b";
+ m_options["throttle-upload"] = value <= 0 ? "" : $"{value}b";
}
}
@@ -962,104 +855,55 @@ namespace Duplicati.Library.Main
get
{
lock (m_lock)
- {
- string v;
- m_options.TryGetValue("throttle-download", out v);
- if (string.IsNullOrEmpty(v))
- return 0;
- else
- return Library.Utility.Sizeparser.ParseSize(v, "kb");
- }
+ return GetSize("throttle-download", "kb", "0b");
}
set
{
lock (m_lock)
- if (value <= 0)
- m_options["throttle-download"] = "";
- else
- m_options["throttle-download"] = value.ToString() + "b";
+ m_options["throttle-download"] = value <= 0 ? "" : $"{value}b";
}
}
///
/// A value indicating if the backup is a full backup
///
- public bool AllowFullRemoval { get { return GetBool("allow-full-removal"); } }
+ public bool AllowFullRemoval => GetBool("allow-full-removal");
///
/// A value indicating if debug output is enabled
///
- public bool DebugOutput { get { return GetBool("debug-output"); } }
+ public bool DebugOutput => GetBool("debug-output");
///
/// A value indicating if unchanged backups are uploaded
///
- public bool UploadUnchangedBackups { get { return GetBool("upload-unchanged-backups"); } }
+ public bool UploadUnchangedBackups => GetBool("upload-unchanged-backups");
///
/// Gets a list of modules that should be loaded
///
public string[] EnableModules
- {
- get
- {
- if (m_options.ContainsKey("enable-module"))
- return m_options["enable-module"].Trim().ToLower(CultureInfo.InvariantCulture).Split(',');
- else
- return new string[0];
- }
- }
+ => m_options.GetValueOrDefault("enable-module")?.Trim().ToLower(CultureInfo.InvariantCulture).Split(',') ?? [];
///
/// Gets a list of modules that should not be loaded
///
public string[] DisableModules
- {
- get
- {
- if (m_options.ContainsKey("disable-module"))
- return m_options["disable-module"].Trim().ToLower(CultureInfo.InvariantCulture).Split(',');
- else
- return new string[0];
- }
- }
+ => m_options.GetValueOrDefault("disable-module")?.Trim().ToLower(CultureInfo.InvariantCulture).Split(',') ?? [];
///
/// Gets the snapshot strategy to use
///
- public OptimizationStrategy SnapShotStrategy
- {
- get
- {
- string strategy;
- if (!m_options.TryGetValue("snapshot-policy", out strategy))
- strategy = "";
-
- OptimizationStrategy r;
- if (!Enum.TryParse(strategy, true, out r))
- r = OptimizationStrategy.Off;
-
- return r;
- }
- }
+ public OptimizationStrategy SnapShotStrategy => GetEnum("snapshot-policy", OptimizationStrategy.Off);
///
/// Gets the snapshot strategy to use
///
public Snapshots.SnapshotProvider SnapShotProvider
- {
- get
- {
- if (!m_options.TryGetValue("snapshot-provider", out var provider))
- provider = "";
-
- Snapshots.SnapshotProvider r;
- if (!Enum.TryParse(provider, true, out r))
- r = OperatingSystem.IsWindows() ? Snapshots.SnapshotProvider.AlphaVSS : Snapshots.SnapshotProvider.LVM;
-
- return r;
- }
- }
+ => GetEnum("snapshot-provider",
+ OperatingSystem.IsWindows()
+ ? Snapshots.SnapshotProvider.AlphaVSS
+ : Snapshots.SnapshotProvider.LVM);
///
/// Gets a flag indicating if advisory locking should be ignored
@@ -1070,125 +914,42 @@ namespace Duplicati.Library.Main
/// Gets the symlink strategy to use
///
public SymlinkStrategy SymlinkPolicy
- {
- get
- {
- string policy;
- if (!m_options.TryGetValue("symlink-policy", out policy))
- policy = "";
-
- SymlinkStrategy r;
- if (!Enum.TryParse(policy, true, out r))
- r = SymlinkStrategy.Store;
-
- return r;
- }
- }
+ => GetEnum("symlink-policy", SymlinkStrategy.Store);
///
/// Gets the hardlink strategy to use
///
public HardlinkStrategy HardlinkPolicy
- {
- get
- {
- string policy;
- if (!m_options.TryGetValue("hardlink-policy", out policy))
- policy = "";
+ => GetEnum("hardlink-policy", HardlinkStrategy.All);
- HardlinkStrategy r;
- if (!Enum.TryParse(policy, true, out r))
- r = HardlinkStrategy.All;
-
- return r;
- }
- }
///
/// Gets the update sequence number (USN) strategy to use
///
public OptimizationStrategy UsnStrategy
- {
- get
- {
- string strategy;
- if (!m_options.TryGetValue("usn-policy", out strategy))
- strategy = "";
-
- OptimizationStrategy r;
- if (!Enum.TryParse(strategy, true, out r))
- r = OptimizationStrategy.Off;
-
- return r;
- }
- }
+ => GetEnum("usn-policy", OptimizationStrategy.Off);
///
/// Gets the number of concurrent volume uploads allowed. Zero for unlimited.
///
public int AsynchronousConcurrentUploadLimit
- {
- get
- {
- if (!m_options.TryGetValue("asynchronous-concurrent-upload-limit", out var value))
- value = null;
-
- if (string.IsNullOrEmpty(value))
- return 4;
- else
- return int.Parse(value);
- }
- }
+ => GetInt("asynchronous-concurrent-upload-limit", DEFAULT_ASYNCHRONOUS_CONCURRENT_UPLOAD_LIMIT);
///
/// Gets the number of volumes to create ahead of time when using async transfers,
/// a value of zero indicates no limit
///
public long AsynchronousUploadLimit
- {
- get
- {
- string value;
- if (!m_options.TryGetValue("asynchronous-upload-limit", out value))
- value = null;
-
- if (string.IsNullOrEmpty(value))
- return 4;
- else
- return long.Parse(value);
- }
- }
+ => GetInt("asynchronous-upload-limit", DEFAULT_ASYNCHRONOUS_UPLOAD_LIMIT);
///
/// Gets the temporary folder to use for asynchronous transfers
///
- public string AsynchronousUploadFolder
- {
- get
- {
- string value;
- if (!m_options.TryGetValue("asynchronous-upload-folder", out value))
- value = null;
-
- if (string.IsNullOrEmpty(value))
- return this.TempDir;
- else
- return value;
- }
- }
+ public string AsynchronousUploadFolder => GetString("asynchronous-upload-folder", TempDir);
///
/// Gets the logfile filename
///
- public string Logfile
- {
- get
- {
- string value;
- if (!m_options.TryGetValue("log-file", out value))
- value = null;
- return value;
- }
- }
+ public string? Logfile => m_options.GetValueOrDefault("log-file");
///
/// Gets the log-file detail level
@@ -1197,8 +958,7 @@ namespace Duplicati.Library.Main
{
get
{
- string value;
- if (!m_options.TryGetValue("log-file-log-level", out value))
+ if (!m_options.TryGetValue("log-file-log-level", out var value))
value = null;
if (string.IsNullOrWhiteSpace(value))
@@ -1249,8 +1009,7 @@ namespace Duplicati.Library.Main
{
get
{
- string value;
- if (!m_options.TryGetValue("console-log-level", out value))
+ if (!m_options.TryGetValue("console-log-level", out var value))
value = null;
if (string.IsNullOrWhiteSpace(value))
@@ -1271,50 +1030,33 @@ namespace Duplicati.Library.Main
///
/// A value indicating if all database queries should be logged
///
- public bool ProfileAllDatabaseQueries { get { return GetBool("profile-all-database-queries"); } }
+ public bool ProfileAllDatabaseQueries => GetBool("profile-all-database-queries");
///
/// Gets the attribute filter used to exclude files and folders.
///
public System.IO.FileAttributes FileAttributeFilter
- {
- get
- {
- System.IO.FileAttributes res = default(System.IO.FileAttributes);
- string v;
- if (!m_options.TryGetValue("exclude-files-attributes", out v))
- return res;
-
- foreach (string s in v.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries))
- {
- System.IO.FileAttributes f;
- if (Enum.TryParse(s.Trim(), true, out f))
- res |= f;
- }
-
- return res;
- }
- }
+ => Library.Utility.Utility.ParseFlagsOption(m_options, "exclude-files-attributes", default(System.IO.FileAttributes));
///
/// A value indicating if server uploads are verified by listing the folder contents
///
- public bool ListVerifyUploads { get { return GetBool("list-verify-uploads"); } }
+ public bool ListVerifyUploads => GetBool("list-verify-uploads");
///
/// A value indicating if connections cannot be re-used
///
- public bool NoConnectionReuse { get { return GetBool("no-connection-reuse"); } }
+ public bool NoConnectionReuse => GetBool("no-connection-reuse");
///
/// A value indicating if the returned value should not be truncated
///
- public bool FullResult { get { return GetBool("full-result"); } }
+ public bool FullResult => GetBool("full-result");
///
/// A value indicating restored files overwrite existing ones
///
- public bool Overwrite { get { return GetBool("overwrite"); } }
+ public bool Overwrite => GetBool("overwrite");
///
/// Gets the total size in bytes that the backup should use, returns -1 if there is no upper limit
@@ -1323,10 +1065,8 @@ namespace Duplicati.Library.Main
{
get
{
- if (!m_options.ContainsKey("quota-size") || string.IsNullOrEmpty(m_options["quota-size"]))
- return -1;
- else
- return Library.Utility.Sizeparser.ParseSize(m_options["quota-size"], "mb");
+ var value = m_options.GetValueOrDefault("quota-size");
+ return string.IsNullOrEmpty(value) ? -1 : Library.Utility.Sizeparser.ParseSize(value, "mb");
}
}
@@ -1337,96 +1077,46 @@ namespace Duplicati.Library.Main
/// This is treated as a percentage, where a warning is given when the amount of free space is less than this percentage of the backup size.
///
public int QuotaWarningThreshold
- {
- get
- {
- string tmp;
- m_options.TryGetValue("quota-warning-threshold", out tmp);
- if (string.IsNullOrEmpty(tmp))
- {
- return DEFAULT_QUOTA_WARNING_THRESHOLD;
- }
- else
- {
- return int.Parse(tmp);
- }
- }
- }
+ => GetInt("quota-warning-threshold", DEFAULT_QUOTA_WARNING_THRESHOLD);
///
/// Gets a flag indicating that backup quota reported by the backend should be ignored
///
/// This is necessary because in some cases the backend might report a wrong quota (especially with some Linux mounts).
- public bool QuotaDisable
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "quota-disable"); }
- }
+ public bool QuotaDisable => GetBool("quota-disable");
///
/// Gets the display name of the backup
///
public string BackupName
{
- get
- {
- string tmp;
- m_options.TryGetValue("backup-name", out tmp);
- if (string.IsNullOrEmpty(tmp))
- return DefaultBackupName;
- else
- return tmp;
- }
- set
- {
- m_options["backup-name"] = value;
- }
+ get => GetString("backup-name", DefaultBackupName);
+ set => m_options["backup-name"] = value;
}
///
/// Gets the ID of the backup
///
- public string BackupId
- {
- get
- {
- m_options.TryGetValue("backup-id", out var tmp);
- return tmp;
- }
- }
+ public string? BackupId => m_options.GetValueOrDefault("backup-id");
///
/// Gets the ID of the machine
///
- public string MachineId
- {
- get
- {
- if (m_options.TryGetValue("machine-id", out var tmp))
- return tmp;
- return Library.AutoUpdater.DataFolderManager.InstallID;
- }
- }
+ public string? MachineId => m_options.GetValueOrDefault("machine-id", Library.AutoUpdater.DataFolderManager.InstallID);
+
///
/// Gets the path to the database
///
- public string Dbpath
+ public string? Dbpath
{
- get
- {
- string tmp;
- m_options.TryGetValue("dbpath", out tmp);
- return tmp;
- }
- set
- {
- m_options["dbpath"] = value;
- }
+ get => m_options.GetValueOrDefault("dbpath", null);
+ set => m_options["dbpath"] = value;
}
///
/// Gets a value indicating whether a blocksize has been specified
///
- public bool HasBlocksize { get { return m_options.ContainsKey("blocksize") && !string.IsNullOrEmpty(m_options["blocksize"]); } }
+ public bool HasBlocksize => !string.IsNullOrEmpty(m_options.GetValueOrDefault("blocksize"));
///
/// Gets the size of file-blocks
@@ -1435,11 +1125,7 @@ namespace Duplicati.Library.Main
{
get
{
- string tmp;
- if (!m_options.TryGetValue("blocksize", out tmp))
- tmp = DEFAULT_BLOCKSIZE;
-
- long blocksize = Library.Utility.Sizeparser.ParseSize(tmp, "kb");
+ var blocksize = GetSize("blocksize", "kb", DEFAULT_BLOCKSIZE);
if (blocksize > int.MaxValue || blocksize < 1024)
throw new ArgumentOutOfRangeException(nameof(blocksize), string.Format("The blocksize cannot be less than {0}, nor larger than {1}", 1024, int.MaxValue));
@@ -1470,142 +1156,68 @@ namespace Duplicati.Library.Main
///
/// Gets a flag indicating if metadata for files and folders should be ignored
///
- public bool SkipMetadata
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "skip-metadata"); }
- }
+ public bool SkipMetadata => GetBool("skip-metadata");
///
/// Gets a flag indicating if empty folders should be ignored
///
- public bool ExcludeEmptyFolders
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "exclude-empty-folders"); }
- }
+ public bool ExcludeEmptyFolders => GetBool("exclude-empty-folders");
///
/// Gets a flag indicating if during restores metadata should be applied to the symlink target.
/// Setting this to true can result in errors if the target no longer exists.
///
- public bool RestoreSymlinkMetadata
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "restore-symlink-metadata"); }
- }
+ public bool RestoreSymlinkMetadata => GetBool("restore-symlink-metadata");
///
/// Gets a flag indicating if permissions should be restored
///
- public bool RestorePermissions
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "restore-permissions"); }
- }
+ public bool RestorePermissions => GetBool("restore-permissions");
///
/// Gets a flag indicating if file hashes are checked after a restore
///
- public bool PerformRestoredFileVerification
- {
- get { return !Library.Utility.Utility.ParseBoolOption(m_options, "skip-restore-verification"); }
- }
+ public bool PerformRestoredFileVerification => !GetBool("skip-restore-verification");
///
/// Gets a flag indicating if synthetic filelist generation is disabled
///
- public bool DisableSyntheticFilelist
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "disable-synthetic-filelist"); }
- }
+ public bool DisableSyntheticFilelist => GetBool("disable-synthetic-filelist");
///
/// Gets the compact threshold
///
- public long Threshold
- {
- get
- {
- string v;
- m_options.TryGetValue("threshold", out v);
- if (string.IsNullOrEmpty(v))
- return DEFAULT_THRESHOLD;
-
- return Convert.ToInt64(v);
- }
- }
+ public long Threshold => GetLong("threshold", DEFAULT_THRESHOLD);
///
/// Gets the size of small volumes
///
- public long SmallFileSize
- {
- get
- {
- string v;
- m_options.TryGetValue("small-file-size", out v);
- if (string.IsNullOrEmpty(v))
- return this.VolumeSize / 5;
-
- return Library.Utility.Sizeparser.ParseSize(v, "mb");
- }
- }
+ public long SmallFileSize => GetSize("small-file-size", "mb", $"{this.VolumeSize / 5}b");
///
/// Gets the maximum number of small volumes
///
- public long SmallFileMaxCount
- {
- get
- {
- string v;
- m_options.TryGetValue("small-file-max-count", out v);
- if (string.IsNullOrEmpty(v))
- return DEFAULT_SMALL_FILE_MAX_COUNT;
-
- return Convert.ToInt64(v);
- }
- }
+ public long SmallFileMaxCount => GetLong("small-file-max-count", DEFAULT_SMALL_FILE_MAX_COUNT);
///
/// List of files to check for changes
///
- public string[] ChangedFilelist
- {
- get
- {
- string v;
- m_options.TryGetValue("changed-files", out v);
- if (string.IsNullOrEmpty(v))
- return null;
-
- return v.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
- }
- }
+ public string[]? ChangedFilelist => m_options.GetValueOrDefault("changed-files")?.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
///
/// List of files to mark as deleted
///
- public string[] DeletedFilelist
- {
- get
- {
- string v;
- m_options.TryGetValue("deleted-files", out v);
- if (string.IsNullOrEmpty(v))
- return null;
-
- return v.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
- }
- }
+ public string[]? DeletedFilelist => m_options.GetValueOrDefault("deleted-files")?.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
///
/// List of filenames that are used to exclude a folder
///
- public string[] IgnoreFilenames
+ public string[]? IgnoreFilenames
{
get
{
- string v;
- if (!m_options.TryGetValue("ignore-filenames", out v))
+ if (!m_options.TryGetValue("ignore-filenames", out var v))
v = "CACHEDIR.TAG";
if (string.IsNullOrEmpty(v))
return null;
@@ -1616,42 +1228,17 @@ namespace Duplicati.Library.Main
///
/// Alternate restore path
///
- public string Restorepath
- {
- get
- {
- string v;
- m_options.TryGetValue("restore-path", out v);
- return v;
- }
- }
+ public string? Restorepath => m_options.GetValueOrDefault("restore-path");
///
/// Gets the index file usage method
///
- public IndexFileStrategy IndexfilePolicy
- {
- get
- {
- string strategy;
- if (!m_options.TryGetValue("index-file-policy", out strategy))
- strategy = "";
-
- IndexFileStrategy res;
- if (!Enum.TryParse(strategy, true, out res))
- res = IndexFileStrategy.Full;
-
- return res;
- }
- }
+ public IndexFileStrategy IndexfilePolicy => GetEnum("index-file-policy", IndexFileStrategy.Full);
///
/// Gets a flag indicating if the check for files on the remote storage should be omitted
///
- public bool NoBackendverification
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "no-backend-verification"); }
- }
+ public bool NoBackendverification => GetBool("no-backend-verification");
///
/// Gets the percentage of samples to test during a backup operation
@@ -1660,11 +1247,9 @@ namespace Duplicati.Library.Main
{
get
{
- m_options.TryGetValue("backup-test-percentage", out string s);
+ m_options.TryGetValue("backup-test-percentage", out var s);
if (string.IsNullOrEmpty(s))
- {
return 0.1m;
- }
decimal percentage;
try
@@ -1688,265 +1273,137 @@ namespace Duplicati.Library.Main
///
/// Gets the number of samples to test during a backup operation
///
- public long BackupTestSampleCount
- {
- get
- {
- string s;
- m_options.TryGetValue("backup-test-samples", out s);
- if (string.IsNullOrEmpty(s))
- return 1;
-
- return long.Parse(s);
- }
- }
+ public long BackupTestSampleCount => Math.Max(0, GetLong("backup-test-samples", DEFAULT_BACKUP_TEST_SAMPLES));
///
/// Gets a flag indicating if compacting should not be done automatically
///
- public bool NoAutoCompact
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "no-auto-compact"); }
- }
+ public bool NoAutoCompact => GetBool("no-auto-compact");
///
/// Gets the minimum time that must elapse after last compaction before running next automatic compaction
///
- public TimeSpan AutoCompactInterval
- {
- get
- {
- if (!m_options.ContainsKey("auto-compact-interval") || string.IsNullOrEmpty(m_options["auto-compact-interval"]))
- return TimeSpan.Zero;
- else
- return Library.Utility.Timeparser.ParseTimeSpan(m_options["auto-compact-interval"]);
- }
- }
+ public TimeSpan AutoCompactInterval => Library.Utility.Utility.ParseTimespanOption(m_options, "auto-compact-interval", "0s");
///
/// Gets a flag indicating if missing source elements should be ignored
///
- public bool AllowMissingSource
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "allow-missing-source"); }
- }
+ public bool AllowMissingSource => GetBool("allow-missing-source");
///
/// Gets a value indicating if a verification file should be uploaded after changing the remote store
///
- public bool UploadVerificationFile
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "upload-verification-file"); }
- }
+ public bool UploadVerificationFile => GetBool("upload-verification-file");
///
/// Gets a value indicating if a passphrase change is allowed
///
- public bool AllowPassphraseChange
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "allow-passphrase-change"); }
- }
+ public bool AllowPassphraseChange => GetBool("allow-passphrase-change");
///
/// Gets a flag indicating if the current operation should merely output the changes
///
- public bool Dryrun
- {
- get
- {
- if (m_options.ContainsKey("dry-run"))
- return Library.Utility.Utility.ParseBoolOption(m_options, "dry-run");
- else
- return Library.Utility.Utility.ParseBoolOption(m_options, "dryrun");
- }
- }
+ public bool Dryrun => GetBool("dry-run") || GetBool("dryrun");
///
/// Gets a value indicating if the remote verification is deep
///
- public RemoteTestStrategy FullRemoteVerification
- {
- get
- {
- string policy;
- if (!m_options.TryGetValue("full-remote-verification", out policy))
- policy = "False";
-
- RemoteTestStrategy r;
- if (!Enum.TryParse(policy, true, out r))
- r = RemoteTestStrategy.True;
-
- return r;
- }
- }
+ public RemoteTestStrategy FullRemoteVerification => GetEnum("full-remote-verification", RemoteTestStrategy.True);
///
/// The block hash algorithm to use
///
- public string BlockHashAlgorithm
- {
- get
- {
- string v;
- m_options.TryGetValue("block-hash-algorithm", out v);
- if (string.IsNullOrEmpty(v))
- return DEFAULT_BLOCK_HASH_ALGORITHM;
-
- return v;
- }
- }
+ public string BlockHashAlgorithm => GetString("block-hash-algorithm", DEFAULT_BLOCK_HASH_ALGORITHM);
///
/// The file hash algorithm to use
///
- public string FileHashAlgorithm
- {
- get
- {
- string v;
- m_options.TryGetValue("file-hash-algorithm", out v);
- if (string.IsNullOrEmpty(v))
- return DEFAULT_FILE_HASH_ALGORITHM;
-
- return v;
- }
- }
+ public string FileHashAlgorithm => GetString("file-hash-algorithm", DEFAULT_FILE_HASH_ALGORITHM);
///
/// Gets a value indicating whether local blocks usage should be used for restore.
///
/// true if no local blocks; otherwise, false.
- public bool UseLocalBlocks
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "restore-with-local-blocks"); }
- }
+ public bool UseLocalBlocks => GetBool("restore-with-local-blocks");
///
/// Gets a flag indicating if the local database should not be used
///
/// true if no local db is used; otherwise, false.
- public bool NoLocalDb
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "no-local-db"); }
- }
+ public bool NoLocalDb => GetBool("no-local-db");
///
/// Gets a flag indicating if the local database should not be used
///
/// true if no local db is used; otherwise, false.
- public bool DontCompressRestorePaths
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "dont-compress-restore-paths"); }
- }
+ public bool DontCompressRestorePaths => GetBool("dont-compress-restore-paths");
///
/// Gets a flag indicating if block hashes are checked before being applied
///
/// true if block hashes are checked; otherwise, false.
- public bool FullBlockVerification
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "full-block-verification"); }
- }
+ public bool FullBlockVerification => GetBool("full-block-verification");
///
/// Gets a flag indicating if the repair process will only restore paths
///
/// true if only paths are restored; otherwise, false.
- public bool RepairOnlyPaths
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "repair-only-paths"); }
- }
+ public bool RepairOnlyPaths => GetBool("repair-only-paths");
///
/// Gets a flag indicating if the repair process will ignore outdated database
///
/// true if repair process will ignore outdated database; otherwise, false.
- public bool RepairIgnoreOutdatedDatabase
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "repair-ignore-outdated-database"); }
- }
+ public bool RepairIgnoreOutdatedDatabase => GetBool("repair-ignore-outdated-database");
///
/// Gets a flag indicating if the repair process will always use blocks
///
/// true if repair process always use blocks; otherwise, false.
- public bool RepairForceBlockUse
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "repair-force-block-use"); }
- }
+ public bool RepairForceBlockUse => GetBool("repair-force-block-use");
///
/// Gets a flag indicating whether the VACUUM operation should ever be run automatically.
///
- public bool AutoVacuum
- {
- get { return GetBool("auto-vacuum"); }
- }
+ public bool AutoVacuum => GetBool("auto-vacuum");
///
/// Gets the minimum time that must elapse after last vacuum before running next automatic vacuum
///
- public TimeSpan AutoVacuumInterval
- {
- get
- {
- if (!m_options.ContainsKey("auto-vacuum-interval") || string.IsNullOrEmpty(m_options["auto-vacuum-interval"]))
- return TimeSpan.Zero;
- else
- return Library.Utility.Timeparser.ParseTimeSpan(m_options["auto-vacuum-interval"]);
- }
- }
+ public TimeSpan AutoVacuumInterval => Library.Utility.Utility.ParseTimespanOption(m_options, "auto-vacuum-interval", "0s");
///
/// Gets a flag indicating if the local filescanner should be disabled
///
/// true if the filescanner should be disabled; otherwise, false.
- public bool DisableFileScanner
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "disable-file-scanner"); }
- }
+ public bool DisableFileScanner => GetBool("disable-file-scanner");
///
/// Gets a flag indicating if the filelist consistency checks should be disabled
///
/// true if the filelist consistency checks should be disabled; otherwise, false.
- public bool DisableFilelistConsistencyChecks
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "disable-filelist-consistency-checks"); }
- }
+ public bool DisableFilelistConsistencyChecks => GetBool("disable-filelist-consistency-checks");
///
/// Gets a flag indicating whether the backup should be disabled when on battery power.
///
/// true if the backup should be disabled when on battery power; otherwise, false.
- public bool DisableOnBattery
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "disable-on-battery"); }
- }
+ public bool DisableOnBattery => GetBool("disable-on-battery");
///
/// Gets a value indicating if missing dblock files are attempted created
///
- public bool RebuildMissingDblockFiles
- {
- get { return GetBool("rebuild-missing-dblock-files"); }
- }
+ public bool RebuildMissingDblockFiles => GetBool("rebuild-missing-dblock-files");
///
/// Gets a value indicating if partial dblock recovery is disabled
///
- public bool DisablePartialDblockRecovery
- {
- get { return GetBool("disable-partial-dblock-recovery"); }
- }
+ public bool DisablePartialDblockRecovery => GetBool("disable-partial-dblock-recovery");
///
/// Gets a value indicating if missing metadata is replaced with empty content on purge-broken-files
///
- public bool DisableReplaceMissingMetadata
- {
- get { return GetBool("disable-replace-missing-metadata"); }
- }
+ public bool DisableReplaceMissingMetadata => GetBool("disable-replace-missing-metadata");
///
/// Gets the threshold for when log data should be cleaned
@@ -1955,86 +1412,30 @@ namespace Duplicati.Library.Main
{
get
{
- string pts;
- if (!m_options.TryGetValue("log-retention", out pts))
- pts = DEFAULT_LOG_RETENTION;
-
- return Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true);
+ var value = GetString("log-retention", DEFAULT_LOG_RETENTION);
+ return Library.Utility.Timeparser.ParseTimeInterval(value, DateTime.Now, true);
}
}
-
///
/// Gets the number of concurrent threads
///
- public int ConcurrencyMaxThreads
- {
- get
- {
- string value;
- if (!m_options.TryGetValue("concurrency-max-threads", out value))
- value = null;
-
- if (string.IsNullOrEmpty(value))
- return 0;
- else
- return int.Parse(value);
- }
- }
+ public int ConcurrencyMaxThreads => GetInt("concurrency-max-threads", 0);
///
/// Gets the number of concurrent block hashers
///
- public int ConcurrencyBlockHashers
- {
- get
- {
- string value;
- if (!m_options.TryGetValue("concurrency-block-hashers", out value))
- value = null;
-
- if (string.IsNullOrEmpty(value))
- return DEFAULT_BLOCK_HASHERS;
- else
- return Math.Max(1, int.Parse(value));
- }
- }
+ public int ConcurrencyBlockHashers => Math.Max(1, GetInt("concurrency-block-hashers", DEFAULT_BLOCK_HASHERS));
///
/// Gets the number of concurrent block hashers
///
- public int ConcurrencyCompressors
- {
- get
- {
- string value;
- if (!m_options.TryGetValue("concurrency-compressors", out value))
- value = null;
-
- if (string.IsNullOrEmpty(value))
- return DEFAULT_COMPRESSORS;
- else
- return Math.Max(1, int.Parse(value));
- }
- }
+ public int ConcurrencyCompressors => Math.Max(1, GetInt("concurrency-compressors", DEFAULT_COMPRESSORS));
///
/// Gets the number of concurrent file processors
///
- public int ConcurrencyFileprocessors
- {
- get
- {
- string value;
- if (!m_options.TryGetValue("concurrency-fileprocessors", out value))
- value = null;
-
- if (string.IsNullOrEmpty(value))
- return DEFAULT_FILE_PROCESSORS;
- else
- return Math.Max(1, int.Parse(value));
- }
- }
+ public int ConcurrencyFileprocessors => Math.Max(1, GetInt("concurrency-fileprocessors", DEFAULT_FILE_PROCESSORS));
///
/// Gets a lookup table with compression hints, the key is the file extension with the leading period
@@ -2047,10 +1448,7 @@ namespace Duplicati.Library.Main
{
var hints = new Dictionary(StringComparer.OrdinalIgnoreCase); // Ignore file system case sensitivity, since file extensions case rarely indicates type
- string file;
- if (!m_options.TryGetValue("compression-extension-file", out file))
- file = DEFAULT_COMPRESSED_EXTENSION_FILE;
-
+ var file = GetString("compression-extension-file", DEFAULT_COMPRESSED_EXTENSION_FILE);
if (!string.IsNullOrEmpty(file) && System.IO.File.Exists(file))
foreach (var _line in Library.Utility.Utility.ReadFileWithDefaultEncoding(file).Split('\n'))
{
@@ -2073,20 +1471,7 @@ namespace Duplicati.Library.Main
///
/// Gets the CPU intensity level indicating target CPU utilization. 1 is the lowest, 10 is the highest. Default is 10.
///
- public int CPUIntensity
- {
- get
- {
- string value;
- if (!m_options.TryGetValue("cpu-intensity", out value))
- value = null;
-
- if (string.IsNullOrEmpty(value))
- return 10;
- else
- return Math.Max(1, Math.Min(10, int.Parse(value)));
- }
- }
+ public int CPUIntensity => Math.Max(1, Math.Min(10, GetInt("cpu-intensity", 10)));
///
/// Gets a compression hint from a filename
@@ -2095,8 +1480,7 @@ namespace Duplicati.Library.Main
/// The compression hint
public CompressionHint GetCompressionHintFromFilename(string filename)
{
- CompressionHint h;
- if (!CompressionHints.TryGetValue(System.IO.Path.GetExtension(filename), out h))
+ if (!CompressionHints.TryGetValue(System.IO.Path.GetExtension(filename), out var h))
return CompressionHint.Default;
return h;
}
@@ -2104,7 +1488,7 @@ namespace Duplicati.Library.Main
///
/// Gets a list of modules, the key indicates if they are loaded
///
- public List> LoadedModules { get { return m_loadedModules; } }
+ public List> LoadedModules => m_loadedModules;
///
/// Helper method to extract boolean values.
@@ -2114,10 +1498,59 @@ namespace Duplicati.Library.Main
/// The name of the option to read
/// The interpreted value of the option
private bool GetBool(string name)
+ => Library.Utility.Utility.ParseBoolOption(m_options, name);
+
+ ///
+ /// Helper method to extract string values.
+ ///
+ /// The option name
+ /// The default value
+ /// The value of the option, or the default value if the option is not present or empty
+ [return: NotNullIfNotNull("default")]
+ private string? GetString(string name, string? @default)
{
- return Library.Utility.Utility.ParseBoolOption(m_options, name);
+ var value = m_options.GetValueOrDefault(name);
+ return string.IsNullOrEmpty(value) ? @default : value;
}
+ ///
+ /// Helper method to extract integer values.
+ ///
+ /// The option name
+ /// The default value
+ /// The value of the option, or the default value if the option is not present or empty
+ private int GetInt(string name, int @default)
+ => Library.Utility.Utility.ParseIntOption(m_options, name, @default);
+
+ ///
+ /// Helper method to extract long values.
+ ///
+ /// The option name
+ /// The default value
+ /// The value of the option, or the default value if the option is not present or empty
+ private long GetLong(string name, long @default)
+ => Library.Utility.Utility.ParseLongOption(m_options, name, @default);
+
+ ///
+ /// Helper method to extract enum values.
+ ///
+ /// The enum type
+ /// The option name
+ /// The default value
+ /// The value of the option, or the default value if the option is not present or empty
+ private T GetEnum(string name, T @default) where T : struct, Enum
+ => Library.Utility.Utility.ParseEnumOption(m_options, name, @default);
+
+ ///
+ /// Helper method to extract size values.
+ ///
+ /// The option name
+ /// The default unit
+ /// The default value
+ /// The value of the option, or the default value if the option is not present or empty
+ private long GetSize(string name, string unit, string @default)
+ => Library.Utility.Utility.ParseSizeOption(m_options, name, unit, @default);
+
///
/// Gets the maximum number of data blocks to keep in the cache. If set to 0, the cache is effictively disabled, but some is still kept for bookkeeping.
///
@@ -2125,11 +1558,7 @@ namespace Duplicati.Library.Main
{
get
{
- if (!m_options.TryGetValue("restore-cache-max", out string v))
- v = DEFAULT_RESTORE_CACHE_MAX;
-
- long max_cache = Sizeparser.ParseSize(v, "mb");
-
+ var max_cache = GetSize("restore-cache-max", "mb", DEFAULT_RESTORE_CACHE_MAX);
if (max_cache > 0 && max_cache < Blocksize)
throw new ArgumentOutOfRangeException(nameof(max_cache), string.Format("The maximum cache size cannot be less than the blocksize if not explicitly 0: {0} < {1}", max_cache, Blocksize));
@@ -2144,11 +1573,9 @@ namespace Duplicati.Library.Main
{
get
{
- m_options.TryGetValue("restore-cache-evict", out string s);
+ m_options.TryGetValue("restore-cache-evict", out var s);
if (string.IsNullOrEmpty(s))
- {
return DEFAULT_RESTORE_CACHE_EVICT / 100f;
- }
int percentage;
try
@@ -2172,108 +1599,42 @@ namespace Duplicati.Library.Main
///
/// Gets the number of file processors to use in the restore process
///
- public int RestoreFileProcessors
- {
- get
- {
- if (!m_options.TryGetValue("restore-file-processors", out string v))
- v = null;
-
- if (string.IsNullOrEmpty(v))
- return DEFAULT_RESTORE_FILE_PROCESSORS;
- else
- return int.Parse(v);
- }
- }
+ public int RestoreFileProcessors => GetInt("restore-file-processors", DEFAULT_RESTORE_FILE_PROCESSORS);
///
/// Gets whether to use the legacy restore method
///
- public bool RestoreLegacy
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "restore-legacy"); }
- }
+ public bool RestoreLegacy => GetBool("restore-legacy");
///
/// Gets whether to preallocate files during restore
///
- public bool RestorePreAllocate
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "restore-pre-allocate"); }
- }
+ public bool RestorePreAllocate => GetBool("restore-pre-allocate");
///
/// Gets the number of volume decryptors to use in the restore process
///
- public int RestoreVolumeDecryptors
- {
- get
- {
- if (!m_options.TryGetValue("restore-volume-decryptors", out string v))
- v = null;
-
- if (string.IsNullOrEmpty(v))
- return DEFAULT_RESTORE_VOLUME_DECRYPTORS;
- else
- return int.Parse(v);
- }
- }
+ public int RestoreVolumeDecryptors => GetInt("restore-volume-decryptors", DEFAULT_RESTORE_VOLUME_DECRYPTORS);
///
/// Gets the number of volume decompressors to use in the restore process
///
- public int RestoreVolumeDecompressors
- {
- get
- {
- if (!m_options.TryGetValue("restore-volume-decompressors", out string v))
- v = null;
-
- if (string.IsNullOrEmpty(v))
- return DEFAULT_RESTORE_VOLUME_DECOMPRESSORS;
- else
- return int.Parse(v);
- }
- }
+ public int RestoreVolumeDecompressors => GetInt("restore-volume-decompressors", DEFAULT_RESTORE_VOLUME_DECOMPRESSORS);
///
/// Gets the number of volume downloaders to use in the restore process
///
- public int RestoreVolumeDownloaders
- {
- get
- {
- if (!m_options.TryGetValue("restore-volume-downloaders", out string v))
- v = null;
+ public int RestoreVolumeDownloaders => GetInt("restore-volume-downloaders", DEFAULT_RESTORE_VOLUME_DOWNLOADERS);
- if (string.IsNullOrEmpty(v))
- return DEFAULT_RESTORE_VOLUME_DOWNLOADERS;
- else
- return int.Parse(v);
- }
- }
-
- public int RestoreChannelBufferSize
- {
- get
- {
- if (!m_options.TryGetValue("restore-channel-buffer-size", out string v))
- v = null;
-
- if (string.IsNullOrEmpty(v))
- return DEFAULT_RESTORE_CHANNEL_BUFFER_SIZE;
- else
- return int.Parse(v);
- }
- }
+ ///
+ /// Gets the size of the buffer used for the restore channel
+ ///
+ public int RestoreChannelBufferSize => GetInt("restore-channel-buffer-size", DEFAULT_RESTORE_CHANNEL_BUFFER_SIZE);
///
/// Toggles whether internal profiling is enabled and should be logged.
///
- public bool InternalProfiling
- {
- get { return Library.Utility.Utility.ParseBoolOption(m_options, "internal-profiling"); }
- }
+ public bool InternalProfiling => GetBool("internal-profiling");
///
/// Gets the size of file-blocks
@@ -2282,21 +1643,17 @@ namespace Duplicati.Library.Main
{
get
{
- if (!m_options.TryGetValue("sqlite-page-cache", out var tmp))
- tmp = DEFAULT_SQLITE_PAGE_CACHE_SIZE;
-
- var pagesize = Sizeparser.ParseSize(tmp, "kb");
- if (pagesize <= SQLiteLoader.MINIMUM_SQLITE_PAGE_CACHE_SIZE)
- return 0;
-
- return pagesize;
+ var pagesize = GetSize("sqlite-page-cache", "kb", DEFAULT_SQLITE_PAGE_CACHE_SIZE);
+ return pagesize <= SQLiteLoader.MINIMUM_SQLITE_PAGE_CACHE_SIZE
+ ? 0
+ : pagesize;
}
}
///
/// Ignores the update if the version already exists in the database.
///
public bool IgnoreUpdateIfVersionExists
- => Library.Utility.Utility.ParseBoolOption(m_options, "ignore-update-if-version-exists");
+ => GetBool("ignore-update-if-version-exists");
///
/// Class for handling a single RetentionPolicy timeframe-interval-pair
diff --git a/Duplicati/Library/Utility/FilterExpression.cs b/Duplicati/Library/Utility/FilterExpression.cs
index ea23eba06..266a3b562 100644
--- a/Duplicati/Library/Utility/FilterExpression.cs
+++ b/Duplicati/Library/Utility/FilterExpression.cs
@@ -1,29 +1,32 @@
-// Copyright (C) 2025, The Duplicati Team
-// https://duplicati.com, hello@duplicati.com
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the "Software"),
-// to deal in the Software without restriction, including without limitation
-// the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and/or sell copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// Copyright (C) 2025, The Duplicati Team
+// https://duplicati.com, hello@duplicati.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+#nullable enable
+
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Globalization;
+using System.Diagnostics.CodeAnalysis;
namespace Duplicati.Library.Utility
{
@@ -71,11 +74,11 @@ namespace Duplicati.Library.Utility
///
/// The filter string
///
- public readonly string Filter;
+ public readonly string? Filter;
///
/// The regular expression version of the filter
///
- public readonly Regex Regexp;
+ public readonly Regex? Regexp;
///
/// The single wildcard character (DOS style)
@@ -104,7 +107,7 @@ namespace Duplicati.Library.Utility
/// Initializes a new instance of the struct.
///
/// The filter string to use.
- public FilterEntry(string filter)
+ public FilterEntry(string? filter)
{
if (string.IsNullOrEmpty(filter))
{
@@ -155,8 +158,7 @@ namespace Duplicati.Library.Utility
private static Regex GetFilterGroupRegex(string filterGroupName)
{
FilterGroup filterGroup = FilterGroups.ParseFilterList(filterGroupName, FilterGroup.None);
- Regex result;
- if (FilterEntry.filterGroupRegexCache.TryGetValue(filterGroup, out result))
+ if (FilterEntry.filterGroupRegexCache.TryGetValue(filterGroup, out var result))
{
return result;
}
@@ -290,17 +292,17 @@ namespace Duplicati.Library.Utility
case FilterType.Simple:
return string.Equals(this.Filter, path, Library.Utility.Utility.ClientFilenameStringComparison);
case FilterType.Wildcard:
- return IsWildcardMatch(!Utility.IsFSCaseSensitive ? path.ToUpper(CultureInfo.InvariantCulture) : path, this.Filter);
+ return IsWildcardMatch(!Utility.IsFSCaseSensitive ? path.ToUpper(CultureInfo.InvariantCulture) : path, this.Filter!);
case FilterType.Regexp:
case FilterType.Group:
- var m = this.Regexp.Match(path);
+ var m = this.Regexp!.Match(path);
return m.Success && m.Length == path.Length;
default:
return false;
}
}
- public override string ToString()
+ public override string? ToString()
{
switch (this.Type)
{
@@ -319,7 +321,7 @@ namespace Duplicati.Library.Utility
///
/// The internal list of expressions
///
- private readonly List m_filters;
+ private readonly List? m_filters;
///
/// Gets the type of the filter
@@ -354,7 +356,7 @@ namespace Duplicati.Library.Utility
///
/// The match result
/// The filter that matched
- public bool Matches(string path, out bool result, out IFilter match)
+ public bool Matches(string path, out bool result, out IFilter? match)
{
result = false;
if (this.Type == FilterType.Empty)
@@ -363,7 +365,7 @@ namespace Duplicati.Library.Utility
return false;
}
- if (m_filters.Any(x => x.Matches(path)))
+ if (m_filters != null && m_filters.Any(x => x.Matches(path)))
{
match = this;
result = this.Result;
@@ -385,7 +387,7 @@ namespace Duplicati.Library.Utility
/// Creates a new instance, representing an empty filter.
///
public FilterExpression()
- : this((IEnumerable)null, true)
+ : this((IEnumerable?)null, true)
{
}
@@ -403,7 +405,7 @@ namespace Duplicati.Library.Utility
///
/// The filter string that represents the filter
/// Return value of in case of match
- public FilterExpression(IEnumerable filter, bool result = true)
+ public FilterExpression(IEnumerable? filter, bool result = true)
{
this.Result = result;
@@ -414,10 +416,9 @@ namespace Duplicati.Library.Utility
}
m_filters = Compact(
- (from n in filter
- let nx = new FilterEntry(n)
- where nx.Type != FilterType.Empty
- select nx)
+ filter
+ .Select(x => new FilterEntry(x))
+ .Where(x => x.Type != FilterType.Empty)
);
if (m_filters.Count == 0)
@@ -426,7 +427,7 @@ namespace Duplicati.Library.Utility
this.Type = m_filters.Max((a) => a.Type);
}
- private static IEnumerable Expand(string filter)
+ private static IEnumerable? Expand(string filter)
{
if (string.IsNullOrWhiteSpace(filter))
return null;
@@ -459,7 +460,7 @@ namespace Duplicati.Library.Utility
// since that would make their names much more difficult to interpret on the command line.
if (f.Type == FilterType.Simple || f.Type == FilterType.Wildcard || f.Type == FilterType.Group)
r.Add(f);
- else if (f.Type != FilterType.Empty)
+ else if (f.Type != FilterType.Empty && f.Regexp != null)
{
combined.Append(f.Regexp.ToString());
first = true;
@@ -518,12 +519,12 @@ namespace Duplicati.Library.Utility
/// The filter to examine
/// True if the filter contains includes, false otherwise.
/// True if the filter contains excludes, false otherwise.
- public static void AnalyzeFilters(IFilter filter, out bool includes, out bool excludes)
+ public static void AnalyzeFilters(IFilter? filter, out bool includes, out bool excludes)
{
includes = false;
excludes = false;
- Tuple cacheLookup = null;
+ Tuple? cacheLookup = null;
// Check for cached results
if (filter != null)
@@ -537,7 +538,7 @@ namespace Duplicati.Library.Utility
// Figure out what components are in the filter
if (cacheLookup == null)
{
- var q = new Queue();
+ var q = new Queue();
q.Enqueue(filter);
while (q.Count > 0)
@@ -564,7 +565,8 @@ namespace Duplicati.Library.Utility
{
if (_matchFallbackLookup.Count > 10)
_matchFallbackLookup.Remove(_matchFallbackLookup.Keys.Skip(new Random().Next(0, _matchFallbackLookup.Count)).First());
- _matchFallbackLookup[filter] = new Tuple(includes, excludes);
+ if (filter != null)
+ _matchFallbackLookup[filter] = new Tuple(includes, excludes);
}
}
}
@@ -575,7 +577,7 @@ namespace Duplicati.Library.Utility
/// The filter to evaluate
/// The path to evaluate
/// The filter that matched
- public static bool Matches(IFilter filter, string path, out IFilter match)
+ public static bool Matches(IFilter filter, string path, out IFilter? match)
{
if (filter == null || filter.Empty)
{
@@ -583,14 +585,11 @@ namespace Duplicati.Library.Utility
return true;
}
- bool result;
- if (filter.Matches(path, out result, out match))
+ if (filter.Matches(path, out bool result, out match))
return result;
- bool includes;
- bool excludes;
- AnalyzeFilters(filter, out includes, out excludes);
+ AnalyzeFilters(filter, out bool includes, out bool excludes);
match = null;
// We have only include filters, we exclude files by default
@@ -611,16 +610,16 @@ namespace Duplicati.Library.Utility
///
/// First.
/// Second.
- public static FilterExpression Combine(FilterExpression first, FilterExpression second)
+ public static FilterExpression? Combine(FilterExpression? first, FilterExpression? second)
{
- if (first == null || first.Empty)
+ if (first?.m_filters == null || first.Empty)
return second;
- if (second == null || second.Empty)
+ if (second?.m_filters == null || second.Empty)
return first;
if (first.Result != second.Result)
throw new ArgumentException("Both filters must have the same result property");
- return new FilterExpression(first.m_filters.Union(second.m_filters).Select(x => x.ToString()), first.Result);
+ return new FilterExpression(first.m_filters.Union(second.m_filters).Select(x => x.ToString()).WhereNotNull(), first.Result);
}
///
@@ -628,7 +627,7 @@ namespace Duplicati.Library.Utility
///
/// First.
/// Second.
- public static IFilter Combine(IFilter first, IFilter second)
+ public static IFilter? Combine(IFilter? first, IFilter? second)
{
if (second == null || second.Empty)
return first;
@@ -663,15 +662,13 @@ namespace Duplicati.Library.Utility
/// Serializes the filter instance into a list of strings
/// that can be passed to the deserialize method
///
- public string[] Serialize()
+ [return: NotNullIfNotNull("m_filters")]
+ public string[]? Serialize()
{
- if (this.Empty)
+ if (this.Empty || m_filters == null)
return null;
- return
- (from n in m_filters
- select $"{(this.Result ? "+" : "-")}{n.ToString()}"
- ).ToArray();
+ return m_filters.Select(n => $"{(this.Result ? "+" : "-")}{n.ToString()}").ToArray();
}
///
@@ -692,7 +689,7 @@ namespace Duplicati.Library.Utility
var f = work.Pop();
if (f is FilterExpression expression)
- res = res.Union(expression.Serialize());
+ res = res.Union(expression.Serialize() ?? []);
else if (f is JoinedFilterExpression filterExpression)
{
work.Push(filterExpression.Second);
@@ -710,12 +707,12 @@ namespace Duplicati.Library.Utility
/// prefixed with either minus or plus
///
/// The filters to deserialize from.
- public static IFilter Deserialize(string[] filters)
+ public static IFilter? Deserialize(string[] filters)
{
if (filters == null || filters.Length == 0)
return null;
- IFilter res = null;
+ IFilter? res = null;
foreach (var n in filters)
{
bool include;
@@ -737,7 +734,7 @@ namespace Duplicati.Library.Utility
///
/// The log filter.
/// The filter string to parse.
- public static IFilter ParseLogFilter(string value)
+ public static IFilter ParseLogFilter(string? value)
{
if (string.IsNullOrWhiteSpace(value))
return new FilterExpression();
@@ -745,7 +742,8 @@ namespace Duplicati.Library.Utility
return value
.Split(new char[] { System.IO.Path.PathSeparator, ':', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(StringToIFilter)
- .Aggregate(FilterExpression.Combine);
+ .Cast()
+ .Aggregate((a, b) => FilterExpression.Combine(a, b)!);
}
///
diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs
index 1df49958a..d2b9c849a 100644
--- a/Duplicati/Library/Utility/Utility.cs
+++ b/Duplicati/Library/Utility/Utility.cs
@@ -789,8 +789,33 @@ namespace Duplicati.Library.Utility
/// default value
///
public static int ParseIntOption(IReadOnlyDictionary options, string value, int @default)
+ => options.TryGetValue(value, out var opt) && int.TryParse(opt ?? string.Empty, out var result) ? result : @default;
+
+ ///
+ /// Parses an option with long value, returning the default value if the option is not found or cannot be parsed
+ ///
+ /// The set of options to look for the setting in
+ /// The value to look for in the settings
+ /// default value
+ ///
+ public static long ParseLongOption(IReadOnlyDictionary options, string value, long @default)
+ => options.TryGetValue(value, out var opt) && long.TryParse(opt ?? string.Empty, out var result) ? result : @default;
+
+ ///
+ /// Parses a size option from the option set, returning the default value if the option is not found or cannot be parsed
+ ///
+ /// The set of options to look for the setting in
+ /// The value to look for in the settings
+ /// Multiplier to use if the value does not have a multiplier
+ /// The default value to return if there are no matches.
+ /// The parsed or default size value.
+ public static long ParseSizeOption(IReadOnlyDictionary options, string value, string defaultMultiplier, string @default)
{
- return options.TryGetValue(value, out var opt) && int.TryParse(opt ?? string.Empty, out var result) ? result : @default;
+ var opt = options.GetValueOrDefault(value);
+ if (string.IsNullOrWhiteSpace(opt))
+ opt = @default;
+
+ return Sizeparser.ParseSize(opt, defaultMultiplier);
}
///