Leaving rooms implemented

both locally using the room menu in nheko, and reacting properly
when leaving a room remotely from another client
This commit is contained in:
Max Sandholm 2017-09-27 20:35:01 +03:00 committed by vurpo
parent 7638e0f97d
commit 6b87574e45
14 changed files with 223 additions and 7 deletions

View File

@ -37,6 +37,8 @@ public:
inline void unmount(); inline void unmount();
inline QString memberDbName(const QString &roomid); inline QString memberDbName(const QString &roomid);
void removeRoom(const QString &roomid);
private: private:
void setNextBatchToken(lmdb::txn &txn, const QString &token); void setNextBatchToken(lmdb::txn &txn, const QString &token);
void insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &state); void insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &state);

View File

@ -61,6 +61,8 @@ private slots:
void changeTopRoomInfo(const QString &room_id); void changeTopRoomInfo(const QString &room_id);
void startSync(); void startSync();
void logout(); void logout();
void joinedRoom(const QString &room_id);
void leftRoom(const QString &room_id);
protected: protected:
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;

View File

@ -53,6 +53,7 @@ public:
void messages(const QString &room_id, const QString &from_token, int limit = 20) noexcept; void messages(const QString &room_id, const QString &from_token, int limit = 20) noexcept;
void uploadImage(const QString &roomid, const QString &filename); void uploadImage(const QString &roomid, const QString &filename);
void joinRoom(const QString &roomIdOrAlias); void joinRoom(const QString &roomIdOrAlias);
void leaveRoom(const QString &roomId);
inline QUrl getHomeServer(); inline QUrl getHomeServer();
inline int transactionId(); inline int transactionId();
@ -96,6 +97,7 @@ signals:
void emoteSent(const QString &event_id, const QString &roomid, const int txn_id); void emoteSent(const QString &event_id, const QString &roomid, const int txn_id);
void messagesRetrieved(const QString &room_id, const RoomMessages &msgs); void messagesRetrieved(const QString &room_id, const RoomMessages &msgs);
void joinedRoom(const QString &room_id); void joinedRoom(const QString &room_id);
void leftRoom(const QString &room_id);
private slots: private slots:
void onResponse(QNetworkReply *reply); void onResponse(QNetworkReply *reply);
@ -118,6 +120,7 @@ private:
UserAvatar, UserAvatar,
Versions, Versions,
JoinRoom, JoinRoom,
LeaveRoom,
}; };
// Response handlers. // Response handlers.
@ -136,6 +139,7 @@ private:
void onUserAvatarResponse(QNetworkReply *reply); void onUserAvatarResponse(QNetworkReply *reply);
void onVersionsResponse(QNetworkReply *reply); void onVersionsResponse(QNetworkReply *reply);
void onJoinRoomResponse(QNetworkReply *reply); void onJoinRoomResponse(QNetworkReply *reply);
void onLeaveRoomResponse(QNetworkReply *reply);
// Client API prefix. // Client API prefix.
QString clientApiUrl_; QString clientApiUrl_;

View File

@ -57,6 +57,7 @@ public:
signals: signals:
void clicked(const QString &room_id); void clicked(const QString &room_id);
void leaveRoom(const QString &room_id);
public slots: public slots:
void setPressedState(bool state); void setPressedState(bool state);
@ -86,6 +87,7 @@ private:
Menu *menu_; Menu *menu_;
QAction *toggleNotifications_; QAction *toggleNotifications_;
QAction *leaveRoom_;
QSharedPointer<RoomSettings> roomSettings_; QSharedPointer<RoomSettings> roomSettings_;

View File

@ -44,6 +44,8 @@ public:
void clear(); void clear();
void removeRoom(const QString &room_id, bool reset);
signals: signals:
void roomChanged(const QString &room_id); void roomChanged(const QString &room_id);
void totalUnreadMessageCountUpdated(int count); void totalUnreadMessageCountUpdated(int count);
@ -53,8 +55,6 @@ public slots:
void highlightSelectedRoom(const QString &room_id); void highlightSelectedRoom(const QString &room_id);
void updateUnreadMessageCount(const QString &roomid, int count); void updateUnreadMessageCount(const QString &roomid, int count);
void updateRoomDescription(const QString &roomid, const DescInfo &info); void updateRoomDescription(const QString &roomid, const DescInfo &info);
private slots:
void closeJoinRoomDialog(bool isJoining, QString roomAlias); void closeJoinRoomDialog(bool isJoining, QString roomAlias);
private: private:

