public void Test()
        {
            Assert.IsFalse(fp.GetDirectoryContents("/nonexisting").Exists);
            Assert.IsTrue(fp.GetDirectoryContents("/tmp").Exists);
            Assert.IsTrue(fp.GetDirectoryContents("/tmp/dir").Exists);
            Assert.IsTrue(fp.GetDirectoryContents("c:").Exists);
            Assert.IsFalse(fp.GetDirectoryContents("/tmp/helloworld.txt").Exists);
            Assert.IsTrue(fp.GetFileInfo("/tmp/helloworld.txt").Exists);
            Assert.IsFalse(fp.GetFileInfo("/tmp").Exists);

            // Read file
            using (Stream s = fp.GetFileInfo("/tmp/helloworld.txt").CreateReadStream())
            {
                byte[] data = new byte[100];
                int    c    = s.Read(data, 0, 100);
                Assert.AreEqual(c, HelloWorld.Length);
                for (int i = 0; i < c; i++)
                {
                    Assert.AreEqual(data[i], HelloWorld[i]);
                }
            }

            // Observe
            IChangeToken token      = fp.Watch("/tmp/**");
            Semaphore    semaphore  = new Semaphore(0, int.MaxValue);
            IDisposable  disposable = token.RegisterChangeCallback(o => semaphore.Release(), null);

            // Test, not activated
            Assert.IsFalse(semaphore.WaitOne(300));
            Assert.IsFalse(token.HasChanged);

            // Test, not activated
            ram.CreateFile("/not-monited-path.txt");
            Assert.IsFalse(semaphore.WaitOne(300));
            Assert.IsFalse(token.HasChanged);

            // Test, not activated
            disposable.Dispose();
            ram.CreateFile("/tmp/monited-path.txt");
            Assert.IsFalse(semaphore.WaitOne(300));
            Assert.IsTrue(token.HasChanged);

            // Observe again
            token      = fp.Watch("/tmp/**");
            semaphore  = new Semaphore(0, int.MaxValue);
            disposable = token.RegisterChangeCallback(o => semaphore.Release(), null);
            ram.CreateFile("/tmp/monited-path-again.txt");
            Assert.IsTrue(semaphore.WaitOne(300));
            Assert.IsTrue(token.HasChanged);

            // Shouldn't activate any more
            ram.CreateFile("/tmp/monited-path-again-one-more-time.txt");
            Assert.IsFalse(semaphore.WaitOne(300));
        }
示例#2
0
        public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetDeleted(bool useWildcard)
        {
            // Arrange
            using var rootOfFile = new DisposableFileSystem();

            string filePath = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());

            File.WriteAllText(filePath, "v1.1");

            using var rootOfLink = new DisposableFileSystem();
            string linkName = Path.GetRandomFileName();
            string linkPath = Path.Combine(rootOfLink.RootPath, linkName);

            File.CreateSymbolicLink(linkPath, filePath);

            string filter = useWildcard ? "*" : linkName;

            using var provider = new PhysicalFileProvider(rootOfLink.RootPath)
                  {
                      UsePollingFileWatcher = true, UseActivePolling = true
                  };
            IChangeToken token = provider.Watch(filter);

            var tcs = new TaskCompletionSource();

            token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);

            // Act
            File.Delete(linkPath);

            // Assert
            Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
                        $"Change event was not raised - current time: {DateTime.UtcNow:O}, file LastWriteTimeUtc: {File.GetLastWriteTimeUtc(filePath):O}.");
        }
