From 203dd1742173cb3d24c19393b1b1497c2cd239f0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 19 Feb 2026 10:23:45 +0000 Subject: [PATCH] for 0.25.43-patched-7, please refer to the updates.md --- manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- src/apps/webpeer/src/P2PReplicatorShim.ts | 18 +- src/lib | 2 +- src/main.ts | 156 +++++--- src/managers/StorageEventManagerObsidian.ts | 6 +- src/modules/AbstractModule.ts | 2 +- src/modules/core/ModuleReplicator.ts | 8 +- src/modules/core/ReplicateResultProcessor.ts | 13 +- .../coreFeatures/ModuleConflictChecker.ts | 2 +- src/modules/coreFeatures/ModuleRedFlag.ts | 4 +- .../essentialObsidian/ModuleObsidianAPI.ts | 14 +- .../essentialObsidian/ModuleObsidianEvents.ts | 31 +- .../essentialObsidian/ModuleObsidianMenu.ts | 4 +- src/modules/features/ModuleLog.ts | 24 +- src/modules/features/ModuleObsidianSetting.ts | 356 ------------------ .../ModuleObsidianSettingAsMarkdown.ts | 4 +- .../ObsidianLiveSyncSettingTab.ts | 2 +- .../SettingDialogue/PaneSyncSettings.ts | 8 +- src/modules/main/ModuleLiveSyncMain.ts | 36 +- src/modules/services/ObsidianAPIService.ts | 20 +- src/modules/services/ObsidianServiceHub.ts | 25 +- src/modules/services/ObsidianServices.ts | 8 +- .../services/ObsidianSettingService.ts | 35 ++ src/modules/services/ObsidianUIService.ts | 11 +- src/serviceFeatures/onLayoutReady.ts | 3 + .../onLayoutReady/enablei18n.ts | 41 ++ src/serviceFeatures/types.ts | 50 +++ src/types.ts | 27 ++ test/harness/harness.ts | 12 +- updates.md | 21 +- 32 files changed, 426 insertions(+), 525 deletions(-) delete mode 100644 src/modules/features/ModuleObsidianSetting.ts create mode 100644 src/modules/services/ObsidianSettingService.ts create mode 100644 src/serviceFeatures/onLayoutReady.ts create mode 100644 src/serviceFeatures/onLayoutReady/enablei18n.ts create mode 100644 src/serviceFeatures/types.ts create mode 100644 src/types.ts diff --git a/manifest.json b/manifest.json index c803f98..e2df7e3 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-6", + "version": "0.25.43-patched-7", "minAppVersion": "0.9.12", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "author": "vorotamoroz", diff --git a/package-lock.json b/package-lock.json index ec37458..7f77fe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-6", + "version": "0.25.43-patched-7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-6", + "version": "0.25.43-patched-7", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 1874541..096b37b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-6", + "version": "0.25.43-patched-7", "description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "main": "main.js", "type": "module", diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index 004ef5e..3168558 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -34,9 +34,11 @@ import { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; import { ServiceContext } from "@lib/services/base/ServiceBase"; import type { InjectableServiceHub } from "@lib/services/InjectableServices"; -import { Menu } from "@/lib/src/services/implements/browser/Menu"; -import type { InjectableVaultServiceCompat } from "@/lib/src/services/implements/injectable/InjectableVaultService"; +import { Menu } from "@lib/services/implements/browser/Menu"; +import type { InjectableVaultServiceCompat } from "@lib/services/implements/injectable/InjectableVaultService"; import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2"; +import type { InjectableAPIService } from "@/lib/src/services/implements/injectable/InjectableAPIService"; +import type { BrowserAPIService } from "@/lib/src/services/implements/browser/BrowserAPIService"; function addToList(item: string, list: string) { return unique( @@ -81,13 +83,13 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { constructor() { const browserServiceHub = new BrowserServiceHub(); this.services = browserServiceHub; - (this.services.vault as InjectableVaultServiceCompat).vaultName.setHandler( + + (this.services.API as BrowserAPIService).getSystemVaultName.setHandler( () => "p2p-livesync-web-peer" ); - - this.services.setting.currentSettings.setHandler(() => { - return this.settings as any; - }); + // this.services.setting.currentSettings.setHandler(() => { + // return this.settings as any; + // }); } async init() { // const { simpleStoreAPI } = await getWrappedSynchromesh(); @@ -106,7 +108,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { const repStore = SimpleStoreIDBv2.open("p2p-livesync-web-peer"); this._simpleStore = repStore; let _settings = (await repStore.get("settings")) || ({ ...P2P_DEFAULT_SETTINGS } as P2PSyncSetting); - + this.services.setting.settings = _settings as any; this.plugin = { saveSettings: async () => { await repStore.set("settings", _settings); diff --git a/src/lib b/src/lib index b59a1de..b2bb669 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit b59a1deba8514ca2c67364569995da13f01be741 +Subproject commit b2bb66970c8732892c238951dd3572ea849b3890 diff --git a/src/main.ts b/src/main.ts index 2769f8a..4ea2d17 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,6 @@ import { Plugin, type App, type PluginManifest } from "./deps"; import { type EntryDoc, type ObsidianLiveSyncSettings, - type DatabaseConnectingStatus, type HasSettings, LOG_LEVEL_INFO, } from "./lib/src/common/types.ts"; @@ -12,7 +11,6 @@ import { type LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstra import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; -import { reactiveSource, type ReactiveValue } from "octagonal-wheels/dataobject/reactive"; import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js"; import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js"; import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js"; @@ -24,7 +22,6 @@ import { ModuleCheckRemoteSize } from "./modules/essentialObsidian/ModuleCheckRe import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver.ts"; import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts"; import { ModuleLog } from "./modules/features/ModuleLog.ts"; -import { ModuleObsidianSettings } from "./modules/features/ModuleObsidianSetting.ts"; import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts"; import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.ts"; @@ -66,6 +63,8 @@ import { __$checkInstanceBinding } from "./lib/src/dev/checks.ts"; import { ServiceFileHandler } from "./serviceModules/FileHandler.ts"; import { FileAccessObsidian } from "./serviceModules/FileAccessObsidian.ts"; import { StorageEventManagerObsidian } from "./managers/StorageEventManagerObsidian.ts"; +import { onLayoutReadyFeatures } from "./serviceFeatures/onLayoutReady.ts"; +import type { ServiceModules } from "./types.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -88,12 +87,27 @@ export default class ObsidianLiveSyncPlugin return this._services; } - private initialiseServices() { - this._services = new ObsidianServiceHub(this); + /** + * Service Modules + */ + protected _serviceModules: ServiceModules; + + get serviceModules() { + return this._serviceModules; } + /** + * addOns: Non-essential and graphically features + */ addOns = [] as LiveSyncCommands[]; + /** + * The modules of the plug-in. Modules are responsible for specific features or functionalities of the plug-in, such as file handling, conflict resolution, replication, etc. + */ + private modules = [ + // Move to registerModules + ] as (IObsidianModule | AbstractModule)[]; + /** * register an add-onn to the plug-in. * Add-ons are features that are not essential to the core functionality of the plugin, @@ -122,13 +136,6 @@ export default class ObsidianLiveSyncPlugin return undefined; } - /** - * The modules of the plug-in. Modules are responsible for specific features or functionalities of the plug-in, such as file handling, conflict resolution, replication, etc. - */ - private modules = [ - // Move to registerModules - ] as (IObsidianModule | AbstractModule)[]; - /** * Get a module by its class. Throws an error if not found. * Mostly used for getting SetupManager. @@ -162,7 +169,6 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleInitializerFile(this)); this._registerModule(new ModuleObsidianAPI(this, this)); this._registerModule(new ModuleObsidianEvents(this, this)); - this._registerModule(new ModuleObsidianSettings(this)); this._registerModule(new ModuleResolvingMismatchedTweaks(this)); this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); this._registerModule(new ModuleObsidianSettingDialogue(this, this)); @@ -206,9 +212,24 @@ export default class ObsidianLiveSyncPlugin return this.services.UI.confirm; } - // This property will be changed from outside often, so will be set later. - settings!: ObsidianLiveSyncSettings; + /** + * @obsolete Use services.setting.currentSettings instead. The current settings of the plug-in. + */ + get settings() { + return this.services.setting.settings; + } + /** + * @obsolete Use services.setting.settings instead. Set the settings of the plug-in. + */ + set settings(value: ObsidianLiveSyncSettings) { + this.services.setting.settings = value; + } + + /** + * @obsolete Use services.setting.currentSettings instead. Get the settings of the plug-in. + * @returns The current settings of the plug-in. + */ getSettings(): ObsidianLiveSyncSettings { return this.settings; } @@ -259,46 +280,63 @@ export default class ObsidianLiveSyncPlugin /// Modules which were relied on services /** * Storage Accessor for handling file operations. + * @obsolete Use serviceModules.storageAccess instead. */ - storageAccess: StorageAccess; + get storageAccess(): StorageAccess { + return this.serviceModules.storageAccess; + } /** * Database File Accessor for handling file operations related to the database, such as exporting the database, importing from a file, etc. + * @obsolete Use serviceModules.databaseFileAccess instead. */ - databaseFileAccess: DatabaseFileAccess; - + get databaseFileAccess(): DatabaseFileAccess { + return this.serviceModules.databaseFileAccess; + } /** * File Handler for handling file operations related to replication, such as resolving conflicts, applying changes from replication, etc. + * @obsolete Use serviceModules.fileHandler instead. */ - fileHandler: IFileHandler; + get fileHandler(): IFileHandler { + return this.serviceModules.fileHandler; + } /** * Rebuilder for handling database rebuilding operations. + * @obsolete Use serviceModules.rebuilder instead. */ - rebuilder: Rebuilder; + get rebuilder(): Rebuilder { + return this.serviceModules.rebuilder; + } - requestCount = reactiveSource(0); - responseCount = reactiveSource(0); - totalQueued = reactiveSource(0); - batched = reactiveSource(0); - processing = reactiveSource(0); - databaseQueueCount = reactiveSource(0); - storageApplyingCount = reactiveSource(0); - replicationResultCount = reactiveSource(0); - conflictProcessQueueCount = reactiveSource(0); - pendingFileEventCount = reactiveSource(0); - processingFileEventCount = reactiveSource(0); + // requestCount = reactiveSource(0); + // responseCount = reactiveSource(0); + // totalQueued = reactiveSource(0); + // batched = reactiveSource(0); + // processing = reactiveSource(0); + // databaseQueueCount = reactiveSource(0); + // storageApplyingCount = reactiveSource(0); + // replicationResultCount = reactiveSource(0); - _totalProcessingCount?: ReactiveValue; + // pendingFileEventCount = reactiveSource(0); + // processingFileEventCount = reactiveSource(0); - replicationStat = reactiveSource({ - sent: 0, - arrived: 0, - maxPullSeq: 0, - maxPushSeq: 0, - lastSyncPullSeq: 0, - lastSyncPushSeq: 0, - syncStatus: "CLOSED" as DatabaseConnectingStatus, - }); + // _totalProcessingCount?: ReactiveValue; + // replicationStat = reactiveSource({ + // sent: 0, + // arrived: 0, + // maxPullSeq: 0, + // maxPushSeq: 0, + // lastSyncPullSeq: 0, + // lastSyncPushSeq: 0, + // syncStatus: "CLOSED" as DatabaseConnectingStatus, + // }); + + private initialiseServices() { + this._services = new ObsidianServiceHub(this); + } + /** + * Initialise service modules. + */ private initialiseServiceModules() { const storageAccessManager = new StorageAccessManager(); // If we want to implement to the other platform, implement ObsidianXXXXXService. @@ -358,6 +396,7 @@ export default class ObsidianLiveSyncPlugin vault: this.services.vault, fileHandler: fileHandler, storageAccess: storageAccess, + control: this.services.control, }); return { rebuilder, @@ -367,34 +406,45 @@ export default class ObsidianLiveSyncPlugin }; } + /** + * @obsolete Use services.setting.saveSettingData instead. Save the settings to the disk. This is usually called after changing the settings in the code, to persist the changes. + */ + async saveSettings() { + await this.services.setting.saveSettingData(); + } + + /** + * Initialise ServiceFeatures. + * (Please refer `serviceFeatures` for more details) + */ + initialiseServiceFeatures() { + for (const feature of onLayoutReadyFeatures) { + const curriedFeature = () => feature(this); + this.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); + } + } + constructor(app: App, manifest: PluginManifest) { super(app, manifest); this.initialiseServices(); this.registerModules(); this.registerAddOns(); - const instances = this.initialiseServiceModules(); - this.rebuilder = instances.rebuilder; - this.fileHandler = instances.fileHandler; - this.databaseFileAccess = instances.databaseFileAccess; - this.storageAccess = instances.storageAccess; + this._serviceModules = this.initialiseServiceModules(); + this.initialiseServiceFeatures(); this.bindModuleFunctions(); } private async _startUp() { - await this.services.appLifecycle.onLoad(); - const onReady = this.services.appLifecycle.onReady.bind(this.services.appLifecycle); + if (!(await this.services.control.onLoad())) return; + const onReady = this.services.control.onReady.bind(this.services.control); this.app.workspace.onLayoutReady(onReady); } onload() { void this._startUp(); } - async saveSettings() { - await this.services.setting.saveSettingData(); - } onunload() { - return void this.services.appLifecycle.onAppUnload(); + return void this.services.control.onUnload(); } - // <-- Plug-in's overrideable functions } // For now, diff --git a/src/managers/StorageEventManagerObsidian.ts b/src/managers/StorageEventManagerObsidian.ts index aaedacf..b48081b 100644 --- a/src/managers/StorageEventManagerObsidian.ts +++ b/src/managers/StorageEventManagerObsidian.ts @@ -203,8 +203,8 @@ export class StorageEventManagerObsidian extends StorageEventManagerBase { const totalItems = allItems.length + this.concurrentProcessing.waiting; const processing = this.processingCount; const batchedCount = this._waitingMap.size; - this.core.batched.value = batchedCount; - this.core.processing.value = processing; - this.core.totalQueued.value = totalItems + batchedCount + processing; + this.fileProcessing.batched.value = batchedCount; + this.fileProcessing.processing.value = processing; + this.fileProcessing.totalQueued.value = totalItems + batchedCount + processing; } } diff --git a/src/modules/AbstractModule.ts b/src/modules/AbstractModule.ts index d208bc7..0b4a2ba 100644 --- a/src/modules/AbstractModule.ts +++ b/src/modules/AbstractModule.ts @@ -2,7 +2,7 @@ import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/co import type { AnyEntry, FilePathWithPrefix } from "@lib/common/types"; import type { LiveSyncCore } from "@/main"; import { stripAllPrefixes } from "@lib/string_and_binary/path"; -import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils"; +import { createInstanceLogFunction } from "@lib/services/lib/logUtils"; export abstract class AbstractModule { _log = createInstanceLogFunction(this.constructor.name, this.services.API); diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 6a6165f..84fb728 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -2,8 +2,8 @@ import { fireAndForget } from "octagonal-wheels/promises"; import { AbstractModule } from "../AbstractModule"; import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LEVEL_NOTICE, type LOG_LEVEL } from "octagonal-wheels/common/logger"; import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; -import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks"; -import { purgeUnreferencedChunks } from "@/lib/src/pouchdb/chunks"; +import { balanceChunkPurgedDBs } from "@lib/pouchdb/chunks"; +import { purgeUnreferencedChunks } from "@lib/pouchdb/chunks"; import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator"; import { type EntryDoc, type RemoteType } from "../../lib/src/common/types"; import { rateLimitedSharedExecution, scheduleTask, updatePreviousExecutionTime } from "../../common/utils"; @@ -12,8 +12,8 @@ import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/ev import { $msg } from "../../lib/src/common/i18n"; import type { LiveSyncCore } from "../../main"; import { ReplicateResultProcessor } from "./ReplicateResultProcessor"; -import { UnresolvedErrorManager } from "@/lib/src/services/base/UnresolvedErrorManager"; -import { clearHandlers } from "@/lib/src/replication/SyncParamsHandler"; +import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; +import { clearHandlers } from "@lib/replication/SyncParamsHandler"; const KEY_REPLICATION_ON_EVENT = "replicationOnEvent"; const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000; diff --git a/src/modules/core/ReplicateResultProcessor.ts b/src/modules/core/ReplicateResultProcessor.ts index 6b77a88..1fe9a53 100644 --- a/src/modules/core/ReplicateResultProcessor.ts +++ b/src/modules/core/ReplicateResultProcessor.ts @@ -6,7 +6,7 @@ import { type EntryLeaf, type LoadedEntry, type MetaEntry, -} from "@/lib/src/common/types"; +} from "@lib/common/types"; import type { ModuleReplicator } from "./ModuleReplicator"; import { isChunk, isValidPath } from "@/common/utils"; import type { LiveSyncCore } from "@/main"; @@ -17,8 +17,8 @@ import { LOG_LEVEL_VERBOSE, Logger, type LOG_LEVEL, -} from "@/lib/src/common/logger"; -import { fireAndForget, isAnyNote, throttle } from "@/lib/src/common/utils"; +} from "@lib/common/logger"; +import { fireAndForget, isAnyNote, throttle } from "@lib/common/utils"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore_v2"; import { serialized } from "octagonal-wheels/concurrency/lock"; import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; @@ -162,7 +162,8 @@ export class ReplicateResultProcessor { * Report the current status. */ protected reportStatus() { - this.core.replicationResultCount.value = this._queuedChanges.length + this._processingChanges.length; + this.services.replication.replicationResultCount.value = + this._queuedChanges.length + this._processingChanges.length; } /** @@ -381,7 +382,7 @@ export class ReplicateResultProcessor { releaser(); } } - }, this.replicator.core.databaseQueueCount); + }, this.services.replication.databaseQueueCount); } // Phase 2.1: process the document and apply to storage // This function is serialized per document to avoid race-condition for the same document. @@ -432,7 +433,7 @@ export class ReplicateResultProcessor { protected applyToStorage(entry: MetaEntry) { return this.withCounting(async () => { await this.services.replication.processSynchroniseResult(entry); - }, this.replicator.core.storageApplyingCount); + }, this.services.replication.storageApplyingCount); } /** diff --git a/src/modules/coreFeatures/ModuleConflictChecker.ts b/src/modules/coreFeatures/ModuleConflictChecker.ts index b004c0e..d3a53d5 100644 --- a/src/modules/coreFeatures/ModuleConflictChecker.ts +++ b/src/modules/coreFeatures/ModuleConflictChecker.ts @@ -71,7 +71,7 @@ export class ModuleConflictChecker extends AbstractModule { delay: 0, keepResultUntilDownstreamConnected: true, pipeTo: this.conflictResolveQueue, - totalRemainingReactiveSource: this.core.conflictProcessQueueCount, + totalRemainingReactiveSource: this.services.conflict.conflictProcessQueueCount, } ); onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { diff --git a/src/modules/coreFeatures/ModuleRedFlag.ts b/src/modules/coreFeatures/ModuleRedFlag.ts index 5ed6e1e..7ebdb0b 100644 --- a/src/modules/coreFeatures/ModuleRedFlag.ts +++ b/src/modules/coreFeatures/ModuleRedFlag.ts @@ -12,8 +12,8 @@ import type { LiveSyncCore } from "../../main.ts"; import FetchEverything from "../features/SetupWizard/dialogs/FetchEverything.svelte"; import RebuildEverything from "../features/SetupWizard/dialogs/RebuildEverything.svelte"; import { extractObject } from "octagonal-wheels/object"; -import { SvelteDialogManagerBase } from "@/lib/src/UI/svelteDialog.ts"; -import type { ServiceContext } from "@/lib/src/services/base/ServiceBase.ts"; +import { SvelteDialogManagerBase } from "@lib/UI/svelteDialog.ts"; +import type { ServiceContext } from "@lib/services/base/ServiceBase.ts"; export class ModuleRedFlag extends AbstractModule { async isFlagFileExist(path: string) { diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts index 3396bb4..9de2ad3 100644 --- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts +++ b/src/modules/essentialObsidian/ModuleObsidianAPI.ts @@ -10,9 +10,9 @@ import { import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts"; import { type CouchDBCredentials, type EntryDoc } from "../../lib/src/common/types.ts"; import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts"; -import { replicationFilter } from "@/lib/src/pouchdb/compress.ts"; -import { disableEncryption } from "@/lib/src/pouchdb/encryption.ts"; -import { enableEncryption } from "@/lib/src/pouchdb/encryption.ts"; +import { replicationFilter } from "@lib/pouchdb/compress.ts"; +import { disableEncryption } from "@lib/pouchdb/encryption.ts"; +import { enableEncryption } from "@lib/pouchdb/encryption.ts"; import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts"; import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts"; import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts"; @@ -96,7 +96,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { const size = body ? ` (${body.length})` : ""; try { const r = await this.__fetchByAPI(url, authHeader, opts); - this.plugin.requestCount.value = this.plugin.requestCount.value + 1; + this.services.API.requestCount.value = this.services.API.requestCount.value + 1; if (method == "POST" || method == "PUT") { this.last_successful_post = r.status - (r.status % 100) == 200; } else { @@ -113,7 +113,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { this._log(ex); throw ex; } finally { - this.plugin.responseCount.value = this.plugin.responseCount.value + 1; + this.services.API.responseCount.value = this.services.API.responseCount.value + 1; } } @@ -171,7 +171,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { headers.append("authorization", authHeader); } try { - this.plugin.requestCount.value = this.plugin.requestCount.value + 1; + this.services.API.requestCount.value = this.services.API.requestCount.value + 1; const response: Response = await (useRequestAPI ? this.__fetchByAPI(url.toString(), authHeader, { ...opts, headers }) : fetch(url, { ...opts, headers })); @@ -245,7 +245,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { this._log(ex); throw ex; } finally { - this.plugin.responseCount.value = this.plugin.responseCount.value + 1; + this.services.API.responseCount.value = this.services.API.responseCount.value + 1; } // return await fetch(url, opts); diff --git a/src/modules/essentialObsidian/ModuleObsidianEvents.ts b/src/modules/essentialObsidian/ModuleObsidianEvents.ts index 7db93a2..63814ff 100644 --- a/src/modules/essentialObsidian/ModuleObsidianEvents.ts +++ b/src/modules/essentialObsidian/ModuleObsidianEvents.ts @@ -5,7 +5,7 @@ import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { type TFile } from "../../deps.ts"; import { fireAndForget } from "octagonal-wheels/promises"; import { type FilePathWithPrefix } from "../../lib/src/common/types.ts"; -import { reactive, reactiveSource } from "octagonal-wheels/dataobject/reactive"; +import { reactive, reactiveSource, type ReactiveSource } from "octagonal-wheels/dataobject/reactive"; import { collectingChunks, pluginScanningCount, @@ -188,20 +188,25 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { } }); } - // TODO: separate + + // Process counting for app reload scheduling + _totalProcessingCount?: ReactiveSource = undefined; private _scheduleAppReload() { - if (!this.core._totalProcessingCount) { + if (!this._totalProcessingCount) { const __tick = reactiveSource(0); - this.core._totalProcessingCount = reactive(() => { - const dbCount = this.core.databaseQueueCount.value; - const replicationCount = this.core.replicationResultCount.value; - const storageApplyingCount = this.core.storageApplyingCount.value; + this._totalProcessingCount = reactive(() => { + const dbCount = this.services.replication.databaseQueueCount.value; + const replicationCount = this.services.replication.replicationResultCount.value; + const storageApplyingCount = this.services.replication.storageApplyingCount.value; const chunkCount = collectingChunks.value; const pluginScanCount = pluginScanningCount.value; const hiddenFilesCount = hiddenFilesEventCount.value + hiddenFilesProcessingCount.value; - const conflictProcessCount = this.core.conflictProcessQueueCount.value; - const e = this.core.pendingFileEventCount.value; - const proc = this.core.processingFileEventCount.value; + const conflictProcessCount = this.services.conflict.conflictProcessQueueCount.value; + // Now no longer `pendingFileEventCount` and `processingFileEventCount` is used + // const e = this.core.pendingFileEventCount.value; + // const proc = this.core.processingFileEventCount.value; + const e = 0; + const proc = 0; // eslint-disable-next-line @typescript-eslint/no-unused-vars const __ = __tick.value; return ( @@ -223,7 +228,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { ); let stableCheck = 3; - this.core._totalProcessingCount.onChanged((e) => { + this._totalProcessingCount.onChanged((e) => { if (e.value == 0) { if (stableCheck-- <= 0) { this.__performAppReload(); @@ -239,10 +244,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { }); } } + _isReloadingScheduled(): boolean { + return this._totalProcessingCount !== undefined; + } onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.askRestart.setHandler(this._askReload.bind(this)); services.appLifecycle.scheduleRestart.setHandler(this._scheduleAppReload.bind(this)); + services.appLifecycle.isReloadingScheduled.setHandler(this._isReloadingScheduled.bind(this)); } } diff --git a/src/modules/essentialObsidian/ModuleObsidianMenu.ts b/src/modules/essentialObsidian/ModuleObsidianMenu.ts index 0e63e9e..905e0bf 100644 --- a/src/modules/essentialObsidian/ModuleObsidianMenu.ts +++ b/src/modules/essentialObsidian/ModuleObsidianMenu.ts @@ -59,7 +59,7 @@ export class ModuleObsidianMenu extends AbstractModule { this.settings.liveSync = true; this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE); } - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); await this.services.setting.saveSettingData(); }, }); @@ -74,7 +74,7 @@ export class ModuleObsidianMenu extends AbstractModule { this.services.appLifecycle.setSuspended(true); this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE); } - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); await this.services.setting.saveSettingData(); }, }); diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index 6664ae0..e82cd1d 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -32,14 +32,14 @@ import { serialized } from "octagonal-wheels/concurrency/lock"; import { $msg } from "src/lib/src/common/i18n.ts"; import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts"; import type { LiveSyncCore } from "../../main.ts"; -import { LiveSyncError } from "@/lib/src/common/LSError.ts"; +import { LiveSyncError } from "@lib/common/LSError.ts"; import { isValidPath } from "@/common/utils.ts"; import { isValidFilenameInAndroid, isValidFilenameInDarwin, isValidFilenameInWidows, -} from "@/lib/src/string_and_binary/path.ts"; -import { MARK_LOG_SEPARATOR } from "@/lib/src/services/lib/logUtils.ts"; +} from "@lib/string_and_binary/path.ts"; +import { MARK_LOG_SEPARATOR } from "@lib/services/lib/logUtils.ts"; // This module cannot be a core module because it depends on the Obsidian UI. @@ -102,12 +102,12 @@ export class ModuleLog extends AbstractObsidianModule { }); return computed(() => formatted.value); } - const labelReplication = padLeftSpComputed(this.core.replicationResultCount, `📥`); - const labelDBCount = padLeftSpComputed(this.core.databaseQueueCount, `📄`); - const labelStorageCount = padLeftSpComputed(this.core.storageApplyingCount, `💾`); + const labelReplication = padLeftSpComputed(this.services.replication.replicationResultCount, `📥`); + const labelDBCount = padLeftSpComputed(this.services.replication.databaseQueueCount, `📄`); + const labelStorageCount = padLeftSpComputed(this.services.replication.storageApplyingCount, `💾`); const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`); const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`); - const labelConflictProcessCount = padLeftSpComputed(this.core.conflictProcessQueueCount, `🔩`); + const labelConflictProcessCount = padLeftSpComputed(this.services.conflict.conflictProcessQueueCount, `🔩`); const hiddenFilesCount = reactive(() => hiddenFilesEventCount.value - hiddenFilesProcessingCount.value); const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`); const queueCountLabelX = reactive(() => { @@ -116,12 +116,12 @@ export class ModuleLog extends AbstractObsidianModule { const queueCountLabel = () => queueCountLabelX.value; const requestingStatLabel = computed(() => { - const diff = this.core.requestCount.value - this.core.responseCount.value; + const diff = this.services.API.requestCount.value - this.services.API.responseCount.value; return diff != 0 ? "📲 " : ""; }); const replicationStatLabel = computed(() => { - const e = this.core.replicationStat.value; + const e = this.services.replicator.replicationStatics.value; const sent = e.sent; const arrived = e.arrived; const maxPullSeq = e.maxPullSeq; @@ -173,9 +173,9 @@ export class ModuleLog extends AbstractObsidianModule { } return { w, sent, pushLast, arrived, pullLast }; }); - const labelProc = padLeftSpComputed(this.core.processing, `⏳`); - const labelPend = padLeftSpComputed(this.core.totalQueued, `🛫`); - const labelInBatchDelay = padLeftSpComputed(this.core.batched, `📬`); + const labelProc = padLeftSpComputed(this.services.fileProcessing.processing, `⏳`); + const labelPend = padLeftSpComputed(this.services.fileProcessing.totalQueued, `🛫`); + const labelInBatchDelay = padLeftSpComputed(this.services.fileProcessing.batched, `📬`); const waitingLabel = computed(() => { return `${labelProc()}${labelPend()}${labelInBatchDelay()}`; }); diff --git a/src/modules/features/ModuleObsidianSetting.ts b/src/modules/features/ModuleObsidianSetting.ts deleted file mode 100644 index ee27279..0000000 --- a/src/modules/features/ModuleObsidianSetting.ts +++ /dev/null @@ -1,356 +0,0 @@ -// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser"; -import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts"; -import { - type BucketSyncSetting, - ChunkAlgorithmNames, - type ConfigPassphraseStore, - type CouchDBConnection, - DEFAULT_SETTINGS, - type ObsidianLiveSyncSettings, - SALT_OF_PASSPHRASE, - SETTING_KEY_P2P_DEVICE_NAME, -} from "../../lib/src/common/types"; -import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger"; -import { $msg, setLang } from "../../lib/src/common/i18n.ts"; -import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb.ts"; -import { getLanguage } from "@/deps.ts"; -import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts"; -import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts"; -import type { LiveSyncCore } from "../../main.ts"; -import { AbstractModule } from "../AbstractModule.ts"; -export class ModuleObsidianSettings extends AbstractModule { - async _everyOnLayoutReady(): Promise { - let isChanged = false; - if (this.settings.displayLanguage == "") { - const obsidianLanguage = getLanguage(); - if ( - SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported - obsidianLanguage != this.settings.displayLanguage // Check if the language is different from the current setting - ) { - // Check if the current setting is not empty (Means migrated or installed). - this.settings.displayLanguage = obsidianLanguage as I18N_LANGS; - isChanged = true; - setLang(this.settings.displayLanguage); - } else if (this.settings.displayLanguage == "") { - this.settings.displayLanguage = "def"; - setLang(this.settings.displayLanguage); - await this.services.setting.saveSettingData(); - } - } - if (isChanged) { - const revert = $msg("dialog.yourLanguageAvailable.btnRevertToDefault"); - if ( - (await this.core.confirm.askSelectStringDialogue($msg(`dialog.yourLanguageAvailable`), ["OK", revert], { - defaultAction: "OK", - title: $msg(`dialog.yourLanguageAvailable.Title`), - })) == revert - ) { - this.settings.displayLanguage = "def"; - setLang(this.settings.displayLanguage); - } - await this.services.setting.saveSettingData(); - } - return true; - } - getPassphrase(settings: ObsidianLiveSyncSettings) { - const methods: Record Promise> = { - "": () => Promise.resolve("*"), - LOCALSTORAGE: () => Promise.resolve(localStorage.getItem("ls-setting-passphrase") ?? false), - ASK_AT_LAUNCH: () => this.core.confirm.askString("Passphrase", "passphrase", ""), - }; - const method = settings.configPassphraseStore; - const methodFunc = method in methods ? methods[method] : methods[""]; - return methodFunc(); - } - - _saveDeviceAndVaultName(): void { - const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName(); - localStorage.setItem(lsKey, this.services.setting.getDeviceAndVaultName() || ""); - } - - usedPassphrase = ""; - private _clearUsedPassphrase(): void { - this.usedPassphrase = ""; - } - - async decryptConfigurationItem(encrypted: string, passphrase: string) { - const dec = await decryptString(encrypted, passphrase + SALT_OF_PASSPHRASE); - if (dec) { - this.usedPassphrase = passphrase; - return dec; - } - return false; - } - - async encryptConfigurationItem(src: string, settings: ObsidianLiveSyncSettings) { - if (this.usedPassphrase != "") { - return await encryptString(src, this.usedPassphrase + SALT_OF_PASSPHRASE); - } - - const passphrase = await this.getPassphrase(settings); - if (passphrase === false) { - this._log( - "Failed to obtain passphrase when saving data.json! Please verify the configuration.", - LOG_LEVEL_URGENT - ); - return ""; - } - const dec = await encryptString(src, passphrase + SALT_OF_PASSPHRASE); - if (dec) { - this.usedPassphrase = passphrase; - return dec; - } - - return ""; - } - - get appId() { - return this.services.API.getAppID(); - } - - async _saveSettingData() { - this.services.setting.saveDeviceAndVaultName(); - const settings = { ...this.settings }; - settings.deviceAndVaultName = ""; - if (settings.P2P_DevicePeerName && settings.P2P_DevicePeerName.trim() !== "") { - console.log("Saving device peer name to small config"); - this.services.config.setSmallConfig(SETTING_KEY_P2P_DEVICE_NAME, settings.P2P_DevicePeerName.trim()); - settings.P2P_DevicePeerName = ""; - } - if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) { - this._log("Failed to retrieve passphrase. data.json contains unencrypted items!", LOG_LEVEL_NOTICE); - } else { - if ( - settings.couchDB_PASSWORD != "" || - settings.couchDB_URI != "" || - settings.couchDB_USER != "" || - settings.couchDB_DBNAME - ) { - const connectionSetting: CouchDBConnection & BucketSyncSetting = { - couchDB_DBNAME: settings.couchDB_DBNAME, - couchDB_PASSWORD: settings.couchDB_PASSWORD, - couchDB_URI: settings.couchDB_URI, - couchDB_USER: settings.couchDB_USER, - accessKey: settings.accessKey, - bucket: settings.bucket, - endpoint: settings.endpoint, - region: settings.region, - secretKey: settings.secretKey, - useCustomRequestHandler: settings.useCustomRequestHandler, - bucketCustomHeaders: settings.bucketCustomHeaders, - couchDB_CustomHeaders: settings.couchDB_CustomHeaders, - useJWT: settings.useJWT, - jwtKey: settings.jwtKey, - jwtAlgorithm: settings.jwtAlgorithm, - jwtKid: settings.jwtKid, - jwtExpDuration: settings.jwtExpDuration, - jwtSub: settings.jwtSub, - useRequestAPI: settings.useRequestAPI, - bucketPrefix: settings.bucketPrefix, - forcePathStyle: settings.forcePathStyle, - }; - settings.encryptedCouchDBConnection = await this.encryptConfigurationItem( - JSON.stringify(connectionSetting), - settings - ); - settings.couchDB_PASSWORD = ""; - settings.couchDB_DBNAME = ""; - settings.couchDB_URI = ""; - settings.couchDB_USER = ""; - settings.accessKey = ""; - settings.bucket = ""; - settings.region = ""; - settings.secretKey = ""; - settings.endpoint = ""; - } - if (settings.encrypt && settings.passphrase != "") { - settings.encryptedPassphrase = await this.encryptConfigurationItem(settings.passphrase, settings); - settings.passphrase = ""; - } - } - await this.core.saveData(settings); - eventHub.emitEvent(EVENT_SETTING_SAVED, settings); - } - - tryDecodeJson(encoded: string | false): object | false { - try { - if (!encoded) return false; - return JSON.parse(encoded); - } catch { - return false; - } - } - - async _decryptSettings(settings: ObsidianLiveSyncSettings): Promise { - const passphrase = await this.getPassphrase(settings); - if (passphrase === false) { - this._log("No passphrase found for data.json! Verify configuration before syncing.", LOG_LEVEL_URGENT); - } else { - if (settings.encryptedCouchDBConnection) { - const keys = [ - "couchDB_URI", - "couchDB_USER", - "couchDB_PASSWORD", - "couchDB_DBNAME", - "accessKey", - "bucket", - "endpoint", - "region", - "secretKey", - ] as (keyof CouchDBConnection | keyof BucketSyncSetting)[]; - const decrypted = this.tryDecodeJson( - await this.decryptConfigurationItem(settings.encryptedCouchDBConnection, passphrase) - ) as CouchDBConnection & BucketSyncSetting; - if (decrypted) { - for (const key of keys) { - if (key in decrypted) { - //@ts-ignore - settings[key] = decrypted[key]; - } - } - } else { - this._log( - "Failed to decrypt passphrase from data.json! Ensure configuration is correct before syncing with remote.", - LOG_LEVEL_URGENT - ); - for (const key of keys) { - //@ts-ignore - settings[key] = ""; - } - } - } - if (settings.encrypt && settings.encryptedPassphrase) { - const encrypted = settings.encryptedPassphrase; - const decrypted = await this.decryptConfigurationItem(encrypted, passphrase); - if (decrypted) { - settings.passphrase = decrypted; - } else { - this._log( - "Failed to decrypt passphrase from data.json! Ensure configuration is correct before syncing with remote.", - LOG_LEVEL_URGENT - ); - settings.passphrase = ""; - } - } - } - return settings; - } - - /** - * This method mutates the settings object. - * @param settings - * @returns - */ - _adjustSettings(settings: ObsidianLiveSyncSettings): Promise { - // Adjust settings as needed - - // Delete this feature to avoid problems on mobile. - settings.disableRequestURI = true; - - // GC is disabled. - settings.gcDelay = 0; - // So, use history is always enabled. - settings.useHistory = true; - - if ("workingEncrypt" in settings) delete settings.workingEncrypt; - if ("workingPassphrase" in settings) delete settings.workingPassphrase; - // Splitter configurations have been replaced with chunkSplitterVersion. - if (settings.chunkSplitterVersion == "") { - if (settings.enableChunkSplitterV2) { - if (settings.useSegmenter) { - settings.chunkSplitterVersion = "v2-segmenter"; - } else { - settings.chunkSplitterVersion = "v2"; - } - } else { - settings.chunkSplitterVersion = ""; - } - } else if (!(settings.chunkSplitterVersion in ChunkAlgorithmNames)) { - settings.chunkSplitterVersion = ""; - } - return Promise.resolve(settings); - } - - async _loadSettings(): Promise { - const settings = Object.assign({}, DEFAULT_SETTINGS, await this.core.loadData()) as ObsidianLiveSyncSettings; - - if (typeof settings.isConfigured == "undefined") { - // If migrated, mark true - if (JSON.stringify(settings) !== JSON.stringify(DEFAULT_SETTINGS)) { - settings.isConfigured = true; - } else { - settings.additionalSuffixOfDatabaseName = this.appId; - settings.isConfigured = false; - } - } - - this.settings = await this.services.setting.decryptSettings(settings); - - setLang(this.settings.displayLanguage); - - await this.services.setting.adjustSettings(this.settings); - - const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName(); - if (this.settings.deviceAndVaultName != "") { - if (!localStorage.getItem(lsKey)) { - this.services.setting.setDeviceAndVaultName(this.settings.deviceAndVaultName); - this.services.setting.saveDeviceAndVaultName(); - this.settings.deviceAndVaultName = ""; - } - } - if (isCloudantURI(this.settings.couchDB_URI) && this.settings.customChunkSize != 0) { - this._log( - "Configuration issues detected and automatically resolved. However, unsynchronized data may exist. Consider rebuilding if necessary.", - LOG_LEVEL_NOTICE - ); - this.settings.customChunkSize = 0; - } - this.services.setting.setDeviceAndVaultName(localStorage.getItem(lsKey) || ""); - if (this.services.setting.getDeviceAndVaultName() == "") { - if (this.settings.usePluginSync) { - this._log("Device name missing. Disabling plug-in sync.", LOG_LEVEL_NOTICE); - this.settings.usePluginSync = false; - } - } - - // this.core.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim()); - eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB); - } - - private _currentSettings(): ObsidianLiveSyncSettings { - return this.settings; - } - private _updateSettings(updateFn: (settings: ObsidianLiveSyncSettings) => ObsidianLiveSyncSettings): Promise { - try { - const updated = updateFn(this.settings); - this.settings = updated; - } catch (ex) { - this._log("Error in update function: " + ex, LOG_LEVEL_URGENT); - return Promise.reject(ex); - } - return Promise.resolve(); - } - private _applyPartial(partial: Partial): Promise { - try { - this.settings = { ...this.settings, ...partial }; - } catch (ex) { - this._log("Error in applying partial settings: " + ex, LOG_LEVEL_URGENT); - return Promise.reject(ex); - } - return Promise.resolve(); - } - - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - super.onBindFunction(core, services); - services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); - services.setting.clearUsedPassphrase.setHandler(this._clearUsedPassphrase.bind(this)); - services.setting.decryptSettings.setHandler(this._decryptSettings.bind(this)); - services.setting.adjustSettings.setHandler(this._adjustSettings.bind(this)); - services.setting.loadSettings.setHandler(this._loadSettings.bind(this)); - services.setting.currentSettings.setHandler(this._currentSettings.bind(this)); - services.setting.updateSettings.setHandler(this._updateSettings.bind(this)); - services.setting.applyPartial.setHandler(this._applyPartial.bind(this)); - services.setting.saveDeviceAndVaultName.setHandler(this._saveDeviceAndVaultName.bind(this)); - services.setting.saveSettingData.setHandler(this._saveSettingData.bind(this)); - } -} diff --git a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts index 12dd74e..cf6bd90 100644 --- a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts +++ b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts @@ -6,8 +6,8 @@ import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSetting import { parseYaml, stringifyYaml } from "../../deps"; 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 { ServiceContext } from "@lib/services/base/ServiceBase.ts"; +import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts"; import type { LiveSyncCore } from "@/main.ts"; const SETTING_HEADER = "````yaml:livesync-setting\n"; const SETTING_FOOTER = "\n````"; diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index f2ae1e4..ce9f71c 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -16,7 +16,7 @@ import { import { delay, isObjectDifferent, sizeToHumanReadable } from "../../../lib/src/common/utils.ts"; import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts"; import { Logger } from "../../../lib/src/common/logger.ts"; -import { checkSyncInfo } from "@/lib/src/pouchdb/negotiation.ts"; +import { checkSyncInfo } from "@lib/pouchdb/negotiation.ts"; import { testCrypt } from "octagonal-wheels/encryption/encryption"; import ObsidianLiveSyncPlugin from "../../../main.ts"; import { scheduleTask } from "../../../common/utils.ts"; diff --git a/src/modules/features/SettingDialogue/PaneSyncSettings.ts b/src/modules/features/SettingDialogue/PaneSyncSettings.ts index c7e2da4..aba3296 100644 --- a/src/modules/features/SettingDialogue/PaneSyncSettings.ts +++ b/src/modules/features/SettingDialogue/PaneSyncSettings.ts @@ -105,7 +105,7 @@ export function paneSyncSettings( if (!this.editingSettings.isConfigured) { this.editingSettings.isConfigured = true; await this.saveAllDirtySettings(); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); await this.rebuildDB("localOnly"); // this.resetEditingSettings(); if ( @@ -124,13 +124,13 @@ export function paneSyncSettings( await this.confirmRebuild(); } else { await this.saveAllDirtySettings(); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); this.services.appLifecycle.askRestart(); } } } else { await this.saveAllDirtySettings(); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); } }); }); @@ -169,7 +169,7 @@ export function paneSyncSettings( } await this.saveSettings(["liveSync", "periodicReplication"]); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); }); new Setting(paneEl) diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index 34dc3f6..af645e7 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -49,7 +49,7 @@ export class ModuleLiveSyncMain extends AbstractModule { } if (!(await this.core.services.appLifecycle.onFirstInitialise())) return false; // await this.core.$$realizeSettingSyncMode(); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); fireAndForget(async () => { this._log($msg("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE); if (!(await this.services.appLifecycle.onScanningStartupIssues())) { @@ -65,7 +65,7 @@ export class ModuleLiveSyncMain extends AbstractModule { eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => { fireAndForget(async () => { try { - await this.core.services.setting.realiseSetting(); + await this.core.services.control.applySettings(); const lang = this.core.services.setting.currentSettings()?.displayLanguage ?? undefined; if (lang !== undefined) { setLang(this.core.services.setting.currentSettings()?.displayLanguage); @@ -163,23 +163,19 @@ export class ModuleLiveSyncMain extends AbstractModule { return; } - private async _realizeSettingSyncMode(): Promise { - await this.services.appLifecycle.onSuspending(); - await this.services.setting.onBeforeRealiseSetting(); - this.localDatabase.refreshSettings(); - await this.services.fileProcessing.commitPendingFileEvents(); - await this.services.setting.onRealiseSetting(); - // disable all sync temporary. - if (this.services.appLifecycle.isSuspended()) return; - await this.services.appLifecycle.onResuming(); - await this.services.appLifecycle.onResumed(); - await this.services.setting.onSettingRealised(); - return; - } - - _isReloadingScheduled(): boolean { - return this.core._totalProcessingCount !== undefined; - } + // private async _realizeSettingSyncMode(): Promise { + // await this.services.appLifecycle.onSuspending(); + // await this.services.setting.onBeforeRealiseSetting(); + // this.localDatabase.refreshSettings(); + // await this.services.fileProcessing.commitPendingFileEvents(); + // await this.services.setting.onRealiseSetting(); + // // disable all sync temporary. + // if (this.services.appLifecycle.isSuspended()) return; + // await this.services.appLifecycle.onResuming(); + // await this.services.appLifecycle.onResumed(); + // await this.services.setting.onSettingRealised(); + // return; + // } isReady = false; @@ -217,11 +213,9 @@ export class ModuleLiveSyncMain extends AbstractModule { services.appLifecycle.markIsReady.setHandler(this._markIsReady.bind(this)); services.appLifecycle.resetIsReady.setHandler(this._resetIsReady.bind(this)); services.appLifecycle.hasUnloaded.setHandler(this._isUnloaded.bind(this)); - services.appLifecycle.isReloadingScheduled.setHandler(this._isReloadingScheduled.bind(this)); services.appLifecycle.onReady.addHandler(this._onLiveSyncReady.bind(this)); services.appLifecycle.onWireUpEvents.addHandler(this._wireUpEvents.bind(this)); services.appLifecycle.onLoad.addHandler(this._onLiveSyncLoad.bind(this)); services.appLifecycle.onAppUnload.addHandler(this._onLiveSyncUnload.bind(this)); - services.setting.realiseSetting.setHandler(this._realizeSettingSyncMode.bind(this)); } } diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts index b771c29..a1e3f1f 100644 --- a/src/modules/services/ObsidianAPIService.ts +++ b/src/modules/services/ObsidianAPIService.ts @@ -1,13 +1,20 @@ -import { InjectableAPIService } from "@/lib/src/services/implements/injectable/InjectableAPIService"; -import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; +import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService"; +import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; import { Platform, type Command, type ViewCreator } from "obsidian"; import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler"; +import { ObsidianConfirm } from "./ObsidianConfirm"; +import type { Confirm } from "@lib/interfaces/Confirm"; // 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; + _confirmInstance: Confirm; + constructor(context: ObsidianServiceContext) { + super(context); + this._confirmInstance = new ObsidianConfirm(context); + } getCustomFetchHandler(): ObsHttpHandler { if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined); return this._customHandler; @@ -63,6 +70,11 @@ export class ObsidianAPIService extends InjectableAPIService(command: TCommand): TCommand { return this.context.plugin.addCommand(command) as TCommand; } diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 8e8969a..8df8284 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -1,6 +1,6 @@ -import { InjectableServiceHub } from "@/lib/src/services/implements/injectable/InjectableServiceHub"; -import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; -import type { ServiceInstances } from "@/lib/src/services/ServiceHub"; +import { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; +import { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; +import type { ServiceInstances } from "@lib/services/ServiceHub"; import type ObsidianLiveSyncPlugin from "@/main"; import { ObsidianConflictService, @@ -8,13 +8,14 @@ import { ObsidianReplicationService, ObsidianReplicatorService, ObsidianRemoteService, - ObsidianSettingService, ObsidianTweakValueService, ObsidianTestService, ObsidianDatabaseEventService, ObsidianConfigService, ObsidianKeyValueDBService, + ObsidianControlService, } from "./ObsidianServices"; +import { ObsidianSettingService } from "./ObsidianSettingService"; import { ObsidianDatabaseService } from "./ObsidianDatabaseService"; import { ObsidianAPIService } from "./ObsidianAPIService"; import { ObsidianAppLifecycleService } from "./ObsidianAppLifecycleService"; @@ -35,10 +36,14 @@ export class ObsidianServiceHub extends InjectableServiceHub>; super(context, serviceInstancesToInit); diff --git a/src/modules/services/ObsidianServices.ts b/src/modules/services/ObsidianServices.ts index 9ac3129..c6812b9 100644 --- a/src/modules/services/ObsidianServices.ts +++ b/src/modules/services/ObsidianServices.ts @@ -4,12 +4,12 @@ import { InjectableFileProcessingService } from "@lib/services/implements/inject import { InjectableRemoteService } from "@lib/services/implements/injectable/InjectableRemoteService"; import { InjectableReplicationService } from "@lib/services/implements/injectable/InjectableReplicationService"; import { InjectableReplicatorService } from "@lib/services/implements/injectable/InjectableReplicatorService"; -import { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService"; import { InjectableTestService } from "@lib/services/implements/injectable/InjectableTestService"; 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 { KeyValueDBService } from "@/lib/src/services/base/KeyValueDBService"; +import { KeyValueDBService } from "@lib/services/base/KeyValueDBService"; +import { ControlService } from "@lib/services/base/ControlService"; export class ObsidianDatabaseEventService extends InjectableDatabaseEventService {} @@ -23,8 +23,6 @@ export class ObsidianReplicationService extends InjectableReplicationService {} // InjectableConflictService export class ObsidianConflictService extends InjectableConflictService {} -// InjectableSettingService -export class ObsidianSettingService extends InjectableSettingService {} // InjectableTweakValueService export class ObsidianTweakValueService extends InjectableTweakValueService {} // InjectableTestService @@ -32,3 +30,5 @@ export class ObsidianTestService extends InjectableTestService {} export class ObsidianKeyValueDBService extends KeyValueDBService {} + +export class ObsidianControlService extends ControlService {} diff --git a/src/modules/services/ObsidianSettingService.ts b/src/modules/services/ObsidianSettingService.ts new file mode 100644 index 0000000..a9dfbb4 --- /dev/null +++ b/src/modules/services/ObsidianSettingService.ts @@ -0,0 +1,35 @@ +import { type ObsidianLiveSyncSettings } from "@lib/common/types"; +import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; +import { eventHub } from "@lib/hub/hub"; +import { SettingService, type SettingServiceDependencies } from "@lib/services/base/SettingService"; +import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; + +export class ObsidianSettingService extends SettingService { + constructor(context: T, dependencies: SettingServiceDependencies) { + super(context, dependencies); + this.onSettingSaved.addHandler((settings) => { + eventHub.emitEvent(EVENT_SETTING_SAVED, settings); + return Promise.resolve(true); + }); + this.onSettingLoaded.addHandler((settings) => { + eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB); + return Promise.resolve(true); + }); + } + protected setItem(key: string, value: string) { + return localStorage.setItem(key, value); + } + protected getItem(key: string): string { + return localStorage.getItem(key) ?? ""; + } + protected deleteItem(key: string): void { + localStorage.removeItem(key); + } + + protected override async saveData(data: ObsidianLiveSyncSettings): Promise { + return await this.context.liveSyncPlugin.saveData(data); + } + protected override async loadData(): Promise { + return await this.context.liveSyncPlugin.loadData(); + } +} diff --git a/src/modules/services/ObsidianUIService.ts b/src/modules/services/ObsidianUIService.ts index 37b7230..d053f25 100644 --- a/src/modules/services/ObsidianUIService.ts +++ b/src/modules/services/ObsidianUIService.ts @@ -2,14 +2,15 @@ import type { ConfigService } from "@lib/services/base/ConfigService"; import type { AppLifecycleService } from "@lib/services/base/AppLifecycleService"; import type { ReplicatorService } from "@lib/services/base/ReplicatorService"; import { UIService } from "@lib/services//implements/base/UIService"; -import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; +import { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; import { ObsidianSvelteDialogManager } from "./SvelteDialogObsidian"; -import { ObsidianConfirm } from "./ObsidianConfirm"; -import DialogToCopy from "@/lib/src/UI/dialogues/DialogueToCopy.svelte"; +import DialogToCopy from "@lib/UI/dialogues/DialogueToCopy.svelte"; +import type { IAPIService } from "@lib/services/base/IService"; export type ObsidianUIServiceDependencies = { appLifecycle: AppLifecycleService; config: ConfigService; replicator: ReplicatorService; + APIService: IAPIService; }; export class ObsidianUIService extends UIService { @@ -17,7 +18,7 @@ export class ObsidianUIService extends UIService { return DialogToCopy; } constructor(context: ObsidianServiceContext, dependents: ObsidianUIServiceDependencies) { - const obsidianConfirm = new ObsidianConfirm(context); + const obsidianConfirm = dependents.APIService.confirm; const obsidianSvelteDialogManager = new ObsidianSvelteDialogManager(context, { appLifecycle: dependents.appLifecycle, config: dependents.config, @@ -27,7 +28,7 @@ export class ObsidianUIService extends UIService { super(context, { appLifecycle: dependents.appLifecycle, dialogManager: obsidianSvelteDialogManager, - confirm: obsidianConfirm, + APIService: dependents.APIService, }); } } diff --git a/src/serviceFeatures/onLayoutReady.ts b/src/serviceFeatures/onLayoutReady.ts new file mode 100644 index 0000000..3ddfa0e --- /dev/null +++ b/src/serviceFeatures/onLayoutReady.ts @@ -0,0 +1,3 @@ +import { enableI18nFeature } from "./onLayoutReady/enablei18n"; + +export const onLayoutReadyFeatures = [enableI18nFeature]; diff --git a/src/serviceFeatures/onLayoutReady/enablei18n.ts b/src/serviceFeatures/onLayoutReady/enablei18n.ts new file mode 100644 index 0000000..d908597 --- /dev/null +++ b/src/serviceFeatures/onLayoutReady/enablei18n.ts @@ -0,0 +1,41 @@ +import { getLanguage } from "@/deps"; +import { createServiceFeature } from "../types.ts"; +import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@lib/common/rosetta"; +import { $msg, setLang } from "@lib/common/i18n"; + +export const enableI18nFeature = createServiceFeature(async ({ services: { setting, API } }) => { + let isChanged = false; + const settings = setting.currentSettings(); + if (settings.displayLanguage == "") { + const obsidianLanguage = getLanguage(); + if ( + SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported + obsidianLanguage != settings.displayLanguage // Check if the language is different from the current setting + ) { + // Check if the current setting is not empty (Means migrated or installed). + // settings.displayLanguage = obsidianLanguage as I18N_LANGS; + await setting.applyPartial({ displayLanguage: obsidianLanguage as I18N_LANGS }); + isChanged = true; + setLang(settings.displayLanguage); + } else if (settings.displayLanguage == "") { + // settings.displayLanguage = "def"; + await setting.applyPartial({ displayLanguage: "def" }); + setLang(settings.displayLanguage); + await setting.saveSettingData(); + } + } + if (isChanged) { + const revert = $msg("dialog.yourLanguageAvailable.btnRevertToDefault"); + if ( + (await API.confirm.askSelectStringDialogue($msg(`dialog.yourLanguageAvailable`), ["OK", revert], { + defaultAction: "OK", + title: $msg(`dialog.yourLanguageAvailable.Title`), + })) == revert + ) { + await setting.applyPartial({ displayLanguage: "def" }); + setLang(settings.displayLanguage); + } + await setting.saveSettingData(); + } + return true; +}); diff --git a/src/serviceFeatures/types.ts b/src/serviceFeatures/types.ts new file mode 100644 index 0000000..bc18fd2 --- /dev/null +++ b/src/serviceFeatures/types.ts @@ -0,0 +1,50 @@ +import type { IServiceHub } from "@lib/services/base/IService"; +import type { DatabaseFileAccess } from "@lib/interfaces/DatabaseFileAccess"; +import type { Rebuilder } from "@lib/interfaces/DatabaseRebuilder"; +import type { IFileHandler } from "@lib/interfaces/FileHandler"; +import type { StorageAccess } from "@lib/interfaces/StorageAccess"; + +export interface ServiceModules { + storageAccess: StorageAccess; + /** + * Database File Accessor for handling file operations related to the database, such as exporting the database, importing from a file, etc. + */ + databaseFileAccess: DatabaseFileAccess; + + /** + * File Handler for handling file operations related to replication, such as resolving conflicts, applying changes from replication, etc. + */ + fileHandler: IFileHandler; + /** + * Rebuilder for handling database rebuilding operations. + */ + rebuilder: Rebuilder; +} +export type RequiredServices = Pick; +export type RequiredServiceModules = Pick; + +export type NecessaryServices = { + services: RequiredServices; + serviceModules: RequiredServiceModules; +}; + +export type ServiceFeatureFunction = ( + host: NecessaryServices +) => TR; + +/** + * Helper function to create a service feature with proper typing. + * @param featureFunction The feature function to be wrapped. + * @returns The same feature function with proper typing. + * @example + * const myFeatureDef = createServiceFeature(({ services: { API }, serviceModules: { storageAccess } }) => { + * // ... + * }); + * const myFeature = myFeatureDef.bind(null, this); // <- `this` may `ObsidianLiveSyncPlugin` or a custom context object + * appLifecycle.onLayoutReady(myFeature); + */ +export function createServiceFeature( + featureFunction: ServiceFeatureFunction +): ServiceFeatureFunction { + return featureFunction; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..22ece98 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,27 @@ +import type { DatabaseFileAccess } from "@/lib/src/interfaces/DatabaseFileAccess"; +import type { Rebuilder } from "@/lib/src/interfaces/DatabaseRebuilder"; +import type { IFileHandler } from "@/lib/src/interfaces/FileHandler"; +import type { StorageAccess } from "@/lib/src/interfaces/StorageAccess"; +import type { IServiceHub } from "./lib/src/services/base/IService"; + +export interface ServiceModules { + storageAccess: StorageAccess; + /** + * Database File Accessor for handling file operations related to the database, such as exporting the database, importing from a file, etc. + */ + databaseFileAccess: DatabaseFileAccess; + + /** + * File Handler for handling file operations related to replication, such as resolving conflicts, applying changes from replication, etc. + */ + fileHandler: IFileHandler; + /** + * Rebuilder for handling database rebuilding operations. + */ + rebuilder: Rebuilder; +} + +export interface LiveSyncHost { + services: IServiceHub; + serviceModules: ServiceModules; +} diff --git a/test/harness/harness.ts b/test/harness/harness.ts index 390934f..42f3f3e 100644 --- a/test/harness/harness.ts +++ b/test/harness/harness.ts @@ -122,13 +122,11 @@ export async function waitForIdle(harness: LiveSyncHarness): Promise { for (let i = 0; i < 20; i++) { await delay(25); const processing = - harness.plugin.databaseQueueCount.value + - harness.plugin.processingFileEventCount.value + - harness.plugin.pendingFileEventCount.value + - harness.plugin.totalQueued.value + - harness.plugin.batched.value + - harness.plugin.processing.value + - harness.plugin.storageApplyingCount.value; + harness.plugin.services.replication.databaseQueueCount.value + + harness.plugin.services.fileProcessing.totalQueued.value + + harness.plugin.services.fileProcessing.batched.value + + harness.plugin.services.fileProcessing.processing.value + + harness.plugin.services.replication.storageApplyingCount.value; if (processing === 0) { if (i > 0) { diff --git a/updates.md b/updates.md index f1ff090..52e5783 100644 --- a/updates.md +++ b/updates.md @@ -5,11 +5,28 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ## 0.25.43-patched-7 --- Unreleased -- +19th February, 2026 + +Right then, let us make a decision already. + +Last time, since I found a bug, I ended up doing a few other things as well, but next time I intend to release it with just the bug fix. It is quite substantial, after all. + +Customisation Sync has mostly been verified. Hidden file synchronisation has not been done yet. ### Fixed -- Fixed an issue where the StorageEventManager was not correctly the loading the settings. +- Fixed an issue where the StorageEventManager was not correctly loading the settings. +- Replication statistics are now correctly reset after switching replicators. + +### Refactored + +- Now, many reactive values which keep the state or statistics of the plugin are moved to the services which have the responsibility for these states. +- `serviceFeatures` are now able to be added to the services; this is not a class module, but a function which accepts dependencies and returns an addHandler-able function. This is for better separation of concerns, better maintainability, and testability. +- `control` service; is a meta-service which is responsible for orchestrating services has been added. + - Don't you think stopping replication or something occurs during `settingService.realiseSetting` is quite weird? It may be done by the control service, which can orchestrate the setting service and the replicator service. + - +- Some functions on services have been moved. e.g., `getSystemVaultName` is now on the API service. +- Setting Service is now responsible for the setting, no longer using dynamic binding for the modules. ## 0.25.43-patched-6