Add Qt-only autosave recovery fallback

Provide a small KAutoSaveFile-compatible implementation for the no-KF5 build path and use it to keep the existing crash-recovery code active when BUILD_WITH_KF5=OFF.

The normal KF5 build still uses the KDE KAutoSaveFile implementation.

Assisted-by: pi coding agent / Mika (OpenAI GPT-5.5)
This commit is contained in:
Gerhard Schwanzer
2026-07-05 13:16:19 +02:00
parent d0cea474a6
commit e6124e941a
8 changed files with 350 additions and 32 deletions
+2
View File
@@ -726,6 +726,8 @@ set(QET_SRC_FILES
if(NOT BUILD_WITH_KF5) if(NOT BUILD_WITH_KF5)
list(APPEND QET_SRC_FILES list(APPEND QET_SRC_FILES
${QET_DIR}/sources/ui/nokde/kautosavefile.cpp
${QET_DIR}/sources/ui/nokde/kautosavefile.h
${QET_DIR}/sources/ui/nokde/kcolorbutton.cpp ${QET_DIR}/sources/ui/nokde/kcolorbutton.cpp
${QET_DIR}/sources/ui/nokde/kcolorbutton.h ${QET_DIR}/sources/ui/nokde/kcolorbutton.h
${QET_DIR}/sources/ui/nokde/kcolorcombo.cpp ${QET_DIR}/sources/ui/nokde/kcolorcombo.cpp
+1 -4
View File
@@ -47,6 +47,7 @@
#include <QProcessEnvironment> #include <QProcessEnvironment>
#include <QRegularExpression> #include <QRegularExpression>
#ifdef BUILD_WITHOUT_KF5 #ifdef BUILD_WITHOUT_KF5
# include "ui/nokde/kautosavefile.h"
#else #else
# include <KAutoSaveFile> # include <KAutoSaveFile>
#endif #endif
@@ -2500,9 +2501,6 @@ void QETApp::buildSystemTrayMenu()
*/ */
void QETApp::checkBackupFiles() void QETApp::checkBackupFiles()
{ {
#ifdef BUILD_WITHOUT_KF5
return;
#else
QList<KAutoSaveFile *> stale_files = KAutoSaveFile::allStaleFiles(); QList<KAutoSaveFile *> stale_files = KAutoSaveFile::allStaleFiles();
//Remove from the list @stale_files, the stales file of opened project //Remove from the list @stale_files, the stales file of opened project
@@ -2577,7 +2575,6 @@ void QETApp::checkBackupFiles()
delete stale; delete stale;
} }
} }
#endif
} }
/** /**
+1 -3
View File
@@ -49,6 +49,7 @@
#include "ui/terminalnumberingdialog.h" #include "ui/terminalnumberingdialog.h"
#include <QDebug> #include <QDebug>
#ifdef BUILD_WITHOUT_KF5 #ifdef BUILD_WITHOUT_KF5
# include "ui/nokde/kautosavefile.h"
#else #else
# include <KAutoSaveFile> # include <KAutoSaveFile>
#endif #endif
@@ -1948,8 +1949,6 @@ bool QETDiagramEditor::drawGrid() const
return m_draw_grid->isChecked(); return m_draw_grid->isChecked();
} }
#ifdef BUILD_WITHOUT_KF5
#else
/** /**
@brief QETDiagramEditor::openBackupFiles @brief QETDiagramEditor::openBackupFiles
@param backup_files @param backup_files
@@ -1980,7 +1979,6 @@ void QETDiagramEditor::openBackupFiles(QList<KAutoSaveFile *> backup_files)
DialogWaiting::dropInstance(); DialogWaiting::dropInstance();
} }
} }
#endif
/** /**
met a jour le menu "Fenetres" met a jour le menu "Fenetres"
*/ */
-6
View File
@@ -44,10 +44,7 @@ class ElementsCollectionWidget;
class AutoNumberingDockWidget; class AutoNumberingDockWidget;
class TerminalNumberingDialog; class TerminalNumberingDialog;
#ifdef BUILD_WITHOUT_KF5
#else
class KAutoSaveFile; class KAutoSaveFile;
#endif
/** /**
This class represents the main window of the QElectroTech diagram editor and, This class represents the main window of the QElectroTech diagram editor and,
ipso facto, the most important part of the QElectroTech user interface. ipso facto, the most important part of the QElectroTech user interface.
@@ -72,10 +69,7 @@ class QETDiagramEditor : public QETMainWindow
ProjectView *currentProjectView() const; ProjectView *currentProjectView() const;
QETProject *currentProject() const; QETProject *currentProject() const;
bool drawGrid() const; bool drawGrid() const;
#ifdef BUILD_WITHOUT_KF5
#else
void openBackupFiles (QList<KAutoSaveFile *> backup_files); void openBackupFiles (QList<KAutoSaveFile *> backup_files);
#endif
protected: protected:
bool event(QEvent *) override; bool event(QEvent *) override;
-9
View File
@@ -93,8 +93,6 @@ QETProject::QETProject(const QString &path, QObject *parent) :
init(); init();
} }
#ifdef BUILD_WITHOUT_KF5
#else
/** /**
@brief QETProject::QETProject @brief QETProject::QETProject
@param backup : backup file to open, QETProject take ownership of backup. @param backup : backup file to open, QETProject take ownership of backup.
@@ -129,7 +127,6 @@ QETProject::QETProject(KAutoSaveFile *backup, QObject *parent) :
init(); init();
} }
#endif
/** /**
@brief QETProject::~QETProject @brief QETProject::~QETProject
@@ -342,15 +339,12 @@ void QETProject::setFilePath(const QString &filepath)
if (filepath == m_file_path) { if (filepath == m_file_path) {
return; return;
} }
#ifdef BUILD_WITHOUT_KF5
#else
//Don't close/re-point the backup file while a backup is still writing it. //Don't close/re-point the backup file while a backup is still writing it.
m_backup_future.waitForFinished(); m_backup_future.waitForFinished();
if (m_backup_file.isOpen()) { if (m_backup_file.isOpen()) {
m_backup_file.close(); m_backup_file.close();
} }
m_backup_file.setManagedFile(QUrl::fromLocalFile(filepath)); m_backup_file.setManagedFile(QUrl::fromLocalFile(filepath));
#endif
m_file_path = filepath; m_file_path = filepath;
QFileInfo fi(m_file_path); QFileInfo fi(m_file_path);
@@ -1813,8 +1807,6 @@ void QETProject::writeBackup()
{ {
if (!m_backup_enabled) if (!m_backup_enabled)
return; return;
#ifdef BUILD_WITHOUT_KF5
#else
# if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove # if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
//Don't launch a new backup while the previous one is still writing: //Don't launch a new backup while the previous one is still writing:
//both would write through &m_backup_file on different threads. //both would write through &m_backup_file on different threads.
@@ -1830,7 +1822,6 @@ void QETProject::writeBackup()
qDebug() << "Help code for QT 6 or later" qDebug() << "Help code for QT 6 or later"
<< "QtConcurrent::run its backwards now...function, object, args"; << "QtConcurrent::run its backwards now...function, object, args";
# endif # endif
#endif
} }
/** /**
+1 -10
View File
@@ -30,6 +30,7 @@
#include "titleblockproperties.h" #include "titleblockproperties.h"
#ifdef BUILD_WITHOUT_KF5 #ifdef BUILD_WITHOUT_KF5
# include "ui/nokde/kautosavefile.h"
#else #else
# include <KAutoSaveFile> # include <KAutoSaveFile>
#endif #endif
@@ -48,10 +49,6 @@ class XmlElementCollection;
class QTimer; class QTimer;
class TerminalStrip; class TerminalStrip;
#ifdef BUILD_WITHOUT_KF5
#else
class KAutoSaveFile;
#endif
/** /**
This class represents a QET project. Typically saved as a .qet file, it This class represents a QET project. Typically saved as a .qet file, it
@@ -79,10 +76,7 @@ class QETProject : public QObject
public: public:
QETProject (QObject *parent = nullptr); QETProject (QObject *parent = nullptr);
QETProject (const QString &path, QObject * = nullptr); QETProject (const QString &path, QObject * = nullptr);
#ifdef BUILD_WITHOUT_KF5
#else
QETProject (KAutoSaveFile *backup, QObject *parent=nullptr); QETProject (KAutoSaveFile *backup, QObject *parent=nullptr);
#endif
~QETProject() override; ~QETProject() override;
private: private:
@@ -297,10 +291,7 @@ class QETProject : public QObject
QTimer m_save_backup_timer, QTimer m_save_backup_timer,
m_autosave_timer; m_autosave_timer;
QFuture<bool> m_backup_future; QFuture<bool> m_backup_future;
#ifdef BUILD_WITHOUT_KF5
#else
KAutoSaveFile m_backup_file; KAutoSaveFile m_backup_file;
#endif
QUuid m_uuid = QUuid::createUuid(); QUuid m_uuid = QUuid::createUuid();
projectDataBase m_data_base; projectDataBase m_data_base;
QVector<TerminalStrip *> m_terminal_strip_vector; QVector<TerminalStrip *> m_terminal_strip_vector;
+284
View File
@@ -0,0 +1,284 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "kautosavefile.h"
#include <QCryptographicHash>
#include <QDir>
#include <QFileInfo>
#include <QLockFile>
#include <QRandomGenerator>
#include <QStandardPaths>
#include <QTextStream>
namespace {
const auto autosaveSuffix = QStringLiteral(".qetautosave");
QString staleFilesDir()
{
auto data_dir = QStandardPaths::writableLocation(
QStandardPaths::AppDataLocation);
while (data_dir.endsWith(QLatin1Char('/'))) {
data_dir.chop(1);
}
if (data_dir.isEmpty()) {
return {};
}
return data_dir + QDir::separator() + QStringLiteral("autosave");
}
QString metadataFileName(const QString &autosave_file_name)
{
return autosave_file_name + QStringLiteral(".path");
}
QUrl normalizeManagedFile(const QUrl &url)
{
if (url.isEmpty()) {
return {};
}
if (url.isLocalFile() || url.scheme().isEmpty()) {
const auto path = url.isLocalFile() ? url.toLocalFile() : url.path();
QUrl normalized;
normalized.setPath(QDir::cleanPath(QFileInfo(path).absoluteFilePath()));
return normalized;
}
return url;
}
QString storedManagedFile(const QUrl &url)
{
if (url.isLocalFile() || url.scheme().isEmpty()) {
return url.path();
}
return url.toString(QUrl::FullyEncoded);
}
QUrl managedFileFromStorage(const QString &stored_path)
{
if (!stored_path.startsWith(QLatin1Char('/'))) {
return QUrl(stored_path);
}
QUrl url;
url.setPath(QDir::cleanPath(stored_path));
return url;
}
bool writeManagedFileMetadata(const QString &autosave_file_name, const QUrl &managed_file)
{
QFile metadata_file(metadataFileName(autosave_file_name));
if (!metadata_file.open(QIODevice::WriteOnly
| QIODevice::Truncate
| QIODevice::Text)) {
return false;
}
QTextStream stream(&metadata_file);
stream << storedManagedFile(managed_file) << '\n';
stream.flush();
return metadata_file.error() == QFile::NoError;
}
QUrl readManagedFileMetadata(const QString &autosave_file_name)
{
QFile metadata_file(metadataFileName(autosave_file_name));
if (!metadata_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return {};
}
QTextStream stream(&metadata_file);
const auto stored_path = stream.readLine().trimmed();
if (stored_path.isEmpty()) {
return {};
}
return normalizeManagedFile(managedFileFromStorage(stored_path));
}
QString autosaveFileName(const QUrl &managed_file)
{
const auto stored_path = storedManagedFile(managed_file);
const auto digest = QCryptographicHash::hash(
stored_path.toUtf8(),
QCryptographicHash::Sha256).toHex().left(16);
auto basename = QFileInfo(managed_file.path()).fileName();
if (basename.isEmpty()) {
basename = QStringLiteral("autosave");
}
auto encoded_basename = QString::fromLatin1(
QUrl::toPercentEncoding(basename));
if (encoded_basename.size() > 80) {
encoded_basename.truncate(80);
}
const auto random = QString::number(
QRandomGenerator::global()->generate64(), 16);
return QStringLiteral("%1_%2_%3%4").arg(
encoded_basename,
QString::fromLatin1(digest),
random,
autosaveSuffix);
}
QStringList findAllStaleFiles(const QString &application_name)
{
Q_UNUSED(application_name)
const auto dir_path = staleFilesDir();
if (dir_path.isEmpty()) {
return {};
}
QDir dir(dir_path);
const auto entries = dir.entryList(
{QStringLiteral("*") + autosaveSuffix},
QDir::Files);
QStringList files;
for (const auto &entry : entries) {
files << dir.absoluteFilePath(entry);
}
return files;
}
} // namespace
KAutoSaveFile::KAutoSaveFile(const QUrl &filename, QObject *parent) :
QFile{parent}
{
setManagedFile(filename);
}
KAutoSaveFile::KAutoSaveFile(QObject *parent) :
QFile{parent}
{
}
KAutoSaveFile::~KAutoSaveFile()
{
releaseLock();
}
QUrl KAutoSaveFile::managedFile() const
{
return m_managed_file;
}
void KAutoSaveFile::setManagedFile(const QUrl &filename)
{
releaseLock();
m_managed_file = normalizeManagedFile(filename);
m_managed_file_name_changed = true;
setFileName({});
}
void KAutoSaveFile::releaseLock()
{
if (m_lock && m_lock->isLocked()) {
const auto autosave_file_name = fileName();
m_lock.reset();
if (!autosave_file_name.isEmpty()) {
QFile::remove(metadataFileName(autosave_file_name));
remove();
}
} else {
m_lock.reset();
}
}
bool KAutoSaveFile::open(OpenMode openmode)
{
if (m_managed_file.isEmpty()) {
return false;
}
if (m_managed_file_name_changed) {
const auto stale_dir = staleFilesDir();
if (stale_dir.isEmpty() || !QDir().mkpath(stale_dir)) {
return false;
}
setFileName(QDir(stale_dir).absoluteFilePath(
autosaveFileName(m_managed_file)));
if (!writeManagedFileMetadata(fileName(), m_managed_file)) {
setFileName({});
return false;
}
m_managed_file_name_changed = false;
}
if (!QFile::open(openmode)) {
return false;
}
if (!m_lock) {
m_lock = std::make_unique<QLockFile>(
fileName() + QStringLiteral(".lock"));
m_lock->setStaleLockTime(60 * 1000);
}
if (m_lock->isLocked() || m_lock->tryLock()) {
return true;
}
close();
return false;
}
QList<KAutoSaveFile *> KAutoSaveFile::staleFiles(
const QUrl &url,
const QString &applicationName)
{
const auto managed_file_filter = normalizeManagedFile(url);
QList<KAutoSaveFile *> stale_files;
for (const auto &file : findAllStaleFiles(applicationName)) {
const auto managed_file = readManagedFileMetadata(file);
if (managed_file.isEmpty()) {
continue;
}
if (!managed_file_filter.isEmpty()
&& managed_file != managed_file_filter) {
continue;
}
auto *stale_file = new KAutoSaveFile(managed_file);
stale_file->setFileName(file);
stale_file->m_managed_file_name_changed = false;
stale_files << stale_file;
}
return stale_files;
}
QList<KAutoSaveFile *> KAutoSaveFile::allStaleFiles(const QString &applicationName)
{
return staleFiles({}, applicationName);
}
+61
View File
@@ -0,0 +1,61 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QET_KAUTOSAVEFILE_H
#define QET_KAUTOSAVEFILE_H
#include <QFile>
#include <QList>
#include <QUrl>
#include <memory>
class QLockFile;
/**
Small Qt-only replacement for the KAutoSaveFile API used by QET.
It stores crash-recovery files below the application data autosave folder
and protects each recovery file with QLockFile. The original managed file
path is kept in a sidecar file so allStaleFiles() can present recoverable
projects on the next startup.
*/
class KAutoSaveFile : public QFile
{
public:
explicit KAutoSaveFile(const QUrl &filename, QObject *parent = nullptr);
explicit KAutoSaveFile(QObject *parent = nullptr);
~KAutoSaveFile() override;
QUrl managedFile() const;
void setManagedFile(const QUrl &filename);
virtual void releaseLock();
bool open(OpenMode openmode) override;
static QList<KAutoSaveFile *> staleFiles(
const QUrl &url,
const QString &applicationName = QString());
static QList<KAutoSaveFile *> allStaleFiles(
const QString &applicationName = QString());
private:
QUrl m_managed_file;
std::unique_ptr<QLockFile> m_lock;
bool m_managed_file_name_changed = false;
};
#endif // QET_KAUTOSAVEFILE_H