Browse Source

chatgui settings: add Database tab with integrity check (quick/full) and optimize

topo_upd
Evgeny 3 months ago
parent
commit
c4d1f984bd
  1. 1
      tools/chatgui/CMakeLists.txt
  2. 50
      tools/chatgui/db/db_manager.cpp
  3. 11
      tools/chatgui/db/db_manager.h
  4. 204
      tools/chatgui/src/databasesettingspage.cpp
  5. 34
      tools/chatgui/src/databasesettingspage.h
  6. 2
      tools/chatgui/src/mainwindow.cpp
  7. 11
      tools/chatgui/src/settingsdialog.cpp
  8. 19
      tools/chatgui/src/settingsdialog.h

1
tools/chatgui/CMakeLists.txt

@ -68,6 +68,7 @@ add_executable(chatgui
src/joindialog.cpp
src/settingsdialog.cpp
src/networksettingspage.cpp
src/databasesettingspage.cpp
transport/utun_node.cpp
transport/node_config.cpp
transport/config_updater.cpp

50
tools/chatgui/db/db_manager.cpp

@ -4,6 +4,7 @@
#include <openssl/evp.h>
#include <cstring>
#include <QSet>
#include <QElapsedTimer>
/* ── helpers ── */
@ -443,4 +444,51 @@ QByteArray DbManager::syncListPeers(const QString& chId) const {
return out;
}
/* ── syncListPeers ── */
/* ── integrity checks ── */
static int integrityCallback(void* data, int nCols, char** values, char** /*names*/) {
auto* result = (IntegrityResult*)data;
for (int i = 0; i < nCols; i++) {
if (values[i]) {
QString line = QString::fromUtf8(values[i]);
if (result->message.isEmpty()) result->message = line;
else if (line != "ok") result->errors.append(line);
}
}
return 0;
}
static IntegrityResult runPragma(DbManager* self, const char* pragma) {
IntegrityResult result;
if (!self->m_db) {
result.ok = false;
result.message = "Database not open";
return result;
}
QElapsedTimer timer;
timer.start();
char* errMsg = nullptr;
int rc = sqlite3_exec(self->m_db, pragma, integrityCallback, &result, &errMsg);
result.elapsedMs = (int)timer.elapsed();
if (rc != SQLITE_OK) {
result.ok = false;
result.message = QString::fromUtf8(errMsg ? errMsg : "unknown error");
sqlite3_free(errMsg);
return result;
}
if (errMsg) sqlite3_free(errMsg);
result.ok = (result.message == "ok" && result.errors.isEmpty());
return result;
}
IntegrityResult DbManager::integrityCheckQuick() const {
return runPragma(const_cast<DbManager*>(this), "PRAGMA quick_check");
}
IntegrityResult DbManager::integrityCheckFull() const {
return runPragma(const_cast<DbManager*>(this), "PRAGMA integrity_check");
}
IntegrityResult DbManager::integrityOptimize() const {
return runPragma(const_cast<DbManager*>(this), "PRAGMA optimize");
}

11
tools/chatgui/db/db_manager.h

@ -62,6 +62,13 @@ struct NodeAddr {
bool isNat = false;
};
struct IntegrityResult {
bool ok = false;
QString message;
QStringList errors;
int elapsedMs = 0;
};
class DbManager : public QObject {
Q_OBJECT
public:
@ -119,6 +126,10 @@ public:
qint64 timestamp, uint64_t datahash);
QByteArray getLastChainHash(const QString& chId) const;
IntegrityResult integrityCheckQuick() const;
IntegrityResult integrityCheckFull() const;
IntegrityResult integrityOptimize() const;
sqlite3* m_db = nullptr;
quint64 m_myNodeId = 0;

204
tools/chatgui/src/databasesettingspage.cpp

@ -0,0 +1,204 @@
// databasesettingspage.cpp — Database maintenance page in settings
#include "databasesettingspage.h"
#include "../db/db_manager.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QLabel>
#include <QFrame>
#include <QFileInfo>
#include <QTimer>
DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
: QWidget(parent)
, m_db(db)
{
auto* layout = new QVBoxLayout(this);
layout->setSpacing(10);
layout->setContentsMargins(0, 0, 0, 0);
auto* title = new QLabel("Database Maintenance", this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(title);
// --- DB info ---
auto* infoLayout = new QFormLayout();
infoLayout->setSpacing(4);
QString dbPath = "—";
QString dbSize = "—";
QString walSize = "—";
if (m_db && m_db->m_db) {
const char* fname = sqlite3_db_filename(m_db->m_db, "main");
if (fname && fname[0]) {
dbPath = QString::fromUtf8(fname);
QFileInfo fi(dbPath);
if (fi.exists()) {
qint64 sz = fi.size();
if (sz < 1024) dbSize = QString("%1 B").arg(sz);
else if (sz < 1024 * 1024) dbSize = QString("%1 KB").arg(sz / 1024);
else dbSize = QString("%1 MB").arg(sz / (1024.0 * 1024.0), 0, 'f', 2);
}
QFileInfo walFi(dbPath + "-wal");
if (walFi.exists()) {
qint64 wsz = walFi.size();
if (wsz < 1024) walSize = QString("%1 B").arg(wsz);
else if (wsz < 1024 * 1024) walSize = QString("%1 KB").arg(wsz / 1024);
else walSize = QString("%1 MB").arg(wsz / (1024.0 * 1024.0), 0, 'f', 2);
} else {
walSize = "n/a";
}
}
}
m_filePathLabel = new QLabel(dbPath, this);
m_filePathLabel->setStyleSheet("font-size: 11px; color: gray;");
m_filePathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
infoLayout->addRow("File:", m_filePathLabel);
m_fileSizeLabel = new QLabel(dbSize, this);
m_fileSizeLabel->setStyleSheet("font-size: 11px;");
infoLayout->addRow("Size:", m_fileSizeLabel);
m_walSizeLabel = new QLabel(walSize, this);
m_walSizeLabel->setStyleSheet("font-size: 11px;");
infoLayout->addRow("WAL:", m_walSizeLabel);
layout->addLayout(infoLayout);
// --- Separator ---
auto* sep = new QFrame(this);
sep->setFrameShape(QFrame::HLine);
sep->setFrameShadow(QFrame::Sunken);
layout->addWidget(sep);
// --- Buttons ---
auto* btnLayout = new QVBoxLayout();
btnLayout->setSpacing(6);
auto* quickRow = new QHBoxLayout();
m_quickBtn = new QPushButton("Quick Check", this);
m_quickBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 5px 14px; font-size: 12px; background: palette(button); }"
"QPushButton:hover { background: palette(light); }");
connect(m_quickBtn, &QPushButton::clicked, this, &DatabaseSettingsPage::onQuickCheck);
quickRow->addWidget(m_quickBtn);
auto* quickDesc = new QLabel("Scans structure without verifying indexes (fast)", this);
quickDesc->setStyleSheet("color: gray; font-size: 11px;");
quickRow->addWidget(quickDesc, 1);
btnLayout->addLayout(quickRow);
auto* fullRow = new QHBoxLayout();
m_fullBtn = new QPushButton("Full Check", this);
m_fullBtn->setStyleSheet(m_quickBtn->styleSheet());
connect(m_fullBtn, &QPushButton::clicked, this, &DatabaseSettingsPage::onFullCheck);
fullRow->addWidget(m_fullBtn);
auto* fullDesc = new QLabel("Verifies all data structures and indexes (thorough)", this);
fullDesc->setStyleSheet("color: gray; font-size: 11px;");
fullRow->addWidget(fullDesc, 1);
btnLayout->addLayout(fullRow);
auto* optRow = new QHBoxLayout();
m_optimizeBtn = new QPushButton("Optimize", this);
m_optimizeBtn->setStyleSheet(m_quickBtn->styleSheet());
connect(m_optimizeBtn, &QPushButton::clicked, this, &DatabaseSettingsPage::onOptimize);
optRow->addWidget(m_optimizeBtn);
auto* optDesc = new QLabel("Update query planner statistics for better performance", this);
optDesc->setStyleSheet("color: gray; font-size: 11px;");
optRow->addWidget(optDesc, 1);
btnLayout->addLayout(optRow);
layout->addLayout(btnLayout);
// --- Status ---
m_statusLabel = new QLabel("", this);
m_statusLabel->setStyleSheet("font-size: 12px; margin-top: 4px;");
layout->addWidget(m_statusLabel);
// --- Results ---
m_resultText = new QPlainTextEdit(this);
m_resultText->setReadOnly(true);
m_resultText->setMaximumHeight(120);
m_resultText->setStyleSheet(
"QPlainTextEdit { font-family: monospace; font-size: 11px;"
" background: palette(base); border: 1px solid palette(mid); border-radius: 4px;"
" padding: 4px; }");
m_resultText->setPlaceholderText("Run a check to see results...");
layout->addWidget(m_resultText);
layout->addStretch();
}
void DatabaseSettingsPage::setBusy(bool busy) {
m_quickBtn->setEnabled(!busy);
m_fullBtn->setEnabled(!busy);
m_optimizeBtn->setEnabled(!busy);
if (busy) {
m_statusLabel->setText("Checking...");
m_statusLabel->setStyleSheet("font-size: 12px; margin-top: 4px; color: gray;");
}
}
void DatabaseSettingsPage::onQuickCheck() {
if (!m_db || !m_db->m_db) {
m_statusLabel->setText("Database not open");
m_statusLabel->setStyleSheet("font-size: 12px; color: red;");
return;
}
setBusy(true);
QTimer::singleShot(50, this, [this]() {
IntegrityResult r = m_db->integrityCheckQuick();
showResult("Quick Check", r);
});
}
void DatabaseSettingsPage::onFullCheck() {
if (!m_db || !m_db->m_db) {
m_statusLabel->setText("Database not open");
m_statusLabel->setStyleSheet("font-size: 12px; color: red;");
return;
}
setBusy(true);
QTimer::singleShot(50, this, [this]() {
IntegrityResult r = m_db->integrityCheckFull();
showResult("Full Check", r);
});
}
void DatabaseSettingsPage::onOptimize() {
if (!m_db || !m_db->m_db) {
m_statusLabel->setText("Database not open");
m_statusLabel->setStyleSheet("font-size: 12px; color: red;");
return;
}
setBusy(true);
QTimer::singleShot(50, this, [this]() {
IntegrityResult r = m_db->integrityOptimize();
showResult("Optimize", r);
});
}
void DatabaseSettingsPage::showResult(const QString& checkName, const IntegrityResult& result) {
m_quickBtn->setEnabled(true);
m_fullBtn->setEnabled(true);
m_optimizeBtn->setEnabled(true);
if (result.ok) {
m_statusLabel->setText(QString("%1: OK (%2 ms)")
.arg(checkName).arg(result.elapsedMs));
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: green;");
} else {
m_statusLabel->setText(QString("%1: FAILED (%2 ms)")
.arg(checkName).arg(result.elapsedMs));
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: red;");
}
if (result.errors.isEmpty()) {
m_resultText->setPlainText(result.message.isEmpty() ? "ok" : result.message);
} else {
QStringList lines;
lines.append(result.message);
lines.append(result.errors);
m_resultText->setPlainText(lines.join('\n'));
}
}