View File

@ -171,15 +171,42 @@ JoinedRoom::timeline() const
return timeline_; return timeline_;
} }
class LeftRoom : public Deserializable
{
public:
inline State state() const;
inline Timeline timeline() const;
void deserialize(const QJsonValue &data) override;
private:
State state_;
Timeline timeline_;
};
inline State
LeftRoom::state() const
{
return state_;
}
inline Timeline
LeftRoom::timeline() const
{
return timeline_;
}
// TODO: Add support for invited and left rooms. // TODO: Add support for invited and left rooms.
class Rooms : public Deserializable class Rooms : public Deserializable
{ {
public: public:
inline QMap<QString, JoinedRoom> join() const; inline QMap<QString, JoinedRoom> join() const;
inline QMap<QString, LeftRoom> leave() const;
void deserialize(const QJsonValue &data) override; void deserialize(const QJsonValue &data) override;
private: private:
QMap<QString, JoinedRoom> join_; QMap<QString, JoinedRoom> join_;
QMap<QString, LeftRoom> leave_;
}; };
inline QMap<QString, JoinedRoom> inline QMap<QString, JoinedRoom>
@ -188,6 +215,12 @@ Rooms::join() const
return join_; return join_;
} }
inline QMap<QString, LeftRoom>
Rooms::leave() const
{
return leave_;
}
class SyncResponse : public Deserializable class SyncResponse : public Deserializable
{ {
public: public:

View File

@ -51,6 +51,9 @@ public:
void reset(); void reset();
signals:
void leaveRoom();
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
@ -65,6 +68,7 @@ private:
QMenu *menu_; QMenu *menu_;
QAction *toggleNotifications_; QAction *toggleNotifications_;
QAction *leaveRoom_;
FlatButton *settingsBtn_; FlatButton *settingsBtn_;

View File

@ -153,6 +153,19 @@ Cache::insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &s
} }
} }
void
Cache::removeRoom(const QString &roomid)
{
auto txn = lmdb::txn::begin(env_, nullptr, 0);
lmdb::dbi_del(txn,
roomDb_,
lmdb::val(roomid.toUtf8(), roomid.toUtf8().size()),
nullptr);
txn.commit();
}
QMap<QString, RoomState> QMap<QString, RoomState>
Cache::states() Cache::states()
{ {

View File

@ -114,6 +114,10 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
connect(user_info_widget_, SIGNAL(logout()), client_.data(), SLOT(logout())); connect(user_info_widget_, SIGNAL(logout()), client_.data(), SLOT(logout()));
connect(client_.data(), SIGNAL(loggedOut()), this, SLOT(logout())); connect(client_.data(), SIGNAL(loggedOut()), this, SLOT(logout()));
connect(top_bar_, &TopRoomBar::leaveRoom, this, [=](){
client_->leaveRoom(current_room_);
});
connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo); connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo);
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit); connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit);
connect( connect(
@ -190,6 +194,14 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
SIGNAL(ownAvatarRetrieved(const QPixmap &)), SIGNAL(ownAvatarRetrieved(const QPixmap &)),
this, this,
SLOT(setOwnAvatar(const QPixmap &))); SLOT(setOwnAvatar(const QPixmap &)));
connect(client_.data(),
SIGNAL(joinedRoom(const QString &)),
this,
SLOT(joinedRoom(const QString &)));
connect(client_.data(),
SIGNAL(leftRoom(const QString &)),
this,
SLOT(leftRoom(const QString &)));
AvatarProvider::init(client); AvatarProvider::init(client);
} }
@ -307,6 +319,7 @@ ChatPage::syncCompleted(const SyncResponse &response)
oldState.update(room_state); oldState.update(room_state);
state_manager_.insert(it.key(), oldState); state_manager_.insert(it.key(), oldState);
} else { } else {
// TODO: Add newly joined room
qWarning() << "New rooms cannot be added after initial sync, yet."; qWarning() << "New rooms cannot be added after initial sync, yet.";
} }
@ -314,6 +327,14 @@ ChatPage::syncCompleted(const SyncResponse &response)
changeTopRoomInfo(it.key()); changeTopRoomInfo(it.key());
} }
auto leave = response.rooms().leave();
for (auto it = leave.constBegin(); it != leave.constEnd(); it++) {
if (state_manager_.contains(it.key())) {
leftRoom(it.key());
}
}
try { try {
cache_->setState(response.nextBatch(), state_manager_); cache_->setState(response.nextBatch(), state_manager_);
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
@ -537,6 +558,33 @@ ChatPage::showQuickSwitcher()
quickSwitcherModal_->fadeIn(); quickSwitcherModal_->fadeIn();
} }
void
ChatPage::joinedRoom(const QString &room_id)
{
RoomState room_state;
state_manager_.insert(room_id, room_state);
settingsManager_.insert(room_id,
QSharedPointer<RoomSettings>(new RoomSettings(room_id)));
this->changeTopRoomInfo(room_id);
}
void
ChatPage::leftRoom(const QString &room_id)
{
state_manager_.remove(room_id);
settingsManager_.remove(room_id);
try {
cache_->removeRoom(room_id);
} catch (const lmdb::error &e) {
qCritical() << "The cache couldn't be updated: " << e.what();
// TODO: Notify the user.
cache_->unmount();
}
room_list_->removeRoom(room_id, room_id == current_room_);
}
ChatPage::~ChatPage() ChatPage::~ChatPage()
{ {
sync_timer_->stop(); sync_timer_->stop();

View File

@ -480,6 +480,22 @@ MatrixClient::onJoinRoomResponse(QNetworkReply *reply)
emit joinedRoom(room_id); emit joinedRoom(room_id);
} }
void
MatrixClient::onLeaveRoomResponse(QNetworkReply *reply)
{
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qWarning() << reply->errorString();
return;
}
QString room_id = reply->property("room_id").toString();
emit leftRoom(room_id);
}
void void
MatrixClient::onResponse(QNetworkReply *reply) MatrixClient::onResponse(QNetworkReply *reply)
{ {
@ -529,6 +545,9 @@ MatrixClient::onResponse(QNetworkReply *reply)
case Endpoint::JoinRoom: case Endpoint::JoinRoom:
onJoinRoomResponse(reply); onJoinRoomResponse(reply);
break; break;
case Endpoint::LeaveRoom:
onLeaveRoomResponse(reply);
break;
default: default:
break; break;
} }
@ -592,7 +611,11 @@ void
MatrixClient::sync() noexcept MatrixClient::sync() noexcept
{ {
QJsonObject filter{ { "room", QJsonObject filter{ { "room",
QJsonObject{ { "ephemeral", QJsonObject{ { "limit", 0 } } } } }, QJsonObject{
{ "include_leave", true },
{ "ephemeral", QJsonObject{ { "limit", 0 } } }
}
},
{ "presence", QJsonObject{ { "limit", 0 } } } }; { "presence", QJsonObject{ { "limit", 0 } } } };
QUrlQuery query; QUrlQuery query;
@ -887,3 +910,21 @@ MatrixClient::joinRoom(const QString &roomIdOrAlias)
QNetworkReply *reply = post(request, "{}"); QNetworkReply *reply = post(request, "{}");
reply->setProperty("endpoint", static_cast<int>(Endpoint::JoinRoom)); reply->setProperty("endpoint", static_cast<int>(Endpoint::JoinRoom));
} }
void
MatrixClient::leaveRoom(const QString &roomId)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/leave").arg(roomId));
endpoint.setQuery(query);
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader,"application/json");
QNetworkReply *reply = post(request, "{}");
reply->setProperty("room_id", roomId);
reply->setProperty("endpoint", static_cast<int>(Endpoint::LeaveRoom));
}

