Skip to content

Commit 223cc4b

Browse files
Issue #113 Support for shared/team Google Drive folders (#114)
* Shared/Team drive folder support for Google Drive * Soft delete Google Drive files * Fix for failing Google Drive tests
1 parent b553530 commit 223cc4b

File tree

9 files changed

+147
-117
lines changed

9 files changed

+147
-117
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,9 @@ var tenantStorage = app.Services.GetRequiredKeyedService<IStorage>("tenant-a");
255255
builder.Services.AddGoogleDriveStorageAsDefault(options =>
256256
{
257257
options.DriveService = driveService;
258-
options.RootFolderId = "root"; // or a specific folder id you control
258+
options.RootFolderId = "root"; // or a specific folder id you control / shared team drive folder id
259259
options.CreateContainerIfNotExists = true;
260+
options.SupportsAllDrives = true; // To support shared/team drives
260261
});
261262
```
262263

@@ -723,7 +724,7 @@ Using in default mode:
723724
public class MyService
724725
{
725726
private readonly IStorage _storage;
726-
727+
727728
public MyService(IStorage storage)
728729
{
729730
_storage = storage;
@@ -797,7 +798,7 @@ Using in default mode:
797798
public class MyService
798799
{
799800
private readonly IStorage _storage;
800-
801+
801802
public MyService(IStorage storage)
802803
{
803804
_storage = storage;
@@ -858,7 +859,7 @@ Using in default mode:
858859
public class MyService
859860
{
860861
private readonly IStorage _storage;
861-
862+
862863
public MyService(IStorage storage)
863864
{
864865
_storage = storage;

Storages/ManagedCode.Storage.GoogleDrive/Clients/GoogleDriveClient.cs

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
using Google.Apis.Drive.v3;
12
using System;
23
using System.Collections.Generic;
34
using System.IO;
5+
using System.IO.Pipelines;
46
using System.Linq;
57
using System.Runtime.CompilerServices;
68
using System.Threading;
79
using System.Threading.Tasks;
8-
using System.IO.Pipelines;
9-
using Google.Apis.Drive.v3;
1010
using DriveFile = Google.Apis.Drive.v3.Data.File;
1111

1212
namespace ManagedCode.Storage.GoogleDrive.Clients;
@@ -27,9 +27,9 @@ public Task EnsureRootAsync(string rootFolderId, bool createIfNotExists, Cancell
2727
return Task.CompletedTask;
2828
}
2929

30-
public async Task<DriveFile> UploadAsync(string rootFolderId, string path, Stream content, string? contentType, CancellationToken cancellationToken)
30+
public async Task<DriveFile> UploadAsync(string rootFolderId, string path, Stream content, string? contentType, bool supportsAllDrives, CancellationToken cancellationToken)
3131
{
32-
var (parentId, fileName) = await EnsureParentFolderAsync(rootFolderId, path, cancellationToken);
32+
var (parentId, fileName) = await EnsureParentFolderAsync(rootFolderId, path, supportsAllDrives, cancellationToken);
3333

3434
var fileMetadata = new DriveFile
3535
{
@@ -39,20 +39,24 @@ public async Task<DriveFile> UploadAsync(string rootFolderId, string path, Strea
3939

4040
var request = _driveService.Files.Create(fileMetadata, content, contentType ?? "application/octet-stream");
4141
request.Fields = "id,name,parents,createdTime,modifiedTime,md5Checksum,size";
42+
request.SupportsAllDrives = supportsAllDrives;
4243
await request.UploadAsync(cancellationToken);
4344
return request.ResponseBody ?? throw new InvalidOperationException("Google Drive upload returned no metadata.");
4445
}
4546

46-
public async Task<Stream> DownloadAsync(string rootFolderId, string path, CancellationToken cancellationToken)
47+
public async Task<Stream> DownloadAsync(string rootFolderId, string path, bool supportsAllDrives, CancellationToken cancellationToken)
4748
{
48-
var file = await FindFileByPathAsync(rootFolderId, path, cancellationToken) ?? throw new FileNotFoundException(path);
49+
var file = await FindFileByPathAsync(rootFolderId, path, supportsAllDrives, cancellationToken) ?? throw new FileNotFoundException(path);
4950
var pipe = new Pipe();
51+
var getRequest = _driveService.Files.Get(file.Id);
52+
getRequest.SupportsAllDrives = supportsAllDrives;
53+
5054
_ = Task.Run(async () =>
5155
{
5256
try
5357
{
5458
await using var destination = pipe.Writer.AsStream(leaveOpen: true);
55-
await _driveService.Files.Get(file.Id).DownloadAsync(destination, cancellationToken);
59+
await getRequest.DownloadAsync(destination, cancellationToken);
5660
pipe.Writer.Complete();
5761
}
5862
catch (Exception ex)
@@ -64,29 +68,29 @@ public async Task<Stream> DownloadAsync(string rootFolderId, string path, Cancel
6468
return pipe.Reader.AsStream();
6569
}
6670

67-
public async Task<bool> DeleteAsync(string rootFolderId, string path, CancellationToken cancellationToken)
71+
public async Task<bool> DeleteAsync(string rootFolderId, string path, bool supportsAllDrives, CancellationToken cancellationToken)
6872
{
69-
var file = await FindFileByPathAsync(rootFolderId, path, cancellationToken);
73+
var file = await FindFileByPathAsync(rootFolderId, path, supportsAllDrives, cancellationToken);
7074
if (file == null)
7175
{
7276
return false;
7377
}
7478

75-
await DeleteRecursiveAsync(file.Id, file.MimeType, cancellationToken);
79+
await DeleteRecursiveAsync(file.Id, file.MimeType, supportsAllDrives, cancellationToken);
7680
return true;
7781
}
7882

79-
public async Task<bool> ExistsAsync(string rootFolderId, string path, CancellationToken cancellationToken)
83+
public async Task<bool> ExistsAsync(string rootFolderId, string path, bool supportsAllDrives, CancellationToken cancellationToken)
8084
{
81-
return await FindFileByPathAsync(rootFolderId, path, cancellationToken) != null;
85+
return await FindFileByPathAsync(rootFolderId, path, supportsAllDrives, cancellationToken) != null;
8286
}
8387

84-
public Task<DriveFile?> GetMetadataAsync(string rootFolderId, string path, CancellationToken cancellationToken)
88+
public Task<DriveFile?> GetMetadataAsync(string rootFolderId, string path, bool supportsAllDrives, CancellationToken cancellationToken)
8589
{
86-
return FindFileByPathAsync(rootFolderId, path, cancellationToken);
90+
return FindFileByPathAsync(rootFolderId, path, supportsAllDrives, cancellationToken);
8791
}
8892

89-
public async IAsyncEnumerable<DriveFile> ListAsync(string rootFolderId, string? directory, [EnumeratorCancellation] CancellationToken cancellationToken)
93+
public async IAsyncEnumerable<DriveFile> ListAsync(string rootFolderId, string? directory, bool supportsAllDrives, [EnumeratorCancellation] CancellationToken cancellationToken)
9094
{
9195
string parentId;
9296
if (string.IsNullOrWhiteSpace(directory))
@@ -95,15 +99,19 @@ public async IAsyncEnumerable<DriveFile> ListAsync(string rootFolderId, string?
9599
}
96100
else
97101
{
98-
parentId = await EnsureFolderPathAsync(rootFolderId, directory!, false, cancellationToken) ?? string.Empty;
102+
parentId = await EnsureFolderPathAsync(rootFolderId, directory!, false, supportsAllDrives, cancellationToken) ?? string.Empty;
99103
if (string.IsNullOrWhiteSpace(parentId))
100104
{
101105
yield break;
102106
}
103107
}
104108

105109
var request = _driveService.Files.List();
110+
request.SupportsAllDrives = supportsAllDrives;
111+
request.IncludeItemsFromAllDrives = supportsAllDrives;
112+
106113
request.Q = $"'{parentId}' in parents and trashed=false";
114+
107115
request.Fields = "nextPageToken,files(id,name,parents,createdTime,modifiedTime,md5Checksum,size,mimeType)";
108116

109117
do
@@ -119,7 +127,7 @@ public async IAsyncEnumerable<DriveFile> ListAsync(string rootFolderId, string?
119127
} while (!string.IsNullOrEmpty(request.PageToken));
120128
}
121129

122-
private async Task<(string ParentId, string Name)> EnsureParentFolderAsync(string rootFolderId, string fullPath, CancellationToken cancellationToken)
130+
private async Task<(string ParentId, string Name)> EnsureParentFolderAsync(string rootFolderId, string fullPath, bool supportsAllDrives, CancellationToken cancellationToken)
123131
{
124132
var normalizedPath = fullPath.Replace("\\", "/").Trim('/');
125133
var segments = normalizedPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
@@ -129,16 +137,16 @@ public async IAsyncEnumerable<DriveFile> ListAsync(string rootFolderId, string?
129137
}
130138

131139
var parentPath = string.Join('/', segments.Take(segments.Length - 1));
132-
var parentId = await EnsureFolderPathAsync(rootFolderId, parentPath, true, cancellationToken) ?? rootFolderId;
140+
var parentId = await EnsureFolderPathAsync(rootFolderId, parentPath, true, supportsAllDrives, cancellationToken) ?? rootFolderId;
133141
return (parentId, segments.Last());
134142
}
135143

136-
private async Task<string?> EnsureFolderPathAsync(string rootFolderId, string path, bool createIfMissing, CancellationToken cancellationToken)
144+
private async Task<string?> EnsureFolderPathAsync(string rootFolderId, string path, bool createIfMissing, bool supportsAllDrives, CancellationToken cancellationToken)
137145
{
138146
var currentId = rootFolderId;
139147
foreach (var segment in path.Split('/', StringSplitOptions.RemoveEmptyEntries))
140148
{
141-
var folder = await FindChildAsync(currentId, segment, cancellationToken);
149+
var folder = await FindChildAsync(currentId, segment, supportsAllDrives, cancellationToken);
142150
if (folder == null)
143151
{
144152
if (!createIfMissing)
@@ -147,7 +155,9 @@ public async IAsyncEnumerable<DriveFile> ListAsync(string rootFolderId, string?
147155
}
148156

149157
var metadata = new DriveFile { Name = segment, MimeType = "application/vnd.google-apps.folder", Parents = new List<string> { currentId } };
150-
folder = await _driveService.Files.Create(metadata).ExecuteAsync(cancellationToken);
158+
var createRequest = _driveService.Files.Create(metadata);
159+
createRequest.SupportsAllDrives = supportsAllDrives;
160+
folder = await createRequest.ExecuteAsync(cancellationToken);
151161
}
152162

153163
currentId = folder.Id;
@@ -156,32 +166,38 @@ public async IAsyncEnumerable<DriveFile> ListAsync(string rootFolderId, string?
156166
return currentId;
157167
}
158168

159-
private async Task<DriveFile?> FindChildAsync(string parentId, string name, CancellationToken cancellationToken)
169+
private async Task<DriveFile?> FindChildAsync(string parentId, string name, bool supportsAllDrives, CancellationToken cancellationToken)
160170
{
161171
var request = _driveService.Files.List();
162172
request.Q = $"'{parentId}' in parents and name='{name}' and trashed=false";
163173
request.Fields = "files(id,name,parents,createdTime,modifiedTime,md5Checksum,size,mimeType)";
174+
request.SupportsAllDrives = supportsAllDrives;
175+
request.IncludeItemsFromAllDrives = supportsAllDrives;
164176
var response = await request.ExecuteAsync(cancellationToken);
165177
return response.Files?.FirstOrDefault();
166178
}
167179

168-
private async Task DeleteRecursiveAsync(string fileId, string? mimeType, CancellationToken cancellationToken)
180+
private async Task DeleteRecursiveAsync(string fileId, string? mimeType, bool supportsAllDrives, CancellationToken cancellationToken)
169181
{
170182
cancellationToken.ThrowIfCancellationRequested();
171183

172184
if (string.Equals(mimeType, FolderMimeType, StringComparison.OrdinalIgnoreCase))
173185
{
174-
await DeleteFolderChildrenAsync(fileId, cancellationToken);
186+
await DeleteFolderChildrenAsync(fileId, supportsAllDrives, cancellationToken);
175187
}
176188

177-
await _driveService.Files.Delete(fileId).ExecuteAsync(cancellationToken);
189+
var trashRequest = _driveService.Files.Update(new DriveFile { Trashed = true }, fileId);
190+
trashRequest.SupportsAllDrives = supportsAllDrives;
191+
await trashRequest.ExecuteAsync(cancellationToken);
178192
}
179193

180-
private async Task DeleteFolderChildrenAsync(string folderId, CancellationToken cancellationToken)
194+
private async Task DeleteFolderChildrenAsync(string folderId, bool supportsAllDrives, CancellationToken cancellationToken)
181195
{
182196
var request = _driveService.Files.List();
183197
request.Q = $"'{folderId}' in parents and trashed=false";
184198
request.Fields = "nextPageToken,files(id,mimeType)";
199+
request.SupportsAllDrives = supportsAllDrives;
200+
request.IncludeItemsFromAllDrives = supportsAllDrives;
185201

186202
do
187203
{
@@ -194,14 +210,14 @@ private async Task DeleteFolderChildrenAsync(string folderId, CancellationToken
194210
continue;
195211
}
196212

197-
await DeleteRecursiveAsync(entry.Id, entry.MimeType, cancellationToken);
213+
await DeleteRecursiveAsync(entry.Id, entry.MimeType, supportsAllDrives, cancellationToken);
198214
}
199215

200216
request.PageToken = response.NextPageToken;
201217
} while (!string.IsNullOrWhiteSpace(request.PageToken));
202218
}
203219

204-
private async Task<DriveFile?> FindFileByPathAsync(string rootFolderId, string path, CancellationToken cancellationToken)
220+
private async Task<DriveFile?> FindFileByPathAsync(string rootFolderId, string path, bool supportsAllDrives, CancellationToken cancellationToken)
205221
{
206222
var normalizedPath = path.Replace("\\", "/").Trim('/');
207223
var segments = normalizedPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
@@ -212,12 +228,12 @@ private async Task DeleteFolderChildrenAsync(string folderId, CancellationToken
212228

213229
var parentPath = string.Join('/', segments.Take(segments.Length - 1));
214230
var fileName = segments.Last();
215-
var parentId = await EnsureFolderPathAsync(rootFolderId, parentPath, false, cancellationToken);
231+
var parentId = await EnsureFolderPathAsync(rootFolderId, parentPath, false, supportsAllDrives, cancellationToken);
216232
if (parentId == null)
217233
{
218234
return null;
219235
}
220236

221-
return await FindChildAsync(parentId, fileName, cancellationToken);
237+
return await FindChildAsync(parentId, fileName, supportsAllDrives, cancellationToken);
222238
}
223239
}

Storages/ManagedCode.Storage.GoogleDrive/Clients/IGoogleDriveClient.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ public interface IGoogleDriveClient
1010
{
1111
Task EnsureRootAsync(string rootFolderId, bool createIfNotExists, CancellationToken cancellationToken);
1212

13-
Task<DriveFile> UploadAsync(string rootFolderId, string path, Stream content, string? contentType, CancellationToken cancellationToken);
13+
Task<DriveFile> UploadAsync(string rootFolderId, string path, Stream content, string? contentType, bool supportsAllDrives, CancellationToken cancellationToken);
1414

15-
Task<Stream> DownloadAsync(string rootFolderId, string path, CancellationToken cancellationToken);
15+
Task<Stream> DownloadAsync(string rootFolderId, string path, bool supportsAllDrives, CancellationToken cancellationToken);
1616

17-
Task<bool> DeleteAsync(string rootFolderId, string path, CancellationToken cancellationToken);
17+
Task<bool> DeleteAsync(string rootFolderId, string path, bool supportsAllDrives, CancellationToken cancellationToken);
1818

19-
Task<bool> ExistsAsync(string rootFolderId, string path, CancellationToken cancellationToken);
19+
Task<bool> ExistsAsync(string rootFolderId, string path, bool supportsAllDrives, CancellationToken cancellationToken);
2020

21-
Task<DriveFile?> GetMetadataAsync(string rootFolderId, string path, CancellationToken cancellationToken);
21+
Task<DriveFile?> GetMetadataAsync(string rootFolderId, string path, bool supportsAllDrives, CancellationToken cancellationToken);
2222

23-
IAsyncEnumerable<DriveFile> ListAsync(string rootFolderId, string? directory, CancellationToken cancellationToken);
23+
IAsyncEnumerable<DriveFile> ListAsync(string rootFolderId, string? directory, bool supportsAllDrives, CancellationToken cancellationToken);
2424
}

0 commit comments

Comments
 (0)