mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-08-28 06:17:06 +00:00
Keep incomplete Fast Setup isolated
This commit is contained in:
@@ -10,11 +10,7 @@ import {
|
||||
synchroniseAllFilesBetweenDBandStorage,
|
||||
type FullScanOptions,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner";
|
||||
import {
|
||||
adjustSettingToRemoteIfNeeded,
|
||||
cancelScheduledInitialisation,
|
||||
processVaultInitialisation,
|
||||
} from "./redFlag";
|
||||
import { adjustSettingToRemoteIfNeeded, cancelScheduledInitialisation, processVaultInitialisation } from "./redFlag";
|
||||
|
||||
export const SIMPLE_FETCH_STAGE1_REMOTE_WINS = "Overwrite all with remote files";
|
||||
export const SIMPLE_FETCH_STAGE1_NEWER_WINS = "Compare time and take newer";
|
||||
@@ -217,7 +213,7 @@ export async function askAndPerformFastSetupOnScheduledFetchAll(
|
||||
return await cancelScheduledInitialisation(host, cleanupFlag);
|
||||
}
|
||||
|
||||
return await processVaultInitialisation(host, log, async () => {
|
||||
const performFastSetup = async () => {
|
||||
// 1. Perform fast DB fetch (download remote DB content to local DB)
|
||||
await host.serviceModules.rebuilder.$fetchLocalDBFast(false);
|
||||
|
||||
@@ -253,5 +249,6 @@ export async function askAndPerformFastSetupOnScheduledFetchAll(
|
||||
clearRememberedSimpleFetchMode(host);
|
||||
log("Simple fetch and scan operation completed.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
return await processVaultInitialisation(host, log, performFastSetup, "keep-on-failure");
|
||||
}
|
||||
|
||||
@@ -372,26 +372,35 @@ export async function cancelScheduledInitialisation(
|
||||
return false;
|
||||
}
|
||||
|
||||
type InitialisationSuspensionPolicy = "resume" | "keep" | "keep-on-failure";
|
||||
|
||||
/**
|
||||
* Process vault initialisation with suspending file watching and sync.
|
||||
* @param proc process to be executed during initialisation, should return true if can be continued, false if app is unable to continue the process.
|
||||
* @param keepSuspending whether to keep suspending file watching after the process.
|
||||
* @returns result of the process, or false if error occurs.
|
||||
* Process Vault initialisation with file watching and synchronisation suspended.
|
||||
* @param proc Process to execute during initialisation. It returns true only when normal operation may resume.
|
||||
* @param suspensionPolicy Final file-reflection state. `keep-on-failure` controls both reflection directions so a partly completed Fast Setup remains isolated.
|
||||
* @returns The result of the process, or false if an error occurs.
|
||||
*/
|
||||
export async function processVaultInitialisation(
|
||||
host: NecessaryServices<"setting", never>,
|
||||
log: LogFunction,
|
||||
proc: () => Promise<boolean>,
|
||||
keepSuspending = false
|
||||
suspensionPolicy: InitialisationSuspensionPolicy = "resume"
|
||||
) {
|
||||
let completed = false;
|
||||
try {
|
||||
// Disable batch saving and file watching during initialisation.
|
||||
await host.services.setting.applyPartial({ batchSave: false }, false);
|
||||
await host.services.setting.suspendAllSync();
|
||||
await host.services.setting.suspendExtraSync();
|
||||
await host.services.setting.applyPartial({ suspendFileWatching: true }, true);
|
||||
await host.services.setting.applyPartial(
|
||||
suspensionPolicy === "keep-on-failure"
|
||||
? { suspendFileWatching: true, suspendParseReplicationResult: true }
|
||||
: { suspendFileWatching: true },
|
||||
true
|
||||
);
|
||||
try {
|
||||
const result = await proc();
|
||||
completed = result;
|
||||
return result;
|
||||
} catch (ex) {
|
||||
log("Error during vault initialisation process.", LOG_LEVEL_NOTICE);
|
||||
@@ -403,9 +412,21 @@ export async function processVaultInitialisation(
|
||||
log(ex, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
} finally {
|
||||
if (!keepSuspending) {
|
||||
// Re-enable file watching after initialisation.
|
||||
if (suspensionPolicy === "resume") {
|
||||
await host.services.setting.applyPartial({ suspendFileWatching: false }, true);
|
||||
} else if (suspensionPolicy === "keep") {
|
||||
await host.services.setting.applyPartial({ suspendFileWatching: true }, true);
|
||||
} else {
|
||||
// Fast Setup owns both directions at this boundary. Reasserting the
|
||||
// outcome also covers a late failure after finishRebuild started to
|
||||
// resume reflection, and the legacy doNotSuspendOnFetching path.
|
||||
await host.services.setting.applyPartial(
|
||||
{
|
||||
suspendFileWatching: !completed,
|
||||
suspendParseReplicationResult: !completed,
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,7 +533,7 @@ export function createSuspendFlagHandler(
|
||||
await host.services.setting.applyPartial({ writeLogToTheFile: true }, true);
|
||||
return Promise.resolve(false);
|
||||
},
|
||||
true
|
||||
"keep"
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -325,13 +325,13 @@ describe("Red Flag Feature", () => {
|
||||
() => {
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
false
|
||||
"resume"
|
||||
);
|
||||
|
||||
expect(host.mocks.setting.currentSettings().suspendFileWatching).toBe(false);
|
||||
});
|
||||
|
||||
it("should keep suspending when keepSuspending is true", async () => {
|
||||
it("should keep suspending when the policy is keep", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
@@ -341,7 +341,7 @@ describe("Red Flag Feature", () => {
|
||||
() => {
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
true
|
||||
"keep"
|
||||
);
|
||||
|
||||
expect(host.mocks.setting.currentSettings().suspendFileWatching).toBe(true);
|
||||
@@ -357,7 +357,7 @@ describe("Red Flag Feature", () => {
|
||||
() => {
|
||||
throw new Error("Process failed");
|
||||
},
|
||||
false
|
||||
"resume"
|
||||
);
|
||||
|
||||
expect(result).toBe(false);
|
||||
@@ -759,6 +759,79 @@ describe("Red Flag Feature", () => {
|
||||
});
|
||||
|
||||
describe("askAndPerformFastSetupOnScheduledFetchAll", () => {
|
||||
it("releases both reflection suspensions after Fast Setup succeeds", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
doNotSuspendOnFetching: true,
|
||||
suspendParseReplicationResult: true,
|
||||
});
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_CLEANUP);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue(availableRemoteTweaks({ batchSave: false }));
|
||||
|
||||
await expect(askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag)).resolves.toBe(true);
|
||||
|
||||
expect(host.mocks.setting.currentSettings()).toMatchObject({
|
||||
suspendFileWatching: false,
|
||||
suspendParseReplicationResult: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps Vault reflection suspended and preserves recovery state when Fast Fetch fails", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_CLEANUP);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue(availableRemoteTweaks({ batchSave: false }));
|
||||
host.mocks.rebuilder.$fetchLocalDBFast.mockRejectedValueOnce(new Error("cannot decrypt remote document"));
|
||||
|
||||
await expect(askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag)).resolves.toBe(false);
|
||||
|
||||
expect(host.mocks.setting.currentSettings()).toMatchObject({
|
||||
suspendFileWatching: true,
|
||||
suspendParseReplicationResult: true,
|
||||
});
|
||||
expect(host.mocks.rebuilder.finishRebuild).not.toHaveBeenCalled();
|
||||
expect(cleanupFlag).not.toHaveBeenCalled();
|
||||
expect(host.mocks.setting.deleteSmallConfig).not.toHaveBeenCalledWith("simple-fetch-mode");
|
||||
});
|
||||
|
||||
it("re-suspends both reflection directions when finalisation fails after releasing them", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_CLEANUP);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue(availableRemoteTweaks({ batchSave: false }));
|
||||
host.mocks.rebuilder.finishRebuild.mockImplementationOnce(async () => {
|
||||
await host.mocks.setting.applyPartial(
|
||||
{
|
||||
suspendFileWatching: false,
|
||||
suspendParseReplicationResult: false,
|
||||
},
|
||||
true
|
||||
);
|
||||
throw new Error("Vault scan failed after reflection resumed");
|
||||
});
|
||||
|
||||
await expect(askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag)).resolves.toBe(false);
|
||||
|
||||
expect(host.mocks.setting.currentSettings()).toMatchObject({
|
||||
suspendFileWatching: true,
|
||||
suspendParseReplicationResult: true,
|
||||
});
|
||||
expect(cleanupFlag).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should remember quick flow choices while the scheduled fetch is pending", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
@@ -1352,7 +1425,7 @@ describe("Red Flag Feature", () => {
|
||||
() => {
|
||||
return Promise.resolve(false);
|
||||
},
|
||||
true
|
||||
"keep"
|
||||
);
|
||||
|
||||
expect(host.mocks.setting.currentSettings().suspendFileWatching).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user