示例#3
0
        public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink(bool useWildcard)
        {
            // Arrange
            using var rootOfFile = new DisposableFileSystem();
            string filePath = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());

            File.WriteAllText(filePath, "v1.1");

            using var rootOfLink = new DisposableFileSystem();
            string linkName = Path.GetRandomFileName();
            string linkPath = Path.Combine(rootOfLink.RootPath, linkName);

            File.CreateSymbolicLink(linkPath, filePath);

            using var provider = new PhysicalFileProvider(rootOfLink.RootPath)
                  {
                      UsePollingFileWatcher = true, UseActivePolling = true
                  };
            IChangeToken token = provider.Watch(useWildcard ? "*" : linkName);

            var tcs = new TaskCompletionSource();

            token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);

            // Act
            await Task.Delay(1000); // Wait a second before writing again, see https://github.com/dotnet/runtime/issues/55951.

            File.WriteAllText(filePath, "v1.2");

            // Assert
            Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
                        $"Change event was not raised - current time: {DateTime.UtcNow:O}, file LastWriteTimeUtc: {File.GetLastWriteTimeUtc(filePath):O}.");
        }
            void Watch()
            {
                IChangeToken t = _root.GetReloadToken();

                t.ActiveChangeCallbacks.Should().BeTrue();
                t.RegisterChangeCallback(SetChange, null);
            }
        public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetNotExists(bool useWildcard)
        {
            // Arrange
            using var rootOfLink = new TempDirectory(GetTestFilePath());
            string linkName = GetTestFileName();
            string linkPath = Path.Combine(rootOfLink.Path, linkName);

            File.CreateSymbolicLink(linkPath, "not-existent-file");

            // Act
            using var provider = new PhysicalFileProvider(rootOfLink.Path)
                  {
                      UsePollingFileWatcher = true, UseActivePolling = true
                  };
            IChangeToken token = provider.Watch(useWildcard ? "*" : linkName);

            var tcs = new TaskCompletionSource();

            token.RegisterChangeCallback(_ => { Assert.True(false, "Change event was raised when it was not expected."); }, null);

            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

            cts.Token.Register(() => tcs.TrySetCanceled());

            await Assert.ThrowsAsync <TaskCanceledException>(() => tcs.Task);
        }
 /// <summary>
 /// Create observer for one file.
 /// </summary>
 /// <param name="filesystem"></param>
 /// <param name="patternInfo"></param>
 /// <param name="observer"></param>
 /// <param name="state"></param>
 /// <param name="eventDispatcher"></param>
 public PatternObserver(IFileSystem filesystem, GlobPatternInfo patternInfo, IObserver <IEvent> observer, object state, IEventDispatcher eventDispatcher = default)
     : base(filesystem, patternInfo.Pattern, observer, state, eventDispatcher)
 {
     this.changeToken      = FileProvider.Watch(patternInfo.Pattern);
     this.previousSnapshot = ReadSnapshot();
     this.watcher          = changeToken.RegisterChangeCallback(OnEvent, this);
 }
        public void PhysicalFileProvider_MoniteFile_MoniteOneTime()
        {
            var          isCallBackInvoke = false;
            var          provider         = new PhysicalFileProvider(_appBasePath);
            IChangeToken token            = provider.Watch("appsettings.json");

            // 没有进行文件更改
            token.RegisterChangeCallback(_ => isCallBackInvoke = true, null);
            Task.Delay(TimeSpan.FromSeconds(2)).Wait();                 // 等待回调执行完成
            Assert.False(isCallBackInvoke);
            Assert.False(token.HasChanged);
            // 进行了文件更改
            var filePath    = Path.Combine(provider.Root, "appsettings.json");
            var fileContent = File.ReadAllText(filePath);

            File.WriteAllText(filePath, DateTime.Now.ToString(CultureInfo.InvariantCulture));
            Task.Delay(TimeSpan.FromSeconds(2)).Wait();                 // 等待回调执行完成
            Assert.True(isCallBackInvoke);
            Assert.True(token.HasChanged);
            // 只能监控一次
            isCallBackInvoke = false;
            File.WriteAllText(filePath, DateTime.Now.ToString(CultureInfo.InvariantCulture));
            Task.Delay(TimeSpan.FromSeconds(2)).Wait(); // 等待回调执行完成
            Assert.False(isCallBackInvoke);             // !!
            Assert.True(token.HasChanged);

            File.WriteAllText(filePath, fileContent);
            // Token.HasChanged没有set方法, 所以只能监控一次
            //token.HasChanged = false;
        }
