@vrtmrz/livesync-commonlib 0.2.1 → 0.2.2
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/processSetting.d.ts +1 -0
- package/dist/common/models/setting.const.defaults.js +1 -0
- package/dist/common/models/setting.const.qr.js +2 -0
- package/dist/common/models/setting.policy.d.ts +4 -0
- package/dist/common/models/setting.policy.js +1 -0
- package/dist/common/models/setting.type.d.ts +4 -0
- package/dist/common/models/tweak.definition.d.ts +1 -0
- package/dist/common/settingConstants.d.ts +1 -0
- package/dist/common/settingConstants.js +4 -0
- package/dist/interfaces/FileHandler.d.ts +2 -0
- package/dist/interfaces/StorageAccess.d.ts +2 -0
- package/dist/interfaces/StorageEventManager.d.ts +2 -0
- package/dist/interfaces/StorageEventManager.js +4 -0
- package/dist/managers/StorageEventManager.d.ts +1 -0
- package/dist/managers/StorageEventManager.js +9 -0
- package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -0
- package/dist/serviceFeatures/offlineScanner.d.ts +4 -4
- package/dist/serviceFeatures/offlineScanner.js +11 -6
- package/dist/serviceFeatures/prepareDatabaseForUse.d.ts +3 -2
- package/dist/serviceFeatures/prepareDatabaseForUse.js +5 -4
- package/dist/serviceModules/ServiceFileAccessBase.d.ts +1 -0
- package/dist/serviceModules/ServiceFileAccessBase.js +3 -0
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +1 -0
- package/dist/serviceModules/ServiceFileHandlerBase.js +21 -0
- package/dist/services/base/DatabaseEventService.d.ts +1 -1
- package/dist/services/base/IService.d.ts +3 -3
- package/dist/services/base/VaultScanResult.d.ts +5 -0
- package/dist/services/base/VaultService.d.ts +1 -1
- package/dist/worker/bgWorker.js +1 -1
- package/docs/offline-scanner.md +12 -4
- package/docs/storage-events-and-reflection.md +6 -2
- package/package.json +1 -1
|
@@ -62,6 +62,7 @@ export declare function decodeSettingsFromSetupURI(uri: string, passphrase: stri
|
|
|
62
62
|
batchSaveInLiveSync?: boolean;
|
|
63
63
|
syncOnSave: boolean;
|
|
64
64
|
syncOnStart: boolean;
|
|
65
|
+
strictContentCheckOnStartup?: boolean;
|
|
65
66
|
syncOnFileOpen: boolean;
|
|
66
67
|
syncOnEditorSave: boolean;
|
|
67
68
|
keepReplicationActiveInBackground: boolean;
|
|
@@ -51,6 +51,8 @@ const KeyIndexOfSettings = {
|
|
|
51
51
|
liveSync: 49,
|
|
52
52
|
syncOnSave: 50,
|
|
53
53
|
syncOnStart: 51,
|
|
54
|
+
strictContentCheckOnStartup: -1,
|
|
55
|
+
// Installation-local preference; do not encode into the QR Code.
|
|
54
56
|
syncOnFileOpen: 52,
|
|
55
57
|
syncOnEditorSave: 53,
|
|
56
58
|
keepReplicationActiveInBackground: -1,
|
|
@@ -65,6 +65,10 @@ export declare const SETTINGS_PROPERTY_POLICY: {
|
|
|
65
65
|
readonly markdown: "include";
|
|
66
66
|
readonly persistence: "include";
|
|
67
67
|
};
|
|
68
|
+
readonly strictContentCheckOnStartup: {
|
|
69
|
+
readonly markdown: "omit";
|
|
70
|
+
readonly persistence: "include";
|
|
71
|
+
};
|
|
68
72
|
readonly syncOnFileOpen: {
|
|
69
73
|
readonly markdown: "include";
|
|
70
74
|
readonly persistence: "include";
|
|
@@ -17,6 +17,7 @@ const SETTINGS_PROPERTY_POLICY = {
|
|
|
17
17
|
batchSaveInLiveSync: SettingPolicies.Default,
|
|
18
18
|
syncOnSave: SettingPolicies.Default,
|
|
19
19
|
syncOnStart: SettingPolicies.Default,
|
|
20
|
+
strictContentCheckOnStartup: SettingPolicies.LocalOnly,
|
|
20
21
|
syncOnFileOpen: SettingPolicies.Default,
|
|
21
22
|
syncOnEditorSave: SettingPolicies.Default,
|
|
22
23
|
keepReplicationActiveInBackground: SettingPolicies.Default,
|
|
@@ -91,6 +91,10 @@ interface SyncMethodSettings {
|
|
|
91
91
|
* automatically run sync on starting the plug-in.
|
|
92
92
|
*/
|
|
93
93
|
syncOnStart: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Compare file contents during startup checks when modification time and size match.
|
|
96
|
+
*/
|
|
97
|
+
strictContentCheckOnStartup?: boolean;
|
|
94
98
|
/**
|
|
95
99
|
* automatically run sync on opening a file.
|
|
96
100
|
*/
|
|
@@ -145,6 +145,7 @@ export declare const TweakValuesTemplate: {
|
|
|
145
145
|
batchSaveInLiveSync?: boolean;
|
|
146
146
|
syncOnSave?: boolean;
|
|
147
147
|
syncOnStart?: boolean;
|
|
148
|
+
strictContentCheckOnStartup?: boolean;
|
|
148
149
|
syncOnFileOpen?: boolean;
|
|
149
150
|
syncOnEditorSave?: boolean;
|
|
150
151
|
keepReplicationActiveInBackground?: boolean;
|
|
@@ -33,6 +33,7 @@ export declare const AllSettingDefault: {
|
|
|
33
33
|
batchSaveInLiveSync?: boolean;
|
|
34
34
|
syncOnSave: boolean;
|
|
35
35
|
syncOnStart: boolean;
|
|
36
|
+
strictContentCheckOnStartup?: boolean;
|
|
36
37
|
syncOnFileOpen: boolean;
|
|
37
38
|
syncOnEditorSave: boolean;
|
|
38
39
|
keepReplicationActiveInBackground: boolean;
|
|
@@ -104,6 +104,10 @@ const SettingInformation = {
|
|
|
104
104
|
name: "Sync on Startup",
|
|
105
105
|
desc: "Automatically Sync all files when opening Obsidian."
|
|
106
106
|
},
|
|
107
|
+
strictContentCheckOnStartup: {
|
|
108
|
+
name: "Strict content check on startup",
|
|
109
|
+
desc: "Compare file contents even when modification time and size match. This can slow startup in large vaults."
|
|
110
|
+
},
|
|
107
111
|
syncAfterMerge: {
|
|
108
112
|
name: "Sync after merging file",
|
|
109
113
|
desc: "Sync automatically after merging files"
|
|
@@ -12,6 +12,8 @@ export interface IFileHandler {
|
|
|
12
12
|
* operation never stores a document revision or writes to storage.
|
|
13
13
|
*/
|
|
14
14
|
tryRecordUntrackedFileRevision(info: UXFileInfoStub | UXFileInfo, expectedRevision: string): Promise<boolean>;
|
|
15
|
+
/** Metadata-only scan hint; never certifies content equality or changes provenance. */
|
|
16
|
+
canSkipUnchangedFile?(info: UXFileInfoStub, expected: MetaEntry): Promise<boolean>;
|
|
15
17
|
storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean, options?: StoreFileOptions): Promise<boolean>;
|
|
16
18
|
/**
|
|
17
19
|
* Store the current storage content as a child of an exact live database revision.
|
|
@@ -12,6 +12,8 @@ export interface IStorageAccessManager {
|
|
|
12
12
|
export interface StorageAccess {
|
|
13
13
|
normalisePath(path: string): string;
|
|
14
14
|
restoreState(): Promise<void>;
|
|
15
|
+
/** Includes queued, running, and failed work; absence requires a conservative content check. */
|
|
16
|
+
hasPendingFileEvent?(path: FilePath): boolean;
|
|
15
17
|
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>;
|
|
16
18
|
renameFile(file: UXFileInfoStub | FilePathWithPrefix, newPath: FilePathWithPrefix): Promise<UXFileInfoStub | null>;
|
|
17
19
|
writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>;
|
|
@@ -11,6 +11,8 @@ export declare abstract class StorageEventManager {
|
|
|
11
11
|
abstract beginWatch(): Promise<void>;
|
|
12
12
|
abstract appendQueue(items: FileEvent[], ctx?: unknown): Promise<void>;
|
|
13
13
|
abstract isWaiting(filename: FilePath): boolean;
|
|
14
|
+
/** Compatibility hosts without an ownership view must retain content checks. */
|
|
15
|
+
hasPendingFileEvent?(_filename: FilePath): boolean;
|
|
14
16
|
abstract waitForIdle(): Promise<void>;
|
|
15
17
|
abstract restoreState(): Promise<void>;
|
|
16
18
|
/**
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
class StorageEventManager {
|
|
2
|
+
/** Compatibility hosts without an ownership view must retain content checks. */
|
|
3
|
+
hasPendingFileEvent(_filename) {
|
|
4
|
+
return true;
|
|
5
|
+
}
|
|
2
6
|
/**
|
|
3
7
|
* Complete accepted work before its storage closes. Custom hosts must stop
|
|
4
8
|
* their watcher admission before invoking this default implementation.
|
|
@@ -134,6 +134,7 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
|
|
|
134
134
|
protected runQueuedEvents(options?: RunQueuedEventsOptions): Promise<void>;
|
|
135
135
|
protected processingCount: number;
|
|
136
136
|
protected requestProcessQueue(fei: FileEventItem): Promise<void>;
|
|
137
|
+
hasPendingFileEvent(filename: FilePath): boolean;
|
|
137
138
|
isWaiting(filename: FilePath): boolean;
|
|
138
139
|
protected handleFileEvent(queue: FileEventItem): Promise<void>;
|
|
139
140
|
protected cancelRelativeEvent(item: FileEventItem): void;
|
|
@@ -530,6 +530,15 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
530
530
|
this.updateStatus();
|
|
531
531
|
}
|
|
532
532
|
}
|
|
533
|
+
hasPendingFileEvent(filename) {
|
|
534
|
+
for (const entry of this.pendingSnapshot.values()) {
|
|
535
|
+
if (entry.completed || entry.event.type === TYPE_SENTINEL_FLUSH) continue;
|
|
536
|
+
const event = entry.event;
|
|
537
|
+
const relatedPath = event.type === "RENAME" ? event.args.oldPath : event.args.renameTarget;
|
|
538
|
+
if ([event.args.file.path, relatedPath].some((path) => path && (filename === path || filename.startsWith(`${path}/`)))) return true;
|
|
539
|
+
}
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
533
542
|
isWaiting(filename) {
|
|
534
543
|
return this._waitingMap.has(filename);
|
|
535
544
|
}
|
|
@@ -147,6 +147,7 @@ export declare class TrysteroReplicator {
|
|
|
147
147
|
batchSaveInLiveSync?: boolean;
|
|
148
148
|
syncOnSave?: boolean;
|
|
149
149
|
syncOnStart?: boolean;
|
|
150
|
+
strictContentCheckOnStartup?: boolean;
|
|
150
151
|
syncOnFileOpen?: boolean;
|
|
151
152
|
syncOnEditorSave?: boolean;
|
|
152
153
|
keepReplicationActiveInBackground?: boolean;
|
|
@@ -336,6 +337,7 @@ export declare class TrysteroReplicator {
|
|
|
336
337
|
batchSaveInLiveSync?: boolean;
|
|
337
338
|
syncOnSave?: boolean;
|
|
338
339
|
syncOnStart?: boolean;
|
|
340
|
+
strictContentCheckOnStartup?: boolean;
|
|
339
341
|
syncOnFileOpen?: boolean;
|
|
340
342
|
syncOnEditorSave?: boolean;
|
|
341
343
|
keepReplicationActiveInBackground?: boolean;
|
|
@@ -3,7 +3,7 @@ import { type LogFunction } from "../services/lib/logUtils.js";
|
|
|
3
3
|
import type { NecessaryServices } from "../interfaces/ServiceModule.js";
|
|
4
4
|
import type { IPathService } from "../services/base/IService.js";
|
|
5
5
|
import { UnresolvedErrorManager } from "../services/base/UnresolvedErrorManager.js";
|
|
6
|
-
import { VaultScanResults, type VaultScanResult } from "../services/base/VaultScanResult.js";
|
|
6
|
+
import { VaultScanResults, type VaultScanResult, type VaultScanOptions } from "../services/base/VaultScanResult.js";
|
|
7
7
|
export { VaultScanResults };
|
|
8
8
|
export type { VaultScanResult };
|
|
9
9
|
/**
|
|
@@ -156,7 +156,7 @@ export declare function getPathFromEntry(host: {
|
|
|
156
156
|
* @param file Storage file information
|
|
157
157
|
* @param doc Database entry
|
|
158
158
|
*/
|
|
159
|
-
export declare function syncFileBetweenDBandStorage(host: NecessaryServices<"setting" | "vault" | "path", "storageAccess" | "fileHandler">, log: LogFunction, file: UXFileInfoStub, doc: MetaEntry): Promise<FilePairProcessResult>;
|
|
159
|
+
export declare function syncFileBetweenDBandStorage(host: NecessaryServices<"setting" | "vault" | "path", "storageAccess" | "fileHandler">, log: LogFunction, file: UXFileInfoStub, doc: MetaEntry, scanOptions?: VaultScanOptions): Promise<FilePairProcessResult>;
|
|
160
160
|
export declare function canProceedScan(host: NecessaryServices<"keyValueDB" | "setting", never>, errorManager: UnresolvedErrorManager, log: LogFunction, showingNotice?: boolean, ignoreSuspending?: boolean): boolean;
|
|
161
161
|
/**
|
|
162
162
|
* Normalise the logical comparison key and lower case it when filename case should be handled insensitively.
|
|
@@ -180,7 +180,7 @@ export declare function collectDatabaseFiles(host: NecessaryServices<"database"
|
|
|
180
180
|
}>;
|
|
181
181
|
export declare function updateToDatabase(host: NecessaryServices<"vault", "fileHandler">, log: LogFunction, logLevel: LOG_LEVEL, file: UXFileInfoStub): Promise<FilePairProcessResult>;
|
|
182
182
|
export declare function updateToStorage(host: NecessaryServices<"vault" | "path", "fileHandler">, log: LogFunction, logLevel: LOG_LEVEL, w: MetaEntry): Promise<FilePairProcessResult>;
|
|
183
|
-
export declare function syncStorageAndDatabase(host: NecessaryServices<"setting" | "vault" | "path", "storageAccess" | "fileHandler">, log: LogFunction, file: UXFileInfoStub, logLevel: LOG_LEVEL, doc: MetaEntry): Promise<FilePairProcessResult>;
|
|
183
|
+
export declare function syncStorageAndDatabase(host: NecessaryServices<"setting" | "vault" | "path", "storageAccess" | "fileHandler">, log: LogFunction, file: UXFileInfoStub, logLevel: LOG_LEVEL, doc: MetaEntry, scanOptions?: VaultScanOptions): Promise<FilePairProcessResult>;
|
|
184
184
|
export declare const FullScanModes: {
|
|
185
185
|
readonly DB_APPLY: "db-apply";
|
|
186
186
|
readonly NEWER_WINS: "newer-wins";
|
|
@@ -205,7 +205,7 @@ export declare const ExtraOnLocal: {
|
|
|
205
205
|
*/
|
|
206
206
|
readonly APPEND_STORAGE_ONLY: "append-storage-only";
|
|
207
207
|
};
|
|
208
|
-
export interface FullScanOptions {
|
|
208
|
+
export interface FullScanOptions extends VaultScanOptions {
|
|
209
209
|
mode: FullScanMode;
|
|
210
210
|
extraOnLocal?: (typeof ExtraOnLocal)[keyof typeof ExtraOnLocal];
|
|
211
211
|
extraOnRemote?: (typeof ExtraOnRemote)[keyof typeof ExtraOnRemote];
|
|
@@ -350,7 +350,7 @@ function getPathFromEntry(host, doc) {
|
|
|
350
350
|
const path = host.services.path.getPath(doc);
|
|
351
351
|
return path;
|
|
352
352
|
}
|
|
353
|
-
async function syncFileBetweenDBandStorage(host, log, file, doc) {
|
|
353
|
+
async function syncFileBetweenDBandStorage(host, log, file, doc, scanOptions) {
|
|
354
354
|
const docPath = getPathFromEntry(host, doc);
|
|
355
355
|
if (!doc) {
|
|
356
356
|
throw new Error(`Missing doc:${docPath}`);
|
|
@@ -393,6 +393,10 @@ async function syncFileBetweenDBandStorage(host, log, file, doc) {
|
|
|
393
393
|
log(`STORAGE ? DB : ${file.path} has been skipped due to file size exceeding the limit`, LOG_LEVEL_NOTICE);
|
|
394
394
|
return FilePairProcessResults.SKIPPED;
|
|
395
395
|
}
|
|
396
|
+
if (scanOptions?.strictContentCheck === false && await host.serviceModules.fileHandler.canSkipUnchangedFile?.(file, doc)) {
|
|
397
|
+
log(`STORAGE ? DB : ${file.path} skipped using unchanged storage and revision metadata`, LOG_LEVEL_DEBUG);
|
|
398
|
+
return FilePairProcessResults.SKIPPED;
|
|
399
|
+
}
|
|
396
400
|
if (doc._rev && await host.serviceModules.fileHandler.tryRecordUntrackedFileRevision(file, doc._rev)) {
|
|
397
401
|
log("STORAGE == DB :" + file.path, LOG_LEVEL_DEBUG);
|
|
398
402
|
return FilePairProcessResults.COMPLETED;
|
|
@@ -549,13 +553,13 @@ async function updateToStorage(host, log, logLevel, w) {
|
|
|
549
553
|
return FilePairProcessResults.FAILED;
|
|
550
554
|
}
|
|
551
555
|
}
|
|
552
|
-
async function syncStorageAndDatabase(host, log, file, logLevel, doc) {
|
|
556
|
+
async function syncStorageAndDatabase(host, log, file, logLevel, doc, scanOptions) {
|
|
553
557
|
if ((doc._conflicts?.length ?? 0) > 0) {
|
|
554
558
|
log(`SYNC DATABASE AND STORAGE: ${file.path} has conflicts. skipped`, LOG_LEVEL_INFO);
|
|
555
559
|
return FilePairProcessResults.SKIPPED;
|
|
556
560
|
}
|
|
557
561
|
if (!host.services.vault.isFileSizeTooLarge(file.stat.size) && !host.services.vault.isFileSizeTooLarge(doc.size)) {
|
|
558
|
-
return await syncFileBetweenDBandStorage(host, log, file, doc);
|
|
562
|
+
return await syncFileBetweenDBandStorage(host, log, file, doc, scanOptions);
|
|
559
563
|
} else {
|
|
560
564
|
log(
|
|
561
565
|
`SYNC DATABASE AND STORAGE: ${getPathFromEntry(host, doc)} has been skipped due to file size exceeding the limit`,
|
|
@@ -698,7 +702,7 @@ async function processFilePair(host, log, pair, options) {
|
|
|
698
702
|
if (!file || !doc) {
|
|
699
703
|
throw new Error(`Cannot compare freshness for ${path}`);
|
|
700
704
|
}
|
|
701
|
-
const syncResult = await syncStorageAndDatabase(host, log, file, LOG_LEVEL_INFO, doc);
|
|
705
|
+
const syncResult = await syncStorageAndDatabase(host, log, file, LOG_LEVEL_INFO, doc, options);
|
|
702
706
|
if (syncResult === FilePairProcessResults.COMPLETED) {
|
|
703
707
|
updateFileMTimeInMap(host, fileMapKey, Math.max(file.stat.mtime, doc.mtime));
|
|
704
708
|
}
|
|
@@ -922,11 +926,12 @@ async function performFullScan(host, log, errorManager, showingNoticeOrOptions =
|
|
|
922
926
|
function useOfflineScanner(host) {
|
|
923
927
|
const log = createInstanceLogFunction("SF:OfflineScanner", host.services.API);
|
|
924
928
|
const errorManager = new UnresolvedErrorManager(host.services.appLifecycle, host.services.context.events);
|
|
925
|
-
const handleScanVault = async (showingNotice, ignoreSuspending = false, continueOnFileFailure = false) => {
|
|
929
|
+
const handleScanVault = async (showingNotice, ignoreSuspending = false, continueOnFileFailure = false, scanOptions) => {
|
|
926
930
|
return await performFullScan(host, log, errorManager, {
|
|
927
931
|
showingNotice,
|
|
928
932
|
ignoreSuspending,
|
|
929
|
-
continueOnFileFailure
|
|
933
|
+
continueOnFileFailure,
|
|
934
|
+
strictContentCheck: scanOptions?.strictContentCheck
|
|
930
935
|
});
|
|
931
936
|
};
|
|
932
937
|
host.services.vault.scanVault.addHandler(handleScanVault);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NecessaryServices } from "../interfaces/ServiceModule.js";
|
|
2
2
|
import { UnresolvedErrorManager } from "../services/base/UnresolvedErrorManager.js";
|
|
3
3
|
import { type LogFunction } from "../services/lib/logUtils.js";
|
|
4
|
-
import { type VaultScanResult } from "../services/base/VaultScanResult.js";
|
|
4
|
+
import { type VaultScanResult, type VaultScanOptions } from "../services/base/VaultScanResult.js";
|
|
5
5
|
/**
|
|
6
6
|
* Initialise the database and trigger a full vault scan.
|
|
7
7
|
* @param host Services container
|
|
@@ -11,9 +11,10 @@ import { type VaultScanResult } from "../services/base/VaultScanResult.js";
|
|
|
11
11
|
* @param reopenDatabase Whether to reopen the database connection
|
|
12
12
|
* @param ignoreSuspending Whether to ignore suspension settings
|
|
13
13
|
* @param continueOnFileFailure Whether individual file failures may satisfy application readiness
|
|
14
|
+
* @param scanOptions Optional content-check policy; omitted options keep strict checks
|
|
14
15
|
* @returns The initialisation outcome, including accepted individual file failures
|
|
15
16
|
*/
|
|
16
|
-
export declare function prepareDatabaseForUse(host: NecessaryServices<"appLifecycle" | "setting" | "vault" | "path" | "database" | "databaseEvents" | "fileProcessing" | "replicator", never>, log: LogFunction, errorManager: UnresolvedErrorManager, showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean): Promise<VaultScanResult>;
|
|
17
|
+
export declare function prepareDatabaseForUse(host: NecessaryServices<"appLifecycle" | "setting" | "vault" | "path" | "database" | "databaseEvents" | "fileProcessing" | "replicator", never>, log: LogFunction, errorManager: UnresolvedErrorManager, showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean, scanOptions?: VaultScanOptions): Promise<VaultScanResult>;
|
|
17
18
|
/**
|
|
18
19
|
* Associate the initialiser file feature with the app lifecycle events.
|
|
19
20
|
* This function binds initialization handlers to the appropriate lifecycle events.
|
|
@@ -6,7 +6,7 @@ function reportPreparationFailure(log, message) {
|
|
|
6
6
|
log(message, LOG_LEVEL_VERBOSE);
|
|
7
7
|
return false;
|
|
8
8
|
}
|
|
9
|
-
async function prepareDatabaseForUse(host, log, errorManager, showingNotice = false, reopenDatabase = true, ignoreSuspending = false, continueOnFileFailure = false) {
|
|
9
|
+
async function prepareDatabaseForUse(host, log, errorManager, showingNotice = false, reopenDatabase = true, ignoreSuspending = false, continueOnFileFailure = false, scanOptions) {
|
|
10
10
|
const appLifecycle = host.services.appLifecycle;
|
|
11
11
|
appLifecycle.resetIsReady();
|
|
12
12
|
try {
|
|
@@ -25,7 +25,7 @@ async function prepareDatabaseForUse(host, log, errorManager, showingNotice = fa
|
|
|
25
25
|
"Database preparation stopped because the local database is not ready."
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
|
-
const scanResult = await host.services.vault.scanVault(showingNotice, ignoreSuspending, continueOnFileFailure);
|
|
28
|
+
const scanResult = await host.services.vault.scanVault(showingNotice, ignoreSuspending, continueOnFileFailure, scanOptions);
|
|
29
29
|
if (scanResult === VaultScanResults.FAILED) {
|
|
30
30
|
return reportPreparationFailure(log, "Database preparation stopped because the Vault scan failed.");
|
|
31
31
|
}
|
|
@@ -57,7 +57,7 @@ async function prepareDatabaseForUse(host, log, errorManager, showingNotice = fa
|
|
|
57
57
|
function usePrepareDatabaseForUse(host) {
|
|
58
58
|
const log = createInstanceLogFunction("SF:prepareDatabaseForUse", host.services.API);
|
|
59
59
|
const errorManager = new UnresolvedErrorManager(host.services.appLifecycle, host.services.context.events);
|
|
60
|
-
const initialiseDatabaseHandler = async (showingNotice = false, reopenDatabase = true, ignoreSuspending = false, continueOnFileFailure = false) => {
|
|
60
|
+
const initialiseDatabaseHandler = async (showingNotice = false, reopenDatabase = true, ignoreSuspending = false, continueOnFileFailure = false, scanOptions) => {
|
|
61
61
|
return await prepareDatabaseForUse(
|
|
62
62
|
host,
|
|
63
63
|
log,
|
|
@@ -65,7 +65,8 @@ function usePrepareDatabaseForUse(host) {
|
|
|
65
65
|
showingNotice,
|
|
66
66
|
reopenDatabase,
|
|
67
67
|
ignoreSuspending,
|
|
68
|
-
continueOnFileFailure
|
|
68
|
+
continueOnFileFailure,
|
|
69
|
+
scanOptions
|
|
69
70
|
);
|
|
70
71
|
};
|
|
71
72
|
host.services.databaseEvents.initialiseDatabase.addHandler(initialiseDatabaseHandler);
|
|
@@ -26,6 +26,7 @@ export declare class ServiceFileAccessBase<TAdapter extends IFileSystemAdapter<a
|
|
|
26
26
|
private vault;
|
|
27
27
|
private setting;
|
|
28
28
|
constructor(services: StorageAccessBaseDependencies<TAdapter>);
|
|
29
|
+
hasPendingFileEvent(path: FilePath): boolean;
|
|
29
30
|
restoreState(): Promise<void>;
|
|
30
31
|
_everyOnFirstInitialize(): Promise<boolean>;
|
|
31
32
|
_everyCommitPendingFileEvent(): Promise<boolean>;
|
|
@@ -98,6 +98,7 @@ export declare abstract class ServiceFileHandlerBase extends ServiceModuleBase<S
|
|
|
98
98
|
getPath(entry: AnyEntry): FilePathWithPrefix;
|
|
99
99
|
getPathWithoutPrefix(entry: AnyEntry): FilePathWithPrefix;
|
|
100
100
|
readFileFromStub(file: UXFileInfoStub | UXFileInfo): Promise<UXFileInfo>;
|
|
101
|
+
canSkipUnchangedFile(info: UXFileInfoStub, expected: MetaEntry): Promise<boolean>;
|
|
101
102
|
tryRecordUntrackedFileRevision(info: UXFileInfoStub | UXFileInfo, expectedRevision: string): Promise<boolean>;
|
|
102
103
|
private infoToStub;
|
|
103
104
|
storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean, options?: StoreFileOptions): Promise<boolean>;
|
|
@@ -258,6 +258,27 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
258
258
|
}
|
|
259
259
|
return readFile;
|
|
260
260
|
}
|
|
261
|
+
async canSkipUnchangedFile(info, expected) {
|
|
262
|
+
const path = getStoragePathFromUXFileInfo(info);
|
|
263
|
+
const provenance = this.fileReflectionProvenance;
|
|
264
|
+
if (!provenance || !expected._rev || expected.deleted || expected._deleted || expected._conflicts?.length || this.storage.hasPendingFileEvent?.(path) !== false) return false;
|
|
265
|
+
try {
|
|
266
|
+
return await this.serializedByFileEventPaths([path], async () => {
|
|
267
|
+
const provenancePath = this.getProvenancePath(path);
|
|
268
|
+
const record = await provenance.get(provenancePath);
|
|
269
|
+
if (!record || !hasContentHash(record) || record.revision !== expected._rev || !Number.isFinite(record.observedStorageMtime) || record.observedStorageMtime !== info.stat.mtime || record.contentSize !== info.stat.size) return false;
|
|
270
|
+
const current = await this.db.fetchEntryMeta(path, void 0, true);
|
|
271
|
+
if (!current || current._rev !== record.revision || current.deleted || current._deleted || (await this.db.getConflictedRevs(path)).length > 0) return false;
|
|
272
|
+
const stat = await this.storage.stat(path);
|
|
273
|
+
if (!stat || stat.type !== "file" || stat.mtime !== record.observedStorageMtime || stat.size !== record.contentSize) return false;
|
|
274
|
+
return sameProvenance(await provenance.get(provenancePath), record) && this.storage.hasPendingFileEvent?.(path) === false;
|
|
275
|
+
});
|
|
276
|
+
} catch (error) {
|
|
277
|
+
this._log(`Could not inspect unchanged file metadata for ${path}`, LOG_LEVEL_VERBOSE);
|
|
278
|
+
this._log(error, LOG_LEVEL_VERBOSE);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
261
282
|
async tryRecordUntrackedFileRevision(info, expectedRevision) {
|
|
262
283
|
if (!this.fileReflectionProvenance || !expectedRevision) return false;
|
|
263
284
|
const path = getStoragePathFromUXFileInfo(info);
|
|
@@ -34,5 +34,5 @@ export declare abstract class DatabaseEventService<T extends ServiceContext = Se
|
|
|
34
34
|
* @param reopenDatabase Whether to reopen the database if it is already open.
|
|
35
35
|
* @param ignoreSuspending Whether to ignore any suspending state.
|
|
36
36
|
*/
|
|
37
|
-
readonly initialiseDatabase: import("../lib/HandlerUtils.js").MultipleHandlerFunction<(showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean) => Promise<import("./VaultScanResult.js").VaultScanResult>, unknown>;
|
|
37
|
+
readonly initialiseDatabase: import("../lib/HandlerUtils.js").MultipleHandlerFunction<(showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean, scanOptions?: import("./VaultScanResult.js").VaultScanOptions) => Promise<import("./VaultScanResult.js").VaultScanResult>, unknown>;
|
|
38
38
|
}
|
|
@@ -17,7 +17,7 @@ import type { ActiveReplicatorContext, ContinuousReplicationRequest, Replication
|
|
|
17
17
|
import type { RemoteResourceKind, RemoteResourceMap } from "../../replication/RemoteResource.js";
|
|
18
18
|
import type { CentralRemoteAdministrationRequest, CentralRemoteAdministrationResult } from "../../replication/CentralRemoteAdministration.js";
|
|
19
19
|
import type { HandlerOutcome, MultipleHandlerFunction } from "../lib/HandlerUtils.js";
|
|
20
|
-
import type { VaultScanResult } from "./VaultScanResult.js";
|
|
20
|
+
import type { VaultScanResult, VaultScanOptions } from "./VaultScanResult.js";
|
|
21
21
|
import type { UICommandRegistry } from "../../uiCommands.js";
|
|
22
22
|
declare global {
|
|
23
23
|
interface OPTIONAL_SYNC_FEATURES {
|
|
@@ -91,7 +91,7 @@ export interface IDatabaseEventService {
|
|
|
91
91
|
onDatabaseInitialised(showNotice: boolean): Promise<boolean>;
|
|
92
92
|
onDatabaseHasReady(): Promise<boolean>;
|
|
93
93
|
onResetDatabase(db: LiveSyncLocalDB): Promise<boolean>;
|
|
94
|
-
initialiseDatabase(showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean): Promise<VaultScanResult>;
|
|
94
|
+
initialiseDatabase(showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean, scanOptions?: VaultScanOptions): Promise<VaultScanResult>;
|
|
95
95
|
}
|
|
96
96
|
export interface IKeyValueDBService {
|
|
97
97
|
openSimpleStore<T>(kind: string): SimpleStore<T>;
|
|
@@ -355,7 +355,7 @@ export interface ITweakValueService {
|
|
|
355
355
|
export interface IVaultService {
|
|
356
356
|
vaultName(): string;
|
|
357
357
|
getVaultName(): string;
|
|
358
|
-
scanVault(showingNotice?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean): Promise<VaultScanResult>;
|
|
358
|
+
scanVault(showingNotice?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean, scanOptions?: VaultScanOptions): Promise<VaultScanResult>;
|
|
359
359
|
isIgnoredByIgnoreFile(file: string | UXFileInfoStub): Promise<boolean>;
|
|
360
360
|
isTargetFile(file: string | UXFileInfoStub,
|
|
361
361
|
/** Inspect selection policy without treating a filename collision as an exclusion. */
|
|
@@ -10,4 +10,9 @@ export declare const VaultScanResults: {
|
|
|
10
10
|
readonly COMPLETED: true;
|
|
11
11
|
readonly COMPLETED_WITH_FILE_FAILURES: "completed-with-file-failures";
|
|
12
12
|
};
|
|
13
|
+
/** Optional policy for an ordinary scan; omitted options retain strict content checks. */
|
|
14
|
+
export interface VaultScanOptions {
|
|
15
|
+
/** False permits unchanged tracked files to be skipped using storage and revision metadata. */
|
|
16
|
+
strictContentCheck?: boolean;
|
|
17
|
+
}
|
|
13
18
|
export type VaultScanResult = (typeof VaultScanResults)[keyof typeof VaultScanResults];
|
|
@@ -26,7 +26,7 @@ export declare abstract class VaultService<T extends ServiceContext = ServiceCon
|
|
|
26
26
|
* @param showingNotice Whether to show a notice to the user.
|
|
27
27
|
* @param ignoreSuspending Whether to ignore any suspending state.
|
|
28
28
|
*/
|
|
29
|
-
readonly scanVault: import("../lib/HandlerUtils.js").MultipleHandlerFunction<(showingNotice?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean) => Promise<import("./VaultScanResult.js").VaultScanResult>, unknown>;
|
|
29
|
+
readonly scanVault: import("../lib/HandlerUtils.js").MultipleHandlerFunction<(showingNotice?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean, scanOptions?: import("./VaultScanResult.js").VaultScanOptions) => Promise<import("./VaultScanResult.js").VaultScanResult>, unknown>;
|
|
30
30
|
/**
|
|
31
31
|
* Check if a file is ignored by the ignore file (e.g., .gitignore, .obsidianignore).
|
|
32
32
|
* @param file The file path or file info stub to check.
|