@filen/sync 0.1.1 → 0.1.4

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.
Files changed (48) hide show
  1. package/LICENSE +661 -661
  2. package/README.md +1 -1
  3. package/SECURITY.md +17 -17
  4. package/dist/constants.d.ts +7 -0
  5. package/dist/constants.js +51 -1
  6. package/dist/constants.js.map +1 -1
  7. package/dist/ignorer.d.ts +14 -0
  8. package/dist/ignorer.js +69 -0
  9. package/dist/ignorer.js.map +1 -0
  10. package/dist/index.d.ts +31 -17
  11. package/dist/index.js +125 -24
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/deltas.d.ts +9 -31
  14. package/dist/lib/deltas.js +194 -99
  15. package/dist/lib/deltas.js.map +1 -1
  16. package/dist/lib/filesystems/local.d.ts +32 -43
  17. package/dist/lib/filesystems/local.js +383 -106
  18. package/dist/lib/filesystems/local.js.map +1 -1
  19. package/dist/lib/filesystems/remote.d.ts +21 -7
  20. package/dist/lib/filesystems/remote.js +531 -67
  21. package/dist/lib/filesystems/remote.js.map +1 -1
  22. package/dist/lib/ipc.d.ts +9 -0
  23. package/dist/lib/ipc.js +60 -0
  24. package/dist/lib/ipc.js.map +1 -0
  25. package/dist/lib/lock.d.ts +13 -0
  26. package/dist/lib/lock.js +73 -0
  27. package/dist/lib/lock.js.map +1 -0
  28. package/dist/lib/logger.d.ts +14 -0
  29. package/dist/lib/logger.js +93 -0
  30. package/dist/lib/logger.js.map +1 -0
  31. package/dist/lib/state.d.ts +4 -15
  32. package/dist/lib/state.js +106 -87
  33. package/dist/lib/state.js.map +1 -1
  34. package/dist/lib/sync.d.ts +30 -10
  35. package/dist/lib/sync.js +363 -36
  36. package/dist/lib/sync.js.map +1 -1
  37. package/dist/lib/tasks.d.ts +27 -40
  38. package/dist/lib/tasks.js +397 -48
  39. package/dist/lib/tasks.js.map +1 -1
  40. package/dist/types.d.ts +340 -0
  41. package/dist/utils.d.ts +42 -0
  42. package/dist/utils.js +164 -1
  43. package/dist/utils.js.map +1 -1
  44. package/index.d.ts +6 -295
  45. package/jest.config.js +5 -0
  46. package/package.json +62 -58
  47. package/tests/utils.test.ts +33 -0
  48. package/tsconfig.json +24 -24
@@ -4,43 +4,286 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.RemoteFileSystem = void 0;
7
+ const sdk_1 = require("@filen/sdk");
7
8
  const path_1 = __importDefault(require("path"));
8
9
  const semaphore_1 = require("../../semaphore");
9
10
  const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const ipc_1 = require("../ipc");
