1
0
forked from BRT/arc

Add support for database wipe on exit, including delayed deletion handling and user-triggered wipe option in GUI. Update DatabaseManager with sentinel logic and file path utilities.

This commit is contained in:
slava 2026-05-30 12:29:23 +05:00
parent 709c7d0490
commit 992073d43d
4 changed files with 78 additions and 6 deletions

View File

@ -33,6 +33,30 @@ DatabaseManager &DatabaseManager::instance() {
return instance;
}
QString DatabaseManager::dbFilePath() {
const QSettings settings("config.ini", QSettings::IniFormat);
QString dbPath = settings.value("db/dbPath").toString();
if (dbPath.startsWith("~")) {
dbPath.replace(0, 1, QDir::homePath());
}
return dbPath;
}
void DatabaseManager::wipeDatabaseFiles() {
const QString dbPath = dbFilePath();
QFile::remove(dbPath);
QFile::remove(dbPath + "-wal");
QFile::remove(dbPath + "-shm");
QFile::remove(dbPath + ".wipe");
}
void DatabaseManager::writeWipeSentinel() {
QFile marker(dbFilePath() + ".wipe");
if (marker.open(QIODevice::WriteOnly)) {
marker.close();
}
}
QSqlDatabase DatabaseManager::database() {
QMutexLocker<QRecursiveMutex> locker(&mutex);
QString name = connectionName();
@ -40,12 +64,8 @@ QSqlDatabase DatabaseManager::database() {
// Создаем новое соединение, если его ещё нет
if (!connectionPool.contains(name)) {
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", name);
const QSettings settings("config.ini", QSettings::IniFormat);
QString dbPath = settings.value("db/dbPath").toString();
if (dbPath.startsWith("~")) {
dbPath.replace(0, 1, QDir::homePath());
}
const QString dbPath = dbFilePath();
// Создаём директорию для БД, если её нет
QDir().mkpath(QFileInfo(dbPath).absolutePath());

View File

@ -5,6 +5,7 @@
#include <QMap>
#include <QMutex>
#include <QSet>
#include <atomic>
class DatabaseManager {
public:
@ -15,6 +16,17 @@ public:
void cleanupOldData(int days = 5);
void closeConnection();
// Путь к файлу БД из config.ini (с раскрытием ~). Единая точка вычисления.
static QString dbFilePath();
// Удалить файл БД вместе с WAL/SHM-сайдкарами и sentinel-файлом.
static void wipeDatabaseFiles();
// Создать sentinel <dbPath>.wipe — отложенное удаление БД при следующем старте.
static void writeWipeSentinel();
// Флаг «удалить БД при выходе», выставляется из GUI-потока.
void setWipeOnExit(bool wipe) { m_wipeOnExit.store(wipe); }
bool wipeOnExit() const { return m_wipeOnExit.load(); }
private:
DatabaseManager();
~DatabaseManager();
@ -24,4 +36,5 @@ private:
QRecursiveMutex mutex;
QSet<QString> activeConnections;
QMap<QString, QSqlDatabase> connectionPool;
std::atomic<bool> m_wipeOnExit{false};
};

View File

@ -350,6 +350,13 @@ int main(int argc, char *argv[]) {
QDir().mkpath(QFileInfo(dbPath).absolutePath());
}
// Отложенное удаление БД (если файл был занят при прошлом выходе, напр. на Windows).
// Делаем здесь, до открытия любого соединения.
if (QFile::exists(DatabaseManager::dbFilePath() + ".wipe")) {
DatabaseManager::wipeDatabaseFiles();
qInfo() << "[main] Локальная БД удалена по запросу пользователя";
}
// Миграции БД
DatabaseManager::instance().initSchema();
DatabaseManager::instance().cleanupOldData(30);
@ -444,5 +451,21 @@ int main(int argc, char *argv[]) {
loader->close();
}, Qt::QueuedConnection);
}
return QApplication::exec();
const int rc = QApplication::exec();
// Пользователь выбрал «Выйти» с удалением БД. Потоки уже остановлены через
// aboutToQuit, их соединения сняты. Пробуем удалить файл сразу; если он занят
// (Windows: незавершившийся поток) — оставляем sentinel, удалим при старте.
if (DatabaseManager::instance().wipeOnExit()) {
dbReady.store(false); // прекращаем дозапись логов в БД
DatabaseManager::instance().closeConnection();
if (QFile::remove(DatabaseManager::dbFilePath())) {
DatabaseManager::wipeDatabaseFiles(); // подчистить -wal/-shm/sentinel
qInfo() << "[main] Локальная БД удалена при выходе";
} else {
DatabaseManager::writeWipeSentinel();
qInfo() << "[main] Файл БД занят — удаление отложено до следующего старта";
}
}
return rc;
}

View File

@ -22,6 +22,7 @@
#include "db/DeviceInfo.h"
#include "device/DeviceWidget.h"
#include "dao/BankProfileDAO.h"
#include "DatabaseManager.h"
#include "dao/DeviceDAO.h"
#include "dao/MaterialDAO.h"
#include "dao/SettingsDAO.h"
@ -63,11 +64,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
auto *tutorialPageAction = new QAction("Инструкция", this);
auto *eventAction = new QAction("События", this);
auto *fileLogAction = new QAction("Логи", this);
auto *exitAction = new QAction("Выйти", this);
menu->addAction(devicesPageAction);
// menu->addAction(accountPageAction);
menu->addAction(eventAction);
menu->addAction(tutorialPageAction);
menu->addAction(fileLogAction);
menu->addAction(exitAction);
connect(devicesPageAction, &QAction::triggered, this, [=]() {
if (!m_devicesWindow) {
@ -114,6 +117,19 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
}
});
connect(exitAction, &QAction::triggered, this, [this]() {
QMessageBox msgBox(this);
msgBox.setWindowTitle("Выход");
msgBox.setText("Удалить локальную базу данных и выйти из приложения?");
auto *yesBtn = msgBox.addButton("Выйти", QMessageBox::YesRole);
msgBox.addButton("Отмена", QMessageBox::NoRole);
msgBox.exec();
if (msgBox.clickedButton() == yesBtn) {
DatabaseManager::instance().setWipeOnExit(true);
qApp->quit(); // штатное завершение фоновых потоков (aboutToQuit)
}
});
if (SettingsDAO::get("tutorial_shown").isEmpty()) {
QTimer::singleShot(0, this, [this]() {
m_tutorialWindow = new TutorialWindow(this);