diff --git a/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs b/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs index 353a8585b..1951efbcd 100644 --- a/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs +++ b/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs @@ -58,6 +58,23 @@ public virtual async Task CanGetFileListForSingleFolderAsync() { } } + public virtual async Task CanGetFileListForSingleFileAsync() { + await ResetAsync(); + + var storage = GetStorage(); + if (storage == null) + return; + + using (storage) { + await storage.SaveFileAsync(@"archived\archived.txt", "archived"); + await storage.SaveFileAsync(@"archived\archived.csv", "archived"); + await storage.SaveFileAsync(@"q\new.txt", "new"); + await storage.SaveFileAsync(@"long/path/in/here/1.hey.stuff-2.json", "archived"); + + Assert.Equal(1, (await storage.GetFileListAsync(@"archived\archived.txt")).Count); + } + } + public virtual async Task CanGetPagedFileListForSingleFolderAsync() { await ResetAsync(); @@ -114,7 +131,7 @@ public virtual async Task CanGetFileInfoAsync() { var fileInfo = await storage.GetFileInfoAsync(Guid.NewGuid().ToString()); Assert.Null(fileInfo); - var startTime = SystemClock.UtcNow.Floor(TimeSpan.FromSeconds(1)); + var startTime = SystemClock.UtcNow.Subtract(TimeSpan.FromMinutes(1)); string path = $"folder\\{Guid.NewGuid()}-nested.txt"; Assert.True(await storage.SaveFileAsync(path, "test")); fileInfo = await storage.GetFileInfoAsync(path); @@ -246,7 +263,7 @@ public virtual async Task CanDeleteEntireFolderAsync() { await storage.SaveFileAsync(@"x\nested\world.csv", "nested world"); Assert.Equal(2, (await storage.GetFileListAsync()).Count); - await storage.DeleteFilesAsync(@"x"); + await storage.DeleteFilesAsync(@"x\*"); Assert.Empty(await storage.GetFileListAsync()); } } @@ -341,7 +358,7 @@ public virtual async Task CanDeleteNestedFolderAsync() { Assert.Equal(2, (await storage.GetFileListAsync(@"x\nested\*")).Count); Assert.Equal(2, (await storage.GetFileListAsync(@"x\*.txt")).Count); - await storage.DeleteFilesAsync(@"x\nested"); + await storage.DeleteFilesAsync(@"x\nested\*"); Assert.Single(await storage.GetFileListAsync()); Assert.True(await storage.ExistsAsync(@"x\hello.txt")); diff --git a/src/Foundatio/Storage/IFileStorage.cs b/src/Foundatio/Storage/IFileStorage.cs index 15467bcbe..02040927c 100644 --- a/src/Foundatio/Storage/IFileStorage.cs +++ b/src/Foundatio/Storage/IFileStorage.cs @@ -160,14 +160,11 @@ public static Task SaveFileAsync(this IFileStorage storage, string path, s public static async Task> GetFileListAsync(this IFileStorage storage, string searchPattern = null, int? limit = null, CancellationToken cancellationToken = default) { var files = new List(); - var result = await storage.GetPagedFileListAsync(100, searchPattern, cancellationToken).AnyContext(); + limit ??= Int32.MaxValue; + var result = await storage.GetPagedFileListAsync(Math.Min(limit.Value, 100), searchPattern, cancellationToken).AnyContext(); do { - foreach (var file in result.Files) { - files.Add(file); - if (limit.HasValue && limit.Value == files.Count) - return files; - } - } while (result.HasMore && await result.NextPageAsync().AnyContext()); + files.AddRange(result.Files); + } while (result.HasMore && files.Count < limit.Value && await result.NextPageAsync().AnyContext()); return files; } diff --git a/src/Foundatio/Storage/ScopedFileStorage.cs b/src/Foundatio/Storage/ScopedFileStorage.cs index a13b5f4dc..6b4ded3ab 100644 --- a/src/Foundatio/Storage/ScopedFileStorage.cs +++ b/src/Foundatio/Storage/ScopedFileStorage.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Foundatio.Serializer; @@ -83,14 +81,17 @@ public Task DeleteFileAsync(string path, CancellationToken cancellationTok } public Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellation = default) { - return UnscopedStorage.DeleteFilesAsync(String.Concat(_pathPrefix, searchPattern), cancellation); + searchPattern = !String.IsNullOrEmpty(searchPattern) ? String.Concat(_pathPrefix, searchPattern) : String.Concat(_pathPrefix, "*"); + return UnscopedStorage.DeleteFilesAsync(searchPattern, cancellation); } public async Task GetPagedFileListAsync(int pageSize = 100, string searchPattern = null, CancellationToken cancellationToken = default) { if (pageSize <= 0) return PagedFileListResult.Empty; - var unscopedResult = await UnscopedStorage.GetPagedFileListAsync(pageSize, String.Concat(_pathPrefix, searchPattern), cancellationToken).AnyContext(); + searchPattern = !String.IsNullOrEmpty(searchPattern) ? String.Concat(_pathPrefix, searchPattern) : String.Concat(_pathPrefix, "*"); + var unscopedResult = await UnscopedStorage.GetPagedFileListAsync(pageSize, searchPattern, cancellationToken).AnyContext(); + unscopedResult = unscopedResult.DeepClone(); foreach (var file in unscopedResult.Files) file.Path = file.Path.Substring(_pathPrefix.Length); diff --git a/tests/Foundatio.Tests/Storage/FolderFileStorageTests.cs b/tests/Foundatio.Tests/Storage/FolderFileStorageTests.cs index 5d5323ac5..7c1d85fb8 100644 --- a/tests/Foundatio.Tests/Storage/FolderFileStorageTests.cs +++ b/tests/Foundatio.Tests/Storage/FolderFileStorageTests.cs @@ -25,6 +25,11 @@ public override Task CanGetFileListForSingleFolderAsync() { public override Task CanGetPagedFileListForSingleFolderAsync() { return base.CanGetPagedFileListForSingleFolderAsync(); } + + [Fact] + public override Task CanGetFileListForSingleFileAsync() { + return base.CanGetFileListForSingleFileAsync(); + } [Fact] public override Task CanGetFileInfoAsync() { diff --git a/tests/Foundatio.Tests/Storage/InMemoryFileStorageTests.cs b/tests/Foundatio.Tests/Storage/InMemoryFileStorageTests.cs index 2e834b8fe..8b67322ed 100644 --- a/tests/Foundatio.Tests/Storage/InMemoryFileStorageTests.cs +++ b/tests/Foundatio.Tests/Storage/InMemoryFileStorageTests.cs @@ -20,6 +20,11 @@ public override Task CanGetEmptyFileListOnMissingDirectoryAsync() { public override Task CanGetFileListForSingleFolderAsync() { return base.CanGetFileListForSingleFolderAsync(); } + + [Fact] + public override Task CanGetFileListForSingleFileAsync() { + return base.CanGetFileListForSingleFileAsync(); + } [Fact] public override Task CanGetPagedFileListForSingleFolderAsync() { diff --git a/tests/Foundatio.Tests/Storage/ScopedFolderFileStorageTests.cs b/tests/Foundatio.Tests/Storage/ScopedFolderFileStorageTests.cs index b37a43ab6..4fce2390b 100644 --- a/tests/Foundatio.Tests/Storage/ScopedFolderFileStorageTests.cs +++ b/tests/Foundatio.Tests/Storage/ScopedFolderFileStorageTests.cs @@ -20,6 +20,11 @@ public override Task CanGetEmptyFileListOnMissingDirectoryAsync() { public override Task CanGetFileListForSingleFolderAsync() { return base.CanGetFileListForSingleFolderAsync(); } + + [Fact] + public override Task CanGetFileListForSingleFileAsync() { + return base.CanGetFileListForSingleFileAsync(); + } [Fact] public override Task CanGetPagedFileListForSingleFolderAsync() { diff --git a/tests/Foundatio.Tests/Storage/ScopedInMemoryFileStorageTests.cs b/tests/Foundatio.Tests/Storage/ScopedInMemoryFileStorageTests.cs new file mode 100644 index 000000000..90d8c84b1 --- /dev/null +++ b/tests/Foundatio.Tests/Storage/ScopedInMemoryFileStorageTests.cs @@ -0,0 +1,109 @@ +using System.Threading.Tasks; +using Foundatio.Storage; +using Xunit; +using Xunit.Abstractions; + +namespace Foundatio.Tests.Storage { + public class ScopedInMemoryFileStorageTests : FileStorageTestsBase { + public ScopedInMemoryFileStorageTests(ITestOutputHelper output) : base(output) {} + + protected override IFileStorage GetStorage() { + return new ScopedFileStorage(new InMemoryFileStorage { MaxFiles = 2000 }, "scoped"); + } + + [Fact] + public override Task CanGetEmptyFileListOnMissingDirectoryAsync() { + return base.CanGetEmptyFileListOnMissingDirectoryAsync(); + } + + [Fact] + public override Task CanGetFileListForSingleFolderAsync() { + return base.CanGetFileListForSingleFolderAsync(); + } + + [Fact] + public override Task CanGetFileListForSingleFileAsync() { + return base.CanGetFileListForSingleFileAsync(); + } + + [Fact] + public override Task CanGetPagedFileListForSingleFolderAsync() { + return base.CanGetPagedFileListForSingleFolderAsync(); + } + + [Fact] + public override Task CanGetFileInfoAsync() { + return base.CanGetFileInfoAsync(); + } + + [Fact] + public override Task CanGetNonExistentFileInfoAsync() { + return base.CanGetNonExistentFileInfoAsync(); + } + + [Fact] + public override Task CanSaveFilesAsync() { + return base.CanSaveFilesAsync(); + } + + [Fact] + public override Task CanManageFilesAsync() { + return base.CanManageFilesAsync(); + } + + [Fact] + public override Task CanRenameFilesAsync() { + return base.CanRenameFilesAsync(); + } + + [Fact] + public override Task CanConcurrentlyManageFilesAsync() { + return base.CanConcurrentlyManageFilesAsync(); + } + + [Fact] + public override void CanUseDataDirectory() { + base.CanUseDataDirectory(); + } + + [Fact] + public override Task CanDeleteEntireFolderAsync() { + return base.CanDeleteEntireFolderAsync(); + } + + [Fact] + public override Task CanDeleteEntireFolderWithWildcardAsync() { + return base.CanDeleteEntireFolderWithWildcardAsync(); + } + + [Fact] + public override Task CanDeleteFolderWithMultiFolderWildcardsAsync() { + return base.CanDeleteFolderWithMultiFolderWildcardsAsync(); + } + + [Fact] + public override Task CanDeleteSpecificFilesAsync() { + return base.CanDeleteSpecificFilesAsync(); + } + + [Fact] + public override Task CanDeleteNestedFolderAsync() { + return base.CanDeleteNestedFolderAsync(); + } + + [Fact] + public override Task CanDeleteSpecificFilesInNestedFolderAsync() { + return base.CanDeleteSpecificFilesInNestedFolderAsync(); + } + + [Fact] + public override Task CanRoundTripSeekableStreamAsync() { + return base.CanRoundTripSeekableStreamAsync(); + } + + [Fact] + public override Task WillRespectStreamOffsetAsync() { + return base.WillRespectStreamOffsetAsync(); + } + } +}