diff --git a/src/FileProviderExtensions.cs b/src/FileProviderExtensions.cs index c69abcb..04dbb4e 100644 --- a/src/FileProviderExtensions.cs +++ b/src/FileProviderExtensions.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; @@ -26,7 +27,8 @@ public static byte[] ReadAsBytes(this IFileInfo file) /// Reads the entire file into a byte array. /// The file to read. /// (awaitable) The contents of the file as a byte array. - public static async Task ReadAsBytesAsync(this IFileInfo file) + /// Cancellation token. + public static async Task ReadAsBytesAsync(this IFileInfo file, CancellationToken cancel = default) { using var stream = file.CreateReadStream(); if (stream is MemoryStream ms) @@ -35,7 +37,7 @@ public static async Task ReadAsBytesAsync(this IFileInfo file) } using var output = new MemoryStream(); - await stream.CopyToAsync(output); + await stream.CopyToAsync(output, 81920, cancel); return output.ToArray(); } diff --git a/src/InMemory/InMemoryFileInfo.cs b/src/InMemory/InMemoryFileInfo.cs index 8b3fdec..43aac05 100644 --- a/src/InMemory/InMemoryFileInfo.cs +++ b/src/InMemory/InMemoryFileInfo.cs @@ -33,7 +33,7 @@ public InMemoryFileInfo(string name, string content, DateTimeOffset? lastModifie /// public string? Name { get; } /// - public DateTimeOffset LastModified { get; } + public DateTimeOffset LastModified { get; internal set; } /// public bool Exists => true; diff --git a/src/InMemory/InMemoryFileProvider.cs b/src/InMemory/InMemoryFileProvider.cs index deb2685..d856f6e 100644 --- a/src/InMemory/InMemoryFileProvider.cs +++ b/src/InMemory/InMemoryFileProvider.cs @@ -200,4 +200,22 @@ private static string NormalizePath(string path) if (path == null) { throw new ArgumentNullException(nameof(path)); } return path.Replace("\\", "/").TrimStart('/'); } + + /// + public void ChangeLastModified(string path, DateTimeOffset lastModified) + { + var fileInfo = this[path]; + if (fileInfo is null) + { + throw new FileNotFoundException("File not found", path); + } + + if (fileInfo is InMemoryFileInfo inMemoryFileInfo) + { + inMemoryFileInfo.LastModified = lastModified; + return; + } + + throw new InvalidOperationException("Cannot change last modified on a non-in-memory file"); + } } \ No newline at end of file diff --git a/src/Mirality.FileProviders.InMemory.csproj b/src/Mirality.FileProviders.InMemory.csproj index 95e89c3..d4205b8 100644 --- a/src/Mirality.FileProviders.InMemory.csproj +++ b/src/Mirality.FileProviders.InMemory.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0 10 6.2.1 enable diff --git a/src/WritableFileProvider.cs b/src/WritableFileProvider.cs index 4c3d2bb..9372dbc 100644 --- a/src/WritableFileProvider.cs +++ b/src/WritableFileProvider.cs @@ -1,9 +1,9 @@ -using Microsoft.Extensions.FileProviders; -using System; +using System; using System.IO; using System.Text; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.FileProviders; namespace Mirality.FileProviders; @@ -19,6 +19,13 @@ public interface IWritableFileProvider : IFileProvider /// It's not an error if the file already does not exist. /// The full relative file path. void Delete(string path); + + /// + /// Changes the last modified time of a file. + /// + /// The full relative file path. + /// When the file was last modified. + void ChangeLastModified(string path, DateTimeOffset lastModified); } /// An that provides synchronous write operations. diff --git a/src/WritablePhysicalFileProvider.cs b/src/WritablePhysicalFileProvider.cs index 535bb3b..d1d5a77 100644 --- a/src/WritablePhysicalFileProvider.cs +++ b/src/WritablePhysicalFileProvider.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Primitives; -using System; +using System; using System.IO; using System.Linq; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; namespace Mirality.FileProviders; @@ -138,4 +138,11 @@ public void Delete(string path) file.Delete(); } + + /// + public void ChangeLastModified(string path, DateTimeOffset lastModified) + { + var file = GetPhysicalFileInfo(path); + file.LastWriteTimeUtc = lastModified.UtcDateTime; + } } \ No newline at end of file diff --git a/tests/InMemoryFileProviderTests.cs b/tests/InMemoryFileProviderTests.cs index edc1843..0650423 100644 --- a/tests/InMemoryFileProviderTests.cs +++ b/tests/InMemoryFileProviderTests.cs @@ -93,6 +93,21 @@ public void ExistingFiles() }); } + [Test] + public void ChangeLastModified() + { + var provider = new InMemoryFileProvider(); + var initialTimestamp = new DateTimeOffset(2021, 12, 25, 12, 34, 56, TimeSpan.Zero); + var updatedTimestamp = new DateTimeOffset(2022, 1, 2, 3, 4, 5, TimeSpan.Zero); + + provider.Write("test.txt", "hello world", initialTimestamp); + provider.ChangeLastModified("test.txt", updatedTimestamp); + + var file = provider.GetFileInfo("test.txt"); + Assert.That(file.Exists); + Assert.That(file.LastModified, Is.EqualTo(updatedTimestamp)); + } + [Test] public void FileInfoForDirectories() { diff --git a/tests/Mirality.FileProviders.InMemory.Tests.csproj b/tests/Mirality.FileProviders.InMemory.Tests.csproj index 593812e..988e921 100644 --- a/tests/Mirality.FileProviders.InMemory.Tests.csproj +++ b/tests/Mirality.FileProviders.InMemory.Tests.csproj @@ -1,7 +1,7 @@  - net48;net6.0 + net48;net8.0;net9.0;net10.0 10.0 enable enable diff --git a/tests/WritablePhysicalFileProviderTests.cs b/tests/WritablePhysicalFileProviderTests.cs index 613b2ad..6066446 100644 --- a/tests/WritablePhysicalFileProviderTests.cs +++ b/tests/WritablePhysicalFileProviderTests.cs @@ -163,7 +163,7 @@ public async Task WriteBytesAsyncGeneric() var timestamp = new DateTimeOffset(2021, 12, 25, 12, 34, 56, TimeSpan.Zero); var bytes = new byte[] { 1, 2, 3, 4, 5, 6 }; - var file = await ((IWritableFileProvider) provider).WriteAsync("test.bin", bytes, timestamp); + var file = await ((IWritableFileProvider)provider).WriteAsync("test.bin", bytes, timestamp); Assert.That(file.Exists); Assert.That(_PhysicalFileProvider.GetFileInfo("test.bin").Exists); @@ -176,13 +176,28 @@ public async Task WriteTextAsyncGeneric() var provider = new WritablePhysicalFileProvider(_TestDirectory, _PhysicalFileProvider); var timestamp = new DateTimeOffset(2021, 12, 25, 12, 34, 56, TimeSpan.Zero); - var file = await ((IWritableFileProvider) provider).WriteAsync("test.txt", "hello world", timestamp); + var file = await ((IWritableFileProvider)provider).WriteAsync("test.txt", "hello world", timestamp); Assert.That(file.Exists); Assert.That(_PhysicalFileProvider.GetFileInfo("test.txt").Exists); Assert.That(_PhysicalFileProvider.GetFileInfo("test.txt").LastModified, Is.EqualTo(timestamp)); } + [Test] + public async Task ChangeLastModified() + { + var provider = new WritablePhysicalFileProvider(_TestDirectory, _PhysicalFileProvider); + var initialTimestamp = new DateTimeOffset(2021, 12, 25, 12, 34, 56, TimeSpan.Zero); + var updatedTimestamp = new DateTimeOffset(2022, 1, 2, 3, 4, 5, TimeSpan.Zero); + + await provider.WriteAsync("test.txt", "hello world", initialTimestamp); + provider.ChangeLastModified("test.txt", updatedTimestamp); + + var file = _PhysicalFileProvider.GetFileInfo("test.txt"); + Assert.That(file.Exists); + Assert.That(file.LastModified, Is.InRange(updatedTimestamp.AddSeconds(-1), updatedTimestamp.AddSeconds(1))); // for some reason this is one second off. + } + [Test] public void Delete() {