@protontech/drive-sdk 0.0.11 → 0.0.13
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/cache/index.d.ts +2 -0
- package/dist/cache/index.js +6 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/interface.d.ts +105 -0
- package/dist/cache/interface.js +3 -0
- package/dist/cache/interface.js.map +1 -0
- package/dist/cache/memoryCache.d.ts +18 -0
- package/dist/cache/memoryCache.js +78 -0
- package/dist/cache/memoryCache.js.map +1 -0
- package/dist/cache/memoryCache.test.d.ts +1 -0
- package/dist/cache/memoryCache.test.js +121 -0
- package/dist/cache/memoryCache.test.js.map +1 -0
- package/dist/crypto/hmac.d.ts +22 -0
- package/dist/crypto/hmac.js +44 -0
- package/dist/crypto/hmac.js.map +1 -0
- package/dist/crypto/utils.d.ts +2 -0
- package/dist/crypto/utils.js +35 -0
- package/dist/crypto/utils.js.map +1 -0
- package/dist/errors.d.ts +142 -0
- package/dist/errors.js +168 -0
- package/dist/errors.js.map +1 -0
- package/dist/interface/account.js +3 -0
- package/dist/interface/account.js.map +1 -0
- package/dist/interface/author.d.ts +26 -0
- package/dist/interface/author.js +3 -0
- package/dist/interface/author.js.map +1 -0
- package/dist/interface/download.d.ts +29 -0
- package/dist/interface/download.js +3 -0
- package/dist/interface/download.js.map +1 -0
- package/dist/interface/httpClient.d.ts +38 -0
- package/dist/interface/httpClient.js +3 -0
- package/dist/interface/httpClient.js.map +1 -0
- package/dist/interface/index.d.ts +1 -1
- package/dist/interface/nodes.d.ts +12 -1
- package/dist/interface/nodes.js +11 -0
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/result.d.ts +9 -0
- package/dist/interface/result.js +11 -0
- package/dist/interface/result.js.map +1 -0
- package/dist/interface/thumbnail.d.ts +17 -0
- package/dist/interface/thumbnail.js +9 -0
- package/dist/interface/thumbnail.js.map +1 -0
- package/dist/interface/upload.d.ts +64 -0
- package/dist/interface/upload.js +3 -0
- package/dist/interface/upload.js.map +1 -0
- package/dist/internal/apiService/driveTypes.d.ts +1341 -465
- package/dist/internal/apiService/errorCodes.d.ts +30 -0
- package/dist/internal/apiService/errorCodes.js +11 -0
- package/dist/internal/apiService/errorCodes.js.map +1 -0
- package/dist/internal/apiService/errors.js +2 -2
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/observerStream.d.ts +3 -0
- package/dist/internal/apiService/observerStream.js +15 -0
- package/dist/internal/apiService/observerStream.js.map +1 -0
- package/dist/internal/apiService/transformers.js +2 -0
- package/dist/internal/apiService/transformers.js.map +1 -1
- package/dist/internal/asyncIteratorMap.d.ts +15 -0
- package/dist/internal/asyncIteratorMap.js +59 -0
- package/dist/internal/asyncIteratorMap.js.map +1 -0
- package/dist/internal/asyncIteratorMap.test.d.ts +1 -0
- package/dist/internal/asyncIteratorMap.test.js +120 -0
- package/dist/internal/asyncIteratorMap.test.js.map +1 -0
- package/dist/internal/batchLoading.d.ts +34 -0
- package/dist/internal/batchLoading.js +68 -0
- package/dist/internal/batchLoading.js.map +1 -0
- package/dist/internal/batchLoading.test.d.ts +1 -0
- package/dist/internal/batchLoading.test.js +50 -0
- package/dist/internal/batchLoading.test.js.map +1 -0
- package/dist/internal/download/controller.d.ts +8 -0
- package/dist/internal/download/controller.js +22 -0
- package/dist/internal/download/controller.js.map +1 -0
- package/dist/internal/download/queue.d.ts +5 -0
- package/dist/internal/download/queue.js +31 -0
- package/dist/internal/download/queue.js.map +1 -0
- package/dist/internal/errors.js +28 -0
- package/dist/internal/errors.js.map +1 -0
- package/dist/internal/errors.test.js +22 -0
- package/dist/internal/errors.test.js.map +1 -0
- package/dist/internal/events/interface.d.ts +47 -0
- package/dist/internal/events/interface.js +12 -0
- package/dist/internal/events/interface.js.map +1 -0
- package/dist/internal/nodes/apiService.d.ts +2 -2
- package/dist/internal/nodes/apiService.js +16 -6
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +30 -8
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cache.js +1 -0
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cache.test.js +1 -0
- package/dist/internal/nodes/cache.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +34 -0
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/index.test.js +3 -1
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +3 -1
- package/dist/internal/nodes/mediaTypes.d.ts +2 -0
- package/dist/internal/nodes/mediaTypes.js +13 -0
- package/dist/internal/nodes/mediaTypes.js.map +1 -0
- package/dist/internal/nodes/nodesAccess.js +28 -7
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +7 -6
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/validations.d.ts +4 -0
- package/dist/internal/nodes/validations.js +21 -0
- package/dist/internal/nodes/validations.js.map +1 -0
- package/dist/internal/sharing/apiService.js +19 -2
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/uids.d.ts +38 -0
- package/dist/internal/uids.js +85 -0
- package/dist/internal/uids.js.map +1 -0
- package/dist/internal/upload/chunkStreamReader.d.ts +13 -0
- package/dist/internal/upload/chunkStreamReader.js +46 -0
- package/dist/internal/upload/chunkStreamReader.js.map +1 -0
- package/dist/internal/upload/chunkStreamReader.test.d.ts +1 -0
- package/dist/internal/upload/chunkStreamReader.test.js +75 -0
- package/dist/internal/upload/chunkStreamReader.test.js.map +1 -0
- package/dist/internal/upload/controller.d.ts +8 -0
- package/dist/internal/upload/controller.js +25 -0
- package/dist/internal/upload/controller.js.map +1 -0
- package/dist/internal/upload/digests.d.ts +8 -0
- package/dist/internal/upload/digests.js +22 -0
- package/dist/internal/upload/digests.js.map +1 -0
- package/dist/internal/upload/fileUploader.d.ts +49 -53
- package/dist/internal/upload/fileUploader.js +91 -395
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +38 -292
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/index.d.ts +3 -3
- package/dist/internal/upload/index.js +20 -41
- package/dist/internal/upload/index.js.map +1 -1
- package/dist/internal/upload/manager.d.ts +1 -1
- package/dist/internal/upload/manager.js +16 -19
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +42 -83
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/queue.d.ts +5 -0
- package/dist/internal/upload/queue.js +32 -0
- package/dist/internal/upload/queue.js.map +1 -0
- package/dist/internal/upload/streamUploader.d.ts +62 -0
- package/dist/internal/upload/streamUploader.js +441 -0
- package/dist/internal/upload/streamUploader.js.map +1 -0
- package/dist/internal/upload/streamUploader.test.d.ts +1 -0
- package/dist/internal/upload/streamUploader.test.js +358 -0
- package/dist/internal/upload/streamUploader.test.js.map +1 -0
- package/dist/internal/utils.d.ts +1 -0
- package/dist/internal/utils.js +13 -0
- package/dist/internal/utils.js.map +1 -0
- package/dist/internal/wait.d.ts +3 -0
- package/dist/internal/wait.js +28 -0
- package/dist/internal/wait.js.map +1 -0
- package/dist/internal/wait.test.d.ts +1 -0
- package/dist/internal/wait.test.js +21 -0
- package/dist/internal/wait.test.js.map +1 -0
- package/dist/protonDriveClient.d.ts +4 -4
- package/dist/protonDriveClient.js +1 -1
- package/dist/protonDriveClient.js.map +1 -1
- package/package.json +2 -2
- package/src/errors.ts +10 -4
- package/src/interface/index.ts +1 -1
- package/src/interface/nodes.ts +11 -0
- package/src/interface/upload.ts +53 -3
- package/src/internal/apiService/driveTypes.ts +1341 -465
- package/src/internal/apiService/errors.ts +3 -2
- package/src/internal/apiService/transformers.ts +2 -0
- package/src/internal/asyncIteratorMap.test.ts +150 -0
- package/src/internal/asyncIteratorMap.ts +64 -0
- package/src/internal/nodes/apiService.test.ts +36 -7
- package/src/internal/nodes/apiService.ts +19 -7
- package/src/internal/nodes/cache.test.ts +1 -0
- package/src/internal/nodes/cache.ts +1 -0
- package/src/internal/nodes/cryptoService.test.ts +38 -0
- package/src/internal/nodes/index.test.ts +3 -1
- package/src/internal/nodes/interface.ts +4 -1
- package/src/internal/nodes/nodesAccess.test.ts +7 -6
- package/src/internal/nodes/nodesAccess.ts +30 -7
- package/src/internal/sharing/apiService.ts +24 -2
- package/src/internal/upload/fileUploader.test.ts +46 -376
- package/src/internal/upload/fileUploader.ts +114 -494
- package/src/internal/upload/index.ts +26 -50
- package/src/internal/upload/manager.test.ts +45 -92
- package/src/internal/upload/manager.ts +30 -32
- package/src/internal/upload/streamUploader.test.ts +469 -0
- package/src/internal/upload/streamUploader.ts +552 -0
- package/src/protonDriveClient.ts +5 -4
|
@@ -3,12 +3,11 @@ import { DriveAPIService } from "../apiService";
|
|
|
3
3
|
import { DriveCrypto } from "../../crypto";
|
|
4
4
|
import { UploadAPIService } from "./apiService";
|
|
5
5
|
import { UploadCryptoService } from "./cryptoService";
|
|
6
|
-
import {
|
|
6
|
+
import { FileUploader, FileRevisionUploader } from "./fileUploader";
|
|
7
7
|
import { NodesService, NodesEvents, SharesService } from "./interface";
|
|
8
|
-
import { Fileuploader } from "./fileUploader";
|
|
9
|
-
import { UploadTelemetry } from "./telemetry";
|
|
10
8
|
import { UploadManager } from "./manager";
|
|
11
|
-
import {
|
|
9
|
+
import { UploadQueue } from "./queue";
|
|
10
|
+
import { UploadTelemetry } from "./telemetry";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Provides facade for the upload module.
|
|
@@ -33,85 +32,62 @@ export function initUploadModule(
|
|
|
33
32
|
|
|
34
33
|
const queue = new UploadQueue();
|
|
35
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Returns a FileUploader instance that can be used to upload a file to
|
|
37
|
+
* a parent folder.
|
|
38
|
+
*
|
|
39
|
+
* This operation does not call the API, it only returns a FileUploader
|
|
40
|
+
* instance when the upload queue has capacity.
|
|
41
|
+
*/
|
|
36
42
|
async function getFileUploader(
|
|
37
43
|
parentFolderUid: string,
|
|
38
44
|
name: string,
|
|
39
45
|
metadata: UploadMetadata,
|
|
40
46
|
signal?: AbortSignal,
|
|
41
|
-
): Promise<
|
|
47
|
+
): Promise<FileUploader> {
|
|
42
48
|
await queue.waitForCapacity(signal);
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
revisionDraft = await manager.createDraftNode(parentFolderUid, name, metadata);
|
|
47
|
-
|
|
48
|
-
blockVerifier = new BlockVerifier(api, cryptoService, revisionDraft.nodeKeys.key, revisionDraft.nodeRevisionUid);
|
|
49
|
-
await blockVerifier.loadVerificationData();
|
|
50
|
-
} catch (error: unknown) {
|
|
51
|
-
queue.releaseCapacity();
|
|
52
|
-
if (revisionDraft) {
|
|
53
|
-
await manager.deleteDraftNode(revisionDraft.nodeUid);
|
|
54
|
-
}
|
|
55
|
-
void uploadTelemetry.uploadInitFailed(parentFolderUid, error, metadata.expectedSize);
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const onFinish = async (failure: boolean) => {
|
|
50
|
+
const onFinish = () => {
|
|
60
51
|
queue.releaseCapacity();
|
|
61
|
-
if (failure) {
|
|
62
|
-
await manager.deleteDraftNode(revisionDraft.nodeUid);
|
|
63
|
-
}
|
|
64
52
|
}
|
|
65
53
|
|
|
66
|
-
return new
|
|
54
|
+
return new FileUploader(
|
|
67
55
|
uploadTelemetry,
|
|
68
56
|
api,
|
|
69
57
|
cryptoService,
|
|
70
58
|
manager,
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
parentFolderUid,
|
|
60
|
+
name,
|
|
73
61
|
metadata,
|
|
74
62
|
onFinish,
|
|
75
63
|
signal,
|
|
76
64
|
);
|
|
77
65
|
}
|
|
78
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Returns a FileUploader instance that can be used to upload a new
|
|
69
|
+
* revision of a file.
|
|
70
|
+
*
|
|
71
|
+
* This operation does not call the API, it only returns a
|
|
72
|
+
* FileRevisionUploader instance when the upload queue has capacity.
|
|
73
|
+
*/
|
|
79
74
|
async function getFileRevisionUploader(
|
|
80
75
|
nodeUid: string,
|
|
81
76
|
metadata: UploadMetadata,
|
|
82
77
|
signal?: AbortSignal,
|
|
83
|
-
): Promise<
|
|
78
|
+
): Promise<FileRevisionUploader> {
|
|
84
79
|
await queue.waitForCapacity(signal);
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
revisionDraft = await manager.createDraftRevision(nodeUid, metadata);
|
|
89
|
-
|
|
90
|
-
blockVerifier = new BlockVerifier(api, cryptoService, revisionDraft.nodeKeys.key, revisionDraft.nodeRevisionUid);
|
|
91
|
-
await blockVerifier.loadVerificationData();
|
|
92
|
-
} catch (error: unknown) {
|
|
93
|
-
queue.releaseCapacity();
|
|
94
|
-
if (revisionDraft) {
|
|
95
|
-
await manager.deleteDraftRevision(revisionDraft.nodeRevisionUid);
|
|
96
|
-
}
|
|
97
|
-
void uploadTelemetry.uploadInitFailed(nodeUid, error, metadata.expectedSize);
|
|
98
|
-
throw error;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const onFinish = async (failure: boolean) => {
|
|
81
|
+
const onFinish = () => {
|
|
102
82
|
queue.releaseCapacity();
|
|
103
|
-
if (failure) {
|
|
104
|
-
await manager.deleteDraftNode(revisionDraft.nodeUid);
|
|
105
|
-
}
|
|
106
83
|
}
|
|
107
84
|
|
|
108
|
-
return new
|
|
85
|
+
return new FileRevisionUploader(
|
|
109
86
|
uploadTelemetry,
|
|
110
87
|
api,
|
|
111
88
|
cryptoService,
|
|
112
89
|
manager,
|
|
113
|
-
|
|
114
|
-
revisionDraft,
|
|
90
|
+
nodeUid,
|
|
115
91
|
metadata,
|
|
116
92
|
onFinish,
|
|
117
93
|
signal,
|
|
@@ -135,14 +135,18 @@ describe("UploadManager", () => {
|
|
|
135
135
|
armoredContentKeyPacketSignature: "newNode:armoredContentKeyPacketSignature",
|
|
136
136
|
signatureEmail: "signatureEmail",
|
|
137
137
|
});
|
|
138
|
-
expect(apiService.checkAvailableHashes).not.toHaveBeenCalled();
|
|
139
138
|
});
|
|
140
139
|
|
|
141
|
-
it("should
|
|
142
|
-
let
|
|
140
|
+
it("should delete existing draft and trying again", async () => {
|
|
141
|
+
let firstCall = true;
|
|
143
142
|
apiService.createDraft = jest.fn().mockImplementation(() => {
|
|
144
|
-
if (
|
|
145
|
-
|
|
143
|
+
if (firstCall) {
|
|
144
|
+
firstCall = false;
|
|
145
|
+
throw new ValidationError("Draft already exists", ErrorCode.ALREADY_EXISTS, {
|
|
146
|
+
ConflictLinkID: "existingLinkId",
|
|
147
|
+
ConflictDraftRevisionID: "existingDraftRevisionId",
|
|
148
|
+
ConflictDraftClientUID: "existingDraftClientUid",
|
|
149
|
+
});
|
|
146
150
|
}
|
|
147
151
|
return {
|
|
148
152
|
nodeUid: "newNode:nodeUid",
|
|
@@ -150,26 +154,8 @@ describe("UploadManager", () => {
|
|
|
150
154
|
};
|
|
151
155
|
});
|
|
152
156
|
|
|
153
|
-
|
|
154
|
-
if (!hashChecked) {
|
|
155
|
-
hashChecked = true;
|
|
156
|
-
return {
|
|
157
|
-
availalbleHashes: ["name1Hash"],
|
|
158
|
-
pendingHashes: [{
|
|
159
|
-
hash: "newNode:hash",
|
|
160
|
-
nodeUid: "nodeUidToDelete"
|
|
161
|
-
}],
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return {
|
|
165
|
-
availalbleHashes: ["name1Hash"],
|
|
166
|
-
pendingHashes: [],
|
|
167
|
-
}
|
|
168
|
-
});
|
|
157
|
+
const result = await manager.createDraftNode("volumeId~parentUid", "name", {} as UploadMetadata);
|
|
169
158
|
|
|
170
|
-
const result = await manager.createDraftNode("parentUid", "name", {} as UploadMetadata);
|
|
171
|
-
|
|
172
|
-
expect(apiService.checkAvailableHashes).toHaveBeenCalledTimes(1);
|
|
173
159
|
expect(apiService.deleteDraft).toHaveBeenCalledTimes(1);
|
|
174
160
|
expect(result).toEqual({
|
|
175
161
|
nodeUid: "newNode:nodeUid",
|
|
@@ -182,20 +168,25 @@ describe("UploadManager", () => {
|
|
|
182
168
|
},
|
|
183
169
|
},
|
|
184
170
|
newNodeInfo: {
|
|
185
|
-
parentUid: "parentUid",
|
|
171
|
+
parentUid: "volumeId~parentUid",
|
|
186
172
|
name: "name",
|
|
187
173
|
encryptedName: "newNode:encryptedName",
|
|
188
174
|
hash: "newNode:hash",
|
|
189
175
|
},
|
|
190
176
|
});
|
|
191
|
-
expect(apiService.deleteDraft).toHaveBeenCalledWith("
|
|
177
|
+
expect(apiService.deleteDraft).toHaveBeenCalledWith("volumeId~existingLinkId");
|
|
192
178
|
});
|
|
193
179
|
|
|
194
180
|
it("should handle error when deleting existing draft", async () => {
|
|
195
|
-
let
|
|
181
|
+
let firstCall = true;
|
|
196
182
|
apiService.createDraft = jest.fn().mockImplementation(() => {
|
|
197
|
-
if (
|
|
198
|
-
|
|
183
|
+
if (firstCall) {
|
|
184
|
+
firstCall = false;
|
|
185
|
+
throw new ValidationError("Draft already exists", ErrorCode.ALREADY_EXISTS, {
|
|
186
|
+
ConflictLinkID: "existingLinkId",
|
|
187
|
+
ConflictDraftRevisionID: "existingDraftRevisionId",
|
|
188
|
+
ConflictDraftClientUID: "existingDraftClientUid",
|
|
189
|
+
});
|
|
199
190
|
}
|
|
200
191
|
return {
|
|
201
192
|
nodeUid: "newNode:nodeUid",
|
|
@@ -206,70 +197,38 @@ describe("UploadManager", () => {
|
|
|
206
197
|
throw new Error("Failed to delete draft");
|
|
207
198
|
});
|
|
208
199
|
|
|
209
|
-
|
|
210
|
-
if (!hashChecked) {
|
|
211
|
-
hashChecked = true;
|
|
212
|
-
return {
|
|
213
|
-
availalbleHashes: ["name1Hash"],
|
|
214
|
-
pendingHashes: [{
|
|
215
|
-
hash: "newNode:hash",
|
|
216
|
-
nodeUid: "nodeUidToDelete"
|
|
217
|
-
}],
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return {
|
|
221
|
-
availalbleHashes: ["name1Hash"],
|
|
222
|
-
pendingHashes: [],
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
const result = manager.createDraftNode("parentUid", "name", {} as UploadMetadata);
|
|
227
|
-
|
|
228
|
-
await expect(result).rejects.toThrow("Draft already exists");
|
|
229
|
-
expect(apiService.checkAvailableHashes).toHaveBeenCalledTimes(1);
|
|
230
|
-
expect(apiService.deleteDraft).toHaveBeenCalledTimes(1);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("should handle existing name by providing available name", async () => {
|
|
234
|
-
let count = 0;
|
|
235
|
-
apiService.createDraft = jest.fn().mockImplementation(() => {
|
|
236
|
-
if (count === 0) {
|
|
237
|
-
count++;
|
|
238
|
-
throw new ValidationError("Draft already exists", ErrorCode.ALREADY_EXISTS);
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
nodeUid: "newNode:nodeUid",
|
|
242
|
-
nodeRevisionUid: "newNode:nodeRevisionUid",
|
|
243
|
-
};
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const result = manager.createDraftNode("parentUid", "name", {} as UploadMetadata);
|
|
247
|
-
|
|
248
|
-
await expect(result).rejects.toThrow("Draft already exists");
|
|
249
|
-
expect(apiService.checkAvailableHashes).toHaveBeenCalledTimes(1);
|
|
200
|
+
const result = manager.createDraftNode("volumeId~parentUid", "name", {} as UploadMetadata);
|
|
250
201
|
|
|
251
202
|
try {
|
|
252
203
|
await result;
|
|
253
204
|
} catch (error: any) {
|
|
254
|
-
expect(error.
|
|
205
|
+
expect(error.message).toBe("Draft already exists");
|
|
206
|
+
expect(error.existingNodeUid).toBe("volumeId~existingLinkId");
|
|
255
207
|
}
|
|
208
|
+
expect(apiService.deleteDraft).toHaveBeenCalledTimes(1);
|
|
256
209
|
});
|
|
210
|
+
});
|
|
257
211
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
apiService.
|
|
261
|
-
if (!hashChecked) {
|
|
262
|
-
throw new ValidationError("Draft already exists", ErrorCode.ALREADY_EXISTS);
|
|
263
|
-
}
|
|
212
|
+
describe("findAvailableName", () => {
|
|
213
|
+
it("should find available name", async () => {
|
|
214
|
+
apiService.checkAvailableHashes = jest.fn().mockImplementation(() => {
|
|
264
215
|
return {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
216
|
+
availalbleHashes: ["name3Hash"],
|
|
217
|
+
pendingHashes: [],
|
|
218
|
+
}
|
|
268
219
|
});
|
|
269
220
|
|
|
221
|
+
const result = await manager.findAvailableName("parentUid", "name");
|
|
222
|
+
expect(result).toBe("name3");
|
|
223
|
+
expect(apiService.checkAvailableHashes).toHaveBeenCalledTimes(1);
|
|
224
|
+
expect(apiService.checkAvailableHashes).toHaveBeenCalledWith("parentUid", ["name1Hash", "name2Hash", "name3Hash"]);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("should find available name with multiple pages", async () => {
|
|
228
|
+
let firstCall = false;
|
|
270
229
|
apiService.checkAvailableHashes = jest.fn().mockImplementation(() => {
|
|
271
|
-
if (!
|
|
272
|
-
|
|
230
|
+
if (!firstCall) {
|
|
231
|
+
firstCall = true;
|
|
273
232
|
return {
|
|
274
233
|
// First page has no available hashes
|
|
275
234
|
availalbleHashes: [],
|
|
@@ -282,16 +241,10 @@ describe("UploadManager", () => {
|
|
|
282
241
|
}
|
|
283
242
|
});
|
|
284
243
|
|
|
285
|
-
const result = manager.
|
|
286
|
-
|
|
287
|
-
await expect(result).rejects.toThrow("Draft already exists");
|
|
244
|
+
const result = await manager.findAvailableName("parentUid", "name");
|
|
245
|
+
expect(result).toBe("name3");
|
|
288
246
|
expect(apiService.checkAvailableHashes).toHaveBeenCalledTimes(2);
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
await result;
|
|
292
|
-
} catch (error: any) {
|
|
293
|
-
expect(error.availableName).toBe("name3");
|
|
294
|
-
}
|
|
247
|
+
expect(apiService.checkAvailableHashes).toHaveBeenCalledWith("parentUid", ["name1Hash", "name2Hash", "name3Hash"]);
|
|
295
248
|
});
|
|
296
249
|
});
|
|
297
250
|
|
|
@@ -300,7 +253,7 @@ describe("UploadManager", () => {
|
|
|
300
253
|
nodeUid: "newNode:nodeUid",
|
|
301
254
|
nodeRevisionUid: "newNode:nodeRevisionUid",
|
|
302
255
|
nodeKeys: {
|
|
303
|
-
key: {_idx: 32321},
|
|
256
|
+
key: { _idx: 32321 },
|
|
304
257
|
contentKeyPacketSessionKey: "newNode:contentKeyPacketSessionKey",
|
|
305
258
|
signatureAddress: {
|
|
306
259
|
email: "signatureEmail",
|
|
@@ -7,6 +7,7 @@ import { DecryptedNode, generateFileExtendedAttributes } from "../nodes";
|
|
|
7
7
|
import { UploadAPIService } from "./apiService";
|
|
8
8
|
import { UploadCryptoService } from "./cryptoService";
|
|
9
9
|
import { NodeRevisionDraft, NodesService, NodesEvents, NodeCrypto } from "./interface";
|
|
10
|
+
import { makeNodeUid, splitNodeUid } from "../uids";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* UploadManager is responsible for creating and deleting draft nodes
|
|
@@ -95,23 +96,26 @@ export class UploadManager {
|
|
|
95
96
|
if (error instanceof ValidationError) {
|
|
96
97
|
if (error.code === ErrorCode.ALREADY_EXISTS) {
|
|
97
98
|
this.logger.info(`Node with given name already exists`);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
|
|
100
|
+
const typedDetails = error.details as {
|
|
101
|
+
ConflictLinkID: string,
|
|
102
|
+
ConflictRevisionID?: string,
|
|
103
|
+
ConflictDraftRevisionID?: string,
|
|
104
|
+
ConflictDraftClientUID?: string,
|
|
105
|
+
} | undefined;
|
|
104
106
|
|
|
105
107
|
// If there is existing draft created by this client,
|
|
106
108
|
// automatically delete it and try to create a new one
|
|
107
109
|
// with the same name again.
|
|
108
|
-
if (
|
|
110
|
+
if (typedDetails?.ConflictDraftRevisionID) {
|
|
111
|
+
const existingDraftNodeUid = makeNodeUid(splitNodeUid(parentFolderUid).volumeId, typedDetails.ConflictLinkID);
|
|
112
|
+
|
|
109
113
|
let deleteFailed = false;
|
|
110
114
|
try {
|
|
111
|
-
this.logger.warn(`Deleting existing draft node ${
|
|
112
|
-
await this.apiService.deleteDraft(
|
|
115
|
+
this.logger.warn(`Deleting existing draft node ${existingDraftNodeUid}`);
|
|
116
|
+
await this.apiService.deleteDraft(existingDraftNodeUid);
|
|
113
117
|
} catch (deleteDraftError: unknown) {
|
|
114
|
-
// Do not throw, let
|
|
118
|
+
// Do not throw, let throw the conflict error.
|
|
115
119
|
deleteFailed = true;
|
|
116
120
|
this.logger.error('Failed to delete existing draft node', deleteDraftError);
|
|
117
121
|
}
|
|
@@ -120,12 +124,14 @@ export class UploadManager {
|
|
|
120
124
|
}
|
|
121
125
|
}
|
|
122
126
|
|
|
127
|
+
const existingNodeUid = typedDetails ? makeNodeUid(splitNodeUid(parentFolderUid).volumeId, typedDetails.ConflictLinkID) : undefined;
|
|
128
|
+
|
|
123
129
|
// If there is existing node, return special error
|
|
124
130
|
// that includes the available name the client can use.
|
|
125
131
|
throw new NodeAlreadyExistsValidationError(
|
|
126
132
|
error.message,
|
|
127
133
|
error.code,
|
|
128
|
-
|
|
134
|
+
existingNodeUid,
|
|
129
135
|
);
|
|
130
136
|
}
|
|
131
137
|
}
|
|
@@ -133,10 +139,12 @@ export class UploadManager {
|
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
142
|
+
async findAvailableName(parentFolderUid: string, name: string): Promise<string> {
|
|
143
|
+
const { hashKey: parentHashKey } = await this.nodesService.getNodeKeys(parentFolderUid);
|
|
144
|
+
if (!parentHashKey) {
|
|
145
|
+
throw new ValidationError(c('Error').t`Creating files in non-folders is not allowed`);
|
|
146
|
+
}
|
|
147
|
+
|
|
140
148
|
const [namePart, extension] = splitExtension(name);
|
|
141
149
|
|
|
142
150
|
const batchSize = 10;
|
|
@@ -149,14 +157,9 @@ export class UploadManager {
|
|
|
149
157
|
|
|
150
158
|
const hashesToCheck = await this.cryptoService.generateNameHashes(parentHashKey, namesToCheck);
|
|
151
159
|
|
|
152
|
-
const {
|
|
160
|
+
const { availalbleHashes } = await this.apiService.checkAvailableHashes(
|
|
153
161
|
parentFolderUid,
|
|
154
|
-
|
|
155
|
-
...hashesToCheck.map(({ hash }) => hash),
|
|
156
|
-
// Adding the current name hash to get the existing draft
|
|
157
|
-
// node UID if it exists.
|
|
158
|
-
...startIndex ? [nameHash] : [],
|
|
159
|
-
],
|
|
162
|
+
hashesToCheck.map(({ hash }) => hash),
|
|
160
163
|
);
|
|
161
164
|
|
|
162
165
|
if (!availalbleHashes.length) {
|
|
@@ -169,12 +172,7 @@ export class UploadManager {
|
|
|
169
172
|
throw Error('Backend returned unexpected hash');
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
|
|
173
|
-
const ownPendingHash = pendingHashes.find(({ hash }) => hash === nameHash);
|
|
174
|
-
return {
|
|
175
|
-
availableName: availableHash.name,
|
|
176
|
-
existingDraftNodeUid: ownPendingHash?.nodeUid,
|
|
177
|
-
}
|
|
175
|
+
return availableHash.name;
|
|
178
176
|
}
|
|
179
177
|
}
|
|
180
178
|
|
|
@@ -261,7 +259,7 @@ export class UploadManager {
|
|
|
261
259
|
// Internal metadata
|
|
262
260
|
hash: nodeRevisionDraft.newNodeInfo.hash,
|
|
263
261
|
encryptedName: nodeRevisionDraft.newNodeInfo.encryptedName,
|
|
264
|
-
|
|
262
|
+
|
|
265
263
|
// Basic node metadata
|
|
266
264
|
uid: nodeRevisionDraft.nodeUid,
|
|
267
265
|
parentUid: nodeRevisionDraft.newNodeInfo.parentUid,
|
|
@@ -269,11 +267,11 @@ export class UploadManager {
|
|
|
269
267
|
mediaType: metadata.mediaType,
|
|
270
268
|
creationTime: new Date(),
|
|
271
269
|
totalStorageSize: encryptedSize,
|
|
272
|
-
|
|
270
|
+
|
|
273
271
|
// Share node metadata
|
|
274
272
|
isShared: false,
|
|
275
273
|
directMemberRole: MemberRole.Inherited,
|
|
276
|
-
|
|
274
|
+
|
|
277
275
|
// Decrypted metadata
|
|
278
276
|
isStale: false,
|
|
279
277
|
keyAuthor: resultOk(nodeRevisionDraft.nodeKeys.signatureAddress.email),
|
|
@@ -297,7 +295,7 @@ export class UploadManager {
|
|
|
297
295
|
*/
|
|
298
296
|
function splitExtension(filename = ''): [string, string] {
|
|
299
297
|
const endIdx = filename.lastIndexOf('.');
|
|
300
|
-
if (endIdx === -1 || endIdx === filename.length-1) {
|
|
298
|
+
if (endIdx === -1 || endIdx === filename.length - 1) {
|
|
301
299
|
return [filename, ''];
|
|
302
300
|
}
|
|
303
301
|
return [filename.slice(0, endIdx), filename.slice(endIdx + 1)];
|