@protontech/drive-sdk 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/batchLoading.d.ts +2 -0
- package/dist/internal/batchLoading.js +18 -5
- package/dist/internal/batchLoading.js.map +1 -1
- package/dist/internal/batchLoading.test.js +92 -0
- package/dist/internal/batchLoading.test.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.js +1 -1
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +3 -2
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/photos/index.js +3 -1
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +1 -1
- package/dist/internal/photos/upload.js +2 -2
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharing/events.d.ts +4 -2
- package/dist/internal/sharing/events.js +40 -9
- package/dist/internal/sharing/events.js.map +1 -1
- package/dist/internal/sharing/events.test.js +30 -2
- package/dist/internal/sharing/events.test.js.map +1 -1
- package/dist/internal/sharing/index.js +1 -1
- package/dist/internal/sharing/index.js.map +1 -1
- package/dist/internal/sharingPublic/session/index.d.ts +1 -0
- package/dist/internal/sharingPublic/session/index.js +3 -1
- package/dist/internal/sharingPublic/session/index.js.map +1 -1
- package/dist/internal/sharingPublic/session/manager.d.ts +2 -0
- package/dist/internal/sharingPublic/session/manager.js +1 -0
- package/dist/internal/sharingPublic/session/manager.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +19 -3
- package/dist/internal/upload/fileUploader.js +31 -5
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +1 -1
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/index.js +4 -11
- package/dist/internal/upload/index.js.map +1 -1
- package/dist/internal/upload/index.test.js +104 -45
- package/dist/internal/upload/index.test.js.map +1 -1
- package/dist/internal/upload/smallFileUploader.d.ts +14 -14
- package/dist/internal/upload/smallFileUploader.js +38 -20
- package/dist/internal/upload/smallFileUploader.js.map +1 -1
- package/dist/internal/upload/smallFileUploader.test.js +35 -36
- package/dist/internal/upload/smallFileUploader.test.js.map +1 -1
- package/dist/protonDriveClient.js +2 -1
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +15 -1
- package/dist/protonDrivePublicLinkClient.js +7 -1
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/package.json +1 -1
- package/src/internal/batchLoading.test.ts +104 -0
- package/src/internal/batchLoading.ts +21 -5
- package/src/internal/nodes/nodesAccess.test.ts +4 -3
- package/src/internal/nodes/nodesAccess.ts +1 -1
- package/src/internal/photos/index.ts +2 -0
- package/src/internal/photos/upload.ts +13 -1
- package/src/internal/sharing/events.test.ts +35 -2
- package/src/internal/sharing/events.ts +47 -10
- package/src/internal/sharing/index.ts +1 -0
- package/src/internal/sharingPublic/session/index.ts +1 -0
- package/src/internal/sharingPublic/session/manager.ts +2 -0
- package/src/internal/upload/fileUploader.test.ts +1 -0
- package/src/internal/upload/fileUploader.ts +60 -2
- package/src/internal/upload/index.test.ts +121 -63
- package/src/internal/upload/index.ts +4 -30
- package/src/internal/upload/smallFileUploader.test.ts +33 -40
- package/src/internal/upload/smallFileUploader.ts +47 -36
- package/src/protonDriveClient.ts +2 -1
- package/src/protonDrivePublicLinkClient.ts +23 -2
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Logger } from '../../interface';
|
|
2
2
|
import { DriveEvent, DriveEventType } from '../events';
|
|
3
3
|
import { SharingCache } from './cache';
|
|
4
|
-
import { SharesService } from './interface';
|
|
4
|
+
import { NodesService, SharesService } from './interface';
|
|
5
5
|
|
|
6
6
|
export class SharingEventHandler {
|
|
7
7
|
constructor(
|
|
8
8
|
private logger: Logger,
|
|
9
9
|
private cache: SharingCache,
|
|
10
10
|
private shares: SharesService,
|
|
11
|
+
private nodesService: NodesService,
|
|
11
12
|
) {}
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -26,23 +27,59 @@ export class SharingEventHandler {
|
|
|
26
27
|
*/
|
|
27
28
|
async handleDriveEvent(event: DriveEvent) {
|
|
28
29
|
try {
|
|
29
|
-
|
|
30
|
-
await this.cache.setSharedWithMeNodeUids(undefined);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
30
|
+
await this.handleSharedWithMeNodeUidsLoaded(event);
|
|
33
31
|
await this.handleSharedByMeNodeUidsLoaded(event);
|
|
34
32
|
} catch (error: unknown) {
|
|
35
|
-
this.logger.error(`Skipping
|
|
33
|
+
this.logger.error(`Skipping sharing cache update`, error);
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
private async
|
|
40
|
-
if (
|
|
41
|
-
|
|
37
|
+
private async handleSharedWithMeNodeUidsLoaded(event: DriveEvent) {
|
|
38
|
+
if (
|
|
39
|
+
![DriveEventType.SharedWithMeUpdated, DriveEventType.TreeRefresh, DriveEventType.TreeRemove].includes(
|
|
40
|
+
event.type,
|
|
41
|
+
)
|
|
42
|
+
) {
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
// When user changes the membership (permissions) for a user, the
|
|
47
|
+
// backend emits both NodeUpdated and SharedWithMeUpdated events.
|
|
48
|
+
// Ideally, the SDK doesn't have to refresh all the shared nodes,
|
|
49
|
+
// only those that were changed via the NodeUpdated event. However,
|
|
50
|
+
// the client very likely will not be subscribed to all shared volumes.
|
|
51
|
+
// When the client only lists the list itself and not the trees, it
|
|
52
|
+
// is still required to refresh all the nodes to be sure to have the
|
|
53
|
+
// latest state.
|
|
54
|
+
// The sharing module doesn't have access to the nodes cache, thus
|
|
55
|
+
// it notifies the nodes via the service. If this fails, we need to
|
|
56
|
+
// log it, but it should not block the event handling. The node might
|
|
57
|
+
// be wrong at the "shared with me" listing, but it will be eventually
|
|
58
|
+
// updated once the user opens the volume tree and client processes
|
|
59
|
+
// the events for that volume.
|
|
60
|
+
// Ideally, in the future, the Drive API provides a custom event with
|
|
61
|
+
// indication of what node was added or removed or updated, instead
|
|
62
|
+
// of emitting destructive SharedWithMeUpdated event.
|
|
63
|
+
const hasSharedWithMeLoaded = await this.cache.hasSharedWithMeNodeUidsLoaded();
|
|
64
|
+
if (event.type === DriveEventType.SharedWithMeUpdated && hasSharedWithMeLoaded) {
|
|
65
|
+
try {
|
|
66
|
+
const sharedWithMeNodeUids = await this.cache.getSharedWithMeNodeUids();
|
|
67
|
+
this.logger.debug(`Shared with me updated, notifying ${sharedWithMeNodeUids.length} nodes`);
|
|
68
|
+
for (const nodeUid of sharedWithMeNodeUids) {
|
|
69
|
+
await this.nodesService.notifyNodeChanged(nodeUid);
|
|
70
|
+
}
|
|
71
|
+
} catch (error: unknown) {
|
|
72
|
+
this.logger.error(`Skipping shared with me node cache update`, error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await this.cache.setSharedWithMeNodeUids(undefined);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private async handleSharedByMeNodeUidsLoaded(event: DriveEvent) {
|
|
80
|
+
if (
|
|
81
|
+
![DriveEventType.NodeCreated, DriveEventType.NodeUpdated, DriveEventType.NodeDeleted].includes(event.type)
|
|
82
|
+
) {
|
|
46
83
|
return;
|
|
47
84
|
}
|
|
48
85
|
|
|
@@ -87,6 +87,7 @@ export class SharingPublicSessionManager {
|
|
|
87
87
|
shareKey: PrivateKey;
|
|
88
88
|
rootUid: string;
|
|
89
89
|
publicRole: MemberRole;
|
|
90
|
+
session: SharingPublicLinkSession;
|
|
90
91
|
}> {
|
|
91
92
|
let info = this.infosPerToken.get(token);
|
|
92
93
|
if (!info) {
|
|
@@ -105,6 +106,7 @@ export class SharingPublicSessionManager {
|
|
|
105
106
|
shareKey,
|
|
106
107
|
rootUid,
|
|
107
108
|
publicRole: permissionsToMemberRole(this.logger, encryptedShare.publicPermissions),
|
|
109
|
+
session,
|
|
108
110
|
};
|
|
109
111
|
}
|
|
110
112
|
|
|
@@ -5,6 +5,7 @@ import { UploadController } from './controller';
|
|
|
5
5
|
import { UploadCryptoService } from './cryptoService';
|
|
6
6
|
import { NodeRevisionDraft } from './interface';
|
|
7
7
|
import { UploadManager } from './manager';
|
|
8
|
+
import { SmallFileRevisionUploader, SmallFileUploader } from './smallFileUploader';
|
|
8
9
|
import { StreamUploader } from './streamUploader';
|
|
9
10
|
import { UploadTelemetry } from './telemetry';
|
|
10
11
|
|
|
@@ -26,6 +27,7 @@ export abstract class Uploader {
|
|
|
26
27
|
protected manager: UploadManager,
|
|
27
28
|
protected metadata: UploadMetadata,
|
|
28
29
|
protected onFinish: () => void,
|
|
30
|
+
protected shouldUseSmallFileUpload: (expectedSize: number) => Promise<boolean>,
|
|
29
31
|
protected signal?: AbortSignal,
|
|
30
32
|
) {
|
|
31
33
|
this.telemetry = telemetry;
|
|
@@ -34,6 +36,7 @@ export abstract class Uploader {
|
|
|
34
36
|
this.manager = manager;
|
|
35
37
|
this.metadata = metadata;
|
|
36
38
|
this.onFinish = onFinish;
|
|
39
|
+
this.shouldUseSmallFileUpload = shouldUseSmallFileUpload;
|
|
37
40
|
|
|
38
41
|
this.signal = signal;
|
|
39
42
|
this.abortController = new AbortController();
|
|
@@ -97,10 +100,28 @@ export abstract class Uploader {
|
|
|
97
100
|
thumbnails: Thumbnail[],
|
|
98
101
|
onProgress?: (uploadedBytes: number) => void,
|
|
99
102
|
): Promise<{ nodeRevisionUid: string; nodeUid: string }> {
|
|
103
|
+
const expectedEncryptedTotalSize = this.getExpectedEncryptedTotalSize(thumbnails);
|
|
104
|
+
if (await this.shouldUseSmallFileUpload(expectedEncryptedTotalSize)) {
|
|
105
|
+
return this.initSmallFileUploader(stream, thumbnails, onProgress);
|
|
106
|
+
}
|
|
107
|
+
|
|
100
108
|
const uploader = await this.initStreamUploader();
|
|
101
109
|
return uploader.start(stream, thumbnails, onProgress);
|
|
102
110
|
}
|
|
103
111
|
|
|
112
|
+
private getExpectedEncryptedTotalSize(thumbnails: Thumbnail[]): number {
|
|
113
|
+
const thumbnailSize = thumbnails.reduce((acc, thumbnail) => acc + thumbnail.thumbnail.length, 0);
|
|
114
|
+
const totalSize = this.metadata.expectedSize + thumbnailSize;
|
|
115
|
+
const expectedEncryptedTotalSize = totalSize * 1.1; // 10% margin for encryption overhead
|
|
116
|
+
return expectedEncryptedTotalSize;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected abstract initSmallFileUploader(
|
|
120
|
+
stream: ReadableStream,
|
|
121
|
+
thumbnails: Thumbnail[],
|
|
122
|
+
onProgress?: (uploadedBytes: number) => void,
|
|
123
|
+
): Promise<{ nodeRevisionUid: string; nodeUid: string }>;
|
|
124
|
+
|
|
104
125
|
protected async initStreamUploader(): Promise<StreamUploader> {
|
|
105
126
|
const { revisionDraft, blockVerifier } = await this.createRevisionDraft();
|
|
106
127
|
|
|
@@ -154,9 +175,10 @@ export class FileUploader extends Uploader {
|
|
|
154
175
|
private name: string,
|
|
155
176
|
metadata: UploadMetadata,
|
|
156
177
|
onFinish: () => void,
|
|
178
|
+
protected shouldUseSmallFileUpload: (expectedSize: number) => Promise<boolean>,
|
|
157
179
|
signal?: AbortSignal,
|
|
158
180
|
) {
|
|
159
|
-
super(telemetry, apiService, cryptoService, manager, metadata, onFinish, signal);
|
|
181
|
+
super(telemetry, apiService, cryptoService, manager, metadata, onFinish, shouldUseSmallFileUpload, signal);
|
|
160
182
|
|
|
161
183
|
this.parentFolderUid = parentFolderUid;
|
|
162
184
|
this.name = name;
|
|
@@ -192,6 +214,24 @@ export class FileUploader extends Uploader {
|
|
|
192
214
|
protected async deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void> {
|
|
193
215
|
await this.manager.deleteDraftNode(revisionDraft.nodeUid);
|
|
194
216
|
}
|
|
217
|
+
|
|
218
|
+
protected async initSmallFileUploader(
|
|
219
|
+
stream: ReadableStream,
|
|
220
|
+
thumbnails: Thumbnail[],
|
|
221
|
+
onProgress?: (uploadedBytes: number) => void,
|
|
222
|
+
): Promise<{ nodeRevisionUid: string; nodeUid: string }> {
|
|
223
|
+
const uploader = new SmallFileUploader(
|
|
224
|
+
this.telemetry,
|
|
225
|
+
this.cryptoService,
|
|
226
|
+
this.manager,
|
|
227
|
+
this.metadata,
|
|
228
|
+
this.onFinish,
|
|
229
|
+
this.signal,
|
|
230
|
+
this.parentFolderUid,
|
|
231
|
+
this.name,
|
|
232
|
+
);
|
|
233
|
+
return uploader.upload(stream, thumbnails, onProgress);
|
|
234
|
+
}
|
|
195
235
|
}
|
|
196
236
|
|
|
197
237
|
/**
|
|
@@ -206,9 +246,10 @@ export class FileRevisionUploader extends Uploader {
|
|
|
206
246
|
private nodeUid: string,
|
|
207
247
|
metadata: UploadMetadata,
|
|
208
248
|
onFinish: () => void,
|
|
249
|
+
protected shouldUseSmallFileUpload: (expectedSize: number) => Promise<boolean>,
|
|
209
250
|
signal?: AbortSignal,
|
|
210
251
|
) {
|
|
211
|
-
super(telemetry, apiService, cryptoService, manager, metadata, onFinish, signal);
|
|
252
|
+
super(telemetry, apiService, cryptoService, manager, metadata, onFinish, shouldUseSmallFileUpload, signal);
|
|
212
253
|
|
|
213
254
|
this.nodeUid = nodeUid;
|
|
214
255
|
}
|
|
@@ -243,4 +284,21 @@ export class FileRevisionUploader extends Uploader {
|
|
|
243
284
|
protected async deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void> {
|
|
244
285
|
await this.manager.deleteDraftRevision(revisionDraft.nodeRevisionUid);
|
|
245
286
|
}
|
|
287
|
+
|
|
288
|
+
protected async initSmallFileUploader(
|
|
289
|
+
stream: ReadableStream,
|
|
290
|
+
thumbnails: Thumbnail[],
|
|
291
|
+
onProgress?: (uploadedBytes: number) => void,
|
|
292
|
+
): Promise<{ nodeRevisionUid: string; nodeUid: string }> {
|
|
293
|
+
const uploader = new SmallFileRevisionUploader(
|
|
294
|
+
this.telemetry,
|
|
295
|
+
this.cryptoService,
|
|
296
|
+
this.manager,
|
|
297
|
+
this.metadata,
|
|
298
|
+
this.onFinish,
|
|
299
|
+
this.signal,
|
|
300
|
+
this.nodeUid,
|
|
301
|
+
);
|
|
302
|
+
return uploader.upload(stream, thumbnails, onProgress);
|
|
303
|
+
}
|
|
246
304
|
}
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import { FeatureFlagProvider,
|
|
1
|
+
import { FeatureFlagProvider, ThumbnailType, UploadMetadata } from '../../interface';
|
|
2
2
|
import { getMockTelemetry } from '../../tests/telemetry';
|
|
3
|
-
import { FileRevisionUploader, FileUploader } from './fileUploader';
|
|
3
|
+
import { FileRevisionUploader, FileUploader, Uploader } from './fileUploader';
|
|
4
4
|
import { initUploadModule } from './index';
|
|
5
|
-
import { SmallFileRevisionUploader, SmallFileUploader } from './smallFileUploader';
|
|
6
5
|
|
|
7
|
-
const
|
|
6
|
+
const RAW_SMALL_FILE_SIZE_LIMIT = (128 * 1024) / 1.1; // 128 KiB, must match index.ts
|
|
8
7
|
|
|
9
|
-
describe('initUploadModule
|
|
8
|
+
describe('initUploadModule', () => {
|
|
10
9
|
const parentFolderUid = 'parent-folder-uid';
|
|
11
10
|
const name = 'test-file.txt';
|
|
12
11
|
const nodeUid = 'node-uid';
|
|
13
12
|
|
|
14
13
|
let featureFlagProvider: jest.Mocked<FeatureFlagProvider>;
|
|
15
14
|
let uploadModule: ReturnType<typeof initUploadModule>;
|
|
15
|
+
let initSmallFileSpy: jest.SpyInstance;
|
|
16
|
+
let initSmallRevisionSpy: jest.SpyInstance;
|
|
17
|
+
let initStreamSpy: jest.SpyInstance;
|
|
18
|
+
|
|
19
|
+
let stream: ReadableStream;
|
|
20
|
+
const thumbnail100k = { type: ThumbnailType.Type1, thumbnail: new Uint8Array(100_000) };
|
|
16
21
|
|
|
17
22
|
beforeEach(() => {
|
|
18
23
|
const apiService = {};
|
|
@@ -31,69 +36,122 @@ describe('initUploadModule - uploader selection', () => {
|
|
|
31
36
|
nodesService as any,
|
|
32
37
|
featureFlagProvider as any,
|
|
33
38
|
);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('getFileUploader', () => {
|
|
37
|
-
it('returns SmallFileUploader when feature flag is enabled and file size is below limit', async () => {
|
|
38
|
-
featureFlagProvider.isEnabled.mockResolvedValue(true);
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
expect(uploader).toBeInstanceOf(SmallFileUploader);
|
|
40
|
+
initSmallFileSpy = jest.spyOn(FileUploader.prototype as any, 'initSmallFileUploader').mockResolvedValue({
|
|
41
|
+
nodeRevisionUid: 'revision-uid',
|
|
42
|
+
nodeUid: 'node-uid',
|
|
44
43
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const uploader = await uploadModule.getFileUploader(parentFolderUid, name, metadata);
|
|
63
|
-
|
|
64
|
-
expect(uploader).toBeInstanceOf(FileUploader);
|
|
44
|
+
initSmallRevisionSpy = jest
|
|
45
|
+
.spyOn(FileRevisionUploader.prototype as any, 'initSmallFileUploader')
|
|
46
|
+
.mockResolvedValue({
|
|
47
|
+
nodeRevisionUid: 'revision-uid',
|
|
48
|
+
nodeUid: 'node-uid',
|
|
49
|
+
});
|
|
50
|
+
initStreamSpy = jest.spyOn(Uploader.prototype as any, 'initStreamUploader').mockResolvedValue({
|
|
51
|
+
start: jest.fn().mockResolvedValue({
|
|
52
|
+
nodeRevisionUid: 'revision-uid',
|
|
53
|
+
nodeUid: 'node-uid',
|
|
54
|
+
}),
|
|
55
|
+
} as any);
|
|
56
|
+
|
|
57
|
+
stream = new ReadableStream({
|
|
58
|
+
start(controller) {
|
|
59
|
+
controller.close();
|
|
60
|
+
},
|
|
65
61
|
});
|
|
66
62
|
});
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const metadata: UploadMetadata = { expectedSize: 1, mediaType: 'text/plain' };
|
|
73
|
-
const uploader = await uploadModule.getFileRevisionUploader(nodeUid, metadata);
|
|
74
|
-
|
|
75
|
-
expect(uploader).toBeInstanceOf(SmallFileRevisionUploader);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('returns FileRevisionUploader when feature flag is enabled but file size exceeds limit', async () => {
|
|
79
|
-
featureFlagProvider.isEnabled.mockResolvedValue(true);
|
|
80
|
-
|
|
81
|
-
const metadata: UploadMetadata = {
|
|
82
|
-
expectedSize: SMALL_FILE_SIZE_LIMIT + 1,
|
|
83
|
-
mediaType: 'text/plain',
|
|
84
|
-
};
|
|
85
|
-
const uploader = await uploadModule.getFileRevisionUploader(nodeUid, metadata);
|
|
86
|
-
|
|
87
|
-
expect(uploader).toBeInstanceOf(FileRevisionUploader);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('returns FileRevisionUploader when feature flag is disabled even for small file', async () => {
|
|
91
|
-
featureFlagProvider.isEnabled.mockResolvedValue(false);
|
|
92
|
-
|
|
93
|
-
const metadata: UploadMetadata = { expectedSize: 1, mediaType: 'text/plain' };
|
|
94
|
-
const uploader = await uploadModule.getFileRevisionUploader(nodeUid, metadata);
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
jest.restoreAllMocks();
|
|
66
|
+
});
|
|
95
67
|
|
|
96
|
-
|
|
68
|
+
async function drainUpload(controller: { completion(): Promise<unknown> }) {
|
|
69
|
+
await controller.completion();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const suites = [
|
|
73
|
+
{
|
|
74
|
+
method: 'getFileUploader',
|
|
75
|
+
getUploader: (metadata: UploadMetadata) => uploadModule.getFileUploader(parentFolderUid, name, metadata),
|
|
76
|
+
expect: (option: 'small' | 'stream') => {
|
|
77
|
+
if (option === 'stream') {
|
|
78
|
+
expect(initStreamSpy).toHaveBeenCalled();
|
|
79
|
+
expect(initSmallFileSpy).not.toHaveBeenCalled();
|
|
80
|
+
expect(initSmallRevisionSpy).not.toHaveBeenCalled();
|
|
81
|
+
} else {
|
|
82
|
+
expect(initSmallFileSpy).toHaveBeenCalled();
|
|
83
|
+
expect(initStreamSpy).not.toHaveBeenCalled();
|
|
84
|
+
expect(initSmallRevisionSpy).not.toHaveBeenCalled();
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
method: 'getFileRevisionUploader',
|
|
90
|
+
getUploader: (metadata: UploadMetadata) => uploadModule.getFileRevisionUploader(nodeUid, metadata),
|
|
91
|
+
expect: (option: 'small' | 'stream') => {
|
|
92
|
+
if (option === 'stream') {
|
|
93
|
+
expect(initStreamSpy).toHaveBeenCalled();
|
|
94
|
+
expect(initSmallFileSpy).not.toHaveBeenCalled();
|
|
95
|
+
expect(initSmallRevisionSpy).not.toHaveBeenCalled();
|
|
96
|
+
} else {
|
|
97
|
+
expect(initSmallRevisionSpy).toHaveBeenCalled();
|
|
98
|
+
expect(initSmallFileSpy).not.toHaveBeenCalled();
|
|
99
|
+
expect(initStreamSpy).not.toHaveBeenCalled();
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
for (const suite of suites) {
|
|
105
|
+
describe(suite.method, () => {
|
|
106
|
+
it('uses stream path when feature flag is disabled even for small file', async () => {
|
|
107
|
+
featureFlagProvider.isEnabled.mockResolvedValue(false);
|
|
108
|
+
|
|
109
|
+
const metadata: UploadMetadata = { expectedSize: 1, mediaType: 'text/plain' };
|
|
110
|
+
const uploader = await suite.getUploader(metadata);
|
|
111
|
+
await drainUpload(await uploader.uploadFromStream(stream, []));
|
|
112
|
+
|
|
113
|
+
suite.expect('stream');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('uses small-file path when flag is on and encrypted total size is below cap', async () => {
|
|
117
|
+
featureFlagProvider.isEnabled.mockResolvedValue(true);
|
|
118
|
+
|
|
119
|
+
const metadata: UploadMetadata = { expectedSize: 100, mediaType: 'text/plain' };
|
|
120
|
+
const uploader = await suite.getUploader(metadata);
|
|
121
|
+
await drainUpload(await uploader.uploadFromStream(stream, []));
|
|
122
|
+
|
|
123
|
+
suite.expect('small');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('uses small-file path when flag is on and encrypted total size with thumbnails is below cap', async () => {
|
|
127
|
+
featureFlagProvider.isEnabled.mockResolvedValue(true);
|
|
128
|
+
|
|
129
|
+
const metadata: UploadMetadata = { expectedSize: 100, mediaType: 'image/jpeg' };
|
|
130
|
+
const uploader = await suite.getUploader(metadata);
|
|
131
|
+
await drainUpload(await uploader.uploadFromStream(stream, [thumbnail100k]));
|
|
132
|
+
|
|
133
|
+
suite.expect('small');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('uses stream path when feature flag is enabled but raw file size exceeds limit', async () => {
|
|
137
|
+
featureFlagProvider.isEnabled.mockResolvedValue(true);
|
|
138
|
+
|
|
139
|
+
const metadata: UploadMetadata = { expectedSize: RAW_SMALL_FILE_SIZE_LIMIT, mediaType: 'text/plain' };
|
|
140
|
+
const uploader = await suite.getUploader(metadata);
|
|
141
|
+
await drainUpload(await uploader.uploadFromStream(stream, []));
|
|
142
|
+
|
|
143
|
+
suite.expect('stream');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('uses stream path when thumbnail bytes push encrypted total size with thumbnail exceeds limit', async () => {
|
|
147
|
+
featureFlagProvider.isEnabled.mockResolvedValue(true);
|
|
148
|
+
|
|
149
|
+
const metadata: UploadMetadata = { expectedSize: 100_000, mediaType: 'image/jpeg' };
|
|
150
|
+
const uploader = await suite.getUploader(metadata);
|
|
151
|
+
await drainUpload(await uploader.uploadFromStream(stream, [thumbnail100k]));
|
|
152
|
+
|
|
153
|
+
suite.expect('stream');
|
|
154
|
+
});
|
|
97
155
|
});
|
|
98
|
-
}
|
|
156
|
+
}
|
|
99
157
|
});
|
|
@@ -8,7 +8,6 @@ import { FileUploader as FileUploaderClass, FileRevisionUploader } from './fileU
|
|
|
8
8
|
import { NodesService, SharesService } from './interface';
|
|
9
9
|
import { UploadManager } from './manager';
|
|
10
10
|
import { UploadQueue } from './queue';
|
|
11
|
-
import { SmallFileRevisionUploader, SmallFileUploader } from './smallFileUploader';
|
|
12
11
|
import { UploadTelemetry } from './telemetry';
|
|
13
12
|
|
|
14
13
|
const SMALL_FILE_SIZE_LIMIT = 128 * 1024; // 128 KiB
|
|
@@ -38,13 +37,13 @@ export function initUploadModule(
|
|
|
38
37
|
|
|
39
38
|
const queue = new UploadQueue();
|
|
40
39
|
|
|
41
|
-
async function
|
|
40
|
+
async function shouldUseSmallFileUpload(expectedSize: number): Promise<boolean> {
|
|
42
41
|
const isEnabled =
|
|
43
42
|
allowSmallFileUpload && (await featureFlagProvider.isEnabled(FeatureFlags.DriveSmallFileUpload));
|
|
44
43
|
if (!isEnabled) {
|
|
45
44
|
return false;
|
|
46
45
|
}
|
|
47
|
-
return
|
|
46
|
+
return expectedSize < SMALL_FILE_SIZE_LIMIT;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
/**
|
|
@@ -66,20 +65,6 @@ export function initUploadModule(
|
|
|
66
65
|
queue.releaseCapacity(metadata.expectedSize);
|
|
67
66
|
};
|
|
68
67
|
|
|
69
|
-
if (await useSmallFileUpload(metadata)) {
|
|
70
|
-
return new SmallFileUploader(
|
|
71
|
-
uploadTelemetry,
|
|
72
|
-
api,
|
|
73
|
-
cryptoService,
|
|
74
|
-
manager,
|
|
75
|
-
metadata,
|
|
76
|
-
onFinish,
|
|
77
|
-
signal,
|
|
78
|
-
parentFolderUid,
|
|
79
|
-
name,
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
68
|
return new FileUploaderClass(
|
|
84
69
|
uploadTelemetry,
|
|
85
70
|
api,
|
|
@@ -89,6 +74,7 @@ export function initUploadModule(
|
|
|
89
74
|
name,
|
|
90
75
|
metadata,
|
|
91
76
|
onFinish,
|
|
77
|
+
shouldUseSmallFileUpload,
|
|
92
78
|
signal,
|
|
93
79
|
);
|
|
94
80
|
}
|
|
@@ -111,19 +97,6 @@ export function initUploadModule(
|
|
|
111
97
|
queue.releaseCapacity(metadata.expectedSize);
|
|
112
98
|
};
|
|
113
99
|
|
|
114
|
-
if (await useSmallFileUpload(metadata)) {
|
|
115
|
-
return new SmallFileRevisionUploader(
|
|
116
|
-
uploadTelemetry,
|
|
117
|
-
api,
|
|
118
|
-
cryptoService,
|
|
119
|
-
manager,
|
|
120
|
-
metadata,
|
|
121
|
-
onFinish,
|
|
122
|
-
signal,
|
|
123
|
-
nodeUid,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
100
|
return new FileRevisionUploader(
|
|
128
101
|
uploadTelemetry,
|
|
129
102
|
api,
|
|
@@ -132,6 +105,7 @@ export function initUploadModule(
|
|
|
132
105
|
nodeUid,
|
|
133
106
|
metadata,
|
|
134
107
|
onFinish,
|
|
108
|
+
shouldUseSmallFileUpload,
|
|
135
109
|
signal,
|
|
136
110
|
);
|
|
137
111
|
}
|