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
{