From e6124e941ade86bc9e9af659a18c708e82d2d5bf Mon Sep 17 00:00:00 2001 From: Gerhard Schwanzer Date: Sun, 5 Jul 2026 13:16:19 +0200 Subject: [PATCH] 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) --- cmake/qet_compilation_vars.cmake | 2 + sources/qetapp.cpp | 5 +- sources/qetdiagrameditor.cpp | 4 +- sources/qetdiagrameditor.h | 6 - sources/qetproject.cpp | 9 - sources/qetproject.h | 11 +- sources/ui/nokde/kautosavefile.cpp | 284 +++++++++++++++++++++++++++++ sources/ui/nokde/kautosavefile.h | 61 +++++++ 8 files changed, 350 insertions(+), 32 deletions(-) create mode 100644 sources/ui/nokde/kautosavefile.cpp create mode 100644 sources/ui/nokde/kautosavefile.h diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index 7c905d87b..3bb29d113 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -726,6 +726,8 @@ set(QET_SRC_FILES if(NOT BUILD_WITH_KF5) 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.h ${QET_DIR}/sources/ui/nokde/kcolorcombo.cpp diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index 88f6b2642..a19461e33 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -47,6 +47,7 @@ #include #include #ifdef BUILD_WITHOUT_KF5 +# include "ui/nokde/kautosavefile.h" #else # include #endif @@ -2500,9 +2501,6 @@ void QETApp::buildSystemTrayMenu() */ void QETApp::checkBackupFiles() { -#ifdef BUILD_WITHOUT_KF5 - return; -#else QList stale_files = KAutoSaveFile::allStaleFiles(); //Remove from the list @stale_files, the stales file of opened project @@ -2577,7 +2575,6 @@ void QETApp::checkBackupFiles() delete stale; } } -#endif } /** diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index 2b1d9607c..55fc73319 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -49,6 +49,7 @@ #include "ui/terminalnumberingdialog.h" #include #ifdef BUILD_WITHOUT_KF5 +# include "ui/nokde/kautosavefile.h" #else # include #endif @@ -1948,8 +1949,6 @@ bool QETDiagramEditor::drawGrid() const return m_draw_grid->isChecked(); } -#ifdef BUILD_WITHOUT_KF5 -#else /** @brief QETDiagramEditor::openBackupFiles @param backup_files @@ -1980,7 +1979,6 @@ void QETDiagramEditor::openBackupFiles(QList backup_files) DialogWaiting::dropInstance(); } } -#endif /** met a jour le menu "Fenetres" */ diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index 5a26b5f06..309e108e5 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -44,10 +44,7 @@ class ElementsCollectionWidget; class AutoNumberingDockWidget; class TerminalNumberingDialog; -#ifdef BUILD_WITHOUT_KF5 -#else class KAutoSaveFile; -#endif /** This class represents the main window of the QElectroTech diagram editor and, ipso facto, the most important part of the QElectroTech user interface. @@ -72,10 +69,7 @@ class QETDiagramEditor : public QETMainWindow ProjectView *currentProjectView() const; QETProject *currentProject() const; bool drawGrid() const; -#ifdef BUILD_WITHOUT_KF5 -#else void openBackupFiles (QList backup_files); -#endif protected: bool event(QEvent *) override; diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 64bead38f..86d884aad 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -93,8 +93,6 @@ QETProject::QETProject(const QString &path, QObject *parent) : init(); } -#ifdef BUILD_WITHOUT_KF5 -#else /** @brief QETProject::QETProject @param backup : backup file to open, QETProject take ownership of backup. @@ -129,7 +127,6 @@ QETProject::QETProject(KAutoSaveFile *backup, QObject *parent) : init(); } -#endif /** @brief QETProject::~QETProject @@ -342,15 +339,12 @@ void QETProject::setFilePath(const QString &filepath) if (filepath == m_file_path) { return; } -#ifdef BUILD_WITHOUT_KF5 -#else //Don't close/re-point the backup file while a backup is still writing it. m_backup_future.waitForFinished(); if (m_backup_file.isOpen()) { m_backup_file.close(); } m_backup_file.setManagedFile(QUrl::fromLocalFile(filepath)); -#endif m_file_path = filepath; QFileInfo fi(m_file_path); @@ -1813,8 +1807,6 @@ void QETProject::writeBackup() { if (!m_backup_enabled) return; -#ifdef BUILD_WITHOUT_KF5 -#else # 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: //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" << "QtConcurrent::run its backwards now...function, object, args"; # endif -#endif } /** diff --git a/sources/qetproject.h b/sources/qetproject.h index 0ed828953..5f13cf4a8 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -30,6 +30,7 @@ #include "titleblockproperties.h" #ifdef BUILD_WITHOUT_KF5 +# include "ui/nokde/kautosavefile.h" #else # include #endif @@ -48,10 +49,6 @@ class XmlElementCollection; class QTimer; class TerminalStrip; -#ifdef BUILD_WITHOUT_KF5 -#else -class KAutoSaveFile; -#endif /** This class represents a QET project. Typically saved as a .qet file, it @@ -79,10 +76,7 @@ class QETProject : public QObject public: QETProject (QObject *parent = nullptr); QETProject (const QString &path, QObject * = nullptr); -#ifdef BUILD_WITHOUT_KF5 -#else QETProject (KAutoSaveFile *backup, QObject *parent=nullptr); -#endif ~QETProject() override; private: @@ -297,10 +291,7 @@ class QETProject : public QObject QTimer m_save_backup_timer, m_autosave_timer; QFuture m_backup_future; -#ifdef BUILD_WITHOUT_KF5 -#else KAutoSaveFile m_backup_file; -#endif QUuid m_uuid = QUuid::createUuid(); projectDataBase m_data_base; QVector m_terminal_strip_vector; diff --git a/sources/ui/nokde/kautosavefile.cpp b/sources/ui/nokde/kautosavefile.cpp new file mode 100644 index 000000000..1e896e38f --- /dev/null +++ b/sources/ui/nokde/kautosavefile.cpp @@ -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 . +*/ +#include "kautosavefile.h" + +#include +#include +#include +#include +#include +#include +#include + +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( + fileName() + QStringLiteral(".lock")); + m_lock->setStaleLockTime(60 * 1000); + } + + if (m_lock->isLocked() || m_lock->tryLock()) { + return true; + } + + close(); + return false; +} + +QList KAutoSaveFile::staleFiles( + const QUrl &url, + const QString &applicationName) +{ + const auto managed_file_filter = normalizeManagedFile(url); + QList 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::allStaleFiles(const QString &applicationName) +{ + return staleFiles({}, applicationName); +} diff --git a/sources/ui/nokde/kautosavefile.h b/sources/ui/nokde/kautosavefile.h new file mode 100644 index 000000000..0389b730c --- /dev/null +++ b/sources/ui/nokde/kautosavefile.h @@ -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 . +*/ +#ifndef QET_KAUTOSAVEFILE_H +#define QET_KAUTOSAVEFILE_H + +#include +#include +#include + +#include + +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 staleFiles( + const QUrl &url, + const QString &applicationName = QString()); + static QList allStaleFiles( + const QString &applicationName = QString()); + + private: + QUrl m_managed_file; + std::unique_ptr m_lock; + bool m_managed_file_name_changed = false; +}; + +#endif // QET_KAUTOSAVEFILE_H