示例#8
0
        public static async Task WaitForChange(this IChangeToken changeToken, int millisecondTimeout)
        {
            var                     tcs           = new TaskCompletionSource <IChangeToken>();
            IDisposable             waitForChange = null;
            CancellationTokenSource ct            = null;

            ct = new CancellationTokenSource(millisecondTimeout);

            ct.Token.Register(() => tcs.TrySetException(new TimeoutException()), useSynchronizationContext: false);

            waitForChange = changeToken.RegisterChangeCallback(_ => tcs.TrySetResult(changeToken), null);

            await tcs.Task;

            if (ct != null)
            {
                ct.Dispose();
                ct = null;
            }

            if (waitForChange != null)
            {
                waitForChange.Dispose();
                waitForChange = null;
            }
        }
示例#9
0
        private async Task BindAsync(CancellationToken cancellationToken)
        {
            await _bindSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

            try
            {
                if (_stopping == 1)
                {
                    throw new InvalidOperationException("Kestrel has already been stopped.");
                }

                IChangeToken?reloadToken = null;

                _serverAddresses.InternalCollection.PreventPublicMutation();

                if (Options.ConfigurationLoader?.ReloadOnChange == true && (!_serverAddresses.PreferHostingUrls || _serverAddresses.InternalCollection.Count == 0))
                {
                    reloadToken = Options.ConfigurationLoader.Configuration.GetReloadToken();
                }

                Options.ConfigurationLoader?.Load();

                await AddressBinder.BindAsync(Options.ListenOptions, AddressBindContext !, cancellationToken).ConfigureAwait(false);

                _configChangedRegistration = reloadToken?.RegisterChangeCallback(TriggerRebind, this);
            }
            finally
            {
                _bindSemaphore.Release();
            }
        }
 /// <summary>
 /// Create observer for one file.
 /// </summary>
 /// <param name="filesystem"></param>
 /// <param name="path"></param>
 /// <param name="observer"></param>
 /// <param name="state"></param>
 /// <param name="eventDispatcher">(optional)</param>
 /// <param name="option">(optional)</param>
 public FileObserver(IFileSystem filesystem, string path, IObserver <IEvent> observer, object state, IEventDispatcher eventDispatcher, IOption option)
     : base(filesystem, path, observer, state, eventDispatcher)
 {
     this.changeToken   = FileProvider.Watch(path);
     this.previousEntry = FileSystem.GetEntry(Filter, option);
     this.watcher       = changeToken.RegisterChangeCallback(OnEvent, this);
     this.option        = option;
 }
        private void PermissionsChangedCallback(object state)
        {
            logger.LogInformation("Reloading permissions.");
            SendServerCommand("permission reload");

            permissionsWatcher = fileProvider.Watch("permissions.json");
            permissionsWatcher.RegisterChangeCallback(PermissionsChangedCallback, null);
        }
        private void WhitelistChangedCallback(object state)
        {
            logger.LogInformation("Reloading whitelist.");
            SendServerCommand("whitelist reload");

            whitelistWatcher = fileProvider.Watch("whitelist.json");
            whitelistWatcher.RegisterChangeCallback(WhitelistChangedCallback, null);
        }
示例#13
0
 private void ConfigChangedCallback(object state)
 {
     using (disposibleRefHandled) { }
     appSettingsSection   = this.configurationRoot.GetSection("AppSettings");
     reloadToken          = appSettingsSection.GetReloadToken();
     disposibleRefHandled = reloadToken.RegisterChangeCallback(this.ConfigChangedCallback, null);
     settings             = appSettingsSection.GetChildren().ToDictionary(x => x.Key, x => x.Value);
     settingsIndex        = settings.Keys.ToList();
 }
        static void loadValues(string path)
        {
            var loadedValues = JsonConvert.DeserializeObject <Messages>(System.IO.File.ReadAllText(path));

            Instance = loadedValues;
            token    = _fileProvider.Watch("messageconfig.json");

            token.RegisterChangeCallback(changeCallBack, path);
        }