12
+ const utils_1 = require("../../utils");
13
+ const uuid_1 = require("uuid");
14
+ const constants_1 = require("../../constants");
10
15
  class RemoteFileSystem {
11
- constructor({ sync }) {
16
+ constructor(sync) {
12
17
  this.getDirectoryTreeCache = {
13
18
  timestamp: 0,
14
19
  tree: {},
15
20
  uuids: {}
16
21
  };
22
+ this.previousTreeRawResponse = "";
17
23
  this.mutex = new semaphore_1.Semaphore(1);
18
24
  this.mkdirMutex = new semaphore_1.Semaphore(1);
25
+ this.itemsMutex = new semaphore_1.Semaphore(1);
26
+ this.deviceIdCache = "";
19
27
  this.sync = sync;
20
28
  }
21
- async getDirectoryTree() {
22
- // TODO: Actual implementation using cache + different endpoint with deviceId
23
- // Account for duplicate path names for directories/files. Only keep the first one discovered in the tree.
29
+ async getDeviceId() {
30
+ if (this.deviceIdCache.length > 0) {
31
+ return this.deviceIdCache;
32
+ }
33
+ const deviceIdFile = path_1.default.join(this.sync.dbPath, this.sync.syncPair.uuid, "deviceId");
34
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(deviceIdFile));
35
+ let deviceId = "";
36
+ if (!(await fs_extra_1.default.exists(deviceIdFile))) {
37
+ deviceId = (0, uuid_1.v4)();
38
+ await fs_extra_1.default.writeFile(deviceIdFile, deviceId, {
39
+ encoding: "utf-8"
40
+ });
41
+ }
42
+ else {
43
+ deviceId = await fs_extra_1.default.readFile(deviceIdFile, {
44
+ encoding: "utf-8"
45
+ });
46
+ }
47
+ this.deviceIdCache = deviceId;
48
+ return deviceId;
49
+ }
50
+ async getDirectoryTree(skipCache = false) {
51
+ var _a;
52
+ const deviceId = await this.getDeviceId();
53
+ const dir = await this.sync.sdk.api(3).dir().tree({
54
+ uuid: this.sync.syncPair.remoteParentUUID,
55
+ deviceId,
56
+ skipCache,
57
+ includeRaw: true
58
+ });
59
+ // Data did not change, use cache
60
+ if (dir.files.length === 0 && dir.folders.length === 0) {
61
+ if (this.getDirectoryTreeCache.timestamp === 0) {
62
+ // Re-run but with API caching disabled since our internal client cache is empty
63
+ return await this.getDirectoryTree(true);
64
+ }
65
+ return {
66
+ result: this.getDirectoryTreeCache,
67
+ ignored: [],
68
+ changed: false
69
+ };
70
+ }
71
+ // eslint-disable-next-line quotes
72
+ const rawEx = dir.raw.split('"randomBytes"');
73
+ // Compare API response with the previous dataset, this way we can save time computing the tree if it's the same
74
+ if (rawEx.length === 2 && this.previousTreeRawResponse === rawEx[0] && this.getDirectoryTreeCache.timestamp !== 0) {
75
+ return {
76
+ result: this.getDirectoryTreeCache,
77
+ ignored: [],
78
+ changed: false
79
+ };
80
+ }
81
+ const baseFolder = dir.folders[0];
82
+ if (!baseFolder) {
83
+ throw new Error("Could not get base folder.");
84
+ }
85
+ const baseFolderParent = baseFolder[2];
86
+ if (baseFolderParent !== "base") {
87
+ throw new Error("Invalid base folder parent.");
88
+ }
89
+ const folderNames = { base: "/" };
24
90
  const tree = {};
25
- const dir = await this.sync.sdk.cloud().getDirectoryTree({ uuid: this.sync.syncPair.remoteParentUUID });
26
91
  const uuids = {};
27
- for (const path in dir) {
28
- if (!dir[path] || dir[path].parent === "base" || path.startsWith(".filen.trash.local")) {
29
- continue;
92
+ const pathsAdded = {};
93
+ const ignored = [];
94
+ for (const folder of dir.folders) {
95
+ try {
96
+ const decrypted = await this.sync.sdk.crypto().decrypt().folderMetadata({ metadata: folder[1] });
97
+ if (folder[2] !== "base" && decrypted.name.length === 0) {
98
+ continue;
99
+ }
100
+ const parentPath = folder[2] === "base" ? "" : `${folderNames[folder[2]]}/`;
101
+ const folderPath = folder[2] === "base" ? "" : `${parentPath}${decrypted.name}`;
102
+ const localPath = path_1.default.join(this.sync.syncPair.localPath, folderPath);
103
+ folderNames[folder[0]] = folderPath;
104
+ if (folderPath.startsWith(constants_1.LOCAL_TRASH_NAME) || decrypted.name.startsWith(constants_1.LOCAL_TRASH_NAME)) {
105
+ continue;
106
+ }
107
+ if (this.sync.remoteIgnorer.ignores(folderPath)) {
108
+ ignored.push({
109
+ localPath,
110
+ relativePath: folderPath,
111
+ reason: "remoteIgnore"
112
+ });
113
+ continue;
114
+ }
115
+ if (this.sync.excludeDotFiles && (0, utils_1.pathIncludesDotFile)(folderPath)) {
116
+ ignored.push({
117
+ localPath,
118
+ relativePath: folderPath,
119
+ reason: "dotFile"
120
+ });
121
+ continue;
122
+ }
123
+ if ((0, utils_1.isPathOverMaxLength)(localPath)) {
124
+ ignored.push({
125
+ localPath,
126
+ relativePath: folderPath,
127
+ reason: "pathLength"
128
+ });
129
+ continue;
130
+ }
131
+ if ((0, utils_1.isNameOverMaxLength)(decrypted.name)) {
132
+ ignored.push({
133
+ localPath,
134
+ relativePath: folderPath,
135
+ reason: "nameLength"
136
+ });
137
+ continue;
138
+ }
139
+ if (!(0, utils_1.isValidPath)(localPath)) {
140
+ ignored.push({
141
+ localPath,
142
+ relativePath: folderPath,
143
+ reason: "invalidPath"
144
+ });
145
+ continue;
146
+ }
147
+ if ((0, utils_1.isDirectoryPathIgnoredByDefault)(folderPath) || (0, utils_1.isRelativePathIgnoredByDefault)(folderPath)) {
148
+ ignored.push({
149
+ localPath,
150
+ relativePath: folderPath,
151
+ reason: "defaultIgnore"
152
+ });
153
+ continue;
154
+ }
155
+ const lowercasePath = folderPath.toLowerCase();
156
+ if (pathsAdded[lowercasePath]) {
157
+ continue;
158
+ }
159
+ pathsAdded[lowercasePath] = true;
160
+ const item = {
161
+ type: "directory",
162
+ uuid: folder[0],
163
+ name: decrypted.name,
164
+ size: 0,
165
+ path: folderPath
166
+ };
167
+ tree[folderPath] = item;
168
+ uuids[folder[0]] = item;
30
169
  }
31
- const item = Object.assign(Object.assign({}, dir[path]), { path });
32
- tree[path] = item;
33
- const treeItem = tree[path];
34
- if (treeItem) {
35
- uuids[treeItem.uuid] = item;
170
+ catch (e) {
171
+ this.sync.worker.logger.log("error", e, "filesystems.remote.getDirectoryTree");
36
172
  }
37
173
  }
174
+ if (Object.keys(folderNames).length === 0) {
175
+ throw new Error("Could not build directory tree.");
176
+ }
177
+ await (0, utils_1.promiseAllSettledChunked)(dir.files.map(async (file) => {
178
+ try {
179
+ const decrypted = await this.sync.sdk.crypto().decrypt().fileMetadata({ metadata: file[5] });
180
+ if (decrypted.name.length === 0) {
181
+ return;
182
+ }
183
+ const parentPath = folderNames[file[4]];
184
+ const filePath = `${parentPath}/${decrypted.name}`;
185
+ const localPath = path_1.default.join(this.sync.syncPair.localPath, filePath);
186
+ if (filePath.startsWith(constants_1.LOCAL_TRASH_NAME) || decrypted.name.startsWith(constants_1.LOCAL_TRASH_NAME)) {
187
+ return;
188
+ }
189
+ if (decrypted.size <= 0) {
190
+ ignored.push({
191
+ localPath,
192
+ relativePath: filePath,
193
+ reason: "empty"
194
+ });
195
+ return;
196
+ }
197
+ if (this.sync.remoteIgnorer.ignores(filePath)) {
198
+ ignored.push({
199
+ localPath,
200
+ relativePath: filePath,
201
+ reason: "remoteIgnore"
202
+ });
203
+ return;
204
+ }
205
+ if (this.sync.excludeDotFiles && (0, utils_1.pathIncludesDotFile)(filePath)) {
206
+ ignored.push({
207
+ localPath,
208
+ relativePath: filePath,
209
+ reason: "dotFile"
210
+ });
211
+ return;
212
+ }
213
+ if ((0, utils_1.isPathOverMaxLength)(localPath)) {
214
+ ignored.push({
215
+ localPath,
216
+ relativePath: filePath,
217
+ reason: "pathLength"
218
+ });
219
+ return;
220
+ }
221
+ if ((0, utils_1.isNameOverMaxLength)(decrypted.name)) {
222
+ ignored.push({
223
+ localPath,
224
+ relativePath: filePath,
225
+ reason: "nameLength"
226
+ });
227
+ return;
228
+ }
229
+ if (!(0, utils_1.isValidPath)(localPath)) {
230
+ ignored.push({
231
+ localPath,
232
+ relativePath: filePath,
233
+ reason: "invalidPath"
234
+ });
235
+ return;
236
+ }
237
+ if ((0, utils_1.isDirectoryPathIgnoredByDefault)(filePath) || (0, utils_1.isRelativePathIgnoredByDefault)(filePath)) {
238
+ ignored.push({
239
+ localPath,
240
+ relativePath: filePath,
241
+ reason: "defaultIgnore"
242
+ });
243
+ return;
244
+ }
245
+ const lowercasePath = filePath.toLowerCase();
246
+ if (pathsAdded[lowercasePath]) {
247
+ return;
248
+ }
249
+ pathsAdded[lowercasePath] = true;
250
+ const item = {
251
+ type: "file",
252
+ uuid: file[0],
253
+ name: decrypted.name,
254
+ size: decrypted.size,
255
+ mime: decrypted.mime,
256
+ lastModified: (0, utils_1.convertTimestampToMs)(decrypted.lastModified),
257
+ version: file[6],
258
+ chunks: file[3],
259
+ key: decrypted.key,
260
+ bucket: file[1],
261
+ region: file[2],
262
+ creation: decrypted.creation,
263
+ hash: decrypted.hash,
264
+ path: filePath
265
+ };
266
+ tree[filePath] = item;
267
+ uuids[item.uuid] = item;
268
+ }
269
+ catch (e) {
270
+ this.sync.worker.logger.log("error", e, "filesystems.remote.getDirectoryTree");
271
+ }
272
+ }));
273
+ this.previousTreeRawResponse = rawEx.length === 2 ? (_a = rawEx[0]) !== null && _a !== void 0 ? _a : "" : "";
38
274
  this.getDirectoryTreeCache = {
39
275
  timestamp: Date.now(),
40
276
  tree,
41
277
  uuids
42
278
  };
43
- return { tree, uuids };
279
+ return {
280
+ result: {
281
+ tree: this.getDirectoryTreeCache.tree,
282
+ uuids: this.getDirectoryTreeCache.uuids
283
+ },
284
+ ignored,
285
+ changed: true
286
+ };
44
287
  }
45
288
  /**
46
289
  * Find the corresponding UUID of the relative path.
@@ -90,6 +333,7 @@ class RemoteFileSystem {
90
333
  const basename = path_1.default.posix.basename(relativePath);
91
334
  if (parentPath === "/" || parentPath === "." || parentPath.length <= 0) {
92
335
  const uuid = await this.sync.sdk.cloud().createDirectory({ name: basename, parent: this.sync.syncPair.remoteParentUUID });
336
+ await this.itemsMutex.acquire();
93
337
  this.getDirectoryTreeCache.tree[relativePath] = {
94
338
  type: "directory",
95
339
  uuid,
@@ -97,6 +341,14 @@ class RemoteFileSystem {
97
341
  size: 0,
98
342
  path: relativePath
99
343
  };
344
+ this.getDirectoryTreeCache.uuids[uuid] = {
345
+ type: "directory",
346
+ uuid,
347
+ name: basename,
348
+ size: 0,
349
+ path: relativePath
350
+ };
351
+ this.itemsMutex.release();
100
352
  return uuid;
101
353
  }
102
354
  const pathEx = relativePath.split("/");
@@ -116,6 +368,7 @@ class RemoteFileSystem {
116
368
  const parentIsBase = partParentPath === "/" || partParentPath === "." || partParentPath === "";
117
369
  const parentUUID = parentIsBase ? this.sync.syncPair.remoteParentUUID : parentItem.uuid;
118
370
  const uuid = await this.sync.sdk.cloud().createDirectory({ name: partBasename, parent: parentUUID });
371
+ await this.itemsMutex.acquire();
119
372
  this.getDirectoryTreeCache.tree[relativePath] = {
120
373
  type: "directory",
121
374
  uuid,
@@ -123,6 +376,14 @@ class RemoteFileSystem {
123
376
  size: 0,
124
377
  path: relativePath
125
378
  };
379
+ this.getDirectoryTreeCache.uuids[uuid] = {
380
+ type: "directory",
381
+ uuid,
382
+ name: partBasename,
383
+ size: 0,
384
+ path: relativePath
385
+ };
386
+ this.itemsMutex.release();
126
387
  }
127
388
  }
128
389
  if (!this.getDirectoryTreeCache.tree[relativePath]) {
@@ -147,17 +408,37 @@ class RemoteFileSystem {
147
408
  * @returns {Promise<void>}
148
409
  */
149
410
  async unlink({ relativePath, type, permanent = false }) {
411
+ let uuid = null;
412
+ const cleanItemEntry = async () => {
413
+ if (!uuid) {
414
+ return;
415
+ }
416
+ await this.itemsMutex.acquire();
417
+ delete this.getDirectoryTreeCache.tree[relativePath];
418
+ delete this.getDirectoryTreeCache.uuids[uuid];
419
+ for (const entry in this.getDirectoryTreeCache.tree) {
420
+ if (entry.startsWith(relativePath + "/") || entry === relativePath) {
421
+ const entryItem = this.getDirectoryTreeCache.tree[entry];
422
+ if (entryItem) {
423
+ delete this.getDirectoryTreeCache.uuids[entryItem.uuid];
424
+ }
425
+ delete this.getDirectoryTreeCache.tree[entry];
426
+ }
427
+ }
428
+ this.itemsMutex.release();
429
+ };
150
430
  await this.mutex.acquire();
151
431
  try {
152
- const uuid = await this.pathToItemUUID({ relativePath });
153
- if (!uuid || !this.getDirectoryTreeCache.tree[relativePath]) {
432
+ uuid = await this.pathToItemUUID({ relativePath });
433
+ const item = this.getDirectoryTreeCache.tree[relativePath];
434
+ if (!uuid || !item) {
154
435
  return;
155
436
  }
156
437
  const acceptedTypes = !type ? ["directory", "file"] : type === "directory" ? ["directory"] : ["file"];
157
- if (!acceptedTypes.includes(this.getDirectoryTreeCache.tree[relativePath].type)) {
438
+ if (!acceptedTypes.includes(item.type)) {
158
439
  return;
159
440
  }
160
- if (this.getDirectoryTreeCache.tree[relativePath].type === "directory") {
441
+ if (item.type === "directory") {
161
442
  if (permanent) {
162
443
  await this.sync.sdk.cloud().deleteDirectory({ uuid });
163
444
  }
@@ -173,11 +454,14 @@ class RemoteFileSystem {
173
454
  await this.sync.sdk.cloud().trashFile({ uuid });
174
455
  }
175
456
  }
176
- delete this.getDirectoryTreeCache.tree[relativePath];
177
- for (const entry in this.getDirectoryTreeCache.tree) {
178
- if (entry.startsWith(relativePath + "/")) {
179
- delete this.getDirectoryTreeCache.tree[entry];
180
- }
457
+ await cleanItemEntry();
458
+ }
459
+ catch (e) {
460
+ if (e instanceof sdk_1.APIError && (e.code === "file_not_found" || e.code === "folder_not_found")) {
461
+ await cleanItemEntry();
462
+ }
463
+ else {
464
+ throw e;
181
465
  }
182
466
  }
183
467
  finally {
@@ -185,7 +469,7 @@ class RemoteFileSystem {
185
469
  }
186
470
  }
187
471
  /**
188
- * Rename a file/directory inside the remote sync path. Recursively creates intermediate directories if needed.
472
+ * Rename/Move a file/directory inside the remote sync path. Recursively creates intermediate directories if needed.
189
473
  * @date 3/2/2024 - 9:35:12 PM
190
474
  *
191
475
  * @public
@@ -199,7 +483,7 @@ class RemoteFileSystem {
199
483
  await this.mutex.acquire();
200
484
  try {
201
485
  if (fromRelativePath === "/" || fromRelativePath === toRelativePath) {
202
- return;
486
+ throw new Error("Invalid paths.");
203
487
  }
204
488
  const uuid = await this.pathToItemUUID({ relativePath: fromRelativePath });
205
489
  const item = this.getDirectoryTreeCache.tree[fromRelativePath];
@@ -225,10 +509,13 @@ class RemoteFileSystem {
225
509
  });
226
510
  if (newParentPath === currentParentPath) {
227
511
  if (toRelativePath === "/" || newBasename.length <= 0) {
228
- return;
512
+ throw new Error("Invalid paths.");
229
513
  }
230
514
  if (item.type === "directory") {
231
- await this.sync.sdk.cloud().renameDirectory({ uuid, name: newBasename });
515
+ await this.sync.sdk.cloud().renameDirectory({
516
+ uuid,
517
+ name: newBasename
518
+ });
232
519
  }
233
520
  else {
234
521
  await this.sync.sdk.cloud().renameFile({
@@ -237,16 +524,17 @@ class RemoteFileSystem {
237
524
  name: newBasename
238
525
  });
239
526
  }
240
- const oldItem = this.getDirectoryTreeCache.tree[fromRelativePath];
241
- if (oldItem) {
242
- this.getDirectoryTreeCache.tree[toRelativePath] = Object.assign(Object.assign({}, oldItem), { name: newBasename });
243
- }
244
- delete this.getDirectoryTreeCache.tree[fromRelativePath];
245
527
  }
246
528
  else {
529
+ if (toRelativePath.startsWith(fromRelativePath)) {
530
+ throw new Error("Invalid paths.");
531
+ }
247
532
  if (oldBasename !== newBasename) {
248
533
  if (item.type === "directory") {
249
- await this.sync.sdk.cloud().renameDirectory({ uuid, name: newBasename });
534
+ await this.sync.sdk.cloud().renameDirectory({
535
+ uuid,
536
+ name: newBasename
537
+ });
250
538
  }
251
539
  else {
252
540
  await this.sync.sdk.cloud().renameFile({
@@ -258,14 +546,18 @@ class RemoteFileSystem {
258
546
  }
259
547
  if (newParentPath === "/" || newParentPath === "." || newParentPath === "") {
260
548
  if (item.type === "directory") {
261
- await this.sync.sdk
262
- .cloud()
263
- .moveDirectory({ uuid, to: this.sync.syncPair.remoteParentUUID, metadata: itemMetadata });
549
+ await this.sync.sdk.cloud().moveDirectory({
550
+ uuid,
551
+ to: this.sync.syncPair.remoteParentUUID,
552
+ metadata: itemMetadata
553
+ });
264
554
  }
265
555
  else {
266
- await this.sync.sdk
267
- .cloud()
268
- .moveFile({ uuid, to: this.sync.syncPair.remoteParentUUID, metadata: itemMetadata });
556
+ await this.sync.sdk.cloud().moveFile({
557
+ uuid,
558
+ to: this.sync.syncPair.remoteParentUUID,
559
+ metadata: itemMetadata
560
+ });
269
561
  }
270
562
  }
271
563
  else {
@@ -275,30 +567,40 @@ class RemoteFileSystem {
275
567
  throw new Error(`Could not find path ${newParentPath}.`);
276
568
  }
277
569
  if (item.type === "directory") {
278
- await this.sync.sdk
279
- .cloud()
280
- .moveDirectory({ uuid, to: newParentItem.uuid, metadata: itemMetadata });
570
+ await this.sync.sdk.cloud().moveDirectory({
571
+ uuid,
572
+ to: newParentItem.uuid,
573
+ metadata: itemMetadata
574
+ });
281
575
  }
282
576
  else {
283
- await this.sync.sdk.cloud().moveFile({ uuid, to: newParentItem.uuid, metadata: itemMetadata });
577
+ await this.sync.sdk.cloud().moveFile({
578
+ uuid,
579
+ to: newParentItem.uuid,
580
+ metadata: itemMetadata
581
+ });
284
582
  }
285
583
  }
286
- const oldItem = this.getDirectoryTreeCache.tree[fromRelativePath];
287
- if (oldItem) {
288
- this.getDirectoryTreeCache.tree[toRelativePath] = Object.assign(Object.assign({}, oldItem), { name: newBasename });
289
- }
290
- delete this.getDirectoryTreeCache.tree[fromRelativePath];
291
- for (const oldPath in this.getDirectoryTreeCache.tree) {
292
- if (oldPath.startsWith(fromRelativePath + "/")) {
293
- const newPath = oldPath.split(fromRelativePath).join(toRelativePath);
294
- const oldItem = this.getDirectoryTreeCache.tree[oldPath];
295
- if (oldItem) {
296
- this.getDirectoryTreeCache.tree[newPath] = Object.assign(Object.assign({}, oldItem), { name: newBasename });
297
- }
584
+ }
585
+ await this.itemsMutex.acquire();
586
+ this.getDirectoryTreeCache.tree[toRelativePath] = Object.assign(Object.assign({}, item), { name: path_1.default.basename(toRelativePath), path: toRelativePath });
587
+ this.getDirectoryTreeCache.uuids[item.uuid] = Object.assign(Object.assign({}, item), { name: path_1.default.basename(toRelativePath), path: toRelativePath });
588
+ delete this.getDirectoryTreeCache.tree[fromRelativePath];
589
+ for (const oldPath in this.getDirectoryTreeCache.tree) {
590
+ if (oldPath.startsWith(fromRelativePath + "/") && oldPath !== fromRelativePath) {
591
+ const newPath = (0, utils_1.replacePathStartWithFromAndTo)(oldPath, fromRelativePath, toRelativePath);
592
+ const oldItem = this.getDirectoryTreeCache.tree[oldPath];
593
+ if (oldItem) {
594
+ this.getDirectoryTreeCache.tree[newPath] = Object.assign(Object.assign({}, oldItem), { name: path_1.default.basename(newPath), path: newPath });
298
595
  delete this.getDirectoryTreeCache.tree[oldPath];
596
+ const oldItemUUID = this.getDirectoryTreeCache.uuids[oldItem.uuid];
597
+ if (oldItemUUID) {
598
+ this.getDirectoryTreeCache.uuids[oldItem.uuid] = Object.assign(Object.assign({}, oldItemUUID), { name: path_1.default.basename(newPath), path: newPath });
599
+ }
299
600
  }
300
601
  }
301
602
  }
603
+ this.itemsMutex.release();
302
604
  }
303
605
  finally {
304
606
  this.mutex.release();
@@ -315,7 +617,10 @@ class RemoteFileSystem {
315
617
  * @returns {Promise<fs.Stats>}
316
618
  */
317
619
  async download({ relativePath }) {
620
+ var _a;
318
621
  const localPath = path_1.default.posix.join(this.sync.syncPair.localPath, relativePath);
622
+ const tmpLocalPath = path_1.default.join(this.sync.syncPair.localPath, constants_1.LOCAL_TRASH_NAME, (0, uuid_1.v4)());
623
+ const signalKey = `upload:${relativePath}`;
319
624
  const uuid = await this.pathToItemUUID({ relativePath });
320
625
  const item = this.getDirectoryTreeCache.tree[relativePath];
321
626
  if (!uuid || !item) {
@@ -324,19 +629,178 @@ class RemoteFileSystem {
324
629
  if (item.type === "directory") {
325
630
  throw new Error(`Could not download ${relativePath}: Not a file.`);
326
631
  }
327
- const tmpPath = await this.sync.sdk.cloud().downloadFileToLocal({
328
- uuid,
329
- bucket: item.bucket,
330
- region: item.region,
331
- chunks: item.chunks,
332
- version: item.version,
333
- key: item.key
334
- });
335
- await fs_extra_1.default.move(tmpPath, localPath, {
336
- overwrite: true
632
+ if (!this.sync.pauseSignals[signalKey]) {
633
+ this.sync.pauseSignals[signalKey] = new sdk_1.PauseSignal();
634
+ }
635
+ if (!this.sync.abortControllers[signalKey]) {
636
+ this.sync.abortControllers[signalKey] = new AbortController();
637
+ }
638
+ (0, ipc_1.postMessageToMain)({
639
+ type: "transfer",
640
+ syncPair: this.sync.syncPair,
641
+ data: {
642
+ of: "download",
643
+ type: "queued",
644
+ relativePath,
645
+ localPath,
646
+ size: item.size
647
+ }
337
648
  });
338
- await fs_extra_1.default.utimes(localPath, Date.now(), item.lastModified);
339
- return await fs_extra_1.default.stat(localPath);
649
+ try {
650
+ await this.sync.sdk.cloud().downloadFileToLocal({
651
+ uuid,
652
+ bucket: item.bucket,
653
+ region: item.region,
654
+ chunks: item.chunks,
655
+ version: item.version,
656
+ to: tmpLocalPath,
657
+ key: item.key,
658
+ size: item.size,
659
+ pauseSignal: this.sync.pauseSignals[signalKey],
660
+ abortSignal: (_a = this.sync.abortControllers[signalKey]) === null || _a === void 0 ? void 0 : _a.signal,
661
+ onError: err => {
662
+ this.sync.worker.logger.log("error", err, "filesystems.remote.download");
663
+ (0, ipc_1.postMessageToMain)({
664
+ type: "transfer",
665
+ syncPair: this.sync.syncPair,
666
+ data: {
667
+ of: "download",
668
+ type: "error",
669
+ relativePath,
670
+ localPath,
671
+ error: (0, utils_1.serializeError)(err),
672
+ size: item.size
673
+ }
674
+ });
675
+ },
676
+ onProgress: bytes => {
677
+ (0, ipc_1.postMessageToMain)({
678
+ type: "transfer",
679
+ syncPair: this.sync.syncPair,
680
+ data: {
681
+ of: "download",
682
+ type: "progress",
683
+ relativePath,
684
+ localPath,
685
+ bytes,
686
+ size: item.size
687
+ }
688
+ });
689
+ },
690
+ onStarted: () => {
691
+ (0, ipc_1.postMessageToMain)({
692
+ type: "transfer",
693
+ syncPair: this.sync.syncPair,
694
+ data: {
695
+ of: "download",
696
+ type: "started",
697
+ relativePath,
698
+ localPath,
699
+ size: item.size
700
+ }
701
+ });
702
+ }
703
+ });
704
+ await fs_extra_1.default.utimes(tmpLocalPath, new Date(), new Date((0, utils_1.convertTimestampToMs)(item.lastModified)));
705
+ await fs_extra_1.default.move(tmpLocalPath, localPath, {
706
+ overwrite: true
707
+ });
708
+ const stats = await fs_extra_1.default.stat(localPath);
709
+ const localItem = {
710
+ type: "file",
711
+ inode: parseInt(stats.ino), // Sometimes comes as a float, but we need an int
712
+ lastModified: parseInt(stats.mtimeMs), // Sometimes comes as a float, but we need an int
713
+ creation: parseInt(stats.birthtimeMs), // Sometimes comes as a float, but we need an int
714
+ size: parseInt(stats.size), // Sometimes comes as a float, but we need an int
715
+ path: relativePath
716
+ };
717
+ await this.sync.localFileSystem.itemsMutex.acquire();
718
+ this.sync.localFileSystem.getDirectoryTreeCache.tree[relativePath] = localItem;
719
+ this.sync.localFileSystem.getDirectoryTreeCache.inodes[localItem.inode] = localItem;
720
+ this.sync.localFileSystem.itemsMutex.release();
721
+ (0, ipc_1.postMessageToMain)({
722
+ type: "transfer",
723
+ syncPair: this.sync.syncPair,
724
+ data: {
725
+ of: "download",
726
+ type: "finished",
727
+ relativePath,
728
+ localPath,
729
+ size: item.size
730
+ }
731
+ });
732
+ return stats;
733
+ }
734
+ catch (e) {
735
+ this.sync.worker.logger.log("error", e, "filesystems.remote.download");
736
+ if (e instanceof Error) {
737
+ (0, ipc_1.postMessageToMain)({
738
+ type: "transfer",
739
+ syncPair: this.sync.syncPair,
740
+ data: {
741
+ of: "download",
742
+ type: "error",
743
+ relativePath,
744
+ localPath,
745
+ error: (0, utils_1.serializeError)(e),
746
+ size: item.size
747
+ }
748
+ });
749
+ }
750
+ throw e;
751
+ }
752
+ finally {
753
+ delete this.sync.pauseSignals[signalKey];
754
+ delete this.sync.abortControllers[signalKey];
755
+ }
756
+ }
757
+ async remoteDirPathExisting() {
758
+ try {
759
+ const present = await this.sync.sdk.api(3).dir().present({ uuid: this.sync.syncPair.remoteParentUUID });
760
+ if (!present.present || present.trash) {
761
+ return false;
762
+ }
763
+ return true;
764
+ }
765
+ catch (_a) {
766
+ return false;
767
+ }
768
+ }
769
+ async directoryExists(relativePath) {
770
+ try {
771
+ const item = this.getDirectoryTreeCache.tree[relativePath];
772
+ if (!item) {
773
+ return false;
774
+ }
775
+ const present = await this.sync.sdk.api(3).dir().present({ uuid: item.uuid });
776
+ if (!present.present || present.trash) {
777
+ return false;
778
+ }
779
+ return true;
780
+ }
781
+ catch (_a) {
782
+ return false;
783
+ }
784
+ }
785
+ async fileExists(relativePath) {
786
+ try {
787
+ const item = this.getDirectoryTreeCache.tree[relativePath];
788
+ const parent = this.getDirectoryTreeCache.tree[path_1.default.posix.dirname(relativePath)];
789
+ if (!item || !parent) {
790
+ return false;
791
+ }
792
+ const { exists, existsUUID } = await this.sync.sdk.api(3).file().exists({
793
+ name: item.name,
794
+ parent: parent.uuid
795
+ });
796
+ if (!exists || existsUUID !== item.uuid) {
797
+ return false;
798
+ }
799
+ return true;
800
+ }
801
+ catch (_a) {
802
+ return false;
803
+ }
340
804
  }
341
805
  }
342
806
  exports.RemoteFileSystem = RemoteFileSystem;