core-3nweb-client-lib 0.25.6 → 0.27.0

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 (215) hide show
  1. package/README.md +2 -2
  2. package/build/api-defs/asmail.d.ts +1 -1
  3. package/build/api-defs/files.d.ts +282 -70
  4. package/build/core/app-files.js +7 -7
  5. package/build/core/asmail/config/common.js +4 -4
  6. package/build/core/asmail/config/index.js +2 -2
  7. package/build/core/asmail/config/published-intro-key.js +1 -1
  8. package/build/core/asmail/delivery/common.js +7 -7
  9. package/build/core/asmail/delivery/index.js +7 -6
  10. package/build/core/asmail/delivery/msg.js +8 -7
  11. package/build/core/asmail/delivery/per-recipient-wip.js +3 -3
  12. package/build/core/asmail/inbox/attachments/fs.js +11 -1
  13. package/build/core/asmail/inbox/cached-msgs.js +3 -3
  14. package/build/core/asmail/inbox/inbox-events.js +5 -4
  15. package/build/core/asmail/inbox/index.js +12 -12
  16. package/build/core/asmail/inbox/msg-downloader.js +3 -3
  17. package/build/core/asmail/inbox/msg-indexing.js +4 -4
  18. package/build/core/asmail/inbox/msg-on-disk.js +7 -7
  19. package/build/core/asmail/index.d.ts +3 -3
  20. package/build/core/asmail/index.js +13 -8
  21. package/build/core/asmail/key-verification.js +5 -5
  22. package/build/core/asmail/keyring/common.js +7 -6
  23. package/build/core/asmail/keyring/correspondent-keys.js +8 -7
  24. package/build/core/asmail/keyring/id-to-email-map.js +2 -1
  25. package/build/core/asmail/keyring/index.d.ts +7 -8
  26. package/build/core/asmail/keyring/index.js +15 -14
  27. package/build/core/asmail/keyring/keyring-storage.js +4 -3
  28. package/build/core/asmail/msg/opener.js +3 -3
  29. package/build/core/asmail/msg/packer.js +13 -13
  30. package/build/core/asmail/sending-params/own-params.js +4 -4
  31. package/build/core/asmail/sending-params/params-from-others.js +3 -3
  32. package/build/core/id-manager.js +8 -5
  33. package/build/core/index.d.ts +2 -1
  34. package/build/core/index.js +14 -14
  35. package/build/core/sign-in.d.ts +5 -4
  36. package/build/core/sign-in.js +12 -14
  37. package/build/core/sign-up.d.ts +1 -0
  38. package/build/core/sign-up.js +15 -11
  39. package/build/core/storage/common/json-saving.d.ts +21 -0
  40. package/build/core/storage/common/json-saving.js +82 -0
  41. package/build/core/storage/common/obj-info-file.d.ts +51 -0
  42. package/build/core/storage/common/obj-info-file.js +153 -5
  43. package/build/core/storage/common/utils.d.ts +2 -0
  44. package/build/core/storage/common/utils.js +32 -0
  45. package/build/core/storage/index.d.ts +3 -17
  46. package/build/core/storage/index.js +57 -77
  47. package/build/core/storage/local/obj-files-gc.d.ts +2 -0
  48. package/build/core/storage/local/obj-files-gc.js +53 -39
  49. package/build/core/storage/local/obj-files.d.ts +6 -9
  50. package/build/core/storage/local/obj-files.js +16 -19
  51. package/build/core/storage/local/obj-status.d.ts +20 -30
  52. package/build/core/storage/local/obj-status.js +46 -113
  53. package/build/core/storage/local/storage.d.ts +15 -5
  54. package/build/core/storage/local/storage.js +37 -18
  55. package/build/core/storage/synced/downloader.js +7 -6
  56. package/build/core/storage/synced/obj-files-gc.d.ts +6 -1
  57. package/build/core/storage/synced/obj-files-gc.js +106 -13
  58. package/build/core/storage/synced/obj-files.d.ts +46 -47
  59. package/build/core/storage/synced/obj-files.js +207 -154
  60. package/build/core/storage/synced/obj-status.d.ts +103 -42
  61. package/build/core/storage/synced/obj-status.js +525 -137
  62. package/build/core/storage/synced/remote-events.d.ts +11 -12
  63. package/build/core/storage/synced/remote-events.js +80 -57
  64. package/build/core/storage/synced/storage.d.ts +24 -5
  65. package/build/core/storage/synced/storage.js +123 -38
  66. package/build/core/storage/synced/upload-header-file.d.ts +4 -0
  67. package/build/core/storage/synced/upload-header-file.js +64 -0
  68. package/build/core/storage/synced/upsyncer.d.ts +15 -9
  69. package/build/core/storage/synced/upsyncer.js +219 -246
  70. package/build/core/storage/system-folders/apps-data.d.ts +16 -0
  71. package/build/core/storage/system-folders/apps-data.js +110 -0
  72. package/build/core/storage/system-folders/index.d.ts +18 -0
  73. package/build/core/storage/system-folders/index.js +77 -0
  74. package/build/core-ipc/common-caps.js +3 -3
  75. package/build/core-ipc/generic.js +8 -8
  76. package/build/core-ipc/startup-caps.js +2 -2
  77. package/build/cryptors.js +6 -2
  78. package/build/ipc-via-protobuf/asmail-cap.js +67 -83
  79. package/build/ipc-via-protobuf/bytes.js +16 -17
  80. package/build/ipc-via-protobuf/connector-clients-side.d.ts +3 -0
  81. package/build/ipc-via-protobuf/connector-clients-side.js +62 -25
  82. package/build/ipc-via-protobuf/connector-services-side.js +10 -10
  83. package/build/ipc-via-protobuf/connector.js +4 -4
  84. package/build/ipc-via-protobuf/file.d.ts +48 -12
  85. package/build/ipc-via-protobuf/file.js +476 -120
  86. package/build/ipc-via-protobuf/fs.d.ts +8 -0
  87. package/build/ipc-via-protobuf/fs.js +592 -159
  88. package/build/ipc-via-protobuf/log-cap.js +2 -2
  89. package/build/ipc-via-protobuf/mailerid.js +3 -3
  90. package/build/ipc-via-protobuf/protobuf-msg.d.ts +1 -0
  91. package/build/ipc-via-protobuf/protobuf-msg.js +11 -7
  92. package/build/ipc-via-protobuf/startup-cap.js +23 -23
  93. package/build/ipc-via-protobuf/storage-cap.js +12 -12
  94. package/build/ipc.js +7 -2
  95. package/build/lib-client/3nstorage/exceptions.d.ts +12 -8
  96. package/build/lib-client/3nstorage/exceptions.js +31 -10
  97. package/build/lib-client/3nstorage/service.d.ts +16 -2
  98. package/build/lib-client/3nstorage/service.js +109 -39
  99. package/build/lib-client/3nstorage/util/file-based-json.d.ts +2 -1
  100. package/build/lib-client/3nstorage/util/file-based-json.js +1 -1
  101. package/build/lib-client/3nstorage/xsp-fs/attrs.js +17 -17
  102. package/build/lib-client/3nstorage/xsp-fs/common.d.ts +52 -14
  103. package/build/lib-client/3nstorage/xsp-fs/common.js +31 -16
  104. package/build/lib-client/3nstorage/xsp-fs/file-node.d.ts +1 -0
  105. package/build/lib-client/3nstorage/xsp-fs/file-node.js +18 -14
  106. package/build/lib-client/3nstorage/xsp-fs/file.d.ts +31 -6
  107. package/build/lib-client/3nstorage/xsp-fs/file.js +74 -23
  108. package/build/lib-client/3nstorage/xsp-fs/folder-node-serialization.js +4 -4
  109. package/build/lib-client/3nstorage/xsp-fs/folder-node.d.ts +24 -11
  110. package/build/lib-client/3nstorage/xsp-fs/folder-node.js +599 -189
  111. package/build/lib-client/3nstorage/xsp-fs/fs.d.ts +45 -9
  112. package/build/lib-client/3nstorage/xsp-fs/fs.js +326 -74
  113. package/build/lib-client/3nstorage/xsp-fs/link-node.d.ts +1 -0
  114. package/build/lib-client/3nstorage/xsp-fs/link-node.js +7 -2
  115. package/build/lib-client/3nstorage/xsp-fs/node-in-fs.d.ts +30 -20
  116. package/build/lib-client/3nstorage/xsp-fs/node-in-fs.js +239 -106
  117. package/build/lib-client/3nstorage/xsp-fs/node-persistence.d.ts +1 -1
  118. package/build/lib-client/3nstorage/xsp-fs/node-persistence.js +18 -19
  119. package/build/lib-client/3nstorage/xsp-fs/xsp-payload-v1.js +5 -5
  120. package/build/lib-client/3nstorage/xsp-fs/xsp-payload-v2.js +56 -56
  121. package/build/lib-client/3nweb-signup.js +4 -4
  122. package/build/lib-client/asmail/recipient.js +15 -15
  123. package/build/lib-client/asmail/sender.js +22 -22
  124. package/build/lib-client/asmail/service-config.js +3 -3
  125. package/build/lib-client/cryptor/cryptor-in-worker.js +19 -17
  126. package/build/lib-client/cryptor/cryptor-wasm.js +1 -1
  127. package/build/lib-client/cryptor/cryptor.js +4 -2
  128. package/build/lib-client/cryptor/cryptor.wasm +0 -0
  129. package/build/lib-client/cryptor/in-proc-js.js +1 -1
  130. package/build/lib-client/cryptor/in-proc-wasm.js +7 -7
  131. package/build/lib-client/cryptor/worker-js.js +2 -2
  132. package/build/lib-client/cryptor/worker-wasm.js +2 -2
  133. package/build/lib-client/files-select.js +1 -1
  134. package/build/lib-client/files.d.ts +1 -1
  135. package/build/lib-client/files.js +71 -4
  136. package/build/lib-client/fs-collection.js +3 -2
  137. package/build/lib-client/fs-sync-utils.d.ts +5 -0
  138. package/build/lib-client/fs-sync-utils.js +61 -0
  139. package/build/lib-client/fs-view.d.ts +14 -0
  140. package/build/lib-client/fs-view.js +33 -0
  141. package/build/lib-client/key-derivation.js +1 -1
  142. package/build/lib-client/local-files/dev-file-sink.js +9 -9
  143. package/build/lib-client/local-files/dev-file-src.js +2 -2
  144. package/build/lib-client/local-files/device-fs.d.ts +1 -1
  145. package/build/lib-client/local-files/device-fs.js +56 -54
  146. package/build/lib-client/logging/log-to-file.d.ts +1 -1
  147. package/build/lib-client/logging/log-to-file.js +11 -11
  148. package/build/lib-client/mailer-id/login.js +7 -7
  149. package/build/lib-client/mailer-id/provisioner.js +12 -12
  150. package/build/lib-client/objs-on-disk/file-writing-proc.js +5 -5
  151. package/build/lib-client/objs-on-disk/obj-folders.js +33 -33
  152. package/build/lib-client/objs-on-disk/obj-on-disk.d.ts +13 -2
  153. package/build/lib-client/objs-on-disk/obj-on-disk.js +24 -9
  154. package/build/lib-client/request-utils.d.ts +1 -0
  155. package/build/lib-client/request-utils.js +14 -14
  156. package/build/lib-client/server-events.d.ts +3 -3
  157. package/build/lib-client/server-events.js +12 -10
  158. package/build/lib-client/service-locator.js +10 -10
  159. package/build/lib-client/user-with-mid-session.js +7 -7
  160. package/build/lib-client/user-with-pkl-session.js +25 -25
  161. package/build/lib-client/ws-utils.js +3 -3
  162. package/build/lib-common/async-cryptor-wrap.js +4 -4
  163. package/build/lib-common/async-fs-node.d.ts +5 -3
  164. package/build/lib-common/async-fs-node.js +19 -18
  165. package/build/lib-common/byte-streaming/pipe.js +1 -1
  166. package/build/lib-common/byte-streaming/wrapping.js +17 -17
  167. package/build/lib-common/canonical-address.js +1 -1
  168. package/build/lib-common/exceptions/error.d.ts +1 -0
  169. package/build/lib-common/exceptions/error.js +7 -6
  170. package/build/lib-common/exceptions/file.js +10 -1
  171. package/build/lib-common/ipc/generic-ipc.js +2 -2
  172. package/build/lib-common/ipc/ws-ipc.js +2 -2
  173. package/build/lib-common/json-utils.js +2 -1
  174. package/build/lib-common/mid-sigs-NaCl-Ed.js +14 -14
  175. package/build/lib-common/objs-on-disk/file-layout.d.ts +19 -0
  176. package/build/lib-common/objs-on-disk/file-layout.js +130 -12
  177. package/build/lib-common/objs-on-disk/obj-file.d.ts +13 -2
  178. package/build/lib-common/objs-on-disk/obj-file.js +99 -37
  179. package/build/lib-common/objs-on-disk/utils.d.ts +1 -0
  180. package/build/lib-common/objs-on-disk/utils.js +4 -4
  181. package/build/lib-common/objs-on-disk/v1-obj-file-format.js +14 -14
  182. package/build/lib-common/processes/deferred.d.ts +6 -0
  183. package/build/lib-common/processes/deferred.js +30 -0
  184. package/build/lib-common/processes/labelled-exec-pools.d.ts +33 -0
  185. package/build/lib-common/processes/labelled-exec-pools.js +141 -0
  186. package/build/lib-common/processes/pressure.d.ts +7 -0
  187. package/build/lib-common/processes/pressure.js +56 -0
  188. package/build/lib-common/processes/sleep.d.ts +1 -0
  189. package/build/lib-common/processes/sleep.js +26 -0
  190. package/build/lib-common/{processes.d.ts → processes/synced.d.ts} +0 -40
  191. package/build/lib-common/{processes.js → processes/synced.js} +187 -204
  192. package/build/lib-common/processes/timeout.d.ts +1 -0
  193. package/build/lib-common/processes/timeout.js +51 -0
  194. package/build/lib-common/random-node.js +7 -7
  195. package/build/lib-common/service-api/3nstorage/owner.d.ts +100 -39
  196. package/build/lib-common/service-api/3nstorage/owner.js +85 -42
  197. package/build/lib-common/service-api/asmail/delivery.js +2 -2
  198. package/build/lib-common/service-api/asmail/retrieval.js +1 -1
  199. package/build/lib-common/timed-cache.d.ts +1 -0
  200. package/build/lib-common/timed-non-weak-cache.d.ts +1 -0
  201. package/build/lib-common/timed-non-weak-cache.js +11 -0
  202. package/build/lib-common/utils-for-observables.d.ts +15 -1
  203. package/build/lib-common/utils-for-observables.js +70 -19
  204. package/build/lib-common/weak-cache.d.ts +1 -0
  205. package/build/lib-common/weak-cache.js +12 -1
  206. package/build/lib-index.d.ts +2 -1
  207. package/build/lib-index.js +10 -7
  208. package/build/protos/asmail.proto.js +12912 -7127
  209. package/build/protos/file.proto.js +4848 -2399
  210. package/build/protos/fs.proto.js +9230 -3445
  211. package/package.json +8 -7
  212. package/protos/file.proto +91 -11
  213. package/protos/fs.proto +107 -8
  214. package/build/core/storage/synced/upsync-status.d.ts +0 -41
  215. package/build/core/storage/synced/upsync-status.js +0 -158