示例#15
0
        internal static bool StartInternal(ref StartParams startParams, ref ApiFunctionPointers apiFunctionPointers, string rootDir)
        {
            if (!File.Exists(m_configFilePath))
            {
                m_log.Log($"XPNet CLR: Will not load plugin because config file does not exist: (Path = {m_configFilePath}).");
                return(false);
            }

            m_config = GetConfig(m_configFilePath);
            if (m_config == null)
            {
                m_log.Log($"XPNet CLR: Will not load plugin because config file was unusable: (Path = {m_configFilePath}).");
                return(false);
            }

            m_configReloadToken         = m_config.GetReloadToken();
            m_configReloadTokenDisposer = m_configReloadToken.RegisterChangeCallback(o =>
            {
                m_log.Log($"XPNet CLR: Config file change detected: (Path = {m_configFilePath}).");

                m_log.Log($"XPNet CLR: Will reconfigure logging.");
                m_api.Log = m_log = ReconfigureLogging(forceLogging: false);

                m_log.Log($"XPNet CLR: Will tell plugin that config changed.");
                m_api.RaiseConfigChanged();
            }, state: null);

            // Make a local copy of the given set of API function pointers.
            ApiFunctions = new ApiFunctions(apiFunctionPointers);

            m_api = new XPlaneApi(m_log, m_config);

            m_plugin = LoadPlugin(rootDir);
            if (m_plugin == null)
            {
                m_log.Log("XPNet CLR: Failed to find a plugin to load.  Will tell X-Plane we failed to start.");
                return(false);
            }

            var typeInfo = m_plugin.GetType().GetTypeInfo();
            var xpattr   = typeInfo.GetCustomAttribute <XPlanePluginAttribute>();

            unsafe
            {
                fixed(byte *pc = startParams.Name)
                Interop.CopyCString(pc, 256, xpattr.Name);

                fixed(byte *pc = startParams.Signature)
                Interop.CopyCString(pc, 256, xpattr.Signature);

                fixed(byte *pc = startParams.Description)
                Interop.CopyCString(pc, 256, xpattr.Description);
            }

            return(true);
        }
示例#16
0
        private static async Task MainAsync()
        {
            IChangeToken token = _fileProvider.Watch("*.txt");
            var          tcs   = new TaskCompletionSource <object>();

            token.RegisterChangeCallback(state => ((TaskCompletionSource <object>)state).TrySetResult(null), tcs);
            await tcs.Task.ConfigureAwait(false);

            System.Console.WriteLine("quotes.txt changed");
        }
        // Note: we can't use DataSourceDependantCache here because we also need to handle a list of change
        // tokens, which is a complication most of our code doesn't have.
        private void Initialize()
        {
            lock (_lock)
            {
                _changeToken = new CompositeChangeToken(_dataSources.Select(d => d.ChangeToken).ToArray());
                _endpoints   = _dataSources.SelectMany(d => d.Endpoints).ToArray();

                _changeToken.RegisterChangeCallback((state) => Initialize(), null);
            }
        }
示例#18
0
            private void RegisterChangeTokenCallback(IChangeToken token)
            {
                if (token is null)
                {
                    return;
                }

                IDisposable registraton = token.RegisterChangeCallback(s => ((ChangeTokenRegistration <TState>)s).OnChangeTokenFired(), this);

                SetDisposable(registraton);
            }
        /// <summary>
        /// Create observer for one file.
        /// </summary>
        /// <param name="assetSource"></param>
        /// <param name="fileProvider"></param>
        /// <param name="observer"></param>
        /// <param name="filePath"></param>
        public FileProviderObserver(IAssetSource assetSource, IFileProvider fileProvider, IObserver <IAssetSourceEvent> observer, string filePath)
        {
            this.assetSource  = assetSource ?? throw new ArgumentNullException(nameof(assetSource));
            this.observer     = observer ?? throw new ArgumentNullException(nameof(observer));
            this.FilePath     = filePath ?? throw new ArgumentNullException(nameof(filePath));
            this.fileProvider = fileProvider ?? throw new ArgumentNullException(nameof(fileProvider));
            existed           = fileProvider.GetFileInfo(filePath).Exists ? 1 : 0;
            IChangeToken changeToken = fileProvider.Watch(filePath);

            watcher = changeToken.RegisterChangeCallback(OnEvent, this);
        }
