Merge pull request #726 from vrtmrz/disenchant

Disenchant
This commit is contained in:
vorotamoroz
2025-10-13 10:40:20 +09:00
committed by GitHub
70 changed files with 1847 additions and 1368 deletions

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.25.2",
"version": "0.25.21.beta2",
"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",

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.25.20",
"version": "0.25.21",
"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",

18
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "0.25.20",
"version": "0.25.21",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.25.20",
"version": "0.25.21",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.808.0",
@@ -19,7 +19,7 @@
"fflate": "^0.8.2",
"idb": "^8.0.3",
"minimatch": "^10.0.2",
"octagonal-wheels": "^0.1.40",
"octagonal-wheels": "^0.1.41",
"qrcode-generator": "^1.4.4",
"trystero": "github:vrtmrz/trystero#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
@@ -8605,9 +8605,9 @@
}
},
"node_modules/octagonal-wheels": {
"version": "0.1.40",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.40.tgz",
"integrity": "sha512-qZkPnuVGCqpfLfu8xtZIxfQRVvmE5BmdzMF/rySriGi5JoctGhMNDjF0aLU/4GWUD5yW1X3io6VhJW4a7k1ieA==",
"version": "0.1.41",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.41.tgz",
"integrity": "sha512-cIvdXsyiSCxknyxTwGrNnDKsaYpgZdXeKAy9cXIAk2Jy7T1z6bLjU4s5z47OySNPVPSr32x5r8hSz7hAYYv7qA==",
"license": "MIT",
"dependencies": {
"idb": "^8.0.3"
@@ -17148,9 +17148,9 @@
}
},
"octagonal-wheels": {
"version": "0.1.40",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.40.tgz",
"integrity": "sha512-qZkPnuVGCqpfLfu8xtZIxfQRVvmE5BmdzMF/rySriGi5JoctGhMNDjF0aLU/4GWUD5yW1X3io6VhJW4a7k1ieA==",
"version": "0.1.41",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.41.tgz",
"integrity": "sha512-cIvdXsyiSCxknyxTwGrNnDKsaYpgZdXeKAy9cXIAk2Jy7T1z6bLjU4s5z47OySNPVPSr32x5r8hSz7hAYYv7qA==",
"requires": {
"idb": "^8.0.3"
}

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.25.20",
"version": "0.25.21",
"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",
@@ -93,7 +93,7 @@
"fflate": "^0.8.2",
"idb": "^8.0.3",
"minimatch": "^10.0.2",
"octagonal-wheels": "^0.1.40",
"octagonal-wheels": "^0.1.41",
"qrcode-generator": "^1.4.4",
"trystero": "github:vrtmrz/trystero#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"

View File

@@ -1,11 +1,6 @@
import { type PluginManifest, TFile } from "../deps.ts";
import {
type DatabaseEntry,
type EntryBody,
type FilePath,
type UXFileInfoStub,
type UXInternalFileInfoStub,
} from "../lib/src/common/types.ts";
import { type DatabaseEntry, type EntryBody, type FilePath } from "../lib/src/common/types.ts";
export type { CacheData, FileEventItem } from "../lib/src/common/types.ts";
export interface PluginDataEntry extends DatabaseEntry {
deviceVaultName: string;
@@ -54,23 +49,6 @@ export type queueItem = {
warned?: boolean;
};
export type CacheData = string | ArrayBuffer;
export type FileEventType = "CREATE" | "DELETE" | "CHANGED" | "INTERNAL";
export type FileEventArgs = {
file: UXFileInfoStub | UXInternalFileInfoStub;
cache?: CacheData;
oldPath?: string;
ctx?: any;
};
export type FileEventItem = {
type: FileEventType;
args: FileEventArgs;
key: string;
skipBatchWait?: boolean;
cancelled?: boolean;
batched?: boolean;
};
// Hidden items (Now means `chunk`)
export const CHeader = "h:";

View File

@@ -189,7 +189,7 @@ export class PeriodicProcessor {
() =>
fireAndForget(async () => {
await this.process();
if (this._plugin.$$isUnloaded()) {
if (this._plugin.services?.appLifecycle?.hasUnloaded()) {
this.disable();
}
}),

View File

@@ -68,14 +68,23 @@ import type ObsidianLiveSyncPlugin from "../../main.ts";
import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64";
import { ConflictResolveModal } from "../../modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "../../common/events.ts";
import { PluginDialogModal } from "./PluginDialogModal.ts";
import { $msg } from "src/lib/src/common/i18n.ts";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts";
const d = "\u200b";
const d2 = "\n";
declare global {
interface OPTIONAL_SYNC_FEATURES {
DISABLE: "DISABLE";
CUSTOMIZE: "CUSTOMIZE";
DISABLE_CUSTOM: "DISABLE_CUSTOM";
}
}
function serialize(data: PluginDataEx): string {
// For higher performance, create custom plug-in data strings.
// Self-hosted LiveSync uses `\n` to split chunks. Therefore, grouping together those with similar entropy would work nicely.
@@ -384,7 +393,7 @@ export type PluginDataEx = {
mtime: number;
};
export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
export class ConfigSync extends LiveSyncCommands {
constructor(plugin: ObsidianLiveSyncPlugin) {
super(plugin);
pluginScanningCount.onChanged((e) => {
@@ -402,7 +411,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
get useSyncPluginEtc() {
return this.plugin.settings.usePluginEtc;
}
_isThisModuleEnabled() {
isThisModuleEnabled() {
return this.plugin.settings.usePluginSync;
}
@@ -411,7 +420,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
pluginList: IPluginDataExDisplay[] = [];
showPluginSyncModal() {
if (!this._isThisModuleEnabled()) {
if (!this.isThisModuleEnabled()) {
return;
}
if (this.pluginDialog) {
@@ -482,8 +491,8 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
// Idea non-filter option?
return this.getFileCategory(filePath) != "";
}
async $everyOnDatabaseInitialized(showNotice: boolean) {
if (!this._isThisModuleEnabled()) return true;
private async _everyOnDatabaseInitialized(showNotice: boolean) {
if (!this.isThisModuleEnabled()) return true;
try {
this._log("Scanning customizations...");
await this.scanAllConfigFiles(showNotice);
@@ -494,16 +503,16 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
return true;
}
async $everyBeforeReplicate(showNotice: boolean) {
if (!this._isThisModuleEnabled()) return true;
async _everyBeforeReplicate(showNotice: boolean) {
if (!this.isThisModuleEnabled()) return true;
if (this.settings.autoSweepPlugins) {
await this.scanAllConfigFiles(showNotice);
return true;
}
return true;
}
async $everyOnResumeProcess(): Promise<boolean> {
if (!this._isThisModuleEnabled()) return true;
async _everyOnResumeProcess(): Promise<boolean> {
if (!this.isThisModuleEnabled()) return true;
if (this._isMainSuspended()) {
return true;
}
@@ -517,9 +526,9 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
);
return true;
}
$everyAfterResumeProcess(): Promise<boolean> {
_everyAfterResumeProcess(): Promise<boolean> {
const q = activeDocument.querySelector(`.livesync-ribbon-showcustom`);
q?.toggleClass("sls-hidden", !this._isThisModuleEnabled());
q?.toggleClass("sls-hidden", !this.isThisModuleEnabled());
return Promise.resolve(true);
}
async reloadPluginList(showMessage: boolean) {
@@ -633,7 +642,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
).startPipeline();
filenameToUnifiedKey(path: string, termOverRide?: string) {
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
const category = this.getFileCategory(path);
const name =
category == "CONFIG" || category == "SNIPPET"
@@ -645,7 +654,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
filenameWithUnifiedKey(path: string, termOverRide?: string) {
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
const category = this.getFileCategory(path);
const name =
category == "CONFIG" || category == "SNIPPET" ? path.split("/").slice(-1)[0] : path.split("/").slice(-2)[0];
@@ -654,7 +663,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
unifiedKeyPrefixOfTerminal(termOverRide?: string) {
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
return `${ICXHeader}${term}/` as FilePathWithPrefix;
}
@@ -831,7 +840,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
const v2Path = (prefixPath + relativeFilename) as FilePathWithPrefix;
// console.warn(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`);
this._log(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`, LOG_LEVEL_VERBOSE);
const newId = await this.plugin.$$path2id(v2Path);
const newId = await this.services.path.path2id(v2Path);
// const buf =
const data = createBlob([DUMMY_HEAD, DUMMY_END, ...getDocDataAsArray(f.data)]);
@@ -861,7 +870,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
async updatePluginList(showMessage: boolean, updatedDocumentPath?: FilePathWithPrefix): Promise<void> {
if (!this._isThisModuleEnabled()) {
if (!this.isThisModuleEnabled()) {
this.pluginScanProcessor.clearQueue();
this.pluginList = [];
pluginList.set(this.pluginList);
@@ -999,7 +1008,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
await this.plugin.storageAccess.ensureDir(path);
// If the content has applied, modified time will be updated to the current time.
await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName());
await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName());
} else {
const files = data.files;
for (const f of files) {
@@ -1042,7 +1051,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
}
this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`);
await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName());
await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName());
}
}
} catch (ex) {
@@ -1114,7 +1123,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
);
}
} else if (data.category == "CONFIG") {
this.plugin.$$askReload();
this.services.appLifecycle.askRestart();
}
return true;
} catch (ex) {
@@ -1157,15 +1166,15 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
return false;
}
}
async $anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) {
if (!docs._id.startsWith(ICXHeader)) return undefined;
if (this._isThisModuleEnabled()) {
async _anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) {
if (!docs._id.startsWith(ICXHeader)) return false;
if (this.isThisModuleEnabled()) {
await this.updatePluginList(
false,
(docs as AnyEntry).path ? (docs as AnyEntry).path : this.getPath(docs as AnyEntry)
);
}
if (this._isThisModuleEnabled() && this.plugin.settings.notifyPluginOrSettingUpdated) {
if (this.isThisModuleEnabled() && this.plugin.settings.notifyPluginOrSettingUpdated) {
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
const fragment = createFragment((doc) => {
doc.createEl("span", undefined, (a) => {
@@ -1205,11 +1214,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
return true;
}
async $everyRealizeSettingSyncMode(): Promise<boolean> {
async _everyRealizeSettingSyncMode(): Promise<boolean> {
this.periodicPluginSweepProcessor?.disable();
if (!this._isMainReady) return true;
if (!this._isMainSuspended()) return true;
if (!this._isThisModuleEnabled()) return true;
if (!this.isThisModuleEnabled()) return true;
if (this.settings.autoSweepPlugins) {
await this.scanAllConfigFiles(false);
}
@@ -1345,7 +1354,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
});
}
async storeCustomizationFiles(path: FilePath, termOverRide?: string) {
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
if (term == "") {
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
return;
@@ -1488,14 +1497,14 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
});
}
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
async _anyProcessOptionalFileEvent(path: FilePath): Promise<boolean> {
return await this.watchVaultRawEventsAsync(path);
}
async watchVaultRawEventsAsync(path: FilePath) {
if (!this._isMainReady) return false;
if (this._isMainSuspended()) return false;
if (!this._isThisModuleEnabled()) return false;
if (!this.isThisModuleEnabled()) return false;
// if (!this.isTargetPath(path)) return false;
const stat = await this.plugin.storageAccess.statHidden(path);
// Make sure that target is a file.
@@ -1535,7 +1544,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
await shareRunningResult("scanAllConfigFiles", async () => {
const logLevel = showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
this._log("Scanning customizing files.", logLevel, "scan-all-config");
const term = this.plugin.$$getDeviceAndVaultName();
const term = this.services.setting.getDeviceAndVaultName();
if (term == "") {
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
return;
@@ -1673,11 +1682,14 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
return filenames as FilePath[];
}
async $allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
await this._askHiddenFileConfiguration(opt);
private async _allAskUsingOptionalSyncFeature(opt: {
enableFetch?: boolean;
enableOverwrite?: boolean;
}): Promise<boolean> {
await this.__askHiddenFileConfiguration(opt);
return true;
}
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
private async __askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
const message = `Would you like to enable **Customization sync**?
> [!DETAILS]-
@@ -1707,7 +1719,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
}
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
_anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
if (isPluginMetadata(path)) {
return Promise.resolve("newer");
}
@@ -1717,7 +1729,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
return Promise.resolve(false);
}
$allSuspendExtraSync(): Promise<boolean> {
private _allSuspendExtraSync(): Promise<boolean> {
if (this.plugin.settings.usePluginSync || this.plugin.settings.autoSweepPlugins) {
this._log(
"Customisation sync have been temporarily disabled. Please enable them after the fetching, if you need them.",
@@ -1729,10 +1741,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
return Promise.resolve(true);
}
async $anyConfigureOptionalSyncFeature(mode: "CUSTOMIZE" | "DISABLE" | "DISABLE_CUSTOM") {
private async _anyConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
await this.configureHiddenFileSync(mode);
return true;
}
async configureHiddenFileSync(mode: "CUSTOMIZE" | "DISABLE" | "DISABLE_CUSTOM") {
async configureHiddenFileSync(mode: keyof OPTIONAL_SYNC_FEATURES) {
if (mode == "DISABLE") {
this.plugin.settings.usePluginSync = false;
await this.plugin.saveSettings();
@@ -1740,7 +1753,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
if (mode == "CUSTOMIZE") {
if (!this.plugin.$$getDeviceAndVaultName()) {
if (!this.services.setting.getDeviceAndVaultName()) {
let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`);
if (!name) {
if (Platform.isAndroidApp) {
@@ -1764,7 +1777,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
name = name + Math.random().toString(36).slice(-4);
}
this.plugin.$$setDeviceAndVaultName(name);
this.services.setting.setDeviceAndVaultName(name);
}
this.plugin.settings.usePluginSync = true;
this.plugin.settings.useAdvancedMode = true;
@@ -1789,4 +1802,17 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}
return files;
}
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.fileProcessing.handleOptionalFileEvent(this._anyProcessOptionalFileEvent.bind(this));
services.conflict.handleGetOptionalConflictCheckMethod(this._anyGetOptionalConflictCheckMethod.bind(this));
services.replication.handleProcessVirtualDocuments(this._anyModuleParsedReplicationResultItem.bind(this));
services.setting.handleOnRealiseSetting(this._everyRealizeSettingSyncMode.bind(this));
services.appLifecycle.handleOnResuming(this._everyOnResumeProcess.bind(this));
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
services.databaseEvents.handleDatabaseInitialised(this._everyOnDatabaseInitialized.bind(this));
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
services.setting.handleSuggestOptionalFeatures(this._allAskUsingOptionalSyncFeature.bind(this));
services.setting.handleEnableOptionalFeature(this._anyConfigureOptionalSyncFeature.bind(this));
}
}

View File

@@ -25,7 +25,7 @@
export let plugin: ObsidianLiveSyncPlugin;
$: hideNotApplicable = false;
$: thisTerm = plugin.$$getDeviceAndVaultName();
$: thisTerm = plugin.services.setting.getDeviceAndVaultName();
const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync;
if (!addOn) {
@@ -98,7 +98,7 @@
await requestUpdate();
}
async function replicate() {
await plugin.$$replicate(true);
await plugin.services.replication.replicate(true);
}
function selectAllNewest(selectMode: boolean) {
selectNewestPulse++;
@@ -237,7 +237,7 @@
plugin.settings.pluginSyncExtendedSetting[key].files = files;
plugin.settings.pluginSyncExtendedSetting[key].mode = mode;
}
plugin.$$saveSettingData();
plugin.services.setting.saveSettingData();
}
function getIcon(mode: SYNC_MODE) {
if (mode in ICONS) {

View File

@@ -51,12 +51,20 @@ import { LiveSyncCommands } from "../LiveSyncCommands.ts";
import { addPrefix, stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts";
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
import type { LiveSyncCore } from "../../main.ts";
type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce";
declare global {
interface OPTIONAL_SYNC_FEATURES {
FETCH: "FETCH";
OVERWRITE: "OVERWRITE";
MERGE: "MERGE";
DISABLE: "DISABLE";
DISABLE_HIDDEN: "DISABLE_HIDDEN";
}
}
function getComparingMTime(
doc: (MetaEntry | LoadedEntry | false) | UXFileInfo | UXStat | null | undefined,
includeDeleted = false
@@ -72,14 +80,14 @@ function getComparingMTime(
return doc.mtime ?? 0;
}
export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule {
_isThisModuleEnabled() {
export class HiddenFileSync extends LiveSyncCommands {
isThisModuleEnabled() {
return this.plugin.settings.syncInternalFiles;
}
periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(
this.plugin,
async () => this._isThisModuleEnabled() && this._isDatabaseReady() && (await this.scanAllStorageChanges(false))
async () => this.isThisModuleEnabled() && this._isDatabaseReady() && (await this.scanAllStorageChanges(false))
);
get kvDB() {
@@ -132,14 +140,18 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
this.updateSettingCache();
});
}
async $everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
// We cannot initialise autosaveCache because kvDB is not ready yet
// async _everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
// this._fileInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed");
// this._databaseInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed-database");
// this._fileInfoLastKnown = await autosaveCache(this.kvDB, "hidden-file-lastKnown");
// return true;
// }
private async _everyOnDatabaseInitialized(showNotice: boolean) {
this._fileInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed");
this._databaseInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed-database");
this._fileInfoLastKnown = await autosaveCache(this.kvDB, "hidden-file-lastKnown");
return true;
}
async $everyOnDatabaseInitialized(showNotice: boolean) {
if (this._isThisModuleEnabled()) {
if (this.isThisModuleEnabled()) {
if (this._fileInfoLastProcessed.size == 0 && this._fileInfoLastProcessed.size == 0) {
this._log(`No cache found. Performing startup scan.`, LOG_LEVEL_VERBOSE);
await this.performStartupScan(true);
@@ -149,9 +161,9 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
}
return true;
}
async $everyBeforeReplicate(showNotice: boolean) {
async _everyBeforeReplicate(showNotice: boolean) {
if (
this._isThisModuleEnabled() &&
this.isThisModuleEnabled() &&
this._isDatabaseReady() &&
this.settings.syncInternalFilesBeforeReplication &&
!this.settings.watchInternalFileChanges
@@ -161,7 +173,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
return true;
}
$everyOnloadAfterLoadSettings(): Promise<boolean> {
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
this.updateSettingCache();
return Promise.resolve(true);
}
@@ -188,7 +200,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
isReady() {
if (!this._isMainReady) return false;
if (this._isMainSuspended()) return false;
if (!this._isThisModuleEnabled()) return false;
if (!this.isThisModuleEnabled()) return false;
return true;
}
shouldSkipFile = [] as FilePathWithPrefixLC[];
@@ -197,26 +209,26 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
await this.applyOfflineChanges(showNotice);
}
async $everyOnResumeProcess(): Promise<boolean> {
async _everyOnResumeProcess(): Promise<boolean> {
this.periodicInternalFileScanProcessor?.disable();
if (this._isMainSuspended()) return true;
if (this._isThisModuleEnabled()) {
if (this.isThisModuleEnabled()) {
await this.performStartupScan(false);
}
this.periodicInternalFileScanProcessor.enable(
this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval
this.isThisModuleEnabled() && this.settings.syncInternalFilesInterval
? this.settings.syncInternalFilesInterval * 1000
: 0
);
return true;
}
$everyRealizeSettingSyncMode(): Promise<boolean> {
_everyRealizeSettingSyncMode(): Promise<boolean> {
this.periodicInternalFileScanProcessor?.disable();
if (this._isMainSuspended()) return Promise.resolve(true);
if (!this.plugin.$$isReady()) return Promise.resolve(true);
if (!this.services.appLifecycle.isReady()) return Promise.resolve(true);
this.periodicInternalFileScanProcessor.enable(
this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval
this.isThisModuleEnabled() && this.settings.syncInternalFilesInterval
? this.settings.syncInternalFilesInterval * 1000
: 0
);
@@ -227,13 +239,14 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
return Promise.resolve(true);
}
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
async _anyProcessOptionalFileEvent(path: FilePath): Promise<boolean> {
if (this.isReady()) {
return await this.trackStorageFileModification(path);
return (await this.trackStorageFileModification(path)) || false;
}
return false;
}
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
_anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
if (isInternalMetadata(path)) {
this.queueConflictCheck(path);
return Promise.resolve(true);
@@ -241,12 +254,12 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
return Promise.resolve(false);
}
async $anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
async _anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean> {
if (isInternalMetadata(doc._id)) {
if (this._isThisModuleEnabled()) {
if (this.isThisModuleEnabled()) {
//system file
const filename = getPath(doc);
if (await this.plugin.$$isTargetFile(filename)) {
if (await this.services.vault.isTargetFile(filename)) {
// this.procInternalFile(filename);
await this.processReplicationResult(doc);
return true;
@@ -1091,14 +1104,14 @@ Offline Changed files: ${files.length}`;
// If something changes left, notify for reloading Obsidian.
if (updatedFolders.indexOf(this.plugin.app.vault.configDir) >= 0) {
if (!this.plugin.$$isReloadingScheduled()) {
if (!this.services.appLifecycle.isReloadingScheduled()) {
this.plugin.confirm.askInPopup(
`updated-any-hidden`,
`Some setting files have been modified\nPress {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`,
(anchor) => {
anchor.text = "HERE";
anchor.addEventListener("click", () => {
this.plugin.$$scheduleAppReload();
this.services.appLifecycle.scheduleRestart();
});
}
);
@@ -1318,7 +1331,7 @@ Offline Changed files: ${files.length}`;
async storeInternalFileToDatabase(file: InternalFileInfo | UXFileInfo, forceWrite = false) {
const storeFilePath = stripAllPrefixes(file.path as FilePath);
const storageFilePath = file.path;
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
return undefined;
}
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
@@ -1372,7 +1385,7 @@ Offline Changed files: ${files.length}`;
const displayFileName = filenameSrc;
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
const mtime = new Date().getTime();
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
return undefined;
}
return await serialized("file-" + prefixedFileName, async () => {
@@ -1432,7 +1445,7 @@ Offline Changed files: ${files.length}`;
includeDeletion = true
) {
const prefixedFileName = addPrefix(storageFilePath, ICHeader);
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
return undefined;
}
return await serialized("file-" + prefixedFileName, async () => {
@@ -1479,7 +1492,7 @@ Offline Changed files: ${files.length}`;
}
const deleted = metaOnDB.deleted || metaOnDB._deleted || false;
if (deleted) {
const result = await this._deleteFile(storageFilePath);
const result = await this.__deleteFile(storageFilePath);
if (result == "OK") {
this.updateLastProcessedDeletion(storageFilePath, metaOnDB);
return true;
@@ -1493,7 +1506,7 @@ Offline Changed files: ${files.length}`;
if (fileOnDB === false) {
throw new Error(`Failed to read file from database:${storageFilePath}`);
}
const resultStat = await this._writeFile(storageFilePath, fileOnDB, force);
const resultStat = await this.__writeFile(storageFilePath, fileOnDB, force);
if (resultStat) {
this.updateLastProcessed(storageFilePath, metaOnDB, resultStat);
this.queueNotification(storageFilePath);
@@ -1526,7 +1539,7 @@ Offline Changed files: ${files.length}`;
}
}
async _writeFile(storageFilePath: FilePath, fileOnDB: LoadedEntry, force: boolean): Promise<false | UXStat> {
async __writeFile(storageFilePath: FilePath, fileOnDB: LoadedEntry, force: boolean): Promise<false | UXStat> {
try {
const statBefore = await this.plugin.storageAccess.statHidden(storageFilePath);
const isExist = statBefore != null;
@@ -1565,7 +1578,7 @@ Offline Changed files: ${files.length}`;
}
}
async _deleteFile(storageFilePath: FilePath): Promise<false | "OK" | "ALREADY"> {
async __deleteFile(storageFilePath: FilePath): Promise<false | "OK" | "ALREADY"> {
const result = await this.__removeFile(storageFilePath);
if (result === false) {
this._log(`STORAGE <x- DB: ${storageFilePath}: deleting (hidden) Failed`);
@@ -1582,11 +1595,11 @@ Offline Changed files: ${files.length}`;
// <-- Database To Storage Functions
async $allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
await this._askHiddenFileConfiguration(opt);
private async _allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
await this.__askHiddenFileConfiguration(opt);
return true;
}
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
private async __askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
const messageFetch = `${opt.enableFetch ? `> - Fetch: Use the files stored from other devices. Choose this option if you have already configured hidden file synchronization on those devices and wish to accept their files.\n` : ""}`;
const messageOverwrite = `${opt.enableOverwrite ? `> - Overwrite: Use the files from this device. Select this option if you want to overwrite the files stored on other devices.\n` : ""}`;
const messageMerge = `> - Merge: Merge the files from this device with those on other devices. Choose this option if you wish to combine files from multiple sources.
@@ -1632,7 +1645,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
}
}
$allSuspendExtraSync(): Promise<boolean> {
private _allSuspendExtraSync(): Promise<boolean> {
if (this.plugin.settings.syncInternalFiles) {
this._log(
"Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.",
@@ -1644,11 +1657,12 @@ ${messageFetch}${messageOverwrite}${messageMerge}
}
// --> Configuration handling
async $anyConfigureOptionalSyncFeature(mode: "FETCH" | "OVERWRITE" | "MERGE" | "DISABLE" | "DISABLE_HIDDEN") {
private async _anyConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
await this.configureHiddenFileSync(mode);
return true;
}
async configureHiddenFileSync(mode: "FETCH" | "OVERWRITE" | "MERGE" | "DISABLE" | "DISABLE_HIDDEN") {
async configureHiddenFileSync(mode: keyof OPTIONAL_SYNC_FEATURES) {
if (
mode != "FETCH" &&
mode != "OVERWRITE" &&
@@ -1718,7 +1732,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
const result: InternalFileInfo[] = [];
for (const f of files) {
const w = await f;
if (await this.plugin.$$isIgnoredByIgnoreFiles(w.path)) {
if (await this.services.vault.isIgnoredByIgnoreFile(w.path)) {
continue;
}
const mtime = w.stat?.mtime ?? 0;
@@ -1756,7 +1770,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
if (ignoreFilter && ignoreFilter.some((ee) => ee.test(file))) {
continue;
}
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
if (await this.services.vault.isIgnoredByIgnoreFile(file)) continue;
files.push(file);
}
L1: for (const v of w.folders) {
@@ -1768,7 +1782,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
if (ignoreFilter && ignoreFilter.some((e) => e.test(v))) {
continue L1;
}
if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) {
if (await this.services.vault.isIgnoredByIgnoreFile(v)) {
continue L1;
}
files = files.concat(await this.getFiles(v, ignoreList, filter, ignoreFilter));
@@ -1777,4 +1791,20 @@ ${messageFetch}${messageOverwrite}${messageMerge}
}
// <-- Local Storage SubFunctions
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
// No longer needed on initialisation
// services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
services.fileProcessing.handleOptionalFileEvent(this._anyProcessOptionalFileEvent.bind(this));
services.conflict.handleGetOptionalConflictCheckMethod(this._anyGetOptionalConflictCheckMethod.bind(this));
services.replication.handleProcessOptionalSynchroniseResult(this._anyProcessOptionalSyncFiles.bind(this));
services.setting.handleOnRealiseSetting(this._everyRealizeSettingSyncMode.bind(this));
services.appLifecycle.handleOnResuming(this._everyOnResumeProcess.bind(this));
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
services.databaseEvents.handleDatabaseInitialised(this._everyOnDatabaseInitialized.bind(this));
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
services.setting.handleSuggestOptionalFeatures(this._allAskUsingOptionalSyncFeature.bind(this));
services.setting.handleEnableOptionalFeature(this._anyConfigureOptionalSyncFeature.bind(this));
}
}

View File

@@ -5,13 +5,14 @@ import {
LOG_LEVEL_NOTICE,
type AnyEntry,
type DocumentID,
type EntryHasPath,
type FilePath,
type FilePathWithPrefix,
type LOG_LEVEL,
} from "../lib/src/common/types.ts";
import type ObsidianLiveSyncPlugin from "../main.ts";
import { MARK_DONE } from "../modules/features/ModuleLog.ts";
import type { LiveSyncCore } from "../main.ts";
import { __$checkInstanceBinding } from "../lib/src/dev/checks.ts";
let noticeIndex = 0;
export abstract class LiveSyncCommands {
@@ -25,12 +26,15 @@ export abstract class LiveSyncCommands {
get localDatabase() {
return this.plugin.localDatabase;
}
id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
return this.plugin.$$id2path(id, entry, stripPrefix);
get services() {
return this.plugin.services;
}
// id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
// return this.plugin.$$id2path(id, entry, stripPrefix);
// }
async path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
return await this.plugin.$$path2id(filename, prefix);
return await this.services.path.path2id(filename, prefix);
}
getPath(entry: AnyEntry): FilePathWithPrefix {
return getPath(entry);
@@ -38,18 +42,20 @@ export abstract class LiveSyncCommands {
constructor(plugin: ObsidianLiveSyncPlugin) {
this.plugin = plugin;
this.onBindFunction(plugin, plugin.services);
__$checkInstanceBinding(this);
}
abstract onunload(): void;
abstract onload(): void | Promise<void>;
_isMainReady() {
return this.plugin.$$isReady();
return this.plugin.services.appLifecycle.isReady();
}
_isMainSuspended() {
return this.plugin.$$isSuspended();
return this.services.appLifecycle.isSuspended();
}
_isDatabaseReady() {
return this.plugin.$$isDatabaseReady();
return this.services.database.isDatabaseReady();
}
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
@@ -89,4 +95,8 @@ export abstract class LiveSyncCommands {
_debug = (msg: any, key?: string) => {
this._log(msg, LOG_LEVEL_VERBOSE, key);
};
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
// Override if needed.
}
}

View File

@@ -10,7 +10,6 @@ import {
type MetaEntry,
} from "../../lib/src/common/types";
import { getNoFromRev } from "../../lib/src/pouchdb/LiveSyncLocalDB";
import type { IObsidianModule } from "../../modules/AbstractObsidianModule";
import { LiveSyncCommands } from "../LiveSyncCommands";
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
import { arrayToChunkedArray } from "octagonal-wheels/collection";
@@ -22,10 +21,7 @@ type NoteDocumentID = DocumentID;
type Rev = string;
type ChunkUsageMap = Map<NoteDocumentID, Map<Rev, Set<ChunkID>>>;
export class LocalDatabaseMaintenance extends LiveSyncCommands implements IObsidianModule {
$everyOnload(): Promise<boolean> {
return Promise.resolve(true);
}
export class LocalDatabaseMaintenance extends LiveSyncCommands {
onunload(): void {
// NO OP.
}

View File

@@ -1,21 +1,27 @@
import type { IObsidianModule } from "../../modules/AbstractObsidianModule";
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts";
import {
AutoAccepting,
LOG_LEVEL_NOTICE,
P2P_DEFAULT_SETTINGS,
REMOTE_P2P,
type EntryDoc,
type P2PSyncSetting,
type RemoteDBSettings,
} from "../../lib/src/common/types.ts";
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts";
import {
LiveSyncTrysteroReplicator,
setReplicatorFunc,
} from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts";
import { EVENT_REQUEST_OPEN_P2P, eventHub } from "../../common/events.ts";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
import { Logger } from "octagonal-wheels/common/logger";
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
import type { CommandShim } from "../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts";
import {
P2PReplicatorMixIn,
addP2PEventHandlers,
closeP2PReplicator,
openP2PReplicator,
P2PLogCollector,
removeP2PReplicatorInstance,
type P2PReplicatorBase,
} from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
@@ -24,8 +30,10 @@ import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
import type ObsidianLiveSyncPlugin from "../../main.ts";
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts";
import type { LiveSyncCore } from "../../main.ts";
import { TrysteroReplicator } from "../../lib/src/replication/trystero/TrysteroReplicator.ts";
class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicatorBase {
export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase, CommandShim {
storeP2PStatusLine = reactiveSource("");
getSettings(): P2PSyncSetting {
@@ -49,47 +57,127 @@ class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicator
constructor(plugin: ObsidianLiveSyncPlugin) {
super(plugin);
setReplicatorFunc(() => this._replicatorInstance);
addP2PEventHandlers(this);
this.afterConstructor();
// onBindFunction is called in super class
// this.onBindFunction(plugin, plugin.services);
}
async handleReplicatedDocuments(docs: EntryDoc[]): Promise<void> {
// console.log("Processing Replicated Docs", docs);
return await this.plugin.$$parseReplicationResult(docs as PouchDB.Core.ExistingDocument<EntryDoc>[]);
}
onunload(): void {
throw new Error("Method not implemented.");
}
onload(): void | Promise<void> {
throw new Error("Method not implemented.");
return await this.services.replication.parseSynchroniseResult(
docs as PouchDB.Core.ExistingDocument<EntryDoc>[]
);
}
init() {
this._simpleStore = this.plugin.$$getSimpleStore("p2p-sync");
return Promise.resolve(this);
}
}
export class P2PReplicator
extends P2PReplicatorMixIn(P2PReplicatorCommandBase)
implements IObsidianModule, CommandShim
{
storeP2PStatusLine = reactiveSource("");
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
const settings = { ...this.settings, ...settingOverride };
if (settings.remoteType == REMOTE_P2P) {
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin));
}
return undefined!;
}
override getPlatform(): string {
_replicatorInstance?: TrysteroReplicator;
p2pLogCollector = new P2PLogCollector();
afterConstructor() {
return;
}
async open() {
await openP2PReplicator(this);
}
async close() {
await closeP2PReplicator(this);
}
getConfig(key: string) {
const vaultName = this.services.vault.getVaultName();
const dbKey = `${vaultName}-${key}`;
return localStorage.getItem(dbKey);
}
setConfig(key: string, value: string) {
const vaultName = this.services.vault.getVaultName();
const dbKey = `${vaultName}-${key}`;
localStorage.setItem(dbKey, value);
}
enableBroadcastCastings() {
return this?._replicatorInstance?.enableBroadcastChanges();
}
disableBroadcastCastings() {
return this?._replicatorInstance?.disableBroadcastChanges();
}
init() {
this._simpleStore = this.services.database.openSimpleStore("p2p-sync");
return Promise.resolve(this);
}
async initialiseP2PReplicator(): Promise<TrysteroReplicator> {
await this.init();
try {
if (this._replicatorInstance) {
await this._replicatorInstance.close();
this._replicatorInstance = undefined;
}
if (!this.settings.P2P_AppID) {
this.settings.P2P_AppID = P2P_DEFAULT_SETTINGS.P2P_AppID;
}
const getInitialDeviceName = () => this.getConfig("p2p_device_name") || this.services.vault.getVaultName();
const getSettings = () => this.settings;
const store = () => this.simpleStore();
const getDB = () => this.getDB();
const getConfirm = () => this.confirm;
const getPlatform = () => this.getPlatform();
const env = {
get db() {
return getDB();
},
get confirm() {
return getConfirm();
},
get deviceName() {
return getInitialDeviceName();
},
get platform() {
return getPlatform();
},
get settings() {
return getSettings();
},
processReplicatedDocs: async (docs: EntryDoc[]): Promise<void> => {
await this.handleReplicatedDocuments(docs);
// No op. This is a client and does not need to process the docs
},
get simpleStore() {
return store();
},
};
this._replicatorInstance = new TrysteroReplicator(env);
return this._replicatorInstance;
} catch (e) {
this._log(
e instanceof Error ? e.message : "Something occurred on Initialising P2P Replicator",
LOG_LEVEL_INFO
);
this._log(e, LOG_LEVEL_VERBOSE);
throw e;
}
}
getPlatform(): string {
return getPlatformName();
}
override onunload(): void {
onunload(): void {
removeP2PReplicatorInstance();
void this.close();
}
override onload(): void | Promise<void> {
onload(): void | Promise<void> {
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => {
void this.openPane();
});
@@ -97,12 +185,12 @@ export class P2PReplicator
this.storeP2PStatusLine.value = line.value;
});
}
async $everyOnInitializeDatabase(): Promise<boolean> {
async _everyOnInitializeDatabase(): Promise<boolean> {
await this.initialiseP2PReplicator();
return Promise.resolve(true);
}
async $allSuspendExtraSync() {
private async _allSuspendExtraSync() {
this.plugin.settings.P2P_Enabled = false;
this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE;
this.plugin.settings.P2P_AutoBroadcast = false;
@@ -112,15 +200,15 @@ export class P2PReplicator
return await Promise.resolve(true);
}
async $everyOnLoadStart() {
return await Promise.resolve();
}
// async $everyOnLoadStart() {
// return await Promise.resolve();
// }
async openPane() {
await this.plugin.$$showView(VIEW_TYPE_P2P);
await this.services.API.showWindow(VIEW_TYPE_P2P);
}
async $everyOnloadStart(): Promise<boolean> {
async _everyOnloadStart(): Promise<boolean> {
this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, this.plugin));
this.plugin.addCommand({
id: "open-p2p-replicator",
@@ -170,7 +258,7 @@ export class P2PReplicator
return await Promise.resolve(true);
}
$everyAfterResumeProcess(): Promise<boolean> {
_everyAfterResumeProcess(): Promise<boolean> {
if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) {
setTimeout(() => void this.open(), 100);
}
@@ -178,9 +266,18 @@ export class P2PReplicator
rep?.allowReconnection();
return Promise.resolve(true);
}
$everyBeforeSuspendProcess(): Promise<boolean> {
_everyBeforeSuspendProcess(): Promise<boolean> {
const rep = this._replicatorInstance;
rep?.disconnectFromServer();
return Promise.resolve(true);
}
override onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
services.appLifecycle.handleOnSuspending(this._everyBeforeSuspendProcess.bind(this));
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
}
}

View File

@@ -32,10 +32,10 @@
const initialSettings = { ...plugin.settings };
let settings = $state<P2PSyncSetting>(initialSettings);
// const vaultName = plugin.$$getVaultName();
// const vaultName = service.vault.getVaultName();
// const dbKey = `${vaultName}-p2p-device-name`;
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.$$getVaultName();
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.services.vault.getVaultName();
let deviceName = $state<string>(initialDeviceName);
let eP2PEnabled = $state<boolean>(initialSettings.P2P_Enabled);

View File

@@ -95,7 +95,7 @@ And you can also drop the local database to rebuild from the remote device.`,
if (yn === DROP) {
await this.plugin.rebuilder.scheduleFetch();
} else {
await this.plugin.$$scheduleAppReload();
this.plugin.services.appLifecycle.scheduleRestart();
}
} else {
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);

Submodule src/lib updated: 21ca077163...422194fb1b

View File

@@ -1,26 +1,10 @@
import { Plugin } from "./deps";
import {
type EntryDoc,
type LoadedEntry,
type ObsidianLiveSyncSettings,
type LOG_LEVEL,
type diff_result,
type DatabaseConnectingStatus,
type EntryHasPath,
type DocumentID,
type FilePathWithPrefix,
type FilePath,
LOG_LEVEL_INFO,
type HasSettings,
type MetaEntry,
type UXFileInfoStub,
type MISSING_OR_ERROR,
type AUTO_MERGED,
type RemoteDBSettings,
type TweakValues,
type CouchDBCredentials,
} from "./lib/src/common/types.ts";
import { type FileEventItem } from "./common/types.ts";
import { type SimpleStore } from "./lib/src/common/utils.ts";
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts";
import {
@@ -35,7 +19,6 @@ import { reactiveSource, type ReactiveValue } from "octagonal-wheels/dataobject/
import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js";
import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js";
import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js";
import { ObsHttpHandler } from "./modules/essentialObsidian/APILib/ObsHttpHandler.js";
import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts";
import { ModuleDev } from "./modules/extras/ModuleDev.ts";
@@ -59,8 +42,7 @@ import { ModuleDatabaseFileAccess } from "./modules/core/ModuleDatabaseFileAcces
import { ModuleFileHandler } from "./modules/core/ModuleFileHandler.ts";
import { ModuleObsidianAPI } from "./modules/essentialObsidian/ModuleObsidianAPI.ts";
import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidianEvents.ts";
import { injectModules, type AbstractModule } from "./modules/AbstractModule.ts";
import type { ICoreModule } from "./modules/ModuleTypes.ts";
import { type AbstractModule } from "./modules/AbstractModule.ts";
import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidianSettingTab.ts";
import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts";
import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts";
@@ -85,13 +67,16 @@ import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleE
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts";
import type { LiveSyncManagers } from "./lib/src/managers/LiveSyncManagers.ts";
import { ObsidianServiceHub } from "./modules/services/ObsidianServices.ts";
import type { InjectableServiceHub } from "./lib/src/services/InjectableServices.ts";
// function throwShouldBeOverridden(): never {
// throw new Error("This function should be overridden by the module.");
// }
// const InterceptiveAll = Promise.resolve(true);
// const InterceptiveEvery = Promise.resolve(true);
// const InterceptiveAny = Promise.resolve(undefined);
function throwShouldBeOverridden(): never {
throw new Error("This function should be overridden by the module.");
}
const InterceptiveAll = Promise.resolve(true);
const InterceptiveEvery = Promise.resolve(true);
const InterceptiveAny = Promise.resolve(undefined);
/**
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
* Please refer to the module's source code to understand the function.
@@ -101,6 +86,13 @@ const InterceptiveAny = Promise.resolve(undefined);
* $any : Process all modules until the first success.
* $ : Other interceptive points. You should manually assign the module
* All of above performed on injectModules function.
*
* No longer used! See AppLifecycleService in Services.ts.
* For a while, just commented out some previously used code. (sorry, some are deleted...)
* 'Convention over configuration' was a lie for me. At least, very lack of refactor-ability.
*
* Still some modules are separated, and connected by `ThroughHole` class.
* However, it is not a good design. I am going to manage the modules in a more explicit way.
*/
export default class ObsidianLiveSyncPlugin
@@ -112,6 +104,18 @@ export default class ObsidianLiveSyncPlugin
LiveSyncCouchDBReplicatorEnv,
HasSettings<ObsidianLiveSyncSettings>
{
/**
* The service hub for managing all services.
*/
_services: InjectableServiceHub = new ObsidianServiceHub();
get services() {
return this._services;
}
/**
* Bind functions to the service hub (for migration purpose).
*/
// bindFunctions = (this.serviceHub as ObsidianServiceHub).bindFunctions.bind(this.serviceHub);
// --> Module System
getAddOn<T extends LiveSyncCommands>(cls: string) {
for (const addon of this.addOns) {
@@ -173,41 +177,9 @@ export default class ObsidianLiveSyncPlugin
new ModuleReplicateTest(this, this),
new ModuleIntegratedTest(this, this),
] as (IObsidianModule | AbstractModule)[];
injected = injectModules(this, [...this.modules, ...this.addOns] as ICoreModule[]);
// injected = injectModules(this, [...this.modules, ...this.addOns] as ICoreModule[]);
// <-- Module System
$$isSuspended(): boolean {
throwShouldBeOverridden();
}
$$setSuspended(value: boolean): void {
throwShouldBeOverridden();
}
$$isDatabaseReady(): boolean {
throwShouldBeOverridden();
}
$$getDeviceAndVaultName(): string {
throwShouldBeOverridden();
}
$$setDeviceAndVaultName(name: string): void {
throwShouldBeOverridden();
}
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
throwShouldBeOverridden();
}
$$isReady(): boolean {
throwShouldBeOverridden();
}
$$markIsReady(): void {
throwShouldBeOverridden();
}
$$resetIsReady(): void {
throwShouldBeOverridden();
}
// Following are plugged by the modules.
settings!: ObsidianLiveSyncSettings;
@@ -229,30 +201,6 @@ export default class ObsidianLiveSyncPlugin
return this.settings;
}
$$markFileListPossiblyChanged(): void {
throwShouldBeOverridden();
}
$$customFetchHandler(): ObsHttpHandler {
throwShouldBeOverridden();
}
$$getLastPostFailedBySize(): boolean {
throwShouldBeOverridden();
}
$$isStorageInsensitive(): boolean {
throwShouldBeOverridden();
}
$$shouldCheckCaseInsensitive(): boolean {
throwShouldBeOverridden();
}
$$isUnloaded(): boolean {
throwShouldBeOverridden();
}
requestCount = reactiveSource(0);
responseCount = reactiveSource(0);
totalQueued = reactiveSource(0);
@@ -277,101 +225,6 @@ export default class ObsidianLiveSyncPlugin
syncStatus: "CLOSED" as DatabaseConnectingStatus,
});
$$isReloadingScheduled(): boolean {
throwShouldBeOverridden();
}
$$getReplicator(): LiveSyncAbstractReplicator {
throwShouldBeOverridden();
}
$$connectRemoteCouchDB(
uri: string,
auth: CouchDBCredentials,
disableRequestURI: boolean,
passphrase: string | false,
useDynamicIterationCount: boolean,
performSetup: boolean,
skipInfo: boolean,
compression: boolean,
customHeaders: Record<string, string>,
useRequestAPI: boolean,
getPBKDF2Salt: () => Promise<Uint8Array>
): Promise<
| string
| {
db: PouchDB.Database<EntryDoc>;
info: PouchDB.Core.DatabaseInfo;
}
> {
throwShouldBeOverridden();
}
$$isMobile(): boolean {
throwShouldBeOverridden();
}
$$vaultName(): string {
throwShouldBeOverridden();
}
// --> Path
$$getActiveFilePath(): FilePathWithPrefix | undefined {
throwShouldBeOverridden();
}
// <-- Path
// --> Path conversion
$$id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
throwShouldBeOverridden();
}
$$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
throwShouldBeOverridden();
}
// <!-- Path conversion
// --> Database
$$createPouchDBInstance<T extends object>(
name?: string,
options?: PouchDB.Configuration.DatabaseConfiguration
): PouchDB.Database<T> {
throwShouldBeOverridden();
}
$allOnDBUnload(db: LiveSyncLocalDB): void {
return;
}
$allOnDBClose(db: LiveSyncLocalDB): void {
return;
}
// <!-- Database
$anyNewReplicator(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<LiveSyncAbstractReplicator> {
throwShouldBeOverridden();
}
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
return InterceptiveEvery;
}
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
return InterceptiveEvery;
}
// end interfaces
$$getVaultName(): string {
throwShouldBeOverridden();
}
$$getSimpleStore<T>(kind: string): SimpleStore<T> {
throwShouldBeOverridden();
}
// trench!: Trench;
// --> Events
/*
@@ -412,331 +265,404 @@ export default class ObsidianLiveSyncPlugin
*/
$everyOnLayoutReady(): Promise<boolean> {
return InterceptiveEvery;
}
$everyOnFirstInitialize(): Promise<boolean> {
return InterceptiveEvery;
}
// $everyOnLayoutReady(): Promise<boolean> {
// //TODO: AppLifecycleService.onLayoutReady
// return InterceptiveEvery;
// }
// $everyOnFirstInitialize(): Promise<boolean> {
// //TODO: AppLifecycleService.onFirstInitialize
// return InterceptiveEvery;
// }
// Some Module should call this function to start the plugin.
$$onLiveSyncReady(): Promise<false | undefined> {
throwShouldBeOverridden();
}
$$wireUpEvents(): void {
throwShouldBeOverridden();
}
$$onLiveSyncLoad(): Promise<void> {
throwShouldBeOverridden();
}
// $$onLiveSyncReady(): Promise<false | undefined> {
// //TODO: AppLifecycleService.onLiveSyncReady
// throwShouldBeOverridden();
// }
// $$wireUpEvents(): void {
// //TODO: AppLifecycleService.wireUpEvents
// throwShouldBeOverridden();
// }
// $$onLiveSyncLoad(): Promise<void> {
// //TODO: AppLifecycleService.onLoad
// throwShouldBeOverridden();
// }
$$onLiveSyncUnload(): Promise<void> {
throwShouldBeOverridden();
}
// $$onLiveSyncUnload(): Promise<void> {
// //TODO: AppLifecycleService.onAppUnload
// throwShouldBeOverridden();
// }
$allScanStat(): Promise<boolean> {
return InterceptiveAll;
}
$everyOnloadStart(): Promise<boolean> {
return InterceptiveEvery;
}
// $allScanStat(): Promise<boolean> {
// //TODO: AppLifecycleService.scanStartupIssues
// return InterceptiveAll;
// }
// $everyOnloadStart(): Promise<boolean> {
// //TODO: AppLifecycleService.onInitialise
// return InterceptiveEvery;
// }
$everyOnloadAfterLoadSettings(): Promise<boolean> {
return InterceptiveEvery;
}
// $everyOnloadAfterLoadSettings(): Promise<boolean> {
// //TODO: AppLifecycleService.onApplyStartupLoaded
// return InterceptiveEvery;
// }
$everyOnload(): Promise<boolean> {
return InterceptiveEvery;
}
// $everyOnload(): Promise<boolean> {
// //TODO: AppLifecycleService.onLoaded
// return InterceptiveEvery;
// }
$anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
return InterceptiveAny;
}
// $anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
// //TODO: FileProcessingService.processFileEvent
// return InterceptiveAny;
// }
$allStartOnUnload(): Promise<boolean> {
return InterceptiveAll;
}
$allOnUnload(): Promise<boolean> {
return InterceptiveAll;
}
// $allStartOnUnload(): Promise<boolean> {
// //TODO: AppLifecycleService.onBeforeUnload
// return InterceptiveAll;
// }
// $allOnUnload(): Promise<boolean> {
// //TODO: AppLifecycleService.onUnload
// return InterceptiveAll;
// }
$$openDatabase(): Promise<boolean> {
throwShouldBeOverridden();
}
// $$openDatabase(): Promise<boolean> {
// // DatabaseService.openDatabase
// throwShouldBeOverridden();
// }
$$realizeSettingSyncMode(): Promise<void> {
throwShouldBeOverridden();
}
$$performRestart() {
throwShouldBeOverridden();
}
// $$realizeSettingSyncMode(): Promise<void> {
// // SettingService.realiseSetting
// throwShouldBeOverridden();
// }
// $$performRestart() {
// // AppLifecycleService.performRestart
// throwShouldBeOverridden();
// }
$$clearUsedPassphrase(): void {
throwShouldBeOverridden();
}
// $$clearUsedPassphrase(): void {
// // SettingService.clearUsedPassphrase
// throwShouldBeOverridden();
// }
$$decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
throwShouldBeOverridden();
}
$$adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
throwShouldBeOverridden();
}
// $$decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
// // SettingService.decryptSettings
// throwShouldBeOverridden();
// }
// $$adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
// // SettingService.adjustSettings
// throwShouldBeOverridden();
// }
$$loadSettings(): Promise<void> {
throwShouldBeOverridden();
}
// $$loadSettings(): Promise<void> {
// // SettingService.loadSettings
// throwShouldBeOverridden();
// }
$$saveDeviceAndVaultName(): void {
throwShouldBeOverridden();
}
// $$saveDeviceAndVaultName(): void {
// // SettingService.saveDeviceAndVaultName
// throwShouldBeOverridden();
// }
$$saveSettingData(): Promise<void> {
throwShouldBeOverridden();
}
// $$saveSettingData(): Promise<void> {
// // SettingService.saveSettingData
// throwShouldBeOverridden();
// }
$anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
return InterceptiveAny;
}
// $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
// // FileProcessingService.processOptionalFileEvent
// return InterceptiveAny;
// }
$everyCommitPendingFileEvent(): Promise<boolean> {
return InterceptiveEvery;
}
// $everyCommitPendingFileEvent(): Promise<boolean> {
// // FileProcessingService.commitPendingFileEvent
// return InterceptiveEvery;
// }
// ->
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | undefined | "newer"> {
return InterceptiveAny;
}
// $anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | undefined | "newer"> {
// return InterceptiveAny;
// }
$$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
throwShouldBeOverridden();
}
// $$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
// // ConflictEventManager.queueCheckForConflictIfOpen
// throwShouldBeOverridden();
// }
$$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
throwShouldBeOverridden();
}
// $$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
// // ConflictEventManager.queueCheckForConflict
// throwShouldBeOverridden();
// }
$$waitForAllConflictProcessed(): Promise<boolean> {
throwShouldBeOverridden();
}
// $$waitForAllConflictProcessed(): Promise<boolean> {
// // ConflictEventManager.ensureAllConflictProcessed
// throwShouldBeOverridden();
// }
//<-- Conflict Check
$anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
return InterceptiveAny;
}
// $anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
// // ReplicationService.processOptionalSyncFile
// return InterceptiveAny;
// }
$anyProcessReplicatedDoc(doc: MetaEntry): Promise<boolean | undefined> {
return InterceptiveAny;
}
// $anyProcessReplicatedDoc(doc: MetaEntry): Promise<boolean | undefined> {
// // ReplicationService.processReplicatedDocument
// return InterceptiveAny;
// }
//---> Sync
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
throwShouldBeOverridden();
}
// $$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
// // ReplicationService.parseSynchroniseResult
// throwShouldBeOverridden();
// }
$anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>): Promise<boolean | undefined> {
return InterceptiveAny;
}
$everyBeforeRealizeSetting(): Promise<boolean> {
return InterceptiveEvery;
}
$everyAfterRealizeSetting(): Promise<boolean> {
return InterceptiveEvery;
}
$everyRealizeSettingSyncMode(): Promise<boolean> {
return InterceptiveEvery;
}
// $anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>): Promise<boolean | undefined> {
// // ReplicationService.processVirtualDocument
// return InterceptiveAny;
// }
// $everyBeforeRealizeSetting(): Promise<boolean> {
// // SettingEventManager.beforeRealiseSetting
// return InterceptiveEvery;
// }
// $everyAfterRealizeSetting(): Promise<boolean> {
// // SettingEventManager.onSettingRealised
// return InterceptiveEvery;
// }
// $everyRealizeSettingSyncMode(): Promise<boolean> {
// // SettingEventManager.onRealiseSetting
// return InterceptiveEvery;
// }
$everyBeforeSuspendProcess(): Promise<boolean> {
return InterceptiveEvery;
}
$everyOnResumeProcess(): Promise<boolean> {
return InterceptiveEvery;
}
$everyAfterResumeProcess(): Promise<boolean> {
return InterceptiveEvery;
}
// $everyBeforeSuspendProcess(): Promise<boolean> {
// // AppLifecycleService.onSuspending
// return InterceptiveEvery;
// }
// $everyOnResumeProcess(): Promise<boolean> {
// // AppLifecycleService.onResuming
// return InterceptiveEvery;
// }
// $everyAfterResumeProcess(): Promise<boolean> {
// // AppLifecycleService.onResumed
// return InterceptiveEvery;
// }
$$fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
throwShouldBeOverridden();
}
$$checkAndAskResolvingMismatchedTweaks(preferred: Partial<TweakValues>): Promise<[TweakValues | boolean, boolean]> {
throwShouldBeOverridden();
}
$$askResolvingMismatchedTweaks(preferredSource: TweakValues): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
throwShouldBeOverridden();
}
// $$fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
// //TODO:TweakValueService.fetchRemotePreferred
// throwShouldBeOverridden();
// }
// $$checkAndAskResolvingMismatchedTweaks(preferred: Partial<TweakValues>): Promise<[TweakValues | boolean, boolean]> {
// //TODO:TweakValueService.checkAndAskResolvingMismatched
// throwShouldBeOverridden();
// }
// $$askResolvingMismatchedTweaks(preferredSource: TweakValues): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
// //TODO:TweakValueService.askResolvingMismatched
// throwShouldBeOverridden();
// }
$$checkAndAskUseRemoteConfiguration(
settings: RemoteDBSettings
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
throwShouldBeOverridden();
}
// $$checkAndAskUseRemoteConfiguration(
// settings: RemoteDBSettings
// ): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
// // TweakValueService.checkAndAskUseRemoteConfiguration
// throwShouldBeOverridden();
// }
$$askUseRemoteConfiguration(
trialSetting: RemoteDBSettings,
preferred: TweakValues
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
throwShouldBeOverridden();
}
$everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
return InterceptiveEvery;
}
// $$askUseRemoteConfiguration(
// trialSetting: RemoteDBSettings,
// preferred: TweakValues
// ): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
// // TweakValueService.askUseRemoteConfiguration
// throwShouldBeOverridden();
// }
// $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
// // ReplicationService.beforeReplicate
// return InterceptiveEvery;
// }
$$canReplicate(showMessage: boolean = false): Promise<boolean> {
throwShouldBeOverridden();
}
// $$canReplicate(showMessage: boolean = false): Promise<boolean> {
// // ReplicationService.isReplicationReady
// throwShouldBeOverridden();
// }
$$replicate(showMessage: boolean = false): Promise<boolean | void> {
throwShouldBeOverridden();
}
$$replicateByEvent(showMessage: boolean = false): Promise<boolean | void> {
throwShouldBeOverridden();
}
// $$replicate(showMessage: boolean = false): Promise<boolean | void> {
// // ReplicationService.replicate
// throwShouldBeOverridden();
// }
// $$replicateByEvent(showMessage: boolean = false): Promise<boolean | void> {
// // ReplicationService.replicateByEvent
// throwShouldBeOverridden();
// }
$everyOnDatabaseInitialized(showingNotice: boolean): Promise<boolean> {
throwShouldBeOverridden();
}
// $everyOnDatabaseInitialized(showingNotice: boolean): Promise<boolean> {
// // DatabaseEventService.onDatabaseInitialised
// throwShouldBeOverridden();
// }
$$initializeDatabase(
showingNotice: boolean = false,
reopenDatabase = true,
ignoreSuspending: boolean = false
): Promise<boolean> {
throwShouldBeOverridden();
}
// $$initializeDatabase(
// showingNotice: boolean = false,
// reopenDatabase = true,
// ignoreSuspending: boolean = false
// ): Promise<boolean> {
// // DatabaseEventService.initializeDatabase
// throwShouldBeOverridden();
// }
$anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
return InterceptiveAny;
}
// $anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
// // ReplicationService.checkConnectionFailure
// return InterceptiveAny;
// }
$$replicateAllToServer(
showingNotice: boolean = false,
sendChunksInBulkDisabled: boolean = false
): Promise<boolean> {
throwShouldBeOverridden();
}
$$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
throwShouldBeOverridden();
}
// $$replicateAllToServer(
// showingNotice: boolean = false,
// sendChunksInBulkDisabled: boolean = false
// ): Promise<boolean> {
// // RemoteService.replicateAllToRemote
// throwShouldBeOverridden();
// }
// $$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
// // RemoteService.replicateAllFromRemote
// throwShouldBeOverridden();
// }
// Remote Governing
$$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
throwShouldBeOverridden();
}
// $$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
// // RemoteService.markLocked;
// throwShouldBeOverridden();
// }
$$markRemoteUnlocked(): Promise<void> {
throwShouldBeOverridden();
}
// $$markRemoteUnlocked(): Promise<void> {
// // RemoteService.markUnlocked;
// throwShouldBeOverridden();
// }
$$markRemoteResolved(): Promise<void> {
throwShouldBeOverridden();
}
// $$markRemoteResolved(): Promise<void> {
// // RemoteService.markResolved;
// throwShouldBeOverridden();
// }
// <-- Remote Governing
$$isFileSizeExceeded(size: number): boolean {
throwShouldBeOverridden();
}
// $$isFileSizeExceeded(size: number): boolean {
// // VaultService.isFileSizeTooLarge
// throwShouldBeOverridden();
// }
$$performFullScan(showingNotice?: boolean, ignoreSuspending?: boolean): Promise<void> {
throwShouldBeOverridden();
}
// $$performFullScan(showingNotice?: boolean, ignoreSuspending?: boolean): Promise<void> {
// // VaultService.scanVault
// throwShouldBeOverridden();
// }
$anyResolveConflictByUI(
filename: FilePathWithPrefix,
conflictCheckResult: diff_result
): Promise<boolean | undefined> {
return InterceptiveAny;
}
$$resolveConflictByDeletingRev(
path: FilePathWithPrefix,
deleteRevision: string,
subTitle = ""
): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
throwShouldBeOverridden();
}
$$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
throwShouldBeOverridden();
}
$anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
throwShouldBeOverridden();
}
// $anyResolveConflictByUI(
// filename: FilePathWithPrefix,
// conflictCheckResult: diff_result
// ): Promise<boolean | undefined> {
// // ConflictService.resolveConflictByUserInteraction
// return InterceptiveAny;
// }
// $$resolveConflictByDeletingRev(
// path: FilePathWithPrefix,
// deleteRevision: string,
// subTitle = ""
// ): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
// // ConflictService.resolveByDeletingRevision
// throwShouldBeOverridden();
// }
// $$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
// // ConflictService.resolveConflict
// throwShouldBeOverridden();
// }
// $anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
// // ConflictService.resolveByNewest
// throwShouldBeOverridden();
// }
$$resetLocalDatabase(): Promise<void> {
throwShouldBeOverridden();
}
// $$resetLocalDatabase(): Promise<void> {
// // DatabaseService.resetDatabase;
// throwShouldBeOverridden();
// }
$$tryResetRemoteDatabase(): Promise<void> {
throwShouldBeOverridden();
}
// $$tryResetRemoteDatabase(): Promise<void> {
// // RemoteService.tryResetDatabase;
// throwShouldBeOverridden();
// }
$$tryCreateRemoteDatabase(): Promise<void> {
throwShouldBeOverridden();
}
// $$tryCreateRemoteDatabase(): Promise<void> {
// // RemoteService.tryCreateDatabase;
// throwShouldBeOverridden();
// }
$$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
throwShouldBeOverridden();
}
// $$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
// // VaultService.isIgnoredByIgnoreFiles
// throwShouldBeOverridden();
// }
$$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false): Promise<boolean> {
throwShouldBeOverridden();
}
// $$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false): Promise<boolean> {
// // VaultService.isTargetFile
// throwShouldBeOverridden();
// }
$$askReload(message?: string) {
throwShouldBeOverridden();
}
$$scheduleAppReload() {
throwShouldBeOverridden();
}
// $$askReload(message?: string) {
// // AppLifecycleService.askRestart
// throwShouldBeOverridden();
// }
// $$scheduleAppReload() {
// // AppLifecycleService.scheduleRestart
// throwShouldBeOverridden();
// }
//--- Setup
$allSuspendAllSync(): Promise<boolean> {
return InterceptiveAll;
}
$allSuspendExtraSync(): Promise<boolean> {
return InterceptiveAll;
}
// $allSuspendAllSync(): Promise<boolean> {
// // SettingEventManager.suspendAllSync
// return InterceptiveAll;
// }
// $allSuspendExtraSync(): Promise<boolean> {
// // SettingEventManager.suspendExtraSync
// return InterceptiveAll;
// }
$allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
throwShouldBeOverridden();
}
$anyConfigureOptionalSyncFeature(mode: string): Promise<void> {
throwShouldBeOverridden();
}
// $allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
// // SettingEventManager.suggestOptionalFeatures
// throwShouldBeOverridden();
// }
// $anyConfigureOptionalSyncFeature(mode: string): Promise<void> {
// // SettingEventManager.enableOptionalFeature
// throwShouldBeOverridden();
// }
$$showView(viewType: string): Promise<void> {
throwShouldBeOverridden();
}
// $$showView(viewType: string): Promise<void> {
// // UIManager.showWindow //
// throwShouldBeOverridden();
// }
// For Development: Ensure reliability MORE AND MORE. May the this plug-in helps all of us.
$everyModuleTest(): Promise<boolean> {
return InterceptiveEvery;
}
$everyModuleTestMultiDevice(): Promise<boolean> {
return InterceptiveEvery;
}
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
throwShouldBeOverridden();
}
// $everyModuleTest(): Promise<boolean> {
// return InterceptiveEvery;
// }
// $everyModuleTestMultiDevice(): Promise<boolean> {
// return InterceptiveEvery;
// }
// $$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
// throwShouldBeOverridden();
// }
_isThisModuleEnabled(): boolean {
return true;
}
// _isThisModuleEnabled(): boolean {
// return true;
// }
$anyGetAppId(): Promise<string | undefined> {
return InterceptiveAny;
}
// $anyGetAppId(): Promise<string | undefined> {
// // APIService.getAppId
// return InterceptiveAny;
// }
// Plug-in's overrideable functions
onload() {
void this.$$onLiveSyncLoad();
void this.services.appLifecycle.onLoad();
}
async saveSettings() {
await this.$$saveSettingData();
await this.services.setting.saveSettingData();
}
onunload() {
return void this.$$onLiveSyncUnload();
return void this.services.appLifecycle.onAppUnload();
}
// <-- Plug-in's overrideable functions
}

View File

@@ -1,34 +1,35 @@
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
import type { LOG_LEVEL } from "../lib/src/common/types";
import type { LiveSyncCore } from "../main";
import { unique } from "octagonal-wheels/collection";
import type { IObsidianModule } from "./AbstractObsidianModule.ts";
import type {
ICoreModuleBase,
AllInjectableProps,
AllExecuteProps,
EveryExecuteProps,
AnyExecuteProps,
ICoreModule,
} from "./ModuleTypes";
import { __$checkInstanceBinding } from "../lib/src/dev/checks";
// import { unique } from "octagonal-wheels/collection";
// import type { IObsidianModule } from "./AbstractObsidianModule.ts";
// import type {
// ICoreModuleBase,
// AllInjectableProps,
// AllExecuteProps,
// EveryExecuteProps,
// AnyExecuteProps,
// ICoreModule,
// } from "./ModuleTypes";
function isOverridableKey(key: string): key is keyof ICoreModuleBase {
return key.startsWith("$");
}
// function isOverridableKey(key: string): key is keyof ICoreModuleBase {
// return key.startsWith("$");
// }
function isInjectableKey(key: string): key is keyof AllInjectableProps {
return key.startsWith("$$");
}
// function isInjectableKey(key: string): key is keyof AllInjectableProps {
// return key.startsWith("$$");
// }
function isAllExecuteKey(key: string): key is keyof AllExecuteProps {
return key.startsWith("$all");
}
function isEveryExecuteKey(key: string): key is keyof EveryExecuteProps {
return key.startsWith("$every");
}
function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
return key.startsWith("$any");
}
// function isAllExecuteKey(key: string): key is keyof AllExecuteProps {
// return key.startsWith("$all");
// }
// function isEveryExecuteKey(key: string): key is keyof EveryExecuteProps {
// return key.startsWith("$every");
// }
// function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
// return key.startsWith("$any");
// }
/**
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
* Please refer to the module's source code to understand the function.
@@ -39,100 +40,100 @@ function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
* $ : Other interceptive points. You should manually assign the module
* All of above performed on injectModules function.
*/
export function injectModules<T extends ICoreModule>(target: T, modules: ICoreModule[]) {
const allKeys = unique([
...Object.keys(Object.getOwnPropertyDescriptors(target)),
...Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(target))),
]).filter((e) => e.startsWith("$")) as (keyof ICoreModule)[];
const moduleMap = new Map<string, IObsidianModule[]>();
for (const module of modules) {
for (const key of allKeys) {
if (isOverridableKey(key)) {
if (key in module) {
const list = moduleMap.get(key) || [];
if (typeof module[key] === "function") {
module[key] = module[key].bind(module) as any;
}
list.push(module);
moduleMap.set(key, list);
}
}
}
}
Logger(`Injecting modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
for (const key of allKeys) {
const modules = moduleMap.get(key) || [];
if (isInjectableKey(key)) {
if (modules.length == 0) {
throw new Error(`No module injected for ${key}. This is a fatal error.`);
}
target[key] = modules[0][key]! as any;
Logger(`[${modules[0].constructor.name}]: Injected ${key} `, LOG_LEVEL_VERBOSE);
} else if (isAllExecuteKey(key)) {
const modules = moduleMap.get(key) || [];
target[key] = async (...args: any) => {
for (const module of modules) {
try {
//@ts-ignore
await module[key]!(...args);
} catch (ex) {
Logger(`[${module.constructor.name}]: All handler for ${key} failed`, LOG_LEVEL_VERBOSE);
Logger(ex, LOG_LEVEL_VERBOSE);
}
}
return true;
};
for (const module of modules) {
Logger(`[${module.constructor.name}]: Injected (All) ${key} `, LOG_LEVEL_VERBOSE);
}
} else if (isEveryExecuteKey(key)) {
target[key] = async (...args: any) => {
for (const module of modules) {
try {
//@ts-ignore:2556
const ret = await module[key]!(...args);
if (ret !== undefined && !ret) {
// Failed then return that falsy value.
return ret;
}
} catch (ex) {
Logger(`[${module.constructor.name}]: Every handler for ${key} failed`);
Logger(ex, LOG_LEVEL_VERBOSE);
}
}
return true;
};
for (const module of modules) {
Logger(`[${module.constructor.name}]: Injected (Every) ${key} `, LOG_LEVEL_VERBOSE);
}
} else if (isAnyExecuteKey(key)) {
//@ts-ignore
target[key] = async (...args: any[]) => {
for (const module of modules) {
try {
//@ts-ignore:2556
const ret = await module[key](...args);
// If truly value returned, then return that value.
if (ret) {
return ret;
}
} catch (ex) {
Logger(`[${module.constructor.name}]: Any handler for ${key} failed`);
Logger(ex, LOG_LEVEL_VERBOSE);
}
}
return false;
};
for (const module of modules) {
Logger(`[${module.constructor.name}]: Injected (Any) ${key} `, LOG_LEVEL_VERBOSE);
}
} else {
Logger(`No injected handler for ${key} `, LOG_LEVEL_VERBOSE);
}
}
Logger(`Injected modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
return true;
}
// export function injectModules<T extends ICoreModule>(target: T, modules: ICoreModule[]) {
// const allKeys = unique([
// ...Object.keys(Object.getOwnPropertyDescriptors(target)),
// ...Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(target))),
// ]).filter((e) => e.startsWith("$")) as (keyof ICoreModule)[];
// const moduleMap = new Map<string, IObsidianModule[]>();
// for (const module of modules) {
// for (const key of allKeys) {
// if (isOverridableKey(key)) {
// if (key in module) {
// const list = moduleMap.get(key) || [];
// if (typeof module[key] === "function") {
// module[key] = module[key].bind(module) as any;
// }
// list.push(module);
// moduleMap.set(key, list);
// }
// }
// }
// }
// Logger(`Injecting modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
// for (const key of allKeys) {
// const modules = moduleMap.get(key) || [];
// if (isInjectableKey(key)) {
// if (modules.length == 0) {
// throw new Error(`No module injected for ${key}. This is a fatal error.`);
// }
// target[key] = modules[0][key]! as any;
// Logger(`[${modules[0].constructor.name}]: Injected ${key} `, LOG_LEVEL_VERBOSE);
// } else if (isAllExecuteKey(key)) {
// const modules = moduleMap.get(key) || [];
// target[key] = async (...args: any) => {
// for (const module of modules) {
// try {
// //@ts-ignore
// await module[key]!(...args);
// } catch (ex) {
// Logger(`[${module.constructor.name}]: All handler for ${key} failed`, LOG_LEVEL_VERBOSE);
// Logger(ex, LOG_LEVEL_VERBOSE);
// }
// }
// return true;
// };
// for (const module of modules) {
// Logger(`[${module.constructor.name}]: Injected (All) ${key} `, LOG_LEVEL_VERBOSE);
// }
// } else if (isEveryExecuteKey(key)) {
// target[key] = async (...args: any) => {
// for (const module of modules) {
// try {
// //@ts-ignore:2556
// const ret = await module[key]!(...args);
// if (ret !== undefined && !ret) {
// // Failed then return that falsy value.
// return ret;
// }
// } catch (ex) {
// Logger(`[${module.constructor.name}]: Every handler for ${key} failed`);
// Logger(ex, LOG_LEVEL_VERBOSE);
// }
// }
// return true;
// };
// for (const module of modules) {
// Logger(`[${module.constructor.name}]: Injected (Every) ${key} `, LOG_LEVEL_VERBOSE);
// }
// } else if (isAnyExecuteKey(key)) {
// //@ts-ignore
// target[key] = async (...args: any[]) => {
// for (const module of modules) {
// try {
// //@ts-ignore:2556
// const ret = await module[key](...args);
// // If truly value returned, then return that value.
// if (ret) {
// return ret;
// }
// } catch (ex) {
// Logger(`[${module.constructor.name}]: Any handler for ${key} failed`);
// Logger(ex, LOG_LEVEL_VERBOSE);
// }
// }
// return false;
// };
// for (const module of modules) {
// Logger(`[${module.constructor.name}]: Injected (Any) ${key} `, LOG_LEVEL_VERBOSE);
// }
// } else {
// Logger(`No injected handler for ${key} `, LOG_LEVEL_VERBOSE);
// }
// }
// Logger(`Injected modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
// return true;
// }
export abstract class AbstractModule {
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
@@ -153,14 +154,18 @@ export abstract class AbstractModule {
this.core.settings = value;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
// Override if needed.
}
constructor(public core: LiveSyncCore) {
this.onBindFunction(core, core.services);
Logger(`[${this.constructor.name}] Loaded`, LOG_LEVEL_VERBOSE);
__$checkInstanceBinding(this);
}
saveSettings = this.core.saveSettings.bind(this.core);
// abstract $everyTest(): Promise<boolean>;
addTestResult(key: string, value: boolean, summary?: string, message?: string) {
this.core.$$addTestResult(`${this.constructor.name}`, key, value, summary, message);
this.services.test.addTestResult(`${this.constructor.name}`, key, value, summary, message);
}
testDone(result: boolean = true) {
return Promise.resolve(result);
@@ -185,4 +190,8 @@ export abstract class AbstractModule {
}
return this.testDone();
}
get services() {
return this.core._services;
}
}

View File

@@ -37,18 +37,18 @@ export abstract class AbstractObsidianModule extends AbstractModule {
saveSettings = this.plugin.saveSettings.bind(this.plugin);
_isMainReady() {
return this.core.$$isReady();
isMainReady() {
return this.services.appLifecycle.isReady();
}
_isMainSuspended() {
return this.core.$$isSuspended();
isMainSuspended() {
return this.services.appLifecycle.isSuspended();
}
_isDatabaseReady() {
return this.core.$$isDatabaseReady();
isDatabaseReady() {
return this.services.database.isDatabaseReady();
}
//should be overridden
_isThisModuleEnabled() {
isThisModuleEnabled() {
return true;
}
}

View File

@@ -17,7 +17,6 @@ import type {
DocumentID,
} from "../../lib/src/common/types";
import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess";
import { type IObsidianModule } from "../AbstractObsidianModule.ts";
import { isPlainText, shouldBeIgnored, stripAllPrefixes } from "../../lib/src/string_and_binary/path";
import {
createBlob,
@@ -30,14 +29,15 @@ import {
import { serialized } from "octagonal-wheels/concurrency/lock";
import { AbstractModule } from "../AbstractModule.ts";
import { ICHeader } from "../../common/types.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidianModule, DatabaseFileAccess {
$everyOnload(): Promise<boolean> {
export class ModuleDatabaseFileAccess extends AbstractModule implements DatabaseFileAccess {
private _everyOnload(): Promise<boolean> {
this.core.databaseFileAccess = this;
return Promise.resolve(true);
}
async $everyModuleTest(): Promise<boolean> {
private async _everyModuleTest(): Promise<boolean> {
if (!this.settings.enableDebugTools) return Promise.resolve(true);
const testString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc";
// Before test, we need to delete completely.
@@ -75,7 +75,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
async checkIsTargetFile(file: UXFileInfoStub | FilePathWithPrefix): Promise<boolean> {
const path = getStoragePathFromUXFileInfo(file);
if (!(await this.core.$$isTargetFile(path))) {
if (!(await this.services.vault.isTargetFile(path))) {
this._log(`File is not target`, LOG_LEVEL_VERBOSE);
return false;
}
@@ -102,11 +102,11 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
}
async createChunks(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
return await this._store(file, force, skipCheck, true);
return await this.__store(file, force, skipCheck, true);
}
async store(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
return await this._store(file, force, skipCheck, false);
return await this.__store(file, force, skipCheck, false);
}
async storeContent(path: FilePathWithPrefix, content: string): Promise<boolean> {
const blob = createTextBlob(content);
@@ -124,10 +124,10 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
body: blob,
isInternal,
};
return await this._store(dummyUXFileInfo, true, false, false);
return await this.__store(dummyUXFileInfo, true, false, false);
}
async _store(
private async __store(
file: UXFileInfo,
force: boolean = false,
skipCheck?: boolean,
@@ -177,7 +177,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
}
}
const idMain = await this.core.$$path2id(fullPath);
const idMain = await this.services.path.path2id(fullPath);
const id = (idPrefix + idMain) as DocumentID;
const d: SavingEntry = {
@@ -345,4 +345,8 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
eventHub.emitEvent(EVENT_FILE_SAVED);
return ret;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
services.test.handleTest(this._everyModuleTest.bind(this));
}
}

View File

@@ -20,11 +20,11 @@ import {
} from "../../common/utils";
import { getDocDataAsArray, isDocContentSame, readAsBlob, readContent } from "../../lib/src/common/utils";
import { shouldBeIgnored } from "../../lib/src/string_and_binary/path";
import type { ICoreModule } from "../ModuleTypes";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
import { eventHub } from "../../common/events.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleFileHandler extends AbstractModule implements ICoreModule {
export class ModuleFileHandler extends AbstractModule {
get db() {
return this.core.databaseFileAccess;
}
@@ -32,7 +32,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
return this.core.storageAccess;
}
$everyOnloadStart(): Promise<boolean> {
_everyOnloadStart(): Promise<boolean> {
this.core.fileHandler = this;
return Promise.resolve(true);
}
@@ -52,7 +52,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix,
force: boolean = false,
onlyChunks: boolean = false
): Promise<boolean | undefined> {
): Promise<boolean> {
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
if (file == null) {
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
@@ -94,10 +94,14 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
let readFile: UXFileInfo | undefined = undefined;
if (!shouldApplied) {
readFile = await this.readFileFromStub(file);
if (!readFile) {
this._log(`File ${file.path} is not exist on the storage`, LOG_LEVEL_NOTICE);
return false;
}
if (await isDocContentSame(getDocDataAsArray(entry.data), readFile.body)) {
// Timestamp is different but the content is same. therefore, two timestamps should be handled as same.
// So, mark the changes are same.
markChangesAreSame(file, file.stat.mtime, entry.mtime);
markChangesAreSame(readFile, readFile.stat.mtime, entry.mtime);
} else {
shouldApplied = true;
}
@@ -125,7 +129,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
}
}
async deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean | undefined> {
async deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean> {
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
if (file == null) {
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
@@ -222,7 +226,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
// NO OP
} else {
// If not, then it should be checked. and will be processed later (i.e., after the conflict is resolved).
await this.core.$$queueConflictCheckIfOpen(path);
await this.services.conflict.queueCheckForIfOpen(path);
return true;
}
}
@@ -313,11 +317,11 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
return ret;
}
async $anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
private async _anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean> {
const eventItem = item.args;
const type = item.type;
const path = eventItem.file.path;
if (!(await this.core.$$isTargetFile(path))) {
if (!(await this.services.vault.isTargetFile(path))) {
this._log(`File ${path} is not the target file`, LOG_LEVEL_VERBOSE);
return false;
}
@@ -343,9 +347,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
});
}
async $anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean | undefined> {
async _anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean> {
return await serialized(entry.path, async () => {
if (!(await this.core.$$isTargetFile(entry.path))) {
if (!(await this.services.vault.isTargetFile(entry.path))) {
this._log(`File ${entry.path} is not the target file`, LOG_LEVEL_VERBOSE);
return false;
}
@@ -362,7 +366,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
return true;
} else {
this._log(
`Processing ${path} (${entry._id.substring(0, 8)}: ${entry._rev?.substring(0, 5)}) :Started...`,
`Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Started...`,
LOG_LEVEL_VERBOSE
);
// Before writing (or skipped ), merging dialogue should be cancelled.
@@ -391,7 +395,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
};
const total = filesStorageSrc.length;
const procAllChunks = filesStorageSrc.map(async (file) => {
if (!(await this.core.$$isTargetFile(file))) {
if (!(await this.services.vault.isTargetFile(file))) {
incProcessed();
return true;
}
@@ -416,4 +420,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
"chunkCreation"
);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
services.fileProcessing.handleProcessFileEvent(this._anyHandlerProcessesFileEvent.bind(this));
services.replication.handleProcessSynchroniseResult(this._anyProcessReplicatedDoc.bind(this));
}
}

View File

@@ -2,18 +2,18 @@ import { $msg } from "../../lib/src/common/i18n";
import { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
import { initializeStores } from "../../common/stores.ts";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
import { LiveSyncManagers } from "../../lib/src/managers/LiveSyncManagers.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICoreModule {
$everyOnloadStart(): Promise<boolean> {
export class ModuleLocalDatabaseObsidian extends AbstractModule {
_everyOnloadStart(): Promise<boolean> {
return Promise.resolve(true);
}
async $$openDatabase(): Promise<boolean> {
private async _openDatabase(): Promise<boolean> {
if (this.localDatabase != null) {
await this.localDatabase.close();
}
const vaultName = this.core.$$getVaultName();
const vaultName = this.services.vault.getVaultName();
this._log($msg("moduleLocalDatabase.logWaitingForReady"));
const getDB = () => this.core.localDatabase.localDatabase;
const getSettings = () => this.core.settings;
@@ -22,8 +22,9 @@ export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICore
return getDB();
},
getActiveReplicator: () => this.core.replicator,
id2path: this.core.$$id2path.bind(this.core),
path2id: this.core.$$path2id.bind(this.core),
id2path: this.services.path.id2path,
// path2id: this.core.$$path2id.bind(this.core),
path2id: this.services.path.path2id,
get settings() {
return getSettings();
},
@@ -34,7 +35,12 @@ export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICore
return await this.localDatabase.initializeDatabase();
}
$$isDatabaseReady(): boolean {
_isDatabaseReady(): boolean {
return this.localDatabase != null && this.localDatabase.isReady;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.database.handleIsDatabaseReady(this._isDatabaseReady.bind(this));
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
services.database.handleOpenDatabase(this._openDatabase.bind(this));
}
}

View File

@@ -1,33 +1,41 @@
import { PeriodicProcessor } from "../../common/utils";
import type { LiveSyncCore } from "../../main";
import { AbstractModule } from "../AbstractModule";
import type { ICoreModule } from "../ModuleTypes";
export class ModulePeriodicProcess extends AbstractModule implements ICoreModule {
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.core.$$replicate());
export class ModulePeriodicProcess extends AbstractModule {
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.services.replication.replicate());
_disablePeriodic() {
disablePeriodic() {
this.periodicSyncProcessor?.disable();
return Promise.resolve(true);
}
_resumePeriodic() {
resumePeriodic() {
this.periodicSyncProcessor.enable(
this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0
);
return Promise.resolve(true);
}
$allOnUnload() {
return this._disablePeriodic();
private _allOnUnload() {
return this.disablePeriodic();
}
$everyBeforeRealizeSetting(): Promise<boolean> {
return this._disablePeriodic();
private _everyBeforeRealizeSetting(): Promise<boolean> {
return this.disablePeriodic();
}
$everyBeforeSuspendProcess(): Promise<boolean> {
return this._disablePeriodic();
private _everyBeforeSuspendProcess(): Promise<boolean> {
return this.disablePeriodic();
}
$everyAfterResumeProcess(): Promise<boolean> {
return this._resumePeriodic();
private _everyAfterResumeProcess(): Promise<boolean> {
return this.resumePeriodic();
}
$everyAfterRealizeSetting(): Promise<boolean> {
return this._resumePeriodic();
private _everyAfterRealizeSetting(): Promise<boolean> {
return this.resumePeriodic();
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnUnload(this._allOnUnload.bind(this));
services.setting.handleBeforeRealiseSetting(this._everyBeforeRealizeSetting.bind(this));
services.setting.handleSettingRealised(this._everyAfterRealizeSetting.bind(this));
services.appLifecycle.handleOnSuspending(this._everyBeforeSuspendProcess.bind(this));
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
}
}

View File

@@ -1,9 +1,9 @@
import { AbstractModule } from "../AbstractModule";
import type { ICoreModule } from "../ModuleTypes";
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
import type { LiveSyncCore } from "../../main";
export class ModulePouchDB extends AbstractModule implements ICoreModule {
$$createPouchDBInstance<T extends object>(
export class ModulePouchDB extends AbstractModule {
_createPouchDBInstance<T extends object>(
name?: string,
options?: PouchDB.Configuration.DatabaseConfiguration
): PouchDB.Database<T> {
@@ -16,4 +16,7 @@ export class ModulePouchDB extends AbstractModule implements ICoreModule {
}
return new PouchDB(name, optionPass);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.database.handleCreatePouchDBInstance(this._createPouchDBInstance.bind(this));
}
}

View File

@@ -9,13 +9,13 @@ import {
} from "../../lib/src/common/types.ts";
import { AbstractModule } from "../AbstractModule.ts";
import type { Rebuilder } from "../interfaces/DatabaseRebuilder.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
import type { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
import { fetchAllUsedChunks } from "@/lib/src/pouchdb/chunks.ts";
import { EVENT_DATABASE_REBUILT, eventHub } from "src/common/events.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebuilder {
$everyOnload(): Promise<boolean> {
export class ModuleRebuilder extends AbstractModule implements Rebuilder {
private _everyOnload(): Promise<boolean> {
this.core.rebuilder = this;
return Promise.resolve(true);
}
@@ -43,47 +43,47 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
{ title: "Enable extra features", defaultOption: "No", timeout: 15 }
)) == "yes"
) {
await this.core.$allAskUsingOptionalSyncFeature(opt);
await this.services.setting.suggestOptionalFeatures(opt);
}
}
async rebuildRemote() {
await this.core.$allSuspendExtraSync();
await this.services.setting.suspendExtraSync();
this.core.settings.isConfigured = true;
await this.core.$$realizeSettingSyncMode();
await this.core.$$markRemoteLocked();
await this.core.$$tryResetRemoteDatabase();
await this.core.$$markRemoteLocked();
await this.services.setting.onRealiseSetting();
await this.services.remote.markLocked();
await this.services.remote.tryResetDatabase();
await this.services.remote.markLocked();
await delay(500);
await this.askUsingOptionalFeature({ enableOverwrite: true });
await delay(1000);
await this.core.$$replicateAllToServer(true);
await this.services.remote.replicateAllToRemote(true);
await delay(1000);
await this.core.$$replicateAllToServer(true, true);
await this.services.remote.replicateAllToRemote(true, true);
}
$rebuildRemote(): Promise<void> {
return this.rebuildRemote();
}
async rebuildEverything() {
await this.core.$allSuspendExtraSync();
await this.services.setting.suspendExtraSync();
await this.askUseNewAdapter();
this.core.settings.isConfigured = true;
await this.core.$$realizeSettingSyncMode();
await this.services.setting.onRealiseSetting();
await this.resetLocalDatabase();
await delay(1000);
await this.core.$$initializeDatabase(true, true, true);
await this.core.$$markRemoteLocked();
await this.core.$$tryResetRemoteDatabase();
await this.core.$$markRemoteLocked();
await this.services.databaseEvents.initialiseDatabase(true, true, true);
await this.services.remote.markLocked();
await this.services.remote.tryResetDatabase();
await this.services.remote.markLocked();
await delay(500);
// We do not have any other devices' data, so we do not need to ask for overwriting.
await this.askUsingOptionalFeature({ enableOverwrite: false });
await delay(1000);
await this.core.$$replicateAllToServer(true);
await this.services.remote.replicateAllToRemote(true);
await delay(1000);
await this.core.$$replicateAllToServer(true, true);
await this.services.remote.replicateAllToRemote(true, true);
}
$rebuildEverything(): Promise<void> {
@@ -101,7 +101,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
this._log("Could not create red_flag_rebuild.md", LOG_LEVEL_NOTICE);
this._log(ex, LOG_LEVEL_VERBOSE);
}
this.core.$$performRestart();
this.services.appLifecycle.performRestart();
}
async scheduleFetch(): Promise<void> {
try {
@@ -110,20 +110,20 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
this._log("Could not create red_flag_fetch.md", LOG_LEVEL_NOTICE);
this._log(ex, LOG_LEVEL_VERBOSE);
}
this.core.$$performRestart();
this.services.appLifecycle.performRestart();
}
async $$tryResetRemoteDatabase(): Promise<void> {
private async _tryResetRemoteDatabase(): Promise<void> {
await this.core.replicator.tryResetRemoteDatabase(this.settings);
}
async $$tryCreateRemoteDatabase(): Promise<void> {
private async _tryCreateRemoteDatabase(): Promise<void> {
await this.core.replicator.tryCreateRemoteDatabase(this.settings);
}
async $$resetLocalDatabase(): Promise<void> {
private async _resetLocalDatabase(): Promise<boolean> {
this.core.storageAccess.clearTouched();
await this.localDatabase.resetDatabase();
return await this.localDatabase.resetDatabase();
}
async suspendAllSync() {
@@ -134,7 +134,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
this.core.settings.syncOnStart = false;
this.core.settings.syncOnFileOpen = false;
this.core.settings.syncAfterMerge = false;
await this.core.$allSuspendExtraSync();
await this.services.setting.suspendExtraSync();
}
async suspendReflectingDatabase() {
if (this.core.settings.doNotSuspendOnFetching) return;
@@ -153,8 +153,8 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
this._log(`Database and storage reflection has been resumed!`, LOG_LEVEL_NOTICE);
this.core.settings.suspendParseReplicationResult = false;
this.core.settings.suspendFileWatching = false;
await this.core.$$performFullScan(true);
await this.core.$everyBeforeReplicate(false); //TODO: Check actual need of this.
await this.services.vault.scanVault(true);
await this.services.replication.onBeforeReplicate(false); //TODO: Check actual need of this.
await this.core.saveSettings();
}
async askUseNewAdapter() {
@@ -177,28 +177,28 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
}
}
async fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean) {
await this.core.$allSuspendExtraSync();
await this.services.setting.suspendExtraSync();
await this.askUseNewAdapter();
this.core.settings.isConfigured = true;
await this.suspendReflectingDatabase();
await this.core.$$realizeSettingSyncMode();
await this.services.setting.onRealiseSetting();
await this.resetLocalDatabase();
await delay(1000);
await this.core.$$openDatabase();
await this.services.database.openDatabase();
// this.core.isReady = true;
this.core.$$markIsReady();
this.services.appLifecycle.markIsReady();
if (makeLocalChunkBeforeSync) {
await this.core.fileHandler.createAllChunks(true);
} else if (!preventMakeLocalFilesBeforeSync) {
await this.core.$$initializeDatabase(true, true, true);
await this.services.databaseEvents.initialiseDatabase(true, true, true);
} else {
// Do not create local file entries before sync (Means use remote information)
}
await this.core.$$markRemoteResolved();
await this.services.remote.markResolved();
await delay(500);
await this.core.$$replicateAllFromServer(true);
await this.services.remote.replicateAllFromRemote(true);
await delay(1000);
await this.core.$$replicateAllFromServer(true);
await this.services.remote.replicateAllFromRemote(true);
await this.resumeReflectingDatabase();
await this.askUsingOptionalFeature({ enableFetch: true });
}
@@ -206,7 +206,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
return await this.fetchLocal(true);
}
async $allSuspendAllSync(): Promise<boolean> {
private async _allSuspendAllSync(): Promise<boolean> {
await this.suspendAllSync();
return true;
}
@@ -214,11 +214,11 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
async resetLocalDatabase() {
if (this.core.settings.isConfigured && this.core.settings.additionalSuffixOfDatabaseName == "") {
// Discard the non-suffixed database
await this.core.$$resetLocalDatabase();
await this.services.database.resetDatabase();
}
const suffix = (await this.core.$anyGetAppId()) || "";
const suffix = this.services.API.getAppID() || "";
this.core.settings.additionalSuffixOfDatabaseName = suffix;
await this.core.$$resetLocalDatabase();
await this.services.database.resetDatabase();
eventHub.emitEvent(EVENT_DATABASE_REBUILT);
}
async fetchRemoteChunks() {
@@ -228,10 +228,10 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
this.core.settings.remoteType == REMOTE_COUCHDB
) {
this._log(`Fetching chunks`, LOG_LEVEL_NOTICE);
const replicator = this.core.$$getReplicator() as LiveSyncCouchDBReplicator;
const replicator = this.services.replicator.getActiveReplicator() as LiveSyncCouchDBReplicator;
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
this.settings,
this.core.$$isMobile(),
this.services.API.isMobile(),
true
);
if (typeof remoteDB == "string") {
@@ -254,8 +254,15 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
LOG_LEVEL_NOTICE,
"resolveAllConflictedFilesByNewerOnes"
);
await this.core.$anyResolveConflictByNewest(file);
await this.services.conflict.resolveByNewest(file);
}
this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes");
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
services.database.handleResetDatabase(this._resetLocalDatabase.bind(this));
services.remote.handleTryResetDatabase(this._tryResetRemoteDatabase.bind(this));
services.remote.handleTryCreateDatabase(this._tryCreateRemoteDatabase.bind(this));
services.setting.handleSuspendAllSync(this._allSuspendAllSync.bind(this));
}
}

View File

@@ -1,7 +1,6 @@
import { fireAndForget, yieldMicrotask } from "octagonal-wheels/promises";
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB";
import { AbstractModule } from "../AbstractModule";
import type { ICoreModule } from "../ModuleTypes";
import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks";
@@ -34,17 +33,18 @@ import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveS
import { $msg } from "../../lib/src/common/i18n";
import { clearHandlers } from "../../lib/src/replication/SyncParamsHandler";
import type { LiveSyncCore } from "../../main";
const KEY_REPLICATION_ON_EVENT = "replicationOnEvent";
const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000;
export class ModuleReplicator extends AbstractModule implements ICoreModule {
export class ModuleReplicator extends AbstractModule {
_replicatorType?: RemoteType;
$everyOnloadAfterLoadSettings(): Promise<boolean> {
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
eventHub.onEvent(EVENT_FILE_SAVED, () => {
if (this.settings.syncOnSave && !this.core.$$isSuspended()) {
scheduleTask("perform-replicate-after-save", 250, () => this.core.$$replicateByEvent());
if (this.settings.syncOnSave && !this.core.services.appLifecycle.isSuspended()) {
scheduleTask("perform-replicate-after-save", 250, () => this.services.replication.replicateByEvent());
}
});
eventHub.onEvent(EVENT_SETTING_SAVED, (setting) => {
@@ -57,7 +57,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
}
async setReplicator() {
const replicator = await this.core.$anyNewReplicator();
const replicator = await this.services.replicator.getNewReplicator();
if (!replicator) {
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
return false;
@@ -74,24 +74,28 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
return true;
}
$$getReplicator(): LiveSyncAbstractReplicator {
_getReplicator(): LiveSyncAbstractReplicator {
return this.core.replicator;
}
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
_everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
return this.setReplicator();
}
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
_everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
return this.setReplicator();
}
async ensureReplicatorPBKDF2Salt(showMessage: boolean = false): Promise<boolean> {
// Checking salt
const replicator = this.core.$$getReplicator();
const replicator = this.services.replicator.getActiveReplicator();
if (!replicator) {
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
return false;
}
return await replicator.ensurePBKDF2Salt(this.settings, showMessage, true);
}
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
async _everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
// Checking salt
if (!this.core.managers.networkManager.isOnline) {
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
@@ -106,7 +110,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
return true;
}
async $$replicate(showMessage: boolean = false): Promise<boolean | void> {
private async _replicate(showMessage: boolean = false): Promise<boolean | void> {
try {
updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT, REPLICATION_ON_EVENT_FORECASTED_TIME);
return await this.$$_replicate(showMessage);
@@ -143,11 +147,11 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
await this.core.rebuilder.$performRebuildDB("localOnly");
}
if (ret == CHOICE_CLEAN) {
const replicator = this.core.$$getReplicator();
const replicator = this.services.replicator.getActiveReplicator();
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
this.settings,
this.core.$$isMobile(),
this.services.API.isMobile(),
true
);
if (typeof remoteDB == "string") {
@@ -162,7 +166,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
this.localDatabase.clearCaches();
await this.core.$$getReplicator().markRemoteResolved(this.settings);
await this.services.replicator.getActiveReplicator()?.markRemoteResolved(this.settings);
Logger("The local database has been cleaned up.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
} else {
Logger(
@@ -174,8 +178,8 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
});
}
async $$canReplicate(showMessage: boolean = false): Promise<boolean> {
if (!this.core.$$isReady()) {
async _canReplicate(showMessage: boolean = false): Promise<boolean> {
if (!this.services.appLifecycle.isReady()) {
Logger(`Not ready`);
return false;
}
@@ -190,7 +194,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
return false;
}
if (!(await this.core.$everyCommitPendingFileEvent())) {
if (!(await this.services.fileProcessing.commitPendingFileEvents())) {
Logger($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE);
return false;
}
@@ -199,7 +203,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
return false;
}
if (!(await this.core.$everyBeforeReplicate(showMessage))) {
if (!(await this.services.replication.onBeforeReplicate(showMessage))) {
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
return false;
}
@@ -207,14 +211,14 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
}
async $$_replicate(showMessage: boolean = false): Promise<boolean | void> {
const checkBeforeReplicate = await this.$$canReplicate(showMessage);
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.core.$$askResolvingMismatchedTweaks(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) {
@@ -236,7 +240,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
if (ret == CHOICE_FETCH) {
this._log($msg("Replicator.Dialogue.Locked.Message.Fetch"), LOG_LEVEL_NOTICE);
await this.core.rebuilder.scheduleFetch();
this.core.$$scheduleAppReload();
this.services.appLifecycle.scheduleRestart();
return;
} else if (ret == CHOICE_UNLOCK) {
await this.core.replicator.markRemoteResolved(this.settings);
@@ -250,16 +254,16 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
return ret;
}
async $$replicateByEvent(): Promise<boolean | void> {
private async _replicateByEvent(): Promise<boolean | void> {
const least = this.settings.syncMinimumInterval;
if (least > 0) {
return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => {
return await this.$$replicate();
return await this.services.replication.replicate();
});
}
return await shareRunningResult(`replication`, () => this.core.$$replicate());
return await shareRunningResult(`replication`, () => this.services.replication.replicate());
}
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
_parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
if (this.settings.suspendParseReplicationResult && !this.replicationResultProcessor.isSuspended) {
this.replicationResultProcessor.suspend();
}
@@ -336,7 +340,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
this.localDatabase.onNewLeaf(change as EntryLeaf);
return;
}
if (await this.core.$anyModuleParsedReplicationResultItem(change)) return;
if (await this.services.replication.processVirtualDocument(change)) return;
// any addon needs this item?
// for (const proc of this.core.addOns) {
// if (await proc.parseReplicationResultItem(change)) {
@@ -361,7 +365,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
}
if (isAnyNote(change)) {
const docPath = getPath(change);
if (!(await this.core.$$isTargetFile(docPath))) {
if (!(await this.services.vault.isTargetFile(docPath))) {
Logger(`Skipped: ${docPath}`, LOG_LEVEL_VERBOSE);
return;
}
@@ -369,7 +373,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
Logger(`Processing scheduled: ${docPath}`, LOG_LEVEL_INFO);
}
const size = change.size;
if (this.core.$$isFileSizeExceeded(size)) {
if (this.services.vault.isFileSizeTooLarge(size)) {
Logger(
`Processing ${docPath} has been skipped due to file size exceeding the limit`,
LOG_LEVEL_NOTICE
@@ -413,7 +417,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
return;
}
if (await this.core.$anyProcessOptionalSyncFiles(dbDoc)) {
if (await this.services.replication.processOptionalSynchroniseResult(dbDoc)) {
// Already processed
} else if (isValidPath(getPath(doc))) {
this.storageApplyingProcessor.enqueue(doc as MetaEntry);
@@ -440,7 +444,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
storageApplyingProcessor = new QueueProcessor(
async (docs: MetaEntry[]) => {
const entry = docs[0];
await this.core.$anyProcessReplicatedDoc(entry);
await this.services.replication.processSynchroniseResult(entry);
return;
},
{
@@ -458,17 +462,17 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
})
.startPipeline();
$everyBeforeSuspendProcess(): Promise<boolean> {
this.core.replicator.closeReplication();
_everyBeforeSuspendProcess(): Promise<boolean> {
this.core.replicator?.closeReplication();
return Promise.resolve(true);
}
async $$replicateAllToServer(
private async _replicateAllToServer(
showingNotice: boolean = false,
sendChunksInBulkDisabled: boolean = false
): Promise<boolean> {
if (!this.core.$$isReady()) return false;
if (!(await this.core.$everyBeforeReplicate(showingNotice))) {
if (!this.services.appLifecycle.isReady()) return false;
if (!(await this.services.replication.onBeforeReplicate(showingNotice))) {
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
return false;
}
@@ -486,16 +490,31 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
}
const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice);
if (ret) return true;
const checkResult = await this.core.$anyAfterConnectCheckFailed();
if (checkResult == "CHECKAGAIN") return await this.core.$$replicateAllToServer(showingNotice);
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<boolean> {
if (!this.core.$$isReady()) return false;
async _replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
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.core.$anyAfterConnectCheckFailed();
if (checkResult == "CHECKAGAIN") return await this.core.$$replicateAllFromServer(showingNotice);
const checkResult = await this.services.replication.checkConnectionFailure();
if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllFromRemote(showingNotice);
return !checkResult;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.replicator.handleGetActiveReplicator(this._getReplicator.bind(this));
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
services.databaseEvents.handleOnResetDatabase(this._everyOnResetDatabase.bind(this));
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
services.replication.handleParseSynchroniseResult(this._parseReplicationResult.bind(this));
services.appLifecycle.handleOnSuspending(this._everyBeforeSuspendProcess.bind(this));
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
services.replication.handleIsReplicationReady(this._canReplicate.bind(this));
services.replication.handleReplicate(this._replicate.bind(this));
services.replication.handleReplicateByEvent(this._replicateByEvent.bind(this));
services.remote.handleReplicateAllToRemote(this._replicateAllToServer.bind(this));
services.remote.handleReplicateAllFromRemote(this._replicateAllFromServer.bind(this));
}
}

View File

@@ -3,20 +3,20 @@ import { REMOTE_MINIO, REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/c
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
import { AbstractModule } from "../AbstractModule";
import type { ICoreModule } from "../ModuleTypes";
import type { LiveSyncCore } from "../../main";
export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModule {
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
export class ModuleReplicatorCouchDB extends AbstractModule {
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
const settings = { ...this.settings, ...settingOverride };
// If new remote types were added, add them here. Do not use `REMOTE_COUCHDB` directly for the safety valve.
if (settings.remoteType == REMOTE_MINIO || settings.remoteType == REMOTE_P2P) {
return undefined!;
return Promise.resolve(false);
}
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
}
$everyAfterResumeProcess(): Promise<boolean> {
if (!this.core.$$isSuspended) return Promise.resolve(true);
if (!this.core.$$isReady) return Promise.resolve(true);
_everyAfterResumeProcess(): Promise<boolean> {
if (!this.services.appLifecycle.isSuspended()) return Promise.resolve(true);
if (!this.services.appLifecycle.isReady()) return Promise.resolve(true);
if (this.settings.remoteType != REMOTE_MINIO && this.settings.remoteType != REMOTE_P2P) {
const LiveSyncEnabled = this.settings.liveSync;
const continuous = LiveSyncEnabled;
@@ -27,7 +27,7 @@ export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModu
// And note that we do not open the conflict detection dialogue directly during this process.
// This should be raised explicitly if needed.
fireAndForget(async () => {
const canReplicate = await this.core.$$canReplicate(false);
const canReplicate = await this.services.replication.isReplicationReady(false);
if (!canReplicate) return;
void this.core.replicator.openReplication(this.settings, continuous, false, false);
});
@@ -36,4 +36,8 @@ export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModu
return Promise.resolve(true);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
}
}

View File

@@ -1,15 +1,18 @@
import { REMOTE_MINIO, type RemoteDBSettings } from "../../lib/src/common/types";
import { LiveSyncJournalReplicator } from "../../lib/src/replication/journal/LiveSyncJournalReplicator";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
import type { LiveSyncCore } from "../../main";
import { AbstractModule } from "../AbstractModule";
import type { ICoreModule } from "../ModuleTypes";
export class ModuleReplicatorMinIO extends AbstractModule implements ICoreModule {
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
export class ModuleReplicatorMinIO extends AbstractModule {
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
const settings = { ...this.settings, ...settingOverride };
if (settings.remoteType == REMOTE_MINIO) {
return Promise.resolve(new LiveSyncJournalReplicator(this.core));
}
return undefined!;
return Promise.resolve(false);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
}
}

View File

@@ -1,18 +1,18 @@
import { REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/common/types";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
import { AbstractModule } from "../AbstractModule";
import type { ICoreModule } from "../ModuleTypes";
import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator";
import type { LiveSyncCore } from "../../main";
export class ModuleReplicatorP2P extends AbstractModule implements ICoreModule {
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
export class ModuleReplicatorP2P extends AbstractModule {
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
const settings = { ...this.settings, ...settingOverride };
if (settings.remoteType == REMOTE_P2P) {
return Promise.resolve(new LiveSyncTrysteroReplicator(this.core));
}
return undefined!;
return Promise.resolve(false);
}
$everyAfterResumeProcess(): Promise<boolean> {
_everyAfterResumeProcess(): Promise<boolean> {
if (this.settings.remoteType == REMOTE_P2P) {
// // If LiveSync enabled, open replication
// if (this.settings.liveSync) {
@@ -27,4 +27,8 @@ export class ModuleReplicatorP2P extends AbstractModule implements ICoreModule {
return Promise.resolve(true);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
}
}

View File

@@ -18,14 +18,14 @@ import {
} from "../../lib/src/common/types";
import { addPrefix, isAcceptedAll } from "../../lib/src/string_and_binary/path";
import { AbstractModule } from "../AbstractModule";
import type { ICoreModule } from "../ModuleTypes";
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
import { isDirty } from "../../lib/src/common/utils";
export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
import type { LiveSyncCore } from "../../main";
export class ModuleTargetFilter extends AbstractModule {
reloadIgnoreFiles() {
this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim());
}
$everyOnload(): Promise<boolean> {
private _everyOnload(): Promise<boolean> {
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => {
this.reloadIgnoreFiles();
});
@@ -35,7 +35,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
return Promise.resolve(true);
}
$$id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
_id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
const tempId = id2path(id, entry);
if (stripPrefix && isInternalMetadata(tempId)) {
const out = stripInternalMetadataPrefix(tempId);
@@ -43,7 +43,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
}
return tempId;
}
async $$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
async _path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
const destPath = addPrefix(filename, prefix ?? "");
return await path2id(
destPath,
@@ -52,7 +52,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
);
}
$$isFileSizeExceeded(size: number) {
private _isFileSizeExceeded(size: number) {
if (this.settings.syncMaxSizeInMB > 0 && size > 0) {
if (this.settings.syncMaxSizeInMB * 1024 * 1024 < size) {
return true;
@@ -61,7 +61,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
return false;
}
$$markFileListPossiblyChanged(): void {
_markFileListPossiblyChanged(): void {
this.totalFileEventCount++;
}
totalFileEventCount = 0;
@@ -72,7 +72,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
return false;
}
async $$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) {
private async _isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) {
const fileCount = useMemo<Record<string, number>>(
{
key: "fileCount", // forceUpdate: !keepFileCheckList,
@@ -109,7 +109,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
const filepath = getStoragePathFromUXFileInfo(file);
const lc = filepath.toLowerCase();
if (this.core.$$shouldCheckCaseInsensitive()) {
if (this.services.setting.shouldCheckCaseInsensitively()) {
if (lc in fileCount && fileCount[lc] > 1) {
return false;
}
@@ -120,7 +120,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
// We must reload ignore files due to the its change.
await this.readIgnoreFile(filepath);
}
if (await this.core.$$isIgnoredByIgnoreFiles(file)) {
if (await this.services.vault.isIgnoredByIgnoreFile(file)) {
return false;
}
}
@@ -150,7 +150,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
return await this.readIgnoreFile(path);
}
}
async $$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
private async _isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
if (!this.settings.useIgnoreFiles) {
return false;
}
@@ -164,4 +164,14 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
}
return false;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.vault.handleMarkFileListPossiblyChanged(this._markFileListPossiblyChanged.bind(this));
services.path.handleId2Path(this._id2path.bind(this));
services.path.handlePath2Id(this._path2id.bind(this));
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
services.vault.handleIsFileSizeTooLarge(this._isFileSizeExceeded.bind(this));
services.vault.handleIsIgnoredByIgnoreFile(this._isIgnoredByIgnoreFiles.bind(this));
services.vault.handleIsTargetFile(this._isTargetFile.bind(this));
}
}

View File

@@ -1,11 +1,11 @@
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import { AbstractModule } from "../AbstractModule.ts";
import { sizeToHumanReadable } from "octagonal-wheels/number";
import type { ICoreModule } from "../ModuleTypes.ts";
import { $msg } from "src/lib/src/common/i18n.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule {
async $allScanStat(): Promise<boolean> {
export class ModuleCheckRemoteSize extends AbstractModule {
async _allScanStat(): Promise<boolean> {
if (this.core.managers.networkManager.isOnline === false) {
this._log("Network is offline, skipping remote size check.", LOG_LEVEL_INFO);
return true;
@@ -109,4 +109,7 @@ export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule
}
return true;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnScanningStartupIssues(this._allScanStat.bind(this));
}
}

View File

@@ -2,35 +2,36 @@ import { AbstractModule } from "../AbstractModule.ts";
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
import { sendValue } from "octagonal-wheels/messagepassing/signal";
import type { ICoreModule } from "../ModuleTypes.ts";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleConflictChecker extends AbstractModule implements ICoreModule {
async $$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
export class ModuleConflictChecker extends AbstractModule {
async _queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
const path = file;
if (this.settings.checkConflictOnlyOnOpen) {
const af = this.core.$$getActiveFilePath();
const af = this.services.vault.getActiveFilePath();
if (af && af != path) {
this._log(`${file} is conflicted, merging process has been postponed.`, LOG_LEVEL_NOTICE);
return;
}
}
await this.core.$$queueConflictCheck(path);
await this.services.conflict.queueCheckFor(path);
}
async $$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
const optionalConflictResult = await this.core.$anyGetOptionalConflictCheckMethod(file);
async _queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
const optionalConflictResult = await this.services.conflict.getOptionalConflictCheckMethod(file);
if (optionalConflictResult == true) {
// The conflict has been resolved by another process.
return;
} else if (optionalConflictResult === "newer") {
// The conflict should be resolved by the newer entry.
await this.core.$anyResolveConflictByNewest(file);
await this.services.conflict.resolveByNewest(file);
} else {
this.conflictCheckQueue.enqueue(file);
}
}
$$waitForAllConflictProcessed(): Promise<boolean> {
_waitForAllConflictProcessed(): Promise<boolean> {
return this.conflictResolveQueue.waitForAllProcessed();
}
@@ -38,7 +39,7 @@ export class ModuleConflictChecker extends AbstractModule implements ICoreModule
conflictResolveQueue = new QueueProcessor(
async (filenames: FilePathWithPrefix[]) => {
const filename = filenames[0];
return await this.core.$$resolveConflict(filename);
return await this.services.conflict.resolve(filename);
},
{
suspended: false,
@@ -73,4 +74,9 @@ export class ModuleConflictChecker extends AbstractModule implements ICoreModule
totalRemainingReactiveSource: this.core.conflictProcessQueueCount,
}
);
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.conflict.handleQueueCheckForIfOpen(this._queueConflictCheckIfOpen.bind(this));
services.conflict.handleQueueCheckFor(this._queueConflictCheck.bind(this));
services.conflict.handleEnsureAllProcessed(this._waitForAllConflictProcessed.bind(this));
}
}

View File

@@ -20,8 +20,9 @@ import {
} from "../../common/utils";
import diff_match_patch from "diff-match-patch";
import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path";
import type { ICoreModule } from "../ModuleTypes.ts";
import { eventHub } from "../../common/events.ts";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts";
declare global {
interface LSEvents {
@@ -29,8 +30,8 @@ declare global {
}
}
export class ModuleConflictResolver extends AbstractModule implements ICoreModule {
async $$resolveConflictByDeletingRev(
export class ModuleConflictResolver extends AbstractModule {
private async _resolveConflictByDeletingRev(
path: FilePathWithPrefix,
deleteRevision: string,
subTitle = ""
@@ -82,7 +83,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
return MISSING_OR_ERROR;
}
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
return await this.core.$$resolveConflictByDeletingRev(path, ret.conflictedRev, "Sensible");
return await this.services.conflict.resolveByDeletingRevision(path, ret.conflictedRev, "Sensible");
}
const { rightRev, leftLeaf, rightLeaf } = ret;
@@ -95,7 +96,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
}
if (rightLeaf == false) {
// Conflicted item could not load, delete this.
return await this.core.$$resolveConflictByDeletingRev(path, rightRev, "MISSING OLD REV");
return await this.services.conflict.resolveByDeletingRevision(path, rightRev, "MISSING OLD REV");
}
const isSame = leftLeaf.data == rightLeaf.data && leftLeaf.deleted == rightLeaf.deleted;
@@ -115,7 +116,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
]
.filter((e) => e.trim())
.join(",");
return await this.core.$$resolveConflictByDeletingRev(path, loser.rev, subTitle);
return await this.services.conflict.resolveByDeletingRevision(path, loser.rev, subTitle);
}
// make diff.
const dmp = new diff_match_patch();
@@ -129,7 +130,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
};
}
async $$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
private async _resolveConflict(filename: FilePathWithPrefix): Promise<void> {
// const filename = filenames[0];
return await serialized(`conflict-resolve:${filename}`, async () => {
const conflictCheckResult = await this.checkConflictAndPerformAutoMerge(filename);
@@ -144,16 +145,16 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
}
if (conflictCheckResult === AUTO_MERGED) {
//auto resolved, but need check again;
if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
if (this.settings.syncAfterMerge && !this.services.appLifecycle.isSuspended()) {
//Wait for the running replication, if not running replication, run it once.
await this.core.$$replicateByEvent();
await this.services.replication.replicateByEvent();
}
this._log("[conflict] Automatically merged, but we have to check it again");
await this.core.$$queueConflictCheck(filename);
await this.services.conflict.queueCheckFor(filename);
return;
}
if (this.settings.showMergeDialogOnlyOnActive) {
const af = this.core.$$getActiveFilePath();
const af = this.services.vault.getActiveFilePath();
if (af && af != filename) {
this._log(
`[conflict] ${filename} is conflicted. Merging process has been postponed to the file have got opened.`,
@@ -164,11 +165,11 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
}
this._log("[conflict] Manual merge required!");
eventHub.emitEvent("conflict-cancelled", filename);
await this.core.$anyResolveConflictByUI(filename, conflictCheckResult);
await this.services.conflict.resolveByUserInteraction(filename, conflictCheckResult);
});
}
async $anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
private async _anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
const currentRev = await this.core.databaseFileAccess.fetchEntryMeta(filename, undefined, true);
if (currentRev == false) {
this._log(`Could not get current revision of ${filename}`);
@@ -206,8 +207,14 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
this._log(
`conflict: Deleting the older revision ${mTimeAndRev[i][1]} (${new Date(mTimeAndRev[i][0]).toLocaleString()}) of ${filename}`
);
await this.core.$$resolveConflictByDeletingRev(filename, mTimeAndRev[i][1], "NEWEST");
await this.services.conflict.resolveByDeletingRevision(filename, mTimeAndRev[i][1], "NEWEST");
}
return true;
}
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.conflict.handleResolveByDeletingRevision(this._resolveConflictByDeletingRev.bind(this));
services.conflict.handleResolve(this._resolveConflict.bind(this));
services.conflict.handleResolveByNewest(this._anyResolveConflictByNewest.bind(this));
}
}

View File

@@ -9,10 +9,10 @@ import {
type ObsidianLiveSyncSettings,
} from "../../lib/src/common/types.ts";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
import { $msg } from "../../lib/src/common/i18n.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleRedFlag extends AbstractModule implements ICoreModule {
export class ModuleRedFlag extends AbstractModule {
async isFlagFileExist(path: string) {
const redflag = await this.core.storageAccess.isExists(normalizePath(path));
if (redflag) {
@@ -48,7 +48,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
await this.deleteFlagFile(FLAGMD_REDFLAG3);
await this.deleteFlagFile(FLAGMD_REDFLAG3_HR);
}
async $everyOnLayoutReady(): Promise<boolean> {
async _everyOnLayoutReady(): Promise<boolean> {
try {
const isRedFlagRaised = await this.isRedFlagRaised();
const isRedFlag2Raised = await this.isRedFlag2Raised();
@@ -63,7 +63,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
)) !== "yes"
) {
await this.deleteRedFlag2();
await this.core.$$performRestart();
this.services.appLifecycle.performRestart();
return false;
}
}
@@ -75,13 +75,13 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
})) !== "yes"
) {
await this.deleteRedFlag3();
await this.core.$$performRestart();
this.services.appLifecycle.performRestart();
return false;
}
}
this.settings.batchSave = false;
await this.core.$allSuspendAllSync();
await this.core.$allSuspendExtraSync();
await this.services.setting.suspendAllSync();
await this.services.setting.suspendExtraSync();
this.settings.suspendFileWatching = true;
await this.saveSettings();
if (isRedFlag2Raised) {
@@ -99,7 +99,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
) {
this.settings.suspendFileWatching = false;
await this.saveSettings();
this.core.$$performRestart();
this.services.appLifecycle.performRestart();
return false;
}
} else if (isRedFlag3Raised) {
@@ -148,7 +148,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
if (fetchRemote === optionFetchRemoteConf) {
this._log("Fetching remote configuration", LOG_LEVEL_NOTICE);
const newSettings = JSON.parse(JSON.stringify(this.core.settings)) as ObsidianLiveSyncSettings;
const remoteConfig = await this.core.$$fetchRemotePreferredTweakValues(newSettings);
const remoteConfig = await this.services.tweakValue.fetchRemotePreferred(newSettings);
if (remoteConfig) {
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
const mergedSettings = {
@@ -174,7 +174,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
) {
this.settings.suspendFileWatching = false;
await this.saveSettings();
this.core.$$performRestart();
this.services.appLifecycle.performRestart();
return false;
}
} else {
@@ -198,4 +198,8 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
}
return true;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
super.onBindFunction(core, services);
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
}
}

View File

@@ -1,16 +1,22 @@
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
export class ModuleRemoteGovernor extends AbstractModule implements ICoreModule {
async $$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
export class ModuleRemoteGovernor extends AbstractModule {
private async _markRemoteLocked(lockByClean: boolean = false): Promise<void> {
return await this.core.replicator.markRemoteLocked(this.settings, true, lockByClean);
}
async $$markRemoteUnlocked(): Promise<void> {
private async _markRemoteUnlocked(): Promise<void> {
return await this.core.replicator.markRemoteLocked(this.settings, false, false);
}
async $$markRemoteResolved(): Promise<void> {
private async _markRemoteResolved(): Promise<void> {
return await this.core.replicator.markRemoteResolved(this.settings);
}
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.remote.handleMarkLocked(this._markRemoteLocked.bind(this));
services.remote.handleMarkUnlocked(this._markRemoteUnlocked.bind(this));
services.remote.handleMarkResolved(this._markRemoteResolved.bind(this));
}
}

View File

@@ -11,21 +11,22 @@ import {
} from "../../lib/src/common/types.ts";
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
import { $msg } from "../../lib/src/common/i18n.ts";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleResolvingMismatchedTweaks extends AbstractModule implements ICoreModule {
async $anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
export class ModuleResolvingMismatchedTweaks extends AbstractModule {
async _anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
if (!this.core.replicator.tweakSettingsMismatched && !this.core.replicator.preferredTweakValue) return false;
const preferred = this.core.replicator.preferredTweakValue;
if (!preferred) return false;
const ret = await this.core.$$askResolvingMismatchedTweaks(preferred);
const ret = await this.services.tweakValue.askResolvingMismatched(preferred);
if (ret == "OK") return false;
if (ret == "CHECKAGAIN") return "CHECKAGAIN";
if (ret == "IGNORE") return true;
}
async $$checkAndAskResolvingMismatchedTweaks(
async _checkAndAskResolvingMismatchedTweaks(
preferred: Partial<TweakValues>
): Promise<[TweakValues | boolean, boolean]> {
const mine = extractObject(TweakValuesShouldMatchedTemplate, this.settings);
@@ -127,7 +128,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
return CHOICES[retKey];
}
async $$askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
async _askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
if (!this.core.replicator.tweakSettingsMismatched) {
return "OK";
}
@@ -137,7 +138,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
}
const preferred = extractObject(TweakValuesShouldMatchedTemplate, tweaks);
const [conf, rebuildRequired] = await this.core.$$checkAndAskResolvingMismatchedTweaks(preferred);
const [conf, rebuildRequired] = await this.services.tweakValue.checkAndAskResolvingMismatched(preferred);
if (!conf) return "IGNORE";
if (conf === true) {
@@ -154,7 +155,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
if (conf) {
this.settings = { ...this.settings, ...conf };
await this.core.replicator.setPreferredRemoteTweakSettings(this.settings);
await this.core.$$saveSettingData();
await this.services.setting.saveSettingData();
if (rebuildRequired) {
await this.core.rebuilder.$fetchLocal();
}
@@ -164,8 +165,12 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
return "IGNORE";
}
async $$fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
const replicator = await this.core.$anyNewReplicator();
async _fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
if (!replicator) {
this._log("The remote type is not supported for fetching preferred tweak values.", LOG_LEVEL_NOTICE);
return false;
}
if (await replicator.tryConnectRemote(trialSetting)) {
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
if (preferred) {
@@ -178,17 +183,17 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
return false;
}
async $$checkAndAskUseRemoteConfiguration(
async _checkAndAskUseRemoteConfiguration(
trialSetting: RemoteDBSettings
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
const preferred = await this.core.$$fetchRemotePreferredTweakValues(trialSetting);
const preferred = await this.services.tweakValue.fetchRemotePreferred(trialSetting);
if (preferred) {
return await this.$$askUseRemoteConfiguration(trialSetting, preferred);
return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred);
}
return { result: false, requireFetch: false };
}
async $$askUseRemoteConfiguration(
async _askUseRemoteConfiguration(
trialSetting: RemoteDBSettings,
preferred: TweakValues
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
@@ -278,4 +283,13 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
}
return { result: false, requireFetch: false };
}
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.tweakValue.handleFetchRemotePreferred(this._fetchRemotePreferredTweakValues.bind(this));
services.tweakValue.handleCheckAndAskResolvingMismatched(this._checkAndAskResolvingMismatchedTweaks.bind(this));
services.tweakValue.handleAskResolvingMismatched(this._askResolvingMismatchedTweaks.bind(this));
services.tweakValue.handleCheckAndAskUseRemoteConfiguration(this._checkAndAskUseRemoteConfiguration.bind(this));
services.tweakValue.handleAskUseRemoteConfiguration(this._askUseRemoteConfiguration.bind(this));
services.replication.handleCheckConnectionFailure(this._anyAfterConnectCheckFailed.bind(this));
}
}

View File

@@ -1,6 +1,6 @@
import { TFile, TFolder, type ListedFiles } from "obsidian";
import { SerializedFileAccess } from "./storageLib/SerializedFileAccess";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import type {
FilePath,
@@ -16,10 +16,13 @@ import { StorageEventManagerObsidian, type StorageEventManager } from "./storage
import type { StorageAccess } from "../interfaces/StorageAccess";
import { createBlob, type CustomRegExp } from "../../lib/src/common/utils";
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
import type { LiveSyncCore } from "../../main.ts";
import type ObsidianLiveSyncPlugin from "../../main.ts";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
const fileLockPrefix = "file-lock:";
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements StorageAccess {
processingFiles: Set<FilePathWithPrefix> = new Set();
processWriteFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T> {
const path = typeof file === "string" ? file : file.path;
@@ -49,39 +52,35 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
}
vaultAccess!: SerializedFileAccess;
vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core, this);
$everyOnload(): Promise<boolean> {
private _everyOnload(): Promise<boolean> {
this.core.storageAccess = this;
return Promise.resolve(true);
}
$everyOnFirstInitialize(): Promise<boolean> {
_everyOnFirstInitialize(): Promise<boolean> {
this.vaultManager.beginWatch();
return Promise.resolve(true);
}
$allOnUnload(): Promise<boolean> {
// this.vaultManager.
return Promise.resolve(true);
}
// $$flushFileEventQueue(): void {
// this.vaultManager.flushQueue();
// }
$everyCommitPendingFileEvent(): Promise<boolean> {
_everyCommitPendingFileEvent(): Promise<boolean> {
this.vaultManager.flushQueue();
return Promise.resolve(true);
}
$everyOnloadStart(): Promise<boolean> {
_everyOnloadStart(): Promise<boolean> {
this.vaultAccess = new SerializedFileAccess(this.app, this.plugin, this);
return Promise.resolve(true);
}
$$isStorageInsensitive(): boolean {
_isStorageInsensitive(): boolean {
return this.vaultAccess.isStorageInsensitive();
}
$$shouldCheckCaseInsensitive(): boolean {
if (this.$$isStorageInsensitive()) return false;
_shouldCheckCaseInsensitive(): boolean {
if (this.services.vault.isStorageInsensitive()) return false;
return !this.settings.handleFilenameCaseSensitive;
}
@@ -223,6 +222,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
return null;
}
}
async readStubContent(stub: UXFileInfoStub): Promise<UXFileInfo | false> {
const file = this.vaultAccess.getAbstractFileByPath(stub.path);
if (!(file instanceof TFile)) {
@@ -232,6 +232,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
const data = await this.vaultAccess.vaultReadAuto(file);
return {
...stub,
...TFileToUXFileInfoStub(file),
body: createBlob(data),
};
}
@@ -275,7 +276,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
if (excludeFilter && excludeFilter.some((ee) => ee.test(file))) {
continue;
}
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
if (await this.services.vault.isIgnoredByIgnoreFile(file)) continue;
files.push(file);
}
@@ -288,7 +289,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
if (excludeFilter && excludeFilter.some((e) => e.test(v))) {
continue;
}
if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) {
if (await this.services.vault.isIgnoredByIgnoreFile(v)) {
continue;
}
// OK, deep dive!
@@ -344,9 +345,9 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
// }
// }
async _deleteVaultItem(file: TFile | TFolder) {
async __deleteVaultItem(file: TFile | TFolder) {
if (file instanceof TFile) {
if (!(await this.core.$$isTargetFile(file.path))) return;
if (!(await this.services.vault.isTargetFile(file.path))) return;
}
const dir = file.parent;
if (this.settings.trashInsteadDelete) {
@@ -362,7 +363,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
this._log(
`All files under the parent directory (${dir.path}) have been deleted, so delete this one.`
);
await this._deleteVaultItem(dir);
await this.__deleteVaultItem(dir);
}
}
}
@@ -373,7 +374,19 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
const file = this.vaultAccess.getAbstractFileByPath(path);
if (file === null) return;
if (file instanceof TFile || file instanceof TFolder) {
return await this._deleteVaultItem(file);
return await this.__deleteVaultItem(file);
}
}
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
super(plugin, core);
}
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.vault.handleIsStorageInsensitive(this._isStorageInsensitive.bind(this));
services.setting.handleShouldCheckCaseInsensitively(this._shouldCheckCaseInsensitive.bind(this));
services.appLifecycle.handleFirstInitialise(this._everyOnFirstInitialize.bind(this));
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
services.fileProcessing.handleCommitPendingFileEvents(this._everyCommitPendingFileEvent.bind(this));
}
}

View File

@@ -1,5 +1,5 @@
// ModuleInputUIObsidian.ts
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject } from "../../common/utils.ts";
import {
@@ -13,12 +13,13 @@ import { Notice } from "../../deps.ts";
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
import { setConfirmInstance } from "../../lib/src/PlatformAPIs/obsidian/Confirm.ts";
import { $msg } from "src/lib/src/common/i18n.ts";
import type { LiveSyncCore } from "../../main.ts";
// This module cannot be a common module because it depends on Obsidian's API.
// However, we have to make compatible one for other platform.
export class ModuleInputUIObsidian extends AbstractObsidianModule implements IObsidianModule, Confirm {
$everyOnload(): Promise<boolean> {
export class ModuleInputUIObsidian extends AbstractObsidianModule implements Confirm {
private _everyOnload(): Promise<boolean> {
this.core.confirm = this;
setConfirmInstance(this);
return Promise.resolve(true);
@@ -110,4 +111,8 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
): Promise<(typeof buttons)[number] | false> {
return confirmWithMessage(this.plugin, title, contentMd, buttons, defaultAction, timeout);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
}
}

View File

@@ -7,13 +7,14 @@ import {
LOG_LEVEL_INFO,
LOG_LEVEL_NOTICE,
LOG_LEVEL_VERBOSE,
type FileEventType,
type FilePath,
type FilePathWithPrefix,
type UXFileInfoStub,
type UXInternalFileInfoStub,
} from "../../../lib/src/common/types.ts";
import { delay, fireAndForget, getFileRegExp } from "../../../lib/src/common/utils.ts";
import { type FileEventItem, type FileEventType } from "../../../common/types.ts";
import { type FileEventItem } from "../../../common/types.ts";
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import {
finishAllWaitingForTimeout,
@@ -48,6 +49,9 @@ export class StorageEventManagerObsidian extends StorageEventManager {
plugin: ObsidianLiveSyncPlugin;
core: LiveSyncCore;
storageAccess: StorageAccess;
get services() {
return this.core.services;
}
get shouldBatchSave() {
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
@@ -175,7 +179,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
if (this.plugin.settings.useIgnoreFiles) {
// If it is one of ignore files, refresh the cached one.
// (Calling$$isTargetFile will refresh the cache)
void this.plugin.$$isTargetFile(path).then(() => this._watchVaultRawEvents(path));
void this.services.vault.isTargetFile(path).then(() => this._watchVaultRawEvents(path));
} else {
this._watchVaultRawEvents(path);
}
@@ -209,7 +213,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
async appendQueue(params: FileEvent[], ctx?: any) {
if (!this.core.settings.isConfigured) return;
if (this.core.settings.suspendFileWatching) return;
this.core.$$markFileListPossiblyChanged();
this.core.services.vault.markFileListPossiblyChanged();
// Flag up to be reload
const processFiles = new Set<FilePath>();
for (const param of params) {
@@ -222,7 +226,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
const oldPath = param.oldPath;
if (type !== "INTERNAL") {
const size = (file as UXFileInfoStub).stat.size;
if (this.core.$$isFileSizeExceeded(size) && (type == "CREATE" || type == "CHANGED")) {
if (this.services.vault.isFileSizeTooLarge(size) && (type == "CREATE" || type == "CHANGED")) {
Logger(
`The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`,
LOG_LEVEL_NOTICE
@@ -234,7 +238,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
// TODO: Confirm why only the TFolder skipping
// Possibly following line is needed...
// if (file?.isFolder) continue;
if (!(await this.core.$$isTargetFile(file.path))) continue;
if (!(await this.services.vault.isTargetFile(file.path))) continue;
// Stop cache using to prevent the corruption;
// let cache: null | string | ArrayBuffer;
@@ -291,7 +295,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
concurrentProcessing = Semaphore(5);
waitedSince = new Map<FilePath | FilePathWithPrefix, number>();
async startStandingBy(filename: FilePath) {
// If waited, cancel previous waiting.
// If waited, no need to start again (looping inside the function)
await skipIfDuplicated(`storage-event-manager-${filename}`, async () => {
Logger(`Processing ${filename}: Starting`, LOG_LEVEL_DEBUG);
const release = await this.concurrentProcessing.acquire();
@@ -311,6 +315,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
// continue;
// }
const type = target.type;
// If already cancelled by other operation, skip this.
if (target.cancelled) {
Logger(`Processing ${filename}: Cancelled (scheduled): ${operationType}`, LOG_LEVEL_DEBUG);
this.cancelStandingBy(target);
@@ -411,12 +416,12 @@ export class StorageEventManagerObsidian extends StorageEventManager {
const lockKey = `handleFile:${file.path}`;
return await serialized(lockKey, async () => {
if (queue.type == "INTERNAL" || file.isInternal) {
await this.core.$anyProcessOptionalFileEvent(file.path as unknown as FilePath);
await this.core.services.fileProcessing.processOptionalFileEvent(file.path as unknown as FilePath);
} else {
const key = `file-last-proc-${queue.type}-${file.path}`;
const last = Number((await this.core.kvDB.get(key)) || 0);
if (queue.type == "DELETE") {
await this.core.$anyHandlerProcessesFileEvent(queue);
await this.core.services.fileProcessing.processFileEvent(queue);
} else {
if (file.stat.mtime == last) {
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
@@ -424,7 +429,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
// this.cancelRelativeEvent(queue);
return;
}
if (!(await this.core.$anyHandlerProcessesFileEvent(queue))) {
if (!(await this.core.services.fileProcessing.processFileEvent(queue))) {
Logger(
`STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`,
LOG_LEVEL_INFO

View File

@@ -17,10 +17,11 @@ import {
import { isAnyNote } from "../../lib/src/common/utils.ts";
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
import { withConcurrency } from "octagonal-wheels/iterable/map";
export class ModuleInitializerFile extends AbstractModule implements ICoreModule {
async $$performFullScan(showingNotice?: boolean, ignoreSuspending: boolean = false): Promise<void> {
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleInitializerFile extends AbstractModule {
private async _performFullScan(showingNotice?: boolean, ignoreSuspending: boolean = false): Promise<boolean> {
this._log("Opening the key-value database", LOG_LEVEL_VERBOSE);
const isInitialized = (await this.core.kvDB.get<boolean>("initialized")) || false;
// synchronize all files between database and storage.
@@ -32,7 +33,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
"syncAll"
);
}
return;
return false;
}
if (!ignoreSuspending && this.settings.suspendFileWatching) {
if (showingNotice) {
@@ -42,7 +43,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
"syncAll"
);
}
return;
return false;
}
if (showingNotice) {
@@ -59,7 +60,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
const _filesStorage = [] as typeof filesStorageSrc;
for (const f of filesStorageSrc) {
if (await this.core.$$isTargetFile(f.path, f != filesStorageSrc[0])) {
if (await this.services.vault.isTargetFile(f.path, f != filesStorageSrc[0])) {
_filesStorage.push(f);
}
}
@@ -103,7 +104,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
);
const path = getPath(doc);
if (isValidPath(path) && (await this.core.$$isTargetFile(path, true))) {
if (isValidPath(path) && (await this.services.vault.isTargetFile(path, true))) {
if (!isMetaEntry(doc)) {
this._log(`Invalid entry: ${path}`, LOG_LEVEL_INFO);
continue;
@@ -133,7 +134,6 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
this._log(`Total files in the database: ${databaseFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
this._log(`Total files in the storage: ${storageFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
this._log(`Total files: ${allFiles.length}`, LOG_LEVEL_VERBOSE, "syncAll");
const filesExistOnlyInStorage = allFiles.filter((e) => !databaseFileNameCI2CS[e]);
const filesExistOnlyInDatabase = allFiles.filter((e) => !storageFileNameCI2CS[e]);
const filesExistBoth = allFiles.filter((e) => databaseFileNameCI2CS[e] && storageFileNameCI2CS[e]);
@@ -192,7 +192,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
runAll("UPDATE DATABASE", filesExistOnlyInStorage, async (e) => {
// Exists in storage but not in database.
const file = storageFileNameMap[storageFileNameCI2CS[e]];
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
if (!this.services.vault.isFileSizeTooLarge(file.stat.size)) {
const path = file.path;
await this.core.fileHandler.storeFileToDB(file);
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(path, true));
@@ -208,7 +208,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
// Exists in database but not in storage.
const path = getPath(w) ?? e;
if (w && !(w.deleted || w._deleted)) {
if (!this.core.$$isFileSizeExceeded(w.size)) {
if (!this.services.vault.isFileSizeTooLarge(w.size)) {
// Prevent applying the conflicted state to the storage.
if (w._conflicts?.length ?? 0 > 0) {
this._log(`UPDATE STORAGE: ${path} has conflicts. skipped (x)`, LOG_LEVEL_INFO);
@@ -250,7 +250,10 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
this._log(`SYNC DATABASE AND STORAGE: ${file.path} has conflicts. skipped`, LOG_LEVEL_INFO);
return;
}
if (!this.core.$$isFileSizeExceeded(file.stat.size) && !this.core.$$isFileSizeExceeded(doc.size)) {
if (
!this.services.vault.isFileSizeTooLarge(file.stat.size) &&
!this.services.vault.isFileSizeTooLarge(doc.size)
) {
await this.syncFileBetweenDBandStorage(file, doc);
} else {
this._log(
@@ -271,6 +274,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
if (showingNotice) {
this._log("Initialize done!", LOG_LEVEL_NOTICE, "syncAll");
}
return true;
}
async syncFileBetweenDBandStorage(file: UXFileInfoStub, doc: MetaEntry) {
@@ -289,7 +293,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
const compareResult = compareFileFreshness(file, doc);
switch (compareResult) {
case BASE_IS_NEW:
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
if (!this.services.vault.isFileSizeTooLarge(file.stat.size)) {
this._log("STORAGE -> DB :" + file.path);
await this.core.fileHandler.storeFileToDB(file);
} else {
@@ -300,7 +304,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
}
break;
case TARGET_IS_NEW:
if (!this.core.$$isFileSizeExceeded(doc.size)) {
if (!this.services.vault.isFileSizeTooLarge(doc.size)) {
this._log("STORAGE <- DB :" + file.path);
if (await this.core.fileHandler.dbToStorage(doc, stripAllPrefixes(file.path), true)) {
eventHub.emitEvent("event-file-changed", {
@@ -365,27 +369,31 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
this._log(`Checking expired file history done`);
}
async $$initializeDatabase(
private async _initializeDatabase(
showingNotice: boolean = false,
reopenDatabase = true,
ignoreSuspending: boolean = false
): Promise<boolean> {
this.core.$$resetIsReady();
if (!reopenDatabase || (await this.core.$$openDatabase())) {
this.services.appLifecycle.resetIsReady();
if (!reopenDatabase || (await this.services.database.openDatabase())) {
if (this.localDatabase.isReady) {
await this.core.$$performFullScan(showingNotice, ignoreSuspending);
await this.services.vault.scanVault(showingNotice, ignoreSuspending);
}
if (!(await this.core.$everyOnDatabaseInitialized(showingNotice))) {
this._log(`Initializing database has been failed on some module`, LOG_LEVEL_NOTICE);
if (!(await this.services.databaseEvents.onDatabaseInitialised(showingNotice))) {
this._log(`Initializing database has been failed on some module!`, LOG_LEVEL_NOTICE);
return false;
}
this.core.$$markIsReady();
this.services.appLifecycle.markIsReady();
// run queued event once.
await this.core.$everyCommitPendingFileEvent();
await this.services.fileProcessing.commitPendingFileEvents();
return true;
} else {
this.core.$$resetIsReady();
this.services.appLifecycle.resetIsReady();
return false;
}
}
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.databaseEvents.handleInitialiseDatabase(this._initializeDatabase.bind(this));
services.vault.handleScanVault(this._performFullScan.bind(this));
}
}

View File

@@ -3,9 +3,9 @@ import { OpenKeyValueDatabase } from "../../common/KeyValueDB.ts";
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
export class ModuleKeyValueDB extends AbstractModule {
tryCloseKvDB() {
try {
this.core.kvDB?.close();
@@ -22,7 +22,7 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
this.tryCloseKvDB();
await delay(10);
await yieldMicrotask();
this.core.kvDB = await OpenKeyValueDatabase(this.core.$$getVaultName() + "-livesync-kv");
this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv");
await yieldMicrotask();
await delay(100);
} catch (e) {
@@ -33,21 +33,23 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
}
return true;
}
$allOnDBUnload(db: LiveSyncLocalDB): void {
_onDBUnload(db: LiveSyncLocalDB) {
if (this.core.kvDB) this.core.kvDB.close();
return Promise.resolve(true);
}
$allOnDBClose(db: LiveSyncLocalDB): void {
_onDBClose(db: LiveSyncLocalDB) {
if (this.core.kvDB) this.core.kvDB.close();
return Promise.resolve(true);
}
async $everyOnloadAfterLoadSettings(): Promise<boolean> {
private async _everyOnloadAfterLoadSettings(): Promise<boolean> {
if (!(await this.openKeyValueDB())) {
return false;
}
this.core.simpleStore = this.core.$$getSimpleStore<any>("os");
this.core.simpleStore = this.services.database.openSimpleStore<any>("os");
return Promise.resolve(true);
}
$$getSimpleStore<T>(kind: string) {
_getSimpleStore<T>(kind: string) {
const prefix = `${kind}-`;
return {
get: async (key: string): Promise<T> => {
@@ -75,18 +77,18 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
},
};
}
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
_everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
return this.openKeyValueDB();
}
async $everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
async _everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
try {
const kvDBKey = "queued-files";
await this.core.kvDB.del(kvDBKey);
// localStorage.removeItem(lsKey);
await this.core.kvDB.destroy();
await yieldMicrotask();
this.core.kvDB = await OpenKeyValueDatabase(this.core.$$getVaultName() + "-livesync-kv");
this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv");
await delay(100);
} catch (e) {
this.core.kvDB = undefined!;
@@ -96,4 +98,12 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
}
return true;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.databaseEvents.handleOnUnloadDatabase(this._onDBUnload.bind(this));
services.databaseEvents.handleOnCloseDatabase(this._onDBClose.bind(this));
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
services.databaseEvents.handleOnResetDatabase(this._everyOnResetDatabase.bind(this));
services.database.handleOpenSimpleStore(this._getSimpleStore.bind(this));
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
}
}

View File

@@ -9,13 +9,13 @@ import {
eventHub,
} from "../../common/events.ts";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
import { $msg } from "src/lib/src/common/i18n.ts";
import { performDoctorConsultation, RebuildOptions } from "../../lib/src/common/configForDoc.ts";
import { getPath, isValidPath } from "../../common/utils.ts";
import { isMetaEntry } from "../../lib/src/common/types.ts";
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts";
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts";
import type { LiveSyncCore } from "../../main.ts";
type ErrorInfo = {
path: string;
@@ -26,7 +26,7 @@ type ErrorInfo = {
isConflicted?: boolean;
};
export class ModuleMigration extends AbstractModule implements ICoreModule {
export class ModuleMigration extends AbstractModule {
async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) {
const { shouldRebuild, shouldRebuildLocal, isModified, settings } = await performDoctorConsultation(
this.core,
@@ -45,11 +45,11 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
if (!skipRebuild) {
if (shouldRebuild) {
await this.core.rebuilder.scheduleRebuild();
await this.core.$$performRestart();
this.services.appLifecycle.performRestart();
return false;
} else if (shouldRebuildLocal) {
await this.core.rebuilder.scheduleFetch();
await this.core.$$performRestart();
this.services.appLifecycle.performRestart();
return false;
}
}
@@ -129,7 +129,7 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
if (!isValidPath(path)) {
continue;
}
if (!(await this.core.$$isTargetFile(path, true))) {
if (!(await this.services.vault.isTargetFile(path, true))) {
continue;
}
if (!isMetaEntry(metaDoc)) {
@@ -257,9 +257,9 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
}
// Check local database for compromised chunks
const localCompromised = await countCompromisedChunks(this.localDatabase.localDatabase);
const remote = this.core.$$getReplicator();
const remote = this.services.replicator.getActiveReplicator();
const remoteCompromised = this.core.managers.networkManager.isOnline
? await remote.countCompromisedChunks()
? await remote?.countCompromisedChunks()
: 0;
if (localCompromised === false) {
Logger(`Failed to count compromised chunks in local database`, LOG_LEVEL_NOTICE);
@@ -293,12 +293,12 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
if (result === REBUILD) {
// Rebuild the database
await this.core.rebuilder.scheduleRebuild();
await this.core.$$performRestart();
this.services.appLifecycle.performRestart();
return false;
} else if (result === FETCH) {
// Fetch the latest data from remote
await this.core.rebuilder.scheduleFetch();
await this.core.$$performRestart();
this.services.appLifecycle.performRestart();
return false;
} else {
// User chose to dismiss the issue
@@ -307,7 +307,7 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
return true;
}
async $everyOnFirstInitialize(): Promise<boolean> {
async _everyOnFirstInitialize(): Promise<boolean> {
if (!this.localDatabase.isReady) {
this._log($msg("moduleMigration.logLocalDatabaseNotReady"), LOG_LEVEL_NOTICE);
return false;
@@ -337,7 +337,7 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
}
return true;
}
$everyOnLayoutReady(): Promise<boolean> {
_everyOnLayoutReady(): Promise<boolean> {
eventHub.onEvent(EVENT_REQUEST_RUN_DOCTOR, async (reason) => {
await this.migrateUsingDoctor(false, reason, true);
});
@@ -346,4 +346,9 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
});
return Promise.resolve(true);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
super.onBindFunction(core, services);
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
services.appLifecycle.handleFirstInitialise(this._everyOnFirstInitialize.bind(this));
}
}

View File

@@ -1,7 +1,7 @@
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { LOG_LEVEL_DEBUG, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts";
import { type CouchDBCredentials, type EntryDoc, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
import { type CouchDBCredentials, type EntryDoc, type FilePath } from "../../lib/src/common/types.ts";
import { getPathFromTFile } from "../../common/utils.ts";
import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
import { replicationFilter } from "@/lib/src/pouchdb/compress.ts";
@@ -11,6 +11,7 @@ import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts";
import { ObsHttpHandler } from "./APILib/ObsHttpHandler.ts";
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts";
import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts";
import type { LiveSyncCore } from "../../main.ts";
setNoticeClass(Notice);
@@ -19,21 +20,21 @@ async function fetchByAPI(request: RequestUrlParam, errorAsResult = false): Prom
return ret;
}
export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidianModule {
export class ModuleObsidianAPI extends AbstractObsidianModule {
_customHandler!: ObsHttpHandler;
_authHeader = new AuthorizationHeaderGenerator();
last_successful_post = false;
$$customFetchHandler(): ObsHttpHandler {
_customFetchHandler(): ObsHttpHandler {
if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
return this._customHandler;
}
$$getLastPostFailedBySize(): boolean {
_getLastPostFailedBySize(): boolean {
return !this.last_successful_post;
}
async _fetchByAPI(url: string, authHeader: string, opts?: RequestInit): Promise<Response> {
async __fetchByAPI(url: string, authHeader: string, opts?: RequestInit): Promise<Response> {
const body = opts?.body as string;
const transformedHeaders = { ...(opts?.headers as Record<string, string>) };
@@ -68,7 +69,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
const body = opts?.body as string;
const size = body ? ` (${body.length})` : "";
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;
if (method == "POST" || method == "PUT") {
this.last_successful_post = r.status - (r.status % 100) == 200;
@@ -90,7 +91,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
}
}
async $$connectRemoteCouchDB(
async _connectRemoteCouchDB(
uri: string,
auth: CouchDBCredentials,
disableRequestURI: boolean,
@@ -148,7 +149,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
try {
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
const response: Response = await (useRequestAPI
? this._fetchByAPI(url.toString(), authHeader, { ...opts, headers })
? this.__fetchByAPI(url.toString(), authHeader, { ...opts, headers })
: fetch(url, { ...opts, headers }));
if (method == "POST" || method == "PUT") {
this.last_successful_post = response.ok;
@@ -252,21 +253,21 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
}
}
$$isMobile(): boolean {
_isMobile(): boolean {
//@ts-ignore : internal API
return this.app.isMobile;
}
$$vaultName(): string {
_vaultName(): string {
return this.app.vault.getName();
}
$$getVaultName(): string {
_getVaultName(): string {
return (
this.core.$$vaultName() +
this.services.vault.vaultName() +
(this.settings?.additionalSuffixOfDatabaseName ? "-" + this.settings.additionalSuffixOfDatabaseName : "")
);
}
$$getActiveFilePath(): FilePathWithPrefix | undefined {
_getActiveFilePath(): FilePath | undefined {
const file = this.app.workspace.getActiveFile();
if (file) {
return getPathFromTFile(file);
@@ -274,7 +275,18 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
return undefined;
}
$anyGetAppId(): Promise<string | undefined> {
return Promise.resolve(`${"appId" in this.app ? this.app.appId : ""}`);
_anyGetAppId(): string {
return `${"appId" in this.app ? this.app.appId : ""}`;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
services.API.handleGetCustomFetchHandler(this._customFetchHandler.bind(this));
services.API.handleIsLastPostFailedDueToPayloadSize(this._getLastPostFailedBySize.bind(this));
services.remote.handleConnect(this._connectRemoteCouchDB.bind(this));
services.API.handleIsMobile(this._isMobile.bind(this));
services.vault.handleGetVaultName(this._getVaultName.bind(this));
services.vault.handleVaultName(this._vaultName.bind(this));
services.vault.handleGetActiveFilePath(this._getActiveFilePath.bind(this));
services.API.handleGetAppID(this._anyGetAppId.bind(this));
}
}

View File

@@ -1,4 +1,4 @@
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { EVENT_FILE_RENAMED, EVENT_LEAF_ACTIVE_CHANGED, eventHub } from "../../common/events.js";
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import { scheduleTask } from "octagonal-wheels/concurrency/task";
@@ -12,9 +12,10 @@ import {
hiddenFilesEventCount,
hiddenFilesProcessingCount,
} from "../../lib/src/mock_and_interop/stores.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleObsidianEvents extends AbstractObsidianModule implements IObsidianModule {
$everyOnloadStart(): Promise<boolean> {
export class ModuleObsidianEvents extends AbstractObsidianModule {
_everyOnloadStart(): Promise<boolean> {
// this.registerEvent(this.app.workspace.on("editor-change", ));
this.plugin.registerEvent(
this.app.vault.on("rename", (file, oldPath) => {
@@ -30,11 +31,11 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
return Promise.resolve(true);
}
$$performRestart(): void {
this._performAppReload();
private _performRestart(): void {
this.__performAppReload();
}
_performAppReload() {
__performAppReload() {
//@ts-ignore
this.app.commands.executeCommandById("app:reload");
}
@@ -49,14 +50,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
this.initialCallback = save;
saveCommandDefinition.callback = () => {
scheduleTask("syncOnEditorSave", 250, () => {
if (this.core.$$isUnloaded()) {
if (this.services.appLifecycle.hasUnloaded()) {
this._log("Unload and remove the handler.", LOG_LEVEL_VERBOSE);
saveCommandDefinition.callback = this.initialCallback;
this.initialCallback = undefined;
} else {
if (this.settings.syncOnEditorSave) {
this._log("Sync on Editor Save.", LOG_LEVEL_VERBOSE);
fireAndForget(() => this.core.$$replicateByEvent());
fireAndForget(() => this.services.replication.replicateByEvent());
}
}
});
@@ -106,14 +107,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
// TODO:FIXME AT V0.17.31, this logic has been disabled.
if (navigator.onLine && this.localDatabase.needScanning) {
this.localDatabase.needScanning = false;
await this.core.$$performFullScan();
await this.services.vault.scanVault();
}
}
async watchWindowVisibilityAsync() {
if (this.settings.suspendFileWatching) return;
if (!this.settings.isConfigured) return;
if (!this.core.$$isReady()) return;
if (!this.services.appLifecycle.isReady()) return;
if (this.isLastHidden && !this.hasFocus) {
// NO OP while non-focused after made hidden;
@@ -126,22 +127,22 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
}
this.isLastHidden = isHidden;
await this.core.$everyCommitPendingFileEvent();
await this.services.fileProcessing.commitPendingFileEvents();
if (isHidden) {
await this.core.$everyBeforeSuspendProcess();
await this.services.appLifecycle.onSuspending();
} else {
// suspend all temporary.
if (this.core.$$isSuspended()) return;
if (this.services.appLifecycle.isSuspended()) return;
if (!this.hasFocus) return;
await this.core.$everyOnResumeProcess();
await this.core.$everyAfterResumeProcess();
await this.services.appLifecycle.onResuming();
await this.services.appLifecycle.onResumed();
}
}
watchWorkspaceOpen(file: TFile | null) {
if (this.settings.suspendFileWatching) return;
if (!this.settings.isConfigured) return;
if (!this.core.$$isReady()) return;
if (!this.services.appLifecycle.isReady()) return;
if (!file) return;
scheduleTask("watch-workspace-open", 500, () => fireAndForget(() => this.watchWorkspaceOpenAsync(file)));
}
@@ -149,25 +150,25 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
async watchWorkspaceOpenAsync(file: TFile) {
if (this.settings.suspendFileWatching) return;
if (!this.settings.isConfigured) return;
if (!this.core.$$isReady()) return;
await this.core.$everyCommitPendingFileEvent();
if (!this.services.appLifecycle.isReady()) return;
await this.services.fileProcessing.commitPendingFileEvents();
if (file == null) {
return;
}
if (this.settings.syncOnFileOpen && !this.core.$$isSuspended()) {
await this.core.$$replicateByEvent();
if (this.settings.syncOnFileOpen && !this.services.appLifecycle.isSuspended()) {
await this.services.replication.replicateByEvent();
}
await this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix);
await this.services.conflict.queueCheckForIfOpen(file.path as FilePathWithPrefix);
}
$everyOnLayoutReady(): Promise<boolean> {
_everyOnLayoutReady(): Promise<boolean> {
this.swapSaveCommand();
this.registerWatchEvents();
return Promise.resolve(true);
}
$$askReload(message?: string) {
if (this.core.$$isReloadingScheduled()) {
private _askReload(message?: string) {
if (this.services.appLifecycle.isReloadingScheduled()) {
this._log(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE);
return;
}
@@ -181,13 +182,13 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
{ defaultAction: RETRY_LATER }
);
if (ret == RESTART_NOW) {
this._performAppReload();
this.__performAppReload();
} else if (ret == RESTART_AFTER_STABLE) {
this.core.$$scheduleAppReload();
this.services.appLifecycle.scheduleRestart();
}
});
}
$$scheduleAppReload() {
private _scheduleAppReload() {
if (!this.core._totalProcessingCount) {
const __tick = reactiveSource(0);
this.core._totalProcessingCount = reactive(() => {
@@ -224,7 +225,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
this.core._totalProcessingCount.onChanged((e) => {
if (e.value == 0) {
if (stableCheck-- <= 0) {
this._performAppReload();
this.__performAppReload();
}
this._log(
`Obsidian will be restarted soon! (Within ${stableCheck} seconds)`,
@@ -237,4 +238,11 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
});
}
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
services.appLifecycle.handlePerformRestart(this._performRestart.bind(this));
services.appLifecycle.handleAskRestart(this._askReload.bind(this));
services.appLifecycle.handleScheduleRestart(this._scheduleAppReload.bind(this));
}
}

View File

@@ -1,11 +1,12 @@
import { fireAndForget } from "octagonal-wheels/promises";
import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts";
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { $msg } from "src/lib/src/common/i18n.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsidianModule {
$everyOnloadStart(): Promise<boolean> {
export class ModuleObsidianMenu extends AbstractObsidianModule {
_everyOnloadStart(): Promise<boolean> {
// UI
addIcon(
"replicate",
@@ -18,21 +19,21 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
);
this.addRibbonIcon("replicate", $msg("moduleObsidianMenu.replicate"), async () => {
await this.core.$$replicate(true);
await this.services.replication.replicate(true);
}).addClass("livesync-ribbon-replicate");
this.addCommand({
id: "livesync-replicate",
name: "Replicate now",
callback: async () => {
await this.core.$$replicate();
await this.services.replication.replicate();
},
});
this.addCommand({
id: "livesync-dump",
name: "Dump information of this doc ",
callback: () => {
const file = this.core.$$getActiveFilePath();
const file = this.services.vault.getActiveFilePath();
if (!file) return;
fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false));
},
@@ -43,7 +44,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => {
const file = view.file;
if (!file) return;
void this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix);
void this.services.conflict.queueCheckForIfOpen(file.path as FilePathWithPrefix);
},
});
@@ -58,23 +59,23 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
this.settings.liveSync = true;
this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE);
}
await this.core.$$realizeSettingSyncMode();
await this.core.$$saveSettingData();
await this.services.setting.onRealiseSetting();
await this.services.setting.saveSettingData();
},
});
this.addCommand({
id: "livesync-suspendall",
name: "Toggle All Sync.",
callback: async () => {
if (this.core.$$isSuspended()) {
this.core.$$setSuspended(false);
if (this.services.appLifecycle.isSuspended()) {
this.services.appLifecycle.setSuspended(false);
this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE);
} else {
this.core.$$setSuspended(true);
this.services.appLifecycle.setSuspended(true);
this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE);
}
await this.core.$$realizeSettingSyncMode();
await this.core.$$saveSettingData();
await this.services.setting.onRealiseSetting();
await this.services.setting.saveSettingData();
},
});
@@ -82,7 +83,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
id: "livesync-scan-files",
name: "Scan storage and database again",
callback: async () => {
await this.core.$$performFullScan(true);
await this.services.vault.scanVault(true);
},
});
@@ -90,7 +91,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
id: "livesync-runbatch",
name: "Run pended batch processes",
callback: async () => {
await this.core.$everyCommitPendingFileEvent();
await this.services.fileProcessing.commitPendingFileEvents();
},
});
@@ -104,12 +105,15 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
});
return Promise.resolve(true);
}
$everyOnload(): Promise<boolean> {
this.app.workspace.onLayoutReady(this.core.$$onLiveSyncReady.bind(this.core));
private __onWorkspaceReady() {
void this.services.appLifecycle.onReady();
}
private _everyOnload(): Promise<boolean> {
this.app.workspace.onLayoutReady(this.__onWorkspaceReady.bind(this));
return Promise.resolve(true);
}
async $$showView(viewType: string) {
private async _showView(viewType: string) {
const leaves = this.app.workspace.getLeavesOfType(viewType);
if (leaves.length == 0) {
await this.app.workspace.getLeaf(true).setViewState({
@@ -126,4 +130,9 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
await this.app.workspace.revealLeaf(leaves[0]);
}
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
services.API.handleShowWindow(this._showView.bind(this));
}
}

View File

@@ -1,12 +1,18 @@
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import type { LiveSyncCore } from "../../main.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
export class ModuleExtraSyncObsidian extends AbstractObsidianModule implements IObsidianModule {
export class ModuleExtraSyncObsidian extends AbstractObsidianModule {
deviceAndVaultName: string = "";
$$getDeviceAndVaultName(): string {
_getDeviceAndVaultName(): string {
return this.deviceAndVaultName;
}
$$setDeviceAndVaultName(name: string): void {
_setDeviceAndVaultName(name: string): void {
this.deviceAndVaultName = name;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.setting.handleGetDeviceAndVaultName(this._getDeviceAndVaultName.bind(this));
services.setting.handleSetDeviceAndVaultName(this._setDeviceAndVaultName.bind(this));
}
}

View File

@@ -1,15 +1,16 @@
import { delay, fireAndForget } from "octagonal-wheels/promises";
import { __onMissingTranslation } from "../../lib/src/common/i18n";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import { eventHub } from "../../common/events";
import { enableTestFunction } from "./devUtil/testUtils.ts";
import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts";
import { writable } from "svelte/store";
import type { FilePathWithPrefix } from "../../lib/src/common/types.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleDev extends AbstractObsidianModule implements IObsidianModule {
$everyOnloadStart(): Promise<boolean> {
export class ModuleDev extends AbstractObsidianModule {
_everyOnloadStart(): Promise<boolean> {
__onMissingTranslation(() => {});
return Promise.resolve(true);
}
@@ -35,7 +36,7 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
}
}
$everyOnloadAfterLoadSettings(): Promise<boolean> {
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
if (!this.settings.enableDebugTools) return Promise.resolve(true);
this.onMissingTranslation = this.onMissingTranslation.bind(this);
__onMissingTranslation((key) => {
@@ -92,12 +93,12 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
id: "view-test",
name: "Open Test dialogue",
callback: () => {
void this.core.$$showView(VIEW_TYPE_TEST);
void this.services.API.showWindow(VIEW_TYPE_TEST);
},
});
return Promise.resolve(true);
}
async $everyOnLayoutReady(): Promise<boolean> {
async _everyOnLayoutReady(): Promise<boolean> {
if (!this.settings.enableDebugTools) return Promise.resolve(true);
// if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) {
// void this.core.$$showView(VIEW_TYPE_TEST);
@@ -121,7 +122,7 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
},
});
if (w) {
const id = await this.core.$$path2id(filename as FilePathWithPrefix);
const id = await this.services.path.path2id(filename as FilePathWithPrefix);
const f = await this.core.localDatabase.getRaw(id);
console.log(f);
console.log(f._rev);
@@ -139,14 +140,14 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
testResults = writable<[boolean, string, string][]>([]);
// testResults: string[] = [];
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
private _addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
const logLine = `${name}: ${key} ${summary ?? ""}`;
this.testResults.update((results) => {
results.push([result, logLine, message ?? ""]);
return results;
});
}
$everyModuleTest(): Promise<boolean> {
private _everyModuleTest(): Promise<boolean> {
if (!this.settings.enableDebugTools) return Promise.resolve(true);
// this.core.$$addTestResult("DevModule", "Test", true);
// return Promise.resolve(true);
@@ -155,4 +156,11 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
// this.addTestResult("Test of test3", true);
return this.testDone();
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
services.test.handleTest(this._everyModuleTest.bind(this));
services.test.handleAddTestResult(this._addTestResult.bind(this));
}
}

View File

@@ -1,9 +1,9 @@
import { delay } from "octagonal-wheels/promises";
import { LOG_LEVEL_NOTICE, REMOTE_MINIO, type FilePathWithPrefix } from "src/lib/src/common/types";
import { shareRunningResult } from "octagonal-wheels/concurrency/lock";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule";
import { AbstractObsidianModule } from "../AbstractObsidianModule";
export class ModuleIntegratedTest extends AbstractObsidianModule implements IObsidianModule {
export class ModuleIntegratedTest extends AbstractObsidianModule {
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
await delay(100);
const start = Date.now();
@@ -45,7 +45,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
}
return true;
}
async _orDie(key: string, proc: () => Promise<boolean>): Promise<true> | never {
async __orDie(key: string, proc: () => Promise<boolean>): Promise<true> | never {
if (!(await this._test(key, proc))) {
throw new Error(`${key}`);
}
@@ -54,7 +54,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
tryReplicate() {
if (!this.settings.liveSync) {
return shareRunningResult("replicate-test", async () => {
await this.core.$$replicate();
await this.services.replication.replicate();
});
}
}
@@ -64,13 +64,13 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
}
return await this.core.storageAccess.readHiddenFileText(file);
}
async _proceed(no: number, title: string): Promise<boolean> {
async __proceed(no: number, title: string): Promise<boolean> {
const stepFile = "_STEP.md" as FilePathWithPrefix;
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
const stepContent = `Step ${no}`;
await this.core.$anyResolveConflictByNewest(stepFile);
await this.services.conflict.resolveByNewest(stepFile);
await this.core.storageAccess.writeFileAuto(stepFile, stepContent);
await this._orDie(`Wait for acknowledge ${no}`, async () => {
await this.__orDie(`Wait for acknowledge ${no}`, async () => {
if (
!(await this.waitWithReplicating(async () => {
return await this.storageContentIsEqual(stepAckFile, stepContent);
@@ -81,13 +81,13 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
});
return true;
}
async _join(no: number, title: string): Promise<boolean> {
async __join(no: number, title: string): Promise<boolean> {
const stepFile = "_STEP.md" as FilePathWithPrefix;
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
// const otherStepFile = `_STEP_${isLeader ? "R" : "L"}.md` as FilePathWithPrefix;
const stepContent = `Step ${no}`;
await this._orDie(`Wait for step ${no} (${title})`, async () => {
await this.__orDie(`Wait for step ${no} (${title})`, async () => {
if (
!(await this.waitWithReplicating(async () => {
return await this.storageContentIsEqual(stepFile, stepContent);
@@ -96,7 +96,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
return false;
return true;
});
await this.core.$anyResolveConflictByNewest(stepAckFile);
await this.services.conflict.resolveByNewest(stepAckFile);
await this.core.storageAccess.writeFileAuto(stepAckFile, stepContent);
await this.tryReplicate();
return true;
@@ -116,16 +116,16 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
check: () => Promise<boolean>;
}): Promise<boolean> {
if (isGameChanger) {
await this._proceed(step, title);
await this.__proceed(step, title);
try {
await proc();
} catch (e) {
this._log(`Error: ${e}`);
return false;
}
return await this._orDie(`Step ${step} - ${title}`, async () => await this.waitWithReplicating(check));
return await this.__orDie(`Step ${step} - ${title}`, async () => await this.waitWithReplicating(check));
} else {
return await this._join(step, title);
return await this.__join(step, title);
}
}
// // see scenario.md
@@ -151,7 +151,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
`Test as ${isLeader ? "Leader" : "Receiver"} command file ${testCommandFile}`
);
if (isLeader) {
await this._proceed(0, "start");
await this.__proceed(0, "start");
}
await this.tryReplicate();
@@ -424,9 +424,9 @@ Line4:D`;
await this._test("basic", async () => await this.nonLiveTestRunner(isLeader, (t, l) => this.testBasic(t, l)));
}
async $everyModuleTestMultiDevice(): Promise<boolean> {
async _everyModuleTestMultiDevice(): Promise<boolean> {
if (!this.settings.enableDebugTools) return Promise.resolve(true);
const isLeader = this.core.$$vaultName().indexOf("recv") === -1;
const isLeader = this.core.services.vault.vaultName().indexOf("recv") === -1;
this.addTestResult("-------", true, `Test as ${isLeader ? "Leader" : "Receiver"}`);
try {
this._log(`Starting Test`);
@@ -440,4 +440,7 @@ Line4:D`;
return Promise.resolve(true);
}
onBindFunction(core: typeof this.core, services: typeof core.services): void {
services.test.handleTestMultiDevice(this._everyModuleTestMultiDevice.bind(this));
}
}

View File

@@ -1,5 +1,5 @@
import { delay } from "octagonal-wheels/promises";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
import { eventHub } from "../../common/events";
import { getWebCrypto } from "../../lib/src/mods.ts";
@@ -8,6 +8,7 @@ import { parseYaml, requestUrl, stringifyYaml } from "obsidian";
import type { FilePath } from "../../lib/src/common/types.ts";
import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { getFileRegExp } from "../../lib/src/common/utils.ts";
import type { LiveSyncCore } from "../../main.ts";
declare global {
interface LSEvents {
@@ -15,12 +16,15 @@ declare global {
}
}
export class ModuleReplicateTest extends AbstractObsidianModule implements IObsidianModule {
export class ModuleReplicateTest extends AbstractObsidianModule {
testRootPath = "_test/";
testInfoPath = "_testinfo/";
get isLeader() {
return this.core.$$getVaultName().indexOf("dev") >= 0 && this.core.$$vaultName().indexOf("recv") < 0;
return (
this.services.vault.getVaultName().indexOf("dev") >= 0 &&
this.services.vault.vaultName().indexOf("recv") < 0
);
}
get nameByKind() {
@@ -52,24 +56,24 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
async dumpList() {
if (this.settings.syncInternalFiles) {
this._log("Write file list (Include Hidden)");
await this._dumpFileListIncludeHidden("files.md");
await this.__dumpFileListIncludeHidden("files.md");
} else {
this._log("Write file list");
await this._dumpFileList("files.md");
await this.__dumpFileList("files.md");
}
}
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
async _everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
if (!this.settings.enableDebugTools) return Promise.resolve(true);
await this.dumpList();
return true;
}
$everyOnloadAfterLoadSettings(): Promise<boolean> {
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
if (!this.settings.enableDebugTools) return Promise.resolve(true);
this.addCommand({
id: "dump-file-structure-normal",
name: `Dump Structure (Normal)`,
callback: () => {
void this._dumpFileList("files.md").finally(() => {
void this.__dumpFileList("files.md").finally(() => {
void this.refreshSyncStatus();
});
},
@@ -79,7 +83,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
name: "Dump Structure (Include Hidden)",
callback: () => {
const d = "files.md";
void this._dumpFileListIncludeHidden(d);
void this.__dumpFileListIncludeHidden(d);
},
});
this.addCommand({
@@ -160,7 +164,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
}
}
async _dumpFileList(outFile?: string) {
async __dumpFileList(outFile?: string) {
if (!this.core || !this.core.storageAccess) {
this._log("No storage access", LOG_LEVEL_INFO);
return;
@@ -169,7 +173,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
const out = [] as any[];
const webcrypto = await getWebCrypto();
for (const file of files) {
if (!(await this.core.$$isTargetFile(file.path))) {
if (!(await this.services.vault.isTargetFile(file.path))) {
continue;
}
if (file.path.startsWith(this.testInfoPath)) continue;
@@ -200,7 +204,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
this._log(`Dumped ${out.length} files`, LOG_LEVEL_INFO);
}
async _dumpFileListIncludeHidden(outFile?: string) {
async __dumpFileListIncludeHidden(outFile?: string) {
const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
const targetPatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
const out = [] as any[];
@@ -316,7 +320,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
}
async testConflictedManually1() {
await this.core.$$replicate();
await this.services.replication.replicate();
const commonFile = `Resolve!
*****, the amazing chocolatier!!`;
@@ -325,8 +329,8 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", commonFile);
}
await this.core.$$replicate();
await this.core.$$replicate();
await this.services.replication.replicate();
await this.services.replication.replicate();
if (
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 1?", {
timeout: 30,
@@ -356,12 +360,12 @@ Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`;
) {
return;
}
await this.core.$$replicate();
await this.core.$$replicate();
await this.services.replication.replicate();
await this.services.replication.replicate();
if (
!(await this.waitFor(async () => {
await this.core.$$replicate();
await this.services.replication.replicate();
return (
(await this.__assertStorageContent(
(this.testRootPath + "wonka.md") as FilePath,
@@ -379,7 +383,7 @@ Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`;
}
async testConflictedManually2() {
await this.core.$$replicate();
await this.services.replication.replicate();
const commonFile = `Resolve To concatenate
ABCDEFG`;
@@ -388,8 +392,8 @@ ABCDEFG`;
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", commonFile);
}
await this.core.$$replicate();
await this.core.$$replicate();
await this.services.replication.replicate();
await this.services.replication.replicate();
if (
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 2?", {
timeout: 30,
@@ -420,12 +424,12 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
) {
return;
}
await this.core.$$replicate();
await this.core.$$replicate();
await this.services.replication.replicate();
await this.services.replication.replicate();
if (
!(await this.waitFor(async () => {
await this.core.$$replicate();
await this.services.replication.replicate();
return (
(await this.__assertStorageContent(
(this.testRootPath + "concat.md") as FilePath,
@@ -457,8 +461,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", baseDoc);
}
await delay(100);
await this.core.$$replicate();
await this.core.$$replicate();
await this.services.replication.replicate();
await this.services.replication.replicate();
if (
(await this.core.confirm.askYesNoDialog("Ready to test conflict?", {
@@ -487,8 +491,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod2Doc);
}
await this.core.$$replicate();
await this.core.$$replicate();
await this.services.replication.replicate();
await this.services.replication.replicate();
await delay(1000);
if (
(await this.core.confirm.askYesNoDialog("Ready to check result?", { timeout: 30, defaultOption: "Yes" })) ==
@@ -496,8 +500,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
) {
return;
}
await this.core.$$replicate();
await this.core.$$replicate();
await this.services.replication.replicate();
await this.services.replication.replicate();
const mergedDoc = `Tasks!
- [ ] Task 1
- [v] Task 2
@@ -511,7 +515,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
this._log("Before testing conflicted files, resolve all once", LOG_LEVEL_NOTICE);
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
await this.core.$$replicate();
await this.services.replication.replicate();
await delay(1000);
if (!(await this.testConflictAutomatic())) {
this._log("Conflict resolution (Auto) failed", LOG_LEVEL_NOTICE);
@@ -569,11 +573,16 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
// return results;
// });
// }
async $everyModuleTestMultiDevice(): Promise<boolean> {
private async _everyModuleTestMultiDevice(): Promise<boolean> {
if (!this.settings.enableDebugTools) return Promise.resolve(true);
// this.core.$$addTestResult("DevModule", "Test", true);
// return Promise.resolve(true);
await this._test("Conflict resolution", async () => await this.checkConflictResolution());
return this.testDone();
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
services.test.handleTestMultiDevice(this._everyModuleTestMultiDevice.bind(this));
}
}

View File

@@ -57,14 +57,14 @@
function moduleMultiDeviceTest() {
if (moduleTesting) return;
moduleTesting = true;
plugin.$everyModuleTestMultiDevice().finally(() => {
plugin.services.test.testMultiDevice().finally(() => {
moduleTesting = false;
});
}
function moduleSingleDeviceTest() {
if (moduleTesting) return;
moduleTesting = true;
plugin.$everyModuleTest().finally(() => {
plugin.services.test.test().finally(() => {
moduleTesting = false;
});
}
@@ -72,8 +72,8 @@
if (moduleTesting) return;
moduleTesting = true;
try {
await plugin.$everyModuleTest();
await plugin.$everyModuleTestMultiDevice();
await plugin.services.test.test();
await plugin.services.test.testMultiDevice();
} finally {
moduleTesting = false;
}

View File

@@ -46,6 +46,9 @@ function readDocument(w: LoadedEntry) {
}
export class DocumentHistoryModal extends Modal {
plugin: ObsidianLiveSyncPlugin;
get services() {
return this.plugin.services;
}
range!: HTMLInputElement;
contentView!: HTMLDivElement;
info!: HTMLDivElement;
@@ -74,7 +77,7 @@ export class DocumentHistoryModal extends Modal {
this.id = id;
this.initialRev = revision;
if (!file && id) {
this.file = this.plugin.$$id2path(id);
this.file = this.services.path.id2path(id);
}
if (localStorage.getItem("ols-history-highlightdiff") == "1") {
this.showDiff = true;
@@ -83,7 +86,7 @@ export class DocumentHistoryModal extends Modal {
async loadFile(initialRev?: string) {
if (!this.id) {
this.id = await this.plugin.$$path2id(this.file);
this.id = await this.services.path.path2id(this.file);
}
const db = this.plugin.localDatabase;
try {
@@ -126,7 +129,7 @@ export class DocumentHistoryModal extends Modal {
}
this.BlobURLs.delete(key);
}
generateBlobURL(key: string, data: Uint8Array) {
generateBlobURL(key: string, data: Uint8Array<ArrayBuffer>) {
this.revokeURL(key);
const v = URL.createObjectURL(new Blob([data], { endings: "transparent", type: "application/octet-stream" }));
this.BlobURLs.set(key, v);
@@ -175,7 +178,10 @@ export class DocumentHistoryModal extends Modal {
result = result.replace(/\n/g, "<br>");
} else if (isImage(this.file)) {
const src = this.generateBlobURL("base", w1data);
const overlay = this.generateBlobURL("overlay", readDocument(w2) as Uint8Array);
const overlay = this.generateBlobURL(
"overlay",
readDocument(w2) as Uint8Array<ArrayBuffer>
);
result = `<div class='ls-imgdiff-wrap'>
<div class='overlay'>
<img class='img-base' src="${src}">

View File

@@ -1,8 +1,8 @@
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { VIEW_TYPE_GLOBAL_HISTORY, GlobalHistoryView } from "./GlobalHistory/GlobalHistoryView.ts";
export class ModuleObsidianGlobalHistory extends AbstractObsidianModule implements IObsidianModule {
$everyOnloadStart(): Promise<boolean> {
export class ModuleObsidianGlobalHistory extends AbstractObsidianModule {
_everyOnloadStart(): Promise<boolean> {
this.addCommand({
id: "livesync-global-history",
name: "Show vault history",
@@ -17,6 +17,9 @@ export class ModuleObsidianGlobalHistory extends AbstractObsidianModule implemen
}
showGlobalHistory() {
void this.core.$$showView(VIEW_TYPE_GLOBAL_HISTORY);
void this.services.API.showWindow(VIEW_TYPE_GLOBAL_HISTORY);
}
onBindFunction(core: typeof this.core, services: typeof core.services): void {
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
}
}

View File

@@ -10,13 +10,14 @@ import {
type diff_result,
} from "../../lib/src/common/types.ts";
import { ConflictResolveModal } from "./InteractiveConflictResolving/ConflictResolveModal.ts";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { displayRev, getPath, getPathWithoutPrefix } from "../../common/utils.ts";
import { fireAndForget } from "octagonal-wheels/promises";
import { serialized } from "octagonal-wheels/concurrency/lock";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule implements IObsidianModule {
$everyOnloadStart(): Promise<boolean> {
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
_everyOnloadStart(): Promise<boolean> {
this.addCommand({
id: "livesync-conflictcheck",
name: "Pick a file to resolve conflict",
@@ -34,7 +35,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
return Promise.resolve(true);
}
async $anyResolveConflictByUI(filename: FilePathWithPrefix, conflictCheckResult: diff_result): Promise<boolean> {
async _anyResolveConflictByUI(filename: FilePathWithPrefix, conflictCheckResult: diff_result): Promise<boolean> {
// UI for resolving conflicts should one-by-one.
return await serialized(`conflict-resolve-ui`, async () => {
this._log("Merge:open conflict dialog", LOG_LEVEL_VERBOSE);
@@ -68,7 +69,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
}
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
if (
(await this.core.$$resolveConflictByDeletingRev(filename, delRev, "UI Concatenated")) ==
(await this.services.conflict.resolveByDeletingRevision(filename, delRev, "UI Concatenated")) ==
MISSING_OR_ERROR
) {
this._log(
@@ -80,7 +81,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
} else if (typeof toDelete === "string") {
// Select one of the conflicted revision to delete.
if (
(await this.core.$$resolveConflictByDeletingRev(filename, toDelete, "UI Selected")) ==
(await this.services.conflict.resolveByDeletingRevision(filename, toDelete, "UI Selected")) ==
MISSING_OR_ERROR
) {
this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE);
@@ -93,11 +94,11 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
// In here, some merge has been processed.
// So we have to run replication if configured.
// TODO: Make this is as a event request
if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
await this.core.$$replicateByEvent();
if (this.settings.syncAfterMerge && !this.services.appLifecycle.isSuspended()) {
await this.services.replication.replicateByEvent();
}
// And, check it again.
await this.core.$$queueConflictCheck(filename);
await this.services.conflict.queueCheckFor(filename);
return false;
});
}
@@ -120,14 +121,14 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList);
if (target) {
const targetItem = notes.find((e) => e.dispPath == target)!;
await this.core.$$queueConflictCheck(targetItem.path);
await this.core.$$waitForAllConflictProcessed();
await this.services.conflict.queueCheckFor(targetItem.path);
await this.services.conflict.ensureAllProcessed();
return true;
}
return false;
}
async $allScanStat(): Promise<boolean> {
async _allScanStat(): Promise<boolean> {
const notes: { path: string; mtime: number }[] = [];
this._log(`Checking conflicted files`, LOG_LEVEL_VERBOSE);
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
@@ -157,4 +158,9 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
}
return true;
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnScanningStartupIssues(this._allScanStat.bind(this));
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
services.conflict.handleResolveByUserInteraction(this._anyResolveConflictByUI.bind(this));
}
}

View File

@@ -20,7 +20,7 @@ import {
} from "../../lib/src/mock_and_interop/stores.ts";
import { eventHub } from "../../lib/src/hub/hub.ts";
import { EVENT_FILE_RENAMED, EVENT_LAYOUT_READY, EVENT_LEAF_ACTIVE_CHANGED } from "../../common/events.ts";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { addIcon, normalizePath, Notice } from "../../deps.ts";
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
@@ -28,6 +28,7 @@ import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
import { serialized } from "octagonal-wheels/concurrency/lock";
import { $msg } from "src/lib/src/common/i18n.ts";
import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
import type { LiveSyncCore } from "../../main.ts";
// This module cannot be a core module because it depends on the Obsidian UI.
@@ -50,7 +51,7 @@ const recentLogProcessor = new QueueProcessor(
const showDebugLog = false;
export const MARK_DONE = "\u{2009}\u{2009}";
export class ModuleLog extends AbstractObsidianModule implements IObsidianModule {
export class ModuleLog extends AbstractObsidianModule {
registerView = this.plugin.registerView.bind(this.plugin);
statusBar?: HTMLElement;
@@ -178,7 +179,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
});
const statusBarLabels = reactive(() => {
const scheduleMessage = this.core.$$isReloadingScheduled()
const scheduleMessage = this.services.appLifecycle.isReloadingScheduled()
? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n`
: "";
const { message } = statusLineLabel();
@@ -199,7 +200,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
statusBarLabels.onChanged((label) => applyToDisplay(label.value));
}
$everyOnload(): Promise<boolean> {
private _everyOnload(): Promise<boolean> {
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange());
eventHub.onceEvent(EVENT_LAYOUT_READY, () => this.onActiveLeafChange());
@@ -219,15 +220,15 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
const thisFile = this.app.workspace.getActiveFile();
if (!thisFile) return "";
// Case Sensitivity
if (this.core.$$shouldCheckCaseInsensitive()) {
if (this.services.setting.shouldCheckCaseInsensitively()) {
const f = this.core.storageAccess
.getFiles()
.map((e) => e.path)
.filter((e) => e.toLowerCase() == thisFile.path.toLowerCase());
if (f.length > 1) return "Not synchronised: There are multiple files with the same name";
}
if (!(await this.core.$$isTargetFile(thisFile.path))) return "Not synchronised: not a target file";
if (this.core.$$isFileSizeExceeded(thisFile.stat.size)) return "Not synchronised: File size exceeded";
if (!(await this.services.vault.isTargetFile(thisFile.path))) return "Not synchronised: not a target file";
if (this.services.vault.isFileSizeTooLarge(thisFile.stat.size)) return "Not synchronised: File size exceeded";
return "";
}
async setFileStatus() {
@@ -287,14 +288,14 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
});
}
$allStartOnUnload(): Promise<boolean> {
private _allStartOnUnload(): Promise<boolean> {
if (this.statusDiv) {
this.statusDiv.remove();
}
document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove());
return Promise.resolve(true);
}
$everyOnloadStart(): Promise<boolean> {
_everyOnloadStart(): Promise<boolean> {
addIcon(
"view-log",
`<g transform="matrix(1.28 0 0 1.28 -131 -411)" fill="currentColor" fill-rule="evenodd">
@@ -303,23 +304,23 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
</g>`
);
this.addRibbonIcon("view-log", $msg("moduleLog.showLog"), () => {
void this.core.$$showView(VIEW_TYPE_LOG);
void this.services.API.showWindow(VIEW_TYPE_LOG);
}).addClass("livesync-ribbon-showlog");
this.addCommand({
id: "view-log",
name: "Show log",
callback: () => {
void this.core.$$showView(VIEW_TYPE_LOG);
void this.services.API.showWindow(VIEW_TYPE_LOG);
},
});
this.registerView(VIEW_TYPE_LOG, (leaf) => new LogPaneView(leaf, this.plugin));
return Promise.resolve(true);
}
$everyOnloadAfterLoadSettings(): Promise<boolean> {
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
logStore
.pipeTo(
new QueueProcessor((logs) => logs.forEach((e) => this.core.$$addLog(e.message, e.level, e.key)), {
new QueueProcessor((logs) => logs.forEach((e) => this.__addLog(e.message, e.level, e.key)), {
suspended: false,
batchSize: 20,
concurrentLimit: 1,
@@ -366,7 +367,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
})
);
}
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
__addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
if (level == LOG_LEVEL_DEBUG && !showDebugLog) {
return;
}
@@ -376,7 +377,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) {
return;
}
const vaultName = this.core.$$getVaultName();
const vaultName = this.services.vault.getVaultName();
const now = new Date();
const timestamp = now.toLocaleString();
const messageContent =
@@ -437,4 +438,10 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
}
}
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
services.appLifecycle.handleOnBeforeUnload(this._allStartOnUnload.bind(this));
}
}

View File

@@ -2,18 +2,18 @@ import { type TFile } from "obsidian";
import { eventHub } from "../../common/events.ts";
import { EVENT_REQUEST_SHOW_HISTORY } from "../../common/obsidianEvents.ts";
import type { FilePathWithPrefix, LoadedEntry, DocumentID } from "../../lib/src/common/types.ts";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts";
import { getPath } from "../../common/utils.ts";
import { fireAndForget } from "octagonal-wheels/promises";
export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implements IObsidianModule {
$everyOnloadStart(): Promise<boolean> {
export class ModuleObsidianDocumentHistory extends AbstractObsidianModule {
_everyOnloadStart(): Promise<boolean> {
this.addCommand({
id: "livesync-history",
name: "Show history",
callback: () => {
const file = this.core.$$getActiveFilePath();
const file = this.services.vault.getActiveFilePath();
if (file) this.showHistory(file, undefined);
},
});
@@ -51,4 +51,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem
this.showHistory(targetId.path, targetId.id);
}
}
onBindFunction(core: typeof this.core, services: typeof core.services): void {
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
}
}

View File

@@ -1,4 +1,4 @@
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
import {
@@ -16,8 +16,9 @@ import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
import { getLanguage } from "obsidian";
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts";
import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts";
export class ModuleObsidianSettings extends AbstractObsidianModule implements IObsidianModule {
async $everyOnLayoutReady(): Promise<boolean> {
import type { LiveSyncCore } from "../../main.ts";
export class ModuleObsidianSettings extends AbstractObsidianModule {
async _everyOnLayoutReady(): Promise<boolean> {
let isChanged = false;
if (this.settings.displayLanguage == "") {
const obsidianLanguage = getLanguage();
@@ -32,7 +33,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
} else if (this.settings.displayLanguage == "") {
this.settings.displayLanguage = "def";
setLang(this.settings.displayLanguage);
await this.core.$$saveSettingData();
await this.services.setting.saveSettingData();
}
}
if (isChanged) {
@@ -46,7 +47,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
this.settings.displayLanguage = "def";
setLang(this.settings.displayLanguage);
}
await this.core.$$saveSettingData();
await this.services.setting.saveSettingData();
}
return true;
}
@@ -61,13 +62,13 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
return methodFunc();
}
$$saveDeviceAndVaultName(): void {
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName();
localStorage.setItem(lsKey, this.core.$$getDeviceAndVaultName() || "");
_saveDeviceAndVaultName(): void {
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName();
localStorage.setItem(lsKey, this.services.setting.getDeviceAndVaultName() || "");
}
usedPassphrase = "";
$$clearUsedPassphrase(): void {
private _clearUsedPassphrase(): void {
this.usedPassphrase = "";
}
@@ -106,8 +107,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
return `${"appId" in this.app ? this.app.appId : ""}`;
}
async $$saveSettingData() {
this.core.$$saveDeviceAndVaultName();
async _saveSettingData() {
this.services.setting.saveDeviceAndVaultName();
const settings = { ...this.settings };
settings.deviceAndVaultName = "";
if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) {
@@ -174,7 +175,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
}
}
async $$decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
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);
@@ -234,7 +235,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
* @param settings
* @returns
*/
$$adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
_adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
// Adjust settings as needed
// Delete this feature to avoid problems on mobile.
@@ -264,7 +265,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
return Promise.resolve(settings);
}
async $$loadSettings(): Promise<void> {
async _loadSettings(): Promise<void> {
const settings = Object.assign({}, DEFAULT_SETTINGS, await this.core.loadData()) as ObsidianLiveSyncSettings;
if (typeof settings.isConfigured == "undefined") {
@@ -277,17 +278,17 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
}
}
this.settings = await this.core.$$decryptSettings(settings);
this.settings = await this.services.setting.decryptSettings(settings);
setLang(this.settings.displayLanguage);
await this.core.$$adjustSettings(this.settings);
await this.services.setting.adjustSettings(this.settings);
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName();
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName();
if (this.settings.deviceAndVaultName != "") {
if (!localStorage.getItem(lsKey)) {
this.core.$$setDeviceAndVaultName(this.settings.deviceAndVaultName);
this.$$saveDeviceAndVaultName();
this.services.setting.setDeviceAndVaultName(this.settings.deviceAndVaultName);
this.services.setting.saveDeviceAndVaultName();
this.settings.deviceAndVaultName = "";
}
}
@@ -298,8 +299,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
);
this.settings.customChunkSize = 0;
}
this.core.$$setDeviceAndVaultName(localStorage.getItem(lsKey) || "");
if (this.core.$$getDeviceAndVaultName() == "") {
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;
@@ -309,4 +310,14 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
// this.core.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim());
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
super.onBindFunction(core, services);
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
services.setting.handleClearUsedPassphrase(this._clearUsedPassphrase.bind(this));
services.setting.handleDecryptSettings(this._decryptSettings.bind(this));
services.setting.handleAdjustSettings(this._adjustSettings.bind(this));
services.setting.handleLoadSettings(this._loadSettings.bind(this));
services.setting.handleSaveDeviceAndVaultName(this._saveDeviceAndVaultName.bind(this));
services.setting.handleSaveSettingData(this._saveSettingData.bind(this));
}
}

View File

@@ -1,4 +1,4 @@
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
import { isObjectDifferent } from "octagonal-wheels/object";
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events";
@@ -8,8 +8,8 @@ import { parseYaml, stringifyYaml } from "../../deps";
import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
const SETTING_HEADER = "````yaml:livesync-setting\n";
const SETTING_FOOTER = "\n````";
export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule implements IObsidianModule {
$everyOnloadStart(): Promise<boolean> {
export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule {
_everyOnloadStart(): Promise<boolean> {
this.addCommand({
id: "livesync-export-config",
name: "Write setting markdown manually",
@@ -18,7 +18,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
return this.settings.settingSyncFile != "";
}
fireAndForget(async () => {
await this.core.$$saveSettingData();
await this.services.setting.saveSettingData();
});
},
});
@@ -160,7 +160,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
result == APPLY_AND_FETCH
) {
this.core.settings = settingToApply;
await this.core.$$saveSettingData();
await this.services.setting.saveSettingData();
if (result == APPLY_ONLY) {
this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE);
return;
@@ -171,7 +171,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
if (result == APPLY_AND_FETCH) {
await this.core.rebuilder.scheduleFetch();
}
this.core.$$performRestart();
this.services.appLifecycle.performRestart();
}
});
});
@@ -242,4 +242,7 @@ We can perform a command in this file.
this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE);
}
}
onBindFunction(core: typeof this.plugin, services: typeof core.services): void {
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
}
}

View File

@@ -1,12 +1,12 @@
import { ObsidianLiveSyncSettingTab } from "./SettingDialogue/ObsidianLiveSyncSettingTab.ts";
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
import { EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, eventHub } from "../../common/events.ts";
export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implements IObsidianModule {
export class ModuleObsidianSettingDialogue extends AbstractObsidianModule {
settingTab!: ObsidianLiveSyncSettingTab;
$everyOnloadStart(): Promise<boolean> {
_everyOnloadStart(): Promise<boolean> {
this.settingTab = new ObsidianLiveSyncSettingTab(this.app, this.plugin);
this.plugin.addSettingTab(this.settingTab);
eventHub.onEvent(EVENT_REQUEST_OPEN_SETTINGS, () => this.openSetting());
@@ -29,4 +29,7 @@ export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implem
get appId() {
return `${"appId" in this.app ? this.app.appId : ""}`;
}
onBindFunction(core: typeof this.plugin, services: typeof core.services): void {
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
}
}

View File

@@ -14,15 +14,16 @@ import {
EVENT_REQUEST_SHOW_SETUP_QR,
eventHub,
} from "../../common/events.ts";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { decodeAnyArray, encodeAnyArray } from "../../common/utils.ts";
import qrcode from "qrcode-generator";
import { $msg } from "../../lib/src/common/i18n.ts";
import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts";
import { encryptString, decryptString } from "@/lib/src/encryption/stringEncryption.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsidianModule {
$everyOnload(): Promise<boolean> {
export class ModuleSetupObsidian extends AbstractObsidianModule {
private _everyOnload(): Promise<boolean> {
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => {
if (conf.settings) {
await this.setupWizard(conf.settings);
@@ -182,7 +183,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
}
const newSettings = JSON.parse(JSON.stringify(tryingSettings)) as ObsidianLiveSyncSettings;
const remoteConfig = await this.core.$$fetchRemotePreferredTweakValues(newSettings);
const remoteConfig = await this.services.tweakValue.fetchRemotePreferred(newSettings);
if (remoteConfig) {
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
const resultSettings = {
@@ -282,16 +283,16 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
);
if (setupType == setupJustImport) {
this.core.settings = newSettingW;
this.core.$$clearUsedPassphrase();
this.services.setting.clearUsedPassphrase();
await this.core.saveSettings();
} else if (setupType == setupAsNew) {
this.core.settings = newSettingW;
this.core.$$clearUsedPassphrase();
this.services.setting.clearUsedPassphrase();
await this.core.saveSettings();
await this.core.rebuilder.$fetchLocal();
} else if (setupType == setupAsMerge) {
this.core.settings = newSettingW;
this.core.$$clearUsedPassphrase();
this.services.setting.clearUsedPassphrase();
await this.core.saveSettings();
await this.core.rebuilder.$fetchLocal(true);
} else if (setupType == setupAgain) {
@@ -308,7 +309,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
}
this.core.settings = newSettingW;
await this.core.saveSettings();
this.core.$$clearUsedPassphrase();
this.services.setting.clearUsedPassphrase();
await this.core.rebuilder.$rebuildEverything();
} else {
// Explicitly cancel the operation or the dialog was closed.
@@ -345,4 +346,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
this._log(ex, LOG_LEVEL_VERBOSE);
}
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
}
}

View File

@@ -86,6 +86,9 @@ export function createStub(name: string, key: string, value: string, panel: stri
export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
plugin: ObsidianLiveSyncPlugin;
get services() {
return this.plugin.services;
}
selectedScreen = "";
_editingSettings?: AllSettings;
@@ -139,8 +142,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
return await Promise.resolve();
}
if (key == "deviceAndVaultName") {
this.plugin.$$setDeviceAndVaultName(this.editingSettings?.[key] ?? "");
this.plugin.$$saveDeviceAndVaultName();
this.services.setting.setDeviceAndVaultName(this.editingSettings?.[key] ?? "");
this.services.setting.saveDeviceAndVaultName();
return await Promise.resolve();
}
}
@@ -210,7 +213,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
const ret = { ...OnDialogSettingsDefault };
ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
ret.preset = "";
ret.deviceAndVaultName = this.plugin.$$getDeviceAndVaultName();
ret.deviceAndVaultName = this.services.setting.getDeviceAndVaultName();
return ret;
}
computeAllLocalSettings(): Partial<OnDialogSettings> {
@@ -295,7 +298,11 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
async testConnection(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<void> {
const trialSetting = { ...this.editingSettings, ...settingOverride };
const replicator = await this.plugin.$anyNewReplicator(trialSetting);
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
if (!replicator) {
Logger("No replicator available for the current settings.", LOG_LEVEL_NOTICE);
return;
}
await replicator.tryConnectRemote(trialSetting);
const status = await replicator.getRemoteStatus(trialSetting);
if (status) {
@@ -546,10 +553,14 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
const settingForCheck: RemoteDBSettings = {
...this.editingSettings,
};
const replicator = this.plugin.$anyNewReplicator(settingForCheck);
const replicator = this.services.replicator.getNewReplicator(settingForCheck);
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return true;
const db = await replicator.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.$$isMobile(), true);
const db = await replicator.connectRemoteCouchDBWithSetting(
settingForCheck,
this.services.API.isMobile(),
true
);
if (typeof db === "string") {
Logger($msg("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", { db }), LOG_LEVEL_NOTICE);
return false;
@@ -588,8 +599,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.editingSettings.passphrase = "";
}
this.applyAllSettings();
await this.plugin.$allSuspendAllSync();
await this.plugin.$allSuspendExtraSync();
await this.services.setting.suspendAllSync();
await this.services.setting.suspendExtraSync();
this.reloadAllSettings();
this.editingSettings.isConfigured = true;
Logger($msg("obsidianLiveSyncSettingTab.logRebuildNote"), LOG_LEVEL_NOTICE);
@@ -638,12 +649,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.applyAllSettings();
if (result == OPTION_FETCH) {
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
this.plugin.$$scheduleAppReload();
this.services.appLifecycle.scheduleRestart();
this.closeSetting();
// await rebuildDB("localOnly");
} else if (result == OPTION_REBUILD_BOTH) {
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
this.plugin.$$scheduleAppReload();
this.services.appLifecycle.scheduleRestart();
this.closeSetting();
} else if (result == OPTION_ONLY_SETTING) {
await this.plugin.saveSettings();

View File

@@ -21,7 +21,7 @@ export function paneCustomisationSync(
text: "Please set device name to identify this device. This name should be unique among your devices. While not configured, we cannot enable this feature.",
cls: "op-warn",
},
(c) => { },
(c) => {},
visibleOnly(() => this.isConfiguredAs("deviceAndVaultName", ""))
);
this.createEl(
@@ -31,7 +31,7 @@ export function paneCustomisationSync(
text: "We cannot change the device name while this feature is enabled. Please disable this feature to change the device name.",
cls: "op-warn-info",
},
(c) => { },
(c) => {},
visibleOnly(() => this.isConfiguredAs("usePluginSync", true))
);

View File

@@ -119,8 +119,8 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
const scheme = pluginConfig.couchDB_URI.startsWith("http:")
? "(HTTP)"
: pluginConfig.couchDB_URI.startsWith("https:")
? "(HTTPS)"
: "";
? "(HTTPS)"
: "";
pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI)
? "cloudant"
: `self-hosted${scheme}`;
@@ -150,13 +150,13 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
const endpointScheme = pluginConfig.endpoint.startsWith("http:")
? "(HTTP)"
: pluginConfig.endpoint.startsWith("https:")
? "(HTTPS)"
: "";
? "(HTTPS)"
: "";
pluginConfig.endpoint = `${endpoint.indexOf(".r2.cloudflarestorage.") !== -1 ? "R2" : "self-hosted?"}(${endpointScheme})`;
}
const obsidianInfo = {
navigator: navigator.userAgent,
fileSystem: this.plugin.$$isStorageInsensitive() ? "insensitive" : "sensitive",
fileSystem: this.plugin.services.vault.isStorageInsensitive() ? "insensitive" : "sensitive",
};
const msgConfig = `# ---- Obsidian info ----
${stringifyYaml(obsidianInfo)}
@@ -166,9 +166,9 @@ ${stringifyYaml(responseConfig)}
---
# ---- Plug-in config ----
${stringifyYaml({
version: this.manifestVersion,
...pluginConfig,
})}`;
version: this.manifestVersion,
...pluginConfig,
})}`;
console.log(msgConfig);
await navigator.clipboard.writeText(msgConfig);
Logger(
@@ -182,10 +182,10 @@ ${stringifyYaml({
void addPanel(paneEl, "Scram Switches").then((paneEl) => {
new Setting(paneEl).autoWireToggle("suspendFileWatching");
this.addOnSaved("suspendFileWatching", () => this.plugin.$$askReload());
this.addOnSaved("suspendFileWatching", () => this.services.appLifecycle.askRestart());
new Setting(paneEl).autoWireToggle("suspendParseReplicationResult");
this.addOnSaved("suspendParseReplicationResult", () => this.plugin.$$askReload());
this.addOnSaved("suspendParseReplicationResult", () => this.services.appLifecycle.askRestart());
});
void addPanel(paneEl, "Recovery and Repair").then((paneEl) => {
@@ -384,15 +384,16 @@ ${stringifyYaml({
? await this.plugin.storageAccess.statHidden(path)
: false;
const fileOnStorage = stat != null ? stat : false;
if (!(await this.plugin.$$isTargetFile(path))) return incProc();
if (!(await this.services.vault.isTargetFile(path))) return incProc();
const releaser = await semaphore.acquire(1);
if (fileOnStorage && this.plugin.$$isFileSizeExceeded(fileOnStorage.size))
if (fileOnStorage && this.services.vault.isFileSizeTooLarge(fileOnStorage.size))
return incProc();
try {
const isHiddenFile = path.startsWith(".");
const dbPath = isHiddenFile ? addPrefix(path, ICHeader) : path;
const fileOnDB = await this.plugin.localDatabase.getDBEntry(dbPath);
if (fileOnDB && this.plugin.$$isFileSizeExceeded(fileOnDB.size)) return incProc();
if (fileOnDB && this.services.vault.isFileSizeTooLarge(fileOnDB.size))
return incProc();
if (!fileOnDB && fileOnStorage) {
Logger(`Compare: Not found on the local database: ${path}`, LOG_LEVEL_NOTICE);
@@ -436,7 +437,7 @@ ${stringifyYaml({
.onClick(async () => {
for await (const docName of this.plugin.localDatabase.findAllDocNames()) {
if (!docName.startsWith("f:")) {
const idEncoded = await this.plugin.$$path2id(docName as FilePathWithPrefix);
const idEncoded = await this.services.path.path2id(docName as FilePathWithPrefix);
const doc = await this.plugin.localDatabase.getRaw(docName as DocumentID);
if (!doc) continue;
if (doc.type != "newnote" && doc.type != "plain") {
@@ -477,7 +478,7 @@ ${stringifyYaml({
if ((await this.plugin.localDatabase.putRaw(doc)).ok) {
Logger(`Old ${docName} has been deleted`, LOG_LEVEL_NOTICE);
}
await this.plugin.$$queueConflictCheckIfOpen(docName as FilePathWithPrefix);
await this.services.conflict.queueCheckForIfOpen(docName as FilePathWithPrefix);
} else {
Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE);
Logger(ret, LOG_LEVEL_VERBOSE);
@@ -512,7 +513,7 @@ ${stringifyYaml({
.onClick(async () => {
this.editingSettings.isConfigured = false;
await this.saveAllDirtySettings();
this.plugin.$$askReload();
this.services.appLifecycle.askRestart();
})
);

View File

@@ -32,7 +32,7 @@ export function paneMaintenance(
(e) => {
e.addEventListener("click", () => {
fireAndForget(async () => {
await this.plugin.$$markRemoteResolved();
await this.services.remote.markResolved();
this.display();
});
});
@@ -59,7 +59,7 @@ export function paneMaintenance(
(e) => {
e.addEventListener("click", () => {
fireAndForget(async () => {
await this.plugin.$$markRemoteUnlocked();
await this.services.remote.markUnlocked();
this.display();
});
});
@@ -78,7 +78,7 @@ export function paneMaintenance(
.setDisabled(false)
.setWarning()
.onClick(async () => {
await this.plugin.$$markRemoteLocked();
await this.services.remote.markLocked();
})
)
.addOnUpdate(this.onlyOnCouchDBOrMinIO);
@@ -93,12 +93,12 @@ export function paneMaintenance(
.setWarning()
.onClick(async () => {
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG, "");
this.plugin.$$performRestart();
this.services.appLifecycle.performRestart();
})
);
});
void addPanel(paneEl, "Syncing", () => { }, this.onlyOnCouchDBOrMinIO).then((paneEl) => {
void addPanel(paneEl, "Syncing", () => {}, this.onlyOnCouchDBOrMinIO).then((paneEl) => {
new Setting(paneEl)
.setName("Resend")
.setDesc("Resend all chunks to the remote.")
@@ -255,7 +255,7 @@ export function paneMaintenance(
.setDisabled(false)
.onClick(async () => {
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
this.plugin.$$performRestart();
this.services.appLifecycle.performRestart();
})
)
.addButton((button) =>
@@ -283,7 +283,7 @@ export function paneMaintenance(
.addOnUpdate(this.onlyOnCouchDB);
});
void addPanel(paneEl, "Total Overhaul", () => { }, this.onlyOnCouchDBOrMinIO).then((paneEl) => {
void addPanel(paneEl, "Total Overhaul", () => {}, this.onlyOnCouchDBOrMinIO).then((paneEl) => {
new Setting(paneEl)
.setName("Rebuild everything")
.setDesc("Rebuild local and remote database with local files.")
@@ -294,7 +294,7 @@ export function paneMaintenance(
.setDisabled(false)
.onClick(async () => {
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
this.plugin.$$performRestart();
this.services.appLifecycle.performRestart();
})
)
.addButton((button) =>
@@ -307,7 +307,7 @@ export function paneMaintenance(
})
);
});
void addPanel(paneEl, "Rebuilding Operations (Remote Only)", () => { }, this.onlyOnCouchDBOrMinIO).then((paneEl) => {
void addPanel(paneEl, "Rebuilding Operations (Remote Only)", () => {}, this.onlyOnCouchDBOrMinIO).then((paneEl) => {
new Setting(paneEl)
.setName("Perform cleanup")
.setDesc(
@@ -405,8 +405,8 @@ export function paneMaintenance(
.setWarning()
.setDisabled(false)
.onClick(async () => {
await this.plugin.$$resetLocalDatabase();
await this.plugin.$$initializeDatabase();
await this.services.database.resetDatabase();
await this.services.databaseEvents.initialiseDatabase();
})
);
});

View File

@@ -63,7 +63,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
this.addOnSaved("additionalSuffixOfDatabaseName", async (key) => {
Logger("Suffix has been changed. Reopening database...", LOG_LEVEL_NOTICE);
await this.plugin.$$initializeDatabase();
await this.services.databaseEvents.initialiseDatabase();
});
new Setting(paneEl).autoWireDropDown("hashAlg", {

View File

@@ -322,7 +322,7 @@ export function paneRemoteConfig(
onUpdate: this.enableOnlySyncDisabled,
});
});
void addPanel(paneEl, "Peer-to-Peer", undefined, this.onlyOnOnlyP2P).then((paneEl) => {
const syncWarnP2P = this.createEl(paneEl, "div", {
text: "",
@@ -405,7 +405,7 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleCouchDB"), undefined, this.onlyOnCouchDB).then(
(paneEl) => {
if (this.plugin.$$isMobile()) {
if (this.services.API.isMobile()) {
this.createEl(
paneEl,
"div",
@@ -477,11 +477,10 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
.addButton((button) =>
button.setButtonText("Generate").onClick(async () => {
const crypto = await getWebCrypto();
const keyPair = await crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-256" },
true,
["sign", "verify"]
);
const keyPair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, [
"sign",
"verify",
]);
const pubKey = await crypto.subtle.exportKey("spki", keyPair.publicKey);
const privateKey = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
const encodedPublicKey = await arrayBufferToBase64Single(pubKey);
@@ -514,7 +513,7 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
paneEl,
"div",
{ text: "" },
(el) => { },
(el) => {},
visibleOnly(() => this.editingSettings.useJWT)
);
@@ -597,7 +596,7 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
.addOnUpdate(this.onlyOnCouchDB);
}
);
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleNotification"), () => { }, this.onlyOnCouchDB).then(
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleNotification"), () => {}, this.onlyOnCouchDB).then(
(paneEl) => {
paneEl.addClass("wizardHidden");
new Setting(paneEl).autoWireNumeric("notifyThresholdOfRemoteStorageSize", {}).setClass("wizardHidden");
@@ -631,7 +630,8 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
.setDisabled(false)
.onClick(async () => {
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting);
const newTweaks =
await this.services.tweakValue.checkAndAskUseRemoteConfiguration(trialSetting);
if (newTweaks.result !== false) {
if (this.inWizard) {
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
@@ -649,15 +649,15 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
}
)) == "no"
) {
await this.plugin.$$saveSettingData();
await this.services.setting.saveSettingData();
return;
}
await this.plugin.$$saveSettingData();
await this.services.setting.saveSettingData();
await this.plugin.rebuilder.scheduleFetch();
await this.plugin.$$scheduleAppReload();
this.services.appLifecycle.scheduleRestart();
return;
} else {
await this.plugin.$$saveSettingData();
await this.services.setting.saveSettingData();
}
}
}
@@ -728,7 +728,7 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
)) == "yes"
) {
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting);
const newTweaks = await this.services.tweakValue.checkAndAskUseRemoteConfiguration(trialSetting);
if (newTweaks.result !== false) {
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
this.requestUpdate();

View File

@@ -46,7 +46,7 @@ export function paneSetup(
text.setButtonText($msg("obsidianLiveSyncSettingTab.btnEnable")).onClick(async () => {
this.editingSettings.isConfigured = true;
await this.saveAllDirtySettings();
this.plugin.$$askReload();
this.services.appLifecycle.askRestart();
});
});
});
@@ -91,10 +91,10 @@ export function paneSetup(
this.editingSettings = { ...this.editingSettings, ...DEFAULT_SETTINGS };
await this.saveAllDirtySettings();
this.plugin.settings = { ...DEFAULT_SETTINGS };
await this.plugin.$$saveSettingData();
await this.plugin.$$resetLocalDatabase();
await this.services.setting.saveSettingData();
await this.services.database.resetDatabase();
// await this.plugin.initializeDatabase();
this.plugin.$$askReload();
this.services.appLifecycle.askRestart();
}
})
.setWarning();

View File

@@ -25,16 +25,16 @@ export function paneSyncSettings(
const options: Record<string, string> =
this.editingSettings.remoteType == REMOTE_COUCHDB
? {
NONE: "",
LIVESYNC: $msg("obsidianLiveSyncSettingTab.optionLiveSync"),
PERIODIC: $msg("obsidianLiveSyncSettingTab.optionPeriodicWithBatch"),
DISABLE: $msg("obsidianLiveSyncSettingTab.optionDisableAllAutomatic"),
}
NONE: "",
LIVESYNC: $msg("obsidianLiveSyncSettingTab.optionLiveSync"),
PERIODIC: $msg("obsidianLiveSyncSettingTab.optionPeriodicWithBatch"),
DISABLE: $msg("obsidianLiveSyncSettingTab.optionDisableAllAutomatic"),
}
: {
NONE: "",
PERIODIC: $msg("obsidianLiveSyncSettingTab.optionPeriodicWithBatch"),
DISABLE: $msg("obsidianLiveSyncSettingTab.optionDisableAllAutomatic"),
};
NONE: "",
PERIODIC: $msg("obsidianLiveSyncSettingTab.optionPeriodicWithBatch"),
DISABLE: $msg("obsidianLiveSyncSettingTab.optionDisableAllAutomatic"),
};
new Setting(paneEl)
.autoWireDropDown("preset", {
@@ -105,7 +105,7 @@ export function paneSyncSettings(
if (!this.editingSettings.isConfigured) {
this.editingSettings.isConfigured = true;
await this.saveAllDirtySettings();
await this.plugin.$$realizeSettingSyncMode();
await this.services.setting.onRealiseSetting();
await this.rebuildDB("localOnly");
// this.resetEditingSettings();
if (
@@ -124,13 +124,13 @@ export function paneSyncSettings(
await this.confirmRebuild();
} else {
await this.saveAllDirtySettings();
await this.plugin.$$realizeSettingSyncMode();
this.plugin.$$askReload();
await this.services.setting.onRealiseSetting();
this.services.appLifecycle.askRestart();
}
}
} else {
await this.saveAllDirtySettings();
await this.plugin.$$realizeSettingSyncMode();
await this.services.setting.onRealiseSetting();
}
});
});
@@ -144,14 +144,14 @@ export function paneSyncSettings(
const optionsSyncMode =
this.editingSettings.remoteType == REMOTE_COUCHDB
? {
ONEVENTS: $msg("obsidianLiveSyncSettingTab.optionOnEvents"),
PERIODIC: $msg("obsidianLiveSyncSettingTab.optionPeriodicAndEvents"),
LIVESYNC: $msg("obsidianLiveSyncSettingTab.optionLiveSync"),
}
ONEVENTS: $msg("obsidianLiveSyncSettingTab.optionOnEvents"),
PERIODIC: $msg("obsidianLiveSyncSettingTab.optionPeriodicAndEvents"),
LIVESYNC: $msg("obsidianLiveSyncSettingTab.optionLiveSync"),
}
: {
ONEVENTS: $msg("obsidianLiveSyncSettingTab.optionOnEvents"),
PERIODIC: $msg("obsidianLiveSyncSettingTab.optionPeriodicAndEvents"),
};
ONEVENTS: $msg("obsidianLiveSyncSettingTab.optionOnEvents"),
PERIODIC: $msg("obsidianLiveSyncSettingTab.optionPeriodicAndEvents"),
};
new Setting(paneEl)
.autoWireDropDown("syncMode", {
@@ -169,7 +169,7 @@ export function paneSyncSettings(
}
await this.saveSettings(["liveSync", "periodicReplication"]);
await this.plugin.$$realizeSettingSyncMode();
await this.services.setting.onRealiseSetting();
});
new Setting(paneEl)
@@ -289,21 +289,21 @@ export function paneSyncSettings(
button.setButtonText("Merge").onClick(async () => {
this.closeSetting();
// this.resetEditingSettings();
await this.plugin.$anyConfigureOptionalSyncFeature("MERGE");
await this.services.setting.enableOptionalFeature("MERGE");
});
})
.addButton((button) => {
button.setButtonText("Fetch").onClick(async () => {
this.closeSetting();
// this.resetEditingSettings();
await this.plugin.$anyConfigureOptionalSyncFeature("FETCH");
await this.services.setting.enableOptionalFeature("FETCH");
});
})
.addButton((button) => {
button.setButtonText("Overwrite").onClick(async () => {
this.closeSetting();
// this.resetEditingSettings();
await this.plugin.$anyConfigureOptionalSyncFeature("OVERWRITE");
await this.services.setting.enableOptionalFeature("OVERWRITE");
});
});
}

View File

@@ -13,12 +13,13 @@ import { versionNumberString2Number } from "../../lib/src/string_and_binary/conv
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/base/APIBase.ts";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
async $$onLiveSyncReady() {
if (!(await this.core.$everyOnLayoutReady())) return;
export class ModuleLiveSyncMain extends AbstractModule {
async _onLiveSyncReady() {
if (!(await this.core.services.appLifecycle.onLayoutReady())) return false;
eventHub.emitEvent(EVENT_LAYOUT_READY);
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
const ANSWER_KEEP = $msg("moduleLiveSyncMain.optionKeepLiveSyncDisabled");
@@ -36,46 +37,49 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
this.settings.suspendFileWatching = false;
this.settings.suspendParseReplicationResult = false;
await this.saveSettings();
await this.core.$$scheduleAppReload();
return;
this.services.appLifecycle.scheduleRestart();
return false;
}
}
const isInitialized = await this.core.$$initializeDatabase(false, false);
const isInitialized = await this.services.databaseEvents.initialiseDatabase(false, false);
if (!isInitialized) {
//TODO:stop all sync.
return false;
}
if (!(await this.core.$everyOnFirstInitialize())) return;
await this.core.$$realizeSettingSyncMode();
if (!(await this.core.services.appLifecycle.onFirstInitialise())) return false;
// await this.core.$$realizeSettingSyncMode();
await this.services.setting.onRealiseSetting();
fireAndForget(async () => {
this._log($msg("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE);
if (!(await this.core.$allScanStat())) {
if (!(await this.services.appLifecycle.onScanningStartupIssues())) {
this._log($msg("moduleLiveSyncMain.logSafetyScanFailed"), LOG_LEVEL_NOTICE);
} else {
this._log($msg("moduleLiveSyncMain.logSafetyScanCompleted"), LOG_LEVEL_VERBOSE);
}
});
return true;
}
$$wireUpEvents(): void {
_wireUpEvents() {
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
this.localDatabase.settings = settings;
setLang(settings.displayLanguage);
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
});
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
fireAndForget(() => this.core.$$realizeSettingSyncMode());
fireAndForget(() => this.core.services.setting.onRealiseSetting());
});
return Promise.resolve(true);
}
async $$onLiveSyncLoad(): Promise<void> {
this.$$wireUpEvents();
async _onLiveSyncLoad(): Promise<boolean> {
await this.services.appLifecycle.onWireUpEvents();
// debugger;
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core);
this._log($msg("moduleLiveSyncMain.logLoadingPlugin"));
if (!(await this.core.$everyOnloadStart())) {
if (!(await this.services.appLifecycle.onInitialise())) {
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
return;
return false;
}
// this.addUIs();
//@ts-ignore
@@ -84,12 +88,12 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
this._log($msg("moduleLiveSyncMain.logPluginVersion", { manifestVersion, packageVersion }));
await this.core.$$loadSettings();
if (!(await this.core.$everyOnloadAfterLoadSettings())) {
await this.services.setting.loadSettings();
if (!(await this.services.appLifecycle.onSettingLoaded())) {
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
return;
return false;
}
const lsKey = "obsidian-live-sync-ver" + this.core.$$getVaultName();
const lsKey = "obsidian-live-sync-ver" + this.services.vault.getVaultName();
const last_version = localStorage.getItem(lsKey);
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
@@ -113,22 +117,23 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
await this.saveSettings();
}
localStorage.setItem(lsKey, `${VER}`);
await this.core.$$openDatabase();
this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this);
await this.services.database.openDatabase();
// this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this);
// this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this);
// this.$$replicate = this.$$replicate.bind(this);
this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this);
await this.core.$everyOnload();
// this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this);
await this.core.services.appLifecycle.onLoaded();
await Promise.all(this.core.addOns.map((e) => e.onload()));
return true;
}
async $$onLiveSyncUnload(): Promise<void> {
async _onLiveSyncUnload(): Promise<void> {
eventHub.emitEvent(EVENT_PLUGIN_UNLOADED);
await this.core.$allStartOnUnload();
await this.services.appLifecycle.onBeforeUnload();
cancelAllPeriodicTask();
cancelAllTasks();
stopAllRunningProcessors();
await this.core.$allOnUnload();
await this.services.appLifecycle.onUnload();
this._unloaded = true;
for (const addOn of this.core.addOns) {
addOn.onunload();
@@ -143,49 +148,68 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
eventHub.emitEvent(EVENT_PLATFORM_UNLOADED);
eventHub.offAll();
this._log($msg("moduleLiveSyncMain.logUnloadingPlugin"));
return;
}
async $$realizeSettingSyncMode(): Promise<void> {
await this.core.$everyBeforeSuspendProcess();
await this.core.$everyBeforeRealizeSetting();
private async _realizeSettingSyncMode(): Promise<void> {
await this.services.appLifecycle.onSuspending();
await this.services.setting.onBeforeRealiseSetting();
this.localDatabase.refreshSettings();
await this.core.$everyCommitPendingFileEvent();
await this.core.$everyRealizeSettingSyncMode();
await this.services.fileProcessing.commitPendingFileEvents();
await this.services.setting.onRealiseSetting();
// disable all sync temporary.
if (this.core.$$isSuspended()) return;
await this.core.$everyOnResumeProcess();
await this.core.$everyAfterResumeProcess();
await this.core.$everyAfterRealizeSetting();
if (this.services.appLifecycle.isSuspended()) return;
await this.services.appLifecycle.onResuming();
await this.services.appLifecycle.onResumed();
await this.services.setting.onSettingRealised();
return;
}
$$isReloadingScheduled(): boolean {
_isReloadingScheduled(): boolean {
return this.core._totalProcessingCount !== undefined;
}
isReady = false;
$$isReady(): boolean {
_isReady(): boolean {
return this.isReady;
}
$$markIsReady(): void {
_markIsReady(): void {
this.isReady = true;
}
$$resetIsReady(): void {
_resetIsReady(): void {
this.isReady = false;
}
_suspended = false;
$$isSuspended(): boolean {
_isSuspended(): boolean {
return this._suspended || !this.settings?.isConfigured;
}
$$setSuspended(value: boolean) {
_setSuspended(value: boolean) {
this._suspended = value;
}
_unloaded = false;
$$isUnloaded(): boolean {
_isUnloaded(): boolean {
return this._unloaded;
}
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
super.onBindFunction(core, services);
services.appLifecycle.handleIsSuspended(this._isSuspended.bind(this));
services.appLifecycle.handleSetSuspended(this._setSuspended.bind(this));
services.appLifecycle.handleIsReady(this._isReady.bind(this));
services.appLifecycle.handleMarkIsReady(this._markIsReady.bind(this));
services.appLifecycle.handleResetIsReady(this._resetIsReady.bind(this));
services.appLifecycle.handleHasUnloaded(this._isUnloaded.bind(this));
services.appLifecycle.handleIsReloadingScheduled(this._isReloadingScheduled.bind(this));
services.appLifecycle.handleOnReady(this._onLiveSyncReady.bind(this));
services.appLifecycle.handleOnWireUpEvents(this._wireUpEvents.bind(this));
services.appLifecycle.handleOnLoad(this._onLiveSyncLoad.bind(this));
services.appLifecycle.handleOnAppUnload(this._onLiveSyncUnload.bind(this));
services.setting.handleRealiseSetting(this._realizeSettingSyncMode.bind(this));
}
}

View File

@@ -0,0 +1,76 @@
import {
InjectableAPIService,
InjectableAppLifecycleService,
InjectableConflictService,
InjectableDatabaseService,
InjectableFileProcessingService,
InjectablePathService,
InjectableRemoteService,
InjectableReplicationService,
InjectableReplicatorService,
InjectableSettingService,
InjectableTestService,
InjectableTweakValueService,
InjectableVaultService,
} from "../../lib/src/services/InjectableServices.ts";
import { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
// All Services will be migrated to be based on Plain Services, not Injectable Services.
// This is a migration step.
export class ObsidianAPIService extends InjectableAPIService {}
export class ObsidianPathService extends InjectablePathService {}
export class ObsidianDatabaseService extends InjectableDatabaseService {}
// InjectableReplicatorService
export class ObsidianReplicatorService extends InjectableReplicatorService {}
// InjectableFileProcessingService
export class ObsidianFileProcessingService extends InjectableFileProcessingService {}
// InjectableReplicationService
export class ObsidianReplicationService extends InjectableReplicationService {}
// InjectableRemoteService
export class ObsidianRemoteService extends InjectableRemoteService {}
// InjectableConflictService
export class ObsidianConflictService extends InjectableConflictService {}
// InjectableAppLifecycleService
export class ObsidianAppLifecycleService extends InjectableAppLifecycleService {}
// InjectableSettingService
export class ObsidianSettingService extends InjectableSettingService {}
// InjectableTweakValueService
export class ObsidianTweakValueService extends InjectableTweakValueService {}
// InjectableVaultService
export class ObsidianVaultService extends InjectableVaultService {}
// InjectableTestService
export class ObsidianTestService extends InjectableTestService {}
// InjectableServiceHub
export class ObsidianServiceHub extends InjectableServiceHub {
protected _api: ObsidianAPIService = new ObsidianAPIService(this._serviceBackend, this._throughHole);
protected _path: ObsidianPathService = new ObsidianPathService(this._serviceBackend, this._throughHole);
protected _database: ObsidianDatabaseService = new ObsidianDatabaseService(this._serviceBackend, this._throughHole);
protected _replicator: ObsidianReplicatorService = new ObsidianReplicatorService(
this._serviceBackend,
this._throughHole
);
protected _fileProcessing: ObsidianFileProcessingService = new ObsidianFileProcessingService(
this._serviceBackend,
this._throughHole
);
protected _replication: ObsidianReplicationService = new ObsidianReplicationService(
this._serviceBackend,
this._throughHole
);
protected _remote: ObsidianRemoteService = new ObsidianRemoteService(this._serviceBackend, this._throughHole);
protected _conflict: ObsidianConflictService = new ObsidianConflictService(this._serviceBackend, this._throughHole);
protected _appLifecycle: ObsidianAppLifecycleService = new ObsidianAppLifecycleService(
this._serviceBackend,
this._throughHole
);
protected _setting: ObsidianSettingService = new ObsidianSettingService(this._serviceBackend, this._throughHole);
protected _tweakValue: ObsidianTweakValueService = new ObsidianTweakValueService(
this._serviceBackend,
this._throughHole
);
protected _vault: ObsidianVaultService = new ObsidianVaultService(this._serviceBackend, this._throughHole);
protected _test: ObsidianTestService = new ObsidianTestService(this._serviceBackend, this._throughHole);
}

View File

@@ -31,12 +31,12 @@ const terserOption = {
evaluate: true,
dead_code: true,
// directives: true,
inline: 3,
inline: false,
join_vars: true,
loops: true,
passes: 4,
reduce_vars: true,
reduce_funcs: true,
reduce_funcs: false,
arrows: true,
collapse_vars: true,
comparisons: true,

View File

@@ -1,12 +1,43 @@
## 0.25
# 0.25
Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
After reading Issue #668, I conducted another self-review of the E2EE-related code. In retrospect, it was clearly written by someone inexperienced, which is understandable, but it is still rather embarrassing. Three years is certainly enough time for growth.
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.
I have now rewritten the E2EE code to be more robust and easier to understand. It is significantly more readable and should be easier to maintain in the future. The performance issue, previously considered a concern, has been addressed by introducing a master key and deriving keys using HKDF. This approach is both fast and robust, and it provides protection against rainbow table attacks. (In addition, this implementation has been [a dedicated package on the npm registry](https://github.com/vrtmrz/octagonal-wheels), and tested in 100% branch-coverage).
## 0.25.21
As a result, this is the first time in a while that forward compatibility has been broken. We have also taken the opportunity to change all metadata to use encryption rather than obfuscation. Furthermore, the `Dynamic Iteration Count` setting is now redundant and has been moved to the `Patches` pane in the settings. Thanks to Rabin-Karp, the eden setting is also no longer necessary and has been relocated accordingly. Therefore, v0.25.0 represents a legitimate and correct evolution.
13th October, 2025
This release including 0.25.21.beta1 and 0.25.21.beta2.
Apologies for taking a little time. I was seriously tackling this.
(Of course, being caught up in an unfamiliar structure due to personnel changes on my workplace played a part, but fortunately I have returned to a place where I can do research and development rather than production. Completely beside the point, though).
Now then, this time, moving away from 'convention over configuration', I have changed to a mechanism for manually binding events. This makes it much easier to leverage IDE assistance.
And, also, we are ready to separate `Features` and `APIs` from `Module`. Features are still in the module, but APIs will be moved to a Service layer. This will make it easier to maintain and extend the codebase in the future.
If you have found any issues, please let me know. I am now on the following:
- GitHub [Issues](https://github.com/vrtmrz/obsidian-livesync/issues) Excellent! May the other contributors will help you too.
- Twitter [@vorotamoroz](https://twitter.com/vorotamoroz) Quickest!
- Matrix [@vrtmrz:matrix.org](https://matrix.to/#/@vrtmrz:matrix.org) Also quick, and if you need to keep it private!
I am creating rooms too, but I'm struggling to figure out how to use them effectively because I cannot tell the difference of use-case between them and discussions. However, if you want to use Discord, this is a answer; We should on E2E encrypted platform.
## 0.25.21.beta2
8th October, 2025
### Fixed
- Fixed wrong event type bindings (which caused some events not to be handled correctly).
- Fixed detected a timing issue in StorageEventManager
- When multiple events for the same file are fired in quick succession, metadata has been kept older information. This induces unexpected wrong notifications and write prevention.
## 0.25.21.beta1
6th October, 2025
### Refactored
- Event handling now does not rely on 'convention over configuration'.
- Services.ts now have a proper event handler registration system.
## 0.25.20