View File

@ -53,12 +53,17 @@ RoomInfoListItem::RoomInfoListItem(QSharedPointer<RoomSettings> settings,
menu_ = new Menu(this); menu_ = new Menu(this);
toggleNotifications_ = new QAction(notificationText(), this); toggleNotifications_ = new QAction(notificationText(), this);
connect(toggleNotifications_, &QAction::triggered, this, [=]() { connect(toggleNotifications_, &QAction::triggered, this, [=]() {
roomSettings_->toggleNotifications(); roomSettings_->toggleNotifications();
}); });
leaveRoom_ = new QAction(tr("Leave room"), this);
connect(leaveRoom_, &QAction::triggered, this, [=](){
emit leaveRoom(room_id);
});
menu_->addAction(toggleNotifications_); menu_->addAction(toggleNotifications_);
menu_->addAction(leaveRoom_);
} }
QString QString

View File

@ -91,6 +91,20 @@ RoomList::clear()
rooms_.clear(); rooms_.clear();
} }
void
RoomList::removeRoom(const QString &room_id, bool reset)
{
rooms_.remove(room_id);
if (rooms_.isEmpty() || !reset)
return;
auto first_room = rooms_.first();
first_room->setPressedState(true);
emit roomChanged(rooms_.firstKey());
}
void void
RoomList::updateUnreadMessageCount(const QString &roomid, int count) RoomList::updateUnreadMessageCount(const QString &roomid, int count)
{ {
@ -137,7 +151,11 @@ RoomList::setInitialRooms(const QMap<QString, QSharedPointer<RoomSettings>> &set
RoomInfoListItem *room_item = RoomInfoListItem *room_item =
new RoomInfoListItem(settings[room_id], state, room_id, scrollArea_); new RoomInfoListItem(settings[room_id], state, room_id, scrollArea_);
connect( connect(
room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom); room_item, &RoomInfoListItem::clicked,
this, &RoomList::highlightSelectedRoom);
connect(
room_item, &RoomInfoListItem::leaveRoom,
client_.data(), &MatrixClient::leaveRoom);
rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item)); rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item));

