@vrtmrz/livesync-commonlib 0.1.18 → 0.1.19-rc.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/dist/API/DirectFileManipulatorV2.js +3 -1
- package/dist/common/models/fileaccess.type.d.ts +7 -0
- package/dist/interfaces/DatabaseRebuilder.d.ts +4 -2
- package/dist/managers/StorageEventManager.d.ts +16 -5
- package/dist/managers/StorageEventManager.js +46 -8
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
- package/dist/pouchdb/LiveSyncLocalDB.js +120 -38
- package/dist/serviceFeatures/prepareDatabaseForUse.js +18 -15
- package/dist/serviceModules/Rebuilder.d.ts +27 -4
- package/dist/serviceModules/Rebuilder.js +113 -35
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +17 -0
- package/dist/serviceModules/ServiceFileHandlerBase.js +145 -9
- package/dist/services/base/DatabaseService.d.ts +11 -3
- package/dist/services/base/DatabaseService.js +63 -7
- package/dist/services/base/IService.d.ts +1 -0
- package/dist/services/base/ReplicationService.d.ts +16 -0
- package/dist/services/base/ReplicationService.js +36 -4
- package/dist/services/base/ReplicatorService.d.ts +1 -0
- package/dist/services/base/ReplicatorService.js +32 -13
- package/docs/development.md +2 -0
- package/docs/proven-in-use.md +3 -1
- package/docs/releasing.md +22 -0
- package/package.json +1 -1
|
@@ -48,7 +48,9 @@ class DirectFileManipulator {
|
|
|
48
48
|
async init() {
|
|
49
49
|
try {
|
|
50
50
|
await this.services.appLifecycle.onReady();
|
|
51
|
-
await this.liveSyncLocalDB.initializeDatabase()
|
|
51
|
+
if (!await this.liveSyncLocalDB.initializeDatabase()) {
|
|
52
|
+
throw new Error("Direct database initialisation was rejected.");
|
|
53
|
+
}
|
|
52
54
|
this.liveSyncLocalDB.refreshSettings();
|
|
53
55
|
this.ready.resolve();
|
|
54
56
|
} catch (error) {
|
|
@@ -59,6 +59,13 @@ export type FileEventItem = {
|
|
|
59
59
|
type: FileEventType;
|
|
60
60
|
args: FileEventArgs;
|
|
61
61
|
key: string;
|
|
62
|
+
/**
|
|
63
|
+
* The event was loaded from the persisted storage-operation snapshot.
|
|
64
|
+
*
|
|
65
|
+
* A restored event records pending operation intent, not current storage
|
|
66
|
+
* state. File handlers must revalidate it before applying side effects.
|
|
67
|
+
*/
|
|
68
|
+
restoredFromPreviousRuntime?: true;
|
|
62
69
|
skipBatchWait?: boolean;
|
|
63
70
|
cancelled?: boolean;
|
|
64
71
|
batched?: boolean;
|
|
@@ -17,7 +17,9 @@ export interface Rebuilder {
|
|
|
17
17
|
/** See {@link scheduleRebuild}; this variant writes the Fetch flag. */
|
|
18
18
|
scheduleFetch(prepareBeforeRestart?: () => Promise<void>): Promise<boolean>;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Complete the final reflection scan and checks owned by a fetch, then establish application readiness.
|
|
21
|
+
*
|
|
22
|
+
* @returns `true` when every required completion phase succeeded; otherwise, `false`.
|
|
21
23
|
*/
|
|
22
|
-
finishRebuild(): Promise<
|
|
24
|
+
finishRebuild(): Promise<boolean>;
|
|
23
25
|
}
|
|
@@ -21,6 +21,9 @@ type FileEventItemSentinelFlush = {
|
|
|
21
21
|
type: typeof TYPE_SENTINEL_FLUSH;
|
|
22
22
|
};
|
|
23
23
|
export type FileEventItemSentinel = FileEventItemSentinelFlush;
|
|
24
|
+
type RunQueuedEventsOptions = {
|
|
25
|
+
waitForProcessing?: boolean;
|
|
26
|
+
};
|
|
24
27
|
export interface StorageEventManagerBaseDependencies {
|
|
25
28
|
setting: SettingService;
|
|
26
29
|
vaultService: IVaultService;
|
|
@@ -61,13 +64,11 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
|
|
|
61
64
|
protected updateStatus(): void;
|
|
62
65
|
/**
|
|
63
66
|
* Snapshot restoration promise.
|
|
64
|
-
*
|
|
65
|
-
* In designed time, this has been called from Initialisation process, which has been implemented on `ModuleInitializerFile.ts`.
|
|
67
|
+
* It resolves after the restored file operations have finished, before Vault watching begins.
|
|
66
68
|
*/
|
|
67
69
|
snapShotRestored: Promise<void> | null;
|
|
68
70
|
/**
|
|
69
|
-
* Restore the previous snapshot
|
|
70
|
-
* @returns
|
|
71
|
+
* Restore and complete the previous storage-event snapshot when it exists.
|
|
71
72
|
*/
|
|
72
73
|
restoreState(): Promise<void>;
|
|
73
74
|
appendQueue(params: FileEvent[], ctx?: unknown): Promise<void>;
|
|
@@ -111,7 +112,17 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
|
|
|
111
112
|
processFileEvent(fei: FileEventItem): Promise<void>;
|
|
112
113
|
_takeSnapshot(): Promise<void>;
|
|
113
114
|
_restoreFromSnapshot(): Promise<void>;
|
|
114
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Dispatch buffered storage events.
|
|
117
|
+
*
|
|
118
|
+
* Ordinary watcher events retain the existing fire-and-forget behaviour.
|
|
119
|
+
* Snapshot restoration requests completion so its caller can use the replay as
|
|
120
|
+
* a lifecycle boundary. In that mode, a sentinel also waits for every operation
|
|
121
|
+
* dispatched before it, preserving the ordering represented by the snapshot.
|
|
122
|
+
* An individual restored operation failure is logged but does not prevent the
|
|
123
|
+
* following Offline Scanner from reconciling the resulting current state.
|
|
124
|
+
*/
|
|
125
|
+
protected runQueuedEvents(options?: RunQueuedEventsOptions): Promise<void>;
|
|
115
126
|
protected processingCount: number;
|
|
116
127
|
protected requestProcessQueue(fei: FileEventItem): Promise<void>;
|
|
117
128
|
isWaiting(filename: FilePath): boolean;
|
|
@@ -72,13 +72,11 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Snapshot restoration promise.
|
|
75
|
-
*
|
|
76
|
-
* In designed time, this has been called from Initialisation process, which has been implemented on `ModuleInitializerFile.ts`.
|
|
75
|
+
* It resolves after the restored file operations have finished, before Vault watching begins.
|
|
77
76
|
*/
|
|
78
77
|
snapShotRestored = null;
|
|
79
78
|
/**
|
|
80
|
-
* Restore the previous snapshot
|
|
81
|
-
* @returns
|
|
79
|
+
* Restore and complete the previous storage-event snapshot when it exists.
|
|
82
80
|
*/
|
|
83
81
|
restoreState() {
|
|
84
82
|
this.snapShotRestored = this._restoreFromSnapshot();
|
|
@@ -371,15 +369,44 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
371
369
|
const snapShot = await this._loadSnapshot();
|
|
372
370
|
if (snapShot && Array.isArray(snapShot) && snapShot.length > 0) {
|
|
373
371
|
this._log(`Restoring storage operation snapshot: ${snapShot.length} items`, LOG_LEVEL_VERBOSE);
|
|
374
|
-
this.bufferedQueuedItems = snapShot.map(
|
|
372
|
+
this.bufferedQueuedItems = snapShot.map(
|
|
373
|
+
(event) => event.type === TYPE_SENTINEL_FLUSH ? event : { ...event, restoredFromPreviousRuntime: true, skipBatchWait: true }
|
|
374
|
+
);
|
|
375
375
|
this.updateStatus();
|
|
376
|
-
await this.runQueuedEvents();
|
|
376
|
+
await this.runQueuedEvents({ waitForProcessing: true });
|
|
377
377
|
} else {
|
|
378
378
|
this._log(`No snapshot to restore`, LOG_LEVEL_VERBOSE);
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
|
-
|
|
381
|
+
/**
|
|
382
|
+
* Dispatch buffered storage events.
|
|
383
|
+
*
|
|
384
|
+
* Ordinary watcher events retain the existing fire-and-forget behaviour.
|
|
385
|
+
* Snapshot restoration requests completion so its caller can use the replay as
|
|
386
|
+
* a lifecycle boundary. In that mode, a sentinel also waits for every operation
|
|
387
|
+
* dispatched before it, preserving the ordering represented by the snapshot.
|
|
388
|
+
* An individual restored operation failure is logged but does not prevent the
|
|
389
|
+
* following Offline Scanner from reconciling the resulting current state.
|
|
390
|
+
*/
|
|
391
|
+
runQueuedEvents(options = {}) {
|
|
392
|
+
const waitForProcessing = options.waitForProcessing ?? false;
|
|
382
393
|
return skipIfDuplicated("storage-event-manager-run-queued-events", async () => {
|
|
394
|
+
let dispatched = [];
|
|
395
|
+
const waitForDispatched = async () => {
|
|
396
|
+
if (dispatched.length === 0) return;
|
|
397
|
+
const pending = dispatched;
|
|
398
|
+
dispatched = [];
|
|
399
|
+
const results = await Promise.allSettled(pending.map(({ processing }) => processing));
|
|
400
|
+
for (const [index, result] of results.entries()) {
|
|
401
|
+
if (result.status === "rejected") {
|
|
402
|
+
this._log(
|
|
403
|
+
`Restored storage operation failed for ${pending[index].path}; the Offline Scanner will reconcile the current state`,
|
|
404
|
+
LOG_LEVEL_NOTICE
|
|
405
|
+
);
|
|
406
|
+
this._log(result.reason, LOG_LEVEL_VERBOSE);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
383
410
|
do {
|
|
384
411
|
if (this.bufferedQueuedItems.length === 0) {
|
|
385
412
|
break;
|
|
@@ -392,12 +419,23 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
392
419
|
this.updateStatus();
|
|
393
420
|
if (fei.type === TYPE_SENTINEL_FLUSH) {
|
|
394
421
|
this._log(`Waiting for idle`, LOG_LEVEL_VERBOSE);
|
|
422
|
+
if (waitForProcessing) {
|
|
423
|
+
await waitForDispatched();
|
|
424
|
+
}
|
|
395
425
|
await this.waitForIdle();
|
|
396
426
|
this.updateStatus();
|
|
397
427
|
continue;
|
|
398
428
|
}
|
|
399
|
-
|
|
429
|
+
const processing = this.processFileEvent(fei);
|
|
430
|
+
if (waitForProcessing) {
|
|
431
|
+
dispatched.push({ path: fei.args.file.path, processing });
|
|
432
|
+
} else {
|
|
433
|
+
fireAndForget(processing);
|
|
434
|
+
}
|
|
400
435
|
} while (this.bufferedQueuedItems.length > 0);
|
|
436
|
+
if (waitForProcessing) {
|
|
437
|
+
await waitForDispatched();
|
|
438
|
+
}
|
|
401
439
|
});
|
|
402
440
|
}
|
|
403
441
|
processingCount = 0;
|
|
@@ -46,11 +46,17 @@ export declare class LiveSyncLocalDB {
|
|
|
46
46
|
env: LiveSyncLocalDBEnv;
|
|
47
47
|
clearCaches(): void;
|
|
48
48
|
_prepareHashFunctions(): Promise<void>;
|
|
49
|
-
onunload(): void;
|
|
50
49
|
refreshSettings(): void;
|
|
51
50
|
offRemoteChunkFetchedHandler?: ReturnType<LiveSyncEventHub["onEvent"]>;
|
|
51
|
+
private databaseCloseCleanup?;
|
|
52
|
+
private databaseUnloadNotification?;
|
|
52
53
|
constructor(dbname: string, env: LiveSyncLocalDBEnv);
|
|
54
|
+
/** Notify dependent services at most once for each physical database lifecycle. */
|
|
55
|
+
private notifyDatabaseUnload;
|
|
56
|
+
onunload(): void;
|
|
53
57
|
close(): Promise<void>;
|
|
58
|
+
private teardownDatabaseDependencies;
|
|
59
|
+
private rollbackFailedInitialisation;
|
|
54
60
|
onNewLeaf(chunk: EntryLeaf): void;
|
|
55
61
|
initializeDatabase(): Promise<boolean>;
|
|
56
62
|
/**
|
|
@@ -37,16 +37,14 @@ class LiveSyncLocalDB {
|
|
|
37
37
|
async _prepareHashFunctions() {
|
|
38
38
|
await this._managers?.prepareHashFunction();
|
|
39
39
|
}
|
|
40
|
-
onunload() {
|
|
41
|
-
void this.env.services.databaseEvents.onUnloadDatabase(this);
|
|
42
|
-
this.localDatabase.removeAllListeners();
|
|
43
|
-
}
|
|
44
40
|
refreshSettings() {
|
|
45
41
|
const settings = this.env.services.setting.currentSettings();
|
|
46
42
|
this.settings = settings;
|
|
47
43
|
void this._prepareHashFunctions();
|
|
48
44
|
}
|
|
49
45
|
offRemoteChunkFetchedHandler;
|
|
46
|
+
databaseCloseCleanup;
|
|
47
|
+
databaseUnloadNotification;
|
|
50
48
|
constructor(dbname, env) {
|
|
51
49
|
this.auth = {
|
|
52
50
|
username: "",
|
|
@@ -57,14 +55,79 @@ class LiveSyncLocalDB {
|
|
|
57
55
|
this._log = createInstanceLogFunction("LiveSyncLocalDB", this.env.services.API);
|
|
58
56
|
this.refreshSettings();
|
|
59
57
|
}
|
|
58
|
+
/** Notify dependent services at most once for each physical database lifecycle. */
|
|
59
|
+
notifyDatabaseUnload() {
|
|
60
|
+
if (!this.databaseUnloadNotification) {
|
|
61
|
+
this.databaseUnloadNotification = Promise.resolve().then(async () => {
|
|
62
|
+
await this.env.services.databaseEvents.onUnloadDatabase(this);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return this.databaseUnloadNotification;
|
|
66
|
+
}
|
|
67
|
+
onunload() {
|
|
68
|
+
void this.notifyDatabaseUnload();
|
|
69
|
+
}
|
|
60
70
|
async close() {
|
|
61
71
|
this._log("Database closed (by close)");
|
|
62
72
|
this.isReady = false;
|
|
63
73
|
this.offRemoteChunkFetchedHandler?.();
|
|
74
|
+
this.offRemoteChunkFetchedHandler = void 0;
|
|
64
75
|
if (this.localDatabase != null) {
|
|
65
|
-
|
|
76
|
+
const database = this.localDatabase;
|
|
77
|
+
await database.close();
|
|
78
|
+
if (this.databaseCloseCleanup?.database === database) {
|
|
79
|
+
await this.databaseCloseCleanup.promise;
|
|
80
|
+
}
|
|
81
|
+
if (this.localDatabase === database) {
|
|
82
|
+
this.localDatabase = null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
this._managers = void 0;
|
|
86
|
+
await this.notifyDatabaseUnload();
|
|
87
|
+
}
|
|
88
|
+
async teardownDatabaseDependencies(managers) {
|
|
89
|
+
try {
|
|
90
|
+
await Promise.resolve(this.env.services.replicator.getActiveReplicator()?.closeReplication());
|
|
91
|
+
} catch (error) {
|
|
92
|
+
this._log("Failed to close replication while tearing down the local database.", LOG_LEVEL_VERBOSE);
|
|
93
|
+
this._log(error, LOG_LEVEL_VERBOSE);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
await managers.teardownManagers();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this._log("Failed to tear down managers while closing the local database.", LOG_LEVEL_VERBOSE);
|
|
99
|
+
this._log(error, LOG_LEVEL_VERBOSE);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async rollbackFailedInitialisation(database, managers) {
|
|
103
|
+
this.isReady = false;
|
|
104
|
+
this.offRemoteChunkFetchedHandler?.();
|
|
105
|
+
this.offRemoteChunkFetchedHandler = void 0;
|
|
106
|
+
const closeCleanup = this.databaseCloseCleanup?.database === database ? this.databaseCloseCleanup : void 0;
|
|
107
|
+
if (closeCleanup) {
|
|
108
|
+
await closeCleanup.promise;
|
|
109
|
+
} else {
|
|
110
|
+
database.removeAllListeners();
|
|
111
|
+
await this.teardownDatabaseDependencies(managers);
|
|
112
|
+
try {
|
|
113
|
+
await database.close();
|
|
114
|
+
} catch (error) {
|
|
115
|
+
this._log("Failed to close the database after its initialisation was rejected.", LOG_LEVEL_VERBOSE);
|
|
116
|
+
this._log(error, LOG_LEVEL_VERBOSE);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (this.localDatabase === database) {
|
|
120
|
+
this.localDatabase = null;
|
|
121
|
+
}
|
|
122
|
+
if (this._managers === managers) {
|
|
123
|
+
this._managers = void 0;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
await this.notifyDatabaseUnload();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
this._log("A database unload handler failed after initialisation was rejected.", LOG_LEVEL_VERBOSE);
|
|
129
|
+
this._log(error, LOG_LEVEL_VERBOSE);
|
|
66
130
|
}
|
|
67
|
-
await this.env.services.databaseEvents.onUnloadDatabase(this);
|
|
68
131
|
}
|
|
69
132
|
onNewLeaf(chunk) {
|
|
70
133
|
this.managers.chunkManager?.emitEvent(EVENT_CHUNK_FETCHED, chunk);
|
|
@@ -84,6 +147,7 @@ class LiveSyncLocalDB {
|
|
|
84
147
|
deterministic_revs: true
|
|
85
148
|
}
|
|
86
149
|
);
|
|
150
|
+
this.databaseUnloadNotification = void 0;
|
|
87
151
|
const manager = new LiveSyncManagers({
|
|
88
152
|
database: this.localDatabase,
|
|
89
153
|
APIService: this.env.services.API,
|
|
@@ -93,38 +157,53 @@ class LiveSyncLocalDB {
|
|
|
93
157
|
databaseService: this.env.services.database
|
|
94
158
|
});
|
|
95
159
|
this._managers = manager;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.localDatabase.on("close", () => {
|
|
104
|
-
this._log("Database closed.");
|
|
105
|
-
this.isReady = false;
|
|
106
|
-
this.localDatabase.removeAllListeners();
|
|
107
|
-
this.env.services.replicator.getActiveReplicator()?.closeReplication();
|
|
108
|
-
void this.managers.teardownManagers();
|
|
109
|
-
});
|
|
110
|
-
const _instance = new FallbackWeakRef(this);
|
|
111
|
-
const unload = this.env.services.context.events.onEvent(REMOTE_CHUNK_FETCHED, (chunk) => {
|
|
112
|
-
if (_instance.deref() == null) {
|
|
113
|
-
unload();
|
|
160
|
+
const openedDatabase = this.localDatabase;
|
|
161
|
+
this.databaseCloseCleanup = void 0;
|
|
162
|
+
try {
|
|
163
|
+
if (!await this.env.services.databaseEvents.onDatabaseInitialisation(this)) {
|
|
164
|
+
await this.rollbackFailedInitialisation(openedDatabase, manager);
|
|
165
|
+
this._log("Database initialisation was rejected by a required module.", LOG_LEVEL_NOTICE);
|
|
166
|
+
return false;
|
|
114
167
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
168
|
+
this._log("Opening Database...");
|
|
169
|
+
this._log("Database info", LOG_LEVEL_VERBOSE);
|
|
170
|
+
this._log(JSON.stringify(await openedDatabase.info(), null, 2), LOG_LEVEL_VERBOSE);
|
|
171
|
+
await manager.initialise();
|
|
172
|
+
openedDatabase.on("close", () => {
|
|
173
|
+
this._log("Database closed.");
|
|
174
|
+
this.isReady = false;
|
|
175
|
+
openedDatabase.removeAllListeners();
|
|
176
|
+
if (this.databaseCloseCleanup?.database !== openedDatabase) {
|
|
177
|
+
this.databaseCloseCleanup = {
|
|
178
|
+
database: openedDatabase,
|
|
179
|
+
promise: this.teardownDatabaseDependencies(manager)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
const _instance = new FallbackWeakRef(this);
|
|
184
|
+
const unload = this.env.services.context.events.onEvent(REMOTE_CHUNK_FETCHED, (chunk) => {
|
|
185
|
+
if (_instance.deref() == null) {
|
|
186
|
+
unload();
|
|
187
|
+
}
|
|
188
|
+
_instance.deref()?.onNewLeaf(chunk);
|
|
189
|
+
});
|
|
190
|
+
this.offRemoteChunkFetchedHandler = unload;
|
|
191
|
+
this.isReady = true;
|
|
192
|
+
const readyAccepted = await this.env.services.databaseEvents.onDatabaseHasReady();
|
|
193
|
+
if (!readyAccepted || this.localDatabase !== openedDatabase || !this.isReady) {
|
|
194
|
+
await this.rollbackFailedInitialisation(openedDatabase, manager);
|
|
195
|
+
this._log(
|
|
196
|
+
"Some module has prevented the database from being ready. The database is initialised but not ready for use.",
|
|
197
|
+
LOG_LEVEL_NOTICE
|
|
198
|
+
);
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
this._log("Database is now ready.");
|
|
202
|
+
return true;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
await this.rollbackFailedInitialisation(openedDatabase, manager);
|
|
205
|
+
throw error;
|
|
125
206
|
}
|
|
126
|
-
this._log("Database is now ready.");
|
|
127
|
-
return true;
|
|
128
207
|
}
|
|
129
208
|
/**
|
|
130
209
|
* Retrieve all used and existing chunks in the database.
|
|
@@ -212,6 +291,7 @@ class LiveSyncLocalDB {
|
|
|
212
291
|
return { used, existing };
|
|
213
292
|
}
|
|
214
293
|
async resetDatabase() {
|
|
294
|
+
this.isReady = false;
|
|
215
295
|
await this.managers.teardownManagers();
|
|
216
296
|
this.env.services.replicator.getActiveReplicator()?.closeReplication();
|
|
217
297
|
if (!await this.env.services.databaseEvents.onResetDatabase(this)) {
|
|
@@ -219,10 +299,12 @@ class LiveSyncLocalDB {
|
|
|
219
299
|
return false;
|
|
220
300
|
}
|
|
221
301
|
Logger("Database closed for reset Database.");
|
|
222
|
-
this.isReady = false;
|
|
223
302
|
await this.localDatabase.destroy();
|
|
224
303
|
this.localDatabase = null;
|
|
225
|
-
await this.initializeDatabase()
|
|
304
|
+
if (!await this.initializeDatabase()) {
|
|
305
|
+
this._log("Local Database could not be reinitialised after reset.", LOG_LEVEL_NOTICE);
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
226
308
|
this._log("Local Database Reset", LOG_LEVEL_NOTICE);
|
|
227
309
|
return true;
|
|
228
310
|
}
|
|
@@ -4,26 +4,29 @@ import { createInstanceLogFunction } from "../services/lib/logUtils.js";
|
|
|
4
4
|
async function prepareDatabaseForUse(host, log, errorManager, showingNotice = false, reopenDatabase = true, ignoreSuspending = false) {
|
|
5
5
|
const appLifecycle = host.services.appLifecycle;
|
|
6
6
|
appLifecycle.resetIsReady();
|
|
7
|
-
if (
|
|
7
|
+
if (reopenDatabase && !await host.services.database.openDatabase({
|
|
8
8
|
databaseEvents: host.services.databaseEvents,
|
|
9
9
|
replicator: host.services.replicator
|
|
10
10
|
})) {
|
|
11
|
-
if (host.services.database.localDatabase.isReady) {
|
|
12
|
-
await host.services.vault.scanVault(showingNotice, ignoreSuspending);
|
|
13
|
-
}
|
|
14
|
-
const ERR_INITIALISATION_FAILED = `Initializing database has been failed on some module!`;
|
|
15
|
-
if (!await host.services.databaseEvents.onDatabaseInitialised(showingNotice)) {
|
|
16
|
-
errorManager.showError(ERR_INITIALISATION_FAILED, LOG_LEVEL_NOTICE);
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
errorManager.clearError(ERR_INITIALISATION_FAILED);
|
|
20
|
-
appLifecycle.markIsReady();
|
|
21
|
-
await host.services.fileProcessing.commitPendingFileEvents();
|
|
22
|
-
return true;
|
|
23
|
-
} else {
|
|
24
|
-
appLifecycle.resetIsReady();
|
|
25
11
|
return false;
|
|
26
12
|
}
|
|
13
|
+
if (!host.services.database.isDatabaseReady()) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (!await host.services.vault.scanVault(showingNotice, ignoreSuspending)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const ERR_INITIALISATION_FAILED = `Initializing database has been failed on some module!`;
|
|
20
|
+
if (!await host.services.databaseEvents.onDatabaseInitialised(showingNotice)) {
|
|
21
|
+
errorManager.showError(ERR_INITIALISATION_FAILED, LOG_LEVEL_NOTICE);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
errorManager.clearError(ERR_INITIALISATION_FAILED);
|
|
25
|
+
if (!await host.services.fileProcessing.commitPendingFileEvents()) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
appLifecycle.markIsReady();
|
|
29
|
+
return true;
|
|
27
30
|
}
|
|
28
31
|
function usePrepareDatabaseForUse(host) {
|
|
29
32
|
const log = createInstanceLogFunction("SF:prepareDatabaseForUse", host.services.API);
|
|
@@ -14,6 +14,7 @@ import type { StorageAccess } from "../interfaces/StorageAccess.js";
|
|
|
14
14
|
import type { LiveSyncEventHub } from "../hub/hub.js";
|
|
15
15
|
import { ServiceModuleBase } from "./ServiceModuleBase.js";
|
|
16
16
|
import type { ControlService } from "../services/base/ControlService.js";
|
|
17
|
+
import type { IFileProcessingService } from "../services/base/IService.js";
|
|
17
18
|
export interface ServiceRebuilderDependencies {
|
|
18
19
|
events: LiveSyncEventHub;
|
|
19
20
|
appLifecycle: AppLifecycleService;
|
|
@@ -28,6 +29,7 @@ export interface ServiceRebuilderDependencies {
|
|
|
28
29
|
replication: ReplicationService;
|
|
29
30
|
database: DatabaseService;
|
|
30
31
|
fileHandler: IFileHandler;
|
|
32
|
+
fileProcessing: IFileProcessingService;
|
|
31
33
|
control: ControlService;
|
|
32
34
|
}
|
|
33
35
|
export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilderDependencies> implements Rebuilder {
|
|
@@ -44,6 +46,7 @@ export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilder
|
|
|
44
46
|
private replication;
|
|
45
47
|
private database;
|
|
46
48
|
private fileHandler;
|
|
49
|
+
private fileProcessing;
|
|
47
50
|
private control;
|
|
48
51
|
constructor(services: ServiceRebuilderDependencies);
|
|
49
52
|
$performRebuildDB(method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks"): Promise<void>;
|
|
@@ -63,17 +66,37 @@ export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilder
|
|
|
63
66
|
private _onResetLocalDatabase;
|
|
64
67
|
suspendAllSync(): Promise<void>;
|
|
65
68
|
suspendReflectingDatabase(ignoreMinIO?: boolean): Promise<void>;
|
|
66
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Stage reflection resumption and perform the final reconciliation checks.
|
|
71
|
+
*
|
|
72
|
+
* File watching is resumed in memory before the final scan so that changes
|
|
73
|
+
* made while the scan is running can enter the storage-event queue. This
|
|
74
|
+
* phase deliberately does not persist the resumed settings. The owning
|
|
75
|
+
* completion boundary persists them only after the scan, replication
|
|
76
|
+
* pre-check, and current batch-wait release have succeeded.
|
|
77
|
+
*
|
|
78
|
+
* @param ignoreMinIO Whether to resume reflection for a MinIO remote.
|
|
79
|
+
* @returns `true` when the applicable reconciliation checks succeeded.
|
|
80
|
+
*/
|
|
81
|
+
resumeReflectingDatabase(ignoreMinIO?: boolean): Promise<boolean>;
|
|
82
|
+
private prepareLocalDatabaseForRebuild;
|
|
83
|
+
private completePreparedRebuild;
|
|
67
84
|
fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean, autoResume?: boolean): Promise<void>;
|
|
68
85
|
private performFetchLocal;
|
|
69
86
|
fetchLocalDBFast(autoResume: boolean): Promise<void>;
|
|
70
87
|
private performFetchLocalDBFast;
|
|
71
88
|
/**
|
|
72
|
-
*
|
|
89
|
+
* Complete the final rebuild phases and establish application readiness.
|
|
90
|
+
*
|
|
91
|
+
* A failed completion restores the previous reflection settings in memory.
|
|
92
|
+
* Persistence atomicity remains a responsibility of the host setting store;
|
|
93
|
+
* Commonlib cannot roll back a store which writes successfully and then
|
|
94
|
+
* reports an error.
|
|
73
95
|
*
|
|
74
|
-
* @param ignoreMinIO Whether to
|
|
96
|
+
* @param ignoreMinIO Whether to resume reflection for a MinIO remote.
|
|
97
|
+
* @returns `true` when finalisation completed; otherwise, `false`.
|
|
75
98
|
*/
|
|
76
|
-
finishRebuild(ignoreMinIO?: boolean): Promise<
|
|
99
|
+
finishRebuild(ignoreMinIO?: boolean): Promise<boolean>;
|
|
77
100
|
/**
|
|
78
101
|
* Fetch local database with making all chunks.
|
|
79
102
|
* This is a wrapper for {@link fetchLocal} with makeLocalChunkBeforeSync = true.
|