diff --git a/src/Cache.cpp b/src/Cache.cpp
index 738f1152..97e99700 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -318,52 +318,67 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
auto txn = lmdb::txn::begin(env_);
lmdb::dbi_put(txn, inboundMegolmSessionDb_, lmdb::val(key), lmdb::val(pickled));
txn.commit();
-
- {
- std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
- session_storage.group_inbound_sessions[key] = std::move(session);
- }
}
-OlmInboundGroupSession *
+mtx::crypto::InboundGroupSessionPtr
Cache::getInboundMegolmSession(const MegolmSessionIndex &index)
{
- std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
- return session_storage.group_inbound_sessions[json(index).dump()].get();
+ using namespace mtx::crypto;
+
+ try {
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ std::string key = json(index).dump();
+ lmdb::val value;
+
+ if (lmdb::dbi_get(txn, inboundMegolmSessionDb_, lmdb::val(key), value)) {
+ auto session = unpickle<InboundSessionObject>(
+ std::string(value.data(), value.size()), SECRET);
+ return session;
+ }
+ } catch (std::exception &e) {
+ nhlog::db()->error("Failed to get inbound megolm session {}", e.what());
+ }
+
+ return nullptr;
}
bool
Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index)
{
- std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
- return session_storage.group_inbound_sessions.find(json(index).dump()) !=
- session_storage.group_inbound_sessions.end();
+ using namespace mtx::crypto;
+
+ try {
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ std::string key = json(index).dump();
+ lmdb::val value;
+
+ return lmdb::dbi_get(txn, inboundMegolmSessionDb_, lmdb::val(key), value);
+ } catch (std::exception &e) {
+ nhlog::db()->error("Failed to get inbound megolm session {}", e.what());
+ }
+
+ return false;
}
void
-Cache::updateOutboundMegolmSession(const std::string &room_id, int message_index)
+Cache::updateOutboundMegolmSession(const std::string &room_id,
+ const OutboundGroupSessionData &data_,
+ mtx::crypto::OutboundGroupSessionPtr &ptr)
{
using namespace mtx::crypto;
if (!outboundMegolmSessionExists(room_id))
return;
- OutboundGroupSessionData data;
- OlmOutboundGroupSession *session;
- {
- std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
- data = session_storage.group_outbound_session_data[room_id];
- session = session_storage.group_outbound_sessions[room_id].get();
-
- // Update with the current message.
- data.message_index = message_index;
- session_storage.group_outbound_session_data[room_id] = data;
- }
+ OutboundGroupSessionData data = data_;
+ data.message_index = olm_outbound_group_session_message_index(ptr.get());
+ data.session_id = mtx::crypto::session_id(ptr.get());
+ data.session_key = mtx::crypto::session_key(ptr.get());
// Save the updated pickled data for the session.
json j;
j["data"] = data;
- j["session"] = pickle<OutboundSessionObject>(session, SECRET);
+ j["session"] = pickle<OutboundSessionObject>(ptr.get(), SECRET);
auto txn = lmdb::txn::begin(env_);
lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(room_id), lmdb::val(j.dump()));
@@ -379,10 +394,6 @@ Cache::dropOutboundMegolmSession(const std::string &room_id)
return;
{
- std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
- session_storage.group_outbound_session_data.erase(room_id);
- session_storage.group_outbound_sessions.erase(room_id);
-
auto txn = lmdb::txn::begin(env_);
lmdb::dbi_del(txn, outboundMegolmSessionDb_, lmdb::val(room_id), nullptr);
txn.commit();
@@ -392,7 +403,7 @@ Cache::dropOutboundMegolmSession(const std::string &room_id)
void
Cache::saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data,
- mtx::crypto::OutboundGroupSessionPtr session)
+ mtx::crypto::OutboundGroupSessionPtr &session)
{
using namespace mtx::crypto;
const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET);
@@ -404,30 +415,40 @@ Cache::saveOutboundMegolmSession(const std::string &room_id,
auto txn = lmdb::txn::begin(env_);
lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(room_id), lmdb::val(j.dump()));
txn.commit();
-
- {
- std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
- session_storage.group_outbound_session_data[room_id] = data;
- session_storage.group_outbound_sessions[room_id] = std::move(session);
- }
}
bool
Cache::outboundMegolmSessionExists(const std::string &room_id) noexcept
{
- std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
- return (session_storage.group_outbound_sessions.find(room_id) !=
- session_storage.group_outbound_sessions.end()) &&
- (session_storage.group_outbound_session_data.find(room_id) !=
- session_storage.group_outbound_session_data.end());
+ try {
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ lmdb::val value;
+ return lmdb::dbi_get(txn, outboundMegolmSessionDb_, lmdb::val(room_id), value);
+ } catch (std::exception &e) {
+ nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
+ return false;
+ }
}
OutboundGroupSessionDataRef
Cache::getOutboundMegolmSession(const std::string &room_id)
{
- std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
- return OutboundGroupSessionDataRef{session_storage.group_outbound_sessions[room_id].get(),
- session_storage.group_outbound_session_data[room_id]};
+ try {
+ using namespace mtx::crypto;
+
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ lmdb::val value;
+ lmdb::dbi_get(txn, outboundMegolmSessionDb_, lmdb::val(room_id), value);
+ auto obj = json::parse(std::string_view(value.data(), value.size()));
+
+ OutboundGroupSessionDataRef ref{};
+ ref.data = obj.at("data").get<OutboundGroupSessionData>();
+ ref.session = unpickle<OutboundSessionObject>(obj.at("session"), SECRET);
+ return ref;
+ } catch (std::exception &e) {
+ nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
+ return {};
+ }
}
//
@@ -537,56 +558,6 @@ Cache::saveOlmAccount(const std::string &data)
txn.commit();
}
-void
-Cache::restoreSessions()
-{
- using namespace mtx::crypto;
-
- auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
- std::string key, value;
-
- //
- // Inbound Megolm Sessions
- //
- {
- auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_);
- while (cursor.get(key, value, MDB_NEXT)) {
- auto session = unpickle<InboundSessionObject>(value, SECRET);
- session_storage.group_inbound_sessions[key] = std::move(session);
- }
- cursor.close();
- }
-
- //
- // Outbound Megolm Sessions
- //
- {
- auto cursor = lmdb::cursor::open(txn, outboundMegolmSessionDb_);
- while (cursor.get(key, value, MDB_NEXT)) {
- json obj;
-
- try {
- obj = json::parse(value);
-
- session_storage.group_outbound_session_data[key] =
- obj.at("data").get<OutboundGroupSessionData>();
-
- auto session =
- unpickle<OutboundSessionObject>(obj.at("session"), SECRET);
- session_storage.group_outbound_sessions[key] = std::move(session);
- } catch (const nlohmann::json::exception &e) {
- nhlog::db()->critical(
- "failed to parse outbound megolm session data: {}", e.what());
- }
- }
- cursor.close();
- }
-
- txn.commit();
-
- nhlog::db()->info("sessions restored");
-}
-
std::string
Cache::restoreOlmAccount()
{
@@ -3125,6 +3096,39 @@ Cache::roomMembers(const std::string &room_id)
return members;
}
+std::map<std::string, std::optional<UserKeyCache>>
+Cache::getMembersWithKeys(const std::string &room_id)
+{
+ lmdb::val keys;
+
+ try {
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ std::map<std::string, std::optional<UserKeyCache>> members;
+
+ auto db = getMembersDb(txn, room_id);
+ auto keysDb = getUserKeysDb(txn);
+
+ std::string user_id, unused;
+ auto cursor = lmdb::cursor::open(txn, db);
+ while (cursor.get(user_id, unused, MDB_NEXT)) {
+ auto res = lmdb::dbi_get(txn, keysDb, lmdb::val(user_id), keys);
+
+ if (res) {
+ members[user_id] =
+ json::parse(std::string_view(keys.data(), keys.size()))
+ .get<UserKeyCache>();
+ } else {
+ members[user_id] = {};
+ }
+ }
+ cursor.close();
+
+ return members;
+ } catch (std::exception &) {
+ return {};
+ }
+}
+
QString
Cache::displayName(const QString &room_id, const QString &user_id)
{
@@ -3265,6 +3269,8 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
updates[user].self_signing_keys = keys;
for (auto &[user, update] : updates) {
+ nhlog::db()->debug("Updated user keys: {}", user);
+
lmdb::val oldKeys;
auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys);
@@ -3327,6 +3333,8 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn,
query.token = sync_token;
for (const auto &user : user_ids) {
+ nhlog::db()->debug("Marking user keys out of date: {}", user);
+
lmdb::val oldKeys;
auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys);
@@ -3681,11 +3689,40 @@ from_json(const json &j, MemberInfo &info)
}
void
+to_json(nlohmann::json &obj, const DeviceAndMasterKeys &msg)
+{
+ obj["devices"] = msg.devices;
+ obj["master_keys"] = msg.master_keys;
+}
+
+void
+from_json(const nlohmann::json &obj, DeviceAndMasterKeys &msg)
+{
+ msg.devices = obj.at("devices").get<decltype(msg.devices)>();
+ msg.master_keys = obj.at("master_keys").get<decltype(msg.master_keys)>();
+}
+
+void
+to_json(nlohmann::json &obj, const SharedWithUsers &msg)
+{
+ obj["keys"] = msg.keys;
+}
+
+void
+from_json(const nlohmann::json &obj, SharedWithUsers &msg)
+{
+ msg.keys = obj.at("keys").get<std::map<std::string, DeviceAndMasterKeys>>();
+}
+
+void
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
{
obj["session_id"] = msg.session_id;
obj["session_key"] = msg.session_key;
obj["message_index"] = msg.message_index;
+
+ obj["initially"] = msg.initially;
+ obj["currently"] = msg.currently;
}
void
@@ -3694,6 +3731,9 @@ from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
msg.session_id = obj.at("session_id");
msg.session_key = obj.at("session_key");
msg.message_index = obj.at("message_index");
+
+ msg.initially = obj.value("initially", SharedWithUsers{});
+ msg.currently = obj.value("currently", SharedWithUsers{});
}
void
@@ -4128,9 +4168,9 @@ isRoomMember(const std::string &user_id, const std::string &room_id)
void
saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data,
- mtx::crypto::OutboundGroupSessionPtr session)
+ mtx::crypto::OutboundGroupSessionPtr &session)
{
- instance_->saveOutboundMegolmSession(room_id, data, std::move(session));
+ instance_->saveOutboundMegolmSession(room_id, data, session);
}
OutboundGroupSessionDataRef
getOutboundMegolmSession(const std::string &room_id)
@@ -4143,9 +4183,11 @@ outboundMegolmSessionExists(const std::string &room_id) noexcept
return instance_->outboundMegolmSessionExists(room_id);
}
void
-updateOutboundMegolmSession(const std::string &room_id, int message_index)
+updateOutboundMegolmSession(const std::string &room_id,
+ const OutboundGroupSessionData &data,
+ mtx::crypto::OutboundGroupSessionPtr &session)
{
- instance_->updateOutboundMegolmSession(room_id, message_index);
+ instance_->updateOutboundMegolmSession(room_id, data, session);
}
void
dropOutboundMegolmSession(const std::string &room_id)
@@ -4173,7 +4215,7 @@ saveInboundMegolmSession(const MegolmSessionIndex &index,
{
instance_->saveInboundMegolmSession(index, std::move(session));
}
-OlmInboundGroupSession *
+mtx::crypto::InboundGroupSessionPtr
getInboundMegolmSession(const MegolmSessionIndex &index)
{
return instance_->getInboundMegolmSession(index);
@@ -4220,10 +4262,4 @@ restoreOlmAccount()
{
return instance_->restoreOlmAccount();
}
-
-void
-restoreSessions()
-{
- return instance_->restoreSessions();
-}
} // namespace cache
diff --git a/src/Cache.h b/src/Cache.h
index 8cbb0006..f38f1960 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -235,13 +235,15 @@ isRoomMember(const std::string &user_id, const std::string &room_id);
void
saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data,
- mtx::crypto::OutboundGroupSessionPtr session);
+ mtx::crypto::OutboundGroupSessionPtr &session);
OutboundGroupSessionDataRef
getOutboundMegolmSession(const std::string &room_id);
bool
outboundMegolmSessionExists(const std::string &room_id) noexcept;
void
-updateOutboundMegolmSession(const std::string &room_id, int message_index);
+updateOutboundMegolmSession(const std::string &room_id,
+ const OutboundGroupSessionData &data,
+ mtx::crypto::OutboundGroupSessionPtr &session);
void
dropOutboundMegolmSession(const std::string &room_id);
@@ -256,7 +258,7 @@ exportSessionKeys();
void
saveInboundMegolmSession(const MegolmSessionIndex &index,
mtx::crypto::InboundGroupSessionPtr session);
-OlmInboundGroupSession *
+mtx::crypto::InboundGroupSessionPtr
getInboundMegolmSession(const MegolmSessionIndex &index);
bool
inboundMegolmSessionExists(const MegolmSessionIndex &index);
@@ -277,9 +279,7 @@ getLatestOlmSession(const std::string &curve25519);
void
saveOlmAccount(const std::string &pickled);
+
std::string
restoreOlmAccount();
-
-void
-restoreSessions();
}
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index 6256dcf9..eb2cc445 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -6,12 +6,28 @@
#include <mtx/responses/crypto.hpp>
#include <mtxclient/crypto/objects.hpp>
+struct DeviceAndMasterKeys
+{
+ // map from device id or master key id to message_index
+ std::map<std::string, uint64_t> devices, master_keys;
+};
+
+struct SharedWithUsers
+{
+ // userid to keys
+ std::map<std::string, DeviceAndMasterKeys> keys;
+};
+
// Extra information associated with an outbound megolm session.
struct OutboundGroupSessionData
{
std::string session_id;
std::string session_key;
uint64_t message_index = 0;
+
+ // who has access to this session.
+ // Rotate, when a user leaves the room and share, when a user gets added.
+ SharedWithUsers initially, currently;
};
void
@@ -21,7 +37,7 @@ from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg);
struct OutboundGroupSessionDataRef
{
- OlmOutboundGroupSession *session;
+ mtx::crypto::OutboundGroupSessionPtr session;
OutboundGroupSessionData data;
};
@@ -52,18 +68,6 @@ to_json(nlohmann::json &obj, const MegolmSessionIndex &msg);
void
from_json(const nlohmann::json &obj, MegolmSessionIndex &msg);
-struct OlmSessionStorage
-{
- // Megolm sessions
- std::map<std::string, mtx::crypto::InboundGroupSessionPtr> group_inbound_sessions;
- std::map<std::string, mtx::crypto::OutboundGroupSessionPtr> group_outbound_sessions;
- std::map<std::string, OutboundGroupSessionData> group_outbound_session_data;
-
- // Guards for accessing megolm sessions.
- std::mutex group_outbound_mtx;
- std::mutex group_inbound_mtx;
-};
-
struct StoredOlmSession
{
std::uint64_t last_message_ts = 0;
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 9c919fb5..fab2d964 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -59,6 +59,8 @@ public:
// user cache stores user keys
std::optional<UserKeyCache> userKeys(const std::string &user_id);
+ std::map<std::string, std::optional<UserKeyCache>> getMembersWithKeys(
+ const std::string &room_id);
void updateUserKeys(const std::string &sync_token,
const mtx::responses::QueryKeys &keyQuery);
void markUserKeysOutOfDate(lmdb::txn &txn,
@@ -232,10 +234,12 @@ public:
//
void saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data,
- mtx::crypto::OutboundGroupSessionPtr session);
+ mtx::crypto::OutboundGroupSessionPtr &session);
OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
- void updateOutboundMegolmSession(const std::string &room_id, int message_index);
+ void updateOutboundMegolmSession(const std::string &room_id,
+ const OutboundGroupSessionData &data,
+ mtx::crypto::OutboundGroupSessionPtr &session);
void dropOutboundMegolmSession(const std::string &room_id);
void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
@@ -246,7 +250,8 @@ public:
//
void saveInboundMegolmSession(const MegolmSessionIndex &index,
mtx::crypto::InboundGroupSessionPtr session);
- OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
+ mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(
+ const MegolmSessionIndex &index);
bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
//
@@ -264,8 +269,6 @@ public:
void saveOlmAccount(const std::string &pickled);
std::string restoreOlmAccount();
- void restoreSessions();
-
signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status);
@@ -577,7 +580,6 @@ private:
QString localUserId_;
QString cacheDirectory_;
- OlmSessionStorage session_storage;
VerificationStorage verification_storage;
};
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index cb5f242f..dab414a9 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -526,7 +526,6 @@ ChatPage::loadStateFromCache()
nhlog::db()->info("restoring state from cache");
try {
- cache::restoreSessions();
olm::client()->load(cache::restoreOlmAccount(), STORAGE_SECRET_KEY);
emit initializeEmptyViews(cache::client()->roomIds());
diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp
index ac625db1..8076d6d6 100644
--- a/src/LoginPage.cpp
+++ b/src/LoginPage.cpp
@@ -90,6 +90,7 @@ LoginPage::LoginPage(QWidget *parent)
matrixid_input_ = new TextField(this);
matrixid_input_->setLabel(tr("Matrix ID"));
+ matrixid_input_->setRegexp(QRegularExpression("@.+?:.{3,}"));
matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
matrixid_input_->setToolTip(
tr("Your login name. A mxid should start with @ followed by the user id. After the user "
@@ -175,7 +176,6 @@ LoginPage::LoginPage(QWidget *parent)
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk);
connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError);
- connect(this, &LoginPage::loginErrorCb, this, &LoginPage::loginError);
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked()));
@@ -186,32 +186,24 @@ LoginPage::LoginPage(QWidget *parent)
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
}
-
void
-LoginPage::loginError(const QString &msg)
+LoginPage::showError(const QString &msg)
{
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
- error_label_->setFixedHeight(qCeil(width / 200) * height);
+ error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
error_label_->setText(msg);
}
void
-LoginPage::matrixIdError(const QString &msg)
-{
- error_matrixid_label_->show();
- error_matrixid_label_->setText(msg);
- matrixid_input_->setValid(false);
-}
-
-bool
-LoginPage::isMatrixIdValid()
+LoginPage::showError(QLabel *label, const QString &msg)
{
- QRegularExpressionValidator v(QRegularExpression("@.+?:.{3,}"), this);
- QString s = matrixid_input_->text();
- int pos = 0;
- return v.validate(s, pos) == QValidator::Acceptable;
+ auto rect = QFontMetrics(font()).boundingRect(msg);
+ int width = rect.width();
+ int height = rect.height();
+ label->setFixedHeight((int)qCeil(width / 200.0) * height);
+ label->setText(msg);
}
void
@@ -221,19 +213,21 @@ LoginPage::onMatrixIdEntered()
User user;
- if (!isMatrixIdValid()) {
- matrixIdError("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ if (!matrixid_input_->isValid()) {
+ error_matrixid_label_->show();
+ showError(error_matrixid_label_,
+ "You have entered an invalid Matrix ID e.g @joe:matrix.org");
return;
} else {
error_matrixid_label_->setText("");
error_matrixid_label_->hide();
- matrixid_input_->setValid(true);
}
try {
user = parse<User>(matrixid_input_->text().toStdString());
} catch (const std::exception &e) {
- matrixIdError("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ showError(error_matrixid_label_,
+ "You have entered an invalid Matrix ID e.g @joe:matrix.org");
return;
}
@@ -345,7 +339,7 @@ LoginPage::onServerAddressEntered()
void
LoginPage::versionError(const QString &error)
{
- loginError(error);
+ showError(error_label_, error);
serverInput_->show();
spinner_->stop();
@@ -383,25 +377,27 @@ LoginPage::onLoginButtonClicked()
User user;
- if (!isMatrixIdValid()) {
- matrixIdError("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ if (!matrixid_input_->isValid()) {
+ error_matrixid_label_->show();
+ showError(error_matrixid_label_,
+ "You have entered an invalid Matrix ID e.g @joe:matrix.org");
return;
} else {
error_matrixid_label_->setText("");
error_matrixid_label_->hide();
- matrixid_input_->setValid(true);
}
try {
user = parse<User>(matrixid_input_->text().toStdString());
} catch (const std::exception &e) {
- matrixIdError("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ showError(error_matrixid_label_,
+ "You have entered an invalid Matrix ID e.g @joe:matrix.org");
return;
}
if (loginMethod == LoginMethod::Password) {
if (password_input_->text().isEmpty())
- return loginError(tr("Empty password"));
+ return showError(error_label_, tr("Empty password"));
http::client()->login(
user.localpart(),
@@ -410,7 +406,8 @@ LoginPage::onLoginButtonClicked()
: deviceName_->text().toStdString(),
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
- emit loginError(QString::fromStdString(err->matrix_error.error));
+ showError(error_label_,
+ QString::fromStdString(err->matrix_error.error));
emit errorOccurred();
return;
}
@@ -435,7 +432,8 @@ LoginPage::onLoginButtonClicked()
http::client()->login(
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
- emit loginError(
+ showError(
+ error_label_,
QString::fromStdString(err->matrix_error.error));
emit errorOccurred();
return;
@@ -453,7 +451,7 @@ LoginPage::onLoginButtonClicked()
sso->deleteLater();
});
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
- emit loginError(tr("SSO login failed"));
+ showError(error_label_, tr("SSO login failed"));
emit errorOccurred();
sso->deleteLater();
});
diff --git a/src/LoginPage.h b/src/LoginPage.h
index 92b60afe..5ed21dec 100644
--- a/src/LoginPage.h
+++ b/src/LoginPage.h
@@ -56,7 +56,6 @@ signals:
//! Used to trigger the corresponding slot outside of the main thread.
void versionErrorCb(const QString &err);
- void loginErrorCb(const QString &err);
void versionOkCb(LoginPage::LoginMethod method);
void loginOk(const mtx::responses::Login &res);
@@ -66,8 +65,8 @@ protected:
public slots:
// Displays errors produced during the login.
- void loginError(const QString &msg);
- void matrixIdError(const QString &msg);
+ void showError(const QString &msg);
+ void showError(QLabel *label, const QString &msg);
private slots:
// Callback for the back button.
@@ -88,7 +87,6 @@ private slots:
void versionOk(LoginPage::LoginMethod method);
private:
- bool isMatrixIdValid();
void checkHomeserverVersion();
std::string initialDeviceName()
{
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 37b54151..60b5168b 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -108,7 +108,7 @@ MainWindow::MainWindow(const QString profile, QWidget *parent)
connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
- login_page_->loginError(msg);
+ login_page_->showError(msg);
showLoginPage();
});
diff --git a/src/Olm.cpp b/src/Olm.cpp
index cdafabf3..808279a3 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -278,11 +278,168 @@ mtx::events::msg::Encrypted
encrypt_group_message(const std::string &room_id, const std::string &device_id, nlohmann::json body)
{
using namespace mtx::events;
+ using namespace mtx::identifiers;
+
+ auto own_user_id = http::client()->user_id().to_string();
+
+ auto members = cache::client()->getMembersWithKeys(room_id);
+
+ std::map<std::string, std::vector<std::string>> sendSessionTo;
+ mtx::crypto::OutboundGroupSessionPtr session = nullptr;
+ OutboundGroupSessionData group_session_data;
+
+ if (cache::outboundMegolmSessionExists(room_id)) {
+ auto res = cache::getOutboundMegolmSession(room_id);
+
+ auto member_it = members.begin();
+ auto session_member_it = res.data.currently.keys.begin();
+ auto session_member_it_end = res.data.currently.keys.end();
+
+ while (member_it != members.end() || session_member_it != session_member_it_end) {
+ if (member_it == members.end()) {
+ // a member left, purge session!
+ nhlog::crypto()->debug(
+ "Rotating megolm session because of left member");
+ break;
+ }
+
+ if (session_member_it == session_member_it_end) {
+ // share with all remaining members
+ while (member_it != members.end()) {
+ sendSessionTo[member_it->first] = {};
+
+ if (member_it->second)
+ for (const auto &dev :
+ member_it->second->device_keys)
+ if (member_it->first != own_user_id ||
+ dev.first != device_id)
+ sendSessionTo[member_it->first]
+ .push_back(dev.first);
+
+ ++member_it;
+ }
+
+ session = std::move(res.session);
+ break;
+ }
+
+ if (member_it->first > session_member_it->first) {
+ // a member left, purge session
+ nhlog::crypto()->debug(
+ "Rotating megolm session because of left member");
+ break;
+ } else if (member_it->first < session_member_it->first) {
+ // new member, send them the session at this index
+ sendSessionTo[member_it->first] = {};
+
+ for (const auto &dev : member_it->second->device_keys)
+ if (member_it->first != own_user_id ||
+ dev.first != device_id)
+ sendSessionTo[member_it->first].push_back(
+ dev.first);
+
+ ++member_it;
+ } else {
+ // compare devices
+ bool device_removed = false;
+ for (const auto &dev : session_member_it->second.devices) {
+ if (!member_it->second ||
+ !member_it->second->device_keys.count(dev.first)) {
+ device_removed = true;
+ break;
+ }
+ }
+
+ if (device_removed) {
+ // device removed, rotate session!
+ nhlog::crypto()->debug(
+ "Rotating megolm session because of removed device of {}",
+ member_it->first);
+ break;
+ }
+
+ // check for new devices to share with
+ if (member_it->second)
+ for (const auto &dev : member_it->second->device_keys)
+ if (!session_member_it->second.devices.count(
+ dev.first) &&
+ (member_it->first != own_user_id ||
+ dev.first != device_id))
+ sendSessionTo[member_it->first].push_back(
+ dev.first);
+
+ ++member_it;
+ ++session_member_it;
+ if (member_it == members.end() &&
+ session_member_it == session_member_it_end) {
+ // all devices match or are newly added
+ session = std::move(res.session);
+ }
+ }
+ }
+
+ group_session_data = std::move(res.data);
+ }
+
+ if (!session) {
+ nhlog::ui()->debug("creating new outbound megolm session");
+
+ // Create a new outbound megolm session.
+ session = olm::client()->init_outbound_group_session();
+ const auto session_id = mtx::crypto::session_id(session.get());
+ const auto session_key = mtx::crypto::session_key(session.get());
+
+ // Saving the new megolm session.
+ OutboundGroupSessionData session_data{};
+ session_data.session_id = mtx::crypto::session_id(session.get());
+ session_data.session_key = mtx::crypto::session_key(session.get());
+ session_data.message_index = 0;
+
+ sendSessionTo.clear();
+
+ for (const auto &[user, devices] : members) {
+ sendSessionTo[user] = {};
+ session_data.initially.keys[user] = {};
+ if (devices) {
+ for (const auto &[device_id_, key] : devices->device_keys) {
+ (void)key;
+ if (device_id != device_id_ || user != own_user_id) {
+ sendSessionTo[user].push_back(device_id_);
+ session_data.initially.keys[user]
+ .devices[device_id_] = 0;
+ }
+ }
+ }
+ }
+
+ cache::saveOutboundMegolmSession(room_id, session_data, session);
+ group_session_data = std::move(session_data);
+
+ {
+ MegolmSessionIndex index;
+ index.room_id = room_id;
+ index.session_id = session_id;
+ index.sender_key = olm::client()->identity_keys().curve25519;
+ auto megolm_session =
+ olm::client()->init_inbound_group_session(session_key);
+ cache::saveInboundMegolmSession(index, std::move(megolm_session));
+ }
+ }
+
+ mtx::events::DeviceEvent<mtx::events::msg::RoomKey> megolm_payload{};
+ megolm_payload.content.algorithm = MEGOLM_ALGO;
+ megolm_payload.content.room_id = room_id;
+ megolm_payload.content.session_id = mtx::crypto::session_id(session.get());
+ megolm_payload.content.session_key = mtx::crypto::session_key(session.get());
+ megolm_payload.type = mtx::events::EventType::RoomKey;
+
+ if (!sendSessionTo.empty())
+ olm::send_encrypted_to_device_messages(sendSessionTo, megolm_payload);
- // relations shouldn't be encrypted...
mtx::common::ReplyRelatesTo relation;
mtx::common::RelatesTo r_relation;
+ // relations shouldn't be encrypted...
if (body["content"].contains("m.relates_to") &&
body["content"]["m.relates_to"].contains("m.in_reply_to")) {
relation = body["content"]["m.relates_to"];
@@ -292,25 +449,35 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
body["content"].erase("m.relates_to");
}
- // Always check before for existence.
- auto res = cache::getOutboundMegolmSession(room_id);
- auto payload = olm::client()->encrypt_group_message(res.session, body.dump());
+ auto payload = olm::client()->encrypt_group_message(session.get(), body.dump());
// Prepare the m.room.encrypted event.
msg::Encrypted data;
data.ciphertext = std::string((char *)payload.data(), payload.size());
data.sender_key = olm::client()->identity_keys().curve25519;
- data.session_id = res.data.session_id;
+ data.session_id = mtx::crypto::session_id(session.get());
data.device_id = device_id;
data.algorithm = MEGOLM_ALGO;
data.relates_to = relation;
data.r_relates_to = r_relation;
- auto message_index = olm_outbound_group_session_message_index(res.session);
- nhlog::crypto()->debug("next message_index {}", message_index);
+ group_session_data.message_index = olm_outbound_group_session_message_index(session.get());
+ nhlog::crypto()->debug("next message_index {}", group_session_data.message_index);
+
+ // update current set of members for the session with the new members and that message_index
+ for (const auto &[user, devices] : sendSessionTo) {
+ if (!group_session_data.currently.keys.count(user))
+ group_session_data.currently.keys[user] = {};
+
+ for (const auto &device_id_ : devices) {
+ if (!group_session_data.currently.keys[user].devices.count(device_id_))
+ group_session_data.currently.keys[user].devices[device_id_] =
+ group_session_data.message_index;
+ }
+ }
// We need to re-pickle the session after we send a message to save the new message_index.
- cache::updateOutboundMegolmSession(room_id, message_index);
+ cache::updateOutboundMegolmSession(room_id, group_session_data, session);
return data;
}
@@ -534,7 +701,7 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
return;
}
- auto session_key = mtx::crypto::export_session(session);
+ auto session_key = mtx::crypto::export_session(session.get());
//
// Prepare the m.room_key event.
//
@@ -584,8 +751,9 @@ decryptEvent(const MegolmSessionIndex &index,
std::string msg_str;
try {
auto session = cache::client()->getInboundMegolmSession(index);
- auto res = olm::client()->decrypt_group_message(session, event.content.ciphertext);
- msg_str = std::string((char *)res.data.data(), res.data.size());
+ auto res =
+ olm::client()->decrypt_group_message(session.get(), event.content.ciphertext);
+ msg_str = std::string((char *)res.data.data(), res.data.size());
} catch (const lmdb::error &e) {
return {DecryptionErrorCode::DbError, e.what(), std::nullopt};
} catch (const mtx::crypto::olm_exception &e) {
diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp
index b8fe93b5..26a66ab7 100644
--- a/src/RegisterPage.cpp
+++ b/src/RegisterPage.cpp
@@ -20,6 +20,7 @@
#include <QPainter>
#include <QStyleOption>
#include <QTimer>
+#include <QtMath>
#include <mtx/responses/register.hpp>
@@ -86,13 +87,13 @@ RegisterPage::RegisterPage(QWidget *parent)
username_input_ = new TextField();
username_input_->setLabel(tr("Username"));
- username_input_->setValidator(
- new QRegularExpressionValidator(QRegularExpression("[a-z0-9._=/-]+"), this));
+ username_input_->setRegexp(QRegularExpression("[a-z0-9._=/-]+"));
username_input_->setToolTip(tr("The username must not be empty, and must contain only the "
"characters a-z, 0-9, ., _, =, -, and /."));
password_input_ = new TextField();
password_input_->setLabel(tr("Password"));
+ password_input_->setRegexp(QRegularExpression("^.{8,}$"));
password_input_->setEchoMode(QLineEdit::Password);
password_input_->setToolTip(tr("Please choose a secure password. The exact requirements "
"for password strength may depend on your server."));
@@ -107,19 +108,32 @@ RegisterPage::RegisterPage(QWidget *parent)
tr("A server that allows registration. Since matrix is decentralized, you need to first "
"find a server you can register on or host your own."));
+ error_username_label_ = new QLabel(this);
+ error_username_label_->setWordWrap(true);
+ error_username_label_->hide();
+
+ error_password_label_ = new QLabel(this);
+ error_password_label_->setWordWrap(true);
+ error_password_label_->hide();
+
+ error_password_confirmation_label_ = new QLabel(this);
+ error_password_confirmation_label_->setWordWrap(true);
+ error_password_confirmation_label_->hide();
+
form_layout_->addWidget(username_input_, Qt::AlignHCenter);
+ form_layout_->addWidget(error_username_label_, Qt::AlignHCenter);
form_layout_->addWidget(password_input_, Qt::AlignHCenter);
+ form_layout_->addWidget(error_password_label_, Qt::AlignHCenter);
form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter);
+ form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter);
form_layout_->addWidget(server_input_, Qt::AlignHCenter);
button_layout_ = new QHBoxLayout();
button_layout_->setSpacing(0);
button_layout_->setMargin(0);
- QFont font;
-
error_label_ = new QLabel(this);
- error_label_->setFont(font);
+ error_label_->setWordWrap(true);
register_button_ = new RaisedButton(tr("REGISTER"), this);
register_button_->setMinimumSize(350, 65);
@@ -135,17 +149,24 @@ RegisterPage::RegisterPage(QWidget *parent)
top_layout_->addLayout(form_wrapper_);
top_layout_->addStretch(1);
top_layout_->addLayout(button_layout_);
- top_layout_->addStretch(1);
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
+ top_layout_->addStretch(1);
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked()));
connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
+ connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkFields);
connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
+ connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkFields);
connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
+ connect(
+ password_confirmation_, &TextField::editingFinished, this, &RegisterPage::checkFields);
connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
- connect(this, &RegisterPage::registerErrorCb, this, &RegisterPage::registerError);
+ connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkFields);
+ connect(this, &RegisterPage::registerErrorCb, this, [this](const QString &msg) {
+ showError(msg);
+ });
connect(
this,
&RegisterPage::registrationFlow,
@@ -299,25 +320,93 @@ RegisterPage::onBackButtonClicked()
}
void
-RegisterPage::registerError(const QString &msg)
+RegisterPage::showError(const QString &msg)
{
emit errorOccurred();
+ auto rect = QFontMetrics(font()).boundingRect(msg);
+ int width = rect.width();
+ int height = rect.height();
+ error_label_->setFixedHeight(qCeil(width / 200.0) * height);
error_label_->setText(msg);
}
void
-RegisterPage::onRegisterButtonClicked()
+RegisterPage::showError(QLabel *label, const QString &msg)
+{
+ emit errorOccurred();
+ auto rect = QFontMetrics(font()).boundingRect(msg);
+ int width = rect.width();
+ int height = rect.height();
+ label->setFixedHeight((int)qCeil(width / 200.0) * height);
+ label->setText(msg);
+}
+
+bool
+RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg)
+{
+ if (t_field->isValid()) {
+ label->setText("");
+ label->hide();
+ return true;
+ } else {
+ label->show();
+ showError(label, msg);
+ return false;
+ }
+}
+
+bool
+RegisterPage::checkFields()
{
error_label_->setText("");
+ error_username_label_->setText("");
+ error_password_label_->setText("");
+ error_password_confirmation_label_->setText("");
+
+ error_username_label_->hide();
+ error_password_label_->hide();
+ error_password_confirmation_label_->hide();
+
+ password_confirmation_->setValid(true);
+ server_input_->setValid(true);
+
+ bool all_fields_good = true;
+ if (username_input_->isModified() &&
+ !checkOneField(error_username_label_,
+ username_input_,
+ tr("The username must not be empty, and must contain only the "
+ "characters a-z, 0-9, ., _, =, -, and /."))) {
+ all_fields_good = false;
+ } else if (password_input_->isModified() &&
+ !checkOneField(error_password_label_,
+ password_input_,
+ tr("Password is not long enough (min 8 chars)"))) {
+ all_fields_good = false;
+ } else if (password_confirmation_->isModified() &&
+ password_input_->text() != password_confirmation_->text()) {
+ error_password_confirmation_label_->show();
+ showError(error_password_confirmation_label_, tr("Passwords don't match"));
+ password_confirmation_->setValid(false);
+ all_fields_good = false;
+ } else if (server_input_->isModified() &&
+ (!server_input_->hasAcceptableInput() || server_input_->text().isEmpty())) {
+ showError(tr("Invalid server name"));
+ server_input_->setValid(false);
+ all_fields_good = false;
+ }
+ if (!username_input_->isModified() || !password_input_->isModified() ||
+ !password_confirmation_->isModified() || !server_input_->isModified()) {
+ all_fields_good = false;
+ }
+ return all_fields_good;
+}
- if (!username_input_->hasAcceptableInput()) {
- registerError(tr("Invalid username"));
- } else if (!password_input_->hasAcceptableInput()) {
- registerError(tr("Password is not long enough (min 8 chars)"));
- } else if (password_input_->text() != password_confirmation_->text()) {
- registerError(tr("Passwords don't match"));
- } else if (!server_input_->hasAcceptableInput()) {
- registerError(tr("Invalid server name"));
+void
+RegisterPage::onRegisterButtonClicked()
+{
+ if (!checkFields()) {
+ showError(error_label_, tr("One or more fields have invalid inputs. Please correct those issues and try again."));
+ return;
} else {
auto username = username_input_->text().toStdString();
auto password = password_input_->text().toStdString();
diff --git a/src/RegisterPage.h b/src/RegisterPage.h
index 59ba3d1d..6d212955 100644
--- a/src/RegisterPage.h
+++ b/src/RegisterPage.h
@@ -57,10 +57,13 @@ private slots:
void onBackButtonClicked();
void onRegisterButtonClicked();
- // Display registration specific errors to the user.
- void registerError(const QString &msg);
+ // function for showing different errors
+ void showError(const QString &msg);
private:
+ bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg);
+ bool checkFields();
+ void showError(QLabel *label, const QString &msg);
QVBoxLayout *top_layout_;
QHBoxLayout *back_layout_;
@@ -69,6 +72,9 @@ private:
QLabel *logo_;
QLabel *error_label_;
+ QLabel *error_username_label_;
+ QLabel *error_password_label_;
+ QLabel *error_password_confirmation_label_;
FlatButton *back_button_;
RaisedButton *register_button_;
diff --git a/src/WelcomePage.cpp b/src/WelcomePage.cpp
index e4b0e1c6..22b73ac7 100644
--- a/src/WelcomePage.cpp
+++ b/src/WelcomePage.cpp
@@ -37,8 +37,7 @@ WelcomePage::WelcomePage(QWidget *parent)
QFont subTitleFont;
subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5);
- QIcon icon;
- icon.addFile(":/logos/splash.png");
+ QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})};
auto logo_ = new QLabel(this);
logo_->setPixmap(icon.pixmap(256));
diff --git a/src/main.cpp b/src/main.cpp
index 6fbccf5c..5eeebb82 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -174,7 +174,7 @@ main(int argc, char *argv[])
parser.process(app);
- app.setWindowIcon(QIcon(":/logos/nheko.png"));
+ app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"}));
http::init();
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 1cb729d3..e561d099 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -604,8 +604,9 @@ EventStore::decryptEvent(const IdIndex &idx,
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());
+ auto res =
+ olm::client()->decrypt_group_message(session.get(), 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,
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 53791c98..11fa60c0 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -910,80 +910,16 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::
{"room_id", room_id}};
try {
- // Check if we have already an outbound megolm session then we can use.
- if (cache::outboundMegolmSessionExists(room_id)) {
- mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> event;
- event.content =
- olm::encrypt_group_message(room_id, http::client()->device_id(), doc);
- event.event_id = msg.event_id;
- event.room_id = room_id;
- event.sender = http::client()->user_id().to_string();
- event.type = mtx::events::EventType::RoomEncrypted;
- event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
-
- emit this->addPendingMessageToStore(event);
- return;
- }
-
- nhlog::ui()->debug("creating new outbound megolm session");
-
- // Create a new outbound megolm session.
- auto outbound_session = olm::client()->init_outbound_group_session();
- const auto session_id = mtx::crypto::session_id(outbound_session.get());
- const auto session_key = mtx::crypto::session_key(outbound_session.get());
-
- mtx::events::DeviceEvent<mtx::events::msg::RoomKey> megolm_payload;
- megolm_payload.content.algorithm = "m.megolm.v1.aes-sha2";
- megolm_payload.content.room_id = room_id;
- megolm_payload.content.session_id = session_id;
- megolm_payload.content.session_key = session_key;
- megolm_payload.type = mtx::events::EventType::RoomKey;
-
- // Saving the new megolm session.
- // TODO: Maybe it's too early to save.
- OutboundGroupSessionData session_data;
- session_data.session_id = session_id;
- session_data.session_key = session_key;
- session_data.message_index = 0;
- cache::saveOutboundMegolmSession(
- room_id, session_data, std::move(outbound_session));
-
- {
- MegolmSessionIndex index;
- index.room_id = room_id;
- index.session_id = session_id;
- index.sender_key = olm::client()->identity_keys().curve25519;
- auto megolm_session =
- olm::client()->init_inbound_group_session(session_key);
- cache::saveInboundMegolmSession(index, std::move(megolm_session));
- }
-
- const auto members = cache::roomMembers(room_id);
- nhlog::ui()->info("retrieved {} members for {}", members.size(), room_id);
-
- std::map<std::string, std::vector<std::string>> targets;
- for (const auto &member : members)
- targets[member] = {};
-
- olm::send_encrypted_to_device_messages(targets, megolm_payload);
-
- try {
- mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> event;
- event.content =
- olm::encrypt_group_message(room_id, http::client()->device_id(), doc);
- event.event_id = msg.event_id;
- event.room_id = room_id;
- event.sender = http::client()->user_id().to_string();
- event.type = mtx::events::EventType::RoomEncrypted;
- event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
-
- emit this->addPendingMessageToStore(event);
- } catch (const lmdb::error &e) {
- nhlog::db()->critical("failed to save megolm outbound session: {}",
- e.what());
- emit ChatPage::instance()->showNotification(
- tr("Failed to encrypt event, sending aborted!"));
- }
+ mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> event;
+ event.content =
+ olm::encrypt_group_message(room_id, http::client()->device_id(), doc);
+ event.event_id = msg.event_id;
+ event.room_id = room_id;
+ event.sender = http::client()->user_id().to_string();
+ event.type = mtx::events::EventType::RoomEncrypted;
+ event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
+
+ emit this->addPendingMessageToStore(event);
// TODO: Let the user know about the errors.
} catch (const lmdb::error &e) {
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index d0356e15..03eb53fc 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -363,6 +363,9 @@ TimelineViewManager::toggleCameraView()
void
TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const
{
+ if (mxcUrl.isEmpty()) {
+ return;
+ }
QQuickImageResponse *imgResponse =
imgProvider->requestImageResponse(mxcUrl.remove("mxc://"), QSize());
connect(imgResponse, &QQuickImageResponse::finished, this, [this, eventId, imgResponse]() {
diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp
index 941d00a3..055fe73b 100644
--- a/src/ui/TextField.cpp
+++ b/src/ui/TextField.cpp
@@ -6,6 +6,7 @@
#include <QPaintEvent>
#include <QPainter>
#include <QPropertyAnimation>
+#include <QRegularExpressionValidator>
TextField::TextField(QWidget *parent)
: QLineEdit(parent)
@@ -70,18 +71,24 @@ TextField::hasLabel() const
return show_label_;
}
-bool
-TextField::isValid() const
-{
- return is_valid_;
-}
-
void
TextField::setValid(bool valid)
{
is_valid_ = valid;
}
+bool
+TextField::isValid() const
+{
+ QString s = text();
+ int pos = 0;
+ if (regexp_.pattern().isEmpty()) {
+ return is_valid_;
+ }
+ QRegularExpressionValidator v(QRegularExpression(regexp_), 0);
+ return v.validate(s, pos) == QValidator::Acceptable;
+}
+
void
TextField::setLabelFontSize(qreal size)
{
@@ -156,6 +163,12 @@ TextField::setUnderlineColor(const QColor &color)
update();
}
+void
+TextField::setRegexp(const QRegularExpression ®exp)
+{
+ regexp_ = regexp;
+}
+
QColor
TextField::underlineColor() const
{
diff --git a/src/ui/TextField.h b/src/ui/TextField.h
index 966155f4..01fd5782 100644
--- a/src/ui/TextField.h
+++ b/src/ui/TextField.h
@@ -4,6 +4,7 @@
#include <QLineEdit>
#include <QPaintEvent>
#include <QPropertyAnimation>
+#include <QRegularExpression>
#include <QStateMachine>
#include <QtGlobal>
@@ -30,6 +31,7 @@ public:
void setLabelFontSize(qreal size);
void setShowLabel(bool value);
void setUnderlineColor(const QColor &color);
+ void setRegexp(const QRegularExpression ®exp);
void setValid(bool valid);
QColor inkColor() const;
@@ -56,6 +58,7 @@ private:
TextFieldLabel *label_;
TextFieldStateMachine *state_machine_;
bool show_label_;
+ QRegularExpression regexp_;
bool is_valid_;
qreal label_font_size_;
};
|