34
tools/chatgui/src/databasesettingspage.h

@ -0,0 +1,34 @@
// databasesettingspage.h — Database maintenance page in settings
#pragma once
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QPlainTextEdit>
class DbManager;
class DatabaseSettingsPage : public QWidget {
Q_OBJECT
public:
explicit DatabaseSettingsPage(DbManager* db, QWidget* parent = nullptr);
private slots:
void onQuickCheck();
void onFullCheck();
void onOptimize();
private:
void showResult(const QString& checkName, const struct IntegrityResult& result);
void setBusy(bool busy);
DbManager* m_db;
QLabel* m_filePathLabel;
QLabel* m_fileSizeLabel;
QLabel* m_walSizeLabel;
QPushButton* m_quickBtn;
QPushButton* m_fullBtn;
QPushButton* m_optimizeBtn;
QLabel* m_statusLabel;
QPlainTextEdit* m_resultText;
};

2
tools/chatgui/src/mainwindow.cpp

@ -260,7 +260,7 @@ void MainWindow::showJoinDialog() {
}
void MainWindow::showSettings() {
SettingsDialog dlg(m_cfgPath, this);
SettingsDialog dlg(m_cfgPath, m_db, this);
dlg.exec();
}

11
tools/chatgui/src/settingsdialog.cpp

