Improved BackgroundFileSystem thread safety

This commit is contained in:
Michael Bucari-Tovo 2021-08-14 00:25:32 -06:00
parent 52fb0a27ce
commit f74b0d78db
3 changed files with 73 additions and 52 deletions

View File

@ -34,7 +34,7 @@ namespace FileManager
} }
} }
private static BackgroundFileSystem BookDirectoryFiles { get; } = new BackgroundFileSystem(); private static BackgroundFileSystem BookDirectoryFiles { get; set; }
#endregion #endregion
#region instance #region instance
@ -47,6 +47,7 @@ namespace FileManager
{ {
extensions_noDots = Extensions.Select(ext => ext.Trim('.')).ToList(); extensions_noDots = Extensions.Select(ext => ext.Trim('.')).ToList();
extAggr = extensions_noDots.Aggregate((a, b) => $"{a}|{b}"); extAggr = extensions_noDots.Aggregate((a, b) => $"{a}|{b}");
BookDirectoryFiles ??= new BackgroundFileSystem(BookDirectoryFiles, "*.*", SearchOption.AllDirectories);
} }
public void Refresh() public void Refresh()
@ -76,7 +77,15 @@ namespace FileManager
{ {
//If user changed the BooksDirectory, reinitialize. //If user changed the BooksDirectory, reinitialize.
if (storageDir != BookDirectoryFiles.RootDirectory) if (storageDir != BookDirectoryFiles.RootDirectory)
BookDirectoryFiles.Init(storageDir, "*.*", SearchOption.AllDirectories); {
lock (BookDirectoryFiles)
{
if (storageDir != BookDirectoryFiles.RootDirectory)
{
BookDirectoryFiles = new BackgroundFileSystem(storageDir, "*.*", SearchOption.AllDirectories);
}
}
}
firstOrNull = BookDirectoryFiles.FindFile(regexPattern, RegexOptions.IgnoreCase); firstOrNull = BookDirectoryFiles.FindFile(regexPattern, RegexOptions.IgnoreCase);
} }

View File

