From e63e3e67255c2cab0c60501f55bb7b7b6f3741b5 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 16 Feb 2026 06:50:31 +0000 Subject: [PATCH] ### Refactor - Module dependency refined. (For details, please refer to updates.md) --- src/features/LiveSyncCommands.ts | 12 +- src/features/P2PSync/CmdP2PReplicator.ts | 2 +- src/lib | 2 +- src/main.ts | 189 +++++++++++------- src/modules/AbstractModule.ts | 21 +- .../core/ModuleLocalDatabaseObsidian.ts | 46 ----- src/modules/core/ModulePouchDB.ts | 23 --- src/modules/core/ModuleRebuilder.ts | 5 +- src/modules/core/ModuleTargetFilter.ts | 5 - .../essential/ModuleInitializerFile.ts | 8 +- src/modules/essential/ModuleKeyValueDB.ts | 114 ----------- .../ModuleExtraSyncObsidian.ts | 18 -- src/modules/features/ModuleLog.ts | 12 +- src/modules/main/ModuleLiveSyncMain.ts | 5 +- src/modules/services/ObsidianAPIService.ts | 92 +++++++++ .../services/ObsidianDatabaseService.ts | 11 + src/modules/services/ObsidianServiceHub.ts | 17 +- src/modules/services/ObsidianServices.ts | 103 +--------- updates.md | 27 ++- 19 files changed, 300 insertions(+), 412 deletions(-) delete mode 100644 src/modules/core/ModuleLocalDatabaseObsidian.ts delete mode 100644 src/modules/core/ModulePouchDB.ts delete mode 100644 src/modules/essential/ModuleKeyValueDB.ts delete mode 100644 src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts create mode 100644 src/modules/services/ObsidianAPIService.ts create mode 100644 src/modules/services/ObsidianDatabaseService.ts diff --git a/src/features/LiveSyncCommands.ts b/src/features/LiveSyncCommands.ts index e7db590..17978ac 100644 --- a/src/features/LiveSyncCommands.ts +++ b/src/features/LiveSyncCommands.ts @@ -1,4 +1,4 @@ -import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; +import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, @@ -12,6 +12,7 @@ import type ObsidianLiveSyncPlugin from "../main.ts"; import { MARK_DONE } from "../modules/features/ModuleLog.ts"; import type { LiveSyncCore } from "../main.ts"; import { __$checkInstanceBinding } from "../lib/src/dev/checks.ts"; +import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils.ts"; let noticeIndex = 0; export abstract class LiveSyncCommands { @@ -43,6 +44,7 @@ export abstract class LiveSyncCommands { constructor(plugin: ObsidianLiveSyncPlugin) { this.plugin = plugin; this.onBindFunction(plugin, plugin.services); + this._log = createInstanceLogFunction(this.constructor.name, this.services.API); __$checkInstanceBinding(this); } abstract onunload(): void; @@ -58,13 +60,7 @@ export abstract class LiveSyncCommands { return this.services.database.isDatabaseReady(); } - _log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => { - if (typeof msg === "string" && level !== LOG_LEVEL_NOTICE) { - msg = `[${this.constructor.name}]\u{200A} ${msg}`; - } - // console.log(msg); - Logger(msg, level, key); - }; + _log: ReturnType; _verbose = (msg: any, key?: string) => { this._log(msg, LOG_LEVEL_VERBOSE, key); diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts index 211fd3a..4a691b3 100644 --- a/src/features/P2PSync/CmdP2PReplicator.ts +++ b/src/features/P2PSync/CmdP2PReplicator.ts @@ -107,7 +107,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase } init() { - this._simpleStore = this.services.database.openSimpleStore("p2p-sync"); + this._simpleStore = this.services.keyValueDB.openSimpleStore("p2p-sync"); return Promise.resolve(this); } diff --git a/src/lib b/src/lib index 69e7a51..3ae1cba 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 69e7a510f19c2aeea53d9862680c133fd9e37c70 +Subproject commit 3ae1cbabdae286f994807c9da4ac2e937f70d3ee diff --git a/src/main.ts b/src/main.ts index 856914a..8a8d1bd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { Plugin } from "./deps"; +import { Plugin, type App, type PluginManifest } from "./deps"; import { type EntryDoc, type ObsidianLiveSyncSettings, @@ -6,12 +6,11 @@ import { type HasSettings, } from "./lib/src/common/types.ts"; import { type SimpleStore } from "./lib/src/common/utils.ts"; -import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts"; +import { type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts"; import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv, } from "./lib/src/replication/LiveSyncAbstractReplicator.js"; -import { type KeyValueDatabase } from "./lib/src/interfaces/KeyValueDatabase.ts"; import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; @@ -48,29 +47,23 @@ import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidian import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts"; import { ModuleObsidianSettingsAsMarkdown } from "./modules/features/ModuleObsidianSettingAsMarkdown.ts"; import { ModuleInitializerFile } from "./modules/essential/ModuleInitializerFile.ts"; -import { ModuleKeyValueDB } from "./modules/essential/ModuleKeyValueDB.ts"; -import { ModulePouchDB } from "./modules/core/ModulePouchDB.ts"; import { ModuleReplicator } from "./modules/core/ModuleReplicator.ts"; import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB.ts"; import { ModuleReplicatorMinIO } from "./modules/core/ModuleReplicatorMinIO.ts"; import { ModuleTargetFilter } from "./modules/core/ModuleTargetFilter.ts"; import { ModulePeriodicProcess } from "./modules/core/ModulePeriodicProcess.ts"; import { ModuleRemoteGovernor } from "./modules/coreFeatures/ModuleRemoteGovernor.ts"; -import { ModuleLocalDatabaseObsidian } from "./modules/core/ModuleLocalDatabaseObsidian.ts"; import { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker.ts"; import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks.ts"; import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts"; import { ModuleRebuilder } from "./modules/core/ModuleRebuilder.ts"; import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts"; import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts"; -import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts"; import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts"; import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts"; -import type { LiveSyncManagers } from "./lib/src/managers/LiveSyncManagers.ts"; import type { InjectableServiceHub } from "./lib/src/services/implements/injectable/InjectableServiceHub.ts"; import { ObsidianServiceHub } from "./modules/services/ObsidianServiceHub.ts"; import type { ServiceContext } from "./lib/src/services/base/ServiceBase.ts"; -// import type { InjectableServiceHub } from "./lib/src/services/InjectableServices.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -84,16 +77,36 @@ export default class ObsidianLiveSyncPlugin /** * The service hub for managing all services. */ - _services: InjectableServiceHub = new ObsidianServiceHub(this); + _services: InjectableServiceHub | undefined = undefined; + get services() { + if (!this._services) { + throw new Error("Services not initialised yet"); + } return this._services; } + + private initialiseServices() { + this._services = new ObsidianServiceHub(this); + } + + // Keep order to display the dialogue in order. + addOns = [] as LiveSyncCommands[]; /** * Bind functions to the service hub (for migration purpose). */ // bindFunctions = (this.serviceHub as ObsidianServiceHub).bindFunctions.bind(this.serviceHub); - // --> Module System + private _registerAddOn(addOn: LiveSyncCommands) { + this.addOns.push(addOn); + } + private registerAddOns() { + this._registerAddOn(new ConfigSync(this)); + this._registerAddOn(new HiddenFileSync(this)); + this._registerAddOn(new LocalDatabaseMaintenance(this)); + this._registerAddOn(new P2PReplicator(this)); + } + getAddOn(cls: string) { for (const addon of this.addOns) { if (addon.constructor.name == cls) return addon as T; @@ -101,56 +114,8 @@ export default class ObsidianLiveSyncPlugin return undefined; } - // Keep order to display the dialogue in order. - addOns = [ - new ConfigSync(this), - new HiddenFileSync(this), - new LocalDatabaseMaintenance(this), - new P2PReplicator(this), - ] as LiveSyncCommands[]; - - modules = [ - new ModuleLiveSyncMain(this), - new ModuleExtraSyncObsidian(this, this), - // Only on Obsidian - new ModuleDatabaseFileAccess(this), - // Common - new ModulePouchDB(this), - new ModuleConflictChecker(this), - new ModuleLocalDatabaseObsidian(this), - new ModuleReplicatorMinIO(this), - new ModuleReplicatorCouchDB(this), - new ModuleReplicator(this), - new ModuleFileHandler(this), - new ModuleConflictResolver(this), - new ModuleRemoteGovernor(this), - new ModuleTargetFilter(this), - new ModulePeriodicProcess(this), - // Essential Modules - new ModuleKeyValueDB(this), - new ModuleInitializerFile(this), - new ModuleObsidianAPI(this, this), - new ModuleObsidianEvents(this, this), - new ModuleFileAccessObsidian(this, this), - new ModuleObsidianSettings(this), - new ModuleResolvingMismatchedTweaks(this), - new ModuleObsidianSettingsAsMarkdown(this), - new ModuleObsidianSettingDialogue(this, this), - new ModuleLog(this, this), - new ModuleObsidianMenu(this), - new ModuleRebuilder(this), - new ModuleSetupObsidian(this), - new ModuleObsidianDocumentHistory(this, this), - new ModuleMigration(this), - new ModuleRedFlag(this), - new ModuleInteractiveConflictResolver(this, this), - new ModuleObsidianGlobalHistory(this, this), - new ModuleCheckRemoteSize(this), - // Test and Dev Modules - new ModuleDev(this, this), - new ModuleReplicateTest(this, this), - new ModuleIntegratedTest(this, this), - new SetupManager(this), + private modules = [ + // Move to registerModules ] as (IObsidianModule | AbstractModule)[]; getModule(constructor: new (...args: any[]) => T): T { @@ -159,26 +124,97 @@ export default class ObsidianLiveSyncPlugin } throw new Error(`Module ${constructor} not found or not loaded.`); } + getModulesByType(constructor: new (...args: any[]) => T): T[] { + const matchedModules: T[] = []; + for (const module of this.modules) { + if (module instanceof constructor) matchedModules.push(module); + } + return matchedModules; + } + + private _registerModule(module: IObsidianModule) { + this.modules.push(module); + } + private registerModules() { + this._registerModule(new ModuleLiveSyncMain(this)); + // Only on Obsidian + this._registerModule(new ModuleDatabaseFileAccess(this)); + // Common + this._registerModule(new ModuleConflictChecker(this)); + this._registerModule(new ModuleReplicatorMinIO(this)); + this._registerModule(new ModuleReplicatorCouchDB(this)); + this._registerModule(new ModuleReplicator(this)); + this._registerModule(new ModuleFileHandler(this)); + this._registerModule(new ModuleConflictResolver(this)); + this._registerModule(new ModuleRemoteGovernor(this)); + this._registerModule(new ModuleTargetFilter(this)); + this._registerModule(new ModulePeriodicProcess(this)); + // Essential Modules + this._registerModule(new ModuleInitializerFile(this)); + this._registerModule(new ModuleObsidianAPI(this, this)); + this._registerModule(new ModuleObsidianEvents(this, this)); + this._registerModule(new ModuleFileAccessObsidian(this, this)); + this._registerModule(new ModuleObsidianSettings(this)); + this._registerModule(new ModuleResolvingMismatchedTweaks(this)); + this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); + this._registerModule(new ModuleObsidianSettingDialogue(this, this)); + this._registerModule(new ModuleLog(this, this)); + this._registerModule(new ModuleObsidianMenu(this)); + this._registerModule(new ModuleRebuilder(this)); + this._registerModule(new ModuleSetupObsidian(this)); + this._registerModule(new ModuleObsidianDocumentHistory(this, this)); + this._registerModule(new ModuleMigration(this)); + this._registerModule(new ModuleRedFlag(this)); + this._registerModule(new ModuleInteractiveConflictResolver(this, this)); + this._registerModule(new ModuleObsidianGlobalHistory(this, this)); + this._registerModule(new ModuleCheckRemoteSize(this)); + // Test and Dev Modules + this._registerModule(new ModuleDev(this, this)); + this._registerModule(new ModuleReplicateTest(this, this)); + this._registerModule(new ModuleIntegratedTest(this, this)); + this._registerModule(new SetupManager(this)); + } - settings!: ObsidianLiveSyncSettings; - localDatabase!: LiveSyncLocalDB; - managers!: LiveSyncManagers; - simpleStore!: SimpleStore; - replicator!: LiveSyncAbstractReplicator; get confirm(): Confirm { return this.services.UI.confirm; } - storageAccess!: StorageAccess; - databaseFileAccess!: DatabaseFileAccess; - fileHandler!: ModuleFileHandler; - rebuilder!: Rebuilder; - kvDB!: KeyValueDatabase; + // This property will be changed from outside often, so will be set later. + settings!: ObsidianLiveSyncSettings; + + getSettings(): ObsidianLiveSyncSettings { + return this.settings; + } + + get localDatabase() { + return this.services.database.localDatabase; + } + + get managers() { + return this.services.database.managers; + } + getDatabase(): PouchDB.Database { return this.localDatabase.localDatabase; } - getSettings(): ObsidianLiveSyncSettings { - return this.settings; + + get simpleStore() { + return this.services.keyValueDB.simpleStore as SimpleStore; + } + + // initialised at ModuleReplicator + replicator!: LiveSyncAbstractReplicator; + // initialised at ModuleFileAccessObsidian + storageAccess!: StorageAccess; + // initialised at ModuleDatabaseFileAccess + databaseFileAccess!: DatabaseFileAccess; + // initialised at ModuleFileHandler + fileHandler!: ModuleFileHandler; + // initialised at ModuleRebuilder + rebuilder!: Rebuilder; + + get kvDB() { + return this.services.keyValueDB.kvDB; } requestCount = reactiveSource(0); @@ -205,6 +241,13 @@ export default class ObsidianLiveSyncPlugin syncStatus: "CLOSED" as DatabaseConnectingStatus, }); + constructor(app: App, manifest: PluginManifest) { + super(app, manifest); + this.initialiseServices(); + this.registerModules(); + this.registerAddOns(); + } + private async _startUp() { await this.services.appLifecycle.onLoad(); const onReady = this.services.appLifecycle.onReady.bind(this.services.appLifecycle); diff --git a/src/modules/AbstractModule.ts b/src/modules/AbstractModule.ts index a354be3..4e63d00 100644 --- a/src/modules/AbstractModule.ts +++ b/src/modules/AbstractModule.ts @@ -1,17 +1,18 @@ -import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; -import type { AnyEntry, FilePathWithPrefix, LOG_LEVEL } from "@lib/common/types"; +import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; +import type { AnyEntry, FilePathWithPrefix } from "@lib/common/types"; import type { LiveSyncCore } from "@/main"; import { __$checkInstanceBinding } from "@lib/dev/checks"; import { stripAllPrefixes } from "@lib/string_and_binary/path"; +import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils"; export abstract class AbstractModule { - _log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => { - if (typeof msg === "string" && level !== LOG_LEVEL_NOTICE) { - msg = `[${this.constructor.name}]\u{200A} ${msg}`; + _log = createInstanceLogFunction(this.constructor.name, this.services.API); + get services() { + if (!this.core._services) { + throw new Error("Services are not ready yet."); } - // console.log(msg); - Logger(msg, level, key); - }; + return this.core._services; + } addCommand = this.services.API.addCommand.bind(this.services.API); registerView = this.services.API.registerWindow.bind(this.services.API); @@ -73,10 +74,6 @@ export abstract class AbstractModule { return this.testDone(); } - get services() { - return this.core._services; - } - isMainReady() { return this.services.appLifecycle.isReady(); } diff --git a/src/modules/core/ModuleLocalDatabaseObsidian.ts b/src/modules/core/ModuleLocalDatabaseObsidian.ts deleted file mode 100644 index 412cadc..0000000 --- a/src/modules/core/ModuleLocalDatabaseObsidian.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { $msg } from "../../lib/src/common/i18n"; -import { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts"; -import { initializeStores } from "../../common/stores.ts"; -import { AbstractModule } from "../AbstractModule.ts"; -import { LiveSyncManagers } from "../../lib/src/managers/LiveSyncManagers.ts"; -import type { LiveSyncCore } from "../../main.ts"; - -export class ModuleLocalDatabaseObsidian extends AbstractModule { - _everyOnloadStart(): Promise { - return Promise.resolve(true); - } - private async _openDatabase(): Promise { - if (this.localDatabase != null) { - await this.localDatabase.close(); - } - const vaultName = this.services.vault.getVaultName(); - this._log($msg("moduleLocalDatabase.logWaitingForReady")); - const getDB = () => this.core.localDatabase.localDatabase; - const getSettings = () => this.core.settings; - this.core.managers = new LiveSyncManagers({ - get database() { - return getDB(); - }, - getActiveReplicator: () => this.core.replicator, - id2path: this.services.path.id2path.bind(this.services.path), - // path2id: this.core.$$path2id.bind(this.core), - path2id: this.services.path.path2id.bind(this.services.path), - get settings() { - return getSettings(); - }, - }); - this.core.localDatabase = new LiveSyncLocalDB(vaultName, this.core); - - initializeStores(vaultName); - return await this.localDatabase.initializeDatabase(); - } - - _isDatabaseReady(): boolean { - return this.localDatabase != null && this.localDatabase.isReady; - } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.database.isDatabaseReady.setHandler(this._isDatabaseReady.bind(this)); - services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); - services.database.openDatabase.setHandler(this._openDatabase.bind(this)); - } -} diff --git a/src/modules/core/ModulePouchDB.ts b/src/modules/core/ModulePouchDB.ts deleted file mode 100644 index 821afdc..0000000 --- a/src/modules/core/ModulePouchDB.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AbstractModule } from "../AbstractModule"; -import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser"; -import type { LiveSyncCore } from "../../main"; -import { ExtraSuffixIndexedDB } from "../../lib/src/common/types"; - -export class ModulePouchDB extends AbstractModule { - _createPouchDBInstance( - name?: string, - options?: PouchDB.Configuration.DatabaseConfiguration - ): PouchDB.Database { - const optionPass = options ?? {}; - if (this.settings.useIndexedDBAdapter) { - optionPass.adapter = "indexeddb"; - //@ts-ignore :missing def - optionPass.purged_infos_limit = 1; - return new PouchDB(name + ExtraSuffixIndexedDB, optionPass); - } - return new PouchDB(name, optionPass); - } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.database.createPouchDBInstance.setHandler(this._createPouchDBInstance.bind(this)); - } -} diff --git a/src/modules/core/ModuleRebuilder.ts b/src/modules/core/ModuleRebuilder.ts index 6e5656b..d5fc7dc 100644 --- a/src/modules/core/ModuleRebuilder.ts +++ b/src/modules/core/ModuleRebuilder.ts @@ -224,7 +224,10 @@ Are you sure you wish to proceed?`; await this.services.setting.realiseSetting(); await this.resetLocalDatabase(); await delay(1000); - await this.services.database.openDatabase(); + await this.services.database.openDatabase({ + databaseEvents: this.services.databaseEvents, + replicator: this.services.replicator, + }); // this.core.isReady = true; this.services.appLifecycle.markIsReady(); if (makeLocalChunkBeforeSync) { diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts index 8aa6ff7..4014976 100644 --- a/src/modules/core/ModuleTargetFilter.ts +++ b/src/modules/core/ModuleTargetFilter.ts @@ -146,10 +146,5 @@ export class ModuleTargetFilter extends AbstractModule { 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 - - // }); } } diff --git a/src/modules/essential/ModuleInitializerFile.ts b/src/modules/essential/ModuleInitializerFile.ts index ed6d661..e246ca4 100644 --- a/src/modules/essential/ModuleInitializerFile.ts +++ b/src/modules/essential/ModuleInitializerFile.ts @@ -393,7 +393,13 @@ export class ModuleInitializerFile extends AbstractModule { ignoreSuspending: boolean = false ): Promise { this.services.appLifecycle.resetIsReady(); - if (!reopenDatabase || (await this.services.database.openDatabase())) { + if ( + !reopenDatabase || + (await this.services.database.openDatabase({ + databaseEvents: this.services.databaseEvents, + replicator: this.services.replicator, + })) + ) { if (this.localDatabase.isReady) { await this.services.vault.scanVault(showingNotice, ignoreSuspending); } diff --git a/src/modules/essential/ModuleKeyValueDB.ts b/src/modules/essential/ModuleKeyValueDB.ts deleted file mode 100644 index 6d657e8..0000000 --- a/src/modules/essential/ModuleKeyValueDB.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { delay, yieldMicrotask } from "octagonal-wheels/promises"; -import { OpenKeyValueDatabase } from "../../common/KeyValueDB.ts"; -import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts"; -import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; -import { AbstractModule } from "../AbstractModule.ts"; -import type { LiveSyncCore } from "../../main.ts"; -import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; -import type { InjectableServiceHub } from "@/lib/src/services/InjectableServices.ts"; -import type { ObsidianDatabaseService } from "../services/ObsidianServices.ts"; - -export class ModuleKeyValueDB extends AbstractModule { - async tryCloseKvDB() { - try { - await this.core.kvDB?.close(); - return true; - } catch (e) { - this._log("Failed to close KeyValueDB", LOG_LEVEL_VERBOSE); - this._log(e); - return false; - } - } - async openKeyValueDB(): Promise { - await delay(10); - try { - await this.tryCloseKvDB(); - await delay(10); - await yieldMicrotask(); - this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv"); - await yieldMicrotask(); - await delay(100); - } catch (e) { - this.core.kvDB = undefined!; - this._log("Failed to open KeyValueDB", LOG_LEVEL_NOTICE); - this._log(e, LOG_LEVEL_VERBOSE); - return false; - } - return true; - } - async _onDBUnload(db: LiveSyncLocalDB) { - if (this.core.kvDB) await this.core.kvDB.close(); - return Promise.resolve(true); - } - async _onDBClose(db: LiveSyncLocalDB) { - if (this.core.kvDB) await this.core.kvDB.close(); - return Promise.resolve(true); - } - - private async _everyOnloadAfterLoadSettings(): Promise { - if (!(await this.openKeyValueDB())) { - return false; - } - this.core.simpleStore = this.services.database.openSimpleStore("os"); - return Promise.resolve(true); - } - _getSimpleStore(kind: string) { - const getDB = () => this.core.kvDB; - const prefix = `${kind}-`; - return { - get: async (key: string): Promise => { - return await getDB().get(`${prefix}${key}`); - }, - set: async (key: string, value: any): Promise => { - await getDB().set(`${prefix}${key}`, value); - }, - delete: async (key: string): Promise => { - await getDB().del(`${prefix}${key}`); - }, - keys: async ( - from: string | undefined, - to: string | undefined, - count?: number | undefined - ): Promise => { - const ret = await getDB().keys( - IDBKeyRange.bound(`${prefix}${from || ""}`, `${prefix}${to || ""}`), - count - ); - return ret - .map((e) => e.toString()) - .filter((e) => e.startsWith(prefix)) - .map((e) => e.substring(prefix.length)); - }, - db: Promise.resolve(getDB()), - } satisfies SimpleStore; - } - _everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise { - return this.openKeyValueDB(); - } - - async _everyOnResetDatabase(db: LiveSyncLocalDB): Promise { - try { - const kvDBKey = "queued-files"; - await this.core.kvDB.del(kvDBKey); - // localStorage.removeItem(lsKey); - await this.core.kvDB.destroy(); - await yieldMicrotask(); - this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv"); - await delay(100); - } catch (e) { - this.core.kvDB = undefined!; - this._log("Failed to reset KeyValueDB", LOG_LEVEL_NOTICE); - this._log(e, LOG_LEVEL_VERBOSE); - return false; - } - return true; - } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { - services.databaseEvents.onUnloadDatabase.addHandler(this._onDBUnload.bind(this)); - services.databaseEvents.onCloseDatabase.addHandler(this._onDBClose.bind(this)); - services.databaseEvents.onDatabaseInitialisation.addHandler(this._everyOnInitializeDatabase.bind(this)); - services.databaseEvents.onResetDatabase.addHandler(this._everyOnResetDatabase.bind(this)); - (services.database as ObsidianDatabaseService).openSimpleStore.setHandler(this._getSimpleStore.bind(this)); - services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); - } -} diff --git a/src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts b/src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts deleted file mode 100644 index 256987a..0000000 --- a/src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { LiveSyncCore } from "../../main.ts"; -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; - -export class ModuleExtraSyncObsidian extends AbstractObsidianModule { - deviceAndVaultName: string = ""; - - _getDeviceAndVaultName(): string { - return this.deviceAndVaultName; - } - _setDeviceAndVaultName(name: string): void { - this.deviceAndVaultName = name; - } - - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.setting.getDeviceAndVaultName.setHandler(this._getDeviceAndVaultName.bind(this)); - services.setting.setDeviceAndVaultName.setHandler(this._setDeviceAndVaultName.bind(this)); - } -} diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index b7c9407..6664ae0 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -39,19 +39,22 @@ import { isValidFilenameInDarwin, isValidFilenameInWidows, } from "@/lib/src/string_and_binary/path.ts"; +import { MARK_LOG_SEPARATOR } from "@/lib/src/services/lib/logUtils.ts"; // This module cannot be a core module because it depends on the Obsidian UI. // DI the log again. const recentLogEntries = reactiveSource([]); -setGlobalLogFunction((message: any, level?: number, key?: string) => { +const globalLogFunction = (message: any, level?: number, key?: string) => { const messageX = message instanceof Error ? new LiveSyncError("[Error Logged]: " + message.message, { cause: message }) : message; const entry = { message: messageX, level, key } as LogEntry; recentLogEntries.value = [...recentLogEntries.value, entry]; -}); +}; + +setGlobalLogFunction(globalLogFunction); let recentLogs = [] as string[]; function addLog(log: string) { @@ -304,9 +307,9 @@ export class ModuleLog extends AbstractObsidianModule { // const recent = logMessages.value; const newMsg = message; let newLog = this.settings?.showOnlyIconsOnEditor ? "" : status; - const moduleTagEnd = newLog.indexOf(`]\u{200A}`); + const moduleTagEnd = newLog.indexOf(`]${MARK_LOG_SEPARATOR}`); if (moduleTagEnd != -1) { - newLog = newLog.substring(moduleTagEnd + 2); + newLog = newLog.substring(moduleTagEnd + MARK_LOG_SEPARATOR.length + 1); } this.statusBar?.setText(newMsg.split("\n")[0]); @@ -493,6 +496,7 @@ export class ModuleLog extends AbstractObsidianModule { } } onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + services.API.addLog.setHandler(globalLogFunction); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index 84550a5..34dc3f6 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -126,7 +126,10 @@ export class ModuleLiveSyncMain extends AbstractModule { await this.saveSettings(); } localStorage.setItem(lsKey, `${VER}`); - await this.services.database.openDatabase(); + await this.services.database.openDatabase({ + databaseEvents: this.services.databaseEvents, + replicator: this.services.replicator, + }); // this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this); // this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this); // this.$$replicate = this.$$replicate.bind(this); diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts new file mode 100644 index 0000000..b771c29 --- /dev/null +++ b/src/modules/services/ObsidianAPIService.ts @@ -0,0 +1,92 @@ +import { InjectableAPIService } from "@/lib/src/services/implements/injectable/InjectableAPIService"; +import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; +import { Platform, type Command, type ViewCreator } from "obsidian"; +import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler"; + +// All Services will be migrated to be based on Plain Services, not Injectable Services. +// This is a migration step. + +export class ObsidianAPIService extends InjectableAPIService { + _customHandler: ObsHttpHandler | undefined; + getCustomFetchHandler(): ObsHttpHandler { + if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined); + return this._customHandler; + } + + async showWindow(viewType: string): Promise { + const leaves = this.app.workspace.getLeavesOfType(viewType); + if (leaves.length == 0) { + await this.app.workspace.getLeaf(true).setViewState({ + type: viewType, + active: true, + }); + } else { + await leaves[0].setViewState({ + type: viewType, + active: true, + }); + } + if (leaves.length > 0) { + await this.app.workspace.revealLeaf(leaves[0]); + } + } + + private get app() { + return this.context.app; + } + + getPlatform(): string { + if (Platform.isAndroidApp) { + return "android-app"; + } else if (Platform.isIosApp) { + return "ios"; + } else if (Platform.isMacOS) { + return "macos"; + } else if (Platform.isMobileApp) { + return "mobile-app"; + } else if (Platform.isMobile) { + return "mobile"; + } else if (Platform.isSafari) { + return "safari"; + } else if (Platform.isDesktop) { + return "desktop"; + } else if (Platform.isDesktopApp) { + return "desktop-app"; + } else { + return "unknown-obsidian"; + } + } + override isMobile(): boolean { + //@ts-ignore : internal API + return this.app.isMobile; + } + override getAppID(): string { + return `${"appId" in this.app ? this.app.appId : ""}`; + } + override getAppVersion(): string { + const navigatorString = globalThis.navigator?.userAgent ?? ""; + const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/); + if (match && match.length >= 2) { + return match[1]; + } + return "0.0.0"; + } + + override getPluginVersion(): string { + return this.context.plugin.manifest.version; + } + + addCommand(command: TCommand): TCommand { + return this.context.plugin.addCommand(command) as TCommand; + } + + registerWindow(type: string, factory: ViewCreator): void { + return this.context.plugin.registerView(type, factory); + } + addRibbonIcon(icon: string, title: string, callback: (evt: MouseEvent) => any): HTMLElement { + return this.context.plugin.addRibbonIcon(icon, title, callback); + } + registerProtocolHandler(action: string, handler: (params: Record) => any): void { + return this.context.plugin.registerObsidianProtocolHandler(action, handler); + } +} diff --git a/src/modules/services/ObsidianDatabaseService.ts b/src/modules/services/ObsidianDatabaseService.ts new file mode 100644 index 0000000..1e88cbb --- /dev/null +++ b/src/modules/services/ObsidianDatabaseService.ts @@ -0,0 +1,11 @@ +import { initializeStores } from "@/common/stores"; + +import { InjectableDatabaseService } from "@/lib/src/services/implements/injectable/InjectableDatabaseService"; +import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; + +export class ObsidianDatabaseService extends InjectableDatabaseService { + override onOpenDatabase(vaultName: string): Promise { + initializeStores(vaultName); + return Promise.resolve(); + } +} diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 4a3a9f3..cd49fc8 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -3,9 +3,7 @@ import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/O import type { ServiceInstances } from "@/lib/src/services/ServiceHub"; import type ObsidianLiveSyncPlugin from "@/main"; import { - ObsidianAPIService, ObsidianConflictService, - ObsidianDatabaseService, ObsidianFileProcessingService, ObsidianReplicationService, ObsidianReplicatorService, @@ -15,7 +13,10 @@ import { ObsidianTestService, ObsidianDatabaseEventService, ObsidianConfigService, + ObsidianKeyValueDBService, } from "./ObsidianServices"; +import { ObsidianDatabaseService } from "./ObsidianDatabaseService"; +import { ObsidianAPIService } from "./ObsidianAPIService"; import { ObsidianAppLifecycleService } from "./ObsidianAppLifecycleService"; import { ObsidianPathService } from "./ObsidianPathService"; import { ObsidianVaultService } from "./ObsidianVaultService"; @@ -30,7 +31,6 @@ export class ObsidianServiceHub extends InjectableServiceHub>; super(context, serviceInstancesToInit); diff --git a/src/modules/services/ObsidianServices.ts b/src/modules/services/ObsidianServices.ts index 2f16c0d..9ac3129 100644 --- a/src/modules/services/ObsidianServices.ts +++ b/src/modules/services/ObsidianServices.ts @@ -1,7 +1,5 @@ -import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService"; import { InjectableConflictService } from "@lib/services/implements/injectable/InjectableConflictService"; import { InjectableDatabaseEventService } from "@lib/services/implements/injectable/InjectableDatabaseEventService"; -import { InjectableDatabaseService } from "@lib/services/implements/injectable/InjectableDatabaseService"; import { InjectableFileProcessingService } from "@lib/services/implements/injectable/InjectableFileProcessingService"; import { InjectableRemoteService } from "@lib/services/implements/injectable/InjectableRemoteService"; import { InjectableReplicationService } from "@lib/services/implements/injectable/InjectableReplicationService"; @@ -11,105 +9,8 @@ import { InjectableTestService } from "@lib/services/implements/injectable/Injec import { InjectableTweakValueService } from "@lib/services/implements/injectable/InjectableTweakValueService"; import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat"; import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext.ts"; -import { Platform } from "@/deps"; -import type { SimpleStore } from "@/lib/src/common/utils"; -import type { IDatabaseService } from "@/lib/src/services/base/IService"; -import { handlers } from "@/lib/src/services/lib/HandlerUtils"; -import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler"; -import type { Command, ViewCreator } from "obsidian"; +import { KeyValueDBService } from "@/lib/src/services/base/KeyValueDBService"; -// All Services will be migrated to be based on Plain Services, not Injectable Services. -// This is a migration step. - -export class ObsidianAPIService extends InjectableAPIService { - _customHandler: ObsHttpHandler | undefined; - getCustomFetchHandler(): ObsHttpHandler { - if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined); - return this._customHandler; - } - - async showWindow(viewType: string): Promise { - const leaves = this.app.workspace.getLeavesOfType(viewType); - if (leaves.length == 0) { - await this.app.workspace.getLeaf(true).setViewState({ - type: viewType, - active: true, - }); - } else { - await leaves[0].setViewState({ - type: viewType, - active: true, - }); - } - if (leaves.length > 0) { - await this.app.workspace.revealLeaf(leaves[0]); - } - } - - private get app() { - return this.context.app; - } - - getPlatform(): string { - if (Platform.isAndroidApp) { - return "android-app"; - } else if (Platform.isIosApp) { - return "ios"; - } else if (Platform.isMacOS) { - return "macos"; - } else if (Platform.isMobileApp) { - return "mobile-app"; - } else if (Platform.isMobile) { - return "mobile"; - } else if (Platform.isSafari) { - return "safari"; - } else if (Platform.isDesktop) { - return "desktop"; - } else if (Platform.isDesktopApp) { - return "desktop-app"; - } else { - return "unknown-obsidian"; - } - } - override isMobile(): boolean { - //@ts-ignore : internal API - return this.app.isMobile; - } - override getAppID(): string { - return `${"appId" in this.app ? this.app.appId : ""}`; - } - override getAppVersion(): string { - const navigatorString = globalThis.navigator?.userAgent ?? ""; - const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/); - if (match && match.length >= 2) { - return match[1]; - } - return "0.0.0"; - } - - override getPluginVersion(): string { - return this.context.plugin.manifest.version; - } - - addCommand(command: TCommand): TCommand { - return this.context.plugin.addCommand(command) as TCommand; - } - - registerWindow(type: string, factory: ViewCreator): void { - return this.context.plugin.registerView(type, factory); - } - addRibbonIcon(icon: string, title: string, callback: (evt: MouseEvent) => any): HTMLElement { - return this.context.plugin.addRibbonIcon(icon, title, callback); - } - registerProtocolHandler(action: string, handler: (params: Record) => any): void { - return this.context.plugin.registerObsidianProtocolHandler(action, handler); - } -} -export class ObsidianDatabaseService extends InjectableDatabaseService { - openSimpleStore = handlers().binder("openSimpleStore") as (( - kind: string - ) => SimpleStore) & { setHandler: (handler: IDatabaseService["openSimpleStore"], override?: boolean) => void }; -} export class ObsidianDatabaseEventService extends InjectableDatabaseEventService {} // InjectableReplicatorService @@ -129,3 +30,5 @@ export class ObsidianTweakValueService extends InjectableTweakValueService {} export class ObsidianConfigService extends ConfigServiceBrowserCompat {} + +export class ObsidianKeyValueDBService extends KeyValueDBService {} diff --git a/updates.md b/updates.md index e7180b2..4814c92 100644 --- a/updates.md +++ b/updates.md @@ -3,13 +3,37 @@ 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. +## 0.25.43-patched-3 + +16th February, 2026 + +### Refactored + +- Now following properties of `ObsidianLiveSyncPlugin` belong to each service: + - property : service + - `localDatabase` : `services.database` + - `managers` : `services.database` + - `simpleStore` : `services.keyValueDB` + - `kvDB`: `services.keyValueDB` +- Initialising modules, addOns, and services are now explicitly separated in the `_startUp` function of the main plug-in class. +- LiveSyncLocalDB now depends more explicitly on specified services, not the whole `ServiceHub`. +- New service `keyValueDB` has been added. This had been separated from the `database` service. +- Non-trivial modules, such as `ModuleExtraSyncObsidian` (which only holds deviceAndVaultName), are simply implemented in the service. +- Add `logUtils` for unifying logging method injection and formatting. This utility is able to accept the API service for log writing. +- `ModuleKeyValueDB` has been removed, and its functionality is now implemented in the `keyValueDB` service. +- `ModulePouchDB` and `ModuleLocalDatabaseObsidian` have been removed, and their functionality is now implemented in the `database` service. + - Please be aware that you have overridden createPouchDBInstance or something by dynamic binding; you should now override the createPouchDBInstance in the database service instead of using the module. + - You can refer to the `DirectFileManipulatorV2` for an example of how to override the createPouchDBInstance function in the database service. + + ## 0.25.43-patched-2 14th February, 2026 ### Fixed + - Application LifeCycle has now started in Main, not ServiceHub. - - Indeed, ServiceHub cannot be known other things in main have got ready, so it is quite natural to start the lifecycle in main. + - Indeed, ServiceHub cannot be known other things in main have got ready, so it is quite natural to start the lifecycle in main. ## 0.25.43-patched-1 @@ -29,6 +53,7 @@ If this cannot be stable, I will revert to 0.24.43 and try again. - VaultService.isTargetFile is now using 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 in a QUITE unexpected place..., isn't it? - Instead of, we should call `await this.services.appLifecycle.onReady()` in other platforms. - As in the browser platform, it will be called at `DOMContentLoaded` event.