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)
|