mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-21 13:41:29 +00:00
0.24.12
Fixed - No longer unnecessary acknowledgements are sent when starting peer-to-peer synchronisation. Refactored - Platform impedance-matching-layer has been improved. - Some UIs have been get isomorphic among Obsidian and web applications (for `webpeer`).
This commit is contained in:
@@ -7,3 +7,5 @@ rollup.config.js
|
|||||||
src/lib/test
|
src/lib/test
|
||||||
src/lib/src/cli
|
src/lib/src/cli
|
||||||
main.js
|
main.js
|
||||||
|
src/lib/apps/webpeer/dist
|
||||||
|
src/lib/apps/webpeer/svelte.config.js
|
||||||
@@ -18,6 +18,10 @@ Note: This plugin cannot synchronise with the official "Obsidian Sync".
|
|||||||
- Supporting End-to-end encryption.
|
- Supporting End-to-end encryption.
|
||||||
- Synchronisation of settings, snippets, themes, and plug-ins, via [Customization sync(Beta)](#customization-sync) or [Hidden File Sync](#hiddenfilesync)
|
- Synchronisation of settings, snippets, themes, and plug-ins, via [Customization sync(Beta)](#customization-sync) or [Hidden File Sync](#hiddenfilesync)
|
||||||
- WebClip from [obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf)
|
- WebClip from [obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf)
|
||||||
|
- WebRTC peer-to-peer synchronisation without the need any `host` is now possible. (Experimental)
|
||||||
|
- This feature is still in the experimental stage. Please be careful when using it.
|
||||||
|
- Instead of using server, you can use [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) the pseudo client for receiving and sending between devices.
|
||||||
|
|
||||||
|
|
||||||
This plug-in might be useful for researchers, engineers, and developers with a need to keep their notes fully self-hosted for security reasons. Or just anyone who would like the peace of mind of knowing that their notes are fully private.
|
This plug-in might be useful for researchers, engineers, and developers with a need to keep their notes fully self-hosted for security reasons. Or just anyone who would like the peace of mind of knowing that their notes are fully private.
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
EVENT_DATABASE_REBUILT,
|
EVENT_DATABASE_REBUILT,
|
||||||
EVENT_PLUGIN_UNLOADED,
|
EVENT_PLUGIN_UNLOADED,
|
||||||
EVENT_REQUEST_OPEN_P2P,
|
EVENT_REQUEST_OPEN_P2P,
|
||||||
|
EVENT_SETTING_SAVED,
|
||||||
eventHub,
|
eventHub,
|
||||||
} from "../../common/events.ts";
|
} from "../../common/events.ts";
|
||||||
import {
|
import {
|
||||||
@@ -31,8 +32,9 @@ import {
|
|||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
||||||
import { Logger } from "octagonal-wheels/common/logger";
|
import { Logger } from "octagonal-wheels/common/logger";
|
||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
|
import type { CommandShim } from "./P2PReplicator/P2PReplicatorPaneCommon.ts";
|
||||||
|
|
||||||
export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
|
export class P2PReplicator extends LiveSyncCommands implements IObsidianModule, CommandShim {
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
if (settings.remoteType == REMOTE_P2P) {
|
if (settings.remoteType == REMOTE_P2P) {
|
||||||
@@ -66,6 +68,9 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
|
|||||||
eventHub.onEvent(EVENT_PLUGIN_UNLOADED, () => {
|
eventHub.onEvent(EVENT_PLUGIN_UNLOADED, () => {
|
||||||
void this.close();
|
void this.close();
|
||||||
});
|
});
|
||||||
|
eventHub.onEvent(EVENT_SETTING_SAVED, async () => {
|
||||||
|
await this.initialiseP2PReplicator();
|
||||||
|
});
|
||||||
// throw new Error("Method not implemented.");
|
// throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
async $everyOnInitializeDatabase(): Promise<boolean> {
|
async $everyOnInitializeDatabase(): Promise<boolean> {
|
||||||
@@ -154,9 +159,14 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
|
|||||||
|
|
||||||
if (!this._replicatorInstance) {
|
if (!this._replicatorInstance) {
|
||||||
await this.initialiseP2PReplicator();
|
await this.initialiseP2PReplicator();
|
||||||
|
if (!this.settings.P2P_AutoStart) {
|
||||||
|
// While auto start is enabled, we don't need to open the connection (Literally, it's already opened automatically)
|
||||||
|
await this._replicatorInstance!.open();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
await this._replicatorInstance?.open();
|
await this._replicatorInstance?.open();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
async close() {
|
async close() {
|
||||||
await this._replicatorInstance?.close();
|
await this._replicatorInstance?.close();
|
||||||
this._replicatorInstance = undefined;
|
this._replicatorInstance = undefined;
|
||||||
@@ -208,7 +218,9 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
|
|||||||
simpleStore: getPlugin().$$getSimpleStore("p2p-sync"),
|
simpleStore: getPlugin().$$getSimpleStore("p2p-sync"),
|
||||||
};
|
};
|
||||||
this._replicatorInstance = new TrysteroReplicator(env);
|
this._replicatorInstance = new TrysteroReplicator(env);
|
||||||
// p2p_replicator.set(this.p2pReplicator);
|
if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) {
|
||||||
|
await this.open();
|
||||||
|
}
|
||||||
return this._replicatorInstance;
|
return this._replicatorInstance;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._log(
|
this._log(
|
||||||
@@ -219,4 +231,10 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
enableBroadcastCastings() {
|
||||||
|
return this?._replicatorInstance?.enableBroadcastChanges();
|
||||||
|
}
|
||||||
|
disableBroadcastCastings() {
|
||||||
|
return this?._replicatorInstance?.disableBroadcastChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, setContext } from "svelte";
|
import { onMount, setContext } from "svelte";
|
||||||
|
import { AutoAccepting, DEFAULT_SETTINGS, type P2PSyncSetting } from "../../../lib/src/common/types";
|
||||||
import {
|
import {
|
||||||
AutoAccepting,
|
AcceptedStatus,
|
||||||
DEFAULT_SETTINGS,
|
ConnectionStatus,
|
||||||
type ObsidianLiveSyncSettings,
|
type CommandShim,
|
||||||
type P2PSyncSetting,
|
type PeerStatus,
|
||||||
} from "../../../lib/src/common/types";
|
type PluginShim,
|
||||||
import { type P2PReplicator } from "../CmdP2PSync";
|
} from "./P2PReplicatorPaneCommon";
|
||||||
import { AcceptedStatus, ConnectionStatus, type PeerStatus } from "./P2PReplicatorPaneView";
|
|
||||||
import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte";
|
import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte";
|
||||||
import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events";
|
import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main";
|
|
||||||
import {
|
import {
|
||||||
type PeerInfo,
|
type PeerInfo,
|
||||||
type P2PServerInfo,
|
type P2PServerInfo,
|
||||||
@@ -22,11 +21,12 @@
|
|||||||
import { $msg as _msg } from "../../../lib/src/common/i18n";
|
import { $msg as _msg } from "../../../lib/src/common/i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: PluginShim;
|
||||||
|
cmdSync: CommandShim;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { plugin }: Props = $props();
|
let { plugin, cmdSync }: Props = $props();
|
||||||
const cmdSync = plugin.getAddOn<P2PReplicator>("P2PReplicator")!;
|
// const cmdSync = plugin.getAddOn<P2PReplicator>("P2PReplicator")!;
|
||||||
setContext("getReplicator", () => cmdSync);
|
setContext("getReplicator", () => cmdSync);
|
||||||
|
|
||||||
const initialSettings = { ...plugin.settings };
|
const initialSettings = { ...plugin.settings };
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
let settings = $state<P2PSyncSetting>(initialSettings);
|
let settings = $state<P2PSyncSetting>(initialSettings);
|
||||||
// const vaultName = plugin.$$getVaultName();
|
// const vaultName = plugin.$$getVaultName();
|
||||||
// const dbKey = `${vaultName}-p2p-device-name`;
|
// const dbKey = `${vaultName}-p2p-device-name`;
|
||||||
|
|
||||||
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.$$getVaultName();
|
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.$$getVaultName();
|
||||||
let deviceName = $state<string>(initialDeviceName);
|
let deviceName = $state<string>(initialDeviceName);
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@
|
|||||||
|
|
||||||
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
||||||
let replicatorInfo = $state<P2PReplicatorStatus | undefined>(undefined);
|
let replicatorInfo = $state<P2PReplicatorStatus | undefined>(undefined);
|
||||||
const applyLoadSettings = (d: ObsidianLiveSyncSettings, force: boolean) => {
|
const applyLoadSettings = (d: P2PSyncSetting, force: boolean) => {
|
||||||
const { P2P_relays, P2P_roomID, P2P_passphrase, P2P_AppID, P2P_AutoAccepting } = d;
|
const { P2P_relays, P2P_roomID, P2P_passphrase, P2P_AppID, P2P_AutoAccepting } = d;
|
||||||
if (force || !isP2PEnabledModified) eP2PEnabled = d.P2P_Enabled;
|
if (force || !isP2PEnabledModified) eP2PEnabled = d.P2P_Enabled;
|
||||||
if (force || !isRelayModified) eRelay = P2P_relays;
|
if (force || !isRelayModified) eRelay = P2P_relays;
|
||||||
@@ -221,10 +222,10 @@
|
|||||||
await cmdSync.close();
|
await cmdSync.close();
|
||||||
}
|
}
|
||||||
function startBroadcasting() {
|
function startBroadcasting() {
|
||||||
cmdSync._replicatorInstance?.enableBroadcastChanges();
|
void cmdSync.enableBroadcastCastings();
|
||||||
}
|
}
|
||||||
function stopBroadcasting() {
|
function stopBroadcasting() {
|
||||||
cmdSync._replicatorInstance?.disableBroadcastChanges();
|
void cmdSync.disableBroadcastCastings();
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialDialogStatusKey = `p2p-dialog-status`;
|
const initialDialogStatusKey = `p2p-dialog-status`;
|
||||||
@@ -283,6 +284,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="wss://exp-relay.vrtmrz.net, wss://xxxxx"
|
placeholder="wss://exp-relay.vrtmrz.net, wss://xxxxx"
|
||||||
bind:value={eRelay}
|
bind:value={eRelay}
|
||||||
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<button onclick={() => useDefaultRelay()}> Use vrtmrz's relay </button>
|
<button onclick={() => useDefaultRelay()}> Use vrtmrz's relay </button>
|
||||||
</label>
|
</label>
|
||||||
@@ -292,7 +294,7 @@
|
|||||||
<th> Room ID </th>
|
<th> Room ID </th>
|
||||||
<td>
|
<td>
|
||||||
<label class={{ "is-dirty": isRoomIdModified }}>
|
<label class={{ "is-dirty": isRoomIdModified }}>
|
||||||
<input type="text" placeholder="anything-you-like" bind:value={eRoomId} />
|
<input type="text" placeholder="anything-you-like" bind:value={eRoomId} autocomplete="off"/>
|
||||||
<button onclick={() => chooseRandom()}> Use Random Number </button>
|
<button onclick={() => chooseRandom()}> Use Random Number </button>
|
||||||
</label>
|
</label>
|
||||||
<span>
|
<span>
|
||||||
@@ -318,7 +320,8 @@
|
|||||||
<th> This device name </th>
|
<th> This device name </th>
|
||||||
<td>
|
<td>
|
||||||
<label class={{ "is-dirty": isDeviceNameModified }}>
|
<label class={{ "is-dirty": isDeviceNameModified }}>
|
||||||
<input type="text" placeholder="iphone-16" bind:value={eDeviceName} />
|
<input type="text" placeholder="iphone-16" bind:value={eDeviceName}
|
||||||
|
autocomplete="off" />
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import type { P2PSyncSetting } from "../../../lib/src/common/types";
|
||||||
|
|
||||||
|
export const EVENT_P2P_PEER_SHOW_EXTRA_MENU = "p2p-peer-show-extra-menu";
|
||||||
|
|
||||||
|
export enum AcceptedStatus {
|
||||||
|
UNKNOWN = "Unknown",
|
||||||
|
ACCEPTED = "Accepted",
|
||||||
|
DENIED = "Denied",
|
||||||
|
ACCEPTED_IN_SESSION = "Accepted in session",
|
||||||
|
DENIED_IN_SESSION = "Denied in session",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PeerExtraMenuEvent = {
|
||||||
|
peer: PeerStatus;
|
||||||
|
event: MouseEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum ConnectionStatus {
|
||||||
|
CONNECTED = "Connected",
|
||||||
|
CONNECTED_LIVE = "Connected(live)",
|
||||||
|
DISCONNECTED = "Disconnected",
|
||||||
|
}
|
||||||
|
export type PeerStatus = {
|
||||||
|
name: string;
|
||||||
|
peerId: string;
|
||||||
|
syncOnConnect: boolean;
|
||||||
|
watchOnConnect: boolean;
|
||||||
|
syncOnReplicationCommand: boolean;
|
||||||
|
accepted: AcceptedStatus;
|
||||||
|
status: ConnectionStatus;
|
||||||
|
isFetching: boolean;
|
||||||
|
isSending: boolean;
|
||||||
|
isWatching: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface LSEvents {
|
||||||
|
[EVENT_P2P_PEER_SHOW_EXTRA_MENU]: PeerExtraMenuEvent;
|
||||||
|
// [EVENT_P2P_REPLICATOR_PROGRESS]: P2PReplicationReport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginShim {
|
||||||
|
saveSettings: () => Promise<void>;
|
||||||
|
settings: P2PSyncSetting;
|
||||||
|
rebuilder: any;
|
||||||
|
$$scheduleAppReload: () => void;
|
||||||
|
$$getVaultName: () => string;
|
||||||
|
// confirm: any;
|
||||||
|
}
|
||||||
|
export interface CommandShim {
|
||||||
|
getConfig(key: string): string | null;
|
||||||
|
setConfig(key: string, value: string): void;
|
||||||
|
open(): Promise<void>;
|
||||||
|
close(): Promise<void>;
|
||||||
|
enableBroadcastCastings(): void; // cmdSync._replicatorInstance?.enableBroadcastChanges();
|
||||||
|
disableBroadcastCastings(): void; ///cmdSync._replicatorInstance?.disableBroadcastChanges();
|
||||||
|
}
|
||||||
@@ -1,35 +1,33 @@
|
|||||||
import { WorkspaceLeaf } from "obsidian";
|
import { Menu, WorkspaceLeaf } from "obsidian";
|
||||||
import ReplicatorPaneComponent from "./P2PReplicatorPane.svelte";
|
import ReplicatorPaneComponent from "./P2PReplicatorPane.svelte";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
import { mount } from "svelte";
|
import { mount } from "svelte";
|
||||||
import { SvelteItemView } from "../../../common/SvelteItemView.ts";
|
import { SvelteItemView } from "../../../common/SvelteItemView.ts";
|
||||||
|
import { eventHub } from "../../../common/events.ts";
|
||||||
|
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus } from "./P2PReplicatorPaneCommon.ts";
|
||||||
|
import { unique } from "octagonal-wheels/collection";
|
||||||
|
import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "../../../lib/src/common/types.ts";
|
||||||
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
|
import { P2PReplicator } from "../CmdP2PSync.ts";
|
||||||
export const VIEW_TYPE_P2P = "p2p-replicator";
|
export const VIEW_TYPE_P2P = "p2p-replicator";
|
||||||
|
|
||||||
export enum AcceptedStatus {
|
function addToList(item: string, list: string) {
|
||||||
UNKNOWN = "Unknown",
|
return unique(
|
||||||
ACCEPTED = "Accepted",
|
list
|
||||||
DENIED = "Denied",
|
.split(",")
|
||||||
ACCEPTED_IN_SESSION = "Accepted in session",
|
.map((e) => e.trim())
|
||||||
DENIED_IN_SESSION = "Denied in session",
|
.concat(item)
|
||||||
|
.filter((p) => p)
|
||||||
|
).join(",");
|
||||||
}
|
}
|
||||||
|
function removeFromList(item: string, list: string) {
|
||||||
export enum ConnectionStatus {
|
return list
|
||||||
CONNECTED = "Connected",
|
.split(",")
|
||||||
CONNECTED_LIVE = "Connected(live)",
|
.map((e) => e.trim())
|
||||||
DISCONNECTED = "Disconnected",
|
.filter((p) => p !== item)
|
||||||
|
.filter((p) => p)
|
||||||
|
.join(",");
|
||||||
}
|
}
|
||||||
export type PeerStatus = {
|
|
||||||
name: string;
|
|
||||||
peerId: string;
|
|
||||||
syncOnConnect: boolean;
|
|
||||||
watchOnConnect: boolean;
|
|
||||||
syncOnReplicationCommand: boolean;
|
|
||||||
accepted: AcceptedStatus;
|
|
||||||
status: ConnectionStatus;
|
|
||||||
isFetching: boolean;
|
|
||||||
isSending: boolean;
|
|
||||||
isWatching: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class P2PReplicatorPaneView extends SvelteItemView {
|
export class P2PReplicatorPaneView extends SvelteItemView {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
@@ -40,10 +38,130 @@ export class P2PReplicatorPaneView extends SvelteItemView {
|
|||||||
getIcon(): string {
|
getIcon(): string {
|
||||||
return "waypoints";
|
return "waypoints";
|
||||||
}
|
}
|
||||||
|
get replicator() {
|
||||||
|
const r = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
|
||||||
|
if (!r || !r._replicatorInstance) {
|
||||||
|
throw new Error("Replicator not found");
|
||||||
|
}
|
||||||
|
return r._replicatorInstance;
|
||||||
|
}
|
||||||
|
async replicateFrom(peer: PeerStatus) {
|
||||||
|
await this.replicator.replicateFrom(peer.peerId);
|
||||||
|
}
|
||||||
|
async replicateTo(peer: PeerStatus) {
|
||||||
|
await this.replicator.requestSynchroniseToPeer(peer.peerId);
|
||||||
|
}
|
||||||
|
async getRemoteConfig(peer: PeerStatus) {
|
||||||
|
Logger(
|
||||||
|
`Requesting remote config for ${peer.name}. Please input the passphrase on the remote device`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
|
const remoteConfig = await this.replicator.getRemoteConfig(peer.peerId);
|
||||||
|
if (remoteConfig) {
|
||||||
|
Logger(`Remote config for ${peer.name} is retrieved successfully`);
|
||||||
|
const DROP = "Yes, and drop local database";
|
||||||
|
const KEEP = "Yes, but keep local database";
|
||||||
|
const CANCEL = "No, cancel";
|
||||||
|
const yn = await this.plugin.confirm.askSelectStringDialogue(
|
||||||
|
`Do you really want to apply the remote config? This will overwrite your current config immediately and restart.
|
||||||
|
And you can also drop the local database to rebuild from the remote device.`,
|
||||||
|
[DROP, KEEP, CANCEL] as const,
|
||||||
|
{
|
||||||
|
defaultAction: CANCEL,
|
||||||
|
title: "Apply Remote Config ",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (yn === DROP || yn === KEEP) {
|
||||||
|
if (yn === DROP) {
|
||||||
|
if (remoteConfig.remoteType !== REMOTE_P2P) {
|
||||||
|
const yn2 = await this.plugin.confirm.askYesNoDialog(
|
||||||
|
`Do you want to set the remote type to "P2P Sync" to rebuild by "P2P replication"?`,
|
||||||
|
{
|
||||||
|
title: "Rebuild from remote device",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (yn2 === "yes") {
|
||||||
|
remoteConfig.remoteType = REMOTE_P2P;
|
||||||
|
remoteConfig.P2P_RebuildFrom = peer.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.plugin.settings = remoteConfig;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
if (yn === DROP) {
|
||||||
|
await this.plugin.rebuilder.scheduleFetch();
|
||||||
|
} else {
|
||||||
|
await this.plugin.$$scheduleAppReload();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger(`Cannot retrieve remote config for ${peer.peerId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleProp(peer: PeerStatus, prop: "syncOnConnect" | "watchOnConnect" | "syncOnReplicationCommand") {
|
||||||
|
const settingMap = {
|
||||||
|
syncOnConnect: "P2P_AutoSyncPeers",
|
||||||
|
watchOnConnect: "P2P_AutoWatchPeers",
|
||||||
|
syncOnReplicationCommand: "P2P_SyncOnReplication",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const targetSetting = settingMap[prop];
|
||||||
|
if (peer[prop]) {
|
||||||
|
this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
} else {
|
||||||
|
this.plugin.settings[targetSetting] = addToList(peer.name, this.plugin.settings[targetSetting]);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}
|
||||||
|
m?: Menu;
|
||||||
constructor(leaf: WorkspaceLeaf, plugin: ObsidianLiveSyncPlugin) {
|
constructor(leaf: WorkspaceLeaf, plugin: ObsidianLiveSyncPlugin) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
eventHub.onEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, ({ peer, event }) => {
|
||||||
|
if (this.m) {
|
||||||
|
this.m.hide();
|
||||||
|
}
|
||||||
|
this.m = new Menu()
|
||||||
|
.addItem((item) => item.setTitle("📥 Only Fetch").onClick(() => this.replicateFrom(peer)))
|
||||||
|
.addItem((item) => item.setTitle("📤 Only Send").onClick(() => this.replicateTo(peer)))
|
||||||
|
.addSeparator()
|
||||||
|
.addItem((item) => {
|
||||||
|
item.setTitle("🔧 Get Configuration").onClick(async () => {
|
||||||
|
await this.getRemoteConfig(peer);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addSeparator()
|
||||||
|
.addItem((item) => {
|
||||||
|
const mark = peer.syncOnConnect ? "checkmark" : null;
|
||||||
|
item.setTitle("Toggle Sync on connect")
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.toggleProp(peer, "syncOnConnect");
|
||||||
|
})
|
||||||
|
.setIcon(mark);
|
||||||
|
})
|
||||||
|
.addItem((item) => {
|
||||||
|
const mark = peer.watchOnConnect ? "checkmark" : null;
|
||||||
|
item.setTitle("Toggle Watch on connect")
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.toggleProp(peer, "watchOnConnect");
|
||||||
|
})
|
||||||
|
.setIcon(mark);
|
||||||
|
})
|
||||||
|
.addItem((item) => {
|
||||||
|
const mark = peer.syncOnReplicationCommand ? "checkmark" : null;
|
||||||
|
item.setTitle("Toggle Sync on `Replicate now` command")
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.toggleProp(peer, "syncOnReplicationCommand");
|
||||||
|
})
|
||||||
|
.setIcon(mark);
|
||||||
|
});
|
||||||
|
this.m.showAtPosition({ x: event.x, y: event.y });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewType() {
|
getViewType() {
|
||||||
@@ -54,11 +172,22 @@ export class P2PReplicatorPaneView extends SvelteItemView {
|
|||||||
return "Peer-to-Peer Replicator";
|
return "Peer-to-Peer Replicator";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async onClose(): Promise<void> {
|
||||||
|
await super.onClose();
|
||||||
|
if (this.m) {
|
||||||
|
this.m.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
instantiateComponent(target: HTMLElement) {
|
instantiateComponent(target: HTMLElement) {
|
||||||
|
const cmdSync = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
|
||||||
|
if (!cmdSync) {
|
||||||
|
throw new Error("Replicator not found");
|
||||||
|
}
|
||||||
return mount(ReplicatorPaneComponent, {
|
return mount(ReplicatorPaneComponent, {
|
||||||
target: target,
|
target: target,
|
||||||
props: {
|
props: {
|
||||||
plugin: this.plugin,
|
plugin: cmdSync.plugin,
|
||||||
|
cmdSync: cmdSync,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { AcceptedStatus, ConnectionStatus, type PeerStatus } from "./P2PReplicatorPaneView";
|
import { AcceptedStatus, type PeerStatus } from "./P2PReplicatorPaneCommon";
|
||||||
import { Menu, Setting } from "obsidian";
|
|
||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
|
|
||||||
import type { P2PReplicator } from "../CmdP2PSync";
|
import type { P2PReplicator } from "../CmdP2PSync";
|
||||||
import { unique } from "../../../lib/src/common/utils";
|
import { eventHub } from "../../../common/events";
|
||||||
import { REMOTE_P2P } from "src/lib/src/common/types";
|
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "./P2PReplicatorPaneCommon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
peerStatus: PeerStatus;
|
peerStatus: PeerStatus;
|
||||||
@@ -94,154 +92,13 @@
|
|||||||
function stopWatching() {
|
function stopWatching() {
|
||||||
replicator.unwatchPeer(peer.peerId);
|
replicator.unwatchPeer(peer.peerId);
|
||||||
}
|
}
|
||||||
function replicateFrom() {
|
|
||||||
replicator.replicateFrom(peer.peerId);
|
|
||||||
}
|
|
||||||
function replicateTo() {
|
|
||||||
replicator.requestSynchroniseToPeer(peer.peerId);
|
|
||||||
}
|
|
||||||
function sync() {
|
function sync() {
|
||||||
replicator.sync(peer.peerId, false);
|
replicator.sync(peer.peerId, false);
|
||||||
}
|
}
|
||||||
function addToList(item: string, list: string) {
|
|
||||||
return unique(
|
|
||||||
list
|
|
||||||
.split(",")
|
|
||||||
.map((e) => e.trim())
|
|
||||||
.concat(item)
|
|
||||||
.filter((p) => p)
|
|
||||||
).join(",");
|
|
||||||
}
|
|
||||||
function removeFromList(item: string, list: string) {
|
|
||||||
return list
|
|
||||||
.split(",")
|
|
||||||
.map((e) => e.trim())
|
|
||||||
.filter((p) => p !== item)
|
|
||||||
.filter((p) => p)
|
|
||||||
.join(",");
|
|
||||||
}
|
|
||||||
function moreMenu(evt: MouseEvent) {
|
function moreMenu(evt: MouseEvent) {
|
||||||
const m = new Menu()
|
eventHub.emitEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, { peer, event: evt });
|
||||||
.addItem((item) => item.setTitle("📥 Only Fetch").onClick(() => replicateFrom()))
|
|
||||||
.addItem((item) => item.setTitle("📤 Only Send").onClick(() => replicateTo()))
|
|
||||||
.addSeparator()
|
|
||||||
.addItem((item) => {
|
|
||||||
item.setTitle("🔧 Get Configuration").onClick(async () => {
|
|
||||||
Logger(
|
|
||||||
`Requesting remote config for ${peer.name}. Please input the passphrase on the remote device`,
|
|
||||||
LOG_LEVEL_NOTICE
|
|
||||||
);
|
|
||||||
const remoteConfig = await replicator.getRemoteConfig(peer.peerId);
|
|
||||||
if (remoteConfig) {
|
|
||||||
Logger(`Remote config for ${peer.name} is retrieved successfully`);
|
|
||||||
const DROP = "Yes, and drop local database";
|
|
||||||
const KEEP = "Yes, but keep local database";
|
|
||||||
const CANCEL = "No, cancel";
|
|
||||||
const yn = await replicator.confirm.askSelectStringDialogue(
|
|
||||||
`Do you really want to apply the remote config? This will overwrite your current config immediately and restart.
|
|
||||||
And you can also drop the local database to rebuild from the remote device.`,
|
|
||||||
[DROP, KEEP, CANCEL] as const,
|
|
||||||
{
|
|
||||||
defaultAction: CANCEL,
|
|
||||||
title: "Apply Remote Config ",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (yn === DROP || yn === KEEP) {
|
|
||||||
if (yn === DROP) {
|
|
||||||
if (remoteConfig.remoteType !== REMOTE_P2P) {
|
|
||||||
const yn2 = await replicator.confirm.askYesNoDialog(
|
|
||||||
`Do you want to set the remote type to "P2P Sync" to rebuild by "P2P replication"?`,
|
|
||||||
{
|
|
||||||
title: "Rebuild from remote device",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (yn2 === "yes") {
|
|
||||||
remoteConfig.remoteType = REMOTE_P2P;
|
|
||||||
remoteConfig.P2P_RebuildFrom = peer.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmdReplicator.plugin.settings = remoteConfig;
|
|
||||||
await cmdReplicator.plugin.saveSettings();
|
|
||||||
// await cmdReplicator.setConfig("rebuildFrom", peer.name);
|
|
||||||
if (yn === DROP) {
|
|
||||||
await cmdReplicator.plugin.rebuilder.scheduleFetch();
|
|
||||||
} else {
|
|
||||||
await cmdReplicator.plugin.$$scheduleAppReload();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger(`Cannot retrieve remote config for ${peer.peerId}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.addSeparator()
|
|
||||||
.addItem((item) => {
|
|
||||||
const mark = peer.syncOnConnect ? "checkmark" : null;
|
|
||||||
item.setTitle("Toggle Sync on connect")
|
|
||||||
.onClick(async () => {
|
|
||||||
// TODO: Fix to prevent writing to settings directly
|
|
||||||
if (peer.syncOnConnect) {
|
|
||||||
cmdReplicator.settings.P2P_AutoSyncPeers = removeFromList(
|
|
||||||
peer.name,
|
|
||||||
cmdReplicator.settings.P2P_AutoSyncPeers
|
|
||||||
);
|
|
||||||
await cmdReplicator.plugin.saveSettings();
|
|
||||||
} else {
|
|
||||||
cmdReplicator.settings.P2P_AutoSyncPeers = addToList(
|
|
||||||
peer.name,
|
|
||||||
cmdReplicator.settings.P2P_AutoSyncPeers
|
|
||||||
);
|
|
||||||
await cmdReplicator.plugin.saveSettings();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setIcon(mark);
|
|
||||||
})
|
|
||||||
.addItem((item) => {
|
|
||||||
const mark = peer.watchOnConnect ? "checkmark" : null;
|
|
||||||
item.setTitle("Toggle Watch on connect")
|
|
||||||
.onClick(async () => {
|
|
||||||
// TODO: Fix to prevent writing to settings directly
|
|
||||||
if (peer.watchOnConnect) {
|
|
||||||
cmdReplicator.settings.P2P_AutoWatchPeers = removeFromList(
|
|
||||||
peer.name,
|
|
||||||
cmdReplicator.settings.P2P_AutoWatchPeers
|
|
||||||
);
|
|
||||||
await cmdReplicator.plugin.saveSettings();
|
|
||||||
} else {
|
|
||||||
cmdReplicator.settings.P2P_AutoWatchPeers = addToList(
|
|
||||||
peer.name,
|
|
||||||
cmdReplicator.settings.P2P_AutoWatchPeers
|
|
||||||
);
|
|
||||||
await cmdReplicator.plugin.saveSettings();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setIcon(mark);
|
|
||||||
})
|
|
||||||
.addItem((item) => {
|
|
||||||
const mark = peer.syncOnReplicationCommand ? "checkmark" : null;
|
|
||||||
item.setTitle("Toggle Sync on `Replicate now` command")
|
|
||||||
.onClick(async () => {
|
|
||||||
// TODO: Fix to prevent writing to settings directly
|
|
||||||
if (peer.syncOnReplicationCommand) {
|
|
||||||
cmdReplicator.settings.P2P_SyncOnReplication = removeFromList(
|
|
||||||
peer.name,
|
|
||||||
cmdReplicator.settings.P2P_SyncOnReplication
|
|
||||||
);
|
|
||||||
await cmdReplicator.plugin.saveSettings();
|
|
||||||
} else {
|
|
||||||
cmdReplicator.settings.P2P_SyncOnReplication = addToList(
|
|
||||||
peer.name,
|
|
||||||
cmdReplicator.settings.P2P_SyncOnReplication
|
|
||||||
);
|
|
||||||
await cmdReplicator.plugin.saveSettings();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setIcon(mark);
|
|
||||||
});
|
|
||||||
m.showAtPosition({ x: evt.x, y: evt.y });
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: b8b05a2146...9f71ed12ad
@@ -14,7 +14,7 @@ import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurre
|
|||||||
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
|
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/APIBase.ts";
|
import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/base/APIBase.ts";
|
||||||
|
|
||||||
export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
||||||
async $$onLiveSyncReady() {
|
async $$onLiveSyncReady() {
|
||||||
|
|||||||
Reference in New Issue
Block a user