示例#20
0
        public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetChanged(bool useWildcard, bool linkWasBroken)
        {
            // Arrange
            using var rootOfFile = new DisposableFileSystem();
            // Create file 2 first as we want to verify that the change is reported regardless of the timestamp being older.
            string file2Path = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());

            File.WriteAllText(file2Path, "v2.1");

            string file1Path = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());

            if (!linkWasBroken)
            {
                await Task.Delay(1000); // Wait a second before writing again, see https://github.com/dotnet/runtime/issues/55951.

                File.WriteAllText(file1Path, "v1.1");
            }

            using var rootOfLink = new DisposableFileSystem();
            string linkName = Path.GetRandomFileName();
            string linkPath = Path.Combine(rootOfLink.RootPath, linkName);

            File.CreateSymbolicLink(linkPath, file1Path);

            string filter = useWildcard ? "*" : linkName;

            using var provider = new PhysicalFileProvider(rootOfLink.RootPath)
                  {
                      UsePollingFileWatcher = true, UseActivePolling = true
                  };
            IChangeToken token = provider.Watch(filter);

            var tcs = new TaskCompletionSource <bool>();

            token.RegisterChangeCallback(_ => { tcs.TrySetResult(true); }, null);

            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

            cts.Token.Register(() => tcs.TrySetCanceled());

            // Act - Change link target to file 2.
            File.Delete(linkPath);
            try
            {
                File.CreateSymbolicLink(linkPath, file2Path);

                // Assert - It should report the change regardless of the timestamp being older.
                Assert.True(await tcs.Task,
                            $"Change event was not raised - current time: {DateTime.UtcNow:O}, file1 LastWriteTimeUtc: {File.GetLastWriteTimeUtc(file1Path):O}, file2 LastWriteTime: {File.GetLastWriteTimeUtc(file2Path):O}.");
            }
            // https://github.com/dotnet/runtime/issues/56810
            catch (UnauthorizedAccessException) { }
        }
示例#21
0
        public static void Load()
        {
            var builder = new ConfigurationBuilder()
                          .AddJsonFile("appsettings.json", false, true)
                          .AddJsonFile("appsettings.env.json", true, true);

            Config = builder.Build();
            Token  = Config.GetReloadToken();
            Token.RegisterChangeCallback(OnChange, null);

            OnLoad?.Invoke();
        }
示例#22
0
        private static async Task MainAsync()
        {
            var          fileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory());
            IChangeToken token        = fileProvider.Watch(_fileFilter);
            var          tcs          = new TaskCompletionSource <object>();

            token.RegisterChangeCallback(state =>
                                         ((TaskCompletionSource <object>)state).TrySetResult(null), tcs);

            await tcs.Task.ConfigureAwait(false);

            Console.WriteLine("file changed");
        }
示例#23
0
        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder();

            builder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
            //builder.AddJsonFile("appsettings.Development.json");
            //builder.AddJsonFile("appsettings.ini");
            var configurationRoot = builder.Build();

            var config = new Config()
            {
                Key1 = "config key1",
                Key5 = false
            };



            configurationRoot.GetSection("OrderService").Bind(config,
                                                              binderOptions => { binderOptions.BindNonPublicProperties = true; });

            Console.WriteLine($"Key1:{config.Key1}");
            Console.WriteLine($"Key5:{config.Key5}");
            Console.WriteLine($"Key6:{config.Key6}");



            //监控配置文件变更
            IChangeToken token = configurationRoot.GetReloadToken();

            token.RegisterChangeCallback(state =>
            {
                Console.WriteLine($"Key1:{configurationRoot["Key1"]}");
                Console.WriteLine($"Key2:{configurationRoot["Key2"]}");
                Console.WriteLine($"Key3:{configurationRoot["Key3"]}");
            }, configurationRoot);


            ChangeToken.OnChange(() => configurationRoot.GetReloadToken(), () =>
            {
                Console.WriteLine($"Key1:{configurationRoot["Key1"]}");
                Console.WriteLine($"Key2:{configurationRoot["Key2"]}");
                Console.WriteLine($"Key3:{configurationRoot["Key3"]}");
            });
            Console.WriteLine("开始了");

            Console.ReadKey();
            //Console.WriteLine($"Key1:{configurationRoot["Key1"]}");
            //Console.WriteLine($"Key2:{configurationRoot["Key2"]}");
            //Console.WriteLine($"Key3:{configurationRoot["Key3"]}");
            Console.ReadKey();
        }
