core-3nweb-client-lib 0.49.5 → 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.
- package/build/api-defs/files.d.ts +11 -5
- package/build/core/asmail/delivery/common.d.ts +8 -28
- package/build/core/asmail/delivery/common.js +1 -47
- package/build/core/asmail/delivery/index.js +3 -8
- package/build/core/asmail/delivery/msg.js +1 -7
- package/build/core/asmail/delivery/per-recipient-wip.js +3 -29
- package/build/core/asmail/inbox/index.d.ts +1 -2
- package/build/core/asmail/inbox/index.js +14 -15
- package/build/core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.d.ts +11 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.js +25 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.d.ts +33 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.js +72 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.d.ts +28 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.js +477 -0
- package/build/core/asmail/inbox/msg-indexing/index.d.ts +12 -26
- package/build/core/asmail/inbox/msg-indexing/index.js +47 -68
- package/build/core/asmail/inbox/msg-on-disk.js +1 -2
- package/build/core/asmail/index.js +8 -7
- package/build/core/asmail/msg/common.d.ts +0 -1
- package/build/core/asmail/msg/common.js +1 -1
- package/build/core/asmail/msg/packer.js +1 -3
- package/build/core/index.js +7 -4
- package/build/core/keyring/common.d.ts +1 -0
- package/build/core/keyring/common.js +2 -1
- package/build/core/keyring/index.d.ts +7 -44
- package/build/core/keyring/index.js +89 -222
- package/build/core/keyring/introductory-keys/published-intro-key.d.ts +17 -0
- package/build/core/keyring/introductory-keys/published-intro-key.js +190 -0
- package/build/core/keyring/peer-keys/dataset/v1/json-files.d.ts +105 -0
- package/build/core/keyring/peer-keys/dataset/v1/json-files.js +89 -0
- package/build/core/keyring/peer-keys/dataset/v2/logs.d.ts +43 -0
- package/build/core/keyring/peer-keys/dataset/v2/logs.js +116 -0
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.d.ts +73 -0
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.js +543 -0
- package/build/core/keyring/peer-keys/index.d.ts +24 -0
- package/build/core/keyring/peer-keys/index.js +208 -0
- package/build/core/storage/synced/obj-files.js +10 -3
- package/build/core/storage/synced/obj-status.d.ts +1 -1
- package/build/core/storage/synced/obj-status.js +8 -2
- package/build/core/storage/synced/storage.d.ts +4 -4
- package/build/core/storage/system-folders/apps-data.d.ts +0 -1
- package/build/core/storage/system-folders/apps-data.js +2 -6
- package/build/lib-client/fs-utils/fs-sync-utils.d.ts +1 -1
- package/build/lib-client/fs-utils/fs-sync-utils.js +70 -13
- package/build/lib-client/xsp-fs/fs.js +3 -1
- package/build/lib-client/xsp-fs/node-in-fs.d.ts +1 -0
- package/build/lib-client/xsp-fs/node-in-fs.js +7 -2
- package/build/lib-client/xsp-fs/node-persistence.js +1 -3
- package/build/lib-client/xsp-fs/xsp-payload-v2.js +1 -2
- package/build/lib-common/dataset-sync/per-device-json-array-logs.d.ts +58 -0
- package/build/lib-common/dataset-sync/per-device-json-array-logs.js +432 -0
- package/build/lib-common/number-line.d.ts +10 -0
- package/build/lib-common/number-line.js +66 -0
- package/build/lib-common/objs-on-disk/obj-version-file.d.ts +1 -1
- package/build/lib-common/objs-on-disk/obj-version-file.js +3 -3
- package/build/lib-common/rng-def.d.ts +1 -0
- package/build/lib-common/rng-def.js +8 -0
- package/build/lib-sqlite-on-3nstorage/for-sqlite.d.ts +38 -0
- package/build/lib-sqlite-on-3nstorage/for-sqlite.js +162 -0
- package/build/lib-sqlite-on-3nstorage/index.d.ts +18 -5
- package/build/lib-sqlite-on-3nstorage/index.js +49 -26
- package/build/tests/caps-api/file-sink-checks/splice.js +72 -1
- package/build/tests/caps-api/fs-checks/sync-on-one-dev/upload.js +27 -1
- package/build/tests/caps-api/fs-checks/sync-with-two-devs/update-propagation.js +12 -3
- package/build/tests/computer.3nweb.core/inbox/msg-indexing.js +16 -12
- package/build/tests/computer.3nweb.core/keyrings.js +113 -28
- package/build/tests/jasmine.js +2 -2
- package/build/tests/units/number-line.js +9 -9
- package/package.json +2 -1
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.d.ts +0 -52
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.js +0 -333
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.d.ts +0 -4
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.js +0 -319
- package/build/core/keyring/correspondent-keys.d.ts +0 -149
- package/build/core/keyring/correspondent-keys.js +0 -403
- package/build/core/keyring/id-to-email-map.d.ts +0 -38
- package/build/core/keyring/id-to-email-map.js +0 -105
- package/build/core/keyring/keyring-storage.d.ts +0 -9
- package/build/core/keyring/keyring-storage.js +0 -54
- package/build/core/keyring/published-intro-key.d.ts +0 -45
- 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
|
|
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
|
-
*
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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.
|
|
20
|
-
const
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
41
|
+
async function remove(msgId) {
|
|
42
|
+
const deliveryTS = await indexed.remove(msgId);
|
|
82
43
|
if (deliveryTS) {
|
|
83
|
-
await
|
|
44
|
+
await logs.recordMsgRemoval(msgId, deliveryTS);
|
|
84
45
|
}
|
|
85
46
|
}
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
return
|
|
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
|
}
|