core-3nweb-client-lib 0.49.6 → 0.49.7

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 (79) hide show
  1. package/build/api-defs/files.d.ts +11 -5
  2. package/build/core/asmail/delivery/common.d.ts +8 -28
  3. package/build/core/asmail/delivery/common.js +1 -47
  4. package/build/core/asmail/delivery/index.js +3 -3
  5. package/build/core/asmail/delivery/per-recipient-wip.js +3 -2
  6. package/build/core/asmail/inbox/index.d.ts +1 -2
  7. package/build/core/asmail/inbox/index.js +14 -15
  8. package/build/core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.d.ts +11 -0
  9. package/build/core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.js +25 -0
  10. package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.d.ts +33 -0
  11. package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.js +72 -0
  12. package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.d.ts +28 -0
  13. package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.js +477 -0
  14. package/build/core/asmail/inbox/msg-indexing/index.d.ts +12 -26
  15. package/build/core/asmail/inbox/msg-indexing/index.js +47 -68
  16. package/build/core/asmail/inbox/msg-on-disk.js +1 -2
  17. package/build/core/asmail/index.js +8 -7
  18. package/build/core/asmail/msg/common.d.ts +0 -1
  19. package/build/core/asmail/msg/common.js +1 -1
  20. package/build/core/index.js +7 -4
  21. package/build/core/keyring/common.d.ts +1 -0
  22. package/build/core/keyring/common.js +2 -1
  23. package/build/core/keyring/index.d.ts +7 -44
  24. package/build/core/keyring/index.js +89 -222
  25. package/build/core/keyring/introductory-keys/published-intro-key.d.ts +17 -0
  26. package/build/core/keyring/introductory-keys/published-intro-key.js +190 -0
  27. package/build/core/keyring/peer-keys/dataset/v1/json-files.d.ts +105 -0
  28. package/build/core/keyring/peer-keys/dataset/v1/json-files.js +89 -0
  29. package/build/core/keyring/peer-keys/dataset/v2/logs.d.ts +43 -0
  30. package/build/core/keyring/peer-keys/dataset/v2/logs.js +116 -0
  31. package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.d.ts +73 -0
  32. package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.js +543 -0
  33. package/build/core/keyring/peer-keys/index.d.ts +24 -0
  34. package/build/core/keyring/peer-keys/index.js +208 -0
  35. package/build/core/storage/synced/obj-files.js +10 -3
  36. package/build/core/storage/synced/obj-status.d.ts +1 -1
  37. package/build/core/storage/synced/obj-status.js +8 -2
  38. package/build/core/storage/synced/storage.d.ts +4 -4
  39. package/build/core/storage/system-folders/apps-data.d.ts +0 -1
  40. package/build/core/storage/system-folders/apps-data.js +2 -6
  41. package/build/lib-client/fs-utils/fs-sync-utils.d.ts +1 -1
  42. package/build/lib-client/fs-utils/fs-sync-utils.js +70 -13
  43. package/build/lib-client/xsp-fs/fs.js +3 -1
  44. package/build/lib-client/xsp-fs/node-in-fs.d.ts +1 -0
  45. package/build/lib-client/xsp-fs/node-in-fs.js +7 -2
  46. package/build/lib-client/xsp-fs/node-persistence.js +1 -3
  47. package/build/lib-client/xsp-fs/xsp-payload-v2.js +1 -2
  48. package/build/lib-common/dataset-sync/per-device-json-array-logs.d.ts +58 -0
  49. package/build/lib-common/dataset-sync/per-device-json-array-logs.js +432 -0
  50. package/build/lib-common/number-line.d.ts +10 -0
  51. package/build/lib-common/number-line.js +66 -0
  52. package/build/lib-common/objs-on-disk/obj-version-file.d.ts +1 -1
  53. package/build/lib-common/objs-on-disk/obj-version-file.js +3 -3
  54. package/build/lib-common/rng-def.d.ts +1 -0
  55. package/build/lib-common/rng-def.js +8 -0
  56. package/build/lib-sqlite-on-3nstorage/for-sqlite.d.ts +38 -0
  57. package/build/lib-sqlite-on-3nstorage/for-sqlite.js +162 -0
  58. package/build/lib-sqlite-on-3nstorage/index.d.ts +18 -5
  59. package/build/lib-sqlite-on-3nstorage/index.js +49 -26
  60. package/build/tests/caps-api/file-sink-checks/splice.js +72 -1
  61. package/build/tests/caps-api/fs-checks/sync-on-one-dev/upload.js +27 -1
  62. package/build/tests/caps-api/fs-checks/sync-with-two-devs/update-propagation.js +12 -3
  63. package/build/tests/computer.3nweb.core/inbox/msg-indexing.js +16 -12
  64. package/build/tests/computer.3nweb.core/keyrings.js +113 -28
  65. package/build/tests/jasmine.js +2 -2
  66. package/build/tests/units/number-line.js +9 -9
  67. package/package.json +2 -1
  68. package/build/core/asmail/inbox/msg-indexing/logs-n-entries.d.ts +0 -52
  69. package/build/core/asmail/inbox/msg-indexing/logs-n-entries.js +0 -333
  70. package/build/core/asmail/inbox/msg-indexing/sql-indexing.d.ts +0 -4
  71. package/build/core/asmail/inbox/msg-indexing/sql-indexing.js +0 -319
  72. package/build/core/keyring/correspondent-keys.d.ts +0 -149
  73. package/build/core/keyring/correspondent-keys.js +0 -403
  74. package/build/core/keyring/id-to-email-map.d.ts +0 -38
  75. package/build/core/keyring/id-to-email-map.js +0 -105
  76. package/build/core/keyring/keyring-storage.d.ts +0 -9
  77. package/build/core/keyring/keyring-storage.js +0 -54
  78. package/build/core/keyring/published-intro-key.d.ts +0 -45
  79. package/build/core/keyring/published-intro-key.js +0 -200
