Refactored, please refer updates.md

This commit is contained in:
vorotamoroz
2026-02-13 12:02:31 +00:00
parent 1b5ca9e52c
commit fb59c4a723
22 changed files with 212 additions and 158 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Self-hosted LiveSync", "name": "Self-hosted LiveSync",
"version": "0.25.43", "version": "0.25.43-patched-1",
"minAppVersion": "0.9.12", "minAppVersion": "0.9.12",
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"author": "vorotamoroz", "author": "vorotamoroz",
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.25.43", "version": "0.25.43-patched-1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.25.43", "version": "0.25.43-patched-1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.808.0", "@aws-sdk/client-s3": "^3.808.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.25.43", "version": "0.25.43-patched-1",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"main": "main.js", "main": "main.js",
"type": "module", "type": "module",
-1
View File
@@ -401,7 +401,6 @@ export function displayRev(rev: string) {
return `${number}-${hash.substring(0, 6)}`; return `${number}-${hash.substring(0, 6)}`;
} }
export function getLogLevel(showNotice: boolean) { export function getLogLevel(showNotice: boolean) {
return showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO; return showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
} }
+1 -1
Submodule src/lib updated: af0189376f...532f25f947
+7 -7
View File
@@ -126,31 +126,31 @@ export default class ObsidianLiveSyncPlugin
new ModuleRemoteGovernor(this), new ModuleRemoteGovernor(this),
new ModuleTargetFilter(this), new ModuleTargetFilter(this),
new ModulePeriodicProcess(this), new ModulePeriodicProcess(this),
// Obsidian modules // Essential Modules
new ModuleKeyValueDB(this), new ModuleKeyValueDB(this),
new ModuleInitializerFile(this), new ModuleInitializerFile(this),
new ModuleObsidianAPI(this, this), new ModuleObsidianAPI(this, this),
new ModuleObsidianEvents(this, this), new ModuleObsidianEvents(this, this),
new ModuleFileAccessObsidian(this, this), new ModuleFileAccessObsidian(this, this),
new ModuleObsidianSettings(this, this), new ModuleObsidianSettings(this),
new ModuleResolvingMismatchedTweaks(this), new ModuleResolvingMismatchedTweaks(this),
new ModuleObsidianSettingsAsMarkdown(this, this), new ModuleObsidianSettingsAsMarkdown(this),
new ModuleObsidianSettingDialogue(this, this), new ModuleObsidianSettingDialogue(this, this),
new ModuleLog(this, this), new ModuleLog(this, this),
new ModuleObsidianMenu(this, this), new ModuleObsidianMenu(this),
new ModuleRebuilder(this), new ModuleRebuilder(this),
new ModuleSetupObsidian(this, this), new ModuleSetupObsidian(this),
new ModuleObsidianDocumentHistory(this, this), new ModuleObsidianDocumentHistory(this, this),
new ModuleMigration(this), new ModuleMigration(this),
new ModuleRedFlag(this), new ModuleRedFlag(this),
new ModuleInteractiveConflictResolver(this, this), new ModuleInteractiveConflictResolver(this, this),
new ModuleObsidianGlobalHistory(this, this), new ModuleObsidianGlobalHistory(this, this),
new ModuleCheckRemoteSize(this, this), new ModuleCheckRemoteSize(this),
// Test and Dev Modules // Test and Dev Modules
new ModuleDev(this, this), new ModuleDev(this, this),
new ModuleReplicateTest(this, this), new ModuleReplicateTest(this, this),
new ModuleIntegratedTest(this, this), new ModuleIntegratedTest(this, this),
new SetupManager(this, this), new SetupManager(this),
] as (IObsidianModule | AbstractModule)[]; ] as (IObsidianModule | AbstractModule)[];
getModule<T extends IObsidianModule>(constructor: new (...args: any[]) => T): T { getModule<T extends IObsidianModule>(constructor: new (...args: any[]) => T): T {
+5
View File
@@ -13,6 +13,11 @@ export abstract class AbstractModule {
Logger(msg, level, key); Logger(msg, level, key);
}; };
addCommand = this.services.API.addCommand.bind(this.services.API);
registerView = this.services.API.registerWindow.bind(this.services.API);
addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API);
registerObsidianProtocolHandler = this.services.API.registerProtocolHandler.bind(this.services.API);
get localDatabase() { get localDatabase() {
return this.core.localDatabase; return this.core.localDatabase;
} }
-5
View File
@@ -10,11 +10,6 @@ export type ModuleKeys = keyof IObsidianModule;
export type ChainableModuleProps = ChainableExecuteFunction<ObsidianLiveSyncPlugin>; export type ChainableModuleProps = ChainableExecuteFunction<ObsidianLiveSyncPlugin>;
export abstract class AbstractObsidianModule extends AbstractModule { export abstract class AbstractObsidianModule extends AbstractModule {
addCommand = this.services.API.addCommand.bind(this.services.API);
registerView = this.services.API.registerWindow.bind(this.services.API);
addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API);
registerObsidianProtocolHandler = this.services.API.registerProtocolHandler.bind(this.services.API);
get app() { get app() {
return this.plugin.app; return this.plugin.app;
} }
+107 -98
View File
@@ -1,146 +1,155 @@
import { LRUCache } from "octagonal-wheels/memory/LRUCache"; import { getStoragePathFromUXFileInfo } from "../../common/utils";
import { getStoragePathFromUXFileInfo, useMemo } from "../../common/utils"; import { LOG_LEVEL_DEBUG, LOG_LEVEL_VERBOSE, type UXFileInfoStub } from "../../lib/src/common/types";
import {
LOG_LEVEL_VERBOSE,
type FilePathWithPrefix,
type ObsidianLiveSyncSettings,
type UXFileInfoStub,
} from "../../lib/src/common/types";
import { isAcceptedAll } from "../../lib/src/string_and_binary/path"; import { isAcceptedAll } from "../../lib/src/string_and_binary/path";
import { AbstractModule } from "../AbstractModule"; import { AbstractModule } from "../AbstractModule";
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
import { isDirty } from "../../lib/src/common/utils";
import type { LiveSyncCore } from "../../main"; import type { LiveSyncCore } from "../../main";
import { Computed } from "octagonal-wheels/dataobject/Computed";
export class ModuleTargetFilter extends AbstractModule { export class ModuleTargetFilter extends AbstractModule {
reloadIgnoreFiles() { ignoreFiles: string[] = [];
private refreshSettings() {
this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim()); this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim());
return Promise.resolve(true);
} }
private _everyOnload(): Promise<boolean> { private _everyOnload(): Promise<boolean> {
this.reloadIgnoreFiles(); void this.refreshSettings();
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => {
this.reloadIgnoreFiles();
});
eventHub.onEvent(EVENT_REQUEST_RELOAD_SETTING_TAB, () => {
this.reloadIgnoreFiles();
});
return Promise.resolve(true); return Promise.resolve(true);
} }
_markFileListPossiblyChanged(): void { _markFileListPossiblyChanged(): void {
this.totalFileEventCount++; this.totalFileEventCount++;
} }
totalFileEventCount = 0;
get fileListPossiblyChanged() {
if (isDirty("totalFileEventCount", this.totalFileEventCount)) {
return true;
}
return false;
}
private async _isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) { fileCountMap = new Computed({
const fileCount = useMemo<Record<string, number>>( evaluation: (fileEventCount: number) => {
{ const vaultFiles = this.core.storageAccess.getFileNames().sort();
key: "fileCount", // forceUpdate: !keepFileCheckList, const fileCountMap: Record<string, number> = {};
}, for (const file of vaultFiles) {
(ctx, prev) => { const lc = file.toLowerCase();
if (keepFileCheckList && prev) return prev; if (!fileCountMap[lc]) {
if (!keepFileCheckList && prev && !this.fileListPossiblyChanged) { fileCountMap[lc] = 1;
return prev; } else {
fileCountMap[lc]++;
} }
const fileList = (ctx.get("fileList") ?? []) as FilePathWithPrefix[];
// const fileNameList = (ctx.get("fileNameList") ?? []) as FilePath[];
// const fileNames =
const vaultFiles = this.core.storageAccess.getFileNames().sort();
if (prev && vaultFiles.length == fileList.length) {
const fl3 = new Set([...fileList, ...vaultFiles]);
if (fileList.length == fl3.size && vaultFiles.length == fl3.size) {
return prev;
}
}
ctx.set("fileList", vaultFiles);
const fileCount: Record<string, number> = {};
for (const file of vaultFiles) {
const lc = file.toLowerCase();
if (!fileCount[lc]) {
fileCount[lc] = 1;
} else {
fileCount[lc]++;
}
}
return fileCount;
} }
); return fileCountMap;
},
requiresUpdate: (args, previousArgs, previousResult) => {
if (!previousResult) return true;
if (previousResult instanceof Error) return true;
if (!previousArgs) return true;
if (args[0] === previousArgs[0]) {
return false;
}
return true;
},
});
totalFileEventCount = 0;
private async _isTargetFileByFileNameDuplication(file: string | UXFileInfoStub) {
await this.fileCountMap.updateValue(this.totalFileEventCount);
const fileCountMap = this.fileCountMap.value;
if (!fileCountMap) {
this._log("File count map is not ready yet.");
return false;
}
const filepath = getStoragePathFromUXFileInfo(file); const filepath = getStoragePathFromUXFileInfo(file);
const lc = filepath.toLowerCase(); const lc = filepath.toLowerCase();
if (this.services.vault.shouldCheckCaseInsensitively()) { if (this.services.vault.shouldCheckCaseInsensitively()) {
if (lc in fileCount && fileCount[lc] > 1) { if (lc in fileCountMap && fileCountMap[lc] > 1) {
this._log("File is duplicated (case-insensitive): " + filepath);
return false; return false;
} }
} }
const fileNameLC = getStoragePathFromUXFileInfo(file).split("/").pop()?.toLowerCase(); this._log("File is not duplicated: " + filepath, LOG_LEVEL_DEBUG);
if (this.settings.useIgnoreFiles) {
if (this.ignoreFiles.some((e) => e.toLowerCase() == fileNameLC)) {
// We must reload ignore files due to the its change.
await this.readIgnoreFile(filepath);
}
if (await this.services.vault.isIgnoredByIgnoreFile(file)) {
return false;
}
}
if (!this.localDatabase?.isTargetFile(filepath)) return false;
return true; return true;
} }
ignoreFileCache = new LRUCache<string, string[] | false>(300, 250000, true); private ignoreFileCacheMap = new Map<string, string[] | undefined | false>();
ignoreFiles = [] as string[];
async readIgnoreFile(path: string) { private invalidateIgnoreFileCache(path: string) {
// This erases `/path/to/.ignorefile` from cache, therefore, next access will reload it.
// When detecting edited the ignore file, this method should be called.
// Do not check whether it exists in cache or not; just delete it.
const key = path.toLowerCase();
this.ignoreFileCacheMap.delete(key);
}
private async getIgnoreFile(path: string): Promise<string[] | false> {
const key = path.toLowerCase();
const cached = this.ignoreFileCacheMap.get(key);
if (cached !== undefined) {
// if cached is not undefined, cache hit (neither exists or not exists, string[] or false).
return cached;
}
try { try {
// this._log(`[ignore]Reading ignore file: ${path}`, LOG_LEVEL_VERBOSE); // load the ignore file
if (!(await this.core.storageAccess.isExistsIncludeHidden(path))) { if (!(await this.core.storageAccess.isExistsIncludeHidden(path))) {
this.ignoreFileCache.set(path, false); // file does not exist, cache as not exists
// this._log(`[ignore]Ignore file not found: ${path}`, LOG_LEVEL_VERBOSE); this.ignoreFileCacheMap.set(key, false);
return false; return false;
} }
const file = await this.core.storageAccess.readHiddenFileText(path); const file = await this.core.storageAccess.readHiddenFileText(path);
const gitignore = file.split(/\r?\n/g); const gitignore = file
this.ignoreFileCache.set(path, gitignore); .split(/\r?\n/g)
this._log(`[ignore]Ignore file loaded: ${path}`, LOG_LEVEL_VERBOSE); .map((e) => e.replace(/\r$/, ""))
.map((e) => e.trim());
this.ignoreFileCacheMap.set(key, gitignore);
this._log(`[ignore] Ignore file loaded: ${path}`, LOG_LEVEL_VERBOSE);
return gitignore; return gitignore;
} catch (ex) { } catch (ex) {
this._log(`[ignore]Failed to read ignore file ${path}`); // Failed to read the ignore file, delete cache.
this._log(`[ignore] Failed to read ignore file ${path}`);
this._log(ex, LOG_LEVEL_VERBOSE); this._log(ex, LOG_LEVEL_VERBOSE);
this.ignoreFileCache.set(path, false); this.ignoreFileCacheMap.set(key, undefined);
return false; return false;
} }
} }
async getIgnoreFile(path: string) {
if (this.ignoreFileCache.has(path)) { private async _isTargetFileByLocalDB(file: string | UXFileInfoStub) {
return this.ignoreFileCache.get(path) ?? false;
} else {
return await this.readIgnoreFile(path);
}
}
private async _isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
if (!this.settings.useIgnoreFiles) {
return false;
}
const filepath = getStoragePathFromUXFileInfo(file); const filepath = getStoragePathFromUXFileInfo(file);
if (this.ignoreFileCache.has(filepath)) { if (!this.localDatabase?.isTargetFile(filepath)) {
// Renew this._log("File is not target by local DB: " + filepath);
await this.readIgnoreFile(filepath); return false;
} }
if (!(await isAcceptedAll(filepath, this.ignoreFiles, (filename) => this.getIgnoreFile(filename)))) { this._log("File is target by local DB: " + filepath, LOG_LEVEL_DEBUG);
return await Promise.resolve(true);
}
private async _isTargetFileFinal(file: string | UXFileInfoStub) {
this._log("File is target finally: " + getStoragePathFromUXFileInfo(file), LOG_LEVEL_DEBUG);
return await Promise.resolve(true);
}
private async _isTargetIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
if (!this.settings.useIgnoreFiles) {
return true; return true;
} }
return false; const filepath = getStoragePathFromUXFileInfo(file);
this.invalidateIgnoreFileCache(filepath);
this._log("Checking ignore files for: " + filepath, LOG_LEVEL_DEBUG);
if (!(await isAcceptedAll(filepath, this.ignoreFiles, (filename) => this.getIgnoreFile(filename)))) {
this._log("File is ignored by ignore files: " + filepath);
return false;
}
this._log("File is not ignored by ignore files: " + filepath, LOG_LEVEL_DEBUG);
return true;
} }
onBindFunction(core: LiveSyncCore, services: typeof core.services): void { onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.vault.markFileListPossiblyChanged.setHandler(this._markFileListPossiblyChanged.bind(this)); services.vault.markFileListPossiblyChanged.setHandler(this._markFileListPossiblyChanged.bind(this));
services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this));
services.vault.isIgnoredByIgnoreFile.setHandler(this._isIgnoredByIgnoreFiles.bind(this)); services.vault.isIgnoredByIgnoreFile.setHandler(this._isTargetIgnoredByIgnoreFiles.bind(this));
services.vault.isTargetFile.setHandler(this._isTargetFile.bind(this)); services.vault.isTargetFile.addHandler(this._isTargetFileByFileNameDuplication.bind(this));
services.vault.isTargetFile.addHandler(this._isTargetIgnoredByIgnoreFiles.bind(this));
services.vault.isTargetFile.addHandler(this._isTargetFileByLocalDB.bind(this));
services.vault.isTargetFile.addHandler(this._isTargetFileFinal.bind(this));
services.setting.onSettingRealised.addHandler(this.refreshSettings.bind(this));
// services.vault.isTargetFile.use((ctx, next) => {
// const [fileName, keepFileCheckList] = ctx.args;
// const file = getS
// });
} }
} }
@@ -56,10 +56,6 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
restoreState() { restoreState() {
return this.vaultManager.restoreState(); return this.vaultManager.restoreState();
} }
private _everyOnload(): Promise<boolean> {
this.core.storageAccess = this;
return Promise.resolve(true);
}
async _everyOnFirstInitialize(): Promise<boolean> { async _everyOnFirstInitialize(): Promise<boolean> {
await this.vaultManager.beginWatch(); await this.vaultManager.beginWatch();
return Promise.resolve(true); return Promise.resolve(true);
@@ -76,6 +72,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
_everyOnloadStart(): Promise<boolean> { _everyOnloadStart(): Promise<boolean> {
this.vaultAccess = new SerializedFileAccess(this.app, this.plugin, this); this.vaultAccess = new SerializedFileAccess(this.app, this.plugin, this);
this.core.storageAccess = this;
return Promise.resolve(true); return Promise.resolve(true);
} }
@@ -379,7 +376,6 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this)); services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this));
services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this));
services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this));
services.fileProcessing.commitPendingFileEvents.addHandler(this._everyCommitPendingFileEvent.bind(this)); services.fileProcessing.commitPendingFileEvents.addHandler(this._everyCommitPendingFileEvent.bind(this));
} }
} }
@@ -2,10 +2,10 @@ import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-w
import { sizeToHumanReadable } from "octagonal-wheels/number"; import { sizeToHumanReadable } from "octagonal-wheels/number";
import { $msg } from "src/lib/src/common/i18n.ts"; import { $msg } from "src/lib/src/common/i18n.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "../../main.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { EVENT_REQUEST_CHECK_REMOTE_SIZE, eventHub } from "@/common/events.ts"; import { EVENT_REQUEST_CHECK_REMOTE_SIZE, eventHub } from "@/common/events.ts";
import { AbstractModule } from "../AbstractModule.ts";
export class ModuleCheckRemoteSize extends AbstractObsidianModule { export class ModuleCheckRemoteSize extends AbstractModule {
checkRemoteSize(): Promise<boolean> { checkRemoteSize(): Promise<boolean> {
this.settings.notifyThresholdOfRemoteStorageSize = 1; this.settings.notifyThresholdOfRemoteStorageSize = 1;
return this._allScanStat(); return this._allScanStat();
@@ -31,13 +31,8 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
return Promise.resolve(true); return Promise.resolve(true);
} }
private _performRestart(): void {
this.__performAppReload();
}
__performAppReload() { __performAppReload() {
//@ts-ignore this.services.appLifecycle.performRestart();
this.app.commands.executeCommandById("app:reload");
} }
initialCallback: any; initialCallback: any;
@@ -193,6 +188,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
} }
}); });
} }
// TODO: separate
private _scheduleAppReload() { private _scheduleAppReload() {
if (!this.core._totalProcessingCount) { if (!this.core._totalProcessingCount) {
const __tick = reactiveSource(0); const __tick = reactiveSource(0);
@@ -246,7 +242,6 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
onBindFunction(core: LiveSyncCore, services: typeof core.services): void { onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this));
services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this));
services.appLifecycle.performRestart.setHandler(this._performRestart.bind(this));
services.appLifecycle.askRestart.setHandler(this._askReload.bind(this)); services.appLifecycle.askRestart.setHandler(this._askReload.bind(this));
services.appLifecycle.scheduleRestart.setHandler(this._scheduleAppReload.bind(this)); services.appLifecycle.scheduleRestart.setHandler(this._scheduleAppReload.bind(this));
} }
@@ -1,11 +1,11 @@
import { fireAndForget } from "octagonal-wheels/promises"; import { fireAndForget } from "octagonal-wheels/promises";
import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts"; import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts";
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts"; import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { $msg } from "src/lib/src/common/i18n.ts"; import { $msg } from "src/lib/src/common/i18n.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "../../main.ts";
import { AbstractModule } from "../AbstractModule.ts";
export class ModuleObsidianMenu extends AbstractObsidianModule { export class ModuleObsidianMenu extends AbstractModule {
_everyOnloadStart(): Promise<boolean> { _everyOnloadStart(): Promise<boolean> {
// UI // UI
addIcon( addIcon(
@@ -105,16 +105,8 @@ export class ModuleObsidianMenu extends AbstractObsidianModule {
}); });
return Promise.resolve(true); return Promise.resolve(true);
} }
private __onWorkspaceReady() {
void this.services.appLifecycle.onReady();
}
private _everyOnload(): Promise<boolean> {
this.app.workspace.onLayoutReady(this.__onWorkspaceReady.bind(this));
return Promise.resolve(true);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void { onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this));
services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this));
} }
} }
-2
View File
@@ -63,8 +63,6 @@ function addLog(log: string) {
const showDebugLog = false; const showDebugLog = false;
export const MARK_DONE = "\u{2009}\u{2009}"; export const MARK_DONE = "\u{2009}\u{2009}";
export class ModuleLog extends AbstractObsidianModule { export class ModuleLog extends AbstractObsidianModule {
registerView = this.plugin.registerView.bind(this.plugin);
statusBar?: HTMLElement; statusBar?: HTMLElement;
statusDiv?: HTMLElement; statusDiv?: HTMLElement;
@@ -1,4 +1,3 @@
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser"; // import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts"; import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
import { import {
@@ -18,7 +17,8 @@ import { getLanguage } from "@/deps.ts";
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts"; import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts";
import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts"; import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "../../main.ts";
export class ModuleObsidianSettings extends AbstractObsidianModule { import { AbstractModule } from "../AbstractModule.ts";
export class ModuleObsidianSettings extends AbstractModule {
async _everyOnLayoutReady(): Promise<boolean> { async _everyOnLayoutReady(): Promise<boolean> {
let isChanged = false; let isChanged = false;
if (this.settings.displayLanguage == "") { if (this.settings.displayLanguage == "") {
@@ -105,7 +105,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule {
} }
get appId() { get appId() {
return `${"appId" in this.app ? this.app.appId : ""}`; return this.services.API.getAppID();
} }
async _saveSettingData() { async _saveSettingData() {
@@ -1,4 +1,3 @@
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser"; // import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
import { isObjectDifferent } from "octagonal-wheels/object"; import { isObjectDifferent } from "octagonal-wheels/object";
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events"; import { EVENT_SETTING_SAVED, eventHub } from "../../common/events";
@@ -6,9 +5,13 @@ import { fireAndForget } from "octagonal-wheels/promises";
import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSettings } from "../../lib/src/common/types"; import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSettings } from "../../lib/src/common/types";
import { parseYaml, stringifyYaml } from "../../deps"; import { parseYaml, stringifyYaml } from "../../deps";
import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import { AbstractModule } from "../AbstractModule.ts";
import type { ServiceContext } from "@/lib/src/services/base/ServiceBase.ts";
import type { InjectableServiceHub } from "@/lib/src/services/InjectableServices.ts";
import type { LiveSyncCore } from "@/main.ts";
const SETTING_HEADER = "````yaml:livesync-setting\n"; const SETTING_HEADER = "````yaml:livesync-setting\n";
const SETTING_FOOTER = "\n````"; const SETTING_FOOTER = "\n````";
export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule { export class ModuleObsidianSettingsAsMarkdown extends AbstractModule {
_everyOnloadStart(): Promise<boolean> { _everyOnloadStart(): Promise<boolean> {
this.addCommand({ this.addCommand({
id: "livesync-export-config", id: "livesync-export-config",
@@ -242,7 +245,8 @@ We can perform a command in this file.
this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE); this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE);
} }
} }
onBindFunction(core: typeof this.plugin, services: typeof core.services): void {
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub<ServiceContext>): void {
services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this));
} }
} }
+3 -3
View File
@@ -9,7 +9,6 @@ import {
EVENT_REQUEST_SHOW_SETUP_QR, EVENT_REQUEST_SHOW_SETUP_QR,
eventHub, eventHub,
} from "../../common/events.ts"; } from "../../common/events.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { $msg } from "../../lib/src/common/i18n.ts"; import { $msg } from "../../lib/src/common/i18n.ts";
// import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts"; // import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "../../main.ts";
@@ -20,11 +19,12 @@ import {
OutputFormat, OutputFormat,
} from "../../lib/src/API/processSetting.ts"; } from "../../lib/src/API/processSetting.ts";
import { SetupManager, UserMode } from "./SetupManager.ts"; import { SetupManager, UserMode } from "./SetupManager.ts";
import { AbstractModule } from "../AbstractModule.ts";
export class ModuleSetupObsidian extends AbstractObsidianModule { export class ModuleSetupObsidian extends AbstractModule {
private _setupManager!: SetupManager; private _setupManager!: SetupManager;
private _everyOnload(): Promise<boolean> { private _everyOnload(): Promise<boolean> {
this._setupManager = this.plugin.getModule(SetupManager); this._setupManager = this.core.getModule(SetupManager);
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => { this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => {
if (conf.settings) { if (conf.settings) {
await this._setupManager.onUseSetupURI( await this._setupManager.onUseSetupURI(
+2 -2
View File
@@ -8,7 +8,6 @@ import {
REMOTE_P2P, REMOTE_P2P,
} from "../../lib/src/common/types.ts"; } from "../../lib/src/common/types.ts";
import { generatePatchObj, isObjectDifferent } from "../../lib/src/common/utils.ts"; import { generatePatchObj, isObjectDifferent } from "../../lib/src/common/utils.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import Intro from "./SetupWizard/dialogs/Intro.svelte"; import Intro from "./SetupWizard/dialogs/Intro.svelte";
import SelectMethodNewUser from "./SetupWizard/dialogs/SelectMethodNewUser.svelte"; import SelectMethodNewUser from "./SetupWizard/dialogs/SelectMethodNewUser.svelte";
import SelectMethodExisting from "./SetupWizard/dialogs/SelectMethodExisting.svelte"; import SelectMethodExisting from "./SetupWizard/dialogs/SelectMethodExisting.svelte";
@@ -23,6 +22,7 @@ import SetupRemoteBucket from "./SetupWizard/dialogs/SetupRemoteBucket.svelte";
import SetupRemoteP2P from "./SetupWizard/dialogs/SetupRemoteP2P.svelte"; import SetupRemoteP2P from "./SetupWizard/dialogs/SetupRemoteP2P.svelte";
import SetupRemoteE2EE from "./SetupWizard/dialogs/SetupRemoteE2EE.svelte"; import SetupRemoteE2EE from "./SetupWizard/dialogs/SetupRemoteE2EE.svelte";
import { decodeSettingsFromQRCodeData } from "../../lib/src/API/processSetting.ts"; import { decodeSettingsFromQRCodeData } from "../../lib/src/API/processSetting.ts";
import { AbstractModule } from "../AbstractModule.ts";
/** /**
* User modes for onboarding and setup * User modes for onboarding and setup
@@ -50,7 +50,7 @@ export const enum UserMode {
/** /**
* Setup Manager to handle onboarding and configuration setup * Setup Manager to handle onboarding and configuration setup
*/ */
export class SetupManager extends AbstractObsidianModule { export class SetupManager extends AbstractModule {
// /** // /**
// * Dialog manager for handling Svelte dialogs // * Dialog manager for handling Svelte dialogs
// */ // */
@@ -0,0 +1,21 @@
import { AppLifecycleServiceBase } from "@/lib/src/services/implements/injectable/InjectableAppLifecycleService";
import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext";
declare module "obsidian" {
interface App {
commands: {
executeCommandById: (id: string) => Promise<void>;
};
}
}
// InjectableAppLifecycleService
export class ObsidianAppLifecycleService<T extends ObsidianServiceContext> extends AppLifecycleServiceBase<T> {
constructor(context: T) {
super(context);
// The main entry point when Obsidian's workspace is ready
const onReady = this.onReady;
this.context.app.workspace.onLayoutReady(onReady);
}
performRestart(): void {
void this.context.plugin.app.commands.executeCommandById("app:reload");
}
}
+1 -1
View File
@@ -4,7 +4,6 @@ import type { ServiceInstances } from "@/lib/src/services/ServiceHub";
import type ObsidianLiveSyncPlugin from "@/main"; import type ObsidianLiveSyncPlugin from "@/main";
import { import {
ObsidianAPIService, ObsidianAPIService,
ObsidianAppLifecycleService,
ObsidianConflictService, ObsidianConflictService,
ObsidianDatabaseService, ObsidianDatabaseService,
ObsidianFileProcessingService, ObsidianFileProcessingService,
@@ -17,6 +16,7 @@ import {
ObsidianDatabaseEventService, ObsidianDatabaseEventService,
ObsidianConfigService, ObsidianConfigService,
} from "./ObsidianServices"; } from "./ObsidianServices";
import { ObsidianAppLifecycleService } from "./ObsidianAppLifecycleService";
import { ObsidianPathService } from "./ObsidianPathService"; import { ObsidianPathService } from "./ObsidianPathService";
import { ObsidianVaultService } from "./ObsidianVaultService"; import { ObsidianVaultService } from "./ObsidianVaultService";
import { ObsidianUIService } from "./ObsidianUIService"; import { ObsidianUIService } from "./ObsidianUIService";
-3
View File
@@ -1,5 +1,4 @@
import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService"; import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService";
import { InjectableAppLifecycleService } from "@lib/services/implements/injectable/InjectableAppLifecycleService";
import { InjectableConflictService } from "@lib/services/implements/injectable/InjectableConflictService"; import { InjectableConflictService } from "@lib/services/implements/injectable/InjectableConflictService";
import { InjectableDatabaseEventService } from "@lib/services/implements/injectable/InjectableDatabaseEventService"; import { InjectableDatabaseEventService } from "@lib/services/implements/injectable/InjectableDatabaseEventService";
import { InjectableDatabaseService } from "@lib/services/implements/injectable/InjectableDatabaseService"; import { InjectableDatabaseService } from "@lib/services/implements/injectable/InjectableDatabaseService";
@@ -123,8 +122,6 @@ export class ObsidianReplicationService extends InjectableReplicationService<Obs
export class ObsidianRemoteService extends InjectableRemoteService<ObsidianServiceContext> {} export class ObsidianRemoteService extends InjectableRemoteService<ObsidianServiceContext> {}
// InjectableConflictService // InjectableConflictService
export class ObsidianConflictService extends InjectableConflictService<ObsidianServiceContext> {} export class ObsidianConflictService extends InjectableConflictService<ObsidianServiceContext> {}
// InjectableAppLifecycleService
export class ObsidianAppLifecycleService extends InjectableAppLifecycleService<ObsidianServiceContext> {}
// InjectableSettingService // InjectableSettingService
export class ObsidianSettingService extends InjectableSettingService<ObsidianServiceContext> {} export class ObsidianSettingService extends InjectableSettingService<ObsidianServiceContext> {}
// InjectableTweakValueService // InjectableTweakValueService
+44 -1
View File
@@ -3,6 +3,50 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope. The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
## 0.25.43-patched-1
13th February, 2026
You know this is a patch version, is the beta-release practically? Do not worry about the following memos, as they are indeed freaking us out. I trust that you have thought this was too large; you're right.
If this cannot be stable, I will revert to 0.24.43 and try again.
### Refactored
- Now resolving unexpected and inexplicable dependency order issues...
- The function which is able to implement to the service is now moved to each service.
- AppLifecycleService.performRestart
- VaultService.isTargetFile is now uses separated multiple checkers instead of a single function.
- This change allows better separation of concerns and easier extension in the future.
- Application LifeCycle has now started in ServiceHub, not ObsidianMenuModule.
- It was on a QUITE unexpected place..., isn't it?
- Instead of, we should call `await this.services.appLifecycle.onReady()` in other platforms.
- As in browser platform, it will be called at `DOMContentLoaded` event.
- ModuleTargetFilter, which is responsible for parsing ignore files has been refined.
- This should be separated to a TargetFilter and a IgnoreFileFilter for better maintainability.
- Using `API.addCommand` or some Obsidian API and shimmer APIs, Many modules have been refactored to be derived to AbstractModule from AbstractObsidianModule, to clarify the dependencies. (we should make `app` usage clearer...)
- Fixed initialising `storageAccess` too late in `FileAccessObsidian` module (I am still wonder why it worked before...).
- Remove some redundant overrides in modules.
### Planned
- Some services have ambiguous name, such as `Injectable`. These will be renamed in the future for better clarity.
- Following properties of `ObsidianLiveSyncPlugin` should be initialised more explicitly:
- property : where it is initialised currently
- `localDatabase` : `ModuleLocalDatabaseObsidian`
- `managers` : `ModuleLocalDatabaseObsidian`
- `replicator` : `ModuleReplicator`
- `simpleStore` : `ModuleKeyValueDB`
- `storageAccess` : `ModuleFileAccessObsidian`
- `databaseFileAccess` : `ModuleDatabaseFileAccess`
- `fileHandler` : `ModuleFileHandler`
- `rebuilder` : `ModuleRebuilder`
- `kvDB`: `ModuleKeyValueDB`
- And I think that having feature in modules directly is not good for maintainability, these should be separated to some module (loader) and implementation (not only service, but also independent something).
- Plug-in statuses such as requestCount, responseCount... should be moved to a status service or somewhere for better separation of concerns.
## 0.25.43 ## 0.25.43
5th, February, 2026 5th, February, 2026
@@ -17,7 +61,6 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid
Quite a few packages have been updated in this release. Please report if you find any unexpected behaviour after this update. Quite a few packages have been updated in this release. Please report if you find any unexpected behaviour after this update.
## 0.25.42 ## 0.25.42
2nd, February, 2026 2nd, February, 2026