-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathRemoteAccessFileProvider.cs
178 lines (142 loc) · 5.89 KB
/
RemoteAccessFileProvider.cs
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
174
175
176
177
178
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
namespace UpdaterMirror;
/// <summary>
/// Provider wrapping a <see cref="CacheManager"/> in a <see cref="IFileProvider"/>
/// </summary>
public class RemoteAccessFileProvider : IFileProvider
{
/// <summary>
/// The cache manager being wrapped
/// </summary>
private readonly CacheManager m_manager;
/// <summary>
/// Constructs a new <see cref="RemoteAccessFileProvider"/>
/// </summary>
/// <param name="manager">The manager to wrap</param>
public RemoteAccessFileProvider(CacheManager manager)
{
m_manager = manager ?? throw new ArgumentNullException(nameof(manager));
}
/// <summary>
/// Default implementation of directory
/// </summary>
/// <param name="subpath">Unused argument</param>
/// <returns>A <see cref="NotFoundDirectoryContents"/> instance</returns>
public IDirectoryContents GetDirectoryContents(string subpath)
=> new NotFoundDirectoryContents();
/// <summary>
/// Returns a <see cref="IFileInfo"/> that wraps a <see cref="RemoteAccessItem"/>
/// </summary>
/// <param name="subpath">The path to use</param>
/// <returns>The wrapping <see cref="IFileInfo"/> instance</returns>
public IFileInfo GetFileInfo(string subpath)
=> subpath == null || subpath.EndsWith("/")
? new NotFoundFileInfo(subpath ?? "")
: new WrappedRemoteAccess(m_manager.Get(subpath));
/// <summary>
/// Default implementation of the change token
/// </summary>
/// <param name="filter">Unused argument</param>
/// <returns>The <see cref="NullChangeToken"/> singleton instance</returns>
public IChangeToken Watch(string filter)
=> NullChangeToken.Singleton;
/// <summary>
/// Helper method to get a <see cref="IFileInfo"/> for a remote access item with async support
/// </summary>
/// <param name="subpath">The path to get the item for</param>
/// <returns>The item or <c>null</c></returns>
public async Task<IFileInfo?> GetRemoteAccessItem(string subpath)
{
var res = GetFileInfo(subpath);
if (res is not WrappedRemoteAccess w)
return null;
if (!await w.Item.Exists())
return null;
return w;
}
/// <summary>
/// Wrapping <see cref="RemoteAccessItem"/> in <see cref="IFileInfo"/>
/// </summary>
/// <param name="Item"></param>
private record WrappedRemoteAccess(RemoteAccessItem Item) : IFileInfo
{
/// <inheritdoc/>
public bool Exists => Item.Exists().Result;
/// <inheritdoc/>
public bool IsDirectory => false;
/// <inheritdoc/>
public DateTimeOffset LastModified => Item.LastModified;
/// <inheritdoc/>
public long Length => Item.FullLength;
/// <inheritdoc/>
public string Name => Item.Key;
/// <inheritdoc/>
public string? PhysicalPath => Item.GetLocalPathIfDownloaded();
/// <inheritdoc/>
public Stream CreateReadStream()
{
if (!Exists)
throw new FileNotFoundException();
var t = Item.Download();
var fs = Item.GetLocalFileStream();
// If the download is complete, just give access to the cached file
if (t.IsCompleted)
return fs;
return new WrappedStream(Item, t, fs);
}
}
/// <summary>
/// Stream implementation for an in-progres downloaded file
/// </summary>
private class WrappedStream(RemoteAccessItem Item, Task Downloaded, FileStream Local) : Stream
{
/// <inheritdoc/>
public override bool CanRead => true;
/// <inheritdoc/>
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite => false;
/// <inheritdoc/>
public override long Length => Item.FullLength;
/// <inheritdoc/>
public override long Position { get => Local.Position; set => throw new InvalidOperationException(); }
/// <inheritdoc/>
public override void Flush() => throw new InvalidOperationException();
/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count)
{
while (Local.Position >= Local.Length && !Downloaded.IsCompleted)
{
// Wait for some data to be available
var _ = Item.NextAvailable!.Result;
}
return Local.Read(buffer, offset, count);
}
/// <inheritdoc/>
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
while (Local.Position >= Local.Length && !Downloaded.IsCompleted && !cancellationToken.IsCancellationRequested)
await Item.NextAvailable!;
cancellationToken.ThrowIfCancellationRequested();
return await Local.ReadAsync(buffer, offset, count, cancellationToken);
}
/// <inheritdoc/>
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
while (Local.Position >= Local.Length && !Downloaded.IsCompleted && !cancellationToken.IsCancellationRequested)
await Item.NextAvailable!;
cancellationToken.ThrowIfCancellationRequested();
return await Local.ReadAsync(buffer, cancellationToken);
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
=> throw new InvalidOperationException();
/// <inheritdoc/>
public override void SetLength(long value)
=> throw new InvalidOperationException();
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
=> throw new InvalidOperationException();
}
}