-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAzureDirectory.cs
More file actions
173 lines (146 loc) · 6.95 KB
/
AzureDirectory.cs
File metadata and controls
173 lines (146 loc) · 6.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using Azure;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
namespace Ramstack.FileSystem.Azure;
/// <summary>
/// Represents an implementation of <see cref="VirtualDirectory"/> that maps a directory
/// to the path within an Azure Blob Storage container.
/// </summary>
internal sealed class AzureDirectory : VirtualDirectory
{
private readonly AzureFileSystem _fs;
/// <inheritdoc />
public override IVirtualFileSystem FileSystem => _fs;
/// <summary>
/// Initializes a new instance of the <see cref="AzureDirectory"/> class.
/// </summary>
/// <param name="fileSystem">The file system associated with this directory.</param>
/// <param name="path">The path to the directory within the Azure Blob Storage container.</param>
public AzureDirectory(AzureFileSystem fileSystem, string path) : base(path) =>
_fs = fileSystem;
/// <inheritdoc />
protected override ValueTask<VirtualNodeProperties?> GetPropertiesCoreAsync(CancellationToken cancellationToken) =>
new ValueTask<VirtualNodeProperties?>(VirtualNodeProperties.None);
/// <inheritdoc />
protected override ValueTask<bool> ExistsCoreAsync(CancellationToken cancellationToken) =>
new ValueTask<bool>(true);
/// <inheritdoc />
protected override ValueTask CreateCoreAsync(CancellationToken cancellationToken) =>
default;
/// <inheritdoc />
protected override async ValueTask DeleteCoreAsync(CancellationToken cancellationToken)
{
var collection = _fs.AzureClient
.GetBlobsAsync(
prefix: GetPrefix(FullName),
cancellationToken: cancellationToken);
var client = _fs.AzureClient.GetBlobBatchClient();
var batch = client.CreateBatch();
// https://learn.microsoft.com/en-us/rest/api/storageservices/blob-batch#remarks
// Each batch request supports a maximum of 256 sub requests.
const int MaxSubRequests = 256;
await foreach (var page in collection.AsPages().WithCancellation(cancellationToken).ConfigureAwait(false))
{
foreach (var blob in page.Values)
{
batch.DeleteBlob(_fs.AzureClient.Name, blob.Name, DeleteSnapshotsOption.IncludeSnapshots, conditions: null);
if (batch.RequestCount != MaxSubRequests)
continue;
await DeleteBlobsAsync(client, batch, cancellationToken).ConfigureAwait(false);
batch = client.CreateBatch();
}
}
if (batch.RequestCount != 0)
await DeleteBlobsAsync(client, batch, cancellationToken).ConfigureAwait(false);
static async ValueTask DeleteBlobsAsync(BlobBatchClient client, BlobBatch batch, CancellationToken cancellationToken)
{
try
{
await client.SubmitBatchAsync(batch, throwOnAnyFailure: true, cancellationToken).ConfigureAwait(false);
}
catch (AggregateException e) when (Processed(e.InnerExceptions))
{
}
finally
{
batch.Dispose();
}
static bool Processed(ReadOnlyCollection<Exception> exceptions)
{
for (int count = exceptions.Count, i = 0; i < count; i++)
if (exceptions[i] is not RequestFailedException { Status: 404 })
return false;
return true;
}
}
}
/// <inheritdoc />
protected override async IAsyncEnumerable<VirtualNode> GetFileNodesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var collection = _fs.AzureClient
.GetBlobsByHierarchyAsync(
delimiter: "/",
prefix: GetPrefix(FullName),
cancellationToken: cancellationToken);
await foreach (var page in collection.AsPages().WithCancellation(cancellationToken).ConfigureAwait(false))
foreach (var item in page.Values)
yield return item.Prefix is not null
? new AzureDirectory(_fs, VirtualPath.Normalize(item.Prefix))
: CreateVirtualFile(item.Blob);
}
/// <inheritdoc />
protected override async IAsyncEnumerable<VirtualFile> GetFilesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var collection = _fs.AzureClient
.GetBlobsByHierarchyAsync(
delimiter: "/",
prefix: GetPrefix(FullName),
cancellationToken: cancellationToken);
await foreach (var page in collection.AsPages().WithCancellation(cancellationToken).ConfigureAwait(false))
foreach (var item in page.Values)
if (item.Prefix is null)
yield return CreateVirtualFile(item.Blob);
}
/// <inheritdoc />
protected override async IAsyncEnumerable<VirtualDirectory> GetDirectoriesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var collection = _fs.AzureClient
.GetBlobsByHierarchyAsync(
delimiter: "/",
prefix: GetPrefix(FullName),
cancellationToken: cancellationToken);
await foreach (var page in collection.AsPages().WithCancellation(cancellationToken).ConfigureAwait(false))
foreach (var item in page.Values)
if (item.Prefix is not null)
yield return new AzureDirectory(_fs, VirtualPath.Normalize(item.Prefix));
}
/// <summary>
/// Creates a <see cref="VirtualFile"/> instance based on the specified blob item.
/// </summary>
/// <param name="blob">The <see cref="BlobItem"/> representing the file.</param>
/// <returns>
/// A new <see cref="AzureFile"/> instance representing the file.
/// </returns>
private VirtualFile CreateVirtualFile(BlobItem blob)
{
var info = blob.Properties;
var properties = VirtualNodeProperties.CreateFileProperties(
creationTime: info.CreatedOn.GetValueOrDefault(),
lastAccessTime: info.LastAccessedOn.GetValueOrDefault(),
lastWriteTime: info.LastModified.GetValueOrDefault(),
length: info.ContentLength.GetValueOrDefault(defaultValue: -1));
var path = VirtualPath.Normalize(blob.Name);
return new AzureFile(_fs, path, properties);
}
/// <summary>
/// Returns the blob prefix for the specified directory path.
/// </summary>
/// <param name="directoryPath">The directory path for which to get the prefix.</param>
/// <returns>
/// The blob prefix associated with the directory.
/// </returns>
private static string GetPrefix(string directoryPath) =>
directoryPath == "/" ? "" : $"{directoryPath[1..]}/";
}