1
0
mirror of https://github.com/vrtmrz/obsidian-livesync.git synced 2024-12-03 08:45:34 +02:00
-   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_PLUGIN_LOADED = "plugin-loaded";
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_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";
@ -22,8 +26,28 @@ export const EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG = "request-open-plugin-sync-d
// export const EVENT_FILE_CHANGED = "file-changed";
import { eventHub } from "../lib/src/hub/hub";
// TODO: Add overloads for the emit method to allow for type checking
declare global {
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 };

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<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 { 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";
@ -287,9 +287,17 @@
menu.addItem((item) => item.setTitle("Compare file").setIsLabel(true));
menu.addSeparator();
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) {
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);

View File

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

View File

@ -1,5 +1,5 @@
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 { 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";
@ -10,6 +10,7 @@ import { addPrefix, stripAllPrefixes } from "../../lib/src/string_and_binary/pat
import { QueueProcessor } from "../../lib/src/concurrency/processor.ts";
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts";
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule {
@ -36,8 +37,13 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
void this.syncInternalFilesAndDatabase("safe", true);
},
});
eventHub.onEvent(EVENT_SETTING_SAVED, () => {
this.updateSettingCache();
});
}
async $everyOnDatabaseInitialized(showNotice: boolean) {
this.knownChanges = await this.plugin.kvDB.get("knownChanges") ?? {};
if (this._isThisModuleEnabled()) {
try {
this._log("Synchronizing hidden files...");
@ -58,12 +64,26 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
}
$everyOnloadAfterLoadSettings(): Promise<boolean> {
this.updateSettingCache();
return Promise.resolve(true);
}
updateSettingCache() {
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns
.replace(/\n| /g, "")
.split(",").filter(e => e).map(e => new RegExp(e, "i"));
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> {
this.periodicInternalFileScanProcessor?.disable();
@ -80,7 +100,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
this.periodicInternalFileScanProcessor?.disable();
if (this._isMainSuspended())
return Promise.resolve(true);
if (!this.plugin.isReady)
if (!this.plugin.$$isReady())
return Promise.resolve(true);
this.periodicInternalFileScanProcessor.enable(this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval ? (this.settings.syncInternalFilesInterval * 1000) : 0);
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns
@ -110,13 +130,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
if (this._isMainSuspended()) return false;
if (!this._isThisModuleEnabled()) return false;
// Exclude files handled by customization sync
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()))) {
if (this.shouldSkipFile.some(e => e.startsWith(path.toLowerCase()))) {
this._log(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE);
return false;
}
@ -504,7 +518,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
// If something changes left, notify for reloading Obsidian.
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) => {
anchor.text = "HERE";
anchor.addEventListener("click", () => {
@ -676,12 +690,16 @@ 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.`);
} else {
this._log(`STORAGE <x- DB: ${displayFileName}: deleted (hidden).`);
await this.plugin.storageAccess.removeHidden(storageFilePath);
try {
await this.plugin.storageAccess.triggerHiddenFile(storageFilePath);
} catch (ex) {
this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
this._log(ex, LOG_LEVEL_VERBOSE);
if (await this.plugin.storageAccess.removeHidden(storageFilePath)) {
try {
await this.plugin.storageAccess.triggerHiddenFile(storageFilePath);
} catch (ex) {
this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
this._log(ex, LOG_LEVEL_VERBOSE);
}
} else {
this._log(`STORAGE <x- DB: ${storageFilePath}: deleted (hidden) Failed`);
return false;
}
}
return true;

View File

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

@ -1 +1 @@
Subproject commit 5079b0bf79d94495beaed174e9a71dbfa537f7c4
Subproject commit a91bb47c90307031ef5ed9814f31ef14153f2782

View File

@ -1,25 +1,18 @@
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 { fireAndForget, 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 { type SimpleStore } from "./lib/src/common/utils.ts";
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts";
import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstractReplicator.js";
import { type KeyValueDatabase } from "./common/KeyValueDB.ts";
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.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 { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js";
import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js";
import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js";
import { ObsHttpHandler } from "./modules/essentialObsidian/APILib/ObsHttpHandler.js";
import { $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 { ModuleDev } from "./modules/extras/ModuleDev.ts";
@ -64,6 +57,8 @@ import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleRe
import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts";
import { ModuleRebuilder } from "./modules/core/ModuleRebuilder.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 {
@ -85,31 +80,8 @@ const InterceptiveAny = Promise.resolve(undefined);
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
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[];
modules = [
new ModuleLiveSyncMain(this),
new ModuleExtraSyncObsidian(this, this),
// Only on Obsidian
new ModuleDatabaseFileAccess(this),
// Common
@ -166,48 +140,53 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
new ModuleIntegratedTest(this, this),
] as (IObsidianModule | AbstractModule)[];
injected = injectModules(this, [...this.modules, ...this.addOns] as ICoreModule[]);
// <-- Module System
$$isSuspended(): boolean { throwShouldBeOverridden(); }
$$setSuspended(value: boolean): void { throwShouldBeOverridden(); }
$$isDatabaseReady(): boolean { throwShouldBeOverridden(); }
$$getDeviceAndVaultName(): string { throwShouldBeOverridden(); }
$$setDeviceAndVaultName(name: string): void { throwShouldBeOverridden(); }
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void { throwShouldBeOverridden() }
$$isReady(): boolean { throwShouldBeOverridden(); }
$$markIsReady(): void { throwShouldBeOverridden(); }
$$resetIsReady(): void { throwShouldBeOverridden(); }
// Following are plugged by the modules.
settings!: ObsidianLiveSyncSettings;
localDatabase!: LiveSyncLocalDB;
simpleStore!: SimpleStore<CheckPointInfo>
replicator!: LiveSyncAbstractReplicator;
confirm!: Confirm;
storageAccess!: StorageAccess;
databaseFileAccess!: DatabaseFileAccess;
fileHandler!: ModuleFileHandler;
rebuilder!: Rebuilder;
// implementing interfaces
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; }
getSettings(): ObsidianLiveSyncSettings { return this.settings; }
getIsMobile(): boolean { return this.isMobile; }
$$markFileListPossiblyChanged(): void { throwShouldBeOverridden(); }
$$customFetchHandler(): ObsHttpHandler { throwShouldBeOverridden(); }
$$getLastPostFailedBySize(): boolean { throwShouldBeOverridden(); }
$$isStorageInsensitive(): boolean { throwShouldBeOverridden() }
get shouldCheckCaseInsensitive() {
if (this.$$isStorageInsensitive()) return false;
return !this.settings.handleFilenameCaseSensitive;
}
$$shouldCheckCaseInsensitive(): boolean { throwShouldBeOverridden(); }
_unloaded = false;
$$isUnloaded(): boolean { throwShouldBeOverridden(); }
requestCount = reactiveSource(0);
responseCount = reactiveSource(0);
@ -222,9 +201,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
processingFileEventCount = reactiveSource(0);
_totalProcessingCount?: ReactiveValue<number>;
get isReloadingScheduled() {
return this._totalProcessingCount !== undefined;
}
replicationStat = reactiveSource({
sent: 0,
@ -236,14 +213,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
syncStatus: "CLOSED" as DatabaseConnectingStatus
});
get isMobile() { return this.$$isMobile(); }
// Plug-in's overrideable functions
onload() { void this.onLiveSyncLoad(); }
async saveSettings() { await this.$$saveSettingData(); }
onunload() { return void this.onLiveSyncUnload(); }
// <-- Plug-in's overrideable functions
$$isReloadingScheduled(): boolean { throwShouldBeOverridden(); }
$$getReplicator(): LiveSyncAbstractReplicator { throwShouldBeOverridden(); }
$$connectRemoteCouchDB(uri: string, auth: {
username: string;
@ -286,20 +257,16 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> { return InterceptiveEvery; }
getReplicator() {
return this.replicator;
}
// end interfaces
$$getVaultName(): string { throwShouldBeOverridden(); }
simpleStore!: SimpleStore<CheckPointInfo>
$$getSimpleStore<T>(kind: string): SimpleStore<T> { throwShouldBeOverridden(); }
// trench!: Trench;
// --> Events
/*
@ -344,139 +311,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
$everyOnFirstInitialize(): Promise<boolean> { return InterceptiveEvery }
// Some Module should call this function to start the plugin.
async onLiveSyncReady() {
if (!await this.$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?
$$onLiveSyncReady(): Promise<false | undefined> { throwShouldBeOverridden(); }
$$wireUpEvents(): void { throwShouldBeOverridden(); }
$$onLiveSyncLoad(): Promise<void> { throwShouldBeOverridden(); }
| 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.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`);
}
$$onLiveSyncUnload(): Promise<void> { throwShouldBeOverridden(); }
$allScanStat(): Promise<boolean> {
return InterceptiveAll;
@ -496,18 +335,7 @@ Do you want to resume them and restart Obsidian?
$$openDatabase(): Promise<boolean> { throwShouldBeOverridden() }
async realizeSettingSyncMode() {
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();
}
$$realizeSettingSyncMode(): Promise<void> { throwShouldBeOverridden(); }
$$performRestart() { throwShouldBeOverridden(); }
$$clearUsedPassphrase(): void { throwShouldBeOverridden() }
@ -568,6 +396,9 @@ Do you want to resume them and restart Obsidian?
$$askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> { throwShouldBeOverridden(); }
$$checkAndAskUseRemoteConfiguration(settings: RemoteDBSettings): Promise<{ result: false | TweakValues, requireFetch: boolean }> { throwShouldBeOverridden(); }
$everyBeforeReplicate(showMessage: boolean): Promise<boolean> { return InterceptiveEvery; }
$$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; }
$$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; }
_isDatabaseReady(): boolean { return this.localDatabase.isReady; }
$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() {
return this.core._isMainReady();
return this.core.$$isReady();
}
_isMainSuspended() {
return this.core._isMainSuspended();
return this.core.$$isSuspended();
}
_isDatabaseReady() {
return this.core._isDatabaseReady();
return this.core.$$isDatabaseReady();
}
//should be overridden

View File

@ -133,6 +133,10 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
//upsert should locked
const msg = `STORAGE -> DB (${datatype}) `;
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.
// if (this.core.storageAccess.recentlyTouched(file)) {
// return true;

View File

@ -223,12 +223,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
}
const docData = readContent(docRead);
if (!existOnStorage) {
// The file is not exist on the storage. We do not care about the differences.
await this.storage.ensureDir(path);
return await this.storage.writeFileAuto(path, docData, { ctime: docRead.ctime, mtime: docRead.mtime });
}
if (!force) {
if (existOnStorage && !force) {
// The file is exist on the storage. Let's check the difference between the file and the entry.
// But, if force is true, then it should be updated.
// Ok, we have to compare.
let shouldApplied = false;
// 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;
}
// Let's apply the changes.
} else {
this._log(`File ${docRead.path} ${existOnStorage ? "(new) " : ""} ${force ? " (forced)" : ""}`, LOG_LEVEL_VERBOSE);
}
await this.storage.ensureDir(path);
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();
}
$$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();
this.core.settings.isConfigured = true;
await this.core.realizeSettingSyncMode();
await this.core.$$realizeSettingSyncMode();
await this.core.$$markRemoteLocked();
await this.core.$$tryResetRemoteDatabase();
await this.core.$$markRemoteLocked();
@ -60,7 +60,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
await this.core.$allSuspendExtraSync();
await this.askUseNewAdapter();
this.core.settings.isConfigured = true;
await this.core.realizeSettingSyncMode();
await this.core.$$realizeSettingSyncMode();
await this.resetLocalDatabase();
await delay(1000);
await this.core.$$initializeDatabase(true);
@ -164,11 +164,12 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
await this.askUseNewAdapter();
this.core.settings.isConfigured = true;
await this.suspendReflectingDatabase();
await this.core.realizeSettingSyncMode();
await this.core.$$realizeSettingSyncMode();
await this.resetLocalDatabase();
await delay(1000);
await this.core.$$openDatabase();
this.core.isReady = true;
// this.core.isReady = true;
this.core.$$markIsReady();
if (makeLocalChunkBeforeSync) {
await this.core.fileHandler.createAllChunks(true);
}
@ -201,8 +202,8 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
async fetchRemoteChunks() {
if (!this.core.settings.doNotSuspendOnFetching && this.core.settings.readChunksOnline && this.core.settings.remoteType == REMOTE_COUCHDB) {
this._log(`Fetching chunks`, LOG_LEVEL_NOTICE);
const replicator = this.core.getReplicator() as LiveSyncCouchDBReplicator;
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(this.settings, this.core.getIsMobile(), true);
const replicator = this.core.$$getReplicator() as LiveSyncCouchDBReplicator;
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(this.settings, this.core.$$isMobile(), true);
if (typeof remoteDB == "string") {
this._log(remoteDB, LOG_LEVEL_NOTICE);
} else {

View File

@ -14,12 +14,13 @@ import { getPath, isChunk, isValidPath, scheduleTask } from "../../common/utils"
import { sendValue } from "octagonal-wheels/messagepassing/signal";
import { isAnyNote } from "../../lib/src/common/utils";
import { EVENT_FILE_SAVED, eventHub } from "../../common/events";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
export class ModuleReplicator extends AbstractModule implements ICoreModule {
$everyOnloadAfterLoadSettings(): Promise<boolean> {
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());
}
})
@ -36,6 +37,11 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
await yieldMicrotask();
return true;
}
$$getReplicator(): LiveSyncAbstractReplicator {
return this.core.replicator;
}
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
return this.setReplicator();
}
@ -51,7 +57,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
}
async $$replicate(showMessage: boolean = false): Promise<boolean | void> {
//--?
if (!this.core.isReady) return;
if (!this.core.$$isReady()) return;
if (isLockAcquired("cleanup")) {
Logger("Database cleaning up is in process. replication has been cancelled", LOG_LEVEL_NOTICE);
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");
}
if (ret == CHOICE_CLEAN) {
const replicator = this.core.getReplicator();
const replicator = this.core.$$getReplicator();
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") {
Logger(remoteDB, LOG_LEVEL_NOTICE);
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 purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
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)
} else {
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> {
if (!this.core.isReady) return false;
if (!this.core.$$isReady()) return false;
if (!await this.core.$everyBeforeReplicate(showingNotice)) {
Logger(`Replication has been cancelled by some module failure`, LOG_LEVEL_NOTICE);
return false;
@ -334,7 +340,7 @@ Or if you are sure know what had been happened, we can unlock the database from
return !checkResult;
}
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);
if (ret) return true;
const checkResult = await this.core.$anyAfterConnectCheckFailed();

View File

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

View File

@ -99,7 +99,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
}
if (conflictCheckResult === AUTO_MERGED) {
//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.
await this.core.$$waitForReplicationOnce();
}

