mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-21 21:51:30 +00:00
## 0.24.13
### Fixed #### General Replication - No longer unexpected errors occur when the replication is stopped during for some reason (e.g., network disconnection). #### Peer-to-Peer Synchronisation - Set-up process will not receive data from unexpected sources. - No longer resource leaks while enabling the `broadcasting changes` - Logs are less verbose. - Received data is now correctly dispatched to other devices. - `Timeout` error now more informative. - No longer timeout error occurs for reporting the progress to other devices. - Decision dialogues for the same thing are not shown multiply at the same time anymore. - Disconnection of the peer-to-peer synchronisation is now more robust and less error-prone. #### Webpeer - Now we can toggle Peers' configuration. ### Refactored - Cross-platform compatibility layer has been improved. - Common events are moved to the common library. - Displaying replication status of the peer-to-peer synchronisation is separated from the main-log-logic. - Some file names have been changed to be more consistent.
This commit is contained in:
@@ -1,19 +1,11 @@
|
|||||||
import type { FilePathWithPrefix, ObsidianLiveSyncSettings } from "../lib/src/common/types";
|
|
||||||
import { eventHub } from "../lib/src/hub/hub";
|
import { eventHub } from "../lib/src/hub/hub";
|
||||||
import type ObsidianLiveSyncPlugin from "../main";
|
import type ObsidianLiveSyncPlugin from "../main";
|
||||||
|
|
||||||
export const EVENT_LAYOUT_READY = "layout-ready";
|
|
||||||
export const EVENT_PLUGIN_LOADED = "plugin-loaded";
|
export const EVENT_PLUGIN_LOADED = "plugin-loaded";
|
||||||
export const EVENT_PLUGIN_UNLOADED = "plugin-unloaded";
|
export const EVENT_PLUGIN_UNLOADED = "plugin-unloaded";
|
||||||
export const EVENT_SETTING_SAVED = "setting-saved";
|
|
||||||
export const EVENT_FILE_RENAMED = "file-renamed";
|
|
||||||
export const EVENT_FILE_SAVED = "file-saved";
|
export const EVENT_FILE_SAVED = "file-saved";
|
||||||
export const EVENT_LEAF_ACTIVE_CHANGED = "leaf-active-changed";
|
export const EVENT_LEAF_ACTIVE_CHANGED = "leaf-active-changed";
|
||||||
|
|
||||||
export const EVENT_DATABASE_REBUILT = "database-rebuilt";
|
|
||||||
|
|
||||||
export const EVENT_LOG_ADDED = "log-added";
|
|
||||||
|
|
||||||
export const EVENT_REQUEST_OPEN_SETTINGS = "request-open-settings";
|
export const EVENT_REQUEST_OPEN_SETTINGS = "request-open-settings";
|
||||||
export const EVENT_REQUEST_OPEN_SETTING_WIZARD = "request-open-setting-wizard";
|
export const EVENT_REQUEST_OPEN_SETTING_WIZARD = "request-open-setting-wizard";
|
||||||
export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri";
|
export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri";
|
||||||
@@ -30,28 +22,19 @@ export const EVENT_REQUEST_CLOSE_P2P = "request-close-p2p";
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface LSEvents {
|
interface LSEvents {
|
||||||
[EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined;
|
|
||||||
[EVENT_FILE_SAVED]: undefined;
|
|
||||||
[EVENT_REQUEST_OPEN_SETUP_URI]: undefined;
|
|
||||||
[EVENT_REQUEST_COPY_SETUP_URI]: undefined;
|
|
||||||
[EVENT_REQUEST_RELOAD_SETTING_TAB]: undefined;
|
|
||||||
[EVENT_PLUGIN_UNLOADED]: undefined;
|
|
||||||
[EVENT_SETTING_SAVED]: ObsidianLiveSyncSettings;
|
|
||||||
[EVENT_PLUGIN_LOADED]: ObsidianLiveSyncPlugin;
|
[EVENT_PLUGIN_LOADED]: ObsidianLiveSyncPlugin;
|
||||||
[EVENT_LAYOUT_READY]: undefined;
|
[EVENT_PLUGIN_UNLOADED]: undefined;
|
||||||
"event-file-changed": { file: FilePathWithPrefix; automated: boolean };
|
[EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined;
|
||||||
"document-stub-created": {
|
|
||||||
toc: Set<string>;
|
|
||||||
stub: { [key: string]: { [key: string]: Map<string, Record<string, string>> } };
|
|
||||||
};
|
|
||||||
[EVENT_REQUEST_OPEN_SETTINGS]: undefined;
|
[EVENT_REQUEST_OPEN_SETTINGS]: undefined;
|
||||||
[EVENT_REQUEST_OPEN_SETTING_WIZARD]: undefined;
|
[EVENT_REQUEST_OPEN_SETTING_WIZARD]: undefined;
|
||||||
[EVENT_FILE_RENAMED]: { newPath: FilePathWithPrefix; old: FilePathWithPrefix };
|
[EVENT_REQUEST_RELOAD_SETTING_TAB]: undefined;
|
||||||
[EVENT_LEAF_ACTIVE_CHANGED]: undefined;
|
[EVENT_LEAF_ACTIVE_CHANGED]: undefined;
|
||||||
[EVENT_REQUEST_OPEN_P2P]: undefined;
|
|
||||||
[EVENT_REQUEST_CLOSE_P2P]: undefined;
|
[EVENT_REQUEST_CLOSE_P2P]: undefined;
|
||||||
[EVENT_DATABASE_REBUILT]: undefined;
|
[EVENT_REQUEST_OPEN_P2P]: undefined;
|
||||||
|
[EVENT_REQUEST_OPEN_SETUP_URI]: undefined;
|
||||||
|
[EVENT_REQUEST_COPY_SETUP_URI]: undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export * from "../lib/src/events/coreEvents.ts";
|
||||||
export { eventHub };
|
export { eventHub };
|
||||||
|
|||||||
175
src/features/P2PSync/CmdP2PReplicator.ts
Normal file
175
src/features/P2PSync/CmdP2PReplicator.ts
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import type { IObsidianModule } from "../../modules/AbstractObsidianModule";
|
||||||
|
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts";
|
||||||
|
import {
|
||||||
|
AutoAccepting,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
REMOTE_P2P,
|
||||||
|
type EntryDoc,
|
||||||
|
type P2PSyncSetting,
|
||||||
|
type RemoteDBSettings,
|
||||||
|
} from "../../lib/src/common/types.ts";
|
||||||
|
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||||
|
import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts";
|
||||||
|
import { EVENT_REQUEST_OPEN_P2P, eventHub } from "../../common/events.ts";
|
||||||
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
||||||
|
import { Logger } from "octagonal-wheels/common/logger";
|
||||||
|
import type { CommandShim } from "../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts";
|
||||||
|
import {
|
||||||
|
P2PReplicatorMixIn,
|
||||||
|
removeP2PReplicatorInstance,
|
||||||
|
type P2PReplicatorBase,
|
||||||
|
} from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
||||||
|
import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
|
||||||
|
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
|
||||||
|
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
||||||
|
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
||||||
|
|
||||||
|
class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicatorBase {
|
||||||
|
storeP2PStatusLine = reactiveSource("");
|
||||||
|
|
||||||
|
getSettings(): P2PSyncSetting {
|
||||||
|
return this.plugin.settings;
|
||||||
|
}
|
||||||
|
get settings() {
|
||||||
|
return this.plugin.settings;
|
||||||
|
}
|
||||||
|
getDB() {
|
||||||
|
return this.plugin.localDatabase.localDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
get confirm(): Confirm {
|
||||||
|
return this.plugin.confirm;
|
||||||
|
}
|
||||||
|
_simpleStore!: SimpleStore<any>;
|
||||||
|
|
||||||
|
simpleStore(): SimpleStore<any> {
|
||||||
|
return this._simpleStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(plugin: ObsidianLiveSyncPlugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleReplicatedDocuments(docs: EntryDoc[]): Promise<void> {
|
||||||
|
// console.log("Processing Replicated Docs", docs);
|
||||||
|
return await this.plugin.$$parseReplicationResult(docs as PouchDB.Core.ExistingDocument<EntryDoc>[]);
|
||||||
|
}
|
||||||
|
onunload(): void {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
onload(): void | Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._simpleStore = this.plugin.$$getSimpleStore("p2p-sync");
|
||||||
|
return Promise.resolve(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class P2PReplicator
|
||||||
|
extends P2PReplicatorMixIn(P2PReplicatorCommandBase)
|
||||||
|
implements IObsidianModule, CommandShim
|
||||||
|
{
|
||||||
|
storeP2PStatusLine = reactiveSource("");
|
||||||
|
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
||||||
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
|
if (settings.remoteType == REMOTE_P2P) {
|
||||||
|
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin));
|
||||||
|
}
|
||||||
|
return undefined!;
|
||||||
|
}
|
||||||
|
|
||||||
|
override onunload(): void {
|
||||||
|
removeP2PReplicatorInstance();
|
||||||
|
void this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
override onload(): void | Promise<void> {
|
||||||
|
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => {
|
||||||
|
void this.openPane();
|
||||||
|
});
|
||||||
|
this.p2pLogCollector.p2pReplicationLine.onChanged((line) => {
|
||||||
|
this.storeP2PStatusLine.value = line.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async $everyOnInitializeDatabase(): Promise<boolean> {
|
||||||
|
await this.initialiseP2PReplicator();
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $allSuspendExtraSync() {
|
||||||
|
this.plugin.settings.P2P_Enabled = false;
|
||||||
|
this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE;
|
||||||
|
this.plugin.settings.P2P_AutoBroadcast = false;
|
||||||
|
this.plugin.settings.P2P_AutoStart = false;
|
||||||
|
this.plugin.settings.P2P_AutoSyncPeers = "";
|
||||||
|
this.plugin.settings.P2P_AutoWatchPeers = "";
|
||||||
|
return await Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $everyOnLoadStart() {
|
||||||
|
return await Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
async openPane() {
|
||||||
|
await this.plugin.$$showView(VIEW_TYPE_P2P);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $everyOnloadStart(): Promise<boolean> {
|
||||||
|
this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, this.plugin));
|
||||||
|
this.plugin.addCommand({
|
||||||
|
id: "open-p2p-replicator",
|
||||||
|
name: "P2P Sync : Open P2P Replicator",
|
||||||
|
callback: async () => {
|
||||||
|
await this.openPane();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.plugin.addCommand({
|
||||||
|
id: "p2p-establish-connection",
|
||||||
|
name: "P2P Sync : Connect to the Signalling Server",
|
||||||
|
checkCallback: (isChecking) => {
|
||||||
|
if (isChecking) {
|
||||||
|
return !(this._replicatorInstance?.server?.isServing ?? false);
|
||||||
|
}
|
||||||
|
void this.open();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.plugin.addCommand({
|
||||||
|
id: "p2p-close-connection",
|
||||||
|
name: "P2P Sync : Disconnect from the Signalling Server",
|
||||||
|
checkCallback: (isChecking) => {
|
||||||
|
if (isChecking) {
|
||||||
|
return this._replicatorInstance?.server?.isServing ?? false;
|
||||||
|
}
|
||||||
|
Logger(`Closing P2P Connection`, LOG_LEVEL_NOTICE);
|
||||||
|
void this.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.plugin.addCommand({
|
||||||
|
id: "replicate-now-by-p2p",
|
||||||
|
name: "Replicate now by P2P",
|
||||||
|
checkCallback: (isChecking) => {
|
||||||
|
if (isChecking) {
|
||||||
|
if (this.settings.remoteType == REMOTE_P2P) return false;
|
||||||
|
if (!this._replicatorInstance?.server?.isServing) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void this._replicatorInstance?.replicateFromCommand(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.plugin
|
||||||
|
.addRibbonIcon("waypoints", "P2P Replicator", async () => {
|
||||||
|
await this.openPane();
|
||||||
|
})
|
||||||
|
.addClass("livesync-ribbon-replicate-p2p");
|
||||||
|
|
||||||
|
return await Promise.resolve(true);
|
||||||
|
}
|
||||||
|
$everyAfterResumeProcess(): Promise<boolean> {
|
||||||
|
if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) {
|
||||||
|
setTimeout(() => void this.open(), 100);
|
||||||
|
}
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule";
|
|
||||||
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts";
|
|
||||||
import { TrysteroReplicator } from "../../lib/src/replication/trystero/TrysteroReplicator.ts";
|
|
||||||
import {
|
|
||||||
AutoAccepting,
|
|
||||||
DEFAULT_SETTINGS,
|
|
||||||
LOG_LEVEL_INFO,
|
|
||||||
LOG_LEVEL_NOTICE,
|
|
||||||
LOG_LEVEL_VERBOSE,
|
|
||||||
REMOTE_P2P,
|
|
||||||
type EntryDoc,
|
|
||||||
type RemoteDBSettings,
|
|
||||||
} from "../../lib/src/common/types.ts";
|
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
|
||||||
import {
|
|
||||||
LiveSyncTrysteroReplicator,
|
|
||||||
setReplicatorFunc,
|
|
||||||
} from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts";
|
|
||||||
import {
|
|
||||||
EVENT_DATABASE_REBUILT,
|
|
||||||
EVENT_PLUGIN_UNLOADED,
|
|
||||||
EVENT_REQUEST_OPEN_P2P,
|
|
||||||
EVENT_SETTING_SAVED,
|
|
||||||
eventHub,
|
|
||||||
} from "../../common/events.ts";
|
|
||||||
import {
|
|
||||||
EVENT_ADVERTISEMENT_RECEIVED,
|
|
||||||
EVENT_DEVICE_LEAVED,
|
|
||||||
EVENT_P2P_REQUEST_FORCE_OPEN,
|
|
||||||
EVENT_REQUEST_STATUS,
|
|
||||||
} from "../../lib/src/replication/trystero/TrysteroReplicatorP2PServer.ts";
|
|
||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
|
||||||
import { Logger } from "octagonal-wheels/common/logger";
|
|
||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
|
||||||
import type { CommandShim } from "./P2PReplicator/P2PReplicatorPaneCommon.ts";
|
|
||||||
|
|
||||||
export class P2PReplicator extends LiveSyncCommands implements IObsidianModule, CommandShim {
|
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
|
||||||
if (settings.remoteType == REMOTE_P2P) {
|
|
||||||
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin));
|
|
||||||
}
|
|
||||||
return undefined!;
|
|
||||||
}
|
|
||||||
|
|
||||||
_replicatorInstance?: TrysteroReplicator;
|
|
||||||
onunload(): void {
|
|
||||||
setReplicatorFunc(() => undefined);
|
|
||||||
void this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
onload(): void | Promise<void> {
|
|
||||||
setReplicatorFunc(() => this._replicatorInstance);
|
|
||||||
eventHub.onEvent(EVENT_ADVERTISEMENT_RECEIVED, (peerId) => this._replicatorInstance?.onNewPeer(peerId));
|
|
||||||
eventHub.onEvent(EVENT_DEVICE_LEAVED, (info) => this._replicatorInstance?.onPeerLeaved(info));
|
|
||||||
eventHub.onEvent(EVENT_REQUEST_STATUS, () => {
|
|
||||||
this._replicatorInstance?.requestStatus();
|
|
||||||
});
|
|
||||||
eventHub.onEvent(EVENT_P2P_REQUEST_FORCE_OPEN, () => {
|
|
||||||
void this.open();
|
|
||||||
});
|
|
||||||
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => {
|
|
||||||
void this.openPane();
|
|
||||||
});
|
|
||||||
eventHub.onEvent(EVENT_DATABASE_REBUILT, async () => {
|
|
||||||
await this.initialiseP2PReplicator();
|
|
||||||
});
|
|
||||||
eventHub.onEvent(EVENT_PLUGIN_UNLOADED, () => {
|
|
||||||
void this.close();
|
|
||||||
});
|
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, async () => {
|
|
||||||
await this.initialiseP2PReplicator();
|
|
||||||
});
|
|
||||||
// throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
async $everyOnInitializeDatabase(): Promise<boolean> {
|
|
||||||
await this.initialiseP2PReplicator();
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async $allSuspendExtraSync() {
|
|
||||||
this.plugin.settings.P2P_Enabled = false;
|
|
||||||
this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE;
|
|
||||||
this.plugin.settings.P2P_AutoBroadcast = false;
|
|
||||||
this.plugin.settings.P2P_AutoStart = false;
|
|
||||||
this.plugin.settings.P2P_AutoSyncPeers = "";
|
|
||||||
this.plugin.settings.P2P_AutoWatchPeers = "";
|
|
||||||
return await Promise.resolve(true);
|
|
||||||
}
|
|
||||||
async $everyOnLoadStart() {
|
|
||||||
return await Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
async openPane() {
|
|
||||||
await this.plugin.$$showView(VIEW_TYPE_P2P);
|
|
||||||
}
|
|
||||||
|
|
||||||
async $everyOnloadStart(): Promise<boolean> {
|
|
||||||
this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, this.plugin));
|
|
||||||
this.plugin.addCommand({
|
|
||||||
id: "open-p2p-replicator",
|
|
||||||
name: "P2P Sync : Open P2P Replicator",
|
|
||||||
callback: async () => {
|
|
||||||
await this.openPane();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.plugin.addCommand({
|
|
||||||
id: "p2p-establish-connection",
|
|
||||||
name: "P2P Sync : Connect to the Signalling Server",
|
|
||||||
checkCallback: (isChecking) => {
|
|
||||||
if (isChecking) {
|
|
||||||
return !(this._replicatorInstance?.server?.isServing ?? false);
|
|
||||||
}
|
|
||||||
void this.open();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.plugin.addCommand({
|
|
||||||
id: "p2p-close-connection",
|
|
||||||
name: "P2P Sync : Disconnect from the Signalling Server",
|
|
||||||
checkCallback: (isChecking) => {
|
|
||||||
if (isChecking) {
|
|
||||||
return this._replicatorInstance?.server?.isServing ?? false;
|
|
||||||
}
|
|
||||||
Logger(`Closing P2P Connection`, LOG_LEVEL_NOTICE);
|
|
||||||
void this.close();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.plugin.addCommand({
|
|
||||||
id: "replicate-now-by-p2p",
|
|
||||||
name: "Replicate now by P2P",
|
|
||||||
checkCallback: (isChecking) => {
|
|
||||||
if (isChecking) {
|
|
||||||
if (this.settings.remoteType == REMOTE_P2P) return false;
|
|
||||||
if (!this._replicatorInstance?.server?.isServing) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
void this._replicatorInstance?.replicateFromCommand(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.plugin
|
|
||||||
.addRibbonIcon("waypoints", "P2P Replicator", async () => {
|
|
||||||
await this.openPane();
|
|
||||||
})
|
|
||||||
.addClass("livesync-ribbon-replicate-p2p");
|
|
||||||
|
|
||||||
return await Promise.resolve(true);
|
|
||||||
}
|
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
|
||||||
if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) {
|
|
||||||
setTimeout(() => void this.open(), 100);
|
|
||||||
}
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
async open() {
|
|
||||||
if (!this.settings.P2P_Enabled) {
|
|
||||||
this._notice($msg("P2P.NotEnabled"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._replicatorInstance) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async close() {
|
|
||||||
await this._replicatorInstance?.close();
|
|
||||||
this._replicatorInstance = undefined;
|
|
||||||
}
|
|
||||||
getConfig(key: string) {
|
|
||||||
const vaultName = this.plugin.$$getVaultName();
|
|
||||||
const dbKey = `${vaultName}-${key}`;
|
|
||||||
return localStorage.getItem(dbKey);
|
|
||||||
}
|
|
||||||
setConfig(key: string, value: string) {
|
|
||||||
const vaultName = this.plugin.$$getVaultName();
|
|
||||||
const dbKey = `${vaultName}-${key}`;
|
|
||||||
localStorage.setItem(dbKey, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialiseP2PReplicator(): Promise<TrysteroReplicator> {
|
|
||||||
const getPlugin = () => this.plugin;
|
|
||||||
try {
|
|
||||||
// const plugin = this.plugin;
|
|
||||||
if (this._replicatorInstance) {
|
|
||||||
await this._replicatorInstance.close();
|
|
||||||
this._replicatorInstance = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.settings.P2P_AppID) {
|
|
||||||
this.settings.P2P_AppID = DEFAULT_SETTINGS.P2P_AppID;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialDeviceName = this.getConfig("p2p_device_name") || this.plugin.$$getDeviceAndVaultName();
|
|
||||||
const env = {
|
|
||||||
get db() {
|
|
||||||
return getPlugin().localDatabase.localDatabase;
|
|
||||||
},
|
|
||||||
get confirm() {
|
|
||||||
return getPlugin().confirm;
|
|
||||||
},
|
|
||||||
get deviceName() {
|
|
||||||
return initialDeviceName;
|
|
||||||
},
|
|
||||||
platform: "wip",
|
|
||||||
get settings() {
|
|
||||||
return getPlugin().settings;
|
|
||||||
},
|
|
||||||
async processReplicatedDocs(docs: EntryDoc[]): Promise<void> {
|
|
||||||
return await getPlugin().$$parseReplicationResult(
|
|
||||||
docs as PouchDB.Core.ExistingDocument<EntryDoc>[]
|
|
||||||
);
|
|
||||||
},
|
|
||||||
simpleStore: getPlugin().$$getSimpleStore("p2p-sync"),
|
|
||||||
};
|
|
||||||
this._replicatorInstance = new TrysteroReplicator(env);
|
|
||||||
if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) {
|
|
||||||
await this.open();
|
|
||||||
}
|
|
||||||
return this._replicatorInstance;
|
|
||||||
} catch (e) {
|
|
||||||
this._log(
|
|
||||||
e instanceof Error ? e.message : "Something occurred on Initialising P2P Replicator",
|
|
||||||
LOG_LEVEL_INFO
|
|
||||||
);
|
|
||||||
this._log(e, LOG_LEVEL_VERBOSE);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enableBroadcastCastings() {
|
|
||||||
return this?._replicatorInstance?.enableBroadcastChanges();
|
|
||||||
}
|
|
||||||
disableBroadcastCastings() {
|
|
||||||
return this?._replicatorInstance?.disableBroadcastChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
type CommandShim,
|
type CommandShim,
|
||||||
type PeerStatus,
|
type PeerStatus,
|
||||||
type PluginShim,
|
type PluginShim,
|
||||||
} from "./P2PReplicatorPaneCommon";
|
} from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon";
|
||||||
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 {
|
import {
|
||||||
@@ -294,7 +294,12 @@
|
|||||||
<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} autocomplete="off"/>
|
<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>
|
||||||
@@ -320,8 +325,7 @@
|
|||||||
<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" />
|
||||||
autocomplete="off" />
|
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
@@ -4,11 +4,15 @@ 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 { eventHub } from "../../../common/events.ts";
|
||||||
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus } from "./P2PReplicatorPaneCommon.ts";
|
|
||||||
import { unique } from "octagonal-wheels/collection";
|
import { unique } from "octagonal-wheels/collection";
|
||||||
import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "../../../lib/src/common/types.ts";
|
import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "../../../lib/src/common/types.ts";
|
||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
import { P2PReplicator } from "../CmdP2PSync.ts";
|
import { P2PReplicator } from "../CmdP2PReplicator.ts";
|
||||||
|
import {
|
||||||
|
EVENT_P2P_PEER_SHOW_EXTRA_MENU,
|
||||||
|
type PeerStatus,
|
||||||
|
} from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts";
|
||||||
export const VIEW_TYPE_P2P = "p2p-replicator";
|
export const VIEW_TYPE_P2P = "p2p-replicator";
|
||||||
|
|
||||||
function addToList(item: string, list: string) {
|
function addToList(item: string, list: string) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { AcceptedStatus, type PeerStatus } from "./P2PReplicatorPaneCommon";
|
import { AcceptedStatus, type PeerStatus } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon";
|
||||||
import type { P2PReplicator } from "../CmdP2PSync";
|
import type { P2PReplicator } from "../CmdP2PReplicator";
|
||||||
import { eventHub } from "../../../common/events";
|
import { eventHub } from "../../../common/events";
|
||||||
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "./P2PReplicatorPaneCommon";
|
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
peerStatus: PeerStatus;
|
peerStatus: PeerStatus;
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: 9f71ed12ad...7c3d7547e2
@@ -82,7 +82,7 @@ import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts";
|
|||||||
import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts";
|
import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts";
|
||||||
import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts";
|
import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts";
|
||||||
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
||||||
import { P2PReplicator } from "./features/P2PSync/CmdP2PSync.ts";
|
import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts";
|
||||||
|
|
||||||
function throwShouldBeOverridden(): never {
|
function throwShouldBeOverridden(): never {
|
||||||
throw new Error("This function should be overridden by the module.");
|
throw new Error("This function should be overridden by the module.");
|
||||||
|
|||||||
@@ -27,14 +27,7 @@ import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
|||||||
import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
|
import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
|
||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
import type { P2PReplicationProgress } from "../../lib/src/replication/trystero/TrysteroReplicator.ts";
|
import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
||||||
import {
|
|
||||||
EVENT_ADVERTISEMENT_RECEIVED,
|
|
||||||
EVENT_DEVICE_LEAVED,
|
|
||||||
EVENT_P2P_CONNECTED,
|
|
||||||
EVENT_P2P_DISCONNECTED,
|
|
||||||
EVENT_P2P_REPLICATOR_PROGRESS,
|
|
||||||
} from "src/lib/src/replication/trystero/TrysteroReplicatorP2PServer.ts";
|
|
||||||
|
|
||||||
// This module cannot be a core module because it depends on the Obsidian UI.
|
// This module cannot be a core module because it depends on the Obsidian UI.
|
||||||
|
|
||||||
@@ -71,6 +64,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
statusBarLabels!: ReactiveValue<{ message: string; status: string }>;
|
statusBarLabels!: ReactiveValue<{ message: string; status: string }>;
|
||||||
statusLog = reactiveSource("");
|
statusLog = reactiveSource("");
|
||||||
notifies: { [key: string]: { notice: Notice; count: number } } = {};
|
notifies: { [key: string]: { notice: Notice; count: number } } = {};
|
||||||
|
p2pLogCollector = new P2PLogCollector();
|
||||||
|
|
||||||
observeForLogs() {
|
observeForLogs() {
|
||||||
const padSpaces = `\u{2007}`.repeat(10);
|
const padSpaces = `\u{2007}`.repeat(10);
|
||||||
@@ -176,7 +170,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
const queued = queueCountLabel();
|
const queued = queueCountLabel();
|
||||||
const waiting = waitingLabel();
|
const waiting = waitingLabel();
|
||||||
const networkActivity = requestingStatLabel();
|
const networkActivity = requestingStatLabel();
|
||||||
const p2p = this.p2pReplicationLine.value;
|
const p2p = this.p2pLogCollector.p2pReplicationLine.value;
|
||||||
return {
|
return {
|
||||||
message: `${networkActivity}Sync: ${w} ↑ ${sent}${pushLast} ↓ ${arrived}${pullLast}${waiting}${queued}${p2p == "" ? "" : "\n" + p2p}`,
|
message: `${networkActivity}Sync: ${w} ↑ ${sent}${pushLast} ↓ ${arrived}${pullLast}${waiting}${queued}${p2p == "" ? "" : "\n" + p2p}`,
|
||||||
};
|
};
|
||||||
@@ -203,90 +197,10 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
statusBarLabels.onChanged((label) => applyToDisplay(label.value));
|
statusBarLabels.onChanged((label) => applyToDisplay(label.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
p2pReplicationResult = new Map<string, P2PReplicationProgress>();
|
|
||||||
updateP2PReplicationLine() {
|
|
||||||
const p2pReplicationResultX = [...this.p2pReplicationResult.values()].sort((a, b) =>
|
|
||||||
a.peerId.localeCompare(b.peerId)
|
|
||||||
);
|
|
||||||
const renderProgress = (current: number, max: number) => {
|
|
||||||
if (current == max) return `${current}`;
|
|
||||||
return `${current} (${max})`;
|
|
||||||
};
|
|
||||||
const line = p2pReplicationResultX
|
|
||||||
.map(
|
|
||||||
(e) =>
|
|
||||||
`${e.fetching.isActive || e.sending.isActive ? "⚡" : "💤"} ${e.peerName} ↑ ${renderProgress(e.sending.current, e.sending.max)} ↓ ${renderProgress(e.fetching.current, e.fetching.max)} `
|
|
||||||
)
|
|
||||||
.join("\n");
|
|
||||||
this.p2pReplicationLine.value = line;
|
|
||||||
}
|
|
||||||
// p2pReplicationResultX = reactiveSource([] as P2PReplicationProgress[]);
|
|
||||||
p2pReplicationLine = reactiveSource("");
|
|
||||||
|
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange());
|
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange());
|
||||||
eventHub.onceEvent(EVENT_LAYOUT_READY, () => this.onActiveLeafChange());
|
eventHub.onceEvent(EVENT_LAYOUT_READY, () => this.onActiveLeafChange());
|
||||||
eventHub.onEvent(EVENT_ADVERTISEMENT_RECEIVED, (data) => {
|
|
||||||
this.p2pReplicationResult.set(data.peerId, {
|
|
||||||
peerId: data.peerId,
|
|
||||||
peerName: data.name,
|
|
||||||
fetching: {
|
|
||||||
current: 0,
|
|
||||||
max: 0,
|
|
||||||
isActive: false,
|
|
||||||
},
|
|
||||||
sending: {
|
|
||||||
current: 0,
|
|
||||||
max: 0,
|
|
||||||
isActive: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.updateP2PReplicationLine();
|
|
||||||
});
|
|
||||||
eventHub.onEvent(EVENT_P2P_CONNECTED, () => {
|
|
||||||
this.p2pReplicationResult.clear();
|
|
||||||
this.updateP2PReplicationLine();
|
|
||||||
});
|
|
||||||
eventHub.onEvent(EVENT_P2P_DISCONNECTED, () => {
|
|
||||||
this.p2pReplicationResult.clear();
|
|
||||||
this.updateP2PReplicationLine();
|
|
||||||
});
|
|
||||||
eventHub.onEvent(EVENT_DEVICE_LEAVED, (peerId) => {
|
|
||||||
this.p2pReplicationResult.delete(peerId);
|
|
||||||
this.updateP2PReplicationLine();
|
|
||||||
});
|
|
||||||
eventHub.onEvent(EVENT_P2P_REPLICATOR_PROGRESS, (data) => {
|
|
||||||
const prev = this.p2pReplicationResult.get(data.peerId) || {
|
|
||||||
peerId: data.peerId,
|
|
||||||
peerName: data.peerName,
|
|
||||||
fetching: {
|
|
||||||
current: 0,
|
|
||||||
max: 0,
|
|
||||||
isActive: false,
|
|
||||||
},
|
|
||||||
sending: {
|
|
||||||
current: 0,
|
|
||||||
max: 0,
|
|
||||||
isActive: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if ("fetching" in data) {
|
|
||||||
if (data.fetching.isActive) {
|
|
||||||
prev.fetching = data.fetching;
|
|
||||||
} else {
|
|
||||||
prev.fetching.isActive = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ("sending" in data) {
|
|
||||||
if (data.sending.isActive) {
|
|
||||||
prev.sending = data.sending;
|
|
||||||
} else {
|
|
||||||
prev.sending.isActive = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.p2pReplicationResult.set(data.peerId, prev);
|
|
||||||
this.updateP2PReplicationLine();
|
|
||||||
});
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
adjustStatusDivPosition() {
|
adjustStatusDivPosition() {
|
||||||
|
|||||||
Reference in New Issue
Block a user