View File

@ -90,7 +90,6 @@ Rooms::deserialize(const QJsonValue &data)
for (auto it = join.constBegin(); it != join.constEnd(); it++) { for (auto it = join.constBegin(); it != join.constEnd(); it++) {
JoinedRoom tmp_room; JoinedRoom tmp_room;
try { try {
tmp_room.deserialize(it.value()); tmp_room.deserialize(it.value());
join_.insert(it.key(), tmp_room); join_.insert(it.key(), tmp_room);
@ -112,8 +111,21 @@ Rooms::deserialize(const QJsonValue &data)
if (!object.value("leave").isObject()) { if (!object.value("leave").isObject()) {
throw DeserializationException("rooms/leave must be a JSON object"); throw DeserializationException("rooms/leave must be a JSON object");
} }
// TODO: Implement leave handling auto leave = object.value("leave").toObject();
for (auto it = leave.constBegin(); it != leave.constEnd(); it++) {
LeftRoom tmp_room;
try {
tmp_room.deserialize(it.value());
leave_.insert(it.key(), tmp_room);
} catch (DeserializationException &e) {
qWarning() << e.what();
qWarning() << "Skipping malformed object for room" << it.key();
} }
}
}
} }
void void
@ -184,6 +196,32 @@ JoinedRoom::deserialize(const QJsonValue &data)
} }
} }
void
LeftRoom::deserialize(const QJsonValue &data)
{
if (!data.isObject())
throw DeserializationException("LeftRoom is not a JSON object");
QJsonObject object = data.toObject();
if (!object.contains("state"))
throw DeserializationException("join/state is missing");
if (!object.contains("timeline"))
throw DeserializationException("join/timeline is missing");
if (!object.value("state").isObject())
throw DeserializationException("join/state should be an object");
QJsonObject state = object.value("state").toObject();
if (!state.contains("events"))
throw DeserializationException("join/state/events is missing");
state_.deserialize(state.value("events"));
timeline_.deserialize(object.value("timeline"));
}
void void
Event::deserialize(const QJsonValue &data) Event::deserialize(const QJsonValue &data)
{ {

View File

@ -83,7 +83,13 @@ TopRoomBar::TopRoomBar(QWidget *parent)
roomSettings_->toggleNotifications(); roomSettings_->toggleNotifications();
}); });
leaveRoom_ = new QAction(tr("Leave room"), this);
connect(leaveRoom_, &QAction::triggered, this, [=](){
emit leaveRoom();
});
menu_->addAction(toggleNotifications_); menu_->addAction(toggleNotifications_);
menu_->addAction(leaveRoom_);
connect(settingsBtn_, &QPushButton::clicked, this, [=]() { connect(settingsBtn_, &QPushButton::clicked, this, [=]() {
if (roomSettings_->isNotificationsEnabled()) if (roomSettings_->isNotificationsEnabled())