@@ -0,0 +1,477 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (C) 2022 - 2023, 2025 - 2026 3NSoft Inc.
4
+
5
+ This program is free software: you can redistribute it and/or modify it under
6
+ the terms of the GNU General Public License as published by the Free Software
7
+ Foundation, either version 3 of the License, or (at your option) any later
8
+ version.
9
+
10
+ This program is distributed in the hope that it will be useful, but
11
+ WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
+ See the GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License along with
16
+ this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.makeSqliteDBs = makeSqliteDBs;
20
+ const timed_cache_1 = require("../../../../../../lib-common/timed-cache");
21
+ const lib_sqlite_on_3nstorage_1 = require("../../../../../../lib-sqlite-on-3nstorage");
22
+ const logs_1 = require("./logs");
23
+ const for_sqlite_1 = require("../../../../../../lib-sqlite-on-3nstorage/for-sqlite");
24
+ const sql_db_v_none_1 = require("../v0/sql-db-v-none");
25
+ const queryToCreateMsgsIndexDbV1 = [
26
+ `--sql
27
+ CREATE TABLE inbox_index (
28
+ msgId TEXT PRIMARY KEY,
29
+ msgType TEXT,
30
+ deliveryTS INTEGER,
31
+ key BLOB,
32
+ keyStatus TEXT,
33
+ mainObjHeaderOfs INTEGER,
34
+ removeAfter INTEGER DEFAULT 0
35
+ ) STRICT
36
+ `,
37
+ `--sql
38
+ CREATE INDEX IF NOT EXISTS delivery_ts ON inbox_index (
39
+ deliveryTS ASC
40
+ )
41
+ `,
42
+ ].join(';\n');
43
+ const msgsIndexFields = {
44
+ msgId: 'as-is',
45
+ msgType: 'as-is',
46
+ deliveryTS: 'as-is',
47
+ key: 'as-is',
48
+ keyStatus: 'as-is',
49
+ mainObjHeaderOfs: 'as-is'
50
+ };
51
+ function msgWhereParamsFor(msgId) {
52
+ const whereMsgParams = (0, for_sqlite_1.queryParamsFrom)({
53
+ msgId
54
+ }, msgsIndexFields);
55
+ const whereMsg = (0, for_sqlite_1.andEqualExprFor)(whereMsgParams);
56
+ return { whereMsgParams, whereMsg };
57
+ }
58
+ function listMsgInfos(db, fromTS) {
59
+ let result;
60
+ if (fromTS) {
61
+ result = db.exec(`--sql
62
+ SELECT msgId, msgType, deliveryTS FROM inbox_index
63
+ WHERE deliveryTS > $fromTS`, { '$fromTS': fromTS });
64
+ }
65
+ else {
66
+ result = db.exec(`--sql
67
+ SELECT msgId, msgType, deliveryTS FROM inbox_index`);
68
+ }
69
+ return ((result.length > 0) ? (0, for_sqlite_1.fromQueryResult)(result[0], msgsIndexFields) : []);
70
+ }
71
+ function deleteMsgFrom(db, msgId) {
72
+ const { whereMsg, whereMsgParams } = msgWhereParamsFor(msgId);
73
+ db.exec(`--sql
74
+ DELETE FROM inbox_index WHERE ${whereMsg}`, whereMsgParams);
75
+ return (db.getRowsModified() > 0);
76
+ }
77
+ function deleteAllMsgsAfterTS(db, ts) {
78
+ db.exec(`--sql
79
+ DELETE FROM inbox_index WHERE deliveryTS > $ts
80
+ `, { '$ts': ts });
81
+ }
82
+ function deleteAllMsgsUpToAndAtTS(db, ts) {
83
+ db.exec(`--sql
84
+ DELETE FROM inbox_index WHERE deliveryTS <= $ts
85
+ `, { '$ts': ts });
86
+ }
87
+ function findMsgAndGetDeliveryTS(db, msgId) {
88
+ const { whereMsg, whereMsgParams } = msgWhereParamsFor(msgId);
89
+ const result = db.exec(`--sql
90
+ SELECT deliveryTS FROM inbox_index WHERE ${whereMsg}`, whereMsgParams)[0];
91
+ if (result) {
92
+ const values = (0, for_sqlite_1.fromQueryResult)(result, msgsIndexFields);
93
+ return ((values.length > 0) ? values[0].deliveryTS : undefined);
94
+ }
95
+ }
96
+ function countUpToTS(db, tillTS) {
97
+ const result = db.exec(`--sql
98
+ SELECT count() FROM inbox_index WHERE deliveryTS <= $tillTS`, { '$tillTS': tillTS });
99
+ return result[0].values[0][0];
100
+ }
101
+ function findMsgKey(db, msgId) {
102
+ const { whereMsg, whereMsgParams } = msgWhereParamsFor(msgId);
103
+ const result = db.exec(`--sql
104
+ SELECT key, keyStatus, mainObjHeaderOfs FROM inbox_index WHERE ${whereMsg}`, whereMsgParams);
105
+ if (result.length > 0) {
106
+ const { key: msgKey, keyStatus: msgKeyRole, mainObjHeaderOfs } = (0, for_sqlite_1.fromQueryResult)(result[0], msgsIndexFields)[0];
107
+ return { msgKey, msgKeyRole, mainObjHeaderOfs };
108
+ }
109
+ else {
110
+ return;
111
+ }
112
+ }
113
+ function isMsgPresent(db, msgId) {
114
+ const { whereMsg, whereMsgParams } = msgWhereParamsFor(msgId);
115
+ const result = db.exec(`--sql
116
+ SELECT msgId FROM inbox_index WHERE ${whereMsg}`, whereMsgParams);
117
+ return (result.length > 0);
118
+ }
119
+ async function readOrInitializeLatestDB(dbsFS) {
120
+ if (await dbsFS.checkFilePresence(LATEST_DB)) {
121
+ const dbFile = await dbsFS.writableFile(LATEST_DB, { create: false });
122
+ return await readDbAndUpgradeVersionIfNeeded(dbFile);
123
+ }
124
+ else {
125
+ const dbFile = await dbsFS.writableFile(LATEST_DB, { create: true, exclusive: true });
126
+ const latest = await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeSynced(dbFile);
127
+ latest.db.run(queryToCreateMsgsIndexDbV1);
128
+ await setMsgIndexInfoIntoAttrOf(latest.dbFile, {});
129
+ await latest.saveToFile();
130
+ await latest.dbFileSync.upload();
131
+ await dbsFS.v.sync.upload('');
132
+ return latest;
133
+ }
134
+ }
135
+ function insertMsgEntryInto(db, msgEntry) {
136
+ const { orderedColumns, orderedValues, insertParams } = (0, for_sqlite_1.forTableInsert)(msgEntry, msgsIndexFields);
137
+ db.run(`--sql
138
+ INSERT INTO inbox_index (${orderedColumns}) VALUES (${orderedValues})`, insertParams);
139
+ }
140
+ async function readDbAndUpgradeVersionIfNeeded(dbFile) {
141
+ const dbInfo = await dbFile.getXAttr(MSG_INDEX_DB_ATTR);
142
+ if (!dbInfo) {
143
+ const preV1 = await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeSynced(dbFile);
144
+ const preV1Records = (0, sql_db_v_none_1.getAllRecordsFromVersionNone)(preV1.db);
145
+ const msgs = preV1Records.map(({ msg_id, msg_type, delivery_ts, key_status, key, main_obj_header_ofs, remove_after }) => ({
146
+ msgId: msg_id, msgType: msg_type, deliveryTS: delivery_ts,
147
+ keyStatus: key_status, key, mainObjHeaderOfs: main_obj_header_ofs
148
+ }));
149
+ const dbV1 = await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeSynced(dbFile, false);
150
+ dbV1.db.run(queryToCreateMsgsIndexDbV1);
151
+ for (const msgEntry of msgs) {
152
+ insertMsgEntryInto(dbV1.db, msgEntry);
153
+ }
154
+ await setMsgIndexInfoIntoAttrOf(dbV1.dbFile, {});
155
+ await dbV1.saveToFile();
156
+ return dbV1;
157
+ }
158
+ else if (dbInfo.dbVersion === 1) {
159
+ return await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeSynced(dbFile);
160
+ }
161
+ else {
162
+ throw new Error(`Platform needs an upgrade: MsgIndex db file has unknown version ${dbInfo.dbVersion}`);
163
+ }
164
+ }
165
+ async function fileTSsOfDBShards(dbsFS) {
166
+ const lst = await dbsFS.listFolder('');
167
+ const fileTSs = [];
168
+ for (const { isFile, name } of lst) {
169
+ if (!isFile || !name.endsWith(DB_EXT)) {
170
+ continue;
171
+ }
172
+ const numStr = name.substring(0, DB_EXT.length);
173
+ const fileTS = parseInt(numStr);
174
+ if (isNaN(fileTS)) {
175
+ continue;
176
+ }
177
+ fileTSs.push(fileTS);
178
+ }
179
+ fileTSs.sort();
180
+ return fileTSs;
181
+ }
182
+ const DB_EXT = '.sqlite';
183
+ const LATEST_DB = `latest${DB_EXT}`;
184
+ const MSG_INDEX_DB_ATTR = `msg-index-db`;
185
+ async function setMsgIndexInfoIntoAttrOf(dbFile, info) {
186
+ const completeInfo = {
187
+ dbVersion: 1,
188
+ ...info
189
+ };
190
+ await dbFile.updateXAttrs({
191
+ set: { [MSG_INDEX_DB_ATTR]: completeInfo }
192
+ });
193
+ }
194
+ function dbFileName(shardTS) {
195
+ return (shardTS ? LATEST_DB : `${shardTS}${DB_EXT}`);
196
+ }
197
+ function shardTSFromFileName(fName) {
198
+ if (fName.endsWith(DB_EXT)) {
199
+ const shardTS = parseInt(fName.slice(0, fName.length - DB_EXT.length));
200
+ return ((isNaN(shardTS) || (fName !== dbFileName(shardTS))) ? undefined : shardTS);
201
+ }
202
+ else {
203
+ return;
204
+ }
205
+ }
206
+ async function makeSqliteDBs(dbsFS) {
207
+ const shardsCache = (0, timed_cache_1.makeTimedCache)(10 * 60 * 1000);
208
+ const latest = await readOrInitializeLatestDB(dbsFS);
209
+ const fileTSs = await fileTSsOfDBShards(dbsFS);
210
+ async function getDBFile(fileTS) {
211
+ return await dbsFS.writableFile(dbFileName(fileTS), { create: false });
212
+ }
213
+ function numberOfRecordsInLatestBefore(ts) {
214
+ return countUpToTS(latest.db, ts);
215
+ }
216
+ /**
217
+ * This return true when msg should be added to log, and false otherwise.
218
+ * @param msgInfo
219
+ * @param decrInfo
220
+ * @param removeAfter
221
+ */
222
+ async function addToLatest(msgInfo, decrInfo) {
223
+ if (!isForLatestDb(msgInfo.deliveryTS)) {
224
+ return false;
225
+ }
226
+ const recordAdded = addMsgRecordToLatest(msgInfo, decrInfo);
227
+ if (recordAdded) {
228
+ await latest.saveToFile();
229
+ }
230
+ return recordAdded;
231
+ }
232
+ function addMsgRecordToLatest(msgInfo, decrInfo) {
233
+ const { msgId, msgType, deliveryTS } = msgInfo;
234
+ const { db } = latest;
235
+ if (isMsgPresent(db, msgId)) {
236
+ return false;
237
+ }
238
+ const { key, keyStatus, msgKeyPackLen: mainObjHeaderOfs } = decrInfo;
239
+ insertMsgEntryInto(db, {
240
+ msgId, msgType, deliveryTS, key: key, keyStatus, mainObjHeaderOfs
241
+ });
242
+ return true;
243
+ }
244
+ function isForLatestDb(deliveryTS) {
245
+ return ((fileTSs.length === 0) || (fileTSs[fileTSs.length - 1] < deliveryTS));
246
+ }
247
+ async function dbShardFromCacheOrFS(fileTS) {
248
+ let db = shardsCache.get(fileTS);
249
+ if (db) {
250
+ return db;
251
+ }
252
+ const dbFile = await getDBFile(fileTS);
253
+ db = await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeSynced(dbFile);
254
+ shardsCache.set(fileTS, db);
255
+ return db;
256
+ }
257
+ async function remove(msgId) {
258
+ for await (const { db, fileTS } of iterateDBsFromLatestToOldest()) {
259
+ const deliveryTS = findMsgAndGetDeliveryTS(db.db, msgId);
260
+ if (deliveryTS) {
261
+ deleteMsgFrom(db.db, msgId);
262
+ await db.saveToFile();
263
+ return deliveryTS;
264
+ }
265
+ }
266
+ return undefined;
267
+ }
268
+ async function* iterateDBsFromLatestToOldest() {
269
+ yield { db: latest };
270
+ for (let i = (fileTSs.length - 1); i >= 0; i = -1) {
271
+ const fileTS = fileTSs[i];
272
+ if (!fileTS) {
273
+ continue;
274
+ }
275
+ const db = await dbShardFromCacheOrFS(fileTS);
276
+ if (db) {
277
+ yield { db, fileTS };
278
+ }
279
+ }
280
+ }
281
+ async function listMsgs(fromTS) {
282
+ let lst = listMsgInfos(latest.db, fromTS);
283
+ for (let i = (fileTSs.length - 1); i >= 0; i -= 1) {
284
+ const fileTS = fileTSs[i];
285
+ if (fromTS && (fileTS <= fromTS)) {
286
+ break;
287
+ }
288
+ const older = await dbShardFromCacheOrFS(fileTS);
289
+ lst = listMsgInfos(older.db, fromTS).concat(lst);
290
+ }
291
+ lst.sort((a, b) => (a.deliveryTS - b.deliveryTS));
292
+ return lst;
293
+ }
294
+ async function getIndexWith(deliveryTS) {
295
+ let fileTS = undefined;
296
+ for (let i = (fileTSs.length - 1); i >= 0; i -= 1) {
297
+ const fTS = fileTSs[i];
298
+ if (fTS < deliveryTS) {
299
+ break;
300
+ }
301
+ fileTS = fTS;
302
+ }
303
+ if (fileTS) {
304
+ return await dbShardFromCacheOrFS(fileTS);
305
+ }
306
+ else {
307
+ return latest;
308
+ }
309
+ }
310
+ async function getKeyFor(msgId, deliveryTS) {
311
+ const db = await getIndexWith(deliveryTS);
312
+ return findMsgKey(db.db, msgId);
313
+ }
314
+ async function msgExists({ msgId, deliveryTS }) {
315
+ const db = await getIndexWith(deliveryTS);
316
+ return isMsgPresent(db.db, msgId);
317
+ }
318
+ // XXX at a startup we should check if current local latest is too old
319
+ // XXX one should watch for reconnect event and run similar checks of possibly missed events
320
+ function watchAndApplyOpsFromOtherDevices(changeProc, resetSyncInterval) {
321
+ return dbsFS.watchTree('.', 1, {
322
+ next: fsEvent => changeProc.startOrChain(async () => {
323
+ if (fsEvent.type === 'remote-change') {
324
+ if (fsEvent.path === '.') {
325
+ doWhenDBsFolderChangedOnRemote();
326
+ }
327
+ else if (fsEvent.path === LATEST_DB) {
328
+ // XXX latest db shard file has been cut
329
+ // - remove stuff from latest and from log
330
+ const { state, remote } = await latest.dbFileSync.status();
331
+ if (state === 'behind') {
332
+ await latest.dbFileSync.adoptRemote();
333
+ }
334
+ else if (state === 'conflicting') {
335
+ const remoteAttr = await latest.dbFile.v.getXAttr(MSG_INDEX_DB_ATTR, { remoteVersion: remote === null || remote === void 0 ? void 0 : remote.latest });
336
+ }
337
+ }
338
+ else {
339
+ // XXX old db shard file has changed.
340
+ // -> file should adopt remote state, and may be trigger download
341
+ }
342
+ }
343
+ else if ((fsEvent.type === 'entry-addition') && (fsEvent.src === 'sync') && fsEvent.entry.isFile) {
344
+ resetSyncInterval();
345
+ await doWhenShardWasAddedFromRemote(fsEvent.entry.name);
346
+ }
347
+ else if ((fsEvent.type === 'entry-removal') && (fsEvent.src === 'sync') && fsEvent.name) {
348
+ doWhenShardWasRemovedFromRemote(fsEvent.name);
349
+ }
350
+ })
351
+ });
352
+ }
353
+ async function doWhenDBsFolderChangedOnRemote() {
354
+ const { state } = await dbsFS.v.sync.status('.');
355
+ if (state === 'behind') {
356
+ await dbsFS.v.sync.adoptRemote('.');
357
+ }
358
+ else if (state === 'conflicting') {
359
+ await dbsFS.v.sync.absorbRemoteFolderChanges('.');
360
+ await dbsFS.v.sync.upload('.');
361
+ }
362
+ }
363
+ async function doWhenShardWasAddedFromRemote(shardFName) {
364
+ const shardTS = shardTSFromFileName(shardFName);
365
+ if (!shardTS) {
366
+ return;
367
+ }
368
+ for (let i = fileTSs.length; i >= 0; i - 1) {
369
+ const ts = fileTSs[i];
370
+ if (ts < shardTS) {
371
+ fileTSs.slice(i + 1, shardTS);
372
+ break;
373
+ }
374
+ else if (ts === shardTS) {
375
+ return;
376
+ }
377
+ }
378
+ const stats = await dbsFS.stat(shardFName);
379
+ await dbsFS.v.sync.startDownload(shardFName, stats.version);
380
+ }
381
+ async function doWhenShardWasRemovedFromRemote(shardFName) {
382
+ const shardTS = shardTSFromFileName(shardFName);
383
+ if (!shardTS) {
384
+ return;
385
+ }
386
+ const tsInd = fileTSs.indexOf(shardTS);
387
+ if (tsInd < 0) {
388
+ return;
389
+ }
390
+ fileTSs.splice(tsInd, 1);
391
+ shardsCache.delete(shardTS);
392
+ }
393
+ async function latestDbHasNewRemoteVersion() {
394
+ const { state } = await latest.dbFile.v.sync.status();
395
+ return ((state === 'behind') || (state === 'conflicting'));
396
+ }
397
+ async function absorbOpsFromOtherDevices(changeProc, ops) {
398
+ await changeProc.startOrChain(async () => {
399
+ let dbsToSave = new Set();
400
+ for (const op of ops) {
401
+ if (op.p) {
402
+ if (isForLatestDb(op.p.deliveryTS)) {
403
+ if (op.p.eventType === 'msg-opened') {
404
+ const { msgInfo, decrInfo } = (0, logs_1.msgOpenedEventToRecordParams)(op.p);
405
+ const addedMsg = addMsgRecordToLatest(msgInfo, decrInfo);
406
+ if (addedMsg) {
407
+ dbsToSave.add(latest);
408
+ }
409
+ }
410
+ else if (op.p.eventType === 'msg-removed') {
411
+ const removedMsg = deleteMsgFrom(latest.db, op.p.msgId);
412
+ if (removedMsg) {
413
+ dbsToSave.add(latest);
414
+ }
415
+ }
416
+ }
417
+ else if (op.p.eventType === 'msg-removed') {
418
+ const shard = await getIndexWith(op.p.deliveryTS);
419
+ const removedMsg = deleteMsgFrom(shard.db, op.p.msgId);
420
+ if (removedMsg) {
421
+ dbsToSave.add(shard);
422
+ }
423
+ }
424
+ }
425
+ else if (op.syncPoint) {
426
+ // XXX
427
+ // -
428
+ }
429
+ }
430
+ for (const db of dbsToSave) {
431
+ await db.saveToFile();
432
+ }
433
+ });
434
+ }
435
+ async function makeNewShardWithRecordsUpToTS(shardTS) {
436
+ // make and upload shard
437
+ const shardFile = await dbsFS.writableFile(dbFileName(shardTS), { create: true, exclusive: true });
438
+ const shard = await latest.makeCopyIn(shardFile);
439
+ deleteAllMsgsAfterTS(shard.db, shardTS);
440
+ await shard.saveToFile();
441
+ await setMsgIndexInfoIntoAttrOf(shard.dbFile, {
442
+ shardTS,
443
+ prevShardTS: ((fileTSs.length > 0) ? fileTSs[fileTSs.length - 1] : undefined)
444
+ });
445
+ await shard.dbFileSync.upload();
446
+ await dbsFS.v.sync.upload('');
447
+ // cut and upload latest
448
+ deleteAllMsgsUpToAndAtTS(latest.db, shardTS);
449
+ fileTSs.push(shardTS);
450
+ await setMsgIndexInfoIntoAttrOf(latest.dbFile, { prevShardTS: shardTS });
451
+ const syncedIndexDbVersion = await latest.dbFileSync.upload();
452
+ return syncedIndexDbVersion;
453
+ }
454
+ async function syncPastShards() {
455
+ // XXX only removals are done on older shards, so conflicts should be easy to process
456
+ // - check sync status of shard file without instantiating dbs.
457
+ // - push unsynced
458
+ // - adopt and download a thing that is behind
459
+ // -
460
+ }
461
+ return {
462
+ absorbOpsFromOtherDevices,
463
+ addToLatest,
464
+ getDBFile,
465
+ getKeyFor,
466
+ isForLatestDb,
467
+ latestDbHasNewRemoteVersion,
468
+ listMsgs,
469
+ msgExists,
470
+ makeNewShardWithRecordsUpToTS,
471
+ syncPastShards,
472
+ numberOfRecordsInLatestBefore,
473
+ remove,
474
+ watchAndApplyOpsFromOtherDevices
475
+ };
476
+ }
477
+ Object.freeze(exports);
@@ -1,31 +1,17 @@
1
- import { MsgKeyInfo, MsgKeyRole } from '../../../keyring';
1
+ import type { MsgKeyInfo } from '../../../keyring';
2
+ import type { LogError } from '../../../../lib-client/logging/log-to-file';
2
3
  type WritableFS = web3n.files.WritableFS;
