mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2024-12-03 08:45:34 +02:00
Fixed
- 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:
parent
8b45dd1d24
commit
2c97289ec8
@ -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 };
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
2
src/lib
2
src/lib
@ -1 +1 @@
|
||||
Subproject commit 5079b0bf79d94495beaed174e9a71dbfa537f7c4
|
||||
Subproject commit a91bb47c90307031ef5ed9814f31ef14153f2782
|
278
src/main.ts
278
src/main.ts
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 });
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
13
src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts
Normal file
13
src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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]) => {
|
||||
|
@ -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 {
|
||||
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -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;
|
||||
|
182
src/modules/main/ModuleLiveSyncMain.ts
Normal file
182
src/modules/main/ModuleLiveSyncMain.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
10
styles.css
10
styles.css
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user