core-3nweb-client-lib 0.28.3 → 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/ipc-via-protobuf/protobuf-msg.js +14 -4
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MsgKeyInfo, MsgKeyRole } from '
|
|
1
|
+
import { MsgKeyInfo, MsgKeyRole } from '../../keyring';
|
|
2
2
|
declare type WritableFS = web3n.files.WritableFS;
|
|
3
3
|
declare type MsgInfo = web3n.asmail.MsgInfo;
|
|
4
4
|
/**
|
|
@@ -14,9 +14,8 @@ declare type MsgInfo = web3n.asmail.MsgInfo;
|
|
|
14
14
|
*
|
|
15
15
|
*/
|
|
16
16
|
export declare class MsgIndex {
|
|
17
|
-
private readonly
|
|
18
|
-
private readonly
|
|
19
|
-
private readonly changes;
|
|
17
|
+
private readonly logs;
|
|
18
|
+
private readonly indexed;
|
|
20
19
|
private constructor();
|
|
21
20
|
static make(syncedFS: WritableFS): Promise<MsgIndex>;
|
|
22
21
|
stopSyncing(): void;
|
|
@@ -0,0 +1,96 @@
|
|
|
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.MsgIndex = void 0;
|
|
20
|
+
const logs_n_entries_1 = require("./logs-n-entries");
|
|
21
|
+
const LOGS_DIR = 'logs';
|
|
22
|
+
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;
|
|
76
|
+
}
|
|
77
|
+
await this.logs.add(msgInfo, decrInfo, removeAfter);
|
|
78
|
+
await this.indexed.add(msgInfo, decrInfo, removeAfter);
|
|
79
|
+
}
|
|
80
|
+
async remove(msgId) {
|
|
81
|
+
const deliveryTS = await this.indexed.remove(msgId);
|
|
82
|
+
if (deliveryTS) {
|
|
83
|
+
await this.logs.remove(msgId, deliveryTS);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
listMsgs(fromTS) {
|
|
87
|
+
return this.indexed.listMsgs(fromTS);
|
|
88
|
+
}
|
|
89
|
+
getKeyFor(msgId, deliveryTS) {
|
|
90
|
+
return this.indexed.getKeyFor(msgId, deliveryTS);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.MsgIndex = MsgIndex;
|
|
94
|
+
Object.freeze(MsgIndex.prototype);
|
|
95
|
+
Object.freeze(MsgIndex);
|
|
96
|
+
Object.freeze(exports);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { MsgKeyInfo, MsgKeyRole } from '../../keyring';
|
|
2
|
+
declare type WritableFS = web3n.files.WritableFS;
|
|
3
|
+
declare type MsgInfo = web3n.asmail.MsgInfo;
|
|
4
|
+
interface MsgOpenedLog {
|
|
5
|
+
msgState: 'opened';
|
|
6
|
+
msgId: string;
|
|
7
|
+
msgType: string;
|
|
8
|
+
deliveryTS: number;
|
|
9
|
+
keyB64: string;
|
|
10
|
+
keyStatus: MsgKeyRole;
|
|
11
|
+
mainObjHeaderOfs: number;
|
|
12
|
+
removeAfter?: number;
|
|
13
|
+
}
|
|
14
|
+
interface MsgRemovedLog {
|
|
15
|
+
msgState: 'removed';
|
|
16
|
+
msgId: string;
|
|
17
|
+
deliveryTS: number;
|
|
18
|
+
}
|
|
19
|
+
declare type MsgLog = MsgOpenedLog | MsgRemovedLog;
|
|
20
|
+
export interface MsgKey {
|
|
21
|
+
msgKey: Uint8Array;
|
|
22
|
+
msgKeyRole: MsgKeyRole;
|
|
23
|
+
mainObjHeaderOfs: number;
|
|
24
|
+
}
|
|
25
|
+
export interface IndexedRecords {
|
|
26
|
+
add(msgInfo: MsgInfo, decrInfo: MsgKeyInfo, removeAfter: number): Promise<void>;
|
|
27
|
+
remove(msgId: string): Promise<number | undefined>;
|
|
28
|
+
listMsgs(fromTS: number | undefined): Promise<MsgInfo[]>;
|
|
29
|
+
getKeyFor(msgId: string, deliveryTS: number): Promise<MsgKey | undefined>;
|
|
30
|
+
msgExists(msgInfo: MsgInfo): Promise<boolean>;
|
|
31
|
+
stopSyncing(): void;
|
|
32
|
+
}
|
|
33
|
+
export declare function makeJsonBasedIndexedRecords(logs: MsgLogs): IndexedRecords;
|
|
34
|
+
export declare class MsgLogs {
|
|
35
|
+
private readonly fs;
|
|
36
|
+
private latest;
|
|
37
|
+
private fileTSs;
|
|
38
|
+
private readonly changeProc;
|
|
39
|
+
private constructor();
|
|
40
|
+
static makeAndStartSyncing(logsFS: WritableFS): Promise<MsgLogs>;
|
|
41
|
+
stopSyncing(): void;
|
|
42
|
+
add(msgInfo: MsgInfo, decrInfo: MsgKeyInfo, removeAfter: number | undefined): Promise<void>;
|
|
43
|
+
private updateLogsFile;
|
|
44
|
+
getLogsFromFile(fileTS: number | undefined): Promise<MsgLog[] | undefined>;
|
|
45
|
+
private fileTSforMsgTS;
|
|
46
|
+
remove(msgId: string, deliveryTS: number): Promise<void>;
|
|
47
|
+
private scheduleRemovalOfMsgOpenedLog;
|
|
48
|
+
getMsgOpenedLog(msgId: string, deliveryTS: number): Promise<MsgOpenedLog | undefined>;
|
|
49
|
+
getLogFileTSs(): number[];
|
|
50
|
+
getLatestMsgLogs(): MsgLog[];
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (C) 2016 - 2020, 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.MsgLogs = exports.makeJsonBasedIndexedRecords = void 0;
|
|
20
|
+
const synced_1 = require("../../../../lib-common/processes/synced");
|
|
21
|
+
const buffer_utils_1 = require("../../../../lib-common/buffer-utils");
|
|
22
|
+
const timed_cache_1 = require("../../../../lib-common/timed-cache");
|
|
23
|
+
const error_1 = require("../../../../lib-common/exceptions/error");
|
|
24
|
+
const LIMIT_RECORDS_PER_FILE = 200;
|
|
25
|
+
const LATEST_LOG = 'latest.json';
|
|
26
|
+
const LOG_EXT = '.json';
|
|
27
|
+
const INDEX_FNAME_REGEXP = /^\d+\.json$/;
|
|
28
|
+
function logsFileName(fileTS) {
|
|
29
|
+
return (fileTS ? LATEST_LOG : `${fileTS}${LOG_EXT}`);
|
|
30
|
+
}
|
|
31
|
+
function fileTSOrderComparator(a, b) {
|
|
32
|
+
return (a - b);
|
|
33
|
+
}
|
|
34
|
+
function insertInto(records, rec) {
|
|
35
|
+
if (records.length === 0) {
|
|
36
|
+
records.push(rec);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const ts = rec.deliveryTS;
|
|
40
|
+
for (let i = (records.length - 1); i >= 0; i -= 1) {
|
|
41
|
+
if (records[i].deliveryTS <= ts) {
|
|
42
|
+
records.splice(i + 1, 0, rec);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
records.splice(0, 0, rec);
|
|
47
|
+
}
|
|
48
|
+
function removeMsgOpenedLogFrom(logs, msgId) {
|
|
49
|
+
const indToRm = logs.findIndex(log => ((log.msgId === msgId) && (log.msgState === 'opened')));
|
|
50
|
+
if (indToRm > -1) {
|
|
51
|
+
logs.splice(indToRm, 1);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* This is a catch callback, which returns undefined on file(folder) not found
|
|
60
|
+
* exception, and re-throws all other exceptions/errors.
|
|
61
|
+
*/
|
|
62
|
+
function notFoundOrReThrow(exc) {
|
|
63
|
+
if (!exc.notFound) {
|
|
64
|
+
throw exc;
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
function makeJsonBasedIndexedRecords(logs) {
|
|
69
|
+
return new JsonBasedIndexedRecords(logs);
|
|
70
|
+
}
|
|
71
|
+
exports.makeJsonBasedIndexedRecords = makeJsonBasedIndexedRecords;
|
|
72
|
+
function toMsgOpenedLog(msgInfo, decrInfo, removeAfter) {
|
|
73
|
+
if (!decrInfo.key) {
|
|
74
|
+
throw new Error(`Given message decryption info doesn't have a key for message ${msgInfo.msgId}`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
msgState: 'opened',
|
|
78
|
+
msgType: msgInfo.msgType,
|
|
79
|
+
msgId: msgInfo.msgId,
|
|
80
|
+
deliveryTS: msgInfo.deliveryTS,
|
|
81
|
+
keyB64: buffer_utils_1.base64.pack(decrInfo.key),
|
|
82
|
+
keyStatus: decrInfo.keyStatus,
|
|
83
|
+
mainObjHeaderOfs: decrInfo.msgKeyPackLen,
|
|
84
|
+
removeAfter
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function keyInfoFrom(log) {
|
|
88
|
+
return {
|
|
89
|
+
msgKey: buffer_utils_1.base64.open(log.keyB64),
|
|
90
|
+
msgKeyRole: log.keyStatus,
|
|
91
|
+
mainObjHeaderOfs: log.mainObjHeaderOfs
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function addOnlyNonRemovedMsgs(dst, ignore, src) {
|
|
95
|
+
for (const log of src) {
|
|
96
|
+
if (log.msgState === 'removed') {
|
|
97
|
+
ignore.add(log.msgId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const log of src) {
|
|
101
|
+
if ((log.msgState === 'opened') && !ignore.has(log.msgId)) {
|
|
102
|
+
dst.push(log);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function cutEarlierMsgs(msgs, fromTS) {
|
|
107
|
+
let timedEnd = msgs.length;
|
|
108
|
+
for (let i = (msgs.length - 1); i >= 0; i -= 1) {
|
|
109
|
+
const msg = msgs[i];
|
|
110
|
+
if (msg.deliveryTS < fromTS) {
|
|
111
|
+
timedEnd = i;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (timedEnd < msgs.length) {
|
|
118
|
+
msgs.splice(timedEnd, (msgs.length - timedEnd));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
class JsonBasedIndexedRecords {
|
|
122
|
+
constructor(logs) {
|
|
123
|
+
this.logs = logs;
|
|
124
|
+
this.cachedLogs = (0, timed_cache_1.makeTimedCache)(10 * 60 * 1000);
|
|
125
|
+
Object.seal(this);
|
|
126
|
+
}
|
|
127
|
+
async add(msgInfo, decrInfo, removeAfter) {
|
|
128
|
+
const msg = toMsgOpenedLog(msgInfo, decrInfo, removeAfter);
|
|
129
|
+
this.cachedLogs.set(msg.msgId, msg);
|
|
130
|
+
}
|
|
131
|
+
async remove(msgId) {
|
|
132
|
+
let foundLog = this.cachedLogs.get(msgId);
|
|
133
|
+
if (foundLog) {
|
|
134
|
+
this.cachedLogs.delete(msgId);
|
|
135
|
+
return foundLog.deliveryTS;
|
|
136
|
+
}
|
|
137
|
+
for (const fileTS of [undefined, ...this.logs.getLogFileTSs()]) {
|
|
138
|
+
let logs;
|
|
139
|
+
if ((fileTS === undefined)) {
|
|
140
|
+
logs = this.logs.getLatestMsgLogs();
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const logsFromFile = await this.logs.getLogsFromFile(fileTS);
|
|
144
|
+
if (!logsFromFile) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
logs = logsFromFile;
|
|
148
|
+
}
|
|
149
|
+
for (const log of logs) {
|
|
150
|
+
if (log.msgId === msgId) {
|
|
151
|
+
return log.deliveryTS;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
async listMsgs(fromTS) {
|
|
158
|
+
const ignore = new Set();
|
|
159
|
+
const msgs = [];
|
|
160
|
+
addOnlyNonRemovedMsgs(msgs, ignore, this.logs.getLatestMsgLogs());
|
|
161
|
+
const fileTSs = this.logs.getLogFileTSs();
|
|
162
|
+
if (fromTS === undefined) {
|
|
163
|
+
for (const fileTS of fileTSs) {
|
|
164
|
+
const logs = await this.logs.getLogsFromFile(fileTS);
|
|
165
|
+
if (!logs) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
addOnlyNonRemovedMsgs(msgs, ignore, logs);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
for (const fileTS of fileTSs) {
|
|
173
|
+
if (fromTS > fileTS) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
const logs = await this.logs.getLogsFromFile(fileTS);
|
|
177
|
+
if (!logs) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
addOnlyNonRemovedMsgs(msgs, ignore, logs);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// note sorting later messages to array's head
|
|
184
|
+
msgs.sort((m1, m2) => (m2.deliveryTS - m1.deliveryTS));
|
|
185
|
+
if (fromTS !== undefined) {
|
|
186
|
+
cutEarlierMsgs(msgs, fromTS);
|
|
187
|
+
}
|
|
188
|
+
for (const log of msgs) {
|
|
189
|
+
this.cachedLogs.set(log.msgId, log);
|
|
190
|
+
}
|
|
191
|
+
return msgs.map(log => ({
|
|
192
|
+
msgId: log.msgId,
|
|
193
|
+
msgType: log.msgType,
|
|
194
|
+
deliveryTS: log.deliveryTS
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
async getKeyFor(msgId, deliveryTS) {
|
|
198
|
+
let log = this.cachedLogs.get(msgId);
|
|
199
|
+
if (log) {
|
|
200
|
+
return keyInfoFrom(log);
|
|
201
|
+
}
|
|
202
|
+
log = await this.logs.getMsgOpenedLog(msgId, deliveryTS);
|
|
203
|
+
if (!log) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
this.cachedLogs.set(log.msgId, log);
|
|
207
|
+
return keyInfoFrom(log);
|
|
208
|
+
}
|
|
209
|
+
async msgExists(msgInfo) {
|
|
210
|
+
const foundLog = this.cachedLogs.get(msgInfo.msgId);
|
|
211
|
+
return (!!foundLog && (foundLog.deliveryTS === msgInfo.deliveryTS));
|
|
212
|
+
}
|
|
213
|
+
stopSyncing() { }
|
|
214
|
+
}
|
|
215
|
+
Object.freeze(JsonBasedIndexedRecords.prototype);
|
|
216
|
+
Object.freeze(JsonBasedIndexedRecords);
|
|
217
|
+
class MsgLogs {
|
|
218
|
+
constructor(fs, latest, fileTSs) {
|
|
219
|
+
this.fs = fs;
|
|
220
|
+
this.latest = latest;
|
|
221
|
+
this.fileTSs = fileTSs;
|
|
222
|
+
this.changeProc = new synced_1.SingleProc();
|
|
223
|
+
Object.seal(this);
|
|
224
|
+
}
|
|
225
|
+
static async makeAndStartSyncing(logsFS) {
|
|
226
|
+
const fName = logsFileName(undefined);
|
|
227
|
+
let latest = await logsFS.readJSONFile(fName)
|
|
228
|
+
.catch(notFoundOrReThrow);
|
|
229
|
+
if (!latest) {
|
|
230
|
+
latest = [];
|
|
231
|
+
await logsFS.writeJSONFile(fName, latest, { create: true, exclusive: true });
|
|
232
|
+
}
|
|
233
|
+
const fileTSs = (await logsFS.listFolder('.'))
|
|
234
|
+
.map(f => f.name)
|
|
235
|
+
.filter(fName => fName.match(INDEX_FNAME_REGEXP))
|
|
236
|
+
.map(fName => parseInt(fName.substring(0, fName.length - LOG_EXT.length)))
|
|
237
|
+
.filter(fileTS => !isNaN(fileTS))
|
|
238
|
+
.sort(fileTSOrderComparator);
|
|
239
|
+
const logs = new MsgLogs(logsFS, latest, fileTSs);
|
|
240
|
+
return logs;
|
|
241
|
+
}
|
|
242
|
+
stopSyncing() {
|
|
243
|
+
// XXX fill this with content
|
|
244
|
+
}
|
|
245
|
+
async add(msgInfo, decrInfo, removeAfter) {
|
|
246
|
+
const msg = toMsgOpenedLog(msgInfo, decrInfo, removeAfter);
|
|
247
|
+
return this.changeProc.startOrChain(async () => {
|
|
248
|
+
const fileTS = this.fileTSforMsgTS(msg.deliveryTS);
|
|
249
|
+
const logs = ((fileTS === undefined) ?
|
|
250
|
+
this.latest : await this.getLogsFromFile(fileTS));
|
|
251
|
+
if (!logs) {
|
|
252
|
+
throw (0, error_1.errWithCause)(`${logsFileName(fileTS)} not found`, `Expectation fail: there should be some message records.`);
|
|
253
|
+
}
|
|
254
|
+
insertInto(logs, msg);
|
|
255
|
+
await this.updateLogsFile(fileTS, logs);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async updateLogsFile(fileTS, logs) {
|
|
259
|
+
await this.fs.writeJSONFile(logsFileName(fileTS), logs, { create: false });
|
|
260
|
+
}
|
|
261
|
+
async getLogsFromFile(fileTS) {
|
|
262
|
+
const fName = logsFileName(undefined);
|
|
263
|
+
const logs = await this.fs.readJSONFile(fName)
|
|
264
|
+
.catch(notFoundOrReThrow);
|
|
265
|
+
return logs;
|
|
266
|
+
}
|
|
267
|
+
fileTSforMsgTS(deliveryTS) {
|
|
268
|
+
if ((this.fileTSs.length === 0)
|
|
269
|
+
|| (deliveryTS >= this.fileTSs[this.fileTSs.length - 1])) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
let fileTS = this.fileTSs[this.fileTSs.length - 1];
|
|
273
|
+
for (let i = (this.fileTSs.length - 2); i <= 0; i -= 1) {
|
|
274
|
+
if (deliveryTS >= this.fileTSs[i]) {
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
fileTS = this.fileTSs[i];
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return fileTS;
|
|
282
|
+
}
|
|
283
|
+
async remove(msgId, deliveryTS) {
|
|
284
|
+
const msgRmLog = {
|
|
285
|
+
msgState: 'removed',
|
|
286
|
+
msgId,
|
|
287
|
+
deliveryTS
|
|
288
|
+
};
|
|
289
|
+
return this.changeProc.startOrChain(async () => {
|
|
290
|
+
this.latest.push(msgRmLog);
|
|
291
|
+
const fileTS = this.fileTSforMsgTS(deliveryTS);
|
|
292
|
+
if (fileTS === undefined) {
|
|
293
|
+
removeMsgOpenedLogFrom(this.latest, msgId);
|
|
294
|
+
}
|
|
295
|
+
await this.updateLogsFile(undefined, this.latest);
|
|
296
|
+
if (fileTS !== undefined) {
|
|
297
|
+
this.scheduleRemovalOfMsgOpenedLog(fileTS, msgId);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
scheduleRemovalOfMsgOpenedLog(fileTS, msgId) {
|
|
302
|
+
this.changeProc.startOrChain(async () => {
|
|
303
|
+
const logs = await this.getLogsFromFile(fileTS);
|
|
304
|
+
if (!logs) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const changed = removeMsgOpenedLogFrom(logs, msgId);
|
|
308
|
+
if (!changed) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
await this.updateLogsFile(fileTS, logs);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
async getMsgOpenedLog(msgId, deliveryTS) {
|
|
315
|
+
const fileTS = this.fileTSforMsgTS(deliveryTS);
|
|
316
|
+
const logs = ((fileTS === undefined) ?
|
|
317
|
+
this.latest : await this.getLogsFromFile(fileTS));
|
|
318
|
+
if (!logs) {
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
return logs.find(log => ((log.msgId === msgId) && (log.msgState === 'opened')));
|
|
322
|
+
}
|
|
323
|
+
getLogFileTSs() {
|
|
324
|
+
return this.fileTSs.concat([]);
|
|
325
|
+
}
|
|
326
|
+
getLatestMsgLogs() {
|
|
327
|
+
return this.latest.concat([]);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
exports.MsgLogs = MsgLogs;
|
|
331
|
+
Object.freeze(MsgLogs.prototype);
|
|
332
|
+
Object.freeze(MsgLogs);
|
|
333
|
+
Object.freeze(exports);
|