@vrtmrz/livesync-commonlib 0.1.26 → 0.1.28
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/common/utils.d.ts +12 -0
- package/dist/common/utils.js +4 -0
- package/dist/interfaces/FileHandler.d.ts +6 -0
- package/dist/managers/EntryManager/EntryManagerImpls.js +1 -1
- package/dist/managers/StorageEventManager.js +2 -2
- package/dist/replication/ReplicatorProvider.d.ts +5 -1
- package/dist/replication/ReplicatorProvider.js +4 -0
- package/dist/replication/SyncParamsHandler.d.ts +2 -0
- package/dist/replication/SyncParamsHandler.js +2 -1
- package/dist/replication/index.d.ts +1 -1
- package/dist/replication/index.js +2 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +2 -0
- package/dist/replication/journal/JournalSyncCore.js +13 -18
- package/dist/replication/journal/LiveSyncJournalReplicator.js +34 -29
- package/dist/serviceFeatures/offlineScanner.js +5 -2
- package/dist/serviceModules/Rebuilder.js +22 -5
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +1 -0
- package/dist/serviceModules/ServiceFileHandlerBase.js +30 -0
- package/dist/services/base/PathService.js +5 -12
- package/dist/string_and_binary/path.js +16 -26
- package/dist/worker/bgWorker.js +1 -1
- package/docs/development.md +2 -0
- package/docs/offline-scanner.md +8 -0
- package/docs/proven-in-use.md +6 -0
- package/package.json +1 -1
package/dist/common/utils.d.ts
CHANGED
|
@@ -68,6 +68,18 @@ export declare function runWithInterval<T>(key: string, interval: number, task:
|
|
|
68
68
|
export declare function runWithStartInterval<T>(key: string, interval: number, task: () => Promise<T>): Promise<T>;
|
|
69
69
|
export declare const globalConcurrencyController: import("octagonal-wheels/concurrency/semaphore_v2").SemaphoreObject;
|
|
70
70
|
export declare function determineTypeFromBlob(data: Blob): "newnote" | "plain";
|
|
71
|
+
/**
|
|
72
|
+
* Whether remediation mode is active.
|
|
73
|
+
*
|
|
74
|
+
* While a modification-time limit is configured, Commonlib refuses every reconciliation scan
|
|
75
|
+
* between the storage and the local database and keeps storage events unqueued, so that a state
|
|
76
|
+
* being recovered is not overwritten by the current one. A host therefore stays unready while the
|
|
77
|
+
* limit is configured, because readiness depends upon that refused scan. Applying the limit to the
|
|
78
|
+
* documents which arrive is the responsibility of the host.
|
|
79
|
+
* @param settings Settings to inspect
|
|
80
|
+
* @returns `true` when a modification-time limit is configured.
|
|
81
|
+
*/
|
|
82
|
+
export declare function isRemediationModeActive(settings: Pick<ObsidianLiveSyncSettings, "maxMTimeForReflectEvents">): boolean;
|
|
71
83
|
export declare function determineType(path: string, data: string | string[] | Uint8Array | ArrayBuffer | Blob): "newnote" | "plain";
|
|
72
84
|
export declare function isAnyNote(doc: DatabaseEntry): doc is NewEntry | PlainEntry;
|
|
73
85
|
export declare function isLoadedEntry(doc: DatabaseEntry): doc is LoadedEntry;
|
package/dist/common/utils.js
CHANGED
|
@@ -199,6 +199,9 @@ const globalConcurrencyController = Semaphore(50);
|
|
|
199
199
|
function determineTypeFromBlob(data) {
|
|
200
200
|
return isTextBlob(data) ? "plain" : "newnote";
|
|
201
201
|
}
|
|
202
|
+
function isRemediationModeActive(settings) {
|
|
203
|
+
return (settings.maxMTimeForReflectEvents ?? 0) > 0;
|
|
204
|
+
}
|
|
202
205
|
function determineType(path, data) {
|
|
203
206
|
if (data instanceof Blob) {
|
|
204
207
|
return determineTypeFromBlob(data);
|
|
@@ -548,6 +551,7 @@ export {
|
|
|
548
551
|
isObfuscatedEntry,
|
|
549
552
|
isObjectDifferent,
|
|
550
553
|
isObjectMargeApplicable,
|
|
554
|
+
isRemediationModeActive,
|
|
551
555
|
isSensibleMargeApplicable,
|
|
552
556
|
isSyncInfoEntry,
|
|
553
557
|
isTextBlob,
|
|
@@ -2,6 +2,12 @@ import type { FilePath, FilePathWithPrefix, MetaEntry } from "../common/models/d
|
|
|
2
2
|
import type { UXFileInfo, UXFileInfoStub, UXInternalFileInfoStub } from "../common/models/fileaccess.type.js";
|
|
3
3
|
export interface IFileHandler {
|
|
4
4
|
readFileFromStub(file: UXFileInfoStub | UXFileInfo): Promise<UXFileInfo>;
|
|
5
|
+
/**
|
|
6
|
+
* Record an untracked, unchanged file during an equal-time scan.
|
|
7
|
+
* Return true only when the exact current revision was recorded. This
|
|
8
|
+
* operation never stores a document revision or writes to storage.
|
|
9
|
+
*/
|
|
10
|
+
tryRecordUntrackedFileRevision(info: UXFileInfoStub | UXFileInfo, expectedRevision: string): Promise<boolean>;
|
|
5
11
|
storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean): Promise<boolean>;
|
|
6
12
|
/**
|
|
7
13
|
* Store the current storage content as a child of an exact live database revision.
|
|
@@ -209,7 +209,7 @@ function isTargetFile(host, filenameSrc) {
|
|
|
209
209
|
const file = filenameSrc.startsWith(ICHeader) ? filenameSrc.substring(ICHeader.length) : filenameSrc;
|
|
210
210
|
if (file.startsWith(ICXHeader)) return true;
|
|
211
211
|
if (file.startsWith(PSCHeader)) return true;
|
|
212
|
-
if (file.
|
|
212
|
+
if (file.startsWith(IDPrefixes.Chunk) || file.startsWith(IDPrefixes.Obfuscated)) {
|
|
213
213
|
return false;
|
|
214
214
|
}
|
|
215
215
|
if (!settings.syncInternalFiles) {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
LOG_LEVEL_NOTICE,
|
|
7
7
|
LOG_LEVEL_VERBOSE
|
|
8
8
|
} from "../common/types.js";
|
|
9
|
-
import { delay, fireAndForget, throttle } from "../common/utils.js";
|
|
9
|
+
import { delay, fireAndForget, isRemediationModeActive, throttle } from "../common/utils.js";
|
|
10
10
|
import {} from "../common/types.js";
|
|
11
11
|
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
|
12
12
|
import { isWaitingForTimeout } from "octagonal-wheels/concurrency/task";
|
|
@@ -86,7 +86,7 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
86
86
|
const settings = this.settings;
|
|
87
87
|
if (!settings.isConfigured) return;
|
|
88
88
|
if (settings.suspendFileWatching) return;
|
|
89
|
-
if (settings
|
|
89
|
+
if (isRemediationModeActive(settings)) {
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
this.fileProcessing.onStorageFileEvent();
|
|
@@ -131,12 +131,16 @@ export type ReplicationFailed = {
|
|
|
131
131
|
export type ReplicationOutcome = ReplicationCompleted | ReplicationCancelled | ReplicationBlocked | ReplicationPartial | ReplicationFailed;
|
|
132
132
|
/** Preparation required before an ordinary operation may use a provider. */
|
|
133
133
|
export interface ReplicationReadinessRequirements {
|
|
134
|
-
readonly centralRemotePreparation: "required" | "not-applicable";
|
|
134
|
+
readonly centralRemotePreparation: "required" | "provider-owned" | "not-applicable";
|
|
135
135
|
}
|
|
136
136
|
/** Shared readiness contract for providers backed by the central remote. */
|
|
137
137
|
export declare const CENTRAL_REMOTE_REPLICATION_READINESS: Readonly<{
|
|
138
138
|
readonly centralRemotePreparation: "required";
|
|
139
139
|
}>;
|
|
140
|
+
/** Central provider which prepares its Security Seed within each transfer. */
|
|
141
|
+
export declare const PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS: Readonly<{
|
|
142
|
+
readonly centralRemotePreparation: "provider-owned";
|
|
143
|
+
}>;
|
|
140
144
|
/** Shared readiness contract for peer-to-peer providers without a central remote. */
|
|
141
145
|
export declare const PEER_REPLICATION_READINESS: Readonly<{
|
|
142
146
|
readonly centralRemotePreparation: "not-applicable";
|
|
@@ -25,6 +25,9 @@ const REPLICATION_CANCELLED = Object.freeze({ status: "cancelled" });
|
|
|
25
25
|
const CENTRAL_REMOTE_REPLICATION_READINESS = Object.freeze({
|
|
26
26
|
centralRemotePreparation: "required"
|
|
27
27
|
});
|
|
28
|
+
const PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS = Object.freeze({
|
|
29
|
+
centralRemotePreparation: "provider-owned"
|
|
30
|
+
});
|
|
28
31
|
const PEER_REPLICATION_READINESS = Object.freeze({
|
|
29
32
|
centralRemotePreparation: "not-applicable"
|
|
30
33
|
});
|
|
@@ -131,6 +134,7 @@ export {
|
|
|
131
134
|
CENTRAL_REMOTE_REPLICATION_READINESS,
|
|
132
135
|
NO_INTERACTION,
|
|
133
136
|
PEER_REPLICATION_READINESS,
|
|
137
|
+
PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS,
|
|
134
138
|
REPLICATION_CANCELLED,
|
|
135
139
|
REPLICATION_COMPLETED,
|
|
136
140
|
REPLICATION_PROGRESS_PRESENTATIONS,
|
|
@@ -32,4 +32,6 @@ export declare class SyncParamsNotFoundError extends SyncParamsHandlerError {
|
|
|
32
32
|
}
|
|
33
33
|
export declare class SyncParamsUpdateError extends SyncParamsHandlerError {
|
|
34
34
|
}
|
|
35
|
+
/** Create a handler owned by one operation rather than the process-wide server cache. */
|
|
36
|
+
export declare function createSyncParamsHandler({ put, get, create }: CreateSyncParamsHanderOptions): SyncParamsHandler;
|
|
35
37
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { CAPABILITY_NOT_APPLICABLE, CAPABILITY_NOT_IMPLEMENTED, CENTRAL_REMOTE_REPLICATION_READINESS, NO_INTERACTION, PEER_REPLICATION_READINESS, REPLICATION_CANCELLED, REPLICATION_COMPLETED, REPLICATION_PROGRESS_PRESENTATIONS, USER_INITIATED_REPLICATION_AUTHORITY, defineReplicatorProviderDefinitions, isReplicationCompleted, outcomeFromContinuousOpenReplication, outcomeFromFiniteOpenReplication, replicationBlocked, replicationFailed, supportedOpenReplicationContinuous, supportedOpenReplicationOneShot, supportedOpenReplicationUnattended, supportedStopActiveTransfer, supportedCapability, } from "./ReplicatorProvider.js";
|
|
1
|
+
export { CAPABILITY_NOT_APPLICABLE, CAPABILITY_NOT_IMPLEMENTED, CENTRAL_REMOTE_REPLICATION_READINESS, NO_INTERACTION, PEER_REPLICATION_READINESS, PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS, REPLICATION_CANCELLED, REPLICATION_COMPLETED, REPLICATION_PROGRESS_PRESENTATIONS, USER_INITIATED_REPLICATION_AUTHORITY, defineReplicatorProviderDefinitions, isReplicationCompleted, outcomeFromContinuousOpenReplication, outcomeFromFiniteOpenReplication, replicationBlocked, replicationFailed, supportedOpenReplicationContinuous, supportedOpenReplicationOneShot, supportedOpenReplicationUnattended, supportedStopActiveTransfer, supportedCapability, } from "./ReplicatorProvider.js";
|
|
2
2
|
export type { ReplicatorInstance } from "./ReplicatorInstance.js";
|
|
3
3
|
export type { ActiveReplicatorContext, CapabilitySupport, ContinuousReplicationRequest, ContinuousReplicationTrigger, ContinuousRunner, InteractionAuthority, InteractionPermissions, NotApplicableCapability, NotImplementedCapability, NoInteraction, PermittedInteractionPermissions, ReplicationBlockReason, ReplicationAttemptFailure, ReplicationCancelled, ReplicationCompleted, ReplicationFailed, ReplicationInteraction, ReplicationOutcome, ReplicationProgressPresentation, ReplicationReadinessRequirements, ReplicationPartial, ReplicationFailureRequest, ReplicatorConfigurationIdentity, ReplicatorProviderDefinition, ReplicatorProviderDefinitionMap, StopActiveTransferRunner, SupportedCapability, UnattendedOneShotRequest, UnattendedReplicationTrigger, UnattendedOneShotRunner, UserInitiatedOneShotRequest, UserInitiatedReplicationAuthority, UserInitiatedOneShotRunner, } from "./ReplicatorProvider.js";
|
|
4
4
|
export { CAPABILITY_SUPPORT_KINDS, CAPABILITY_UNAVAILABLE_REASONS } from "./ProviderCapability.js";
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
CENTRAL_REMOTE_REPLICATION_READINESS,
|
|
5
5
|
NO_INTERACTION,
|
|
6
6
|
PEER_REPLICATION_READINESS,
|
|
7
|
+
PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS,
|
|
7
8
|
REPLICATION_CANCELLED,
|
|
8
9
|
REPLICATION_COMPLETED,
|
|
9
10
|
REPLICATION_PROGRESS_PRESENTATIONS,
|
|
@@ -48,6 +49,7 @@ export {
|
|
|
48
49
|
NO_INTERACTION,
|
|
49
50
|
NO_REMOTE_RESOURCE_CAPABILITIES,
|
|
50
51
|
PEER_REPLICATION_READINESS,
|
|
52
|
+
PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS,
|
|
51
53
|
REMOTE_RESOURCE_KINDS,
|
|
52
54
|
REPLICATION_CANCELLED,
|
|
53
55
|
REPLICATION_COMPLETED,
|
|
@@ -16,6 +16,7 @@ export declare class JournalSyncCore {
|
|
|
16
16
|
env: LiveSyncJournalReplicatorEnv;
|
|
17
17
|
store: SimpleStore<CheckPointInfo>;
|
|
18
18
|
requestedStop: boolean;
|
|
19
|
+
private syncParamsHandler?;
|
|
19
20
|
getInitialSyncParameters(): Promise<SyncParameters>;
|
|
20
21
|
getSyncParameters(): Promise<SyncParameters>;
|
|
21
22
|
putSyncParameters(params: SyncParameters): Promise<boolean>;
|
|
@@ -44,6 +45,7 @@ export declare class JournalSyncCore {
|
|
|
44
45
|
resetBucket(): Promise<boolean>;
|
|
45
46
|
getRemoteKey(): string;
|
|
46
47
|
getReplicationPBKDF2Salt(refresh?: boolean): Promise<Uint8Array<ArrayBuffer>>;
|
|
48
|
+
private getSyncParamsHandler;
|
|
47
49
|
isEncryptionPrevented(fileName: string): boolean;
|
|
48
50
|
private decryptDataV2;
|
|
49
51
|
private decryptDataV1;
|
|
@@ -22,8 +22,7 @@ import {
|
|
|
22
22
|
JournalStorageReadStatuses
|
|
23
23
|
} from "./objectstore/JournalStorageAdapter.js";
|
|
24
24
|
import {
|
|
25
|
-
|
|
26
|
-
createSyncParamsHanderForServer,
|
|
25
|
+
createSyncParamsHandler,
|
|
27
26
|
SyncParamsFetchError,
|
|
28
27
|
SyncParamsNotFoundError,
|
|
29
28
|
SyncParamsUpdateError
|
|
@@ -61,6 +60,7 @@ class JournalSyncCore {
|
|
|
61
60
|
env;
|
|
62
61
|
store;
|
|
63
62
|
requestedStop = false;
|
|
63
|
+
syncParamsHandler;
|
|
64
64
|
getInitialSyncParameters() {
|
|
65
65
|
return Promise.resolve({
|
|
66
66
|
...DEFAULT_SYNC_PARAMETERS,
|
|
@@ -109,7 +109,6 @@ class JournalSyncCore {
|
|
|
109
109
|
this.store = store;
|
|
110
110
|
this.hash = this.getHash(settings);
|
|
111
111
|
this.storage = storage;
|
|
112
|
-
clearHandlers();
|
|
113
112
|
}
|
|
114
113
|
async downloadJson(key) {
|
|
115
114
|
try {
|
|
@@ -156,7 +155,7 @@ class JournalSyncCore {
|
|
|
156
155
|
this.store = store;
|
|
157
156
|
this.hash = this.getHash(settings);
|
|
158
157
|
this.storage.applyNewConfig(settings);
|
|
159
|
-
|
|
158
|
+
this.resetAllCaches();
|
|
160
159
|
}
|
|
161
160
|
updateInfo(info) {
|
|
162
161
|
const old = this.env.services.replicator.replicationStatics.value;
|
|
@@ -205,23 +204,18 @@ class JournalSyncCore {
|
|
|
205
204
|
return this._currentCheckPointInfo;
|
|
206
205
|
}
|
|
207
206
|
resetAllCaches() {
|
|
208
|
-
|
|
207
|
+
this.syncParamsHandler = void 0;
|
|
209
208
|
}
|
|
210
209
|
async resetCheckpointInfo() {
|
|
211
210
|
await this.updateCheckPointInfo((info) => ({ ...CheckPointInfoDefault }));
|
|
212
|
-
clearHandlers();
|
|
213
211
|
}
|
|
214
212
|
getJournalEpochFromSyncParams(params) {
|
|
215
213
|
return `${params.protocolVersion}:${params.pbkdf2salt}`;
|
|
216
214
|
}
|
|
217
215
|
async ensureCheckpointCachesAreFresh() {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
journalEpoch = this.getJournalEpochFromSyncParams(params);
|
|
222
|
-
} catch {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
216
|
+
const params = await this.getSyncParamsHandler().fetch(true);
|
|
217
|
+
if (!params) throw new SyncParamsFetchError("Could not prepare Journal synchronisation parameters");
|
|
218
|
+
const journalEpoch = this.getJournalEpochFromSyncParams(params);
|
|
225
219
|
const current = await this.getCheckpointInfo();
|
|
226
220
|
if (current.journalEpoch === journalEpoch) {
|
|
227
221
|
return;
|
|
@@ -252,12 +246,12 @@ class JournalSyncCore {
|
|
|
252
246
|
receivedFiles: /* @__PURE__ */ new Set(),
|
|
253
247
|
sentFiles: /* @__PURE__ */ new Set()
|
|
254
248
|
}));
|
|
255
|
-
clearHandlers();
|
|
256
249
|
}
|
|
257
250
|
async isAvailable() {
|
|
258
251
|
return await this.storage.isAvailable();
|
|
259
252
|
}
|
|
260
253
|
async resetBucket() {
|
|
254
|
+
this.resetAllCaches();
|
|
261
255
|
let files = [];
|
|
262
256
|
try {
|
|
263
257
|
do {
|
|
@@ -269,7 +263,6 @@ class JournalSyncCore {
|
|
|
269
263
|
return false;
|
|
270
264
|
}
|
|
271
265
|
} while (files.length != 0);
|
|
272
|
-
clearHandlers();
|
|
273
266
|
} catch (ex) {
|
|
274
267
|
Logger(`WARNING! Could not delete files.`, LOG_LEVEL_NOTICE, "reset-bucket");
|
|
275
268
|
Logger(ex, LOG_LEVEL_VERBOSE);
|
|
@@ -291,13 +284,14 @@ class JournalSyncCore {
|
|
|
291
284
|
return this.getHash(this._settings);
|
|
292
285
|
}
|
|
293
286
|
async getReplicationPBKDF2Salt(refresh) {
|
|
294
|
-
|
|
295
|
-
|
|
287
|
+
return await this.getSyncParamsHandler().getPBKDF2Salt(refresh);
|
|
288
|
+
}
|
|
289
|
+
getSyncParamsHandler() {
|
|
290
|
+
return this.syncParamsHandler ??= createSyncParamsHandler({
|
|
296
291
|
put: (params) => this.putSyncParameters(params),
|
|
297
292
|
get: () => this.getSyncParameters(),
|
|
298
293
|
create: () => this.getInitialSyncParameters()
|
|
299
294
|
});
|
|
300
|
-
return await manager.getPBKDF2Salt(refresh);
|
|
301
295
|
}
|
|
302
296
|
isEncryptionPrevented(fileName) {
|
|
303
297
|
if (fileName.endsWith(DOCID_JOURNAL_SYNC_PARAMETERS)) return true;
|
|
@@ -762,6 +756,7 @@ class JournalSyncCore {
|
|
|
762
756
|
/** Stop this Journal operation owner and release its storage transport. */
|
|
763
757
|
dispose() {
|
|
764
758
|
this.requestStop();
|
|
759
|
+
this.resetAllCaches();
|
|
765
760
|
this.storage.dispose?.();
|
|
766
761
|
}
|
|
767
762
|
}
|
|
@@ -12,7 +12,6 @@ import { MinioStorageAdapter } from "./objectstore/MinioStorageAdapter.js";
|
|
|
12
12
|
import { LiveSyncAbstractReplicator } from "../LiveSyncAbstractReplicator.js";
|
|
13
13
|
import { ensureRemoteIsCompatible } from "../../pouchdb/LiveSyncDBFunctions.js";
|
|
14
14
|
import { extractObject } from "../../common/utils.js";
|
|
15
|
-
import { clearHandlers } from "../SyncParamsHandler.js";
|
|
16
15
|
import { JournalStorageReadStatuses } from "./objectstore/JournalStorageAdapter.js";
|
|
17
16
|
import {
|
|
18
17
|
CENTRAL_COMPATIBILITY_ACCEPTED,
|
|
@@ -154,20 +153,24 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
154
153
|
async openReplication(setting, _, showResult, ignoreCleanLock = false, recordCompatibilityDecision) {
|
|
155
154
|
return await this.runJournalTransfer(async (stopGeneration) => {
|
|
156
155
|
const client = this.setupJournalSyncClient(setting);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
156
|
+
try {
|
|
157
|
+
stopGeneration = this.journalTransferStopGeneration;
|
|
158
|
+
if (!await this.checkReplicationConnectivity(
|
|
159
|
+
false,
|
|
160
|
+
ignoreCleanLock,
|
|
161
|
+
showResult,
|
|
162
|
+
setting,
|
|
163
|
+
client,
|
|
164
|
+
recordCompatibilityDecision
|
|
165
|
+
)) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
if (stopGeneration !== this.journalTransferStopGeneration) return false;
|
|
169
|
+
this.hasEnteredReplication = true;
|
|
170
|
+
return await client.sync(showResult);
|
|
171
|
+
} finally {
|
|
172
|
+
client.resetAllCaches();
|
|
167
173
|
}
|
|
168
|
-
if (stopGeneration !== this.journalTransferStopGeneration) return false;
|
|
169
|
-
this.hasEnteredReplication = true;
|
|
170
|
-
return await client.sync(showResult);
|
|
171
174
|
});
|
|
172
175
|
}
|
|
173
176
|
/**
|
|
@@ -191,20 +194,24 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
191
194
|
async runDirectionalReplication(setting, showingNotice, transfer, recordCompatibilityDecision) {
|
|
192
195
|
return await this.runJournalTransfer(async (stopGeneration) => {
|
|
193
196
|
const client = this.setupJournalSyncClient(setting);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
197
|
+
try {
|
|
198
|
+
stopGeneration = this.journalTransferStopGeneration;
|
|
199
|
+
if (!await this.checkReplicationConnectivity(
|
|
200
|
+
false,
|
|
201
|
+
false,
|
|
202
|
+
!!showingNotice,
|
|
203
|
+
setting,
|
|
204
|
+
client,
|
|
205
|
+
recordCompatibilityDecision
|
|
206
|
+
)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (stopGeneration !== this.journalTransferStopGeneration) return false;
|
|
210
|
+
this.hasEnteredReplication = true;
|
|
211
|
+
return await transfer(client);
|
|
212
|
+
} finally {
|
|
213
|
+
client.resetAllCaches();
|
|
204
214
|
}
|
|
205
|
-
if (stopGeneration !== this.journalTransferStopGeneration) return false;
|
|
206
|
-
this.hasEnteredReplication = true;
|
|
207
|
-
return await transfer(client);
|
|
208
215
|
});
|
|
209
216
|
}
|
|
210
217
|
/** Capture only the compatibility decision made by this exact borrowed client. */
|
|
@@ -344,7 +351,6 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
344
351
|
if (!await this.client.resetBucket()) {
|
|
345
352
|
throw new Error("Could not reset remote bucket");
|
|
346
353
|
}
|
|
347
|
-
clearHandlers();
|
|
348
354
|
Logger("Remote Bucket Cleared", LOG_LEVEL_NOTICE);
|
|
349
355
|
await this.tryCreateRemoteDatabase(setting);
|
|
350
356
|
} catch (ex) {
|
|
@@ -356,7 +362,6 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
356
362
|
async tryCreateRemoteDatabase(setting) {
|
|
357
363
|
this.closeReplication();
|
|
358
364
|
Logger("Remote Database Created or Connected", LOG_LEVEL_NOTICE);
|
|
359
|
-
clearHandlers();
|
|
360
365
|
if (!await this.ensurePBKDF2Salt(setting, true, false)) {
|
|
361
366
|
throw new Error("Could not ensure PBKDF2 salt (Security Seed)");
|
|
362
367
|
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
LOG_LEVEL_VERBOSE,
|
|
8
8
|
isMetaEntry
|
|
9
9
|
} from "../common/types.js";
|
|
10
|
-
import { compareMTime, isAnyNote } from "../common/utils.js";
|
|
10
|
+
import { compareMTime, isAnyNote, isRemediationModeActive } from "../common/utils.js";
|
|
11
11
|
import { shouldBeIgnored, stripAllPrefixes } from "../string_and_binary/path.js";
|
|
12
12
|
import { createInstanceLogFunction } from "../services/lib/logUtils.js";
|
|
13
13
|
import { BASE_IS_NEW, EVEN, TARGET_IS_NEW } from "../common/models/shared.const.symbols.js";
|
|
@@ -389,6 +389,9 @@ async function syncFileBetweenDBandStorage(host, log, file, doc) {
|
|
|
389
389
|
return FilePairProcessResults.SKIPPED;
|
|
390
390
|
}
|
|
391
391
|
case EVEN:
|
|
392
|
+
if (doc._rev) {
|
|
393
|
+
await host.serviceModules.fileHandler.tryRecordUntrackedFileRevision(file, doc._rev);
|
|
394
|
+
}
|
|
392
395
|
log("STORAGE == DB :" + file.path, LOG_LEVEL_DEBUG);
|
|
393
396
|
return FilePairProcessResults.COMPLETED;
|
|
394
397
|
default:
|
|
@@ -411,7 +414,7 @@ function canProceedScan(host, errorManager, log, showingNotice = false, ignoreSu
|
|
|
411
414
|
}
|
|
412
415
|
errorManager.clearError(ERR_SUSPENDING);
|
|
413
416
|
const MSG_IN_REMEDIATION = `Started in remediation Mode! (Max mtime for reflect events is set). Synchronising between the storage and the local database is now prevented.`;
|
|
414
|
-
if (settings
|
|
417
|
+
if (isRemediationModeActive(settings)) {
|
|
415
418
|
errorManager.showError(MSG_IN_REMEDIATION, LOG_LEVEL_NOTICE);
|
|
416
419
|
return false;
|
|
417
420
|
}
|
|
@@ -8,7 +8,7 @@ import { ServiceModuleBase } from "./ServiceModuleBase.js";
|
|
|
8
8
|
import { fetchChangesForInitialSync, isRetryableStreamingFetchFailure } from "../pouchdb/StreamingFetch.js";
|
|
9
9
|
import { getConfiguredFunctionsForEncryption } from "../pouchdb/encryption.js";
|
|
10
10
|
import { AuthorizationHeaderGenerator, generateCredentialObject } from "../replication/httplib.js";
|
|
11
|
-
import { parseHeaderValues } from "../common/utils.js";
|
|
11
|
+
import { isRemediationModeActive, parseHeaderValues } from "../common/utils.js";
|
|
12
12
|
import { sizeToHumanReadable } from "octagonal-wheels/number";
|
|
13
13
|
import { REMOTE_RESOURCE_KINDS } from "../replication/index.js";
|
|
14
14
|
const FAST_FETCH_CHECKPOINT_KEY = "fast-fetch-checkpoint";
|
|
@@ -106,6 +106,15 @@ Please enable them from the settings screen after setup is complete.`,
|
|
|
106
106
|
await this.informOptionalFeatures();
|
|
107
107
|
}
|
|
108
108
|
async performRebuildEverything() {
|
|
109
|
+
if (isRemediationModeActive(this.setting.currentSettings())) {
|
|
110
|
+
this._log(
|
|
111
|
+
`Rebuilding has been refused: remediation mode is active. Clear the modification-time limit to rebuild.`,
|
|
112
|
+
LOG_LEVEL_NOTICE
|
|
113
|
+
);
|
|
114
|
+
throw new Error(
|
|
115
|
+
"Rebuilding is not available while remediation mode is active. Clear the modification-time limit first."
|
|
116
|
+
);
|
|
117
|
+
}
|
|
109
118
|
this.appLifecycle.resetIsReady();
|
|
110
119
|
await this.setting.suspendExtraSync();
|
|
111
120
|
await this.setting.applyPartial({
|
|
@@ -249,7 +258,8 @@ Please enable them from the settings screen after setup is complete.`,
|
|
|
249
258
|
suspendParseReplicationResult: false,
|
|
250
259
|
suspendFileWatching: false
|
|
251
260
|
});
|
|
252
|
-
|
|
261
|
+
const inRemediationMode = isRemediationModeActive(this.setting.currentSettings());
|
|
262
|
+
if (!inRemediationMode && !await this.vault.scanVault(true)) return false;
|
|
253
263
|
if (!await this.replication.onBeforeReplicate(false)) return false;
|
|
254
264
|
return true;
|
|
255
265
|
}
|
|
@@ -281,7 +291,7 @@ Please enable them from the settings screen after setup is complete.`,
|
|
|
281
291
|
notifyThresholdOfRemoteStorageSize: DEFAULT_SETTINGS.notifyThresholdOfRemoteStorageSize
|
|
282
292
|
});
|
|
283
293
|
const settings = this.setting.currentSettings();
|
|
284
|
-
if (settings
|
|
294
|
+
if (isRemediationModeActive(settings)) {
|
|
285
295
|
const date = new Date(settings.maxMTimeForReflectEvents);
|
|
286
296
|
const ask = `Your settings restrict file reflection times to no later than ${date.toLocaleString()}.
|
|
287
297
|
|
|
@@ -318,7 +328,12 @@ Are you sure you wish to proceed?`;
|
|
|
318
328
|
await this.resetLocalDatabase();
|
|
319
329
|
this.clearFastFetchCheckpoint();
|
|
320
330
|
await delay(1e3);
|
|
321
|
-
if (
|
|
331
|
+
if (isRemediationModeActive(this.setting.currentSettings())) {
|
|
332
|
+
this._log(
|
|
333
|
+
`Remediation mode: the files in storage are not stored in the database before fetching.`,
|
|
334
|
+
LOG_LEVEL_NOTICE
|
|
335
|
+
);
|
|
336
|
+
} else if (makeLocalChunkBeforeSync) {
|
|
322
337
|
await this.fileHandler.createAllChunks(true);
|
|
323
338
|
} else if (!preventMakeLocalFilesBeforeSync) {
|
|
324
339
|
await this.prepareLocalDatabaseForRebuild();
|
|
@@ -496,7 +511,9 @@ Total bytes fetched: ${sizeToHumanReadable(progress.totalBytes)}`,
|
|
|
496
511
|
if (controlsReflection) {
|
|
497
512
|
await this.setting.saveSettingData();
|
|
498
513
|
}
|
|
499
|
-
this.
|
|
514
|
+
if (!isRemediationModeActive(this.setting.currentSettings())) {
|
|
515
|
+
this.appLifecycle.markIsReady();
|
|
516
|
+
}
|
|
500
517
|
completed = true;
|
|
501
518
|
return true;
|
|
502
519
|
} finally {
|
|
@@ -94,6 +94,7 @@ export declare abstract class ServiceFileHandlerBase extends ServiceModuleBase<S
|
|
|
94
94
|
getPath(entry: AnyEntry): FilePathWithPrefix;
|
|
95
95
|
getPathWithoutPrefix(entry: AnyEntry): FilePathWithPrefix;
|
|
96
96
|
readFileFromStub(file: UXFileInfoStub | UXFileInfo): Promise<UXFileInfo>;
|
|
97
|
+
tryRecordUntrackedFileRevision(info: UXFileInfoStub | UXFileInfo, expectedRevision: string): Promise<boolean>;
|
|
97
98
|
private infoToStub;
|
|
98
99
|
storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean): Promise<boolean>;
|
|
99
100
|
storeFileToDBWithBaseRevision(info: UXFileInfoStub | UXFileInfo | FilePathWithPrefix, baseRevision: string, createIfDifferent?: boolean): Promise<boolean>;
|
|
@@ -215,6 +215,36 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
215
215
|
}
|
|
216
216
|
return readFile;
|
|
217
217
|
}
|
|
218
|
+
async tryRecordUntrackedFileRevision(info, expectedRevision) {
|
|
219
|
+
if (!this.fileReflectionProvenance || !expectedRevision) return false;
|
|
220
|
+
const path = getStoragePathFromUXFileInfo(info);
|
|
221
|
+
try {
|
|
222
|
+
return await this.serializedByFileEventPaths([path], async () => {
|
|
223
|
+
const provenance = this.fileReflectionProvenance;
|
|
224
|
+
if (await provenance.get(path)) return false;
|
|
225
|
+
const stub = await this.storage.getStub(path);
|
|
226
|
+
if (!stub || isFolderInfo(stub) || stub.path !== path) return false;
|
|
227
|
+
const file = await this.readFileFromStub(stub);
|
|
228
|
+
const current = await this.db.fetchEntryMeta(path, void 0, true);
|
|
229
|
+
if (!current || current._rev !== expectedRevision || current.deleted || current._deleted) return false;
|
|
230
|
+
if ((await this.db.getConflictedRevs(path)).length > 0) return false;
|
|
231
|
+
const entry = await this.db.fetchEntry(path, expectedRevision, true, true, true);
|
|
232
|
+
if (!entry || entry._rev !== expectedRevision || entry.deleted || entry._deleted) return false;
|
|
233
|
+
if (!await isDocContentSame(readContent(entry), file.body)) return false;
|
|
234
|
+
const latest = await this.db.fetchEntryMeta(path, void 0, true);
|
|
235
|
+
if (!latest || latest._rev !== expectedRevision || latest.deleted || latest._deleted) return false;
|
|
236
|
+
if ((await this.db.getConflictedRevs(path)).length > 0) return false;
|
|
237
|
+
if (!await this.currentStorageSnapshotMatches(file, void 0)) return false;
|
|
238
|
+
if (await provenance.get(path)) return false;
|
|
239
|
+
await provenance.set(path, { revision: expectedRevision, observedStorageMtime: file.stat.mtime });
|
|
240
|
+
return true;
|
|
241
|
+
});
|
|
242
|
+
} catch (ex) {
|
|
243
|
+
this._log(`Could not record unchanged file provenance for ${path}`, LOG_LEVEL_VERBOSE);
|
|
244
|
+
this._log(ex, LOG_LEVEL_VERBOSE);
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
218
248
|
async infoToStub(info) {
|
|
219
249
|
if (info == null) return null;
|
|
220
250
|
const file = typeof info === "string" ? await this.storage.getFileStub(info) : info;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ServiceBase } from "./ServiceBase.js";
|
|
2
|
-
import { addPrefix, id2path_base, path2id_base } from "../../string_and_binary/path.js";
|
|
2
|
+
import { addPrefix, expandFilePathPrefix, id2path_base, path2id_base } from "../../string_and_binary/path.js";
|
|
3
3
|
import { isInternalMetadata, stripInternalMetadataPrefix } from "../../common/typeUtils.js";
|
|
4
4
|
class PathService extends ServiceBase {
|
|
5
5
|
settingService;
|
|
@@ -14,19 +14,12 @@ class PathService extends ServiceBase {
|
|
|
14
14
|
}
|
|
15
15
|
_id2path(id, entry) {
|
|
16
16
|
const filename = id2path_base(id, entry);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const normalizedPath = this.normalizePath(path);
|
|
20
|
-
temp.push(normalizedPath);
|
|
21
|
-
const fixedPath = temp.join(":");
|
|
22
|
-
return fixedPath;
|
|
17
|
+
const [prefix, path] = expandFilePathPrefix(filename);
|
|
18
|
+
return prefix + this.normalizePath(path);
|
|
23
19
|
}
|
|
24
20
|
async _path2id(filename, obfuscatePassphrase, caseInsensitive) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const normalizedPath = this.normalizePath(path);
|
|
28
|
-
temp.push(normalizedPath);
|
|
29
|
-
const fixedPath = temp.join(":");
|
|
21
|
+
const [prefix, path] = expandFilePathPrefix(filename);
|
|
22
|
+
const fixedPath = prefix + this.normalizePath(path);
|
|
30
23
|
const out = await path2id_base(fixedPath, obfuscatePassphrase, caseInsensitive);
|
|
31
24
|
return out;
|
|
32
25
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { memorizeFuncWithLRUCache } from "../common/utils.js";
|
|
14
14
|
import { uint8ArrayToHexString, writeString } from "./convert.js";
|
|
15
15
|
import { unique } from "octagonal-wheels/collection.js";
|
|
16
|
+
import { CHeader, ICHeader, ICXHeader, PSCHeader } from "../common/models/fileaccess.const.js";
|
|
16
17
|
function isValidFilenameInWidows(filename) {
|
|
17
18
|
const regex = /[\u0000-\u001f]|[\\":?<>|*#]/g;
|
|
18
19
|
if (regex.test(filename)) return false;
|
|
@@ -21,11 +22,11 @@ function isValidFilenameInWidows(filename) {
|
|
|
21
22
|
return true;
|
|
22
23
|
}
|
|
23
24
|
function isValidFilenameInDarwin(filename) {
|
|
24
|
-
const regex = /[\u0000-\u001f]
|
|
25
|
+
const regex = /[\u0000-\u001f]/g;
|
|
25
26
|
return !regex.test(filename);
|
|
26
27
|
}
|
|
27
28
|
function isValidFilenameInLinux(filename) {
|
|
28
|
-
const regex = /[\u0000-\u001f]
|
|
29
|
+
const regex = /[\u0000-\u001f]/g;
|
|
29
30
|
return !regex.test(filename);
|
|
30
31
|
}
|
|
31
32
|
function isValidFilenameInAndroid(filename) {
|
|
@@ -33,37 +34,30 @@ function isValidFilenameInAndroid(filename) {
|
|
|
33
34
|
return !regex.test(filename);
|
|
34
35
|
}
|
|
35
36
|
function isFilePath(path) {
|
|
36
|
-
|
|
37
|
-
return false;
|
|
37
|
+
return expandFilePathPrefix(path)[0] === "";
|
|
38
38
|
}
|
|
39
39
|
function stripAllPrefixes(prefixedPath) {
|
|
40
|
-
|
|
41
|
-
const [, body] = expandFilePathPrefix(prefixedPath);
|
|
42
|
-
return stripAllPrefixes(body);
|
|
40
|
+
return stripPrefix(prefixedPath);
|
|
43
41
|
}
|
|
44
42
|
function addPrefix(path, prefix) {
|
|
45
43
|
if (prefix && path.startsWith(prefix)) return path;
|
|
46
44
|
return `${prefix ?? ""}${path}`;
|
|
47
45
|
}
|
|
48
46
|
function expandFilePathPrefix(path) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} else {
|
|
54
|
-
prefix = prefix + ":";
|
|
47
|
+
for (const prefix of [ICHeader, ICXHeader, PSCHeader]) {
|
|
48
|
+
if (path.startsWith(prefix)) {
|
|
49
|
+
return [prefix, path.substring(prefix.length)];
|
|
50
|
+
}
|
|
55
51
|
}
|
|
56
|
-
return [
|
|
52
|
+
return ["", path];
|
|
57
53
|
}
|
|
58
54
|
function expandDocumentIDPrefix(id) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
} else {
|
|
64
|
-
prefix = prefix + ":";
|
|
55
|
+
for (const prefix of [CHeader, PREFIX_OBFUSCATED]) {
|
|
56
|
+
if (id.startsWith(prefix)) {
|
|
57
|
+
return [prefix, id.substring(prefix.length)];
|
|
58
|
+
}
|
|
65
59
|
}
|
|
66
|
-
return
|
|
60
|
+
return expandFilePathPrefix(id);
|
|
67
61
|
}
|
|
68
62
|
const _hashString = memorizeFuncWithLRUCache(async (key) => {
|
|
69
63
|
const buff = writeString(key);
|
|
@@ -112,11 +106,7 @@ function getPathWithoutPrefix(entry) {
|
|
|
112
106
|
return stripAllPrefixes(f);
|
|
113
107
|
}
|
|
114
108
|
function stripPrefix(prefixedPath) {
|
|
115
|
-
|
|
116
|
-
if (!body) {
|
|
117
|
-
return prefix;
|
|
118
|
-
}
|
|
119
|
-
return body;
|
|
109
|
+
return expandFilePathPrefix(prefixedPath)[1];
|
|
120
110
|
}
|
|
121
111
|
function shouldBeIgnored(filename) {
|
|
122
112
|
if (filename == FLAGMD_REDFLAG) {
|