02-001 FileSystem 之 IFileProvider, IExpirationTrigger

代碼:html

    public interface IFileProvider
    {
        /// <summary>
        /// Locate a file at the given path.
        /// </summary>
        /// <param name="subpath">Relative path that identifies the file.</param>
        /// <returns>The file information. Caller must check Exists property.</returns>
        IFileInfo GetFileInfo(string subpath);

        /// <summary>
        /// Enumerate a directory at the given path, if any.
        /// </summary>
        /// <param name="subpath">Relative path that identifies the directory.</param>
        /// <returns>Returns the contents of the directory.</returns>
        IDirectoryContents GetDirectoryContents(string subpath);

        /// <summary>
        /// Creates a change trigger with the specified filter.
        /// </summary>
        /// <param name="filter">Filter string used to determine what files or folders to monitor. Example: **/*.cs, *.*, subFolder/**/*.cshtml.</param>
        /// <returns>An <see cref="IExpirationTrigger"/> that is triggered when a file matching <paramref name="filter"/> is added, modified or deleted.</returns>
        IExpirationTrigger Watch(string filter);
    }

應用的例子以下:async

var provider = new PhysicalFileProvider(Environment.CurrentDirectory);
 var info = provider.GetFileInfo("File.txt");
 var info2= provider.GetFileInfo("/File.txt");

 var  dir1= provider.GetFileInfo("");
 var  dir2= provider.GetFileInfo("sub");
 var  dir3= provider.GetFileInfo("/sub");

 Watch 的實現代碼:ide

    public IExpirationTrigger Watch(string filter)
        {
            if (filter == null)
            {
                return NoopTrigger.Singleton;
            }

            // Relative paths starting with a leading slash okay
            if (filter.StartsWith("/", StringComparison.Ordinal))
            {
                filter = filter.Substring(1);
            }

            // Absolute paths not permitted.
            if (Path.IsPathRooted(filter))
            {
                return NoopTrigger.Singleton;
            }

            return _filesWatcher.CreateFileChangeTrigger(filter);
        }

 Watch 的示例:oop

   var expirationTrigger = provider.Watch(fileName);
   int invocationCount = 0;
   expirationTrigger.RegisterExpirationCallback(_ => { invocationCount++; }, null);

首先Watch一個文件或者文件夾  ---- expirationTrigger = _triggerCache.GetOrAdd(fileName, new FileChangeTrigger(fileName));測試

 當文件系統 有變動時:Created Deleted Changed Renamed 時  (這裏指的是 root 文件夾:FileSystemWatcher(root);)ui

首先調用方法 OnFileSystemEntryChange     ---須要查找    _triggerCache.ContainsKey(relativePath)this

若是triggerCache 中匹配指定的文件或者文件夾  而後調用 ReportChangeForMatchedEntries   orm

而後: _triggerCache.TryRemove(pattern, out expirationTrigger)   htm

繼續調用   expirationTrigger.Changed();  實現代碼爲:  TokenSource.Cancel();blog

最後若是註冊有方法 則會調用 註冊的 callback 方法。

注意:這個Watch 是一次性的 就是變動後,下一次變動 註冊的方法就再也不調用了。

能夠經過測試用例來驗證:

 public async Task FileTrigger_NotTriggered_After_Expiry()
        {
            var fileName = Guid.NewGuid().ToString();
            var fileLocation = Path.Combine(Path.GetTempPath(), fileName);
            File.WriteAllText(fileLocation, "Content");
            var provider = new PhysicalFileProvider(Path.GetTempPath());

            var expirationTrigger = provider.Watch(fileName);
            int invocationCount = 0;
            expirationTrigger.RegisterExpirationCallback(_ => { invocationCount++; }, null);

            // Callback expected for this change.
            File.AppendAllText(fileLocation, "UpdatedContent1");

            // Callback not expected for this change.
            File.AppendAllText(fileLocation, "UpdatedContent2");

            // Wait for callbacks to be fired.
            await Task.Delay(WAIT_TIME_FOR_TRIGGER_TO_FIRE);

            invocationCount.ShouldBe(1);

            provider.GetFileInfo(fileName).Delete();
        }
相關文章
相關標籤/搜索