@ -1,8 +1,10 @@
// settingsdialog.cpp — Settings dialog with category list
#include "settingsdialog.h"
#include "networksettingspage.h"
#include "databasesettingspage.h"
#include "../transport/gui_bridge.h"
#include "../../lib/mem.h"
#include "../db/db_manager.h"
extern "C" {
#include "../transport/chat_core.h"
}
@ -15,12 +17,12 @@ extern "C" {
#include <QFile>
#include <QTextStream>
SettingsDialog::SettingsDialog(const QString& configPath, QWidget* parent)
SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, QWidget* parent)
: QDialog(parent)
, m_configPath(configPath)
{
setWindowTitle("Settings");
setMinimumSize(560, 380);
setMinimumSize(560, 460);
setModal(true);
auto* mainLayout = new QHBoxLayout(this);
@ -37,6 +39,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, QWidget* parent)
"QListWidget::item:selected { background: #3390EC; color: white; }");
m_categoryList->addItem("Profile");
m_categoryList->addItem("Network");
m_categoryList->addItem("Database");
mainLayout->addWidget(m_categoryList);
// --- Right panel: stacked pages ---
@ -64,6 +67,10 @@ SettingsDialog::SettingsDialog(const QString& configPath, QWidget* parent)
// Network page
m_networkPage = new NetworkSettingsPage(m_pages);
m_pages->addWidget(m_networkPage);
// Database page
m_databasePage = new DatabaseSettingsPage(db, m_pages);
m_pages->addWidget(m_databasePage);
rightLayout->addWidget(m_pages, 1);
// --- Bottom buttons ---

19
tools/chatgui/src/settingsdialog.h

@ -9,11 +9,13 @@
#include <QString>
class NetworkSettingsPage;
class DatabaseSettingsPage;
class DbManager;
class SettingsDialog : public QDialog {
Q_OBJECT
public:
explicit SettingsDialog(const QString& configPath, QWidget* parent = nullptr);
explicit SettingsDialog(const QString& configPath, DbManager* db, QWidget* parent = nullptr);
private slots:
void onCategoryChanged(int row);
@ -22,11 +24,12 @@ private slots:
private:
void loadProfileFromConfig(const QString& path);
QString m_configPath;
QListWidget* m_categoryList;
QStackedWidget* m_pages;
QPushButton* m_saveBtn;
QLineEdit* m_nicknameEdit;
QString m_oldNickname;
NetworkSettingsPage* m_networkPage;
QString m_configPath;
QListWidget* m_categoryList;
QStackedWidget* m_pages;
QPushButton* m_saveBtn;
QLineEdit* m_nicknameEdit;
QString m_oldNickname;
NetworkSettingsPage* m_networkPage;
DatabaseSettingsPage* m_databasePage;
};

Loading…
Cancel
Save