core-3nweb-client-lib 0.28.4 → 0.29.1

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.
@@ -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);
@@ -198,7 +198,7 @@ var extAttrs;
198
198
  }
199
199
  extAttrs.readNamedAttr = readNamedAttr;
200
200
  function parseTypeByte(b) {
201
- const type = ((b & 0b11111000) >> 5);
201
+ const type = ((b & 0b11111000) >> 3);
202
202
  const nameLen = ((b & 0b00000100) >> 2) + 1;
203
203
  const contentLen = (b & 0b00000011) + 1;
204
204
  return { type, contentLen, nameLen };