mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-23 09:33:20 +00:00
Fix automatic P2P settings restoration in CLI host mode
This commit is contained in:
1 parent
0956663f7b
commit
3e2b4dcb3b
16 files changed
+944
-25
No files matched your search
@@ -5,6 +5,7 @@ import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFu
|
||||
import { LiveSyncError } from "@vrtmrz/livesync-commonlib/compat/common/LSError";
|
||||
import type { P2PPeerConnectionMetrics, P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||
import { fsPromises } from "@vrtmrz/livesync-commonlib/node";
|
||||
import type { CLIP2PHostSettings } from "./types";
|
||||
|
||||
type CLIP2PPeer = {
|
||||
peerId: string;
|
||||
@@ -191,8 +192,11 @@ export async function syncWithPeer(
|
||||
/** Connect the headless P2P host and transfer transport ownership to the caller. */
|
||||
export async function openP2PHost(
|
||||
core: LiveSyncBaseCore<ServiceContext, never>,
|
||||
p2pService: CLIP2PService | undefined
|
||||
p2pService: CLIP2PService | undefined,
|
||||
originalP2PSettings: CLIP2PHostSettings
|
||||
): Promise<CLIP2PService> {
|
||||
// Restore only P2P policy after database readiness, before peer discovery.
|
||||
await core.services.setting.applyPartial(originalP2PSettings);
|
||||
const service = requireP2PService(core, p2pService);
|
||||
await service.transportLifecycle.connect();
|
||||
return service;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { collectPeers, createPeerConnectionStatsPayload, parseTimeoutSeconds, syncWithPeer } from "./p2p";
|
||||
import { collectPeers, createPeerConnectionStatsPayload, openP2PHost, parseTimeoutSeconds, syncWithPeer } from "./p2p";
|
||||
import { AutoAccepting } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { CLIP2PHostSettings } from "./types";
|
||||
|
||||
function createCore() {
|
||||
const settings = { P2P_Enabled: true, P2P_AppID: "app-id", P2P_IsHeadless: false };
|
||||
@@ -37,6 +39,87 @@ function createP2PService() {
|
||||
}
|
||||
|
||||
describe("p2p command helpers", () => {
|
||||
const passiveHostSettings: CLIP2PHostSettings = {
|
||||
P2P_Enabled: true,
|
||||
P2P_AutoAccepting: AutoAccepting.NONE,
|
||||
P2P_AutoStart: false,
|
||||
P2P_AutoBroadcast: false,
|
||||
P2P_AutoSyncPeers: "",
|
||||
P2P_AutoWatchPeers: "",
|
||||
};
|
||||
|
||||
function createHostCore() {
|
||||
const settings = {
|
||||
...passiveHostSettings,
|
||||
P2P_Enabled: false,
|
||||
P2P_IsHeadless: false,
|
||||
P2P_AppID: "app-id",
|
||||
liveSync: false,
|
||||
suspendFileWatching: true,
|
||||
};
|
||||
const applyPartial = vi.fn(async (partial: CLIP2PHostSettings) => {
|
||||
Object.assign(settings, partial);
|
||||
});
|
||||
const saveSettingData = vi.fn();
|
||||
const applySettings = vi.fn();
|
||||
return {
|
||||
core: {
|
||||
services: {
|
||||
setting: { currentSettings: () => settings, applyPartial, saveSettingData },
|
||||
control: { applySettings },
|
||||
},
|
||||
},
|
||||
settings,
|
||||
applyPartial,
|
||||
saveSettingData,
|
||||
applySettings,
|
||||
};
|
||||
}
|
||||
|
||||
it.each([
|
||||
passiveHostSettings,
|
||||
{ ...passiveHostSettings, P2P_AutoBroadcast: true, P2P_AutoWatchPeers: "Desktop" },
|
||||
{ ...passiveHostSettings, P2P_AutoAccepting: AutoAccepting.ALL, P2P_AutoSyncPeers: "Desktop,android" },
|
||||
])("connects with the configured host policy without saving or restarting the lifecycle (%j)", async (original) => {
|
||||
const host = createHostCore();
|
||||
const { service, connect, disconnect } = createP2PService();
|
||||
connect.mockImplementation(async () => {
|
||||
expect(host.settings).toMatchObject({ ...original, P2P_IsHeadless: true });
|
||||
});
|
||||
|
||||
await expect(openP2PHost(host.core as never, service as never, original)).resolves.toBe(service);
|
||||
|
||||
expect(host.applyPartial).toHaveBeenCalledExactlyOnceWith(original);
|
||||
expect(connect).toHaveBeenCalledOnce();
|
||||
expect(disconnect).not.toHaveBeenCalled();
|
||||
expect(host.settings).toMatchObject({ liveSync: false, suspendFileWatching: true });
|
||||
expect(host.saveSettingData).not.toHaveBeenCalled();
|
||||
expect(host.applySettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps P2P disabled when the saved host policy disables it", async () => {
|
||||
const host = createHostCore();
|
||||
const { service, connect } = createP2PService();
|
||||
|
||||
await expect(
|
||||
openP2PHost(host.core as never, service as never, { ...passiveHostSettings, P2P_Enabled: false })
|
||||
).rejects.toThrow("P2P is disabled");
|
||||
|
||||
expect(connect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not connect when restoring the host policy fails", async () => {
|
||||
const host = createHostCore();
|
||||
const { service, connect } = createP2PService();
|
||||
host.applyPartial.mockRejectedValue(new Error("settings failed"));
|
||||
|
||||
await expect(openP2PHost(host.core as never, service as never, passiveHostSettings)).rejects.toThrow(
|
||||
"settings failed"
|
||||
);
|
||||
|
||||
expect(connect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("accepts non-negative timeout", () => {
|
||||
expect(parseTimeoutSeconds("0", "p2p-peers")).toBe(0);
|
||||
expect(parseTimeoutSeconds("2.5", "p2p-sync")).toBe(2.5);
|
||||
|
||||
@@ -214,7 +214,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext
|
||||
|
||||
if (options.command === "p2p-host") {
|
||||
writeStderrLine(standardIo, "[Command] p2p-host");
|
||||
await openP2PHost(core, context.p2pReplicator);
|
||||
await openP2PHost(core, context.p2pReplicator, context.originalP2PSettings);
|
||||
writeStderrLine(standardIo, "[Ready] P2P host is running. Press Ctrl+C to stop.");
|
||||
await new Promise(() => {});
|
||||
return true;
|
||||
|
||||
@@ -49,6 +49,16 @@ export interface CLIOptions {
|
||||
interval?: number;
|
||||
}
|
||||
|
||||
export type CLIP2PHostSettings = Pick<
|
||||
ObsidianLiveSyncSettings,
|
||||
| "P2P_Enabled"
|
||||
| "P2P_AutoAccepting"
|
||||
| "P2P_AutoStart"
|
||||
| "P2P_AutoBroadcast"
|
||||
| "P2P_AutoSyncPeers"
|
||||
| "P2P_AutoWatchPeers"
|
||||
>;
|
||||
|
||||
export interface CLICommandContext {
|
||||
databasePath: string;
|
||||
vaultPath: string;
|
||||
@@ -58,6 +68,8 @@ export interface CLICommandContext {
|
||||
/** Current-result contract owned by the P2P service feature. */
|
||||
p2pReplicator?: UseP2PReplicatorResult;
|
||||
settingsPath: string;
|
||||
/** Configured P2P policy before temporary CLI startup suspension. */
|
||||
originalP2PSettings: CLIP2PHostSettings;
|
||||
originalSyncSettings: Pick<
|
||||
ObsidianLiveSyncSettings,
|
||||
| "liveSync"
|
||||
|
||||
Reference in new issue
Block a user