summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-06-28 16:16:43 +0300
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-06-28 16:17:39 +0300
commit4344b6964f63921aa300112bc3b62fdbaa64866a (patch)
tree9fdcad21eada05c41b31acc37162b681a10af5f0 /src
parentUpgrade matrix-structs & mtxclient (diff)
downloadnheko-4344b6964f63921aa300112bc3b62fdbaa64866a.tar.xz
Save timeline messages in cache for faster startup times
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cc121
-rw-r--r--src/ChatPage.cc41
-rw-r--r--src/MainWindow.cc3
-rw-r--r--src/RoomList.cc8
-rw-r--r--src/Utils.cc36
-rw-r--r--src/timeline/TimelineView.cc5
-rw-r--r--src/timeline/TimelineViewManager.cc22
7 files changed, 196 insertions, 40 deletions
diff --git a/src/Cache.cc b/src/Cache.cc

index ed4194ec..a276f554 100644 --- a/src/Cache.cc +++ b/src/Cache.cc
@@ -21,8 +21,10 @@ #include <QByteArray> #include <QFile> #include <QHash> +#include <QSettings> #include <QStandardPaths> +#include <mtx/responses/common.hpp> #include <variant.hpp> #include "Cache.h" @@ -38,6 +40,8 @@ static const lmdb::val NEXT_BATCH_KEY("next_batch"); static const lmdb::val OLM_ACCOUNT_KEY("olm_account"); static const lmdb::val CACHE_FORMAT_VERSION_KEY("cache_format_version"); +constexpr size_t MAX_RESTORED_MESSAGES = 30; + //! Cache databases and their format. //! //! Contains UI information for the joined rooms. (i.e name, topic, avatar url etc). @@ -85,6 +89,7 @@ init(const QString &user_id) qRegisterMetaType<RoomInfo>(); qRegisterMetaType<QMap<QString, RoomInfo>>(); qRegisterMetaType<std::map<QString, RoomInfo>>(); + qRegisterMetaType<std::map<QString, mtx::responses::Timeline>>(); instance_ = std::make_unique<Cache>(user_id); } @@ -744,6 +749,8 @@ Cache::saveState(const mtx::responses::Sync &res) saveStateEvents(txn, statesdb, membersdb, room.first, room.second.state.events); saveStateEvents(txn, statesdb, membersdb, room.first, room.second.timeline.events); + saveTimelineMessages(txn, room.first, room.second.timeline); + RoomInfo updatedInfo; updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString(); updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString(); @@ -944,6 +951,57 @@ Cache::getRoomInfo(const std::vector<std::string> &rooms) return room_info; } +std::map<QString, mtx::responses::Timeline> +Cache::roomMessages() +{ + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + + std::map<QString, mtx::responses::Timeline> msgs; + std::string room_id, unused; + + auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); + while (roomsCursor.get(room_id, unused, MDB_NEXT)) + msgs.emplace(QString::fromStdString(room_id), getTimelineMessages(txn, room_id)); + + roomsCursor.close(); + txn.commit(); + + return msgs; +} + +mtx::responses::Timeline +Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id) +{ + auto db = getMessagesDb(txn, room_id); + + mtx::responses::Timeline timeline; + std::string timestamp, msg; + + auto cursor = lmdb::cursor::open(txn, db); + + size_t index = 0; + + while (cursor.get(timestamp, msg, MDB_NEXT) && index < MAX_RESTORED_MESSAGES) { + auto obj = json::parse(msg); + + if (obj.count("event") == 0 || obj.count("token") == 0) + continue; + + mtx::events::collections::TimelineEvents event; + mtx::events::collections::from_json(obj.at("event"), event); + + index += 1; + + timeline.events.push_back(event); + timeline.prev_batch = obj.at("token").get<std::string>(); + } + cursor.close(); + + std::reverse(timeline.events.begin(), timeline.events.end()); + + return timeline; +} + QMap<QString, RoomInfo> Cache::roomInfo(bool withInvites) { @@ -959,6 +1017,8 @@ Cache::roomInfo(bool withInvites) while (roomsCursor.get(room_id, room_data, MDB_NEXT)) { RoomInfo tmp = json::parse(std::move(room_data)); tmp.member_count = getMembersDb(txn, room_id).size(txn); + tmp.msgInfo = getLastMessageInfo(txn, room_id); + result.insert(QString::fromStdString(std::move(room_id)), std::move(tmp)); } roomsCursor.close(); @@ -979,6 +1039,38 @@ Cache::roomInfo(bool withInvites) return result; } +DescInfo +Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id) +{ + auto db = getMessagesDb(txn, room_id); + + if (db.size(txn) == 0) + return DescInfo{}; + + std::string timestamp, msg; + + QSettings settings; + auto local_user = settings.value("auth/user_id").toString(); + + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(timestamp, msg, MDB_NEXT)) { + auto obj = json::parse(msg); + + if (obj.count("event") == 0) + continue; + + mtx::events::collections::TimelineEvents event; + mtx::events::collections::from_json(obj.at("event"), event); + + cursor.close(); + return utils::getMessageDescription( + event, local_user, QString::fromStdString(room_id)); + } + cursor.close(); + + return DescInfo{}; +} + std::map<QString, bool> Cache::invites() { @@ -1513,6 +1605,35 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_ } void +Cache::saveTimelineMessages(lmdb::txn &txn, + const std::string &room_id, + const mtx::responses::Timeline &res) +{ + auto db = getMessagesDb(txn, room_id); + + using namespace mtx::events; + using namespace mtx::events::state; + + for (const auto &e : res.events) { + if (isStateEvent(e)) + continue; + + if (mpark::holds_alternative<RedactionEvent<msg::Redaction>>(e)) + continue; + + json obj = json::object(); + + obj["event"] = utils::serialize_event(e); + obj["token"] = res.prev_batch; + + lmdb::dbi_put(txn, + db, + lmdb::val(std::to_string(utils::event_timestamp(e))), + lmdb::val(obj.dump())); + } +} + +void Cache::markSentNotification(const std::string &event_id) { auto txn = lmdb::txn::begin(env_); diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index cc9473e6..2b8a6b89 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc
@@ -516,23 +516,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendDesktopNotifications); - showContentTimer_ = new QTimer(this); - showContentTimer_->setSingleShot(true); - connect(showContentTimer_, &QTimer::timeout, this, [this]() { - consensusTimer_->stop(); - emit contentLoaded(); - }); - - consensusTimer_ = new QTimer(this); - connect(consensusTimer_, &QTimer::timeout, this, [this]() { - if (view_manager_->hasLoaded()) { - // Remove the spinner overlay. - emit contentLoaded(); - showContentTimer_->stop(); - consensusTimer_->stop(); - } - }); - connect(communitiesList_, &CommunitiesList::communityChanged, this, @@ -552,20 +535,15 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) this, &ChatPage::setGroupViewState); - connect(this, &ChatPage::startConsesusTimer, this, [this]() { - consensusTimer_->start(CONSENSUS_TIMEOUT); - showContentTimer_->start(SHOW_CONTENT_TIMEOUT); - }); connect(this, &ChatPage::initializeRoomList, room_list_, &RoomList::initialize); connect(this, &ChatPage::initializeViews, view_manager_, [this](const mtx::responses::Rooms &rooms) { view_manager_->initialize(rooms); }); - connect( - this, - &ChatPage::initializeEmptyViews, - this, - [this](const std::vector<std::string> &rooms) { view_manager_->initialize(rooms); }); + connect(this, + &ChatPage::initializeEmptyViews, + view_manager_, + &TimelineViewManager::initWithMessages); connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) { try { room_list_->cleanupInvites(cache::client()->invites()); @@ -817,6 +795,8 @@ ChatPage::showUnreadMessageNotification(int count) void ChatPage::loadStateFromCache() { + emit contentLoaded(); + nhlog::db()->info("restoring state from cache"); getProfileInfo(); @@ -829,8 +809,9 @@ ChatPage::loadStateFromCache() cache::client()->populateMembers(); - emit initializeEmptyViews(cache::client()->joinedRooms()); + emit initializeEmptyViews(cache::client()->roomMessages()); emit initializeRoomList(cache::client()->roomInfo()); + } catch (const mtx::crypto::olm_exception &e) { nhlog::crypto()->critical("failed to restore olm account: {}", e.what()); emit dropToLoginPageCb( @@ -841,6 +822,9 @@ ChatPage::loadStateFromCache() emit dropToLoginPageCb( tr("Failed to restore save data. Please login again.")); return; + } catch (const json::exception &e) { + nhlog::db()->critical("failed to parse cache data: {}", e.what()); + return; } nhlog::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519); @@ -848,9 +832,6 @@ ChatPage::loadStateFromCache() // Start receiving events. emit trySyncCb(); - - // Check periodically if the timelines have been loaded. - emit startConsesusTimer(); }); } diff --git a/src/MainWindow.cc b/src/MainWindow.cc
index 088bb5c0..c7c3432f 100644 --- a/src/MainWindow.cc +++ b/src/MainWindow.cc
@@ -234,7 +234,8 @@ MainWindow::showChatPage() showOverlayProgressBar(); - QTimer::singleShot(100, this, [this]() { pageStack_->setCurrentWidget(chat_page_); }); + welcome_page_->hide(); + pageStack_->setCurrentWidget(chat_page_); login_page_->reset(); chat_page_->bootstrap(userid, homeserver, token); diff --git a/src/RoomList.cc b/src/RoomList.cc
index b5bcdad6..5f094a1c 100644 --- a/src/RoomList.cc +++ b/src/RoomList.cc
@@ -15,6 +15,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <QApplication> #include <QBuffer> #include <QObject> #include <QTimer> @@ -171,6 +172,8 @@ RoomList::initialize(const QMap<QString, RoomInfo> &info) rooms_.clear(); + setUpdatesEnabled(false); + for (auto it = info.begin(); it != info.end(); it++) { if (it.value().is_invite) addInvitedRoom(it.key(), it.value()); @@ -178,6 +181,11 @@ RoomList::initialize(const QMap<QString, RoomInfo> &info) addRoom(it.key(), it.value()); } + for (auto it = info.begin(); it != info.end(); it++) + updateRoomDescription(it.key(), it.value().msgInfo); + + setUpdatesEnabled(true); + if (rooms_.empty()) return; diff --git a/src/Utils.cc b/src/Utils.cc
index 7b3574db..705a9e21 100644 --- a/src/Utils.cc +++ b/src/Utils.cc
@@ -28,13 +28,14 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUser, const QString &room_id) { - using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; - using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; - using File = mtx::events::RoomEvent<mtx::events::msg::File>; - using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; - using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; - using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; - using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; + using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; + using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; + using File = mtx::events::RoomEvent<mtx::events::msg::File>; + using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; + using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; + using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; + using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; + using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; if (mpark::holds_alternative<Audio>(event)) { return createDescriptionInfo<Audio>(event, localUser, room_id); @@ -52,6 +53,27 @@ utils::getMessageDescription(const TimelineEvent &event, return createDescriptionInfo<Video>(event, localUser, room_id); } else if (mpark::holds_alternative<mtx::events::Sticker>(event)) { return createDescriptionInfo<mtx::events::Sticker>(event, localUser, room_id); + } else if (mpark::holds_alternative<Encrypted>(event)) { + const auto msg = mpark::get<Encrypted>(event); + const auto sender = QString::fromStdString(msg.sender); + + const auto username = Cache::displayName(room_id, sender); + const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts); + + DescInfo info; + if (sender == localUser) + info.username = "You"; + else + info.username = username; + + info.userid = sender; + info.body = QString(" %1").arg(messageDescription<Encrypted>()); + info.timestamp = utils::descriptiveTime(ts); + info.datetime = ts; + + return info; + } else { + std::cout << "type not found: " << serialize_event(event).dump(2) << '\n'; } return DescInfo{}; diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index 1a329594..98ff0983 100644 --- a/src/timeline/TimelineView.cc +++ b/src/timeline/TimelineView.cc
@@ -378,7 +378,7 @@ TimelineView::renderBottomEvents(const std::vector<TimelineEvent> &events) // Prevent blocking of the event-loop // by calling processEvents every 10 items we render. - if (counter % 10 == 0) + if (counter % 4 == 0) QApplication::processEvents(); } } @@ -1035,7 +1035,8 @@ TimelineEvent TimelineView::findLastViewableEvent(const std::vector<TimelineEvent> &events) { auto it = std::find_if(events.rbegin(), events.rend(), [](const auto &event) { - return mtx::events::EventType::RoomMessage == utils::event_type(event); + return (mtx::events::EventType::RoomMessage == utils::event_type(event)) || + (mtx::events::EventType::RoomEncrypted == utils::event_type(event)); }); return (it == std::rend(events)) ? events.back() : *it; diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc
index 7ea1ee4a..dda71d2f 100644 --- a/src/timeline/TimelineViewManager.cc +++ b/src/timeline/TimelineViewManager.cc
@@ -21,6 +21,7 @@ #include <QFileInfo> #include <QSettings> +#include "Cache.h" #include "Logging.hpp" #include "timeline/TimelineView.h" #include "timeline/TimelineViewManager.h" @@ -147,6 +148,27 @@ TimelineViewManager::initialize(const mtx::responses::Rooms &rooms) } void +TimelineViewManager::initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs) +{ + for (auto it = msgs.cbegin(); it != msgs.cend(); ++it) { + if (timelineViewExists(it->first)) + return; + + // Create a history view with the room events. + TimelineView *view = new TimelineView(it->second, it->first); + views_.emplace(it->first, QSharedPointer<TimelineView>(view)); + + connect(view, + &TimelineView::updateLastTimelineMessage, + this, + &TimelineViewManager::updateRoomsLastMessage); + + // Add the view in the widget stack. + addWidget(view); + } +} + +void TimelineViewManager::initialize(const std::vector<std::string> &rooms) { for (const auto &roomid : rooms)