mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-16 14:22:32 +00:00
Enhance P2P synchronization features and UI improvements
This commit is contained in:
@@ -13,17 +13,26 @@ export class P2POpenReplicationModal extends Modal {
|
|||||||
callback?: P2POpenReplicationModalCallback;
|
callback?: P2POpenReplicationModalCallback;
|
||||||
component?: ReturnType<typeof mount>;
|
component?: ReturnType<typeof mount>;
|
||||||
showResult: boolean;
|
showResult: boolean;
|
||||||
|
title: string;
|
||||||
|
onClosed?: () => void;
|
||||||
|
rebuildMode: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
app: App,
|
app: App,
|
||||||
liveSyncReplicator: LiveSyncTrysteroReplicator,
|
liveSyncReplicator: LiveSyncTrysteroReplicator,
|
||||||
callback?: P2POpenReplicationModalCallback,
|
callback?: P2POpenReplicationModalCallback,
|
||||||
showResult: boolean = false
|
showResult: boolean = false,
|
||||||
|
title: string = "P2P Replication",
|
||||||
|
onClosed?: () => void,
|
||||||
|
rebuildMode: boolean = false
|
||||||
) {
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
this.liveSyncReplicator = liveSyncReplicator;
|
this.liveSyncReplicator = liveSyncReplicator;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.showResult = showResult;
|
this.showResult = showResult;
|
||||||
|
this.title = title;
|
||||||
|
this.onClosed = onClosed;
|
||||||
|
this.rebuildMode = rebuildMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSync(peerId: string) {
|
async onSync(peerId: string) {
|
||||||
@@ -41,7 +50,7 @@ export class P2POpenReplicationModal extends Modal {
|
|||||||
|
|
||||||
override onOpen() {
|
override onOpen() {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
this.titleEl.setText("P2P Replication");
|
this.titleEl.setText(this.title);
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
|
|
||||||
if (this.component === undefined) {
|
if (this.component === undefined) {
|
||||||
@@ -53,6 +62,7 @@ export class P2POpenReplicationModal extends Modal {
|
|||||||
onSyncAndClose: (peerId: string) => this.onSyncAndClose(peerId),
|
onSyncAndClose: (peerId: string) => this.onSyncAndClose(peerId),
|
||||||
onClose: () => this.close(),
|
onClose: () => this.close(),
|
||||||
showResult: this.showResult,
|
showResult: this.showResult,
|
||||||
|
rebuildMode: this.rebuildMode,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -65,5 +75,6 @@ export class P2POpenReplicationModal extends Modal {
|
|||||||
void unmount(this.component);
|
void unmount(this.component);
|
||||||
this.component = undefined;
|
this.component = undefined;
|
||||||
}
|
}
|
||||||
|
this.onClosed?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,10 @@
|
|||||||
onSyncAndClose: (_peerId: string) => Promise<void>;
|
onSyncAndClose: (_peerId: string) => Promise<void>;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
showResult: boolean;
|
showResult: boolean;
|
||||||
|
rebuildMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { onSync, onSyncAndClose, onClose, showResult, liveSyncReplicator }: Props = $props();
|
let { onSync, onSyncAndClose, onClose, showResult, liveSyncReplicator, rebuildMode = false }: Props = $props();
|
||||||
|
|
||||||
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
||||||
let syncingPeerId = $state<string | null>(null);
|
let syncingPeerId = $state<string | null>(null);
|
||||||
@@ -36,10 +37,10 @@
|
|||||||
const unsubscribe = eventHub.onEvent(EVENT_SERVER_STATUS, (status) => {
|
const unsubscribe = eventHub.onEvent(EVENT_SERVER_STATUS, (status) => {
|
||||||
serverInfo = status;
|
serverInfo = status;
|
||||||
});
|
});
|
||||||
fireAndForget(async ()=>{
|
fireAndForget(async () => {
|
||||||
await delay(100);
|
await delay(100);
|
||||||
await requestServerStatus();
|
await requestServerStatus();
|
||||||
})
|
});
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,6 +56,18 @@
|
|||||||
syncingPeerId = null;
|
syncingPeerId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function handleSyncThenClose(peerId: string) {
|
||||||
|
try {
|
||||||
|
syncingPeerId = peerId;
|
||||||
|
Logger(`Starting sync with ${peerId}`, logLevel);
|
||||||
|
await onSyncAndClose(peerId);
|
||||||
|
Logger(`Sync completed with ${peerId}`, logLevel);
|
||||||
|
} catch (e) {
|
||||||
|
Logger(`Error during sync: ${e instanceof Error ? e.message : String(e)}`, logLevel);
|
||||||
|
} finally {
|
||||||
|
syncingPeerId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSyncAndClose(peerId: string) {
|
async function handleSyncAndClose(peerId: string) {
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
@@ -68,7 +81,7 @@
|
|||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
async function disconnect(){
|
async function disconnect() {
|
||||||
try {
|
try {
|
||||||
await liveSyncReplicator.close();
|
await liveSyncReplicator.close();
|
||||||
Logger("Signalling connection closed.", logLevel);
|
Logger("Signalling connection closed.", logLevel);
|
||||||
@@ -76,7 +89,7 @@
|
|||||||
Logger(`Failed to close signalling connection: ${e instanceof Error ? e.message : String(e)}`, logLevel);
|
Logger(`Failed to close signalling connection: ${e instanceof Error ? e.message : String(e)}`, logLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function onCloseAndDisconnect(){
|
async function onCloseAndDisconnect() {
|
||||||
await disconnect();
|
await disconnect();
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
@@ -97,10 +110,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p2p-container">
|
<div class="p2p-container">
|
||||||
<P2PServerStatusCard
|
<P2PServerStatusCard {liveSyncReplicator} showBroadcastToggle={false} />
|
||||||
{liveSyncReplicator}
|
|
||||||
showBroadcastToggle={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="peers-section">
|
<div class="peers-section">
|
||||||
<h3>Available Peers</h3>
|
<h3>Available Peers</h3>
|
||||||
@@ -121,20 +131,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="peer-actions">
|
<div class="peer-actions">
|
||||||
<button
|
{#if !rebuildMode}
|
||||||
class="btn btn-primary"
|
<button
|
||||||
disabled={syncingPeerId !== null}
|
class="btn btn-primary"
|
||||||
onclick={() => handleSync(peer.peerId)}
|
disabled={syncingPeerId !== null}
|
||||||
>
|
onclick={() => handleSync(peer.peerId)}
|
||||||
{syncingPeerId === peer.peerId ? "Syncing..." : "Sync"}
|
>
|
||||||
</button>
|
{syncingPeerId === peer.peerId ? "Syncing..." : "Sync"}
|
||||||
<button
|
</button>
|
||||||
class="btn btn-secondary"
|
<button
|
||||||
disabled={syncingPeerId !== null}
|
class="btn {rebuildMode ? 'btn-primary' : 'btn-secondary'}"
|
||||||
onclick={() => handleSyncAndClose(peer.peerId)}
|
disabled={syncingPeerId !== null}
|
||||||
>
|
onclick={() => handleSyncAndClose(peer.peerId)}
|
||||||
Start Sync & Close
|
>
|
||||||
</button>
|
{syncingPeerId === peer.peerId ? "Syncing..." : "Start Sync & Close"}
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
class="btn {rebuildMode ? 'btn-primary' : 'btn-secondary'}"
|
||||||
|
disabled={syncingPeerId !== null}
|
||||||
|
onclick={() => handleSyncThenClose(peer.peerId)}
|
||||||
|
>
|
||||||
|
{syncingPeerId === peer.peerId ? "Syncing..." : "Sync"}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -145,8 +165,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<button class="btn btn-cancel" onclick={onClose}>Close</button>
|
{#if rebuildMode}
|
||||||
<button class="btn btn-cancel" onclick={onCloseAndDisconnect}>Close & Disconnect</button>
|
<button class="btn btn-cancel" onclick={onClose} disabled={syncingPeerId !== null}>Skip and close</button>
|
||||||
|
{:else}
|
||||||
|
<button class="btn btn-cancel" onclick={onClose}>Close</button>
|
||||||
|
<button class="btn btn-cancel" onclick={onCloseAndDisconnect}>Close & Disconnect</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { App } from "@/deps.ts";
|
import { App } from "@/deps.ts";
|
||||||
import { Logger } from "@lib/common/logger";
|
import { Logger } from "@lib/common/logger";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@lib/common/types";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@lib/common/types";
|
||||||
import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator";
|
import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||||
import { P2POpenReplicationModal } from "./P2POpenReplicationModal";
|
import { P2POpenReplicationModal } from "./P2POpenReplicationModal";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,3 +71,61 @@ export function createOpenReplicationUI(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an openRebuildUI factory for Obsidian environments.
|
||||||
|
* Opens the P2P Replication modal in "rebuild" mode — one-way pull only,
|
||||||
|
* with setOnSetup / clearOnSetup bracketing the replicateFrom call.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* const factory = createOpenRebuildUI(app);
|
||||||
|
* useP2PReplicatorFeature(core, createOpenReplicationUI(app), factory);
|
||||||
|
*/
|
||||||
|
export function createOpenRebuildUI(
|
||||||
|
app: App
|
||||||
|
): (replicator: LiveSyncTrysteroReplicator) => (showResult: boolean) => Promise<boolean | void> {
|
||||||
|
return (replicator: LiveSyncTrysteroReplicator) =>
|
||||||
|
(showResult: boolean): Promise<boolean | void> => {
|
||||||
|
const logLevel = showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
||||||
|
return new Promise<boolean | void>((resolve) => {
|
||||||
|
let resolved = false;
|
||||||
|
const safeResolve = (val: boolean) => {
|
||||||
|
if (!resolved) {
|
||||||
|
resolved = true;
|
||||||
|
resolve(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const doRebuild = async (peerId: string) => {
|
||||||
|
replicator.setOnSetup();
|
||||||
|
try {
|
||||||
|
Logger(`Rebuilding from peer ${peerId}`, logLevel);
|
||||||
|
const result = await replicator.replicateFrom(peerId, showResult);
|
||||||
|
safeResolve(result?.ok ?? false);
|
||||||
|
} catch (e) {
|
||||||
|
Logger(
|
||||||
|
`Error in rebuild from ${peerId}: ${e instanceof Error ? e.message : String(e)}`,
|
||||||
|
logLevel
|
||||||
|
);
|
||||||
|
safeResolve(false);
|
||||||
|
} finally {
|
||||||
|
replicator.clearOnSetup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const modal = new P2POpenReplicationModal(
|
||||||
|
app,
|
||||||
|
replicator,
|
||||||
|
{
|
||||||
|
onSync: doRebuild,
|
||||||
|
onSyncAndClose: doRebuild,
|
||||||
|
},
|
||||||
|
showResult,
|
||||||
|
"P2P Rebuild",
|
||||||
|
() => safeResolve(false),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
modal.open();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
Submodule src/lib updated: 4ce9136a43...cc552acad2
+6
-2
@@ -44,7 +44,7 @@ import { useSetupManagerHandlersFeature } from "./serviceFeatures/setupObsidian/
|
|||||||
import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature.ts";
|
import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature.ts";
|
||||||
import { useP2PReplicatorCommands } from "@lib/replication/trystero/useP2PReplicatorCommands.ts";
|
import { useP2PReplicatorCommands } from "@lib/replication/trystero/useP2PReplicatorCommands.ts";
|
||||||
import { useP2PReplicatorUI } from "./serviceFeatures/useP2PReplicatorUI.ts";
|
import { useP2PReplicatorUI } from "./serviceFeatures/useP2PReplicatorUI.ts";
|
||||||
import { createOpenReplicationUI } from "./features/P2PSync/P2PReplicator/P2PReplicationUI.ts";
|
import { createOpenReplicationUI, createOpenRebuildUI } from "./features/P2PSync/P2PReplicator/P2PReplicationUI.ts";
|
||||||
export type LiveSyncCore = LiveSyncBaseCore<ObsidianServiceContext, LiveSyncCommands>;
|
export type LiveSyncCore = LiveSyncBaseCore<ObsidianServiceContext, LiveSyncCommands>;
|
||||||
export default class ObsidianLiveSyncPlugin extends Plugin {
|
export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||||
core: LiveSyncCore;
|
core: LiveSyncCore;
|
||||||
@@ -177,7 +177,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const curriedFeature = () => featuresInitialiser(core);
|
const curriedFeature = () => featuresInitialiser(core);
|
||||||
core.services.appLifecycle.onLayoutReady.addHandler(curriedFeature);
|
core.services.appLifecycle.onLayoutReady.addHandler(curriedFeature);
|
||||||
const setupManager = core.getModule(SetupManager);
|
const setupManager = core.getModule(SetupManager);
|
||||||
const replicator = useP2PReplicatorFeature(core, createOpenReplicationUI(this.app));
|
const replicator = useP2PReplicatorFeature(
|
||||||
|
core,
|
||||||
|
createOpenReplicationUI(this.app),
|
||||||
|
createOpenRebuildUI(this.app)
|
||||||
|
);
|
||||||
useP2PReplicatorCommands(core, replicator);
|
useP2PReplicatorCommands(core, replicator);
|
||||||
useP2PReplicatorUI(core, core, replicator);
|
useP2PReplicatorUI(core, core, replicator);
|
||||||
useRemoteConfiguration(core);
|
useRemoteConfiguration(core);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { AbstractModule } from "../AbstractModule.ts";
|
|||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
import type { LiveSyncCore } from "../../main.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
import { REMOTE_P2P } from "@lib/common/models/setting.const.ts";
|
||||||
|
|
||||||
export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||||
async _anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
async _anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
||||||
@@ -186,6 +187,9 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
|||||||
async _checkAndAskUseRemoteConfiguration(
|
async _checkAndAskUseRemoteConfiguration(
|
||||||
trialSetting: RemoteDBSettings
|
trialSetting: RemoteDBSettings
|
||||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
|
if (trialSetting.remoteType === REMOTE_P2P) {
|
||||||
|
return { result: false, requireFetch: false };
|
||||||
|
}
|
||||||
const preferred = await this.services.tweakValue.fetchRemotePreferred(trialSetting);
|
const preferred = await this.services.tweakValue.fetchRemotePreferred(trialSetting);
|
||||||
if (preferred) {
|
if (preferred) {
|
||||||
return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred);
|
return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred);
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async showWindowOnRight(viewType: string): Promise<void> {
|
override async showWindowOnRight(viewType: string): Promise<void> {
|
||||||
|
const existing = this.app.workspace.getLeavesOfType(viewType);
|
||||||
|
if (existing.length > 0) {
|
||||||
|
await this.app.workspace.revealLeaf(existing[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const rightLeaf = this.app.workspace.getRightLeaf(false);
|
const rightLeaf = this.app.workspace.getRightLeaf(false);
|
||||||
if (rightLeaf) {
|
if (rightLeaf) {
|
||||||
await rightLeaf.setViewState({
|
await rightLeaf.setViewState({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { FlagFilesHumanReadable, FlagFilesOriginal } from "@lib/common/models/re
|
|||||||
import FetchEverything from "../modules/features/SetupWizard/dialogs/FetchEverything.svelte";
|
import FetchEverything from "../modules/features/SetupWizard/dialogs/FetchEverything.svelte";
|
||||||
import RebuildEverything from "../modules/features/SetupWizard/dialogs/RebuildEverything.svelte";
|
import RebuildEverything from "../modules/features/SetupWizard/dialogs/RebuildEverything.svelte";
|
||||||
import { extractObject } from "octagonal-wheels/object";
|
import { extractObject } from "octagonal-wheels/object";
|
||||||
import { REMOTE_MINIO } from "@lib/common/models/setting.const";
|
import { REMOTE_MINIO, REMOTE_P2P } from "@lib/common/models/setting.const";
|
||||||
import type { ObsidianLiveSyncSettings } from "@lib/common/models/setting.type";
|
import type { ObsidianLiveSyncSettings } from "@lib/common/models/setting.type";
|
||||||
import { TweakValuesShouldMatchedTemplate } from "@lib/common/models/tweak.definition";
|
import { TweakValuesShouldMatchedTemplate } from "@lib/common/models/tweak.definition";
|
||||||
|
|
||||||
@@ -200,6 +200,13 @@ export async function adjustSettingToRemoteIfNeeded(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// P2P has no centralised remote configuration; skip to avoid a spurious
|
||||||
|
// "Failed to connect to the remote server" error dialog.
|
||||||
|
if (config.remoteType === REMOTE_P2P) {
|
||||||
|
log("Remote configuration fetch skipped (P2P mode).", LOG_LEVEL_INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Remote configuration fetched and applied.
|
// Remote configuration fetched and applied.
|
||||||
if (await adjustSettingToRemote(host, log, config)) {
|
if (await adjustSettingToRemote(host, log, config)) {
|
||||||
config = host.services.setting.currentSettings();
|
config = host.services.setting.currentSettings();
|
||||||
|
|||||||
Reference in New Issue
Block a user