Ask before saving over a project file someone else changed

Saving replaced the project file whatever had happened to it since it was
opened. If another user, another program or a second QET had saved the same
file in between (a shared drive, a copy synced from another PC), their work
was lost without a word.

QETProject now remembers the project file's size and modification time when
it reads it and after each save of its own. Before writing, write() compares
them with the file on disk and, if they differ, writes nothing and returns
"someone else changed the file on disk at <time> since it was opened".

- Interactive save (Ctrl+S, and the save on closing a project) asks:
  "Someone else changed <file> on disk at <time> since you opened it" with
  Save as a copy (opens Save As), Overwrite, or Cancel.
- Scripts (--run), the command line, MCP and the autosave timer have no one
  to ask, so they keep the refusal (autosave logs it). Setting
  QET_OVERWRITE_CHANGED=1 makes a script overwrite on purpose.
- A file that does not exist yet, and a Save As to another name, are not a
  conflict; after Save As the remembered state is the new file's.

No change to what is written, so .qet and .qetz stay as they were.

Evidence: new tst_changedondisk (unchanged file saves twice; a file written
behind the open project is refused; the override overwrites; a Save As
target is no conflict) fails on the changed-file case without the source
change and passes with it. Full ctest 107/107. Physical check with
qet-gui-dialog.sh: file modified behind the open project, Ctrl+S shows the
three-button dialog.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgPFANhWgD66V3v9KVKV58
This commit is contained in:
ispyisail
2026-10-16 12:17:21 +13:00
parent c4b5cf2f11
commit e8a5d6aec0
6 changed files with 257 additions and 5 deletions
+10 -1
View File
@@ -39,6 +39,7 @@
# include <KAutoSaveFile>
#endif
#include <QDateTime>
#include <QHash>
#include <QSet>
#include <QUuid>
@@ -332,7 +333,10 @@ class QETProject : public QObject
QStringList *kept_parts = nullptr);
static QString spliceKeptFolios(QString text, const QHash<int, QByteArray> &kept_folios);
bool close();
QETResult write();
QETResult write(bool overwrite_changed = false);
QString writeTarget() const;
void rememberDiskState();
bool changedOnDisk(QDateTime *when = nullptr) const;
bool isReadOnly() const;
/// Opened from a .qetz of a later format (opened read-only)
bool isFromLaterVersion() const {return m_from_later_version;}
@@ -554,6 +558,11 @@ class QETProject : public QObject
bool m_kept_folios_trusted = false;
/// Read from a .qet whose path nobody chose since: saved beside it
bool m_opened_from_qet = false;
/// The project file as this QET last read or wrote it, to see
/// whether something else changed it since (changedOnDisk())
QString m_disk_path;
qint64 m_disk_size = -1;
QDateTime m_disk_mtime;
/// Saving a .qetz may copy the part of a folio not built
/// (toXml(), stage D4)
bool m_kept_parts_trusted = false;