forked from mirror/nheko
parent
fe12e63c7c
commit
530c531c4b
@ -0,0 +1,259 @@ |
||||
#include "EventStore.h" |
||||
|
||||
#include <QThread> |
||||
|
||||
#include "Cache_p.h" |
||||
#include "EventAccessors.h" |
||||
#include "Logging.h" |
||||
#include "Olm.h" |
||||
|
||||
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::decryptedEvents_{ |
||||
1000}; |
||||
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{ |
||||
1000}; |
||||
QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::events_{1000}; |
||||
|
||||
EventStore::EventStore(std::string room_id, QObject *) |
||||
: room_id_(std::move(room_id)) |
||||
{ |
||||
auto range = cache::client()->getTimelineRange(room_id_); |
||||
|
||||
if (range) { |
||||
this->first = range->first; |
||||
this->last = range->last; |
||||
} |
||||
} |
||||
|
||||
void |
||||
EventStore::handleSync(const mtx::responses::Timeline &events) |
||||
{ |
||||
if (this->thread() != QThread::currentThread()) |
||||
nhlog::db()->warn("{} called from a different thread!", __func__); |
||||
|
||||
auto range = cache::client()->getTimelineRange(room_id_); |
||||
|
||||
if (range && range->last > this->last) { |
||||
emit beginInsertRows(toExternalIdx(this->last + 1), toExternalIdx(range->last)); |
||||
this->last = range->last; |
||||
emit endInsertRows(); |
||||
} |
||||
|
||||
for (const auto &event : events.events) { |
||||
std::string relates_to; |
||||
if (auto redaction = |
||||
std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>( |
||||
&event)) { |
||||
relates_to = redaction->redacts; |
||||
} else if (auto reaction = |
||||
std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>( |
||||
&event)) { |
||||
relates_to = reaction->content.relates_to.event_id; |
||||
} else { |
||||
relates_to = mtx::accessors::in_reply_to_event(event); |
||||
} |
||||
|
||||
if (!relates_to.empty()) { |
||||
auto idx = cache::client()->getTimelineIndex(room_id_, relates_to); |
||||
if (idx) |
||||
emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
mtx::events::collections::TimelineEvents * |
||||
EventStore::event(int idx, bool decrypt) |
||||
{ |
||||
if (this->thread() != QThread::currentThread()) |
||||
nhlog::db()->warn("{} called from a different thread!", __func__); |
||||
|
||||
Index index{room_id_, toInternalIdx(idx)}; |
||||
if (index.idx > last || index.idx < first) |
||||
return nullptr; |
||||
|
||||
auto event_ptr = events_.object(index); |
||||
if (!event_ptr) { |
||||
auto event_id = cache::client()->getTimelineEventId(room_id_, index.idx); |
||||
if (!event_id) |
||||
return nullptr; |
||||
|
||||
auto event = cache::client()->getEvent(room_id_, *event_id); |
||||
if (!event) |
||||
return nullptr; |
||||
else |
||||
event_ptr = |
||||
new mtx::events::collections::TimelineEvents(std::move(event->data)); |
||||
events_.insert(index, event_ptr); |
||||
} |
||||
|
||||
if (decrypt) |
||||
if (auto encrypted = |
||||
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( |
||||
event_ptr)) |
||||
return decryptEvent({room_id_, encrypted->event_id}, *encrypted); |
||||
|
||||
return event_ptr; |
||||
} |
||||
|
||||
std::optional<int> |
||||
EventStore::idToIndex(std::string_view id) const |
||||
{ |
||||
if (this->thread() != QThread::currentThread()) |
||||
nhlog::db()->warn("{} called from a different thread!", __func__); |
||||
|
||||
auto idx = cache::client()->getTimelineIndex(room_id_, id); |
||||
if (idx) |
||||
return toExternalIdx(*idx); |
||||
else |
||||
return std::nullopt; |
||||
} |
||||
std::optional<std::string> |
||||
EventStore::indexToId(int idx) const |
||||
{ |
||||
if (this->thread() != QThread::currentThread()) |
||||
nhlog::db()->warn("{} called from a different thread!", __func__); |
||||
|
||||
return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx)); |
||||
} |
||||
|
||||
mtx::events::collections::TimelineEvents * |
||||
EventStore::decryptEvent(const IdIndex &idx, |
||||
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) |
||||
{ |
||||
if (auto cachedEvent = decryptedEvents_.object(idx)) |
||||
return cachedEvent; |
||||
|
||||
MegolmSessionIndex index; |
||||
index.room_id = room_id_; |
||||
index.session_id = e.content.session_id; |
||||
index.sender_key = e.content.sender_key; |
||||
|
||||
mtx::events::RoomEvent<mtx::events::msg::Notice> dummy; |
||||
dummy.origin_server_ts = e.origin_server_ts; |
||||
dummy.event_id = e.event_id; |
||||
dummy.sender = e.sender; |
||||
dummy.content.body = |
||||
tr("-- Encrypted Event (No keys found for decryption) --", |
||||
"Placeholder, when the message was not decrypted yet or can't be decrypted.") |
||||
.toStdString(); |
||||
|
||||
auto asCacheEntry = [&idx](mtx::events::collections::TimelineEvents &&event) { |
||||
auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(event)); |
||||
decryptedEvents_.insert(idx, event_ptr); |
||||
return event_ptr; |
||||
}; |
||||
|
||||
try { |
||||
if (!cache::client()->inboundMegolmSessionExists(index)) { |
||||
nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})", |
||||
index.room_id, |
||||
index.session_id, |
||||
e.sender); |
||||
// TODO: request megolm session_id & session_key from the sender.
|
||||
return asCacheEntry(std::move(dummy)); |
||||
} |
||||
} catch (const lmdb::error &e) { |
||||
nhlog::db()->critical("failed to check megolm session's existence: {}", e.what()); |
||||
dummy.content.body = tr("-- Decryption Error (failed to communicate with DB) --", |
||||
"Placeholder, when the message can't be decrypted, because " |
||||
"the DB access failed when trying to lookup the session.") |
||||
.toStdString(); |
||||
return asCacheEntry(std::move(dummy)); |
||||
} |
||||
|
||||
std::string msg_str; |
||||
try { |
||||
auto session = cache::client()->getInboundMegolmSession(index); |
||||
auto res = olm::client()->decrypt_group_message(session, e.content.ciphertext); |
||||
msg_str = std::string((char *)res.data.data(), res.data.size()); |
||||
} catch (const lmdb::error &e) { |
||||
nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})", |
||||
index.room_id, |
||||
index.session_id, |
||||
index.sender_key, |
||||
e.what()); |
||||
dummy.content.body = |
||||
tr("-- Decryption Error (failed to retrieve megolm keys from db) --", |
||||
"Placeholder, when the message can't be decrypted, because the DB access " |
||||
"failed.") |
||||
.toStdString(); |
||||
return asCacheEntry(std::move(dummy)); |
||||
} catch (const mtx::crypto::olm_exception &e) { |
||||
nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}", |
||||
index.room_id, |
||||
index.session_id, |
||||
index.sender_key, |
||||
e.what()); |
||||
dummy.content.body = |
||||
tr("-- Decryption Error (%1) --", |
||||
"Placeholder, when the message can't be decrypted. In this case, the Olm " |
||||
"decrytion returned an error, which is passed as %1.") |
||||
.arg(e.what()) |
||||
.toStdString(); |
||||
return asCacheEntry(std::move(dummy)); |
||||
} |
||||
|
||||
// Add missing fields for the event.
|
||||
json body = json::parse(msg_str); |
||||
body["event_id"] = e.event_id; |
||||
body["sender"] = e.sender; |
||||
body["origin_server_ts"] = e.origin_server_ts; |
||||
body["unsigned"] = e.unsigned_data; |
||||
|
||||
// relations are unencrypted in content...
|
||||
if (json old_ev = e; old_ev["content"].count("m.relates_to") != 0) |
||||
body["content"]["m.relates_to"] = old_ev["content"]["m.relates_to"]; |
||||
|
||||
json event_array = json::array(); |
||||
event_array.push_back(body); |
||||
|
||||
std::vector<mtx::events::collections::TimelineEvents> temp_events; |
||||
mtx::responses::utils::parse_timeline_events(event_array, temp_events); |
||||
|
||||
if (temp_events.size() == 1) { |
||||
auto encInfo = mtx::accessors::file(temp_events[0]); |
||||
|
||||
if (encInfo) |
||||
emit newEncryptedImage(encInfo.value()); |
||||
|
||||
return asCacheEntry(std::move(temp_events[0])); |
||||
} |
||||
|
||||
dummy.content.body = |
||||
tr("-- Encrypted Event (Unknown event type) --", |
||||
"Placeholder, when the message was decrypted, but we couldn't parse it, because " |
||||
"Nheko/mtxclient don't support that event type yet.") |
||||
.toStdString(); |
||||
return asCacheEntry(std::move(dummy)); |
||||
} |
||||
|
||||
mtx::events::collections::TimelineEvents * |
||||
EventStore::event(std::string_view id, std::string_view related_to, bool decrypt) |
||||
{ |
||||
if (this->thread() != QThread::currentThread()) |
||||
nhlog::db()->warn("{} called from a different thread!", __func__); |
||||
|
||||
if (id.empty()) |
||||
return nullptr; |
||||
|
||||
IdIndex index{room_id_, std::string(id.data(), id.size())}; |
||||
|
||||
auto event_ptr = events_by_id_.object(index); |
||||
if (!event_ptr) { |
||||
auto event = cache::client()->getEvent(room_id_, index.id); |
||||
if (!event) { |
||||
// fetch
|
||||
(void)related_to; |
||||
return nullptr; |
||||
} |
||||
event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data)); |
||||
events_by_id_.insert(index, event_ptr); |
||||
} |
||||
|
||||
if (decrypt) |
||||
if (auto encrypted = |
||||
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( |
||||
event_ptr)) |
||||
return decryptEvent(index, *encrypted); |
||||
|
||||
return event_ptr; |
||||
} |
@ -0,0 +1,98 @@ |
||||
#pragma once |
||||
|
||||
#include <limits> |
||||
#include <string> |
||||
|
||||
#include <QCache> |
||||
#include <QObject> |
||||
#include <qhashfunctions.h> |
||||
|
||||
#include <mtx/events/collections.hpp> |
||||
#include <mtx/responses/sync.hpp> |
||||
|
||||
class EventStore : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
EventStore(std::string room_id, QObject *parent); |
||||
|
||||
struct Index |
||||
{ |
||||
std::string room; |
||||
int64_t idx; |
||||
|
||||
friend uint qHash(const Index &i, uint seed = 0) noexcept |
||||
{ |
||||
QtPrivate::QHashCombine hash; |
||||
seed = hash(seed, QByteArray::fromRawData(i.room.data(), i.room.size())); |
||||
seed = hash(seed, i.idx); |
||||
return seed; |
||||
} |
||||
|
||||
friend bool operator==(const Index &a, const Index &b) noexcept |
||||
{ |
||||
return a.idx == b.idx && a.room == b.room; |
||||
} |
||||
}; |
||||
struct IdIndex |
||||
{ |
||||
std::string room, id; |
||||
|
||||
friend uint qHash(const IdIndex &i, uint seed = 0) noexcept |
||||
{ |
||||
QtPrivate::QHashCombine hash; |
||||
seed = hash(seed, QByteArray::fromRawData(i.room.data(), i.room.size())); |
||||
seed = hash(seed, QByteArray::fromRawData(i.id.data(), i.id.size())); |
||||
return seed; |
||||
} |
||||
|
||||
friend bool operator==(const IdIndex &a, const IdIndex &b) noexcept |
||||
{ |
||||
return a.id == b.id && a.room == b.room; |
||||
} |
||||
}; |
||||
|
||||
void fetchMore(); |
||||
void handleSync(const mtx::responses::Timeline &events); |
||||
|
||||
// optionally returns the event or nullptr and fetches it, after which it emits a
|
||||
// relatedFetched event
|
||||
mtx::events::collections::TimelineEvents *event(std::string_view id, |
||||
std::string_view related_to, |
||||
bool decrypt = true); |
||||
// always returns a proper event as long as the idx is valid
|
||||
mtx::events::collections::TimelineEvents *event(int idx, bool decrypt = true); |
||||
|
||||
int size() const |
||||
{ |
||||
return last != std::numeric_limits<int64_t>::max() |
||||
? static_cast<int>(last - first) + 1 |
||||
: 0; |
||||
} |
||||
int toExternalIdx(int64_t idx) const { return static_cast<int>(idx - first); } |
||||
int64_t toInternalIdx(int idx) const { return first + idx; } |
||||
|
||||
std::optional<int> idToIndex(std::string_view id) const; |
||||
std::optional<std::string> indexToId(int idx) const; |
||||
|
||||
signals: |
||||
void beginInsertRows(int from, int to); |
||||
void endInsertRows(); |
||||
void dataChanged(int from, int to); |
||||
void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo); |
||||
|
||||
private: |
||||
mtx::events::collections::TimelineEvents *decryptEvent( |
||||
const IdIndex &idx, |
||||
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e); |
||||
|
||||
std::string room_id_; |
||||
|
||||
int64_t first = std::numeric_limits<int64_t>::max(), |
||||
last = std::numeric_limits<int64_t>::max(); |
||||
|
||||
static QCache<IdIndex, mtx::events::collections::TimelineEvents> decryptedEvents_; |
||||
static QCache<Index, mtx::events::collections::TimelineEvents> events_; |
||||
static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_; |
||||
}; |
Loading…
Reference in new issue