Fixed post-processing of modified file list obtain from USN
This commit is contained in:
@@ -444,7 +444,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
// the modified sources may contain non-existent files / folders, which need to
|
||||
// be removed before trying to enumerate them
|
||||
var existingModifiedSources = modifiedSources.Files.Where(snapshot.FileExists)
|
||||
.Concat(modifiedSources.Folders.Where(snapshot.FolderExists));
|
||||
.Concat(modifiedSources.Folders.Where(snapshot.DirectoryExists));
|
||||
|
||||
foreach (var path in filterhandler.EnumerateFilesAndFolders(existingModifiedSources))
|
||||
{
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// </summary>
|
||||
/// <param name="localFolderPath">The local path</param>
|
||||
/// <returns>True if folder exists, false otherwise</returns>
|
||||
bool FolderExists(string localFolderPath);
|
||||
bool DirectoryExists(string localFolderPath);
|
||||
|
||||
/// <summary>
|
||||
/// Tests if a file or folder exists in the snapshot
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Duplicati.Library.Snapshots
|
||||
public IEnumerable<string> EnumerateFilesAndFolders(IEnumerable<string> sources, Utility.Utility.EnumerationFilterDelegate callback, Utility.Utility.ReportAccessError errorCallback)
|
||||
{
|
||||
// Add trailing slashes to folders
|
||||
var sanitizedSources = sources.Select(x => Directory.Exists(x) ? Utility.Utility.AppendDirSeparator(x) : x).ToList();
|
||||
var sanitizedSources = sources.Select(x => DirectoryExists(x) ? Utility.Utility.AppendDirSeparator(x) : x).ToList();
|
||||
|
||||
return sanitizedSources.SelectMany(
|
||||
s => Utility.Utility.EnumerateFileSystemEntries(s, callback, ListFolders, ListFiles, GetAttributes, errorCallback)
|
||||
@@ -152,19 +152,19 @@ namespace Duplicati.Library.Snapshots
|
||||
/// <inheritdoc />
|
||||
public bool FileExists(string localFilePath)
|
||||
{
|
||||
return new FileInfo(ConvertToSnapshotPath(localFilePath)).Exists;
|
||||
return File.Exists(ConvertToSnapshotPath(localFilePath));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool FolderExists(string localFolderPath)
|
||||
public bool DirectoryExists(string localFolderPath)
|
||||
{
|
||||
return new DirectoryInfo(ConvertToSnapshotPath(localFolderPath)).Exists;
|
||||
return Directory.Exists(ConvertToSnapshotPath(localFolderPath));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool PathExists(string localFileOrFolderPath)
|
||||
{
|
||||
return FileExists(localFileOrFolderPath) || FolderExists(localFileOrFolderPath);
|
||||
return FileExists(localFileOrFolderPath) || DirectoryExists(localFileOrFolderPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -133,30 +133,31 @@ namespace Duplicati.Library.Snapshots
|
||||
/// <summary>
|
||||
/// Returns a list of files or folders that have changed since the recorded USN
|
||||
/// </summary>
|
||||
/// <param name="sourceFolder">The folder to find entries for</param>
|
||||
/// <param name="sourceFileOrFolder">The file or folder to find entries for</param>
|
||||
/// <param name="minUsn">Minimum USN of entry</param>
|
||||
/// <returns>A list of tuples with changed files and folders and their type</returns>
|
||||
public IEnumerable<Tuple<string, EntryType>> GetChangedFileSystemEntries(string sourceFolder, long minUsn)
|
||||
public IEnumerable<Tuple<string, EntryType>> GetChangedFileSystemEntries(string sourceFileOrFolder, long minUsn)
|
||||
{
|
||||
return GetChangedFileSystemEntries(sourceFolder, minUsn, ChangeReason.Any);
|
||||
return GetChangedFileSystemEntries(sourceFileOrFolder, minUsn, ChangeReason.Any);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of files or folders that have changed since the recorded USN
|
||||
/// </summary>
|
||||
/// <param name="sourceFolder">The folder to find entries for</param>
|
||||
/// <param name="sourceFileOrFolder">The file or folder to find entries for</param>
|
||||
/// <param name="minUsn">Minimum USN of entry</param>
|
||||
/// <param name="reason">Filter expression for change reason</param>
|
||||
/// <returns>A list of tuples with changed files and folders and their type</returns>
|
||||
public IEnumerable<Tuple<string, EntryType>> GetChangedFileSystemEntries(string sourceFolder, long minUsn, ChangeReason reason)
|
||||
public IEnumerable<Tuple<string, EntryType>> GetChangedFileSystemEntries(string sourceFileOrFolder, long minUsn, ChangeReason reason)
|
||||
{
|
||||
var isFolder = sourceFileOrFolder.EndsWith(Utility.Utility.DirectorySeparatorString, StringComparison.Ordinal);
|
||||
|
||||
foreach (var r in GetRecords(minUsn))
|
||||
{
|
||||
if (r.UsnRecord.Usn >= minUsn
|
||||
&& (reason == ChangeReason.Any || (MapChangeReason(r.UsnRecord.Reason) & reason) != 0)
|
||||
&& (string.IsNullOrEmpty(sourceFolder) || r.FullPath.Equals(sourceFolder,
|
||||
Utility.Utility.ClientFilenameStringComparision)
|
||||
|| Utility.Utility.IsPathBelowFolder(r.FullPath, sourceFolder)))
|
||||
&& (r.FullPath.Equals(sourceFileOrFolder, Utility.Utility.ClientFilenameStringComparision)
|
||||
|| isFolder && Utility.Utility.IsPathBelowFolder(r.FullPath, sourceFileOrFolder)))
|
||||
{
|
||||
yield return Tuple.Create(r.FullPath,
|
||||
r.UsnRecord.FileAttributes.HasFlag(Win32USN.FileAttributes.Directory)
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace Duplicati.Library.Snapshots
|
||||
foreach (var sourcesPerVolume in SortByVolume(sources))
|
||||
{
|
||||
var volume = sourcesPerVolume.Key;
|
||||
var volumeSources = sourcesPerVolume.Value;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -107,7 +108,7 @@ namespace Duplicati.Library.Snapshots
|
||||
var changedFolders = new HashSet<string>(Utility.Utility.ClientFilenameStringComparer);
|
||||
|
||||
// obtain changed files and folders, per volume
|
||||
foreach (var source in sourcesPerVolume.Value)
|
||||
foreach (var source in volumeSources)
|
||||
{
|
||||
foreach (var entry in journal.GetChangedFileSystemEntries(source, data.NextUsn))
|
||||
{
|
||||
@@ -122,6 +123,11 @@ namespace Duplicati.Library.Snapshots
|
||||
}
|
||||
}
|
||||
|
||||
// prepare cache for includes (value = true) and excludes (value = false, will be populated
|
||||
// on-demand)
|
||||
var cache = new Dictionary<string, bool>();
|
||||
volumeSources.ForEach(p => cache[p] = true);
|
||||
|
||||
// At this point we have:
|
||||
// - a list of folders (changedFolders) that were possibly modified
|
||||
// - a list of files (changedFiles) that were possibly modified
|
||||
@@ -137,8 +143,9 @@ namespace Duplicati.Library.Snapshots
|
||||
// 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<string>(); // cache to speed up processing
|
||||
// folders.
|
||||
// The sources are needed to stop evaluating parent folders above the specified source folders
|
||||
|
||||
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).
|
||||
@@ -178,10 +185,10 @@ namespace Duplicati.Library.Snapshots
|
||||
/// </summary>
|
||||
/// <param name="files">Files to filter</param>
|
||||
/// <param name="filter">Exclusion filter</param>
|
||||
/// <param name="cache">Cache of excluded folders (optional)</param>
|
||||
/// <param name="cache">Cache of included and exculded files / folders</param>
|
||||
/// <returns>Filtered files</returns>
|
||||
private IEnumerable<string> FilterExcludedFiles(IEnumerable<string> files,
|
||||
Utility.Utility.EnumerationFilterDelegate filter, ISet<string> cache = null)
|
||||
Utility.Utility.EnumerationFilterDelegate filter, IDictionary<string, bool> cache)
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
@@ -221,7 +228,7 @@ namespace Duplicati.Library.Snapshots
|
||||
/// <param name="cache">Cache of excluded folders (optional)</param>
|
||||
/// <returns>Filtered folders</returns>
|
||||
private IEnumerable<string> FilterExcludedFolders(IEnumerable<string> folders,
|
||||
Utility.Utility.EnumerationFilterDelegate filter, ISet<string> cache = null)
|
||||
Utility.Utility.EnumerationFilterDelegate filter, IDictionary<string, bool> cache)
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
@@ -255,26 +262,29 @@ namespace Duplicati.Library.Snapshots
|
||||
/// <param name="filter">Filter</param>
|
||||
/// <param name="cache">Cache of excluded folders (optional)</param>
|
||||
/// <returns>True if excluded, false otherwise</returns>
|
||||
private bool IsFolderOrAncestorsExcluded(string folder, Utility.Utility.EnumerationFilterDelegate filter, ISet<string> cache = null)
|
||||
private bool IsFolderOrAncestorsExcluded(string folder, Utility.Utility.EnumerationFilterDelegate filter, IDictionary<string, bool> cache)
|
||||
{
|
||||
List<string> parents = null;
|
||||
while (folder != null)
|
||||
{
|
||||
// first check cache
|
||||
if (cache != null)
|
||||
if (cache.TryGetValue(folder, out var include))
|
||||
{
|
||||
if (cache.Contains(folder))
|
||||
break; // hit!
|
||||
if (include)
|
||||
return false;
|
||||
|
||||
// remember folder for cache
|
||||
if (parents == null)
|
||||
{
|
||||
parents = new List<string>(); // create on-demand
|
||||
}
|
||||
parents.Add(folder);
|
||||
break; // hit!
|
||||
}
|
||||
|
||||
var attr = m_snapshot.FolderExists(folder) ? m_snapshot.GetAttributes(folder) : FileAttributes.Directory;
|
||||
// remember folder for cache
|
||||
if (parents == null)
|
||||
{
|
||||
parents = new List<string>(); // create on-demand
|
||||
}
|
||||
parents.Add(folder);
|
||||
|
||||
|
||||
var attr = m_snapshot.DirectoryExists(folder) ? m_snapshot.GetAttributes(folder) : FileAttributes.Directory;
|
||||
|
||||
if (!filter(folder, folder, attr))
|
||||
break; // excluded
|
||||
@@ -282,10 +292,10 @@ namespace Duplicati.Library.Snapshots
|
||||
folder = Utility.Utility.GetParent(folder, true);
|
||||
}
|
||||
|
||||
if (folder != null && parents != null)
|
||||
if (folder != null)
|
||||
{
|
||||
// update cache
|
||||
cache.UnionWith(parents);
|
||||
parents?.ForEach(p => cache[p] = false);
|
||||
}
|
||||
|
||||
return folder != null;
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace Duplicati.Library.Utility
|
||||
/// <returns>A list of the full filenames</returns>
|
||||
public static IEnumerable<string> EnumerateFileSystemEntries(string rootpath, EnumerationFilterDelegate callback, FileSystemInteraction folderList, FileSystemInteraction fileList, ExtractFileAttributes attributeReader, ReportAccessError errorCallback = null)
|
||||
{
|
||||
Stack<string> lst = new Stack<string>();
|
||||
var lst = new Stack<string>();
|
||||
|
||||
if (IsFolder(rootpath, attributeReader))
|
||||
{
|
||||
@@ -262,7 +262,7 @@ namespace Duplicati.Library.Utility
|
||||
try
|
||||
{
|
||||
|
||||
FileAttributes attr = attributeReader == null ? FileAttributes.Directory : attributeReader(rootpath);
|
||||
var attr = attributeReader?.Invoke(rootpath) ?? FileAttributes.Directory;
|
||||
if (callback(rootpath, rootpath, attr))
|
||||
lst.Push(rootpath);
|
||||
}
|
||||
@@ -272,25 +272,24 @@ namespace Duplicati.Library.Utility
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorCallback != null)
|
||||
errorCallback(rootpath, rootpath, ex);
|
||||
errorCallback?.Invoke(rootpath, rootpath, ex);
|
||||
callback(rootpath, rootpath, FileAttributes.Directory | ATTRIBUTE_ERROR);
|
||||
}
|
||||
|
||||
while (lst.Count > 0)
|
||||
{
|
||||
string f = AppendDirSeparator(lst.Pop());
|
||||
var f = AppendDirSeparator(lst.Pop());
|
||||
|
||||
yield return f;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (string s in folderList(f))
|
||||
foreach (var s in folderList(f))
|
||||
{
|
||||
var sf = AppendDirSeparator(s);
|
||||
try
|
||||
{
|
||||
FileAttributes attr = attributeReader == null ? FileAttributes.Directory : attributeReader(sf);
|
||||
var attr = attributeReader?.Invoke(sf) ?? FileAttributes.Directory;
|
||||
if (callback(rootpath, sf, attr))
|
||||
lst.Push(sf);
|
||||
}
|
||||
@@ -300,8 +299,7 @@ namespace Duplicati.Library.Utility
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorCallback != null)
|
||||
errorCallback(rootpath, sf, ex);
|
||||
errorCallback?.Invoke(rootpath, sf, ex);
|
||||
callback(rootpath, sf, FileAttributes.Directory | ATTRIBUTE_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -312,13 +310,13 @@ namespace Duplicati.Library.Utility
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorCallback != null)
|
||||
errorCallback(rootpath, f, ex);
|
||||
errorCallback?.Invoke(rootpath, f, ex);
|
||||
callback(rootpath, f, FileAttributes.Directory | ATTRIBUTE_ERROR);
|
||||
}
|
||||
|
||||
string[] files = null;
|
||||
if (fileList != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
files = fileList(f);
|
||||
@@ -329,17 +327,18 @@ namespace Duplicati.Library.Utility
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorCallback != null)
|
||||
errorCallback(rootpath, f, ex);
|
||||
errorCallback?.Invoke(rootpath, f, ex);
|
||||
callback(rootpath, f, FileAttributes.Directory | ATTRIBUTE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
foreach (var s in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileAttributes attr = attributeReader == null ? FileAttributes.Normal : attributeReader(s);
|
||||
var attr = attributeReader?.Invoke(s) ?? FileAttributes.Normal;
|
||||
if (!callback(rootpath, s, attr))
|
||||
continue;
|
||||
}
|
||||
@@ -349,20 +348,20 @@ namespace Duplicati.Library.Utility
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorCallback != null)
|
||||
errorCallback(rootpath, s, ex);
|
||||
errorCallback?.Invoke(rootpath, s, ex);
|
||||
callback(rootpath, s, ATTRIBUTE_ERROR);
|
||||
continue;
|
||||
}
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
FileAttributes attr = attributeReader == null ? FileAttributes.Normal : attributeReader(rootpath);
|
||||
var attr = attributeReader?.Invoke(rootpath) ?? FileAttributes.Normal;
|
||||
if (!callback(rootpath, rootpath, attr))
|
||||
yield break;
|
||||
}
|
||||
@@ -372,8 +371,7 @@ namespace Duplicati.Library.Utility
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorCallback != null)
|
||||
errorCallback(rootpath, rootpath, ex);
|
||||
errorCallback?.Invoke(rootpath, rootpath, ex);
|
||||
callback(rootpath, rootpath, ATTRIBUTE_ERROR);
|
||||
yield break;
|
||||
}
|
||||
@@ -531,9 +529,7 @@ namespace Duplicati.Library.Utility
|
||||
/// <returns>The path with the directory separator appended</returns>
|
||||
public static string AppendDirSeparator(string path)
|
||||
{
|
||||
return !path.EndsWith(DirectorySeparatorString, StringComparison.Ordinal)
|
||||
? path + DirectorySeparatorString
|
||||
: path;
|
||||
return AppendDirSeparator(path, DirectorySeparatorString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -545,7 +541,9 @@ namespace Duplicati.Library.Utility
|
||||
/// <returns>The path with the directory separator appended</returns>
|
||||
public static string AppendDirSeparator(string path, string separator)
|
||||
{
|
||||
return AppendDirSeparator(separator);
|
||||
return !path.EndsWith(DirectorySeparatorString, StringComparison.Ordinal)
|
||||
? path + separator
|
||||
: path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user