Skip to content

[BUG] External file changes are not detected if file is overwritten by move #3468

@jkhsjdhjs

Description

@jkhsjdhjs

Issue Description

QOwnNotes doesn't detect modifications of notes if they're synced by the Nextcloud desktop client or more generally, if they're performed by a move operation. This was already discussed in #3366, but now I finally had some time to investigate this issue further.

Nextcloud Desktop File Synchronization

When the Nextcloud desktop client syncs changes to an already-existing file, it doesn't overwrite it directly.
Instead, it first creates a temporary file .<name-of-note>.~<unique-id>, e.g. .Welcome to QOwnNotes.md.~dce5e2c.
All remote changes are written to this file first. Only afterwards, the original note file is overwritten by a move to finalize the synchronization.

So why are these changes not detected by QOwnNotes?

QOwnNotes File Change Detection

QOwnNotes uses two kinds of filesystem watchers: a directory watcher and a file watcher:

void MainWindow::connectFileWatcher(bool delayed) {
if (!delayed) {
connect(&noteDirectoryWatcher, &QFileSystemWatcher::directoryChanged, this,
&MainWindow::notesDirectoryWasModified, Qt::UniqueConnection);
connect(&noteDirectoryWatcher, &QFileSystemWatcher::fileChanged, this,
&MainWindow::notesWereModified, Qt::UniqueConnection);
} else {
// In some cases, there are delayed signals coming in which we don't want to handle
// so reconnect with delay
QTimer::singleShot(300, this, [this] {
connect(&noteDirectoryWatcher, &QFileSystemWatcher::directoryChanged, this,
&MainWindow::notesDirectoryWasModified, Qt::UniqueConnection);
connect(&noteDirectoryWatcher, &QFileSystemWatcher::fileChanged, this,
&MainWindow::notesWereModified, Qt::UniqueConnection);
});
}
}

The directory watcher is attached to the note directory and the file watcher to every note.
On a directory event (i.e. the creation of a new file), the notesDirectoryWasModified callback is called.
If a file is changed, notesWereModified is called instead.

Control Flow during Synchronization

If the Nextcloud client syncs a file, QOwnNotes' notesDirectoryWasModified callback is run first, since the Nextcloud desktop client first creates the temporary file. This callback also contains a workaround to call notesWereModified for the currently active note. However, since the Nextcloud client at this point only created the temporary file, the currently active note hasn't been modified yet. Thus, notesWereModified returns early. Afterwards, the temporary note file is modified by the Nextcloud sync client, which triggers notesDirectoryWasModified again for some reason, with the same result: no changes were detected because no changes to the actual note were performed yet.

Finally, the Nextcloud sync client moves the temporary note to the location of the actual note, thereby overwriting it. This triggers notesDirectoryWasModified again, but it still doesn't detect any changes, although they would be detectable by now. This is because before calling notesWereModified, notesDirectoryWasModified first rebuilds and reloads the note directory list, which results in the changes not being detected for some reason.

QOwnNotes/src/mainwindow.cpp

Lines 3112 to 3123 in 025e685

// rebuild and reload the notes directory list
buildNotesIndexAndLoadNoteDirectoryList();
// check if the current note was modified
// this fixes not detected external note changes of the current note if the
// event for the change in the current note comes after the event that the
// note folder was modified
QString noteFileName = currentNote.getFileName();
if (!noteFileName.isEmpty()) {
// Use the full path, like a filesystem watcher would, instead of just the file-name
notesWereModified(currentNote.fullNoteFilePath());
}

Moving the call to buildNotesIndexAndLoadNoteDirectoryList() after notesWereModified() makes the detection functional. However, I'm not sure if this has additional unwanted side-effects.

This final move operation also triggers notesWereModified directly, because in addition to the notes directory, files are also watched. However, since the note index has been reloaded and rebuilt, no changes are detected here as well.

Another issue results from how Qt handles file replacements. Nextcloud overwriting the note file via a move operation destroys the original file's inode. This causes QFileSystemWatcher to silently detach from the note. The watcher is never reattached to the new inode, meaning subsequent modifications to that note are completely ignored.

Summary

In summary, there are two important issues with the change detection the way it's currently implemented:

  1. Rebuilding the notes index breaks the change detection
  2. File watchers aren't reattached if files are overwritten

As I don't know much about the inner workings of the note index, I don't have a suggestion for a fix there.
However, the second issue could be fixed by re-adding files to the noteDirectoryWatcher after they have been overwritten.

Related Issues

#3366
#3428 @ohnemich

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions