File indexing completed on 2024-05-12 16:23:41

0001 // SPDX-FileCopyrightText: 2020 Jonah BrĂ¼chert <jbb@kaidan.im>
0002 // SPDX-FileCopyrightText: 2020 Rinigus <rinigus.git@gmail.com>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 #include "dbmanager.h"
0007 #include "iconimageprovider.h"
0008 
0009 #include <QDateTime>
0010 #include <QDebug>
0011 #include <QDir>
0012 #include <QSqlDatabase>
0013 #include <QStandardPaths>
0014 #include <QVariant>
0015 
0016 #include <exception>
0017 
0018 #include <QCoroFuture>
0019 
0020 constexpr int MAX_BROWSER_HISTORY_SIZE = 3000;
0021 
0022 DBManager::DBManager(QObject *parent)
0023     : QObject(parent)
0024 {
0025     const QString dbpath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
0026     const QString dbname = dbpath + QStringLiteral("/angelfish.sqlite");
0027 
0028     if (!QDir().mkpath(dbpath)) {
0029         qCritical() << "Database directory does not exist and cannot be created: " << dbpath;
0030         throw std::runtime_error("Database directory does not exist and cannot be created: " + dbpath.toStdString());
0031     }
0032 
0033     DatabaseConfiguration config;
0034     config.setDatabaseName(dbname);
0035     config.setType(DatabaseType::SQLite);
0036 
0037     m_database = ThreadedDatabase::establishConnection(config);
0038     m_database->runMigrations(QStringLiteral(":/contents/migrations/"));
0039 
0040     if (!m_database) {
0041         qCritical() << "Failed to open database" << dbname;
0042         throw std::runtime_error("Failed to open database " + dbname.toStdString());
0043     }
0044 
0045     // TODO DB: Add back migrations
0046 
0047     trimHistory();
0048     trimIcons();
0049 }
0050 
0051 QCoro::Task<> DBManager::execute(const QString command)
0052 {
0053     co_await m_database->execute(command);
0054 }
0055 
0056 QCoro::Task<> DBManager::trimHistory()
0057 {
0058     co_await m_database->execute(
0059         QStringLiteral("DELETE FROM history WHERE rowid NOT IN (SELECT rowid FROM history "
0060                        "ORDER BY lastVisited DESC LIMIT ?)"),
0061                 MAX_BROWSER_HISTORY_SIZE);
0062 }
0063 
0064 QCoro::Task<> DBManager::trimIcons()
0065 {
0066     co_await m_database->execute(
0067         QStringLiteral("DELETE FROM icons WHERE url NOT IN "
0068                        "(SELECT icon FROM history UNION SELECT icon FROM bookmarks)"));
0069 }
0070 
0071 QCoro::Task<> DBManager::addRecord(const QString table, const QVariantMap pagedata)
0072 {
0073     const QString url = pagedata.value(QStringLiteral("url")).toString();
0074     const QString title = pagedata.value(QStringLiteral("title")).toString();
0075     const QString icon = pagedata.value(QStringLiteral("icon")).toString();
0076     const qint64 lastVisited = QDateTime::currentSecsSinceEpoch();
0077 
0078     if (url.isEmpty() || url == QStringLiteral("about:blank"))
0079         co_return;
0080 
0081     co_await m_database->execute(QStringLiteral(
0082                                 "INSERT OR REPLACE INTO %1 (url, title, icon, lastVisited) "
0083                                 "VALUES (?, ?, ?, ?)").arg(table),
0084                                 url, title, icon, lastVisited);
0085 
0086     Q_EMIT databaseTableChanged(table);
0087 }
0088 
0089 QCoro::Task<> DBManager::removeRecord(const QString table, const QString url)
0090 {
0091     if (url.isEmpty())
0092         co_return;
0093 
0094     co_await m_database->execute(QStringLiteral("DELETE FROM %1 WHERE url = ?").arg(table), url);
0095 
0096     Q_EMIT databaseTableChanged(table);
0097 }
0098 
0099 QCoro::Task<> DBManager::removeAllRecords(const QString table)
0100 {
0101     co_await m_database->execute(QStringLiteral("DELETE FROM ?"), table);
0102     Q_EMIT databaseTableChanged(table);
0103 }
0104 
0105 QCoro::Task<bool> DBManager::hasRecord(const QString table, const QString url) const
0106 {
0107     auto maybeExists = co_await m_database
0108             ->getResult<SingleValue<bool>>(QStringLiteral("SELECT COUNT(url) > 0 FROM %1 WHERE url = ?").arg(table), url);
0109 
0110     if (maybeExists.has_value()) {
0111         co_return maybeExists->value;
0112     }
0113 
0114     co_return false;
0115 }
0116 
0117 QCoro::Task<> DBManager::updateIconRecord(const QString table, const QString url, const QString iconSource)
0118 {
0119     if (url.isEmpty())
0120         co_return;
0121 
0122     m_database->execute(QStringLiteral("UPDATE %1 SET icon = ? WHERE url = ?").arg(table), iconSource, url);
0123 
0124     Q_EMIT databaseTableChanged(table);
0125 }
0126 
0127 QCoro::Task<> DBManager::setLastVisitedRecord(const QString table, const QString url)
0128 {
0129     if (url.isEmpty())
0130         co_return;
0131 
0132     const qint64 lastVisited = QDateTime::currentSecsSinceEpoch();
0133     co_await m_database->execute(QStringLiteral("UPDATE %1 SET lastVisited = ? WHERE url = ?").arg(table), url, lastVisited);
0134 
0135     Q_EMIT databaseTableChanged(table);
0136 }
0137 
0138 QCoro::Task<> DBManager::addBookmark(const QVariantMap bookmarkdata)
0139 {
0140     co_await addRecord(QStringLiteral("bookmarks"), bookmarkdata);
0141 }
0142 
0143 QCoro::Task<> DBManager::removeBookmark(const QString url)
0144 {
0145     co_await removeRecord(QStringLiteral("bookmarks"), url);
0146 }
0147 
0148 QCoro::Task<bool> DBManager::isBookmarked(const QString url) const
0149 {
0150     co_return co_await hasRecord(QStringLiteral("bookmarks"), url);
0151 }
0152 
0153 QCoro::Task<> DBManager::addToHistory(const QVariantMap pagedata)
0154 {
0155     co_await addRecord(QStringLiteral("history"), pagedata);
0156 }
0157 
0158  QCoro::Task<> DBManager::removeFromHistory(const QString url)
0159 {
0160     co_await removeRecord(QStringLiteral("history"), url);
0161 }
0162 
0163 QCoro::Task<> DBManager::clearHistory()
0164 {
0165     co_await removeAllRecords(QStringLiteral("history"));
0166 }
0167 
0168 QCoro::Task<> DBManager::updateLastVisited(const QString url)
0169 {
0170     co_await setLastVisitedRecord(QStringLiteral("bookmarks"), url);
0171     co_await setLastVisitedRecord(QStringLiteral("history"), url);
0172 }
0173 
0174 QCoro::Task<> DBManager::updateIcon(QQmlEngine *engine, const QString url, const QString iconSource)
0175 {
0176     const QString updatedSource = co_await storeIcon(engine, iconSource);
0177     co_await updateIconRecord(QStringLiteral("bookmarks"), url, updatedSource);
0178     co_await updateIconRecord(QStringLiteral("history"), url, updatedSource);
0179 }
0180 
0181 #include "moc_dbmanager.cpp"