diff --git a/manifest.json b/manifest.json index e2df7e3..9f9365d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-7", + "version": "0.25.43-patched-8", "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 7f77fe8..9137591 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-7", + "version": "0.25.43-patched-8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-7", + "version": "0.25.43-patched-8", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 096b37b..d3858a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-7", + "version": "0.25.43-patched-8", "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 3168558..73fe39a 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -87,9 +87,6 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { (this.services.API as BrowserAPIService).getSystemVaultName.setHandler( () => "p2p-livesync-web-peer" ); - // this.services.setting.currentSettings.setHandler(() => { - // return this.settings as any; - // }); } async init() { // const { simpleStoreAPI } = await getWrappedSynchromesh(); @@ -150,9 +147,9 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { simpleStore(): SimpleStore { return this._simpleStore; } - handleReplicatedDocuments(docs: EntryDoc[]): Promise { + handleReplicatedDocuments(docs: EntryDoc[]): Promise { // No op. This is a client and does not need to process the docs - return Promise.resolve(); + return Promise.resolve(true); } getPluginShim() { diff --git a/src/common/SvelteItemView.ts b/src/common/SvelteItemView.ts index 3b8f788..fb421e4 100644 --- a/src/common/SvelteItemView.ts +++ b/src/common/SvelteItemView.ts @@ -4,7 +4,7 @@ import { type mount, unmount } from "svelte"; export abstract class SvelteItemView extends ItemView { abstract instantiateComponent(target: HTMLElement): ReturnType | Promise>; component?: ReturnType; - async onOpen() { + override async onOpen() { await super.onOpen(); this.contentEl.empty(); await this._dismountComponent(); @@ -17,7 +17,7 @@ export abstract class SvelteItemView extends ItemView { this.component = undefined; } } - async onClose() { + override async onClose() { await super.onClose(); if (this.component) { await unmount(this.component); diff --git a/src/common/utils.ts b/src/common/utils.ts index 3ef6d80..ef1eba1 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -31,7 +31,6 @@ import { sameChangePairs } from "./stores.ts"; import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { EVENT_PLUGIN_UNLOADED, eventHub } from "./events.ts"; -import { promiseWithResolver, type PromiseWithResolvers } from "octagonal-wheels/promises"; import { AuthorizationHeaderGenerator } from "../lib/src/replication/httplib.ts"; import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts"; @@ -152,7 +151,7 @@ export class PeriodicProcessor { () => fireAndForget(async () => { await this.process(); - if (this._plugin.services?.appLifecycle?.hasUnloaded()) { + if (this._plugin.services?.control?.hasUnloaded()) { this.disable(); } }), @@ -459,47 +458,3 @@ export function onlyInNTimes(n: number, proc: (progress: number) => any) { } }; } - -const waitingTasks = {} as Record; previous: number; leastNext: number }>; - -export function rateLimitedSharedExecution(key: string, interval: number, proc: () => Promise): Promise { - if (!(key in waitingTasks)) { - waitingTasks[key] = { task: undefined, previous: 0, leastNext: 0 }; - } - if (waitingTasks[key].task) { - // Extend the previous execution time. - waitingTasks[key].leastNext = Date.now() + interval; - return waitingTasks[key].task.promise; - } - - const previous = waitingTasks[key].previous; - - const delay = previous == 0 ? 0 : Math.max(interval - (Date.now() - previous), 0); - - const task = promiseWithResolver(); - void task.promise.finally(() => { - if (waitingTasks[key].task === task) { - waitingTasks[key].task = undefined; - waitingTasks[key].previous = Math.max(Date.now(), waitingTasks[key].leastNext); - } - }); - waitingTasks[key] = { - task, - previous: Date.now(), - leastNext: Date.now() + interval, - }; - void scheduleTask("thin-out-" + key, delay, async () => { - try { - task.resolve(await proc()); - } catch (ex) { - task.reject(ex); - } - }); - return task.promise; -} -export function updatePreviousExecutionTime(key: string, timeDelta: number = 0) { - if (!(key in waitingTasks)) { - waitingTasks[key] = { task: undefined, previous: 0, leastNext: 0 }; - } - waitingTasks[key].leastNext = Math.max(Date.now() + timeDelta, waitingTasks[key].leastNext); -} diff --git a/src/features/ConfigSync/CmdConfigSync.ts b/src/features/ConfigSync/CmdConfigSync.ts index 2a0d046..e51af06 100644 --- a/src/features/ConfigSync/CmdConfigSync.ts +++ b/src/features/ConfigSync/CmdConfigSync.ts @@ -1802,7 +1802,7 @@ export class ConfigSync extends LiveSyncCommands { } return files; } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.fileProcessing.processOptionalFileEvent.addHandler(this._anyProcessOptionalFileEvent.bind(this)); services.conflict.getOptionalConflictCheckMethod.addHandler(this._anyGetOptionalConflictCheckMethod.bind(this)); services.replication.processVirtualDocument.addHandler(this._anyModuleParsedReplicationResultItem.bind(this)); diff --git a/src/features/ConfigSync/PluginDialogModal.ts b/src/features/ConfigSync/PluginDialogModal.ts index 3b1d8c1..d75043b 100644 --- a/src/features/ConfigSync/PluginDialogModal.ts +++ b/src/features/ConfigSync/PluginDialogModal.ts @@ -14,7 +14,7 @@ export class PluginDialogModal extends Modal { this.plugin = plugin; } - onOpen() { + override onOpen() { const { contentEl } = this; this.contentEl.style.overflow = "auto"; this.contentEl.style.display = "flex"; @@ -28,7 +28,7 @@ export class PluginDialogModal extends Modal { } } - onClose() { + override onClose() { if (this.component) { void unmount(this.component); this.component = undefined; diff --git a/src/features/HiddenFileCommon/JsonResolveModal.ts b/src/features/HiddenFileCommon/JsonResolveModal.ts index 309e5cc..c10baab 100644 --- a/src/features/HiddenFileCommon/JsonResolveModal.ts +++ b/src/features/HiddenFileCommon/JsonResolveModal.ts @@ -50,7 +50,7 @@ export class JsonResolveModal extends Modal { this.callback = undefined; } - onOpen() { + override onOpen() { const { contentEl } = this; this.titleEl.setText(this.title); contentEl.empty(); @@ -74,7 +74,7 @@ export class JsonResolveModal extends Modal { return; } - onClose() { + override onClose() { const { contentEl } = this; contentEl.empty(); // contentEl.empty(); diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index 8d50d74..d8c4d60 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -1934,7 +1934,7 @@ ${messageFetch}${messageOverwrite}${messageMerge} */ // <-- Local Storage SubFunctions - onBindFunction(core: LiveSyncCore, services: typeof core.services) { + override onBindFunction(core: LiveSyncCore, services: typeof core.services) { // No longer needed on initialisation // services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this)); services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts index 4a691b3..e9b2125 100644 --- a/src/features/P2PSync/CmdP2PReplicator.ts +++ b/src/features/P2PSync/CmdP2PReplicator.ts @@ -40,9 +40,6 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase getSettings(): P2PSyncSetting { return this.plugin.settings; } - get settings() { - return this.plugin.settings; - } getDB() { return this.plugin.localDatabase.localDatabase; } @@ -65,7 +62,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase // this.onBindFunction(plugin, plugin.services); } - async handleReplicatedDocuments(docs: EntryDoc[]): Promise { + async handleReplicatedDocuments(docs: EntryDoc[]): Promise { // console.log("Processing Replicated Docs", docs); return await this.services.replication.parseSynchroniseResult( docs as PouchDB.Core.ExistingDocument[] diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts index b22c4bc..806da90 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts @@ -35,11 +35,11 @@ function removeFromList(item: string, list: string) { export class P2PReplicatorPaneView extends SvelteItemView { plugin: ObsidianLiveSyncPlugin; - icon = "waypoints"; + override icon = "waypoints"; title: string = ""; - navigation = false; + override navigation = false; - getIcon(): string { + override getIcon(): string { return "waypoints"; } get replicator() { diff --git a/src/lib b/src/lib index 820a266..d402f2d 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 820a26641a92242ae2939467ee9cf857afee3ed2 +Subproject commit d402f2d7f3d5677bee4ca11846b0c4f2259840f1 diff --git a/src/main.ts b/src/main.ts index 4ea2d17..4f335df 100644 --- a/src/main.ts +++ b/src/main.ts @@ -43,7 +43,6 @@ import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB. 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 { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker.ts"; import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks.ts"; import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts"; @@ -115,6 +114,7 @@ export default class ObsidianLiveSyncPlugin */ private _registerAddOn(addOn: LiveSyncCommands) { this.addOns.push(addOn); + this.services.appLifecycle.onUnload.addHandler(() => Promise.resolve(addOn.onunload()).then(() => true)); } private registerAddOns() { @@ -163,7 +163,6 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleReplicatorCouchDB(this)); this._registerModule(new ModuleReplicator(this)); this._registerModule(new ModuleConflictResolver(this)); - this._registerModule(new ModuleRemoteGovernor(this)); this._registerModule(new ModuleTargetFilter(this)); this._registerModule(new ModulePeriodicProcess(this)); this._registerModule(new ModuleInitializerFile(this)); @@ -439,10 +438,10 @@ export default class ObsidianLiveSyncPlugin const onReady = this.services.control.onReady.bind(this.services.control); this.app.workspace.onLayoutReady(onReady); } - onload() { + override onload() { void this._startUp(); } - onunload() { + override onunload() { return void this.services.control.onUnload(); } } diff --git a/src/modules/AbstractObsidianModule.ts b/src/modules/AbstractObsidianModule.ts index cf2ab2e..0d188a3 100644 --- a/src/modules/AbstractObsidianModule.ts +++ b/src/modules/AbstractObsidianModule.ts @@ -16,7 +16,7 @@ export abstract class AbstractObsidianModule extends AbstractModule { constructor( public plugin: ObsidianLiveSyncPlugin, - public core: LiveSyncCore + core: LiveSyncCore ) { super(core); } diff --git a/src/modules/core/ModulePeriodicProcess.ts b/src/modules/core/ModulePeriodicProcess.ts index 30e2039..a50a95e 100644 --- a/src/modules/core/ModulePeriodicProcess.ts +++ b/src/modules/core/ModulePeriodicProcess.ts @@ -31,7 +31,7 @@ export class ModulePeriodicProcess extends AbstractModule { return this.resumePeriodic(); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onUnload.addHandler(this._allOnUnload.bind(this)); services.setting.onBeforeRealiseSetting.addHandler(this._everyBeforeRealizeSetting.bind(this)); services.setting.onSettingRealised.addHandler(this._everyAfterRealizeSetting.bind(this)); diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 84fb728..622d13f 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -1,12 +1,12 @@ 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 { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "octagonal-wheels/common/logger"; +import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; 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"; +import { scheduleTask } from "../../common/utils"; import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/events"; import { $msg } from "../../lib/src/common/i18n"; @@ -14,9 +14,45 @@ import type { LiveSyncCore } from "../../main"; import { ReplicateResultProcessor } from "./ReplicateResultProcessor"; import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; import { clearHandlers } from "@lib/replication/SyncParamsHandler"; +import type { NecessaryServices } from "@/serviceFeatures/types"; -const KEY_REPLICATION_ON_EVENT = "replicationOnEvent"; -const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000; +function isOnlineAndCanReplicate( + errorManager: UnresolvedErrorManager, + host: NecessaryServices<"database", any>, + showMessage: boolean +): Promise { + const errorMessage = "Network is offline"; + const manager = host.services.database.managers.networkManager; + if (!manager.isOnline) { + errorManager.showError(errorMessage, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); + return Promise.resolve(false); + } + errorManager.clearError(errorMessage); + return Promise.resolve(true); +} +async function canReplicateWithPBKDF2( + errorManager: UnresolvedErrorManager, + host: NecessaryServices<"replicator" | "setting", any>, + showMessage: boolean +): Promise { + const currentSettings = host.services.setting.currentSettings(); + // TODO: check using PBKDF2 salt? + const errorMessage = $msg("Replicator.Message.InitialiseFatalError"); + const replicator = host.services.replicator.getActiveReplicator(); + if (!replicator) { + errorManager.showError(errorMessage, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); + return false; + } + errorManager.clearError(errorMessage); + const ensureMessage = "Failed to initialise the encryption key, preventing replication."; + const ensureResult = await replicator.ensurePBKDF2Salt(currentSettings, showMessage, true); + if (!ensureResult) { + errorManager.showError(ensureMessage, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); + return false; + } + errorManager.clearError(ensureMessage); + return ensureResult; // is true. +} export class ModuleReplicator extends AbstractModule { _replicatorType?: RemoteType; @@ -26,9 +62,6 @@ export class ModuleReplicator extends AbstractModule { this.core.services.appLifecycle ); - showError(msg: string, max_log_level: LOG_LEVEL = LEVEL_NOTICE) { - this._unresolvedErrorManager.showError(msg, max_log_level); - } clearErrors() { this._unresolvedErrorManager.clearErrors(); } @@ -40,10 +73,6 @@ export class ModuleReplicator extends AbstractModule { } }); eventHub.onEvent(EVENT_SETTING_SAVED, (setting) => { - // ReplicatorService responds to `settingService.onRealiseSetting`. - // if (this._replicatorType !== setting.remoteType) { - // void this.setReplicator(); - // } if (this.core.settings.suspendParseReplicationResult) { this.processor.suspend(); } else { @@ -65,41 +94,12 @@ export class ModuleReplicator extends AbstractModule { return Promise.resolve(true); } - async ensureReplicatorPBKDF2Salt(showMessage: boolean = false): Promise { - // Checking salt - const replicator = this.services.replicator.getActiveReplicator(); - if (!replicator) { - this.showError($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE); - return false; - } - return await replicator.ensurePBKDF2Salt(this.settings, showMessage, true); - } - async _everyBeforeReplicate(showMessage: boolean): Promise { - // Checking salt - if (!this.core.managers.networkManager.isOnline) { - this.showError("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); - return false; - } - // Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it). - if (!(await this.ensureReplicatorPBKDF2Salt(false))) { - this.showError("Failed to initialise the encryption key, preventing replication."); - return false; - } await this.processor.restoreFromSnapshotOnce(); this.clearErrors(); return true; } - private async _replicate(showMessage: boolean = false): Promise { - try { - updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT, REPLICATION_ON_EVENT_FORECASTED_TIME); - return await this.$$_replicate(showMessage); - } finally { - updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT); - } - } - /** * obsolete method. No longer maintained and will be removed in the future. * @deprecated v0.24.17 @@ -159,149 +159,129 @@ Even if you choose to clean up, you will see this option again if you exit Obsid }); } - async _canReplicate(showMessage: boolean = false): Promise { - if (!this.services.appLifecycle.isReady()) { - Logger(`Not ready`); + private async onReplicationFailed(showMessage: boolean = false): Promise { + const activeReplicator = this.services.replicator.getActiveReplicator(); + if (!activeReplicator) { + Logger(`No active replicator found`, LOG_LEVEL_INFO); return false; } - - if (isLockAcquired("cleanup")) { - Logger($msg("Replicator.Message.Cleaned"), LOG_LEVEL_NOTICE); - return false; - } - - if (this.settings.versionUpFlash != "") { - Logger($msg("Replicator.Message.VersionUpFlash"), LOG_LEVEL_NOTICE); - return false; - } - - if (!(await this.services.fileProcessing.commitPendingFileEvents())) { - this.showError($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE); - return false; - } - - if (!this.core.managers.networkManager.isOnline) { - this.showError("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); - return false; - } - if (!(await this.services.replication.onBeforeReplicate(showMessage))) { - this.showError($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE); - return false; - } - this.clearErrors(); - return true; - } - - async $$_replicate(showMessage: boolean = false): Promise { - const checkBeforeReplicate = await this.services.replication.isReplicationReady(showMessage); - if (!checkBeforeReplicate) return false; - - //<-- Here could be an module. - const ret = await this.core.replicator.openReplication(this.settings, false, showMessage, false); - if (!ret) { - if (this.core.replicator.tweakSettingsMismatched && this.core.replicator.preferredTweakValue) { - await this.services.tweakValue.askResolvingMismatched(this.core.replicator.preferredTweakValue); - } else { - if (this.core.replicator?.remoteLockedAndDeviceNotAccepted) { - if (this.core.replicator.remoteCleaned && this.settings.useIndexedDBAdapter) { - await this.cleaned(showMessage); - } else { - const message = $msg("Replicator.Dialogue.Locked.Message"); - const CHOICE_FETCH = $msg("Replicator.Dialogue.Locked.Action.Fetch"); - const CHOICE_DISMISS = $msg("Replicator.Dialogue.Locked.Action.Dismiss"); - const CHOICE_UNLOCK = $msg("Replicator.Dialogue.Locked.Action.Unlock"); - const ret = await this.core.confirm.askSelectStringDialogue( - message, - [CHOICE_FETCH, CHOICE_UNLOCK, CHOICE_DISMISS], - { - title: $msg("Replicator.Dialogue.Locked.Title"), - defaultAction: CHOICE_DISMISS, - timeout: 60, - } - ); - if (ret == CHOICE_FETCH) { - this._log($msg("Replicator.Dialogue.Locked.Message.Fetch"), LOG_LEVEL_NOTICE); - await this.core.rebuilder.scheduleFetch(); - this.services.appLifecycle.scheduleRestart(); - return; - } else if (ret == CHOICE_UNLOCK) { - await this.core.replicator.markRemoteResolved(this.settings); - this._log($msg("Replicator.Dialogue.Locked.Message.Unlocked"), LOG_LEVEL_NOTICE); - return; + if (activeReplicator.tweakSettingsMismatched && activeReplicator.preferredTweakValue) { + await this.services.tweakValue.askResolvingMismatched(activeReplicator.preferredTweakValue); + } else { + if (activeReplicator.remoteLockedAndDeviceNotAccepted) { + if (activeReplicator.remoteCleaned && this.settings.useIndexedDBAdapter) { + await this.cleaned(showMessage); + } else { + const message = $msg("Replicator.Dialogue.Locked.Message"); + const CHOICE_FETCH = $msg("Replicator.Dialogue.Locked.Action.Fetch"); + const CHOICE_DISMISS = $msg("Replicator.Dialogue.Locked.Action.Dismiss"); + const CHOICE_UNLOCK = $msg("Replicator.Dialogue.Locked.Action.Unlock"); + const ret = await this.core.confirm.askSelectStringDialogue( + message, + [CHOICE_FETCH, CHOICE_UNLOCK, CHOICE_DISMISS], + { + title: $msg("Replicator.Dialogue.Locked.Title"), + defaultAction: CHOICE_DISMISS, + timeout: 60, } + ); + if (ret == CHOICE_FETCH) { + this._log($msg("Replicator.Dialogue.Locked.Message.Fetch"), LOG_LEVEL_NOTICE); + await this.core.rebuilder.scheduleFetch(); + this.services.appLifecycle.scheduleRestart(); + return false; + } else if (ret == CHOICE_UNLOCK) { + await activeReplicator.markRemoteResolved(this.settings); + this._log($msg("Replicator.Dialogue.Locked.Message.Unlocked"), LOG_LEVEL_NOTICE); + return false; } } } } - return ret; + // TODO: Check again and true/false return. This will be the result for performReplication. + return false; } - private async _replicateByEvent(): Promise { - const least = this.settings.syncMinimumInterval; - if (least > 0) { - return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => { - return await this.services.replication.replicate(); - }); - } - return await shareRunningResult(`replication`, () => this.services.replication.replicate()); - } + // private async _replicateByEvent(): Promise { + // const least = this.settings.syncMinimumInterval; + // if (least > 0) { + // return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => { + // return await this.services.replication.replicate(); + // }); + // } + // return await shareRunningResult(`replication`, () => this.services.replication.replicate()); + // } - _parseReplicationResult(docs: Array>): void { + _parseReplicationResult(docs: Array>): Promise { this.processor.enqueueAll(docs); - } - - _everyBeforeSuspendProcess(): Promise { - this.core.replicator?.closeReplication(); return Promise.resolve(true); } - private async _replicateAllToServer( - showingNotice: boolean = false, - sendChunksInBulkDisabled: boolean = false - ): Promise { - if (!this.services.appLifecycle.isReady()) return false; - if (!(await this.services.replication.onBeforeReplicate(showingNotice))) { - Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE); - return false; - } - if (!sendChunksInBulkDisabled) { - if (this.core.replicator instanceof LiveSyncCouchDBReplicator) { - if ( - (await this.core.confirm.askYesNoDialog("Do you want to send all chunks before replication?", { - defaultOption: "No", - timeout: 20, - })) == "yes" - ) { - await this.core.replicator.sendChunks(this.core.settings, undefined, true, 0); - } - } - } - const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice); - if (ret) return true; - const checkResult = await this.services.replication.checkConnectionFailure(); - if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllToRemote(showingNotice); - return !checkResult; - } - async _replicateAllFromServer(showingNotice: boolean = false): Promise { - if (!this.services.appLifecycle.isReady()) return false; - const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice); - if (ret) return true; - const checkResult = await this.services.replication.checkConnectionFailure(); - if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllFromRemote(showingNotice); - return !checkResult; - } + // _everyBeforeSuspendProcess(): Promise { + // this.core.replicator?.closeReplication(); + // return Promise.resolve(true); + // } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + // private async _replicateAllToServer( + // showingNotice: boolean = false, + // sendChunksInBulkDisabled: boolean = false + // ): Promise { + // if (!this.services.appLifecycle.isReady()) return false; + // if (!(await this.services.replication.onBeforeReplicate(showingNotice))) { + // Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE); + // return false; + // } + // if (!sendChunksInBulkDisabled) { + // if (this.core.replicator instanceof LiveSyncCouchDBReplicator) { + // if ( + // (await this.core.confirm.askYesNoDialog("Do you want to send all chunks before replication?", { + // defaultOption: "No", + // timeout: 20, + // })) == "yes" + // ) { + // await this.core.replicator.sendChunks(this.core.settings, undefined, true, 0); + // } + // } + // } + // const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice); + // if (ret) return true; + // const checkResult = await this.services.replication.checkConnectionFailure(); + // if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllToRemote(showingNotice); + // return !checkResult; + // } + // async _replicateAllFromServer(showingNotice: boolean = false): Promise { + // if (!this.services.appLifecycle.isReady()) return false; + // const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice); + // if (ret) return true; + // const checkResult = await this.services.replication.checkConnectionFailure(); + // if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllFromRemote(showingNotice); + // return !checkResult; + // } + + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.replicator.onReplicatorInitialised.addHandler(this._onReplicatorInitialised.bind(this)); services.databaseEvents.onDatabaseInitialised.addHandler(this._everyOnDatabaseInitialized.bind(this)); services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); - services.replication.parseSynchroniseResult.setHandler(this._parseReplicationResult.bind(this)); - services.appLifecycle.onSuspending.addHandler(this._everyBeforeSuspendProcess.bind(this)); - services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this)); - services.replication.isReplicationReady.setHandler(this._canReplicate.bind(this)); - services.replication.replicate.setHandler(this._replicate.bind(this)); - services.replication.replicateByEvent.setHandler(this._replicateByEvent.bind(this)); - services.remote.replicateAllToRemote.setHandler(this._replicateAllToServer.bind(this)); - services.remote.replicateAllFromRemote.setHandler(this._replicateAllFromServer.bind(this)); + services.replication.parseSynchroniseResult.addHandler(this._parseReplicationResult.bind(this)); + + // --> These handlers can be separated. + const isOnlineAndCanReplicateWithHost = isOnlineAndCanReplicate.bind(null, this._unresolvedErrorManager, { + services: { + database: services.database, + }, + serviceModules: {}, + }); + const canReplicateWithPBKDF2WithHost = canReplicateWithPBKDF2.bind(null, this._unresolvedErrorManager, { + services: { + replicator: services.replicator, + setting: services.setting, + }, + serviceModules: {}, + }); + services.replication.onBeforeReplicate.addHandler(isOnlineAndCanReplicateWithHost, 10); + services.replication.onBeforeReplicate.addHandler(canReplicateWithPBKDF2WithHost, 20); + // <-- End of handlers that can be separated. + services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this), 100); + services.replication.onReplicationFailed.addHandler(this.onReplicationFailed.bind(this)); } } diff --git a/src/modules/core/ModuleReplicatorCouchDB.ts b/src/modules/core/ModuleReplicatorCouchDB.ts index 97b5ef1..9fdc6d7 100644 --- a/src/modules/core/ModuleReplicatorCouchDB.ts +++ b/src/modules/core/ModuleReplicatorCouchDB.ts @@ -35,7 +35,7 @@ export class ModuleReplicatorCouchDB extends AbstractModule { return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.replicator.getNewReplicator.addHandler(this._anyNewReplicator.bind(this)); services.appLifecycle.onResumed.addHandler(this._everyAfterResumeProcess.bind(this)); } diff --git a/src/modules/core/ModuleReplicatorMinIO.ts b/src/modules/core/ModuleReplicatorMinIO.ts index fb00330..ab6800e 100644 --- a/src/modules/core/ModuleReplicatorMinIO.ts +++ b/src/modules/core/ModuleReplicatorMinIO.ts @@ -12,7 +12,7 @@ export class ModuleReplicatorMinIO extends AbstractModule { } return Promise.resolve(false); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.replicator.getNewReplicator.addHandler(this._anyNewReplicator.bind(this)); } } diff --git a/src/modules/core/ModuleReplicatorP2P.ts b/src/modules/core/ModuleReplicatorP2P.ts index 0817367..1a40eca 100644 --- a/src/modules/core/ModuleReplicatorP2P.ts +++ b/src/modules/core/ModuleReplicatorP2P.ts @@ -27,7 +27,7 @@ export class ModuleReplicatorP2P extends AbstractModule { return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.replicator.getNewReplicator.addHandler(this._anyNewReplicator.bind(this)); services.appLifecycle.onResumed.addHandler(this._everyAfterResumeProcess.bind(this)); } diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts index 956f657..6c5af7e 100644 --- a/src/modules/core/ModuleTargetFilter.ts +++ b/src/modules/core/ModuleTargetFilter.ts @@ -137,7 +137,7 @@ export class ModuleTargetFilter extends AbstractModule { return true; } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.vault.markFileListPossiblyChanged.setHandler(this._markFileListPossiblyChanged.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); services.vault.isIgnoredByIgnoreFile.setHandler(this._isTargetIgnoredByIgnoreFiles.bind(this)); diff --git a/src/modules/coreFeatures/ModuleConflictChecker.ts b/src/modules/coreFeatures/ModuleConflictChecker.ts index d3a53d5..d81e40d 100644 --- a/src/modules/coreFeatures/ModuleConflictChecker.ts +++ b/src/modules/coreFeatures/ModuleConflictChecker.ts @@ -74,7 +74,7 @@ export class ModuleConflictChecker extends AbstractModule { totalRemainingReactiveSource: this.services.conflict.conflictProcessQueueCount, } ); - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.conflict.queueCheckForIfOpen.setHandler(this._queueConflictCheckIfOpen.bind(this)); services.conflict.queueCheckFor.setHandler(this._queueConflictCheck.bind(this)); services.conflict.ensureAllProcessed.setHandler(this._waitForAllConflictProcessed.bind(this)); diff --git a/src/modules/coreFeatures/ModuleConflictResolver.ts b/src/modules/coreFeatures/ModuleConflictResolver.ts index 7111665..f09271a 100644 --- a/src/modules/coreFeatures/ModuleConflictResolver.ts +++ b/src/modules/coreFeatures/ModuleConflictResolver.ts @@ -229,7 +229,7 @@ export class ModuleConflictResolver extends AbstractModule { this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes"); } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.conflict.resolveByDeletingRevision.setHandler(this._resolveConflictByDeletingRev.bind(this)); services.conflict.resolve.setHandler(this._resolveConflict.bind(this)); services.conflict.resolveByNewest.setHandler(this._anyResolveConflictByNewest.bind(this)); diff --git a/src/modules/coreFeatures/ModuleRedFlag.ts b/src/modules/coreFeatures/ModuleRedFlag.ts index 7ebdb0b..d57cb0f 100644 --- a/src/modules/coreFeatures/ModuleRedFlag.ts +++ b/src/modules/coreFeatures/ModuleRedFlag.ts @@ -324,7 +324,7 @@ export class ModuleRedFlag extends AbstractModule { } return true; } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { super.onBindFunction(core, services); services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); } diff --git a/src/modules/coreFeatures/ModuleRemoteGovernor.ts b/src/modules/coreFeatures/ModuleRemoteGovernor.ts deleted file mode 100644 index 4252643..0000000 --- a/src/modules/coreFeatures/ModuleRemoteGovernor.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts"; -import type { LiveSyncCore } from "../../main.ts"; -import { AbstractModule } from "../AbstractModule.ts"; - -export class ModuleRemoteGovernor extends AbstractModule { - private async _markRemoteLocked(lockByClean: boolean = false): Promise { - return await this.core.replicator.markRemoteLocked(this.settings, true, lockByClean); - } - - private async _markRemoteUnlocked(): Promise { - return await this.core.replicator.markRemoteLocked(this.settings, false, false); - } - - private async _markRemoteResolved(): Promise { - return await this.core.replicator.markRemoteResolved(this.settings); - } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { - services.remote.markLocked.setHandler(this._markRemoteLocked.bind(this)); - services.remote.markUnlocked.setHandler(this._markRemoteUnlocked.bind(this)); - services.remote.markResolved.setHandler(this._markRemoteResolved.bind(this)); - } -} diff --git a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts index f184922..fab0091 100644 --- a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts +++ b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts @@ -284,7 +284,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule { return { result: false, requireFetch: false }; } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.tweakValue.fetchRemotePreferred.setHandler(this._fetchRemotePreferredTweakValues.bind(this)); services.tweakValue.checkAndAskResolvingMismatched.setHandler( this._checkAndAskResolvingMismatchedTweaks.bind(this) diff --git a/src/modules/coreObsidian/UILib/dialogs.ts b/src/modules/coreObsidian/UILib/dialogs.ts index 832fea6..ce77bd9 100644 --- a/src/modules/coreObsidian/UILib/dialogs.ts +++ b/src/modules/coreObsidian/UILib/dialogs.ts @@ -13,7 +13,7 @@ class AutoClosableModal extends Modal { this._closeByUnload = this._closeByUnload.bind(this); eventHub.once(EVENT_PLUGIN_UNLOADED, this._closeByUnload); } - onClose() { + override onClose() { eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload); } } @@ -43,7 +43,7 @@ export class InputStringDialog extends AutoClosableModal { this.isPassword = isPassword; } - onOpen() { + override onOpen() { const { contentEl } = this; this.titleEl.setText(this.title); const formEl = contentEl.createDiv(); @@ -75,7 +75,7 @@ export class InputStringDialog extends AutoClosableModal { ); } - onClose() { + override onClose() { super.onClose(); const { contentEl } = this; contentEl.empty(); @@ -87,7 +87,7 @@ export class InputStringDialog extends AutoClosableModal { } } export class PopoverSelectString extends FuzzySuggestModal { - app: App; + _app: App; callback: ((e: string) => void) | undefined = () => {}; getItemsFun: () => string[] = () => { return ["yes", "no"]; @@ -101,7 +101,7 @@ export class PopoverSelectString extends FuzzySuggestModal { callback: (e: string) => void ) { super(app); - this.app = app; + this._app = app; this.setPlaceholder((placeholder ?? "y/n) ") + note); if (getItemsFun) this.getItemsFun = getItemsFun; this.callback = callback; @@ -120,7 +120,7 @@ export class PopoverSelectString extends FuzzySuggestModal { this.callback?.(item); this.callback = undefined; } - onClose(): void { + override onClose(): void { setTimeout(() => { if (this.callback) { this.callback(""); @@ -184,7 +184,7 @@ export class MessageBox extends AutoClosableModal { } } - onOpen() { + override onOpen() { const { contentEl } = this; this.titleEl.setText(this.title); const div = contentEl.createDiv(); @@ -242,7 +242,7 @@ export class MessageBox extends AutoClosableModal { } } - onClose() { + override onClose() { super.onClose(); const { contentEl } = this; contentEl.empty(); diff --git a/src/modules/essential/ModuleInitializerFile.ts b/src/modules/essential/ModuleInitializerFile.ts index e246ca4..8112bfe 100644 --- a/src/modules/essential/ModuleInitializerFile.ts +++ b/src/modules/essential/ModuleInitializerFile.ts @@ -421,7 +421,7 @@ export class ModuleInitializerFile extends AbstractModule { private _reportDetectedErrors(): Promise { return Promise.resolve(Array.from(this._detectedErrors)); } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.appLifecycle.getUnresolvedMessages.addHandler(this._reportDetectedErrors.bind(this)); services.databaseEvents.initialiseDatabase.setHandler(this._initializeDatabase.bind(this)); services.vault.scanVault.setHandler(this._performFullScan.bind(this)); diff --git a/src/modules/essential/ModuleMigration.ts b/src/modules/essential/ModuleMigration.ts index e6c03df..8ff2227 100644 --- a/src/modules/essential/ModuleMigration.ts +++ b/src/modules/essential/ModuleMigration.ts @@ -353,7 +353,7 @@ export class ModuleMigration extends AbstractModule { }); return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { super.onBindFunction(core, services); services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this)); diff --git a/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts b/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts index 45002d9..5504918 100644 --- a/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts +++ b/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts @@ -28,7 +28,10 @@ export class ObsHttpHandler extends FetchHttpHandler { this.reverseProxyNoSignUrl = reverseProxyNoSignUrl; } // eslint-disable-next-line require-await - async handle(request: HttpRequest, { abortSignal }: HttpHandlerOptions = {}): Promise<{ response: HttpResponse }> { + override async handle( + request: HttpRequest, + { abortSignal }: HttpHandlerOptions = {} + ): Promise<{ response: HttpResponse }> { if (abortSignal?.aborted) { const abortError = new Error("Request aborted"); abortError.name = "AbortError"; diff --git a/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts b/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts index af67bf3..3dedf56 100644 --- a/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts +++ b/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts @@ -127,7 +127,7 @@ export class ModuleCheckRemoteSize extends AbstractModule { eventHub.onEvent(EVENT_REQUEST_CHECK_REMOTE_SIZE, () => this.checkRemoteSize()); return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onScanningStartupIssues.addHandler(this._allScanStat.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts index 9de2ad3..ac52c1b 100644 --- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts +++ b/src/modules/essentialObsidian/ModuleObsidianAPI.ts @@ -282,7 +282,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { return Promise.resolve([...this._previousErrors]); } - onBindFunction(core: LiveSyncCore, services: typeof core.services) { + override onBindFunction(core: LiveSyncCore, services: typeof core.services) { services.API.isLastPostFailedDueToPayloadSize.setHandler(this._getLastPostFailedBySize.bind(this)); services.remote.connect.setHandler(this._connectRemoteCouchDB.bind(this)); services.appLifecycle.getUnresolvedMessages.addHandler(this._reportUnresolvedMessages.bind(this)); diff --git a/src/modules/essentialObsidian/ModuleObsidianEvents.ts b/src/modules/essentialObsidian/ModuleObsidianEvents.ts index 63814ff..7445e11 100644 --- a/src/modules/essentialObsidian/ModuleObsidianEvents.ts +++ b/src/modules/essentialObsidian/ModuleObsidianEvents.ts @@ -45,7 +45,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { this.initialCallback = save; saveCommandDefinition.callback = () => { scheduleTask("syncOnEditorSave", 250, () => { - if (this.services.appLifecycle.hasUnloaded()) { + if (this.services.control.hasUnloaded()) { this._log("Unload and remove the handler.", LOG_LEVEL_VERBOSE); saveCommandDefinition.callback = this.initialCallback; this.initialCallback = undefined; @@ -247,7 +247,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { _isReloadingScheduled(): boolean { return this._totalProcessingCount !== undefined; } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override 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)); diff --git a/src/modules/essentialObsidian/ModuleObsidianMenu.ts b/src/modules/essentialObsidian/ModuleObsidianMenu.ts index 905e0bf..cab9093 100644 --- a/src/modules/essentialObsidian/ModuleObsidianMenu.ts +++ b/src/modules/essentialObsidian/ModuleObsidianMenu.ts @@ -106,7 +106,7 @@ export class ModuleObsidianMenu extends AbstractModule { return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/extras/ModuleDev.ts b/src/modules/extras/ModuleDev.ts index 06f12e9..a69776a 100644 --- a/src/modules/extras/ModuleDev.ts +++ b/src/modules/extras/ModuleDev.ts @@ -156,7 +156,7 @@ export class ModuleDev extends AbstractObsidianModule { // this.addTestResult("Test of test3", true); return this.testDone(); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override 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.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); diff --git a/src/modules/extras/ModuleIntegratedTest.ts b/src/modules/extras/ModuleIntegratedTest.ts index a5b724f..df5cc3a 100644 --- a/src/modules/extras/ModuleIntegratedTest.ts +++ b/src/modules/extras/ModuleIntegratedTest.ts @@ -440,7 +440,7 @@ Line4:D`; return Promise.resolve(true); } - onBindFunction(core: typeof this.core, services: typeof core.services): void { + override onBindFunction(core: typeof this.core, services: typeof core.services): void { services.test.testMultiDevice.addHandler(this._everyModuleTestMultiDevice.bind(this)); } } diff --git a/src/modules/extras/ModuleReplicateTest.ts b/src/modules/extras/ModuleReplicateTest.ts index 4de3492..999bdce 100644 --- a/src/modules/extras/ModuleReplicateTest.ts +++ b/src/modules/extras/ModuleReplicateTest.ts @@ -581,7 +581,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`; await this._test("Conflict resolution", async () => await this.checkConflictResolution()); return this.testDone(); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this)); services.test.testMultiDevice.addHandler(this._everyModuleTestMultiDevice.bind(this)); diff --git a/src/modules/extras/devUtil/TestPaneView.ts b/src/modules/extras/devUtil/TestPaneView.ts index 9b15fa6..32e29d4 100644 --- a/src/modules/extras/devUtil/TestPaneView.ts +++ b/src/modules/extras/devUtil/TestPaneView.ts @@ -8,11 +8,11 @@ export class TestPaneView extends ItemView { component?: TestPaneComponent; plugin: ObsidianLiveSyncPlugin; moduleDev: ModuleDev; - icon = "view-log"; + override icon = "view-log"; title: string = "Self-hosted LiveSync Test and Results"; - navigation = true; + override navigation = true; - getIcon(): string { + override getIcon(): string { return "view-log"; } @@ -30,7 +30,7 @@ export class TestPaneView extends ItemView { return "Self-hosted LiveSync Test and Results"; } - async onOpen() { + override async onOpen() { this.component = new TestPaneComponent({ target: this.contentEl, props: { @@ -41,7 +41,7 @@ export class TestPaneView extends ItemView { await Promise.resolve(); } - async onClose() { + override async onClose() { this.component?.$destroy(); await Promise.resolve(); } diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 35e53cb..00bcf64 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -214,7 +214,7 @@ export class DocumentHistoryModal extends Modal { } } - onOpen() { + override onOpen() { const { contentEl } = this; this.titleEl.setText("Document History"); contentEl.empty(); @@ -299,7 +299,7 @@ export class DocumentHistoryModal extends Modal { }); }); } - onClose() { + override onClose() { const { contentEl } = this; contentEl.empty(); this.BlobURLs.forEach((value) => { diff --git a/src/modules/features/GlobalHistory/GlobalHistoryView.ts b/src/modules/features/GlobalHistory/GlobalHistoryView.ts index 1b95821..0edcfe4 100644 --- a/src/modules/features/GlobalHistory/GlobalHistoryView.ts +++ b/src/modules/features/GlobalHistory/GlobalHistoryView.ts @@ -16,11 +16,11 @@ export class GlobalHistoryView extends SvelteItemView { } plugin: ObsidianLiveSyncPlugin; - icon = "clock"; + override icon = "clock"; title: string = ""; - navigation = true; + override navigation = true; - getIcon(): string { + override getIcon(): string { return "clock"; } diff --git a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts index 6a14488..826fbec 100644 --- a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts +++ b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts @@ -44,7 +44,7 @@ export class ConflictResolveModal extends Modal { // sendValue("close-resolve-conflict:" + this.filename, false); } - onOpen() { + override onOpen() { const { contentEl } = this; // Send cancel signal for the previous merge dialogue // if not there, simply be ignored. @@ -119,7 +119,7 @@ export class ConflictResolveModal extends Modal { this.close(); } - onClose() { + override onClose() { const { contentEl } = this; contentEl.empty(); if (this.offEvent) { diff --git a/src/modules/features/Log/LogPaneView.ts b/src/modules/features/Log/LogPaneView.ts index b68c491..5af45f6 100644 --- a/src/modules/features/Log/LogPaneView.ts +++ b/src/modules/features/Log/LogPaneView.ts @@ -19,11 +19,11 @@ export class LogPaneView extends SvelteItemView { } plugin: ObsidianLiveSyncPlugin; - icon = "view-log"; + override icon = "view-log"; title: string = ""; - navigation = false; + override navigation = false; - getIcon(): string { + override getIcon(): string { return "view-log"; } diff --git a/src/modules/features/ModuleGlobalHistory.ts b/src/modules/features/ModuleGlobalHistory.ts index 5dc6374..3e955c9 100644 --- a/src/modules/features/ModuleGlobalHistory.ts +++ b/src/modules/features/ModuleGlobalHistory.ts @@ -19,7 +19,7 @@ export class ModuleObsidianGlobalHistory extends AbstractObsidianModule { showGlobalHistory() { void this.services.API.showWindow(VIEW_TYPE_GLOBAL_HISTORY); } - onBindFunction(core: typeof this.core, services: typeof core.services): void { + override onBindFunction(core: typeof this.core, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/ModuleInteractiveConflictResolver.ts b/src/modules/features/ModuleInteractiveConflictResolver.ts index aea1fdf..0c04092 100644 --- a/src/modules/features/ModuleInteractiveConflictResolver.ts +++ b/src/modules/features/ModuleInteractiveConflictResolver.ts @@ -169,7 +169,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule { } return true; } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onScanningStartupIssues.addHandler(this._allScanStat.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.conflict.resolveByUserInteraction.addHandler(this._anyResolveConflictByUI.bind(this)); diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index e82cd1d..ce674bf 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -495,7 +495,7 @@ export class ModuleLog extends AbstractObsidianModule { } } } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override 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)); diff --git a/src/modules/features/ModuleObsidianDocumentHistory.ts b/src/modules/features/ModuleObsidianDocumentHistory.ts index 7a49dd7..d1182fc 100644 --- a/src/modules/features/ModuleObsidianDocumentHistory.ts +++ b/src/modules/features/ModuleObsidianDocumentHistory.ts @@ -50,7 +50,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule { this.showHistory(targetId.path, targetId.id); } } - onBindFunction(core: typeof this.core, services: typeof core.services): void { + override onBindFunction(core: typeof this.core, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts index cf6bd90..21f1008 100644 --- a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts +++ b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts @@ -246,7 +246,7 @@ We can perform a command in this file. } } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/ModuleObsidianSettingTab.ts b/src/modules/features/ModuleObsidianSettingTab.ts index e150bd8..52e8ccd 100644 --- a/src/modules/features/ModuleObsidianSettingTab.ts +++ b/src/modules/features/ModuleObsidianSettingTab.ts @@ -29,7 +29,7 @@ export class ModuleObsidianSettingDialogue extends AbstractObsidianModule { get appId() { return `${"appId" in this.app ? this.app.appId : ""}`; } - onBindFunction(core: typeof this.plugin, services: typeof core.services): void { + override onBindFunction(core: typeof this.plugin, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/ModuleSetupObsidian.ts b/src/modules/features/ModuleSetupObsidian.ts index b66d244..d09dbf4 100644 --- a/src/modules/features/ModuleSetupObsidian.ts +++ b/src/modules/features/ModuleSetupObsidian.ts @@ -194,7 +194,7 @@ export class ModuleSetupObsidian extends AbstractModule { // } // } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); } } diff --git a/src/modules/features/SettingDialogue/LiveSyncSetting.ts b/src/modules/features/SettingDialogue/LiveSyncSetting.ts index 714ad88..0a7f90e 100644 --- a/src/modules/features/SettingDialogue/LiveSyncSetting.ts +++ b/src/modules/features/SettingDialogue/LiveSyncSetting.ts @@ -58,7 +58,7 @@ export class LiveSyncSetting extends Setting { } } - setDesc(desc: string | DocumentFragment): this { + override setDesc(desc: string | DocumentFragment): this { this.descBuf = desc; DEV: { this._createDocStub("desc", desc); @@ -66,7 +66,7 @@ export class LiveSyncSetting extends Setting { super.setDesc(desc); return this; } - setName(name: string | DocumentFragment): this { + override setName(name: string | DocumentFragment): this { this.nameBuf = name; DEV: { this._createDocStub("name", name); diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index ce9f71c..23c65bc 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -374,7 +374,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { this.initialSettings = undefined; } - hide() { + override hide() { this.isShown = false; } isShown: boolean = false; diff --git a/src/modules/features/SettingDialogue/PaneMaintenance.ts b/src/modules/features/SettingDialogue/PaneMaintenance.ts index 7ea5968..4a9838a 100644 --- a/src/modules/features/SettingDialogue/PaneMaintenance.ts +++ b/src/modules/features/SettingDialogue/PaneMaintenance.ts @@ -32,7 +32,7 @@ export function paneMaintenance( (e) => { e.addEventListener("click", () => { fireAndForget(async () => { - await this.services.remote.markResolved(); + await this.services.replication.markResolved(); this.display(); }); }); @@ -59,7 +59,7 @@ export function paneMaintenance( (e) => { e.addEventListener("click", () => { fireAndForget(async () => { - await this.services.remote.markUnlocked(); + await this.services.replication.markUnlocked(); this.display(); }); }); @@ -78,7 +78,7 @@ export function paneMaintenance( .setDisabled(false) .setWarning() .onClick(async () => { - await this.services.remote.markLocked(); + await this.services.replication.markLocked(); }) ) .addOnUpdate(this.onlyOnCouchDBOrMinIO); diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index af645e7..3618c61 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -3,17 +3,13 @@ import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, VER, type ObsidianLiveSyncSettings import { EVENT_LAYOUT_READY, EVENT_PLUGIN_LOADED, - EVENT_PLUGIN_UNLOADED, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub, } from "../../common/events.ts"; import { $msg, setLang } from "../../lib/src/common/i18n.ts"; import { versionNumberString2Number } from "../../lib/src/string_and_binary/convert.ts"; -import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task"; -import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor"; import { AbstractModule } from "../AbstractModule.ts"; -import { EVENT_PLATFORM_UNLOADED } from "@lib/events/coreEvents"; import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts"; import type { LiveSyncCore } from "../../main.ts"; import { initialiseWorkerModule } from "@lib/worker/bgWorker.ts"; @@ -139,29 +135,29 @@ export class ModuleLiveSyncMain extends AbstractModule { return true; } - async _onLiveSyncUnload(): Promise { - eventHub.emitEvent(EVENT_PLUGIN_UNLOADED); - await this.services.appLifecycle.onBeforeUnload(); - cancelAllPeriodicTask(); - cancelAllTasks(); - stopAllRunningProcessors(); - await this.services.appLifecycle.onUnload(); - this._unloaded = true; - for (const addOn of this.core.addOns) { - addOn.onunload(); - } - if (this.localDatabase != null) { - this.localDatabase.onunload(); - if (this.core.replicator) { - this.core.replicator?.closeReplication(); - } - await this.localDatabase.close(); - } - eventHub.emitEvent(EVENT_PLATFORM_UNLOADED); - eventHub.offAll(); - this._log($msg("moduleLiveSyncMain.logUnloadingPlugin")); - return; - } + // async _onLiveSyncUnload(): Promise { + // eventHub.emitEvent(EVENT_PLUGIN_UNLOADED); + // await this.services.appLifecycle.onBeforeUnload(); + // cancelAllPeriodicTask(); + // cancelAllTasks(); + // stopAllRunningProcessors(); + // await this.services.appLifecycle.onUnload(); + // this._unloaded = true; + // for (const addOn of this.core.addOns) { + // addOn.onunload(); + // } + // if (this.localDatabase != null) { + // this.localDatabase.onunload(); + // if (this.core.replicator) { + // this.core.replicator?.closeReplication(); + // } + // await this.localDatabase.close(); + // } + // eventHub.emitEvent(EVENT_PLATFORM_UNLOADED); + // eventHub.offAll(); + // this._log($msg("moduleLiveSyncMain.logUnloadingPlugin")); + // return; + // } // private async _realizeSettingSyncMode(): Promise { // await this.services.appLifecycle.onSuspending(); @@ -177,45 +173,44 @@ export class ModuleLiveSyncMain extends AbstractModule { // return; // } - isReady = false; + // isReady = false; - _isReady(): boolean { - return this.isReady; - } + // _isReady(): boolean { + // return this.isReady; + // } - _markIsReady(): void { - this.isReady = true; - } + // _markIsReady(): void { + // this.isReady = true; + // } - _resetIsReady(): void { - this.isReady = false; - } + // _resetIsReady(): void { + // this.isReady = false; + // } - _suspended = false; - _isSuspended(): boolean { - return this._suspended || !this.settings?.isConfigured; - } + // _suspended = false; + // _isSuspended(): boolean { + // return this._suspended || !this.settings?.isConfigured; + // } - _setSuspended(value: boolean) { - this._suspended = value; - } + // _setSuspended(value: boolean) { + // this._suspended = value; + // } - _unloaded = false; - _isUnloaded(): boolean { - return this._unloaded; - } + // _unloaded = false; + // _isUnloaded(): boolean { + // return this._unloaded; + // } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { super.onBindFunction(core, services); - services.appLifecycle.isSuspended.setHandler(this._isSuspended.bind(this)); - services.appLifecycle.setSuspended.setHandler(this._setSuspended.bind(this)); - services.appLifecycle.isReady.setHandler(this._isReady.bind(this)); - 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.isSuspended.setHandler(this._isSuspended.bind(this)); + // services.appLifecycle.setSuspended.setHandler(this._setSuspended.bind(this)); + // services.appLifecycle.isReady.setHandler(this._isReady.bind(this)); + // 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.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)); } } diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts index a1e3f1f..5e5e580 100644 --- a/src/modules/services/ObsidianAPIService.ts +++ b/src/modules/services/ObsidianAPIService.ts @@ -42,7 +42,7 @@ export class ObsidianAPIService extends InjectableAPIService = { appLifecycle: AppLifecycleService; config: ConfigService; replicator: ReplicatorService; APIService: IAPIService; + control: IControlService; }; export class ObsidianUIService extends UIService { @@ -24,6 +25,7 @@ export class ObsidianUIService extends UIService { config: dependents.config, replicator: dependents.replicator, confirm: obsidianConfirm, + control: dependents.control, }); super(context, { appLifecycle: dependents.appLifecycle, diff --git a/src/modules/services/ObsidianVaultService.ts b/src/modules/services/ObsidianVaultService.ts index 00912b7..4229e00 100644 --- a/src/modules/services/ObsidianVaultService.ts +++ b/src/modules/services/ObsidianVaultService.ts @@ -11,7 +11,7 @@ declare module "obsidian" { // InjectableVaultService export class ObsidianVaultService extends InjectableVaultService { - vaultName(): string { + override vaultName(): string { return this.context.app.vault.getName(); } getActiveFilePath(): FilePath | undefined { diff --git a/src/serviceFeatures/types.ts b/src/serviceFeatures/types.ts index bc18fd2..cd24144 100644 --- a/src/serviceFeatures/types.ts +++ b/src/serviceFeatures/types.ts @@ -3,6 +3,7 @@ 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"; +import type { LogFunction } from "@/lib/src/services/lib/logUtils"; export interface ServiceModules { storageAccess: StorageAccess; @@ -31,6 +32,13 @@ export type NecessaryServices = ( host: NecessaryServices ) => TR; +type ServiceFeatureContext = T & { + _log: LogFunction; +}; +export type ServiceFeatureFunctionWithContext = ( + host: NecessaryServices, + context: ServiceFeatureContext +) => TR; /** * Helper function to create a service feature with proper typing. @@ -48,3 +56,23 @@ export function createServiceFeature { return featureFunction; } + +type ContextFactory = ( + host: NecessaryServices +) => ServiceFeatureContext; + +export function serviceFeature() { + return { + create(featureFunction: ServiceFeatureFunction) { + return featureFunction; + }, + withContext(ContextFactory: ContextFactory) { + return { + create: + (featureFunction: ServiceFeatureFunctionWithContext) => + (host: NecessaryServices, context: ServiceFeatureContext) => + featureFunction(host, ContextFactory(host)), + }; + }, + }; +} diff --git a/src/serviceModules/FileAccessObsidian.ts b/src/serviceModules/FileAccessObsidian.ts index 4755717..823fd16 100644 --- a/src/serviceModules/FileAccessObsidian.ts +++ b/src/serviceModules/FileAccessObsidian.ts @@ -1,7 +1,7 @@ import { markChangesAreSame } from "@/common/utils"; import type { FilePath, UXDataWriteOptions, UXFileInfoStub, UXFolderInfo } from "@lib/common/types"; -import { TFolder, type TAbstractFile, TFile, type Stat, type App, type DataWriteOptions } from "@/deps"; +import { TFolder, type TAbstractFile, TFile, type Stat, type App, type DataWriteOptions, normalizePath } from "@/deps"; import { FileAccessBase, toArrayBuffer, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase.ts"; import { TFileToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian"; @@ -52,6 +52,10 @@ export class FileAccessObsidian extends FileAccessBase { export async function waitForClosed(harness: LiveSyncHarness): Promise { await delay(100); for (let i = 0; i < 10; i++) { - if (harness.plugin.services.appLifecycle.hasUnloaded()) { - console.log("App Lifecycle has unloaded"); + if (harness.plugin.services.control.hasUnloaded()) { + console.log("App has unloaded"); return; } await delay(100); diff --git a/tsconfig.json b/tsconfig.json index 1ee9b7a..5e8bd6c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "alwaysStrict": true, "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, + "noImplicitOverride": true, "noEmit": true, "lib": ["es2018", "DOM", "ES5", "ES6", "ES7", "es2019.array", "ES2021.WeakRef", "ES2020.BigInt", "ESNext.Intl"], "strictBindCallApply": true, diff --git a/updates.md b/updates.md index 78e7dc2..abb2044 100644 --- a/updates.md +++ b/updates.md @@ -3,11 +3,23 @@ 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. -## --next -- +## 0.25.43-patched-8 + +I really must thank you all. You know that it seems we have just a little more to do. +Note: This version is not fully tested yet. Be careful to use this. Very dogfood-y one. ### Fixed -- Now device name is saved correctly. +- Now the device name is saved correctly. + +### Refactored + +- Add `override` keyword to all overridden items. +- More dynamic binding has been removed. +- The number of inverted dependencies has decreased much more. +- Some check-logic; i.e., like pre-replication check is now separated into check functions and added to the service as handlers, layered. + - This may help with better testing and better maintainability. + ## 0.25.43-patched-7