core-3nweb-client-lib 0.28.4 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/core/asmail/inbox/{msg-indexing.d.ts → msg-indexing/index.d.ts} +3 -4
- package/build/core/asmail/inbox/msg-indexing/index.js +96 -0
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.d.ts +52 -0
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.js +333 -0
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.d.ts +4 -0
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.js +320 -0
- package/build/lib-client/cryptor/cryptor-wasm.js +1 -1
- package/build/lib-client/cryptor/cryptor.wasm +0 -0
- package/build/lib-sqlite-on-3nstorage/deferred.d.ts +6 -0
- package/build/lib-sqlite-on-3nstorage/deferred.js +29 -0
- package/build/lib-sqlite-on-3nstorage/index.d.ts +34 -17
- package/build/lib-sqlite-on-3nstorage/index.js +158 -55
- package/build/lib-sqlite-on-3nstorage/sqljs.js +13 -8
- package/build/lib-sqlite-on-3nstorage/synced.d.ts +73 -0
- package/build/lib-sqlite-on-3nstorage/synced.js +167 -0
- package/package.json +1 -1
- package/build/core/asmail/inbox/msg-indexing.js +0 -518
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (C) 2022 - 2023 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.makeSqliteBasedIndexedRecords = void 0;
|
|
20
|
+
const timed_cache_1 = require("../../../../lib-common/timed-cache");
|
|
21
|
+
const lib_sqlite_on_3nstorage_1 = require("../../../../lib-sqlite-on-3nstorage");
|
|
22
|
+
const file_1 = require("../../../../lib-common/exceptions/file");
|
|
23
|
+
const fs_sync_utils_1 = require("../../../../lib-client/fs-sync-utils");
|
|
24
|
+
const operators_1 = require("rxjs/operators");
|
|
25
|
+
const tab = new lib_sqlite_on_3nstorage_1.TableColumnsAndParams('inbox_index', {
|
|
26
|
+
msgId: ['msg_id', 'TEXT PRIMARY KEY'],
|
|
27
|
+
msgType: ['msg_type', 'TEXT'],
|
|
28
|
+
deliveryTS: ['delivery_ts', 'INTEGER'],
|
|
29
|
+
key: ['key', 'BLOB'],
|
|
30
|
+
keyStatus: ['key_status', 'TEXT'],
|
|
31
|
+
mainObjHeaderOfs: ['main_obj_header_ofs', 'INTEGER'],
|
|
32
|
+
removeAfter: ['remove_after', 'INTEGER DEFAULT 0']
|
|
33
|
+
});
|
|
34
|
+
const createIndexTab = `CREATE TABLE ${tab.name} (
|
|
35
|
+
${tab.columnsCreateSection}
|
|
36
|
+
) STRICT`;
|
|
37
|
+
const selectAllMsgInfos = `SELECT ${tab.colsSection('msgId', 'msgType', 'deliveryTS')}
|
|
38
|
+
FROM ${tab.name}`;
|
|
39
|
+
const selectMsgInfosFromTS = `SELECT ${tab.colsSection('msgId', 'msgType', 'deliveryTS')}
|
|
40
|
+
FROM ${tab.name}
|
|
41
|
+
WHERE ${tab.c['deliveryTS']}>$fromTS`;
|
|
42
|
+
function listMsgInfos(db, fromTS) {
|
|
43
|
+
let result;
|
|
44
|
+
if (fromTS) {
|
|
45
|
+
result = db.exec(selectMsgInfosFromTS, { '$fromTS': fromTS });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
result = db.exec(selectAllMsgInfos);
|
|
49
|
+
}
|
|
50
|
+
return tab.fromQueryExecResult(result);
|
|
51
|
+
}
|
|
52
|
+
const deleteRec = `DELETE FROM ${tab.name}
|
|
53
|
+
WHERE ${tab.colsEqualSection('msgId')}`;
|
|
54
|
+
function deleteMsgFrom(db, msgId) {
|
|
55
|
+
db.exec(deleteRec, tab.toParams({ msgId }));
|
|
56
|
+
}
|
|
57
|
+
const selectMsgDeliveryTS = `SELECT ${tab.colsSection('deliveryTS')}
|
|
58
|
+
FROM ${tab.name}
|
|
59
|
+
WHERE ${tab.colsEqualSection('msgId')}`;
|
|
60
|
+
function findMsgAndGetDeliveryTS(db, msgId) {
|
|
61
|
+
const result = db.exec(selectMsgDeliveryTS, tab.toParams({ msgId }));
|
|
62
|
+
const values = tab.fromQueryExecResult(result);
|
|
63
|
+
return ((values.length > 0) ? values[0].deliveryTS : undefined);
|
|
64
|
+
}
|
|
65
|
+
const selectMsgKey = `SELECT ${tab.colsSection('key', 'keyStatus', 'mainObjHeaderOfs')}
|
|
66
|
+
FROM ${tab.name}
|
|
67
|
+
WHERE ${tab.colsEqualSection('msgId')}`;
|
|
68
|
+
function findMsgKey(db, msgId) {
|
|
69
|
+
const result = db.exec(selectMsgKey, tab.toParams({ msgId }));
|
|
70
|
+
if (result.length > 0) {
|
|
71
|
+
const { key: msgKey, keyStatus: msgKeyRole, mainObjHeaderOfs } = tab.fromQueryExecResult(result)[0];
|
|
72
|
+
return { msgKey, msgKeyRole, mainObjHeaderOfs };
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const selectMsgPresence = `SELECT ${tab.colsSection('msgId')}
|
|
79
|
+
FROM ${tab.name}
|
|
80
|
+
WHERE ${tab.colsEqualSection('msgId')}`;
|
|
81
|
+
function isMsgPresent(db, msgId) {
|
|
82
|
+
const result = db.exec(selectMsgPresence, tab.toParams({ msgId }));
|
|
83
|
+
return (result.length > 0);
|
|
84
|
+
}
|
|
85
|
+
const LIMIT_RECORDS_PER_FILE = 200;
|
|
86
|
+
class RecordsInSQL {
|
|
87
|
+
constructor(files, latest, fileTSs) {
|
|
88
|
+
this.files = files;
|
|
89
|
+
this.latest = latest;
|
|
90
|
+
this.fileTSs = fileTSs;
|
|
91
|
+
this.older = (0, timed_cache_1.makeTimedCache)(10 * 60 * 1000);
|
|
92
|
+
Object.seal(this);
|
|
93
|
+
}
|
|
94
|
+
async add(msgInfo, decrInfo, removeAfter) {
|
|
95
|
+
const { msgId, msgType, deliveryTS } = msgInfo;
|
|
96
|
+
const { key, keyStatus, msgKeyPackLen: mainObjHeaderOfs } = decrInfo;
|
|
97
|
+
const { db } = await this.getDbFor(msgInfo.deliveryTS);
|
|
98
|
+
db.db.exec(tab.insertQuery, tab.toParams({
|
|
99
|
+
msgId, msgType, deliveryTS,
|
|
100
|
+
key, keyStatus,
|
|
101
|
+
mainObjHeaderOfs, removeAfter
|
|
102
|
+
}));
|
|
103
|
+
await db.saveToFile();
|
|
104
|
+
}
|
|
105
|
+
async saveLatestWithAttr() {
|
|
106
|
+
// XXX
|
|
107
|
+
}
|
|
108
|
+
async getDbFor(deliveryTS) {
|
|
109
|
+
if ((this.fileTSs.length === 0)
|
|
110
|
+
|| (this.fileTSs[this.fileTSs.length - 1] < deliveryTS)) {
|
|
111
|
+
return { db: this.latest };
|
|
112
|
+
}
|
|
113
|
+
let fileTS = this.fileTSs[this.fileTSs.length - 1];
|
|
114
|
+
for (let i = (this.fileTSs.length - 2); i >= 0; i -= 1) {
|
|
115
|
+
if (this.fileTSs[i] >= deliveryTS) {
|
|
116
|
+
fileTS = this.fileTSs[i];
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const db = await this.dbFromCacheOrInit(fileTS);
|
|
123
|
+
return { db, fileTS };
|
|
124
|
+
}
|
|
125
|
+
async dbFromCacheOrInit(fileTS) {
|
|
126
|
+
let db = this.older.get(fileTS);
|
|
127
|
+
if (db) {
|
|
128
|
+
return db;
|
|
129
|
+
}
|
|
130
|
+
const dbFile = await this.files.getDBFile(fileTS);
|
|
131
|
+
db = await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeAndStart(dbFile);
|
|
132
|
+
this.older.set(fileTS, db);
|
|
133
|
+
return db;
|
|
134
|
+
}
|
|
135
|
+
async remove(msgId) {
|
|
136
|
+
for await (const { db, fileTS } of this.iterateDBs()) {
|
|
137
|
+
const deliveryTS = findMsgAndGetDeliveryTS(db.db, msgId);
|
|
138
|
+
if (deliveryTS) {
|
|
139
|
+
deleteMsgFrom(db.db, msgId);
|
|
140
|
+
await db.saveToFile();
|
|
141
|
+
return deliveryTS;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
async *iterateDBs() {
|
|
147
|
+
yield { db: this.latest };
|
|
148
|
+
for (let i = (this.fileTSs.length - 1); i >= 0; i = -1) {
|
|
149
|
+
const fileTS = this.fileTSs[i];
|
|
150
|
+
if (!fileTS) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const db = await this.dbFromCacheOrInit(fileTS);
|
|
154
|
+
if (db) {
|
|
155
|
+
yield { db, fileTS };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async listMsgs(fromTS) {
|
|
160
|
+
let lst = listMsgInfos(this.latest.db, fromTS);
|
|
161
|
+
for (let i = (this.fileTSs.length - 1); i >= 0; i -= 1) {
|
|
162
|
+
const fileTS = this.fileTSs[i];
|
|
163
|
+
if (fromTS && (fileTS <= fromTS)) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
const older = await this.dbFromCacheOrInit(fileTS);
|
|
167
|
+
lst = listMsgInfos(older.db, fromTS).concat(lst);
|
|
168
|
+
}
|
|
169
|
+
lst.sort((a, b) => (a.deliveryTS - b.deliveryTS));
|
|
170
|
+
return lst;
|
|
171
|
+
}
|
|
172
|
+
async getIndexWith(deliveryTS) {
|
|
173
|
+
let fileTS = undefined;
|
|
174
|
+
for (let i = (this.fileTSs.length - 1); i >= 0; i -= 1) {
|
|
175
|
+
const fTS = this.fileTSs[i];
|
|
176
|
+
if (fTS < deliveryTS) {
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
fileTS = fTS;
|
|
180
|
+
}
|
|
181
|
+
if (fileTS) {
|
|
182
|
+
return await this.dbFromCacheOrInit(fileTS);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
return this.latest;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async getKeyFor(msgId, deliveryTS) {
|
|
189
|
+
const db = await this.getIndexWith(deliveryTS);
|
|
190
|
+
return findMsgKey(db.db, msgId);
|
|
191
|
+
}
|
|
192
|
+
async msgExists({ msgId, deliveryTS }) {
|
|
193
|
+
const db = await this.getIndexWith(deliveryTS);
|
|
194
|
+
return isMsgPresent(db.db, msgId);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
Object.freeze(RecordsInSQL.prototype);
|
|
198
|
+
Object.freeze(RecordsInSQL);
|
|
199
|
+
const DB_EXT = '.sqlite';
|
|
200
|
+
const LATEST_DB = `latest${DB_EXT}`;
|
|
201
|
+
function dbFileName(fileTS) {
|
|
202
|
+
return (fileTS ? LATEST_DB : `${fileTS}${DB_EXT}`);
|
|
203
|
+
}
|
|
204
|
+
class SqliteFiles {
|
|
205
|
+
constructor(dbsFS) {
|
|
206
|
+
this.dbsFS = dbsFS;
|
|
207
|
+
this.syncing = undefined;
|
|
208
|
+
Object.seal(this);
|
|
209
|
+
}
|
|
210
|
+
static async makeAndStart(syncedFS) {
|
|
211
|
+
(0, file_1.ensureCorrectFS)(syncedFS, 'synced', true);
|
|
212
|
+
const files = new SqliteFiles(syncedFS);
|
|
213
|
+
const records = await files.makeRecords();
|
|
214
|
+
files.startSyncing();
|
|
215
|
+
return { files, records };
|
|
216
|
+
}
|
|
217
|
+
async makeRecords() {
|
|
218
|
+
const latest = await this.readOrInitializeLatestDB();
|
|
219
|
+
const fileTSs = await this.fileTSsOfDBShards();
|
|
220
|
+
return new RecordsInSQL(this, latest, fileTSs);
|
|
221
|
+
}
|
|
222
|
+
async getDBFile(fileTS) {
|
|
223
|
+
return await this.dbsFS.writableFile(dbFileName(fileTS), { create: false });
|
|
224
|
+
}
|
|
225
|
+
async readOrInitializeLatestDB() {
|
|
226
|
+
if (await this.dbsFS.checkFilePresence(LATEST_DB)) {
|
|
227
|
+
const dbFile = await this.dbsFS.writableFile(LATEST_DB, { create: false });
|
|
228
|
+
return await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeAndStart(dbFile);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
const dbFile = await this.dbsFS.writableFile(LATEST_DB, { create: true, exclusive: true });
|
|
232
|
+
const latest = await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeAndStart(dbFile);
|
|
233
|
+
latest.db.run(createIndexTab);
|
|
234
|
+
await latest.saveToFile();
|
|
235
|
+
return latest;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async fileTSsOfDBShards() {
|
|
239
|
+
const lst = await this.dbsFS.listFolder('');
|
|
240
|
+
const fileTSs = [];
|
|
241
|
+
for (const { isFile, name } of lst) {
|
|
242
|
+
if (!isFile || !name.endsWith(DB_EXT)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const numStr = name.substring(0, DB_EXT.length);
|
|
246
|
+
const fileTS = parseInt(numStr);
|
|
247
|
+
if (isNaN(fileTS)) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
fileTSs.push(fileTS);
|
|
251
|
+
}
|
|
252
|
+
fileTSs.sort();
|
|
253
|
+
return fileTSs;
|
|
254
|
+
}
|
|
255
|
+
startSyncing() {
|
|
256
|
+
const db$ = (0, fs_sync_utils_1.observableFromTreeEvents)(this.dbsFS, '');
|
|
257
|
+
// XXX
|
|
258
|
+
// - start from data in fs, attempt to get fresh, etc. Both log and data
|
|
259
|
+
// (in parallel ?).
|
|
260
|
+
// - start sync process
|
|
261
|
+
// - unblock processing, as init is done
|
|
262
|
+
// Write theses in functions that use RecordsInSQL and LogOfChanges
|
|
263
|
+
// structures.
|
|
264
|
+
// Somehow aforementioned processes ain't exclusive to either point.
|
|
265
|
+
// XXX
|
|
266
|
+
// should start from reading folder and placing logs into yet unsynced db
|
|
267
|
+
this.syncing = db$
|
|
268
|
+
.pipe((0, operators_1.filter)(ev => ev.type.startsWith('remote-')))
|
|
269
|
+
// .subscribe({
|
|
270
|
+
// next: ev => console.log(`------ fs event:`, ev),
|
|
271
|
+
// complete: () => console.log(` +++ MsgIndex's sync process completed`),
|
|
272
|
+
// error: err => console.log(` *** error in MsgIndex's sync process`, err)
|
|
273
|
+
// });
|
|
274
|
+
.subscribe();
|
|
275
|
+
}
|
|
276
|
+
stopSyncing() {
|
|
277
|
+
if (this.syncing) {
|
|
278
|
+
this.syncing.unsubscribe();
|
|
279
|
+
this.syncing = undefined;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
Object.freeze(SqliteFiles.prototype);
|
|
284
|
+
Object.freeze(SqliteFiles);
|
|
285
|
+
class SqliteBasedIndexedRecords {
|
|
286
|
+
constructor(files, records) {
|
|
287
|
+
this.files = files;
|
|
288
|
+
this.records = records;
|
|
289
|
+
Object.seal(this);
|
|
290
|
+
}
|
|
291
|
+
static async makeAndStart(syncedFS) {
|
|
292
|
+
const { files, records } = await SqliteFiles.makeAndStart(syncedFS);
|
|
293
|
+
return new SqliteBasedIndexedRecords(files, records);
|
|
294
|
+
}
|
|
295
|
+
async add(msgInfo, decrInfo, removeAfter) {
|
|
296
|
+
await this.records.add(msgInfo, decrInfo, removeAfter);
|
|
297
|
+
}
|
|
298
|
+
remove(msgId) {
|
|
299
|
+
return this.records.remove(msgId);
|
|
300
|
+
}
|
|
301
|
+
listMsgs(fromTS) {
|
|
302
|
+
return this.records.listMsgs(fromTS);
|
|
303
|
+
}
|
|
304
|
+
getKeyFor(msgId, deliveryTS) {
|
|
305
|
+
return this.records.getKeyFor(msgId, deliveryTS);
|
|
306
|
+
}
|
|
307
|
+
async msgExists({ msgId, deliveryTS }) {
|
|
308
|
+
return !!(await this.records.getKeyFor(msgId, deliveryTS));
|
|
309
|
+
}
|
|
310
|
+
stopSyncing() {
|
|
311
|
+
this.files.stopSyncing();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
Object.freeze(SqliteBasedIndexedRecords.prototype);
|
|
315
|
+
Object.freeze(SqliteBasedIndexedRecords);
|
|
316
|
+
function makeSqliteBasedIndexedRecords(syncedFS) {
|
|
317
|
+
return SqliteBasedIndexedRecords.makeAndStart(syncedFS);
|
|
318
|
+
}
|
|
319
|
+
exports.makeSqliteBasedIndexedRecords = makeSqliteBasedIndexedRecords;
|
|
320
|
+
Object.freeze(exports);
|