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"