Newer
Older
"Failed to read member ({}) in room ({}): {}", user_id, room_id, e.what());
}
return std::nullopt;
std::vector<RoomMember>
Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
{
auto txn = ro_txn(env_);
auto db = getMembersDb(txn, room_id);
auto cursor = lmdb::cursor::open(txn, db);
const auto endIndex = std::min(startIndex + len, db.size(txn));
std::string_view user_id, user_data;
while (cursor.get(user_id, user_data, MDB_NEXT)) {
if (currentIndex < startIndex) {
currentIndex += 1;
continue;
}
if (currentIndex >= endIndex)
break;
MemberInfo tmp = nlohmann::json::parse(user_data).get<MemberInfo>();
members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)),
QString::fromStdString(tmp.name)});
} catch (const nlohmann::json::exception &e) {
cursor.close();
return members;
Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len)
auto txn = ro_txn(env_);
std::vector<RoomMember> members;
try {
auto db = getInviteMembersDb(txn, room_id);
auto cursor = lmdb::cursor::open(txn, db);
const auto endIndex = std::min(startIndex + len, db.size(txn));
std::string_view user_id, user_data;
while (cursor.get(user_id, user_data, MDB_NEXT)) {
if (currentIndex < startIndex) {
currentIndex += 1;
continue;
}
if (currentIndex >= endIndex)
break;
MemberInfo tmp = nlohmann::json::parse(user_data).get<MemberInfo>();
members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)),
QString::fromStdString(tmp.name),
tmp.is_direct});
} catch (const nlohmann::json::exception &e) {
nhlog::db()->warn("{}", e.what());
}
currentIndex += 1;
cursor.close();
} catch (const lmdb::error &e) {
nhlog::db()->warn("Failed to retrieve members {}", e.what());
bool
Cache::isRoomMember(const std::string &user_id, const std::string &room_id)
{
try {
auto txn = ro_txn(env_);
auto db = getMembersDb(txn, room_id);
std::string_view value;
bool res = db.get(txn, user_id, value);
return res;
} catch (std::exception &e) {
nhlog::db()->warn(
"Failed to read member membership ({}) in room ({}): {}", user_id, room_id, e.what());
}
return false;
}
void
Cache::savePendingMessage(const std::string &room_id,
const mtx::events::collections::TimelineEvent &message)
{
auto txn = lmdb::txn::begin(env_);
auto eventsDb = getEventsDb(txn, room_id);
mtx::responses::Timeline timeline;
timeline.events.push_back(message.data);
saveTimelineMessages(txn, eventsDb, room_id, timeline);
auto pending = getPendingMessagesDb(txn, room_id);
int64_t now = QDateTime::currentMSecsSinceEpoch();
pending.put(txn, lmdb::to_sv(now), mtx::accessors::event_id(message.data));
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
std::vector<std::string>
Cache::pendingEvents(const std::string &room_id)
{
auto txn = ro_txn(env_);
auto pending = getPendingMessagesDb(txn, room_id);
std::vector<std::string> related_ids;
try {
{
auto pendingCursor = lmdb::cursor::open(txn, pending);
std::string_view tsIgnored, pendingTxn;
while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
related_ids.emplace_back(pendingTxn.data(), pendingTxn.size());
}
}
} catch (const lmdb::error &e) {
nhlog::db()->error("pending events error: {}", e.what());
}
return related_ids;
}
std::optional<mtx::events::collections::TimelineEvent>
Cache::firstPendingMessage(const std::string &room_id)
{
auto txn = lmdb::txn::begin(env_);
auto pending = getPendingMessagesDb(txn, room_id);
{
auto pendingCursor = lmdb::cursor::open(txn, pending);
std::string_view tsIgnored, pendingTxn;
while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
auto eventsDb = getEventsDb(txn, room_id);
std::string_view event;
if (!eventsDb.get(txn, pendingTxn, event)) {
pending.del(txn, tsIgnored, pendingTxn);
continue;
}
try {
mtx::events::collections::TimelineEvent te;
mtx::events::collections::from_json(nlohmann::json::parse(event), te);
pendingCursor.close();
txn.commit();
return te;
} catch (std::exception &e) {
nhlog::db()->error("Failed to parse message from cache {}", e.what());
pending.del(txn, tsIgnored, pendingTxn);
continue;
}
}
void
Cache::removePendingStatus(const std::string &room_id, const std::string &txn_id)
{
auto txn = lmdb::txn::begin(env_);
auto pending = getPendingMessagesDb(txn, room_id);
{
auto pendingCursor = lmdb::cursor::open(txn, pending);
std::string_view tsIgnored, pendingTxn;
while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id)
lmdb::cursor_del(pendingCursor);
void
Cache::saveTimelineMessages(lmdb::txn &txn,
const std::string &room_id,
const mtx::responses::Timeline &res)
{
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
if (res.events.empty())
return;
auto relationsDb = getRelationsDb(txn, room_id);
auto orderDb = getEventOrderDb(txn, room_id);
auto evToOrderDb = getEventToOrderDb(txn, room_id);
auto msg2orderDb = getMessageToOrderDb(txn, room_id);
auto order2msgDb = getOrderToMessageDb(txn, room_id);
auto pending = getPendingMessagesDb(txn, room_id);
if (res.limited) {
lmdb::dbi_drop(txn, orderDb, false);
lmdb::dbi_drop(txn, evToOrderDb, false);
lmdb::dbi_drop(txn, msg2orderDb, false);
lmdb::dbi_drop(txn, order2msgDb, false);
lmdb::dbi_drop(txn, pending, true);
}
using namespace mtx::events;
using namespace mtx::events::state;
std::string_view indexVal, val;
uint64_t index = std::numeric_limits<uint64_t>::max() / 2;
auto cursor = lmdb::cursor::open(txn, orderDb);
if (cursor.get(indexVal, val, MDB_LAST)) {
index = lmdb::from_sv<uint64_t>(indexVal);
}
uint64_t msgIndex = std::numeric_limits<uint64_t>::max() / 2;
auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
if (msgCursor.get(indexVal, val, MDB_LAST)) {
msgIndex = lmdb::from_sv<uint64_t>(indexVal);
}
bool first = true;
for (const auto &e : res.events) {
auto event = mtx::accessors::serialize_event(e);
auto txn_id = mtx::accessors::transaction_id(e);
std::string event_id_val = event.value("event_id", "");
if (event_id_val.empty()) {
nhlog::db()->error("Event without id!");
continue;
}
std::string_view event_id = event_id_val;
nlohmann::json orderEntry = nlohmann::json::object();
orderEntry["event_id"] = event_id_val;
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
if (first && !res.prev_batch.empty())
orderEntry["prev_batch"] = res.prev_batch;
std::string_view txn_order;
if (!txn_id.empty() && evToOrderDb.get(txn, txn_id, txn_order)) {
eventsDb.put(txn, event_id, event.dump());
eventsDb.del(txn, txn_id);
std::string_view msg_txn_order;
if (msg2orderDb.get(txn, txn_id, msg_txn_order)) {
order2msgDb.put(txn, msg_txn_order, event_id);
msg2orderDb.put(txn, event_id, msg_txn_order);
msg2orderDb.del(txn, txn_id);
}
orderDb.put(txn, txn_order, orderEntry.dump());
evToOrderDb.put(txn, event_id, txn_order);
evToOrderDb.del(txn, txn_id);
auto relations = mtx::accessors::relations(e);
if (!relations.relations.empty()) {
for (const auto &r : relations.relations) {
if (!r.event_id.empty()) {
relationsDb.del(txn, r.event_id, txn_id);
relationsDb.put(txn, r.event_id, event_id);
}
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
}
auto pendingCursor = lmdb::cursor::open(txn, pending);
std::string_view tsIgnored, pendingTxn;
while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id)
lmdb::cursor_del(pendingCursor);
}
} else if (auto redaction =
std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&e)) {
if (redaction->redacts.empty())
continue;
std::string_view oldEvent;
bool success = eventsDb.get(txn, redaction->redacts, oldEvent);
if (!success)
continue;
mtx::events::collections::TimelineEvent te;
try {
mtx::events::collections::from_json(
nlohmann::json::parse(std::string_view(oldEvent.data(), oldEvent.size())), te);
// overwrite the content and add redation data
std::visit(
[redaction](auto &ev) {
ev.unsigned_data.redacted_because = *redaction;
ev.unsigned_data.redacted_by = redaction->event_id;
},
te.data);
event = mtx::accessors::serialize_event(te.data);
event["content"].clear();
} catch (std::exception &e) {
nhlog::db()->error("Failed to parse message from cache {}", e.what());
continue;
}
eventsDb.put(txn, redaction->redacts, event.dump());
eventsDb.put(txn, redaction->event_id, nlohmann::json(*redaction).dump());
// This check protects against duplicates in the timeline. If the event_id
// is already in the DB, we skip putting it (again) in ordered DBs, and only
// update the event itself and its relations.
std::string_view unused_read;
if (!evToOrderDb.get(txn, event_id, unused_read)) {
++index;
nhlog::db()->debug("saving '{}'", orderEntry.dump());
cursor.put(lmdb::to_sv(index), orderEntry.dump(), MDB_APPEND);
evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
// TODO(Nico): Allow blacklisting more event types in UI
if (!isHiddenEvent(txn, e, room_id)) {
++msgIndex;
msgCursor.put(lmdb::to_sv(msgIndex), event_id, MDB_APPEND);
msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
}
} else {
nhlog::db()->warn("duplicate event '{}'", orderEntry.dump());
}
eventsDb.put(txn, event_id, event.dump());
auto relations = mtx::accessors::relations(e);
if (!relations.relations.empty()) {
for (const auto &r : relations.relations) {
if (!r.event_id.empty()) {
relationsDb.put(txn, r.event_id, event_id);
}
uint64_t
Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res)
{
auto txn = lmdb::txn::begin(env_);
auto eventsDb = getEventsDb(txn, room_id);
auto relationsDb = getRelationsDb(txn, room_id);
auto orderDb = getEventOrderDb(txn, room_id);
auto evToOrderDb = getEventToOrderDb(txn, room_id);
auto msg2orderDb = getMessageToOrderDb(txn, room_id);
auto order2msgDb = getOrderToMessageDb(txn, room_id);
std::string_view indexVal, val;
uint64_t index = std::numeric_limits<uint64_t>::max() / 2;
{
auto cursor = lmdb::cursor::open(txn, orderDb);
if (cursor.get(indexVal, val, MDB_FIRST)) {
index = lmdb::from_sv<uint64_t>(indexVal);
uint64_t msgIndex = std::numeric_limits<uint64_t>::max() / 2;
{
auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
if (msgCursor.get(indexVal, val, MDB_FIRST)) {
msgIndex = lmdb::from_sv<uint64_t>(indexVal);
}
}
if (res.chunk.empty()) {
if (orderDb.get(txn, lmdb::to_sv(index), val)) {
auto orderEntry = nlohmann::json::parse(val);
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
orderEntry["prev_batch"] = res.end;
orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
txn.commit();
}
return index;
}
std::string event_id_val;
for (const auto &e : res.chunk) {
if (std::holds_alternative<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e))
continue;
auto event = mtx::accessors::serialize_event(e);
event_id_val = event["event_id"].get<std::string>();
std::string_view event_id = event_id_val;
// This check protects against duplicates in the timeline. If the event_id is
// already in the DB, we skip putting it (again) in ordered DBs, and only update the
// event itself and its relations.
std::string_view unused_read;
if (!evToOrderDb.get(txn, event_id, unused_read)) {
--index;
nlohmann::json orderEntry = nlohmann::json::object();
orderEntry["event_id"] = event_id_val;
orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
// TODO(Nico): Allow blacklisting more event types in UI
if (!isHiddenEvent(txn, e, room_id)) {
--msgIndex;
order2msgDb.put(txn, lmdb::to_sv(msgIndex), event_id);
msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
}
}
eventsDb.put(txn, event_id, event.dump());
auto relations = mtx::accessors::relations(e);
if (!relations.relations.empty()) {
for (const auto &r : relations.relations) {
if (!r.event_id.empty()) {
relationsDb.put(txn, r.event_id, event_id);
nlohmann::json orderEntry = nlohmann::json::object();
orderEntry["event_id"] = event_id_val;
orderEntry["prev_batch"] = res.end;
orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
void
Cache::clearTimeline(const std::string &room_id)
{
auto txn = lmdb::txn::begin(env_);
auto eventsDb = getEventsDb(txn, room_id);
auto relationsDb = getRelationsDb(txn, room_id);
auto orderDb = getEventOrderDb(txn, room_id);
auto evToOrderDb = getEventToOrderDb(txn, room_id);
auto msg2orderDb = getMessageToOrderDb(txn, room_id);
auto order2msgDb = getOrderToMessageDb(txn, room_id);
std::string_view indexVal, val;
auto cursor = lmdb::cursor::open(txn, orderDb);
bool start = true;
bool passed_pagination_token = false;
while (cursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) {
start = false;
obj = nlohmann::json::parse(std::string_view(val.data(), val.size()));
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
} catch (std::exception &) {
// workaround bug in the initial db format, where we sometimes didn't store
// json...
obj = {{"event_id", std::string(val.data(), val.size())}};
}
if (passed_pagination_token) {
if (obj.count("event_id") != 0) {
std::string event_id = obj["event_id"].get<std::string>();
if (!event_id.empty()) {
evToOrderDb.del(txn, event_id);
eventsDb.del(txn, event_id);
relationsDb.del(txn, event_id);
std::string_view order{};
bool exists = msg2orderDb.get(txn, event_id, order);
if (exists) {
order2msgDb.del(txn, order);
msg2orderDb.del(txn, event_id);
}
}
lmdb::cursor_del(cursor);
} else {
if (obj.count("prev_batch") != 0)
passed_pagination_token = true;
auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
start = true;
while (msgCursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) {
start = false;
std::string_view eventId;
bool innerStart = true;
bool found = false;
while (cursor.get(indexVal, eventId, innerStart ? MDB_LAST : MDB_PREV)) {
innerStart = false;
obj = nlohmann::json::parse(std::string_view(eventId.data(), eventId.size()));
} catch (std::exception &) {
obj = {{"event_id", std::string(eventId.data(), eventId.size())}};
}
if (obj["event_id"] == std::string(val.data(), val.size())) {
found = true;
break;
}
if (!start) {
do {
lmdb::cursor_del(msgCursor);
} while (msgCursor.get(indexVal, val, MDB_PREV));
}
cursor.close();
msgCursor.close();
txn.commit();
Cache::getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id)
if (db.size(txn) == 0) {
return mtx::responses::Notifications{};
}
mtx::responses::Notifications notif;
std::string_view event_id, msg;
auto cursor = lmdb::cursor::open(txn, db);
while (cursor.get(event_id, msg, MDB_NEXT)) {
auto obj = nlohmann::json::parse(msg);
if (obj.count("event") == 0)
continue;
mtx::responses::Notification notification;
mtx::responses::from_json(obj, notification);
notif.notifications.push_back(notification);
}
cursor.close();
std::reverse(notif.notifications.begin(), notif.notifications.end());
//! Add all notifications containing a user mention to the db.
void
Cache::saveTimelineMentions(const mtx::responses::Notifications &res)
{
QMap<std::string, QList<mtx::responses::Notification>> notifsByRoom;
// Sort into room-specific 'buckets'
for (const auto ¬if : res.notifications) {
notifsByRoom[notif.room_id].push_back(notif);
}
auto txn = lmdb::txn::begin(env_);
// Insert the entire set of mentions for each room at a time.
QMap<std::string, QList<mtx::responses::Notification>>::const_iterator it =
notifsByRoom.constBegin();
auto end = notifsByRoom.constEnd();
while (it != end) {
nhlog::db()->debug("Storing notifications for " + it.key());
saveTimelineMentions(txn, it.key(), std::move(it.value()));
++it;
}
void
Cache::saveTimelineMentions(lmdb::txn &txn,
const std::string &room_id,
using namespace mtx::events;
using namespace mtx::events::state;
for (const auto ¬if : res) {
const auto event_id = mtx::accessors::event_id(notif.event);
// double check that we have the correct room_id...
if (room_id.compare(notif.room_id) != 0) {
return;
}
db.put(txn, event_id, obj.dump());
}
void
Cache::markSentNotification(const std::string &event_id)
{
auto txn = lmdb::txn::begin(env_);
notificationsDb_.put(txn, event_id, "");
txn.commit();
}
void
Cache::removeReadNotification(const std::string &event_id)
{
}
bool
Cache::isNotificationSent(const std::string &event_id)
{
std::string_view value;
bool res = notificationsDb_.get(txn, event_id, value);
std::vector<std::string>
Cache::getRoomIds(lmdb::txn &txn)
{
auto cursor = lmdb::cursor::open(txn, roomsDb_);
std::string_view room_id, _unused;
while (cursor.get(room_id, _unused, MDB_NEXT))
rooms.emplace_back(room_id);
}
void
Cache::deleteOldMessages()
{
auto txn = lmdb::txn::begin(env_);
auto room_ids = getRoomIds(txn);
for (const auto &room_id : room_ids) {
auto orderDb = getEventOrderDb(txn, room_id);
auto evToOrderDb = getEventToOrderDb(txn, room_id);
auto o2m = getOrderToMessageDb(txn, room_id);
auto m2o = getMessageToOrderDb(txn, room_id);
auto eventsDb = getEventsDb(txn, room_id);
auto relationsDb = getRelationsDb(txn, room_id);
auto cursor = lmdb::cursor::open(txn, orderDb);
uint64_t first, last;
if (cursor.get(indexVal, val, MDB_LAST)) {
last = lmdb::from_sv<uint64_t>(indexVal);
} else {
continue;
}
if (cursor.get(indexVal, val, MDB_FIRST)) {
first = lmdb::from_sv<uint64_t>(indexVal);
} else {
continue;
}
size_t message_count = static_cast<size_t>(last - first);
if (message_count < MAX_RESTORED_MESSAGES)
continue;
bool start = true;
while (cursor.get(indexVal, val, start ? MDB_FIRST : MDB_NEXT) &&
message_count-- > MAX_RESTORED_MESSAGES) {
start = false;
auto obj = nlohmann::json::parse(std::string_view(val.data(), val.size()));
if (obj.count("event_id") != 0) {
std::string event_id = obj["event_id"].get<std::string>();
evToOrderDb.del(txn, event_id);
eventsDb.del(txn, event_id);
relationsDb.del(txn, event_id);
std::string_view order{};
bool exists = m2o.get(txn, event_id, order);
if (exists) {
o2m.del(txn, order);
m2o.del(txn, event_id);
cursor.close();
}
txn.commit();
}
void
Cache::deleteOldData() noexcept
{
try {
deleteOldMessages();
} catch (const lmdb::error &e) {
nhlog::db()->error("failed to delete old messages: {}", e.what());
}
void
Cache::updateSpaces(lmdb::txn &txn,
const std::set<std::string> &spaces_with_updates,
std::set<std::string> rooms_with_updates)
{
if (spaces_with_updates.empty() && rooms_with_updates.empty())
return;
for (const auto &space : spaces_with_updates) {
// delete old entries
{
auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_);
bool first = true;
std::string_view sp = space, space_child = "";
if (cursor.get(sp, space_child, MDB_SET)) {
while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
first = false;
spacesParentsDb_.del(txn, space_child, space);
}
cursor.close();
spacesChildrenDb_.del(txn, space);
}
for (const auto &event :
getStateEventsWithType<mtx::events::state::space::Child>(txn, space)) {
if (event.content.via.has_value() && event.state_key.size() > 3 &&
event.state_key.at(0) == '!') {
spacesChildrenDb_.put(txn, space, event.state_key);
spacesParentsDb_.put(txn, event.state_key, space);
}
for (const auto &r : getRoomIds(txn)) {
if (auto parent = getStateEvent<mtx::events::state::space::Parent>(txn, r, space)) {
rooms_with_updates.insert(r);
}
}
const auto space_event_type = to_string(mtx::events::EventType::SpaceChild);
for (const auto &room : rooms_with_updates) {
for (const auto &event :
getStateEventsWithType<mtx::events::state::space::Parent>(txn, room)) {
if (event.content.via.has_value() && event.state_key.size() > 3 &&
event.state_key.at(0) == '!') {
const std::string &space = event.state_key;
auto pls = getStateEvent<mtx::events::state::PowerLevels>(txn, space);
if (pls->content.user_level(event.sender) >=
pls->content.state_level(space_event_type)) {
spacesChildrenDb_.put(txn, space, room);
spacesParentsDb_.put(txn, room, space);
} else {
nhlog::db()->debug("Skipping {} in {} because of missing PL. {}: {} < {}",
room,
space,
event.sender,
pls->content.user_level(event.sender),
pls->content.state_level(space_event_type));
}
QMap<QString, std::optional<RoomInfo>>
Cache::spaces()
{
auto txn = ro_txn(env_);
QMap<QString, std::optional<RoomInfo>> ret;
{
auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_);
bool first = true;
std::string_view space_id, space_child;
while (cursor.get(space_id, space_child, first ? MDB_FIRST : MDB_NEXT)) {
first = false;
if (!space_child.empty()) {
std::string_view room_data;
if (roomsDb_.get(txn, space_id, room_data)) {
RoomInfo tmp = nlohmann::json::parse(std::move(room_data)).get<RoomInfo>();
ret.insert(QString::fromUtf8(space_id.data(), space_id.size()), tmp);
} else {
ret.insert(QString::fromUtf8(space_id.data(), space_id.size()), std::nullopt);
}
std::vector<std::string>
Cache::getParentRoomIds(const std::string &room_id)
{
std::vector<std::string> roomids;
{
auto cursor = lmdb::cursor::open(txn, spacesParentsDb_);
bool first = true;
std::string_view sp = room_id, space_parent;
if (cursor.get(sp, space_parent, MDB_SET)) {
while (cursor.get(sp, space_parent, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
first = false;
if (!space_parent.empty())
roomids.emplace_back(space_parent);
}
}
std::vector<std::string>
Cache::getChildRoomIds(const std::string &room_id)
{
std::vector<std::string> roomids;
{
auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_);
bool first = true;
std::string_view sp = room_id, space_child;
if (cursor.get(sp, space_child, MDB_SET)) {
while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
first = false;
if (!space_child.empty())
roomids.emplace_back(space_child);
}
std::vector<ImagePackInfo>
Cache::getImagePacks(const std::string &room_id, std::optional<bool> stickers)
auto txn = ro_txn(env_);
std::vector<ImagePackInfo> infos;
auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack,
const std::string &source_room,
const std::string &state_key) {
bool pack_is_sticker = pack.pack ? pack.pack->is_sticker() : true;
bool pack_is_emoji = pack.pack ? pack.pack->is_emoji() : true;
bool pack_matches =
!stickers.has_value() || (stickers.value() ? pack_is_sticker : pack_is_emoji);
ImagePackInfo info;
info.source_room = source_room;
info.state_key = state_key;
info.pack.pack = pack.pack;
for (const auto &img : pack.images) {
if (stickers.has_value() &&
(img.second.overrides_usage()
? (stickers.value() ? !img.second.is_sticker() : !img.second.is_emoji())
: !pack_matches))
continue;
info.pack.images.insert(img);
if (!info.pack.images.empty())
infos.push_back(std::move(info));
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
};
// packs from account data
if (auto accountpack =
getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) {
auto tmp =
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>(&*accountpack);
if (tmp)
addPack(tmp->content, "", "");
}
// packs from rooms, that were enabled globally
if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) {
auto tmp = std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
&*roomPacks);
if (tmp) {
for (const auto &[room_id2, state_to_d] : tmp->content.rooms) {
// don't add stickers from this room twice
if (room_id2 == room_id)
continue;
for (const auto &[state_id, d] : state_to_d) {
(void)d;
if (auto pack =
getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id2, state_id))
addPack(pack->content, room_id2, state_id);
// packs from current room
if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id)) {
addPack(pack->content, room_id, "");
}
for (const auto &pack : getStateEventsWithType<mtx::events::msc2545::ImagePack>(txn, room_id)) {
addPack(pack.content, room_id, pack.state_key);
}
std::optional<mtx::events::collections::RoomAccountDataEvents>
Cache::getAccountData(mtx::events::EventType type, const std::string &room_id)
{
auto txn = ro_txn(env_);
return getAccountData(txn, type, room_id);
std::optional<mtx::events::collections::RoomAccountDataEvents>
Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
{
try {
auto db = getAccountDataDb(txn, room_id);
std::string_view data;
if (db.get(txn, to_string(type), data)) {
mtx::responses::utils::RoomAccountDataEvents events;
nlohmann::json j = nlohmann::json::array({
nlohmann::json::parse(data),
});
mtx::responses::utils::parse_room_account_data_events(j, events);
if (events.size() == 1)
return events.front();
} catch (...) {
}
return std::nullopt;
bool
Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
const std::string &room_id,
const std::string &user_id)
{
using namespace mtx::events;
using namespace mtx::events::state;