11th March, 2026

Now, Self-hosted LiveSync has finally begun to be split into the Self-hosted LiveSync plugin for Obsidian, and a properly abstracted version of it.
This may not offer much benefit to Obsidian plugin users, or might even cause a slight inconvenience, but I believe it will certainly help improve testability and make the ecosystem better.
However, I do not see the point in putting something with little benefit into beta, so I am handling this on the alpha branch. I would actually preferred to create an R&D branch, but I was not keen on the ampersand, and I feel it will eventually become a proper beta anyway.

### Refactored

- Separated `ObsidianLiveSyncPlugin` into `ObsidianLiveSyncPlugin` and `LiveSyncBaseCore`.
- Now `LiveSyncCore` indicates the type specified version of `LiveSyncBaseCore`.
- Referencing `plugin.xxx` has been rewritten to referencing the corresponding service or `core.xxx`.

### Internal API changes

- Storage Access APIs are now yielding Promises. This is to allow more limited storage platforms to be supported.

### R&D

- Browser-version of Self-hosted LiveSync is now in development. This is not intended for public use now, but I will eventually make it available for testing.
- We can see the code in `src/apps/webapp` for the browser version.
This commit is contained in:
vorotamoroz
2026-03-11 05:47:00 +01:00
parent 9cf630320c
commit 0dfd42259d
77 changed files with 2849 additions and 909 deletions

View File

