mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-04-24 01:48:34 +00:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user