Changeset View
Changeset View
Standalone View
Standalone View
src/db-store.cpp
| /* | /* | ||||
| * This file is part of kazv. | * This file is part of kazv. | ||||
| * SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe> | * SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe> | ||||
| * SPDX-License-Identifier: AGPL-3.0-or-later | * SPDX-License-Identifier: AGPL-3.0-or-later | ||||
| */ | */ | ||||
| #include "db-store.hpp" | #include "db-store.hpp" | ||||
| #include "matrix-sdk.hpp" | #include "matrix-sdk.hpp" | ||||
| #include "event.hpp" | #include "event.hpp" | ||||
| #include "kazv-log.hpp" | #include "kazv-log.hpp" | ||||
| #include <QPromise> | #include <QPromise> | ||||
| #include <QSqlError> | #include <QSqlError> | ||||
| #include <QSqlQuery> | #include <QSqlQuery> | ||||
| #include <QSqlRecord> | |||||
| #include <QCoroFuture> | #include <QCoroFuture> | ||||
| using namespace Qt::Literals::StringLiterals; | using namespace Qt::Literals::StringLiterals; | ||||
| using namespace Kazv; | using namespace Kazv; | ||||
| QString DbStore::getHandleFor(std::string userId, std::string deviceId) | QString DbStore::getHandleFor(std::string userId, std::string deviceId) | ||||
| { | { | ||||
| return QString::fromStdString(userId + "/" + deviceId); | return QString::fromStdString(userId + "/" + deviceId); | ||||
| } | } | ||||
| std::optional<Kazv::Event> DbStore::resultToEvent(bool encrypted, bool decrypted, QString originalJson, QString decryptedJson) | |||||
| { | |||||
| Event e; | |||||
| try { | |||||
| auto oj = json::parse(std::move(originalJson).toStdString()); | |||||
| e = Event(JsonWrap(oj)); | |||||
| } catch (const json::parse_error &) { | |||||
| return std::nullopt; | |||||
| } | |||||
| try { | |||||
| if (encrypted) { | |||||
| auto dj = json::parse(std::move(decryptedJson).toStdString()); | |||||
| e = std::move(e).setDecryptedJson( | |||||
| JsonWrap(dj), | |||||
| decrypted ? Event::Decrypted : Event::NotDecrypted | |||||
| ); | |||||
| } | |||||
| return e; | |||||
| } catch (const json::parse_error &) { | |||||
| return e; | |||||
| } | |||||
| } | |||||
| DbStore::DbStore() | DbStore::DbStore() | ||||
| : m_thread(new QThread) | : m_thread(new QThread) | ||||
| , m_obj(new QObject) | , m_obj(new QObject) | ||||
| , m_d(std::nullopt) | , m_d(std::nullopt) | ||||
| , m_handle() | , m_handle() | ||||
| { | { | ||||
| m_obj->moveToThread(m_thread); | m_obj->moveToThread(m_thread); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | for (const auto &[roomId, room] : model.c().roomList.rooms) { | ||||
| if (count % 100 == 0) { | if (count % 100 == 0) { | ||||
| qCInfo(kazvLog) << "Imported" << count << "events"; | qCInfo(kazvLog) << "Imported" << count << "events"; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| co_return {true, QString()}; | co_return {true, QString()}; | ||||
| } | } | ||||
| QCoro::Task<std::pair<bool, QString>> DbStore::saveEvents(Kazv::SaveEventsRequested s) | |||||
| { | |||||
| for (const auto &[roomId, el] : s.timelineEvents) { | |||||
| auto roomIdQs = QString::fromStdString(roomId); | |||||
| for (const auto &event : el) { | |||||
| auto res = co_await importOne(roomIdQs, event, /* isInTimeline = */ true); | |||||
| if (!res.first) { | |||||
| qCWarning(kazvLog) << "Error inserting value:" << res.second; | |||||
| co_return res; | |||||
| } | |||||
| } | |||||
| } | |||||
| for (const auto &[roomId, el] : s.nonTimelineEvents) { | |||||
| auto roomIdQs = QString::fromStdString(roomId); | |||||
| for (const auto &event : el) { | |||||
| auto res = co_await importOne(roomIdQs, event, /* isInTimeline = */ false); | |||||
| if (!res.first) { | |||||
| qCWarning(kazvLog) << "Error inserting value:" << res.second; | |||||
| co_return res; | |||||
| } | |||||
| } | |||||
| } | |||||
| co_return {true, QString()}; | |||||
| } | |||||
| inline const auto getEventByIdQuery = uR"xxx(SELECT | |||||
| encrypted, decrypted, | |||||
| original_json, decrypted_json, is_in_timeline | |||||
| FROM events WHERE | |||||
| room_id = :room_id AND event_id = :event_id | |||||
| ;)xxx"_s; | |||||
| QCoro::Task<std::optional<std::pair<Kazv::Event, bool>>> DbStore::getEventById(const QString &roomId, const QString &eventId) | |||||
| { | |||||
| QPromise<std::optional<std::pair<Kazv::Event, bool>>> p; | |||||
| auto fut = p.future(); | |||||
| QMetaObject::invokeMethod(m_obj, [this, p=std::move(p), roomId, eventId]() mutable { | |||||
| QSqlQuery q(m_d.value()); | |||||
| auto res = q.prepare(getEventByIdQuery); | |||||
| if (!res) { | |||||
| p.addResult(std::nullopt); | |||||
| p.finish(); | |||||
| return; | |||||
| } | |||||
| q.bindValue(u":room_id"_s, roomId); | |||||
| q.bindValue(u":event_id"_s, eventId); | |||||
| res = q.exec(); | |||||
| if (!res || !q.next()) { | |||||
| p.addResult(std::nullopt); | |||||
| p.finish(); | |||||
| return; | |||||
| } | |||||
| auto encrypted = q.value(0).toBool(); | |||||
| auto decrypted = q.value(1).toBool(); | |||||
| auto originalJson = q.value(2).toString(); | |||||
| auto decryptedJson = q.value(3).toString(); | |||||
| auto isInTimeline = q.value(4).toBool(); | |||||
| auto eventOpt = resultToEvent(encrypted, decrypted, std::move(originalJson), std::move(decryptedJson)); | |||||
| auto ret = eventOpt | |||||
| ? std::optional<std::pair<Event, bool>>( | |||||
| {eventOpt.value(), isInTimeline} | |||||
| ) : std::nullopt; | |||||
| p.addResult(ret); | |||||
| p.finish(); | |||||
| }); | |||||
| co_return co_await fut; | |||||
| } | |||||
| bool DbStore::valid() const | bool DbStore::valid() const | ||||
| { | { | ||||
| return m_d.has_value(); | return m_d.has_value(); | ||||
| } | } | ||||
| void DbStore::cleanup() | void DbStore::cleanup() | ||||
| { | { | ||||
| if (m_d.has_value()) { | if (m_d.has_value()) { | ||||
| ▲ Show 20 Lines • Show All 79 Lines • ▼ Show 20 Lines | |||||
| QCoro::Task<std::pair<bool, QString>> DbStore::asyncQuery(const QString &qs, std::function<void(QSqlQuery &)> tf) | QCoro::Task<std::pair<bool, QString>> DbStore::asyncQuery(const QString &qs, std::function<void(QSqlQuery &)> tf) | ||||
| { | { | ||||
| // This function exists because QSqlQuery must be used in the thread where | // This function exists because QSqlQuery must be used in the thread where | ||||
| // the QSqlDatabase is in. | // the QSqlDatabase is in. | ||||
| // We invoke the query inside the thread of the database, then use the | // We invoke the query inside the thread of the database, then use the | ||||
| // promise-future protocol to pass the result back as a coroutine. | // promise-future protocol to pass the result back as a coroutine. | ||||
| QPromise<std::pair<bool, QString>> p; | QPromise<std::pair<bool, QString>> p; | ||||
| qCDebug(kazvLog) << "DbStore: Async Query:" << qs; | |||||
| auto fut = p.future(); | auto fut = p.future(); | ||||
| QMetaObject::invokeMethod(m_obj, [this, p=std::move(p), s=std::move(qs), tf]() mutable { | QMetaObject::invokeMethod(m_obj, [this, p=std::move(p), s=std::move(qs), tf]() mutable { | ||||
| p.start(); | p.start(); | ||||
| QSqlQuery q(m_d.value()); | QSqlQuery q(m_d.value()); | ||||
| qCDebug(kazvLog) << "DbStore: Invoking Query:" << s; | |||||
| auto res = q.prepare(std::move(s)); | auto res = q.prepare(std::move(s)); | ||||
| if (!res) { | if (!res) { | ||||
| qCDebug(kazvLog) << "DbStore: Async Query Prepare Failed: Driver:" << q.lastError().driverText(); | qCWarning(kazvLog) << "DbStore: Async Query Prepare Failed: Driver:" << q.lastError().driverText(); | ||||
| qCDebug(kazvLog) << "DbStore: Async Query Prepare Failed: Db:" << q.lastError().databaseText(); | qCWarning(kazvLog) << "DbStore: Async Query Prepare Failed: Db:" << q.lastError().databaseText(); | ||||
| p.addResult({false, q.lastError().text()}); | p.addResult({false, q.lastError().text()}); | ||||
| p.finish(); | p.finish(); | ||||
| return; | return; | ||||
| } | } | ||||
| tf(q); | tf(q); | ||||
| qCDebug(kazvLog) << "DbStore: where:" << q.boundValueNames() << q.boundValues(); | |||||
| res = q.exec(); | res = q.exec(); | ||||
| qCDebug(kazvLog) << "DbStore: Async Query Done:" << res; | |||||
| if (!res) { | if (!res) { | ||||
| qCDebug(kazvLog) << "DbStore: Async Query Failed: Driver:" << q.lastError().driverText(); | qCWarning(kazvLog) << "DbStore: Async Query Failed: Driver:" << q.lastError().driverText(); | ||||
| qCDebug(kazvLog) << "DbStore: Async Query Failed: Db:" << q.lastError().databaseText(); | qCWarning(kazvLog) << "DbStore: Async Query Failed: Db:" << q.lastError().databaseText(); | ||||
| p.addResult({false, q.lastError().text()}); | p.addResult({false, q.lastError().text()}); | ||||
| } else { | } else { | ||||
| p.addResult({true, QString()}); | p.addResult({true, QString()}); | ||||
| } | } | ||||
| p.finish(); | p.finish(); | ||||
| }); | }); | ||||
| co_return co_await fut; | co_return co_await fut; | ||||
| } | } | ||||