3
4
  type MsgInfo = web3n.asmail.MsgInfo;
4
5
  /**
5
- * This message index stores info for messages present on the server, in the
6
- * inbox. Records contain message key info, time of delivery, and time of
7
- * desired removal.
8
- *
9
- * Message info with keys is stored in SQLite dbs sharded/partitioned by
10
- * delivery timestamp. The latest shard, shard without upper time limit
11
- * is stored in local storage, while all other shards with limits are stored in
12
- * synced storage. Information in synced storage is a sum of all limited shards
13
- * and action logs. Action logs
14
- *
6
+ * This message index stores info for messages present on the server, in the inbox.
7
+ * Records contain message key info, time of delivery, and time of desired removal.
15
8
  */
16
- export declare class MsgIndex {
17
- private readonly logs;
18
- private readonly indexed;
19
- private constructor();
20
- static make(syncedFS: WritableFS): Promise<MsgIndex>;
21
- stopSyncing(): void;
22
- add(msgInfo: MsgInfo, decrInfo: MsgKeyInfo, removeAfter?: number): Promise<void>;
23
- remove(msgId: string): Promise<void>;
24
- listMsgs(fromTS: number | undefined): Promise<MsgInfo[]>;
25
- getKeyFor(msgId: string, deliveryTS: number): Promise<{
26
- msgKey: Uint8Array;
27
- msgKeyRole: MsgKeyRole;
28
- mainObjHeaderOfs: number;
29
- } | undefined>;
30
- }
9
+ export type MsgIndex = Awaited<ReturnType<typeof makeMsgIndex>>;
10
+ export declare function makeMsgIndex(syncedFS: WritableFS, localFS: WritableFS, logError: LogError): Promise<{
11
+ add: (msgInfo: MsgInfo, decrInfo: MsgKeyInfo) => Promise<void>;
12
+ remove: (msgId: string) => Promise<void>;
13
+ listMsgs: (fromTS: number | undefined) => Promise<web3n.asmail.MsgInfo[]>;
14
+ getKeyFor: (msgId: string, deliveryTS: number) => Promise<import("./dataset/v1/sql-db").MsgKey | undefined>;
15
+ stopSyncing: () => void;
16
+ }>;
31
17
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2022 - 2023 3NSoft Inc.
3
+ Copyright (C) 2022 - 2023, 2026 3NSoft Inc.
4
4
 
