core-3nweb-client-lib 0.26.1 → 0.27.3

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 (211) hide show
  1. package/build/api-defs/asmail.d.ts +1 -1
  2. package/build/api-defs/files.d.ts +281 -75
  3. package/build/core/app-files.js +7 -7
  4. package/build/core/asmail/config/common.js +2 -2
  5. package/build/core/asmail/config/index.js +2 -2
  6. package/build/core/asmail/config/published-intro-key.js +1 -1
  7. package/build/core/asmail/delivery/common.js +7 -7
  8. package/build/core/asmail/delivery/index.js +5 -5
  9. package/build/core/asmail/delivery/msg.js +4 -4
  10. package/build/core/asmail/delivery/per-recipient-wip.js +1 -1
  11. package/build/core/asmail/inbox/attachments/fs.d.ts +2 -1
  12. package/build/core/asmail/inbox/attachments/fs.js +9 -4
  13. package/build/core/asmail/inbox/cached-msgs.js +1 -1
  14. package/build/core/asmail/inbox/inbox-events.js +4 -4
  15. package/build/core/asmail/inbox/index.js +10 -10
  16. package/build/core/asmail/inbox/msg-downloader.js +1 -1
  17. package/build/core/asmail/inbox/msg-indexing.js +1 -1
  18. package/build/core/asmail/inbox/msg-on-disk.js +5 -5
  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 +2 -1
  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 +2 -2
  31. package/build/core/asmail/sending-params/params-from-others.js +1 -1
  32. package/build/core/id-manager/index.d.ts +43 -0
  33. package/build/core/{id-manager.js → id-manager/index.js} +36 -114
  34. package/build/core/id-manager/key-storage.d.ts +21 -0
  35. package/build/core/id-manager/key-storage.js +96 -0
  36. package/build/core/index.d.ts +2 -1
  37. package/build/core/index.js +31 -33
  38. package/build/core/sign-in.d.ts +1 -2
  39. package/build/core/sign-in.js +8 -17
  40. package/build/core/sign-up.d.ts +2 -0
  41. package/build/core/sign-up.js +11 -10
  42. package/build/core/storage/common/json-saving.js +2 -2
  43. package/build/core/storage/common/obj-info-file.d.ts +12 -4
  44. package/build/core/storage/common/obj-info-file.js +66 -34
  45. package/build/core/storage/common/utils.d.ts +2 -0
  46. package/build/core/storage/common/utils.js +32 -0
  47. package/build/core/storage/index.d.ts +5 -17
  48. package/build/core/storage/index.js +78 -119
  49. package/build/core/storage/local/obj-files-gc.d.ts +2 -0
  50. package/build/core/storage/local/obj-files-gc.js +49 -37
  51. package/build/core/storage/local/obj-files.d.ts +4 -7
  52. package/build/core/storage/local/obj-files.js +7 -10
  53. package/build/core/storage/local/obj-status.d.ts +12 -6
  54. package/build/core/storage/local/obj-status.js +24 -9
  55. package/build/core/storage/local/storage.d.ts +10 -7
  56. package/build/core/storage/local/storage.js +29 -18
  57. package/build/core/storage/synced/downloader.js +1 -1
  58. package/build/core/storage/synced/obj-files-gc.d.ts +1 -0
  59. package/build/core/storage/synced/obj-files-gc.js +76 -39
  60. package/build/core/storage/synced/obj-files.d.ts +50 -36
  61. package/build/core/storage/synced/obj-files.js +201 -162
  62. package/build/core/storage/synced/obj-status.d.ts +99 -86
  63. package/build/core/storage/synced/obj-status.js +520 -251
  64. package/build/core/storage/synced/remote-events.d.ts +11 -12
  65. package/build/core/storage/synced/remote-events.js +73 -56
  66. package/build/core/storage/synced/storage.d.ts +24 -10
  67. package/build/core/storage/synced/storage.js +147 -47
  68. package/build/core/storage/synced/upload-header-file.d.ts +4 -0
  69. package/build/core/storage/synced/upload-header-file.js +64 -0
  70. package/build/core/storage/synced/upsyncer.d.ts +12 -7
  71. package/build/core/storage/synced/upsyncer.js +210 -280
  72. package/build/core/storage/system-folders/apps-data.d.ts +16 -0
  73. package/build/core/storage/system-folders/apps-data.js +110 -0
  74. package/build/core/storage/system-folders/index.d.ts +18 -0
  75. package/build/core/storage/system-folders/index.js +77 -0
  76. package/build/core-ipc/common-caps.js +3 -3
  77. package/build/core-ipc/generic.js +8 -8
  78. package/build/core-ipc/startup-caps.js +2 -2
  79. package/build/cryptors.js +6 -2
  80. package/build/ipc-via-protobuf/asmail-cap.js +58 -57
  81. package/build/ipc-via-protobuf/bytes.js +16 -17
  82. package/build/ipc-via-protobuf/connector-clients-side.d.ts +1 -0
  83. package/build/ipc-via-protobuf/connector-clients-side.js +14 -15
  84. package/build/ipc-via-protobuf/connector-services-side.js +10 -10
  85. package/build/ipc-via-protobuf/connector.js +4 -4
  86. package/build/ipc-via-protobuf/file.d.ts +48 -12
  87. package/build/ipc-via-protobuf/file.js +474 -126
  88. package/build/ipc-via-protobuf/fs.d.ts +8 -0
  89. package/build/ipc-via-protobuf/fs.js +577 -142
  90. package/build/ipc-via-protobuf/log-cap.js +2 -2
  91. package/build/ipc-via-protobuf/mailerid.js +3 -3
  92. package/build/ipc-via-protobuf/protobuf-msg.d.ts +1 -0
  93. package/build/ipc-via-protobuf/protobuf-msg.js +11 -7
  94. package/build/ipc-via-protobuf/startup-cap.js +21 -21
  95. package/build/ipc-via-protobuf/storage-cap.js +12 -12
  96. package/build/ipc.js +7 -2
  97. package/build/lib-client/3nstorage/exceptions.d.ts +16 -1
  98. package/build/lib-client/3nstorage/exceptions.js +21 -3
  99. package/build/lib-client/3nstorage/service.d.ts +21 -3
  100. package/build/lib-client/3nstorage/service.js +128 -46
  101. package/build/lib-client/3nstorage/util/file-based-json.d.ts +2 -1
  102. package/build/lib-client/3nstorage/util/file-based-json.js +3 -2
  103. package/build/lib-client/3nstorage/util/for-arrays.d.ts +1 -0
  104. package/build/lib-client/3nstorage/util/for-arrays.js +32 -0
  105. package/build/lib-client/3nstorage/xsp-fs/attrs.js +17 -17
  106. package/build/lib-client/3nstorage/xsp-fs/common.d.ts +44 -19
  107. package/build/lib-client/3nstorage/xsp-fs/common.js +30 -19
  108. package/build/lib-client/3nstorage/xsp-fs/file-node.d.ts +1 -0
  109. package/build/lib-client/3nstorage/xsp-fs/file-node.js +17 -13
  110. package/build/lib-client/3nstorage/xsp-fs/file.d.ts +31 -6
  111. package/build/lib-client/3nstorage/xsp-fs/file.js +73 -25
  112. package/build/lib-client/3nstorage/xsp-fs/folder-node-serialization.js +4 -4
  113. package/build/lib-client/3nstorage/xsp-fs/folder-node.d.ts +32 -13
  114. package/build/lib-client/3nstorage/xsp-fs/folder-node.js +752 -192
  115. package/build/lib-client/3nstorage/xsp-fs/fs.d.ts +35 -4
  116. package/build/lib-client/3nstorage/xsp-fs/fs.js +236 -119
  117. package/build/lib-client/3nstorage/xsp-fs/link-node.d.ts +1 -0
  118. package/build/lib-client/3nstorage/xsp-fs/link-node.js +7 -2
  119. package/build/lib-client/3nstorage/xsp-fs/node-in-fs.d.ts +30 -29
  120. package/build/lib-client/3nstorage/xsp-fs/node-in-fs.js +232 -127
  121. package/build/lib-client/3nstorage/xsp-fs/node-persistence.d.ts +1 -1
  122. package/build/lib-client/3nstorage/xsp-fs/node-persistence.js +17 -18
  123. package/build/lib-client/3nstorage/xsp-fs/xsp-payload-v1.js +3 -3
  124. package/build/lib-client/3nstorage/xsp-fs/xsp-payload-v2.js +53 -53
  125. package/build/lib-client/3nweb-signup.js +4 -4
  126. package/build/lib-client/asmail/recipient.js +15 -15
  127. package/build/lib-client/asmail/sender.js +22 -22
  128. package/build/lib-client/asmail/service-config.js +3 -3
  129. package/build/lib-client/cryptor/cryptor-in-worker.js +18 -16
  130. package/build/lib-client/cryptor/cryptor-wasm.js +1 -1
  131. package/build/lib-client/cryptor/cryptor.js +4 -2
  132. package/build/lib-client/cryptor/cryptor.wasm +0 -0
  133. package/build/lib-client/cryptor/in-proc-js.js +1 -1
  134. package/build/lib-client/cryptor/in-proc-wasm.js +6 -6
  135. package/build/lib-client/cryptor/worker-js.js +2 -2
  136. package/build/lib-client/cryptor/worker-wasm.js +2 -2
  137. package/build/lib-client/files-select.js +1 -1
  138. package/build/lib-client/files.d.ts +1 -1
  139. package/build/lib-client/files.js +71 -6
  140. package/build/lib-client/fs-collection.js +1 -1
  141. package/build/lib-client/fs-sync-utils.d.ts +5 -0
  142. package/build/lib-client/fs-sync-utils.js +61 -0
  143. package/build/lib-client/fs-view.d.ts +14 -0
  144. package/build/lib-client/fs-view.js +33 -0
  145. package/build/lib-client/key-derivation.js +1 -1
  146. package/build/lib-client/local-files/dev-file-sink.js +9 -9
  147. package/build/lib-client/local-files/dev-file-src.js +2 -2
  148. package/build/lib-client/local-files/device-fs.d.ts +1 -1
  149. package/build/lib-client/local-files/device-fs.js +56 -54
  150. package/build/lib-client/logging/log-to-file.d.ts +1 -1
  151. package/build/lib-client/logging/log-to-file.js +7 -7
  152. package/build/lib-client/mailer-id/login.js +7 -7
  153. package/build/lib-client/mailer-id/provisioner.js +12 -12
  154. package/build/lib-client/objs-on-disk/file-writing-proc.js +3 -3
  155. package/build/lib-client/objs-on-disk/obj-folders.js +31 -31
  156. package/build/lib-client/objs-on-disk/obj-on-disk.d.ts +13 -2
  157. package/build/lib-client/objs-on-disk/obj-on-disk.js +24 -9
  158. package/build/lib-client/request-utils.d.ts +1 -0
  159. package/build/lib-client/request-utils.js +13 -13
  160. package/build/lib-client/server-events.d.ts +3 -3
  161. package/build/lib-client/server-events.js +9 -8
  162. package/build/lib-client/service-locator.js +10 -10
  163. package/build/lib-client/user-with-mid-session.d.ts +2 -1
  164. package/build/lib-client/user-with-mid-session.js +14 -8
  165. package/build/lib-client/user-with-pkl-session.js +25 -25
  166. package/build/lib-client/ws-utils.js +2 -2
  167. package/build/lib-common/async-cryptor-wrap.js +4 -4
  168. package/build/lib-common/async-fs-node.d.ts +5 -3
  169. package/build/lib-common/async-fs-node.js +17 -17
  170. package/build/lib-common/byte-streaming/pipe.js +1 -1
  171. package/build/lib-common/byte-streaming/wrapping.js +13 -13
  172. package/build/lib-common/canonical-address.js +1 -1
  173. package/build/lib-common/exceptions/error.d.ts +1 -0
  174. package/build/lib-common/exceptions/error.js +7 -6
  175. package/build/lib-common/exceptions/file.d.ts +4 -2
  176. package/build/lib-common/exceptions/file.js +24 -54
  177. package/build/lib-common/ipc/generic-ipc.js +5 -4
  178. package/build/lib-common/ipc/ws-ipc.js +2 -2
  179. package/build/lib-common/mid-sigs-NaCl-Ed.js +14 -14
  180. package/build/lib-common/objs-on-disk/file-layout.d.ts +19 -0
  181. package/build/lib-common/objs-on-disk/file-layout.js +130 -12
  182. package/build/lib-common/objs-on-disk/obj-file.d.ts +13 -2
  183. package/build/lib-common/objs-on-disk/obj-file.js +96 -35
  184. package/build/lib-common/objs-on-disk/utils.d.ts +1 -0
  185. package/build/lib-common/objs-on-disk/utils.js +3 -3
  186. package/build/lib-common/objs-on-disk/v1-obj-file-format.js +14 -14
  187. package/build/lib-common/processes/labelled-exec-pools.d.ts +1 -1
  188. package/build/lib-common/processes/labelled-exec-pools.js +1 -1
  189. package/build/lib-common/processes/pressure.js +2 -2
  190. package/build/lib-common/processes/synced.js +1 -1
  191. package/build/lib-common/processes/timeout.js +2 -2
  192. package/build/lib-common/random-node.js +7 -7
  193. package/build/lib-common/service-api/3nstorage/owner.d.ts +101 -42
  194. package/build/lib-common/service-api/3nstorage/owner.js +83 -40
  195. package/build/lib-common/service-api/asmail/delivery.js +2 -2
  196. package/build/lib-common/service-api/asmail/retrieval.js +1 -1
  197. package/build/lib-common/timed-cache.d.ts +1 -0
  198. package/build/lib-common/timed-non-weak-cache.d.ts +1 -0
  199. package/build/lib-common/timed-non-weak-cache.js +11 -0
  200. package/build/lib-common/utils-for-observables.js +4 -4
  201. package/build/lib-common/weak-cache.d.ts +1 -0
  202. package/build/lib-common/weak-cache.js +12 -1
  203. package/build/lib-index.d.ts +2 -1
  204. package/build/lib-index.js +10 -7
  205. package/build/protos/asmail.proto.js +12955 -7496
  206. package/build/protos/file.proto.js +4867 -2744
  207. package/build/protos/fs.proto.js +9227 -3768
  208. package/package.json +7 -5
  209. package/protos/file.proto +91 -19
  210. package/protos/fs.proto +107 -8
  211. package/build/core/id-manager.d.ts +0 -46
