From d95506751bc182b6ba4fa2e09047b3c04a9ee849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4usler?= Date: Tue, 22 Feb 2022 21:44:56 +0100 Subject: [PATCH] Ignore watcher event if mtime doesn't changed (#3057) --- docs/FAQ.md | 2 +- docs/dev/BUILD.md | 2 +- src/main/filesystem/markdown.js | 6 ++-- src/main/filesystem/watcher.js | 34 ++++++++++++++----- src/main/menu/index.js | 2 +- src/main/preferences/index.js | 2 +- src/main/windows/editor.js | 6 ++-- .../components/editorWithTabs/editor.vue | 2 +- src/renderer/spellchecker/index.js | 2 +- 9 files changed, 38 insertions(+), 20 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 5910e364..eb6401e0 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -39,5 +39,5 @@ You can report bugs and problems via our [GitHub issue tracker](https://github.c Normally, you should never get this error but if you disabled user namespaces, this error message may appears in the command output when launching MarkText. To solve the issue, that Chromium cannot start the sandbox (process), you can choose one of the following steps: - Enable Linux kernel user namespaces to use the preferred sandbox: `sudo sysctl kernel.unprivileged_userns_clone=1`. -- Set correct SUID sandbox helper binary permissions: `sudo chown root /chrome-sandbox && sudo chmod 4755 /chrome-sandbox`. This is prefered if you don't want to enable user namespaces. +- Set correct SUID sandbox helper binary permissions: `sudo chown root /chrome-sandbox && sudo chmod 4755 /chrome-sandbox`. This is preferred if you don't want to enable user namespaces. - Launch MarkText with `--no-sandbox` argument. diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index a7bc36d1..9753394c 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -29,7 +29,7 @@ On Red Hat-based Linux: `sudo dnf install libX11-devel libxkbfile-devel libsecre **Additional development dependencies on Windows:** - Windows 10 SDK (only needed before Windows 10) -- Visual Studio 2019 (prefered) +- Visual Studio 2019 (preferred) ### Let's build diff --git a/src/main/filesystem/markdown.js b/src/main/filesystem/markdown.js index b7f8b5a2..46a01ce3 100644 --- a/src/main/filesystem/markdown.js +++ b/src/main/filesystem/markdown.js @@ -71,12 +71,12 @@ export const writeMarkdownFile = (pathname, content, options) => { * Reads the contents of a markdown file. * * @param {string} pathname The path to the markdown file. - * @param {string} preferedEol The prefered EOL. + * @param {string} preferredEol The preferred EOL. * @param {boolean} autoGuessEncoding Whether we should try to auto guess encoding. * @param {*} trimTrailingNewline The trim trailing newline option. * @returns {IMarkdownDocumentRaw} Returns a raw markdown document. */ -export const loadMarkdownFile = async (pathname, preferedEol, autoGuessEncoding = true, trimTrailingNewline = 2) => { +export const loadMarkdownFile = async (pathname, preferredEol, autoGuessEncoding = true, trimTrailingNewline = 2) => { // TODO: Use streams to not buffer the file multiple times and only guess // encoding on the first 256/512 bytes. @@ -95,7 +95,7 @@ export const loadMarkdownFile = async (pathname, preferedEol, autoGuessEncoding const isCrlf = CRLF_LINE_ENDING_REG.test(markdown) const isMixedLineEndings = isLf && isCrlf const isUnknownEnding = !isLf && !isCrlf - let lineEnding = preferedEol + let lineEnding = preferredEol if (isLf && !isCrlf) { lineEnding = 'lf' } else if (isCrlf && !isLf) { diff --git a/src/main/filesystem/watcher.js b/src/main/filesystem/watcher.js index 7af35f3d..b76ebe28 100644 --- a/src/main/filesystem/watcher.js +++ b/src/main/filesystem/watcher.js @@ -186,18 +186,18 @@ class Watcher { let renameTimer = null watcher - .on('add', pathname => { - if (!this._shouldIgnoreEvent(win.id, pathname, type)) { + .on('add', async pathname => { + if (!await this._shouldIgnoreEvent(win.id, pathname, type, usePolling)) { const { _preferences } = this - const eol = _preferences.getPreferedEol() + const eol = _preferences.getPreferredEol() const { autoGuessEncoding, trimTrailingNewline } = _preferences.getAll() add(win, pathname, type, eol, autoGuessEncoding, trimTrailingNewline) } }) - .on('change', pathname => { - if (!this._shouldIgnoreEvent(win.id, pathname, type)) { + .on('change', async pathname => { + if (!await this._shouldIgnoreEvent(win.id, pathname, type, usePolling)) { const { _preferences } = this - const eol = _preferences.getPreferedEol() + const eol = _preferences.getPreferredEol() const { autoGuessEncoding, trimTrailingNewline } = _preferences.getAll() change(win, pathname, type, eol, autoGuessEncoding, trimTrailingNewline) } @@ -323,7 +323,7 @@ class Watcher { * @param {string} pathname The path to ignore. * @param {number} [duration] The duration in ms to ignore the changed event. */ - ignoreChangedEvent (windowId, pathname, duration = WATCHER_STABILITY_THRESHOLD + WATCHER_STABILITY_POLL_INTERVAL + 1000) { + ignoreChangedEvent (windowId, pathname, duration = WATCHER_STABILITY_THRESHOLD + (WATCHER_STABILITY_POLL_INTERVAL * 2)) { this._ignoreChangeEvents.push({ windowId, pathname, duration, start: new Date() }) } @@ -333,8 +333,9 @@ class Watcher { * @param {number} winId * @param {string} pathname * @param {string} type + * @param {boolean} usePolling */ - _shouldIgnoreEvent (winId, pathname, type) { + async _shouldIgnoreEvent (winId, pathname, type, usePolling) { if (type === 'file') { const { _ignoreChangeEvents } = this const currentTime = new Date() @@ -343,9 +344,26 @@ class Watcher { if (windowId === winId && pathToIgnore === pathname) { _ignoreChangeEvents.splice(i, 1) --i + + // Modification origin is the editor and we should ignore the event. if (currentTime - start < duration) { return true } + + // Try to catch cloud drives that emit the change event not immediately or re-sync the change (GH#3044). + if (!usePolling) { + try { + const fileInfo = await fsPromises.stat(pathname) + if (fileInfo.mtime - start < duration) { + if (global.MARKTEXT_DEBUG_VERBOSE >= 3) { + console.log(`Ignoring file event after "stat": current="${currentTime}", start="${start}", file="${fileInfo.mtime}".`) + } + return true + } + } catch (error) { + console.error('Failed to "stat" file to determine modification time:', error) + } + } } } } diff --git a/src/main/menu/index.js b/src/main/menu/index.js index 5b66cca6..344f3f2b 100644 --- a/src/main/menu/index.js +++ b/src/main/menu/index.js @@ -148,7 +148,7 @@ class AppMenu { const { menu, shortcutMap } = windowMenus.get(window.id) - // Set source-code editor if prefered. + // Set source-code editor if preferred. const sourceCodeModeMenuItem = menu.getMenuItemById('sourceCodeModeMenuItem') sourceCodeModeMenuItem.checked = isSourceMode diff --git a/src/main/preferences/index.js b/src/main/preferences/index.js index a4f6f8d8..95ceac27 100644 --- a/src/main/preferences/index.js +++ b/src/main/preferences/index.js @@ -121,7 +121,7 @@ class Preference extends EventEmitter { }) } - getPreferedEol () { + getPreferredEol () { const endOfLine = this.getItem('endOfLine') if (endOfLine === 'lf') { return 'lf' diff --git a/src/main/windows/editor.js b/src/main/windows/editor.js index 34c51c22..5d5dea2c 100644 --- a/src/main/windows/editor.js +++ b/src/main/windows/editor.js @@ -82,7 +82,7 @@ class EditorWindow extends BaseWindow { // Restore and focus window this.bringToFront() - const lineEnding = preferences.getPreferedEol() + const lineEnding = preferences.getPreferredEol() appMenu.updateLineEndingMenu(this.id, lineEnding) win.webContents.send('mt::bootstrap-editor', { @@ -235,7 +235,7 @@ class EditorWindow extends BaseWindow { const { browserWindow } = this const { preferences } = this._accessor - const eol = preferences.getPreferedEol() + const eol = preferences.getPreferredEol() const { autoGuessEncoding, trimTrailingNewline } = preferences.getAll() for (const { filePath, options, selected } of fileList) { @@ -393,7 +393,7 @@ class EditorWindow extends BaseWindow { this.lifecycle = WindowLifecycle.READY const { preferences } = this._accessor const { sideBarVisibility, tabBarVisibility, sourceCodeModeEnabled } = preferences.getAll() - const lineEnding = preferences.getPreferedEol() + const lineEnding = preferences.getPreferredEol() browserWindow.webContents.send('mt::bootstrap-editor', { addBlankTab: true, markdownList: [], diff --git a/src/renderer/components/editorWithTabs/editor.vue b/src/renderer/components/editorWithTabs/editor.vue index 9136e1a3..96701c28 100644 --- a/src/renderer/components/editorWithTabs/editor.vue +++ b/src/renderer/components/editorWithTabs/editor.vue @@ -614,7 +614,7 @@ export default { const { container } = this.editor = new Muya(ele, options) - // Create spell check wrapper and enable spell checking if prefered. + // Create spell check wrapper and enable spell checking if preferred. this.spellchecker = new SpellChecker(spellcheckerEnabled) if (spellcheckerEnabled) { this.initSpellchecker() diff --git a/src/renderer/spellchecker/index.js b/src/renderer/spellchecker/index.js index db3f7bbc..5321bf35 100644 --- a/src/renderer/spellchecker/index.js +++ b/src/renderer/spellchecker/index.js @@ -123,7 +123,7 @@ export class SpellChecker { * @param {boolean} enabled Whether spell checking is enabled. */ constructor (enabled = true) { - // Hunspell is used on Linux and Windows but macOS can use Hunspell if prefered. + // Hunspell is used on Linux and Windows but macOS can use Hunspell if preferred. this.isHunspell = !isOsSpellcheckerSupported() || !!process.env['SPELLCHECKER_PREFER_HUNSPELL'] // eslint-disable-line dot-notation // Initialize spell check provider. If spell check is not enabled don't