diff --git a/Duplicati/Library/Interface/USNJournalDataEntry.cs b/Duplicati/Library/Interface/USNJournalDataEntry.cs
index 7970b99b5..12d1c4f99 100644
--- a/Duplicati/Library/Interface/USNJournalDataEntry.cs
+++ b/Duplicati/Library/Interface/USNJournalDataEntry.cs
@@ -28,21 +28,21 @@ namespace Duplicati.Library.Interface
///
/// Volume of journal
///
- public string Volume;
+ public string Volume { get; set; }
///
/// Journal ID, as recorded in the journal
///
- public long JournalId;
+ public long JournalId { get; set; }
///
/// USN to start at for next fileset (backup)
///
- public long NextUsn;
+ public long NextUsn { get; set; }
///
/// A hash value representing the active exclusion filter
///
- public string ConfigHash;
+ public string ConfigHash { get; set; }
}
}
diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs
index b7c57a45e..3c5eabd2c 100644
--- a/Duplicati/Library/Main/Operation/BackupHandler.cs
+++ b/Duplicati/Library/Main/Operation/BackupHandler.cs
@@ -420,7 +420,7 @@ namespace Duplicati.Library.Main.Operation
{
try
{
- var journalService = new UsnJournalService(UsnErrorHandler, () =>
+ var journalService = new UsnJournalService(snapshot, UsnErrorHandler, () =>
{
if (m_result.TaskControlRendevouz() == TaskControlState.Stop)
throw new OperationCanceledException();
@@ -436,7 +436,7 @@ namespace Duplicati.Library.Main.Operation
// filter sources using USN journal, to obtain a sub-set of files / folders that
// may have been modified
// ReSharper disable once PossibleMultipleEnumeration
- var modifiedSources = journalService.FilterSources(sources, configHash, journalData);
+ var modifiedSources = journalService.FilterSources(sources, filterhandler.AttributeFilter, configHash, journalData);
// scan modified sources for modifications
if (modifiedSources.Files.Any() || modifiedSources.Folders.Any())
@@ -477,6 +477,8 @@ namespace Duplicati.Library.Main.Operation
else
{
// add full previous fileset, as nothing has changed
+ //TODO: possible optimization is to somehow skip this step if compacting is enabled,
+ // as all this informatino will be removed anyway.
m_database.AppendFilesFromPreviousSet(m_transaction);
}
diff --git a/Duplicati/Library/Main/Operation/SystemInfoHandler.cs b/Duplicati/Library/Main/Operation/SystemInfoHandler.cs
index 56fd95bbd..14935a15b 100644
--- a/Duplicati/Library/Main/Operation/SystemInfoHandler.cs
+++ b/Duplicati/Library/Main/Operation/SystemInfoHandler.cs
@@ -30,7 +30,7 @@ namespace Duplicati.Library.Main.Operation
public static IEnumerable GetSystemInfo()
{
- yield return string.Format("Duplicati: {0} ({1})", Duplicati.Library.Utility.Utility.getEntryAssembly().FullName, System.Reflection.Assembly.GetExecutingAssembly().FullName);
+ yield return string.Format("Duplicati: {0} ({1})", Duplicati.Library.Utility.Utility.GetEntryAssembly().FullName, System.Reflection.Assembly.GetExecutingAssembly().FullName);
yield return string.Format("Autoupdate urls: {0}", string.Join(";", Duplicati.Library.AutoUpdater.AutoUpdateSettings.URLs));
yield return string.Format("Update folder: {0}", Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLDIR);
diff --git a/Duplicati/Library/Main/Options.cs b/Duplicati/Library/Main/Options.cs
index 1e9f0eea4..351d90d16 100644
--- a/Duplicati/Library/Main/Options.cs
+++ b/Duplicati/Library/Main/Options.cs
@@ -395,7 +395,7 @@ namespace Duplicati.Library.Main
{
get
{
- return System.IO.Path.GetFileNameWithoutExtension(Library.Utility.Utility.getEntryAssembly().Location);
+ return System.IO.Path.GetFileNameWithoutExtension(Library.Utility.Utility.GetEntryAssembly().Location);
}
}
diff --git a/Duplicati/Library/Modules/Builtin/ReportHelper.cs b/Duplicati/Library/Modules/Builtin/ReportHelper.cs
index 4f5f145c3..1a9df0b35 100644
--- a/Duplicati/Library/Modules/Builtin/ReportHelper.cs
+++ b/Duplicati/Library/Modules/Builtin/ReportHelper.cs
@@ -314,7 +314,7 @@ namespace Duplicati.Library.Modules.Builtin
if (m_options.ContainsKey("backup-name"))
extra["backup-name"] = m_options["backup-name"];
else
- extra["backup-name"] = System.IO.Path.GetFileNameWithoutExtension(Duplicati.Library.Utility.Utility.getEntryAssembly().Location);
+ extra["backup-name"] = System.IO.Path.GetFileNameWithoutExtension(Duplicati.Library.Utility.Utility.GetEntryAssembly().Location);
}
foreach (KeyValuePair kv in m_options)
@@ -344,7 +344,7 @@ namespace Duplicati.Library.Modules.Builtin
input = Regex.Replace(input, "\\%" + kv.Key + "\\%", kv.Value ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!m_options.ContainsKey("backup-name"))
- input = Regex.Replace(input, "\\%backup-name\\%", System.IO.Path.GetFileNameWithoutExtension(Duplicati.Library.Utility.Utility.getEntryAssembly().Location) ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
+ input = Regex.Replace(input, "\\%backup-name\\%", System.IO.Path.GetFileNameWithoutExtension(Duplicati.Library.Utility.Utility.GetEntryAssembly().Location) ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
input = Regex.Replace(input, "\\%[^\\%]+\\%", "");
return input;
diff --git a/Duplicati/Library/Modules/Builtin/RunScript.cs b/Duplicati/Library/Modules/Builtin/RunScript.cs
index 25db9a530..b787a088c 100644
--- a/Duplicati/Library/Modules/Builtin/RunScript.cs
+++ b/Duplicati/Library/Modules/Builtin/RunScript.cs
@@ -210,7 +210,7 @@ namespace Duplicati.Library.Modules.Builtin
psi.EnvironmentVariables["DUPLICATI__" + kv.Key.Replace('-', '_')] = kv.Value;
if (!options.ContainsKey("backup-name"))
- psi.EnvironmentVariables["DUPLICATI__backup_name"] = System.IO.Path.GetFileNameWithoutExtension(Duplicati.Library.Utility.Utility.getEntryAssembly().Location);
+ psi.EnvironmentVariables["DUPLICATI__backup_name"] = System.IO.Path.GetFileNameWithoutExtension(Duplicati.Library.Utility.Utility.GetEntryAssembly().Location);
psi.EnvironmentVariables["DUPLICATI__EVENTNAME"] = eventname;
psi.EnvironmentVariables["DUPLICATI__OPERATIONNAME"] = operationname;
diff --git a/Duplicati/Library/Snapshots/IChangeJournalService.cs b/Duplicati/Library/Snapshots/IChangeJournalService.cs
index 15984f427..b2e3ad79d 100644
--- a/Duplicati/Library/Snapshots/IChangeJournalService.cs
+++ b/Duplicati/Library/Snapshots/IChangeJournalService.cs
@@ -32,10 +32,11 @@ namespace Duplicati.Library.Snapshots
/// change, as specified by journalData.
///
/// Sources to filter
+ /// Filter callback to exclude filtered items
/// A hash value representing current exclusion filter. A full scan is triggered if hash has changed.
/// The journal data of the previous backup (fileset)
/// Filtered sources
- FilterData FilterSources(IEnumerable sources, string filterHash, IEnumerable journalData);
+ FilterData FilterSources(IEnumerable sources, Utility.Utility.EnumerationFilterDelegate filter, string filterHash, IEnumerable journalData);
}
///
diff --git a/Duplicati/Library/Snapshots/SnapshotBase.cs b/Duplicati/Library/Snapshots/SnapshotBase.cs
index f06b13931..130222cb9 100644
--- a/Duplicati/Library/Snapshots/SnapshotBase.cs
+++ b/Duplicati/Library/Snapshots/SnapshotBase.cs
@@ -205,6 +205,8 @@ namespace Duplicati.Library.Snapshots
return Directory.GetFiles(localFolderPath);
}
+ #region IDisposable interface
+
///
public void Dispose()
{
@@ -213,7 +215,9 @@ namespace Duplicati.Library.Snapshots
// Now suppress finalization for this object, since we've already handled our resource cleanup tasks
GC.SuppressFinalize(this);
- }
+ }
+
+ #endregion
///
/// Releases unmanaged and - optionally - managed resources
diff --git a/Duplicati/Library/Snapshots/UsnJournalService.cs b/Duplicati/Library/Snapshots/UsnJournalService.cs
index 45a17fad4..38bb53b9a 100644
--- a/Duplicati/Library/Snapshots/UsnJournalService.cs
+++ b/Duplicati/Library/Snapshots/UsnJournalService.cs
@@ -22,6 +22,7 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using Duplicati.Library.Interface;
using Duplicati.Library.Logging;
@@ -35,16 +36,20 @@ namespace Duplicati.Library.Snapshots
///
private static readonly string LOGTAG = Log.LogTagFromType();
+ private readonly ISnapshotService m_snapshot;
private readonly Utility.Utility.ReportAccessError m_errorCallback;
private readonly Action m_cancelHandler;
///
/// Constructor.
///
+ ///
/// Callback for handling errors
/// Callback for aborting operation. Throw OperationCancelledException() to abort.
- public UsnJournalService(Utility.Utility.ReportAccessError errorCallback, Action cancelHandler)
+ public UsnJournalService(ISnapshotService snapshot, Utility.Utility.ReportAccessError errorCallback,
+ Action cancelHandler)
{
+ m_snapshot = snapshot;
m_errorCallback = errorCallback;
m_cancelHandler = cancelHandler;
}
@@ -52,7 +57,8 @@ namespace Duplicati.Library.Snapshots
#region IChangeJournalService Members
///
- public FilterData FilterSources(IEnumerable sources, string filterHash, IEnumerable journalData)
+ public FilterData FilterSources(IEnumerable sources, Utility.Utility.EnumerationFilterDelegate filter,
+ string filterHash, IEnumerable journalData)
{
// create lookup for journal data
var journalDataDict = journalData.ToDictionary(data => data.Volume);
@@ -100,7 +106,7 @@ namespace Duplicati.Library.Snapshots
var changedFiles = new HashSet(Utility.Utility.ClientFilenameStringComparer);
var changedFolders = new HashSet(Utility.Utility.ClientFilenameStringComparer);
- // iterate over sources and retrieve *renamed* directories
+ // obtain changed files and folders, per volume
foreach (var source in sourcesPerVolume.Value)
{
foreach (var entry in journal.GetChangedFileSystemEntries(source, data.NextUsn))
@@ -116,10 +122,38 @@ namespace Duplicati.Library.Snapshots
}
}
- var reducedFolderList = Utility.Utility.SimplifyFolderList(changedFolders).ToList();
- var reducedFileList = Utility.Utility.GetFilesNotInFolders(changedFiles, reducedFolderList);
- result.Folders.AddRange(reducedFolderList);
- result.Files.UnionWith(reducedFileList);
+ // At this point we have:
+ // - a list of folders (changedFolders) that were possibly modified
+ // - a list of files (changedFiles) that were possibly modified
+ //
+ // With this, we need still need to do the following:
+ //
+ // 1. Simplify the folder list, such that it only contains the parent-most entries
+ // (eg. { "C:\A\B\", "C:\A\B\C\", "C:\A\B\D\E\" } => { "C:\A\B\" }
+ var simplifiedFolders = Utility.Utility.SimplifyFolderList(changedFolders).ToList();
+
+ // 2. Check the simplified folders, and their parent folders against the exclusion filter.
+ // This is needed because the filter may exclude "C:\A\", but this won't match the more
+ // specific "C:\A\B\" in our list, even though it's meant to be excluded.
+ // The reason why the filter doesn't exclude it is because during a regular (non-USN) full scan,
+ // FilterHandler.EnumerateFilesAndFolders() works top-down, and won't even enumerate child
+ // folders.
+ var cache = new HashSet(); // cache to speed up processing
+ result.Folders.AddRange(FilterExcludedFolders(simplifiedFolders, filter, cache));
+
+ // 3. Our list of files may contain entries inside one of the simplified folders (from step 1., above).
+ // Since that folder is going to be fully scanned, those files can be removed.
+ // Note: it would be wrong to use the result from step 2. as the folder list! The entries removed
+ // between 1. and 2. are *excluded* folders, and files below them are to be *excluded*, too.
+ var simplifiedFiles = Utility.Utility.GetFilesNotInFolders(changedFiles, simplifiedFolders);
+
+ // 4. The simplified file list still needs to be checked against the exclusion filter, as it
+ // may contain entries excluded due to attributes, but also because they are below excluded
+ // folders, which themselves aren't in the folder list from step 1.
+ // Note that the simplified file list may contain entries that have been deleted! They need to
+ // be kept in the list (unless excluded by the filter) in order for the backup handler to record their
+ // deletion.
+ result.Files.UnionWith(FilterExcludedFiles(simplifiedFiles, filter, cache));
}
}
catch (UsnJournalSoftFailureException)
@@ -138,6 +172,125 @@ namespace Duplicati.Library.Snapshots
return result;
}
+ ///
+ /// Filter supplied files, removing any files which itself, or one
+ /// of its parent folders, is excluded by the filter.
+ ///
+ /// Files to filter
+ /// Exclusion filter
+ /// Cache of excluded folders (optional)
+ /// Filtered files
+ private IEnumerable FilterExcludedFiles(IEnumerable files,
+ Utility.Utility.EnumerationFilterDelegate filter, ISet cache = null)
+ {
+ var result = new List();
+
+ foreach (var file in files)
+ {
+ var attr = m_snapshot.FileExists(file) ? m_snapshot.GetAttributes(file) : FileAttributes.Normal;
+ try
+ {
+ if (!filter(file, file, attr))
+ continue;
+
+ if (!IsFolderOrAncestorsExcluded(Utility.Utility.GetParent(file, true), filter, cache))
+ {
+ result.Add(file);
+ }
+ }
+ catch (System.Threading.ThreadAbortException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ m_errorCallback?.Invoke(file, file, ex);
+ filter(file, file, attr | Utility.Utility.ATTRIBUTE_ERROR);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Filter supplied folders, removing any folder which itself, or one
+ /// of its ancestors, is excluded by the filter.
+ ///
+ /// Folder to filter
+ /// Exclusion filter
+ /// Cache of excluded folders (optional)
+ /// Filtered folders
+ private IEnumerable FilterExcludedFolders(IEnumerable folders,
+ Utility.Utility.EnumerationFilterDelegate filter, ISet cache = null)
+ {
+ var result = new List();
+
+ foreach (var folder in folders)
+ {
+ try
+ {
+ if (!IsFolderOrAncestorsExcluded(folder, filter, cache))
+ {
+ result.Add(folder);
+ }
+ }
+ catch (System.Threading.ThreadAbortException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ m_errorCallback?.Invoke(folder, folder, ex);
+ filter(folder, folder, FileAttributes.Directory | Utility.Utility.ATTRIBUTE_ERROR);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Tests if specified folder, or any of its ancestors, is excluded by the filter
+ ///
+ /// Folder to test
+ /// Filter
+ /// Cache of excluded folders (optional)
+ /// True if excluded, false otherwise
+ private bool IsFolderOrAncestorsExcluded(string folder, Utility.Utility.EnumerationFilterDelegate filter, ISet cache = null)
+ {
+ List parents = null;
+ while (folder != null)
+ {
+ // first check cache
+ if (cache != null)
+ {
+ if (cache.Contains(folder))
+ break; // hit!
+
+ // remember folder for cache
+ if (parents == null)
+ {
+ parents = new List(); // create on-demand
+ }
+ parents.Add(folder);
+ }
+
+ var attr = m_snapshot.FolderExists(folder) ? m_snapshot.GetAttributes(folder) : FileAttributes.Directory;
+
+ if (!filter(folder, folder, attr))
+ break; // excluded
+
+ folder = Utility.Utility.GetParent(folder, true);
+ }
+
+ if (folder != null && parents != null)
+ {
+ // update cache
+ cache.UnionWith(parents);
+ }
+
+ return folder != null;
+ }
+
///
/// Add ALL sources for volume to result set
///
diff --git a/Duplicati/Library/Utility/TempFile.cs b/Duplicati/Library/Utility/TempFile.cs
index ba7a49706..e2934109c 100644
--- a/Duplicati/Library/Utility/TempFile.cs
+++ b/Duplicati/Library/Utility/TempFile.cs
@@ -31,7 +31,7 @@ namespace Duplicati.Library.Utility
///
/// The prefix applied to all temporary files
///
- public static string APPLICATION_PREFIX = Utility.getEntryAssembly().FullName.Substring(0, 3).ToLower() + "-";
+ public static string APPLICATION_PREFIX = Utility.GetEntryAssembly().FullName.Substring(0, 3).ToLower() + "-";
private string m_path;
private bool m_protect;
diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs
index 1abf66f59..2882f03cc 100644
--- a/Duplicati/Library/Utility/Utility.cs
+++ b/Duplicati/Library/Utility/Utility.cs
@@ -38,12 +38,12 @@ namespace Duplicati.Library.Utility
///
/// A cache of the FileSystemCaseSensitive property, which is computed upon the first access.
///
- private static bool? CachedIsFSCaseSensitive = null;
+ private static bool? CachedIsFSCaseSensitive;
///
/// Gets the hash algorithm used for calculating a hash
///
- public static string HashAlgorithm { get { return "SHA256"; } }
+ public static string HashAlgorithm => "SHA256";
///
/// The EPOCH offset (unix style)
@@ -53,7 +53,7 @@ namespace Duplicati.Library.Utility
///
/// The attribute value used to indicate error
///
- public const System.IO.FileAttributes ATTRIBUTE_ERROR = (System.IO.FileAttributes)(1 << 30);
+ public const FileAttributes ATTRIBUTE_ERROR = (FileAttributes)(1 << 30);
///
/// The callback delegate type used to collecting file information
@@ -62,14 +62,14 @@ namespace Duplicati.Library.Utility
/// The current element
/// The attributes of the element
/// A value indicating if the folder should be recursed, ignored for other types
- public delegate bool EnumerationFilterDelegate(string rootpath, string path, System.IO.FileAttributes attributes);
+ public delegate bool EnumerationFilterDelegate(string rootpath, string path, FileAttributes attributes);
///
/// Copies the content of one stream into another
///
/// The stream to read from
/// The stream to write to
- public static void CopyStream(System.IO.Stream source, System.IO.Stream target)
+ public static void CopyStream(Stream source, Stream target)
{
CopyStream(source, target, true);
}
@@ -80,11 +80,15 @@ namespace Duplicati.Library.Utility
/// The stream to read from
/// The stream to write to
/// True if an attempt should be made to rewind the source stream, false otherwise
- public static void CopyStream(System.IO.Stream source, System.IO.Stream target, bool tryRewindSource, byte[] buf = null)
+ /// Temporary buffer to use (optional)
+ public static void CopyStream(Stream source, Stream target, bool tryRewindSource, byte[] buf = null)
{
if (tryRewindSource && source.CanSeek)
try { source.Position = 0; }
- catch { }
+ catch
+ {
+ // ignored
+ }
buf = buf ?? new byte[DEFAULT_BUFFER_SIZE];
@@ -96,21 +100,7 @@ namespace Duplicati.Library.Utility
///
/// These are characters that must be escaped when using a globbing expression
///
- private static readonly string BADCHARS = "\\" + string.Join("|\\", new string[] {
- "\\",
- "+",
- "|",
- "{",
- "[",
- "(",
- ")",
- "]",
- "}",
- "^",
- "$",
- "#",
- "."
- });
+ private static readonly string BADCHARS = @"\\|\+|\||\{|\[|\(|\)|\]|\}|\^|\$|\#|\.";
///
/// Most people will probably want to use fileglobbing, but RegExp's are more flexible.
@@ -121,7 +111,7 @@ namespace Duplicati.Library.Utility
public static string ConvertGlobbingToRegExp(string globexp)
{
//First escape all special characters
- globexp = Regex.Replace(globexp, BADCHARS, "\\$&");
+ globexp = Regex.Replace(globexp, BADCHARS, @"\$&");
//Replace the globbing expressions with the corresponding regular expressions
globexp = globexp.Replace('?', '.').Replace("*", ".*");
@@ -194,11 +184,9 @@ namespace Duplicati.Library.Utility
/// A list of the full filenames and foldernames. Foldernames ends with the directoryseparator char
public static IEnumerable EnumerateFileSystemEntries(string basepath, IFilter filter)
{
- IFilter match;
filter = filter ?? new FilterExpression();
return EnumerateFileSystemEntries(basepath, (rootpath, path, attributes) => {
- bool result;
- if (!filter.Matches(path, out result, out match))
+ if (!filter.Matches(path, out var result, out _))
result = true;
return result;
@@ -217,7 +205,7 @@ namespace Duplicati.Library.Utility
///
/// The path to return data from
/// Attributes for the file or folder
- public delegate System.IO.FileAttributes ExtractFileAttributes(string path);
+ public delegate FileAttributes ExtractFileAttributes(string path);
///
/// A callback delegate used for extracting attributes from a file or folder
@@ -236,7 +224,7 @@ namespace Duplicati.Library.Utility
/// A list of the full filenames
public static IEnumerable EnumerateFileSystemEntries(string rootpath, EnumerationFilterDelegate callback)
{
- return EnumerateFileSystemEntries(rootpath, callback, new FileSystemInteraction(System.IO.Directory.GetDirectories), new FileSystemInteraction(System.IO.Directory.GetFiles));
+ return EnumerateFileSystemEntries(rootpath, callback, Directory.GetDirectories, Directory.GetFiles);
}
///
@@ -250,7 +238,7 @@ namespace Duplicati.Library.Utility
/// A list of the full filenames
public static IEnumerable EnumerateFileSystemEntries(string rootpath, EnumerationFilterDelegate callback, FileSystemInteraction folderList, FileSystemInteraction fileList)
{
- return EnumerateFileSystemEntries(rootpath, callback, folderList, fileList, null, null);
+ return EnumerateFileSystemEntries(rootpath, callback, folderList, fileList, null);
}
///
@@ -274,7 +262,7 @@ namespace Duplicati.Library.Utility
try
{
- System.IO.FileAttributes attr = attributeReader == null ? System.IO.FileAttributes.Directory : attributeReader(rootpath);
+ FileAttributes attr = attributeReader == null ? FileAttributes.Directory : attributeReader(rootpath);
if (callback(rootpath, rootpath, attr))
lst.Push(rootpath);
}
@@ -286,7 +274,7 @@ namespace Duplicati.Library.Utility
{
if (errorCallback != null)
errorCallback(rootpath, rootpath, ex);
- callback(rootpath, rootpath, System.IO.FileAttributes.Directory | ATTRIBUTE_ERROR);
+ callback(rootpath, rootpath, FileAttributes.Directory | ATTRIBUTE_ERROR);
}
while (lst.Count > 0)
@@ -302,7 +290,7 @@ namespace Duplicati.Library.Utility
var sf = AppendDirSeparator(s);
try
{
- System.IO.FileAttributes attr = attributeReader == null ? System.IO.FileAttributes.Directory : attributeReader(sf);
+ FileAttributes attr = attributeReader == null ? FileAttributes.Directory : attributeReader(sf);
if (callback(rootpath, sf, attr))
lst.Push(sf);
}
@@ -314,7 +302,7 @@ namespace Duplicati.Library.Utility
{
if (errorCallback != null)
errorCallback(rootpath, sf, ex);
- callback(rootpath, sf, System.IO.FileAttributes.Directory | ATTRIBUTE_ERROR);
+ callback(rootpath, sf, FileAttributes.Directory | ATTRIBUTE_ERROR);
}
}
}
@@ -326,7 +314,7 @@ namespace Duplicati.Library.Utility
{
if (errorCallback != null)
errorCallback(rootpath, f, ex);
- callback(rootpath, f, System.IO.FileAttributes.Directory | ATTRIBUTE_ERROR);
+ callback(rootpath, f, FileAttributes.Directory | ATTRIBUTE_ERROR);
}
string[] files = null;
@@ -343,7 +331,7 @@ namespace Duplicati.Library.Utility
{
if (errorCallback != null)
errorCallback(rootpath, f, ex);
- callback(rootpath, f, System.IO.FileAttributes.Directory | ATTRIBUTE_ERROR);
+ callback(rootpath, f, FileAttributes.Directory | ATTRIBUTE_ERROR);
}
if (files != null)
@@ -351,7 +339,7 @@ namespace Duplicati.Library.Utility
{
try
{
- System.IO.FileAttributes attr = attributeReader == null ? System.IO.FileAttributes.Normal : attributeReader(s);
+ FileAttributes attr = attributeReader == null ? FileAttributes.Normal : attributeReader(s);
if (!callback(rootpath, s, attr))
continue;
}
@@ -374,7 +362,7 @@ namespace Duplicati.Library.Utility
{
try
{
- System.IO.FileAttributes attr = attributeReader == null ? System.IO.FileAttributes.Normal : attributeReader(rootpath);
+ FileAttributes attr = attributeReader == null ? FileAttributes.Normal : attributeReader(rootpath);
if (!callback(rootpath, rootpath, attr))
yield break;
}
@@ -407,7 +395,7 @@ namespace Duplicati.Library.Utility
try
{
- return attributeReader(path).HasFlag(System.IO.FileAttributes.Directory);
+ return attributeReader(path).HasFlag(FileAttributes.Directory);
}
catch
{
@@ -416,25 +404,16 @@ namespace Duplicati.Library.Utility
}
///
- /// Tests if path refers to a file, or folder, below the parent folder
+ /// Tests if path refers to a file, or folder, below the parent folder
///
/// File or folder to test
/// Candidate parent folder
/// True if below parent folder, false otherwise (note that this returns false if the two argument paths are identical!)
public static bool IsPathBelowFolder(string fileOrFolderPath, string parentFolder)
{
- var candidatePath = fileOrFolderPath;
var sanitizedParentFolder = AppendDirSeparator(parentFolder);
-
- while (true)
- {
- candidatePath = GetParent(candidatePath, true);
- if (candidatePath == null)
- return false;
-
- if (candidatePath.Equals(sanitizedParentFolder, ClientFilenameStringComparision))
- return true;
- }
+ return fileOrFolderPath.StartsWith(sanitizedParentFolder, ClientFilenameStringComparision) &&
+ !fileOrFolderPath.Equals(sanitizedParentFolder, ClientFilenameStringComparision);
}
///
@@ -535,13 +514,13 @@ namespace Duplicati.Library.Utility
/// The combined size of all files that match the filter
public static long GetDirectorySize(string folder, IFilter filter)
{
- return EnumerateFolders(folder, filter).Sum((path) => new System.IO.FileInfo(path).Length);
+ return EnumerateFolders(folder, filter).Sum((path) => new FileInfo(path).Length);
}
///
/// A cached instance of the directory separator as a string
///
- public static readonly string DirectorySeparatorString = System.IO.Path.DirectorySeparatorChar.ToString();
+ public static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString();
///
/// Appends the appropriate directory separator to paths, depending on OS.
@@ -551,10 +530,9 @@ namespace Duplicati.Library.Utility
/// The path with the directory separator appended
public static string AppendDirSeparator(string path)
{
- if (!path.EndsWith(DirectorySeparatorString, StringComparison.Ordinal))
- return path += DirectorySeparatorString;
- else
- return path;
+ return !path.EndsWith(DirectorySeparatorString, StringComparison.Ordinal)
+ ? path + DirectorySeparatorString
+ : path;
}
///
@@ -566,10 +544,7 @@ namespace Duplicati.Library.Utility
/// The path with the directory separator appended
public static string AppendDirSeparator(string path, string separator)
{
- if (!path.EndsWith(separator, StringComparison.Ordinal))
- return path += separator;
- else
- return path;
+ return AppendDirSeparator(separator);
}
///
@@ -591,7 +566,7 @@ namespace Duplicati.Library.Utility
/// The buffer to read into
/// The amout of bytes to read
/// The actual number of bytes read
- public static int ForceStreamRead(System.IO.Stream stream, byte[] buf, int count)
+ public static int ForceStreamRead(Stream stream, byte[] buf, int count)
{
int a;
int index = 0;
@@ -612,7 +587,7 @@ namespace Duplicati.Library.Utility
/// Another stream
/// True if the length of the two streams should be compared
/// True if they are equal, false otherwise
- public static bool CompareStreams(System.IO.Stream stream1, System.IO.Stream stream2, bool checkLength)
+ public static bool CompareStreams(Stream stream1, Stream stream2, bool checkLength)
{
if (checkLength)
{
@@ -661,7 +636,7 @@ namespace Duplicati.Library.Utility
/// The base64 encoded hash
public static string CalculateHash(string path)
{
- using (System.IO.FileStream fs = System.IO.File.Open(path, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
+ using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
return CalculateHash(fs);
}
@@ -670,7 +645,7 @@ namespace Duplicati.Library.Utility
///
/// The stream to calculate the hash for
/// The base64 encoded hash
- public static string CalculateHash(System.IO.Stream stream)
+ public static string CalculateHash(Stream stream)
{
return Convert.ToBase64String(HashAlgorithmHelper.Create(HashAlgorithm).ComputeHash(stream));
}
@@ -685,14 +660,15 @@ namespace Duplicati.Library.Utility
// Since StreamReader defaults to UTF8 and most text files will NOT be UTF8 without BOM,
// we need to detect the encoding (at least that it's not UTF8).
// So we read the first 4096 bytes and try to decode them as UTF8.
- byte[] buffer = new byte[4096];
- using (System.IO.FileStream file = new System.IO.FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
+ var buffer = new byte[4096];
+ using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
file.Read(buffer, 0, 4096);
- Encoding enc = Encoding.UTF8;
+ var enc = Encoding.UTF8;
try
{
// this will throw an error if not really UTF8
+ // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
new UTF8Encoding(false, true).GetString(buffer);
}
catch (Exception)
@@ -701,7 +677,7 @@ namespace Duplicati.Library.Utility
}
// This will load the text using the BOM, or the detected encoding if no BOM.
- using (System.IO.StreamReader reader = new System.IO.StreamReader(filename, enc, true))
+ using (var reader = new StreamReader(filename, enc, true))
{
// Remove all \r from the file and split on \n, then pass directly to ExtractOptions
return reader.ReadToEnd();
@@ -709,7 +685,7 @@ namespace Duplicati.Library.Utility
}
///
- /// Formats a size into a human readable format, eg. 2048 becomes "2 KB" or -2283 becomes &qout;-2.23 KB%quot.
+ /// Formats a size into a human readable format, eg. 2048 becomes "2 KB" or -2283 becomes "-2.23 KB%quot.
///
/// The size to format
/// A human readable string representing the size
@@ -797,7 +773,7 @@ namespace Duplicati.Library.Utility
/// The parsed value, or the default value.
public static bool ParseBool(string value, bool @default)
{
- return Utility.ParseBool(value, () => @default);
+ return ParseBool(value, () => @default);
}
///
@@ -825,12 +801,7 @@ namespace Duplicati.Library.Utility
/// The enum type parameter.
public static T ParseEnumOption(IDictionary options, string value, T @default)
{
- string opt;
- if (options.TryGetValue(value, out opt))
- return ParseEnum(opt, @default);
- else
- return @default;
-
+ return options.TryGetValue(value, out var opt) ? ParseEnum(opt, @default) : @default;
}
///
@@ -842,7 +813,7 @@ namespace Duplicati.Library.Utility
/// The enum type parameter.
public static T ParseEnum(string value, T @default)
{
- foreach (string s in Enum.GetNames(typeof(T)))
+ foreach (var s in Enum.GetNames(typeof(T)))
if (s.Equals(value, StringComparison.OrdinalIgnoreCase))
return (T)Enum.Parse(typeof(T), s);
@@ -918,9 +889,11 @@ namespace Duplicati.Library.Utility
{
if (UNAME == null)
{
- var psi = new System.Diagnostics.ProcessStartInfo("uname");
- psi.RedirectStandardOutput = true;
- psi.UseShellExecute = false;
+ var psi = new System.Diagnostics.ProcessStartInfo("uname")
+ {
+ RedirectStandardOutput = true,
+ UseShellExecute = false
+ };
var pi = System.Diagnostics.Process.Start(psi);
pi.WaitForExit(5000);
@@ -959,6 +932,7 @@ namespace Duplicati.Library.Utility
}
catch
{
+ // ignored
}
return null;
@@ -967,24 +941,12 @@ namespace Duplicati.Library.Utility
///
/// Gets or sets a value indicating if the client is Linux/Unix based
///
- public static bool IsClientLinux
- {
- get
- {
- return Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX;
- }
- }
+ public static bool IsClientLinux => Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX;
///
/// Gets a value indicating if the client is Windows based
///
- public static bool IsClientWindows
- {
- get
- {
- return !IsClientLinux;
- }
- }
+ public static bool IsClientWindows => !IsClientLinux;
///
/// Returns a value indicating if the filesystem, is case sensitive
@@ -999,10 +961,7 @@ namespace Duplicati.Library.Utility
// TODO: This should probably be determined by filesystem rather than OS,
// OSX can actually have the disks formated as Case Sensitive, but insensitive is default
-
- Func defaultReply = () => Utility.IsClientLinux && !Utility.IsClientOSX;
-
- CachedIsFSCaseSensitive = Utility.ParseBool(str, defaultReply);
+ CachedIsFSCaseSensitive = ParseBool(str, () => IsClientLinux && !IsClientOSX);
}
return CachedIsFSCaseSensitive.Value;
@@ -1012,13 +971,7 @@ namespace Duplicati.Library.Utility
///
/// Returns a value indicating if the app is running under Mono
///
- public static bool IsMono
- {
- get
- {
- return Type.GetType("Mono.Runtime") != null;
- }
- }
+ public static bool IsMono => Type.GetType("Mono.Runtime") != null;
///
/// Gets the current Mono runtime version, will return 0.0 if not running Mono
@@ -1032,7 +985,7 @@ namespace Duplicati.Library.Utility
var v = MonoDisplayVersion;
if (v != null)
{
- var regex = new System.Text.RegularExpressions.Regex(@"\d+\.\d+(\.\d+)?(\.\d+)?");
+ var regex = new Regex(@"\d+\.\d+(\.\d+)?(\.\d+)?");
var match = regex.Match(v);
if (match.Success)
return new Version(match.Value);
@@ -1040,6 +993,7 @@ namespace Duplicati.Library.Utility
}
catch
{
+ // ignored
}
return new Version();
@@ -1055,16 +1009,17 @@ namespace Duplicati.Library.Utility
{
try
{
- Type t = Type.GetType("Mono.Runtime");
+ var t = Type.GetType("Mono.Runtime");
if (t != null)
{
- System.Reflection.MethodInfo mi = t.GetMethod("GetDisplayName", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
+ var mi = t.GetMethod("GetDisplayName", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
if (mi != null)
return (string)mi.Invoke(null, null);
}
}
catch
{
+ // ignored
}
return null;
@@ -1074,28 +1029,17 @@ namespace Duplicati.Library.Utility
///
/// Gets the users default UI language
///
- public static System.Globalization.CultureInfo DefaultCulture
- {
- get
- {
- System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(DummyMethod));
- return t.CurrentUICulture;
- }
- }
- //Unused function, used to create a dummy thread
- private static void DummyMethod()
- {
- }
+ public static System.Globalization.CultureInfo DefaultCulture => new System.Threading.Thread(() => { }).CurrentUICulture;
///
/// Gets a string comparer that matches the client filesystems case sensitivity
///
- public static StringComparer ClientFilenameStringComparer { get { return Utility.IsFSCaseSensitive ? StringComparer.CurrentCulture : StringComparer.CurrentCultureIgnoreCase; } }
+ public static StringComparer ClientFilenameStringComparer => IsFSCaseSensitive ? StringComparer.CurrentCulture : StringComparer.CurrentCultureIgnoreCase;
///
/// Gets the string comparision that matches the client filesystems case sensitivity
///
- public static StringComparison ClientFilenameStringComparision { get { return Utility.IsFSCaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase; } }
+ public static StringComparison ClientFilenameStringComparision => IsFSCaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
///
/// Searches the system paths for the file specified
@@ -1106,33 +1050,36 @@ namespace Duplicati.Library.Utility
{
try
{
- if (System.IO.Path.IsPathRooted(filename))
- return System.IO.File.Exists(filename) ? filename : null;
+ if (Path.IsPathRooted(filename))
+ return File.Exists(filename) ? filename : null;
try
{
- filename = System.IO.Path.GetFileName(filename);
+ filename = Path.GetFileName(filename);
}
catch
{
+ // ignored
}
- string homedir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + System.IO.Path.PathSeparator.ToString();
+ string homedir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + Path.PathSeparator.ToString();
//Look in application base folder and all system path folders
- foreach (string s in (homedir + Environment.GetEnvironmentVariable("PATH")).Split(System.IO.Path.PathSeparator))
+ foreach (string s in (homedir + Environment.GetEnvironmentVariable("PATH")).Split(Path.PathSeparator))
if (!string.IsNullOrEmpty(s) && s.Trim().Length > 0)
try
{
- foreach (string sx in System.IO.Directory.GetFiles(ExpandEnvironmentVariables(s), filename))
+ foreach (string sx in Directory.GetFiles(ExpandEnvironmentVariables(s), filename))
return sx;
}
catch
{
+ // ignored
}
}
catch
{
+ // ignored
}
return null;
@@ -1172,14 +1119,14 @@ namespace Duplicati.Library.Utility
public static string ExpandEnvironmentVariablesRegexp(string str, Func lookup = null)
{
if (lookup == null)
- lookup = x => Environment.GetEnvironmentVariable(x);
+ lookup = Environment.GetEnvironmentVariable;
return
// TODO: Should we switch to using the native format, instead of following the Windows scheme?
//IsClientLinux ? ENVIRONMENT_VARIABLE_MATCHER_LINUX : ENVIRONMENT_VARIABLE_MATCHER_WINDOWS
- ENVIRONMENT_VARIABLE_MATCHER_WINDOWS.Replace(str, (m) => Regex.Escape(lookup(m.Groups["name"].Value)));
+ ENVIRONMENT_VARIABLE_MATCHER_WINDOWS.Replace(str, m => Regex.Escape(lookup(m.Groups["name"].Value)));
}
///
@@ -1232,8 +1179,7 @@ namespace Duplicati.Library.Utility
/// The parsed instance
public static DateTime DeserializeDateTime(string str)
{
- DateTime dt;
- if (!TryDeserializeDateTime(str, out dt))
+ if (!TryDeserializeDateTime(str, out var dt))
throw new Exception(Strings.Utility.InvalidDateError(str));
return dt;
@@ -1248,7 +1194,7 @@ namespace Duplicati.Library.Utility
/// The unique items from .
public static ISet GetUniqueItems(IEnumerable collection, out ISet duplicateItems)
{
- return Utility.GetUniqueItems(collection, EqualityComparer.Default, out duplicateItems);
+ return GetUniqueItems(collection, EqualityComparer.Default, out duplicateItems);
}
///
@@ -1261,12 +1207,14 @@ namespace Duplicati.Library.Utility
/// The unique items from .
public static ISet GetUniqueItems(IEnumerable collection, IEqualityComparer comparer, out ISet duplicateItems)
{
- HashSet uniqueItems = new HashSet(comparer);
+ var uniqueItems = new HashSet(comparer);
duplicateItems = new HashSet(comparer);
- foreach (T item in collection)
+ foreach (var item in collection)
+ {
if (!uniqueItems.Add(item))
duplicateItems.Add(item);
+ }
return uniqueItems;
}
@@ -1278,8 +1226,8 @@ namespace Duplicati.Library.Utility
/// The file to replace with
public static void ReplaceFile(string target, string sourcefile)
{
- if (System.IO.File.Exists(target))
- System.IO.File.Delete(target);
+ if (File.Exists(target))
+ File.Delete(target);
//Nasty workaround for the fact that a recently deleted file occasionally blocks a new write
long i = 5;
@@ -1287,13 +1235,14 @@ namespace Duplicati.Library.Utility
{
try
{
- System.IO.File.Move(sourcefile, target);
+ File.Move(sourcefile, target);
break;
}
catch (Exception ex)
{
if (i == 0)
- throw new Exception(string.Format("Failed to replace the file \"{0}\" volume with the \"{1}\", error: {2}", target, sourcefile, ex.Message));
+ throw new Exception(
+ $"Failed to replace the file \"{target}\" volume with the \"{sourcefile}\", error: {ex.Message}");
System.Threading.Thread.Sleep(250);
}
} while (i-- > 0);
@@ -1305,7 +1254,7 @@ namespace Duplicati.Library.Utility
// so this layer of indirection is necessary
//
// entry assembly or reasonable approximation
- public static System.Reflection.Assembly getEntryAssembly()
+ public static System.Reflection.Assembly GetEntryAssembly()
{
return System.Reflection.Assembly.GetEntryAssembly() ?? System.Reflection.Assembly.GetExecutingAssembly();
}
@@ -1394,7 +1343,7 @@ namespace Duplicati.Library.Utility
/// true, the item was printed, false otherwise.
/// The item to write.
/// The target writer.
- private static bool PrintSerializeIfPrimitive(object item, System.IO.TextWriter writer)
+ private static bool PrintSerializeIfPrimitive(object item, TextWriter writer)
{
if (item == null)
{
@@ -1430,7 +1379,7 @@ namespace Duplicati.Library.Utility
/// The string indentation
/// A lookup table with visited objects, used to avoid inifinite recursion
/// The maximum number of items to report from an IEnumerable instance
- public static void PrintSerializeObject(object item, System.IO.TextWriter writer, Func filter = null, bool recurseobjects = false, int indentation = 0, int collectionlimit = 0, Dictionary