-   Vault History can show the correct information of match-or-not for each file and database even if it is a binary file.
-   `Sync settings via markdown` is now hidden during the setup wizard.
-   Verify and Fix will ignore the hidden files if the hidden file sync is disabled.

New feature
-   Now we can fetch the tweaks from the remote database while the setting dialogue and wizard are processing.

Improved
-   More things are moved to the modules.
    -   Includes the Main codebase. Now `main.ts` is almost stub.
-   EventHub is now more robust and typesafe.
This commit is contained in:
vorotamoroz
2024-11-11 00:58:31 +00:00
parent 8b45dd1d24
commit 2c97289ec8
38 changed files with 636 additions and 400 deletions

View File

@@ -1,3 +1,7 @@
import type { FilePathWithPrefix, ObsidianLiveSyncSettings } from "../lib/src/common/types";
import { eventHub } from "../lib/src/hub/hub";
import type ObsidianLiveSyncPlugin from "../main";
export const EVENT_LAYOUT_READY = "layout-ready"; export const EVENT_LAYOUT_READY = "layout-ready";
export const EVENT_PLUGIN_LOADED = "plugin-loaded"; export const EVENT_PLUGIN_LOADED = "plugin-loaded";
export const EVENT_PLUGIN_UNLOADED = "plugin-unloaded"; export const EVENT_PLUGIN_UNLOADED = "plugin-unloaded";
@@ -13,7 +17,7 @@ export const EVENT_REQUEST_OPEN_SETTING_WIZARD = "request-open-setting-wizard";
export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri"; export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri";
export const EVENT_REQUEST_COPY_SETUP_URI = "request-copy-setup-uri"; export const EVENT_REQUEST_COPY_SETUP_URI = "request-copy-setup-uri";
export const EVENT_REQUEST_SHOW_HISTORY = "show-history";
export const EVENT_REQUEST_RELOAD_SETTING_TAB = "reload-setting-tab"; export const EVENT_REQUEST_RELOAD_SETTING_TAB = "reload-setting-tab";
@@ -22,8 +26,28 @@ export const EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG = "request-open-plugin-sync-d
// export const EVENT_FILE_CHANGED = "file-changed"; // export const EVENT_FILE_CHANGED = "file-changed";
import { eventHub } from "../lib/src/hub/hub"; declare global {
// TODO: Add overloads for the emit method to allow for type checking interface LSEvents {
[EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined;
[EVENT_FILE_SAVED]: undefined;
[EVENT_REQUEST_OPEN_SETUP_URI]: undefined;
[EVENT_REQUEST_COPY_SETUP_URI]: undefined;
[EVENT_REQUEST_RELOAD_SETTING_TAB]: undefined;
[EVENT_PLUGIN_UNLOADED]: undefined;
[EVENT_SETTING_SAVED]: ObsidianLiveSyncSettings;
[EVENT_PLUGIN_LOADED]: ObsidianLiveSyncPlugin;
[EVENT_LAYOUT_READY]: undefined;
"event-file-changed": { file: FilePathWithPrefix, automated: boolean };
"document-stub-created":
{
toc: Set<string>, stub: { [key: string]: { [key: string]: Map<string, Record<string, string>> } }
},
[EVENT_REQUEST_OPEN_SETTINGS]: undefined;
[EVENT_REQUEST_OPEN_SETTING_WIZARD]: undefined;
[EVENT_FILE_RENAMED]: { newPath: FilePathWithPrefix, old: FilePathWithPrefix };
[EVENT_LEAF_ACTIVE_CHANGED]: undefined;
}
}
export { eventHub }; export { eventHub };

View File

