From 745b4a936ce6d829088117c728fe26bcfc972ae3 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Thu, 24 Sep 2026 21:57:42 +1200 Subject: [PATCH] Fix bugtracker #273: skip crash-recovery backups of an unchanged project writeBackup() rebuilds the whole project's XML with toXml() on the GUI thread before handing the file write to a worker thread. On a big project that freezes the interface for seconds (bugtracker #273, and #329, where the interval was raised from 2 to 20 minutes to make it rarer). It ran right after every project was opened, and then every 20 minutes whether or not anything had changed. Only write a backup when something changed since the last one: the undo stack moved, setModified(true) was called, or the embedded element or title block collections changed. A project just opened from a file starts clean, since the file is already what a crash would restore. New projects and projects restored from a backup are backed up at once, as before. Measured on examples/industrial.qet (50 folios), each backup blocks the GUI thread for about 0.28 s on a fast machine; with a 2 s test interval, the old code backed up on every tick, the new code only after a change. Co-Authored-By: Claude Opus 5.5 --- sources/qetproject.cpp | 25 +++++++++++++++++++++++++ sources/qetproject.h | 2 ++ 2 files changed, 27 insertions(+) diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index af0f00b25..24fe557d2 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -132,6 +132,9 @@ QETProject::QETProject(const QString &path, QObject *parent) : return; } + //The file just read already holds everything a crash could lose, so + //there is nothing to back up until the project is changed. + m_backup_needed = false; init(); } @@ -258,6 +261,19 @@ void QETProject::init() m_undo_stack = new QUndoStack(this); connect(m_undo_stack, &QUndoStack::cleanChanged, this, &QETProject::undoStackChanged); + //What counts as a change for writeBackup(): the undo stack moving, + //setModified(true), and the embedded collections, which can change + //without going through either. + const auto backup_needed = [this]() { m_backup_needed = true; }; + connect(m_undo_stack, &QUndoStack::indexChanged, this, backup_needed); + connect(&m_titleblocks_collection, &TitleBlockTemplatesCollection::changed, this, backup_needed); + connect(&m_titleblocks_collection, &TitleBlockTemplatesCollection::aboutToRemove, this, backup_needed); + connect(m_elements_collection, &XmlElementCollection::elementAdded, this, backup_needed); + connect(m_elements_collection, &XmlElementCollection::elementChanged, this, backup_needed); + connect(m_elements_collection, &XmlElementCollection::elementRemoved, this, backup_needed); + connect(m_elements_collection, &XmlElementCollection::directorieAdded, this, backup_needed); + connect(m_elements_collection, &XmlElementCollection::directoryRemoved, this, backup_needed); + m_save_backup_timer.setInterval(BACKUP_INTERVAL); connect(&m_save_backup_timer, &QTimer::timeout, this, &QETProject::writeBackup); m_save_backup_timer.start(); @@ -1564,6 +1580,9 @@ void QETProject::diagramOrderChanged(int old_index, int new_index) { Mark this project as modified and emit the projectModified() signal. */ void QETProject::setModified(bool modified) { + if (modified) { + m_backup_needed = true; + } if (m_modified != modified) { m_modified = modified; emit projectModified(this, m_modified); @@ -2146,6 +2165,12 @@ void QETProject::writeBackup() //both would write through &m_backup_file on different threads. if (m_backup_future.isRunning()) return; + //toXml() walks the whole project on the GUI thread, which freezes + //big projects for seconds (bugtracker #273, #329). A backup of an + //unchanged project would be identical to the last one, so skip it. + if (!m_backup_needed) + return; + m_backup_needed = false; //Capture the document by value (implicitly shared, so cheap): the //Qt5-style QtConcurrent::run(function, reference-args) call did not //survive the Qt6 API change, a lambda behaves identically on both. diff --git a/sources/qetproject.h b/sources/qetproject.h index e898a65e6..0fd8c7598 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -297,6 +297,8 @@ class QETProject : public QObject private: /// When false, writeBackup() is a no-op (set by the headless CLI) static bool m_backup_enabled; + /// Something changed since the last backup, see writeBackup() + bool m_backup_needed = true; /// File path this project is saved to QString m_file_path; /// Current state of the project