mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-23 19:17:07 +00:00
refactor: compose Journal remotes generically
This commit is contained in:
@@ -21,7 +21,7 @@ import { AbstractModule } from "./modules/AbstractModule";
|
|||||||
import { ModulePeriodicProcess } from "./modules/core/ModulePeriodicProcess";
|
import { ModulePeriodicProcess } from "./modules/core/ModulePeriodicProcess";
|
||||||
import { ModuleReplicator } from "./modules/core/ModuleReplicator";
|
import { ModuleReplicator } from "./modules/core/ModuleReplicator";
|
||||||
import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB";
|
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 { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker";
|
||||||
import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver";
|
import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver";
|
||||||
import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks";
|
import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks";
|
||||||
@@ -139,7 +139,7 @@ export class LiveSyncBaseCore<
|
|||||||
public registerModules(extraModules: AbstractModule[] = []) {
|
public registerModules(extraModules: AbstractModule[] = []) {
|
||||||
this._registerModule(new ModuleLiveSyncMain(this));
|
this._registerModule(new ModuleLiveSyncMain(this));
|
||||||
this._registerModule(new ModuleConflictChecker(this));
|
this._registerModule(new ModuleConflictChecker(this));
|
||||||
this._registerModule(new ModuleReplicatorMinIO(this));
|
this._registerModule(new ModuleReplicatorJournal(this));
|
||||||
this._registerModule(new ModuleReplicatorCouchDB(this));
|
this._registerModule(new ModuleReplicatorCouchDB(this));
|
||||||
this._registerModule(new ModuleReplicator(this));
|
this._registerModule(new ModuleReplicator(this));
|
||||||
this._registerModule(new ModuleConflictResolver(this));
|
this._registerModule(new ModuleConflictResolver(this));
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
type FilePathWithPrefix,
|
type FilePathWithPrefix,
|
||||||
type ObsidianLiveSyncSettings,
|
type ObsidianLiveSyncSettings,
|
||||||
REMOTE_COUCHDB,
|
REMOTE_COUCHDB,
|
||||||
REMOTE_MINIO,
|
isJournalRemoteType,
|
||||||
type EntryMilestoneInfo,
|
type EntryMilestoneInfo,
|
||||||
type EntryDoc,
|
type EntryDoc,
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||||
@@ -61,7 +61,7 @@ async function verifyRemoteState(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
milestone = await dbRet.db.get(MILESTONE_DOCID);
|
milestone = await dbRet.db.get(MILESTONE_DOCID);
|
||||||
} else if (settings.remoteType === REMOTE_MINIO) {
|
} else if (isJournalRemoteType(settings.remoteType)) {
|
||||||
const journalReplicator = replicator as LiveSyncJournalReplicator;
|
const journalReplicator = replicator as LiveSyncJournalReplicator;
|
||||||
if (journalProtocolConfigurationForSettings(settings).journalFormat === "adaptive-v1") {
|
if (journalProtocolConfigurationForSettings(settings).journalFormat === "adaptive-v1") {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
REMOTE_COUCHDB,
|
REMOTE_COUCHDB,
|
||||||
REMOTE_MINIO,
|
REMOTE_MINIO,
|
||||||
REMOTE_P2P,
|
REMOTE_P2P,
|
||||||
|
REMOTE_WEBDAV,
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||||
import { runCommand } from "./runCommand";
|
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 () => {
|
it("mark-resolved with remote-id temporarily activates it and runs markResolved", async () => {
|
||||||
const core = createCoreMock();
|
const core = createCoreMock();
|
||||||
const settings = core.services.setting.currentSettings();
|
const settings = core.services.setting.currentSettings();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
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 { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||||
import type { LiveSyncAbstractReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator";
|
import type { LiveSyncAbstractReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator";
|
||||||
import { AbstractModule } from "@/modules/AbstractModule";
|
import { AbstractModule } from "@/modules/AbstractModule";
|
||||||
@@ -9,7 +9,7 @@ export class ModuleReplicatorCouchDB extends AbstractModule {
|
|||||||
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
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 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(false);
|
||||||
}
|
}
|
||||||
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
||||||
@@ -17,7 +17,7 @@ export class ModuleReplicatorCouchDB extends AbstractModule {
|
|||||||
_everyAfterResumeProcess(): Promise<boolean> {
|
_everyAfterResumeProcess(): Promise<boolean> {
|
||||||
if (this.services.appLifecycle.isSuspended()) return Promise.resolve(true);
|
if (this.services.appLifecycle.isSuspended()) return Promise.resolve(true);
|
||||||
if (!this.services.appLifecycle.isReady()) 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 LiveSyncEnabled = this.settings.liveSync;
|
||||||
const continuous = LiveSyncEnabled;
|
const continuous = LiveSyncEnabled;
|
||||||
const eventualOnStart = !LiveSyncEnabled && this.settings.syncOnStart;
|
const eventualOnStart = !LiveSyncEnabled && this.settings.syncOnStart;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { REMOTE_WEBDAV } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||||
import { ModuleReplicatorCouchDB } from "./ModuleReplicatorCouchDB.ts";
|
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 openReplication = vi.fn(async () => true);
|
||||||
const runFiniteReplicationActivity = vi.fn(async (task: () => unknown) => await task());
|
const runFiniteReplicationActivity = vi.fn(async (task: () => unknown) => await task());
|
||||||
const services = {
|
const services = {
|
||||||
@@ -86,4 +90,19 @@ describe("ModuleReplicatorCouchDB resume replication activity", () => {
|
|||||||
expect(runFiniteReplicationActivity).not.toHaveBeenCalled();
|
expect(runFiniteReplicationActivity).not.toHaveBeenCalled();
|
||||||
expect(openReplication).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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+3
-3
@@ -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 { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator";
|
||||||
import type { LiveSyncAbstractReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator";
|
import type { LiveSyncAbstractReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator";
|
||||||
import type { LiveSyncCore } from "@/main";
|
import type { LiveSyncCore } from "@/main";
|
||||||
import { AbstractModule } from "@/modules/AbstractModule";
|
import { AbstractModule } from "@/modules/AbstractModule";
|
||||||
|
|
||||||
export class ModuleReplicatorMinIO extends AbstractModule {
|
export class ModuleReplicatorJournal extends AbstractModule {
|
||||||
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
if (settings.remoteType == REMOTE_MINIO) {
|
if (isJournalRemoteType(settings.remoteType)) {
|
||||||
return Promise.resolve(new LiveSyncJournalReplicator(this.core));
|
return Promise.resolve(new LiveSyncJournalReplicator(this.core));
|
||||||
}
|
}
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -83,7 +83,10 @@ function suggestRemoteConfigurationName(parsed: RemoteConfigurationResult): stri
|
|||||||
if (parsed.type === "s3") {
|
if (parsed.type === "s3") {
|
||||||
return `S3 ${parsed.settings.bucket || parsed.settings.endpoint}`;
|
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(
|
export function paneRemoteConfig(
|
||||||
|
|||||||
Reference in New Issue
Block a user