@@ -24,19 +24,22 @@ exports.FolderNode = void 0;
24
24
  const buffer_utils_1 = require("../../../lib-common/buffer-utils");
25
25
  const file_1 = require("../../../lib-common/exceptions/file");
26
26
  const error_1 = require("../../../lib-common/exceptions/error");
27
+ const common_1 = require("./common");
27
28
  const node_in_fs_1 = require("./node-in-fs");
28
29
  const file_node_1 = require("./file-node");
29
30
  const link_node_1 = require("./link-node");
30
- const processes_1 = require("../../../lib-common/processes");
31
+ const exceptions_1 = require("../exceptions");
32
+ const deferred_1 = require("../../../lib-common/processes/deferred");
31
33
  const json_utils_1 = require("../../../lib-common/json-utils");
32
34
  const xsp_files_1 = require("xsp-files");
33
35
  const random = require("../../../lib-common/random-node");
34
36
  const folder_node_serialization_1 = require("./folder-node-serialization");
35
37
  const attrs_1 = require("./attrs");
36
38
  const node_persistence_1 = require("./node-persistence");
39
+ const assert_1 = require("../../../lib-common/assert");
37
40
  function jsonToInfoAndAttrs(json) {
38
41
  const folderInfo = {
39
- nodes: json_utils_1.copy(json.nodes)
42
+ nodes: (0, json_utils_1.copy)(json.nodes)
40
43
  };
41
44
  for (const node of Object.values(folderInfo.nodes)) {
42
45
  node.key = buffer_utils_1.base64.open(node.key);
@@ -51,34 +54,38 @@ class FolderPersistance extends node_persistence_1.NodePersistance {
51
54
  Object.seal(this);
52
55
  }
53
56
  async write(folderInfo, version, attrs, xattrs) {
54
- const bytes = folder_node_serialization_1.serializeFolderInfo(folderInfo);
57
+ const bytes = (0, folder_node_serialization_1.serializeFolderInfo)(folderInfo);
55
58
  return this.writeWhole(bytes, version, attrs, xattrs);
56
59
  }
57
60
  async read(src) {
58
61
  const { content, xattrs, attrs } = await this.readAll(src);
59
- const folderInfo = folder_node_serialization_1.parseFolderInfo(content);
62
+ const folderInfo = (0, folder_node_serialization_1.parseFolderInfo)(content);
60
63
  return { folderInfo, xattrs, attrs: attrs_1.CommonAttrs.fromAttrs(attrs) };
61
64
  }
62
65
  }
63
66
  Object.freeze(FolderPersistance.prototype);
64
67
  Object.freeze(FolderPersistance);
68
+ let nextMoveLabel = Math.floor(Math.random() / 2 * Number.MAX_SAFE_INTEGER);
69
+ function getMoveLabel() {
70
+ const label = nextMoveLabel;
71
+ if (nextMoveLabel >= Number.MAX_SAFE_INTEGER) {
72
+ nextMoveLabel = 0;
73
+ }
74
+ else {
75
+ nextMoveLabel += 1;
76
+ }
77
+ return label;
78
+ }
65
79
  class FolderNode extends node_in_fs_1.NodeInFS {
66
80
  constructor(storage, name, objId, zNonce, version, parentId, key, setNewAttrs) {
67
81
  super(storage, 'folder', name, objId, version, parentId);
68
82
  this.currentState = { nodes: {} };
69
- if (!name && (objId || parentId)) {
70
- throw new Error("Root folder must " +
71
- "have both objId and parent as nulls.");
83
+ if (name === undefined) {
84
+ (0, assert_1.assert)(!objId && !parentId, `Root folder must have both objId and parent as nulls.`);
72
85
  }
73
86
  else if (objId === null) {
74
87
  new Error("Missing objId for non-root folder");
75
88
  }
76
- if (!zNonce) {
77
- if (!objId) {
78
- throw new Error(`Missing object id for folder, when zeroth nonce is not given`);
79
- }
80
- zNonce = xsp_files_1.idToHeaderNonce(objId);
81
- }
82
89
  this.crypto = new FolderPersistance(zNonce, key, storage.cryptor);
83
90
  if (setNewAttrs) {
84
91
  this.attrs = attrs_1.CommonAttrs.makeForTimeNow();
@@ -93,20 +100,21 @@ class FolderNode extends node_in_fs_1.NodeInFS {
93
100
  return rf;
94
101
  }
95
102
  static async rootFromObjBytes(storage, name, objId, src, key) {
96
- let zNonce = undefined;
97
- if (!objId) {
98
- const header = await src.readHeader();
99
- zNonce = xsp_files_1.calculateNonce(header.subarray(0, xsp_files_1.NONCE_LENGTH), -src.version);
100
- }
101
- const rf = await FolderNode.readNodeFromObjBytes(storage, name, objId, zNonce, src, key);
103
+ const rf = await FolderNode.readNodeFromObjBytes(storage, name, objId, src, key);
102
104
  rf.storage.nodes.set(rf);
103
105
  return rf;
104
106
  }
105
- static async readNodeFromObjBytes(storage, name, objId, zNonce, src, key) {
107
+ static async readNodeFromObjBytes(storage, name, objId, src, key) {
108
+ let zNonce;
109
+ if (objId) {
110
+ zNonce = (0, xsp_files_1.idToHeaderNonce)(objId);
111
+ }
112
+ else {
113
+ const header = await src.readHeader();
114
+ zNonce = (0, xsp_files_1.calculateNonce)(header.subarray(0, xsp_files_1.NONCE_LENGTH), -src.version);
115
+ }
106
116
  const rf = new FolderNode(storage, name, objId, zNonce, src.version, undefined, key, false);
107
- const { folderInfo, attrs, xattrs } = await rf.crypto.read(src);
108
- rf.currentState = folderInfo;
109
- rf.setUpdatedParams(src.version, attrs, xattrs);
117
+ await rf.setCurrentStateFrom(src);
110
118
  return rf;
111
119
  }
112
120
  static async rootFromLinkParams(storage, params) {
@@ -121,36 +129,94 @@ class FolderNode extends node_in_fs_1.NodeInFS {
121
129
  existingNode.crypto.compareKey(params.fKey);
122
130
  return existingNode;
123
131
  }
124
- const src = await storage.getObj(params.objId);
132
+ const src = await storage.getObjSrc(params.objId);
125
133
  const key = buffer_utils_1.base64.open(params.fKey);
126
134
  return FolderNode.rootFromObjBytes(storage, params.folderName, params.objId, src, key);
127
135
  }
128
136
  static rootFromJSON(storage, name, folderJson) {
129
- const rf = new FolderNode(storage, name, 'readonly-root', EMPTY_ARR, 0, undefined, undefined, false);
137
+ const rf = new FolderNode(storage, name, 'readonly-root', EMPTY_ARR, 0, undefined, EMPTY_ARR, false);
130
138
  const { folderInfo, attrs, xattrs } = jsonToInfoAndAttrs(folderJson);
131
139
  rf.currentState = folderInfo;
132
140
  rf.setUpdatedParams(0, attrs, xattrs);
133
141
  return rf;
134
142
  }
135
- list() {
136
- const names = Object.keys(this.currentState.nodes);
137
- const lst = new Array(names.length);
138
- for (let i = 0; i < names.length; i += 1) {
139
- const entity = this.currentState.nodes[names[i]];
140
- const info = { name: entity.name };
141
- if (entity.isFolder) {
142
- info.isFolder = true;
143
- }
144
- else if (entity.isFile) {
145
- info.isFile = true;
143
+ async setCurrentStateFrom(src) {
144
+ const { folderInfo, attrs, xattrs } = await this.crypto.read(src);
145
+ this.currentState = folderInfo;
146
+ this.setUpdatedParams(src.version, attrs, xattrs);
147
+ }
148
+ async adoptRemote(opts) {
149
+ const objsToRm = await this.doChange(true, async () => {
150
+ try {
151
+ const adopted = await this.syncedStorage().adoptRemote(this.objId, opts);
152
+ if (!adopted) {
153
+ return;
154
+ }
155
+ const src = await this.storage.getObjSrc(this.objId, adopted);
156
+ const originalState = this.currentState;
157
+ await this.setCurrentStateFrom(src);
158
+ const newState = this.currentState;
159
+ const { renamedNodes, addedNodes, removedNodes } = identifyChanges(originalState.nodes, newState.nodes);
160
+ for (const node of removedNodes) {
161
+ const event = {
162
+ type: 'entry-removal',
163
+ src: 'sync',
164
+ path: this.name,
165
+ newVersion: this.version,
166
+ name: node.name
167
+ };
168
+ this.broadcastEvent(event, false, node.objId);
169
+ }
170
+ for (const node of addedNodes) {
171
+ const event = {
172
+ type: 'entry-addition',
173
+ src: 'sync',
174
+ entry: nodeInfoToListingEntry(node),
175
+ path: this.name,
176
+ newVersion: this.version
177
+ };
178
+ this.broadcastEvent(event, false, node.objId);
179
+ }
180
+ for (const { objId, oldName, newName } of renamedNodes) {
181
+ const event = {
182
+ type: 'entry-renaming',
183
+ src: 'sync',
184
+ oldName,
185
+ newName,
186
+ path: this.name,
187
+ newVersion: this.version
188
+ };
189
+ this.broadcastEvent(event, false, objId);
190
+ }
191
+ return removedNodes;
146
192
  }
147
- else if (entity.isLink) {
148
- info.isLink = true;
193
+ catch (exc) {
194
+ throw (0, common_1.setPathInExc)(exc, this.name);
149
195
  }
150
- lst[i] = info;
196
+ });
197
+ if (!objsToRm) {
198
+ return;
199
+ }
200
+ for (const objToRm of objsToRm) {
201
+ (await this.getOrMakeChildNodeForInfo(objToRm)).removeObj('sync');
151
202
  }
203
+ }
204
+ list() {
205
+ const lst = Object.values(this.currentState.nodes)
206
+ .map(nodeInfoToListingEntry);
152
207
  return { lst, version: this.version };
153
208
  }
209
+ async childExistsInSyncedVersion(childObjId) {
210
+ const status = await this.syncStatus();
211
+ if (status.state === 'synced') {
212
+ return true;
213
+ }
214
+ if (!status.synced) {
215
+ return false;
216
+ }
217
+ // XXX FolderNode.childExistsInSyncedVersion() should implement check in synced version
218
+ return false;
219
+ }
154
220
  listFolders() {
155
221
  return Object.keys(this.currentState.nodes).filter(name => !!this.currentState.nodes[name].isFolder);
156
222
  }
@@ -163,30 +229,12 @@ class FolderNode extends node_in_fs_1.NodeInFS {
163
229
  return;
164
230
  }
165
231
  else {
166
- throw file_1.makeFileException(file_1.Code.notFound, name);
232
+ throw (0, file_1.makeFileException)(file_1.Code.notFound, name);
167
233
  }
168
234
  }
169
235
  hasChild(childName, throwIfMissing = false) {
170
236
  return !!this.getNodeInfo(childName, !throwIfMissing);
171
237
  }
172
- /**
173
- * @param objId
174
- * @return either node (promise for node), or a deferred, which promise has
175
- * been registered under a given id, and, therefore, has to be resolved with
176
- * node.
177
- */
178
- getNodeOrArrangePromise(objId) {
179
- const { node, nodePromise } = this.storage.nodes.getNodeOrPromise(objId);
180
- if (node) {
181
- return { nodeOrPromise: node };
182
- }
183
- if (nodePromise) {
184
- return { nodeOrPromise: nodePromise };
185
- }
186
- const deferred = processes_1.defer();
187
- this.storage.nodes.setPromise(objId, deferred.promise);
188
- return { deferred };
189
- }
190
238
  async getNode(type, name, undefOnMissing = false) {
191
239
  const childInfo = this.getNodeInfo(name, undefOnMissing);
192
240
  if (!childInfo) {
@@ -194,36 +242,38 @@ class FolderNode extends node_in_fs_1.NodeInFS {
194
242
  }
195
243
  if (type) {
196
244
  if ((type === 'file') && !childInfo.isFile) {
197
- throw file_1.makeFileException(file_1.Code.notFile, childInfo.name);
245
+ throw (0, file_1.makeFileException)(file_1.Code.notFile, childInfo.name);
198
246
  }
199
247
  else if ((type === 'folder') && !childInfo.isFolder) {
200
- throw file_1.makeFileException(file_1.Code.notDirectory, childInfo.name);
248
+ throw (0, file_1.makeFileException)(file_1.Code.notDirectory, childInfo.name);
201
249
  }
202
250
  else if ((type === 'link') && !childInfo.isLink) {
203
- throw file_1.makeFileException(file_1.Code.notLink, childInfo.name);
251
+ throw (0, file_1.makeFileException)(file_1.Code.notLink, childInfo.name);
204
252
  }
205
253
  }
206
- const { nodeOrPromise: child, deferred } = this.getNodeOrArrangePromise(childInfo.objId);
207
- if (child) {
208
- return child;
254
+ return this.getOrMakeChildNodeForInfo(childInfo);
255
+ }
256
+ async getOrMakeChildNodeForInfo(info) {
257
+ const { node, nodePromise } = this.storage.nodes.getNodeOrPromise(info.objId);
258
+ if (node) {
259
+ return node;
260
+ }
261
+ if (nodePromise) {
262
+ return nodePromise;
209
263
  }
264
+ const deferred = (0, deferred_1.defer)();
265
+ this.storage.nodes.setPromise(info.objId, deferred.promise);
210
266
  try {
211
267
  let node;
212
- if (childInfo.isFile) {
213
- node = await file_node_1.FileNode.makeForExisting(this.storage, this.objId, name, childInfo.objId, childInfo.key);
214
- }
215
- else if (childInfo.isFolder) {
216
- const src = await this.storage.getObj(childInfo.objId);
217
- // const f = new FolderNode(
218
- // this.storage, childInfo.name, childInfo.objId, undefined,
219
- // src.version, this.objId, childInfo.key, false);
220
- // const { folderInfo, attrs } = await f.crypto.open(src);
221
- // f.currentState = folderInfo;
222
- // f.attrs = (attrs ? attrs : AttrsHolder.makeReadonlyForFolder(0));
223
- node = await FolderNode.readNodeFromObjBytes(this.storage, childInfo.name, childInfo.objId, undefined, src, childInfo.key);
224
- }
225
- else if (childInfo.isLink) {
226
- node = await link_node_1.LinkNode.makeForExisting(this.storage, this.objId, name, childInfo.objId, childInfo.key);
268
+ if (info.isFile) {
269
+ node = await file_node_1.FileNode.makeForExisting(this.storage, this.objId, info.name, info.objId, info.key);
270
+ }
271
+ else if (info.isFolder) {
272
+ const src = await this.storage.getObjSrc(info.objId);
273
+ node = await FolderNode.readNodeFromObjBytes(this.storage, info.name, info.objId, src, info.key);
274
+ }
275
+ else if (info.isLink) {
276
+ node = await link_node_1.LinkNode.makeForExisting(this.storage, this.objId, info.name, info.objId, info.key);
227
277
  }
228
278
  else {
229
279
  throw new Error(`Unknown type of fs node`);
@@ -234,9 +284,9 @@ class FolderNode extends node_in_fs_1.NodeInFS {
234
284
  catch (exc) {
235
285
  deferred.reject(exc);
236
286
  if (exc.objNotFound) {
237
- await this.fixMissingChildAndThrow(exc, childInfo);
287
+ await this.fixMissingChildAndThrow(exc, info);
238
288
  }
239
- throw error_1.errWithCause(exc, `Cannot instantiate ${type} node '${this.name}/${childInfo.name}' from obj ${childInfo.objId}`);
289
+ throw (0, error_1.errWithCause)(exc, `Failed to instantiate fs node '${this.name}/${info.name}'`);
240
290
  }
241
291
  }
242
292
  getFolder(name, undefOnMissing = false) {
@@ -250,16 +300,21 @@ class FolderNode extends node_in_fs_1.NodeInFS {
250
300
  }
251
301
  async fixMissingChildAndThrow(exc, childInfo) {
252
302
  await this.doTransition(async (state, version) => {
303
+ const presentChild = state.nodes[childInfo.name];
304
+ if (!presentChild || (presentChild.objId !== childInfo.objId)) {
305
+ return [];
306
+ }
253
307
  delete state.nodes[childInfo.name];
254
308
  const event = {
255
309
  type: 'entry-removal',
256
310
  path: this.name,
311
+ src: 'local',
257
312
  name: childInfo.name,
258
313
  newVersion: version
259
314
  };
260
- return event;
315
+ return { event, childObjId: childInfo.objId };
261
316
  }).catch(noop);
262
- const fileExc = file_1.makeFileException(file_1.Code.notFound, childInfo.name, exc);
317
+ const fileExc = (0, file_1.makeFileException)(file_1.Code.notFound, childInfo.name, exc);
263
318
  fileExc.inconsistentStateOfFS = true;
264
319
  throw fileExc;
265
320
  }
@@ -278,11 +333,11 @@ class FolderNode extends node_in_fs_1.NodeInFS {
278
333
  async performTransition(change) {
279
334
  // start transition and prepare transition state
280
335
  // Note on copy: byte arrays are not cloned
281
- const state = json_utils_1.copy(this.currentState);
336
+ const state = (0, json_utils_1.copy)(this.currentState);
282
337
  const version = this.version + 1;
283
338
  const attrs = this.attrs.copy();
284
339
  // do action within transition state
285
- const event = await change(state, version);
340
+ const changeEvents = await change(state, version);
286
341
  // save transition state
287
342
  const encSub = await this.crypto.write(state, version, attrs, this.xattrs);
288
343
  await this.storage.saveObj(this.objId, version, encSub);
@@ -290,7 +345,15 @@ class FolderNode extends node_in_fs_1.NodeInFS {
290
345
  this.currentState = state;
291
346
  this.setCurrentVersion(version);
292
347
  this.attrs = attrs;
293
- this.broadcastEvent(event);
348
+ if (Array.isArray(changeEvents)) {
349
+ for (const { event, childObjId } of changeEvents) {
350
+ this.broadcastEvent(event, false, childObjId);
351
+ }
352
+ }
353
+ else {
354
+ const { event, childObjId } = changeEvents;
355
+ this.broadcastEvent(event, false, childObjId);
356
+ }
294
357
  }
295
358
  /**
296
359
  * This function only creates folder node, but it doesn't insert it anywhere.
@@ -299,7 +362,7 @@ class FolderNode extends node_in_fs_1.NodeInFS {
299
362
  async makeAndSaveNewChildFolderNode(name, changes) {
300
363
  const key = await random.bytes(xsp_files_1.KEY_LENGTH);
301
364
  const childObjId = await this.storage.generateNewObjId();
302
- const node = new FolderNode(this.storage, name, childObjId, undefined, 0, this.objId, key, true);
365
+ const node = new FolderNode(this.storage, name, childObjId, (0, xsp_files_1.idToHeaderNonce)(childObjId), 0, this.objId, key, true);
303
366
  await node.saveFirstVersion(changes).catch((exc) => {
304
367
  if (!exc.objExists) {
305
368
  throw exc;
@@ -358,7 +421,7 @@ class FolderNode extends node_in_fs_1.NodeInFS {
358
421
  // do check for concurrent creation of a node
359
422
  if (this.getNodeInfo(name, true)) {
360
423
  if (exclusive) {
361
- throw file_1.makeFileException(file_1.Code.alreadyExists, name);
424
+ throw (0, file_1.makeFileException)(file_1.Code.alreadyExists, name);
362
425
  }
363
426
  else if (type === 'folder') {
364
427
  return (await this.getNode('folder', name));
@@ -394,15 +457,11 @@ class FolderNode extends node_in_fs_1.NodeInFS {
394
457
  const event = {
395
458
  type: 'entry-addition',
396
459
  path: this.name,
460
+ src: 'local',
397
461
  newVersion: version,
398
- entry: {
399
- name: node.name,
400
- isFile: (node.type === 'file'),
401
- isFolder: (node.type === 'folder'),
402
- isLink: (node.type === 'link')
403
- }
462
+ entry: nodeToListingEntry(node)
404
463
  };
405
- return event;
464
+ return { event, childObjId: node.objId };
406
465
  });
407
466
  return node;
408
467
  });
@@ -426,15 +485,16 @@ class FolderNode extends node_in_fs_1.NodeInFS {
426
485
  const event = {
427
486
  type: 'entry-removal',
428
487
  path: this.name,
488
+ src: 'local',
429
489
  name: f.name,
430
490
  newVersion: version
431
491
  };
432
- return event;
492
+ return { event, childObjId: f.objId };
433
493
  });
434
494
  // explicitly do not wait on a result of child's delete, cause if it fails
435
495
  // we just get traceable garbage, yet, the rest of a live/non-deleted tree
436
496
  // stays consistent
437
- f.localDelete();
497
+ f.removeObj();
438
498
  }
439
499
  changeChildName(initName, newName) {
440
500
  return this.doTransition(async (state, version) => {
@@ -449,57 +509,59 @@ class FolderNode extends node_in_fs_1.NodeInFS {
449
509
  const event = {
450
510
  type: 'entry-renaming',
451
511
  path: this.name,
512
+ src: 'local',
452
513
  newName,
453
514
  oldName: initName,
454
515
  newVersion: version
455
516
  };
456
- return event;
517
+ return { event, childObjId: child.objId };
457
518
  });
458
519
  }
459
520
  async moveChildTo(childName, dst, nameInDst) {
460
521
  if (dst.hasChild(nameInDst)) {
461
- throw file_1.makeFileException(file_1.Code.alreadyExists, nameInDst);
522
+ throw (0, file_1.makeFileException)(file_1.Code.alreadyExists, nameInDst);
462
523
  }
463
524
  if (dst === this) {
464
525
  // In this case we only need to change child's name
465
- return this.changeChildName(childName, nameInDst);
526
+ await this.changeChildName(childName, nameInDst);
527
+ }
528
+ else {
529
+ const childJSON = this.getNodeInfo(childName);
530
+ // we have two transitions here, in this and in dst.
531
+ const moveLabel = getMoveLabel();
532
+ await dst.moveChildIn(nameInDst, childJSON, moveLabel);
533
+ await this.moveChildOut(childName, childJSON.objId, moveLabel);
466
534
  }
467
- const childJSON = this.getNodeInfo(childName);
468
- // we have two transitions here, in this and in dst.
469
- await Promise.all([
470
- await dst.moveChildIn(nameInDst, childJSON),
471
- await this.moveChildOut(childName)
472
- ]);
473
535
  }
474
- async moveChildOut(name) {
536
+ async moveChildOut(name, childObjId, moveLabel) {
475
537
  await this.doTransition(async (state, version) => {
538
+ const child = state.nodes[name];
476
539
  delete state.nodes[name];
477
540
  const event = {
478
541
  type: 'entry-removal',
479
542
  path: this.name,
543
+ src: 'local',
480
544
  name,
481
- newVersion: version
545
+ newVersion: version,
546
+ moveLabel
482
547
  };
483
- return event;
548
+ return { event, childObjId };
484
549
  });
485
550
  }
486
- async moveChildIn(newName, child) {
487
- child = json_utils_1.copy(child);
551
+ async moveChildIn(newName, child, moveLabel) {
552
+ child = (0, json_utils_1.copy)(child);
488
553
  await this.doTransition(async (state, version) => {
489
554
  child.name = newName;
490
555
  state.nodes[child.name] = child;
491
556
  const event = {
492
557
  type: 'entry-addition',
493
558
  path: this.name,
494
- entry: {
495
- name: child.name,
496
- isFile: child.isFile,
497
- isFolder: child.isFolder,
498
- isLink: child.isLink
499
- },
500
- newVersion: version
559
+ src: 'local',
560
+ entry: nodeInfoToListingEntry(child),
561
+ newVersion: version,
562
+ moveLabel
501
563
  };
502
- return event;
564
+ return { event, childObjId: child.objId };
503
565
  });
504
566
  }
505
567
  async getFolderInThisSubTree(path, createIfMissing = false, exclusiveCreate = false) {
@@ -512,7 +574,7 @@ class FolderNode extends node_in_fs_1.NodeInFS {
512
574
  // existing folder at this point
513
575
  if (path.length === 1) {
514
576
  if (exclusiveCreate) {
515
- throw file_1.makeFileException(file_1.Code.alreadyExists, path[0]);
577
+ throw (0, file_1.makeFileException)(file_1.Code.alreadyExists, path[0]);
516
578
  }
517
579
  else {
518
580
  return f;
@@ -569,10 +631,7 @@ class FolderNode extends node_in_fs_1.NodeInFS {
569
631
  }
570
632
  return content;
571
633
  }
572
- async delete(remoteEvent) {
573
- if (remoteEvent) {
574
- return super.delete(true);
575
- }
634
+ async delete(src) {
576
635
  const childrenNodes = await this.getAllNodes();
577
636
  await this.storage.removeObj(this.objId);
578
637
  this.storage.nodes.delete(this);
@@ -581,14 +640,11 @@ class FolderNode extends node_in_fs_1.NodeInFS {
581
640
  const event = {
582
641
  type: 'removed',
583
642
  path: this.name,
584
- isRemote: remoteEvent
643
+ src
585
644
  };
586
645
  this.broadcastEvent(event, true);
587
- // explicitly do not wait on a result of child's delete, cause if it fails
588
- // we just get traceable garbage, yet, the rest of a live/non-deleted tree
589
- // stays consistent
590
646
  for (const node of childrenNodes) {
591
- node.localDelete();
647
+ node.removeObj(src);
592
648
  }
593
649
  }
594
650
  getParamsForLink() {
@@ -607,76 +663,213 @@ class FolderNode extends node_in_fs_1.NodeInFS {
607
663
  };
608
664
  return linkParams;
609
665
  }
610
- // XXX make default conflict resolution for folder
611
- // async resolveConflict(remoteVersion: number): Promise<void> {
612
- // // XXX we need to read remote version outside of doChange
613
- // const src = await (this.storage as SyncedStorage).getSyncedObjVersion(
614
- // this.objId, remoteVersion);
615
- // const remInfo = await this.crypto.open(src);
616
- // await this.doChange(true, async () => {
617
- // // XXX this is a default conflict resolution
618
- // // XXX drop all previous local versions(!)
619
- // });
620
- // }
621
- async doOnExternalChange(expectedVersion) {
622
- if (expectedVersion <= this.version) {
666
+ async upload(opts) {
667
+ // XXX temporary use of super's method
668
+ return super.upload(opts);
669
+ // const storage = this.syncedStorage();
670
+ // XXX
671
+ // - use identifyChanges() to find changes, minding particular version,
672
+ // ensuring that concurrent change won't mess things up.
673
+ // - items not present in uploaded version, relative to latest sync,
674
+ // i.e. removed, removal should be uploaded with
675
+ // storage.uploadObjRemoval(objId);
676
+ }
677
+ async needUpload(localVersion) {
678
+ const toUpload = await super.needUpload(localVersion);
679
+ if (!toUpload) {
623
680
  return;
624
681
  }
625
- const src = await this.storage.getObj(this.objId);
626
- const newVersion = src.version;
627
- if (newVersion <= this.version) {
628
- return;
682
+ // XXX can put the following into private ensureAllChildrenUploaded(),
683
+ // but it also need to get a consistent state, while current one can
684
+ // be broken by simultaneous change in folder.
685
+ const storage = this.syncedStorage();
686
+ if (toUpload.localVersion === this.version) {
687
+ for (const { objId, name } of Object.values(this.currentState.nodes)) {
688
+ if ((await storage.status(objId)).neverUploaded()) {
689
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
690
+ childNeverUploaded: true, childName: name
691
+ });
692
+ }
693
+ }
694
+ }
695
+ else {
696
+ throw new Error(`Reading particular version is not implemented, yet`);
697
+ }
698
+ return toUpload;
699
+ }
700
+ async adoptRemoteFolderItem(itemName, opts) {
701
+ if (opts === null || opts === void 0 ? void 0 : opts.localVersion) {
702
+ if (this.version !== opts.localVersion) {
703
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
704
+ versionMismatch: true,
705
+ message: `Given local version ${opts.localVersion} is not equal to current ${this.version}`
706
+ });
707
+ }
708
+ }
709
+ const { remote } = await this.syncStatus();
710
+ if (!remote) {
711
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
712
+ versionMismatch: true,
713
+ message: `No remote versions`
714
+ });
715
+ }
716
+ if (opts === null || opts === void 0 ? void 0 : opts.remoteVersion) {
717
+ if (opts.remoteVersion !== remote.latest) {
718
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
719
+ versionMismatch: true,
720
+ message: `Unknown remote version ${opts.remoteVersion}`
721
+ });
722
+ }
723
+ }
724
+ else if (!remote.latest) {
725
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
726
+ remoteIsArchived: true
727
+ });
728
+ }
729
+ const storage = this.syncedStorage();
730
+ const srcOfRemote = await storage.getObjSrcOfRemoteVersion(this.objId, remote.latest);
731
+ const { folderInfo: { nodes: remoteNodes } } = await this.crypto.read(srcOfRemote);
732
+ const remoteChildNode = remoteNodes[itemName];
733
+ if (!remoteChildNode) {
734
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
735
+ remoteFolderItemNotFound: true,
736
+ message: `Item '${itemName}' is not found in remote version`
737
+ });
738
+ }
739
+ const localNodeToRm = this.getNodeInfo(itemName, true);
740
+ if (!localNodeToRm) {
741
+ return await this.addRemoteChild(remoteChildNode);
629
742
  }
630
- const { folderInfo, attrs } = await this.crypto.read(src);
631
- const initState = this.currentState;
632
- this.currentState = checkFolderInfo(folderInfo);
633
- this.setCurrentVersion(newVersion);
634
- if (attrs) {
635
- this.attrs = attrs;
636
- }
637
- const addedEntries = Object.keys(this.currentState.nodes)
638
- .filter(name => !initState.nodes[name]);
639
- const removedEntries = Object.keys(initState.nodes)
640
- .filter(name => !this.currentState.nodes[name]);
641
- if ((addedEntries.length === 1) && (removedEntries.length === 1)) {
743
+ else if (localNodeToRm.objId === remoteChildNode.objId) {
744
+ return await this.setRemoteNodeInfo(remoteChildNode);
745
+ }
746
+ else {
747
+ return await this.replaceLocalChildWithRemote(localNodeToRm, remoteChildNode);
748
+ }
749
+ }
750
+ async addRemoteChild(remoteChildNode) {
751
+ let newVersion;
752
+ await this.doTransition(async (state, version) => {
753
+ newVersion = version;
754
+ state.nodes[remoteChildNode.name] = remoteChildNode;
642
755
  const event = {
643
- type: 'entry-renaming',
756
+ type: 'entry-addition',
757
+ path: this.name,
758
+ src: 'sync',
759
+ entry: nodeInfoToListingEntry(remoteChildNode),
760
+ newVersion
761
+ };
762
+ return { event, childObjId: remoteChildNode.objId };
763
+ });
764
+ // XXX child should update sync info, and adopt change.
765
+ return newVersion;
766
+ }
767
+ async setRemoteNodeInfo(remoteChildNode) {
768
+ let newVersion;
769
+ await this.doTransition(async (state, version) => {
770
+ newVersion = version;
771
+ state.nodes[remoteChildNode.name] = remoteChildNode;
772
+ return [];
773
+ });
774
+ // XXX child should update sync info, and adopt change, cause changed key
775
+ // requires changed cypher.
776
+ return newVersion;
777
+ }
778
+ async replaceLocalChildWithRemote(origChildNode, remoteChildNode) {
779
+ let newVersion;
780
+ await this.doTransition(async (state, version) => {
781
+ newVersion = version;
782
+ const presentNode = state.nodes[remoteChildNode.name];
783
+ state.nodes[remoteChildNode.name] = remoteChildNode;
784
+ const additionEvent = {
785
+ type: 'entry-addition',
644
786
  path: this.name,
645
- isRemote: true,
646
- oldName: removedEntries[0],
647
- newName: addedEntries[0],
787
+ src: 'sync',
788
+ entry: nodeInfoToListingEntry(remoteChildNode),
648
789
  newVersion
649
790
  };
650
- this.broadcastEvent(event);
791
+ const rmEvent = {
792
+ type: 'entry-removal',
793
+ path: this.name,
794
+ src: 'sync',
795
+ name: origChildNode.name
796
+ };
797
+ return [
798
+ { event: rmEvent, childObjId: origChildNode.objId },
799
+ { event: additionEvent, childObjId: remoteChildNode.objId }
800
+ ];
801
+ });
802
+ // XXX
803
+ // remote child should update sync info, and adopt change.
804
+ // original child:
805
+ // - if only local, should be removed,
806
+ // - else should update sync info, and adopt both removal and change.
807
+ (await this.getOrMakeChildNodeForInfo(origChildNode)).removeObj('sync');
808
+ return newVersion;
809
+ }
810
+ async diffCurrentAndRemote(remoteVersion) {
811
+ var _a;
812
+ const { state, remote } = await this.syncStatus();
813
+ let isCurrentLocal;
814
+ if (state === 'behind') {
815
+ isCurrentLocal = false;
816
+ }
817
+ else if (state === 'conflicting') {
818
+ isCurrentLocal = true;
651
819
  }
652
820
  else {
653
- addedEntries.forEach(name => {
654
- const addedNode = this.currentState.nodes[name];
655
- const event = {
656
- type: 'entry-addition',
657
- path: this.name,
658
- isRemote: true,
659
- entry: {
660
- name: addedNode.name,
661
- isFile: addedNode.isFile,
662
- isFolder: addedNode.isFolder,
663
- isLink: addedNode.isLink
664
- },
665
- newVersion
666
- };
667
- this.broadcastEvent(event);
668
- });
669
- removedEntries.forEach(name => {
670
- const event = {
671
- type: 'entry-removal',
672
- path: this.name,
673
- isRemote: true,
674
- name,
675
- newVersion
676
- };
677
- this.broadcastEvent(event);
678
- });
821
+ return;
822
+ }
823
+ if (remote.isArchived) {
824
+ return this.diffWithArchivedRemote(isCurrentLocal);
679
825
  }
826
+ else {
827
+ if (remoteVersion) {
828
+ if ((remoteVersion !== remote.latest)
829
+ && !((_a = remote.archived) === null || _a === void 0 ? void 0 : _a.includes(remoteVersion))) {
830
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
831
+ versionMismatch: true,
832
+ message: `Unknown remote version ${remoteVersion}`
833
+ });
834
+ }
835
+ }
836
+ else {
837
+ remoteVersion = remote.latest;
838
+ }
839
+ const storage = this.syncedStorage();
840
+ const srcOfRemote = await storage.getObjSrcOfRemoteVersion(this.objId, remote.latest);
841
+ const { folderInfo, attrs, xattrs } = await this.crypto.read(srcOfRemote);
842
+ return this.diffWithRemote(isCurrentLocal, remoteVersion, folderInfo, attrs, xattrs);
843
+ }
844
+ }
845
+ diffWithArchivedRemote(isCurrentLocal) {
846
+ return {
847
+ currentVersion: this.version,
848
+ isCurrentLocal,
849
+ isRemoteArchived: true,
850
+ ctime: {
851
+ current: new Date(this.attrs.ctime)
852
+ },
853
+ mtime: {
854
+ current: new Date(this.attrs.ctime)
855
+ }
856
+ };
857
+ }
858
+ diffWithRemote(isCurrentLocal, remoteVersion, folderInfo, attrs, xattrs) {
859
+ const { ctime, mtime } = diffAttrs(this.attrs, attrs);
860
+ const { inCurrent, inRemote, nameOverlaps } = diffNodes(this.currentState, folderInfo);
861
+ return {
862
+ currentVersion: this.version,
863
+ isCurrentLocal,
864
+ isRemoteArchived: false,
865
+ remoteVersion,
866
+ inCurrent,
867
+ inRemote,
868
+ nameOverlaps,
869
+ ctime,
870
+ mtime,
871
+ xattrs: diffXAttrs(this.xattrs, xattrs)
872
+ };
680
873
  }
681
874
  }
682
875
  exports.FolderNode = FolderNode;
@@ -688,6 +881,32 @@ function checkFolderInfo(folderJson) {
688
881
  // TODO throw if folderJson is not ok
689
882
  return folderJson;
690
883
  }
884
+ function nodeInfoToListingEntry({ name, isFile, isFolder, isLink }) {
885
+ if (isFolder) {
886
+ return { name, isFolder };
887
+ }
888
+ else if (isFile) {
889
+ return { name, isFile };
890
+ }
891
+ else if (isLink) {
892
+ return { name, isLink };
893
+ }
894
+ else {
895
+ return { name };
896
+ }
897
+ }
898
+ function nodeToListingEntry({ name, type }) {
899
+ switch (type) {
900
+ case 'file':
901
+ return { name, isFolder: true };
902
+ case 'folder':
903
+ return { name, isFolder: true };
904
+ case 'link':
905
+ return { name, isLink: true };
906
+ default:
907
+ return { name };
908
+ }
909
+ }
691
910
  function addToTransitionState(state, f, key) {
692
911
  const nodeInfo = {
693
912
  name: f.name,
@@ -708,4 +927,195 @@ function addToTransitionState(state, f, key) {
708
927
  }
709
928
  state.nodes[nodeInfo.name] = nodeInfo;
710
929
  }
930
+ function diffAttrs(current, remote) {
931
+ return {
932
+ ctime: {
933
+ current: new Date(current.ctime),
934
+ remote: new Date(remote.ctime)
935
+ },
936
+ mtime: {
937
+ current: new Date(current.mtime),
938
+ remote: new Date(remote.mtime)
939
+ }
940
+ };
941
+ }
942
+ function diffNodes(current, remote) {
943
+ const inCurrent = getOnlyNotEqualNodes(current.nodes, remote.nodes);
944
+ const inRemote = getOnlyNotEqualNodes(remote.nodes, current.nodes);
945
+ const combined = combineCheckingNameOverlaps(inCurrent, inRemote);
946
+ return (combined ? combined : {});
947
+ }
948
+ function getOnlyNotEqualNodes(src, exclude) {
949
+ const collected = [];
950
+ for (const [name, nodeInSrc] of Object.entries(src)) {
951
+ const nodeInExclude = exclude[name];
952
+ if (!nodeInExclude
953
+ || !areNodesEqual(nodeInSrc, nodeInExclude)) {
954
+ collected.push(nodeInfoToListingEntry(nodeInSrc));
955
+ }
956
+ }
957
+ return ((collected.length > 0) ? collected : undefined);
958
+ }
959
+ function areNodesEqual(n1, n2) {
960
+ if (n1.name !== n2.name) {
961
+ return false;
962
+ }
963
+ if (n1.isFile && !n2.isFile) {
964
+ return false;
965
+ }
966
+ if (n1.isFolder && !n2.isFolder) {
967
+ return false;
968
+ }
969
+ if (n1.isLink && !n2.isLink) {
970
+ return false;
971
+ }
972
+ if (n1.objId !== n2.objId) {
973
+ return false;
974
+ }
975
+ if (!areBytesEqual(n1.key, n2.key)) {
976
+ return false;
977
+ }
978
+ return true;
979
+ }
980
+ function combineCheckingNameOverlaps(inCurrent, inRemote) {
981
+ if (inCurrent) {
982
+ if (inRemote) {
983
+ const nameOverlaps = [];
984
+ for (const { name: nameInCurrent } of inCurrent) {
985
+ if (inRemote.find(({ name }) => (name === nameInCurrent))) {
986
+ nameOverlaps.push(nameInCurrent);
987
+ }
988
+ }
989
+ if (nameOverlaps.length > 0) {
990
+ return { inCurrent, inRemote, nameOverlaps };
991
+ }
992
+ else {
993
+ return { inCurrent, inRemote };
994
+ }
995
+ }
996
+ else {
997
+ return { inCurrent };
998
+ }
999
+ }
1000
+ else {
1001
+ if (inRemote) {
1002
+ return { inRemote };
1003
+ }
1004
+ else {
1005
+ return;
1006
+ }
1007
+ }
1008
+ }
1009
+ function diffXAttrs(current, remote) {
1010
+ const haveCurrentXAttrs = (current && (current.list().length > 0));
1011
+ const haveRemoteXAttrs = (remote && (remote.list().length > 0));
1012
+ if (haveCurrentXAttrs && haveRemoteXAttrs) {
1013
+ const inCurrent = getOnlyNotEqualXAttrs(current, remote);
1014
+ const inRemote = getOnlyNotEqualXAttrs(remote, current);
1015
+ return combineCheckingNameOverlaps(inCurrent, inRemote);
1016
+ }
1017
+ else if (haveCurrentXAttrs && !haveRemoteXAttrs) {
1018
+ return { inCurrent: allXAttrsToDiff(current) };
1019
+ }
1020
+ else if (!haveCurrentXAttrs && haveRemoteXAttrs) {
1021
+ return { inRemote: allXAttrsToDiff(remote) };
1022
+ }
1023
+ else {
1024
+ return;
1025
+ }
1026
+ }
1027
+ function allXAttrsToDiff(src) {
1028
+ const collected = [];
1029
+ for (const name of src.list()) {
1030
+ const value = src.get(name);
1031
+ if (value !== undefined) {
1032
+ collected.push({ name, value });
1033
+ }
1034
+ }
1035
+ return collected;
1036
+ }
1037
+ function getOnlyNotEqualXAttrs(src, exclude) {
1038
+ const collected = [];
1039
+ for (const name of src.list()) {
1040
+ const valueInSrc = src.get(name);
1041
+ if (valueInSrc === undefined) {
1042
+ continue;
1043
+ }
1044
+ const valueInExclude = exclude.get(name);
1045
+ if ((valueInExclude === undefined)
1046
+ || !areXAttrValuesEqual(valueInSrc, valueInExclude)) {
1047
+ collected.push({ name, value: valueInSrc });
1048
+ }
1049
+ }
1050
+ return ((collected.length > 0) ? collected : undefined);
1051
+ }
1052
+ function areXAttrValuesEqual(v1, v2) {
1053
+ if (Buffer.isBuffer(v1) || ArrayBuffer.isView(v1)) {
1054
+ if (Buffer.isBuffer(v2) || ArrayBuffer.isView(v2)) {
1055
+ return areBytesEqual(v1, v2);
1056
+ }
1057
+ {
1058
+ return false;
1059
+ }
1060
+ }
1061
+ else if (typeof v1 === 'string') {
1062
+ if (typeof v2 === 'string') {
1063
+ return (v1 === v2);
1064
+ }
1065
+ else {
1066
+ return false;
1067
+ }
1068
+ }
1069
+ else {
1070
+ if (Buffer.isBuffer(v2) || ArrayBuffer.isView(v2)
1071
+ || (typeof v2 === 'string')) {
1072
+ return false;
1073
+ }
1074
+ else {
1075
+ return (0, json_utils_1.deepEqual)(v1, v2);
1076
+ }
1077
+ }
1078
+ }
1079
+ function areBytesEqual(b1, b2) {
1080
+ if (b1.length !== b2.length) {
1081
+ return false;
1082
+ }
1083
+ for (let i = 0; i < b1.length; i += 1) {
1084
+ if (b1[i] !== b2[i]) {
1085
+ return false;
1086
+ }
1087
+ }
1088
+ return true;
1089
+ }
1090
+ function identifyChanges(originalNodes, newNodes) {
1091
+ const removedNodes = [];
1092
+ const addedNodes = [];
1093
+ const renamedNodes = [];
1094
+ for (const [name, node] of Object.entries(originalNodes)) {
1095
+ const newNode = newNodes[name];
1096
+ if (newNode && (newNode.objId === node.objId)) {
1097
+ continue;
1098
+ }
1099
+ removedNodes.push(node);
1100
+ }
1101
+ for (const [name, newNode] of Object.entries(newNodes)) {
1102
+ const node = originalNodes[name];
1103
+ if (node && (newNode.objId === node.objId)) {
1104
+ continue;
1105
+ }
1106
+ const indInRm = removedNodes.findIndex(n => (n.objId === newNode.objId));
1107
+ if (indInRm < 0) {
1108
+ addedNodes.push(newNode);
1109
+ }
1110
+ else {
1111
+ const oldNode = removedNodes.splice(indInRm, 1)[0];
1112
+ renamedNodes.push({
1113
+ oldName: oldNode.name,
1114
+ newName: newNode.name,
1115
+ objId: newNode.objId
1116
+ });
1117
+ }
1118
+ }
1119
+ return { addedNodes, removedNodes, renamedNodes };
1120
+ }
711
1121
  Object.freeze(exports);