View File

@ -1,6 +1,6 @@
import { Logger, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
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 { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
@ -94,4 +94,78 @@ Please select which one you want to use.
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();
}
$$shouldCheckCaseInsensitive(): boolean {
if (this.$$isStorageInsensitive()) return false;
return !this.settings.handleFilenameCaseSensitive;
}
async writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean> {
const file = this.vaultAccess.getAbstractFileByPath(path);
if (file instanceof TFile) {
@ -115,6 +120,9 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
async removeHidden(path: string): Promise<boolean> {
try {
await this.vaultAccess.adapterRemove(path);
if (this.vaultAccess.adapterStat(path) !== null) {
return false;
}
return true;
} catch (e) {
this._log(`Could not remove hidden file: ${path}`, LOG_LEVEL_VERBOSE);

View File

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

View File

@ -1,7 +1,7 @@
import { TAbstractFile, TFile, TFolder } from "../../../deps.ts";
import { Logger } from "../../../lib/src/common/logger.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 { type FileEventItem, type FileEventType } from "../../../common/types.ts";
import { serialized, skipIfDuplicated } from "../../../lib/src/concurrency/lock.ts";
@ -38,13 +38,13 @@ export class StorageEventManagerObsidian extends StorageEventManager {
core: LiveSyncCore;
get shouldBatchSave() {
return this.plugin.shouldBatchSave;
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
}
get batchSaveMinimumDelay(): number {
return this.plugin.batchSaveMinimumDelay;
return this.core.settings?.batchSaveMinimumDelay ?? DEFAULT_SETTINGS.batchSaveMinimumDelay
}
get batchSaveMaximumDelay(): number {
return this.plugin.batchSaveMaximumDelay
return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay
}
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
super();
@ -155,9 +155,9 @@ export class StorageEventManagerObsidian extends StorageEventManager {
}
// Cache file and waiting to can be proceed.
async appendQueue(params: FileEvent[], ctx?: any) {
if (!this.plugin.settings.isConfigured) return;
if (this.plugin.settings.suspendFileWatching) return;
this.plugin.totalFileEventCount++;
if (!this.core.settings.isConfigured) return;
if (this.core.settings.suspendFileWatching) return;
this.core.$$markFileListPossiblyChanged();
// Flag up to be reload
const processFiles = new Set<FilePath>();
for (const param of params) {
@ -176,13 +176,13 @@ export class StorageEventManagerObsidian extends StorageEventManager {
const oldPath = param.oldPath;
if (type !== "INTERNAL") {
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);
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;
// let cache: null | string | ArrayBuffer;
@ -190,7 +190,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) {
// Wait for a bit while to let the writer has marked `touched` at the file.
await delay(10);
if (this.plugin.storageAccess.recentlyTouched(file)) {
if (this.core.storageAccess.recentlyTouched(file)) {
continue;
}
}
@ -338,11 +338,11 @@ export class StorageEventManagerObsidian extends StorageEventManager {
const key = `file-last-proc-${queue.type}-${file.path}`;
const last = Number(await this.core.kvDB.get(key) || 0);
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 {
// let mtime = file.stat.mtime;
if (queue.type == "DELETE") {
await this.plugin.$anyHandlerProcessesFileEvent(queue);
await this.core.$anyHandlerProcessesFileEvent(queue);
} else {
if (file.stat.mtime == last) {
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);
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);
// cancel running queues and remove one of atomic operation (e.g. rename)
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> {
this.core.isReady = false;
this.core.$$resetIsReady();
if ((!reopenDatabase) || await this.core.$$openDatabase()) {
if (this.localDatabase.isReady) {
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);
return false;
}
this.core.isReady = true;
this.core.$$markIsReady();
// run queued event once.
await this.core.$everyCommitPendingFileEvent();
return true;
} else {
this.core.isReady = false;
this.core.$$resetIsReady();
return false;
}
}

View File

@ -33,10 +33,14 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
authHeader = reactive(() =>
this.authHeaderSource.value == "" ? "" : "Basic " + window.btoa(this.authHeaderSource.value));
last_successful_post = false;
$$customFetchHandler(): ObsHttpHandler {
if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
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 }> {
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) {
// over 10MB
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);
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;
const r = await fetchByAPI(requestParam);
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 {
this.plugin.last_successful_post = true;
this.last_successful_post = true;
}
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);
// limit only in bulk_docs.
if (url.toString().indexOf("_bulk_docs") !== -1) {
this.plugin.last_successful_post = false;
this.last_successful_post = false;
}
this._log(ex);
throw ex;
@ -123,9 +127,9 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
const response: Response = await fetch(url, opts);
if (method == "POST" || method == "PUT") {
this.plugin.last_successful_post = response.ok;
this.last_successful_post = response.ok;
} else {
this.plugin.last_successful_post = true;
this.last_successful_post = true;
}
this._log(`HTTP:${method}${size} to:${localURL} -> ${response.status}`, LOG_LEVEL_DEBUG);
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);
// limit only in bulk_docs.
if (url.toString().indexOf("_bulk_docs") !== -1) {
this.plugin.last_successful_post = false;
this.last_successful_post = false;
}
this._log(ex);
throw ex;

View File

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

View File

@ -19,7 +19,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
);
this.addRibbonIcon("replicate", "Replicate", async () => {
await this.plugin.$$replicate(true);
await this.core.$$replicate(true);
}).addClass("livesync-ribbon-replicate");
@ -27,14 +27,14 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
id: "livesync-replicate",
name: "Replicate now",
callback: async () => {
await this.plugin.$$replicate();
await this.core.$$replicate();
},
});
this.addCommand({
id: "livesync-dump",
name: "Dump information of this doc ",
callback: () => {
const file = this.plugin.$$getActiveFilePath();
const file = this.core.$$getActiveFilePath();
if (!file) return;
fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false));
},
@ -45,7 +45,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => {
const file = view.file;
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._log("LiveSync Enabled.", LOG_LEVEL_NOTICE);
}
await this.plugin.realizeSettingSyncMode();
await this.plugin.saveSettings();
await this.core.$$realizeSettingSyncMode();
await this.core.$$saveSettingData();
},
});
this.addCommand({
id: "livesync-suspendall",
name: "Toggle All Sync.",
callback: async () => {
if (this.plugin.suspended) {
this.plugin.suspended = false;
if (this.core.$$isSuspended()) {
this.core.$$setSuspended(false);
this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE);
} else {
this.plugin.suspended = true;
this.core.$$setSuspended(true);
this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE);
}
await this.plugin.realizeSettingSyncMode();
await this.plugin.saveSettings();
await this.core.$$realizeSettingSyncMode();
await this.core.$$saveSettingData();
},
});
@ -84,7 +84,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
id: "livesync-scan-files",
name: "Scan storage and database again",
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",
name: "Abort synchronization immediately",
callback: () => {
this.plugin.replicator.terminateSync();
this.core.replicator.terminateSync();
},
})
return Promise.resolve(true);
}
$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
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>,
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 () => {
const stub = e.detail.stub;
const toc = e.detail.toc;
const stub = detail.stub;
const toc = detail.toc;
const stubDocX =
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 { scheduleTask } from "octagonal-wheels/concurrency/task";
declare global {
interface LSEvents {
"debug-sync-status": string[];
}
}
export class ModuleReplicateTest extends AbstractObsidianModule implements IObsidianModule {

View File

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

View File

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

View File

@ -72,7 +72,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
// In here, some merge has been processed.
// So we have to run replication if configured.
// TODO: Make this is as a event request
if (this.settings.syncAfterMerge && !this.plugin.suspended) {
if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
await this.core.$$waitForReplicationOnce();
}
// And, check it again.
@ -96,7 +96,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
this._log("There are no conflicted documents", LOG_LEVEL_NOTICE);
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) {
const targetItem = notes.find(e => e.dispPath == target)!;
await this.core.$$queueConflictCheck(targetItem.path);
@ -114,7 +114,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
notes.push({ path: getPath(doc), mtime: doc.mtime });
}
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.addEventListener("click", () => {
fireAndForget(() => this.allConflictCheck())

View File

@ -67,12 +67,12 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
})
return computed(() => formatted.value);
}
const labelReplication = padLeftSpComputed(this.plugin.replicationResultCount, `📥`);
const labelDBCount = padLeftSpComputed(this.plugin.databaseQueueCount, `📄`);
const labelStorageCount = padLeftSpComputed(this.plugin.storageApplyingCount, `💾`);
const labelReplication = padLeftSpComputed(this.core.replicationResultCount, `📥`);
const labelDBCount = padLeftSpComputed(this.core.databaseQueueCount, `📄`);
const labelStorageCount = padLeftSpComputed(this.core.storageApplyingCount, `💾`);
const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`);
const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`);
const labelConflictProcessCount = padLeftSpComputed(this.plugin.conflictProcessQueueCount, `🔩`);
const labelConflictProcessCount = padLeftSpComputed(this.core.conflictProcessQueueCount, `🔩`);
const hiddenFilesCount = reactive(() => hiddenFilesEventCount.value + hiddenFilesProcessingCount.value);
const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`)
const queueCountLabelX = reactive(() => {
@ -81,12 +81,12 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
const queueCountLabel = () => queueCountLabelX.value;
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 ? "📲 " : "";
})
const replicationStatLabel = computed(() => {
const e = this.plugin.replicationStat.value;
const e = this.core.replicationStat.value;
const sent = e.sent;
const arrived = e.arrived;
const maxPullSeq = e.maxPullSeq;
@ -128,9 +128,9 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
}
return { w, sent, pushLast, arrived, pullLast };
})
const labelProc = padLeftSpComputed(this.plugin.processing, ``);
const labelPend = padLeftSpComputed(this.plugin.totalQueued, `🛫`);
const labelInBatchDelay = padLeftSpComputed(this.plugin.batched, `📬`);
const labelProc = padLeftSpComputed(this.core.processing, ``);
const labelPend = padLeftSpComputed(this.core.totalQueued, `🛫`);
const labelInBatchDelay = padLeftSpComputed(this.core.batched, `📬`);
const waitingLabel = computed(() => {
return `${labelProc()}${labelPend()}${labelInBatchDelay()}`;
})
@ -144,7 +144,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
};
})
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 status = scheduleMessage + this.statusLog.value;
@ -181,7 +181,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
const thisFile = this.app.workspace.getActiveFile();
if (!thisFile) return "";
// 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());
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> {
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();
});
@ -290,7 +290,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" });
eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition());
if (this.settings?.showStatusOnStatusbar) {
this.statusBar = this.plugin.addStatusBarItem();
this.statusBar = this.core.addStatusBarItem();
this.statusBar.addClass("syncstatusbar");
}
this.adjustStatusDivPosition();
@ -318,7 +318,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) {
return;
}
const vaultName = this.plugin.$$getVaultName();
const vaultName = this.core.$$getVaultName();
const now = new Date();
const timestamp = now.toLocaleString();
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 { 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 { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts";
@ -29,7 +30,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem
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);
})
return Promise.resolve(true);
@ -46,7 +47,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem
}
notes.sort((a, b) => b.mtime - a.mtime);
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) {
const targetId = notes.find(e => e.dispPath == target)!;
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>)> = {
"": () => Promise.resolve("*"),
"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 methodFunc = method in methods ? methods[method] : methods[""];
@ -20,8 +20,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
}
$$saveDeviceAndVaultName(): void {
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.plugin.$$getVaultName();
localStorage.setItem(lsKey, this.plugin.deviceAndVaultName || "");
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName();
localStorage.setItem(lsKey, this.core.$$getDeviceAndVaultName() || "");
}
usedPassphrase = "";
@ -64,7 +64,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
}
async $$saveSettingData() {
this.plugin.$$saveDeviceAndVaultName();
this.core.$$saveDeviceAndVaultName();
const settings = { ...this.settings };
settings.deviceAndVaultName = "";
if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) {
@ -182,11 +182,11 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
// So, use history is always enabled.
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 (!localStorage.getItem(lsKey)) {
this.core.deviceAndVaultName = this.settings.deviceAndVaultName;
localStorage.setItem(lsKey, this.core.deviceAndVaultName);
this.core.$$setDeviceAndVaultName(this.settings.deviceAndVaultName);
this.$$saveDeviceAndVaultName();
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.settings.customChunkSize = 0;
}
this.core.deviceAndVaultName = localStorage.getItem(lsKey) || "";
if (this.core.deviceAndVaultName == "") {
this.core.$$setDeviceAndVaultName(localStorage.getItem(lsKey) || "");
if (this.core.$$getDeviceAndVaultName() == "") {
if (this.settings.usePluginSync) {
this._log("Device name is not set. Plug-in sync has been disabled.", LOG_LEVEL_NOTICE);
this.settings.usePluginSync = false;

View File

@ -19,7 +19,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
return this.settings.settingSyncFile != "";
}
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
}) => {
fireAndForget(() => this.checkAndApplySettingFromMarkdown(info.file, info.automated));
});
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: CustomEvent<ObsidianLiveSyncSettings>) => {
const settings = evt.detail;
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
if (settings.settingSyncFile != "") {
fireAndForget(() => this.saveSettingToMarkdown(settings.settingSyncFile));
}
@ -123,7 +122,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
return
}
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.addEventListener("click", () => {
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_FETCH = "Apply settings and restart obsidian with red_flag_fetch.md";
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_ONLY,
APPLY_AND_FETCH,
APPLY_AND_REBUILD,
CANCEL], { defaultAction: APPLY_AND_RESTART });
if (result == APPLY_ONLY || result == APPLY_AND_RESTART || result == APPLY_AND_REBUILD || result == APPLY_AND_FETCH) {
this.plugin.settings = settingToApply;
await this.plugin.$$saveSettingData();
this.core.settings = settingToApply;
await this.core.$$saveSettingData();
if (result == APPLY_ONLY) {
this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE);
return;
}
if (result == APPLY_AND_REBUILD) {
await this.plugin.rebuilder.scheduleRebuild();
await this.core.rebuilder.scheduleRebuild();
}
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 { fireAndForget, yieldNextAnimationFrame } from "octagonal-wheels/promises";
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 { JournalSyncMinio } from "../../../lib/src/replication/journal/objectstore/JournalSyncMinio.ts";
import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts";
import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts";
import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts";
export type OnUpdateResult = {
visibility?: boolean,
@ -162,7 +163,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
return await Promise.resolve();
}
if (key == "deviceAndVaultName") {
this.plugin.deviceAndVaultName = this.editingSettings?.[key] ?? "";
this.plugin.$$setDeviceAndVaultName(this.editingSettings?.[key] ?? "");
this.plugin.$$saveDeviceAndVaultName();
return await Promise.resolve();
}
@ -230,7 +231,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
const ret = { ...OnDialogSettingsDefault };
ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
ret.preset = ""
ret.deviceAndVaultName = this.plugin.deviceAndVaultName;
ret.deviceAndVaultName = this.plugin.$$getDeviceAndVaultName();
return ret;
}
computeAllLocalSettings(): Partial<OnDialogSettings> {
@ -304,7 +305,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
super(app, plugin);
this.plugin = plugin;
Setting.env = this;
eventHub.on(EVENT_REQUEST_RELOAD_SETTING_TAB, () => {
eventHub.onEvent(EVENT_REQUEST_RELOAD_SETTING_TAB, () => {
this.requestReload();
})
}
@ -710,7 +711,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
const replicator = this.plugin.$anyNewReplicator(settingForCheck);
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") {
Logger(`ERROR: Failed to check passphrase with the remote server: \n${db}.`, LOG_LEVEL_NOTICE);
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 => {
if (this.plugin.isMobile) {
if (this.plugin.$$isMobile()) {
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.`,
}, 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");
});
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)
.setClass("wizardOnly")
.addButton((button) => button
@ -1313,6 +1331,16 @@ However, your report is needed to stabilise this. I appreciate you for your grea
} else {
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")
}));
});
@ -1360,7 +1388,8 @@ However, your report is needed to stabilise this. I appreciate you for your grea
}).addButton(button => {
button.setButtonText("Apply");
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) {
this.editingSettings.isConfigured = true;
await this.saveAllDirtySettings();
await this.plugin.realizeSettingSyncMode();
await this.plugin.$$realizeSettingSyncMode();
await rebuildDB("localOnly");
// this.resetEditingSettings();
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();
} else {
await this.saveAllDirtySettings();
await this.plugin.realizeSettingSyncMode();
await this.plugin.$$realizeSettingSyncMode();
this.plugin.$$askReload();
}
}
} else {
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.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) => {
paneEl.addClass("wizardHidden");
new Setting(paneEl)
.autoWireText("settingSyncFile", { holdValue: true })
.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 hiddenFileSyncSettingDiv = hiddenFileSyncSettingEl.createDiv("");
hiddenFileSyncSettingDiv.innerText = this.editingSettings.syncInternalFiles ? LABEL_ENABLED : LABEL_DISABLED;
if (this.editingSettings.syncInternalFiles) {
new Setting(paneEl)
.setName("Disable Hidden files sync")
@ -1979,7 +2009,7 @@ ${stringifyYaml(pluginConfig)}`;
.split(",").filter(e => e).map(e => new RegExp(e, "i"));
this.plugin.localDatabase.hashCaches.clear();
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 adn = this.plugin.localDatabase.findAllDocs()
@ -1987,6 +2017,7 @@ ${stringifyYaml(pluginConfig)}`;
const path = getPath(i);
if (path.startsWith(ICXHeader)) continue;
if (path.startsWith(PSCHeader)) continue;
if (!this.plugin.settings.syncInternalFiles && path.startsWith(ICHeader)) continue;
documents.push(stripAllPrefixes(path));
}
const allPaths = [
@ -2648,9 +2679,9 @@ ${stringifyYaml(pluginConfig)}`;
async dryRunGC() {
await skipIfDuplicated("cleanup", async () => {
const replicator = this.plugin.getReplicator();
const replicator = this.plugin.$$getReplicator();
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") {
Logger(remoteDBConn);
return;
@ -2664,10 +2695,10 @@ ${stringifyYaml(pluginConfig)}`;
async dbGC() {
// Lock the remote completely once.
await skipIfDuplicated("cleanup", async () => {
const replicator = this.plugin.getReplicator();
const replicator = this.plugin.$$getReplicator();
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
await this.plugin.getReplicator().markRemoteLocked(this.plugin.settings, true, true);
const remoteDBConnection = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.isMobile)
await this.plugin.$$getReplicator().markRemoteLocked(this.plugin.settings, true, true);
const remoteDBConnection = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.$$isMobile())
if (typeof (remoteDBConnection) == "string") {
Logger(remoteDBConnection);
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

@ -426,4 +426,14 @@ span.ls-mark-cr::after {
background-color: rgba(var(--background-primary), 0.3);
backdrop-filter: blur(4px);
border-radius: 30%;
}
.sls-dialogue-note-wrapper {
display: flex;
justify-content: flex-end;
align-items: center;
}
.sls-dialogue-note-countdown {
font-size: 0.8em;
}