@@ -24,9 +24,11 @@ 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");
31
+ const exceptions_1 = require("../exceptions");
30
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");
@@ -34,9 +36,11 @@ 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");
40
+ const for_arrays_1 = require("../util/for-arrays");
37
41
  function jsonToInfoAndAttrs(json) {
38
42
  const folderInfo = {
39
- nodes: json_utils_1.copy(json.nodes)
43
+ nodes: (0, json_utils_1.copy)(json.nodes)
40
44
  };
41
45
  for (const node of Object.values(folderInfo.nodes)) {
42
46
  node.key = buffer_utils_1.base64.open(node.key);
@@ -51,14 +55,22 @@ class FolderPersistance extends node_persistence_1.NodePersistance {
51
55
  Object.seal(this);
52
56
  }
53
57
  async write(folderInfo, version, attrs, xattrs) {
54
- const bytes = folder_node_serialization_1.serializeFolderInfo(folderInfo);
58
+ const bytes = (0, folder_node_serialization_1.serializeFolderInfo)(folderInfo);
55
59
  return this.writeWhole(bytes, version, attrs, xattrs);
56
60
  }
57
61
  async read(src) {
58
62
  const { content, xattrs, attrs } = await this.readAll(src);
59
- const folderInfo = folder_node_serialization_1.parseFolderInfo(content);
63
+ const folderInfo = (0, folder_node_serialization_1.parseFolderInfo)(content);
60
64
  return { folderInfo, xattrs, attrs: attrs_1.CommonAttrs.fromAttrs(attrs) };
61
65
  }
66
+ static async readFolderContent(objId, key, src, cryptor) {
67
+ if (objId === null) {
68
+ throw new Error("Missing objId for non-root folder");
69
+ }
70
+ const zNonce = (0, xsp_files_1.idToHeaderNonce)(objId);
71
+ const { folderInfo } = await (new FolderPersistance(zNonce, key, cryptor)).read(src);
72
+ return folderInfo;
73
+ }
62
74
  }
63
75
  Object.freeze(FolderPersistance.prototype);
64
76
  Object.freeze(FolderPersistance);
@@ -77,18 +89,11 @@ class FolderNode extends node_in_fs_1.NodeInFS {
77
89
  constructor(storage, name, objId, zNonce, version, parentId, key, setNewAttrs) {
78
90
  super(storage, 'folder', name, objId, version, parentId);
79
91
  this.currentState = { nodes: {} };
80
- if (!name && (objId || parentId)) {
81
- throw new Error("Root folder must " +
82
- "have both objId and parent as nulls.");
92
+ if (name === undefined) {
93
+ (0, assert_1.assert)(!objId && !parentId, `Root folder must have both objId and parent as nulls.`);
83
94
  }
84
95
  else if (objId === null) {
85
- new Error("Missing objId for non-root folder");
86
- }
87
- if (!zNonce) {
88
- if (!objId) {
89
- throw new Error(`Missing object id for folder, when zeroth nonce is not given`);
90
- }
91
- zNonce = xsp_files_1.idToHeaderNonce(objId);
96
+ throw new Error("Missing objId for non-root folder");
92
97
  }
93
98
  this.crypto = new FolderPersistance(zNonce, key, storage.cryptor);
94
99
  if (setNewAttrs) {
@@ -104,20 +109,21 @@ class FolderNode extends node_in_fs_1.NodeInFS {
104
109
  return rf;
105
110
  }
106
111
  static async rootFromObjBytes(storage, name, objId, src, key) {
107
- let zNonce = undefined;
108
- if (!objId) {
109
- const header = await src.readHeader();
110
- zNonce = xsp_files_1.calculateNonce(header.subarray(0, xsp_files_1.NONCE_LENGTH), -src.version);
111
- }
112
- const rf = await FolderNode.readNodeFromObjBytes(storage, name, objId, zNonce, src, key);
112
+ const rf = await FolderNode.readNodeFromObjBytes(storage, name, objId, src, key);
113
113
  rf.storage.nodes.set(rf);
114
114
  return rf;
115
115
  }
116
- static async readNodeFromObjBytes(storage, name, objId, zNonce, src, key) {
116
+ static async readNodeFromObjBytes(storage, name, objId, src, key) {
117
+ let zNonce;
118
+ if (objId) {
119
+ zNonce = (0, xsp_files_1.idToHeaderNonce)(objId);
120
+ }
121
+ else {
122
+ const header = await src.readHeader();
123
+ zNonce = (0, xsp_files_1.calculateNonce)(header.subarray(0, xsp_files_1.NONCE_LENGTH), -src.version);
124
+ }
117
125
  const rf = new FolderNode(storage, name, objId, zNonce, src.version, undefined, key, false);
118
- const { folderInfo, attrs, xattrs } = await rf.crypto.read(src);
119
- rf.currentState = folderInfo;
120
- rf.setUpdatedParams(src.version, attrs, xattrs);
126
+ await rf.setCurrentStateFrom(src);
121
127
  return rf;
122
128
  }
123
129
  static async rootFromLinkParams(storage, params) {
@@ -132,38 +138,112 @@ class FolderNode extends node_in_fs_1.NodeInFS {
132
138
  existingNode.crypto.compareKey(params.fKey);
133
139
  return existingNode;
134
140
  }
135
- const src = await storage.getObj(params.objId);
141
+ const src = await storage.getObjSrc(params.objId);
136
142
  const key = buffer_utils_1.base64.open(params.fKey);
137
143
  return FolderNode.rootFromObjBytes(storage, params.folderName, params.objId, src, key);
138
144
  }
139
145
  static rootFromJSON(storage, name, folderJson) {
140
- const rf = new FolderNode(storage, name, 'readonly-root', EMPTY_ARR, 0, undefined, undefined, false);
146
+ const rf = new FolderNode(storage, name, 'readonly-root', EMPTY_ARR, 0, undefined, EMPTY_ARR, false);
141
147
  const { folderInfo, attrs, xattrs } = jsonToInfoAndAttrs(folderJson);
142
148
  rf.currentState = folderInfo;
143
149
  rf.setUpdatedParams(0, attrs, xattrs);
144
150
  return rf;
145
151
  }
146
- list() {
147
- const names = Object.keys(this.currentState.nodes);
148
- const lst = new Array(names.length);
149
- for (let i = 0; i < names.length; i += 1) {
150
- const entity = this.currentState.nodes[names[i]];
151
- const info = { name: entity.name };
152
- if (entity.isFolder) {
153
- info.isFolder = true;
154
- }
155
- else if (entity.isFile) {
156
- info.isFile = true;
152
+ async setCurrentStateFrom(src) {
153
+ const { folderInfo, attrs, xattrs } = await this.crypto.read(src);
154
+ this.currentState = folderInfo;
155
+ this.setUpdatedParams(src.version, attrs, xattrs);
156
+ }
157
+ async adoptRemote(opts) {
158
+ const objsToRm = await this.doChange(true, async () => {
159
+ try {
160
+ const adopted = await this.syncedStorage().adoptRemote(this.objId, opts);
161
+ if (!adopted) {
162
+ return;
163
+ }
164
+ const src = await this.storage.getObjSrc(this.objId, adopted);
165
+ const originalState = this.currentState;
166
+ await this.setCurrentStateFrom(src);
167
+ const newState = this.currentState;
168
+ const { renamedNodes, addedNodes, removedNodes } = identifyChanges(originalState.nodes, newState.nodes);
169
+ for (const node of removedNodes) {
170
+ const event = {
171
+ type: 'entry-removal',
172
+ src: 'sync',
173
+ path: this.name,
174
+ newVersion: this.version,
175
+ name: node.name
176
+ };
177
+ this.broadcastEvent(event, false, node.objId);
178
+ }
179
+ for (const node of addedNodes) {
180
+ const event = {
181
+ type: 'entry-addition',
182
+ src: 'sync',
183
+ entry: nodeInfoToListingEntry(node),
184
+ path: this.name,
185
+ newVersion: this.version
186
+ };
187
+ this.broadcastEvent(event, false, node.objId);
188
+ }
189
+ for (const { objId, oldName, newName } of renamedNodes) {
190
+ const event = {
191
+ type: 'entry-renaming',
192
+ src: 'sync',
193
+ oldName,
194
+ newName,
195
+ path: this.name,
196
+ newVersion: this.version
197
+ };
198
+ this.broadcastEvent(event, false, objId);
199
+ }
200
+ return removedNodes;
157
201
  }
158
- else if (entity.isLink) {
159
- info.isLink = true;
202
+ catch (exc) {
203
+ throw (0, common_1.setPathInExc)(exc, this.name);
160
204
  }
161
- lst[i] = info;
205
+ });
206
+ if (!objsToRm) {
207
+ return;
208
+ }
209
+ for (const objToRm of objsToRm) {
210
+ const nodeToRm = await this.getOrMakeChildNodeForInfo(objToRm);
211
+ this.callRemoveObjOn('sync', nodeToRm);
212
+ }
213
+ }
214
+ callRemoveObjOn(src, node) {
215
+ if (node.type === 'folder') {
216
+ node.removeFolderObj(src);
162
217
  }
218
+ else {
219
+ node.removeNonFolderObj(src);
220
+ }
221
+ }
222
+ list() {
223
+ const lst = Object.values(this.currentState.nodes)
224
+ .map(nodeInfoToListingEntry);
163
225
  return { lst, version: this.version };
164
226
  }
165
- listFolders() {
166
- return Object.keys(this.currentState.nodes).filter(name => !!this.currentState.nodes[name].isFolder);
227
+ async childExistsInSyncedVersion(childObjId) {
228
+ const { state, synced } = await this.syncStatus();
229
+ if (state === 'synced') {
230
+ return true;
231
+ }
232
+ if (!(synced === null || synced === void 0 ? void 0 : synced.latest)) {
233
+ return false;
234
+ }
235
+ const storage = this.syncedStorage();
236
+ try {
237
+ const syncedContent = await storage.getObjSrc(this.objId, synced.latest);
238
+ const { folderInfo } = await this.crypto.read(syncedContent);
239
+ const childIsInSynced = !!Object.values(folderInfo.nodes)
240
+ .find(({ objId }) => (objId === childObjId));
241
+ return childIsInSynced;
242
+ }
243
+ catch (exc) {
244
+ storage.logError(exc, `Exception thrown in checking whether child node's obj is present in synced version.`);
245
+ return false;
246
+ }
167
247
  }
168
248
  getNodeInfo(name, undefOnMissing = false) {
169
249
  const fj = this.currentState.nodes[name];
@@ -174,30 +254,12 @@ class FolderNode extends node_in_fs_1.NodeInFS {
174
254
  return;
175
255
  }
176
256
  else {
177
- throw file_1.makeFileException(file_1.Code.notFound, name);
257
+ throw (0, file_1.makeFileException)('notFound', name);
178
258
  }
179
259
  }
180
260
  hasChild(childName, throwIfMissing = false) {
181
261
  return !!this.getNodeInfo(childName, !throwIfMissing);
182
262
  }
183
- /**
184
- * @param objId
185
- * @return either node (promise for node), or a deferred, which promise has
186
- * been registered under a given id, and, therefore, has to be resolved with
187
- * node.
188
- */
189
- getNodeOrArrangePromise(objId) {
190
- const { node, nodePromise } = this.storage.nodes.getNodeOrPromise(objId);
191
- if (node) {
192
- return { nodeOrPromise: node };
193
- }
194
- if (nodePromise) {
195
- return { nodeOrPromise: nodePromise };
196
- }
197
- const deferred = deferred_1.defer();
198
- this.storage.nodes.setPromise(objId, deferred.promise);
199
- return { deferred };
200
- }
201
263
  async getNode(type, name, undefOnMissing = false) {
202
264
  const childInfo = this.getNodeInfo(name, undefOnMissing);
203
265
  if (!childInfo) {
@@ -205,36 +267,38 @@ class FolderNode extends node_in_fs_1.NodeInFS {
205
267
  }
206
268
  if (type) {
207
269
  if ((type === 'file') && !childInfo.isFile) {
208
- throw file_1.makeFileException(file_1.Code.notFile, childInfo.name);
270
+ throw (0, file_1.makeFileException)('notFile', childInfo.name);
209
271
  }
210
272
  else if ((type === 'folder') && !childInfo.isFolder) {
211
- throw file_1.makeFileException(file_1.Code.notDirectory, childInfo.name);
273
+ throw (0, file_1.makeFileException)('notDirectory', childInfo.name);
212
274
  }
213
275
  else if ((type === 'link') && !childInfo.isLink) {
214
- throw file_1.makeFileException(file_1.Code.notLink, childInfo.name);
276
+ throw (0, file_1.makeFileException)('notLink', childInfo.name);
215
277
  }
216
278
  }
217
- const { nodeOrPromise: child, deferred } = this.getNodeOrArrangePromise(childInfo.objId);
218
- if (child) {
219
- return child;
279
+ return this.getOrMakeChildNodeForInfo(childInfo);
280
+ }
281
+ async getOrMakeChildNodeForInfo(info) {
282
+ const { node, nodePromise } = this.storage.nodes.getNodeOrPromise(info.objId);
283
+ if (node) {
284
+ return node;
285
+ }
286
+ if (nodePromise) {
287
+ return nodePromise;
220
288
  }
289
+ const deferred = (0, deferred_1.defer)();
290
+ this.storage.nodes.setPromise(info.objId, deferred.promise);
221
291
  try {
222
292
  let node;
223
- if (childInfo.isFile) {
224
- node = await file_node_1.FileNode.makeForExisting(this.storage, this.objId, name, childInfo.objId, childInfo.key);
225
- }
226
- else if (childInfo.isFolder) {
227
- const src = await this.storage.getObj(childInfo.objId);
228
- // const f = new FolderNode(
229
- // this.storage, childInfo.name, childInfo.objId, undefined,
230
- // src.version, this.objId, childInfo.key, false);
231
- // const { folderInfo, attrs } = await f.crypto.open(src);
232
- // f.currentState = folderInfo;
233
- // f.attrs = (attrs ? attrs : AttrsHolder.makeReadonlyForFolder(0));
234
- node = await FolderNode.readNodeFromObjBytes(this.storage, childInfo.name, childInfo.objId, undefined, src, childInfo.key);
235
- }
236
- else if (childInfo.isLink) {
237
- node = await link_node_1.LinkNode.makeForExisting(this.storage, this.objId, name, childInfo.objId, childInfo.key);
293
+ if (info.isFile) {
294
+ node = await file_node_1.FileNode.makeForExisting(this.storage, this.objId, info.name, info.objId, info.key);
295
+ }
296
+ else if (info.isFolder) {
297
+ const src = await this.storage.getObjSrc(info.objId);
298
+ node = await FolderNode.readNodeFromObjBytes(this.storage, info.name, info.objId, src, info.key);
299
+ }
300
+ else if (info.isLink) {
301
+ node = await link_node_1.LinkNode.makeForExisting(this.storage, this.objId, info.name, info.objId, info.key);
238
302
  }
239
303
  else {
240
304
  throw new Error(`Unknown type of fs node`);
@@ -245,9 +309,9 @@ class FolderNode extends node_in_fs_1.NodeInFS {
245
309
  catch (exc) {
246
310
  deferred.reject(exc);
247
311
  if (exc.objNotFound) {
248
- await this.fixMissingChildAndThrow(exc, childInfo);
312
+ await this.fixMissingChildAndThrow(exc, info);
249
313
  }
250
- throw error_1.errWithCause(exc, `Cannot instantiate ${type} node '${this.name}/${childInfo.name}' from obj ${childInfo.objId}`);
314
+ throw (0, error_1.errWithCause)(exc, `Failed to instantiate fs node '${this.name}/${info.name}'`);
251
315
  }
252
316
  }
253
317
  getFolder(name, undefOnMissing = false) {
@@ -261,16 +325,21 @@ class FolderNode extends node_in_fs_1.NodeInFS {
261
325
  }
262
326
  async fixMissingChildAndThrow(exc, childInfo) {
263
327
  await this.doTransition(async (state, version) => {
328
+ const presentChild = state.nodes[childInfo.name];
329
+ if (!presentChild || (presentChild.objId !== childInfo.objId)) {
330
+ return [];
331
+ }
264
332
  delete state.nodes[childInfo.name];
265
333
  const event = {
266
334
  type: 'entry-removal',
267
335
  path: this.name,
336
+ src: 'local',
268
337
  name: childInfo.name,
269
338
  newVersion: version
270
339
  };
271
- return event;
340
+ return { event, childObjId: childInfo.objId };
272
341
  }).catch(noop);
273
- const fileExc = file_1.makeFileException(file_1.Code.notFound, childInfo.name, exc);
342
+ const fileExc = (0, file_1.makeFileException)('notFound', childInfo.name, exc);
274
343
  fileExc.inconsistentStateOfFS = true;
275
344
  throw fileExc;
276
345
  }
@@ -289,11 +358,11 @@ class FolderNode extends node_in_fs_1.NodeInFS {
289
358
  async performTransition(change) {
290
359
  // start transition and prepare transition state
291
360
  // Note on copy: byte arrays are not cloned
292
- const state = json_utils_1.copy(this.currentState);
361
+ const state = (0, json_utils_1.copy)(this.currentState);
293
362
  const version = this.version + 1;
294
363
  const attrs = this.attrs.copy();
295
364
  // do action within transition state
296
- const event = await change(state, version);
365
+ const changeEvents = await change(state, version);
297
366
  // save transition state
298
367
  const encSub = await this.crypto.write(state, version, attrs, this.xattrs);
299
368
  await this.storage.saveObj(this.objId, version, encSub);
@@ -301,7 +370,15 @@ class FolderNode extends node_in_fs_1.NodeInFS {
301
370
  this.currentState = state;
302
371
  this.setCurrentVersion(version);
303
372
  this.attrs = attrs;
304
- this.broadcastEvent(event);
373
+ if (Array.isArray(changeEvents)) {
374
+ for (const { event, childObjId } of changeEvents) {
375
+ this.broadcastEvent(event, false, childObjId);
376
+ }
377
+ }
378
+ else {
379
+ const { event, childObjId } = changeEvents;
380
+ this.broadcastEvent(event, false, childObjId);
381
+ }
305
382
  }
306
383
  /**
307
384
  * This function only creates folder node, but it doesn't insert it anywhere.
@@ -310,7 +387,7 @@ class FolderNode extends node_in_fs_1.NodeInFS {
310
387
  async makeAndSaveNewChildFolderNode(name, changes) {
311
388
  const key = await random.bytes(xsp_files_1.KEY_LENGTH);
312
389
  const childObjId = await this.storage.generateNewObjId();
313
- const node = new FolderNode(this.storage, name, childObjId, undefined, 0, this.objId, key, true);
390
+ const node = new FolderNode(this.storage, name, childObjId, (0, xsp_files_1.idToHeaderNonce)(childObjId), 0, this.objId, key, true);
314
391
  await node.saveFirstVersion(changes).catch((exc) => {
315
392
  if (!exc.objExists) {
316
393
  throw exc;
@@ -369,7 +446,7 @@ class FolderNode extends node_in_fs_1.NodeInFS {
369
446
  // do check for concurrent creation of a node
370
447
  if (this.getNodeInfo(name, true)) {
371
448
  if (exclusive) {
372
- throw file_1.makeFileException(file_1.Code.alreadyExists, name);
449
+ throw (0, file_1.makeFileException)('alreadyExists', name);
373
450
  }
374
451
  else if (type === 'folder') {
375
452
  return (await this.getNode('folder', name));
@@ -405,15 +482,11 @@ class FolderNode extends node_in_fs_1.NodeInFS {
405
482
  const event = {
406
483
  type: 'entry-addition',
407
484
  path: this.name,
485
+ src: 'local',
408
486
  newVersion: version,
409
- entry: {
410
- name: node.name,
411
- isFile: (node.type === 'file'),
412
- isFolder: (node.type === 'folder'),
413
- isLink: (node.type === 'link')
414
- }
487
+ entry: nodeToListingEntry(node)
415
488
  };
416
- return event;
489
+ return { event, childObjId: node.objId };
417
490
  });
418
491
  return node;
419
492
  });
@@ -437,15 +510,16 @@ class FolderNode extends node_in_fs_1.NodeInFS {
437
510
  const event = {
438
511
  type: 'entry-removal',
439
512
  path: this.name,
513
+ src: 'local',
440
514
  name: f.name,
441
515
  newVersion: version
442
516
  };
443
- return event;
517
+ return { event, childObjId: f.objId };
444
518
  });
445
519
  // explicitly do not wait on a result of child's delete, cause if it fails
446
520
  // we just get traceable garbage, yet, the rest of a live/non-deleted tree
447
521
  // stays consistent
448
- f.localDelete();
522
+ this.callRemoveObjOn('local', f);
449
523
  }
450
524
  changeChildName(initName, newName) {
451
525
  return this.doTransition(async (state, version) => {
@@ -460,16 +534,17 @@ class FolderNode extends node_in_fs_1.NodeInFS {
460
534
  const event = {
461
535
  type: 'entry-renaming',
462
536
  path: this.name,
537
+ src: 'local',
463
538
  newName,
464
539
  oldName: initName,
465
540
  newVersion: version
466
541
  };
467
- return event;
542
+ return { event, childObjId: child.objId };
468
543
  });
469
544
  }
470
545
  async moveChildTo(childName, dst, nameInDst) {
471
546
  if (dst.hasChild(nameInDst)) {
472
- throw file_1.makeFileException(file_1.Code.alreadyExists, nameInDst);
547
+ throw (0, file_1.makeFileException)('alreadyExists', nameInDst);
473
548
  }
474
549
  if (dst === this) {
475
550
  // In this case we only need to change child's name
@@ -480,40 +555,38 @@ class FolderNode extends node_in_fs_1.NodeInFS {
480
555
  // we have two transitions here, in this and in dst.
481
556
  const moveLabel = getMoveLabel();
482
557
  await dst.moveChildIn(nameInDst, childJSON, moveLabel);
483
- await this.moveChildOut(childName, moveLabel);
558
+ await this.moveChildOut(childName, childJSON.objId, moveLabel);
484
559
  }
485
560
  }
486
- async moveChildOut(name, moveLabel) {
561
+ async moveChildOut(name, childObjId, moveLabel) {
487
562
  await this.doTransition(async (state, version) => {
563
+ const child = state.nodes[name];
488
564
  delete state.nodes[name];
489
565
  const event = {
490
566
  type: 'entry-removal',
491
567
  path: this.name,
568
+ src: 'local',
492
569
  name,
493
570
  newVersion: version,
494
571
  moveLabel
495
572
  };
496
- return event;
573
+ return { event, childObjId };
497
574
  });
498
575
  }
499
576
  async moveChildIn(newName, child, moveLabel) {
500
- child = json_utils_1.copy(child);
577
+ child = (0, json_utils_1.copy)(child);
501
578
  await this.doTransition(async (state, version) => {
502
579
  child.name = newName;
503
580
  state.nodes[child.name] = child;
504
581
  const event = {
505
582
  type: 'entry-addition',
506
583
  path: this.name,
507
- entry: {
508
- name: child.name,
509
- isFile: child.isFile,
510
- isFolder: child.isFolder,
511
- isLink: child.isLink
512
- },
584
+ src: 'local',
585
+ entry: nodeInfoToListingEntry(child),
513
586
  newVersion: version,
514
587
  moveLabel
515
588
  };
516
- return event;
589
+ return { event, childObjId: child.objId };
517
590
  });
518
591
  }
519
592
  async getFolderInThisSubTree(path, createIfMissing = false, exclusiveCreate = false) {
@@ -526,7 +599,7 @@ class FolderNode extends node_in_fs_1.NodeInFS {
526
599
  // existing folder at this point
527
600
  if (path.length === 1) {
528
601
  if (exclusiveCreate) {
529
- throw file_1.makeFileException(file_1.Code.alreadyExists, path[0]);
602
+ throw (0, file_1.makeFileException)('alreadyExists', path[0]);
530
603
  }
531
604
  else {
532
605
  return f;
@@ -583,28 +656,91 @@ class FolderNode extends node_in_fs_1.NodeInFS {
583
656
  }
584
657
  return content;
585
658
  }
586
- async delete(remoteEvent) {
587
- if (remoteEvent) {
588
- return super.delete(true);
659
+ async removeFolderObj(src, passIdsForRmUpload = false) {
660
+ const childrenNodes = await this.doChange(true, async () => {
661
+ const childrenNodes = await this.getAllNodes();
662
+ await this.removeThisNodeAsLeaf(src);
663
+ return childrenNodes;
664
+ });
665
+ if (childrenNodes.length === 0) {
666
+ return;
589
667
  }
590
- const childrenNodes = await this.getAllNodes();
591
- await this.storage.removeObj(this.objId);
592
- this.storage.nodes.delete(this);
593
- this.setCurrentVersion(-1);
594
- this.currentState = { nodes: {} };
595
- const event = {
596
- type: 'removed',
597
- path: this.name,
598
- isRemote: remoteEvent
599
- };
600
- this.broadcastEvent(event, true);
601
- // explicitly do not wait on a result of child's delete, cause if it fails
602
- // we just get traceable garbage, yet, the rest of a live/non-deleted tree
603
- // stays consistent
604
- for (const node of childrenNodes) {
605
- node.localDelete();
668
+ if (this.isInSyncedStorage) {
669
+ if (passIdsForRmUpload) {
670
+ return this.removeChildrenObjsInSyncedStorage(childrenNodes, src, true);
671
+ }
672
+ else {
673
+ this.removeChildrenObjsInSyncedStorage(childrenNodes, src, false);
674
+ }
675
+ }
676
+ else {
677
+ this.callRemoveObjOnAll(src, childrenNodes);
606
678
  }
607
679
  }
680
+ callRemoveObjOnAll(src, nodes) {
681
+ for (const f of nodes) {
682
+ this.callRemoveObjOn(src, f);
683
+ }
684
+ }
685
+ async removeChildrenObjsInSyncedStorage(childrenNodes, src, passIdsForRmUpload) {
686
+ let uploadRmsHere;
687
+ let collectedIds;
688
+ if (passIdsForRmUpload) {
689
+ collectedIds = [];
690
+ uploadRmsHere = false;
691
+ }
692
+ else {
693
+ try {
694
+ const status = await this.syncedStorage().status(this.objId);
695
+ if (status.neverUploaded()) {
696
+ collectedIds = [];
697
+ uploadRmsHere = true;
698
+ }
699
+ else {
700
+ collectedIds = undefined;
701
+ uploadRmsHere = false;
702
+ }
703
+ }
704
+ catch (exc) {
705
+ if (exc.objNotFound) {
706
+ collectedIds = [];
707
+ uploadRmsHere = true;
708
+ }
709
+ else {
710
+ throw exc;
711
+ }
712
+ }
713
+ }
714
+ if (!collectedIds) {
715
+ this.callRemoveObjOnAll(src, childrenNodes);
716
+ return;
717
+ }
718
+ await Promise.allSettled(childrenNodes.map(async (child) => {
719
+ const status = await this.syncedStorage().status(child.objId);
720
+ if (!status.neverUploaded()) {
721
+ collectedIds.push(child.objId);
722
+ }
723
+ if (child.type === 'folder') {
724
+ const ids = await child.removeFolderObj(src, passIdsForRmUpload);
725
+ if (ids) {
726
+ collectedIds.push(...ids);
727
+ }
728
+ }
729
+ else {
730
+ await child.removeNonFolderObj(src);
731
+ }
732
+ }));
733
+ if (uploadRmsHere) {
734
+ await this.uploadRemovalOfObjs(collectedIds);
735
+ }
736
+ else {
737
+ return collectedIds;
738
+ }
739
+ }
740
+ async uploadRemovalOfObjs(objsToRm) {
741
+ objsToRm.sort(); // shuffle order
742
+ await Promise.allSettled(objsToRm.map(objToRm => this.syncedStorage().uploadObjRemoval(objToRm)));
743
+ }
608
744
  getParamsForLink() {
609
745
  if ((this.storage.type !== 'synced') && (this.storage.type !== 'local')) {
610
746
  throw new Error(`Creating link parameters to object in ${this.storage.type} file system, is not implemented.`);
@@ -621,77 +757,270 @@ class FolderNode extends node_in_fs_1.NodeInFS {
621
757
  };
622
758
  return linkParams;
623
759
  }
624
- // XXX make default conflict resolution for folder
625
- // async resolveConflict(remoteVersion: number): Promise<void> {
626
- // // XXX we need to read remote version outside of doChange
627
- // const src = await (this.storage as SyncedStorage).getSyncedObjVersion(
628
- // this.objId, remoteVersion);
629
- // const remInfo = await this.crypto.open(src);
630
- // await this.doChange(true, async () => {
631
- // // XXX this is a default conflict resolution
632
- // // XXX drop all previous local versions(!)
633
- // });
634
- // }
635
- async doOnExternalChange(expectedVersion) {
636
- if (expectedVersion <= this.version) {
637
- return;
760
+ async upload(opts) {
761
+ try {
762
+ const toUpload = await this.needUpload(opts === null || opts === void 0 ? void 0 : opts.localVersion);
763
+ if (!toUpload) {
764
+ return;
765
+ }
766
+ if (toUpload.createOnRemote) {
767
+ await super.upload(opts);
768
+ return;
769
+ }
770
+ const storage = this.syncedStorage();
771
+ const { localVersion, uploadVersion } = toUpload;
772
+ const removedNodes = await this.getNodesRemovedBetweenVersions(localVersion, uploadVersion - 1);
773
+ const uploadHeader = await this.uploadHeaderChange(localVersion, uploadVersion);
774
+ await storage.upload(this.objId, localVersion, uploadVersion, uploadHeader, false);
775
+ if (removedNodes.length > 0) {
776
+ // start upload of children's removal after folder's upload;
777
+ // we also don't await for chidren removal
778
+ this.uploadRemovalOf(removedNodes);
779
+ }
638
780
  }
639
- const src = await this.storage.getObj(this.objId);
640
- const newVersion = src.version;
641
- if (newVersion <= this.version) {
781
+ catch (exc) {
782
+ throw (0, common_1.setPathInExc)(exc, this.name);
783
+ }
784
+ }
785
+ async uploadRemovalOf(removedNodes) {
786
+ const storage = this.syncedStorage();
787
+ const rmObjs = [];
788
+ for (const node of removedNodes) {
789
+ (0, for_arrays_1.appendArray)(rmObjs, await this.listRemovedInTreeToUploadRm(node));
790
+ }
791
+ await this.uploadRemovalOfObjs(rmObjs);
792
+ }
793
+ async listRemovedInTreeToUploadRm(node) {
794
+ const storage = this.syncedStorage();
795
+ let versionBeforeRm;
796
+ try {
797
+ const status = await storage.status(node.objId);
798
+ versionBeforeRm = status.versionBeforeUnsyncedRemoval();
799
+ if (!versionBeforeRm) {
800
+ return;
801
+ }
802
+ }
803
+ catch (exc) {
804
+ if (exc.objNotFound
805
+ || exc.notFound) {
806
+ return;
807
+ }
808
+ throw exc;
809
+ }
810
+ if (node.isFolder) {
811
+ const objs = [node.objId];
812
+ const src = await storage.getObjSrc(node.objId, versionBeforeRm, true);
813
+ const folderContent = await FolderPersistance.readFolderContent(node.objId, node.key, src, storage.cryptor);
814
+ for (const child of Object.values(folderContent.nodes)) {
815
+ (0, for_arrays_1.appendArray)(objs, await this.listRemovedInTreeToUploadRm(child));
816
+ }
817
+ return objs;
818
+ }
819
+ else {
820
+ return node.objId;
821
+ }
822
+ }
823
+ async needUpload(localVersion) {
824
+ const toUpload = await super.needUpload(localVersion);
825
+ if (!toUpload) {
642
826
  return;
643
827
  }
644
- const { folderInfo, attrs } = await this.crypto.read(src);
645
- const initState = this.currentState;
646
- this.currentState = checkFolderInfo(folderInfo);
647
- this.setCurrentVersion(newVersion);
648
- if (attrs) {
649
- this.attrs = attrs;
650
- }
651
- const addedEntries = Object.keys(this.currentState.nodes)
652
- .filter(name => !initState.nodes[name]);
653
- const removedEntries = Object.keys(initState.nodes)
654
- .filter(name => !this.currentState.nodes[name]);
655
- if ((addedEntries.length === 1) && (removedEntries.length === 1)) {
828
+ const storage = this.syncedStorage();
829
+ if (toUpload.localVersion === this.version) {
830
+ for (const { objId, name } of Object.values(this.currentState.nodes)) {
831
+ if ((await storage.status(objId)).neverUploaded()) {
832
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
833
+ childNeverUploaded: true, childName: name
834
+ });
835
+ }
836
+ }
837
+ }
838
+ else {
839
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
840
+ versionMismatch: true,
841
+ localVersion: toUpload.localVersion,
842
+ message: `Local version has concurrently changed from ${toUpload.localVersion} to current ${this.version}`
843
+ });
844
+ }
845
+ return toUpload;
846
+ }
847
+ async getNodesRemovedBetweenVersions(newVer, syncedVer) {
848
+ const storage = this.syncedStorage();
849
+ let stateToUpload;
850
+ if (this.version === newVer) {
851
+ stateToUpload = this.currentState;
852
+ }
853
+ else {
854
+ const localVerSrc = await storage.getObjSrcOfRemoteVersion(this.objId, newVer);
855
+ ({ folderInfo: stateToUpload } = await this.crypto.read(localVerSrc));
856
+ }
857
+ const syncedVerSrc = await storage.getObjSrcOfRemoteVersion(this.objId, syncedVer);
858
+ const { folderInfo: syncedState } = await this.crypto.read(syncedVerSrc);
859
+ const { removedNodes } = identifyChanges(syncedState.nodes, stateToUpload.nodes);
860
+ return removedNodes;
861
+ }
862
+ async adoptRemoteFolderItem(itemName, opts) {
863
+ if (opts === null || opts === void 0 ? void 0 : opts.localVersion) {
864
+ if (this.version !== opts.localVersion) {
865
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
866
+ versionMismatch: true,
867
+ message: `Given local version ${opts.localVersion} is not equal to current ${this.version}`
868
+ });
869
+ }
870
+ }
871
+ const { remote } = await this.syncStatus();
872
+ if (!remote) {
873
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
874
+ versionMismatch: true,
875
+ message: `No remote versions`
876
+ });
877
+ }
878
+ if (opts === null || opts === void 0 ? void 0 : opts.remoteVersion) {
879
+ if (opts.remoteVersion !== remote.latest) {
880
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
881
+ versionMismatch: true,
882
+ message: `Unknown remote version ${opts.remoteVersion}`
883
+ });
884
+ }
885
+ }
886
+ else if (!remote.latest) {
887
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
888
+ remoteIsArchived: true
889
+ });
890
+ }
891
+ const storage = this.syncedStorage();
892
+ const srcOfRemote = await storage.getObjSrcOfRemoteVersion(this.objId, remote.latest);
893
+ const { folderInfo: { nodes: remoteNodes } } = await this.crypto.read(srcOfRemote);
894
+ const remoteChildNode = remoteNodes[itemName];
895
+ if (!remoteChildNode) {
896
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
897
+ remoteFolderItemNotFound: true,
898
+ message: `Item '${itemName}' is not found in remote version`
899
+ });
900
+ }
901
+ const localNodeToRm = this.getNodeInfo(itemName, true);
902
+ if (!localNodeToRm) {
903
+ return await this.addRemoteChild(remoteChildNode);
904
+ }
905
+ else if (localNodeToRm.objId === remoteChildNode.objId) {
906
+ (0, assert_1.assert)(typeOfNode(localNodeToRm) === typeOfNode(remoteChildNode));
907
+ // XXX
908
+ throw new Error(`Adaptation of changing keys needs implementation`);
909
+ }
910
+ else {
911
+ return await this.replaceLocalChildWithRemote(remoteChildNode);
912
+ }
913
+ }
914
+ async addRemoteChild(remoteChildNode) {
915
+ let newVersion;
916
+ await this.doTransition(async (state, version) => {
917
+ newVersion = version;
918
+ state.nodes[remoteChildNode.name] = remoteChildNode;
656
919
  const event = {
657
- type: 'entry-renaming',
920
+ type: 'entry-addition',
658
921
  path: this.name,
659
- isRemote: true,
660
- oldName: removedEntries[0],
661
- newName: addedEntries[0],
922
+ src: 'sync',
923
+ entry: nodeInfoToListingEntry(remoteChildNode),
662
924
  newVersion
663
925
  };
664
- this.broadcastEvent(event);
926
+ return { event, childObjId: remoteChildNode.objId };
927
+ });
928
+ return newVersion;
929
+ }
930
+ async replaceLocalChildWithRemote(remoteChildNode) {
931
+ let origChildNode;
932
+ let newVersion;
933
+ await this.doTransition(async (state, version) => {
934
+ newVersion = version;
935
+ origChildNode = state.nodes[remoteChildNode.name];
936
+ state.nodes[remoteChildNode.name] = remoteChildNode;
937
+ const additionEvent = {
938
+ type: 'entry-addition',
939
+ path: this.name,
940
+ src: 'sync',
941
+ entry: nodeInfoToListingEntry(remoteChildNode),
942
+ newVersion
943
+ };
944
+ const rmEvent = {
945
+ type: 'entry-removal',
946
+ path: this.name,
947
+ src: 'sync',
948
+ name: origChildNode.name
949
+ };
950
+ return [
951
+ { event: rmEvent, childObjId: origChildNode.objId },
952
+ { event: additionEvent, childObjId: remoteChildNode.objId }
953
+ ];
954
+ });
955
+ const origChild = await this.getOrMakeChildNodeForInfo(origChildNode);
956
+ const removalsToUpload = (await this.removeChildrenObjsInSyncedStorage([origChild], 'sync', true));
957
+ this.uploadRemovalOfObjs(removalsToUpload);
958
+ return newVersion;
959
+ }
960
+ async diffCurrentAndRemote(remoteVersion) {
961
+ var _a;
962
+ const { state, remote } = await this.syncStatus();
963
+ let isCurrentLocal;
964
+ if (state === 'behind') {
965
+ isCurrentLocal = false;
966
+ }
967
+ else if (state === 'conflicting') {
968
+ isCurrentLocal = true;
665
969
  }
666
970
  else {
667
- addedEntries.forEach(name => {
668
- const addedNode = this.currentState.nodes[name];
669
- const event = {
670
- type: 'entry-addition',
671
- path: this.name,
672
- isRemote: true,
673
- entry: {
674
- name: addedNode.name,
675
- isFile: addedNode.isFile,
676
- isFolder: addedNode.isFolder,
677
- isLink: addedNode.isLink
678
- },
679
- newVersion
680
- };
681
- this.broadcastEvent(event);
682
- });
683
- removedEntries.forEach(name => {
684
- const event = {
685
- type: 'entry-removal',
686
- path: this.name,
687
- isRemote: true,
688
- name,
689
- newVersion
690
- };
691
- this.broadcastEvent(event);
692
- });
971
+ return;
972
+ }
973
+ if (remote.isArchived) {
974
+ return this.diffWithArchivedRemote(isCurrentLocal);
975
+ }
976
+ else {
977
+ if (remoteVersion) {
978
+ if ((remoteVersion !== remote.latest)
979
+ && !((_a = remote.archived) === null || _a === void 0 ? void 0 : _a.includes(remoteVersion))) {
980
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
981
+ versionMismatch: true,
982
+ message: `Unknown remote version ${remoteVersion}`
983
+ });
984
+ }
985
+ }
986
+ else {
987
+ remoteVersion = remote.latest;
988
+ }
989
+ const storage = this.syncedStorage();
990
+ const srcOfRemote = await storage.getObjSrcOfRemoteVersion(this.objId, remote.latest);
991
+ const { folderInfo, attrs, xattrs } = await this.crypto.read(srcOfRemote);
992
+ return this.diffWithRemote(isCurrentLocal, remoteVersion, folderInfo, attrs, xattrs);
693
993
  }
694
994
  }
995
+ diffWithArchivedRemote(isCurrentLocal) {
996
+ return {
997
+ currentVersion: this.version,
998
+ isCurrentLocal,
999
+ isRemoteArchived: true,
1000
+ ctime: {
1001
+ current: new Date(this.attrs.ctime)
1002
+ },
1003
+ mtime: {
1004
+ current: new Date(this.attrs.ctime)
1005
+ }
1006
+ };
1007
+ }
1008
+ diffWithRemote(isCurrentLocal, remoteVersion, folderInfo, attrs, xattrs) {
1009
+ const { ctime, mtime } = diffAttrs(this.attrs, attrs);
1010
+ const { inCurrent, inRemote, nameOverlaps } = diffNodes(this.currentState, folderInfo);
1011
+ return {
1012
+ currentVersion: this.version,
1013
+ isCurrentLocal,
1014
+ isRemoteArchived: false,
1015
+ remoteVersion,
1016
+ inCurrent,
1017
+ inRemote,
1018
+ nameOverlaps,
1019
+ ctime,
1020
+ mtime,
1021
+ xattrs: diffXAttrs(this.xattrs, xattrs)
1022
+ };
1023
+ }
695
1024
  }
696
1025
  exports.FolderNode = FolderNode;
697
1026
  Object.freeze(FolderNode.prototype);
@@ -702,6 +1031,46 @@ function checkFolderInfo(folderJson) {
702
1031
  // TODO throw if folderJson is not ok
703
1032
  return folderJson;
704
1033
  }
1034
+ function nodeInfoToListingEntry({ name, isFile, isFolder, isLink }) {
1035
+ if (isFolder) {
1036
+ return { name, isFolder };
1037
+ }
1038
+ else if (isFile) {
1039
+ return { name, isFile };
1040
+ }
1041
+ else if (isLink) {
1042
+ return { name, isLink };
1043
+ }
1044
+ else {
1045
+ return { name };
1046
+ }
1047
+ }
1048
+ function nodeToListingEntry({ name, type }) {
1049
+ switch (type) {
1050
+ case 'file':
1051
+ return { name, isFolder: true };
1052
+ case 'folder':
1053
+ return { name, isFolder: true };
1054
+ case 'link':
1055
+ return { name, isLink: true };
1056
+ default:
1057
+ return { name };
1058
+ }
1059
+ }
1060
+ function typeOfNode(nodeInfo) {
1061
+ if (nodeInfo.isFolder) {
1062
+ return 'folder';
1063
+ }
1064
+ else if (nodeInfo.isFile) {
1065
+ return 'file';
1066
+ }
1067
+ else if (nodeInfo.isLink) {
1068
+ return 'link';
1069
+ }
1070
+ else {
1071
+ throw new Error(`Unknown type of node info`);
1072
+ }
1073
+ }
705
1074
  function addToTransitionState(state, f, key) {
706
1075
  const nodeInfo = {
707
1076
  name: f.name,
@@ -722,4 +1091,195 @@ function addToTransitionState(state, f, key) {
722
1091
  }
723
1092
  state.nodes[nodeInfo.name] = nodeInfo;
724
1093
  }
1094
+ function diffAttrs(current, remote) {
1095
+ return {
1096
+ ctime: {
1097
+ current: new Date(current.ctime),
1098
+ remote: new Date(remote.ctime)
1099
+ },
1100
+ mtime: {
1101
+ current: new Date(current.mtime),
1102
+ remote: new Date(remote.mtime)
1103
+ }
1104
+ };
1105
+ }
1106
+ function diffNodes(current, remote) {
1107
+ const inCurrent = getOnlyNotEqualNodes(current.nodes, remote.nodes);
1108
+ const inRemote = getOnlyNotEqualNodes(remote.nodes, current.nodes);
1109
+ const combined = combineCheckingNameOverlaps(inCurrent, inRemote);
1110
+ return (combined ? combined : {});
1111
+ }
1112
+ function getOnlyNotEqualNodes(src, exclude) {
1113
+ const collected = [];
1114
+ for (const [name, nodeInSrc] of Object.entries(src)) {
1115
+ const nodeInExclude = exclude[name];
1116
+ if (!nodeInExclude
1117
+ || !areNodesEqual(nodeInSrc, nodeInExclude)) {
1118
+ collected.push(nodeInfoToListingEntry(nodeInSrc));
1119
+ }
1120
+ }
1121
+ return ((collected.length > 0) ? collected : undefined);
1122
+ }
1123
+ function areNodesEqual(n1, n2) {
1124
+ if (n1.name !== n2.name) {
1125
+ return false;
1126
+ }
1127
+ if (n1.isFile && !n2.isFile) {
1128
+ return false;
1129
+ }
1130
+ if (n1.isFolder && !n2.isFolder) {
1131
+ return false;
1132
+ }
1133
+ if (n1.isLink && !n2.isLink) {
1134
+ return false;
1135
+ }
1136
+ if (n1.objId !== n2.objId) {
1137
+ return false;
1138
+ }
1139
+ if (!areBytesEqual(n1.key, n2.key)) {
1140
+ return false;
1141
+ }
1142
+ return true;
1143
+ }
1144
+ function combineCheckingNameOverlaps(inCurrent, inRemote) {
1145
+ if (inCurrent) {
1146
+ if (inRemote) {
1147
+ const nameOverlaps = [];
1148
+ for (const { name: nameInCurrent } of inCurrent) {
1149
+ if (inRemote.find(({ name }) => (name === nameInCurrent))) {
1150
+ nameOverlaps.push(nameInCurrent);
1151
+ }
1152
+ }
1153
+ if (nameOverlaps.length > 0) {
1154
+ return { inCurrent, inRemote, nameOverlaps };
1155
+ }
1156
+ else {
1157
+ return { inCurrent, inRemote };
1158
+ }
1159
+ }
1160
+ else {
1161
+ return { inCurrent };
1162
+ }
1163
+ }
1164
+ else {
1165
+ if (inRemote) {
1166
+ return { inRemote };
1167
+ }
1168
+ else {
1169
+ return;
1170
+ }
1171
+ }
1172
+ }
1173
+ function diffXAttrs(current, remote) {
1174
+ const haveCurrentXAttrs = (current && (current.list().length > 0));
1175
+ const haveRemoteXAttrs = (remote && (remote.list().length > 0));
1176
+ if (haveCurrentXAttrs && haveRemoteXAttrs) {
1177
+ const inCurrent = getOnlyNotEqualXAttrs(current, remote);
1178
+ const inRemote = getOnlyNotEqualXAttrs(remote, current);
1179
+ return combineCheckingNameOverlaps(inCurrent, inRemote);
1180
+ }
1181
+ else if (haveCurrentXAttrs && !haveRemoteXAttrs) {
1182
+ return { inCurrent: allXAttrsToDiff(current) };
1183
+ }
1184
+ else if (!haveCurrentXAttrs && haveRemoteXAttrs) {
1185
+ return { inRemote: allXAttrsToDiff(remote) };
1186
+ }
1187
+ else {
1188
+ return;
1189
+ }
1190
+ }
1191
+ function allXAttrsToDiff(src) {
1192
+ const collected = [];
1193
+ for (const name of src.list()) {
1194
+ const value = src.get(name);
1195
+ if (value !== undefined) {
1196
+ collected.push({ name, value });
1197
+ }
1198
+ }
1199
+ return collected;
1200
+ }
1201
+ function getOnlyNotEqualXAttrs(src, exclude) {
1202
+ const collected = [];
1203
+ for (const name of src.list()) {
1204
+ const valueInSrc = src.get(name);
1205
+ if (valueInSrc === undefined) {
1206
+ continue;
1207
+ }
1208
+ const valueInExclude = exclude.get(name);
1209
+ if ((valueInExclude === undefined)
1210
+ || !areXAttrValuesEqual(valueInSrc, valueInExclude)) {
1211
+ collected.push({ name, value: valueInSrc });
1212
+ }
1213
+ }
1214
+ return ((collected.length > 0) ? collected : undefined);
1215
+ }
1216
+ function areXAttrValuesEqual(v1, v2) {
1217
+ if (Buffer.isBuffer(v1) || ArrayBuffer.isView(v1)) {
1218
+ if (Buffer.isBuffer(v2) || ArrayBuffer.isView(v2)) {
1219
+ return areBytesEqual(v1, v2);
1220
+ }
1221
+ {
1222
+ return false;
1223
+ }
1224
+ }
1225
+ else if (typeof v1 === 'string') {
1226
+ if (typeof v2 === 'string') {
1227
+ return (v1 === v2);
1228
+ }
1229
+ else {
1230
+ return false;
1231
+ }
1232
+ }
1233
+ else {
1234
+ if (Buffer.isBuffer(v2) || ArrayBuffer.isView(v2)
1235
+ || (typeof v2 === 'string')) {
1236
+ return false;
1237
+ }
1238
+ else {
1239
+ return (0, json_utils_1.deepEqual)(v1, v2);
1240
+ }
1241
+ }
1242
+ }
1243
+ function areBytesEqual(b1, b2) {
1244
+ if (b1.length !== b2.length) {
1245
+ return false;
1246
+ }
1247
+ for (let i = 0; i < b1.length; i += 1) {
1248
+ if (b1[i] !== b2[i]) {
1249
+ return false;
1250
+ }
1251
+ }
1252
+ return true;
1253
+ }
1254
+ function identifyChanges(originalNodes, newNodes) {
1255
+ const removedNodes = [];
1256
+ const addedNodes = [];
1257
+ const renamedNodes = [];
1258
+ for (const [name, node] of Object.entries(originalNodes)) {
1259
+ const newNode = newNodes[name];
1260
+ if (newNode && (newNode.objId === node.objId)) {
1261
+ continue;
1262
+ }
1263
+ removedNodes.push(node);
1264
+ }
1265
+ for (const [name, newNode] of Object.entries(newNodes)) {
1266
+ const node = originalNodes[name];
1267
+ if (node && (newNode.objId === node.objId)) {
1268
+ continue;
1269
+ }
1270
+ const indInRm = removedNodes.findIndex(n => (n.objId === newNode.objId));
1271
+ if (indInRm < 0) {
1272
+ addedNodes.push(newNode);
1273
+ }
1274
+ else {
1275
+ const oldNode = removedNodes.splice(indInRm, 1)[0];
1276
+ renamedNodes.push({
1277
+ oldName: oldNode.name,
1278
+ newName: newNode.name,
1279
+ objId: newNode.objId
1280
+ });
1281
+ }
1282
+ }
1283
+ return { addedNodes, removedNodes, renamedNodes };
1284
+ }
725
1285
  Object.freeze(exports);