diff --git a/package-lock.json b/package-lock.json index ce3a646..5f4d9e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,9 +18,8 @@ "fflate": "^0.8.2", "idb": "^8.0.0", "minimatch": "^10.0.1", - "octagonal-wheels": "^0.1.20", + "octagonal-wheels": "^0.1.21", "svelte-check": "^4.0.4", - "xxhash-wasm": "0.4.2", "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" }, "devDependencies": { @@ -5105,13 +5104,11 @@ } }, "node_modules/octagonal-wheels": { - "version": "0.1.20", - "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.20.tgz", - "integrity": "sha512-cHM0UroCsZUZ/u0CA+DTOeasH2lPd8kkrfUiIFzg0d51ANY5KDJNk70rHoJbdph7+npNYgKSsdl1D3fEkzggFQ==", + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.21.tgz", + "integrity": "sha512-yJYzli50IwXW1ARgVnHR8LpudhRay0pfkSYJyyxqDuk0WM8hbeWDWLtlB/77xga8WsqW1HnZZP/8LfyZYVg1ew==", "dependencies": { - "idb": "^8.0.0", - "xxhash-wasm": "0.4.2", - "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" + "idb": "^8.0.0" } }, "node_modules/once": { @@ -6631,11 +6628,6 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "node_modules/xxhash-wasm": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz", - "integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA==" - }, "node_modules/xxhash-wasm-102": { "name": "xxhash-wasm", "version": "1.0.2", @@ -10316,13 +10308,11 @@ } }, "octagonal-wheels": { - "version": "0.1.20", - "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.20.tgz", - "integrity": "sha512-cHM0UroCsZUZ/u0CA+DTOeasH2lPd8kkrfUiIFzg0d51ANY5KDJNk70rHoJbdph7+npNYgKSsdl1D3fEkzggFQ==", + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.21.tgz", + "integrity": "sha512-yJYzli50IwXW1ARgVnHR8LpudhRay0pfkSYJyyxqDuk0WM8hbeWDWLtlB/77xga8WsqW1HnZZP/8LfyZYVg1ew==", "requires": { - "idb": "^8.0.0", - "xxhash-wasm": "0.4.2", - "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" + "idb": "^8.0.0" } }, "once": { @@ -11427,11 +11417,6 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "xxhash-wasm": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz", - "integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA==" - }, "xxhash-wasm-102": { "version": "npm:xxhash-wasm@1.0.2", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz", diff --git a/package.json b/package.json index 71e10cd..af2d530 100644 --- a/package.json +++ b/package.json @@ -72,9 +72,8 @@ "fflate": "^0.8.2", "idb": "^8.0.0", "minimatch": "^10.0.1", - "octagonal-wheels": "^0.1.20", + "octagonal-wheels": "^0.1.21", "svelte-check": "^4.0.4", - "xxhash-wasm": "0.4.2", "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" } } diff --git a/src/common/utils.ts b/src/common/utils.ts index d0a687e..d9747d6 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -85,6 +85,7 @@ export function getStoragePathFromUXFileInfo(file: UXFileInfoStub | string | Fil return stripAllPrefixes(file.path); } export function getDatabasePathFromUXFileInfo(file: UXFileInfoStub | string | FilePathWithPrefix) { + if (typeof file == "string" && file.startsWith(ICXHeader)) return file as FilePathWithPrefix; const prefix = isInternalFile(file) ? ICHeader : ""; if (typeof file == "string") return (prefix + stripAllPrefixes(file as FilePathWithPrefix)) as FilePathWithPrefix; return (prefix + stripAllPrefixes(file.path)) as FilePathWithPrefix; diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index 70dc1ba..d040c49 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -154,7 +154,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule this.settings.syncInternalFilesBeforeReplication && !this.settings.watchInternalFileChanges ) { - await this.scanAllStorageChanges(); + await this.scanAllStorageChanges(showNotice); } return true; } @@ -1108,6 +1108,9 @@ Offline Changed files: ${files.length}`; } queueNotification(key: FilePath) { + if (this.settings.suppressNotifyHiddenFilesChange) { + return; + } const configDir = this.plugin.app.vault.configDir; if (!key.startsWith(configDir)) return; const dirName = key.split("/").slice(0, -1).join("/"); diff --git a/src/lib b/src/lib index b299a42..89e825e 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit b299a4255c3dd98832f46389cc094328e0302e05 +Subproject commit 89e825ef3b26e7299f9ba74b1ffeed67e4103db7 diff --git a/src/modules/core/ModuleFileHandler.ts b/src/modules/core/ModuleFileHandler.ts index d56e3e5..bf190fa 100644 --- a/src/modules/core/ModuleFileHandler.ts +++ b/src/modules/core/ModuleFileHandler.ts @@ -22,6 +22,7 @@ import { getDocDataAsArray, isDocContentSame, readContent } from "../../lib/src/ import { shouldBeIgnored } from "../../lib/src/string_and_binary/path"; import type { ICoreModule } from "../ModuleTypes"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; +import { eventHub } from "../../common/events.ts"; export class ModuleFileHandler extends AbstractModule implements ICoreModule { get db() { @@ -356,6 +357,8 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule { `Processing ${path} (${entry._id.substring(0, 8)}: ${entry._rev?.substring(0, 5)}) :Started...`, LOG_LEVEL_VERBOSE ); + // Before writing (or skipped ), merging dialogue should be cancelled. + eventHub.emitEvent("conflict-cancelled", path); const ret = await this.dbToStorage(entry, targetFile); this._log(`Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Done`); return ret; diff --git a/src/modules/coreFeatures/ModuleConflictResolver.ts b/src/modules/coreFeatures/ModuleConflictResolver.ts index bc33786..b994c85 100644 --- a/src/modules/coreFeatures/ModuleConflictResolver.ts +++ b/src/modules/coreFeatures/ModuleConflictResolver.ts @@ -11,10 +11,23 @@ import { type diff_check_result, type FilePathWithPrefix, } from "../../lib/src/common/types"; -import { compareMTime, displayRev, TARGET_IS_NEW } from "../../common/utils"; +import { + compareMTime, + displayRev, + isCustomisationSyncMetadata, + isPluginMetadata, + TARGET_IS_NEW, +} from "../../common/utils"; import diff_match_patch from "diff-match-patch"; import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path"; import type { ICoreModule } from "../ModuleTypes.ts"; +import { eventHub } from "../../common/events.ts"; + +declare global { + interface LSEvents { + "conflict-cancelled": FilePathWithPrefix; + } +} export class ModuleConflictResolver extends AbstractModule implements ICoreModule { async $$resolveConflictByDeletingRev( @@ -30,11 +43,16 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul ); return MISSING_OR_ERROR; } + eventHub.emitEvent("conflict-cancelled", path); this._log(`${title} Conflicted revision deleted ${displayRev(deleteRevision)} ${path}`, LOG_LEVEL_INFO); if ((await this.core.databaseFileAccess.getConflictedRevs(path)).length != 0) { this._log(`${title} some conflicts are left in ${path}`, LOG_LEVEL_INFO); return AUTO_MERGED; } + this._log(`${title} ${path} is a plugin metadata file, no need to write to storage`, LOG_LEVEL_INFO); + if (isPluginMetadata(path) || isCustomisationSyncMetadata(path)) { + return AUTO_MERGED; + } // If no conflicts were found, write the resolved content to the storage. if (!(await this.core.fileHandler.dbToStorage(path, stripAllPrefixes(path), true))) { this._log(`Could not write the resolved content to the storage: ${path}`, LOG_LEVEL_NOTICE); @@ -139,26 +157,42 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul } } this._log("[conflict] Manual merge required!"); + eventHub.emitEvent("conflict-cancelled", filename); await this.core.$anyResolveConflictByUI(filename, conflictCheckResult); }); } async $anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise { + const currentRev = await this.core.databaseFileAccess.fetchEntryMeta(filename, undefined, true); + if (currentRev == false) { + this._log(`Could not get current revision of ${filename}`); + return Promise.resolve(false); + } const revs = await this.core.databaseFileAccess.getConflictedRevs(filename); if (revs.length == 0) { return Promise.resolve(true); } const mTimeAndRev = ( - await Promise.all( - revs.map(async (rev) => { - const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev); - if (leaf == false) { - return [0, rev] as [number, string]; - } - return [leaf.mtime, rev] as [number, string]; - }) - ) - ).sort((a, b) => b[0] - a[0]); + [ + [currentRev.mtime, currentRev._rev], + ...(await Promise.all( + revs.map(async (rev) => { + const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev); + if (leaf == false) { + return [0, rev] as [number, string]; + } + return [leaf.mtime, rev] as [number, string]; + }) + )), + ] as [number, string][] + ).sort((a, b) => { + const diff = b[0] - a[0]; + if (diff == 0) { + return a[1].localeCompare(b[1], "en", { numeric: true }); + } + return diff; + }); + console.warn(mTimeAndRev); this._log( `Resolving conflict by newest: ${filename} (Newest: ${new Date(mTimeAndRev[0][0]).toLocaleString()}) (${mTimeAndRev.length} revisions exists)` ); diff --git a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts index d87078d..3ef3654 100644 --- a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts +++ b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts @@ -1,10 +1,19 @@ import { App, Modal } from "../../../deps.ts"; import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch"; -import { CANCELLED, LEAVE_TO_SUBSEQUENT, RESULT_TIMED_OUT, type diff_result } from "../../../lib/src/common/types.ts"; +import { CANCELLED, LEAVE_TO_SUBSEQUENT, type diff_result } from "../../../lib/src/common/types.ts"; import { escapeStringToHTML } from "../../../lib/src/string_and_binary/convert.ts"; -import { delay, fireAndForget, sendValue, waitForValue } from "../../../lib/src/common/utils.ts"; +import { delay } from "../../../lib/src/common/utils.ts"; +import { eventHub } from "../../../common/events.ts"; +import { globalSlipBoard } from "../../../lib/src/bureau/bureau.ts"; + +export type MergeDialogResult = typeof CANCELLED | typeof LEAVE_TO_SUBSEQUENT | string; + +declare global { + interface Slips extends LSSlips { + "conflict-resolved": typeof CANCELLED | MergeDialogResult; + } +} -export type MergeDialogResult = typeof LEAVE_TO_SUBSEQUENT | typeof CANCELLED | string; export class ConflictResolveModal extends Modal { result: diff_result; filename: string; @@ -18,6 +27,7 @@ export class ConflictResolveModal extends Modal { pluginPickMode: boolean = false; localName: string = "Keep A"; remoteName: string = "Keep B"; + offEvent?: ReturnType; constructor(app: App, filename: string, diff: diff_result, pluginPickMode?: boolean, remoteName?: string) { super(app); @@ -32,23 +42,21 @@ export class ConflictResolveModal extends Modal { // Send cancel signal for the previous merge dialogue // if not there, simply be ignored. // sendValue("close-resolve-conflict:" + this.filename, false); - sendValue("cancel-resolve-conflict:" + this.filename, true); } onOpen() { const { contentEl } = this; // Send cancel signal for the previous merge dialogue // if not there, simply be ignored. - sendValue("cancel-resolve-conflict:" + this.filename, true); - setTimeout(() => { - fireAndForget(async () => { - const forceClose = await waitForValue("cancel-resolve-conflict:" + this.filename); - // debugger; - if (forceClose) { - this.sendResponse(CANCELLED); - } - }); - }, 10); + globalSlipBoard.submit("conflict-resolved", this.filename, CANCELLED); + if (this.offEvent) { + this.offEvent(); + } + this.offEvent = eventHub.onEvent("conflict-cancelled", (path) => { + if (path === this.filename) { + this.sendResponse(CANCELLED); + } + }); // sendValue("close-resolve-conflict:" + this.filename, false); this.titleEl.setText(this.title); contentEl.empty(); @@ -111,18 +119,19 @@ export class ConflictResolveModal extends Modal { onClose() { const { contentEl } = this; contentEl.empty(); + if (this.offEvent) { + this.offEvent(); + } if (this.consumed) { return; } this.consumed = true; - sendValue("close-resolve-conflict:" + this.filename, this.response); - sendValue("cancel-resolve-conflict:" + this.filename, false); + globalSlipBoard.submit("conflict-resolved", this.filename, this.response); } async waitForResult(): Promise { await delay(100); - const r = await waitForValue("close-resolve-conflict:" + this.filename); - if (r === RESULT_TIMED_OUT) return CANCELLED; + const r = await globalSlipBoard.awaitNext("conflict-resolved", this.filename); return r; } } diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index 4d5598d..62cc79d 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -1909,6 +1909,7 @@ I appreciate you for your great dedication. }); } + new Setting(paneEl).setClass("wizardHidden").autoWireToggle("suppressNotifyHiddenFilesChange", {}); new Setting(paneEl).setClass("wizardHidden").autoWireToggle("syncInternalFilesBeforeReplication", { onUpdate: visibleOnly(() => this.isConfiguredAs("watchInternalFileChanges", true)), }); @@ -2748,9 +2749,10 @@ ${stringifyYaml(pluginConfig)}`; new Setting(paneEl).autoWireDropDown("hashAlg", { options: { "": "Old Algorithm", - xxhash32: "xxhash32 (Fast)", + xxhash32: "xxhash32 (Fast but less collision resistance)", xxhash64: "xxhash64 (Fastest)", - sha1: "Fallback (Without WebAssembly)", + "mixed-purejs": "PureJS fallback (Fast, W/O WebAssembly)", + sha1: "Older fallback (Slow, W/O WebAssembly)", } as Record, }); this.addOnSaved("hashAlg", async () => { diff --git a/src/modules/features/SettingDialogue/settingConstants.ts b/src/modules/features/SettingDialogue/settingConstants.ts index 3dbea0d..a8c3a4a 100644 --- a/src/modules/features/SettingDialogue/settingConstants.ts +++ b/src/modules/features/SettingDialogue/settingConstants.ts @@ -369,6 +369,10 @@ export const SettingInformation: Partial