Reserve setup initialisation before enabling settings

This commit is contained in:
vorotamoroz
2026-07-20 14:20:10 +00:00
parent 7faf51a6c4
commit 40a4987b0b
6 changed files with 222 additions and 16 deletions
+40 -14
View File
@@ -40,6 +40,10 @@ import type {
SetupRemoteResultType,
UseSetupURIResultType,
} from "./SetupWizard/dialogs/setupDialogTypes.ts";
import {
applySettingsAndFetchOnActivation,
applySettingsWithScheduledInitialisation,
} from "@/serviceFeatures/setupObsidian/setupActivationLifecycle.ts";
/**
* User modes for onboarding and setup
@@ -341,9 +345,9 @@ export class SetupManager extends AbstractModule {
// console.dir(patch);
if (!activate) {
extra();
await this.applySetting(newConf, UserMode.ExistingUser);
this._log("Setting Applied", LOG_LEVEL_NOTICE);
return true;
const applied = await this.applySettingAndScheduleFetchOnActivation(newConf, UserMode.ExistingUser);
if (applied) this._log("Setting Applied", LOG_LEVEL_NOTICE);
return applied;
}
// Check virtual changes
const original = { ...this.settings, P2P_DevicePeerName: "" } as ObsidianLiveSyncSettings;
@@ -351,9 +355,9 @@ export class SetupManager extends AbstractModule {
const isOnlyVirtualChange = isObjectDifferent(original, modified, true) === false;
if (isOnlyVirtualChange) {
extra();
await this.applySetting(newConf, UserMode.ExistingUser);
this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE);
return true;
const applied = await this.applySettingAndScheduleFetchOnActivation(newConf, UserMode.ExistingUser);
if (applied) this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE);
return applied;
} else {
const userModeResult =
await this.dialogManager.openWithExplicitCancel<OutroAskUserModeResultType>(OutroAskUserMode);
@@ -363,9 +367,12 @@ export class SetupManager extends AbstractModule {
userMode = UserMode.ExistingUser;
} else if (userModeResult === "compatible-existing-user") {
extra();
await this.applySetting(newConf, UserMode.ExistingUser);
this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE);
return true;
const applied = await this.applySettingAndScheduleFetchOnActivation(
newConf,
UserMode.ExistingUser
);
if (applied) this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE);
return applied;
} else if (userModeResult === "cancelled") {
this._log("User cancelled applying settings from wizard.", LOG_LEVEL_NOTICE);
return false;
@@ -382,13 +389,17 @@ export class SetupManager extends AbstractModule {
}
if (confirm) {
extra();
await this.applySetting(newConf, userMode);
if (userMode === UserMode.NewUser) {
// For new users, schedule a rebuild everything.
await this.core.rebuilder.scheduleRebuild();
// Reserve Rebuild before enabling the imported settings, so
// the current runtime cannot begin ordinary processing first.
await applySettingsWithScheduledInitialisation(this.core.rebuilder, "rebuild", async () => {
await this.applySetting(newConf, userMode);
});
} else {
// For existing users, schedule a fetch.
await this.core.rebuilder.scheduleFetch();
// Existing data must be fetched before the ordinary startup scan.
await applySettingsWithScheduledInitialisation(this.core.rebuilder, "fetch", async () => {
await this.applySetting(newConf, userMode);
});
}
}
// Settings applied, but may require rebuild to take effect.
@@ -430,4 +441,19 @@ export class SetupManager extends AbstractModule {
await this.services.setting.applyExternalSettings(newConf, true);
return true;
}
private async applySettingAndScheduleFetchOnActivation(
newConf: ObsidianLiveSyncSettings,
userMode: UserMode
): Promise<boolean> {
const wasConfigured = this.settings.isConfigured;
return await applySettingsAndFetchOnActivation(
this.core.rebuilder,
wasConfigured,
newConf.isConfigured,
async () => {
await this.applySetting(newConf, userMode);
}
);
}
}
+73 -2
View File
@@ -98,8 +98,14 @@ function createSetupManager() {
const core: any = {
_services: services,
rebuilder: {
scheduleRebuild: vi.fn(() => Promise.resolve()),
scheduleFetch: vi.fn(() => Promise.resolve()),
scheduleRebuild: vi.fn(async (prepareBeforeRestart?: () => Promise<void>) => {
await prepareBeforeRestart?.();
return true;
}),
scheduleFetch: vi.fn(async (prepareBeforeRestart?: () => Promise<void>) => {
await prepareBeforeRestart?.();
return true;
}),
},
};
Object.defineProperty(core, "services", {
@@ -169,4 +175,69 @@ describe("SetupManager", () => {
);
expect(setting.currentSettings().activeConfigurationId).toBe("legacy-couchdb");
});
it("reserves Rebuild before saving a new-user configuration", async () => {
const { manager, setting, dialogManager, core } = createSetupManager();
setting.settings = { ...setting.currentSettings(), isConfigured: false };
const applyExternalSettings = vi.spyOn(setting, "applyExternalSettings");
dialogManager.openWithExplicitCancel.mockResolvedValueOnce(true);
await manager.onConfirmApplySettingsFromWizard(
{ ...createLegacyRemoteSetting(), isConfigured: true },
UserMode.NewUser
);
expect(core.rebuilder.scheduleRebuild).toHaveBeenCalledWith(expect.any(Function));
expect(core.rebuilder.scheduleRebuild.mock.invocationCallOrder[0]).toBeLessThan(
applyExternalSettings.mock.invocationCallOrder[0]
);
expect(setting.currentSettings().isConfigured).toBe(true);
});
it("reserves Fetch when compatible imported settings activate an unconfigured device", async () => {
const { manager, setting, dialogManager, core } = createSetupManager();
setting.settings = { ...setting.currentSettings(), isConfigured: false };
const applyExternalSettings = vi.spyOn(setting, "applyExternalSettings");
dialogManager.openWithExplicitCancel
.mockResolvedValueOnce({ ...createLegacyRemoteSetting(), isConfigured: true })
.mockResolvedValueOnce("compatible-existing-user");
await manager.onUseSetupURI(UserMode.Unknown, "mock-config://settings");
expect(core.rebuilder.scheduleFetch).toHaveBeenCalledWith(expect.any(Function));
expect(core.rebuilder.scheduleFetch.mock.invocationCallOrder[0]).toBeLessThan(
applyExternalSettings.mock.invocationCallOrder[0]
);
expect(setting.currentSettings().isConfigured).toBe(true);
});
it("applies compatible settings to an already configured device without scheduling Fetch", async () => {
const { manager, setting, dialogManager, core } = createSetupManager();
setting.settings = { ...setting.currentSettings(), isConfigured: true };
dialogManager.openWithExplicitCancel
.mockResolvedValueOnce({ ...createLegacyRemoteSetting(), isConfigured: true })
.mockResolvedValueOnce("compatible-existing-user");
await manager.onUseSetupURI(UserMode.Unknown, "mock-config://settings");
expect(core.rebuilder.scheduleFetch).not.toHaveBeenCalled();
expect(setting.currentSettings().isConfigured).toBe(true);
});
it("does not enable imported settings when the initialisation flag cannot be reserved", async () => {
const { manager, setting, dialogManager, core } = createSetupManager();
setting.settings = { ...setting.currentSettings(), isConfigured: false };
const applyExternalSettings = vi.spyOn(setting, "applyExternalSettings");
core.rebuilder.scheduleRebuild.mockResolvedValueOnce(false);
dialogManager.openWithExplicitCancel.mockResolvedValueOnce(true);
await manager.onConfirmApplySettingsFromWizard(
{ ...createLegacyRemoteSetting(), isConfigured: true },
UserMode.NewUser
);
expect(core.rebuilder.scheduleRebuild).toHaveBeenCalledWith(expect.any(Function));
expect(applyExternalSettings).not.toHaveBeenCalled();
expect(setting.currentSettings().isConfigured).toBe(false);
});
});