5
5
  This program is free software: you can redistribute it and/or modify it under
6
6
  the terms of the GNU General Public License as published by the Free Software
@@ -16,81 +16,60 @@
16
16
  this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.MsgIndex = void 0;
20
- const logs_n_entries_1 = require("./logs-n-entries");
19
+ exports.makeMsgIndex = makeMsgIndex;
20
+ const sql_db_1 = require("./dataset/v1/sql-db");
21
+ const logs_1 = require("./dataset/v1/logs");
22
+ const fs_sync_utils_1 = require("../../../../lib-client/fs-utils/fs-sync-utils");
23
+ const synced_1 = require("../../../../lib-common/processes/synced");
24
+ const per_device_json_array_logs_1 = require("../../../../lib-common/dataset-sync/per-device-json-array-logs");
21
25
  const LOGS_DIR = 'logs';
22
26
  const INDEX_DIR = 'index';
23
- async function getOrMakeDirStructure(syncedFS) {
24
- const logsFS = await syncedFS.writableSubRoot(LOGS_DIR);
25
- const indexFS = await syncedFS.writableSubRoot(INDEX_DIR);
26
- return { logsFS, indexFS };
27
- }
28
- async function syncDirStructureIfNeeded(syncedFS) {
29
- // XXX need uploading of initial folders, and of syncedFS itself
30
- // const logsDirInfo = await logsFS.v!.sync!.status(LOGS_DIR);
31
- // logsDirInfo.state
32
- // Or, is this enough?
33
- // await getRemoteFolderChanges(syncedFS);
34
- // await uploadFolderChangesIfAny(syncedFS);
35
- }
36
- /**
37
- * This message index stores info for messages present on the server, in the
38
- * inbox. Records contain message key info, time of delivery, and time of
39
- * desired removal.
40
- *
41
- * Message info with keys is stored in SQLite dbs sharded/partitioned by
42
- * delivery timestamp. The latest shard, shard without upper time limit
43
- * is stored in local storage, while all other shards with limits are stored in
44
- * synced storage. Information in synced storage is a sum of all limited shards
45
- * and action logs. Action logs
46
- *
47
- */
48
- class MsgIndex {
49
- constructor(logs, indexed) {
50
- this.logs = logs;
51
- this.indexed = indexed;
52
- Object.seal(this);
53
- }
54
- static async make(syncedFS) {
55
- const { logsFS, indexFS } = await getOrMakeDirStructure(syncedFS);
56
- await syncDirStructureIfNeeded(syncedFS);
57
- const logs = await logs_n_entries_1.MsgLogs.makeAndStartSyncing(logsFS);
58
- let indexed;
59
- if (global.WebAssembly) {
60
- indexed = await require('./sql-indexing').makeSqliteBasedIndexedRecords(indexFS);
61
- }
62
- else {
63
- indexed = (0, logs_n_entries_1.makeJsonBasedIndexedRecords)(logs);
64
- }
65
- const index = new MsgIndex(logs, indexed);
66
- return index;
67
- }
68
- stopSyncing() {
69
- this.logs.stopSyncing();
70
- this.indexed.stopSyncing();
71
- }
72
- async add(msgInfo, decrInfo, removeAfter = 0) {
73
- const msgAlreadyExists = await this.indexed.msgExists(msgInfo);
74
- if (msgAlreadyExists) {
75
- return;
27
+ const NUM_OF_ENTRIES_TO_CHOP_DB = 200;
28
+ async function makeMsgIndex(syncedFS, localFS, logError) {
29
+ // file systems within dataset
30
+ const logsFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(syncedFS, LOGS_DIR);
31
+ const localLogsFS = await localFS.writableSubRoot(LOGS_DIR);
32
+ const indexFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(syncedFS, INDEX_DIR);
33
+ const logs = await (0, logs_1.makeRecentMsgIndexChangesLogs)(logsFS, localLogsFS, logError);
34
+ const indexed = await (0, sql_db_1.makeSqliteDBs)(indexFS);
35
+ async function add(msgInfo, decrInfo) {
36
+ const shouldAddToLog = await indexed.addToLatest(msgInfo, decrInfo);
37
+ if (shouldAddToLog) {
38
+ await logs.recordMsgAddition(msgInfo, decrInfo);
76
39
  }
77
- await this.logs.add(msgInfo, decrInfo, removeAfter);
78
- await this.indexed.add(msgInfo, decrInfo, removeAfter);
79
40
  }
80
- async remove(msgId) {
81
- const deliveryTS = await this.indexed.remove(msgId);
41
+ async function remove(msgId) {
42
+ const deliveryTS = await indexed.remove(msgId);
82
43
  if (deliveryTS) {
83
- await this.logs.remove(msgId, deliveryTS);
44
+ await logs.recordMsgRemoval(msgId, deliveryTS);
84
45
  }
85
46
  }
86
- listMsgs(fromTS) {
87
- return this.indexed.listMsgs(fromTS);
47
+ async function periodicCheckAndUploadOfData() {
48
+ // XXX disable uploading for a moment by commenting out
49
+ // const shardTS = Date.now() - 10*60*1000;
50
+ // const countBefore = indexed.numberOfRecordsInLatestBefore(shardTS);
51
+ // if (countBefore >= NUM_OF_ENTRIES_TO_CHOP_DB) {
52
+ // const syncedIndexDbVersion = await indexed.makeNewShardWithRecordsUpToTS(shardTS);
53
+ // await logs.cutLogOnDatasetSyncAndUpload(syncedIndexDbVersion, { shardTS });
54
+ // }
88
55
  }
89
- getKeyFor(msgId, deliveryTS) {
90
- return this.indexed.getKeyFor(msgId, deliveryTS);
56
+ function millisBeforeNextRun() {
57
+ return 30 * 60 * 1000 + Math.floor(10 * 60 * 1000 * Math.random());
91
58
  }
59
+ const { stopSyncing, changeProc } = (0, per_device_json_array_logs_1.startDatasetSync)(periodicCheckAndUploadOfData, millisBeforeNextRun, {
60
+ watchAndApplyOpsFromOtherDevices: logs.watchAndApplyOpsFromOtherDevices
61
+ }, {
62
+ absorbOpsFromOtherDevices: indexed.absorbOpsFromOtherDevices,
63
+ watchAndApplyOpsFromOtherDevices: indexed.watchAndApplyOpsFromOtherDevices
64
+ });
65
+ // XXX
66
+ // - check if indexed latest is too old, chopping to the latest shard
67
+ return {
68
+ add: (0, synced_1.makeSyncedFunc)(changeProc, undefined, add),
69
+ remove: (0, synced_1.makeSyncedFunc)(changeProc, undefined, remove),
70
+ listMsgs: (0, synced_1.makeSyncedFunc)(changeProc, undefined, indexed.listMsgs),
71
+ getKeyFor: (0, synced_1.makeSyncedFunc)(changeProc, undefined, indexed.getKeyFor),
72
+ stopSyncing
73
+ };
92
74
  }
93
- exports.MsgIndex = MsgIndex;
94
- Object.freeze(MsgIndex.prototype);
95
- Object.freeze(MsgIndex);
96
75
  Object.freeze(exports);
@@ -75,8 +75,7 @@ class MsgOnDisk {
75
75
  throw new Error(`Obj ${objId} not present in message ${this.msgId}`);
76
76
  }
77
77
  const path = this.objFilePath(objId);
78
- const isOnDisk = !!(await fs.stat(path)
79
- .catch((exc) => {
78
+ const isOnDisk = !!(await fs.stat(path).catch((exc) => {
80
79
  if (exc.notFound) {
81
80
  return;
82
81
  }