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");
        }
Beispiel #2
0
        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);
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        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);
        }
Beispiel #6
0
        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);
        }
Beispiel #7
0
        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);
        }
Beispiel #8
0
        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);
        }
Beispiel #9
0
        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
            {
            }
        }
    }
Beispiel #12
0
        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();
            }
        }