示例#24
0
        private void ResetChangeSourceCore()
        {
            if (_changeTokenReleaser != null)
            {
                _changeTokenReleaser.Dispose();
                _changeTokenReleaser = null;
            }

            if (_changeTokenFactory != null)
            {
                IChangeToken changeToken = _changeTokenFactory();
                _changeTokenReleaser = changeToken.RegisterChangeCallback(OnChanged, null);
            }
        }
示例#25
0
        public MarkdownProvider(string repository)
        {
            this.path   = Path.Combine(repository, "content");
            this.source = new PhysicalFileProvider(path, ExclusionFilters.Sensitive);

            converter = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
            changes   = source.Watch("*.md");
            changes.RegisterChangeCallback(o => OnSourceChanged(), changes);
            header = source.GetFileInfo("header.html");
            footer = source.GetFileInfo("footer.html");
            lock (renderedPages)
            {
                renderDirectory(new DirectoryInfo(path)).GetAwaiter().GetResult();
            }
        }
        private void PropertiesChangedCallback(object state)
        {
            logger.LogInformation("Server properties changed, triggering server restart.");

            if (CurrentPlayerCount() != 0)
            {
                SendServerCountdown("Server restart in {0}.", 30, 20, 10, 5, 3, 2, 1);
            }

            SendServerMessage("Restarting server now.");
            StopServer();
            StartServer();

            propertiesWatcher = fileProvider.Watch("server.properties");
            propertiesWatcher.RegisterChangeCallback(PropertiesChangedCallback, null);
        }
示例#27
0
        private IChangeToken InitFileWatcher(string directoryPath)
        {
            string pattern = directoryPath + "/**/*";

            this._logger.LogDebug($"Initialize watcher for directory {pattern}");
            IChangeToken watcher = this._hostingEnvironment.WebRootFileProvider.Watch(pattern);

            if (!watcher.ActiveChangeCallbacks)
            {
                this._logger.LogWarning("File watcher does not support active callbacks. File lookup cache refresh not supported.");
                return(watcher);
            }

            watcher.RegisterChangeCallback(_ => this.OnFileDirectoryChanged(), null);
            return(null);
        }
示例#28
0
 internal void AttachTokens(CacheEntry cacheEntry)
 {
     if (_expirationTokens != null)
     {
         lock (this)
         {
             for (int i = 0; i < _expirationTokens.Count; i++)
             {
                 IChangeToken expirationToken = _expirationTokens[i];
                 if (expirationToken.ActiveChangeCallbacks)
                 {
                     _expirationTokenRegistrations ??= new List <IDisposable>(1);
                     IDisposable registration = expirationToken.RegisterChangeCallback(ExpirationCallback, cacheEntry);
                     _expirationTokenRegistrations.Add(registration);
                 }
             }
         }
     }
 }
示例#29
0
        public void Test1()
        {
            Debug.WriteLine("开始监测文件夹");

            string rootPath        = Directory.GetCurrentDirectory();
            var    phyFileProvider = new PhysicalFileProvider(rootPath);

            IChangeToken changeToken = phyFileProvider.Watch("*.json");

            changeToken.RegisterChangeCallback(_ =>
            {
                CallBack();
            }, "xiaoming");

            Console.ReadLine();

            /*
             * 只能触发一次
             */
        }
示例#30
0
        private static void WatchForFileChanges()
        {
            IEnumerable <string> files = Directory.EnumerateFiles(_filePath, "*.*", SearchOption.AllDirectories);

            foreach (string file in files)
            {
                if (_files.TryGetValue(file, out DateTime existingTime))
                {
                    _files.TryUpdate(file, File.GetLastWriteTime(file), existingTime);
                }
                else
                {
                    if (File.Exists(file))
                    {
                        _files.TryAdd(file, File.GetLastWriteTime(file));
                    }
                }
            }
            _fileChangeToken = _fileProvider.Watch("**/*.*");
            _fileChangeToken.RegisterChangeCallback(Notify, default);
        }