diff --git a/src/CmdSetupLiveSync.ts b/src/CmdSetupLiveSync.ts index 6dd5dee..104b38c 100644 --- a/src/CmdSetupLiveSync.ts +++ b/src/CmdSetupLiveSync.ts @@ -5,6 +5,7 @@ import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { askSelectString, askYesNo, askString } from "./utils"; import { decrypt, encrypt } from "./lib/src/e2ee_v2"; import { LiveSyncCommands } from "./LiveSyncCommands"; +import { delay } from "./lib/src/utils"; export class SetupLiveSync extends LiveSyncCommands { onunload() { } @@ -97,7 +98,8 @@ export class SetupLiveSync extends LiveSyncCommands { const setupAsNew = "Set it up as secondary or subsequent device"; const setupAgain = "Reconfigure and reconstitute the data"; const setupManually = "Leave everything to me"; - + newSettingW.syncInternalFiles = false; + newSettingW.usePluginSync = false; const setupType = await askSelectString(this.app, "How would you like to set it up?", [setupAsNew, setupAgain, setupJustImport, setupManually]); if (setupType == setupJustImport) { this.plugin.settings = newSettingW; @@ -106,11 +108,7 @@ export class SetupLiveSync extends LiveSyncCommands { } else if (setupType == setupAsNew) { this.plugin.settings = newSettingW; this.plugin.usedPassphrase = ""; - await this.plugin.saveSettings(); - await this.plugin.resetLocalDatabase(); - await this.plugin.localDatabase.initializeDatabase(); - await this.plugin.markRemoteResolved(); - await this.plugin.replicate(true); + await this.fetchLocal(); } else if (setupType == setupAgain) { const confirm = "I know this operation will rebuild all my databases with files on this device, and files that are on the remote database and I didn't synchronize to any other devices will be lost and want to proceed indeed."; if (await askSelectString(this.app, "Do you really want to do this?", ["Cancel", confirm]) != confirm) { @@ -118,15 +116,7 @@ export class SetupLiveSync extends LiveSyncCommands { } this.plugin.settings = newSettingW; this.plugin.usedPassphrase = ""; - await this.plugin.saveSettings(); - await this.plugin.resetLocalDatabase(); - await this.plugin.localDatabase.initializeDatabase(); - await this.plugin.initializeDatabase(true); - await this.plugin.tryResetRemoteDatabase(); - await this.plugin.markRemoteLocked(); - await this.plugin.markRemoteResolved(); - await this.plugin.replicate(true); - + await this.rebuildEverything(); } else if (setupType == setupManually) { const keepLocalDB = await askYesNo(this.app, "Keep local DB?"); const keepRemoteDB = await askYesNo(this.app, "Keep remote DB?"); @@ -134,6 +124,8 @@ export class SetupLiveSync extends LiveSyncCommands { // nothing to do. so peaceful. this.plugin.settings = newSettingW; this.plugin.usedPassphrase = ""; + this.suspendAllSync(); + this.suspendExtraSync(); await this.plugin.saveSettings(); const replicate = await askYesNo(this.app, "Unlock and replicate?"); if (replicate == "yes") { @@ -189,4 +181,52 @@ export class SetupLiveSync extends LiveSyncCommands { Logger("Couldn't parse or decrypt configuration uri.", LOG_LEVEL.NOTICE); } } + + suspendExtraSync() { + Logger("Hidden files and plugin synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL.NOTICE) + this.plugin.settings.syncInternalFiles = false; + this.plugin.settings.usePluginSync = false; + this.plugin.settings.autoSweepPlugins = false; + } + suspendAllSync() { + this.plugin.settings.liveSync = false; + this.plugin.settings.periodicReplication = false; + this.plugin.settings.syncOnSave = false; + this.plugin.settings.syncOnStart = false; + this.plugin.settings.syncOnFileOpen = false; + this.plugin.settings.syncAfterMerge = false; + //this.suspendExtraSync(); + } + async fetchLocal() { + this.suspendExtraSync(); + await this.plugin.realizeSettingSyncMode(); + await this.plugin.resetLocalDatabase(); + await delay(1000); + await this.plugin.markRemoteResolved(); + await this.plugin.openDatabase(); + this.plugin.isReady = true; + await delay(500); + await this.plugin.replicateAllFromServer(true); + } + async rebuildRemote() { + this.suspendExtraSync(); + await this.plugin.realizeSettingSyncMode(); + await this.plugin.markRemoteLocked(); + await this.plugin.tryResetRemoteDatabase(); + await this.plugin.markRemoteLocked(); + await delay(500); + await this.plugin.replicateAllToServer(true); + } + async rebuildEverything() { + this.suspendExtraSync(); + await this.plugin.realizeSettingSyncMode(); + await this.plugin.resetLocalDatabase(); + await delay(1000); + await this.plugin.initializeDatabase(true); + await this.plugin.markRemoteLocked(); + await this.plugin.tryResetRemoteDatabase(); + await this.plugin.markRemoteLocked(); + await delay(500); + await this.plugin.replicateAllToServer(true); + } } diff --git a/src/ObsidianLiveSyncSettingTab.ts b/src/ObsidianLiveSyncSettingTab.ts index 2c37afb..7bed886 100644 --- a/src/ObsidianLiveSyncSettingTab.ts +++ b/src/ObsidianLiveSyncSettingTab.ts @@ -420,25 +420,16 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { if (!encrypt) { passphrase = ""; } - this.plugin.settings.liveSync = false; - this.plugin.settings.periodicReplication = false; - this.plugin.settings.syncOnSave = false; - this.plugin.settings.syncOnStart = false; - this.plugin.settings.syncOnFileOpen = false; - this.plugin.settings.syncAfterMerge = false; + this.plugin.addOnSetup.suspendAllSync(); + this.plugin.addOnSetup.suspendExtraSync(); this.plugin.settings.encrypt = encrypt; this.plugin.settings.passphrase = passphrase; this.plugin.settings.useDynamicIterationCount = useDynamicIterationCount; this.plugin.settings.usePathObfuscation = usePathObfuscation; - await this.plugin.saveSettings(); markDirtyControl(); if (sendToServer) { - await this.plugin.initializeDatabase(true); - await this.plugin.markRemoteLocked(); - await this.plugin.tryResetRemoteDatabase(); - await this.plugin.markRemoteLocked(); - await this.plugin.replicateAllToServer(true); + await this.plugin.addOnSetup.rebuildRemote() } else { await this.plugin.markRemoteResolved(); await this.plugin.replicate(true); @@ -489,47 +480,27 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { if (!encrypt) { passphrase = ""; } - this.plugin.settings.liveSync = false; - this.plugin.settings.periodicReplication = false; - this.plugin.settings.syncOnSave = false; - this.plugin.settings.syncOnStart = false; - this.plugin.settings.syncOnFileOpen = false; - this.plugin.settings.syncAfterMerge = false; - this.plugin.settings.syncInternalFiles = false; - this.plugin.settings.usePluginSync = false; + this.plugin.addOnSetup.suspendAllSync(); + this.plugin.addOnSetup.suspendExtraSync(); this.plugin.settings.encrypt = encrypt; this.plugin.settings.passphrase = passphrase; this.plugin.settings.useDynamicIterationCount = useDynamicIterationCount; this.plugin.settings.usePathObfuscation = usePathObfuscation; - Logger("Hidden files and plugin synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL.NOTICE) + Logger("All synchronizations have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL.NOTICE) await this.plugin.saveSettings(); markDirtyControl(); applyDisplayEnabled(); // @ts-ignore - this.plugin.app.setting.close() + this.plugin.app.setting.close(); await delay(2000); if (method == "localOnly") { - await this.plugin.resetLocalDatabase(); - await delay(1000); - await this.plugin.markRemoteResolved(); - await this.plugin.openDatabase(); - this.plugin.isReady = true; - await this.plugin.replicateAllFromServer(true); + await this.plugin.addOnSetup.fetchLocal(); } if (method == "remoteOnly") { - await this.plugin.markRemoteLocked(); - await this.plugin.tryResetRemoteDatabase(); - await this.plugin.markRemoteLocked(); - await this.plugin.replicateAllToServer(true); + await this.plugin.addOnSetup.rebuildRemote(); } if (method == "rebuildBothByThisDevice") { - await this.plugin.resetLocalDatabase(); - await delay(1000); - await this.plugin.initializeDatabase(true); - await this.plugin.markRemoteLocked(); - await this.plugin.tryResetRemoteDatabase(); - await this.plugin.markRemoteLocked(); - await this.plugin.replicateAllToServer(true); + await this.plugin.addOnSetup.rebuildEverything(); } } @@ -1149,32 +1120,42 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { .addButton((button) => { button.setButtonText("Merge") .onClick(async () => { - this.plugin.settings.syncInternalFiles = true; + this.plugin.addOnSetup.suspendExtraSync(); this.display(); + Logger("Gathering files for enabling Hidden File Sync", LOG_LEVEL.NOTICE); await this.plugin.addOnHiddenFileSync.syncInternalFilesAndDatabase("safe", true); + this.plugin.settings.syncInternalFiles = true; await this.plugin.saveSettings(); - this.display(); + Logger(`Done!`, LOG_LEVEL.NOTICE); }) }) .addButton((button) => { button.setButtonText("Fetch") .onClick(async () => { - this.plugin.settings.syncInternalFiles = true; - this.display(); + this.plugin.addOnSetup.suspendExtraSync(); + // @ts-ignore + this.plugin.app.setting.close() + Logger("Gathering files for enabling Hidden File Sync", LOG_LEVEL.NOTICE); await this.plugin.addOnHiddenFileSync.syncInternalFilesAndDatabase("pullForce", true); + this.plugin.settings.syncInternalFiles = true; await this.plugin.saveSettings(); - Logger(`Restarting the app is strongly recommended!`, LOG_LEVEL.NOTICE); - this.display(); + Logger(`Done! Restarting the app is strongly recommended!`, LOG_LEVEL.NOTICE); + // this.display(); }) }) .addButton((button) => { button.setButtonText("Overwrite") .onClick(async () => { - this.plugin.settings.syncInternalFiles = true; - this.display(); + this.plugin.addOnSetup.suspendExtraSync(); + // @ts-ignore + this.plugin.app.setting.close() + Logger("Gathering files for enabling Hidden File Sync", LOG_LEVEL.NOTICE); + // this.display(); await this.plugin.addOnHiddenFileSync.syncInternalFilesAndDatabase("pushForce", true); + this.plugin.settings.syncInternalFiles = true; await this.plugin.saveSettings(); - this.display(); + Logger(`Done!`, LOG_LEVEL.NOTICE); + // this.display(); }) }); } diff --git a/src/StorageEventManager.ts b/src/StorageEventManager.ts index 762fcc0..ebcf9cd 100644 --- a/src/StorageEventManager.ts +++ b/src/StorageEventManager.ts @@ -57,8 +57,8 @@ export class StorageEventManagerObsidian extends StorageEventManager { watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) { if (file instanceof TFile) { this.appendWatchEvent([ - { type: "CREATE", file }, { type: "DELETE", file: { path: oldFile as FilePath, mtime: file.stat.mtime, ctime: file.stat.ctime, size: file.stat.size, deleted: true } }, + { type: "CREATE", file }, ], ctx); } } diff --git a/src/main.ts b/src/main.ts index e50b82e..a800da3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,7 @@ import { Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "di import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, RequestUrlParam, RequestUrlResponse, requestUrl } from "./deps"; import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, SALT_OF_PASSPHRASE, ConfigPassphraseStore, CouchDBConnection, FLAGMD_REDFLAG2, FLAGMD_REDFLAG3, PREFIXMD_LOGFILE, DatabaseConnectingStatus, EntryHasPath, DocumentID, FilePathWithPrefix, FilePath, AnyEntry } from "./lib/src/types"; import { InternalFileInfo, queueItem, CacheData, FileEventItem, FileWatchEventQueueMax } from "./types"; -import { delay, getDocData, isDocContentSame } from "./lib/src/utils"; +import { getDocData, isDocContentSame } from "./lib/src/utils"; import { Logger } from "./lib/src/logger"; import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { LogDisplayModal } from "./LogDisplayModal"; @@ -387,25 +387,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin try { if (this.isRedFlagRaised() || this.isRedFlag2Raised() || this.isRedFlag3Raised()) { this.settings.batchSave = false; - this.settings.liveSync = false; - this.settings.periodicReplication = false; - this.settings.syncOnSave = false; - this.settings.syncOnStart = false; - this.settings.syncOnFileOpen = false; - this.settings.syncAfterMerge = false; - this.settings.autoSweepPlugins = false; - this.settings.usePluginSync = false; + this.addOnSetup.suspendAllSync(); + this.addOnSetup.suspendExtraSync(); this.settings.suspendFileWatching = true; - this.settings.syncInternalFiles = false; await this.saveSettings(); if (this.isRedFlag2Raised()) { Logger(`${FLAGMD_REDFLAG2} has been detected! Self-hosted LiveSync suspends all sync and rebuild everything.`, LOG_LEVEL.NOTICE); - await this.resetLocalDatabase(); - await this.initializeDatabase(true); - await this.markRemoteLocked(); - await this.tryResetRemoteDatabase(); - await this.markRemoteLocked(); - await this.replicateAllToServer(true); + await this.addOnSetup.rebuildEverything(); await this.deleteRedFlag2(); if (await askYesNo(this.app, "Do you want to disable Suspend file watching and restart obsidian now?") == "yes") { this.settings.suspendFileWatching = false; @@ -415,12 +403,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin } } else if (this.isRedFlag3Raised()) { Logger(`${FLAGMD_REDFLAG3} has been detected! Self-hosted LiveSync will discard the local database and fetch everything from the remote once again.`, LOG_LEVEL.NOTICE); - await this.resetLocalDatabase(); - await delay(1000); - await this.markRemoteResolved(); - await this.openDatabase(); - this.isReady = true; - await this.replicateAllFromServer(true); + await this.addOnSetup.fetchLocal(); await this.deleteRedFlag3(); if (await askYesNo(this.app, "Do you want to disable Suspend file watching and restart obsidian now?") == "yes") { this.settings.suspendFileWatching = false; @@ -912,8 +895,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin const file = queue.args.file; const key = `file-last-proc-${queue.type}-${file.path}`; const last = Number(await this.kvDB.get(key) || 0); + let mtime = file.mtime; if (queue.type == "DELETE") { await this.deleteFromDBbyPath(file.path); + mtime = file.mtime - 1; + const keyD1 = `file-last-proc-CREATE-${file.path}`; + const keyD2 = `file-last-proc-CHANGED-${file.path}`; + await this.kvDB.set(keyD1, mtime); + await this.kvDB.set(keyD2, mtime); } else if (queue.type == "INTERNAL") { await this.addOnHiddenFileSync.watchVaultRawEventsAsync(file.path); } else { @@ -930,6 +919,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin const cache = queue.args.cache; if (queue.type == "CREATE" || queue.type == "CHANGED") { + const keyD1 = `file-last-proc-DELETED-${file.path}`; + await this.kvDB.set(keyD1, mtime); if (!await this.updateIntoDB(targetFile, false, cache)) { Logger(`DB -> STORAGE: failed, cancel the relative operations: ${targetFile.path}`, LOG_LEVEL.INFO); // cancel running queues and remove one of atomic operation @@ -942,7 +933,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin await this.watchVaultRenameAsync(targetFile, queue.args.oldPath); } } - await this.kvDB.set(key, file.mtime); + await this.kvDB.set(key, mtime); } while (this.vaultManager.getQueueLength() > 0); return true; }) @@ -1114,13 +1105,16 @@ export default class ObsidianLiveSyncPlugin extends Plugin // This occurs not only when files are deleted, but also when conflicts are resolved. // We have to check no other revisions are left. const lastDocs = await this.localDatabase.getDBEntry(path); + if (path != file.path) { + Logger(`delete skipped: ${file.path} :Not exactly matched`, LOG_LEVEL.VERBOSE); + } if (lastDocs === false) { await this.deleteVaultItem(file); } else { // it perhaps delete some revisions. // may be we have to reload this await this.pullFile(path, null, true); - Logger(`delete skipped:${lastDocs._id}`, LOG_LEVEL.VERBOSE); + Logger(`delete skipped:${file.path}`, LOG_LEVEL.VERBOSE); } return; } @@ -1381,8 +1375,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin //---> Sync async parseReplicationResult(docs: Array>): Promise { + const docsSorted = docs.sort((a, b) => b.mtime - a.mtime); L1: - for (const change of docs) { + for (const change of docsSorted) { for (const proc of this.addOns) { if (await proc.parseReplicationResultItem(change)) { continue L1; diff --git a/src/utils.ts b/src/utils.ts index 156a6bb..5b52d82 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -325,14 +325,16 @@ export function isValidPath(filename: string) { let touchedFiles: string[] = []; export function getAbstractFileByPath(path: FilePath): TAbstractFile | null { - // Hidden API but so useful. - // @ts-ignore - if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) { - // @ts-ignore - return app.vault.getAbstractFileByPathInsensitive(path); - } else { - return app.vault.getAbstractFileByPath(path); - } + // Disabled temporary. + return app.vault.getAbstractFileByPath(path); + // // Hidden API but so useful. + // // @ts-ignore + // if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) { + // // @ts-ignore + // return app.vault.getAbstractFileByPathInsensitive(path); + // } else { + // return app.vault.getAbstractFileByPath(path); + // } } export function trimPrefix(target: string, prefix: string) { return target.startsWith(prefix) ? target.substring(prefix.length) : target;