diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts index 116d7b06..36fdde5d 100644 --- a/src/LiveSyncBaseCore.ts +++ b/src/LiveSyncBaseCore.ts @@ -21,7 +21,7 @@ import { AbstractModule } from "./modules/AbstractModule"; import { ModulePeriodicProcess } from "./modules/core/ModulePeriodicProcess"; import { ModuleReplicator } from "./modules/core/ModuleReplicator"; import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB"; -import { ModuleReplicatorMinIO } from "./modules/core/ModuleReplicatorMinIO"; +import { ModuleReplicatorJournal } from "./modules/core/ModuleReplicatorJournal"; import { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker"; import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver"; import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks"; @@ -139,7 +139,7 @@ export class LiveSyncBaseCore< public registerModules(extraModules: AbstractModule[] = []) { this._registerModule(new ModuleLiveSyncMain(this)); this._registerModule(new ModuleConflictChecker(this)); - this._registerModule(new ModuleReplicatorMinIO(this)); + this._registerModule(new ModuleReplicatorJournal(this)); this._registerModule(new ModuleReplicatorCouchDB(this)); this._registerModule(new ModuleReplicator(this)); this._registerModule(new ModuleConflictResolver(this)); diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index d6f65c42..19838052 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -6,7 +6,7 @@ import { type FilePathWithPrefix, type ObsidianLiveSyncSettings, REMOTE_COUCHDB, - REMOTE_MINIO, + isJournalRemoteType, type EntryMilestoneInfo, type EntryDoc, } from "@vrtmrz/livesync-commonlib/compat/common/types"; @@ -61,7 +61,7 @@ async function verifyRemoteState( return false; } milestone = await dbRet.db.get(MILESTONE_DOCID); - } else if (settings.remoteType === REMOTE_MINIO) { + } else if (isJournalRemoteType(settings.remoteType)) { const journalReplicator = replicator as LiveSyncJournalReplicator; if (journalProtocolConfigurationForSettings(settings).journalFormat === "adaptive-v1") { try { diff --git a/src/apps/cli/commands/runCommand.unit.spec.ts b/src/apps/cli/commands/runCommand.unit.spec.ts index 4cfaf611..641038be 100644 --- a/src/apps/cli/commands/runCommand.unit.spec.ts +++ b/src/apps/cli/commands/runCommand.unit.spec.ts @@ -7,6 +7,7 @@ import { REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, + REMOTE_WEBDAV, } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { runCommand } from "./runCommand"; @@ -750,6 +751,32 @@ describe("runCommand abnormal cases", () => { ); }); + it("uses the Adaptive verification path for a WebDAV remote", async () => { + const core = createCoreMock(); + const settings = core.services.setting.currentSettings(); + settings.remoteType = REMOTE_WEBDAV; + settings.webDAVactiveConnectionURI = "sls+webdav://dav.example/dav"; + settings.journalFormat = "adaptive-v1"; + settings.packReadPolicy = "whole-pack"; + + const ensureCheckpointCachesAreFresh = vi.fn(async () => {}); + core.services.replicator.getActiveReplicator.mockReturnValue({ + nodeid: "test-node-id", + initializeDatabaseForReplication: vi.fn(async () => {}), + client: { + ensureCheckpointCachesAreFresh, + }, + }); + + const result = await runCommand(makeOptions("mark-resolved", []), { + ...context, + core, + }); + + expect(result).toBe(true); + expect(ensureCheckpointCachesAreFresh).toHaveBeenCalledTimes(1); + }); + it("mark-resolved with remote-id temporarily activates it and runs markResolved", async () => { const core = createCoreMock(); const settings = core.services.setting.currentSettings(); diff --git a/src/modules/core/ModuleReplicatorCouchDB.ts b/src/modules/core/ModuleReplicatorCouchDB.ts index d96fb5c0..4e7a82a6 100644 --- a/src/modules/core/ModuleReplicatorCouchDB.ts +++ b/src/modules/core/ModuleReplicatorCouchDB.ts @@ -1,5 +1,5 @@ import { fireAndForget } from "octagonal-wheels/promises"; -import { REMOTE_MINIO, REMOTE_P2P, type RemoteDBSettings } from "@vrtmrz/livesync-commonlib/compat/common/types"; +import { isJournalRemoteType, REMOTE_P2P, type RemoteDBSettings } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; import type { LiveSyncAbstractReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator"; import { AbstractModule } from "@/modules/AbstractModule"; @@ -9,7 +9,7 @@ export class ModuleReplicatorCouchDB extends AbstractModule { _anyNewReplicator(settingOverride: Partial = {}): Promise { const settings = { ...this.settings, ...settingOverride }; // If new remote types were added, add them here. Do not use `REMOTE_COUCHDB` directly for the safety valve. - if (settings.remoteType == REMOTE_MINIO || settings.remoteType == REMOTE_P2P) { + if (isJournalRemoteType(settings.remoteType) || settings.remoteType == REMOTE_P2P) { return Promise.resolve(false); } return Promise.resolve(new LiveSyncCouchDBReplicator(this.core)); @@ -17,7 +17,7 @@ export class ModuleReplicatorCouchDB extends AbstractModule { _everyAfterResumeProcess(): Promise { if (this.services.appLifecycle.isSuspended()) return Promise.resolve(true); if (!this.services.appLifecycle.isReady()) return Promise.resolve(true); - if (this.settings.remoteType != REMOTE_MINIO && this.settings.remoteType != REMOTE_P2P) { + if (!isJournalRemoteType(this.settings.remoteType) && this.settings.remoteType != REMOTE_P2P) { const LiveSyncEnabled = this.settings.liveSync; const continuous = LiveSyncEnabled; const eventualOnStart = !LiveSyncEnabled && this.settings.syncOnStart; diff --git a/src/modules/core/ModuleReplicatorCouchDB.unit.spec.ts b/src/modules/core/ModuleReplicatorCouchDB.unit.spec.ts index c4296e19..94b7beca 100644 --- a/src/modules/core/ModuleReplicatorCouchDB.unit.spec.ts +++ b/src/modules/core/ModuleReplicatorCouchDB.unit.spec.ts @@ -1,7 +1,11 @@ import { describe, expect, it, vi } from "vitest"; +import { REMOTE_WEBDAV } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { ModuleReplicatorCouchDB } from "./ModuleReplicatorCouchDB.ts"; -function createModule(settings: { liveSync: boolean; syncOnStart: boolean }, isReplicationReady = true) { +function createModule( + settings: { liveSync: boolean; remoteType?: string; syncOnStart: boolean }, + isReplicationReady = true +) { const openReplication = vi.fn(async () => true); const runFiniteReplicationActivity = vi.fn(async (task: () => unknown) => await task()); const services = { @@ -86,4 +90,19 @@ describe("ModuleReplicatorCouchDB resume replication activity", () => { expect(runFiniteReplicationActivity).not.toHaveBeenCalled(); expect(openReplication).not.toHaveBeenCalled(); }); + + it("does not claim or resume WebDAV as CouchDB", async () => { + const { module, openReplication, runFiniteReplicationActivity } = createModule({ + liveSync: true, + remoteType: REMOTE_WEBDAV, + syncOnStart: true, + }); + + await expect(module._anyNewReplicator()).resolves.toBe(false); + await module._everyAfterResumeProcess(); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(runFiniteReplicationActivity).not.toHaveBeenCalled(); + expect(openReplication).not.toHaveBeenCalled(); + }); }); diff --git a/src/modules/core/ModuleReplicatorMinIO.ts b/src/modules/core/ModuleReplicatorJournal.ts similarity index 79% rename from src/modules/core/ModuleReplicatorMinIO.ts rename to src/modules/core/ModuleReplicatorJournal.ts index 68dcc22e..260cf10f 100644 --- a/src/modules/core/ModuleReplicatorMinIO.ts +++ b/src/modules/core/ModuleReplicatorJournal.ts @@ -1,13 +1,13 @@ -import { REMOTE_MINIO, type RemoteDBSettings } from "@vrtmrz/livesync-commonlib/compat/common/types"; +import { isJournalRemoteType, type RemoteDBSettings } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator"; import type { LiveSyncAbstractReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator"; import type { LiveSyncCore } from "@/main"; import { AbstractModule } from "@/modules/AbstractModule"; -export class ModuleReplicatorMinIO extends AbstractModule { +export class ModuleReplicatorJournal extends AbstractModule { _anyNewReplicator(settingOverride: Partial = {}): Promise { const settings = { ...this.settings, ...settingOverride }; - if (settings.remoteType == REMOTE_MINIO) { + if (isJournalRemoteType(settings.remoteType)) { return Promise.resolve(new LiveSyncJournalReplicator(this.core)); } return Promise.resolve(false); diff --git a/src/modules/core/ModuleReplicatorJournal.unit.spec.ts b/src/modules/core/ModuleReplicatorJournal.unit.spec.ts new file mode 100644 index 00000000..fe79b18a --- /dev/null +++ b/src/modules/core/ModuleReplicatorJournal.unit.spec.ts @@ -0,0 +1,47 @@ +import { describe, expect, it, vi } from "vitest"; +import { REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_WEBDAV } from "@vrtmrz/livesync-commonlib/compat/common/types"; + +const journalReplicatorConstructor = vi.hoisted(() => vi.fn()); + +vi.mock("@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator", () => ({ + LiveSyncJournalReplicator: class { + constructor(env: unknown) { + journalReplicatorConstructor(env); + } + }, +})); + +import { ModuleReplicatorJournal } from "./ModuleReplicatorJournal.ts"; + +function createModule(remoteType: string) { + const services = { + API: { + addCommand: vi.fn(), + addLog: vi.fn(), + addRibbonIcon: vi.fn(), + registerProtocolHandler: vi.fn(), + registerWindow: vi.fn(), + }, + }; + const core = { + _services: services, + services, + settings: { remoteType }, + } as any; + return { core, module: new ModuleReplicatorJournal(core) }; +} + +describe("ModuleReplicatorJournal", () => { + it.each([REMOTE_MINIO, REMOTE_WEBDAV])("creates a Journal replicator for %s", async (remoteType) => { + const { core, module } = createModule(remoteType); + + await expect(module._anyNewReplicator()).resolves.not.toBe(false); + expect(journalReplicatorConstructor).toHaveBeenLastCalledWith(core); + }); + + it("does not claim a CouchDB remote", async () => { + const { module } = createModule(REMOTE_COUCHDB); + + await expect(module._anyNewReplicator()).resolves.toBe(false); + }); +}); diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index 9ec37c56..7f627f2e 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -83,7 +83,10 @@ function suggestRemoteConfigurationName(parsed: RemoteConfigurationResult): stri if (parsed.type === "s3") { return `S3 ${parsed.settings.bucket || parsed.settings.endpoint}`; } - return `P2P ${parsed.settings.P2P_roomID || "Remote"}`; + if (parsed.type === "p2p") { + return `P2P ${parsed.settings.P2P_roomID || "Remote"}`; + } + return "WebDAV Remote"; } export function paneRemoteConfig(