From 0cd9d4e89d07e721e1af6d2a46cd67a48d340dc2 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Thu, 14 Jun 2018 00:31:46 +0300 Subject: [PATCH] Implemented file watch for dynamic file provider. --- .../VirtualFileSystem/DynamicFileProvider.cs | 67 +++++++++++++++++-- .../DynamicFileProvider_Tests.cs | 49 ++++++++++++++ 2 files changed, 112 insertions(+), 4 deletions(-) diff --git a/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DynamicFileProvider.cs b/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DynamicFileProvider.cs index 7bb625e94b..dcf95222ce 100644 --- a/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DynamicFileProvider.cs +++ b/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DynamicFileProvider.cs @@ -1,18 +1,29 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; using Volo.Abp.DependencyInjection; namespace Volo.Abp.VirtualFileSystem { - //TODO: Implement Watch! - //TODO: Work with dictionaries? + //TODO: Work with directory & wildcard watches! + //TODO: Work with dictionaries! + + /// + /// Current implementation only supports file watch. + /// Does not support directory or wildcard watches. + /// public class DynamicFileProvider : DictionaryBasedFileProvider, IDynamicFileProvider, ISingletonDependency { protected override IDictionary Files => DynamicFiles; protected ConcurrentDictionary DynamicFiles { get; } + private readonly ConcurrentDictionary _filePathTokenLookup = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + public DynamicFileProvider() { DynamicFiles = new ConcurrentDictionary(); @@ -21,11 +32,59 @@ namespace Volo.Abp.VirtualFileSystem public void AddOrUpdate(IFileInfo fileInfo) { DynamicFiles.AddOrUpdate(fileInfo.PhysicalPath, fileInfo, (key, value) => fileInfo); + ReportChange(fileInfo.PhysicalPath); } public bool Delete(string filePath) { - return DynamicFiles.TryRemove(filePath, out _); + if (!DynamicFiles.TryRemove(filePath, out _)) + { + return false; + } + + ReportChange(filePath); + return true; + } + + public override IChangeToken Watch(string filter) + { + return GetOrAddChangeToken(filter); + } + + private IChangeToken GetOrAddChangeToken(string filePath) + { + if (!_filePathTokenLookup.TryGetValue(filePath, out var tokenInfo)) + { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationChangeToken = new CancellationChangeToken(cancellationTokenSource.Token); + tokenInfo = new ChangeTokenInfo(cancellationTokenSource, cancellationChangeToken); + tokenInfo = _filePathTokenLookup.GetOrAdd(filePath, tokenInfo); + } + + return tokenInfo.ChangeToken; + } + + private void ReportChange(string filePath) + { + if (_filePathTokenLookup.TryRemove(filePath, out var tokenInfo)) + { + tokenInfo.TokenSource.Cancel(); + } + } + + private struct ChangeTokenInfo + { + public ChangeTokenInfo( + CancellationTokenSource tokenSource, + CancellationChangeToken changeToken) + { + TokenSource = tokenSource; + ChangeToken = changeToken; + } + + public CancellationTokenSource TokenSource { get; } + + public CancellationChangeToken ChangeToken { get; } } } } \ No newline at end of file diff --git a/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/DynamicFileProvider_Tests.cs b/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/DynamicFileProvider_Tests.cs index 82ef7a6683..4fd4c03d46 100644 --- a/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/DynamicFileProvider_Tests.cs +++ b/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/DynamicFileProvider_Tests.cs @@ -1,6 +1,7 @@ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; using Shouldly; using Volo.Abp.Modularity; using Xunit; @@ -34,6 +35,54 @@ namespace Volo.Abp.VirtualFileSystem fileInfo.ReadAsString().ShouldBe(fileContent); } + [Fact] + public void Should_Get_Notified_On_File_Change() + { + //Create a dynamic file + + _dynamicFileProvider.AddOrUpdate( + new InMemoryFileInfo( + "Hello World".GetBytes(), + "/my-files/test.txt", + "test.txt" + ) + ); + + //Register to change on that file + + var fileCallbackCalled = false; + + ChangeToken.OnChange( + () => _dynamicFileProvider.Watch("/my-files/test.txt"), + () => { fileCallbackCalled = true; }); + + //Updating the file should trigger the callback + + _dynamicFileProvider.AddOrUpdate( + new InMemoryFileInfo( + "Hello World UPDATED".GetBytes(), + "/my-files/test.txt", + "test.txt" + ) + ); + + fileCallbackCalled.ShouldBeTrue(); + + //Updating the file should trigger the callback (2nd test) + + fileCallbackCalled = false; + + _dynamicFileProvider.AddOrUpdate( + new InMemoryFileInfo( + "Hello World UPDATED 2".GetBytes(), + "/my-files/test.txt", + "test.txt" + ) + ); + + fileCallbackCalled.ShouldBeTrue(); + } + [DependsOn(typeof(AbpVirtualFileSystemModule))] public class TestModule : AbpModule {