From a6f6969912a45114c0c9849705c33beed49cb0e8 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 16 Mar 2020 22:37:04 +0100 Subject: [PATCH] Rework the database system --- src/CMakeLists.txt | 1 + src/database.cpp | 89 ++++++++++++++++++++++++++++++++++++++++++ src/database.h | 42 ++++++++++++++++++++ src/entryListModel.cpp | 7 ++-- src/feedListModel.cpp | 29 ++++++++------ src/fetcher.cpp | 11 +++--- src/main.cpp | 17 +------- 7 files changed, 160 insertions(+), 36 deletions(-) create mode 100644 src/database.cpp create mode 100644 src/database.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5028d749..f741a1af 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ set(alligator_SRCS fetcher.cpp feed.cpp entry.cpp + database.cpp resources.qrc ) diff --git a/src/database.cpp b/src/database.cpp new file mode 100644 index 00000000..1e9ad61b --- /dev/null +++ b/src/database.cpp @@ -0,0 +1,89 @@ +/** + * Copyright 2020 Tobias Fella + * + * 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 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 +#include +#include +#include + +#include "database.h" +#include "alligator-debug.h" + +#define TRUE_OR_RETURN(x) if(!x) return false; + +Database::Database() +{ + QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE")); + QString databasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir(databasePath).mkpath(databasePath); + db.setDatabaseName(databasePath + QStringLiteral("/database.db3")); + qCDebug(ALLIGATOR) << "Opening database " << databasePath << "/database.db3"; + db.open(); + + if(!migrate()) { + qCCritical(ALLIGATOR) << "Failed to migrate the database"; + } +} + +bool Database::migrate() { + qCDebug(ALLIGATOR) << "Migrating database"; + if(version() < 1) TRUE_OR_RETURN(migrateTo1()); + return true; +} + +bool Database::migrateTo1() { + QSqlQuery query(QSqlDatabase::database()); + TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Feeds (name TEXT, url TEXT);"))); + TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Entries (feed TEXT, id TEXT, title TEXT, content TEXT);"))); + TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Authors (id TEXT, name TEXT, uri TEXT, email TEXT);"))); + TRUE_OR_RETURN(execute(QStringLiteral("PRAGMA user_version = 1;"))); + return true; +} + +bool Database::execute(QString query) { + QSqlQuery q; + q.prepare(query); + return execute(q); +} + +bool Database::execute(QSqlQuery &query) { + if(!query.exec()) { + qCWarning(ALLIGATOR) << "Failed to execute SQL Query"; + qCWarning(ALLIGATOR) << query.lastQuery(); + qCWarning(ALLIGATOR) << query.lastError(); + return false; + } + return true; +} + +int Database::version() { + QSqlQuery query; + query.prepare(QStringLiteral("PRAGMA user_version;")); + execute(query); + if(query.next()) { + bool ok; + int value = query.value(0).toInt(&ok); + qCDebug(ALLIGATOR) << "Database version " << value; + if(ok) return value; + } else { + qCCritical(ALLIGATOR) << "Failed to check database version"; + } + return -1; +} diff --git a/src/database.h b/src/database.h new file mode 100644 index 00000000..0804b1ae --- /dev/null +++ b/src/database.h @@ -0,0 +1,42 @@ +/** + * Copyright 2020 Tobias Fella + * + * 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 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + */ + +#pragma once + +#include + +class Database +{ +public: + static Database &instance() + { + static Database _instance; + return _instance; + } + bool execute(QSqlQuery &query); + bool execute(QString query); + +private: + Database(); + int version(); + + bool migrate(); + bool migrateTo1(); +}; diff --git a/src/entryListModel.cpp b/src/entryListModel.cpp index 28c708fd..89f1dde7 100644 --- a/src/entryListModel.cpp +++ b/src/entryListModel.cpp @@ -18,12 +18,11 @@ * along with this program. If not, see . */ -#include -#include #include #include "entryListModel.h" #include "fetcher.h" +#include "database.h" EntryListModel::EntryListModel(QObject *parent) : QAbstractListModel(parent) @@ -70,10 +69,10 @@ void EntryListModel::fetch() { connect(&Fetcher::instance(), &Fetcher::finished, this, [this]() { beginResetModel(); - QSqlQuery query(QSqlDatabase::database()); + QSqlQuery query; query.prepare(QStringLiteral("SELECT id, title, content FROM Entries WHERE feed=:feed;")); query.bindValue(QStringLiteral(":feed"), m_feed); - query.exec(); + Database::instance().execute(query); while (query.next()) { m_entries.append(Entry(query.value(1).toString(), query.value(2).toString(), false, false)); } diff --git a/src/feedListModel.cpp b/src/feedListModel.cpp index e935cfba..42e9747c 100644 --- a/src/feedListModel.cpp +++ b/src/feedListModel.cpp @@ -18,21 +18,22 @@ * along with this program. If not, see . */ -#include -#include #include -#include #include #include "feedListModel.h" #include "fetcher.h" +#include "database.h" + +#include "alligator-debug.h" FeedListModel::FeedListModel(QObject *parent) : QAbstractListModel(parent) { - QSqlQuery query(QSqlDatabase::database()); - query.exec(QStringLiteral("SELECT name, url FROM Feeds")); + QSqlQuery query; + query.prepare(QStringLiteral("SELECT name, url FROM Feeds")); + Database::instance().execute(query); beginInsertRows(QModelIndex(), 0, query.size()); while (query.next()) { feeds += Feed(query.value(1).toString(), query.value(0).toString()); @@ -63,11 +64,17 @@ int FeedListModel::rowCount(const QModelIndex &index) const void FeedListModel::addFeed(QString url) { + QSqlQuery query; + query.prepare(QStringLiteral("SELECT COUNT (url) FROM Feeds WHERE url=:url;")); + query.bindValue(QStringLiteral(":url"), url); + Database::instance().execute(query); + query.next(); + if(query.value(0).toInt() != 0) return; connect(&Fetcher::instance(), &Fetcher::finished, this, [this, url]() { - QSqlQuery query(QSqlDatabase::database()); + QSqlQuery query; query.prepare(QStringLiteral("SELECT name FROM Feeds WHERE url=:url;")); query.bindValue(QStringLiteral(":url"), url); - query.exec(); + Database::instance().execute(query); query.next(); for(int i = 0; i < feeds.length(); i++) { if(feeds[i].url() == url) { @@ -82,21 +89,21 @@ void FeedListModel::addFeed(QString url) beginInsertRows(QModelIndex(), feeds.size(), feeds.size()); feeds.append(Feed(url)); endInsertRows(); - QSqlQuery query(QSqlDatabase::database()); + query.prepare(QStringLiteral("INSERT INTO Feeds VALUES (:url, :name);")); query.bindValue(QStringLiteral(":url"), url); query.bindValue(QStringLiteral(":name"), url); - query.exec(); + Database::instance().execute(query); } void FeedListModel::remove_feed(int index) { Feed toRemove = feeds[index]; - QSqlQuery query(QSqlDatabase::database()); + QSqlQuery query; query.prepare(QStringLiteral("DELETE FROM Feeds WHERE name=:name AND url=url;")); query.bindValue(QStringLiteral(":url"), toRemove.url()); query.bindValue(QStringLiteral(":name"), toRemove.name()); - query.exec(); + Database::instance().execute(query); beginRemoveRows(QModelIndex(), index, index); feeds.remove(index); endRemoveRows(); diff --git a/src/fetcher.cpp b/src/fetcher.cpp index 57be9efa..9c8ab464 100644 --- a/src/fetcher.cpp +++ b/src/fetcher.cpp @@ -20,13 +20,13 @@ #include #include -#include -#include #include #include "fetcher.h" +#include "database.h" + Fetcher::Fetcher() { } @@ -47,7 +47,6 @@ void Fetcher::fetch(QUrl url) QSqlQuery query(db); for (const auto &entry : feed->items()) { - query = QSqlQuery(db); query.prepare(QStringLiteral("INSERT INTO Entries VALUES (:feed, :id, :title, :content);")); query.bindValue(QStringLiteral(":feed"), url.toString()); query.bindValue(QStringLiteral(":id"), entry->id()); @@ -56,7 +55,7 @@ void Fetcher::fetch(QUrl url) query.bindValue(QStringLiteral(":content"), entry->content()); else query.bindValue(QStringLiteral(":content"), entry->description()); - query.exec(); + Database::instance().execute(query); for (const auto &author : entry->authors()) { query = QSqlQuery(db); query.prepare(QStringLiteral("INSERT INTO Authors VALUES(:id, :name, :uri, :email);")); @@ -64,12 +63,12 @@ void Fetcher::fetch(QUrl url) query.bindValue(QStringLiteral(":name"), author->name()); query.bindValue(QStringLiteral(":uri"), author->uri()); query.bindValue(QStringLiteral(":email"), author->email()); - query.exec(); + Database::instance().execute(query); } query.prepare(QStringLiteral("UPDATE Feeds SET name=:name WHERE url=:url;")); query.bindValue(QStringLiteral(":name"), feed->title()); query.bindValue(QStringLiteral(":url"), url.toString()); - query.exec(); + Database::instance().execute(query); } delete reply; emit finished(); diff --git a/src/main.cpp b/src/main.cpp index d73a9131..144e6306 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,14 +21,10 @@ #include #include #include -#include -#include -#include -#include -#include #include "entryListModel.h" #include "feedListModel.h" +#include "database.h" #include "alligator-debug.h" @@ -48,16 +44,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) QQmlApplicationEngine engine; - QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE")); - QString databasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - QDir(databasePath).mkpath(databasePath); - db.setDatabaseName(databasePath + "/database.db3"); - db.open(); - - QSqlQuery query(db); - query.exec(QStringLiteral("CREATE TABLE IF NOT EXISTS Feeds (name TEXT, url TEXT);")); - query.exec(QStringLiteral("CREATE TABLE IF NOT EXISTS Entries (feed TEXT, id TEXT UNIQUE, title TEXT, content TEXT);")); - query.exec(QStringLiteral("CREATE TABLE IF NOT EXISTS Authors (id TEXT, name TEXT, uri TEXT, email TEXT);")); + Database::instance(); engine.load(QUrl(QStringLiteral("qrc:///main.qml")));