diff --git a/src/common/events.ts b/src/common/events.ts index a871dab..d359af8 100644 --- a/src/common/events.ts +++ b/src/common/events.ts @@ -18,6 +18,8 @@ export const EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG = "request-open-plugin-sync-d export const EVENT_REQUEST_OPEN_P2P = "request-open-p2p"; export const EVENT_REQUEST_CLOSE_P2P = "request-close-p2p"; +export const EVENT_REQUEST_RUN_DOCTOR = "request-run-doctor"; + // export const EVENT_FILE_CHANGED = "file-changed"; declare global { @@ -33,6 +35,7 @@ declare global { [EVENT_REQUEST_OPEN_P2P]: undefined; [EVENT_REQUEST_OPEN_SETUP_URI]: undefined; [EVENT_REQUEST_COPY_SETUP_URI]: undefined; + [EVENT_REQUEST_RUN_DOCTOR]: string; } } diff --git a/src/common/utils.ts b/src/common/utils.ts index d9747d6..1b324f7 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -30,6 +30,7 @@ import { sameChangePairs } from "./stores.ts"; import type { KeyValueDatabase } from "./KeyValueDB.ts"; import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { EVENT_PLUGIN_UNLOADED, eventHub } from "./events.ts"; +import { promiseWithResolver, type PromiseWithResolvers } from "octagonal-wheels/promises"; export { scheduleTask, cancelTask, cancelAllTasks } from "../lib/src/concurrency/task.ts"; @@ -493,3 +494,47 @@ export function onlyInNTimes(n: number, proc: (progress: number) => any) { } }; } + +const waitingTasks = {} as Record; previous: number; leastNext: number }>; + +export function rateLimitedSharedExecution(key: string, interval: number, proc: () => Promise): Promise { + if (!(key in waitingTasks)) { + waitingTasks[key] = { task: undefined, previous: 0, leastNext: 0 }; + } + if (waitingTasks[key].task) { + // Extend the previous execution time. + waitingTasks[key].leastNext = Date.now() + interval; + return waitingTasks[key].task.promise; + } + + const previous = waitingTasks[key].previous; + + const delay = previous == 0 ? 0 : Math.max(interval - (Date.now() - previous), 0); + + const task = promiseWithResolver(); + void task.promise.finally(() => { + if (waitingTasks[key].task === task) { + waitingTasks[key].task = undefined; + waitingTasks[key].previous = Math.max(Date.now(), waitingTasks[key].leastNext); + } + }); + waitingTasks[key] = { + task, + previous: Date.now(), + leastNext: Date.now() + interval, + }; + void scheduleTask("thin-out-" + key, delay, async () => { + try { + task.resolve(await proc()); + } catch (ex) { + task.reject(ex); + } + }); + return task.promise; +} +export function updatePreviousExecutionTime(key: string, timeDelta: number = 0) { + if (!(key in waitingTasks)) { + waitingTasks[key] = { task: undefined, previous: 0, leastNext: 0 }; + } + waitingTasks[key].leastNext = Math.max(Date.now() + timeDelta, waitingTasks[key].leastNext); +} diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts index 22bd45e..e2a3687 100644 --- a/src/features/P2PSync/CmdP2PReplicator.ts +++ b/src/features/P2PSync/CmdP2PReplicator.ts @@ -23,6 +23,7 @@ import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; import type { Confirm } from "../../lib/src/interfaces/Confirm.ts"; import type ObsidianLiveSyncPlugin from "../../main.ts"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; +import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts"; class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicatorBase { storeP2PStatusLine = reactiveSource(""); @@ -79,6 +80,9 @@ export class P2PReplicator } return undefined!; } + override getPlatform(): string { + return getPlatformName(); + } override onunload(): void { removeP2PReplicatorInstance(); diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte index 27aa541..ef633dd 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte @@ -299,6 +299,8 @@ placeholder="anything-you-like" bind:value={eRoomId} autocomplete="off" + spellcheck="false" + autocorrect="off" /> @@ -327,6 +329,12 @@ + + + Device name to identify the device. Please use shorter one for the stable peer + detection, i.e., "iphone-16" or "macbook-2021". + + diff --git a/src/lib b/src/lib index 2a0dd3c..9cf9bb6 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 2a0dd3c3acec81d17c4ecc3eaa541a418d11fda4 +Subproject commit 9cf9bb6f1ffe781c98ab54753f9753a10bd525f6 diff --git a/src/main.ts b/src/main.ts index 14731f8..0e6fae0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -570,6 +570,9 @@ export default class ObsidianLiveSyncPlugin $$replicate(showMessage: boolean = false): Promise { throwShouldBeOverridden(); } + $$replicateByEvent(showMessage: boolean = false): Promise { + throwShouldBeOverridden(); + } $everyOnDatabaseInitialized(showingNotice: boolean): Promise { throwShouldBeOverridden(); @@ -636,10 +639,6 @@ export default class ObsidianLiveSyncPlugin throwShouldBeOverridden(); } - $$waitForReplicationOnce(): Promise { - throwShouldBeOverridden(); - } - $$resetLocalDatabase(): Promise { throwShouldBeOverridden(); } diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 92ecba5..275fb4f 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -18,17 +18,26 @@ import { type MetaEntry, } from "../../lib/src/common/types"; import { QueueProcessor } from "octagonal-wheels/concurrency/processor"; -import { getPath, isChunk, isValidPath, scheduleTask } from "../../common/utils"; +import { + getPath, + isChunk, + isValidPath, + rateLimitedSharedExecution, + scheduleTask, + updatePreviousExecutionTime, +} from "../../common/utils"; import { isAnyNote } from "../../lib/src/common/utils"; import { EVENT_FILE_SAVED, eventHub } from "../../common/events"; import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator"; import { globalSlipBoard } from "../../lib/src/bureau/bureau"; +const KEY_REPLICATION_ON_EVENT = "replicationOnEvent"; +const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000; export class ModuleReplicator extends AbstractModule implements ICoreModule { $everyOnloadAfterLoadSettings(): Promise { eventHub.onEvent(EVENT_FILE_SAVED, () => { if (this.settings.syncOnSave && !this.core.$$isSuspended()) { - scheduleTask("perform-replicate-after-save", 250, () => this.core.$$waitForReplicationOnce()); + scheduleTask("perform-replicate-after-save", 250, () => this.core.$$replicateByEvent()); } }); return Promise.resolve(true); @@ -61,7 +70,16 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule { await this.loadQueuedFiles(); return true; } + async $$replicate(showMessage: boolean = false): Promise { + try { + updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT, REPLICATION_ON_EVENT_FORECASTED_TIME); + return await this.$$_replicate(showMessage); + } finally { + updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT); + } + } + async $$_replicate(showMessage: boolean = false): Promise { //--? if (!this.core.$$isReady()) return; if (isLockAcquired("cleanup")) { @@ -192,6 +210,15 @@ Or if you are sure know what had been happened, we can unlock the database from return ret; } + async $$replicateByEvent(): Promise { + const least = this.settings.syncMinimumInterval; + if (least > 0) { + return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => { + return await this.$$replicate(); + }); + } + return await shareRunningResult(`replication`, () => this.core.$$replicate()); + } $$parseReplicationResult(docs: Array>): void { if (this.settings.suspendParseReplicationResult && !this.replicationResultProcessor.isSuspended) { this.replicationResultProcessor.suspend(); @@ -416,8 +443,4 @@ Or if you are sure know what had been happened, we can unlock the database from if (checkResult == "CHECKAGAIN") return await this.core.$$replicateAllFromServer(showingNotice); return !checkResult; } - - async $$waitForReplicationOnce(): Promise { - return await shareRunningResult(`replication`, () => this.core.$$replicate()); - } } diff --git a/src/modules/coreFeatures/ModuleConflictResolver.ts b/src/modules/coreFeatures/ModuleConflictResolver.ts index 1afada1..1d5c0f1 100644 --- a/src/modules/coreFeatures/ModuleConflictResolver.ts +++ b/src/modules/coreFeatures/ModuleConflictResolver.ts @@ -140,7 +140,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul //auto resolved, but need check again; if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) { //Wait for the running replication, if not running replication, run it once. - await this.core.$$waitForReplicationOnce(); + await this.core.$$replicateByEvent(); } this._log("[conflict] Automatically merged, but we have to check it again"); await this.core.$$queueConflictCheck(filename); diff --git a/src/modules/essential/ModuleMigration.ts b/src/modules/essential/ModuleMigration.ts index f5b8b32..d7219c6 100644 --- a/src/modules/essential/ModuleMigration.ts +++ b/src/modules/essential/ModuleMigration.ts @@ -1,17 +1,141 @@ -import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger.js"; -import { SETTING_VERSION_SUPPORT_CASE_INSENSITIVE } from "../../lib/src/common/types.js"; +import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger.js"; +import { type ObsidianLiveSyncSettings } from "../../lib/src/common/types.js"; import { EVENT_REQUEST_OPEN_P2P, EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI, + EVENT_REQUEST_RUN_DOCTOR, eventHub, } from "../../common/events.ts"; import { AbstractModule } from "../AbstractModule.ts"; import type { ICoreModule } from "../ModuleTypes.ts"; import { $msg } from "src/lib/src/common/i18n.ts"; +import { checkUnsuitableValues, RuleLevel, type RuleForType } from "../../lib/src/common/configForDoc.ts"; +import { getConfName, type AllSettingItemKey } from "../features/SettingDialogue/settingConstants.ts"; export class ModuleMigration extends AbstractModule implements ICoreModule { + async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) { + const r = checkUnsuitableValues(this.core.settings); + if (!forceRescan && r.version == this.settings.doctorProcessedVersion) { + const isIssueFound = Object.keys(r.rules).length > 0; + const msg = isIssueFound ? "Issues found" : "No issues found"; + this._log(`${msg} but marked as to be silent`, LOG_LEVEL_VERBOSE); + return; + } + const issues = Object.entries(r.rules); + if (issues.length == 0) { + this._log($msg("Doctor.Message.NoIssues"), LOG_LEVEL_NOTICE); + return; + } else { + const OPT_YES = `${$msg("Doctor.Button.Yes")}` as const; + const OPT_NO = `${$msg("Doctor.Button.No")}` as const; + const OPT_DISMISS = `${$msg("Doctor.Button.DismissThisVersion")}` as const; + // this._log(`Issues found in ${key}`, LOG_LEVEL_VERBOSE); + const issues = Object.keys(r.rules) + .map((key) => `- ${getConfName(key as AllSettingItemKey)}`) + .join("\n"); + const msg = await this.core.confirm.askSelectStringDialogue( + $msg("Doctor.Dialogue.Main", { activateReason, issues }), + [OPT_YES, OPT_NO, OPT_DISMISS], + { + title: $msg("Doctor.Dialogue.Title"), + defaultAction: OPT_YES, + } + ); + if (msg == OPT_DISMISS) { + this.settings.doctorProcessedVersion = r.version; + await this.core.saveSettings(); + this._log("Marked as to be silent", LOG_LEVEL_VERBOSE); + return; + } + if (msg != OPT_YES) return; + let shouldRebuild = false; + let shouldRebuildLocal = false; + const issueItems = Object.entries(r.rules) as [keyof ObsidianLiveSyncSettings, RuleForType][]; + this._log(`${issueItems.length} Issue(s) found `, LOG_LEVEL_VERBOSE); + let idx = 0; + const applySettings = {} as Partial; + const OPT_FIX = `${$msg("Doctor.Button.Fix")}` as const; + const OPT_SKIP = `${$msg("Doctor.Button.Skip")}` as const; + const OPT_FIXBUTNOREBUILD = `${$msg("Doctor.Button.FixButNoRebuild")}` as const; + let skipped = 0; + for (const [key, value] of issueItems) { + const levelMap = { + [RuleLevel.Necessary]: $msg("Doctor.Level.Necessary"), + [RuleLevel.Recommended]: $msg("Doctor.Level.Recommended"), + [RuleLevel.Optional]: $msg("Doctor.Level.Optional"), + [RuleLevel.Must]: $msg("Doctor.Level.Must"), + }; + const level = value.level ? levelMap[value.level] : "Unknown"; + const options = [OPT_FIX]; + if ((!skipRebuild && value.requireRebuild) || value.requireRebuildLocal) { + options.push(OPT_FIXBUTNOREBUILD); + } + options.push(OPT_SKIP); + const note = skipRebuild + ? "" + : `${value.requireRebuild ? $msg("Doctor.Message.RebuildRequired") : ""}${value.requireRebuildLocal ? $msg("Doctor.Message.RebuildLocalRequired") : ""}`; + + const ret = await this.core.confirm.askSelectStringDialogue( + $msg("Doctor.Dialogue.MainFix", { + name: getConfName(key as AllSettingItemKey), + current: `${this.settings[key]}`, + reason: value.reason ?? " N/A ", + ideal: `${value.value}`, + level: `${level}`, + note: note, + }), + options, + { + title: $msg("Doctor.Dialogue.TitleFix", { current: `${++idx}`, total: `${issueItems.length}` }), + defaultAction: OPT_FIX, + } + ); + + if (ret == OPT_FIX || ret == OPT_FIXBUTNOREBUILD) { + //@ts-ignore + applySettings[key] = value.value; + if (ret == OPT_FIX) { + shouldRebuild = shouldRebuild || value.requireRebuild || false; + shouldRebuildLocal = shouldRebuildLocal || value.requireRebuildLocal || false; + } + } else { + skipped++; + } + } + if (Object.keys(applySettings).length > 0) { + this.settings = { + ...this.settings, + ...applySettings, + }; + } + if (skipped == 0) { + this.settings.doctorProcessedVersion = r.version; + } else { + if ( + (await this.core.confirm.askYesNoDialog($msg("Doctor.Message.SomeSkipped"), { + title: $msg("Doctor.Dialogue.TitleAlmostDone"), + defaultOption: "No", + })) == "no" + ) { + // Some skipped, and user wants + this.settings.doctorProcessedVersion = r.version; + } + } + await this.core.saveSettings(); + if (!skipRebuild) { + if (shouldRebuild) { + await this.core.rebuilder.scheduleRebuild(); + await this.core.$$performRestart(); + } else if (shouldRebuildLocal) { + await this.core.rebuilder.scheduleFetch(); + await this.core.$$performRestart(); + } + } + } + } + async migrateDisableBulkSend() { if (this.settings.sendChunksBulk) { this._log($msg("moduleMigration.logBulkSendCorrupted"), LOG_LEVEL_NOTICE); @@ -20,157 +144,157 @@ export class ModuleMigration extends AbstractModule implements ICoreModule { await this.saveSettings(); } } - async migrationCheck() { - const old = this.settings.settingVersion; - const current = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; - // Check each migrations(old -> current) - if (!(await this.migrateToCaseInsensitive(old, current))) { - this._log( - $msg("moduleMigration.logMigrationFailed", { - old: old.toString(), - current: current.toString(), - }), - LOG_LEVEL_NOTICE - ); - return; - } - } - async migrateToCaseInsensitive(old: number, current: number) { - if ( - this.settings.handleFilenameCaseSensitive !== undefined && - this.settings.doNotUseFixedRevisionForChunks !== undefined - ) { - if (current < SETTING_VERSION_SUPPORT_CASE_INSENSITIVE) { - this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; - await this.saveSettings(); - } - return true; - } - if ( - old >= SETTING_VERSION_SUPPORT_CASE_INSENSITIVE && - this.settings.handleFilenameCaseSensitive !== undefined && - this.settings.doNotUseFixedRevisionForChunks !== undefined - ) { - return true; - } + // async migrationCheck() { + // const old = this.settings.settingVersion; + // const current = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; + // // Check each migrations(old -> current) + // if (!(await this.migrateToCaseInsensitive(old, current))) { + // this._log( + // $msg("moduleMigration.logMigrationFailed", { + // old: old.toString(), + // current: current.toString(), + // }), + // LOG_LEVEL_NOTICE + // ); + // return; + // } + // } + // async migrateToCaseInsensitive(old: number, current: number) { + // if ( + // this.settings.handleFilenameCaseSensitive !== undefined && + // this.settings.doNotUseFixedRevisionForChunks !== undefined + // ) { + // if (current < SETTING_VERSION_SUPPORT_CASE_INSENSITIVE) { + // this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; + // await this.saveSettings(); + // } + // return true; + // } + // if ( + // old >= SETTING_VERSION_SUPPORT_CASE_INSENSITIVE && + // this.settings.handleFilenameCaseSensitive !== undefined && + // this.settings.doNotUseFixedRevisionForChunks !== undefined + // ) { + // return true; + // } - let remoteHandleFilenameCaseSensitive: undefined | boolean = undefined; - let remoteDoNotUseFixedRevisionForChunks: undefined | boolean = undefined; - let remoteChecked = false; - try { - const remoteInfo = await this.core.replicator.getRemotePreferredTweakValues(this.settings); - if (remoteInfo) { - remoteHandleFilenameCaseSensitive = - "handleFilenameCaseSensitive" in remoteInfo ? remoteInfo.handleFilenameCaseSensitive : false; - remoteDoNotUseFixedRevisionForChunks = - "doNotUseFixedRevisionForChunks" in remoteInfo ? remoteInfo.doNotUseFixedRevisionForChunks : false; - if ( - remoteHandleFilenameCaseSensitive !== undefined || - remoteDoNotUseFixedRevisionForChunks !== undefined - ) { - remoteChecked = true; - } - } else { - this._log($msg("moduleMigration.logFetchRemoteTweakFailed"), LOG_LEVEL_INFO); - } - } catch (ex) { - this._log($msg("moduleMigration.logRemoteTweakUnavailable"), LOG_LEVEL_INFO); - this._log(ex, LOG_LEVEL_VERBOSE); - } + // let remoteHandleFilenameCaseSensitive: undefined | boolean = undefined; + // let remoteDoNotUseFixedRevisionForChunks: undefined | boolean = undefined; + // let remoteChecked = false; + // try { + // const remoteInfo = await this.core.replicator.getRemotePreferredTweakValues(this.settings); + // if (remoteInfo) { + // remoteHandleFilenameCaseSensitive = + // "handleFilenameCaseSensitive" in remoteInfo ? remoteInfo.handleFilenameCaseSensitive : false; + // remoteDoNotUseFixedRevisionForChunks = + // "doNotUseFixedRevisionForChunks" in remoteInfo ? remoteInfo.doNotUseFixedRevisionForChunks : false; + // if ( + // remoteHandleFilenameCaseSensitive !== undefined || + // remoteDoNotUseFixedRevisionForChunks !== undefined + // ) { + // remoteChecked = true; + // } + // } else { + // this._log($msg("moduleMigration.logFetchRemoteTweakFailed"), LOG_LEVEL_INFO); + // } + // } catch (ex) { + // this._log($msg("moduleMigration.logRemoteTweakUnavailable"), LOG_LEVEL_INFO); + // this._log(ex, LOG_LEVEL_VERBOSE); + // } - if (remoteChecked) { - // The case that the remote could be checked. - if (remoteHandleFilenameCaseSensitive && remoteDoNotUseFixedRevisionForChunks) { - // Migrated, but configured as same as old behaviour. - this.settings.handleFilenameCaseSensitive = true; - this.settings.doNotUseFixedRevisionForChunks = true; - this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; - this._log( - $msg("moduleMigration.logMigratedSameBehaviour", { - current: current.toString(), - }), - LOG_LEVEL_INFO - ); - await this.saveSettings(); - return true; - } - const message = $msg("moduleMigration.msgFetchRemoteAgain"); - const OPTION_FETCH = $msg("moduleMigration.optionYesFetchAgain"); - const DISMISS = $msg("moduleMigration.optionNoAskAgain"); - const options = [OPTION_FETCH, DISMISS]; - const ret = await this.core.confirm.confirmWithMessage( - $msg("moduleMigration.titleCaseSensitivity"), - message, - options, - DISMISS, - 40 - ); - if (ret == OPTION_FETCH) { - this.settings.handleFilenameCaseSensitive = remoteHandleFilenameCaseSensitive || false; - this.settings.doNotUseFixedRevisionForChunks = remoteDoNotUseFixedRevisionForChunks || false; - this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; - await this.saveSettings(); - try { - await this.core.rebuilder.scheduleFetch(); - return; - } catch (ex) { - this._log($msg("moduleMigration.logRedflag2CreationFail"), LOG_LEVEL_VERBOSE); - this._log(ex, LOG_LEVEL_VERBOSE); - } - return false; - } else { - return false; - } - } + // if (remoteChecked) { + // // The case that the remote could be checked. + // if (remoteHandleFilenameCaseSensitive && remoteDoNotUseFixedRevisionForChunks) { + // // Migrated, but configured as same as old behaviour. + // this.settings.handleFilenameCaseSensitive = true; + // this.settings.doNotUseFixedRevisionForChunks = true; + // this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; + // this._log( + // $msg("moduleMigration.logMigratedSameBehaviour", { + // current: current.toString(), + // }), + // LOG_LEVEL_INFO + // ); + // await this.saveSettings(); + // return true; + // } + // const message = $msg("moduleMigration.msgFetchRemoteAgain"); + // const OPTION_FETCH = $msg("moduleMigration.optionYesFetchAgain"); + // const DISMISS = $msg("moduleMigration.optionNoAskAgain"); + // const options = [OPTION_FETCH, DISMISS]; + // const ret = await this.core.confirm.confirmWithMessage( + // $msg("moduleMigration.titleCaseSensitivity"), + // message, + // options, + // DISMISS, + // 40 + // ); + // if (ret == OPTION_FETCH) { + // this.settings.handleFilenameCaseSensitive = remoteHandleFilenameCaseSensitive || false; + // this.settings.doNotUseFixedRevisionForChunks = remoteDoNotUseFixedRevisionForChunks || false; + // this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; + // await this.saveSettings(); + // try { + // await this.core.rebuilder.scheduleFetch(); + // return; + // } catch (ex) { + // this._log($msg("moduleMigration.logRedflag2CreationFail"), LOG_LEVEL_VERBOSE); + // this._log(ex, LOG_LEVEL_VERBOSE); + // } + // return false; + // } else { + // return false; + // } + // } - const ENABLE_BOTH = $msg("moduleMigration.optionEnableBoth"); - const ENABLE_FILENAME_CASE_INSENSITIVE = $msg("moduleMigration.optionEnableFilenameCaseInsensitive"); - const ENABLE_FIXED_REVISION_FOR_CHUNKS = $msg("moduleMigration.optionEnableFixedRevisionForChunks"); - const ADJUST_TO_REMOTE = $msg("moduleMigration.optionAdjustRemote"); - const KEEP = $msg("moduleMigration.optionKeepPreviousBehaviour"); - const DISMISS = $msg("moduleMigration.optionDecideLater"); - const message = $msg("moduleMigration.msgSinceV02321"); - const options = [ENABLE_BOTH, ENABLE_FILENAME_CASE_INSENSITIVE, ENABLE_FIXED_REVISION_FOR_CHUNKS]; - if (remoteChecked) { - options.push(ADJUST_TO_REMOTE); - } - options.push(KEEP, DISMISS); - const ret = await this.core.confirm.confirmWithMessage( - $msg("moduleMigration.titleCaseSensitivity"), - message, - options, - DISMISS, - 40 - ); - console.dir(ret); - switch (ret) { - case ENABLE_BOTH: - this.settings.handleFilenameCaseSensitive = false; - this.settings.doNotUseFixedRevisionForChunks = false; - break; - case ENABLE_FILENAME_CASE_INSENSITIVE: - this.settings.handleFilenameCaseSensitive = false; - this.settings.doNotUseFixedRevisionForChunks = true; - break; - case ENABLE_FIXED_REVISION_FOR_CHUNKS: - this.settings.doNotUseFixedRevisionForChunks = false; - this.settings.handleFilenameCaseSensitive = true; - break; - case KEEP: - this.settings.handleFilenameCaseSensitive = true; - this.settings.doNotUseFixedRevisionForChunks = true; - this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; - await this.saveSettings(); - return true; - case DISMISS: - default: - return false; - } - this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; - await this.saveSettings(); - await this.core.rebuilder.scheduleRebuild(); - await this.core.$$performRestart(); - } + // const ENABLE_BOTH = $msg("moduleMigration.optionEnableBoth"); + // const ENABLE_FILENAME_CASE_INSENSITIVE = $msg("moduleMigration.optionEnableFilenameCaseInsensitive"); + // const ENABLE_FIXED_REVISION_FOR_CHUNKS = $msg("moduleMigration.optionEnableFixedRevisionForChunks"); + // const ADJUST_TO_REMOTE = $msg("moduleMigration.optionAdjustRemote"); + // const KEEP = $msg("moduleMigration.optionKeepPreviousBehaviour"); + // const DISMISS = $msg("moduleMigration.optionDecideLater"); + // const message = $msg("moduleMigration.msgSinceV02321"); + // const options = [ENABLE_BOTH, ENABLE_FILENAME_CASE_INSENSITIVE, ENABLE_FIXED_REVISION_FOR_CHUNKS]; + // if (remoteChecked) { + // options.push(ADJUST_TO_REMOTE); + // } + // options.push(KEEP, DISMISS); + // const ret = await this.core.confirm.confirmWithMessage( + // $msg("moduleMigration.titleCaseSensitivity"), + // message, + // options, + // DISMISS, + // 40 + // ); + // console.dir(ret); + // switch (ret) { + // case ENABLE_BOTH: + // this.settings.handleFilenameCaseSensitive = false; + // this.settings.doNotUseFixedRevisionForChunks = false; + // break; + // case ENABLE_FILENAME_CASE_INSENSITIVE: + // this.settings.handleFilenameCaseSensitive = false; + // this.settings.doNotUseFixedRevisionForChunks = true; + // break; + // case ENABLE_FIXED_REVISION_FOR_CHUNKS: + // this.settings.doNotUseFixedRevisionForChunks = false; + // this.settings.handleFilenameCaseSensitive = true; + // break; + // case KEEP: + // this.settings.handleFilenameCaseSensitive = true; + // this.settings.doNotUseFixedRevisionForChunks = true; + // this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; + // await this.saveSettings(); + // return true; + // case DISMISS: + // default: + // return false; + // } + // this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE; + // await this.saveSettings(); + // await this.core.rebuilder.scheduleRebuild(); + // await this.core.$$performRestart(); + // } async initialMessage() { const message = $msg("moduleMigration.msgInitialSetup", { @@ -226,7 +350,8 @@ export class ModuleMigration extends AbstractModule implements ICoreModule { return false; } if (this.settings.isConfigured) { - await this.migrationCheck(); + await this.migrateUsingDoctor(false); + // await this.migrationCheck(); await this.migrateDisableBulkSend(); } if (!this.settings.isConfigured) { @@ -235,7 +360,14 @@ export class ModuleMigration extends AbstractModule implements ICoreModule { this._log($msg("moduleMigration.logSetupCancelled"), LOG_LEVEL_NOTICE); return false; } + await this.migrateUsingDoctor(true); } return true; } + $everyOnLayoutReady(): Promise { + eventHub.onEvent(EVENT_REQUEST_RUN_DOCTOR, async (reason) => { + await this.migrateUsingDoctor(false, reason, true); + }); + return Promise.resolve(true); + } } diff --git a/src/modules/essentialObsidian/ModuleObsidianEvents.ts b/src/modules/essentialObsidian/ModuleObsidianEvents.ts index c420163..45ebda1 100644 --- a/src/modules/essentialObsidian/ModuleObsidianEvents.ts +++ b/src/modules/essentialObsidian/ModuleObsidianEvents.ts @@ -56,7 +56,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs } else { if (this.settings.syncOnEditorSave) { this._log("Sync on Editor Save.", LOG_LEVEL_VERBOSE); - fireAndForget(() => this.core.$$replicate()); + fireAndForget(() => this.core.$$replicateByEvent()); } } }); @@ -155,7 +155,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs return; } if (this.settings.syncOnFileOpen && !this.core.$$isSuspended()) { - await this.core.$$replicate(); + await this.core.$$replicateByEvent(); } await this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix); } diff --git a/src/modules/features/ModuleInteractiveConflictResolver.ts b/src/modules/features/ModuleInteractiveConflictResolver.ts index d98ad2d..8f2b4d5 100644 --- a/src/modules/features/ModuleInteractiveConflictResolver.ts +++ b/src/modules/features/ModuleInteractiveConflictResolver.ts @@ -90,7 +90,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im // So we have to run replication if configured. // TODO: Make this is as a event request if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) { - await this.core.$$waitForReplicationOnce(); + await this.core.$$replicateByEvent(); } // And, check it again. await this.core.$$queueConflictCheck(filename); diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index fd4824a..d65f0b0 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -71,6 +71,7 @@ import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, EVENT_REQUEST_OPEN_SETUP_URI, EVENT_REQUEST_RELOAD_SETTING_TAB, + EVENT_REQUEST_RUN_DOCTOR, eventHub, } from "../../../common/events.ts"; import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; @@ -1890,6 +1891,9 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal }) .setClass("wizardHidden"); + new Setting(paneEl).autoWireNumeric("syncMinimumInterval", { + onUpdate: onlyOnNonLiveSync, + }); new Setting(paneEl) .setClass("wizardHidden") .autoWireToggle("syncOnSave", { onUpdate: onlyOnNonLiveSync }); @@ -2226,10 +2230,23 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal void addPane(containerEl, "Hatch", "🧰", 50, true).then((paneEl) => { // const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` }); // hatchWarn.addClass("op-warn-info"); - void addPanel(paneEl, "Reporting Issue").then((paneEl) => { - new Setting(paneEl).setName("Make report to inform the issue").addButton((button) => + void addPanel(paneEl, $msg("Setting.TroubleShooting")).then((paneEl) => { + new Setting(paneEl) + .setName($msg("Setting.TroubleShooting.Doctor")) + .setDesc($msg("Setting.TroubleShooting.Doctor.Desc")) + .addButton((button) => + button + .setButtonText("Run Doctor") + .setCta() + .setDisabled(false) + .onClick(() => { + this.closeSetting(); + eventHub.emitEvent(EVENT_REQUEST_RUN_DOCTOR, "you wanted(Thank you)!"); + }) + ); + new Setting(paneEl).setName("Prepare the 'report' to create an issue").addButton((button) => button - .setButtonText("Make report") + .setButtonText("Copy Report to clipboard") .setCta() .setDisabled(false) .onClick(async () => { @@ -2310,7 +2327,10 @@ version:${manifestVersion} ${stringifyYaml(pluginConfig)}`; console.log(msgConfig); await navigator.clipboard.writeText(msgConfig); - Logger(`Information has been copied to clipboard`, LOG_LEVEL_NOTICE); + Logger( + `Generated report has been copied to clipboard. Please report the issue with this! Thank you for your cooperation!`, + LOG_LEVEL_NOTICE + ); }) ); new Setting(paneEl).autoWireToggle("writeLogToTheFile"); diff --git a/src/modules/features/SettingDialogue/settingConstants.ts b/src/modules/features/SettingDialogue/settingConstants.ts index a8c3a4a..929e001 100644 --- a/src/modules/features/SettingDialogue/settingConstants.ts +++ b/src/modules/features/SettingDialogue/settingConstants.ts @@ -346,8 +346,8 @@ export const SettingInformation: Partial