From 167ae2af5051d058b24d7b4370d4362f812b54e3 Mon Sep 17 00:00:00 2001 From: nowrep Date: Sat, 15 Mar 2014 01:03:06 +0100 Subject: [PATCH] [SqlDatabase] New class allowing to exec Sql queries in separate thread It uses its own QSqlDatabase connection, which is supported according to docs. --- src/lib/app/mainapplication.cpp | 2 + src/lib/app/profilemanager.cpp | 2 +- src/lib/lib.pro | 6 +- src/lib/tools/sqldatabase.cpp | 187 ++++++++++++++++++++++++++++++++ src/lib/tools/sqldatabase.h | 101 +++++++++++++++++ 5 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 src/lib/tools/sqldatabase.cpp create mode 100644 src/lib/tools/sqldatabase.h diff --git a/src/lib/app/mainapplication.cpp b/src/lib/app/mainapplication.cpp index 0033309a0..3f41ea703 100644 --- a/src/lib/app/mainapplication.cpp +++ b/src/lib/app/mainapplication.cpp @@ -32,6 +32,7 @@ #include "rssmanager.h" #include "proxystyle.h" #include "pluginproxy.h" +#include "sqldatabase.h" #include "iconprovider.h" #include "browserwindow.h" #include "cookiemanager.h" @@ -271,6 +272,7 @@ MainApplication::~MainApplication() delete m_bookmarks; delete m_cookieJar; + SqlDatabase::destroy(); IconProvider::instance()->saveIconsToDatabase(); } diff --git a/src/lib/app/profilemanager.cpp b/src/lib/app/profilemanager.cpp index a61780774..3499b3f74 100644 --- a/src/lib/app/profilemanager.cpp +++ b/src/lib/app/profilemanager.cpp @@ -250,7 +250,7 @@ void ProfileManager::connectDatabase() // Reconnect if (m_databaseConnected) { - QSqlDatabase::removeDatabase(QLatin1String("qt_sql_default_connection")); + QSqlDatabase::removeDatabase(QSqlDatabase::database().connectionName()); } QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE")); diff --git a/src/lib/lib.pro b/src/lib/lib.pro index 7d33c56d6..9fcb89952 100644 --- a/src/lib/lib.pro +++ b/src/lib/lib.pro @@ -260,7 +260,8 @@ SOURCES += \ history/historymenu.cpp \ app/datapaths.cpp \ app/profilemanager.cpp \ - app/mainmenu.cpp + app/mainmenu.cpp \ + tools/sqldatabase.cpp HEADERS += \ @@ -461,7 +462,8 @@ HEADERS += \ history/historymenu.h \ app/datapaths.h \ app/profilemanager.h \ - app/mainmenu.h + app/mainmenu.h \ + tools/sqldatabase.h FORMS += \ preferences/autofillmanager.ui \ diff --git a/src/lib/tools/sqldatabase.cpp b/src/lib/tools/sqldatabase.cpp new file mode 100644 index 000000000..fa6f692c6 --- /dev/null +++ b/src/lib/tools/sqldatabase.cpp @@ -0,0 +1,187 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2014 David Rosca +* +* This program 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 3 of the License, or +* (at your option) any later version. +* +* This program 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 this program. If not, see . +* ============================================================ */ +#include "sqldatabase.h" + +#include + +#define CONNECTION_NAME QSL("QupZilla::DatabaseWorker") + +SqlDatabase* SqlDatabase::s_instance = 0; + +// SqlDatabase +SqlDatabase::SqlDatabase(QObject* parent) + : QObject(parent) +{ + qRegisterMetaType("QSqlQuery"); + + m_worker = new DatabaseWorker; + m_thread = new DatabaseWorkerThread(m_worker); + m_worker->moveToThread(m_thread); + m_thread->start(); + + connect(m_thread, SIGNAL(started()), m_worker, SLOT(threadStarted())); +} + +SqlDatabase::~SqlDatabase() +{ + m_thread->exit(); + m_thread->wait(); + + delete m_thread; + delete m_worker; +} + +void SqlDatabase::execAsync(const QSqlQuery &query, QObject* receiver, const char* slot) +{ + m_worker->execQueryAsync(query, receiver, slot); +} + +void SqlDatabase::transactionAsync(const QList &queries) +{ + m_worker->transactionAsync(queries); +} + +SqlDatabase* SqlDatabase::instance() +{ + if (!s_instance) { + s_instance = new SqlDatabase; + } + return s_instance; +} + +void SqlDatabase::destroy() +{ + delete s_instance; + s_instance = 0; +} + +// DatabaseWorker +DatabaseWorker::DatabaseWorker() + : QObject() + , m_started(false) +{ +} + +void DatabaseWorker::execQueryAsync(const QSqlQuery &query, QObject* receiver, const char* slot) +{ + QueryData data; + data.queries.append(query); + data.receiver = receiver; + data.slot = slot; + + m_queries.enqueue(data); + + if (m_started) { + QMetaObject::invokeMethod(this, "execPendingQueries", Qt::QueuedConnection); + } +} + +void DatabaseWorker::transactionAsync(const QList &queries) +{ + QueryData data; + data.queries = queries; + data.receiver = 0; + data.slot = 0; + + m_queries.enqueue(data); + + if (m_started) { + QMetaObject::invokeMethod(this, "execPendingQueries", Qt::QueuedConnection); + } +} + +bool DatabaseWorker::hasPendingQueries() const +{ + return !m_queries.isEmpty(); +} + +void DatabaseWorker::threadStarted() +{ + m_started = true; + + QSqlDatabase::cloneDatabase(QSqlDatabase::database(), CONNECTION_NAME); + m_db = QSqlDatabase::database(CONNECTION_NAME); + + // Execute queries that got queued before starting thread + if (hasPendingQueries()) { + QMetaObject::invokeMethod(this, "execPendingQueries", Qt::QueuedConnection); + } +} + +static QSqlQuery copyQueryToDatabase(const QSqlQuery &query, const QSqlDatabase &db) +{ + QSqlQuery out(db); + out.prepare(query.lastQuery()); + + const QList boundValues = query.boundValues().values(); + + foreach (const QVariant &variant, boundValues) { + out.addBindValue(variant); + } + + return out; +} + +void DatabaseWorker::execPendingQueries() +{ + while (hasPendingQueries()) { + QueryData data = m_queries.dequeue(); + + Q_ASSERT(!data.queries.isEmpty()); + + // Transaction + if (data.queries.size() > 1) { + m_db.transaction(); + foreach (const QSqlQuery &q, data.queries) { + QSqlQuery query = copyQueryToDatabase(q, m_db); + query.exec(); + } + m_db.commit(); + } + // Single query + else { + QSqlQuery query = copyQueryToDatabase(data.queries.takeFirst(), m_db); + query.exec(); + + // Invoke connected slot + if (data.receiver) { + // SLOT() macro is prepending a "1" to the slot signature + int index = data.receiver->metaObject()->indexOfMethod(QMetaObject::normalizedSignature(data.slot + 1)); + Q_ASSERT(index >= 0); + QMetaMethod method = data.receiver->metaObject()->method(index); + method.invoke(data.receiver, Qt::QueuedConnection, Q_ARG(QSqlQuery, query)); + } + } + } +} + +// DatabaseWorkerThread +DatabaseWorkerThread::DatabaseWorkerThread(DatabaseWorker* worker) + : QThread() + , m_worker(worker) +{ +} + +void DatabaseWorkerThread::run() +{ + exec(); + + if (m_worker->hasPendingQueries()) { + m_worker->execPendingQueries(); + } +} diff --git a/src/lib/tools/sqldatabase.h b/src/lib/tools/sqldatabase.h new file mode 100644 index 000000000..b5d94aee9 --- /dev/null +++ b/src/lib/tools/sqldatabase.h @@ -0,0 +1,101 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2014 David Rosca +* +* This program 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 3 of the License, or +* (at your option) any later version. +* +* This program 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 this program. If not, see . +* ============================================================ */ +#ifndef SQLDATABASE_H +#define SQLDATABASE_H + +#include +#include +#include +#include + +#include "qzcommon.h" + +class QThread; +class QSqlQuery; + +class DatabaseWorker; +class DatabaseWorkerThread; + +// Queries are executed in FIFO order +class QUPZILLA_EXPORT SqlDatabase : public QObject +{ + Q_OBJECT + +public: + explicit SqlDatabase(QObject* parent = 0); + ~SqlDatabase(); + + // Executes query async and send result if receiver is not null. + // Slot must have "name(QSqlQuery)" signature + void execAsync(const QSqlQuery &query, QObject* receiver = 0, const char* slot = 0); + + // Executes transaction async without sending result + void transactionAsync(const QList &queries); + + // May be called only after creating QSqlDatabase connection in main thread + static SqlDatabase* instance(); + // Must be called before closing main thread QSqlDatabase connection + static void destroy(); + +private: + DatabaseWorker* m_worker; + DatabaseWorkerThread* m_thread; + + static SqlDatabase* s_instance; +}; + +class DatabaseWorker : public QObject +{ + Q_OBJECT + +public: + explicit DatabaseWorker(); + + void execQueryAsync(const QSqlQuery &query, QObject* receiver = 0, const char* slot = 0); + void transactionAsync(const QList &queries); + + bool hasPendingQueries() const; + +public slots: + void threadStarted(); + void execPendingQueries(); + +private: + struct QueryData { + QList queries; + QObject* receiver; + const char* slot; + }; + + QQueue m_queries; + QSqlDatabase m_db; + bool m_started; +}; + +class DatabaseWorkerThread : public QThread +{ +public: + explicit DatabaseWorkerThread(DatabaseWorker* worker); + +private: + void run(); + + DatabaseWorker* m_worker; +}; + +#endif // SQLDATABASE_H