mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-03-19 08:15:17 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed76125f3d | ||
|
|
70f4e23474 | ||
|
|
f6d5b78cc8 | ||
|
|
405624b51b | ||
|
|
90c0ff22b9 |
@@ -1,11 +0,0 @@
|
||||
node_modules
|
||||
build
|
||||
.eslintrc.js.bak
|
||||
src/lib/src/patches/pouchdb-utils
|
||||
esbuild.config.mjs
|
||||
rollup.config.js
|
||||
src/lib/test
|
||||
src/lib/src/cli
|
||||
main.js
|
||||
src/lib/apps/webpeer/dist
|
||||
src/lib/apps/webpeer/svelte.config.js
|
||||
31
.eslintrc
31
.eslintrc
@@ -1,13 +1,34 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended"],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"eslint-plugin-svelte",
|
||||
"eslint-plugin-import"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"project": ["tsconfig.json"]
|
||||
"project": [
|
||||
"tsconfig.json"
|
||||
]
|
||||
},
|
||||
"ignorePatterns": [],
|
||||
"ignorePatterns": [
|
||||
"**/node_modules/*",
|
||||
"**/jest.config.js",
|
||||
"src/lib/coverage",
|
||||
"src/lib/browsertest",
|
||||
"**/test.ts",
|
||||
"**/tests.ts",
|
||||
"**/**test.ts",
|
||||
"**/**.test.ts",
|
||||
"esbuild.*.mjs",
|
||||
"terser.*.mjs"
|
||||
],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
@@ -34,4 +55,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import sveltePlugin from "esbuild-svelte";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
import { sveltePreprocess } from "svelte-preprocess";
|
||||
import fs from "node:fs";
|
||||
// import terser from "terser";
|
||||
import { minify } from "terser";
|
||||
|
||||
100
eslint.config.mjs
Normal file
100
eslint.config.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||
import svelte from "eslint-plugin-svelte";
|
||||
import _import from "eslint-plugin-import";
|
||||
import { fixupPluginRules } from "@eslint/compat";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import js from "@eslint/js";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
"**/node_modules/*",
|
||||
"**/jest.config.js",
|
||||
"src/lib/coverage",
|
||||
"src/lib/browsertest",
|
||||
"**/test.ts",
|
||||
"**/tests.ts",
|
||||
"**/**test.ts",
|
||||
"**/**.test.ts",
|
||||
"**/esbuild.*.mjs",
|
||||
"**/terser.*.mjs",
|
||||
"**/node_modules",
|
||||
"**/build",
|
||||
"**/.eslintrc.js.bak",
|
||||
"src/lib/src/patches/pouchdb-utils",
|
||||
"**/esbuild.config.mjs",
|
||||
"**/rollup.config.js",
|
||||
"modules/octagonal-wheels/rollup.config.js",
|
||||
"modules/octagonal-wheels/dist/**/*",
|
||||
"src/lib/test",
|
||||
"src/lib/src/cli",
|
||||
"**/main.js",
|
||||
"src/lib/apps/webpeer/dist",
|
||||
"src/lib/apps/webpeer/svelte.config.js",
|
||||
],
|
||||
},
|
||||
...compat.extends(
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
),
|
||||
{
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
svelte,
|
||||
import: fixupPluginRules(_import),
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
ecmaVersion: 5,
|
||||
sourceType: "module",
|
||||
|
||||
parserOptions: {
|
||||
project: ["tsconfig.json"],
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
args: "none",
|
||||
},
|
||||
],
|
||||
|
||||
"no-unused-labels": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"require-await": "error",
|
||||
"@typescript-eslint/require-await": "warn",
|
||||
"@typescript-eslint/no-misused-promises": "warn",
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"no-async-promise-executor": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
|
||||
"no-constant-condition": [
|
||||
"error",
|
||||
{
|
||||
checkLoops: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.24.12",
|
||||
"version": "0.24.14",
|
||||
"minAppVersion": "0.9.12",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
5091
package-lock.json
generated
5091
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.24.12",
|
||||
"version": "0.24.14",
|
||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
@@ -22,6 +22,9 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@chialab/esbuild-plugin-worker": "^0.18.1",
|
||||
"@eslint/compat": "^1.2.6",
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.20.0",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
"@types/node": "^22.5.4",
|
||||
@@ -33,17 +36,17 @@
|
||||
"@types/pouchdb-mapreduce": "^6.1.10",
|
||||
"@types/pouchdb-replication": "^6.4.7",
|
||||
"@types/transform-pouch": "^1.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.23.0",
|
||||
"@typescript-eslint/parser": "^8.23.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
||||
"@typescript-eslint/parser": "^8.24.1",
|
||||
"builtin-modules": "^4.0.0",
|
||||
"esbuild": "0.24.2",
|
||||
"esbuild-svelte": "^0.9.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint": "^9.20.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"events": "^3.3.0",
|
||||
"obsidian": "^1.7.2",
|
||||
"postcss": "^8.5.1",
|
||||
"postcss": "^8.5.2",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
"pouchdb-adapter-http": "^9.0.0",
|
||||
"pouchdb-adapter-idb": "^9.0.0",
|
||||
@@ -55,10 +58,10 @@
|
||||
"pouchdb-merge": "^9.0.0",
|
||||
"pouchdb-replication": "^9.0.0",
|
||||
"pouchdb-utils": "^9.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"svelte": "^5.19.7",
|
||||
"prettier": "^3.5.1",
|
||||
"svelte": "^5.20.1",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"terser": "^5.37.0",
|
||||
"terser": "^5.39.0",
|
||||
"transform-pouch": "^2.0.0",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.19.2",
|
||||
@@ -76,7 +79,7 @@
|
||||
"minimatch": "^10.0.1",
|
||||
"octagonal-wheels": "^0.1.23",
|
||||
"svelte-check": "^4.1.4",
|
||||
"trystero": "^0.20.0",
|
||||
"trystero": "^0.20.1",
|
||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import type { FilePathWithPrefix, ObsidianLiveSyncSettings } from "../lib/src/common/types";
|
||||
import { eventHub } from "../lib/src/hub/hub";
|
||||
import type ObsidianLiveSyncPlugin from "../main";
|
||||
|
||||
export const EVENT_LAYOUT_READY = "layout-ready";
|
||||
export const EVENT_PLUGIN_LOADED = "plugin-loaded";
|
||||
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_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_SETTING_WIZARD = "request-open-setting-wizard";
|
||||
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 {
|
||||
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_LAYOUT_READY]: undefined;
|
||||
"event-file-changed": { file: FilePathWithPrefix; automated: boolean };
|
||||
"document-stub-created": {
|
||||
toc: Set<string>;
|
||||
stub: { [key: string]: { [key: string]: Map<string, Record<string, string>> } };
|
||||
};
|
||||
[EVENT_PLUGIN_UNLOADED]: undefined;
|
||||
[EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined;
|
||||
[EVENT_REQUEST_OPEN_SETTINGS]: 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_REQUEST_OPEN_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 };
|
||||
|
||||
@@ -2,13 +2,14 @@ import { App, Modal } from "../../deps.ts";
|
||||
import { type FilePath, type LoadedEntry } from "../../lib/src/common/types.ts";
|
||||
import JsonResolvePane from "./JsonResolvePane.svelte";
|
||||
import { waitForSignal } from "../../lib/src/common/utils.ts";
|
||||
import { mount, unmount } from "svelte";
|
||||
|
||||
export class JsonResolveModal extends Modal {
|
||||
// result: Array<[number, string]>;
|
||||
filename: FilePath;
|
||||
callback?: (keepRev?: string, mergedStr?: string) => Promise<void>;
|
||||
docs: LoadedEntry[];
|
||||
component?: JsonResolvePane;
|
||||
component?: ReturnType<typeof mount>;
|
||||
nameA: string;
|
||||
nameB: string;
|
||||
defaultSelect: string;
|
||||
@@ -55,7 +56,7 @@ export class JsonResolveModal extends Modal {
|
||||
contentEl.empty();
|
||||
|
||||
if (this.component == undefined) {
|
||||
this.component = new JsonResolvePane({
|
||||
this.component = mount(JsonResolvePane, {
|
||||
target: contentEl,
|
||||
props: {
|
||||
docs: this.docs,
|
||||
@@ -81,7 +82,7 @@ export class JsonResolveModal extends Modal {
|
||||
void this.callback(undefined);
|
||||
}
|
||||
if (this.component != undefined) {
|
||||
this.component.$destroy();
|
||||
void unmount(this.component);
|
||||
this.component = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,64 @@
|
||||
import { type Diff, DIFF_DELETE, DIFF_INSERT, diff_match_patch } from "../../deps.ts";
|
||||
import type { FilePath, LoadedEntry } from "../../lib/src/common/types.ts";
|
||||
import { decodeBinary, readString } from "../../lib/src/string_and_binary/convert.ts";
|
||||
import { getDocData, mergeObject } from "../../lib/src/common/utils.ts";
|
||||
import { getDocData, isObjectDifferent, mergeObject } from "../../lib/src/common/utils.ts";
|
||||
|
||||
export let docs: LoadedEntry[] = [];
|
||||
export let callback: (keepRev?: string, mergedStr?: string) => Promise<void> = async (_, __) => {
|
||||
Promise.resolve();
|
||||
};
|
||||
export let filename: FilePath = "" as FilePath;
|
||||
export let nameA: string = "A";
|
||||
export let nameB: string = "B";
|
||||
export let defaultSelect: string = "";
|
||||
export let keepOrder = false;
|
||||
export let hideLocal: boolean = false;
|
||||
let docA: LoadedEntry;
|
||||
let docB: LoadedEntry;
|
||||
let docAContent = "";
|
||||
let docBContent = "";
|
||||
let objA: any = {};
|
||||
let objB: any = {};
|
||||
let objAB: any = {};
|
||||
let objBA: any = {};
|
||||
let diffs: Diff[];
|
||||
interface Props {
|
||||
docs?: LoadedEntry[];
|
||||
callback?: (keepRev?: string, mergedStr?: string) => Promise<void>;
|
||||
filename?: FilePath;
|
||||
nameA?: string;
|
||||
nameB?: string;
|
||||
defaultSelect?: string;
|
||||
keepOrder?: boolean;
|
||||
hideLocal?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
docs = $bindable([]),
|
||||
callback = $bindable((async (_, __) => {
|
||||
Promise.resolve();
|
||||
}) as (keepRev?: string, mergedStr?: string) => Promise<void>),
|
||||
filename = $bindable("" as FilePath),
|
||||
nameA = $bindable("A"),
|
||||
nameB = $bindable("B"),
|
||||
defaultSelect = $bindable("" as string),
|
||||
keepOrder = $bindable(false),
|
||||
hideLocal = $bindable(false),
|
||||
}: Props = $props();
|
||||
type JSONData = Record<string | number | symbol, any> | [any];
|
||||
|
||||
const docsArray = $derived.by(() => {
|
||||
if (docs && docs.length >= 1) {
|
||||
if (keepOrder || docs[0].mtime < docs[1].mtime) {
|
||||
return { a: docs[0], b: docs[1] } as const;
|
||||
} else {
|
||||
return { a: docs[1], b: docs[0] } as const;
|
||||
}
|
||||
}
|
||||
return { a: false, b: false } as const;
|
||||
});
|
||||
const docA = $derived(docsArray.a);
|
||||
const docB = $derived(docsArray.b);
|
||||
const docAContent = $derived(docA && docToString(docA));
|
||||
const docBContent = $derived(docB && docToString(docB));
|
||||
|
||||
function parseJson(json: string | false) {
|
||||
if (json === false) return false;
|
||||
try {
|
||||
return JSON.parse(json) as JSONData;
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const objA = $derived(parseJson(docAContent) || {});
|
||||
const objB = $derived(parseJson(docBContent) || {});
|
||||
const objAB = $derived(mergeObject(objA, objB));
|
||||
const objBAw = $derived(mergeObject(objB, objA));
|
||||
const objBA = $derived(isObjectDifferent(objBAw, objAB) ? objBAw : false);
|
||||
let diffs: Diff[] = $derived.by(() => (objA && selectedObj ? getJsonDiff(objA, selectedObj) : []));
|
||||
type SelectModes = "" | "A" | "B" | "AB" | "BA";
|
||||
let mode: SelectModes = defaultSelect as SelectModes;
|
||||
let mode: SelectModes = $state(defaultSelect as SelectModes);
|
||||
|
||||
function docToString(doc: LoadedEntry) {
|
||||
return doc.datatype == "plain" ? getDocData(doc.data) : readString(new Uint8Array(decodeBinary(doc.data)));
|
||||
@@ -45,6 +80,7 @@
|
||||
return getDiff(JSON.stringify(a, null, 2), JSON.stringify(b, null, 2));
|
||||
}
|
||||
function apply() {
|
||||
if (!docA || !docB) return;
|
||||
if (docA._id == docB._id) {
|
||||
if (mode == "A") return callback(docA._rev!, undefined);
|
||||
if (mode == "B") return callback(docB._rev!, undefined);
|
||||
@@ -59,50 +95,23 @@
|
||||
function cancel() {
|
||||
callback(undefined, undefined);
|
||||
}
|
||||
$: {
|
||||
if (docs && docs.length >= 1) {
|
||||
if (keepOrder || docs[0].mtime < docs[1].mtime) {
|
||||
docA = docs[0];
|
||||
docB = docs[1];
|
||||
} else {
|
||||
docA = docs[1];
|
||||
docB = docs[0];
|
||||
}
|
||||
docAContent = docToString(docA);
|
||||
docBContent = docToString(docB);
|
||||
const mergedObjs = $derived.by(
|
||||
() =>
|
||||
({
|
||||
"": false,
|
||||
A: objA,
|
||||
B: objB,
|
||||
AB: objAB,
|
||||
BA: objBA,
|
||||
}) as Record<SelectModes, JSONData | false>
|
||||
);
|
||||
|
||||
try {
|
||||
objA = false;
|
||||
objB = false;
|
||||
objA = JSON.parse(docAContent);
|
||||
objB = JSON.parse(docBContent);
|
||||
objAB = mergeObject(objA, objB);
|
||||
objBA = mergeObject(objB, objA);
|
||||
if (JSON.stringify(objAB) == JSON.stringify(objBA)) {
|
||||
objBA = false;
|
||||
}
|
||||
} catch (ex) {
|
||||
objBA = false;
|
||||
objAB = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$: mergedObjs = {
|
||||
"": false,
|
||||
A: objA,
|
||||
B: objB,
|
||||
AB: objAB,
|
||||
BA: objBA,
|
||||
};
|
||||
let selectedObj = $derived(mode in mergedObjs ? mergedObjs[mode] : {});
|
||||
|
||||
$: selectedObj = mode in mergedObjs ? mergedObjs[mode] : {};
|
||||
$: {
|
||||
diffs = getJsonDiff(objA, selectedObj);
|
||||
}
|
||||
let modesSrc = $state([] as ["" | "A" | "B" | "AB" | "BA", string][]);
|
||||
|
||||
let modes = [] as ["" | "A" | "B" | "AB" | "BA", string][];
|
||||
$: {
|
||||
let newModes = [] as typeof modes;
|
||||
const modes = $derived.by(() => {
|
||||
let newModes = [] as typeof modesSrc;
|
||||
|
||||
if (!hideLocal) {
|
||||
newModes.push(["", "Not now"]);
|
||||
@@ -111,15 +120,15 @@
|
||||
newModes.push(["B", nameB || "B"]);
|
||||
newModes.push(["AB", `${nameA || "A"} + ${nameB || "B"}`]);
|
||||
newModes.push(["BA", `${nameB || "B"} + ${nameA || "A"}`]);
|
||||
modes = newModes;
|
||||
}
|
||||
return newModes;
|
||||
});
|
||||
</script>
|
||||
|
||||
<h2>{filename}</h2>
|
||||
{#if !docA || !docB}
|
||||
<div class="message">Just for a minute, please!</div>
|
||||
<div class="buttons">
|
||||
<button on:click={apply}>Dismiss</button>
|
||||
<button onclick={apply}>Dismiss</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="options">
|
||||
@@ -148,39 +157,39 @@
|
||||
<div class="infos">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{nameA}</th>
|
||||
<td
|
||||
>{#if docA._id == docB._id}
|
||||
Rev:{revStringToRevNumber(docA._rev)}
|
||||
{/if}
|
||||
{new Date(docA.mtime).toLocaleString()}</td
|
||||
>
|
||||
<td>
|
||||
{docAContent.length} letters
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{nameB}</th>
|
||||
<td
|
||||
>{#if docA._id == docB._id}
|
||||
Rev:{revStringToRevNumber(docB._rev)}
|
||||
{/if}
|
||||
{new Date(docB.mtime).toLocaleString()}</td
|
||||
>
|
||||
<td>
|
||||
{docBContent.length} letters
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{nameA}</th>
|
||||
<td
|
||||
>{#if docA._id == docB._id}
|
||||
Rev:{revStringToRevNumber(docA._rev)}
|
||||
{/if}
|
||||
{new Date(docA.mtime).toLocaleString()}</td
|
||||
>
|
||||
<td>
|
||||
{docAContent && docAContent.length} letters
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{nameB}</th>
|
||||
<td
|
||||
>{#if docA._id == docB._id}
|
||||
Rev:{revStringToRevNumber(docB._rev)}
|
||||
{/if}
|
||||
{new Date(docB.mtime).toLocaleString()}</td
|
||||
>
|
||||
<td>
|
||||
{docBContent && docBContent.length} letters
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
{#if hideLocal}
|
||||
<button on:click={cancel}>Cancel</button>
|
||||
<button onclick={cancel}>Cancel</button>
|
||||
{/if}
|
||||
<button on:click={apply}>Apply</button>
|
||||
<button onclick={apply}>Apply</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -701,7 +701,7 @@ Offline Changed files: ${processFiles.length}`;
|
||||
?.filter((e) => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo)
|
||||
.first()?.rev ?? "";
|
||||
const result = await this.plugin.localDatabase.mergeObject(
|
||||
path,
|
||||
doc.path,
|
||||
commonBase,
|
||||
doc._rev,
|
||||
conflictedRev
|
||||
|
||||
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 PeerStatus,
|
||||
type PluginShim,
|
||||
} from "./P2PReplicatorPaneCommon";
|
||||
} from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte";
|
||||
import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events";
|
||||
import {
|
||||
@@ -294,7 +294,12 @@
|
||||
<th> Room ID </th>
|
||||
<td>
|
||||
<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>
|
||||
</label>
|
||||
<span>
|
||||
@@ -320,8 +325,7 @@
|
||||
<th> This device name </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isDeviceNameModified }}>
|
||||
<input type="text" placeholder="iphone-16" bind:value={eDeviceName}
|
||||
autocomplete="off" />
|
||||
<input type="text" placeholder="iphone-16" bind:value={eDeviceName} autocomplete="off" />
|
||||
</label>
|
||||
</td>
|
||||
</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 { 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";
|
||||
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";
|
||||
|
||||
function addToList(item: string, list: string) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
import { AcceptedStatus, type PeerStatus } from "./P2PReplicatorPaneCommon";
|
||||
import type { P2PReplicator } from "../CmdP2PSync";
|
||||
import { AcceptedStatus, type PeerStatus } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { P2PReplicator } from "../CmdP2PReplicator";
|
||||
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 {
|
||||
peerStatus: PeerStatus;
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: 9f71ed12ad...2a0dd3c3ac
@@ -82,7 +82,7 @@ import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts";
|
||||
import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts";
|
||||
import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.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 {
|
||||
throw new Error("This function should be overridden by the module.");
|
||||
|
||||
@@ -106,7 +106,6 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
||||
}
|
||||
$everyOnload(): Promise<boolean> {
|
||||
this.app.workspace.onLayoutReady(this.core.$$onLiveSyncReady.bind(this.core));
|
||||
// eslint-disable-next-line no-unused-labels
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
||||
|
||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||
// eslint-disable-next-line no-unused-labels
|
||||
this.onMissingTranslation = this.onMissingTranslation.bind(this);
|
||||
__onMissingTranslation((key) => {
|
||||
void this.onMissingTranslation(key);
|
||||
|
||||
@@ -30,7 +30,6 @@ export class TestPaneView extends ItemView {
|
||||
return "Self-hosted LiveSync Test and Results";
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-await
|
||||
async onOpen() {
|
||||
this.component = new TestPaneComponent({
|
||||
target: this.contentEl,
|
||||
@@ -42,7 +41,6 @@ export class TestPaneView extends ItemView {
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-await
|
||||
async onClose() {
|
||||
this.component?.$destroy();
|
||||
await Promise.resolve();
|
||||
|
||||
@@ -27,14 +27,7 @@ import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||
import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
|
||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||
import type { P2PReplicationProgress } from "../../lib/src/replication/trystero/TrysteroReplicator.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";
|
||||
import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
||||
|
||||
// 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 }>;
|
||||
statusLog = reactiveSource("");
|
||||
notifies: { [key: string]: { notice: Notice; count: number } } = {};
|
||||
p2pLogCollector = new P2PLogCollector();
|
||||
|
||||
observeForLogs() {
|
||||
const padSpaces = `\u{2007}`.repeat(10);
|
||||
@@ -176,7 +170,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
||||
const queued = queueCountLabel();
|
||||
const waiting = waitingLabel();
|
||||
const networkActivity = requestingStatLabel();
|
||||
const p2p = this.p2pReplicationLine.value;
|
||||
const p2p = this.p2pLogCollector.p2pReplicationLine.value;
|
||||
return {
|
||||
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));
|
||||
}
|
||||
|
||||
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> {
|
||||
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => 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);
|
||||
}
|
||||
adjustStatusDivPosition() {
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
"noEmit": true,
|
||||
"lib": ["es2018", "DOM", "ES5", "ES6", "ES7", "es2019.array", "ES2021.WeakRef", "ES2020.BigInt", "ESNext.Intl"],
|
||||
"strictBindCallApply": true,
|
||||
"strictFunctionTypes": true
|
||||
"strictFunctionTypes": true,
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@lib/*": ["src/lib/src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["pouchdb-browser-webpack", "utils"]
|
||||
|
||||
46
updates.md
46
updates.md
@@ -10,6 +10,52 @@ Nevertheless, that being said, to be more honest, I still have not decided what
|
||||
|
||||
Note: Already you have noticed this, but let me mention it again, this is a significantly large update. If you have noticed anything, please let me know. I will try to fix it as soon as possible (Some address is on my [profile](https://github.com/vrtmrz)).
|
||||
|
||||
## 0.24.14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Resolving conflicts of JSON files (and sensibly merging them) is now working fine, again!
|
||||
- And, failure logs are more informative.
|
||||
- More robust to release the event listeners on unwatching the local database.
|
||||
|
||||
### Refactored
|
||||
|
||||
- JSON file conflict resolution dialogue has been rewritten into svelte v5.
|
||||
- Upgrade eslint.
|
||||
- Remove unnecessary pragma comments for eslint.
|
||||
|
||||
## 0.24.13
|
||||
|
||||
Sorry for the lack of replies. The ones that were not good are popping up, so I am just going to go ahead and get this one... However, they realised that refactoring and restructuring is about clarifying the problem. Your patience and understanding is much appreciated.
|
||||
|
||||
### 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.
|
||||
|
||||
## 0.24.12
|
||||
|
||||
I created a SPA called [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) (well, right... I will think of a name again), which replaces the server when using Peer-to-Peer synchronisation. This is a pseudo-client that appears to other devices as if it were one of the clients. . As with the client, it receives and sends data without storing it as a file.
|
||||
|
||||
Reference in New Issue
Block a user