@ -18,12 +18,19 @@ namespace FileManager
private FileSystemWatcher fileSystemWatcher { get; set; } private FileSystemWatcher fileSystemWatcher { get; set; }
private BlockingCollection<FileSystemEventArgs> directoryChangesEvents { get; set; } private BlockingCollection<FileSystemEventArgs> directoryChangesEvents { get; set; }
private Task backgroundScanner { get; set; } private Task backgroundScanner { get; set; }
private List<string> fsCache { get; set; } private List<string> fsCache { get; } = new();
public BackgroundFileSystem(string rootDirectory, string searchPattern, SearchOption searchOptions)
{
RootDirectory = rootDirectory;
SearchPattern = searchPattern;
SearchOption = searchOptions;
Init();
}
public string FindFile(string regexPattern, RegexOptions options) public string FindFile(string regexPattern, RegexOptions options)
{ {
if (fsCache is null) return null;
lock (fsCache) lock (fsCache)
{ {
return fsCache.FirstOrDefault(s => Regex.IsMatch(s, regexPattern, options)); return fsCache.FirstOrDefault(s => Regex.IsMatch(s, regexPattern, options));
@ -32,8 +39,6 @@ namespace FileManager
public void RefreshFiles() public void RefreshFiles()
{ {
if (fsCache is null) return;
lock (fsCache) lock (fsCache)
{ {
fsCache.Clear(); fsCache.Clear();
@ -41,19 +46,12 @@ namespace FileManager
} }
} }
public void Init(string rootDirectory, string searchPattern, SearchOption searchOptions) private void Init()
{ {
RootDirectory = rootDirectory; Stop();
SearchPattern = searchPattern;
SearchOption = searchOptions;
//Calling CompleteAdding() will cause background scanner to terminate. lock (fsCache)
directoryChangesEvents?.CompleteAdding(); fsCache.AddRange(Directory.EnumerateFiles(RootDirectory, SearchPattern, SearchOption));
fsCache?.Clear();
directoryChangesEvents?.Dispose();
fileSystemWatcher?.Dispose();
fsCache = Directory.EnumerateFiles(RootDirectory, SearchPattern, SearchOption).ToList();
directoryChangesEvents = new BlockingCollection<FileSystemEventArgs>(); directoryChangesEvents = new BlockingCollection<FileSystemEventArgs>();
fileSystemWatcher = new FileSystemWatcher(RootDirectory); fileSystemWatcher = new FileSystemWatcher(RootDirectory);
@ -64,28 +62,31 @@ namespace FileManager
fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.IncludeSubdirectories = true;
fileSystemWatcher.EnableRaisingEvents = true; fileSystemWatcher.EnableRaisingEvents = true;
//Wait for background scanner to terminate before reinitializing.
backgroundScanner?.Wait();
backgroundScanner = new Task(BackgroundScanner); backgroundScanner = new Task(BackgroundScanner);
backgroundScanner.Start(); backgroundScanner.Start();
} }
private void Stop()
{
//Stop raising events
fileSystemWatcher?.Dispose();
private void AddUniqueFiles(IEnumerable<string> newFiles) //Calling CompleteAdding() will cause background scanner to terminate.
{ directoryChangesEvents?.CompleteAdding();
foreach (var file in newFiles)
{ //Wait for background scanner to terminate before reinitializing.
AddUniqueFile(file); backgroundScanner?.Wait();
}
} //Dispose of directoryChangesEvents after backgroundScanner exists.
private void AddUniqueFile(string newFile) directoryChangesEvents?.Dispose();
{
if (!fsCache.Contains(newFile)) lock (fsCache)
fsCache.Add(newFile); fsCache.Clear();
} }
private void FileSystemWatcher_Error(object sender, ErrorEventArgs e) private void FileSystemWatcher_Error(object sender, ErrorEventArgs e)
{ {
Init(RootDirectory, SearchPattern, SearchOption); Stop();
Init();
} }
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e) private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
@ -97,12 +98,13 @@ namespace FileManager
private void BackgroundScanner() private void BackgroundScanner()
{ {
while (directoryChangesEvents.TryTake(out FileSystemEventArgs change, -1)) while (directoryChangesEvents.TryTake(out FileSystemEventArgs change, -1))
{
lock (fsCache)
UpdateLocalCache(change); UpdateLocalCache(change);
} }
}
private void UpdateLocalCache(FileSystemEventArgs change) private void UpdateLocalCache(FileSystemEventArgs change)
{
lock (fsCache)
{ {
if (change.ChangeType == WatcherChangeTypes.Deleted) if (change.ChangeType == WatcherChangeTypes.Deleted)
{ {
@ -112,15 +114,12 @@ namespace FileManager
{ {
AddPath(change.FullPath); AddPath(change.FullPath);
} }
else if (change.ChangeType == WatcherChangeTypes.Renamed) else if (change.ChangeType == WatcherChangeTypes.Renamed && change is RenamedEventArgs renameChange)
{ {
var renameChange = change as RenamedEventArgs;
RemovePath(renameChange.OldFullPath); RemovePath(renameChange.OldFullPath);
AddPath(renameChange.FullPath); AddPath(renameChange.FullPath);
} }
} }
}
private void RemovePath(string path) private void RemovePath(string path)
{ {
@ -137,6 +136,21 @@ namespace FileManager
else else
AddUniqueFile(path); AddUniqueFile(path);
} }
private void AddUniqueFiles(IEnumerable<string> newFiles)
{
foreach (var file in newFiles)
{
AddUniqueFile(file);
}
}
private void AddUniqueFile(string newFile)
{
if (!fsCache.Contains(newFile))
fsCache.Add(newFile);
}
#endregion #endregion
~BackgroundFileSystem() => Stop();
} }
} }

View File

@ -23,8 +23,6 @@ namespace LibationWinForms
if (DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor != foreColor) if (DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor != foreColor)
{ {
DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = foreColor; DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = foreColor;
DataGridView.InvalidateRow(RowIndex);
} }
if (tagsString.Length == 0) if (tagsString.Length == 0)