private IFileSystemWatcher HookAncestor(IReadOnlyList <Exception> exceptions) { foreach (var(ancestorDir, childName) in recoveryStrategy.HookableParents(sourcePath)) { Assert(childName?.EndsWith(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) ?? true); if (ancestorDir.Exists) { if (childName == null) { // the sourcePath should exist now return(Hook()); } return(FileSystemHook.Hook( sourcePath: ancestorDir.FullName, sourcePatterns: new[] { Path.Combine(childName, "**") }, onCreated: (sender, e) => Start(), onError: CreateErrorHandler(exceptions), cancellationToken: cancellationToken, triggerOnCreatedOnExistingFiles: true)); } } throw new Exception("No hookable ancestor exists"); }
public async Task Can_detect_directory_creation_with_empty_filter() { string dir = GetTempDirectory(); string nestedDir = Path.Combine(dir, "a"); using var hook = FileSystemHook.Hook(dir, sourcePatterns: Array.Empty <string>(), onCreated: RecordTrigger(out var trigger) ); await WaitWithTimeout(trigger, () => { Directory.CreateDirectory(nestedDir); }); Assert.IsTrue(trigger.IsCompletedSuccessfully); }
public async Task DisposalStopsDetection() { string dir = GetTempDirectory(); string filename = "a.txt"; FileSystemHook.Hook(dir, sourcePatterns: new[] { "*" }, onCreated: RecordTrigger(out var trigger) ).Dispose(); await WaitWithTimeout(trigger, () => { CreateFile(dir, filename); }); Assert.IsFalse(trigger.IsCompletedSuccessfully); }
public async Task Can_detect_nested_file_creation() { string dir = GetTempDirectory(); using var hook = FileSystemHook.Hook(dir, sourcePatterns: new[] { "**" }, onCreated: RecordTrigger(out var trigger) ); await WaitWithTimeout(trigger, () => { Directory.CreateDirectory(Path.Combine(dir, "a")); CreateFile(dir, "a", "b.txt"); }); Assert.IsTrue(trigger.IsCompletedSuccessfully); }
public async Task Creating_file_doesnt_trigger_directory_creation() { string parentDir = GetTempDirectory(); string dirname = "a"; using var hook = FileSystemHook.Hook(parentDir, sourcePatterns: new[] { "*" }, // listens to files only onCreated: RecordTrigger(out var trigger) ); await WaitWithTimeout(trigger, () => { Directory.CreateDirectory(Path.Combine(parentDir, dirname)); }); Assert.IsFalse(trigger.IsCompletedSuccessfully); }
public async Task Can_detect_creating_directory() { string parentDir = GetTempDirectory(); string dirname = "a"; using var hook = FileSystemHook.Hook(parentDir, sourcePatterns: new[] { "*/" }, onCreated: RecordTrigger(out var trigger) ); await WaitWithTimeout(trigger, () => { Directory.CreateDirectory(Path.Combine(parentDir, dirname)); }); Assert.IsTrue(trigger.IsCompletedSuccessfully); }
public async Task Can_detect_creating_file() { string dir = GetTempDirectory(); string filename = "a.txt"; using var hook = FileSystemHook.Hook(dir, sourcePatterns: new[] { "*" }, onCreated: RecordTrigger(out var trigger) ); await WaitWithTimeout(trigger, () => { CreateFile(dir, filename); }); Assert.IsTrue(trigger.IsCompletedSuccessfully); }
public async Task Creating_nested_file_is_filtered_out() { string dir = GetTempDirectory(); string nestedDir = "a"; string filename = "a"; Directory.CreateDirectory(Path.Combine(dir, nestedDir)); using var hook = FileSystemHook.Hook(dir, sourcePatterns: new[] { "*" }, onCreated: RecordTrigger(out var trigger) ); await WaitWithTimeout(trigger, () => { CreateFile(dir, nestedDir, filename); }); Assert.IsFalse(trigger.IsCompletedSuccessfully); }
public async Task Creation_already_existing_dir_is_dismissed() { // Arrange string dir = GetTempDirectory(); string nestedDir = "a"; Directory.CreateDirectory(Path.Combine(dir, nestedDir)); using var hook = FileSystemHook.Hook(dir, sourcePatterns: new[] { "*" }, onCreated: RecordTrigger(out var trigger) ); // Act await WaitWithTimeout(trigger, () => { Directory.CreateDirectory(Path.Combine(dir, nestedDir)); }); // Assert Assert.IsFalse(trigger.IsCompletedSuccessfully); }
private IFileSystemWatcher Hook() { // a new exceptions list is ok because if this os ever called, that means that the hook was created succesfully return(FileSystemHook.Hook(sourcePath, sourcePatterns, sourceIgnorePatterns, onCreated, onModified, onDeleted, CreateErrorHandler(new List <Exception>()))); }
/// <summary> /// Mirrors a globbed source file system to a destination file system. /// </summary> public static IDisposable Mirror(string sourcePath, string destPath, IEnumerable <string>?sourcePatterns = null, IEnumerable <string>?sourceIgnorePatterns = null, bool mirrorDeletions = true, RecoveryStrategy?recoveryStrategy = null, ILogger?logger = null, CancellationToken cancellationToken = default) { if (Path.GetFullPath(sourcePath).StartsWith(Path.GetFullPath(destPath))) { throw new ArgumentException("The destination path cannot be (non-strictly) nested in the source path"); } sourcePatterns ??= new string[] { "**", "**/" }; if (recoveryStrategy is null) { return(FileSystemHook.Hook(sourcePath, sourcePatterns, sourceIgnorePatterns, onCreated: createCallback(CopyFile), onDeleted: mirrorDeletions ? createCallback(DeleteFile) : null, onModified: createCallback(CopyFile), triggerOnCreatedOnExistingFiles: true, cancellationToken: cancellationToken)); } else { return(FileSystemHook.RecoverableHook( recoveryStrategy, sourcePath, sourcePatterns, sourceIgnorePatterns, onCreated: createCallback(CopyFile), onDeleted: mirrorDeletions ? createCallback(DeleteFile) : null, onModified: createCallback(CopyFile), cancellationToken: cancellationToken )); } FileSystemEventHandler createCallback(Action <string> action) { void ErrorHandlerAndLogger(object sender, FileSystemEventArgs e) { logEntry(e); // don't await the task. Trigger asyncronously. We're on the "event loop thread" Task.Run(() => { try { action(e.FullPath); } catch (Exception ex) { logFailure(e, ex); return; } logSuccess(e); }); }; return(ErrorHandlerAndLogger); } void ensureDirectoryExists(string path) { string dir = Path.GetDirectoryName(path) !; Directory.CreateDirectory(dir); } void CopyFile(string fullPath) { string relativePath = ToRelativePath(fullPath, sourcePath); string dest = Path.Combine(destPath, relativePath); ensureDirectoryExists(dest); File.Copy(fullPath, dest, overwrite: true); } void DeleteFile(string fullPath) { string relativePath = ToRelativePath(fullPath, sourcePath); string dest = Path.Combine(destPath, relativePath); File.Delete(dest); } void logEntry(FileSystemEventArgs e) { try { logger?.LogEntry(e); } catch { } } void logFailure(FileSystemEventArgs e, Exception ex) { try { logger?.LogFailure(e, ex); } catch { } } void logSuccess(FileSystemEventArgs e) { try { logger?.LogSuccess(e); } catch { } } }
public async Task Can_repurpose_hook_on_parent_directory_on_directory_deletion() { // this test will hook on a directory, delete that, and the hook with the same hook on the parent directory string parentDir = GetTempDirectory(); string dir = Path.Combine(parentDir, "a"); Directory.CreateDirectory(dir); var erroredTcs = new TaskCompletionSource(); var createdTcs = new TaskCompletionSource(); IFileSystemWatcher hook = null !; using (hook = FileSystemHook.Hook(dir, sourcePatterns: Array.Empty <string>(), onError: onError, onCreated: onCreated)) { // Act 1 var completedTask = await WaitWithTimeout(erroredTcs.Task, () => { Directory.Delete(dir); }); // Assert 1 Assert.IsTrue(erroredTcs.Task.IsCompletedSuccessfully); Assert.IsTrue(hook.EnableRaisingEvents); Assert.AreEqual(hook.Path, parentDir); Assert.AreEqual(hook.Filter, "*"); // Act 2 await WaitWithTimeout(createdTcs.Task, () => { Assert.IsTrue(hook.EnableRaisingEvents); Directory.CreateDirectory(dir); }); // Assert 2 Assert.IsTrue(hook.EnableRaisingEvents); Assert.IsTrue(createdTcs.Task.IsCompletedSuccessfully); Assert.IsTrue(hook.EnableRaisingEvents); // Arrange 3 hook.Created -= onCreated; hook.Created += RecordTrigger(out var trigger); // Act 3 await WaitWithTimeout(trigger, () => { CreateFile(dir, "file"); }); // Assert 3 Assert.IsTrue(trigger.IsCompletedSuccessfully); } void onError(object sender, ErrorEventArgs e) { Assert.IsFalse(erroredTcs.Task.IsCompleted); hook.BeginInit(); hook.Path = parentDir; hook.NotifyFilter = allNotifyFilters; hook.EndInit(); Assert.IsTrue(hook.EnableRaisingEvents); // the unfortunate reality is that EnabledRaisingEvents is set to false after triggering this method // if we threw an exception, that would still set the directoryHandle to null, which would still need // EnableRaisingEvents to be set to true again Task.Run(() => { while (true) { if (!hook.EnableRaisingEvents) { hook.EnableRaisingEvents = true; erroredTcs.SetResult(); return; } } }); } void onCreated(object sender, FileSystemEventArgs e) { Assert.IsFalse(createdTcs.Task.IsCompleted); Thread.Sleep(100); hook.BeginInit(); hook.Path = dir; hook.Filter = ""; hook.NotifyFilter = allNotifyFilters; hook.EndInit(); createdTcs.SetResult(); } }