Skip to content

Commit 8a2dedd

Browse files
committed
VFS
1 parent 9ac8955 commit 8a2dedd

2 files changed

Lines changed: 263 additions & 6 deletions

File tree

Components/Pages/Gallery.razor

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,118 @@
1-
@page "/gallery"
1+
@page "/gallery"
2+
@using ManagedCode.Storage.FileSystem
3+
@using ManagedCode.Storage.FileSystem.Options
4+
@implements IAsyncDisposable
25

36
<h3>Gallery @folderName</h3>
47

8+
<button @onclick="RefreshEntries" class="btn btn-primary mb-3">Оновити список</button>
9+
10+
@if (entries == null)
11+
{
12+
<p><em>Завантаження...</em></p>
13+
}
14+
else if (entries.Count == 0)
15+
{
16+
<p><em>Немає файлів у цій папці</em></p>
17+
}
18+
else
19+
{
20+
<div class="row">
21+
@foreach (var entry in entries)
22+
{
23+
<div class="col-md-3 mb-3">
24+
<div class="card">
25+
<div class="card-body">
26+
<h5 class="card-title">@entry.Name</h5>
27+
<p class="card-text">
28+
<small class="text-muted">
29+
Тип: @GetEntryType(entry)<br/>
30+
Шлях: @entry.Path
31+
</small>
32+
</p>
33+
</div>
34+
</div>
35+
</div>
36+
}
37+
</div>
38+
<p class="mt-3">Всього елементів: @entries.Count</p>
39+
}
40+
541
@code {
642

743
[Inject]
844
IVirtualFileSystem storage { get; set; } = default!;
945

1046
string? folderName;
47+
string? folderPath;
48+
List<IVfsNode>? entries;
49+
VirtualFileSystemTestContext? context;
1150

1251
protected override async Task OnInitializedAsync()
1352
{
53+
await LoadEntries();
54+
}
55+
56+
private async Task LoadEntries()
57+
{
58+
var folderInfo = await FolderPicker.Default.PickAsync();
59+
60+
if (folderInfo?.Folder == null)
61+
return;
62+
63+
folderName = folderInfo.Folder.Name;
64+
folderPath = folderInfo.Folder.Path;
65+
66+
var options = new FileSystemStorageOptions
67+
{
68+
BaseFolder = folderPath,
69+
CreateContainerIfNotExists = true
70+
};
71+
72+
var storage = new FileSystemStorage(options);
1473

15-
var folderInfo = (await FolderPicker.Default.PickAsync()).Folder;
74+
//create context with base folder
75+
context = await VirtualFileSystemTestContext.CreateAsync(
76+
storage,
77+
containerName: string.Empty,
78+
ownsStorage: true,
79+
serviceProvider: null,
80+
cleanup: null);
1681

17-
folderName = folderInfo?.Name;
18-
var vfsPath = new VfsPath(folderInfo.Path);
19-
var entries = new List<IVfsNode>();
20-
await foreach (var entry in storage.ListAsync(vfsPath, new ListOptions { PageSize = 2 }))
82+
var vfsPath = new VfsPath("/"); // Root of the selected folder, becase this is a root of VFS
83+
entries = new List<IVfsNode>();
84+
await foreach (var entry in context.FileSystem.ListAsync(vfsPath, new ListOptions { PageSize = 100 }))
2185
{
2286
entries.Add(entry);
2387
}
88+
89+
StateHasChanged();
90+
}
91+
92+
private async Task RefreshEntries()
93+
{
94+
if (context != null && !string.IsNullOrEmpty(folderPath))
95+
{
96+
var vfsPath = new VfsPath("/");
97+
entries = new List<IVfsNode>();
98+
await foreach (var entry in context.FileSystem.ListAsync(vfsPath, new ListOptions { PageSize = 100 }))
99+
{
100+
entries.Add(entry);
101+
}
102+
StateHasChanged();
103+
}
104+
}
105+
106+
private string GetEntryType(IVfsNode entry)
107+
{
108+
return entry.GetType().Name.Contains("Directory") ? "Папка" : "Файл";
109+
}
110+
111+
public async ValueTask DisposeAsync()
112+
{
113+
if (context != null)
114+
{
115+
await context.DisposeAsync();
116+
}
24117
}
25118
}