@@ -143,7 +143,7 @@ export class PeriodicProcessor {
if (interval == 0) return; if (interval == 0) return;
this._timer = window.setInterval(() => fireAndForget(async () => { this._timer = window.setInterval(() => fireAndForget(async () => {
await this.process(); await this.process();
if (this._plugin._unloaded) { if (this._plugin.$$isUnloaded()) {
this.disable(); this.disable();
} }
}), interval); }), interval);

View File

@@ -543,7 +543,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
filenameToUnifiedKey(path: string, termOverRide?: string) { filenameToUnifiedKey(path: string, termOverRide?: string) {
const term = termOverRide || this.plugin.deviceAndVaultName; const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
const category = this.getFileCategory(path); const category = this.getFileCategory(path);
const name = (category == "CONFIG" || category == "SNIPPET") ? const name = (category == "CONFIG" || category == "SNIPPET") ?
(path.split("/").slice(-1)[0]) : (path.split("/").slice(-1)[0]) :
@@ -554,7 +554,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
} }
filenameWithUnifiedKey(path: string, termOverRide?: string) { filenameWithUnifiedKey(path: string, termOverRide?: string) {
const term = termOverRide || this.plugin.deviceAndVaultName; const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
const category = this.getFileCategory(path); const category = this.getFileCategory(path);
const name = (category == "CONFIG" || category == "SNIPPET") ? const name = (category == "CONFIG" || category == "SNIPPET") ?
(path.split("/").slice(-1)[0]) : path.split("/").slice(-2)[0]; (path.split("/").slice(-1)[0]) : path.split("/").slice(-2)[0];
@@ -563,7 +563,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
} }
unifiedKeyPrefixOfTerminal(termOverRide?: string) { unifiedKeyPrefixOfTerminal(termOverRide?: string) {
const term = termOverRide || this.plugin.deviceAndVaultName; const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
return `${ICXHeader}${term}/` as FilePathWithPrefix; return `${ICXHeader}${term}/` as FilePathWithPrefix;
} }
@@ -870,7 +870,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
await this.plugin.storageAccess.ensureDir(path); await this.plugin.storageAccess.ensureDir(path);
// If the content has applied, modified time will be updated to the current time. // If the content has applied, modified time will be updated to the current time.
await this.plugin.storageAccess.writeHiddenFileAuto(path, content); await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
await this.storeCustomisationFileV2(path, this.plugin.deviceAndVaultName); await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName());
} else { } else {
const files = data.files; const files = data.files;
@@ -914,7 +914,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat); await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
} }
this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`); this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`);
await this.storeCustomisationFileV2(path, this.plugin.deviceAndVaultName); await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName());
} }
} }
} catch (ex) { } catch (ex) {
@@ -1189,7 +1189,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
}) })
} }
async storeCustomizationFiles(path: FilePath, termOverRide?: string) { async storeCustomizationFiles(path: FilePath, termOverRide?: string) {
const term = termOverRide || this.plugin.deviceAndVaultName; const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
if (term == "") { if (term == "") {
this._log("We have to configure the device name", LOG_LEVEL_NOTICE); this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
return; return;
@@ -1362,7 +1362,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
await shareRunningResult("scanAllConfigFiles", async () => { await shareRunningResult("scanAllConfigFiles", async () => {
const logLevel = showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO; const logLevel = showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
this._log("Scanning customizing files.", logLevel, "scan-all-config"); this._log("Scanning customizing files.", logLevel, "scan-all-config");
const term = this.plugin.deviceAndVaultName; const term = this.plugin.$$getDeviceAndVaultName();
if (term == "") { if (term == "") {
this._log("We have to configure the device name", LOG_LEVEL_NOTICE); this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
return; return;
@@ -1505,7 +1505,10 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
choices.push(CHOICE_DISABLE); choices.push(CHOICE_DISABLE);
choices.push(CHOICE_DISMISS); choices.push(CHOICE_DISMISS);
const ret = await this.plugin.confirm.confirmWithMessage("Customisation sync", message, choices, CHOICE_DISMISS, 40); const ret = await this.plugin.confirm.askSelectStringDialogue(message, choices, {
defaultAction: CHOICE_DISMISS, timeout: 40,
title: "Customisation sync"
});
if (ret == CHOICE_CUSTOMIZE) { if (ret == CHOICE_CUSTOMIZE) {
await this.configureHiddenFileSync("CUSTOMIZE"); await this.configureHiddenFileSync("CUSTOMIZE");
} else if (ret == CHOICE_DISABLE) { } else if (ret == CHOICE_DISABLE) {
@@ -1544,7 +1547,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
} }
if (mode == "CUSTOMIZE") { if (mode == "CUSTOMIZE") {
if (!this.plugin.deviceAndVaultName) { if (!this.plugin.$$getDeviceAndVaultName()) {
let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`); let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`);
if (!name) { if (!name) {
if (Platform.isAndroidApp) { if (Platform.isAndroidApp) {
@@ -1568,7 +1571,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
} }
name = name + Math.random().toString(36).slice(-4); name = name + Math.random().toString(36).slice(-4);
} }
this.plugin.deviceAndVaultName = name; this.plugin.$$setDeviceAndVaultName(name);
} }
this.plugin.settings.usePluginSync = true; this.plugin.settings.usePluginSync = true;
this.plugin.settings.useAdvancedMode = true; this.plugin.settings.useAdvancedMode = true;

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { ConfigSync, PluginDataExDisplayV2, type IPluginDataExDisplay } from "./CmdConfigSync.ts"; import { ConfigSync, PluginDataExDisplayV2, type IPluginDataExDisplay, type PluginDataExFile } from "./CmdConfigSync.ts";
import { Logger } from "../../lib/src/common/logger"; import { Logger } from "../../lib/src/common/logger";
import { type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../lib/src/common/types"; import { type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../lib/src/common/types";
import { getDocData, timeDeltaToHumanReadable, unique } from "../../lib/src/common/utils"; import { getDocData, timeDeltaToHumanReadable, unique } from "../../lib/src/common/utils";
@@ -287,9 +287,17 @@
menu.addItem((item) => item.setTitle("Compare file").setIsLabel(true)); menu.addItem((item) => item.setTitle("Compare file").setIsLabel(true));
menu.addSeparator(); menu.addSeparator();
const files = unique(local.files.map((e) => e.filename).concat(selectedItem.files.map((e) => e.filename))); const files = unique(local.files.map((e) => e.filename).concat(selectedItem.files.map((e) => e.filename)));
const convDate = (dt: PluginDataExFile | undefined) => {
if (!dt) return "(Missing)";
const d = new Date(dt.mtime);
return d.toLocaleString();
};
for (const filename of files) { for (const filename of files) {
menu.addItem((item) => { menu.addItem((item) => {
item.setTitle(filename).onClick((e) => compareItems(local, selectedItem, filename)); const localFile = local.files.find((e) => e.filename == filename);
const remoteFile = selectedItem.files.find((e) => e.filename == filename);
const title = `${filename} (${convDate(localFile)} <--> ${convDate(remoteFile)})`;
item.setTitle(title).onClick((e) => compareItems(local, selectedItem, filename));
}); });
} }
menu.showAtMouseEvent(evt); menu.showAtMouseEvent(evt);

View File

@@ -12,7 +12,7 @@
export let plugin: ObsidianLiveSyncPlugin; export let plugin: ObsidianLiveSyncPlugin;
$: hideNotApplicable = false; $: hideNotApplicable = false;
$: thisTerm = plugin.deviceAndVaultName; $: thisTerm = plugin.$$getDeviceAndVaultName();
const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync; const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync;
if (!addOn) { if (!addOn) {

View File

@@ -1,5 +1,5 @@
import { normalizePath, type PluginManifest, type ListedFiles } from "../../deps.ts"; import { normalizePath, type PluginManifest, type ListedFiles } from "../../deps.ts";
import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED, type SavingEntry, type DocumentID, type UXStat, MODE_AUTOMATIC } from "../../lib/src/common/types.ts"; import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED, type SavingEntry, type DocumentID, type UXStat, MODE_AUTOMATIC, type FilePathWithPrefixLC } from "../../lib/src/common/types.ts";
import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "../../common/types.ts"; import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "../../common/types.ts";
import { readAsBlob, isDocContentSame, sendSignal, readContent, createBlob, fireAndForget } from "../../lib/src/common/utils.ts"; import { readAsBlob, isDocContentSame, sendSignal, readContent, createBlob, fireAndForget } from "../../lib/src/common/utils.ts";
import { BASE_IS_NEW, compareMTime, EVEN, getPath, isInternalMetadata, isMarkedAsSameChanges, markChangesAreSame, PeriodicProcessor, TARGET_IS_NEW } from "../../common/utils.ts"; import { BASE_IS_NEW, compareMTime, EVEN, getPath, isInternalMetadata, isMarkedAsSameChanges, markChangesAreSame, PeriodicProcessor, TARGET_IS_NEW } from "../../common/utils.ts";
@@ -10,6 +10,7 @@ import { addPrefix, stripAllPrefixes } from "../../lib/src/string_and_binary/pat
import { QueueProcessor } from "../../lib/src/concurrency/processor.ts"; import { QueueProcessor } from "../../lib/src/concurrency/processor.ts";
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts"; import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts";
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts"; import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule { export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule {
@@ -36,8 +37,13 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
void this.syncInternalFilesAndDatabase("safe", true); void this.syncInternalFilesAndDatabase("safe", true);
}, },
}); });
eventHub.onEvent(EVENT_SETTING_SAVED, () => {
this.updateSettingCache();
});
} }
async $everyOnDatabaseInitialized(showNotice: boolean) { async $everyOnDatabaseInitialized(showNotice: boolean) {
this.knownChanges = await this.plugin.kvDB.get("knownChanges") ?? {};
if (this._isThisModuleEnabled()) { if (this._isThisModuleEnabled()) {
try { try {
this._log("Synchronizing hidden files..."); this._log("Synchronizing hidden files...");
@@ -58,12 +64,26 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
} }
$everyOnloadAfterLoadSettings(): Promise<boolean> { $everyOnloadAfterLoadSettings(): Promise<boolean> {
this.updateSettingCache();
return Promise.resolve(true);
}
updateSettingCache() {
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns
.replace(/\n| /g, "") .replace(/\n| /g, "")
.split(",").filter(e => e).map(e => new RegExp(e, "i")); .split(",").filter(e => e).map(e => new RegExp(e, "i"));
this.ignorePatterns = ignorePatterns; this.ignorePatterns = ignorePatterns;
return Promise.resolve(true); this.shouldSkipFile = [] as FilePathWithPrefixLC[];
// Exclude files handled by customization sync
const configDir = normalizePath(this.app.vault.configDir);
const shouldSKip = !this.settings.usePluginSync ? [] :
Object.values(this.settings.pluginSyncExtendedSetting).
filter(e => e.mode == MODE_SELECTIVE || e.mode == MODE_PAUSED).
map(e => e.files).flat().map(e => `${configDir}/${e}`.toLowerCase());
this.shouldSkipFile = shouldSKip as FilePathWithPrefixLC[];
this._log(`Hidden file will skip ${this.shouldSkipFile.length} files`, LOG_LEVEL_INFO);
} }
shouldSkipFile = [] as FilePathWithPrefixLC[];
async $everyOnResumeProcess(): Promise<boolean> { async $everyOnResumeProcess(): Promise<boolean> {
this.periodicInternalFileScanProcessor?.disable(); this.periodicInternalFileScanProcessor?.disable();
@@ -80,7 +100,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
this.periodicInternalFileScanProcessor?.disable(); this.periodicInternalFileScanProcessor?.disable();
if (this._isMainSuspended()) if (this._isMainSuspended())
return Promise.resolve(true); return Promise.resolve(true);
if (!this.plugin.isReady) if (!this.plugin.$$isReady())
return Promise.resolve(true); return Promise.resolve(true);
this.periodicInternalFileScanProcessor.enable(this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval ? (this.settings.syncInternalFilesInterval * 1000) : 0); this.periodicInternalFileScanProcessor.enable(this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval ? (this.settings.syncInternalFilesInterval * 1000) : 0);
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns
@@ -110,13 +130,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
if (this._isMainSuspended()) return false; if (this._isMainSuspended()) return false;
if (!this._isThisModuleEnabled()) return false; if (!this._isThisModuleEnabled()) return false;
// Exclude files handled by customization sync if (this.shouldSkipFile.some(e => e.startsWith(path.toLowerCase()))) {
const configDir = normalizePath(this.app.vault.configDir);
const synchronisedInConfigSync = !this.settings.usePluginSync ? [] :
Object.values(this.settings.pluginSyncExtendedSetting).
filter(e => e.mode == MODE_SELECTIVE || e.mode == MODE_PAUSED).
map(e => e.files).flat().map(e => `${configDir}/${e}`.toLowerCase());
if (synchronisedInConfigSync.some(e => e.startsWith(path.toLowerCase()))) {
this._log(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE); this._log(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE);
return false; return false;
} }
@@ -504,7 +518,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
// If something changes left, notify for reloading Obsidian. // If something changes left, notify for reloading Obsidian.
if (updatedCount != 0) { if (updatedCount != 0) {
if (!this.plugin.isReloadingScheduled) { if (!this.plugin.$$isReloadingScheduled()) {
this.plugin.confirm.askInPopup(`updated-any-hidden`, `Hidden files have been synchronised, Press {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`, (anchor) => { this.plugin.confirm.askInPopup(`updated-any-hidden`, `Hidden files have been synchronised, Press {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`, (anchor) => {
anchor.text = "HERE"; anchor.text = "HERE";
anchor.addEventListener("click", () => { anchor.addEventListener("click", () => {
@@ -676,13 +690,17 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
this._log(`STORAGE <x- DB: ${displayFileName}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`); this._log(`STORAGE <x- DB: ${displayFileName}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`);
} else { } else {
this._log(`STORAGE <x- DB: ${displayFileName}: deleted (hidden).`); this._log(`STORAGE <x- DB: ${displayFileName}: deleted (hidden).`);
await this.plugin.storageAccess.removeHidden(storageFilePath); if (await this.plugin.storageAccess.removeHidden(storageFilePath)) {
try { try {
await this.plugin.storageAccess.triggerHiddenFile(storageFilePath); await this.plugin.storageAccess.triggerHiddenFile(storageFilePath);
} catch (ex) { } catch (ex) {
this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE); this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
this._log(ex, LOG_LEVEL_VERBOSE); this._log(ex, LOG_LEVEL_VERBOSE);
} }
} else {
this._log(`STORAGE <x- DB: ${storageFilePath}: deleted (hidden) Failed`);
return false;
}
} }
return true; return true;
} }

View File

@@ -33,13 +33,13 @@ export abstract class LiveSyncCommands {
abstract onload(): void | Promise<void>; abstract onload(): void | Promise<void>;
_isMainReady() { _isMainReady() {
return this.plugin._isMainReady(); return this.plugin.$$isReady();
} }
_isMainSuspended() { _isMainSuspended() {
return this.plugin._isMainSuspended(); return this.plugin.$$isSuspended();
} }
_isDatabaseReady() { _isDatabaseReady() {
return this.plugin._isDatabaseReady(); return this.plugin.$$isDatabaseReady();
} }
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => { _log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
@@ -49,4 +49,5 @@ export abstract class LiveSyncCommands {
// console.log(msg); // console.log(msg);
Logger(msg, level, key); Logger(msg, level, key);
}; };
} }

Submodule src/lib updated: 5079b0bf79...a91bb47c90

View File

@@ -1,25 +1,18 @@
import { Plugin } from "./deps"; import { Plugin } from "./deps";
import { type EntryDoc, type LoadedEntry, type ObsidianLiveSyncSettings, type LOG_LEVEL, VER, DEFAULT_SETTINGS, type diff_result, type DatabaseConnectingStatus, type EntryHasPath, type DocumentID, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, type HasSettings, type MetaEntry, type UXFileInfoStub, type MISSING_OR_ERROR, type AUTO_MERGED, } from "./lib/src/common/types.ts"; 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, } from "./lib/src/common/types.ts";
import { type FileEventItem } from "./common/types.ts"; import { type FileEventItem } from "./common/types.ts";
import { fireAndForget, type SimpleStore } from "./lib/src/common/utils.ts"; import { type SimpleStore } from "./lib/src/common/utils.ts";
import { Logger } from "./lib/src/common/logger.ts";
import { cancelAllPeriodicTask, cancelAllTasks } from "./common/utils.ts";
import { versionNumberString2Number } from "./lib/src/string_and_binary/convert.ts";
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts"; import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts";
import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstractReplicator.js"; import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstractReplicator.js";
import { type KeyValueDatabase } from "./common/KeyValueDB.ts"; import { type KeyValueDatabase } from "./common/KeyValueDB.ts";
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts";
import { stopAllRunningProcessors } from "./lib/src/concurrency/processor.js";
import { reactiveSource, type ReactiveValue } from "./lib/src/dataobject/reactive.js"; import { reactiveSource, type ReactiveValue } from "./lib/src/dataobject/reactive.js";
import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js"; import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js";
import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js"; import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js";
import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js"; import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js";
import { ObsHttpHandler } from "./modules/essentialObsidian/APILib/ObsHttpHandler.js"; import { ObsHttpHandler } from "./modules/essentialObsidian/APILib/ObsHttpHandler.js";
import { $f, setLang } from "./lib/src/common/i18n.ts";
import { eventHub } from "./lib/src/hub/hub.ts";
import { EVENT_LAYOUT_READY, EVENT_PLUGIN_LOADED, EVENT_PLUGIN_UNLOADED, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED } from "./common/events.ts";
import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts"; import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts";
import { ModuleDev } from "./modules/extras/ModuleDev.ts"; import { ModuleDev } from "./modules/extras/ModuleDev.ts";
@@ -64,6 +57,8 @@ import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleRe
import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts"; import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts";
import { ModuleRebuilder } from "./modules/core/ModuleRebuilder.ts"; import { ModuleRebuilder } from "./modules/core/ModuleRebuilder.ts";
import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts"; import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts";
import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts";
import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts";
function throwShouldBeOverridden(): never { function throwShouldBeOverridden(): never {
@@ -85,31 +80,8 @@ const InterceptiveAny = Promise.resolve(undefined);
export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLocalDBEnv, LiveSyncReplicatorEnv, LiveSyncJournalReplicatorEnv, LiveSyncCouchDBReplicatorEnv, HasSettings<ObsidianLiveSyncSettings> { export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLocalDBEnv, LiveSyncReplicatorEnv, LiveSyncJournalReplicatorEnv, LiveSyncCouchDBReplicatorEnv, HasSettings<ObsidianLiveSyncSettings> {
_log = Logger;
settings!: ObsidianLiveSyncSettings;
localDatabase!: LiveSyncLocalDB;
replicator!: LiveSyncAbstractReplicator;
_suspended = false;
get suspended() {
return this._suspended || !this.settings?.isConfigured;
}
set suspended(value: boolean) {
this._suspended = value;
}
get shouldBatchSave() {
return this.settings?.batchSave && this.settings?.liveSync != true;
}
get batchSaveMinimumDelay(): number {
return this.settings?.batchSaveMinimumDelay ?? DEFAULT_SETTINGS.batchSaveMinimumDelay
}
get batchSaveMaximumDelay(): number {
return this.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay
}
deviceAndVaultName = "";
isReady = false;
packageVersion = "";
manifestVersion = "";
// --> Module System // --> Module System
getAddOn<T extends LiveSyncCommands>(cls: string) { getAddOn<T extends LiveSyncCommands>(cls: string) {
@@ -123,6 +95,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
addOns = [new ConfigSync(this), new HiddenFileSync(this)] as LiveSyncCommands[]; addOns = [new ConfigSync(this), new HiddenFileSync(this)] as LiveSyncCommands[];
modules = [ modules = [
new ModuleLiveSyncMain(this),
new ModuleExtraSyncObsidian(this, this),
// Only on Obsidian // Only on Obsidian
new ModuleDatabaseFileAccess(this), new ModuleDatabaseFileAccess(this),
// Common // Common
@@ -166,48 +140,53 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
new ModuleIntegratedTest(this, this), new ModuleIntegratedTest(this, this),
] as (IObsidianModule | AbstractModule)[]; ] 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() } $$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. // Following are plugged by the modules.
settings!: ObsidianLiveSyncSettings;
localDatabase!: LiveSyncLocalDB;
simpleStore!: SimpleStore<CheckPointInfo>
replicator!: LiveSyncAbstractReplicator;
confirm!: Confirm; confirm!: Confirm;
storageAccess!: StorageAccess; storageAccess!: StorageAccess;
databaseFileAccess!: DatabaseFileAccess; databaseFileAccess!: DatabaseFileAccess;
fileHandler!: ModuleFileHandler; fileHandler!: ModuleFileHandler;
rebuilder!: Rebuilder; rebuilder!: Rebuilder;
// implementing interfaces
kvDB!: KeyValueDatabase; kvDB!: KeyValueDatabase;
last_successful_post = false;
totalFileEventCount = 0;
$$customFetchHandler(): ObsHttpHandler {
throw new Error("This function should be overridden by the module.");
}
customFetchHandler() {
return this.$$customFetchHandler();
}
getLastPostFailedBySize() {
return !this.last_successful_post;
}
getDatabase(): PouchDB.Database<EntryDoc> { return this.localDatabase.localDatabase; } getDatabase(): PouchDB.Database<EntryDoc> { return this.localDatabase.localDatabase; }
getSettings(): ObsidianLiveSyncSettings { return this.settings; } getSettings(): ObsidianLiveSyncSettings { return this.settings; }
getIsMobile(): boolean { return this.isMobile; }
$$markFileListPossiblyChanged(): void { throwShouldBeOverridden(); }
$$customFetchHandler(): ObsHttpHandler { throwShouldBeOverridden(); }
$$getLastPostFailedBySize(): boolean { throwShouldBeOverridden(); }
$$isStorageInsensitive(): boolean { throwShouldBeOverridden() } $$isStorageInsensitive(): boolean { throwShouldBeOverridden() }
get shouldCheckCaseInsensitive() { $$shouldCheckCaseInsensitive(): boolean { throwShouldBeOverridden(); }
if (this.$$isStorageInsensitive()) return false;
return !this.settings.handleFilenameCaseSensitive;
}
_unloaded = false; $$isUnloaded(): boolean { throwShouldBeOverridden(); }
requestCount = reactiveSource(0); requestCount = reactiveSource(0);
responseCount = reactiveSource(0); responseCount = reactiveSource(0);
@@ -222,9 +201,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
processingFileEventCount = reactiveSource(0); processingFileEventCount = reactiveSource(0);
_totalProcessingCount?: ReactiveValue<number>; _totalProcessingCount?: ReactiveValue<number>;
get isReloadingScheduled() {
return this._totalProcessingCount !== undefined;
}
replicationStat = reactiveSource({ replicationStat = reactiveSource({
sent: 0, sent: 0,
@@ -236,14 +213,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
syncStatus: "CLOSED" as DatabaseConnectingStatus syncStatus: "CLOSED" as DatabaseConnectingStatus
}); });
get isMobile() { return this.$$isMobile(); } $$isReloadingScheduled(): boolean { throwShouldBeOverridden(); }
$$getReplicator(): LiveSyncAbstractReplicator { throwShouldBeOverridden(); }
// Plug-in's overrideable functions
onload() { void this.onLiveSyncLoad(); }
async saveSettings() { await this.$$saveSettingData(); }
onunload() { return void this.onLiveSyncUnload(); }
// <-- Plug-in's overrideable functions
$$connectRemoteCouchDB(uri: string, auth: { $$connectRemoteCouchDB(uri: string, auth: {
username: string; username: string;
@@ -286,20 +257,16 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> { return InterceptiveEvery; } $everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> { return InterceptiveEvery; }
getReplicator() {
return this.replicator;
}
// end interfaces // end interfaces
$$getVaultName(): string { throwShouldBeOverridden(); } $$getVaultName(): string { throwShouldBeOverridden(); }
simpleStore!: SimpleStore<CheckPointInfo>
$$getSimpleStore<T>(kind: string): SimpleStore<T> { throwShouldBeOverridden(); } $$getSimpleStore<T>(kind: string): SimpleStore<T> { throwShouldBeOverridden(); }
// trench!: Trench; // trench!: Trench;
// --> Events // --> Events
/* /*
@@ -344,139 +311,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
$everyOnFirstInitialize(): Promise<boolean> { return InterceptiveEvery } $everyOnFirstInitialize(): Promise<boolean> { return InterceptiveEvery }
// Some Module should call this function to start the plugin. // Some Module should call this function to start the plugin.
async onLiveSyncReady() { $$onLiveSyncReady(): Promise<false | undefined> { throwShouldBeOverridden(); }
if (!await this.$everyOnLayoutReady()) return; $$wireUpEvents(): void { throwShouldBeOverridden(); }
eventHub.emitEvent(EVENT_LAYOUT_READY); $$onLiveSyncLoad(): Promise<void> { throwShouldBeOverridden(); }
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
const ANSWER_KEEP = "Keep this plug-in suspended";
const ANSWER_RESUME = "Resume and restart Obsidian";
const message = `Self-hosted LiveSync has been configured to ignore some events. Is this intentional for you?
| Type | Status | Note | $$onLiveSyncUnload(): Promise<void> { throwShouldBeOverridden(); }
|:---:|:---:|---|
| Storage Events | ${this.settings.suspendFileWatching ? "suspended" : "active"} | Every modification will be ignored |
| Database Events | ${this.settings.suspendParseReplicationResult ? "suspended" : "active"} | Every synchronised change will be postponed |
Do you want to resume them and restart Obsidian?
> [!DETAILS]-
> These flags are set by the plug-in while rebuilding, or fetching. If the process ends abnormally, it may be kept unintended.
> If you are not sure, you can try to rerun these processes. Make sure to back your vault up.
`;
if (await this.confirm.askSelectStringDialogue(message, [ANSWER_KEEP, ANSWER_RESUME], { defaultAction: ANSWER_KEEP, title: "Scram Enabled" }) == ANSWER_RESUME) {
this.settings.suspendFileWatching = false;
this.settings.suspendParseReplicationResult = false;
await this.saveSettings();
await this.$$scheduleAppReload();
return;
}
}
const isInitialized = await this.$$initializeDatabase(false, false);
if (!isInitialized) {
//TODO:stop all sync.
return false;
}
if (!await this.$everyOnFirstInitialize()) return;
await this.realizeSettingSyncMode();
fireAndForget(async () => {
Logger(`Additional safety scan..`, LOG_LEVEL_VERBOSE);
if (!await this.$allScanStat()) {
Logger(`Additional safety scan has been failed on some module`, LOG_LEVEL_NOTICE);
} else {
Logger(`Additional safety scan done`, LOG_LEVEL_VERBOSE);
}
});
}
wireUpEvents() {
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: CustomEvent<ObsidianLiveSyncSettings>) => {
const settings = evt.detail;
this.localDatabase.settings = settings;
setLang(settings.displayLanguage);
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
});
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: CustomEvent<ObsidianLiveSyncSettings>) => {
fireAndForget(() => this.realizeSettingSyncMode());
})
}
async onLiveSyncLoad() {
this.wireUpEvents();
// debugger;
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this);
Logger("loading plugin");
if (!await this.$everyOnloadStart()) {
Logger("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE);
return;
}
// this.addUIs();
//@ts-ignore
const manifestVersion: string = MANIFEST_VERSION || "0.0.0";
//@ts-ignore
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
this.manifestVersion = manifestVersion;
this.packageVersion = packageVersion;
Logger($f`Self-hosted LiveSync${" v"}${manifestVersion} ${packageVersion}`);
await this.$$loadSettings();
if (!await this.$everyOnloadAfterLoadSettings()) {
Logger("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE);
return;
}
const lsKey = "obsidian-live-sync-ver" + this.$$getVaultName();
const last_version = localStorage.getItem(lsKey);
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) {
Logger($f`You have some unread release notes! Please read them once!`, LOG_LEVEL_NOTICE);
}
//@ts-ignore
if (this.isMobile) {
this.settings.disableRequestURI = true;
}
if (last_version && Number(last_version) < VER) {
this.settings.liveSync = false;
this.settings.syncOnSave = false;
this.settings.syncOnEditorSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.syncAfterMerge = false;
this.settings.periodicReplication = false;
this.settings.versionUpFlash = $f`Self-hosted LiveSync has been upgraded and some behaviors have changed incompatibly. All automatic synchronization is now disabled temporary. Ensure that other devices are also upgraded, and enable synchronization again.`;
await this.saveSettings();
}
localStorage.setItem(lsKey, `${VER}`);
await this.$$openDatabase();
this.realizeSettingSyncMode = this.realizeSettingSyncMode.bind(this);
// this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this);
// this.$$replicate = this.$$replicate.bind(this);
this.onLiveSyncReady = this.onLiveSyncReady.bind(this);
await this.$everyOnload();
await Promise.all(this.addOns.map(e => e.onload()));
}
async onLiveSyncUnload() {
eventHub.emitEvent(EVENT_PLUGIN_UNLOADED);
await this.$allStartOnUnload();
cancelAllPeriodicTask();
cancelAllTasks();
stopAllRunningProcessors();
await this.$allOnUnload();
this._unloaded = true;
for (const addOn of this.addOns) {
addOn.onunload();
}
if (this.localDatabase != null) {
this.localDatabase.onunload();
if (this.replicator) {
this.replicator?.closeReplication();
}
await this.localDatabase.close();
}
Logger($f`unloading plugin`);
}
$allScanStat(): Promise<boolean> { $allScanStat(): Promise<boolean> {
return InterceptiveAll; return InterceptiveAll;
@@ -496,18 +335,7 @@ Do you want to resume them and restart Obsidian?
$$openDatabase(): Promise<boolean> { throwShouldBeOverridden() } $$openDatabase(): Promise<boolean> { throwShouldBeOverridden() }
async realizeSettingSyncMode() { $$realizeSettingSyncMode(): Promise<void> { throwShouldBeOverridden(); }
await this.$everyBeforeSuspendProcess();
await this.$everyBeforeRealizeSetting();
this.localDatabase.refreshSettings();
await this.$everyCommitPendingFileEvent();
await this.$everyRealizeSettingSyncMode();
// disable all sync temporary.
if (this.suspended) return;
await this.$everyOnResumeProcess();
await this.$everyAfterResumeProcess();
await this.$everyAfterRealizeSetting();
}
$$performRestart() { throwShouldBeOverridden(); } $$performRestart() { throwShouldBeOverridden(); }
$$clearUsedPassphrase(): void { throwShouldBeOverridden() } $$clearUsedPassphrase(): void { throwShouldBeOverridden() }
@@ -568,6 +396,9 @@ Do you want to resume them and restart Obsidian?
$$askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> { throwShouldBeOverridden(); } $$askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> { throwShouldBeOverridden(); }
$$checkAndAskUseRemoteConfiguration(settings: RemoteDBSettings): Promise<{ result: false | TweakValues, requireFetch: boolean }> { throwShouldBeOverridden(); }
$everyBeforeReplicate(showMessage: boolean): Promise<boolean> { return InterceptiveEvery; } $everyBeforeReplicate(showMessage: boolean): Promise<boolean> { return InterceptiveEvery; }
$$replicate(showMessage: boolean = false): Promise<boolean | void> { throwShouldBeOverridden() } $$replicate(showMessage: boolean = false): Promise<boolean | void> { throwShouldBeOverridden() }
@@ -636,12 +467,17 @@ Do you want to resume them and restart Obsidian?
$everyModuleTestMultiDevice(): Promise<boolean> { return InterceptiveEvery; } $everyModuleTestMultiDevice(): Promise<boolean> { return InterceptiveEvery; }
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void { throwShouldBeOverridden(); } $$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void { throwShouldBeOverridden(); }
_isMainReady(): boolean { return this.isReady; }
_isMainSuspended(): boolean { return this.suspended; }
_isThisModuleEnabled(): boolean { return true; } _isThisModuleEnabled(): boolean { return true; }
_isDatabaseReady(): boolean { return this.localDatabase.isReady; }
$anyGetAppId(): Promise<string | undefined> { return InterceptiveAny; } $anyGetAppId(): Promise<string | undefined> { return InterceptiveAny; }
// Plug-in's overrideable functions
onload() { void this.$$onLiveSyncLoad(); }
async saveSettings() { await this.$$saveSettingData(); }
onunload() { return void this.$$onLiveSyncUnload(); }
// <-- Plug-in's overrideable functions
} }

View File

@@ -39,13 +39,13 @@ export abstract class AbstractObsidianModule extends AbstractModule {
_isMainReady() { _isMainReady() {
return this.core._isMainReady(); return this.core.$$isReady();
} }
_isMainSuspended() { _isMainSuspended() {
return this.core._isMainSuspended(); return this.core.$$isSuspended();
} }
_isDatabaseReady() { _isDatabaseReady() {
return this.core._isDatabaseReady(); return this.core.$$isDatabaseReady();
} }
//should be overridden //should be overridden

View File

@@ -133,6 +133,10 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
//upsert should locked //upsert should locked
const msg = `STORAGE -> DB (${datatype}) `; const msg = `STORAGE -> DB (${datatype}) `;
const isNotChanged = await serialized("file-" + fullPath, async () => { const isNotChanged = await serialized("file-" + fullPath, async () => {
if (force) {
this._log(msg + "Force writing " + fullPath, LOG_LEVEL_VERBOSE);
return false;
}
// Commented out temporarily: this checks that the file was made ourself. // Commented out temporarily: this checks that the file was made ourself.
// if (this.core.storageAccess.recentlyTouched(file)) { // if (this.core.storageAccess.recentlyTouched(file)) {
// return true; // return true;

View File

@@ -223,12 +223,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
} }
const docData = readContent(docRead); const docData = readContent(docRead);
if (!existOnStorage) { if (existOnStorage && !force) {
// The file is not exist on the storage. We do not care about the differences. // The file is exist on the storage. Let's check the difference between the file and the entry.
await this.storage.ensureDir(path); // But, if force is true, then it should be updated.
return await this.storage.writeFileAuto(path, docData, { ctime: docRead.ctime, mtime: docRead.mtime });
}
if (!force) {
// Ok, we have to compare. // Ok, we have to compare.
let shouldApplied = false; let shouldApplied = false;
// 1. if the time stamp is far different, then it should be updated. // 1. if the time stamp is far different, then it should be updated.
@@ -257,6 +254,8 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
return true; return true;
} }
// Let's apply the changes. // Let's apply the changes.
} else {
this._log(`File ${docRead.path} ${existOnStorage ? "(new) " : ""} ${force ? " (forced)" : ""}`, LOG_LEVEL_VERBOSE);
} }
await this.storage.ensureDir(path); await this.storage.ensureDir(path);
const ret = await this.storage.writeFileAuto(path, docData, { ctime: docRead.ctime, mtime: docRead.mtime }); const ret = await this.storage.writeFileAuto(path, docData, { ctime: docRead.ctime, mtime: docRead.mtime });

View File

@@ -20,4 +20,8 @@ export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICore
return await this.localDatabase.initializeDatabase(); return await this.localDatabase.initializeDatabase();
} }
$$isDatabaseReady(): boolean {
return this.localDatabase != null && this.localDatabase.isReady;
}
} }

View File

@@ -40,7 +40,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
await this.core.$allSuspendExtraSync(); await this.core.$allSuspendExtraSync();
this.core.settings.isConfigured = true; this.core.settings.isConfigured = true;
await this.core.realizeSettingSyncMode(); await this.core.$$realizeSettingSyncMode();
await this.core.$$markRemoteLocked(); await this.core.$$markRemoteLocked();
await this.core.$$tryResetRemoteDatabase(); await this.core.$$tryResetRemoteDatabase();
await this.core.$$markRemoteLocked(); await this.core.$$markRemoteLocked();
@@ -60,7 +60,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
await this.core.$allSuspendExtraSync(); await this.core.$allSuspendExtraSync();
await this.askUseNewAdapter(); await this.askUseNewAdapter();
this.core.settings.isConfigured = true; this.core.settings.isConfigured = true;
await this.core.realizeSettingSyncMode(); await this.core.$$realizeSettingSyncMode();
await this.resetLocalDatabase(); await this.resetLocalDatabase();
await delay(1000); await delay(1000);
await this.core.$$initializeDatabase(true); await this.core.$$initializeDatabase(true);
@@ -164,11 +164,12 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
await this.askUseNewAdapter(); await this.askUseNewAdapter();
this.core.settings.isConfigured = true; this.core.settings.isConfigured = true;
await this.suspendReflectingDatabase(); await this.suspendReflectingDatabase();
await this.core.realizeSettingSyncMode(); await this.core.$$realizeSettingSyncMode();
await this.resetLocalDatabase(); await this.resetLocalDatabase();
await delay(1000); await delay(1000);
await this.core.$$openDatabase(); await this.core.$$openDatabase();
this.core.isReady = true; // this.core.isReady = true;
this.core.$$markIsReady();
if (makeLocalChunkBeforeSync) { if (makeLocalChunkBeforeSync) {
await this.core.fileHandler.createAllChunks(true); await this.core.fileHandler.createAllChunks(true);
} }
@@ -201,8 +202,8 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
async fetchRemoteChunks() { async fetchRemoteChunks() {
if (!this.core.settings.doNotSuspendOnFetching && this.core.settings.readChunksOnline && this.core.settings.remoteType == REMOTE_COUCHDB) { if (!this.core.settings.doNotSuspendOnFetching && this.core.settings.readChunksOnline && this.core.settings.remoteType == REMOTE_COUCHDB) {
this._log(`Fetching chunks`, LOG_LEVEL_NOTICE); this._log(`Fetching chunks`, LOG_LEVEL_NOTICE);
const replicator = this.core.getReplicator() as LiveSyncCouchDBReplicator; const replicator = this.core.$$getReplicator() as LiveSyncCouchDBReplicator;
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(this.settings, this.core.getIsMobile(), true); const remoteDB = await replicator.connectRemoteCouchDBWithSetting(this.settings, this.core.$$isMobile(), true);
if (typeof remoteDB == "string") { if (typeof remoteDB == "string") {
this._log(remoteDB, LOG_LEVEL_NOTICE); this._log(remoteDB, LOG_LEVEL_NOTICE);
} else { } else {

View File

@@ -14,12 +14,13 @@ import { getPath, isChunk, isValidPath, scheduleTask } from "../../common/utils"
import { sendValue } from "octagonal-wheels/messagepassing/signal"; import { sendValue } from "octagonal-wheels/messagepassing/signal";
import { isAnyNote } from "../../lib/src/common/utils"; import { isAnyNote } from "../../lib/src/common/utils";
import { EVENT_FILE_SAVED, eventHub } from "../../common/events"; import { EVENT_FILE_SAVED, eventHub } from "../../common/events";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
export class ModuleReplicator extends AbstractModule implements ICoreModule { export class ModuleReplicator extends AbstractModule implements ICoreModule {
$everyOnloadAfterLoadSettings(): Promise<boolean> { $everyOnloadAfterLoadSettings(): Promise<boolean> {
eventHub.onEvent(EVENT_FILE_SAVED, () => { eventHub.onEvent(EVENT_FILE_SAVED, () => {
if (this.settings.syncOnSave && !this.core.suspended) { if (this.settings.syncOnSave && !this.core.$$isSuspended()) {
scheduleTask("perform-replicate-after-save", 250, () => this.core.$$waitForReplicationOnce()); scheduleTask("perform-replicate-after-save", 250, () => this.core.$$waitForReplicationOnce());
} }
}) })
@@ -36,6 +37,11 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
await yieldMicrotask(); await yieldMicrotask();
return true; return true;
} }
$$getReplicator(): LiveSyncAbstractReplicator {
return this.core.replicator;
}
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> { $everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
return this.setReplicator(); return this.setReplicator();
} }
@@ -51,7 +57,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
} }
async $$replicate(showMessage: boolean = false): Promise<boolean | void> { async $$replicate(showMessage: boolean = false): Promise<boolean | void> {
//--? //--?
if (!this.core.isReady) return; if (!this.core.$$isReady()) return;
if (isLockAcquired("cleanup")) { if (isLockAcquired("cleanup")) {
Logger("Database cleaning up is in process. replication has been cancelled", LOG_LEVEL_NOTICE); Logger("Database cleaning up is in process. replication has been cancelled", LOG_LEVEL_NOTICE);
return; return;
@@ -97,9 +103,9 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
await this.core.rebuilder.$performRebuildDB("localOnly"); await this.core.rebuilder.$performRebuildDB("localOnly");
} }
if (ret == CHOICE_CLEAN) { if (ret == CHOICE_CLEAN) {
const replicator = this.core.getReplicator(); const replicator = this.core.$$getReplicator();
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return; if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(this.settings, this.core.getIsMobile(), true); const remoteDB = await replicator.connectRemoteCouchDBWithSetting(this.settings, this.core.$$isMobile(), true);
if (typeof remoteDB == "string") { if (typeof remoteDB == "string") {
Logger(remoteDB, LOG_LEVEL_NOTICE); Logger(remoteDB, LOG_LEVEL_NOTICE);
return false; return false;
@@ -112,7 +118,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 balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false); await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
this.localDatabase.hashCaches.clear(); this.localDatabase.hashCaches.clear();
await this.core.getReplicator().markRemoteResolved(this.settings); await this.core.$$getReplicator().markRemoteResolved(this.settings);
Logger("The local database has been cleaned up.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO) Logger("The local database has been cleaned up.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO)
} else { } else {
Logger("Replication has been cancelled. Please try it again.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO) Logger("Replication has been cancelled. Please try it again.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO)
@@ -315,7 +321,7 @@ Or if you are sure know what had been happened, we can unlock the database from
} }
async $$replicateAllToServer(showingNotice: boolean = false, sendChunksInBulkDisabled: boolean = false): Promise<boolean> { async $$replicateAllToServer(showingNotice: boolean = false, sendChunksInBulkDisabled: boolean = false): Promise<boolean> {
if (!this.core.isReady) return false; if (!this.core.$$isReady()) return false;
if (!await this.core.$everyBeforeReplicate(showingNotice)) { if (!await this.core.$everyBeforeReplicate(showingNotice)) {
Logger(`Replication has been cancelled by some module failure`, LOG_LEVEL_NOTICE); Logger(`Replication has been cancelled by some module failure`, LOG_LEVEL_NOTICE);
return false; return false;
@@ -334,7 +340,7 @@ Or if you are sure know what had been happened, we can unlock the database from
return !checkResult; return !checkResult;
} }
async $$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> { async $$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
if (!this.core.isReady) return false; if (!this.core.$$isReady()) return false;
const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice); const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice);
if (ret) return true; if (ret) return true;
const checkResult = await this.core.$anyAfterConnectCheckFailed(); const checkResult = await this.core.$anyAfterConnectCheckFailed();

View File

@@ -13,10 +13,10 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
} }
$everyOnload(): Promise<boolean> { $everyOnload(): Promise<boolean> {
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: CustomEvent<ObsidianLiveSyncSettings>) => { eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => {
this.reloadIgnoreFiles(); this.reloadIgnoreFiles();
}); });
eventHub.onEvent(EVENT_REQUEST_RELOAD_SETTING_TAB, (evt: CustomEvent<ObsidianLiveSyncSettings>) => { eventHub.onEvent(EVENT_REQUEST_RELOAD_SETTING_TAB, () => {
this.reloadIgnoreFiles(); this.reloadIgnoreFiles();
}); });
return Promise.resolve(true); return Promise.resolve(true);
@@ -45,8 +45,13 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
return false; return false;
} }
$$markFileListPossiblyChanged(): void {
this.totalFileEventCount++;
}
totalFileEventCount = 0;
get fileListPossiblyChanged() { get fileListPossiblyChanged() {
if (isDirty("totalFileEventCount", this.core.totalFileEventCount)) { if (isDirty("totalFileEventCount", this.totalFileEventCount)) {
return true; return true;
} }
return false; return false;
@@ -88,7 +93,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
const filepath = getPathFromUXFileInfo(file); const filepath = getPathFromUXFileInfo(file);
const lc = filepath.toLowerCase(); const lc = filepath.toLowerCase();
if (this.core.shouldCheckCaseInsensitive) { if (this.core.$$shouldCheckCaseInsensitive()) {
if (lc in fileCount && fileCount[lc] > 1) { if (lc in fileCount && fileCount[lc] > 1) {
return false; return false;
} }

View File

@@ -99,7 +99,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
} }
if (conflictCheckResult === AUTO_MERGED) { if (conflictCheckResult === AUTO_MERGED) {
//auto resolved, but need check again; //auto resolved, but need check again;
if (this.settings.syncAfterMerge && !this.core.suspended) { if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
//Wait for the running replication, if not running replication, run it once. //Wait for the running replication, if not running replication, run it once.
await this.core.$$waitForReplicationOnce(); await this.core.$$waitForReplicationOnce();
} }

View File

@@ -1,6 +1,6 @@
import { Logger, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger"; import { Logger, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
import { extractObject } from "octagonal-wheels/object"; import { extractObject } from "octagonal-wheels/object";
import { TweakValuesShouldMatchedTemplate, CompatibilityBreakingTweakValues, confName, type TweakValues } from "../../lib/src/common/types.ts"; import { TweakValuesShouldMatchedTemplate, CompatibilityBreakingTweakValues, confName, type TweakValues, type RemoteDBSettings } from "../../lib/src/common/types.ts";
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts"; import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
import { AbstractModule } from "../AbstractModule.ts"; import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts"; import type { ICoreModule } from "../ModuleTypes.ts";
@@ -94,4 +94,78 @@ Please select which one you want to use.
return "IGNORE"; return "IGNORE";
} }
async $$checkAndAskUseRemoteConfiguration(trialSetting: RemoteDBSettings): Promise<{ result: false | TweakValues, requireFetch: boolean }> {
const replicator = await this.core.$anyNewReplicator(trialSetting);
if (await replicator.tryConnectRemote(trialSetting)) {
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
if (preferred) {
const items = Object.entries(TweakValuesShouldMatchedTemplate);
let rebuildRequired = false;
// Making tables:
let table = `| Value name | This device | Stored | \n` + `|: --- |: ---- :|: ---- :| \n`;
let differenceCount = 0;
// const items = [mine,preferred]
for (const v of items) {
const key = v[0] as keyof typeof TweakValuesShouldMatchedTemplate;
const valuePreferred = escapeMarkdownValue(preferred[key]);
const currentDisp = `${escapeMarkdownValue((trialSetting as TweakValues)?.[key])} |`;
if ((trialSetting as TweakValues)?.[key] !== preferred[key]) {
if (CompatibilityBreakingTweakValues.indexOf(key) !== -1) {
rebuildRequired = true;
}
} else {
continue;
}
table += `| ${confName(key)} | ${currentDisp} ${valuePreferred} | \n`;
differenceCount++;
}
if (differenceCount === 0) {
this._log("The settings in the remote database are the same as the local database.", LOG_LEVEL_NOTICE);
return { result: false, requireFetch: false };
}
const additionalMessage = (rebuildRequired && this.core.settings.isConfigured) ? `
>[!WARNING]
> Some remote configurations are not compatible with the local database of this device. Rebuilding the local database will be required.
***Please ensure that you have time and are connected to a stable network to apply!***` : "";
const message = `
The settings in the remote database are as follows.
If you want to use these settings, please select "Use configured".
If you want to keep the settings of this device, please select "Dismiss".
${table}
>[!TIP]
> If you want to synchronise all settings, please use \`Sync settings via markdown\` after applying minimal configuration with this feature.
${additionalMessage}`;
const CHOICE_USE_REMOTE = "Use configured";
const CHOICE_DISMISS = "Dismiss";
// const CHOICE_AND_VALUES = [
// [CHOICE_USE_REMOTE, preferred],
// [CHOICE_DISMISS, false]]
const CHOICES = [CHOICE_USE_REMOTE, CHOICE_DISMISS];
const retKey = await this.core.confirm.askSelectStringDialogue(message, CHOICES, {
title: "Use Remote Configuration",
timeout: 0,
defaultAction: CHOICE_DISMISS
});
if (!retKey) return { result: false, requireFetch: false };
if (retKey === CHOICE_DISMISS) return { result: false, requireFetch: false };
if (retKey === CHOICE_USE_REMOTE) {
return { result: { ...trialSetting, ...preferred }, requireFetch: rebuildRequired };
}
} else {
this._log("Failed to get the preferred tweak values from the remote server.", LOG_LEVEL_NOTICE);
}
return { result: false, requireFetch: false };
} else {
this._log("Failed to connect to the remote server.", LOG_LEVEL_NOTICE);
return { result: false, requireFetch: false };
}
}
} }

View File

@@ -43,6 +43,11 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
return this.vaultAccess.isStorageInsensitive(); return this.vaultAccess.isStorageInsensitive();
} }
$$shouldCheckCaseInsensitive(): boolean {
if (this.$$isStorageInsensitive()) return false;
return !this.settings.handleFilenameCaseSensitive;
}
async writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean> { async writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean> {
const file = this.vaultAccess.getAbstractFileByPath(path); const file = this.vaultAccess.getAbstractFileByPath(path);
if (file instanceof TFile) { if (file instanceof TFile) {
@@ -115,6 +120,9 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
async removeHidden(path: string): Promise<boolean> { async removeHidden(path: string): Promise<boolean> {
try { try {
await this.vaultAccess.adapterRemove(path); await this.vaultAccess.adapterRemove(path);
if (this.vaultAccess.adapterStat(path) !== null) {
return false;
}
return true; return true;
} catch (e) { } catch (e) {
this._log(`Could not remove hidden file: ${path}`, LOG_LEVEL_VERBOSE); this._log(`Could not remove hidden file: ${path}`, LOG_LEVEL_VERBOSE);

View File

@@ -8,7 +8,7 @@ class AutoClosableModal extends Modal {
constructor(app: App) { constructor(app: App) {
super(app); super(app);
this.removeEvent = eventHub.on(EVENT_PLUGIN_UNLOADED, async () => { this.removeEvent = eventHub.onEvent(EVENT_PLUGIN_UNLOADED, async () => {
await delay(100); await delay(100);
if (!this.removeEvent) return; if (!this.removeEvent) return;
this.close(); this.close();

View File

@@ -1,7 +1,7 @@
import { TAbstractFile, TFile, TFolder } from "../../../deps.ts"; import { TAbstractFile, TFile, TFolder } from "../../../deps.ts";
import { Logger } from "../../../lib/src/common/logger.ts"; import { Logger } from "../../../lib/src/common/logger.ts";
import { shouldBeIgnored } from "../../../lib/src/string_and_binary/path.ts"; import { shouldBeIgnored } from "../../../lib/src/string_and_binary/path.ts";
import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, type FilePath, type FilePathWithPrefix, type UXFileInfoStub, type UXInternalFileInfoStub } from "../../../lib/src/common/types.ts"; import { DEFAULT_SETTINGS, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, type FilePath, type FilePathWithPrefix, type UXFileInfoStub, type UXInternalFileInfoStub } from "../../../lib/src/common/types.ts";
import { delay, fireAndForget } from "../../../lib/src/common/utils.ts"; import { delay, fireAndForget } from "../../../lib/src/common/utils.ts";
import { type FileEventItem, type FileEventType } from "../../../common/types.ts"; import { type FileEventItem, type FileEventType } from "../../../common/types.ts";
import { serialized, skipIfDuplicated } from "../../../lib/src/concurrency/lock.ts"; import { serialized, skipIfDuplicated } from "../../../lib/src/concurrency/lock.ts";
@@ -38,13 +38,13 @@ export class StorageEventManagerObsidian extends StorageEventManager {
core: LiveSyncCore; core: LiveSyncCore;
get shouldBatchSave() { get shouldBatchSave() {
return this.plugin.shouldBatchSave; return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
} }
get batchSaveMinimumDelay(): number { get batchSaveMinimumDelay(): number {
return this.plugin.batchSaveMinimumDelay; return this.core.settings?.batchSaveMinimumDelay ?? DEFAULT_SETTINGS.batchSaveMinimumDelay
} }
get batchSaveMaximumDelay(): number { get batchSaveMaximumDelay(): number {
return this.plugin.batchSaveMaximumDelay return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay
} }
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) { constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
super(); super();
@@ -155,9 +155,9 @@ export class StorageEventManagerObsidian extends StorageEventManager {
} }
// Cache file and waiting to can be proceed. // Cache file and waiting to can be proceed.
async appendQueue(params: FileEvent[], ctx?: any) { async appendQueue(params: FileEvent[], ctx?: any) {
if (!this.plugin.settings.isConfigured) return; if (!this.core.settings.isConfigured) return;
if (this.plugin.settings.suspendFileWatching) return; if (this.core.settings.suspendFileWatching) return;
this.plugin.totalFileEventCount++; this.core.$$markFileListPossiblyChanged();
// Flag up to be reload // Flag up to be reload
const processFiles = new Set<FilePath>(); const processFiles = new Set<FilePath>();
for (const param of params) { for (const param of params) {
@@ -176,13 +176,13 @@ export class StorageEventManagerObsidian extends StorageEventManager {
const oldPath = param.oldPath; const oldPath = param.oldPath;
if (type !== "INTERNAL") { if (type !== "INTERNAL") {
const size = (file as UXFileInfoStub).stat.size; const size = (file as UXFileInfoStub).stat.size;
if (this.plugin.$$isFileSizeExceeded(size) && (type == "CREATE" || type == "CHANGED")) { if (this.core.$$isFileSizeExceeded(size) && (type == "CREATE" || type == "CHANGED")) {
Logger(`The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`, LOG_LEVEL_NOTICE); Logger(`The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`, LOG_LEVEL_NOTICE);
continue; continue;
} }
} }
if (file instanceof TFolder) continue; if (file instanceof TFolder) continue;
if (!await this.plugin.$$isTargetFile(file.path)) continue; if (!await this.core.$$isTargetFile(file.path)) continue;
// Stop cache using to prevent the corruption; // Stop cache using to prevent the corruption;
// let cache: null | string | ArrayBuffer; // let cache: null | string | ArrayBuffer;
@@ -190,7 +190,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) { if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) {
// Wait for a bit while to let the writer has marked `touched` at the file. // Wait for a bit while to let the writer has marked `touched` at the file.
await delay(10); await delay(10);
if (this.plugin.storageAccess.recentlyTouched(file)) { if (this.core.storageAccess.recentlyTouched(file)) {
continue; continue;
} }
} }
@@ -338,11 +338,11 @@ export class StorageEventManagerObsidian extends StorageEventManager {
const key = `file-last-proc-${queue.type}-${file.path}`; const key = `file-last-proc-${queue.type}-${file.path}`;
const last = Number(await this.core.kvDB.get(key) || 0); const last = Number(await this.core.kvDB.get(key) || 0);
if (queue.type == "INTERNAL" || file.isInternal) { if (queue.type == "INTERNAL" || file.isInternal) {
await this.plugin.$anyProcessOptionalFileEvent(file.path as unknown as FilePath); await this.core.$anyProcessOptionalFileEvent(file.path as unknown as FilePath);
} else { } else {
// let mtime = file.stat.mtime; // let mtime = file.stat.mtime;
if (queue.type == "DELETE") { if (queue.type == "DELETE") {
await this.plugin.$anyHandlerProcessesFileEvent(queue); await this.core.$anyHandlerProcessesFileEvent(queue);
} else { } else {
if (file.stat.mtime == last) { if (file.stat.mtime == last) {
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE); Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
@@ -350,7 +350,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
// this.cancelRelativeEvent(queue); // this.cancelRelativeEvent(queue);
return; return;
} }
if (!await this.plugin.$anyHandlerProcessesFileEvent(queue)) { if (!await this.core.$anyHandlerProcessesFileEvent(queue)) {
Logger(`STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`, LOG_LEVEL_INFO); Logger(`STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`, LOG_LEVEL_INFO);
// cancel running queues and remove one of atomic operation (e.g. rename) // cancel running queues and remove one of atomic operation (e.g. rename)
this.cancelRelativeEvent(queue); this.cancelRelativeEvent(queue);

View File

@@ -307,7 +307,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
} }
async $$initializeDatabase(showingNotice: boolean = false, reopenDatabase = true): Promise<boolean> { async $$initializeDatabase(showingNotice: boolean = false, reopenDatabase = true): Promise<boolean> {
this.core.isReady = false; this.core.$$resetIsReady();
if ((!reopenDatabase) || await this.core.$$openDatabase()) { if ((!reopenDatabase) || await this.core.$$openDatabase()) {
if (this.localDatabase.isReady) { if (this.localDatabase.isReady) {
await this.core.$$performFullScan(showingNotice); await this.core.$$performFullScan(showingNotice);
@@ -316,12 +316,12 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
this._log(`Initializing database has been failed on some module`, LOG_LEVEL_NOTICE); this._log(`Initializing database has been failed on some module`, LOG_LEVEL_NOTICE);
return false; return false;
} }
this.core.isReady = true; this.core.$$markIsReady();
// run queued event once. // run queued event once.
await this.core.$everyCommitPendingFileEvent(); await this.core.$everyCommitPendingFileEvent();
return true; return true;
} else { } else {
this.core.isReady = false; this.core.$$resetIsReady();
return false; return false;
} }
} }

View File

@@ -33,10 +33,14 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
authHeader = reactive(() => authHeader = reactive(() =>
this.authHeaderSource.value == "" ? "" : "Basic " + window.btoa(this.authHeaderSource.value)); this.authHeaderSource.value == "" ? "" : "Basic " + window.btoa(this.authHeaderSource.value));
last_successful_post = false;
$$customFetchHandler(): ObsHttpHandler { $$customFetchHandler(): ObsHttpHandler {
if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined); if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
return this._customHandler; return this._customHandler;
} }
$$getLastPostFailedBySize(): boolean {
return !this.last_successful_post;
}
async $$connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | false, useDynamicIterationCount: boolean, performSetup: boolean, skipInfo: boolean, compression: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> { async $$connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | false, useDynamicIterationCount: boolean, performSetup: boolean, skipInfo: boolean, compression: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> {
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid"; if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
@@ -62,7 +66,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
if (opts_length > 1000 * 1000 * 10) { if (opts_length > 1000 * 1000 * 10) {
// over 10MB // over 10MB
if (isCloudantURI(uri)) { if (isCloudantURI(uri)) {
this.plugin.last_successful_post = false; this.last_successful_post = false;
this._log("This request should fail on IBM Cloudant.", LOG_LEVEL_VERBOSE); this._log("This request should fail on IBM Cloudant.", LOG_LEVEL_VERBOSE);
throw new Error("This request should fail on IBM Cloudant."); throw new Error("This request should fail on IBM Cloudant.");
} }
@@ -91,9 +95,9 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
this.plugin.requestCount.value = this.plugin.requestCount.value + 1; this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
const r = await fetchByAPI(requestParam); const r = await fetchByAPI(requestParam);
if (method == "POST" || method == "PUT") { if (method == "POST" || method == "PUT") {
this.plugin.last_successful_post = r.status - (r.status % 100) == 200; this.last_successful_post = r.status - (r.status % 100) == 200;
} else { } else {
this.plugin.last_successful_post = true; this.last_successful_post = true;
} }
this._log(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL_DEBUG); this._log(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL_DEBUG);
@@ -106,7 +110,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE);
// limit only in bulk_docs. // limit only in bulk_docs.
if (url.toString().indexOf("_bulk_docs") !== -1) { if (url.toString().indexOf("_bulk_docs") !== -1) {
this.plugin.last_successful_post = false; this.last_successful_post = false;
} }
this._log(ex); this._log(ex);
throw ex; throw ex;
@@ -123,9 +127,9 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
this.plugin.requestCount.value = this.plugin.requestCount.value + 1; this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
const response: Response = await fetch(url, opts); const response: Response = await fetch(url, opts);
if (method == "POST" || method == "PUT") { if (method == "POST" || method == "PUT") {
this.plugin.last_successful_post = response.ok; this.last_successful_post = response.ok;
} else { } else {
this.plugin.last_successful_post = true; this.last_successful_post = true;
} }
this._log(`HTTP:${method}${size} to:${localURL} -> ${response.status}`, LOG_LEVEL_DEBUG); this._log(`HTTP:${method}${size} to:${localURL} -> ${response.status}`, LOG_LEVEL_DEBUG);
if (Math.floor(response.status / 100) !== 2) { if (Math.floor(response.status / 100) !== 2) {
@@ -148,7 +152,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE);
// limit only in bulk_docs. // limit only in bulk_docs.
if (url.toString().indexOf("_bulk_docs") !== -1) { if (url.toString().indexOf("_bulk_docs") !== -1) {
this.plugin.last_successful_post = false; this.last_successful_post = false;
} }
this._log(ex); this._log(ex);
throw ex; throw ex;

View File

@@ -13,7 +13,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
$everyOnloadStart(): Promise<boolean> { $everyOnloadStart(): Promise<boolean> {
// this.registerEvent(this.app.workspace.on("editor-change", )); // this.registerEvent(this.app.workspace.on("editor-change", ));
this.plugin.registerEvent(this.app.vault.on("rename", (file, oldPath) => { this.plugin.registerEvent(this.app.vault.on("rename", (file, oldPath) => {
eventHub.emitEvent(EVENT_FILE_RENAMED, { newPath: file.path, old: oldPath }); eventHub.emitEvent(EVENT_FILE_RENAMED, { newPath: file.path as FilePathWithPrefix, old: oldPath as FilePathWithPrefix });
})); }));
this.plugin.registerEvent(this.app.workspace.on("active-leaf-change", () => eventHub.emitEvent(EVENT_LEAF_ACTIVE_CHANGED))); this.plugin.registerEvent(this.app.workspace.on("active-leaf-change", () => eventHub.emitEvent(EVENT_LEAF_ACTIVE_CHANGED)));
return Promise.resolve(true); return Promise.resolve(true);
@@ -41,7 +41,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
this.initialCallback = save; this.initialCallback = save;
saveCommandDefinition.callback = () => { saveCommandDefinition.callback = () => {
scheduleTask("syncOnEditorSave", 250, () => { scheduleTask("syncOnEditorSave", 250, () => {
if (this.plugin._unloaded) { if (this.core.$$isUnloaded()) {
this._log("Unload and remove the handler.", LOG_LEVEL_VERBOSE); this._log("Unload and remove the handler.", LOG_LEVEL_VERBOSE);
saveCommandDefinition.callback = this.initialCallback; saveCommandDefinition.callback = this.initialCallback;
this.initialCallback = undefined; this.initialCallback = undefined;
@@ -98,14 +98,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
// TODO:FIXME AT V0.17.31, this logic has been disabled. // TODO:FIXME AT V0.17.31, this logic has been disabled.
if (navigator.onLine && this.localDatabase.needScanning) { if (navigator.onLine && this.localDatabase.needScanning) {
this.localDatabase.needScanning = false; this.localDatabase.needScanning = false;
await this.plugin.$$performFullScan(); await this.core.$$performFullScan();
} }
} }
async watchWindowVisibilityAsync() { async watchWindowVisibilityAsync() {
if (this.settings.suspendFileWatching) return; if (this.settings.suspendFileWatching) return;
if (!this.settings.isConfigured) return; if (!this.settings.isConfigured) return;
if (!this.plugin.isReady) return; if (!this.core.$$isReady()) return;
if (this.isLastHidden && !this.hasFocus) { if (this.isLastHidden && !this.hasFocus) {
// NO OP while non-focused after made hidden; // NO OP while non-focused after made hidden;
@@ -124,7 +124,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
await this.core.$everyBeforeSuspendProcess(); await this.core.$everyBeforeSuspendProcess();
} else { } else {
// suspend all temporary. // suspend all temporary.
if (this.plugin.suspended) return; if (this.core.$$isSuspended()) return;
if (!this.hasFocus) return; if (!this.hasFocus) return;
await this.core.$everyOnResumeProcess(); await this.core.$everyOnResumeProcess();
await this.core.$everyAfterResumeProcess(); await this.core.$everyAfterResumeProcess();
@@ -133,7 +133,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
watchWorkspaceOpen(file: TFile | null) { watchWorkspaceOpen(file: TFile | null) {
if (this.settings.suspendFileWatching) return; if (this.settings.suspendFileWatching) return;
if (!this.settings.isConfigured) return; if (!this.settings.isConfigured) return;
if (!this.plugin.isReady) return; if (!this.core.$$isReady()) return;
if (!file) return; if (!file) return;
scheduleTask("watch-workspace-open", 500, () => fireAndForget(() => this.watchWorkspaceOpenAsync(file))); scheduleTask("watch-workspace-open", 500, () => fireAndForget(() => this.watchWorkspaceOpenAsync(file)));
} }
@@ -141,12 +141,12 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
async watchWorkspaceOpenAsync(file: TFile) { async watchWorkspaceOpenAsync(file: TFile) {
if (this.settings.suspendFileWatching) return; if (this.settings.suspendFileWatching) return;
if (!this.settings.isConfigured) return; if (!this.settings.isConfigured) return;
if (!this.plugin.isReady) return; if (!this.core.$$isReady()) return;
await this.core.$everyCommitPendingFileEvent(); await this.core.$everyCommitPendingFileEvent();
if (file == null) { if (file == null) {
return; return;
} }
if (this.settings.syncOnFileOpen && !this.plugin.suspended) { if (this.settings.syncOnFileOpen && !this.core.$$isSuspended()) {
await this.core.$$replicate(); await this.core.$$replicate();
} }
await this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix); await this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix);
@@ -160,7 +160,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
$$askReload(message?: string) { $$askReload(message?: string) {
if (this.core.isReloadingScheduled) { if (this.core.$$isReloadingScheduled()) {
this._log(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE); this._log(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE);
return; return;
} }

View File

@@ -19,7 +19,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
); );
this.addRibbonIcon("replicate", "Replicate", async () => { this.addRibbonIcon("replicate", "Replicate", async () => {
await this.plugin.$$replicate(true); await this.core.$$replicate(true);
}).addClass("livesync-ribbon-replicate"); }).addClass("livesync-ribbon-replicate");
@@ -27,14 +27,14 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
id: "livesync-replicate", id: "livesync-replicate",
name: "Replicate now", name: "Replicate now",
callback: async () => { callback: async () => {
await this.plugin.$$replicate(); await this.core.$$replicate();
}, },
}); });
this.addCommand({ this.addCommand({
id: "livesync-dump", id: "livesync-dump",
name: "Dump information of this doc ", name: "Dump information of this doc ",
callback: () => { callback: () => {
const file = this.plugin.$$getActiveFilePath(); const file = this.core.$$getActiveFilePath();
if (!file) return; if (!file) return;
fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false)); fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false));
}, },
@@ -45,7 +45,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => { editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => {
const file = view.file; const file = view.file;
if (!file) return; if (!file) return;
void this.plugin.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix); void this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix);
}, },
}); });
@@ -60,23 +60,23 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
this.settings.liveSync = true; this.settings.liveSync = true;
this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE); this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE);
} }
await this.plugin.realizeSettingSyncMode(); await this.core.$$realizeSettingSyncMode();
await this.plugin.saveSettings(); await this.core.$$saveSettingData();
}, },
}); });
this.addCommand({ this.addCommand({
id: "livesync-suspendall", id: "livesync-suspendall",
name: "Toggle All Sync.", name: "Toggle All Sync.",
callback: async () => { callback: async () => {
if (this.plugin.suspended) { if (this.core.$$isSuspended()) {
this.plugin.suspended = false; this.core.$$setSuspended(false);
this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE); this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE);
} else { } else {
this.plugin.suspended = true; this.core.$$setSuspended(true);
this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE); this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE);
} }
await this.plugin.realizeSettingSyncMode(); await this.core.$$realizeSettingSyncMode();
await this.plugin.saveSettings(); await this.core.$$saveSettingData();
}, },
}); });
@@ -84,7 +84,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
id: "livesync-scan-files", id: "livesync-scan-files",
name: "Scan storage and database again", name: "Scan storage and database again",
callback: async () => { callback: async () => {
await this.plugin.$$performFullScan(true) await this.core.$$performFullScan(true)
} }
}) })
@@ -101,14 +101,14 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
id: "livesync-abortsync", id: "livesync-abortsync",
name: "Abort synchronization immediately", name: "Abort synchronization immediately",
callback: () => { callback: () => {
this.plugin.replicator.terminateSync(); this.core.replicator.terminateSync();
}, },
}) })
return Promise.resolve(true); return Promise.resolve(true);
} }
$everyOnload(): Promise<boolean> { $everyOnload(): Promise<boolean> {
this.app.workspace.onLayoutReady(this.plugin.onLiveSyncReady.bind(this.plugin)); this.app.workspace.onLayoutReady(this.core.$$onLiveSyncReady.bind(this.core));
// eslint-disable-next-line no-unused-labels // eslint-disable-next-line no-unused-labels
return Promise.resolve(true); return Promise.resolve(true);
} }

View File

@@ -0,0 +1,13 @@
import { AbstractObsidianModule, type IObsidianModule } from '../AbstractObsidianModule.ts';
export class ModuleExtraSyncObsidian extends AbstractObsidianModule implements IObsidianModule {
deviceAndVaultName: string = "";
$$getDeviceAndVaultName(): string {
return this.deviceAndVaultName;
}
$$setDeviceAndVaultName(name: string): void {
this.deviceAndVaultName = name;
}
}

View File

@@ -42,10 +42,10 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
toc: Set<string>, toc: Set<string>,
stub: { [key: string]: { [key: string]: Map<string, Record<string, string>> } } stub: { [key: string]: { [key: string]: Map<string, Record<string, string>> } }
}; };
eventHub.onEvent("document-stub-created", (e: CustomEvent<STUB>) => { eventHub.onEvent("document-stub-created", (detail: STUB) => {
fireAndForget(async () => { fireAndForget(async () => {
const stub = e.detail.stub; const stub = detail.stub;
const toc = e.detail.toc; const toc = detail.toc;
const stubDocX = const stubDocX =
Object.entries(stub).map(([key, value]) => { Object.entries(stub).map(([key, value]) => {

View File

@@ -8,6 +8,12 @@ import { parseYaml, requestUrl, stringifyYaml } from "obsidian";
import type { FilePath } from "../../lib/src/common/types.ts"; import type { FilePath } from "../../lib/src/common/types.ts";
import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { scheduleTask } from "octagonal-wheels/concurrency/task";
declare global {
interface LSEvents {
"debug-sync-status": string[];
}
}
export class ModuleReplicateTest extends AbstractObsidianModule implements IObsidianModule { export class ModuleReplicateTest extends AbstractObsidianModule implements IObsidianModule {

View File

@@ -41,7 +41,7 @@
isReady = true; isReady = true;
// performTest(); // performTest();
eventHub.once(EVENT_LAYOUT_READY, async () => { eventHub.onceEvent(EVENT_LAYOUT_READY, async () => {
if (await plugin.storageAccess.isExistsIncludeHidden("_AUTO_TEST.md")) { if (await plugin.storageAccess.isExistsIncludeHidden("_AUTO_TEST.md")) {
new Notice("Auto test file found, running tests..."); new Notice("Auto test file found, running tests...");
fireAndForget(async () => { fireAndForget(async () => {
@@ -83,7 +83,7 @@
$: resultLines = $results; $: resultLines = $results;
let syncStatus = [] as string[]; let syncStatus = [] as string[];
eventHub.on<string[]>("debug-sync-status", (status) => { eventHub.onEvent("debug-sync-status", (status) => {
syncStatus = [...status]; syncStatus = [...status];
}); });
</script> </script>

View File

@@ -6,7 +6,6 @@
import { diff_match_patch } from "../../../deps.ts"; import { diff_match_patch } from "../../../deps.ts";
import { DocumentHistoryModal } from "../DocumentHistory/DocumentHistoryModal.ts"; import { DocumentHistoryModal } from "../DocumentHistory/DocumentHistoryModal.ts";
import { isPlainText, stripAllPrefixes } from "../../../lib/src/string_and_binary/path.ts"; import { isPlainText, stripAllPrefixes } from "../../../lib/src/string_and_binary/path.ts";
import { TFile } from "../../../deps.ts";
import { getPath } from "../../../common/utils.ts"; import { getPath } from "../../../common/utils.ts";
export let plugin: ObsidianLiveSyncPlugin; export let plugin: ObsidianLiveSyncPlugin;
@@ -105,9 +104,9 @@
} }
if (rev == docA._rev) { if (rev == docA._rev) {
if (checkStorageDiff) { if (checkStorageDiff) {
const isExist = await plugin.storageAccess.isExists(stripAllPrefixes(getPath(docA))); const isExist = await plugin.storageAccess.isExistsIncludeHidden(stripAllPrefixes(getPath(docA)));
if (isExist) { if (isExist) {
const data = await plugin.storageAccess.readFileAuto(stripAllPrefixes(getPath(docA))); const data = await plugin.storageAccess.readHiddenFileBinary(stripAllPrefixes(getPath(docA)));
const d = readAsBlob(doc); const d = readAsBlob(doc);
const result = await isDocContentSame(data, d); const result = await isDocContentSame(data, d);
if (result) { if (result) {

View File

@@ -72,7 +72,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
// In here, some merge has been processed. // In here, some merge has been processed.
// So we have to run replication if configured. // So we have to run replication if configured.
// TODO: Make this is as a event request // TODO: Make this is as a event request
if (this.settings.syncAfterMerge && !this.plugin.suspended) { if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
await this.core.$$waitForReplicationOnce(); await this.core.$$waitForReplicationOnce();
} }
// And, check it again. // And, check it again.
@@ -96,7 +96,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
this._log("There are no conflicted documents", LOG_LEVEL_NOTICE); this._log("There are no conflicted documents", LOG_LEVEL_NOTICE);
return false; return false;
} }
const target = await this.plugin.confirm.askSelectString("File to resolve conflict", notesList); const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList);
if (target) { if (target) {
const targetItem = notes.find(e => e.dispPath == target)!; const targetItem = notes.find(e => e.dispPath == target)!;
await this.core.$$queueConflictCheck(targetItem.path); await this.core.$$queueConflictCheck(targetItem.path);
@@ -114,7 +114,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
notes.push({ path: getPath(doc), mtime: doc.mtime }); notes.push({ path: getPath(doc), mtime: doc.mtime });
} }
if (notes.length > 0) { if (notes.length > 0) {
this.plugin.confirm.askInPopup(`conflicting-detected-on-safety`, `Some files have been left conflicted! Press {HERE} to resolve them, or you can do it later by "Pick a file to resolve conflict`, (anchor) => { this.core.confirm.askInPopup(`conflicting-detected-on-safety`, `Some files have been left conflicted! Press {HERE} to resolve them, or you can do it later by "Pick a file to resolve conflict`, (anchor) => {
anchor.text = "HERE"; anchor.text = "HERE";
anchor.addEventListener("click", () => { anchor.addEventListener("click", () => {
fireAndForget(() => this.allConflictCheck()) fireAndForget(() => this.allConflictCheck())

View File

@@ -67,12 +67,12 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
}) })
return computed(() => formatted.value); return computed(() => formatted.value);
} }
const labelReplication = padLeftSpComputed(this.plugin.replicationResultCount, `📥`); const labelReplication = padLeftSpComputed(this.core.replicationResultCount, `📥`);
const labelDBCount = padLeftSpComputed(this.plugin.databaseQueueCount, `📄`); const labelDBCount = padLeftSpComputed(this.core.databaseQueueCount, `📄`);
const labelStorageCount = padLeftSpComputed(this.plugin.storageApplyingCount, `💾`); const labelStorageCount = padLeftSpComputed(this.core.storageApplyingCount, `💾`);
const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`); const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`);
const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`); const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`);
const labelConflictProcessCount = padLeftSpComputed(this.plugin.conflictProcessQueueCount, `🔩`); const labelConflictProcessCount = padLeftSpComputed(this.core.conflictProcessQueueCount, `🔩`);
const hiddenFilesCount = reactive(() => hiddenFilesEventCount.value + hiddenFilesProcessingCount.value); const hiddenFilesCount = reactive(() => hiddenFilesEventCount.value + hiddenFilesProcessingCount.value);
const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`) const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`)
const queueCountLabelX = reactive(() => { const queueCountLabelX = reactive(() => {
@@ -81,12 +81,12 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
const queueCountLabel = () => queueCountLabelX.value; const queueCountLabel = () => queueCountLabelX.value;
const requestingStatLabel = computed(() => { const requestingStatLabel = computed(() => {
const diff = this.plugin.requestCount.value - this.plugin.responseCount.value; const diff = this.core.requestCount.value - this.core.responseCount.value;
return diff != 0 ? "📲 " : ""; return diff != 0 ? "📲 " : "";
}) })
const replicationStatLabel = computed(() => { const replicationStatLabel = computed(() => {
const e = this.plugin.replicationStat.value; const e = this.core.replicationStat.value;
const sent = e.sent; const sent = e.sent;
const arrived = e.arrived; const arrived = e.arrived;
const maxPullSeq = e.maxPullSeq; const maxPullSeq = e.maxPullSeq;
@@ -128,9 +128,9 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
} }
return { w, sent, pushLast, arrived, pullLast }; return { w, sent, pushLast, arrived, pullLast };
}) })
const labelProc = padLeftSpComputed(this.plugin.processing, ``); const labelProc = padLeftSpComputed(this.core.processing, ``);
const labelPend = padLeftSpComputed(this.plugin.totalQueued, `🛫`); const labelPend = padLeftSpComputed(this.core.totalQueued, `🛫`);
const labelInBatchDelay = padLeftSpComputed(this.plugin.batched, `📬`); const labelInBatchDelay = padLeftSpComputed(this.core.batched, `📬`);
const waitingLabel = computed(() => { const waitingLabel = computed(() => {
return `${labelProc()}${labelPend()}${labelInBatchDelay()}`; return `${labelProc()}${labelPend()}${labelInBatchDelay()}`;
}) })
@@ -144,7 +144,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
}; };
}) })
const statusBarLabels = reactive(() => { const statusBarLabels = reactive(() => {
const scheduleMessage = this.plugin.isReloadingScheduled ? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n` : ""; const scheduleMessage = this.core.$$isReloadingScheduled() ? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n` : "";
const { message } = statusLineLabel(); const { message } = statusLineLabel();
const status = scheduleMessage + this.statusLog.value; const status = scheduleMessage + this.statusLog.value;
@@ -181,7 +181,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
const thisFile = this.app.workspace.getActiveFile(); const thisFile = this.app.workspace.getActiveFile();
if (!thisFile) return ""; if (!thisFile) return "";
// Case Sensitivity // Case Sensitivity
if (this.core.shouldCheckCaseInsensitive) { if (this.core.$$shouldCheckCaseInsensitive()) {
const f = this.core.storageAccess.getFiles().map(e => e.path).filter(e => e.toLowerCase() == thisFile.path.toLowerCase()); 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 (f.length > 1) return "Not synchronised: There are multiple files with the same name";
} }
@@ -274,7 +274,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
} }
$everyOnloadAfterLoadSettings(): Promise<boolean> { $everyOnloadAfterLoadSettings(): Promise<boolean> {
logStore.pipeTo(new QueueProcessor(logs => logs.forEach(e => this.core.$$addLog(e.message, e.level, e.key)), { suspended: false, batchSize: 20, concurrentLimit: 1, delay: 0 })).startPipeline(); logStore.pipeTo(new QueueProcessor(logs => logs.forEach(e => this.core.$$addLog(e.message, e.level, e.key)), { suspended: false, batchSize: 20, concurrentLimit: 1, delay: 0 })).startPipeline();
eventHub.onEvent(EVENT_FILE_RENAMED, (evt: CustomEvent<{ oldPath: string, newPath: string }>) => { eventHub.onEvent(EVENT_FILE_RENAMED, (data) => {
void this.setFileStatus(); void this.setFileStatus();
}); });
@@ -290,7 +290,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" }); this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" });
eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition()); eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition());
if (this.settings?.showStatusOnStatusbar) { if (this.settings?.showStatusOnStatusbar) {
this.statusBar = this.plugin.addStatusBarItem(); this.statusBar = this.core.addStatusBarItem();
this.statusBar.addClass("syncstatusbar"); this.statusBar.addClass("syncstatusbar");
} }
this.adjustStatusDivPosition(); this.adjustStatusDivPosition();
@@ -318,7 +318,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) { if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) {
return; return;
} }
const vaultName = this.plugin.$$getVaultName(); const vaultName = this.core.$$getVaultName();
const now = new Date(); const now = new Date();
const timestamp = now.toLocaleString(); const timestamp = now.toLocaleString();
const messageContent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2); const messageContent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2);

View File

@@ -1,5 +1,6 @@
import { type TFile } from "obsidian"; import { type TFile } from "obsidian";
import { eventHub, EVENT_REQUEST_SHOW_HISTORY } from "../../common/events.ts"; 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 type { FilePathWithPrefix, LoadedEntry, DocumentID } from "../../lib/src/common/types.ts";
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts"; import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts"; import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts";
@@ -29,7 +30,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem
fireAndForget(async () => await this.fileHistory()); fireAndForget(async () => await this.fileHistory());
}, },
}); });
eventHub.on(EVENT_REQUEST_SHOW_HISTORY, ({ file, fileOnDB }: { file: TFile, fileOnDB: LoadedEntry }) => { eventHub.onEvent(EVENT_REQUEST_SHOW_HISTORY, ({ file, fileOnDB }: { file: TFile | FilePathWithPrefix, fileOnDB: LoadedEntry }) => {
this.showHistory(file, fileOnDB._id); this.showHistory(file, fileOnDB._id);
}) })
return Promise.resolve(true); return Promise.resolve(true);
@@ -46,7 +47,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem
} }
notes.sort((a, b) => b.mtime - a.mtime); notes.sort((a, b) => b.mtime - a.mtime);
const notesList = notes.map(e => e.dispPath); const notesList = notes.map(e => e.dispPath);
const target = await this.plugin.confirm.askSelectString("File to view History", notesList); const target = await this.core.confirm.askSelectString("File to view History", notesList);
if (target) { if (target) {
const targetId = notes.find(e => e.dispPath == target)!; const targetId = notes.find(e => e.dispPath == target)!;
this.showHistory(targetId.path, targetId.id); this.showHistory(targetId.path, targetId.id);

View File

@@ -12,7 +12,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
const methods: Record<ConfigPassphraseStore, (() => Promise<string | false>)> = { const methods: Record<ConfigPassphraseStore, (() => Promise<string | false>)> = {
"": () => Promise.resolve("*"), "": () => Promise.resolve("*"),
"LOCALSTORAGE": () => Promise.resolve(localStorage.getItem("ls-setting-passphrase") ?? false), "LOCALSTORAGE": () => Promise.resolve(localStorage.getItem("ls-setting-passphrase") ?? false),
"ASK_AT_LAUNCH": () => this.plugin.confirm.askString("Passphrase", "passphrase", "") "ASK_AT_LAUNCH": () => this.core.confirm.askString("Passphrase", "passphrase", "")
} }
const method = settings.configPassphraseStore; const method = settings.configPassphraseStore;
const methodFunc = method in methods ? methods[method] : methods[""]; const methodFunc = method in methods ? methods[method] : methods[""];
@@ -20,8 +20,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
} }
$$saveDeviceAndVaultName(): void { $$saveDeviceAndVaultName(): void {
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.plugin.$$getVaultName(); const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName();
localStorage.setItem(lsKey, this.plugin.deviceAndVaultName || ""); localStorage.setItem(lsKey, this.core.$$getDeviceAndVaultName() || "");
} }
usedPassphrase = ""; usedPassphrase = "";
@@ -64,7 +64,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
} }
async $$saveSettingData() { async $$saveSettingData() {
this.plugin.$$saveDeviceAndVaultName(); this.core.$$saveDeviceAndVaultName();
const settings = { ...this.settings }; const settings = { ...this.settings };
settings.deviceAndVaultName = ""; settings.deviceAndVaultName = "";
if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) { if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) {
@@ -182,11 +182,11 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
// So, use history is always enabled. // So, use history is always enabled.
this.settings.useHistory = true; this.settings.useHistory = true;
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.plugin.$$getVaultName(); const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName();
if (this.settings.deviceAndVaultName != "") { if (this.settings.deviceAndVaultName != "") {
if (!localStorage.getItem(lsKey)) { if (!localStorage.getItem(lsKey)) {
this.core.deviceAndVaultName = this.settings.deviceAndVaultName; this.core.$$setDeviceAndVaultName(this.settings.deviceAndVaultName);
localStorage.setItem(lsKey, this.core.deviceAndVaultName); this.$$saveDeviceAndVaultName();
this.settings.deviceAndVaultName = ""; this.settings.deviceAndVaultName = "";
} }
} }
@@ -194,8 +194,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
this._log("Configuration verification founds problems with your configuration. This has been fixed automatically. But you may already have data that cannot be synchronised. If this is the case, please rebuild everything.", LOG_LEVEL_NOTICE) this._log("Configuration verification founds problems with your configuration. This has been fixed automatically. But you may already have data that cannot be synchronised. If this is the case, please rebuild everything.", LOG_LEVEL_NOTICE)
this.settings.customChunkSize = 0; this.settings.customChunkSize = 0;
} }
this.core.deviceAndVaultName = localStorage.getItem(lsKey) || ""; this.core.$$setDeviceAndVaultName(localStorage.getItem(lsKey) || "");
if (this.core.deviceAndVaultName == "") { if (this.core.$$getDeviceAndVaultName() == "") {
if (this.settings.usePluginSync) { if (this.settings.usePluginSync) {
this._log("Device name is not set. Plug-in sync has been disabled.", LOG_LEVEL_NOTICE); this._log("Device name is not set. Plug-in sync has been disabled.", LOG_LEVEL_NOTICE);
this.settings.usePluginSync = false; this.settings.usePluginSync = false;

View File

@@ -19,7 +19,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
return this.settings.settingSyncFile != ""; return this.settings.settingSyncFile != "";
} }
fireAndForget(async () => { fireAndForget(async () => {
await this.plugin.$$saveSettingData(); await this.core.$$saveSettingData();
}); });
} }
}) })
@@ -38,13 +38,12 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
} }
}, },
}) })
eventHub.on("event-file-changed", (info: { eventHub.onEvent("event-file-changed", (info: {
file: FilePathWithPrefix, automated: boolean file: FilePathWithPrefix, automated: boolean
}) => { }) => {
fireAndForget(() => this.checkAndApplySettingFromMarkdown(info.file, info.automated)); fireAndForget(() => this.checkAndApplySettingFromMarkdown(info.file, info.automated));
}); });
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: CustomEvent<ObsidianLiveSyncSettings>) => { eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
const settings = evt.detail;
if (settings.settingSyncFile != "") { if (settings.settingSyncFile != "") {
fireAndForget(() => this.saveSettingToMarkdown(settings.settingSyncFile)); fireAndForget(() => this.saveSettingToMarkdown(settings.settingSyncFile));
} }
@@ -123,7 +122,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
return return
} }
const addMsg = this.settings.settingSyncFile != filename ? " (This is not-active file)" : ""; const addMsg = this.settings.settingSyncFile != filename ? " (This is not-active file)" : "";
this.plugin.confirm.askInPopup("apply-setting-from-md", `Setting markdown ${filename}${addMsg} has been detected. Apply this from {HERE}.`, (anchor) => { this.core.confirm.askInPopup("apply-setting-from-md", `Setting markdown ${filename}${addMsg} has been detected. Apply this from {HERE}.`, (anchor) => {
anchor.text = "HERE"; anchor.text = "HERE";
anchor.addEventListener("click", () => { anchor.addEventListener("click", () => {
fireAndForget(async () => { fireAndForget(async () => {
@@ -132,26 +131,26 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
const APPLY_AND_REBUILD = "Apply settings and restart obsidian with red_flag_rebuild.md"; const APPLY_AND_REBUILD = "Apply settings and restart obsidian with red_flag_rebuild.md";
const APPLY_AND_FETCH = "Apply settings and restart obsidian with red_flag_fetch.md"; const APPLY_AND_FETCH = "Apply settings and restart obsidian with red_flag_fetch.md";
const CANCEL = "Cancel"; const CANCEL = "Cancel";
const result = await this.plugin.confirm.askSelectStringDialogue("Ready for apply the setting.", [ const result = await this.core.confirm.askSelectStringDialogue("Ready for apply the setting.", [
APPLY_AND_RESTART, APPLY_AND_RESTART,
APPLY_ONLY, APPLY_ONLY,
APPLY_AND_FETCH, APPLY_AND_FETCH,
APPLY_AND_REBUILD, APPLY_AND_REBUILD,
CANCEL], { defaultAction: APPLY_AND_RESTART }); CANCEL], { defaultAction: APPLY_AND_RESTART });
if (result == APPLY_ONLY || result == APPLY_AND_RESTART || result == APPLY_AND_REBUILD || result == APPLY_AND_FETCH) { if (result == APPLY_ONLY || result == APPLY_AND_RESTART || result == APPLY_AND_REBUILD || result == APPLY_AND_FETCH) {
this.plugin.settings = settingToApply; this.core.settings = settingToApply;
await this.plugin.$$saveSettingData(); await this.core.$$saveSettingData();
if (result == APPLY_ONLY) { if (result == APPLY_ONLY) {
this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE); this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE);
return; return;
} }
if (result == APPLY_AND_REBUILD) { if (result == APPLY_AND_REBUILD) {
await this.plugin.rebuilder.scheduleRebuild(); await this.core.rebuilder.scheduleRebuild();
} }
if (result == APPLY_AND_FETCH) { if (result == APPLY_AND_FETCH) {
await this.plugin.rebuilder.scheduleFetch(); await this.core.rebuilder.scheduleFetch();
} }
this.plugin.$$performRestart(); this.core.$$performRestart();
} }
}) })
}) })

View File

@@ -20,11 +20,12 @@ import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
import { fireAndForget, yieldNextAnimationFrame } from "octagonal-wheels/promises"; import { fireAndForget, yieldNextAnimationFrame } from "octagonal-wheels/promises";
import { confirmWithMessage } from "../../coreObsidian/UILib/dialogs.ts"; import { confirmWithMessage } from "../../coreObsidian/UILib/dialogs.ts";
import { EVENT_REQUEST_COPY_SETUP_URI, EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, EVENT_REQUEST_OPEN_SETUP_URI, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_REQUEST_SHOW_HISTORY, eventHub } from "../../../common/events.ts"; import { EVENT_REQUEST_COPY_SETUP_URI, EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, EVENT_REQUEST_OPEN_SETUP_URI, EVENT_REQUEST_RELOAD_SETTING_TAB, eventHub } from "../../../common/events.ts";
import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import { JournalSyncMinio } from "../../../lib/src/replication/journal/objectstore/JournalSyncMinio.ts"; import { JournalSyncMinio } from "../../../lib/src/replication/journal/objectstore/JournalSyncMinio.ts";
import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts"; import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts";
import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts"; import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts";
import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts";
export type OnUpdateResult = { export type OnUpdateResult = {
visibility?: boolean, visibility?: boolean,
@@ -162,7 +163,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
return await Promise.resolve(); return await Promise.resolve();
} }
if (key == "deviceAndVaultName") { if (key == "deviceAndVaultName") {
this.plugin.deviceAndVaultName = this.editingSettings?.[key] ?? ""; this.plugin.$$setDeviceAndVaultName(this.editingSettings?.[key] ?? "");
this.plugin.$$saveDeviceAndVaultName(); this.plugin.$$saveDeviceAndVaultName();
return await Promise.resolve(); return await Promise.resolve();
} }
@@ -230,7 +231,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
const ret = { ...OnDialogSettingsDefault }; const ret = { ...OnDialogSettingsDefault };
ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || ""; ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
ret.preset = "" ret.preset = ""
ret.deviceAndVaultName = this.plugin.deviceAndVaultName; ret.deviceAndVaultName = this.plugin.$$getDeviceAndVaultName();
return ret; return ret;
} }
computeAllLocalSettings(): Partial<OnDialogSettings> { computeAllLocalSettings(): Partial<OnDialogSettings> {
@@ -304,7 +305,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
super(app, plugin); super(app, plugin);
this.plugin = plugin; this.plugin = plugin;
Setting.env = this; Setting.env = this;
eventHub.on(EVENT_REQUEST_RELOAD_SETTING_TAB, () => { eventHub.onEvent(EVENT_REQUEST_RELOAD_SETTING_TAB, () => {
this.requestReload(); this.requestReload();
}) })
} }
@@ -710,7 +711,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
const replicator = this.plugin.$anyNewReplicator(settingForCheck); const replicator = this.plugin.$anyNewReplicator(settingForCheck);
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return true; if (!(replicator instanceof LiveSyncCouchDBReplicator)) return true;
const db = await replicator.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.isMobile, true); const db = await replicator.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.$$isMobile(), true);
if (typeof db === "string") { if (typeof db === "string") {
Logger(`ERROR: Failed to check passphrase with the remote server: \n${db}.`, LOG_LEVEL_NOTICE); Logger(`ERROR: Failed to check passphrase with the remote server: \n${db}.`, LOG_LEVEL_NOTICE);
return false; return false;
@@ -1187,7 +1188,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea
void addPanel(paneEl, "CouchDB", undefined, onlyOnCouchDB).then(paneEl => { void addPanel(paneEl, "CouchDB", undefined, onlyOnCouchDB).then(paneEl => {
if (this.plugin.isMobile) { if (this.plugin.$$isMobile()) {
this.createEl(paneEl, "div", { this.createEl(paneEl, "div", {
text: `Configured as using non-HTTPS. We cannot connect to the remote. Please set up the credentials and use HTTPS for the remote URI.`, text: `Configured as using non-HTTPS. We cannot connect to the remote. Please set up the credentials and use HTTPS for the remote URI.`,
}, undefined, visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))) }, undefined, visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://")))
@@ -1280,6 +1281,23 @@ However, your report is needed to stabilise this. I appreciate you for your grea
}).setClass("wizardHidden"); }).setClass("wizardHidden");
}); });
void addPanel(paneEl, "Fetch settings").then((paneEl) => {
new Setting(paneEl)
.setName("Fetch tweaks from the remote")
.setDesc("Fetch other necessary settings from already configured remote.")
.addButton((button) => button
.setButtonText("Fetch")
.setDisabled(false)
.onClick(async () => {
const trialSetting = { ...this.initialSettings, ...this.editingSettings, };
const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting);
if (newTweaks.result !== false) {
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
this.requestUpdate();
}
}));
});
new Setting(paneEl) new Setting(paneEl)
.setClass("wizardOnly") .setClass("wizardOnly")
.addButton((button) => button .addButton((button) => button
@@ -1313,6 +1331,16 @@ However, your report is needed to stabilise this. I appreciate you for your grea
} else { } else {
this.editingSettings = { ...this.editingSettings, ...PREFERRED_SETTING_SELF_HOSTED }; this.editingSettings = { ...this.editingSettings, ...PREFERRED_SETTING_SELF_HOSTED };
} }
if (await this.plugin.confirm.askYesNoDialog("Do you want to fetch the tweaks from the remote?", { defaultOption: "Yes", title: "Fetch tweaks" }) == "yes") {
const trialSetting = { ...this.initialSettings, ...this.editingSettings, };
const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting);
if (newTweaks.result !== false) {
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
this.requestUpdate();
} else {
// Messages should be already shown.
}
}
changeDisplay("30") changeDisplay("30")
})); }));
}); });
@@ -1360,7 +1388,8 @@ However, your report is needed to stabilise this. I appreciate you for your grea
}).addButton(button => { }).addButton(button => {
button.setButtonText("Apply"); button.setButtonText("Apply");
button.onClick(async () => { button.onClick(async () => {
await this.saveSettings(["preset"]); // await this.saveSettings(["preset"]);
await this.saveAllDirtySettings();
}) })
}) })
@@ -1416,7 +1445,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea
if (!this.editingSettings.isConfigured) { if (!this.editingSettings.isConfigured) {
this.editingSettings.isConfigured = true; this.editingSettings.isConfigured = true;
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
await this.plugin.realizeSettingSyncMode(); await this.plugin.$$realizeSettingSyncMode();
await rebuildDB("localOnly"); await rebuildDB("localOnly");
// this.resetEditingSettings(); // this.resetEditingSettings();
if (await this.plugin.confirm.askYesNoDialog( if (await this.plugin.confirm.askYesNoDialog(
@@ -1430,13 +1459,13 @@ However, your report is needed to stabilise this. I appreciate you for your grea
await confirmRebuild(); await confirmRebuild();
} else { } else {
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
await this.plugin.realizeSettingSyncMode(); await this.plugin.$$realizeSettingSyncMode();
this.plugin.$$askReload(); this.plugin.$$askReload();
} }
} }
} else { } else {
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
await this.plugin.realizeSettingSyncMode(); await this.plugin.$$realizeSettingSyncMode();
} }
}) })
@@ -1471,7 +1500,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea
} }
await this.saveSettings(["liveSync", "periodicReplication"]); await this.saveSettings(["liveSync", "periodicReplication"]);
await this.plugin.realizeSettingSyncMode(); await this.plugin.$$realizeSettingSyncMode();
}) })
@@ -1546,6 +1575,8 @@ However, your report is needed to stabilise this. I appreciate you for your grea
}); });
void addPanel(paneEl, "Sync settings via markdown", undefined, undefined, LEVEL_ADVANCED).then((paneEl) => { void addPanel(paneEl, "Sync settings via markdown", undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
paneEl.addClass("wizardHidden");
new Setting(paneEl) new Setting(paneEl)
.autoWireText("settingSyncFile", { holdValue: true }) .autoWireText("settingSyncFile", { holdValue: true })
.addApplyButton(["settingSyncFile"]) .addApplyButton(["settingSyncFile"])
@@ -1569,7 +1600,6 @@ However, your report is needed to stabilise this. I appreciate you for your grea
const hiddenFileSyncSettingEl = hiddenFileSyncSetting.settingEl const hiddenFileSyncSettingEl = hiddenFileSyncSetting.settingEl
const hiddenFileSyncSettingDiv = hiddenFileSyncSettingEl.createDiv(""); const hiddenFileSyncSettingDiv = hiddenFileSyncSettingEl.createDiv("");
hiddenFileSyncSettingDiv.innerText = this.editingSettings.syncInternalFiles ? LABEL_ENABLED : LABEL_DISABLED; hiddenFileSyncSettingDiv.innerText = this.editingSettings.syncInternalFiles ? LABEL_ENABLED : LABEL_DISABLED;
if (this.editingSettings.syncInternalFiles) { if (this.editingSettings.syncInternalFiles) {
new Setting(paneEl) new Setting(paneEl)
.setName("Disable Hidden files sync") .setName("Disable Hidden files sync")
@@ -1979,7 +2009,7 @@ ${stringifyYaml(pluginConfig)}`;
.split(",").filter(e => e).map(e => new RegExp(e, "i")); .split(",").filter(e => e).map(e => new RegExp(e, "i"));
this.plugin.localDatabase.hashCaches.clear(); this.plugin.localDatabase.hashCaches.clear();
Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify"); Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify");
const files = await this.plugin.storageAccess.getFilesIncludeHidden("/", undefined, ignorePatterns) const files = this.plugin.settings.syncInternalFiles ? (await this.plugin.storageAccess.getFilesIncludeHidden("/", undefined, ignorePatterns)) : (await this.plugin.storageAccess.getFileNames());
const documents = [] as FilePath[]; const documents = [] as FilePath[];
const adn = this.plugin.localDatabase.findAllDocs() const adn = this.plugin.localDatabase.findAllDocs()
@@ -1987,6 +2017,7 @@ ${stringifyYaml(pluginConfig)}`;
const path = getPath(i); const path = getPath(i);
if (path.startsWith(ICXHeader)) continue; if (path.startsWith(ICXHeader)) continue;
if (path.startsWith(PSCHeader)) continue; if (path.startsWith(PSCHeader)) continue;
if (!this.plugin.settings.syncInternalFiles && path.startsWith(ICHeader)) continue;
documents.push(stripAllPrefixes(path)); documents.push(stripAllPrefixes(path));
} }
const allPaths = [ const allPaths = [
@@ -2648,9 +2679,9 @@ ${stringifyYaml(pluginConfig)}`;
async dryRunGC() { async dryRunGC() {
await skipIfDuplicated("cleanup", async () => { await skipIfDuplicated("cleanup", async () => {
const replicator = this.plugin.getReplicator(); const replicator = this.plugin.$$getReplicator();
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return; if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
const remoteDBConn = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.isMobile) const remoteDBConn = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.$$isMobile())
if (typeof (remoteDBConn) == "string") { if (typeof (remoteDBConn) == "string") {
Logger(remoteDBConn); Logger(remoteDBConn);
return; return;
@@ -2664,10 +2695,10 @@ ${stringifyYaml(pluginConfig)}`;
async dbGC() { async dbGC() {
// Lock the remote completely once. // Lock the remote completely once.
await skipIfDuplicated("cleanup", async () => { await skipIfDuplicated("cleanup", async () => {
const replicator = this.plugin.getReplicator(); const replicator = this.plugin.$$getReplicator();
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return; if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
await this.plugin.getReplicator().markRemoteLocked(this.plugin.settings, true, true); await this.plugin.$$getReplicator().markRemoteLocked(this.plugin.settings, true, true);
const remoteDBConnection = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.isMobile) const remoteDBConnection = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.$$isMobile())
if (typeof (remoteDBConnection) == "string") { if (typeof (remoteDBConnection) == "string") {
Logger(remoteDBConnection); Logger(remoteDBConnection);
return; return;

View File

@@ -0,0 +1,182 @@
import { fireAndForget } from "octagonal-wheels/promises";
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, VER, type ObsidianLiveSyncSettings } from "../../lib/src/common/types.ts";
import { EVENT_LAYOUT_READY, EVENT_PLUGIN_LOADED, EVENT_PLUGIN_UNLOADED, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
import { $f, setLang } from "../../lib/src/common/i18n.ts";
import { versionNumberString2Number } from "../../lib/src/string_and_binary/convert.ts";
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
async $$onLiveSyncReady() {
if (!await this.core.$everyOnLayoutReady()) return;
eventHub.emitEvent(EVENT_LAYOUT_READY);
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
const ANSWER_KEEP = "Keep this plug-in suspended";
const ANSWER_RESUME = "Resume and restart Obsidian";
const message = `Self-hosted LiveSync has been configured to ignore some events. Is this intentional for you?
| Type | Status | Note |
|:---:|:---:|---|
| Storage Events | ${this.settings.suspendFileWatching ? "suspended" : "active"} | Every modification will be ignored |
| Database Events | ${this.settings.suspendParseReplicationResult ? "suspended" : "active"} | Every synchronised change will be postponed |
Do you want to resume them and restart Obsidian?
> [!DETAILS]-
> These flags are set by the plug-in while rebuilding, or fetching. If the process ends abnormally, it may be kept unintended.
> If you are not sure, you can try to rerun these processes. Make sure to back your vault up.
`;
if (await this.core.confirm.askSelectStringDialogue(message, [ANSWER_KEEP, ANSWER_RESUME], { defaultAction: ANSWER_KEEP, title: "Scram Enabled" }) == ANSWER_RESUME) {
this.settings.suspendFileWatching = false;
this.settings.suspendParseReplicationResult = false;
await this.saveSettings();
await this.core.$$scheduleAppReload();
return;
}
}
const isInitialized = await this.core.$$initializeDatabase(false, false);
if (!isInitialized) {
//TODO:stop all sync.
return false;
}
if (!await this.core.$everyOnFirstInitialize()) return;
await this.core.$$realizeSettingSyncMode();
fireAndForget(async () => {
this._log(`Additional safety scan..`, LOG_LEVEL_VERBOSE);
if (!await this.core.$allScanStat()) {
this._log(`Additional safety scan has been failed on some module`, LOG_LEVEL_NOTICE);
} else {
this._log(`Additional safety scan done`, LOG_LEVEL_VERBOSE);
}
});
}
$$wireUpEvents(): void {
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());
})
}
async $$onLiveSyncLoad(): Promise<void> {
this.$$wireUpEvents();
// debugger;
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core);
this._log("loading plugin");
if (!await this.core.$everyOnloadStart()) {
this._log("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE);
return;
}
// this.addUIs();
//@ts-ignore
const manifestVersion: string = MANIFEST_VERSION || "0.0.0";
//@ts-ignore
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
this._log($f`Self-hosted LiveSync${" v"}${manifestVersion} ${packageVersion}`);
await this.core.$$loadSettings();
if (!await this.core.$everyOnloadAfterLoadSettings()) {
this._log("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE);
return;
}
const lsKey = "obsidian-live-sync-ver" + this.core.$$getVaultName();
const last_version = localStorage.getItem(lsKey);
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) {
this._log($f`You have some unread release notes! Please read them once!`, LOG_LEVEL_NOTICE);
}
//@ts-ignore
if (this.isMobile) {
this.settings.disableRequestURI = true;
}
if (last_version && Number(last_version) < VER) {
this.settings.liveSync = false;
this.settings.syncOnSave = false;
this.settings.syncOnEditorSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.syncAfterMerge = false;
this.settings.periodicReplication = false;
this.settings.versionUpFlash = $f`Self-hosted LiveSync has been upgraded and some behaviors have changed incompatibly. All automatic synchronization is now disabled temporary. Ensure that other devices are also upgraded, and enable synchronization again.`;
await this.saveSettings();
}
localStorage.setItem(lsKey, `${VER}`);
await this.core.$$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();
await Promise.all(this.core.addOns.map(e => e.onload()));
}
async $$onLiveSyncUnload(): Promise<void> {
eventHub.emitEvent(EVENT_PLUGIN_UNLOADED);
await this.core.$allStartOnUnload();
cancelAllPeriodicTask();
cancelAllTasks();
stopAllRunningProcessors();
await this.core.$allOnUnload();
this._unloaded = true;
for (const addOn of this.core.addOns) {
addOn.onunload();
}
if (this.localDatabase != null) {
this.localDatabase.onunload();
if (this.core.replicator) {
this.core.replicator?.closeReplication();
}
await this.localDatabase.close();
}
this._log($f`unloading plugin`);
}
async $$realizeSettingSyncMode(): Promise<void> {
await this.core.$everyBeforeSuspendProcess();
await this.core.$everyBeforeRealizeSetting();
this.localDatabase.refreshSettings();
await this.core.$everyCommitPendingFileEvent();
await this.core.$everyRealizeSettingSyncMode();
// disable all sync temporary.
if (this.core.$$isSuspended()) return;
await this.core.$everyOnResumeProcess();
await this.core.$everyAfterResumeProcess();
await this.core.$everyAfterRealizeSetting();
}
$$isReloadingScheduled(): boolean {
return this.core._totalProcessingCount !== undefined;
}
isReady = false;
$$isReady(): boolean { return this.isReady; }
$$markIsReady(): void { this.isReady = true; }
$$resetIsReady(): void { this.isReady = false; }
_suspended = false;
$$isSuspended(): boolean {
return this._suspended || !this.settings?.isConfigured;
}
$$setSuspended(value: boolean) {
this._suspended = value;
}
_unloaded = false;
$$isUnloaded(): boolean {
return this._unloaded;
}
}

View File

@@ -427,3 +427,13 @@ span.ls-mark-cr::after {
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
border-radius: 30%; border-radius: 30%;
} }
.sls-dialogue-note-wrapper {
display: flex;
justify-content: flex-end;
align-items: center;
}
.sls-dialogue-note-countdown {
font-size: 0.8em;
}