@@ -50,7 +50,6 @@ import { LiveSyncCommands } from "../LiveSyncCommands.ts";
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
import {
EVEN,
PeriodicProcessor,
disposeMemoObject,
isCustomisationSyncMetadata,
isPluginMetadata,
@@ -59,6 +58,7 @@ import {
retrieveMemoObject,
scheduleTask,
} from "../../common/utils.ts";
import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts";
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
import { pluginScanningCount } from "../../lib/src/mock_and_interop/stores.ts";
@@ -392,29 +392,32 @@ export type PluginDataEx = {
};
export class ConfigSync extends LiveSyncCommands {
constructor(plugin: ObsidianLiveSyncPlugin) {
super(plugin);
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
super(plugin, core);
pluginScanningCount.onChanged((e) => {
const total = e.value;
pluginIsEnumerating.set(total != 0);
});
}
get configDir() {
return this.core.services.API.getSystemConfigDir();
}
get kvDB() {
return this.plugin.kvDB;
return this.core.kvDB;
}
get useV2() {
return this.plugin.settings.usePluginSyncV2;
return this.core.settings.usePluginSyncV2;
}
get useSyncPluginEtc() {
return this.plugin.settings.usePluginEtc;
return this.core.settings.usePluginEtc;
}
isThisModuleEnabled() {
return this.plugin.settings.usePluginSync;
return this.core.settings.usePluginSync;
}
pluginDialog?: PluginDialogModal = undefined;
periodicPluginSweepProcessor = new PeriodicProcessor(this.plugin, async () => await this.scanAllConfigFiles(false));
periodicPluginSweepProcessor = new PeriodicProcessor(this.core, async () => await this.scanAllConfigFiles(false));
pluginList: IPluginDataExDisplay[] = [];
showPluginSyncModal() {
@@ -439,7 +442,7 @@ export class ConfigSync extends LiveSyncCommands {
this.hidePluginSyncModal();
this.periodicPluginSweepProcessor?.disable();
}
addRibbonIcon = this.plugin.addRibbonIcon.bind(this.plugin);
addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API);
onload() {
addIcon(
"custom-sync",
@@ -447,7 +450,7 @@ export class ConfigSync extends LiveSyncCommands {
<path d="m272 166-9.38 9.38 9.38 9.38 9.38-9.38c1.96-1.93 5.11-1.9 7.03 0.058 1.91 1.94 1.91 5.04 0 6.98l-9.38 9.38 5.86 5.86-11.7 11.7c-8.34 8.35-21.4 9.68-31.3 3.19l-3.84 3.98c-8.45 8.7-20.1 13.6-32.2 13.6h-5.55v-9.95h5.55c9.43-0.0182 18.5-3.84 25-10.6l3.95-4.09c-6.54-9.86-5.23-23 3.14-31.3l11.7-11.7 5.86 5.86 9.38-9.38c1.96-1.93 5.11-1.9 7.03 0.0564 1.91 1.93 1.91 5.04 2e-3 6.98z"/>
</g>`
);
this.plugin.addCommand({
this.services.API.addCommand({
id: "livesync-plugin-dialog-ex",
name: "Show customization sync dialog",
callback: () => {
@@ -464,10 +467,9 @@ export class ConfigSync extends LiveSyncCommands {
filePath: string
): "CONFIG" | "THEME" | "SNIPPET" | "PLUGIN_MAIN" | "PLUGIN_ETC" | "PLUGIN_DATA" | "" {
if (filePath.split("/").length == 2 && filePath.endsWith(".json")) return "CONFIG";
if (filePath.split("/").length == 4 && filePath.startsWith(`${this.app.vault.configDir}/themes/`))
return "THEME";
if (filePath.startsWith(`${this.app.vault.configDir}/snippets/`) && filePath.endsWith(".css")) return "SNIPPET";
if (filePath.startsWith(`${this.app.vault.configDir}/plugins/`)) {
if (filePath.split("/").length == 4 && filePath.startsWith(`${this.configDir}/themes/`)) return "THEME";
if (filePath.startsWith(`${this.configDir}/snippets/`) && filePath.endsWith(".css")) return "SNIPPET";
if (filePath.startsWith(`${this.configDir}/plugins/`)) {
if (
filePath.endsWith("/styles.css") ||
filePath.endsWith("/manifest.json") ||
@@ -485,7 +487,7 @@ export class ConfigSync extends LiveSyncCommands {
return "";
}
isTargetPath(filePath: string): boolean {
if (!filePath.startsWith(this.app.vault.configDir)) return false;
if (!filePath.startsWith(this.configDir)) return false;
// Idea non-filter option?
return this.getFileCategory(filePath) != "";
}
@@ -854,7 +856,7 @@ export class ConfigSync extends LiveSyncCommands {
children: [],
eden: {},
};
const r = await this.plugin.localDatabase.putDBEntry(saving);
const r = await this.core.localDatabase.putDBEntry(saving);
if (r && r.ok) {
this._log(`Migrated ${v1Path} / ${f.filename} to ${v2Path}`, LOG_LEVEL_INFO);
const delR = await this.deleteConfigOnDatabase(v1Path);
@@ -996,16 +998,16 @@ export class ConfigSync extends LiveSyncCommands {
}
}
async applyDataV2(data: PluginDataExDisplayV2, content?: string): Promise<boolean> {
const baseDir = this.app.vault.configDir;
const baseDir = this.configDir;
try {
if (content) {
// const dt = createBlob(content);
const filename = data.files[0].filename;
this._log(`Applying ${filename} of ${data.displayName || data.name}..`);
const path = `${baseDir}/${filename}` as FilePath;
await this.plugin.storageAccess.ensureDir(path);
await this.core.storageAccess.ensureDir(path);
// If the content has applied, modified time will be updated to the current time.
await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
await this.core.storageAccess.writeHiddenFileAuto(path, content);
await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName());
} else {
const files = data.files;
@@ -1015,12 +1017,12 @@ export class ConfigSync extends LiveSyncCommands {
const path = `${baseDir}/${f.filename}` as FilePath;
this._log(`Applying ${f.filename} of ${data.displayName || data.name}..`);
// const contentEach = createBlob(f.data);
await this.plugin.storageAccess.ensureDir(path);
await this.core.storageAccess.ensureDir(path);
if (f.datatype == "newnote") {
let oldData;
try {
oldData = await this.plugin.storageAccess.readHiddenFileBinary(path);
oldData = await this.core.storageAccess.readHiddenFileBinary(path);
} catch (ex) {
this._log(`Could not read the file ${f.filename}`, LOG_LEVEL_VERBOSE);
this._log(ex, LOG_LEVEL_VERBOSE);
@@ -1031,11 +1033,11 @@ export class ConfigSync extends LiveSyncCommands {
this._log(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE);
continue;
}
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
await this.core.storageAccess.writeHiddenFileAuto(path, content, stat);
} else {
let oldData;
try {
oldData = await this.plugin.storageAccess.readHiddenFileText(path);
oldData = await this.core.storageAccess.readHiddenFileText(path);
} catch (ex) {
this._log(`Could not read the file ${f.filename}`, LOG_LEVEL_VERBOSE);
this._log(ex, LOG_LEVEL_VERBOSE);
@@ -1046,7 +1048,7 @@ export class ConfigSync extends LiveSyncCommands {
this._log(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE);
continue;
}
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
await this.core.storageAccess.writeHiddenFileAuto(path, content, stat);
}
this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`);
await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName());
@@ -1065,7 +1067,7 @@ export class ConfigSync extends LiveSyncCommands {
if (data instanceof PluginDataExDisplayV2) {
return this.applyDataV2(data, content);
}
const baseDir = this.app.vault.configDir;
const baseDir = this.configDir;
try {
if (!data.documentPath) throw "InternalError: Document path not exist";
const dx = await this.localDatabase.getDBEntry(data.documentPath);
@@ -1078,12 +1080,12 @@ export class ConfigSync extends LiveSyncCommands {
try {
// console.dir(f);
const path = `${baseDir}/${f.filename}`;
await this.plugin.storageAccess.ensureDir(path);
await this.core.storageAccess.ensureDir(path);
if (!content) {
const dt = decodeBinary(f.data);
await this.plugin.storageAccess.writeHiddenFileAuto(path, dt);
await this.core.storageAccess.writeHiddenFileAuto(path, dt);
} else {
await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
await this.core.storageAccess.writeHiddenFileAuto(path, content);
}
this._log(`Applying ${f.filename} of ${data.displayName || data.name}.. Done`);
} catch (ex) {
@@ -1172,7 +1174,7 @@ export class ConfigSync extends LiveSyncCommands {
(docs as AnyEntry).path ? (docs as AnyEntry).path : this.getPath(docs as AnyEntry)
);
}
if (this.isThisModuleEnabled() && this.plugin.settings.notifyPluginOrSettingUpdated) {
if (this.isThisModuleEnabled() && this.core.settings.notifyPluginOrSettingUpdated) {
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
const fragment = createFragment((doc) => {
doc.createEl("span", undefined, (a) => {
@@ -1230,13 +1232,13 @@ export class ConfigSync extends LiveSyncCommands {
recentProcessedInternalFiles = [] as string[];
async makeEntryFromFile(path: FilePath): Promise<false | PluginDataExFile> {
const stat = await this.plugin.storageAccess.statHidden(path);
const stat = await this.core.storageAccess.statHidden(path);
let version: string | undefined;
let displayName: string | undefined;
if (!stat) {
return false;
}
const contentBin = await this.plugin.storageAccess.readHiddenFileBinary(path);
const contentBin = await this.core.storageAccess.readHiddenFileBinary(path);
let content: string[];
try {
content = await arrayBufferToBase64(contentBin);
@@ -1265,7 +1267,7 @@ export class ConfigSync extends LiveSyncCommands {
}
const mtime = stat.mtime;
return {
filename: path.substring(this.app.vault.configDir.length + 1),
filename: path.substring(this.configDir.length + 1),
data: content,
mtime,
size: stat.size,
@@ -1280,12 +1282,12 @@ export class ConfigSync extends LiveSyncCommands {
const prefixedFileName = vf;
const id = await this.path2id(prefixedFileName);
const stat = await this.plugin.storageAccess.statHidden(path);
const stat = await this.core.storageAccess.statHidden(path);
if (!stat) {
return false;
}
const mtime = stat.mtime;
const content = await this.plugin.storageAccess.readHiddenFileBinary(path);
const content = await this.core.storageAccess.readHiddenFileBinary(path);
const contentBlob = createBlob([DUMMY_HEAD, DUMMY_END, ...(await arrayBufferToBase64(content))]);
// const contentBlob = createBlob(content);
try {
@@ -1504,11 +1506,11 @@ export class ConfigSync extends LiveSyncCommands {
if (this._isMainSuspended()) return false;
if (!this.isThisModuleEnabled()) return false;
// if (!this.isTargetPath(path)) return false;
const stat = await this.plugin.storageAccess.statHidden(path);
const stat = await this.core.storageAccess.statHidden(path);
// Make sure that target is a file.
if (stat && stat.type != "file") return false;
const configDir = normalizePath(this.app.vault.configDir);
const configDir = normalizePath(this.configDir);
const synchronisedInConfigSync = Object.values(this.settings.pluginSyncExtendedSetting)
.filter((e) => e.mode != MODE_SELECTIVE && e.mode != MODE_SHINY)
.map((e) => e.files)
@@ -1674,7 +1676,7 @@ export class ConfigSync extends LiveSyncCommands {
}
async scanInternalFiles(): Promise<FilePath[]> {
const filenames = (await this.getFiles(this.app.vault.configDir, 2))
const filenames = (await this.getFiles(this.configDir, 2))
.filter((e) => e.startsWith("."))
.filter((e) => !e.startsWith(".trash"));
return filenames as FilePath[];
@@ -1705,7 +1707,7 @@ export class ConfigSync extends LiveSyncCommands {
choices.push(CHOICE_DISABLE);
choices.push(CHOICE_DISMISS);
const ret = await this.plugin.confirm.askSelectStringDialogue(message, choices, {
const ret = await this.core.confirm.askSelectStringDialogue(message, choices, {
defaultAction: CHOICE_DISMISS,
timeout: 40,
title: "Customisation sync",
@@ -1728,13 +1730,13 @@ export class ConfigSync extends LiveSyncCommands {
}
private _allSuspendExtraSync(): Promise<boolean> {
if (this.plugin.settings.usePluginSync || this.plugin.settings.autoSweepPlugins) {
if (this.core.settings.usePluginSync || this.core.settings.autoSweepPlugins) {
this._log(
"Customisation sync have been temporarily disabled. Please enable them after the fetching, if you need them.",
LOG_LEVEL_NOTICE
);
this.plugin.settings.usePluginSync = false;
this.plugin.settings.autoSweepPlugins = false;
this.core.settings.usePluginSync = false;
this.core.settings.autoSweepPlugins = false;
}
return Promise.resolve(true);
}
@@ -1745,14 +1747,20 @@ export class ConfigSync extends LiveSyncCommands {
}
async configureHiddenFileSync(mode: keyof OPTIONAL_SYNC_FEATURES) {
if (mode == "DISABLE") {
this.plugin.settings.usePluginSync = false;
await this.plugin.saveSettings();
// this.plugin.settings.usePluginSync = false;
// await this.plugin.saveSettings();
await this.core.services.setting.applyPartial(
{
usePluginSync: false,
},
true
);
return;
}
if (mode == "CUSTOMIZE") {
if (!this.services.setting.getDeviceAndVaultName()) {
let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`);
let name = await this.core.confirm.askString("Device name", "Please set this device name", `desktop`);
if (!name) {
if (Platform.isAndroidApp) {
name = "android-app";
@@ -1777,9 +1785,16 @@ export class ConfigSync extends LiveSyncCommands {
}
this.services.setting.setDeviceAndVaultName(name);
}
this.plugin.settings.usePluginSync = true;
this.plugin.settings.useAdvancedMode = true;
await this.plugin.saveSettings();
// this.core.settings.usePluginSync = true;
// this.core.settings.useAdvancedMode = true;
// await this.core.saveSettings();
await this.core.services.setting.applyPartial(
{
usePluginSync: true,
useAdvancedMode: true,
},
true
);
await this.scanAllConfigFiles(true);
}
}

View File

@@ -30,7 +30,8 @@
export let plugin: ObsidianLiveSyncPlugin;
export let isMaintenanceMode: boolean = false;
export let isFlagged: boolean = false;
const addOn = plugin.getAddOn<ConfigSync>(ConfigSync.name)!;
$: core = plugin.core;
const addOn = plugin.core.getAddOn<ConfigSync>(ConfigSync.name)!;
if (!addOn) {
Logger(`Could not load the add-on ${ConfigSync.name}`, LOG_LEVEL_INFO);
throw new Error(`Could not load the add-on ${ConfigSync.name}`);
@@ -334,13 +335,13 @@
Logger(`Could not find local item`, LOG_LEVEL_VERBOSE);
return;
}
const duplicateTermName = await plugin.confirm.askString("Duplicate", "device name", "");
const duplicateTermName = await core.confirm.askString("Duplicate", "device name", "");
if (duplicateTermName) {
if (duplicateTermName.contains("/")) {
Logger(`We can not use "/" to the device name`, LOG_LEVEL_NOTICE);
return;
}
const key = `${plugin.app.vault.configDir}/${local.files[0].filename}`;
const key = `${plugin.core.services.API.getSystemConfigDir()}/${local.files[0].filename}`;
await addOn.storeCustomizationFiles(key as FilePath, duplicateTermName);
await addOn.updatePluginList(false, addOn.filenameToUnifiedKey(key, duplicateTermName));
}

View File

@@ -23,7 +23,7 @@ export class PluginDialogModal extends Modal {
if (!this.component) {
this.component = mount(PluginPane, {
target: contentEl,
props: { plugin: this.plugin },
props: { plugin: this.plugin, core: this.plugin.core },
});
}
}

View File

@@ -22,19 +22,22 @@
import { normalizePath } from "../../deps";
import { HiddenFileSync } from "../HiddenFileSync/CmdHiddenFileSync.ts";
import { LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
export let plugin: ObsidianLiveSyncPlugin;
export let core :LiveSyncBaseCore;
// $: core = plugin.core;
$: hideNotApplicable = false;
$: thisTerm = plugin.services.setting.getDeviceAndVaultName();
$: thisTerm = core.services.setting.getDeviceAndVaultName();
const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync;
const addOn = core.getAddOn<ConfigSync>(ConfigSync.name)!;
if (!addOn) {
const msg =
"AddOn Module (ConfigSync) has not been loaded. This is very unexpected situation. Please report this issue.";
Logger(msg, LOG_LEVEL_NOTICE);
throw new Error(msg);
}
const addOnHiddenFileSync = plugin.getAddOn(HiddenFileSync.name) as HiddenFileSync;
const addOnHiddenFileSync = core.getAddOn<HiddenFileSync>(HiddenFileSync.name) as HiddenFileSync;
if (!addOnHiddenFileSync) {
const msg =
"AddOn Module (HiddenFileSync) has not been loaded. This is very unexpected situation. Please report this issue.";
@@ -98,7 +101,7 @@
await requestUpdate();
}
async function replicate() {
await plugin.services.replication.replicate(true);
await core.services.replication.replicate(true);
}
function selectAllNewest(selectMode: boolean) {
selectNewestPulse++;
@@ -147,8 +150,8 @@
}
function applyAutomaticSync(key: string, direction: "pushForce" | "pullForce" | "safe") {
setMode(key, MODE_AUTOMATIC);
const configDir = normalizePath(plugin.app.vault.configDir);
const files = (plugin.settings.pluginSyncExtendedSetting[key]?.files ?? []).map((e) => `${configDir}/${e}`);
const configDir = normalizePath(plugin.core.services.API.getSystemConfigDir());
const files = (plugin.core.settings.pluginSyncExtendedSetting[key]?.files ?? []).map((e) => `${configDir}/${e}`);
addOnHiddenFileSync.initialiseInternalFileSync(direction, true, files);
}
function askOverwriteModeForAutomatic(evt: MouseEvent, key: string) {
@@ -222,22 +225,22 @@
);
if (mode == MODE_SELECTIVE) {
automaticList.delete(key);
delete plugin.settings.pluginSyncExtendedSetting[key];
delete plugin.core.settings.pluginSyncExtendedSetting[key];
automaticListDisp = automaticList;
} else {
automaticList.set(key, mode);
automaticListDisp = automaticList;
if (!(key in plugin.settings.pluginSyncExtendedSetting)) {
plugin.settings.pluginSyncExtendedSetting[key] = {
if (!(key in plugin.core.settings.pluginSyncExtendedSetting)) {
plugin.core.settings.pluginSyncExtendedSetting[key] = {
key,
mode,
files: [],
};
}
plugin.settings.pluginSyncExtendedSetting[key].files = files;
plugin.settings.pluginSyncExtendedSetting[key].mode = mode;
plugin.core.settings.pluginSyncExtendedSetting[key].files = files;
plugin.core.settings.pluginSyncExtendedSetting[key].mode = mode;
}
plugin.services.setting.saveSettingData();
core.services.setting.saveSettingData();
}
function getIcon(mode: SYNC_MODE) {
if (mode in ICONS) {
@@ -250,7 +253,7 @@
let automaticListDisp = new Map<string, SYNC_MODE>();
// apply current configuration to the dialogue
for (const { key, mode } of Object.values(plugin.settings.pluginSyncExtendedSetting)) {
for (const { key, mode } of Object.values(plugin.core.settings.pluginSyncExtendedSetting)) {
automaticList.set(key, mode);
}
@@ -259,7 +262,7 @@
let displayKeys: Record<string, string[]> = {};
function computeDisplayKeys(list: IPluginDataExDisplay[]) {
const extraKeys = Object.keys(plugin.settings.pluginSyncExtendedSetting);
const extraKeys = Object.keys(plugin.core.settings.pluginSyncExtendedSetting);
return [
...list,
...extraKeys
@@ -321,7 +324,7 @@
$: {
pluginEntries = groupBy(filterList(list, ["PLUGIN_MAIN", "PLUGIN_DATA", "PLUGIN_ETC"]), "name");
}
let useSyncPluginEtc = plugin.settings.usePluginEtc;
let useSyncPluginEtc = plugin.core.settings.usePluginEtc;
</script>
<div class="buttonsWrap">

View File

@@ -30,7 +30,6 @@ import {
import {
compareMTime,
isInternalMetadata,
PeriodicProcessor,
TARGET_IS_NEW,
scheduleTask,
getLogLevel,
@@ -41,6 +40,7 @@ import {
EVEN,
displayRev,
} from "../../common/utils.ts";
import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts";
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
@@ -78,25 +78,25 @@ function getComparingMTime(
export class HiddenFileSync extends LiveSyncCommands {
isThisModuleEnabled() {
return this.plugin.settings.syncInternalFiles;
return this.core.settings.syncInternalFiles;
}
periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(
this.plugin,
this.core,
async () => this.isThisModuleEnabled() && this._isDatabaseReady() && (await this.scanAllStorageChanges(false))
);
get kvDB() {
return this.plugin.kvDB;
return this.core.kvDB;
}
getConflictedDoc(path: FilePathWithPrefix, rev: string) {
return this.localDatabase.managers.conflictManager.getConflictedDoc(path, rev);
return this.core.localDatabase.managers.conflictManager.getConflictedDoc(path, rev);
}
onunload() {
this.periodicInternalFileScanProcessor?.disable();
}
onload() {
this.plugin.addCommand({
this.services.API.addCommand({
id: "livesync-sync-internal",
name: "(re)initialise hidden files between storage and database",
callback: () => {
@@ -105,7 +105,7 @@ export class HiddenFileSync extends LiveSyncCommands {
}
},
});
this.plugin.addCommand({
this.services.API.addCommand({
id: "livesync-scaninternal-storage",
name: "Scan hidden file changes on the storage",
callback: () => {
@@ -114,7 +114,7 @@ export class HiddenFileSync extends LiveSyncCommands {
}
},
});
this.plugin.addCommand({
this.services.API.addCommand({
id: "livesync-scaninternal-database",
name: "Scan hidden file changes on the local database",
callback: () => {
@@ -123,7 +123,7 @@ export class HiddenFileSync extends LiveSyncCommands {
}
},
});
this.plugin.addCommand({
this.services.API.addCommand({
id: "livesync-internal-scan-offline-changes",
name: "Scan and apply all offline hidden-file changes",
callback: () => {
@@ -267,7 +267,7 @@ export class HiddenFileSync extends LiveSyncCommands {
}
async loadFileWithInfo(path: FilePath): Promise<UXFileInfo> {
const stat = await this.plugin.storageAccess.statHidden(path);
const stat = await this.core.storageAccess.statHidden(path);
if (!stat)
return {
name: path.split("/").pop() ?? "",
@@ -282,7 +282,7 @@ export class HiddenFileSync extends LiveSyncCommands {
deleted: true,
body: createBlob(new Uint8Array(0)),
};
const content = await this.plugin.storageAccess.readHiddenFileAuto(path);
const content = await this.core.storageAccess.readHiddenFileAuto(path);
return {
name: path.split("/").pop() ?? "",
path,
@@ -304,7 +304,7 @@ export class HiddenFileSync extends LiveSyncCommands {
return `${doc.mtime}-${doc.size}-${doc._rev}-${doc._deleted || doc.deleted || false ? "-0" : "-1"}`;
}
async fileToStatKey(file: FilePath, stat: UXStat | null = null) {
if (!stat) stat = await this.plugin.storageAccess.statHidden(file);
if (!stat) stat = await this.core.storageAccess.statHidden(file);
return this.statToKey(stat);
}
@@ -318,7 +318,7 @@ export class HiddenFileSync extends LiveSyncCommands {
}
async updateLastProcessedAsActualFile(file: FilePath, stat?: UXStat | null | undefined) {
if (!stat) stat = await this.plugin.storageAccess.statHidden(file);
if (!stat) stat = await this.core.storageAccess.statHidden(file);
this._fileInfoLastProcessed.set(file, this.statToKey(stat));
}
@@ -371,27 +371,27 @@ export class HiddenFileSync extends LiveSyncCommands {
this.updateLastProcessedFile(path, this.statToKey(null));
}
async ensureDir(path: FilePath) {
const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(path);
const isExists = await this.core.storageAccess.isExistsIncludeHidden(path);
if (!isExists) {
await this.plugin.storageAccess.ensureDir(path);
await this.core.storageAccess.ensureDir(path);
}
}
async writeFile(path: FilePath, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<UXStat | null> {
await this.plugin.storageAccess.writeHiddenFileAuto(path, data, opt);
const stat = await this.plugin.storageAccess.statHidden(path);
await this.core.storageAccess.writeHiddenFileAuto(path, data, opt);
const stat = await this.core.storageAccess.statHidden(path);
// this.updateLastProcessedFile(path, this.statToKey(stat));
return stat;
}
async __removeFile(path: FilePath): Promise<"OK" | "ALREADY" | false> {
try {
if (!(await this.plugin.storageAccess.isExistsIncludeHidden(path))) {
if (!(await this.core.storageAccess.isExistsIncludeHidden(path))) {
// Already deleted
// this.updateLastProcessedFile(path, this.statToKey(null));
return "ALREADY";
}
if (await this.plugin.storageAccess.removeHidden(path)) {
if (await this.core.storageAccess.removeHidden(path)) {
// this.updateLastProcessedFile(path, this.statToKey(null));
return "OK";
}
@@ -404,7 +404,7 @@ export class HiddenFileSync extends LiveSyncCommands {
async triggerEvent(path: FilePath) {
try {
// await this.app.vault.adapter.reconcileInternalFile(filename);
await this.plugin.storageAccess.triggerHiddenFile(path);
await this.core.storageAccess.triggerHiddenFile(path);
} catch (ex) {
this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
this._log(ex, LOG_LEVEL_VERBOSE);
@@ -518,7 +518,7 @@ export class HiddenFileSync extends LiveSyncCommands {
LOG_LEVEL_VERBOSE
);
const taskNameAndMeta = [...files].map(
async (e) => [e, await this.plugin.storageAccess.statHidden(e)] as const
async (e) => [e, await this.core.storageAccess.statHidden(e)] as const
);
const nameAndMeta = await Promise.all(taskNameAndMeta);
const processFiles = nameAndMeta
@@ -560,7 +560,7 @@ Offline Changed files: ${processFiles.length}`;
}
try {
return await this.serializedForEvent(path, async () => {
let stat = await this.plugin.storageAccess.statHidden(path);
let stat = await this.core.storageAccess.statHidden(path);
// sometimes folder is coming.
if (stat != null && stat.type != "file") {
return false;
@@ -815,9 +815,9 @@ Offline Changed files: ${processFiles.length}`;
}
}
if (!keep && result) {
const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(storageFilePath);
const isExists = await this.core.storageAccess.isExistsIncludeHidden(storageFilePath);
if (!isExists) {
await this.plugin.storageAccess.ensureDir(storageFilePath);
await this.core.storageAccess.ensureDir(storageFilePath);
}
const stat = await this.writeFile(storageFilePath, result);
if (!stat) {
@@ -894,7 +894,7 @@ Offline Changed files: ${processFiles.length}`;
* @returns An object containing the ignore and target filters.
*/
parseRegExpSettings() {
const regExpKey = `${this.plugin.settings.syncInternalFilesTargetPatterns}||${this.plugin.settings.syncInternalFilesIgnorePatterns}`;
const regExpKey = `${this.core.settings.syncInternalFilesTargetPatterns}||${this.core.settings.syncInternalFilesIgnorePatterns}`;
let ignoreFilter: CustomRegExp[];
let targetFilter: CustomRegExp[];
if (this.cacheFileRegExps.has(regExpKey)) {
@@ -902,8 +902,8 @@ Offline Changed files: ${processFiles.length}`;
ignoreFilter = cached[1];
targetFilter = cached[0];
} else {
ignoreFilter = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
targetFilter = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
ignoreFilter = getFileRegExp(this.core.settings, "syncInternalFilesIgnorePatterns");
targetFilter = getFileRegExp(this.core.settings, "syncInternalFilesTargetPatterns");
this.cacheFileRegExps.clear();
this.cacheFileRegExps.set(regExpKey, [targetFilter, ignoreFilter]);
}
@@ -941,7 +941,7 @@ Offline Changed files: ${processFiles.length}`;
* @returns An array of ignored file paths (lowercase).
*/
getCustomisationSynchronizationIgnoredFiles(): string[] {
const configDir = this.plugin.app.vault.configDir;
const configDir = this.services.API.getSystemConfigDir();
const key =
JSON.stringify(this.settings.pluginSyncExtendedSetting) + `||${this.settings.usePluginSync}||${configDir}`;
if (this.cacheCustomisationSyncIgnoredFiles.has(key)) {
@@ -1058,7 +1058,7 @@ Common untracked files: ${bothUntracked.length}`;
notifyProgress();
const rel = await semaphores.acquire();
try {
const fileStat = await this.plugin.storageAccess.statHidden(file);
const fileStat = await this.core.storageAccess.statHidden(file);
if (fileStat == null) {
// This should not be happened. But, if it happens, we should skip this.
this._log(`Unexpected error: Failed to stat file during applyOfflineChange :${file}`);
@@ -1206,7 +1206,7 @@ Offline Changed files: ${files.length}`;
// If notified about plug-ins, reloading Obsidian may not be necessary.
const updatePluginId = manifest.id;
const updatePluginName = manifest.name;
this.plugin.confirm.askInPopup(
this.core.confirm.askInPopup(
`updated-${updatePluginId}`,
`Files in ${updatePluginName} has been updated!\nPress {HERE} to reload ${updatePluginName}, or press elsewhere to dismiss this message.`,
(anchor) => {
@@ -1238,9 +1238,9 @@ Offline Changed files: ${files.length}`;
}
// If something changes left, notify for reloading Obsidian.
if (updatedFolders.indexOf(this.plugin.app.vault.configDir) >= 0) {
if (updatedFolders.indexOf(this.services.API.getSystemConfigDir()) >= 0) {
if (!this.services.appLifecycle.isReloadingScheduled()) {
this.plugin.confirm.askInPopup(
this.core.confirm.askInPopup(
`updated-any-hidden`,
`Some setting files have been modified\nPress {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`,
(anchor) => {
@@ -1258,7 +1258,7 @@ Offline Changed files: ${files.length}`;
if (this.settings.suppressNotifyHiddenFilesChange) {
return;
}
const configDir = this.plugin.app.vault.configDir;
const configDir = this.services.API.getSystemConfigDir();
if (!key.startsWith(configDir)) return;
const dirName = key.split("/").slice(0, -1).join("/");
this.queuedNotificationFiles.add(dirName);
@@ -1296,7 +1296,7 @@ Offline Changed files: ${files.length}`;
const eachProgress = onlyInNTimes(100, (progress) => p.log(`Checking ${progress}/${allFileNames.size}`));
for (const file of allFileNames) {
eachProgress();
const storageMTime = await this.plugin.storageAccess.statHidden(file);
const storageMTime = await this.core.storageAccess.statHidden(file);
const mtimeStorage = getComparingMTime(storageMTime);
const dbEntry = allDatabaseMap.get(file)!;
const mtimeDB = getComparingMTime(dbEntry);
@@ -1616,7 +1616,7 @@ Offline Changed files: ${files.length}`;
if (onlyNew) {
// Check the file is new or not.
const dbMTime = getComparingMTime(metaOnDB, includeDeletion); // metaOnDB.mtime;
const storageStat = await this.plugin.storageAccess.statHidden(storageFilePath);
const storageStat = await this.core.storageAccess.statHidden(storageFilePath);
const storageMTimeActual = storageStat?.mtime ?? 0;
const storageMTime =
storageMTimeActual == 0 ? this.getLastProcessedFileMTime(storageFilePath) : storageMTimeActual;
@@ -1670,7 +1670,7 @@ Offline Changed files: ${files.length}`;
async __checkIsNeedToWriteFile(storageFilePath: FilePath, content: string | ArrayBuffer): Promise<boolean> {
try {
const storageContent = await this.plugin.storageAccess.readHiddenFileAuto(storageFilePath);
const storageContent = await this.core.storageAccess.readHiddenFileAuto(storageFilePath);
const needWrite = !(await isDocContentSame(storageContent, content));
return needWrite;
} catch (ex) {
@@ -1682,7 +1682,7 @@ Offline Changed files: ${files.length}`;
async __writeFile(storageFilePath: FilePath, fileOnDB: LoadedEntry, force: boolean): Promise<false | UXStat> {
try {
const statBefore = await this.plugin.storageAccess.statHidden(storageFilePath);
const statBefore = await this.core.storageAccess.statHidden(storageFilePath);
const isExist = statBefore != null;
const writeContent = readContent(fileOnDB);
await this.ensureDir(storageFilePath);
@@ -1768,7 +1768,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
choices.push(CHOICE_MERGE);
choices.push(CHOICE_DISABLE);
const ret = await this.plugin.confirm.confirmWithMessage(
const ret = await this.core.confirm.confirmWithMessage(
"Hidden file sync",
message,
choices,
@@ -1787,12 +1787,12 @@ ${messageFetch}${messageOverwrite}${messageMerge}
}
private _allSuspendExtraSync(): Promise<boolean> {
if (this.plugin.settings.syncInternalFiles) {
if (this.core.settings.syncInternalFiles) {
this._log(
"Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.",
LOG_LEVEL_NOTICE
);
this.plugin.settings.syncInternalFiles = false;
this.core.settings.syncInternalFiles = false;
}
return Promise.resolve(true);
}
@@ -1815,9 +1815,15 @@ ${messageFetch}${messageOverwrite}${messageMerge}
}
if (mode == "DISABLE" || mode == "DISABLE_HIDDEN") {
// await this.plugin.$allSuspendExtraSync();
this.plugin.settings.syncInternalFiles = false;
await this.plugin.saveSettings();
// await this.core.$allSuspendExtraSync();
await this.core.services.setting.applyPartial(
{
syncInternalFiles: false,
},
true
);
// this.core.settings.syncInternalFiles = false;
// await this.core.saveSettings();
return;
}
this._log("Gathering files for enabling Hidden File Sync", LOG_LEVEL_NOTICE);
@@ -1828,10 +1834,17 @@ ${messageFetch}${messageOverwrite}${messageMerge}
} else if (mode == "MERGE") {
await this.initialiseInternalFileSync("safe", true);
}
this.plugin.settings.useAdvancedMode = true;
this.plugin.settings.syncInternalFiles = true;
await this.core.services.setting.applyPartial(
{
useAdvancedMode: true,
syncInternalFiles: true,
},
true
);
// this.plugin.settings.useAdvancedMode = true;
// this.plugin.settings.syncInternalFiles = true;
await this.plugin.saveSettings();
// await this.plugin.saveSettings();
this._log(`Done! Restarting the app is strongly recommended!`, LOG_LEVEL_NOTICE);
}
// <-- Configuration handling
@@ -1851,7 +1864,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
const files = fileNames.map(async (e) => {
return {
path: e,
stat: await this.plugin.storageAccess.statHidden(e), // this.plugin.vaultAccess.adapterStat(e)
stat: await this.core.storageAccess.statHidden(e), // this.plugin.vaultAccess.adapterStat(e)
};
});
const result: InternalFileInfo[] = [];
@@ -1956,5 +1969,6 @@ ${messageFetch}${messageOverwrite}${messageMerge}
services.setting.suspendExtraSync.addHandler(this._allSuspendExtraSync.bind(this));
services.setting.suggestOptionalFeatures.addHandler(this._allAskUsingOptionalSyncFeature.bind(this));
services.setting.enableOptionalFeature.addHandler(this._allConfigureOptionalSyncFeature.bind(this));
services.vault.isTargetFileInExtra.addHandler(this.isTargetFile.bind(this));
}
}

View File

@@ -16,18 +16,22 @@ import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils.ts";
let noticeIndex = 0;
export abstract class LiveSyncCommands {
/**
* @deprecated This class is deprecated. Please use core
*/
plugin: ObsidianLiveSyncPlugin;
core: LiveSyncCore;
get app() {
return this.plugin.app;
}
get settings() {
return this.plugin.settings;
return this.core.settings;
}
get localDatabase() {
return this.plugin.localDatabase;
return this.core.localDatabase;
}
get services() {
return this.plugin.services;
return this.core.services;
}
// id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
@@ -41,9 +45,10 @@ export abstract class LiveSyncCommands {
return this.services.path.getPath(entry);
}
constructor(plugin: ObsidianLiveSyncPlugin) {
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
this.plugin = plugin;
this.onBindFunction(plugin, plugin.services);
this.core = core;
this.onBindFunction(this.core, this.core.services);
this._log = createInstanceLogFunction(this.constructor.name, this.services.API);
__$checkInstanceBinding(this);
}
@@ -51,7 +56,7 @@ export abstract class LiveSyncCommands {
abstract onload(): void | Promise<void>;
_isMainReady() {
return this.plugin.services.appLifecycle.isReady();
return this.services.appLifecycle.isReady();
}
_isMainSuspended() {
return this.services.appLifecycle.isSuspended();

View File

@@ -71,7 +71,7 @@ export class LocalDatabaseMaintenance extends LiveSyncCommands {
async confirm(title: string, message: string, affirmative = "Yes", negative = "No") {
return (
(await this.plugin.confirm.askSelectStringDialogue(message, [affirmative, negative], {
(await this.core.confirm.askSelectStringDialogue(message, [affirmative, negative], {
title,
defaultAction: affirmative,
})) === affirmative
@@ -302,7 +302,7 @@ Note: **Make sure to synchronise all devices before deletion.**
}
async scanUnusedChunks() {
const kvDB = this.plugin.kvDB;
const kvDB = this.core.kvDB;
const chunkSet = (await kvDB.get<Set<DocumentID>>(DB_KEY_CHUNK_SET)) || new Set();
const chunkUsageMap = (await kvDB.get<ChunkUsageMap>(DB_KEY_DOC_USAGE_MAP)) || new Map();
const KEEP_MAX_REVS = 10;
@@ -328,7 +328,7 @@ Note: **Make sure to synchronise all devices before deletion.**
async trackChanges(fromStart: boolean = false, showNotice: boolean = false) {
if (!this.isAvailable()) return;
const logLevel = showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
const kvDB = this.plugin.kvDB;
const kvDB = this.core.kvDB;
const previousSeq = fromStart ? "" : await kvDB.get<string>(DB_KEY_SEQ);
const chunkSet = (await kvDB.get<Set<DocumentID>>(DB_KEY_CHUNK_SET)) || new Set();
@@ -457,7 +457,7 @@ Are you ready to delete unused chunks?`;
const BUTTON_OK = `Yes, delete chunks`;
const BUTTON_CANCEL = "Cancel";
const result = await this.plugin.confirm.askSelectStringDialogue(
const result = await this.core.confirm.askSelectStringDialogue(
confirmMessage,
[BUTTON_OK, BUTTON_CANCEL] as const,
{
@@ -506,7 +506,7 @@ Are you ready to delete unused chunks?`;
const message = `Garbage Collection completed.
Success: ${successCount}, Errored: ${errored}`;
this._log(message, logLevel);
const kvDB = this.plugin.kvDB;
const kvDB = this.core.kvDB;
await kvDB.set(DB_KEY_CHUNK_SET, chunkSet);
}
@@ -723,7 +723,7 @@ Success: ${successCount}, Errored: ${errored}`;
}
async compactDatabase() {
const replicator = this.plugin.replicator as LiveSyncCouchDBReplicator;
const replicator = this.core.replicator as LiveSyncCouchDBReplicator;
const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true);
if (!remote) {
this._notice("Failed to connect to remote for compaction.", "gc-compact");
@@ -767,7 +767,7 @@ Success: ${successCount}, Errored: ${errored}`;
// Temporarily set revs_limit to 1, perform compaction, and restore the original revs_limit.
// Very dangerous operation, so now suppressed.
return false;
const replicator = this.plugin.replicator as LiveSyncCouchDBReplicator;
const replicator = this.core.replicator as LiveSyncCouchDBReplicator;
const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true);
if (!remote) {
this._notice("Failed to connect to remote for compaction.");
@@ -822,7 +822,7 @@ Success: ${successCount}, Errored: ${errored}`;
}
async gcv3() {
if (!this.isAvailable()) return;
const replicator = this.plugin.replicator as LiveSyncCouchDBReplicator;
const replicator = this.core.replicator as LiveSyncCouchDBReplicator;
// Start one-shot replication to ensure all changes are synced before GC.
const r0 = await replicator.openOneShotReplication(this.settings, false, false, "sync");
if (!r0) {
@@ -835,7 +835,7 @@ Success: ${successCount}, Errored: ${errored}`;
// Delete the chunk, but first verify the following:
// Fetch the list of accepted nodes from the replicator.
const OPTION_CANCEL = "Cancel Garbage Collection";
const info = await this.plugin.replicator.getConnectedDeviceList();
const info = await this.core.replicator.getConnectedDeviceList();
if (!info) {
this._notice("No connected device information found. Cancelling Garbage Collection.");
return;
@@ -855,7 +855,7 @@ It is preferable to update all devices if possible. If you have any devices that
const OPTION_IGNORE = "Ignore and Proceed";
// const OPTION_DELETE = "Delete them and proceed";
const buttons = [OPTION_CANCEL, OPTION_IGNORE] as const;
const result = await this.plugin.confirm.askSelectStringDialogue(message, buttons, {
const result = await this.core.confirm.askSelectStringDialogue(message, buttons, {
title: "Node Information Missing",
defaultAction: OPTION_CANCEL,
});
@@ -896,7 +896,7 @@ This may indicate that some devices have not completed synchronisation, which co
: `All devices have the same progress value (${maxProgress}). Your devices seem to be synchronised. And be able to proceed with Garbage Collection.`;
const buttons = [OPTION_PROCEED, OPTION_CANCEL] as const;
const defaultAction = progressDifference != 0 ? OPTION_CANCEL : OPTION_PROCEED;
const result = await this.plugin.confirm.askSelectStringDialogue(message + "\n\n" + detail, buttons, {
const result = await this.core.confirm.askSelectStringDialogue(message + "\n\n" + detail, buttons, {
title: "Garbage Collection Confirmation",
defaultAction,
});

View File

@@ -38,14 +38,14 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase
storeP2PStatusLine = reactiveSource("");
getSettings(): P2PSyncSetting {
return this.plugin.settings;
return this.core.settings;
}
getDB() {
return this.plugin.localDatabase.localDatabase;
return this.core.localDatabase.localDatabase;
}
get confirm(): Confirm {
return this.plugin.confirm;
return this.core.confirm;
}
_simpleStore!: SimpleStore<any>;
@@ -53,8 +53,8 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase
return this._simpleStore;
}
constructor(plugin: ObsidianLiveSyncPlugin) {
super(plugin);
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
super(plugin, core);
setReplicatorFunc(() => this._replicatorInstance);
addP2PEventHandlers(this);
this.afterConstructor();
@@ -72,7 +72,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
const settings = { ...this.settings, ...settingOverride };
if (settings.remoteType == REMOTE_P2P) {
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin));
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin.core));
}
return undefined!;
}
@@ -183,12 +183,12 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase
}
private 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 = "";
this.plugin.core.settings.P2P_Enabled = false;
this.plugin.core.settings.P2P_AutoAccepting = AutoAccepting.NONE;
this.plugin.core.settings.P2P_AutoBroadcast = false;
this.plugin.core.settings.P2P_AutoStart = false;
this.plugin.core.settings.P2P_AutoSyncPeers = "";
this.plugin.core.settings.P2P_AutoWatchPeers = "";
return await Promise.resolve(true);
}
@@ -201,7 +201,10 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase
}
async _everyOnloadStart(): Promise<boolean> {
this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, this.plugin));
this.plugin.registerView(
VIEW_TYPE_P2P,
(leaf) => new P2PReplicatorPaneView(leaf, this.plugin.core, this.plugin)
);
this.plugin.addCommand({
id: "open-p2p-replicator",
name: "P2P Sync : Open P2P Replicator",

View File

@@ -20,17 +20,18 @@
import { type P2PReplicatorStatus } from "../../../lib/src/replication/trystero/TrysteroReplicator";
import { $msg as _msg } from "../../../lib/src/common/i18n";
import { SETTING_KEY_P2P_DEVICE_NAME } from "../../../lib/src/common/types";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
interface Props {
plugin: PluginShim;
cmdSync: CommandShim;
core: LiveSyncBaseCore;
}
let { plugin, cmdSync }: Props = $props();
let { cmdSync, core }: Props = $props();
// const cmdSync = plugin.getAddOn<P2PReplicator>("P2PReplicator")!;
setContext("getReplicator", () => cmdSync);
const initialSettings = { ...plugin.settings };
const currentSettings = () => core.services.setting.currentSettings() as P2PSyncSetting;
const initialSettings = { ...currentSettings() } as P2PSyncSetting;
let settings = $state<P2PSyncSetting>(initialSettings);
@@ -70,21 +71,33 @@
);
async function saveAndApply() {
const newSettings = {
...plugin.settings,
P2P_Enabled: eP2PEnabled,
P2P_relays: eRelay,
P2P_roomID: eRoomId,
P2P_passphrase: ePassword,
P2P_AppID: eAppId,
P2P_AutoAccepting: eAutoAccept ? AutoAccepting.ALL : AutoAccepting.NONE,
P2P_AutoStart: eAutoStart,
P2P_AutoBroadcast: eAutoBroadcast,
};
plugin.settings = newSettings;
// const newSettings = {
// ...currentSettings(),
// P2P_Enabled: eP2PEnabled,
// P2P_relays: eRelay,
// P2P_roomID: eRoomId,
// P2P_passphrase: ePassword,
// P2P_AppID: eAppId,
// P2P_AutoAccepting: eAutoAccept ? AutoAccepting.ALL : AutoAccepting.NONE,
// P2P_AutoStart: eAutoStart,
// P2P_AutoBroadcast: eAutoBroadcast,
// };
await core.services.setting.applyPartial(
{
P2P_Enabled: eP2PEnabled,
P2P_relays: eRelay,
P2P_roomID: eRoomId,
P2P_passphrase: ePassword,
P2P_AppID: eAppId,
P2P_AutoAccepting: eAutoAccept ? AutoAccepting.ALL : AutoAccepting.NONE,
P2P_AutoStart: eAutoStart,
P2P_AutoBroadcast: eAutoBroadcast,
},
true
);
cmdSync.setConfig(SETTING_KEY_P2P_DEVICE_NAME, eDeviceName);
deviceName = eDeviceName;
await plugin.saveSettings();
// await plugin.saveSettings();
}
async function revert() {
eP2PEnabled = settings.P2P_Enabled;
@@ -100,8 +113,9 @@
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
let replicatorInfo = $state<P2PReplicatorStatus | undefined>(undefined);
const applyLoadSettings = (d: P2PSyncSetting, force: boolean) => {
if(force){
const initDeviceName = cmdSync.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? plugin.services.vault.getVaultName();
if (force) {
const initDeviceName =
cmdSync.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? core.services.vault.getVaultName();
deviceName = initDeviceName;
eDeviceName = initDeviceName;
}
@@ -124,7 +138,7 @@
closeServer();
});
const rx = eventHub.onEvent(EVENT_LAYOUT_READY, () => {
applyLoadSettings(plugin.settings, true);
applyLoadSettings(currentSettings(), true);
});
const r2 = eventHub.onEvent(EVENT_SERVER_STATUS, (status) => {
serverInfo = status;
@@ -254,7 +268,7 @@
cmdSync.setConfig(initialDialogStatusKey, JSON.stringify(dialogStatus));
});
let isObsidian = $derived.by(() => {
return plugin.services.API.getPlatform() === "obsidian";
return core.services.API.getPlatform() === "obsidian";
});
</script>

View File

@@ -13,6 +13,7 @@ import {
EVENT_P2P_PEER_SHOW_EXTRA_MENU,
type PeerStatus,
} from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
export const VIEW_TYPE_P2P = "p2p-replicator";
function addToList(item: string, list: string) {
@@ -34,7 +35,8 @@ function removeFromList(item: string, list: string) {
}
export class P2PReplicatorPaneView extends SvelteItemView {
plugin: ObsidianLiveSyncPlugin;
// plugin: ObsidianLiveSyncPlugin;
core: LiveSyncBaseCore;
override icon = "waypoints";
title: string = "";
override navigation = false;
@@ -43,7 +45,7 @@ export class P2PReplicatorPaneView extends SvelteItemView {
return "waypoints";
}
get replicator() {
const r = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
const r = this.core.getAddOn<P2PReplicator>(P2PReplicator.name);
if (!r || !r._replicatorInstance) {
throw new Error("Replicator not found");
}
@@ -66,7 +68,7 @@ export class P2PReplicatorPaneView extends SvelteItemView {
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(
const yn = await this.core.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,
@@ -78,7 +80,7 @@ And you can also drop the local database to rebuild from the remote device.`,
if (yn === DROP || yn === KEEP) {
if (yn === DROP) {
if (remoteConfig.remoteType !== REMOTE_P2P) {
const yn2 = await this.plugin.confirm.askYesNoDialog(
const yn2 = await this.core.confirm.askYesNoDialog(
`Do you want to set the remote type to "P2P Sync" to rebuild by "P2P replication"?`,
{
title: "Rebuild from remote device",
@@ -90,12 +92,14 @@ And you can also drop the local database to rebuild from the remote device.`,
}
}
}
this.plugin.settings = remoteConfig;
await this.plugin.saveSettings();
// this.plugin.settings = remoteConfig;
// await this.plugin.saveSettings();
await this.core.services.setting.applyPartial(remoteConfig);
if (yn === DROP) {
await this.plugin.rebuilder.scheduleFetch();
await this.core.rebuilder.scheduleFetch();
} else {
this.plugin.services.appLifecycle.scheduleRestart();
this.core.services.appLifecycle.scheduleRestart();
}
} else {
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
@@ -113,19 +117,24 @@ And you can also drop the local database to rebuild from the remote device.`,
} as const;
const targetSetting = settingMap[prop];
const currentSettingAll = this.core.services.setting.currentSettings();
const currentSetting = {
[targetSetting]: currentSettingAll ? currentSettingAll[targetSetting] : "",
};
if (peer[prop]) {
this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]);
await this.plugin.saveSettings();
// this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]);
// await this.plugin.saveSettings();
currentSetting[targetSetting] = removeFromList(peer.name, currentSetting[targetSetting]);
} else {
this.plugin.settings[targetSetting] = addToList(peer.name, this.plugin.settings[targetSetting]);
await this.plugin.saveSettings();
currentSetting[targetSetting] = addToList(peer.name, currentSetting[targetSetting]);
}
await this.plugin.saveSettings();
await this.core.services.setting.applyPartial(currentSetting, true);
}
m?: Menu;
constructor(leaf: WorkspaceLeaf, plugin: ObsidianLiveSyncPlugin) {
constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, plugin: ObsidianLiveSyncPlugin) {
super(leaf);
this.plugin = plugin;
// this.plugin = plugin;
this.core = core;
eventHub.onEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, ({ peer, event }) => {
if (this.m) {
this.m.hide();
@@ -183,15 +192,15 @@ And you can also drop the local database to rebuild from the remote device.`,
}
}
instantiateComponent(target: HTMLElement) {
const cmdSync = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
const cmdSync = this.core.getAddOn<P2PReplicator>(P2PReplicator.name);
if (!cmdSync) {
throw new Error("Replicator not found");
}
return mount(ReplicatorPaneComponent, {
target: target,
props: {
plugin: cmdSync.plugin,
cmdSync: cmdSync,
core: this.core,
},
});
}