Skip to content

Commit f2eb064

Browse files
authored
Merge pull request #39 from SharpGrip/feature/new-adapter-google-cloud-storage
add google cloud storage adapter
2 parents 8c73c61 + f76508f commit f2eb064

8 files changed

Lines changed: 441 additions & 15 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<RootNamespace>SharpGrip.FileSystem.Adapters.GoogleCloudStorage</RootNamespace>
5+
</PropertyGroup>
6+
7+
<PropertyGroup>
8+
<AssemblyName>SharpGrip.FileSystem.Adapters.GoogleCloudStorage</AssemblyName>
9+
<PackageId>SharpGrip.FileSystem.Adapters.GoogleCloudStorage</PackageId>
10+
<Title>SharpGrip FileSystem Google Cloud Storage adapter</Title>
11+
<Description>The SharpGrip FileSystem Google Cloud Storage adapter.</Description>
12+
<PackageTags>sharpgrip;file-system;google-cloud-storage</PackageTags>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<None Include="..\README.md" Pack="true" PackagePath="\" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<PackageReference Include="Google.Cloud.Storage.V1" Version="4.7.0" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<ProjectReference Include="..\FileSystem\FileSystem.csproj"/>
25+
</ItemGroup>
26+
27+
</Project>
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Net;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Google;
9+
using Google.Cloud.Storage.V1;
10+
using SharpGrip.FileSystem.Exceptions;
11+
using SharpGrip.FileSystem.Extensions;
12+
using SharpGrip.FileSystem.Models;
13+
using SharpGrip.FileSystem.Utilities;
14+
using DirectoryNotFoundException = SharpGrip.FileSystem.Exceptions.DirectoryNotFoundException;
15+
using FileNotFoundException = SharpGrip.FileSystem.Exceptions.FileNotFoundException;
16+
using Object = Google.Apis.Storage.v1.Data.Object;
17+
18+
namespace SharpGrip.FileSystem.Adapters.GoogleCloudStorage
19+
{
20+
public class GoogleCloudStorageAdapter : Adapter<GoogleCloudStorageAdapterConfiguration, string, string>
21+
{
22+
private readonly StorageClient client;
23+
private readonly string bucketName;
24+
25+
public GoogleCloudStorageAdapter(string prefix, string rootPath, StorageClient client, string bucketName, Action<GoogleCloudStorageAdapterConfiguration>? configuration = null)
26+
: base(prefix, rootPath, configuration)
27+
{
28+
this.client = client;
29+
this.bucketName = bucketName;
30+
}
31+
32+
public override void Dispose()
33+
{
34+
client.Dispose();
35+
}
36+
37+
public override async Task ConnectAsync(CancellationToken cancellationToken = default)
38+
{
39+
Logger.LogStartConnectingAdapter(this);
40+
await Task.CompletedTask;
41+
Logger.LogFinishedConnectingAdapter(this);
42+
}
43+
44+
public override async Task<IFile> GetFileAsync(string virtualPath, CancellationToken cancellationToken = default)
45+
{
46+
var path = GetPath(virtualPath).RemoveLeadingForwardSlash().RemoveTrailingForwardSlash();
47+
48+
try
49+
{
50+
var file = await client.GetObjectAsync(bucketName, path, new GetObjectOptions(), cancellationToken);
51+
52+
if (file == null)
53+
{
54+
throw new FileNotFoundException(path, Prefix);
55+
}
56+
57+
return ModelFactory.CreateFile(file, path, virtualPath);
58+
}
59+
catch (GoogleApiException googleApiException) when (googleApiException.HttpStatusCode == HttpStatusCode.NotFound)
60+
{
61+
throw new FileNotFoundException(path, Prefix);
62+
}
63+
catch (Exception exception)
64+
{
65+
throw Exception(exception);
66+
}
67+
}
68+
69+
public override async Task<IDirectory> GetDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
70+
{
71+
var path = GetPath(virtualPath).RemoveLeadingForwardSlash().EnsureTrailingForwardSlash();
72+
var parentPath = GetParentPathPart(path).EnsureTrailingForwardSlash();
73+
74+
try
75+
{
76+
if (path.IsNullOrEmpty() || path == "/")
77+
{
78+
return ModelFactory.CreateDirectory("/", path, virtualPath);
79+
}
80+
81+
var request = client.Service.Objects.List(bucketName);
82+
83+
request.Prefix = parentPath == "/" ? null : parentPath;
84+
request.Delimiter = "/";
85+
86+
do
87+
{
88+
var objects = await request.ExecuteAsync(cancellationToken: cancellationToken);
89+
90+
if (objects.Prefixes != null)
91+
{
92+
foreach (var directoryPath in objects.Prefixes)
93+
{
94+
if (directoryPath == path)
95+
{
96+
var directoryName = GetLastPathPart(directoryPath);
97+
98+
return ModelFactory.CreateDirectory(directoryName.RemoveTrailingForwardSlash(), directoryPath.EnsureTrailingForwardSlash(), GetVirtualPath(directoryPath));
99+
}
100+
}
101+
}
102+
103+
request.PageToken = objects.NextPageToken;
104+
} while (request.PageToken != null);
105+
106+
throw new DirectoryNotFoundException(path, Prefix);
107+
}
108+
catch (GoogleApiException googleApiException) when (googleApiException.HttpStatusCode == HttpStatusCode.NotFound)
109+
{
110+
throw new DirectoryNotFoundException(path, Prefix);
111+
}
112+
catch (Exception exception)
113+
{
114+
throw Exception(exception);
115+
}
116+
}
117+
118+
public override async Task<IEnumerable<IFile>> GetFilesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
119+
{
120+
await GetDirectoryAsync(virtualPath, cancellationToken);
121+
122+
var path = GetPath(virtualPath).RemoveLeadingForwardSlash().EnsureTrailingForwardSlash();
123+
124+
try
125+
{
126+
var files = new List<IFile>();
127+
128+
var request = client.Service.Objects.List(bucketName);
129+
130+
request.Prefix = path == "/" ? null : path;
131+
request.Delimiter = "/";
132+
133+
do
134+
{
135+
var objects = await request.ExecuteAsync(cancellationToken: cancellationToken);
136+
137+
if (objects.Items != null)
138+
{
139+
foreach (var file in objects.Items.Where(item => item.ContentType != null))
140+
{
141+
files.Add(ModelFactory.CreateFile(file, file.Name.RemoveTrailingForwardSlash(), GetVirtualPath(file.Name)));
142+
}
143+
}
144+
145+
request.PageToken = objects.NextPageToken;
146+
} while (request.PageToken != null);
147+
148+
return files;
149+
}
150+
catch (Exception exception)
151+
{
152+
throw Exception(exception);
153+
}
154+
}
155+
156+
public override async Task<IEnumerable<IDirectory>> GetDirectoriesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
157+
{
158+
await GetDirectoryAsync(virtualPath, cancellationToken);
159+
160+
var path = GetPath(virtualPath).RemoveLeadingForwardSlash().EnsureTrailingForwardSlash();
161+
162+
try
163+
{
164+
var directories = new List<IDirectory>();
165+
166+
var request = client.Service.Objects.List(bucketName);
167+
168+
request.Prefix = path == "/" ? null : path;
169+
request.Delimiter = "/";
170+
171+
do
172+
{
173+
var objects = await request.ExecuteAsync(cancellationToken: cancellationToken);
174+
175+
if (objects.Prefixes != null)
176+
{
177+
foreach (var directoryPath in objects.Prefixes)
178+
{
179+
var directoryName = GetLastPathPart(directoryPath);
180+
181+
directories.Add(ModelFactory.CreateDirectory(directoryName.RemoveTrailingForwardSlash(), directoryPath.EnsureTrailingForwardSlash(), GetVirtualPath(directoryPath)));
182+
}
183+
}
184+
185+
request.PageToken = objects.NextPageToken;
186+
} while (request.PageToken != null);
187+
188+
return directories;
189+
}
190+
catch (Exception exception)
191+
{
192+
throw Exception(exception);
193+
}
194+
}
195+
196+
public override async Task CreateDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
197+
{
198+
if (await DirectoryExistsAsync(virtualPath, cancellationToken))
199+
{
200+
throw new DirectoryExistsException(GetPath(virtualPath), Prefix);
201+
}
202+
203+
var path = GetPath(virtualPath).RemoveLeadingForwardSlash().EnsureTrailingForwardSlash();
204+
205+
try
206+
{
207+
await client.UploadObjectAsync(bucketName, path.EnsureTrailingForwardSlash(), null, Stream.Null, cancellationToken: cancellationToken);
208+
}
209+
catch (Exception exception)
210+
{
211+
throw Exception(exception);
212+
}
213+
}
214+
215+
public override async Task DeleteDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
216+
{
217+
await GetDirectoryAsync(virtualPath, cancellationToken);
218+
219+
var path = GetPath(virtualPath).RemoveLeadingForwardSlash().EnsureTrailingForwardSlash();
220+
221+
try
222+
{
223+
var files = await GetFilesAsync(virtualPath, cancellationToken);
224+
225+
foreach (var file in files)
226+
{
227+
await DeleteFileAsync(file.VirtualPath, cancellationToken);
228+
}
229+
230+
var subDirectories = await GetDirectoriesAsync(virtualPath, cancellationToken);
231+
232+
foreach (var subDirectory in subDirectories)
233+
{
234+
await DeleteDirectoryAsync(subDirectory.VirtualPath, cancellationToken);
235+
}
236+
237+
await client.DeleteObjectAsync(bucketName, path, cancellationToken: cancellationToken);
238+
}
239+
catch (Exception exception)
240+
{
241+
throw Exception(exception);
242+
}
243+
}
244+
245+
public override async Task DeleteFileAsync(string virtualPath, CancellationToken cancellationToken = default)
246+
{
247+
await GetFileAsync(virtualPath, cancellationToken);
248+
249+
var path = GetPath(virtualPath).RemoveLeadingForwardSlash().RemoveTrailingForwardSlash();
250+
251+
try
252+
{
253+
await client.DeleteObjectAsync(bucketName, path, cancellationToken: cancellationToken);
254+
}
255+
catch (Exception exception)
256+
{
257+
throw Exception(exception);
258+
}
259+
}
260+
261+
public override async Task<Stream> ReadFileStreamAsync(string virtualPath, CancellationToken cancellationToken = default)
262+
{
263+
await GetFileAsync(virtualPath, cancellationToken);
264+
265+
var path = GetPath(virtualPath).RemoveLeadingForwardSlash().RemoveTrailingForwardSlash();
266+
267+
try
268+
{
269+
var file = await client.GetObjectAsync(bucketName, path, new GetObjectOptions(), cancellationToken);
270+
271+
var memoryStream = new MemoryStream();
272+
273+
await client.DownloadObjectAsync(file, memoryStream, new DownloadObjectOptions(), cancellationToken);
274+
275+
memoryStream.Position = 0;
276+
277+
return memoryStream;
278+
}
279+
catch (Exception exception)
280+
{
281+
throw Exception(exception);
282+
}
283+
}
284+
285+
public override async Task WriteFileAsync(string virtualPath, Stream contents, bool overwrite = false, CancellationToken cancellationToken = default)
286+
{
287+
if (!overwrite && await FileExistsAsync(virtualPath, cancellationToken))
288+
{
289+
throw new FileExistsException(GetPath(virtualPath), Prefix);
290+
}
291+
292+
var path = GetPath(virtualPath).RemoveLeadingForwardSlash().RemoveTrailingForwardSlash();
293+
294+
try
295+
{
296+
var file = new Object
297+
{
298+
Bucket = bucketName,
299+
Name = path,
300+
ContentType = ContentTypeProvider.GetContentType(path)
301+
};
302+
303+
await client.UploadObjectAsync(file, contents, new UploadObjectOptions(), cancellationToken);
304+
}
305+
catch (Exception exception)
306+
{
307+
throw Exception(exception);
308+
}
309+
}
310+
311+
protected override Exception Exception(Exception exception)
312+
{
313+
if (exception is FileSystemException)
314+
{
315+
return exception;
316+
}
317+
318+
return new AdapterRuntimeException(exception);
319+
}
320+
}
321+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using SharpGrip.FileSystem.Configuration;
2+
3+
namespace SharpGrip.FileSystem.Adapters.GoogleCloudStorage
4+
{
5+
public class GoogleCloudStorageAdapterConfiguration : AdapterConfiguration
6+
{
7+
}
8+
}

0 commit comments

Comments
 (0)