diff --git a/src/LicenseModel/LicenseModel.cpp b/src/LicenseModel/LicenseModel.cpp index 7f328df..8b98657 100644 --- a/src/LicenseModel/LicenseModel.cpp +++ b/src/LicenseModel/LicenseModel.cpp @@ -1,10 +1,14 @@ #include "LicenseModel.h" // Qt +#include +#include +#include +#include #include #include #include -#include +#include // Self #include @@ -14,10 +18,19 @@ const static int COLUMN_COUNT = 9; namespace { -quintptr licenseItemToInternalId(const LicenseModel::LicenseItem &item) +struct LoadResult { - return reinterpret_cast(&item); -} + QList data; + LicenseModel::Status status = LicenseModel::Status::Ok; + QString error; +}; + +struct DeleteResult +{ + QList ids; + LicenseModel::Status status = LicenseModel::Status::Ok; + QString error; +}; } LicenseModel::LicenseModel(QObject* parent) @@ -43,11 +56,13 @@ LicenseModel::LicenseModel(QObject* parent) m_status = Status::Ok; } } + + if (m_db.open()) + m_db.close(); } LicenseModel::~LicenseModel() { - m_db.close(); } int LicenseModel::rowCount(const QModelIndex &parent) const @@ -166,6 +181,8 @@ bool LicenseModel::checkTables() } if (!clientTableExist) return false; + else + return true; } bool LicenseModel::prepareDatabase() @@ -181,11 +198,11 @@ bool LicenseModel::prepareDatabase() { if (item.trimmed().isEmpty()) continue; - QSqlQuery query(item.trimmed() + ";", m_db); - if (!query.exec()) + QSqlQuery query(m_db); + if (!query.exec(item.trimmed())) { m_status = Status::DbStructError; - qDebug() << item.trimmed() + ";" << query.lastError().text(); + qDebug() << item.trimmed() << query.lastError().text(); m_errors.append(query.lastError().text()); return false; } @@ -196,28 +213,272 @@ bool LicenseModel::prepareDatabase() void LicenseModel::addClient(const LicenseItem &item) { - beginInsertRows({}, m_data.size() - 1, m_data.size() - 1); - m_data.append(item); - endInsertRows(); + m_status = Status::Working; + emit statusChanged(); + + // handler thread finish + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, this, [this, watcher]() { + const int row = m_data.size(); + beginInsertRows({}, row, row); + m_data.append(watcher->result()); + endInsertRows(); + + m_status = Status::Ok; + emit statusChanged(); + + watcher->deleteLater(); + }); + + // another thread lambda + watcher->setFuture(QtConcurrent::run([item]() { + const QString connectionName = QString("license_add_%1").arg(QUuid::createUuid().toString(QUuid::Id128)); + LicenseItem output; + bool ok = false; + { + auto db = QSqlDatabase::addDatabase("QSQLITE", connectionName); + db.setDatabaseName(DB_PATH); + if (!db.open()) + qDebug() << "db not open"; + else + { + QSqlQuery queryInsert(db); + queryInsert.prepare(QString( + "INSERT INTO clients(lastName, firstName, patronymic, phone, email, city, yourCompany, hardwareHash, comment) " + "VALUES (:lastName, :firstName, :patronymic, :phone, :email, :city, :yourCompany, :hardwareHash, :comment);" + )); + queryInsert.bindValue(":lastName", item.lastName); + queryInsert.bindValue(":firstName", item.firstName); + queryInsert.bindValue(":patronymic", item.patronymic); + queryInsert.bindValue(":phone", item.phone); + queryInsert.bindValue(":email", item.email); + queryInsert.bindValue(":city", item.city); + queryInsert.bindValue(":yourCompany", item.yourCompany); + queryInsert.bindValue(":hardwareHash", item.hardwareHash); + queryInsert.bindValue(":comment", item.comment); + + if (!queryInsert.exec()) + qDebug() << "queryInsert error" << queryInsert.lastError(); + else + { + QString itemId = QString::number(queryInsert.lastInsertId().toLongLong()); + + QSqlQuery querySelect(db); + querySelect.prepare(QString("SELECT * FROM clients WHERE id=:id;")); + querySelect.bindValue(":id", itemId); + + if (!querySelect.exec()) + qDebug() << "querySelect exec error" << querySelect.lastError(); + else if (!querySelect.next()) + qDebug() << "querySelect no rows for id" << itemId; + else + { + output.id = itemId; + output.lastName = querySelect.value("lastName").toString(); + output.firstName = querySelect.value("firstName").toString(); + output.patronymic = querySelect.value("patronymic").toString(); + output.phone = querySelect.value("phone").toString(); + output.email = querySelect.value("email").toString(); + output.city = querySelect.value("city").toString(); + output.yourCompany = querySelect.value("yourCompany").toString(); + output.hardwareHash = querySelect.value("hardwareHash").toString(); + output.comment = querySelect.value("comment").toString(); + ok = true; + } + } + } + } + QSqlDatabase::removeDatabase(connectionName); + if (!ok) + return LicenseItem(); + return output; + })); } -void LicenseModel::deleteClient(int index) +void LicenseModel::deleteClient(const QList &rows) { - beginRemoveRows({}, index, index); - m_data.removeAt(index); - endRemoveRows(); + m_status = Status::Working; + emit statusChanged(); + + QList ids; + ids.reserve(rows.size()); + for (const auto row : rows) + { + if (row < 0 || row >= m_data.size()) + continue; + const QString id = m_data[row].id; + if (!id.isEmpty()) + ids.append(id); + } + + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, this, [this, watcher]() { + const DeleteResult result = watcher->result(); + if (result.status != Status::Ok) + { + m_status = result.status; + if (!result.error.isEmpty()) + m_errors.append(result.error); + emit statusChanged(); + watcher->deleteLater(); + return; + } + + QList indices; + indices.reserve(result.ids.size()); + for (const auto &id : result.ids) + { + for (int i = 0; i < m_data.size(); ++i) + { + if (m_data[i].id == id) + { + indices.append(i); + break; + } + } + } + + std::sort(indices.begin(), indices.end(), std::greater()); + for (const auto row : indices) + { + if (row < 0 || row >= m_data.size()) + continue; + beginRemoveRows({}, row, row); + m_data.removeAt(row); + endRemoveRows(); + } + + m_status = Status::Ok; + emit statusChanged(); + watcher->deleteLater(); + }); + + watcher->setFuture(QtConcurrent::run([ids]() { + DeleteResult result; + result.ids = ids; + if (ids.isEmpty()) + return result; + + const QString connectionName = QString("license_delete_%1") + .arg(QUuid::createUuid().toString(QUuid::Id128)); + { + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connectionName); + db.setDatabaseName(DB_PATH); + if (!db.open()) + { + result.status = LicenseModel::Status::DbExistError; + result.error = "Database connection failed: " + db.lastError().text(); + } + else + { + QStringList placeholders; + placeholders.reserve(ids.size()); + for (int i = 0; i < ids.size(); ++i) + placeholders << "?"; + + QSqlQuery query(db); + query.prepare(QString("DELETE FROM clients WHERE id IN (%1);") + .arg(placeholders.join(','))); + for (const auto &id : ids) + query.addBindValue(id); + + if (!query.exec()) + { + result.status = LicenseModel::Status::DbStructError; + result.error = query.lastError().text(); + } + } + } + QSqlDatabase::removeDatabase(connectionName); + return result; + })); } void LicenseModel::editClient(const LicenseItem &item, int index) { + m_status = Status::Working; + emit statusChanged(); + m_data[index] = item; - dataChanged(this->index(index, 0), this->index(index, COLUMN_COUNT)); + dataChanged(this->index(index, 0), this->index(index, COLUMN_COUNT - 1)); } void LicenseModel::updateModel() { - beginResetModel(); - endResetModel(); + m_status = Status::Working; + emit statusChanged(); + + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, this, [this, watcher]() { + const LoadResult result = watcher->result(); + if (result.status != Status::Ok) + { + m_status = result.status; + if (!result.error.isEmpty()) + m_errors.append(result.error); + emit statusChanged(); + watcher->deleteLater(); + return; + } + + beginResetModel(); + m_data = result.data; + endResetModel(); + + m_status = Status::Ok; + emit statusChanged(); + watcher->deleteLater(); + }); + + watcher->setFuture(QtConcurrent::run([]() { + LoadResult result; + const QString connectionName = QString("license_load_%1") + .arg(QUuid::createUuid().toString(QUuid::Id128)); + { + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connectionName); + db.setDatabaseName(DB_PATH); + if (!db.open()) + { + result.status = LicenseModel::Status::DbExistError; + result.error = "Database connection failed: " + db.lastError().text(); + } + else + { + QSqlQuery query(db); + if (!query.exec( + "SELECT id, lastName, firstName, patronymic, phone, email, city, yourCompany, hardwareHash, comment " + "FROM clients ORDER BY id DESC;" + )) + { + result.status = LicenseModel::Status::DbStructError; + result.error = query.lastError().text(); + } + else + { + const int createdAtUtcIndex = query.record().indexOf("createdAtUtc"); + while (query.next()) + { + LicenseItem item; + item.id = query.value("id").toString(); + item.lastName = query.value("lastName").toString(); + item.firstName = query.value("firstName").toString(); + item.patronymic = query.value("patronymic").toString(); + item.phone = query.value("phone").toString(); + item.email = query.value("email").toString(); + item.city = query.value("city").toString(); + item.yourCompany = query.value("yourCompany").toString(); + item.hardwareHash = query.value("hardwareHash").toString(); + item.comment = query.value("comment").toString(); + if (createdAtUtcIndex >= 0) + item.createdAtUtc = query.value(createdAtUtcIndex).toString(); + result.data.append(item); + } + } + } + } + QSqlDatabase::removeDatabase(connectionName); + return result; + })); } LicenseModel::LicenseItem LicenseModel::getItem(int index) const diff --git a/src/LicenseModel/LicenseModel.h b/src/LicenseModel/LicenseModel.h index 4a157d6..73cda99 100644 --- a/src/LicenseModel/LicenseModel.h +++ b/src/LicenseModel/LicenseModel.h @@ -3,6 +3,7 @@ // Qt #include +#include #include class LicenseModel : public QAbstractItemModel @@ -16,6 +17,7 @@ public: Ok, DbStructError, DbExistError, + Working, }; struct LicenseItem @@ -46,10 +48,13 @@ public: QString getStatusText(); bool prepareDatabase(); void addClient(const LicenseItem &item); - void deleteClient(int index); + void deleteClient(const QList &rows); void editClient(const LicenseItem &item, int index); + void updateModel(); LicenseItem getItem(int index) const; +signals: + void statusChanged(); private: bool checkTables(); private: diff --git a/src/MainWidget/MainWidget.cpp b/src/MainWidget/MainWidget.cpp index a5d5533..f9281d1 100644 --- a/src/MainWidget/MainWidget.cpp +++ b/src/MainWidget/MainWidget.cpp @@ -9,11 +9,11 @@ #include #include #include +#include #include #include #include #include -#include // Self #include "../def.h" @@ -29,7 +29,16 @@ MainWidget::MainWidget(QWidget *parent) setWindowIcon(QIcon(QStringLiteral("qrc:/deps/icon.png"))); setWindowTitle("LicenseManager"); setMinimumSize({800, 600}); - resize({800, 600}); + resize(minimumSize()); + + // Status bar + { + m_statusBar = new QStatusBar(this); + setStatusBar(m_statusBar); + m_statusLabel = new QLabel("No status", this); + m_statusBar->addWidget(m_statusLabel); + connect(m_licenseModel, &LicenseModel::statusChanged, this, &MainWidget::modelStatusChanged); + } // Model init { @@ -100,6 +109,7 @@ MainWidget::MainWidget(QWidget *parent) m_tableView->resizeColumnsToContents(); selectionChanged({}, {}); + modelStatusChanged(); loadSettings(); } @@ -170,8 +180,11 @@ void MainWidget::onDeleteClientTriggered() if (selectedRows.isEmpty()) return; - for (auto i = selectedRows.size() - 1; i >= 0; i--) - m_licenseModel->deleteClient(selectedRows[i].row()); + QList list; + for (auto i = selectedRows.size() - 1; i >= 0; --i) + list << selectedRows.at(i).row(); + + m_licenseModel->deleteClient(list); } void MainWidget::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) @@ -231,3 +244,31 @@ void MainWidget::onEditClientDialogClosed(int result) break; } } + +void MainWidget::modelStatusChanged() +{ + switch (m_licenseModel->getStatus()) + { + case LicenseModel::Status::Ok: + m_statusLabel->setText(tr("Database ready")); + break; + case LicenseModel::Status::DbExistError: + m_statusLabel->setText(tr("Database exist error")); + break; + case LicenseModel::Status::DbStructError: + m_statusLabel->setText(tr("Database struct error")); + break; + case LicenseModel::Status::Working: + m_statusLabel->setText(tr("Working")); + break; + default: + m_statusLabel->setText(tr("Unknown status")); + break; + } + + m_addClientToolBarAction->setEnabled(m_licenseModel->getStatus() == LicenseModel::Status::Ok); + m_addClientsMenuAction->setEnabled(m_licenseModel->getStatus() == LicenseModel::Status::Ok); + m_editClientsMenuAction->setEnabled(m_licenseModel->getStatus() == LicenseModel::Status::Ok); + m_deleteClientsMenuAction->setEnabled(m_licenseModel->getStatus() == LicenseModel::Status::Ok); +} + diff --git a/src/MainWidget/MainWidget.h b/src/MainWidget/MainWidget.h index de75cc0..f68298f 100644 --- a/src/MainWidget/MainWidget.h +++ b/src/MainWidget/MainWidget.h @@ -4,13 +4,15 @@ // Qt #include #include -class QTableView; -class QMenu; class QAction; +class QLabel; +class QMenu; +class QStatusBar; +class QTableView; // Self +#include "LicenseModel/LicenseModel.h" class EditClientDialog; -class LicenseModel; class MainWidget : public QMainWindow { @@ -31,7 +33,7 @@ private slots: void onDeleteClientTriggered(); void onEditClientDialogClosed(int result); void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); - + void modelStatusChanged(); private: void saveSettings(); void loadSettings(); @@ -48,6 +50,8 @@ private: QAction* m_deleteClientsMenuAction{nullptr}; QAction* m_editClientsMenuAction{nullptr}; EditClientDialog* m_editClientDialog{nullptr}; + QStatusBar* m_statusBar{nullptr}; + QLabel* m_statusLabel{nullptr}; }; #endif // LICENSEMANAGER_MAINWIDGET_H