diff --git a/src/Cache.cpp b/src/Cache.cpp index 90c04e61..2b83fbb5 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1570,6 +1570,47 @@ Cache::updateState(const std::string &room, const mtx::responses::StateEvents &s txn.commit(); } +namespace { +template +auto +isMessage(const mtx::events::RoomEvent &e) + -> std::enable_if_t::value, bool> +{ + return true; +} + +template +auto +isMessage(const mtx::events::Event &) +{ + return false; +} + +template +auto +isMessage(const mtx::events::EncryptedEvent &) +{ + return true; +} + +auto +isMessage(const mtx::events::RoomEvent &) +{ + return true; +} + +auto +isMessage(const mtx::events::RoomEvent &) +{ + return true; +} +auto +isMessage(const mtx::events::RoomEvent &) +{ + return true; +} +} + void Cache::saveState(const mtx::responses::Sync &res) { @@ -1623,6 +1664,25 @@ Cache::saveState(const mtx::responses::Sync &res) saveTimelineMessages(txn, eventsDb, room.first, room.second.timeline); RoomInfo updatedInfo; + { + // retrieve the old tags and modification ts + std::string_view data; + if (roomsDb_.get(txn, room.first, data)) { + try { + RoomInfo tmp = json::parse(std::string_view(data.data(), data.size())); + updatedInfo.tags = std::move(tmp.tags); + + updatedInfo.approximate_last_modification_ts = + tmp.approximate_last_modification_ts; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", + room.first, + std::string(data.data(), data.size()), + e.what()); + } + } + } + updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString(); updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString(); updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); @@ -1666,7 +1726,6 @@ Cache::saveState(const mtx::responses::Sync &res) rooms_with_space_updates.insert(room.first); } - bool has_new_tags = false; // Process the account_data associated with this room if (!room.second.account_data.events.empty()) { auto accountDataDb = getAccountDataDb(txn, room.first); @@ -1691,7 +1750,8 @@ Cache::saveState(const mtx::responses::Sync &res) // for tag events if (std::holds_alternative>(evt)) { auto tags_evt = std::get>(evt); - has_new_tags = true; + + updatedInfo.tags.clear(); for (const auto &tag : tags_evt.content.tags) { updatedInfo.tags.push_back(tag.first); } @@ -1704,20 +1764,12 @@ Cache::saveState(const mtx::responses::Sync &res) } } } - if (!has_new_tags) { - // retrieve the old tags, they haven't changed - std::string_view data; - if (roomsDb_.get(txn, room.first, data)) { - try { - RoomInfo tmp = json::parse(std::string_view(data.data(), data.size())); - updatedInfo.tags = tmp.tags; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", - room.first, - std::string(data.data(), data.size()), - e.what()); - } - } + + for (const auto &e : room.second.timeline.events) { + if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, e)) + continue; + updatedInfo.approximate_last_modification_ts = + mtx::accessors::origin_server_ts(e).toMSecsSinceEpoch(); } roomsDb_.put(txn, room.first, json(updatedInfo).dump()); @@ -4709,6 +4761,8 @@ to_json(json &j, const RoomInfo &info) j["join_rule"] = info.join_rule; j["guest_access"] = info.guest_access; + j["app_l_ts"] = info.approximate_last_modification_ts; + j["notification_count"] = info.notification_count; j["highlight_count"] = info.highlight_count; @@ -4732,6 +4786,8 @@ from_json(const json &j, RoomInfo &info) info.join_rule = j.at("join_rule"); info.guest_access = j.at("guest_access"); + info.approximate_last_modification_ts = j.value("app_l_ts", 0); + info.notification_count = j.value("notification_count", 0); info.highlight_count = j.value("highlight_count", 0); diff --git a/src/CacheStructs.h b/src/CacheStructs.h index b3917058..43055145 100644 --- a/src/CacheStructs.h +++ b/src/CacheStructs.h @@ -49,7 +49,7 @@ struct DescInfo QString userid; QString body; QString descriptiveTime; - uint64_t timestamp; + uint64_t timestamp = 0; QDateTime datetime; }; @@ -89,6 +89,10 @@ struct RoomInfo //! The list of tags associated with this room std::vector tags; + //! An approximate timestamp of when the last message was sent in the room. + //! Use the TimelineModel::lastMessage for an accurate timestamp. + uint64_t approximate_last_modification_ts = 0; + uint16_t highlight_count = 0; uint16_t notification_count = 0; }; diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 1e0b4c23..972f061d 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -121,7 +121,7 @@ RoomlistModel::data(const QModelIndex &index, int role) const case Roles::Time: return room->lastMessage().descriptiveTime; case Roles::Timestamp: - return QVariant{static_cast(room->lastMessage().timestamp)}; + return QVariant{static_cast(room->lastMessageTimestamp())}; case Roles::HasUnreadMessages: return this->roomReadStatus.count(roomid) && this->roomReadStatus.at(roomid); case Roles::HasLoudNotification: @@ -333,7 +333,7 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification) emit totalUnreadMessageCountUpdated(total_unread_msgs); }); - newRoom->updateLastMessage(); + // newRoom->updateLastMessage(); std::vector previewsToAdd; if (newRoom->isSpace()) { diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 142ca793..578d63b7 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -353,14 +353,13 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , manager_(manager) , permissions_{room_id_} { - lastMessage_.timestamp = 0; - this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); auto roomInfo = cache::singleRoomInfo(room_id_.toStdString()); this->isSpace_ = roomInfo.is_space; this->notification_count = roomInfo.notification_count; this->highlight_count = roomInfo.highlight_count; + lastMessage_.timestamp = roomInfo.approximate_last_modification_ts; // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it // needs to be @@ -1025,10 +1024,21 @@ isYourJoin(const mtx::events::Event &) return false; } +DescInfo +TimelineModel::lastMessage() const +{ + if (lastMessage_.event_id.isEmpty()) + QTimer::singleShot(0, this, &TimelineModel::updateLastMessage); + + return lastMessage_; +} + void TimelineModel::updateLastMessage() { - for (auto it = events.size() - 1; it >= 0; --it) { + // only try to generate a preview for the last 1000 messages + auto end = std::max(events.size() - 1001, 0); + for (auto it = events.size() - 1; it >= end; --it) { auto event = events.get(it, decryptDescription); if (!event) continue; diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index dae64094..ec9a34f1 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -311,7 +311,9 @@ public: void sendMessageEvent(const T &content, mtx::events::EventType eventType); RelatedInfo relatedInfo(const QString &id); - DescInfo lastMessage() const { return lastMessage_; } + DescInfo lastMessage() const; + uint64_t lastMessageTimestamp() const { return lastMessage_.timestamp; } + bool isSpace() const { return isSpace_; } bool isEncrypted() const { return isEncrypted_; } crypto::Trust trustlevel() const;