VirtualFileSystemTestContext.cs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
using System.Collections.Concurrent;
2+
using ManagedCode.Storage.Core;
3+
using ManagedCode.Storage.Core.Models;
4+
using ManagedCode.Storage.VirtualFileSystem.Metadata;
5+
using ManagedCode.Storage.VirtualFileSystem.Options;
6+
using Microsoft.Extensions.Caching.Memory;
7+
using Microsoft.Extensions.Logging.Abstractions;
8+
using Microsoft.Extensions.Options;
9+
using VfsImplementation = ManagedCode.Storage.VirtualFileSystem.Implementations.VirtualFileSystem;
10+
11+
namespace MediaLibrary;
12+
13+
public sealed class VirtualFileSystemTestContext : IAsyncDisposable
14+
{
15+
private readonly bool _ownsStorage;
16+
private readonly IServiceProvider? _serviceProvider;
17+
private readonly Func<ValueTask>? _cleanup;
18+
private readonly MemoryCache _cache;
19+
20+
private VirtualFileSystemTestContext(
21+
IStorage storage,
22+
TestMetadataManager metadataManager,
23+
VfsImplementation fileSystem,
24+
MemoryCache cache,
25+
bool ownsStorage,
26+
IServiceProvider? serviceProvider,
27+
string containerName,
28+
Func<ValueTask>? cleanup)
29+
{
30+
Storage = storage;
31+
MetadataManager = metadataManager;
32+
FileSystem = fileSystem;
33+
ContainerName = containerName;
34+
_cache = cache;
35+
_ownsStorage = ownsStorage;
36+
_serviceProvider = serviceProvider;
37+
_cleanup = cleanup;
38+
}
39+
40+
public IStorage Storage { get; }
41+
public TestMetadataManager MetadataManager { get; }
42+
public VfsImplementation FileSystem { get; }
43+
public string ContainerName { get; }
44+
45+
public static async Task<VirtualFileSystemTestContext> CreateAsync(
46+
IStorage storage,
47+
string containerName,
48+
bool ownsStorage,
49+
IServiceProvider? serviceProvider,
50+
Func<ValueTask>? cleanup = null)
51+
{
52+
var metadataManager = new TestMetadataManager(storage);
53+
var cache = new MemoryCache(new MemoryCacheOptions());
54+
var options = Options.Create(new VfsOptions
55+
{
56+
DefaultContainer = containerName,
57+
DirectoryStrategy = DirectoryStrategy.Virtual,
58+
EnableCache = true
59+
});
60+
61+
var vfs = new VfsImplementation(
62+
storage,
63+
metadataManager,
64+
options,
65+
cache,
66+
NullLogger<VfsImplementation>.Instance);
67+
68+
var createResult = await storage.CreateContainerAsync();
69+
if (!createResult.IsSuccess)
70+
{
71+
throw new InvalidOperationException($"Failed to create container '{containerName}'.");
72+
}
73+
74+
return new VirtualFileSystemTestContext(storage, metadataManager, vfs, cache, ownsStorage, serviceProvider, containerName, cleanup);
75+
}
76+
77+
public async ValueTask DisposeAsync()
78+
{
79+
await FileSystem.DisposeAsync();
80+
81+
if (_cleanup is not null)
82+
{
83+
await _cleanup();
84+
}
85+
86+
_cache.Dispose();
87+
88+
if (_ownsStorage)
89+
{
90+
switch (Storage)
91+
{
92+
case IAsyncDisposable asyncDisposable:
93+
await asyncDisposable.DisposeAsync();
94+
break;
95+
case IDisposable disposable:
96+
disposable.Dispose();
97+
break;
98+
}
99+
}
100+
101+
if (_serviceProvider is IAsyncDisposable asyncProvider)
102+
{
103+
await asyncProvider.DisposeAsync();
104+
}
105+
else if (_serviceProvider is IDisposable disposableProvider)
106+
{
107+
disposableProvider.Dispose();
108+
}
109+
}
110+
}
111+
112+
public sealed class TestMetadataManager : IMetadataManager
113+
{
114+
private readonly IStorage _storage;
115+
private readonly ConcurrentDictionary<string, VfsMetadata> _metadata = new();
116+
private readonly ConcurrentDictionary<string, IReadOnlyDictionary<string, string>> _customMetadata = new();
117+
118+
public TestMetadataManager(IStorage storage)
119+
{
120+
_storage = storage;
121+
}
122+
123+
public int BlobInfoRequests { get; private set; }
124+
public int CustomMetadataRequests { get; private set; }
125+
126+
public void ResetCounters()
127+
{
128+
BlobInfoRequests = 0;
129+
CustomMetadataRequests = 0;
130+
}
131+
132+
public Task SetVfsMetadataAsync(string blobName, VfsMetadata metadata, IDictionary<string, string>? customMetadata = null, string? expectedETag = null, CancellationToken cancellationToken = default)
133+
{
134+
_metadata[blobName] = metadata;
135+
_customMetadata[blobName] = customMetadata is null
136+
? new Dictionary<string, string>()
137+
: new Dictionary<string, string>(customMetadata);
138+
return Task.CompletedTask;
139+
}
140+
141+
public Task<VfsMetadata?> GetVfsMetadataAsync(string blobName, CancellationToken cancellationToken = default)
142+
{
143+
_metadata.TryGetValue(blobName, out var metadata);
144+
return Task.FromResult(metadata);
145+
}
146+
147+
public Task<IReadOnlyDictionary<string, string>> GetCustomMetadataAsync(string blobName, CancellationToken cancellationToken = default)
148+
{
149+
CustomMetadataRequests++;
150+
if (_customMetadata.TryGetValue(blobName, out var metadata))
151+
{
152+
return Task.FromResult(metadata);
153+
}
154+
155+
return Task.FromResult<IReadOnlyDictionary<string, string>>(new Dictionary<string, string>());
156+
}
157+
158+
public async Task<BlobMetadata?> GetBlobInfoAsync(string blobName, CancellationToken cancellationToken = default)
159+
{
160+
BlobInfoRequests++;
161+
var result = await _storage.GetBlobMetadataAsync(blobName, cancellationToken);
162+
return result.IsSuccess ? result.Value : null;
163+
}
164+
}

0 commit comments

Comments
 (0)