Skip to content

Commit 4c44903

Browse files
authored
Merge pull request #39 from rameel/gcs-support
Add support for Google Cloud Storage
2 parents 5396424 + 2bd0aa4 commit 4c44903

File tree

23 files changed

+1051
-0
lines changed

23 files changed

+1051
-0
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<PackageVersion Include="AWSSDK.S3" Version="3.7.402.11" />
88
<PackageVersion Include="Azure.Storage.Blobs" Version="12.21.2" />
99
<PackageVersion Include="Azure.Storage.Blobs.Batch" Version="12.18.1" />
10+
<PackageVersion Include="Google.Cloud.Storage.V1" Version="4.10.0" />
1011
<PackageVersion Include="Microsoft.Extensions.FileProviders.Abstractions" Version="6.0.0" />
1112
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="6.0.0" />
1213
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ if (!fs.IsReadOnly)
141141
- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
142142
- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob storage.
143143
- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
144+
- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud storage.
144145
- [Ramstack.FileSystem.Zip](https://www.nuget.org/packages/Ramstack.FileSystem.Zip) - Provides an implementation based on ZIP archives.
145146
- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
146147
- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.

Ramstack.FileSystem.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ramstack.FileSystem.Amazon"
6262
EndProject
6363
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ramstack.FileSystem.Amazon.Tests", "tests\Ramstack.FileSystem.Amazon.Tests\Ramstack.FileSystem.Amazon.Tests.csproj", "{FCD52644-C99C-4AE7-A52A-3D326D42FCAA}"
6464
EndProject
65+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ramstack.FileSystem.Google", "src\Ramstack.FileSystem.Google\Ramstack.FileSystem.Google.csproj", "{C36C2D26-3BB9-494A-B00C-4ED3E6E459F5}"
66+
EndProject
67+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ramstack.FileSystem.Google.Tests", "tests\Ramstack.FileSystem.Google.Tests\Ramstack.FileSystem.Google.Tests.csproj", "{7F93F3F3-EFE4-467A-98CF-C84A85DD8229}"
68+
EndProject
6569
Global
6670
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6771
Debug|Any CPU = Debug|Any CPU
@@ -160,6 +164,14 @@ Global
160164
{FCD52644-C99C-4AE7-A52A-3D326D42FCAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
161165
{FCD52644-C99C-4AE7-A52A-3D326D42FCAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
162166
{FCD52644-C99C-4AE7-A52A-3D326D42FCAA}.Release|Any CPU.Build.0 = Release|Any CPU
167+
{C36C2D26-3BB9-494A-B00C-4ED3E6E459F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
168+
{C36C2D26-3BB9-494A-B00C-4ED3E6E459F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
169+
{C36C2D26-3BB9-494A-B00C-4ED3E6E459F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
170+
{C36C2D26-3BB9-494A-B00C-4ED3E6E459F5}.Release|Any CPU.Build.0 = Release|Any CPU
171+
{7F93F3F3-EFE4-467A-98CF-C84A85DD8229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
172+
{7F93F3F3-EFE4-467A-98CF-C84A85DD8229}.Debug|Any CPU.Build.0 = Debug|Any CPU
173+
{7F93F3F3-EFE4-467A-98CF-C84A85DD8229}.Release|Any CPU.ActiveCfg = Release|Any CPU
174+
{7F93F3F3-EFE4-467A-98CF-C84A85DD8229}.Release|Any CPU.Build.0 = Release|Any CPU
163175
EndGlobalSection
164176
GlobalSection(SolutionProperties) = preSolution
165177
HideSolutionNode = FALSE
@@ -188,6 +200,8 @@ Global
188200
{7EC7E717-F5B0-448B-A8AB-5C1385F432D9} = {FDD0140D-CFCB-4875-990F-BF1227CD3CF6}
189201
{A989B94C-94D5-43C0-A4E9-C62287B50A85} = {778473B7-67CD-4F46-93B8-B2FF5046E492}
190202
{FCD52644-C99C-4AE7-A52A-3D326D42FCAA} = {FDD0140D-CFCB-4875-990F-BF1227CD3CF6}
203+
{C36C2D26-3BB9-494A-B00C-4ED3E6E459F5} = {778473B7-67CD-4F46-93B8-B2FF5046E492}
204+
{7F93F3F3-EFE4-467A-98CF-C84A85DD8229} = {FDD0140D-CFCB-4875-990F-BF1227CD3CF6}
191205
EndGlobalSection
192206
GlobalSection(ExtensibilityGlobals) = postSolution
193207
SolutionGuid = {B4B772EB-04D5-46D8-81E5-A77BE4E9AAB0}

src/Ramstack.FileSystem.Abstractions/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ if (!fs.IsReadOnly)
147147
- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
148148
- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob storage.
149149
- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
150+
- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud storage.
150151
- [Ramstack.FileSystem.Zip](https://www.nuget.org/packages/Ramstack.FileSystem.Zip) - Provides an implementation based on ZIP archives.
151152
- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
152153
- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.

src/Ramstack.FileSystem.Adapters/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ await foreach (VirtualFile file in fs.GetFilesAsync("/"))
3636
- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
3737
- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob storage.
3838
- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
39+
- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud storage.
3940
- [Ramstack.FileSystem.Zip](https://www.nuget.org/packages/Ramstack.FileSystem.Zip) - Provides an implementation based on ZIP archives.
4041
- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
4142
- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.

src/Ramstack.FileSystem.Amazon/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ AmazonS3FileSystem fs = new AmazonS3FileSystem(
4646
- [Ramstack.FileSystem.Abstractions](https://www.nuget.org/packages/Ramstack.FileSystem.Abstractions) - Provides a virtual file system abstraction.
4747
- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
4848
- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob storage.
49+
- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud storage.
4950
- [Ramstack.FileSystem.Zip](https://www.nuget.org/packages/Ramstack.FileSystem.Zip) - Provides an implementation based on ZIP archives.
5051
- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
5152
- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.

src/Ramstack.FileSystem.Azure/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ AzureFileSystem fs = new AzureFileSystem(connectionString, containerName: "stora
3838
- [Ramstack.FileSystem.Abstractions](https://www.nuget.org/packages/Ramstack.FileSystem.Abstractions) - Provides a virtual file system abstraction.
3939
- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
4040
- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
41+
- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud storage.
4142
- [Ramstack.FileSystem.Zip](https://www.nuget.org/packages/Ramstack.FileSystem.Zip) - Provides an implementation based on ZIP archives.
4243
- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
4344
- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.

src/Ramstack.FileSystem.Composite/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ await foreach (VirtualFile file in fs.GetFilesAsync("/"))
3030
- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
3131
- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob storage.
3232
- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
33+
- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud storage.
3334
- [Ramstack.FileSystem.Zip](https://www.nuget.org/packages/Ramstack.FileSystem.Zip) - Provides an implementation based on ZIP archives.
3435
- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
3536
- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.

src/Ramstack.FileSystem.Globbing/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ await foreach (VirtualFile file in fs.GetFilesAsync("/"))
3535
- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
3636
- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob storage.
3737
- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
38+
- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud storage.
3839
- [Ramstack.FileSystem.Zip](https://www.nuget.org/packages/Ramstack.FileSystem.Zip) - Provides an implementation based on ZIP archives.
3940
- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
4041
- [Ramstack.FileSystem.Prefixed](https://www.nuget.org/packages/Ramstack.FileSystem.Prefixed) - Adds a prefix to file paths within the underlying file system.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using System.Net;
2+
using System.Runtime.CompilerServices;
3+
4+
using Google;
5+
using Google.Cloud.Storage.V1;
6+
7+
namespace Ramstack.FileSystem.Google;
8+
9+
/// <summary>
10+
/// Represents an implementation of <see cref="VirtualDirectory"/> that maps a directory
11+
/// to the path within a Google Cloud Storage bucket.
12+
/// </summary>
13+
internal sealed class GcsDirectory : VirtualDirectory
14+
{
15+
private readonly GoogleFileSystem _fs;
16+
private readonly string _prefix;
17+
18+
/// <inheritdoc />
19+
public override IVirtualFileSystem FileSystem => _fs;
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="GcsDirectory"/> class.
23+
/// </summary>
24+
/// <param name="fileSystem">The file system associated with this directory.</param>
25+
/// <param name="path">The path to the directory within the Google Cloud Storage bucket.</param>
26+
public GcsDirectory(GoogleFileSystem fileSystem, string path) : base(path)
27+
{
28+
_fs = fileSystem;
29+
_prefix = path == "/" ? "" : $"{path[1..]}/";
30+
}
31+
32+
/// <inheritdoc />
33+
protected override ValueTask<VirtualNodeProperties?> GetPropertiesCoreAsync(CancellationToken cancellationToken) =>
34+
new ValueTask<VirtualNodeProperties?>(VirtualNodeProperties.None);
35+
36+
/// <inheritdoc />
37+
protected override ValueTask<bool> ExistsCoreAsync(CancellationToken cancellationToken) =>
38+
new ValueTask<bool>(true);
39+
40+
/// <inheritdoc />
41+
protected override ValueTask CreateCoreAsync(CancellationToken cancellationToken) =>
42+
default;
43+
44+
/// <inheritdoc />
45+
protected override async ValueTask DeleteCoreAsync(CancellationToken cancellationToken)
46+
{
47+
await foreach (var obj in _fs.StorageClient.ListObjectsAsync(_fs.BucketName, _prefix).WithCancellation(cancellationToken).ConfigureAwait(false))
48+
{
49+
try
50+
{
51+
await _fs.StorageClient
52+
.DeleteObjectAsync(_fs.BucketName, obj.Name, cancellationToken: cancellationToken)
53+
.ConfigureAwait(false);
54+
}
55+
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound)
56+
{
57+
}
58+
}
59+
}
60+
61+
/// <inheritdoc />
62+
protected override async IAsyncEnumerable<VirtualNode> GetFileNodesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
63+
{
64+
var options = new ListObjectsOptions
65+
{
66+
Delimiter = "/",
67+
IncludeFoldersAsPrefixes = true,
68+
IncludeTrailingDelimiter = true
69+
};
70+
71+
var responses = _fs.StorageClient
72+
.ListObjectsAsync(_fs.BucketName, _prefix, options)
73+
.AsRawResponses();
74+
75+
await foreach (var page in responses.WithCancellation(cancellationToken).ConfigureAwait(false))
76+
{
77+
if (page.Prefixes is not null)
78+
foreach (var prefix in page.Prefixes)
79+
yield return new GcsDirectory(_fs, VirtualPath.Normalize(prefix));
80+
81+
if (page.Items is null)
82+
continue;
83+
84+
foreach (var obj in page.Items)
85+
{
86+
var properties = VirtualNodeProperties.CreateFileProperties(
87+
obj.TimeCreatedDateTimeOffset.GetValueOrDefault(),
88+
DateTimeOffset.MinValue,
89+
obj.UpdatedDateTimeOffset.GetValueOrDefault(),
90+
(long)(obj.Size ?? 0));
91+
yield return new GcsFile(_fs, VirtualPath.Normalize(obj.Name), properties);
92+
}
93+
}
94+
}
95+
96+
/// <inheritdoc />
97+
protected override async IAsyncEnumerable<VirtualFile> GetFilesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
98+
{
99+
var options = new ListObjectsOptions
100+
{
101+
Delimiter = "/",
102+
IncludeFoldersAsPrefixes = true,
103+
IncludeTrailingDelimiter = true
104+
};
105+
106+
var response = _fs.StorageClient.ListObjectsAsync(_fs.BucketName, _prefix, options);
107+
108+
await foreach (var obj in response.WithCancellation(cancellationToken).ConfigureAwait(false))
109+
{
110+
var properties = VirtualNodeProperties.CreateFileProperties(
111+
obj.TimeCreatedDateTimeOffset.GetValueOrDefault(),
112+
DateTimeOffset.MinValue,
113+
obj.UpdatedDateTimeOffset.GetValueOrDefault(),
114+
(long)(obj.Size ?? 0));
115+
116+
yield return new GcsFile(_fs, VirtualPath.Normalize(obj.Name), properties);
117+
}
118+
}
119+
120+
/// <inheritdoc />
121+
protected override async IAsyncEnumerable<VirtualDirectory> GetDirectoriesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
122+
{
123+
var options = new ListObjectsOptions
124+
{
125+
Delimiter = "/",
126+
IncludeFoldersAsPrefixes = true,
127+
IncludeTrailingDelimiter = true
128+
};
129+
130+
var response = _fs.StorageClient
131+
.ListObjectsAsync(_fs.BucketName, _prefix, options)
132+
.AsRawResponses();
133+
134+
await foreach (var page in response.WithCancellation(cancellationToken).ConfigureAwait(false))
135+
{
136+
if (page.Prefixes is null)
137+
continue;
138+
139+
foreach (var prefix in page.Prefixes)
140+
yield return new GcsDirectory(_fs, VirtualPath.Normalize(prefix));
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)