for 0.25.43-patched-7, please refer to the updates.md

This commit is contained in:
vorotamoroz
2026-02-19 10:23:45 +00:00
parent 1bde2b2ff1
commit 203dd17421
32 changed files with 426 additions and 525 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Self-hosted LiveSync", "name": "Self-hosted LiveSync",
"version": "0.25.43-patched-6", "version": "0.25.43-patched-7",
"minAppVersion": "0.9.12", "minAppVersion": "0.9.12",
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"author": "vorotamoroz", "author": "vorotamoroz",
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.25.43-patched-6", "version": "0.25.43-patched-7",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.25.43-patched-6", "version": "0.25.43-patched-7",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.808.0", "@aws-sdk/client-s3": "^3.808.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.25.43-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.", "description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"main": "main.js", "main": "main.js",
"type": "module", "type": "module",
+10 -8
View File
@@ -34,9 +34,11 @@ import { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator
import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types";
import { ServiceContext } from "@lib/services/base/ServiceBase"; import { ServiceContext } from "@lib/services/base/ServiceBase";
import type { InjectableServiceHub } from "@lib/services/InjectableServices"; import type { InjectableServiceHub } from "@lib/services/InjectableServices";
import { Menu } from "@/lib/src/services/implements/browser/Menu"; import { Menu } from "@lib/services/implements/browser/Menu";
import type { InjectableVaultServiceCompat } from "@/lib/src/services/implements/injectable/InjectableVaultService"; import type { InjectableVaultServiceCompat } from "@lib/services/implements/injectable/InjectableVaultService";
import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2"; 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) { function addToList(item: string, list: string) {
return unique( return unique(
@@ -81,13 +83,13 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim {
constructor() { constructor() {
const browserServiceHub = new BrowserServiceHub<ServiceContext>(); const browserServiceHub = new BrowserServiceHub<ServiceContext>();
this.services = browserServiceHub; this.services = browserServiceHub;
(this.services.vault as InjectableVaultServiceCompat<ServiceContext>).vaultName.setHandler(
(this.services.API as BrowserAPIService<ServiceContext>).getSystemVaultName.setHandler(
() => "p2p-livesync-web-peer" () => "p2p-livesync-web-peer"
); );
// this.services.setting.currentSettings.setHandler(() => {
this.services.setting.currentSettings.setHandler(() => { // return this.settings as any;
return this.settings as any; // });
});
} }
async init() { async init() {
// const { simpleStoreAPI } = await getWrappedSynchromesh(); // const { simpleStoreAPI } = await getWrappedSynchromesh();
@@ -106,7 +108,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim {
const repStore = SimpleStoreIDBv2.open<any>("p2p-livesync-web-peer"); const repStore = SimpleStoreIDBv2.open<any>("p2p-livesync-web-peer");
this._simpleStore = repStore; this._simpleStore = repStore;
let _settings = (await repStore.get("settings")) || ({ ...P2P_DEFAULT_SETTINGS } as P2PSyncSetting); let _settings = (await repStore.get("settings")) || ({ ...P2P_DEFAULT_SETTINGS } as P2PSyncSetting);
this.services.setting.settings = _settings as any;
this.plugin = { this.plugin = {
saveSettings: async () => { saveSettings: async () => {
await repStore.set("settings", _settings); await repStore.set("settings", _settings);
+1 -1
Submodule src/lib updated: b59a1deba8...b2bb66970c
+103 -53
View File
@@ -2,7 +2,6 @@ import { Plugin, type App, type PluginManifest } from "./deps";
import { import {
type EntryDoc, type EntryDoc,
type ObsidianLiveSyncSettings, type ObsidianLiveSyncSettings,
type DatabaseConnectingStatus,
type HasSettings, type HasSettings,
LOG_LEVEL_INFO, LOG_LEVEL_INFO,
} from "./lib/src/common/types.ts"; } 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 { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.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 LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js";
import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js"; import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js";
import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.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 { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver.ts";
import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts"; import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts";
import { ModuleLog } from "./modules/features/ModuleLog.ts"; import { ModuleLog } from "./modules/features/ModuleLog.ts";
import { ModuleObsidianSettings } from "./modules/features/ModuleObsidianSetting.ts";
import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts"; import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts";
import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts";
import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.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 { ServiceFileHandler } from "./serviceModules/FileHandler.ts";
import { FileAccessObsidian } from "./serviceModules/FileAccessObsidian.ts"; import { FileAccessObsidian } from "./serviceModules/FileAccessObsidian.ts";
import { StorageEventManagerObsidian } from "./managers/StorageEventManagerObsidian.ts"; import { StorageEventManagerObsidian } from "./managers/StorageEventManagerObsidian.ts";
import { onLayoutReadyFeatures } from "./serviceFeatures/onLayoutReady.ts";
import type { ServiceModules } from "./types.ts";
export default class ObsidianLiveSyncPlugin export default class ObsidianLiveSyncPlugin
extends Plugin extends Plugin
@@ -88,12 +87,27 @@ export default class ObsidianLiveSyncPlugin
return this._services; 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[]; 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. * register an add-onn to the plug-in.
* Add-ons are features that are not essential to the core functionality of the plugin, * 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; 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. * Get a module by its class. Throws an error if not found.
* Mostly used for getting SetupManager. * Mostly used for getting SetupManager.
@@ -162,7 +169,6 @@ export default class ObsidianLiveSyncPlugin
this._registerModule(new ModuleInitializerFile(this)); this._registerModule(new ModuleInitializerFile(this));
this._registerModule(new ModuleObsidianAPI(this, this)); this._registerModule(new ModuleObsidianAPI(this, this));
this._registerModule(new ModuleObsidianEvents(this, this)); this._registerModule(new ModuleObsidianEvents(this, this));
this._registerModule(new ModuleObsidianSettings(this));
this._registerModule(new ModuleResolvingMismatchedTweaks(this)); this._registerModule(new ModuleResolvingMismatchedTweaks(this));
this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); this._registerModule(new ModuleObsidianSettingsAsMarkdown(this));
this._registerModule(new ModuleObsidianSettingDialogue(this, this)); this._registerModule(new ModuleObsidianSettingDialogue(this, this));
@@ -206,9 +212,24 @@ export default class ObsidianLiveSyncPlugin
return this.services.UI.confirm; 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 { getSettings(): ObsidianLiveSyncSettings {
return this.settings; return this.settings;
} }
@@ -259,46 +280,63 @@ export default class ObsidianLiveSyncPlugin
/// Modules which were relied on services /// Modules which were relied on services
/** /**
* Storage Accessor for handling file operations. * 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. * 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. * 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. * Rebuilder for handling database rebuilding operations.
* @obsolete Use serviceModules.rebuilder instead.
*/ */
rebuilder: Rebuilder; get rebuilder(): Rebuilder {
return this.serviceModules.rebuilder;
}
requestCount = reactiveSource(0); // requestCount = reactiveSource(0);
responseCount = reactiveSource(0); // responseCount = reactiveSource(0);
totalQueued = reactiveSource(0); // totalQueued = reactiveSource(0);
batched = reactiveSource(0); // batched = reactiveSource(0);
processing = reactiveSource(0); // processing = reactiveSource(0);
databaseQueueCount = reactiveSource(0); // databaseQueueCount = reactiveSource(0);
storageApplyingCount = reactiveSource(0); // storageApplyingCount = reactiveSource(0);
replicationResultCount = reactiveSource(0); // replicationResultCount = reactiveSource(0);
conflictProcessQueueCount = reactiveSource(0);
pendingFileEventCount = reactiveSource(0);
processingFileEventCount = reactiveSource(0);
_totalProcessingCount?: ReactiveValue<number>; // pendingFileEventCount = reactiveSource(0);
// processingFileEventCount = reactiveSource(0);
replicationStat = reactiveSource({ // _totalProcessingCount?: ReactiveValue<number>;
sent: 0,
arrived: 0,
maxPullSeq: 0,
maxPushSeq: 0,
lastSyncPullSeq: 0,
lastSyncPushSeq: 0,
syncStatus: "CLOSED" as DatabaseConnectingStatus,
});
// 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() { private initialiseServiceModules() {
const storageAccessManager = new StorageAccessManager(); const storageAccessManager = new StorageAccessManager();
// If we want to implement to the other platform, implement ObsidianXXXXXService. // If we want to implement to the other platform, implement ObsidianXXXXXService.
@@ -358,6 +396,7 @@ export default class ObsidianLiveSyncPlugin
vault: this.services.vault, vault: this.services.vault,
fileHandler: fileHandler, fileHandler: fileHandler,
storageAccess: storageAccess, storageAccess: storageAccess,
control: this.services.control,
}); });
return { return {
rebuilder, 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) { constructor(app: App, manifest: PluginManifest) {
super(app, manifest); super(app, manifest);
this.initialiseServices(); this.initialiseServices();
this.registerModules(); this.registerModules();
this.registerAddOns(); this.registerAddOns();
const instances = this.initialiseServiceModules(); this._serviceModules = this.initialiseServiceModules();
this.rebuilder = instances.rebuilder; this.initialiseServiceFeatures();
this.fileHandler = instances.fileHandler;
this.databaseFileAccess = instances.databaseFileAccess;
this.storageAccess = instances.storageAccess;
this.bindModuleFunctions(); this.bindModuleFunctions();
} }
private async _startUp() { private async _startUp() {
await this.services.appLifecycle.onLoad(); if (!(await this.services.control.onLoad())) return;
const onReady = this.services.appLifecycle.onReady.bind(this.services.appLifecycle); const onReady = this.services.control.onReady.bind(this.services.control);
this.app.workspace.onLayoutReady(onReady); this.app.workspace.onLayoutReady(onReady);
} }
onload() { onload() {
void this._startUp(); void this._startUp();
} }
async saveSettings() {
await this.services.setting.saveSettingData();
}
onunload() { onunload() {
return void this.services.appLifecycle.onAppUnload(); return void this.services.control.onUnload();
} }
// <-- Plug-in's overrideable functions
} }
// For now, // For now,
+3 -3
View File
@@ -203,8 +203,8 @@ export class StorageEventManagerObsidian extends StorageEventManagerBase {
const totalItems = allItems.length + this.concurrentProcessing.waiting; const totalItems = allItems.length + this.concurrentProcessing.waiting;
const processing = this.processingCount; const processing = this.processingCount;
const batchedCount = this._waitingMap.size; const batchedCount = this._waitingMap.size;
this.core.batched.value = batchedCount; this.fileProcessing.batched.value = batchedCount;
this.core.processing.value = processing; this.fileProcessing.processing.value = processing;
this.core.totalQueued.value = totalItems + batchedCount + processing; this.fileProcessing.totalQueued.value = totalItems + batchedCount + processing;
} }
} }
+1 -1
View File
@@ -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 { AnyEntry, FilePathWithPrefix } from "@lib/common/types";
import type { LiveSyncCore } from "@/main"; import type { LiveSyncCore } from "@/main";
import { stripAllPrefixes } from "@lib/string_and_binary/path"; 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 { export abstract class AbstractModule {
_log = createInstanceLogFunction(this.constructor.name, this.services.API); _log = createInstanceLogFunction(this.constructor.name, this.services.API);
+4 -4
View File
@@ -2,8 +2,8 @@ import { fireAndForget } from "octagonal-wheels/promises";
import { AbstractModule } from "../AbstractModule"; import { AbstractModule } from "../AbstractModule";
import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LEVEL_NOTICE, type LOG_LEVEL } from "octagonal-wheels/common/logger"; 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 { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks"; import { balanceChunkPurgedDBs } from "@lib/pouchdb/chunks";
import { purgeUnreferencedChunks } from "@/lib/src/pouchdb/chunks"; import { purgeUnreferencedChunks } from "@lib/pouchdb/chunks";
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator"; import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
import { type EntryDoc, type RemoteType } from "../../lib/src/common/types"; import { type EntryDoc, type RemoteType } from "../../lib/src/common/types";
import { rateLimitedSharedExecution, scheduleTask, updatePreviousExecutionTime } from "../../common/utils"; 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 { $msg } from "../../lib/src/common/i18n";
import type { LiveSyncCore } from "../../main"; import type { LiveSyncCore } from "../../main";
import { ReplicateResultProcessor } from "./ReplicateResultProcessor"; import { ReplicateResultProcessor } from "./ReplicateResultProcessor";
import { UnresolvedErrorManager } from "@/lib/src/services/base/UnresolvedErrorManager"; import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
import { clearHandlers } from "@/lib/src/replication/SyncParamsHandler"; import { clearHandlers } from "@lib/replication/SyncParamsHandler";
const KEY_REPLICATION_ON_EVENT = "replicationOnEvent"; const KEY_REPLICATION_ON_EVENT = "replicationOnEvent";
const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000; const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000;
+7 -6
View File
@@ -6,7 +6,7 @@ import {
type EntryLeaf, type EntryLeaf,
type LoadedEntry, type LoadedEntry,
type MetaEntry, type MetaEntry,
} from "@/lib/src/common/types"; } from "@lib/common/types";
import type { ModuleReplicator } from "./ModuleReplicator"; import type { ModuleReplicator } from "./ModuleReplicator";
import { isChunk, isValidPath } from "@/common/utils"; import { isChunk, isValidPath } from "@/common/utils";
import type { LiveSyncCore } from "@/main"; import type { LiveSyncCore } from "@/main";
@@ -17,8 +17,8 @@ import {
LOG_LEVEL_VERBOSE, LOG_LEVEL_VERBOSE,
Logger, Logger,
type LOG_LEVEL, type LOG_LEVEL,
} from "@/lib/src/common/logger"; } from "@lib/common/logger";
import { fireAndForget, isAnyNote, throttle } from "@/lib/src/common/utils"; import { fireAndForget, isAnyNote, throttle } from "@lib/common/utils";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore_v2"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore_v2";
import { serialized } from "octagonal-wheels/concurrency/lock"; import { serialized } from "octagonal-wheels/concurrency/lock";
import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
@@ -162,7 +162,8 @@ export class ReplicateResultProcessor {
* Report the current status. * Report the current status.
*/ */
protected reportStatus() { 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(); releaser();
} }
} }
}, this.replicator.core.databaseQueueCount); }, this.services.replication.databaseQueueCount);
} }
// Phase 2.1: process the document and apply to storage // Phase 2.1: process the document and apply to storage
// This function is serialized per document to avoid race-condition for the same document. // 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) { protected applyToStorage(entry: MetaEntry) {
return this.withCounting(async () => { return this.withCounting(async () => {
await this.services.replication.processSynchroniseResult(entry); await this.services.replication.processSynchroniseResult(entry);
}, this.replicator.core.storageApplyingCount); }, this.services.replication.storageApplyingCount);
} }
/** /**
@@ -71,7 +71,7 @@ export class ModuleConflictChecker extends AbstractModule {
delay: 0, delay: 0,
keepResultUntilDownstreamConnected: true, keepResultUntilDownstreamConnected: true,
pipeTo: this.conflictResolveQueue, pipeTo: this.conflictResolveQueue,
totalRemainingReactiveSource: this.core.conflictProcessQueueCount, totalRemainingReactiveSource: this.services.conflict.conflictProcessQueueCount,
} }
); );
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
+2 -2
View File
@@ -12,8 +12,8 @@ import type { LiveSyncCore } from "../../main.ts";
import FetchEverything from "../features/SetupWizard/dialogs/FetchEverything.svelte"; import FetchEverything from "../features/SetupWizard/dialogs/FetchEverything.svelte";
import RebuildEverything from "../features/SetupWizard/dialogs/RebuildEverything.svelte"; import RebuildEverything from "../features/SetupWizard/dialogs/RebuildEverything.svelte";
import { extractObject } from "octagonal-wheels/object"; import { extractObject } from "octagonal-wheels/object";
import { SvelteDialogManagerBase } from "@/lib/src/UI/svelteDialog.ts"; import { SvelteDialogManagerBase } from "@lib/UI/svelteDialog.ts";
import type { ServiceContext } from "@/lib/src/services/base/ServiceBase.ts"; import type { ServiceContext } from "@lib/services/base/ServiceBase.ts";
export class ModuleRedFlag extends AbstractModule { export class ModuleRedFlag extends AbstractModule {
async isFlagFileExist(path: string) { async isFlagFileExist(path: string) {
@@ -10,9 +10,9 @@ import {
import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts"; import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts";
import { type CouchDBCredentials, type EntryDoc } from "../../lib/src/common/types.ts"; import { type CouchDBCredentials, type EntryDoc } from "../../lib/src/common/types.ts";
import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts"; import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
import { replicationFilter } from "@/lib/src/pouchdb/compress.ts"; import { replicationFilter } from "@lib/pouchdb/compress.ts";
import { disableEncryption } from "@/lib/src/pouchdb/encryption.ts"; import { disableEncryption } from "@lib/pouchdb/encryption.ts";
import { enableEncryption } from "@/lib/src/pouchdb/encryption.ts"; import { enableEncryption } from "@lib/pouchdb/encryption.ts";
import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts"; import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts";
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts"; import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts";
import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts"; import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts";
@@ -96,7 +96,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
const size = body ? ` (${body.length})` : ""; const size = body ? ` (${body.length})` : "";
try { try {
const r = await this.__fetchByAPI(url, authHeader, opts); 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") { if (method == "POST" || method == "PUT") {
this.last_successful_post = r.status - (r.status % 100) == 200; this.last_successful_post = r.status - (r.status % 100) == 200;
} else { } else {
@@ -113,7 +113,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
this._log(ex); this._log(ex);
throw ex; throw ex;
} finally { } 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); headers.append("authorization", authHeader);
} }
try { 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 const response: Response = await (useRequestAPI
? this.__fetchByAPI(url.toString(), authHeader, { ...opts, headers }) ? this.__fetchByAPI(url.toString(), authHeader, { ...opts, headers })
: fetch(url, { ...opts, headers })); : fetch(url, { ...opts, headers }));
@@ -245,7 +245,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
this._log(ex); this._log(ex);
throw ex; throw ex;
} finally { } 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); // return await fetch(url, opts);
@@ -5,7 +5,7 @@ import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { type TFile } from "../../deps.ts"; import { type TFile } from "../../deps.ts";
import { fireAndForget } from "octagonal-wheels/promises"; import { fireAndForget } from "octagonal-wheels/promises";
import { type FilePathWithPrefix } from "../../lib/src/common/types.ts"; 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 { import {
collectingChunks, collectingChunks,
pluginScanningCount, pluginScanningCount,
@@ -188,20 +188,25 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
} }
}); });
} }
// TODO: separate
// Process counting for app reload scheduling
_totalProcessingCount?: ReactiveSource<number> = undefined;
private _scheduleAppReload() { private _scheduleAppReload() {
if (!this.core._totalProcessingCount) { if (!this._totalProcessingCount) {
const __tick = reactiveSource(0); const __tick = reactiveSource(0);
this.core._totalProcessingCount = reactive(() => { this._totalProcessingCount = reactive(() => {
const dbCount = this.core.databaseQueueCount.value; const dbCount = this.services.replication.databaseQueueCount.value;
const replicationCount = this.core.replicationResultCount.value; const replicationCount = this.services.replication.replicationResultCount.value;
const storageApplyingCount = this.core.storageApplyingCount.value; const storageApplyingCount = this.services.replication.storageApplyingCount.value;
const chunkCount = collectingChunks.value; const chunkCount = collectingChunks.value;
const pluginScanCount = pluginScanningCount.value; const pluginScanCount = pluginScanningCount.value;
const hiddenFilesCount = hiddenFilesEventCount.value + hiddenFilesProcessingCount.value; const hiddenFilesCount = hiddenFilesEventCount.value + hiddenFilesProcessingCount.value;
const conflictProcessCount = this.core.conflictProcessQueueCount.value; const conflictProcessCount = this.services.conflict.conflictProcessQueueCount.value;
const e = this.core.pendingFileEventCount.value; // Now no longer `pendingFileEventCount` and `processingFileEventCount` is used
const proc = this.core.processingFileEventCount.value; // 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 // eslint-disable-next-line @typescript-eslint/no-unused-vars
const __ = __tick.value; const __ = __tick.value;
return ( return (
@@ -223,7 +228,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
); );
let stableCheck = 3; let stableCheck = 3;
this.core._totalProcessingCount.onChanged((e) => { this._totalProcessingCount.onChanged((e) => {
if (e.value == 0) { if (e.value == 0) {
if (stableCheck-- <= 0) { if (stableCheck-- <= 0) {
this.__performAppReload(); 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 { onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this));
services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this));
services.appLifecycle.askRestart.setHandler(this._askReload.bind(this)); services.appLifecycle.askRestart.setHandler(this._askReload.bind(this));
services.appLifecycle.scheduleRestart.setHandler(this._scheduleAppReload.bind(this)); services.appLifecycle.scheduleRestart.setHandler(this._scheduleAppReload.bind(this));
services.appLifecycle.isReloadingScheduled.setHandler(this._isReloadingScheduled.bind(this));
} }
} }
@@ -59,7 +59,7 @@ export class ModuleObsidianMenu extends AbstractModule {
this.settings.liveSync = true; this.settings.liveSync = true;
this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE); this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE);
} }
await this.services.setting.realiseSetting(); await this.services.control.applySettings();
await this.services.setting.saveSettingData(); await this.services.setting.saveSettingData();
}, },
}); });
@@ -74,7 +74,7 @@ export class ModuleObsidianMenu extends AbstractModule {
this.services.appLifecycle.setSuspended(true); this.services.appLifecycle.setSuspended(true);
this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE); this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE);
} }
await this.services.setting.realiseSetting(); await this.services.control.applySettings();
await this.services.setting.saveSettingData(); await this.services.setting.saveSettingData();
}, },
}); });
+12 -12
View File
@@ -32,14 +32,14 @@ import { serialized } from "octagonal-wheels/concurrency/lock";
import { $msg } from "src/lib/src/common/i18n.ts"; import { $msg } from "src/lib/src/common/i18n.ts";
import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts"; import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
import type { LiveSyncCore } from "../../main.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 { isValidPath } from "@/common/utils.ts";
import { import {
isValidFilenameInAndroid, isValidFilenameInAndroid,
isValidFilenameInDarwin, isValidFilenameInDarwin,
isValidFilenameInWidows, isValidFilenameInWidows,
} from "@/lib/src/string_and_binary/path.ts"; } from "@lib/string_and_binary/path.ts";
import { MARK_LOG_SEPARATOR } from "@/lib/src/services/lib/logUtils.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. // 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); return computed(() => formatted.value);
} }
const labelReplication = padLeftSpComputed(this.core.replicationResultCount, `📥`); const labelReplication = padLeftSpComputed(this.services.replication.replicationResultCount, `📥`);
const labelDBCount = padLeftSpComputed(this.core.databaseQueueCount, `📄`); const labelDBCount = padLeftSpComputed(this.services.replication.databaseQueueCount, `📄`);
const labelStorageCount = padLeftSpComputed(this.core.storageApplyingCount, `💾`); const labelStorageCount = padLeftSpComputed(this.services.replication.storageApplyingCount, `💾`);
const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`); const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`);
const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`); 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 hiddenFilesCount = reactive(() => hiddenFilesEventCount.value - hiddenFilesProcessingCount.value);
const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`); const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`);
const queueCountLabelX = reactive(() => { const queueCountLabelX = reactive(() => {
@@ -116,12 +116,12 @@ export class ModuleLog extends AbstractObsidianModule {
const queueCountLabel = () => queueCountLabelX.value; const queueCountLabel = () => queueCountLabelX.value;
const requestingStatLabel = computed(() => { 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 ? "📲 " : ""; return diff != 0 ? "📲 " : "";
}); });
const replicationStatLabel = computed(() => { const replicationStatLabel = computed(() => {
const e = this.core.replicationStat.value; const e = this.services.replicator.replicationStatics.value;
const sent = e.sent; const sent = e.sent;
const arrived = e.arrived; const arrived = e.arrived;
const maxPullSeq = e.maxPullSeq; const maxPullSeq = e.maxPullSeq;
@@ -173,9 +173,9 @@ export class ModuleLog extends AbstractObsidianModule {
} }
return { w, sent, pushLast, arrived, pullLast }; return { w, sent, pushLast, arrived, pullLast };
}); });
const labelProc = padLeftSpComputed(this.core.processing, ``); const labelProc = padLeftSpComputed(this.services.fileProcessing.processing, ``);
const labelPend = padLeftSpComputed(this.core.totalQueued, `🛫`); const labelPend = padLeftSpComputed(this.services.fileProcessing.totalQueued, `🛫`);
const labelInBatchDelay = padLeftSpComputed(this.core.batched, `📬`); const labelInBatchDelay = padLeftSpComputed(this.services.fileProcessing.batched, `📬`);
const waitingLabel = computed(() => { const waitingLabel = computed(() => {
return `${labelProc()}${labelPend()}${labelInBatchDelay()}`; return `${labelProc()}${labelPend()}${labelInBatchDelay()}`;
}); });
@@ -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<boolean> {
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<ConfigPassphraseStore, () => Promise<string | false>> = {
"": () => 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<ObsidianLiveSyncSettings> {
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<ObsidianLiveSyncSettings> {
// 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<void> {
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<void> {
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<ObsidianLiveSyncSettings>): Promise<void> {
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));
}
}
@@ -6,8 +6,8 @@ import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSetting
import { parseYaml, stringifyYaml } from "../../deps"; import { parseYaml, stringifyYaml } from "../../deps";
import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import { AbstractModule } from "../AbstractModule.ts"; import { AbstractModule } from "../AbstractModule.ts";
import type { ServiceContext } from "@/lib/src/services/base/ServiceBase.ts"; import type { ServiceContext } from "@lib/services/base/ServiceBase.ts";
import type { InjectableServiceHub } from "@/lib/src/services/InjectableServices.ts"; import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts";
import type { LiveSyncCore } from "@/main.ts"; import type { LiveSyncCore } from "@/main.ts";
const SETTING_HEADER = "````yaml:livesync-setting\n"; const SETTING_HEADER = "````yaml:livesync-setting\n";
const SETTING_FOOTER = "\n````"; const SETTING_FOOTER = "\n````";
@@ -16,7 +16,7 @@ import {
import { delay, isObjectDifferent, sizeToHumanReadable } from "../../../lib/src/common/utils.ts"; import { delay, isObjectDifferent, sizeToHumanReadable } from "../../../lib/src/common/utils.ts";
import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts"; import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts";
import { Logger } from "../../../lib/src/common/logger.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 { testCrypt } from "octagonal-wheels/encryption/encryption";
import ObsidianLiveSyncPlugin from "../../../main.ts"; import ObsidianLiveSyncPlugin from "../../../main.ts";
import { scheduleTask } from "../../../common/utils.ts"; import { scheduleTask } from "../../../common/utils.ts";
@@ -105,7 +105,7 @@ export function paneSyncSettings(
if (!this.editingSettings.isConfigured) { if (!this.editingSettings.isConfigured) {
this.editingSettings.isConfigured = true; this.editingSettings.isConfigured = true;
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
await this.services.setting.realiseSetting(); await this.services.control.applySettings();
await this.rebuildDB("localOnly"); await this.rebuildDB("localOnly");
// this.resetEditingSettings(); // this.resetEditingSettings();
if ( if (
@@ -124,13 +124,13 @@ export function paneSyncSettings(
await this.confirmRebuild(); await this.confirmRebuild();
} else { } else {
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
await this.services.setting.realiseSetting(); await this.services.control.applySettings();
this.services.appLifecycle.askRestart(); this.services.appLifecycle.askRestart();
} }
} }
} else { } else {
await this.saveAllDirtySettings(); 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.saveSettings(["liveSync", "periodicReplication"]);
await this.services.setting.realiseSetting(); await this.services.control.applySettings();
}); });
new Setting(paneEl) new Setting(paneEl)
+15 -21
View File
@@ -49,7 +49,7 @@ export class ModuleLiveSyncMain extends AbstractModule {
} }
if (!(await this.core.services.appLifecycle.onFirstInitialise())) return false; if (!(await this.core.services.appLifecycle.onFirstInitialise())) return false;
// await this.core.$$realizeSettingSyncMode(); // await this.core.$$realizeSettingSyncMode();
await this.services.setting.realiseSetting(); await this.services.control.applySettings();
fireAndForget(async () => { fireAndForget(async () => {
this._log($msg("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE); this._log($msg("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE);
if (!(await this.services.appLifecycle.onScanningStartupIssues())) { if (!(await this.services.appLifecycle.onScanningStartupIssues())) {
@@ -65,7 +65,7 @@ export class ModuleLiveSyncMain extends AbstractModule {
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => { eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
fireAndForget(async () => { fireAndForget(async () => {
try { try {
await this.core.services.setting.realiseSetting(); await this.core.services.control.applySettings();
const lang = this.core.services.setting.currentSettings()?.displayLanguage ?? undefined; const lang = this.core.services.setting.currentSettings()?.displayLanguage ?? undefined;
if (lang !== undefined) { if (lang !== undefined) {
setLang(this.core.services.setting.currentSettings()?.displayLanguage); setLang(this.core.services.setting.currentSettings()?.displayLanguage);
@@ -163,23 +163,19 @@ export class ModuleLiveSyncMain extends AbstractModule {
return; return;
} }
private async _realizeSettingSyncMode(): Promise<void> { // private async _realizeSettingSyncMode(): Promise<void> {
await this.services.appLifecycle.onSuspending(); // await this.services.appLifecycle.onSuspending();
await this.services.setting.onBeforeRealiseSetting(); // await this.services.setting.onBeforeRealiseSetting();
this.localDatabase.refreshSettings(); // this.localDatabase.refreshSettings();
await this.services.fileProcessing.commitPendingFileEvents(); // await this.services.fileProcessing.commitPendingFileEvents();
await this.services.setting.onRealiseSetting(); // await this.services.setting.onRealiseSetting();
// disable all sync temporary. // // disable all sync temporary.
if (this.services.appLifecycle.isSuspended()) return; // if (this.services.appLifecycle.isSuspended()) return;
await this.services.appLifecycle.onResuming(); // await this.services.appLifecycle.onResuming();
await this.services.appLifecycle.onResumed(); // await this.services.appLifecycle.onResumed();
await this.services.setting.onSettingRealised(); // await this.services.setting.onSettingRealised();
return; // return;
} // }
_isReloadingScheduled(): boolean {
return this.core._totalProcessingCount !== undefined;
}
isReady = false; isReady = false;
@@ -217,11 +213,9 @@ export class ModuleLiveSyncMain extends AbstractModule {
services.appLifecycle.markIsReady.setHandler(this._markIsReady.bind(this)); services.appLifecycle.markIsReady.setHandler(this._markIsReady.bind(this));
services.appLifecycle.resetIsReady.setHandler(this._resetIsReady.bind(this)); services.appLifecycle.resetIsReady.setHandler(this._resetIsReady.bind(this));
services.appLifecycle.hasUnloaded.setHandler(this._isUnloaded.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.onReady.addHandler(this._onLiveSyncReady.bind(this));
services.appLifecycle.onWireUpEvents.addHandler(this._wireUpEvents.bind(this)); services.appLifecycle.onWireUpEvents.addHandler(this._wireUpEvents.bind(this));
services.appLifecycle.onLoad.addHandler(this._onLiveSyncLoad.bind(this)); services.appLifecycle.onLoad.addHandler(this._onLiveSyncLoad.bind(this));
services.appLifecycle.onAppUnload.addHandler(this._onLiveSyncUnload.bind(this)); services.appLifecycle.onAppUnload.addHandler(this._onLiveSyncUnload.bind(this));
services.setting.realiseSetting.setHandler(this._realizeSettingSyncMode.bind(this));
} }
} }
+18 -2
View File
@@ -1,13 +1,20 @@
import { InjectableAPIService } from "@/lib/src/services/implements/injectable/InjectableAPIService"; import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService";
import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import { Platform, type Command, type ViewCreator } from "obsidian"; import { Platform, type Command, type ViewCreator } from "obsidian";
import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler"; 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. // All Services will be migrated to be based on Plain Services, not Injectable Services.
// This is a migration step. // This is a migration step.
export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceContext> { export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceContext> {
_customHandler: ObsHttpHandler | undefined; _customHandler: ObsHttpHandler | undefined;
_confirmInstance: Confirm;
constructor(context: ObsidianServiceContext) {
super(context);
this._confirmInstance = new ObsidianConfirm(context);
}
getCustomFetchHandler(): ObsHttpHandler { getCustomFetchHandler(): ObsHttpHandler {
if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined); if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
return this._customHandler; return this._customHandler;
@@ -63,6 +70,11 @@ export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceCont
override getAppID(): string { override getAppID(): string {
return `${"appId" in this.app ? this.app.appId : ""}`; return `${"appId" in this.app ? this.app.appId : ""}`;
} }
override getSystemVaultName(): string {
return this.app.vault.getName();
}
override getAppVersion(): string { override getAppVersion(): string {
const navigatorString = globalThis.navigator?.userAgent ?? ""; const navigatorString = globalThis.navigator?.userAgent ?? "";
const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/); const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/);
@@ -76,6 +88,10 @@ export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceCont
return this.context.plugin.manifest.version; return this.context.plugin.manifest.version;
} }
get confirm(): Confirm {
return this._confirmInstance;
}
addCommand<TCommand extends Command>(command: TCommand): TCommand { addCommand<TCommand extends Command>(command: TCommand): TCommand {
return this.context.plugin.addCommand(command) as TCommand; return this.context.plugin.addCommand(command) as TCommand;
} }
+19 -6
View File
@@ -1,6 +1,6 @@
import { InjectableServiceHub } from "@/lib/src/services/implements/injectable/InjectableServiceHub"; import { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub";
import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; import { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import type { ServiceInstances } from "@/lib/src/services/ServiceHub"; import type { ServiceInstances } from "@lib/services/ServiceHub";
import type ObsidianLiveSyncPlugin from "@/main"; import type ObsidianLiveSyncPlugin from "@/main";
import { import {
ObsidianConflictService, ObsidianConflictService,
@@ -8,13 +8,14 @@ import {
ObsidianReplicationService, ObsidianReplicationService,
ObsidianReplicatorService, ObsidianReplicatorService,
ObsidianRemoteService, ObsidianRemoteService,
ObsidianSettingService,
ObsidianTweakValueService, ObsidianTweakValueService,
ObsidianTestService, ObsidianTestService,
ObsidianDatabaseEventService, ObsidianDatabaseEventService,
ObsidianConfigService, ObsidianConfigService,
ObsidianKeyValueDBService, ObsidianKeyValueDBService,
ObsidianControlService,
} from "./ObsidianServices"; } from "./ObsidianServices";
import { ObsidianSettingService } from "./ObsidianSettingService";
import { ObsidianDatabaseService } from "./ObsidianDatabaseService"; import { ObsidianDatabaseService } from "./ObsidianDatabaseService";
import { ObsidianAPIService } from "./ObsidianAPIService"; import { ObsidianAPIService } from "./ObsidianAPIService";
import { ObsidianAppLifecycleService } from "./ObsidianAppLifecycleService"; import { ObsidianAppLifecycleService } from "./ObsidianAppLifecycleService";
@@ -35,10 +36,14 @@ export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceCont
const replication = new ObsidianReplicationService(context); const replication = new ObsidianReplicationService(context);
const remote = new ObsidianRemoteService(context); const remote = new ObsidianRemoteService(context);
const setting = new ObsidianSettingService(context);
const tweakValue = new ObsidianTweakValueService(context); const tweakValue = new ObsidianTweakValueService(context);
const setting = new ObsidianSettingService(context, {
APIService: API,
});
const vault = new ObsidianVaultService(context, { const vault = new ObsidianVaultService(context, {
settingService: setting, settingService: setting,
APIService: API,
}); });
const test = new ObsidianTestService(context); const test = new ObsidianTestService(context);
const databaseEvents = new ObsidianDatabaseEventService(context); const databaseEvents = new ObsidianDatabaseEventService(context);
@@ -56,7 +61,6 @@ export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceCont
vault: vault, vault: vault,
}); });
const config = new ObsidianConfigService(context, { const config = new ObsidianConfigService(context, {
vaultService: vault,
settingService: setting, settingService: setting,
APIService: API, APIService: API,
}); });
@@ -69,6 +73,14 @@ export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceCont
appLifecycle, appLifecycle,
config, config,
replicator, replicator,
APIService: API,
});
const control = new ObsidianControlService(context, {
appLifecycleService: appLifecycle,
databaseService: database,
fileProcessingService: fileProcessing,
settingService: setting,
APIService: API,
}); });
// Using 'satisfies' to ensure all services are provided // Using 'satisfies' to ensure all services are provided
@@ -90,6 +102,7 @@ export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceCont
API: API, API: API,
config: config, config: config,
keyValueDB: keyValueDB, keyValueDB: keyValueDB,
control: control,
} satisfies Required<ServiceInstances<ObsidianServiceContext>>; } satisfies Required<ServiceInstances<ObsidianServiceContext>>;
super(context, serviceInstancesToInit); super(context, serviceInstancesToInit);
+4 -4
View File
@@ -4,12 +4,12 @@ import { InjectableFileProcessingService } from "@lib/services/implements/inject
import { InjectableRemoteService } from "@lib/services/implements/injectable/InjectableRemoteService"; import { InjectableRemoteService } from "@lib/services/implements/injectable/InjectableRemoteService";
import { InjectableReplicationService } from "@lib/services/implements/injectable/InjectableReplicationService"; import { InjectableReplicationService } from "@lib/services/implements/injectable/InjectableReplicationService";
import { InjectableReplicatorService } from "@lib/services/implements/injectable/InjectableReplicatorService"; 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 { InjectableTestService } from "@lib/services/implements/injectable/InjectableTestService";
import { InjectableTweakValueService } from "@lib/services/implements/injectable/InjectableTweakValueService"; import { InjectableTweakValueService } from "@lib/services/implements/injectable/InjectableTweakValueService";
import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat"; import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat";
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext.ts"; 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<ObsidianServiceContext> {} export class ObsidianDatabaseEventService extends InjectableDatabaseEventService<ObsidianServiceContext> {}
@@ -23,8 +23,6 @@ export class ObsidianReplicationService extends InjectableReplicationService<Obs
export class ObsidianRemoteService extends InjectableRemoteService<ObsidianServiceContext> {} export class ObsidianRemoteService extends InjectableRemoteService<ObsidianServiceContext> {}
// InjectableConflictService // InjectableConflictService
export class ObsidianConflictService extends InjectableConflictService<ObsidianServiceContext> {} export class ObsidianConflictService extends InjectableConflictService<ObsidianServiceContext> {}
// InjectableSettingService
export class ObsidianSettingService extends InjectableSettingService<ObsidianServiceContext> {}
// InjectableTweakValueService // InjectableTweakValueService
export class ObsidianTweakValueService extends InjectableTweakValueService<ObsidianServiceContext> {} export class ObsidianTweakValueService extends InjectableTweakValueService<ObsidianServiceContext> {}
// InjectableTestService // InjectableTestService
@@ -32,3 +30,5 @@ export class ObsidianTestService extends InjectableTestService<ObsidianServiceCo
export class ObsidianConfigService extends ConfigServiceBrowserCompat<ObsidianServiceContext> {} export class ObsidianConfigService extends ConfigServiceBrowserCompat<ObsidianServiceContext> {}
export class ObsidianKeyValueDBService extends KeyValueDBService<ObsidianServiceContext> {} export class ObsidianKeyValueDBService extends KeyValueDBService<ObsidianServiceContext> {}
export class ObsidianControlService extends ControlService<ObsidianServiceContext> {}
@@ -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<T extends ObsidianServiceContext> extends SettingService<T> {
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<void> {
return await this.context.liveSyncPlugin.saveData(data);
}
protected override async loadData(): Promise<ObsidianLiveSyncSettings | undefined> {
return await this.context.liveSyncPlugin.loadData();
}
}
+6 -5
View File
@@ -2,14 +2,15 @@ import type { ConfigService } from "@lib/services/base/ConfigService";
import type { AppLifecycleService } from "@lib/services/base/AppLifecycleService"; import type { AppLifecycleService } from "@lib/services/base/AppLifecycleService";
import type { ReplicatorService } from "@lib/services/base/ReplicatorService"; import type { ReplicatorService } from "@lib/services/base/ReplicatorService";
import { UIService } from "@lib/services//implements/base/UIService"; 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 { ObsidianSvelteDialogManager } from "./SvelteDialogObsidian";
import { ObsidianConfirm } from "./ObsidianConfirm"; import DialogToCopy from "@lib/UI/dialogues/DialogueToCopy.svelte";
import DialogToCopy from "@/lib/src/UI/dialogues/DialogueToCopy.svelte"; import type { IAPIService } from "@lib/services/base/IService";
export type ObsidianUIServiceDependencies<T extends ObsidianServiceContext = ObsidianServiceContext> = { export type ObsidianUIServiceDependencies<T extends ObsidianServiceContext = ObsidianServiceContext> = {
appLifecycle: AppLifecycleService<T>; appLifecycle: AppLifecycleService<T>;
config: ConfigService<T>; config: ConfigService<T>;
replicator: ReplicatorService<T>; replicator: ReplicatorService<T>;
APIService: IAPIService;
}; };
export class ObsidianUIService extends UIService<ObsidianServiceContext> { export class ObsidianUIService extends UIService<ObsidianServiceContext> {
@@ -17,7 +18,7 @@ export class ObsidianUIService extends UIService<ObsidianServiceContext> {
return DialogToCopy; return DialogToCopy;
} }
constructor(context: ObsidianServiceContext, dependents: ObsidianUIServiceDependencies<ObsidianServiceContext>) { constructor(context: ObsidianServiceContext, dependents: ObsidianUIServiceDependencies<ObsidianServiceContext>) {
const obsidianConfirm = new ObsidianConfirm(context); const obsidianConfirm = dependents.APIService.confirm;
const obsidianSvelteDialogManager = new ObsidianSvelteDialogManager<ObsidianServiceContext>(context, { const obsidianSvelteDialogManager = new ObsidianSvelteDialogManager<ObsidianServiceContext>(context, {
appLifecycle: dependents.appLifecycle, appLifecycle: dependents.appLifecycle,
config: dependents.config, config: dependents.config,
@@ -27,7 +28,7 @@ export class ObsidianUIService extends UIService<ObsidianServiceContext> {
super(context, { super(context, {
appLifecycle: dependents.appLifecycle, appLifecycle: dependents.appLifecycle,
dialogManager: obsidianSvelteDialogManager, dialogManager: obsidianSvelteDialogManager,
confirm: obsidianConfirm, APIService: dependents.APIService,
}); });
} }
} }
+3
View File
@@ -0,0 +1,3 @@
import { enableI18nFeature } from "./onLayoutReady/enablei18n";
export const onLayoutReadyFeatures = [enableI18nFeature];
@@ -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;
});
+50
View File
@@ -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<T extends keyof IServiceHub> = Pick<IServiceHub, T>;
export type RequiredServiceModules<T extends keyof ServiceModules> = Pick<ServiceModules, T>;
export type NecessaryServices<T extends keyof IServiceHub, U extends keyof ServiceModules> = {
services: RequiredServices<T>;
serviceModules: RequiredServiceModules<U>;
};
export type ServiceFeatureFunction<T extends keyof IServiceHub, U extends keyof ServiceModules, TR> = (
host: NecessaryServices<T, U>
) => 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<T extends keyof IServiceHub, U extends keyof ServiceModules, TR>(
featureFunction: ServiceFeatureFunction<T, U, TR>
): ServiceFeatureFunction<T, U, TR> {
return featureFunction;
}
+27
View File
@@ -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;
}
+5 -7
View File
@@ -122,13 +122,11 @@ export async function waitForIdle(harness: LiveSyncHarness): Promise<void> {
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
await delay(25); await delay(25);
const processing = const processing =
harness.plugin.databaseQueueCount.value + harness.plugin.services.replication.databaseQueueCount.value +
harness.plugin.processingFileEventCount.value + harness.plugin.services.fileProcessing.totalQueued.value +
harness.plugin.pendingFileEventCount.value + harness.plugin.services.fileProcessing.batched.value +
harness.plugin.totalQueued.value + harness.plugin.services.fileProcessing.processing.value +
harness.plugin.batched.value + harness.plugin.services.replication.storageApplyingCount.value;
harness.plugin.processing.value +
harness.plugin.storageApplyingCount.value;
if (processing === 0) { if (processing === 0) {
if (i > 0) { if (i > 0) {
+19 -2
View File
@@ -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 ## 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
- 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 ## 0.25.43-patched-6