From 6dd950637d0c90c7540cd64b2eb002f1414389a5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 4 Nov 2018 18:01:49 +0900 Subject: Room: store state events in a unified way Closes #194. --- lib/room.cpp | 156 ++++++++++++++++++++++------------------------------------- 1 file changed, 59 insertions(+), 97 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index f8ed2721..aa1dfbf6 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -92,18 +92,17 @@ class Room::Private void updateDisplayname(); Connection* connection; + QString id; + JoinState joinState; + // The state of the room at timeline position before-0 + std::unordered_map baseState; + // The state of the room at timeline position after-maxTimelineIndex() + QHash currentState; Timeline timeline; PendingEvents unsyncedEvents; QHash eventsIndex; - QString id; - QStringList aliases; - QString canonicalAlias; - QString name; QString displayname; - QString topic; - QString encryptionAlgorithm; Avatar avatar; - JoinState joinState; int highlightCount = 0; int notificationCount = 0; members_map_t membersMap; @@ -171,6 +170,15 @@ class Room::Private void getPreviousContent(int limit = 10); + template + const EventT* getCurrentState(QString stateKey = {}) const + { + static const EventT emptyEvent { QJsonObject{} }; + return static_cast( + currentState.value({EventT::typeId(), stateKey}, + &emptyEvent)); + } + bool isEventNotable(const TimelineItem& ti) const { return !ti->isRedacted() && @@ -288,17 +296,17 @@ const Room::PendingEvents& Room::pendingEvents() const QString Room::name() const { - return d->name; + return d->getCurrentState()->name(); } QStringList Room::aliases() const { - return d->aliases; + return d->getCurrentState()->aliases(); } QString Room::canonicalAlias() const { - return d->canonicalAlias; + return d->getCurrentState()->alias(); } QString Room::displayName() const @@ -308,7 +316,7 @@ QString Room::displayName() const QString Room::topic() const { - return d->topic; + return d->getCurrentState()->topic(); } QString Room::avatarMediaId() const @@ -945,7 +953,7 @@ int Room::timelineSize() const bool Room::usesEncryption() const { - return !d->encryptionAlgorithm.isEmpty(); + return !d->getCurrentState()->algorithm().isEmpty(); } void Room::Private::insertMemberIntoMap(User *u) @@ -1088,8 +1096,12 @@ void Room::updateData(SyncRoomData&& data) if (!data.state.empty()) { et.restart(); - for (const auto& e: data.state) - emitNamesChanged |= processStateEvent(*e); + for (auto&& eptr: data.state) + { + const auto& evt = *eptr; + d->baseState[{evt.type(),evt.stateKey()}] = move(eptr); + emitNamesChanged |= processStateEvent(evt); + } qCDebug(PROFILER) << "*** Room::processStateEvents():" << data.state.size() << "event(s)," << et; @@ -1747,38 +1759,32 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) bool Room::processStateEvent(const RoomEvent& e) { + if (!e.isStateEvent()) + return false; + + d->currentState[{e.type(),e.stateKey()}] = + static_cast(&e); + if (!is(e)) + qCDebug(EVENTS) << "Room state event:" << e; + return visit(e - , [this] (const RoomNameEvent& evt) { - d->name = evt.name(); - qCDebug(MAIN) << "Room name updated:" << d->name; + , [] (const RoomNameEvent&) { return true; } - , [this] (const RoomAliasesEvent& evt) { - d->aliases = evt.aliases(); - qCDebug(MAIN) << "Room aliases updated:" << d->aliases; + , [] (const RoomAliasesEvent&) { return true; } , [this] (const RoomCanonicalAliasEvent& evt) { - d->canonicalAlias = evt.alias(); - if (!d->canonicalAlias.isEmpty()) - setObjectName(d->canonicalAlias); - qCDebug(MAIN) << "Room canonical alias updated:" - << d->canonicalAlias; + setObjectName(evt.alias().isEmpty() ? d->id : evt.alias()); return true; } - , [this] (const RoomTopicEvent& evt) { - d->topic = evt.topic(); - qCDebug(MAIN) << "Room topic updated:" << d->topic; + , [this] (const RoomTopicEvent&) { emit topicChanged(); return false; } , [this] (const RoomAvatarEvent& evt) { if (d->avatar.updateUrl(evt.url())) - { - qCDebug(MAIN) << "Room avatar URL updated:" - << evt.url().toString(); emit avatarChanged(); - } return false; } , [this] (const RoomMemberEvent& evt) { @@ -1819,10 +1825,7 @@ bool Room::processStateEvent(const RoomEvent& e) } return false; } - , [this] (const EncryptionEvent& evt) { - d->encryptionAlgorithm = evt.algorithm(); - qCDebug(MAIN) << "Encryption switched on in room" << id() - << "with algorithm" << d->encryptionAlgorithm; + , [this] (const EncryptionEvent&) { emit encryption(); return false; } @@ -1976,27 +1979,29 @@ QString Room::Private::calculateDisplayname() const // Numbers below refer to respective parts in the spec. // 1. Name (from m.room.name) - if (!name.isEmpty()) { - return name; + auto dispName = q->name(); + if (!dispName.isEmpty()) { + return dispName; } // 2. Canonical alias - if (!canonicalAlias.isEmpty()) - return canonicalAlias; + dispName = q->canonicalAlias(); + if (!dispName.isEmpty()) + return dispName; // Using m.room.aliases in naming is explicitly discouraged by the spec - //if (!aliases.empty() && !aliases.at(0).isEmpty()) - // return aliases.at(0); + //if (!q->aliases().empty() && !q->aliases().at(0).isEmpty()) + // return q->aliases().at(0); // 3. Room members - QString topMemberNames = roomNameFromMemberNames(membersMap.values()); - if (!topMemberNames.isEmpty()) - return topMemberNames; + dispName = roomNameFromMemberNames(membersMap.values()); + if (!dispName.isEmpty()) + return dispName; // 4. Users that previously left the room - topMemberNames = roomNameFromMemberNames(membersLeft); - if (!topMemberNames.isEmpty()) - return tr("Empty room (was: %1)").arg(topMemberNames); + dispName = roomNameFromMemberNames(membersLeft); + if (!dispName.isEmpty()) + return tr("Empty room (was: %1)").arg(dispName); // 5. Fail miserably return tr("Empty room (%1)").arg(id); @@ -2015,34 +2020,6 @@ void Room::Private::updateDisplayname() } } -void appendStateEvent(QJsonArray& events, const QString& type, - const QJsonObject& content, const QString& stateKey = {}) -{ - if (!content.isEmpty() || !stateKey.isEmpty()) - { - auto json = basicEventJson(type, content); - json.insert(QStringLiteral("state_key"), stateKey); - events.append(json); - } -} - -#define ADD_STATE_EVENT(events, type, name, content) \ - appendStateEvent((events), QStringLiteral(type), \ - {{ QStringLiteral(name), content }}); - -void appendEvent(QJsonArray& events, const QString& type, - const QJsonObject& content) -{ - if (!content.isEmpty()) - events.append(basicEventJson(type, content)); -} - -template -void appendEvent(QJsonArray& events, const EvtT& event) -{ - appendEvent(events, EvtT::matrixTypeId(), event.toJson()); -} - QJsonObject Room::Private::toJson() const { QElapsedTimer et; et.start(); @@ -2050,23 +2027,8 @@ QJsonObject Room::Private::toJson() const { QJsonArray stateEvents; - ADD_STATE_EVENT(stateEvents, "m.room.name", "name", name); - ADD_STATE_EVENT(stateEvents, "m.room.topic", "topic", topic); - ADD_STATE_EVENT(stateEvents, "m.room.avatar", "url", - avatar.url().toString()); - ADD_STATE_EVENT(stateEvents, "m.room.aliases", "aliases", - QJsonArray::fromStringList(aliases)); - ADD_STATE_EVENT(stateEvents, "m.room.canonical_alias", "alias", - canonicalAlias); - ADD_STATE_EVENT(stateEvents, "m.room.encryption", "algorithm", - encryptionAlgorithm); - - for (const auto *m : membersMap) - appendStateEvent(stateEvents, QStringLiteral("m.room.member"), - { { QStringLiteral("membership"), QStringLiteral("join") } - , { QStringLiteral("displayname"), m->rawName(q) } - , { QStringLiteral("avatar_url"), m->avatarUrl(q).toString() } - }, m->id()); + for (const auto& evt: currentState) + stateEvents.append(evt->fullJson()); const auto stateObjName = joinState == JoinState::Invite ? QStringLiteral("invite_state") : QStringLiteral("state"); @@ -2074,14 +2036,14 @@ QJsonObject Room::Private::toJson() const QJsonObject {{ QStringLiteral("events"), stateEvents }}); } - QJsonArray accountDataEvents; if (!accountData.empty()) { + QJsonArray accountDataEvents; for (const auto& e: accountData) - appendEvent(accountDataEvents, e.first, e.second->contentJson()); + accountDataEvents.append(e.second->fullJson()); + result.insert(QStringLiteral("account_data"), + QJsonObject {{ QStringLiteral("events"), accountDataEvents }}); } - result.insert(QStringLiteral("account_data"), - QJsonObject {{ QStringLiteral("events"), accountDataEvents }}); QJsonObject unreadNotifObj { { SyncRoomData::UnreadCountKey, unreadMessages } }; -- cgit v1.2.3 From 23ebed25b79f4b6edf630546d7d9d571398a1640 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 4 Nov 2018 19:55:20 +0900 Subject: Profiler logging fixes and improvements --- lib/room.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index aa1dfbf6..9cdacd91 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -416,7 +416,7 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to) QElapsedTimer et; et.start(); const auto newUnreadMessages = count_if(from, to, std::bind(&Room::Private::isEventNotable, this, _1)); - if (et.nsecsElapsed() > 10000) + if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Counting gained unread messages took" << et; if(newUnreadMessages > 0) @@ -458,7 +458,7 @@ void Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, bool force) QElapsedTimer et; et.start(); unreadMessages = count_if(eagerMarker, timeline.cend(), std::bind(&Room::Private::isEventNotable, this, _1)); - if (et.nsecsElapsed() > 10000) + if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Recounting unread messages took" << et; // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count @@ -1103,8 +1103,9 @@ void Room::updateData(SyncRoomData&& data) emitNamesChanged |= processStateEvent(evt); } - qCDebug(PROFILER) << "*** Room::processStateEvents():" - << data.state.size() << "event(s)," << et; + if (data.state.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) + qCDebug(PROFILER) << "*** Room::processStateEvents():" + << data.state.size() << "event(s)," << et; } if (!data.timeline.empty()) { @@ -1112,8 +1113,9 @@ void Room::updateData(SyncRoomData&& data) // State changes can arrive in a timeline event; so check those. for (const auto& e: data.timeline) emitNamesChanged |= processStateEvent(*e); - qCDebug(PROFILER) << "*** Room::processStateEvents(timeline):" - << data.timeline.size() << "event(s)," << et; + if (data.timeline.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) + qCDebug(PROFILER) << "*** Room::processStateEvents(timeline):" + << data.timeline.size() << "event(s)," << et; } if (emitNamesChanged) emit namesChanged(this); @@ -1123,7 +1125,8 @@ void Room::updateData(SyncRoomData&& data) { et.restart(); d->addNewMessageEvents(move(data.timeline)); - qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" << et; + if (data.timeline.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) + qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" << et; } for( auto&& ephemeralEvent: data.ephemeral ) processEphemeralEvent(move(ephemeralEvent)); @@ -1162,7 +1165,7 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) QString Room::Private::doSendEvent(const RoomEvent* pEvent) { auto txnId = pEvent->transactionId(); - // TODO: Enqueue the job rather than immediately trigger it + // TODO, #133: Enqueue the job rather than immediately trigger it. auto call = connection->callApi(BackgroundRequest, id, pEvent->matrixType(), txnId, pEvent->contentJson()); Room::connect(call, &BaseJob::started, q, @@ -1844,15 +1847,17 @@ void Room::processEphemeralEvent(EventPtr&& event) if (memberJoinState(u) == JoinState::Join) d->usersTyping.append(u); } - if (!evt->users().isEmpty()) + if (evt->users().size() > 3 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) << "*** Room::processEphemeralEvent(typing):" << evt->users().size() << "users," << et; emit typingChanged(); } if (auto* evt = eventCast(event)) { + int totalReceipts = 0; for( const auto &p: qAsConst(evt->eventsWithReceipts()) ) { + totalReceipts += p.receipts.size(); { if (p.receipts.size() == 1) qCDebug(EPHEMERAL) << "Marking" << p.evtId @@ -1891,10 +1896,11 @@ void Room::processEphemeralEvent(EventPtr&& event) } } } - if (!evt->eventsWithReceipts().isEmpty()) + if (evt->eventsWithReceipts().size() > 3 || totalReceipts > 10 || + et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) << "*** Room::processEphemeralEvent(receipts):" << evt->eventsWithReceipts().size() - << "events with receipts," << et; + << "event(s) with" << totalReceipts << "receipt(s)," << et; } } @@ -2055,7 +2061,7 @@ QJsonObject Room::Private::toJson() const result.insert(QStringLiteral("unread_notifications"), unreadNotifObj); - if (et.elapsed() > 50) + if (et.elapsed() > 30) qCDebug(PROFILER) << "Room::toJson() for" << displayname << "took" << et; return result; -- cgit v1.2.3 From be7d25ed22abd07a254bfb8ff6c30de4fcc79e6a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Nov 2018 07:54:29 +0900 Subject: isEchoEvent: check the pending event for ids, not the synced one Synced events always have their event ids, so checking those for event id renders most of the function useless (and returns an incorrect result). Closes #248. --- lib/room.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 9cdacd91..cd4d4dca 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1300,15 +1300,15 @@ bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re) if (le->type() != re->type()) return false; - if (!le->id().isEmpty()) + if (!re->id().isEmpty()) return le->id() == re->id(); - if (!le->transactionId().isEmpty()) + if (!re->transactionId().isEmpty()) return le->transactionId() == re->transactionId(); // This one is not reliable (there can be two unsynced // events with the same type, sender and state key) but // it's the best we have for state events. - if (le->isStateEvent()) + if (re->isStateEvent()) return le->stateKey() == re->stateKey(); // Empty id and no state key, hmm... (shrug) -- cgit v1.2.3 From e85137fca110de758f59cde2f6c6368090cf65c5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 14 Nov 2018 16:19:58 +0900 Subject: Room: ensure proper error signalling on event sending failures --- lib/room.cpp | 94 +++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 52 insertions(+), 42 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index cd4d4dca..5dd244f2 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -223,6 +223,8 @@ class Room::Private QString doSendEvent(const RoomEvent* pEvent); PendingEvents::iterator findAsPending(const RoomEvent* rawEvtPtr); + void onEventSendingFailure(const RoomEvent* pEvent, + const QString& txnId, BaseJob* call = nullptr); template auto requestSetState(const QString& stateKey, const EvT& event) @@ -1166,49 +1168,41 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) { auto txnId = pEvent->transactionId(); // TODO, #133: Enqueue the job rather than immediately trigger it. - auto call = connection->callApi(BackgroundRequest, - id, pEvent->matrixType(), txnId, pEvent->contentJson()); - Room::connect(call, &BaseJob::started, q, - [this,pEvent,txnId] { - auto it = findAsPending(pEvent); - if (it == unsyncedEvents.end()) - { - qWarning(EVENTS) << "Pending event for transaction" << txnId - << "not found - got synced so soon?"; - return; - } - it->setDeparted(); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); - }); - Room::connect(call, &BaseJob::failure, q, - [this,pEvent,txnId,call] { - auto it = findAsPending(pEvent); - if (it == unsyncedEvents.end()) - { - qCritical(EVENTS) << "Pending event for transaction" << txnId - << "got lost without successful sending"; - return; - } - it->setSendingFailed( - call->statusCaption() % ": " % call->errorString()); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); - - }); - Room::connect(call, &BaseJob::success, q, - [this,call,pEvent,txnId] { - // Find an event by the pointer saved in the lambda (the pointer - // may be dangling by now but we can still search by it). - auto it = findAsPending(pEvent); - if (it == unsyncedEvents.end()) - { - qDebug(EVENTS) << "Pending event for transaction" << txnId - << "already merged"; - return; - } + if (auto call = connection->callApi(BackgroundRequest, + id, pEvent->matrixType(), txnId, pEvent->contentJson())) + { + Room::connect(call, &BaseJob::started, q, + [this,pEvent,txnId] { + auto it = findAsPending(pEvent); + if (it == unsyncedEvents.end()) + { + qWarning(EVENTS) << "Pending event for transaction" << txnId + << "not found - got synced so soon?"; + return; + } + it->setDeparted(); + emit q->pendingEventChanged(it - unsyncedEvents.begin()); + }); + Room::connect(call, &BaseJob::failure, q, + std::bind(&Room::Private::onEventSendingFailure, + this, pEvent, txnId, call)); + Room::connect(call, &BaseJob::success, q, + [this,call,pEvent,txnId] { + // Find an event by the pointer saved in the lambda (the pointer + // may be dangling by now but we can still search by it). + auto it = findAsPending(pEvent); + if (it == unsyncedEvents.end()) + { + qDebug(EVENTS) << "Pending event for transaction" << txnId + << "already merged"; + return; + } - it->setReachedServer(call->eventId()); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); - }); + it->setReachedServer(call->eventId()); + emit q->pendingEventChanged(it - unsyncedEvents.begin()); + }); + } else + onEventSendingFailure(pEvent, txnId); return txnId; } @@ -1221,6 +1215,22 @@ Room::PendingEvents::iterator Room::Private::findAsPending( return std::find_if(unsyncedEvents.begin(), unsyncedEvents.end(), comp); } +void Room::Private::onEventSendingFailure(const RoomEvent* pEvent, + const QString& txnId, BaseJob* call) +{ + auto it = findAsPending(pEvent); + if (it == unsyncedEvents.end()) + { + qCritical(EVENTS) << "Pending event for transaction" << txnId + << "could not be sent"; + return; + } + it->setSendingFailed(call + ? call->statusCaption() % ": " % call->errorString() + : tr("The call could not be started")); + emit q->pendingEventChanged(it - unsyncedEvents.begin()); +} + QString Room::retryMessage(const QString& txnId) { auto it = std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(), -- cgit v1.2.3 From 6ca6dde46b9c72fc8833bc6fb81614fb705424f2 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 11 Nov 2018 15:24:13 +0900 Subject: Improvements in comments - registerEventType(): comment the cryptic _ variable - Room::postEvent: document the return value - Room::Private: upgrade comments to doc-comments - even though in Private, they still are helpful to show hints in IDEs. - General cleanup --- lib/room.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 5dd244f2..088d1d8e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -94,9 +94,11 @@ class Room::Private Connection* connection; QString id; JoinState joinState; - // The state of the room at timeline position before-0 + /// The state of the room at timeline position before-0 + /// \sa timelineBase std::unordered_map baseState; - // The state of the room at timeline position after-maxTimelineIndex() + /// The state of the room at timeline position after-maxTimelineIndex() + /// \sa Room::syncEdge QHash currentState; Timeline timeline; PendingEvents unsyncedEvents; @@ -156,8 +158,8 @@ class Room::Private fileTransfers[tid].status = FileTransferInfo::Failed; emit q->fileTransferFailed(tid, errorMessage); } - // A map from event/txn ids to information about the long operation; - // used for both download and upload operations + /// A map from event/txn ids to information about the long operation; + /// used for both download and upload operations QHash fileTransfers; const RoomMessageEvent* getEventWithFile(const QString& eventId) const; -- cgit v1.2.3 From 2a72a0ad0f8b4a0d24c9c2262917ff658ca5fec4 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 11 Nov 2018 15:33:03 +0900 Subject: Room: historyEdge(), syncEdge, Private::timelineBase() Also: make moveEventsToTimeline() always put historical events from position -1 rather than 0 so that Private::baseState could always correspond to the before-0 position. --- lib/room.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 088d1d8e..fd4add3b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -170,6 +170,9 @@ class Room::Private void renameMember(User* u, QString oldName); void removeMemberFromMap(const QString& username, User* u); + /// A point in the timeline corresponding to baseState + rev_iter_t timelineBase() const { return q->findInTimeline(-1); } + void getPreviousContent(int limit = 10); template @@ -525,11 +528,21 @@ int Room::unreadCount() const return d->unreadMessages; } -Room::rev_iter_t Room::timelineEdge() const +Room::rev_iter_t Room::historyEdge() const { return d->timeline.crend(); } +Room::Timeline::const_iterator Room::syncEdge() const +{ + return d->timeline.cend(); +} + +Room::rev_iter_t Room::timelineEdge() const +{ + return historyEdge(); +} + TimelineItem::index_t Room::minTimelineIndex() const { return d->timeline.empty() ? 0 : d->timeline.front().index(); @@ -1018,8 +1031,9 @@ Room::Timeline::difference_type Room::Private::moveEventsToTimeline( { Q_ASSERT(!events.empty()); // Historical messages arrive in newest-to-oldest order, so the process for - // them is symmetric to the one for new messages. - auto index = timeline.empty() ? -int(placement) : + // them is almost symmetric to the one for new messages. New messages get + // appended from index 0; old messages go backwards from index -1. + auto index = timeline.empty() ? -((placement+1)/2) /* 1 -> -1; -1 -> 0 */ : placement == Older ? timeline.front().index() : timeline.back().index(); auto baseIndex = index; @@ -1040,7 +1054,7 @@ Room::Timeline::difference_type Room::Private::moveEventsToTimeline( eventsIndex.insert(eId, index); Q_ASSERT(q->findInTimeline(eId)->event()->id() == eId); } - const auto insertedSize = (index - baseIndex) * int(placement); + const auto insertedSize = (index - baseIndex) * placement; Q_ASSERT(insertedSize == int(events.size())); return insertedSize; } -- cgit v1.2.3 From 3478e691df49b9c0938220db57b03a9c6fcbec8d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 14 Nov 2018 07:26:31 +0900 Subject: Room: fix incorrect handling of state event redactions Also: use Matrix type instead of internal type id in StateEventKey (Because internal type id maps to the library type system which will not discern between Unknown events and therefore will mix together events of different types in Room::Private::baseState/currentState. The Room code is updated accordingly (bonus: more asserts there).) Closes #255. --- lib/room.cpp | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index fd4add3b..38a4157e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -178,10 +178,12 @@ class Room::Private template const EventT* getCurrentState(QString stateKey = {}) const { - static const EventT emptyEvent { QJsonObject{} }; - return static_cast( - currentState.value({EventT::typeId(), stateKey}, - &emptyEvent)); + static const EventT empty; + const auto* evt = + currentState.value({EventT::matrixTypeId(), stateKey}, &empty); + Q_ASSERT(evt->type() == EventT::typeId() && + evt->matrixType() == EventT::matrixTypeId()); + return static_cast(evt); } bool isEventNotable(const TimelineItem& ti) const @@ -1117,7 +1119,8 @@ void Room::updateData(SyncRoomData&& data) for (auto&& eptr: data.state) { const auto& evt = *eptr; - d->baseState[{evt.type(),evt.stateKey()}] = move(eptr); + Q_ASSERT(evt.isStateEvent()); + d->baseState[{evt.matrixType(),evt.stateKey()}] = move(eptr); emitNamesChanged |= processStateEvent(evt); } @@ -1628,11 +1631,26 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) return true; } - // Make a new event from the redacted JSON, exchange events, - // notify everyone and delete the old event + // Make a new event from the redacted JSON and put it in the timeline + // instead of the redacted one. oldEvent will be deleted on return. auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction)); - q->onRedaction(*oldEvent, *ti.event()); qCDebug(MAIN) << "Redacted" << oldEvent->id() << "with" << redaction.id(); + if (oldEvent->isStateEvent()) + { + const StateEventKey evtKey { oldEvent->matrixType(), oldEvent->stateKey() }; + Q_ASSERT(currentState.contains(evtKey)); + if (currentState[evtKey] == oldEvent.get()) + { + Q_ASSERT(ti.index() >= 0); // Historical states can't be in currentState + qCDebug(MAIN).nospace() << "Reverting state " + << oldEvent->matrixType() << "/" << oldEvent->stateKey(); + // Retarget the current state to the newly made event. + if (q->processStateEvent(*ti)) + emit q->namesChanged(q); + updateDisplayname(); + } + } + q->onRedaction(*oldEvent, *ti); emit q->replacedEvent(ti.event(), rawPtr(oldEvent)); return true; } @@ -1791,7 +1809,7 @@ bool Room::processStateEvent(const RoomEvent& e) if (!e.isStateEvent()) return false; - d->currentState[{e.type(),e.stateKey()}] = + d->currentState[{e.matrixType(),e.stateKey()}] = static_cast(&e); if (!is(e)) qCDebug(EVENTS) << "Room state event:" << e; @@ -2060,7 +2078,10 @@ QJsonObject Room::Private::toJson() const QJsonArray stateEvents; for (const auto& evt: currentState) + { + Q_ASSERT(evt->isStateEvent()); stateEvents.append(evt->fullJson()); + } const auto stateObjName = joinState == JoinState::Invite ? QStringLiteral("invite_state") : QStringLiteral("state"); -- cgit v1.2.3 From 6f18091a48530399908fbc6ebcb0697bae970abb Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 17 Nov 2018 20:40:17 +0900 Subject: Room::processStateEvent: process banning correctly Closes #258. --- lib/room.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 38a4157e..3718a54b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1860,10 +1860,12 @@ bool Room::processStateEvent(const RoomEvent& e) emit userAdded(u); } } - else if( evt.membership() == MembershipType::Leave ) + else if( evt.membership() != MembershipType::Join ) { if (memberJoinState(u) == JoinState::Join) { + if (evt.membership() == MembershipType::Invite) + qCWarning(MAIN) << "Invalid membership change:" << evt; if (!d->membersLeft.contains(u)) d->membersLeft.append(u); d->removeMemberFromMap(u->name(this), u); -- cgit v1.2.3 From 06edc1033427ca96f03954d810aef33e5c940597 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 17 Nov 2018 21:55:32 +0900 Subject: Room: cleanup --- lib/room.cpp | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 3718a54b..0db124ee 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1675,45 +1675,37 @@ inline bool isRedaction(const RoomEventPtr& ep) void Room::Private::addNewMessageEvents(RoomEvents&& events) { dropDuplicateEvents(events); + if (events.empty()) + return; // Pre-process redactions so that events that get redacted in the same // batch landed in the timeline already redacted. - // XXX: The code below is written (and commented) so that it could be - // quickly converted to not-saving redaction events in the timeline. - // See #220 for details. - auto newEnd = std::find_if(events.begin(), events.end(), isRedaction); - // Either process the redaction, or shift the non-redaction event - // overwriting redactions in a remove_if fashion. - for(const auto& eptr: RoomEventsRange(newEnd, events.end())) + // NB: We have to store redaction events to the timeline too - see #220. + auto redactionIt = std::find_if(events.begin(), events.end(), isRedaction); + for(const auto& eptr: RoomEventsRange(redactionIt, events.end())) if (auto* r = eventCast(eptr)) { // Try to find the target in the timeline, then in the batch. if (processRedaction(*r)) continue; - auto targetIt = std::find_if(events.begin(), newEnd, + auto targetIt = std::find_if(events.begin(), redactionIt, [id=r->redactedEvent()] (const RoomEventPtr& ep) { return ep->id() == id; }); - if (targetIt != newEnd) + if (targetIt != redactionIt) *targetIt = makeRedacted(**targetIt, *r); else qCDebug(MAIN) << "Redaction" << r->id() << "ignored: target event" << r->redactedEvent() << "is not found"; - // If the target events comes later, it comes already redacted. + // If the target event comes later, it comes already redacted. } -// else // This should be uncommented once we stop adding redactions to the timeline -// *newEnd++ = std::move(eptr); - newEnd = events.end(); // This line should go if/when we stop adding redactions to the timeline - - if (events.begin() == newEnd) - return; auto timelineSize = timeline.size(); auto totalInserted = 0; - for (auto it = events.begin(); it != newEnd;) + for (auto it = events.begin(); it != events.end();) { - auto nextPendingPair = findFirstOf(it, newEnd, + auto nextPendingPair = findFirstOf(it, events.end(), unsyncedEvents.begin(), unsyncedEvents.end(), isEchoEvent); auto nextPending = nextPendingPair.first; @@ -1727,7 +1719,7 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events) q->onAddNewTimelineEvents(firstInserted); emit q->addedMessages(firstInserted->index(), timeline.back().index()); } - if (nextPending == newEnd) + if (nextPending == events.end()) break; it = nextPending + 1; @@ -2079,7 +2071,7 @@ QJsonObject Room::Private::toJson() const { QJsonArray stateEvents; - for (const auto& evt: currentState) + for (const auto* evt: currentState) { Q_ASSERT(evt->isStateEvent()); stateEvents.append(evt->fullJson()); -- cgit v1.2.3 From e9d72c469b4c9a2246a086e4c47d80fe7d011179 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 18 Nov 2018 20:21:06 +0900 Subject: Room: profile addHistoricalMessageEvents (+cleanup) --- lib/room.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 0db124ee..6777732b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -435,7 +435,7 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to) unreadMessages = 0; unreadMessages += newUnreadMessages; - qCDebug(MAIN) << "Room" << displayname << "has gained" + qCDebug(MAIN) << "Room" << q->objectName() << "has gained" << newUnreadMessages << "unread message(s)," << (q->readMarker() == timeline.crend() ? "in total at least" : "in total") @@ -1747,7 +1747,7 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events) if (totalInserted > 0) { qCDebug(MAIN) - << "Room" << displayname << "received" << totalInserted + << "Room" << q->objectName() << "received" << totalInserted << "new events; the last event is now" << timeline.back(); // The first event in the just-added batch (referred to by `from`) @@ -1772,6 +1772,7 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events) void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) { + QElapsedTimer et; et.start(); const auto timelineSize = timeline.size(); dropDuplicateEvents(events); @@ -1794,6 +1795,9 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) updateUnreadCount(from, timeline.crend()); Q_ASSERT(timeline.size() == timelineSize + insertedSize); + if (insertedSize > 9 || et.nsecsElapsed() >= profilerMinNsecs()) + qCDebug(PROFILER) << "*** Room::addHistoricalMessageEvents():" + << insertedSize << "event(s)," << et; } bool Room::processStateEvent(const RoomEvent& e) @@ -1831,8 +1835,7 @@ bool Room::processStateEvent(const RoomEvent& e) u->processEvent(evt, this); if (u == localUser() && memberJoinState(u) == JoinState::Invite && evt.isDirect()) - connection()->addToDirectChats(this, - user(evt.senderId())); + connection()->addToDirectChats(this, user(evt.senderId())); if( evt.membership() == MembershipType::Join ) { -- cgit v1.2.3 From 82c78b63cdd093853fd058740e7038e3c8a1cbbd Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 18 Nov 2018 20:18:10 +0900 Subject: Room: expose eventsHistoryJob as a Q_PROPERTY --- lib/room.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 6777732b..4bd96fc3 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -25,7 +25,6 @@ #include "csapi/receipts.h" #include "csapi/redaction.h" #include "csapi/account-data.h" -#include "csapi/message_pagination.h" #include "csapi/room_state.h" #include "csapi/room_send.h" #include "csapi/tags.h" @@ -975,6 +974,11 @@ bool Room::usesEncryption() const return !d->getCurrentState()->algorithm().isEmpty(); } +GetRoomEventsJob* Room::eventsHistoryJob() const +{ + return d->eventsHistoryJob; +} + void Room::Private::insertMemberIntoMap(User *u) { const auto userName = u->name(q); @@ -1393,10 +1397,13 @@ void Room::Private::getPreviousContent(int limit) { eventsHistoryJob = connection->callApi(id, prevBatch, "b", "", limit); + emit q->eventsHistoryJobChanged(); connect( eventsHistoryJob, &BaseJob::success, q, [=] { prevBatch = eventsHistoryJob->end(); addHistoricalMessageEvents(eventsHistoryJob->chunk()); }); + connect( eventsHistoryJob, &QObject::destroyed, + q, &Room::eventsHistoryJobChanged); } } -- cgit v1.2.3 From 06998c1406aab9943b62d6facb36cdee1cf52115 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 19 Nov 2018 09:18:53 +0900 Subject: Room: process new state events after applying redactions This was one more cause of #257 - the case when a redaction on a state event arrives in the same batch as the redacted event. --- lib/room.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 4bd96fc3..5faff271 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -192,7 +192,7 @@ class Room::Private is(*ti); } - void addNewMessageEvents(RoomEvents&& events); + bool addNewMessageEvents(RoomEvents&& events); void addHistoricalMessageEvents(RoomEvents&& events); /** Move events into the timeline @@ -1135,24 +1135,15 @@ void Room::updateData(SyncRoomData&& data) if (!data.timeline.empty()) { et.restart(); - // State changes can arrive in a timeline event; so check those. - for (const auto& e: data.timeline) - emitNamesChanged |= processStateEvent(*e); + emitNamesChanged |= d->addNewMessageEvents(move(data.timeline)); if (data.timeline.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::processStateEvents(timeline):" + qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" << data.timeline.size() << "event(s)," << et; } if (emitNamesChanged) emit namesChanged(this); d->updateDisplayname(); - if (!data.timeline.empty()) - { - et.restart(); - d->addNewMessageEvents(move(data.timeline)); - if (data.timeline.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" << et; - } for( auto&& ephemeralEvent: data.ephemeral ) processEphemeralEvent(move(ephemeralEvent)); @@ -1679,11 +1670,11 @@ inline bool isRedaction(const RoomEventPtr& ep) return is(*ep); } -void Room::Private::addNewMessageEvents(RoomEvents&& events) +bool Room::Private::addNewMessageEvents(RoomEvents&& events) { dropDuplicateEvents(events); if (events.empty()) - return; + return false; // Pre-process redactions so that events that get redacted in the same // batch landed in the timeline already redacted. @@ -1708,6 +1699,15 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events) // If the target event comes later, it comes already redacted. } + // State changes arrive as a part of timeline; the current room state gets + // updated before merging events to the timeline because that's what + // clients historically expect. This may eventually change though if we + // postulate that the current state is only current between syncs but not + // within a sync. + bool emitNamesChanged = false; + for (const auto& eptr: events) + emitNamesChanged |= q->processStateEvent(*eptr); + auto timelineSize = timeline.size(); auto totalInserted = 0; for (auto it = events.begin(); it != events.end();) @@ -1775,6 +1775,7 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events) } Q_ASSERT(timeline.size() == timelineSize + totalInserted); + return emitNamesChanged; } void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) -- cgit v1.2.3 From e1fdb33a4161b29d6df590ccea339d361d9fc4e8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 19 Nov 2018 09:22:27 +0900 Subject: Don't cache empty events; prepare for lazy-loading These two are intermingled in Room::addHistoricalMessageEvents because processing empty events found in a historical batch is no different from discovering (not lazy-loaded) members. --- lib/room.cpp | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 5faff271..656788cb 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1784,14 +1784,25 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) const auto timelineSize = timeline.size(); dropDuplicateEvents(events); - RoomEventsRange normalEvents { - events.begin(), events.end() //remove_if(events.begin(), events.end(), isRedaction) - }; - if (normalEvents.empty()) + if (events.empty()) return; - emit q->aboutToAddHistoricalMessages(normalEvents); - const auto insertedSize = moveEventsToTimeline(normalEvents, Older); + // In case of lazy-loading new members may be loaded with historical + // messages. Also, the cache doesn't store events with empty content; + // so when such events show up in the timeline they should be properly + // incorporated. + for (const auto& eptr: events) + { + const auto& e = *eptr; + if (e.isStateEvent() && + !currentState.contains({e.matrixType(), e.stateKey()})) + { + q->processStateEvent(e); + } + } + + emit q->aboutToAddHistoricalMessages(events); + const auto insertedSize = moveEventsToTimeline(events, Older); const auto from = timeline.crend() - insertedSize; qCDebug(MAIN) << "Room" << displayname << "received" << insertedSize @@ -2085,7 +2096,15 @@ QJsonObject Room::Private::toJson() const for (const auto* evt: currentState) { Q_ASSERT(evt->isStateEvent()); - stateEvents.append(evt->fullJson()); + if ((evt->isRedacted() && !is(*evt)) || + evt->contentJson().isEmpty()) + continue; + + auto json = evt->fullJson(); + auto unsignedJson = evt->unsignedJson(); + unsignedJson.remove(QStringLiteral("prev_content")); + json[UnsignedKeyL] = unsignedJson; + stateEvents.append(json); } const auto stateObjName = joinState == JoinState::Invite ? @@ -2098,7 +2117,10 @@ QJsonObject Room::Private::toJson() const { QJsonArray accountDataEvents; for (const auto& e: accountData) - accountDataEvents.append(e.second->fullJson()); + { + if (!e.second->contentJson().isEmpty()) + accountDataEvents.append(e.second->fullJson()); + } result.insert(QStringLiteral("account_data"), QJsonObject {{ QStringLiteral("events"), accountDataEvents }}); } -- cgit v1.2.3 From dc3d6bd3b46ae7a9e8d9b9f62e50db982ef2b004 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 20 Nov 2018 13:24:40 +0900 Subject: Make SyncData more self-contained and prepare for cache splitting SyncData now resides in its own pair of files and is capable to load either from file or from JSON. There is also (yet untested) capability to load rooms from files if a file name stands is the value for a given room id. This allows to store the master cache file separately from cache files for each room, massively easing the problem of bulky accounts that can overflow the poor capacity of Qt's JSON engine. --- lib/room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 656788cb..e5653258 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -45,10 +45,10 @@ #include "connection.h" #include "user.h" #include "converters.h" +#include "syncdata.h" #include #include // for efficient string concats (operator%) -#include #include #include #include -- cgit v1.2.3 From 0c3a45356a803baa0eb5e553262a85cac897ac4f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 22 Nov 2018 13:00:33 +0900 Subject: Room: Change enum, Changes flag set, and changed() signal This allows to batch updates into signals being emitted only once per sync. Also supercedes emitNamesChanged flag used in a few places. --- lib/room.cpp | 49 +++++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 22 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index e5653258..55923ed8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -192,7 +192,7 @@ class Room::Private is(*ti); } - bool addNewMessageEvents(RoomEvents&& events); + Changes addNewMessageEvents(RoomEvents&& events); void addHistoricalMessageEvents(RoomEvents&& events); /** Move events into the timeline @@ -1116,7 +1116,7 @@ void Room::updateData(SyncRoomData&& data) for (auto&& event: data.accountData) processAccountDataEvent(move(event)); - bool emitNamesChanged = false; + Changes roomChanges = Change::NoChange; if (!data.state.empty()) { et.restart(); @@ -1125,7 +1125,7 @@ void Room::updateData(SyncRoomData&& data) const auto& evt = *eptr; Q_ASSERT(evt.isStateEvent()); d->baseState[{evt.matrixType(),evt.stateKey()}] = move(eptr); - emitNamesChanged |= processStateEvent(evt); + roomChanges |= processStateEvent(evt); } if (data.state.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) @@ -1135,13 +1135,17 @@ void Room::updateData(SyncRoomData&& data) if (!data.timeline.empty()) { et.restart(); - emitNamesChanged |= d->addNewMessageEvents(move(data.timeline)); + roomChanges |= d->addNewMessageEvents(move(data.timeline)); if (data.timeline.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" << data.timeline.size() << "event(s)," << et; } - if (emitNamesChanged) + if (roomChanges&TopicChange) + emit topicChanged(); + + if (roomChanges&NameChange) emit namesChanged(this); + d->updateDisplayname(); for( auto&& ephemeralEvent: data.ephemeral ) @@ -1165,6 +1169,8 @@ void Room::updateData(SyncRoomData&& data) d->notificationCount = data.notificationCount; emit notificationCountChanged(this); } + if (roomChanges != Change::NoChange) + emit changed(roomChanges); } QString Room::Private::sendEvent(RoomEventPtr&& event) @@ -1670,11 +1676,11 @@ inline bool isRedaction(const RoomEventPtr& ep) return is(*ep); } -bool Room::Private::addNewMessageEvents(RoomEvents&& events) +Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) { dropDuplicateEvents(events); if (events.empty()) - return false; + return Change::NoChange; // Pre-process redactions so that events that get redacted in the same // batch landed in the timeline already redacted. @@ -1704,9 +1710,9 @@ bool Room::Private::addNewMessageEvents(RoomEvents&& events) // clients historically expect. This may eventually change though if we // postulate that the current state is only current between syncs but not // within a sync. - bool emitNamesChanged = false; + Changes stateChanges = Change::NoChange; for (const auto& eptr: events) - emitNamesChanged |= q->processStateEvent(*eptr); + stateChanges |= q->processStateEvent(*eptr); auto timelineSize = timeline.size(); auto totalInserted = 0; @@ -1775,7 +1781,7 @@ bool Room::Private::addNewMessageEvents(RoomEvents&& events) } Q_ASSERT(timeline.size() == timelineSize + totalInserted); - return emitNamesChanged; + return stateChanges; } void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) @@ -1819,10 +1825,10 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) << insertedSize << "event(s)," << et; } -bool Room::processStateEvent(const RoomEvent& e) +Room::Changes Room::processStateEvent(const RoomEvent& e) { if (!e.isStateEvent()) - return false; + return Change::NoChange; d->currentState[{e.matrixType(),e.stateKey()}] = static_cast(&e); @@ -1831,23 +1837,22 @@ bool Room::processStateEvent(const RoomEvent& e) return visit(e , [] (const RoomNameEvent&) { - return true; + return NameChange; } , [] (const RoomAliasesEvent&) { - return true; + return OtherChange; } , [this] (const RoomCanonicalAliasEvent& evt) { setObjectName(evt.alias().isEmpty() ? d->id : evt.alias()); - return true; + return CanonicalAliasChange; } - , [this] (const RoomTopicEvent&) { - emit topicChanged(); - return false; + , [] (const RoomTopicEvent&) { + return TopicChange; } , [this] (const RoomAvatarEvent& evt) { if (d->avatar.updateUrl(evt.url())) emit avatarChanged(); - return false; + return AvatarChange; } , [this] (const RoomMemberEvent& evt) { auto* u = user(evt.userId()); @@ -1886,11 +1891,11 @@ bool Room::processStateEvent(const RoomEvent& e) emit userRemoved(u); } } - return false; + return MembersChange; } , [this] (const EncryptionEvent&) { - emit encryption(); - return false; + emit encryption(); // It can only be done once, so emit it here. + return EncryptionOn; } ); } -- cgit v1.2.3 From 5fb74ca3d253b658fe77aaeb6a106cf6c0a9e7f0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 22 Nov 2018 16:51:49 +0900 Subject: Save state cache per-room Closes #257. --- lib/room.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 55923ed8..2d958dca 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1170,7 +1170,10 @@ void Room::updateData(SyncRoomData&& data) emit notificationCountChanged(this); } if (roomChanges != Change::NoChange) + { emit changed(roomChanges); + connection()->saveRoomState(this); + } } QString Room::Private::sendEvent(RoomEventPtr&& event) -- cgit v1.2.3 From 49ad563550ba9d2d03fc7a519ccb857a6d08791c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 23 Nov 2018 15:38:59 +0900 Subject: Room/Connection: don't save the just loaded room cache --- lib/room.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 2d958dca..22a0d585 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1106,7 +1106,7 @@ QString Room::roomMembername(const QString& userId) const return roomMembername(user(userId)); } -void Room::updateData(SyncRoomData&& data) +void Room::updateData(SyncRoomData&& data, bool fromCache) { if( d->prevBatch.isEmpty() ) d->prevBatch = data.timelinePrevBatch; @@ -1172,7 +1172,8 @@ void Room::updateData(SyncRoomData&& data) if (roomChanges != Change::NoChange) { emit changed(roomChanges); - connection()->saveRoomState(this); + if (!fromCache) + connection()->saveRoomState(this); } } -- cgit v1.2.3 From 52081fe91724e5f2a82c55f0e6230d8841dd859a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 23 Nov 2018 15:53:16 +0900 Subject: Room: track more Changes --- lib/room.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 22a0d585..2a1d6f66 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -382,6 +382,7 @@ void Room::setJoinState(JoinState state) d->joinState = state; qCDebug(MAIN) << "Room" << id() << "changed state: " << int(oldState) << "->" << int(state); + emit changed(Change::JoinStateChange); emit joinStateChanged(oldState, state); } @@ -1112,11 +1113,11 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) d->prevBatch = data.timelinePrevBatch; setJoinState(data.joinState); + Changes roomChanges = Change::NoChange; QElapsedTimer et; et.start(); for (auto&& event: data.accountData) - processAccountDataEvent(move(event)); + roomChanges |= processAccountDataEvent(move(event)); - Changes roomChanges = Change::NoChange; if (!data.state.empty()) { et.restart(); @@ -1973,7 +1974,7 @@ void Room::processEphemeralEvent(EventPtr&& event) } } -void Room::processAccountDataEvent(EventPtr&& event) +Room::Changes Room::processAccountDataEvent(EventPtr&& event) { if (auto* evt = eventCast(event)) d->setTags(evt->tags()); @@ -2000,7 +2001,9 @@ void Room::processAccountDataEvent(EventPtr&& event) qCDebug(MAIN) << "Updated account data of type" << currentData->matrixType(); emit accountDataChanged(currentData->matrixType()); + return Change::AccountDataChange; } + return Change::NoChange; } QString Room::Private::roomNameFromMemberNames(const QList &userlist) const -- cgit v1.2.3 From fd524590e3888ee5b4c0e25eb2138db4763dd0ec Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 23 Nov 2018 16:40:10 +0900 Subject: Room::setLastReadEvent: save room state when updating own read marker --- lib/room.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 2a1d6f66..8b81bfb2 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -401,6 +401,7 @@ void Room::Private::setLastReadEvent(User* u, QString eventId) if (storedId != serverReadMarker) connection->callApi(id, storedId); emit q->readMarkerMoved(eventId, storedId); + connection->saveRoomState(q); } } -- cgit v1.2.3 From 9272d21ce6e5439444794e6da58e08421e8973db Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 8 Dec 2018 15:37:16 +0900 Subject: Room summaries --- lib/room.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 8b81bfb2..439baeb5 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -93,6 +93,7 @@ class Room::Private Connection* connection; QString id; JoinState joinState; + RoomSummary summary; /// The state of the room at timeline position before-0 /// \sa timelineBase std::unordered_map baseState; @@ -164,6 +165,8 @@ class Room::Private const RoomMessageEvent* getEventWithFile(const QString& eventId) const; QString fileNameToDownload(const RoomMessageEvent* event) const; + Changes setSummary(RoomSummary&& newSummary); + //void inviteUser(User* u); // We might get it at some point in time. void insertMemberIntoMap(User* u); void renameMember(User* u, QString oldName); @@ -596,6 +599,10 @@ void Room::setDisplayed(bool displayed) { resetHighlightCount(); resetNotificationCount(); +// if (d->lazyLoaded) +// { +// // TODO: Get all members +// } } } @@ -976,11 +983,41 @@ bool Room::usesEncryption() const return !d->getCurrentState()->algorithm().isEmpty(); } +int Room::joinedCount() const +{ + return d->summary.joinedMemberCount > 0 + ? d->summary.joinedMemberCount + : d->membersMap.size(); +} + +int Room::invitedCount() const +{ + // TODO: Store invited users in Room too + return d->summary.invitedMemberCount; +} + +int Room::totalMemberCount() const +{ + return joinedCount() + invitedCount(); +} + GetRoomEventsJob* Room::eventsHistoryJob() const { return d->eventsHistoryJob; } +Room::Changes Room::Private::setSummary(RoomSummary&& newSummary) +{ + if (summary == newSummary) + return Change::NoChange; + summary = move(newSummary); + qCDebug(MAIN).nospace() + << "Updated room summary: joined " << summary.joinedMemberCount + << ", invited " << summary.invitedMemberCount + << ", heroes: " << summary.heroes.join(','); + return Change::SummaryChange; +} + void Room::Private::insertMemberIntoMap(User *u) { const auto userName = u->name(q); @@ -1148,6 +1185,7 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) if (roomChanges&NameChange) emit namesChanged(this); + d->setSummary(move(data.summary)); d->updateDisplayname(); for( auto&& ephemeralEvent: data.ephemeral ) @@ -2073,7 +2111,14 @@ QString Room::Private::calculateDisplayname() const // return q->aliases().at(0); // 3. Room members - dispName = roomNameFromMemberNames(membersMap.values()); + if (!summary.heroes.empty()) + { + QList users; users.reserve(summary.heroes.size()); + for (const auto& h: summary.heroes) + users.push_back(q->user(h)); + dispName = roomNameFromMemberNames(users); + } else + dispName = roomNameFromMemberNames(membersMap.values()); if (!dispName.isEmpty()) return dispName; @@ -2103,6 +2148,7 @@ QJsonObject Room::Private::toJson() const { QElapsedTimer et; et.start(); QJsonObject result; + addParam(result, QStringLiteral("summary"), summary); { QJsonArray stateEvents; -- cgit v1.2.3 From d51684b759f686b2c9895c5013dce88fead9661b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 8 Dec 2018 22:42:25 +0900 Subject: MSC 688: MSC-compliant RoomSummary; update Room::calculateDisplayname() The members of the summary can be omitted in the payload; this change fixes calculation of the roomname from hero names passed in room summary. Also: RoomSummary can be dumped to QDebug now. --- lib/room.cpp | 126 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 68 insertions(+), 58 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 439baeb5..fec2dc18 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -263,8 +263,11 @@ class Room::Private QJsonObject toJson() const; private: + using users_shortlist_t = std::array; + template + users_shortlist_t buildShortlist(const ContT& users) const; + users_shortlist_t buildShortlist(const QStringList& userIds) const; QString calculateDisplayname() const; - QString roomNameFromMemberNames(const QList& userlist) const; bool isLocalUser(const User* u) const { @@ -985,9 +988,9 @@ bool Room::usesEncryption() const int Room::joinedCount() const { - return d->summary.joinedMemberCount > 0 - ? d->summary.joinedMemberCount - : d->membersMap.size(); + return d->summary.joinedMemberCount.omitted() + ? d->membersMap.size() + : d->summary.joinedMemberCount.value(); } int Room::invitedCount() const @@ -1008,13 +1011,14 @@ GetRoomEventsJob* Room::eventsHistoryJob() const Room::Changes Room::Private::setSummary(RoomSummary&& newSummary) { - if (summary == newSummary) + if (!summary.merge(newSummary)) return Change::NoChange; summary = move(newSummary); qCDebug(MAIN).nospace() - << "Updated room summary: joined " << summary.joinedMemberCount + << "Updated room summary for" << q->objectName() + << ": joined " << summary.joinedMemberCount << ", invited " << summary.invitedMemberCount - << ", heroes: " << summary.heroes.join(','); + << ", heroes: " << summary.heroes.value().join(','); return Change::SummaryChange; } @@ -1391,7 +1395,7 @@ bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re) bool Room::supportsCalls() const { - return d->membersMap.size() == 2; + return joinedCount() == 2; } void Room::inviteCall(const QString& callId, const int lifetime, @@ -2045,49 +2049,35 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) return Change::NoChange; } -QString Room::Private::roomNameFromMemberNames(const QList &userlist) const +template +Room::Private::users_shortlist_t +Room::Private::buildShortlist(const ContT& users) const { - // This is part 3(i,ii,iii) in the room displayname algorithm described - // in the CS spec (see also Room::Private::updateDisplayname() ). - // The spec requires to sort users lexicographically by state_key (user id) - // and use disambiguated display names of two topmost users excluding - // the current one to render the name of the room. - - // std::array is the leanest C++ container - std::array first_two = { {nullptr, nullptr} }; + // To calculate room display name the spec requires to sort users + // lexicographically by state_key (user id) and use disambiguated + // display names of two topmost users excluding the current one to render + // the name of the room. The below code selects 3 topmost users, + // slightly extending the spec. + users_shortlist_t shortlist { }; // Prefill with nullptrs std::partial_sort_copy( - userlist.begin(), userlist.end(), - first_two.begin(), first_two.end(), - [this](const User* u1, const User* u2) { - // Filter out the "me" user so that it never hits the room name + users.begin(), users.end(), + shortlist.begin(), shortlist.end(), + [this] (const User* u1, const User* u2) { + // localUser(), if it's in the list, is sorted below all others return isLocalUser(u2) || (!isLocalUser(u1) && u1->id() < u2->id()); } ); + return shortlist; +} - // Spec extension. A single person in the chat but not the local user - // (the local user is invited). - if (userlist.size() == 1 && !isLocalUser(first_two.front()) && - joinState == JoinState::Invite) - return tr("Invitation from %1") - .arg(q->roomMembername(first_two.front())); - - // i. One-on-one chat. first_two[1] == localUser() in this case. - if (userlist.size() == 2) - return q->roomMembername(first_two[0]); - - // ii. Two users besides the current one. - if (userlist.size() == 3) - return tr("%1 and %2") - .arg(q->roomMembername(first_two[0]), - q->roomMembername(first_two[1])); - - // iii. More users. - if (userlist.size() > 3) - return tr("%1 and %Ln other(s)", "", userlist.size() - 3) - .arg(q->roomMembername(first_two[0])); - - // userlist.size() < 2 - apparently, there's only current user in the room - return QString(); +Room::Private::users_shortlist_t +Room::Private::buildShortlist(const QStringList& userIds) const +{ + QList users; + users.reserve(userIds.size()); + for (const auto& h: userIds) + users.push_back(q->user(h)); + return buildShortlist(users); } QString Room::Private::calculateDisplayname() const @@ -2110,22 +2100,42 @@ QString Room::Private::calculateDisplayname() const //if (!q->aliases().empty() && !q->aliases().at(0).isEmpty()) // return q->aliases().at(0); - // 3. Room members - if (!summary.heroes.empty()) + // Supplementary code for 3 and 4: build the shortlist of users whose names + // will be used to construct the room name. Takes into account MSC688's + // "heroes" if available. + + const bool emptyRoom = membersMap.isEmpty() || + (membersMap.size() == 1 && isLocalUser(*membersMap.begin())); + const auto shortlist = + !summary.heroes.omitted() ? buildShortlist(summary.heroes.value()) : + !emptyRoom ? buildShortlist(membersMap) : + buildShortlist(membersLeft); + + QStringList names; + for (auto u: shortlist) { - QList users; users.reserve(summary.heroes.size()); - for (const auto& h: summary.heroes) - users.push_back(q->user(h)); - dispName = roomNameFromMemberNames(users); - } else - dispName = roomNameFromMemberNames(membersMap.values()); - if (!dispName.isEmpty()) - return dispName; + if (u == nullptr || isLocalUser(u)) + break; + names.push_back(q->roomMembername(u)); + } + + auto usersCountExceptLocal = emptyRoom + ? membersLeft.size() - int(joinState == JoinState::Leave) + : q->joinedCount() - int(joinState == JoinState::Join); + if (usersCountExceptLocal > int(shortlist.size())) + names << + tr("%Ln other(s)", + "Used to make a room name from user names: A, B and _N others_", + usersCountExceptLocal); + auto namesList = QLocale().createSeparatedList(names); + + // 3. Room members + if (!emptyRoom) + return namesList; // 4. Users that previously left the room - dispName = roomNameFromMemberNames(membersLeft); - if (!dispName.isEmpty()) - return tr("Empty room (was: %1)").arg(dispName); + if (membersLeft.size() > 0) + return tr("Empty room (was: %1)").arg(namesList); // 5. Fail miserably return tr("Empty room (%1)").arg(id); -- cgit v1.2.3 From 9225eaec426ecd44a1c203e11e1aafe7772c46d7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 9 Dec 2018 19:55:14 +0900 Subject: Room: track more changes; fix cache smashing upon restart Commit fd52459 introduced a regression rendering the cache unusable after a client restart (an empty state overwrites whatever state was in the cache). This commit contains the fix, along with more room change tracking. --- lib/room.cpp | 59 +++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 24 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index fec2dc18..ca5495ea 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -215,12 +215,12 @@ class Room::Private */ void dropDuplicateEvents(RoomEvents& events) const; - void setLastReadEvent(User* u, QString eventId); + Changes setLastReadEvent(User* u, QString eventId); void updateUnreadCount(rev_iter_t from, rev_iter_t to); - void promoteReadMarker(User* u, rev_iter_t newMarker, - bool force = false); + Changes promoteReadMarker(User* u, rev_iter_t newMarker, + bool force = false); - void markMessagesAsRead(rev_iter_t upToMarker); + Changes markMessagesAsRead(rev_iter_t upToMarker); QString sendEvent(RoomEventPtr&& event); @@ -392,11 +392,11 @@ void Room::setJoinState(JoinState state) emit joinStateChanged(oldState, state); } -void Room::Private::setLastReadEvent(User* u, QString eventId) +Room::Changes Room::Private::setLastReadEvent(User* u, QString eventId) { auto& storedId = lastReadEventIds[u]; if (storedId == eventId) - return; + return Change::NoChange; eventIdReadUsers.remove(storedId, u); eventIdReadUsers.insert(eventId, u); swap(storedId, eventId); @@ -407,8 +407,9 @@ void Room::Private::setLastReadEvent(User* u, QString eventId) if (storedId != serverReadMarker) connection->callApi(id, storedId); emit q->readMarkerMoved(eventId, storedId); - connection->saveRoomState(q); + return Change::ReadMarkerChange; } + return Change::NoChange; } void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to) @@ -451,14 +452,15 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to) } } -void Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, bool force) +Room::Changes Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, + bool force) { Q_ASSERT_X(u, __FUNCTION__, "User* should not be nullptr"); Q_ASSERT(newMarker >= timeline.crbegin() && newMarker <= timeline.crend()); const auto prevMarker = q->readMarker(u); if (!force && prevMarker <= newMarker) // Remember, we deal with reverse iterators - return; + return Change::NoChange; Q_ASSERT(newMarker < timeline.crend()); @@ -467,7 +469,7 @@ void Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, bool force) auto eagerMarker = find_if(newMarker.base(), timeline.cend(), [=](const TimelineItem& ti) { return ti->senderId() != u->id(); }); - setLastReadEvent(u, (*(eagerMarker - 1))->id()); + auto changes = setLastReadEvent(u, (*(eagerMarker - 1))->id()); if (isLocalUser(u)) { const auto oldUnreadCount = unreadMessages; @@ -491,14 +493,16 @@ void Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, bool force) qCDebug(MAIN) << "Room" << displayname << "still has" << unreadMessages << "unread message(s)"; emit q->unreadMessagesChanged(q); + changes |= Change::UnreadNotifsChange; } } + return changes; } -void Room::Private::markMessagesAsRead(rev_iter_t upToMarker) +Room::Changes Room::Private::markMessagesAsRead(rev_iter_t upToMarker) { const auto prevMarker = q->readMarker(); - promoteReadMarker(q->localUser(), upToMarker); + auto changes = promoteReadMarker(q->localUser(), upToMarker); if (prevMarker != upToMarker) qCDebug(MAIN) << "Marked messages as read until" << *q->readMarker(); @@ -514,6 +518,7 @@ void Room::Private::markMessagesAsRead(rev_iter_t upToMarker) break; } } + return changes; } void Room::markMessagesAsRead(QString uptoEventId) @@ -1193,7 +1198,7 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) d->updateDisplayname(); for( auto&& ephemeralEvent: data.ephemeral ) - processEphemeralEvent(move(ephemeralEvent)); + roomChanges |= processEphemeralEvent(move(ephemeralEvent)); // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages) @@ -1758,9 +1763,9 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // clients historically expect. This may eventually change though if we // postulate that the current state is only current between syncs but not // within a sync. - Changes stateChanges = Change::NoChange; + Changes roomChanges = Change::NoChange; for (const auto& eptr: events) - stateChanges |= q->processStateEvent(*eptr); + roomChanges |= q->processStateEvent(*eptr); auto timelineSize = timeline.size(); auto totalInserted = 0; @@ -1820,16 +1825,17 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) auto firstWriter = q->user((*from)->senderId()); if (q->readMarker(firstWriter) != timeline.crend()) { - promoteReadMarker(firstWriter, rev_iter_t(from) - 1); + roomChanges |= promoteReadMarker(firstWriter, rev_iter_t(from) - 1); qCDebug(MAIN) << "Auto-promoted read marker for" << firstWriter->id() << "to" << *q->readMarker(firstWriter); } updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); + roomChanges |= Change::UnreadNotifsChange; } Q_ASSERT(timeline.size() == timelineSize + totalInserted); - return stateChanges; + return roomChanges; } void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) @@ -1948,8 +1954,9 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) ); } -void Room::processEphemeralEvent(EventPtr&& event) +Room::Changes Room::processEphemeralEvent(EventPtr&& event) { + Changes changes = NoChange; QElapsedTimer et; et.start(); if (auto* evt = eventCast(event)) { @@ -1988,7 +1995,7 @@ void Room::processEphemeralEvent(EventPtr&& event) continue; // FIXME, #185 auto u = user(r.userId); if (memberJoinState(u) == JoinState::Join) - d->promoteReadMarker(u, newMarker); + changes |= d->promoteReadMarker(u, newMarker); } } else { @@ -2005,7 +2012,7 @@ void Room::processEphemeralEvent(EventPtr&& event) auto u = user(r.userId); if (memberJoinState(u) == JoinState::Join && readMarker(u) == timelineEdge()) - d->setLastReadEvent(u, p.evtId); + changes |= d->setLastReadEvent(u, p.evtId); } } } @@ -2015,12 +2022,17 @@ void Room::processEphemeralEvent(EventPtr&& event) << evt->eventsWithReceipts().size() << "event(s) with" << totalReceipts << "receipt(s)," << et; } + return changes; } Room::Changes Room::processAccountDataEvent(EventPtr&& event) { + Changes changes = NoChange; if (auto* evt = eventCast(event)) + { d->setTags(evt->tags()); + changes |= Change::TagsChange; + } if (auto* evt = eventCast(event)) { @@ -2028,10 +2040,9 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) qCDebug(MAIN) << "Server-side read marker at" << readEventId; d->serverReadMarker = readEventId; const auto newMarker = findInTimeline(readEventId); - if (newMarker != timelineEdge()) - d->markMessagesAsRead(newMarker); - else - d->setLastReadEvent(localUser(), readEventId); + changes |= newMarker != timelineEdge() + ? d->markMessagesAsRead(newMarker) + : d->setLastReadEvent(localUser(), readEventId); } // For all account data events auto& currentData = d->accountData[event->matrixType()]; -- cgit v1.2.3 From 9b3e437f3268e251f1950000b210cf849d49c24e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 9 Dec 2018 20:04:45 +0900 Subject: Room: defer memberListChanged(); track room summary changes This concludes beta-version of lazy-loading support in libQMatrixClient (#253). --- lib/room.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index ca5495ea..84072d3e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -282,9 +282,6 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) // See "Accessing the Public Class" section in // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ d->q = this; - connect(this, &Room::userAdded, this, &Room::memberListChanged); - connect(this, &Room::userRemoved, this, &Room::memberListChanged); - connect(this, &Room::memberRenamed, this, &Room::memberListChanged); qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id; } @@ -1018,12 +1015,9 @@ Room::Changes Room::Private::setSummary(RoomSummary&& newSummary) { if (!summary.merge(newSummary)) return Change::NoChange; - summary = move(newSummary); - qCDebug(MAIN).nospace() - << "Updated room summary for" << q->objectName() - << ": joined " << summary.joinedMemberCount - << ", invited " << summary.invitedMemberCount - << ", heroes: " << summary.heroes.value().join(','); + qCDebug(MAIN).nospace().noquote() + << "Updated room summary for " << q->objectName() << ": " << summary; + emit q->memberListChanged(); return Change::SummaryChange; } @@ -1194,7 +1188,10 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) if (roomChanges&NameChange) emit namesChanged(this); - d->setSummary(move(data.summary)); + if (roomChanges&MembersChange) + emit memberListChanged(); + + roomChanges |= d->setSummary(move(data.summary)); d->updateDisplayname(); for( auto&& ephemeralEvent: data.ephemeral ) -- cgit v1.2.3 From 501c79f55b5f6cb5df80993330d0b1ae1764024a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 10 Dec 2018 16:32:33 +0900 Subject: Room::getPreviousContent: use early return ...instead of the entire function body wrapped in an if block. --- lib/room.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 84072d3e..3cbd2271 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1440,18 +1440,18 @@ void Room::getPreviousContent(int limit) void Room::Private::getPreviousContent(int limit) { - if( !isJobRunning(eventsHistoryJob) ) - { - eventsHistoryJob = - connection->callApi(id, prevBatch, "b", "", limit); - emit q->eventsHistoryJobChanged(); - connect( eventsHistoryJob, &BaseJob::success, q, [=] { - prevBatch = eventsHistoryJob->end(); - addHistoricalMessageEvents(eventsHistoryJob->chunk()); - }); - connect( eventsHistoryJob, &QObject::destroyed, - q, &Room::eventsHistoryJobChanged); - } + if (isJobRunning(eventsHistoryJob)) + return; + + eventsHistoryJob = + connection->callApi(id, prevBatch, "b", "", limit); + emit q->eventsHistoryJobChanged(); + connect( eventsHistoryJob, &BaseJob::success, q, [=] { + prevBatch = eventsHistoryJob->end(); + addHistoricalMessageEvents(eventsHistoryJob->chunk()); + }); + connect( eventsHistoryJob, &QObject::destroyed, + q, &Room::eventsHistoryJobChanged); } void Room::inviteToRoom(const QString& memberId) -- cgit v1.2.3 From f0bd24a830aef3405994849ce413e2d488f75429 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 11 Dec 2018 12:29:57 +0900 Subject: Make Room::setDisplayed() trigger loading all members Closes #253. --- lib/room.cpp | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 3cbd2271..439bec0f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -27,6 +27,7 @@ #include "csapi/account-data.h" #include "csapi/room_state.h" #include "csapi/room_send.h" +#include "csapi/rooms.h" #include "csapi/tags.h" #include "events/simplestateevents.h" #include "events/roomavatarevent.h" @@ -121,6 +122,7 @@ class Room::Private std::unordered_map accountData; QString prevBatch; QPointer eventsHistoryJob; + QPointer allMembersJob; struct FileTransferPrivateInfo { @@ -222,6 +224,8 @@ class Room::Private Changes markMessagesAsRead(rev_iter_t upToMarker); + void getAllMembers(); + QString sendEvent(RoomEventPtr&& event); template @@ -588,6 +592,36 @@ Room::rev_iter_t Room::findInTimeline(const QString& evtId) const return timelineEdge(); } +void Room::Private::getAllMembers() +{ + // If already loaded or already loading, there's nothing to do here. + if (q->joinedCount() <= membersMap.size() || isJobRunning(allMembersJob)) + return; + + allMembersJob = connection->callApi( + id, connection->nextBatchToken(), "join"); + auto nextIndex = timeline.empty() ? 0 : timeline.back().index() + 1; + connect( allMembersJob, &BaseJob::success, q, [=] { + Q_ASSERT(timeline.empty() || nextIndex <= q->maxTimelineIndex() + 1); + Changes roomChanges = NoChange; + for (auto&& e: allMembersJob->chunk()) + { + const auto& evt = *e; + baseState[{evt.matrixType(),evt.stateKey()}] = move(e); + roomChanges |= q->processStateEvent(evt); + } + // Replay member events that arrived after the point for which + // the full members list was requested. + if (!timeline.empty() ) + for (auto it = q->findInTimeline(nextIndex).base(); + it != timeline.cend(); ++it) + if (is(**it)) + roomChanges |= q->processStateEvent(**it); + if (roomChanges&MembersChange) + emit q->memberListChanged(); + }); +} + bool Room::displayed() const { return d->displayed; @@ -604,10 +638,7 @@ void Room::setDisplayed(bool displayed) { resetHighlightCount(); resetNotificationCount(); -// if (d->lazyLoaded) -// { -// // TODO: Get all members -// } + d->getAllMembers(); } } -- cgit v1.2.3 From 095444aff98ac56663bb205837a57e746d950f3b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 12 Dec 2018 13:20:05 +0900 Subject: Room::allMembersLoaded(); more doc-comments --- lib/room.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 439bec0f..7232741a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -619,6 +619,7 @@ void Room::Private::getAllMembers() roomChanges |= q->processStateEvent(**it); if (roomChanges&MembersChange) emit q->memberListChanged(); + emit q->allMembersLoaded(); }); } -- cgit v1.2.3 From 2cbb053faeae1f23606c56ef9fd9d13ca4a2dd21 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 13 Dec 2018 19:56:18 +0900 Subject: Room::getAllMembers: fix off-by-one error --- lib/room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 7232741a..8f9095dd 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -595,7 +595,7 @@ Room::rev_iter_t Room::findInTimeline(const QString& evtId) const void Room::Private::getAllMembers() { // If already loaded or already loading, there's nothing to do here. - if (q->joinedCount() <= membersMap.size() || isJobRunning(allMembersJob)) + if (q->joinedCount() - 1 <= membersMap.size() || isJobRunning(allMembersJob)) return; allMembersJob = connection->callApi( -- cgit v1.2.3 From bf5401753432533b31e7d18519c2031c84e774b7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 16 Dec 2018 14:10:05 +0900 Subject: Room::getAllMembers: revert off-by-one "bugfix" It actually introduces an off-by-one error; the original code was correct. #qmatrixclient:matrix.org is used instead of #test:matrix.org to check lazy-loading (see https://github.com/matrix-org/synapse/issues/4300) --- lib/room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 8f9095dd..7232741a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -595,7 +595,7 @@ Room::rev_iter_t Room::findInTimeline(const QString& evtId) const void Room::Private::getAllMembers() { // If already loaded or already loading, there's nothing to do here. - if (q->joinedCount() - 1 <= membersMap.size() || isJobRunning(allMembersJob)) + if (q->joinedCount() <= membersMap.size() || isJobRunning(allMembersJob)) return; allMembersJob = connection->callApi( -- cgit v1.2.3 From 6276e6694a8fe2f8b37374ac8080a92721064eba Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 16 Dec 2018 14:11:51 +0900 Subject: Room: messageSent(), better pendingEventAboutToAdd(), more doc-comments --- lib/room.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 7232741a..156b5b1f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1260,7 +1260,7 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) if (event->transactionId().isEmpty()) event->setTransactionId(connection->generateTxnId()); auto* pEvent = rawPtr(event); - emit q->pendingEventAboutToAdd(); + emit q->pendingEventAboutToAdd(pEvent); unsyncedEvents.emplace_back(move(event)); emit q->pendingEventAdded(); return doSendEvent(pEvent); @@ -1290,6 +1290,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) this, pEvent, txnId, call)); Room::connect(call, &BaseJob::success, q, [this,call,pEvent,txnId] { + emit q->messageSent(txnId, call->eventId()); // Find an event by the pointer saved in the lambda (the pointer // may be dangling by now but we can still search by it). auto it = findAsPending(pEvent); -- cgit v1.2.3 From e017dd42637071687f88f5a36e7e03f1536332be Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 28 Dec 2018 12:53:01 +0900 Subject: FileTransferInfo: new properties: isUpload and started Also: use constructors instead of list-based initialisation in FileTransferPrivateInfo to enable a case of "invalid/empty" FileTransferPrivateInfo with status == None. --- lib/room.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 156b5b1f..d613fd77 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -126,15 +126,17 @@ class Room::Private struct FileTransferPrivateInfo { -#ifdef WORKAROUND_EXTENDED_INITIALIZER_LIST FileTransferPrivateInfo() = default; - FileTransferPrivateInfo(BaseJob* j, QString fileName) - : job(j), localFileInfo(fileName) + FileTransferPrivateInfo(BaseJob* j, const QString& fileName, + bool isUploading = false) + : status(FileTransferInfo::Started), job(j) + , localFileInfo(fileName), isUpload(isUploading) { } -#endif + + FileTransferInfo::Status status = FileTransferInfo::None; QPointer job = nullptr; QFileInfo localFileInfo { }; - FileTransferInfo::Status status = FileTransferInfo::Started; + bool isUpload = false; qint64 progress = 0; qint64 total = -1; @@ -969,7 +971,7 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const fti.localPath = QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()); return fti; #else - return { infoIt->status, int(progress), int(total), + return { infoIt->status, infoIt->isUpload, int(progress), int(total), QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()), QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()) }; @@ -1532,7 +1534,7 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, auto job = connection()->uploadFile(fileName, overrideContentType); if (isJobRunning(job)) { - d->fileTransfers.insert(id, { job, fileName }); + d->fileTransfers.insert(id, { job, fileName, true }); connect(job, &BaseJob::uploadProgress, this, [this,id] (qint64 sent, qint64 total) { d->fileTransfers[id].update(sent, total); -- cgit v1.2.3 From 143fffcf3962184befbbe37bebc5544d25bc7c39 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 28 Dec 2018 12:55:21 +0900 Subject: Room::fileSource Also: const'ified other methods related to file urls. --- lib/room.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index d613fd77..23bbbc5b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -911,7 +911,7 @@ QString Room::Private::fileNameToDownload(const RoomMessageEvent* event) const return fileName; } -QUrl Room::urlToThumbnail(const QString& eventId) +QUrl Room::urlToThumbnail(const QString& eventId) const { if (auto* event = d->getEventWithFile(eventId)) if (event->hasThumbnail()) @@ -925,7 +925,7 @@ QUrl Room::urlToThumbnail(const QString& eventId) return {}; } -QUrl Room::urlToDownload(const QString& eventId) +QUrl Room::urlToDownload(const QString& eventId) const { if (auto* event = d->getEventWithFile(eventId)) { @@ -937,7 +937,7 @@ QUrl Room::urlToDownload(const QString& eventId) return {}; } -QString Room::fileNameToDownload(const QString& eventId) +QString Room::fileNameToDownload(const QString& eventId) const { if (auto* event = d->getEventWithFile(eventId)) return d->fileNameToDownload(event); @@ -978,6 +978,21 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const #endif } +QUrl Room::fileSource(const QString& id) const +{ + auto url = urlToDownload(id); + if (url.isValid()) + return url; + + // No urlToDownload means it's a pending or completed upload. + auto infoIt = d->fileTransfers.find(id); + if (infoIt != d->fileTransfers.end()) + return QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()); + + qCWarning(MAIN) << "File source for identifier" << id << "not found"; + return {}; +} + QString Room::prettyPrint(const QString& plainText) const { return QMatrixClient::prettyPrint(plainText); -- cgit v1.2.3 From 3dcf0d3fd1e64d64b57976e400888fe8a02c4451 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 28 Dec 2018 15:00:08 +0900 Subject: Room::postFile() and supplementary things in Room::Private --- lib/room.cpp | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 5 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 23bbbc5b..6500366e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include @@ -67,9 +68,11 @@ using std::llround; enum EventsPlacement : int { Older = -1, Newer = 1 }; -// A workaround for MSVC 2015 that fails with "error C2440: 'return': -// cannot convert from 'initializer list' to 'QMatrixClient::FileTransferInfo'" -#if (defined(_MSC_VER) && _MSC_VER < 1910) || (defined(__GNUC__) && __GNUC__ <= 4) +// A workaround for MSVC 2015 and older GCC's that don't handle initializer +// lists right (MSVC 2015, notably, fails with "error C2440: 'return': +// cannot convert from 'initializer list' to 'QMatrixClient::FileTransferInfo'") +#if (defined(_MSC_VER) && _MSC_VER < 1910) || \ + (defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 4) # define WORKAROUND_EXTENDED_INITIALIZER_LIST #endif @@ -236,6 +239,8 @@ class Room::Private return sendEvent(makeEvent(std::forward(eventArgs)...)); } + RoomEvent* addAsPending(RoomEventPtr&& event); + QString doSendEvent(const RoomEvent* pEvent); PendingEvents::iterator findAsPending(const RoomEvent* rawEvtPtr); void onEventSendingFailure(const RoomEvent* pEvent, @@ -1272,7 +1277,7 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) } } -QString Room::Private::sendEvent(RoomEventPtr&& event) +RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event) { if (event->transactionId().isEmpty()) event->setTransactionId(connection->generateTxnId()); @@ -1280,7 +1285,12 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) emit q->pendingEventAboutToAdd(pEvent); unsyncedEvents.emplace_back(move(event)); emit q->pendingEventAdded(); - return doSendEvent(pEvent); + return pEvent; +} + +QString Room::Private::sendEvent(RoomEventPtr&& event) +{ + return doSendEvent(addAsPending(std::move(event))); } QString Room::Private::doSendEvent(const RoomEvent* pEvent) @@ -1357,6 +1367,7 @@ QString Room::retryMessage(const QString& txnId) [txnId] (const auto& evt) { return evt->transactionId() == txnId; }); Q_ASSERT(it != d->unsyncedEvents.end()); qDebug(EVENTS) << "Retrying transaction" << txnId; + // TODO: Support retrying uploads it->resetStatus(); return d->doSendEvent(it->event()); } @@ -1367,6 +1378,7 @@ void Room::discardMessage(const QString& txnId) [txnId] (const auto& evt) { return evt->transactionId() == txnId; }); Q_ASSERT(it != d->unsyncedEvents.end()); qDebug(EVENTS) << "Discarding transaction" << txnId; + // TODO: Discard an ongoing upload if there is any emit pendingEventAboutToDiscard(it - d->unsyncedEvents.begin()); d->unsyncedEvents.erase(it); emit pendingEventDiscarded(); @@ -1394,6 +1406,42 @@ QString Room::postHtmlText(const QString& plainText, const QString& html) return postHtmlMessage(plainText, html, MessageEventType::Text); } +QString Room::postFile(const QString& plainText, const QUrl& localPath) +{ + QFileInfo localFile { localPath.toLocalFile() }; + Q_ASSERT(localFile.isFile()); + // Remote URL will only be known after upload, see below. + auto* content = new EventContent::FileContent(QUrl(), localFile.size(), + QMimeDatabase().mimeTypeForUrl(localPath)); + // TODO: Set the msgtype based on MIME type + auto* pEvent = d->addAsPending( + makeEvent(plainText, "m.file", content)); + const auto txnId = pEvent->transactionId(); + uploadFile(txnId, localPath); + QMetaObject::Connection c; + c = connect(this, &Room::fileTransferCompleted, this, + [c,this,pEvent,txnId] (const QString& id, QUrl, const QUrl& mxcUri) { + if (id == txnId) + { + auto it = d->findAsPending(pEvent); + if (it != d->unsyncedEvents.end()) + { + it->setFileUploaded(mxcUri); + emit pendingEventChanged(it - d->unsyncedEvents.begin()); + d->doSendEvent(pEvent); + } else { + // Normally in this situation we should instruct + // the media server to delete the file; alas, there's no + // API specced for that. + qCWarning(MAIN) << "File uploaded to" << mxcUri + << "but the event referring to it was cancelled"; + } + disconnect(c); + } + }); + return txnId; +} + QString Room::postEvent(RoomEvent* event) { if (usesEncryption()) -- cgit v1.2.3 From e3c1b93483eafbb94f1224b57e562984f4100538 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 29 Dec 2018 23:12:46 +0900 Subject: Support file events in Room::retryMessage/discardMessage --- lib/room.cpp | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 6500366e..2c90955e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1367,7 +1367,32 @@ QString Room::retryMessage(const QString& txnId) [txnId] (const auto& evt) { return evt->transactionId() == txnId; }); Q_ASSERT(it != d->unsyncedEvents.end()); qDebug(EVENTS) << "Retrying transaction" << txnId; - // TODO: Support retrying uploads + const auto& transferIt = d->fileTransfers.find(txnId); + if (transferIt != d->fileTransfers.end()) + { + Q_ASSERT(transferIt->isUpload); + if (transferIt->status == FileTransferInfo::Completed) + { + qCDebug(MAIN) << "File for transaction" << txnId + << "has already been uploaded, bypassing re-upload"; + } else { + if (isJobRunning(transferIt->job)) + { + qCDebug(MAIN) << "Abandoning the upload job for transaction" + << txnId << "and starting again"; + transferIt->job->abandon(); + emit fileTransferFailed(txnId, tr("File upload will be retried")); + } + uploadFile(txnId, + QUrl::fromLocalFile(transferIt->localFileInfo.absoluteFilePath())); + // FIXME: Content type is no more passed here but it should + } + } + if (it->deliveryStatus() == EventStatus::ReachedServer) + { + qCWarning(MAIN) << "The previous attempt has reached the server; two" + " events are likely to be in the timeline after retry"; + } it->resetStatus(); return d->doSendEvent(it->event()); } @@ -1378,7 +1403,21 @@ void Room::discardMessage(const QString& txnId) [txnId] (const auto& evt) { return evt->transactionId() == txnId; }); Q_ASSERT(it != d->unsyncedEvents.end()); qDebug(EVENTS) << "Discarding transaction" << txnId; - // TODO: Discard an ongoing upload if there is any + const auto& transferIt = d->fileTransfers.find(txnId); + if (transferIt != d->fileTransfers.end()) + { + Q_ASSERT(transferIt->isUpload); + if (isJobRunning(transferIt->job)) + { + transferIt->status = FileTransferInfo::Cancelled; + transferIt->job->abandon(); + emit fileTransferFailed(txnId, tr("File upload cancelled")); + } else if (transferIt->status == FileTransferInfo::Completed) + { + qCWarning(MAIN) << "File for transaction" << txnId + << "has been uploaded but the message was discarded"; + } + } emit pendingEventAboutToDiscard(it - d->unsyncedEvents.begin()); d->unsyncedEvents.erase(it); emit pendingEventDiscarded(); -- cgit v1.2.3 From fb46c2d2a6e53557452837c2690f32a56387fcac Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 3 Jan 2019 22:28:09 +0900 Subject: Room: findPendingEvent; fixes for postFile() --- lib/room.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 20 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 2c90955e..8f50607f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -599,6 +599,19 @@ Room::rev_iter_t Room::findInTimeline(const QString& evtId) const return timelineEdge(); } +Room::PendingEvents::iterator Room::findPendingEvent(const QString& txnId) +{ + return std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(), + [txnId] (const auto& item) { return item->transactionId() == txnId; }); +} + +Room::PendingEvents::const_iterator +Room::findPendingEvent(const QString& txnId) const +{ + return std::find_if(d->unsyncedEvents.cbegin(), d->unsyncedEvents.cend(), + [txnId] (const auto& item) { return item->transactionId() == txnId; }); +} + void Room::Private::getAllMembers() { // If already loaded or already loading, there's nothing to do here. @@ -1363,8 +1376,7 @@ void Room::Private::onEventSendingFailure(const RoomEvent* pEvent, QString Room::retryMessage(const QString& txnId) { - auto it = std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(), - [txnId] (const auto& evt) { return evt->transactionId() == txnId; }); + const auto it = findPendingEvent(txnId); Q_ASSERT(it != d->unsyncedEvents.end()); qDebug(EVENTS) << "Retrying transaction" << txnId; const auto& transferIt = d->fileTransfers.find(txnId); @@ -1418,7 +1430,7 @@ void Room::discardMessage(const QString& txnId) << "has been uploaded but the message was discarded"; } } - emit pendingEventAboutToDiscard(it - d->unsyncedEvents.begin()); + emit pendingEventAboutToDiscard(int(it - d->unsyncedEvents.begin())); d->unsyncedEvents.erase(it); emit pendingEventDiscarded(); } @@ -1445,28 +1457,29 @@ QString Room::postHtmlText(const QString& plainText, const QString& html) return postHtmlMessage(plainText, html, MessageEventType::Text); } -QString Room::postFile(const QString& plainText, const QUrl& localPath) +QString Room::postFile(const QString& plainText, const QUrl& localPath, + bool asGenericFile) { QFileInfo localFile { localPath.toLocalFile() }; Q_ASSERT(localFile.isFile()); - // Remote URL will only be known after upload, see below. - auto* content = new EventContent::FileContent(QUrl(), localFile.size(), - QMimeDatabase().mimeTypeForUrl(localPath)); - // TODO: Set the msgtype based on MIME type + // Remote URL will only be known after upload; fill in the local path + // to enable the preview while the event is pending. auto* pEvent = d->addAsPending( - makeEvent(plainText, "m.file", content)); + makeEvent(plainText, localFile, asGenericFile)); const auto txnId = pEvent->transactionId(); uploadFile(txnId, localPath); - QMetaObject::Connection c; - c = connect(this, &Room::fileTransferCompleted, this, - [c,this,pEvent,txnId] (const QString& id, QUrl, const QUrl& mxcUri) { + QMetaObject::Connection cCompleted, cCancelled; + cCompleted = connect(this, &Room::fileTransferCompleted, this, + [cCompleted,cCancelled,this,pEvent,txnId] + (const QString& id, QUrl, const QUrl& mxcUri) { if (id == txnId) { auto it = d->findAsPending(pEvent); if (it != d->unsyncedEvents.end()) { it->setFileUploaded(mxcUri); - emit pendingEventChanged(it - d->unsyncedEvents.begin()); + emit pendingEventChanged( + int(it - d->unsyncedEvents.begin())); d->doSendEvent(pEvent); } else { // Normally in this situation we should instruct @@ -1475,9 +1488,27 @@ QString Room::postFile(const QString& plainText, const QUrl& localPath) qCWarning(MAIN) << "File uploaded to" << mxcUri << "but the event referring to it was cancelled"; } - disconnect(c); + disconnect(cCompleted); + disconnect(cCancelled); } }); + cCancelled = connect(this, &Room::fileTransferCancelled, this, + [cCompleted,cCancelled,this,pEvent,txnId] (const QString& id) { + if (id == txnId) + { + auto it = d->findAsPending(pEvent); + if (it != d->unsyncedEvents.end()) + { + emit pendingEventAboutToDiscard( + int(it - d->unsyncedEvents.begin())); + d->unsyncedEvents.erase(it); + emit pendingEventDiscarded(); + } + disconnect(cCompleted); + disconnect(cCancelled); + } + }); + return txnId; } @@ -1659,8 +1690,8 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) if (ongoingTransfer != d->fileTransfers.end() && ongoingTransfer->status == FileTransferInfo::Started) { - qCWarning(MAIN) << "Download for" << eventId - << "already started; to restart, cancel it first"; + qCWarning(MAIN) << "Transfer for" << eventId + << "is ongoing; download won't start"; return; } @@ -1923,11 +1954,15 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) break; it = nextPending + 1; - emit q->pendingEventAboutToMerge(nextPending->get(), - nextPendingPair.second - unsyncedEvents.begin()); + auto* nextPendingEvt = nextPending->get(); + emit q->pendingEventAboutToMerge(nextPendingEvt, + int(nextPendingPair.second - unsyncedEvents.begin())); qDebug(EVENTS) << "Merging pending event from transaction" - << (*nextPending)->transactionId() << "into" - << (*nextPending)->id(); + << nextPendingEvt->transactionId() << "into" + << nextPendingEvt->id(); + auto transfer = fileTransfers.take(nextPendingEvt->transactionId()); + if (transfer.status != FileTransferInfo::None) + fileTransfers.insert(nextPendingEvt->id(), transfer); unsyncedEvents.erase(nextPendingPair.second); if (auto insertedSize = moveEventsToTimeline({nextPending, it}, Newer)) { -- cgit v1.2.3 From d5c07b98cd708d0bf4590e7fd249aa972b090461 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 13 Jan 2019 13:32:04 +0900 Subject: Fix Omittables accidentally becoming non-omitted when compared with non-Omittable values --- lib/room.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 8f50607f..7ff8f5e9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -97,7 +97,7 @@ class Room::Private Connection* connection; QString id; JoinState joinState; - RoomSummary summary; + RoomSummary summary = { none, 0, none }; /// The state of the room at timeline position before-0 /// \sa timelineBase std::unordered_map baseState; @@ -1065,7 +1065,8 @@ int Room::joinedCount() const int Room::invitedCount() const { // TODO: Store invited users in Room too - return d->summary.invitedMemberCount; + Q_ASSERT(!d->summary.invitedMemberCount.omitted()); + return d->summary.invitedMemberCount.value(); } int Room::totalMemberCount() const -- cgit v1.2.3 From 3f39c30bffcb4ebc8eefe9bd9feb1b71b1c13981 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 13 Jan 2019 16:00:35 +0900 Subject: Room::Room: initialise display name So that the room has at least some display name before any events come to it. --- lib/room.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 7ff8f5e9..1931be49 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -89,11 +89,6 @@ class Room::Private Room* q; - // This updates the room displayname field (which is the way a room - // should be shown in the room list) It should be called whenever the - // list of members or the room name (m.room.name) or canonical alias change. - void updateDisplayname(); - Connection* connection; QString id; JoinState joinState; @@ -179,6 +174,14 @@ class Room::Private void renameMember(User* u, QString oldName); void removeMemberFromMap(const QString& username, User* u); + // This updates the room displayname field (which is the way a room + // should be shown in the room list); called whenever the list of + // members, the room name (m.room.name) or canonical alias change. + void updateDisplayname(); + // This is used by updateDisplayname() but only calculates the new name + // without any updates. + QString calculateDisplayname() const; + /// A point in the timeline corresponding to baseState rev_iter_t timelineBase() const { return q->findInTimeline(-1); } @@ -278,7 +281,6 @@ class Room::Private template users_shortlist_t buildShortlist(const ContT& users) const; users_shortlist_t buildShortlist(const QStringList& userIds) const; - QString calculateDisplayname() const; bool isLocalUser(const User* u) const { @@ -293,6 +295,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) // See "Accessing the Public Class" section in // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ d->q = this; + d->displayname = d->calculateDisplayname(); // Set initial "Empty room" name qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id; } -- cgit v1.2.3 From a9bdc89f66ba283859fd9ca7383a7256198174ed Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 13 Jan 2019 13:36:57 +0900 Subject: Connection: fix/workaround glitches on joining/leaving Closes #273, in particular. --- lib/room.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 1931be49..d806183f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1633,7 +1633,8 @@ void Room::inviteToRoom(const QString& memberId) LeaveRoomJob* Room::leaveRoom() { - return connection()->callApi(id()); + // FIXME, #63: It should be RoomManager, not Connection + return connection()->leaveRoom(this); } SetRoomStateWithKeyJob*Room::setMemberState(const QString& memberId, const RoomMemberEvent& event) const -- cgit v1.2.3 From 87018c0a180248df4a2f61665efbfb3af84bbfea Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 15 Feb 2019 11:44:29 +0900 Subject: Room::baseStateLoaded Mirroring Connection::loadedRoomState but for each single room (will be used as a NOTIFY signal for one-time-set events). --- lib/room.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index d806183f..663f6037 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -296,6 +296,12 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ d->q = this; d->displayname = d->calculateDisplayname(); // Set initial "Empty room" name + connectUntil(connection, &Connection::loadedRoomState, this, + [this] (Room* r) { + if (this == r) + emit baseStateLoaded(); + return this == r; // loadedRoomState fires only once per room + }); qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id; } -- cgit v1.2.3 From 173cfceab7da61e85467658a2c320609485b1139 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 15 Feb 2019 11:45:23 +0900 Subject: Add a FIXME upon the recent failure under Valgrind --- lib/room.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 663f6037..60c61f2b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1306,6 +1306,8 @@ RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event) event->setTransactionId(connection->generateTxnId()); auto* pEvent = rawPtr(event); emit q->pendingEventAboutToAdd(pEvent); + // FIXME: This sometimes causes a bad read: + // https://travis-ci.org/QMatrixClient/libqmatrixclient/jobs/492156899#L2596 unsyncedEvents.emplace_back(move(event)); emit q->pendingEventAdded(); return pEvent; -- cgit v1.2.3 From f3ec748689db531df787d19bcfe76b0a40665b67 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 15 Feb 2019 11:49:40 +0900 Subject: Room: version(), predecessorId(), successorId() Use RoomCreateEvent and RoomTombstoneEvent in the backend, covering most of #235. --- lib/room.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 60c61f2b..e1625478 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -30,6 +30,8 @@ #include "csapi/rooms.h" #include "csapi/tags.h" #include "events/simplestateevents.h" +#include "events/roomcreateevent.h" +#include "events/roomtombstoneevent.h" #include "events/roomavatarevent.h" #include "events/roommemberevent.h" #include "events/typingevent.h" @@ -315,6 +317,21 @@ const QString& Room::id() const return d->id; } +QString Room::version() const +{ + return d->getCurrentState()->version(); +} + +QString Room::predecessorId() const +{ + return d->getCurrentState()->predecessor().roomId; +} + +QString Room::successorId() const +{ + return d->getCurrentState()->successorRoomId(); +} + const Room::Timeline& Room::messageEvents() const { return d->timeline; @@ -1807,7 +1824,7 @@ RoomEventPtr makeRedacted(const RoomEvent& target, std::vector> keepContentKeysMap { { RoomMemberEvent::typeId(), { QStringLiteral("membership") } } -// , { RoomCreateEvent::typeId(), { QStringLiteral("creator") } } + , { RoomCreateEvent::typeId(), { QStringLiteral("creator") } } // , { RoomJoinRules::typeId(), { QStringLiteral("join_rule") } } // , { RoomPowerLevels::typeId(), // { QStringLiteral("ban"), QStringLiteral("events"), -- cgit v1.2.3 From 0f4368a19e344c8e3d74d97d4c9de171e723a9a1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 15 Feb 2019 12:19:38 +0900 Subject: Disallow sending events to rooms that have been upgraded This concludes the mandatory part of #235. --- lib/room.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index e1625478..6b9702cd 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -252,11 +252,17 @@ class Room::Private const QString& txnId, BaseJob* call = nullptr); template - auto requestSetState(const QString& stateKey, const EvT& event) + SetRoomStateWithKeyJob* requestSetState(const QString& stateKey, + const EvT& event) { - // TODO: Queue up state events sending (see #133). - return connection->callApi( + if (q->successorId().isEmpty()) + { + // TODO: Queue up state events sending (see #133). + return connection->callApi( id, EvT::matrixTypeId(), stateKey, event.contentJson()); + } + qCWarning(MAIN) << q << "has been upgraded, state won't be set"; + return nullptr; } template @@ -1332,7 +1338,11 @@ RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event) QString Room::Private::sendEvent(RoomEventPtr&& event) { - return doSendEvent(addAsPending(std::move(event))); + if (q->successorId().isEmpty()) + return doSendEvent(addAsPending(std::move(event))); + + qCWarning(MAIN) << q << "has been upgraded, event won't be sent"; + return {}; } QString Room::Private::doSendEvent(const RoomEvent* pEvent) -- cgit v1.2.3 From 5ac901775c5ebd39338ae7854d2c3391cf9084fa Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 15 Feb 2019 12:24:41 +0900 Subject: Room::upgraded() A signal emitted when the room receives a tombstone event from the server. --- lib/room.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 6b9702cd..af97dc11 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2160,6 +2160,9 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) emit encryption(); // It can only be done once, so emit it here. return EncryptionOn; } + , [this] (const RoomTombstoneEvent& evt) { + emit upgraded(evt.serverMessage(), evt.successorRoomId()); + } ); } -- cgit v1.2.3 From ac7d2ad8b0942cc465c0d340f159cb0b343008ab Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 15 Feb 2019 12:29:02 +0900 Subject: Room::checkVersion() and Room::unstableVersion() Initial (sans power levels checking) implementation of the check that room should be upgraded. Closes most of #236. --- lib/room.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index af97dc11..580d04b8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1604,7 +1604,22 @@ bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re) bool Room::supportsCalls() const { - return joinedCount() == 2; + return joinedCount() == 2; +} + +void Room::checkVersion() +{ + const auto defaultVersion = connection()->defaultRoomVersion(); + const auto stableVersions = connection()->stableRoomVersions(); + Q_ASSERT(!defaultVersion.isEmpty() && successorId().isEmpty()); + if (!stableVersions.contains(version())) + { + qCDebug(MAIN) << this << "version is" << version() + << "which the server doesn't count as stable"; + // TODO: m.room.power_levels + qCDebug(MAIN) << "The current user has enough privileges to fix it"; + emit unstableVersion(defaultVersion, stableVersions); + } } void Room::inviteCall(const QString& callId, const int lifetime, -- cgit v1.2.3 From 5460bf4024999b78fb3837ffc14ca818a71dd4dc Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 15 Feb 2019 15:45:18 +0900 Subject: Use Changes enum properly Don't use distinct items for each type of event; only for repeated/ combinable ones. --- lib/room.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 580d04b8..23fb19db 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2173,10 +2173,11 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) } , [this] (const EncryptionEvent&) { emit encryption(); // It can only be done once, so emit it here. - return EncryptionOn; + return OtherChange; } , [this] (const RoomTombstoneEvent& evt) { emit upgraded(evt.serverMessage(), evt.successorRoomId()); + return OtherChange; } ); } -- cgit v1.2.3 From 11b1bfe8f3640bfb1e2dd1710624c67aedb4f98b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 16 Feb 2019 17:27:39 +0900 Subject: Room::switchVersion() Closes #236. --- lib/room.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 23fb19db..aa835860 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -29,6 +29,7 @@ #include "csapi/room_send.h" #include "csapi/rooms.h" #include "csapi/tags.h" +#include "csapi/room_upgrades.h" #include "events/simplestateevents.h" #include "events/roomcreateevent.h" #include "events/roomtombstoneevent.h" @@ -791,6 +792,11 @@ void Room::resetHighlightCount() emit highlightCountChanged(this); } +void Room::switchVersion(QString newVersion) +{ + connection()->callApi(id(), newVersion); +} + bool Room::hasAccountData(const QString& type) const { return d->accountData.find(type) != d->accountData.end(); -- cgit v1.2.3 From 4e2de22c7d327836d2fe44764f8c7855a51f6206 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 16 Feb 2019 17:29:15 +0900 Subject: Room::checkVersion(): check power levels This is a flimsy implementation without proper RoomPowerLevelEvent definition, just to enable upgrades without causing noise to each and every user of a room on an unstable version. --- lib/room.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index aa835860..3a37053d 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1622,9 +1622,25 @@ void Room::checkVersion() { qCDebug(MAIN) << this << "version is" << version() << "which the server doesn't count as stable"; - // TODO: m.room.power_levels - qCDebug(MAIN) << "The current user has enough privileges to fix it"; - emit unstableVersion(defaultVersion, stableVersions); + // TODO, #276: m.room.power_levels + if (const auto* plEvt = + d->currentState.value({"m.room.power_levels", ""})) + { + const auto plJson = plEvt->contentJson(); + const auto currentUserLevel = + plJson.value("users"_ls).toObject() + .value(localUser()->id()).toInt( + plJson.value("users_default"_ls).toInt()); + const auto tombstonePowerLevel = + plJson.value("events").toObject() + .value("m.room.tombstone"_ls).toInt( + plJson.value("state_default"_ls).toInt()); + if (currentUserLevel >= tombstonePowerLevel) + { + qCDebug(MAIN) << "The current user has enough privileges to fix it"; + emit unstableVersion(defaultVersion, stableVersions); + } + } } } -- cgit v1.2.3 From 7e9bf3911e0457bf5af21672d4325882584b78ad Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 16 Feb 2019 20:00:46 +0900 Subject: Room::canSwitchVersions() --- lib/room.cpp | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 3a37053d..538c1562 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -573,6 +573,26 @@ void Room::markAllMessagesAsRead() d->markMessagesAsRead(d->timeline.crbegin()); } +bool Room::canSwitchVersions() const +{ + // TODO, #276: m.room.power_levels + const auto* plEvt = + d->currentState.value({"m.room.power_levels", ""}); + if (!plEvt) + return true; + + const auto plJson = plEvt->contentJson(); + const auto currentUserLevel = + plJson.value("users"_ls).toObject() + .value(localUser()->id()).toInt( + plJson.value("users_default"_ls).toInt()); + const auto tombstonePowerLevel = + plJson.value("events").toObject() + .value("m.room.tombstone"_ls).toInt( + plJson.value("state_default"_ls).toInt()); + return currentUserLevel >= tombstonePowerLevel; +} + bool Room::hasUnreadMessages() const { return unreadCount() >= 0; @@ -1622,24 +1642,10 @@ void Room::checkVersion() { qCDebug(MAIN) << this << "version is" << version() << "which the server doesn't count as stable"; - // TODO, #276: m.room.power_levels - if (const auto* plEvt = - d->currentState.value({"m.room.power_levels", ""})) + if (canSwitchVersions()) { - const auto plJson = plEvt->contentJson(); - const auto currentUserLevel = - plJson.value("users"_ls).toObject() - .value(localUser()->id()).toInt( - plJson.value("users_default"_ls).toInt()); - const auto tombstonePowerLevel = - plJson.value("events").toObject() - .value("m.room.tombstone"_ls).toInt( - plJson.value("state_default"_ls).toInt()); - if (currentUserLevel >= tombstonePowerLevel) - { - qCDebug(MAIN) << "The current user has enough privileges to fix it"; - emit unstableVersion(defaultVersion, stableVersions); - } + qCDebug(MAIN) << "The current user has enough privileges to fix it"; + emit unstableVersion(defaultVersion, stableVersions); } } } -- cgit v1.2.3 From 73e6bd47f4bafa7e65f8d826d8c6527c59aeb865 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 16 Feb 2019 20:22:01 +0900 Subject: Room::version(): Fallback an empty version to "1" --- lib/room.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 538c1562..14e60f51 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -326,7 +326,8 @@ const QString& Room::id() const QString Room::version() const { - return d->getCurrentState()->version(); + const auto v = d->getCurrentState()->version(); + return v.isEmpty() ? "1" : v; } QString Room::predecessorId() const -- cgit v1.2.3 From 061c6a69fd55696e7dd82854ace9aa67915628d7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 17 Feb 2019 17:46:26 +0900 Subject: Room: emit room, not id in upgraded(); add upgradeFailed() --- lib/room.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 14e60f51..9c923de7 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -815,7 +815,10 @@ void Room::resetHighlightCount() void Room::switchVersion(QString newVersion) { - connection()->callApi(id(), newVersion); + auto* job = connection()->callApi(id(), newVersion); + connect(job, &BaseJob::failure, this, [this,job] { + emit upgradeFailed(job->errorString()); + }); } bool Room::hasAccountData(const QString& type) const @@ -2205,7 +2208,19 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return OtherChange; } , [this] (const RoomTombstoneEvent& evt) { - emit upgraded(evt.serverMessage(), evt.successorRoomId()); + const auto newRoomId = evt.successorRoomId(); + if (auto* newRoom = connection()->room(newRoomId)) + emit upgraded(evt.serverMessage(), newRoom); + else + connectUntil(connection(), &Connection::loadedRoomState, this, + [this,newRoomId,serverMsg=evt.serverMessage()] + (Room* newRoom) { + if (newRoom->id() != newRoomId) + return false; + emit upgraded(serverMsg, newRoom); + return true; + }); + return OtherChange; } ); -- cgit v1.2.3 From ad5d44f31b3ab7e582b84ab05161c97cbc7eefc8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 17 Feb 2019 18:54:52 +0900 Subject: Room: add isUnstable(); unstableVersion() -> stabilityUpdated() --- lib/room.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 9c923de7..b13e7873 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -330,6 +330,12 @@ QString Room::version() const return v.isEmpty() ? "1" : v; } +bool Room::isUnstable() const +{ + return !connection()->loadingCapabilities() && + !connection()->stableRoomVersions().contains(version()); +} + QString Room::predecessorId() const { return d->getCurrentState()->predecessor().roomId; @@ -1642,15 +1648,15 @@ void Room::checkVersion() const auto defaultVersion = connection()->defaultRoomVersion(); const auto stableVersions = connection()->stableRoomVersions(); Q_ASSERT(!defaultVersion.isEmpty() && successorId().isEmpty()); + // This method is only called after the base state has been loaded + // or the server capabilities have been loaded. + emit stabilityUpdated(defaultVersion, stableVersions); if (!stableVersions.contains(version())) { qCDebug(MAIN) << this << "version is" << version() << "which the server doesn't count as stable"; if (canSwitchVersions()) - { qCDebug(MAIN) << "The current user has enough privileges to fix it"; - emit unstableVersion(defaultVersion, stableVersions); - } } } -- cgit v1.2.3 From 9ef28a3b43dc576716ace005e300b43c3af74b9f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 18 Feb 2019 07:00:28 +0900 Subject: Room: fix building with MSVC --- lib/room.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index b13e7873..f6956d82 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2214,14 +2214,14 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return OtherChange; } , [this] (const RoomTombstoneEvent& evt) { - const auto newRoomId = evt.successorRoomId(); - if (auto* newRoom = connection()->room(newRoomId)) - emit upgraded(evt.serverMessage(), newRoom); + const auto successorId = evt.successorRoomId(); + if (auto* successor = connection()->room(successorId)) + emit upgraded(evt.serverMessage(), successor); else connectUntil(connection(), &Connection::loadedRoomState, this, - [this,newRoomId,serverMsg=evt.serverMessage()] + [this,successorId,serverMsg=evt.serverMessage()] (Room* newRoom) { - if (newRoom->id() != newRoomId) + if (newRoom->id() != successorId) return false; emit upgraded(serverMsg, newRoom); return true; -- cgit v1.2.3 From 6edd8a23765285350a667ae214f5e450f5f24129 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 19 Feb 2019 21:12:15 +0900 Subject: Room::downloadFile: construct the temporary filename more carefully Closes #279. --- lib/room.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index f6956d82..c6376a26 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -1804,7 +1805,8 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) { // Build our own file path, starting with temp directory and eventId. filePath = eventId; - filePath = QDir::tempPath() % '/' % filePath.replace(':', '_') % + filePath = QDir::tempPath() % '/' % + filePath.replace(QRegularExpression("[/\\<>|\"*?:]"), "_") % '#' % d->fileNameToDownload(event); } auto job = connection()->downloadFile(fileUrl, filePath); -- cgit v1.2.3 From 297216e95c0802248110403f1b8fdcd5eb02fae6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 1 Feb 2019 07:30:09 +0900 Subject: Room::setAliases, Connection: roomByAlias, updateRoomAliases --- lib/room.cpp | 64 +++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 24 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index c6376a26..f9c899cb 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -209,6 +209,28 @@ class Room::Private is(*ti); } + template + Changes updateStateFrom(EventArrayT&& events) + { + Changes changes = NoChange; + if (!events.empty()) + { + QElapsedTimer et; et.start(); + for (auto&& eptr: events) + { + const auto& evt = *eptr; + Q_ASSERT(evt.isStateEvent()); + // Update baseState afterwards to make sure that the old state + // is valid and usable inside processStateEvent + changes |= q->processStateEvent(evt); + baseState[{evt.matrixType(),evt.stateKey()}] = move(eptr); + } + if (events.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) + qCDebug(PROFILER) << "*** Room::Private::updateStateFrom():" + << events.size() << "event(s)," << et; + } + return changes; + } Changes addNewMessageEvents(RoomEvents&& events); void addHistoricalMessageEvents(RoomEvents&& events); @@ -684,13 +706,7 @@ void Room::Private::getAllMembers() auto nextIndex = timeline.empty() ? 0 : timeline.back().index() + 1; connect( allMembersJob, &BaseJob::success, q, [=] { Q_ASSERT(timeline.empty() || nextIndex <= q->maxTimelineIndex() + 1); - Changes roomChanges = NoChange; - for (auto&& e: allMembersJob->chunk()) - { - const auto& evt = *e; - baseState[{evt.matrixType(),evt.stateKey()}] = move(e); - roomChanges |= q->processStateEvent(evt); - } + auto roomChanges = updateStateFrom(allMembersJob->chunk()); // Replay member events that arrived after the point for which // the full members list was requested. if (!timeline.empty() ) @@ -1296,21 +1312,8 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) for (auto&& event: data.accountData) roomChanges |= processAccountDataEvent(move(event)); - if (!data.state.empty()) - { - et.restart(); - for (auto&& eptr: data.state) - { - const auto& evt = *eptr; - Q_ASSERT(evt.isStateEvent()); - d->baseState[{evt.matrixType(),evt.stateKey()}] = move(eptr); - roomChanges |= processStateEvent(evt); - } + roomChanges |= d->updateStateFrom(data.state); - if (data.state.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::processStateEvents():" - << data.state.size() << "event(s)," << et; - } if (!data.timeline.empty()) { et.restart(); @@ -1614,6 +1617,11 @@ void Room::setCanonicalAlias(const QString& newAlias) d->requestSetState(RoomCanonicalAliasEvent(newAlias)); } +void Room::setAliases(const QStringList& aliases) +{ + d->requestSetState(RoomAliasesEvent(aliases)); +} + void Room::setTopic(const QString& newTopic) { d->requestSetState(RoomTopicEvent(newTopic)); @@ -2148,8 +2156,12 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) if (!e.isStateEvent()) return Change::NoChange; - d->currentState[{e.matrixType(),e.stateKey()}] = - static_cast(&e); + const auto* oldStateEvent = std::exchange( + d->currentState[{e.matrixType(),e.stateKey()}], + static_cast(&e)); + Q_ASSERT(!oldStateEvent || + (oldStateEvent->matrixType() == e.matrixType() && + oldStateEvent->stateKey() == e.stateKey())); if (!is(e)) qCDebug(EVENTS) << "Room state event:" << e; @@ -2157,7 +2169,11 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) , [] (const RoomNameEvent&) { return NameChange; } - , [] (const RoomAliasesEvent&) { + , [this,oldStateEvent] (const RoomAliasesEvent& ae) { + const auto previousAliases = oldStateEvent + ? static_cast(oldStateEvent)->aliases() + : QStringList(); + connection()->updateRoomAliases(id(), previousAliases, ae.aliases()); return OtherChange; } , [this] (const RoomCanonicalAliasEvent& evt) { -- cgit v1.2.3 From 6636e46a2e9049f261b8a64cb6c1bf7c4f076c54 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 25 Feb 2019 11:17:34 +0900 Subject: makeRedacted: update the list of preserved parts Closes #256. --- lib/room.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index f9c899cb..7e7d8505 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1889,22 +1889,29 @@ RoomEventPtr makeRedacted(const RoomEvent& target, const RedactionEvent& redaction) { auto originalJson = target.originalJsonObject(); - static const QStringList keepKeys = - { EventIdKey, TypeKey, QStringLiteral("room_id"), - QStringLiteral("sender"), QStringLiteral("state_key"), - QStringLiteral("prev_content"), ContentKey, - QStringLiteral("origin_server_ts") }; + static const QStringList keepKeys { + EventIdKey, TypeKey, QStringLiteral("room_id"), + QStringLiteral("sender"), QStringLiteral("state_key"), + QStringLiteral("prev_content"), ContentKey, + QStringLiteral("hashes"), QStringLiteral("signatures"), + QStringLiteral("depth"), QStringLiteral("prev_events"), + QStringLiteral("prev_state"), QStringLiteral("auth_events"), + QStringLiteral("origin"), QStringLiteral("origin_server_ts"), + QStringLiteral("membership") + }; std::vector> keepContentKeysMap { { RoomMemberEvent::typeId(), { QStringLiteral("membership") } } - , { RoomCreateEvent::typeId(), { QStringLiteral("creator") } } + , { RoomCreateEvent::typeId(), { QStringLiteral("creator") } } // , { RoomJoinRules::typeId(), { QStringLiteral("join_rule") } } // , { RoomPowerLevels::typeId(), // { QStringLiteral("ban"), QStringLiteral("events"), // QStringLiteral("events_default"), QStringLiteral("kick"), // QStringLiteral("redact"), QStringLiteral("state_default"), // QStringLiteral("users"), QStringLiteral("users_default") } } - , { RoomAliasesEvent::typeId(), { QStringLiteral("alias") } } + , { RoomAliasesEvent::typeId(), { QStringLiteral("aliases") } } +// , { RoomHistoryVisibility::typeId(), +// { QStringLiteral("history_visibility") } } }; for (auto it = originalJson.begin(); it != originalJson.end();) { -- cgit v1.2.3 From 93876f06b6a1929dc757595ba4410b742402b7ab Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 25 Feb 2019 11:27:24 +0900 Subject: Room::postHtmlMessage: default message type to m.text postHtmlText becomes just a synonym for 2-arg postHtmlMessage (hopefully at least this doesn't confuse QML that is generally terrible at resolving overloads). --- lib/room.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 7e7d8505..c7723832 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1525,7 +1525,7 @@ QString Room::postPlainText(const QString& plainText) } QString Room::postHtmlMessage(const QString& plainText, const QString& html, - MessageEventType type) + MessageEventType type) { return d->sendEvent(plainText, type, new EventContent::TextContent(html, QStringLiteral("text/html"))); @@ -1533,7 +1533,7 @@ QString Room::postHtmlMessage(const QString& plainText, const QString& html, QString Room::postHtmlText(const QString& plainText, const QString& html) { - return postHtmlMessage(plainText, html, MessageEventType::Text); + return postHtmlMessage(plainText, html); } QString Room::postFile(const QString& plainText, const QUrl& localPath, -- cgit v1.2.3 From 5b5eb135be40449a6a63eb9872787bec1ecd0fc2 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 25 Feb 2019 14:46:00 +0900 Subject: Have a build-wide macro for compilers that don't handle init-lists right WORKAROUND_EXTENDED_INITIALIZER_LIST -> BROKEN_INITIALIZER_LISTS is available from util.h now. --- lib/room.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index c7723832..9340bd58 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -72,14 +72,6 @@ using std::llround; enum EventsPlacement : int { Older = -1, Newer = 1 }; -// A workaround for MSVC 2015 and older GCC's that don't handle initializer -// lists right (MSVC 2015, notably, fails with "error C2440: 'return': -// cannot convert from 'initializer list' to 'QMatrixClient::FileTransferInfo'") -#if (defined(_MSC_VER) && _MSC_VER < 1910) || \ - (defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 4) -# define WORKAROUND_EXTENDED_INITIALIZER_LIST -#endif - class Room::Private { public: @@ -1065,7 +1057,7 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const total = INT_MAX; } -#ifdef WORKAROUND_EXTENDED_INITIALIZER_LIST +#ifdef BROKEN_INITIALIZER_LISTS FileTransferInfo fti; fti.status = infoIt->status; fti.progress = int(progress); -- cgit v1.2.3 From 395ed0ca307a4cef696048b30718f8d5c99492a0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 26 Feb 2019 07:55:25 +0900 Subject: Room::addNewMessageEvents: fix possible use of an invalid iterator Closes #286. --- lib/room.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 9340bd58..b0be288b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1361,8 +1361,6 @@ RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event) event->setTransactionId(connection->generateTxnId()); auto* pEvent = rawPtr(event); emit q->pendingEventAboutToAdd(pEvent); - // FIXME: This sometimes causes a bad read: - // https://travis-ci.org/QMatrixClient/libqmatrixclient/jobs/492156899#L2596 unsyncedEvents.emplace_back(move(event)); emit q->pendingEventAdded(); return pEvent; @@ -2057,15 +2055,21 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) it = nextPending + 1; auto* nextPendingEvt = nextPending->get(); - emit q->pendingEventAboutToMerge(nextPendingEvt, - int(nextPendingPair.second - unsyncedEvents.begin())); + const auto pendingEvtIdx = + int(nextPendingPair.second - unsyncedEvents.begin()); + emit q->pendingEventAboutToMerge(nextPendingEvt, pendingEvtIdx); qDebug(EVENTS) << "Merging pending event from transaction" << nextPendingEvt->transactionId() << "into" << nextPendingEvt->id(); auto transfer = fileTransfers.take(nextPendingEvt->transactionId()); if (transfer.status != FileTransferInfo::None) fileTransfers.insert(nextPendingEvt->id(), transfer); - unsyncedEvents.erase(nextPendingPair.second); + // After emitting pendingEventAboutToMerge() above we cannot rely + // on the previously obtained nextPendingPair.second staying valid + // because a signal handler may send another message, thereby altering + // unsyncedEvents (see #286). Fortunately, unsyncedEvents only grows at + // its back so we can rely on the index staying valid at least. + unsyncedEvents.erase(unsyncedEvents.begin() + pendingEvtIdx); if (auto insertedSize = moveEventsToTimeline({nextPending, it}, Newer)) { totalInserted += insertedSize; -- cgit v1.2.3 From c9b3cce218c5724d511b6e0dffb8d389ce19c08f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 26 Feb 2019 12:01:51 +0900 Subject: Room: avoid dangling pointers, even if not dereferenced Closes #288; fixes one more case similar to #286. Also: disconnect file transfer signals correctly in Room::postFile. --- lib/room.cpp | 111 +++++++++++++++++++++++++---------------------------------- 1 file changed, 47 insertions(+), 64 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index b0be288b..8395baca 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -263,9 +263,7 @@ class Room::Private RoomEvent* addAsPending(RoomEventPtr&& event); QString doSendEvent(const RoomEvent* pEvent); - PendingEvents::iterator findAsPending(const RoomEvent* rawEvtPtr); - void onEventSendingFailure(const RoomEvent* pEvent, - const QString& txnId, BaseJob* call = nullptr); + void onEventSendingFailure(const QString& txnId, BaseJob* call = nullptr); template SetRoomStateWithKeyJob* requestSetState(const QString& stateKey, @@ -1377,14 +1375,14 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) QString Room::Private::doSendEvent(const RoomEvent* pEvent) { - auto txnId = pEvent->transactionId(); + const auto txnId = pEvent->transactionId(); // TODO, #133: Enqueue the job rather than immediately trigger it. if (auto call = connection->callApi(BackgroundRequest, id, pEvent->matrixType(), txnId, pEvent->contentJson())) { Room::connect(call, &BaseJob::started, q, - [this,pEvent,txnId] { - auto it = findAsPending(pEvent); + [this,txnId] { + auto it = q->findPendingEvent(txnId); if (it == unsyncedEvents.end()) { qWarning(EVENTS) << "Pending event for transaction" << txnId @@ -1395,14 +1393,11 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) emit q->pendingEventChanged(it - unsyncedEvents.begin()); }); Room::connect(call, &BaseJob::failure, q, - std::bind(&Room::Private::onEventSendingFailure, - this, pEvent, txnId, call)); + std::bind(&Room::Private::onEventSendingFailure, this, txnId, call)); Room::connect(call, &BaseJob::success, q, - [this,call,pEvent,txnId] { + [this,call,txnId] { emit q->messageSent(txnId, call->eventId()); - // Find an event by the pointer saved in the lambda (the pointer - // may be dangling by now but we can still search by it). - auto it = findAsPending(pEvent); + auto it = q->findPendingEvent(txnId); if (it == unsyncedEvents.end()) { qDebug(EVENTS) << "Pending event for transaction" << txnId @@ -1414,23 +1409,13 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) emit q->pendingEventChanged(it - unsyncedEvents.begin()); }); } else - onEventSendingFailure(pEvent, txnId); + onEventSendingFailure(txnId); return txnId; } -Room::PendingEvents::iterator Room::Private::findAsPending( - const RoomEvent* rawEvtPtr) +void Room::Private::onEventSendingFailure(const QString& txnId, BaseJob* call) { - const auto comp = - [rawEvtPtr] (const auto& pe) { return pe.event() == rawEvtPtr; }; - - return std::find_if(unsyncedEvents.begin(), unsyncedEvents.end(), comp); -} - -void Room::Private::onEventSendingFailure(const RoomEvent* pEvent, - const QString& txnId, BaseJob* call) -{ - auto it = findAsPending(pEvent); + auto it = q->findPendingEvent(txnId); if (it == unsyncedEvents.end()) { qCritical(EVENTS) << "Pending event for transaction" << txnId @@ -1533,50 +1518,48 @@ QString Room::postFile(const QString& plainText, const QUrl& localPath, Q_ASSERT(localFile.isFile()); // Remote URL will only be known after upload; fill in the local path // to enable the preview while the event is pending. - auto* pEvent = d->addAsPending( - makeEvent(plainText, localFile, asGenericFile)); - const auto txnId = pEvent->transactionId(); + const auto txnId = d->addAsPending(makeEvent( + plainText, localFile, asGenericFile) + )->transactionId(); uploadFile(txnId, localPath); - QMetaObject::Connection cCompleted, cCancelled; - cCompleted = connect(this, &Room::fileTransferCompleted, this, - [cCompleted,cCancelled,this,pEvent,txnId] - (const QString& id, QUrl, const QUrl& mxcUri) { - if (id == txnId) + auto* context = new QObject(this); + connect(this, &Room::fileTransferCompleted, context, + [context,this,txnId] (const QString& id, QUrl, const QUrl& mxcUri) { + if (id == txnId) + { + auto it = findPendingEvent(txnId); + if (it != d->unsyncedEvents.end()) { - auto it = d->findAsPending(pEvent); - if (it != d->unsyncedEvents.end()) - { - it->setFileUploaded(mxcUri); - emit pendingEventChanged( - int(it - d->unsyncedEvents.begin())); - d->doSendEvent(pEvent); - } else { - // Normally in this situation we should instruct - // the media server to delete the file; alas, there's no - // API specced for that. - qCWarning(MAIN) << "File uploaded to" << mxcUri - << "but the event referring to it was cancelled"; - } - disconnect(cCompleted); - disconnect(cCancelled); + it->setFileUploaded(mxcUri); + emit pendingEventChanged( + int(it - d->unsyncedEvents.begin())); + d->doSendEvent(it->get()); + } else { + // Normally in this situation we should instruct + // the media server to delete the file; alas, there's no + // API specced for that. + qCWarning(MAIN) << "File uploaded to" << mxcUri + << "but the event referring to it was cancelled"; } - }); - cCancelled = connect(this, &Room::fileTransferCancelled, this, - [cCompleted,cCancelled,this,pEvent,txnId] (const QString& id) { - if (id == txnId) + context->deleteLater(); + } + }); + connect(this, &Room::fileTransferCancelled, this, + [context,this,txnId] (const QString& id) { + if (id == txnId) + { + auto it = findPendingEvent(txnId); + if (it != d->unsyncedEvents.end()) { - auto it = d->findAsPending(pEvent); - if (it != d->unsyncedEvents.end()) - { - emit pendingEventAboutToDiscard( - int(it - d->unsyncedEvents.begin())); - d->unsyncedEvents.erase(it); - emit pendingEventDiscarded(); - } - disconnect(cCompleted); - disconnect(cCancelled); + const auto idx = int(it - d->unsyncedEvents.begin()); + emit pendingEventAboutToDiscard(idx); + // See #286 on why iterator may not be valid here. + d->unsyncedEvents.erase(d->unsyncedEvents.begin() + idx); + emit pendingEventDiscarded(); } - }); + context->deleteLater(); + } + }); return txnId; } -- cgit v1.2.3 From a78ae0e75225629563ce253308e9b88383b0ea4d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 26 Feb 2019 14:11:37 +0900 Subject: Room::avatarObject Closes #268. --- lib/room.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 8395baca..5da9373e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -404,6 +404,11 @@ QUrl Room::avatarUrl() const return d->avatar.url(); } +const Avatar& Room::avatarObject() const +{ + return d->avatar; +} + QImage Room::avatar(int dimension) { return avatar(dimension, dimension); -- cgit v1.2.3 From 8c685b4ae5b47e55a55f23e16ccbda0132cb60c5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 10 Mar 2019 16:59:55 +0900 Subject: Room::checkVersion(): be tolerant to already upgraded rooms --- lib/room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 5da9373e..f2e03e94 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1634,7 +1634,7 @@ void Room::checkVersion() { const auto defaultVersion = connection()->defaultRoomVersion(); const auto stableVersions = connection()->stableRoomVersions(); - Q_ASSERT(!defaultVersion.isEmpty() && successorId().isEmpty()); + Q_ASSERT(!defaultVersion.isEmpty()); // This method is only called after the base state has been loaded // or the server capabilities have been loaded. emit stabilityUpdated(defaultVersion, stableVersions); -- cgit v1.2.3 From f13d54bd9931a340af862cc0a03af2ac68fe5e06 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Mar 2019 07:42:24 +0900 Subject: Fix read receipts and redactions on v3 rooms Previously slashes in eventIds (that come plenty in v3 due to base64 encoding) were not properly encoded - they are now. --- lib/room.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index f2e03e94..dbddad05 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -579,8 +579,8 @@ Room::Changes Room::Private::markMessagesAsRead(rev_iter_t upToMarker) { if ((*upToMarker)->senderId() != q->localUser()->id()) { - connection->callApi(id, "m.read", - (*upToMarker)->id()); + connection->callApi(id, QStringLiteral("m.read"), + QUrl::toPercentEncoding((*upToMarker)->id())); break; } } @@ -1734,8 +1734,8 @@ void Room::unban(const QString& userId) void Room::redactEvent(const QString& eventId, const QString& reason) { - connection()->callApi( - id(), eventId, connection()->generateTxnId(), reason); + connection()->callApi(id(), + QUrl::toPercentEncoding(eventId), connection()->generateTxnId(), reason); } void Room::uploadFile(const QString& id, const QUrl& localFilename, -- cgit v1.2.3 From 23352250c9b9f9fa7d1d46294f8c1a7de1e19f61 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 23 Mar 2019 20:43:02 +0900 Subject: Room::downloadFile(): Tighten URL validations Check the URL before passing over to Connection::downloadFile(), not only the file name. --- lib/room.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index dbddad05..411a17d6 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1785,7 +1785,14 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) Q_ASSERT(false); return; } - const auto fileUrl = event->content()->fileInfo()->url; + const auto* const fileInfo = event->content()->fileInfo(); + if (!fileInfo->isValid()) + { + qCWarning(MAIN) << "Event" << eventId + << "has an empty or malformed mxc URL; won't download"; + return; + } + const auto fileUrl = fileInfo->url; auto filePath = localFilename.toLocalFile(); if (filePath.isEmpty()) { -- cgit v1.2.3 From d8147d4ad8493ae9de94aee8a222a24d000a7c96 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 26 Mar 2019 11:58:12 +0900 Subject: Room::canSwitchVersions(): return false on tombstoned rooms A softer take on #306. --- lib/room.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 411a17d6..4ce1bee3 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -600,6 +600,9 @@ void Room::markAllMessagesAsRead() bool Room::canSwitchVersions() const { + if (!successorId().isEmpty()) + return false; // Noone can upgrade a room that's already upgraded + // TODO, #276: m.room.power_levels const auto* plEvt = d->currentState.value({"m.room.power_levels", ""}); -- cgit v1.2.3 From 3d13e32530990569a418449c86d9848fd71490e4 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 29 Mar 2019 13:02:12 +0900 Subject: Room::processRedaction(): avoid accidental creation of entries in currentState; cleanup --- lib/room.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 4ce1bee3..38f5c4ac 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1959,10 +1959,10 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) { const StateEventKey evtKey { oldEvent->matrixType(), oldEvent->stateKey() }; Q_ASSERT(currentState.contains(evtKey)); - if (currentState[evtKey] == oldEvent.get()) + if (currentState.value(evtKey) == oldEvent.get()) { Q_ASSERT(ti.index() >= 0); // Historical states can't be in currentState - qCDebug(MAIN).nospace() << "Reverting state " + qCDebug(MAIN).nospace() << "Redacting state " << oldEvent->matrixType() << "/" << oldEvent->stateKey(); // Retarget the current state to the newly made event. if (q->processStateEvent(*ti)) @@ -2163,7 +2163,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) Q_ASSERT(!oldStateEvent || (oldStateEvent->matrixType() == e.matrixType() && oldStateEvent->stateKey() == e.stateKey())); - if (!is(e)) + if (!is(e)) // Room member events are too numerous qCDebug(EVENTS) << "Room state event:" << e; return visit(e -- cgit v1.2.3 From 460aa0350c22312829d3e2a3d8556221b3f89173 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 29 Mar 2019 13:26:36 +0900 Subject: Room::processStateEvent, User: take the previous membership state from oldStateEvent memberJoinState() just happens to return the not-yet-updated state, making its use around state changes very sensitive to moving things around. The event's own prevContent is unsigned, therefore untrusted. --- lib/room.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 38f5c4ac..0305cf7b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1177,7 +1177,11 @@ void Room::Private::insertMemberIntoMap(User *u) const auto userName = u->name(q); // If there is exactly one namesake of the added user, signal member renaming // for that other one because the two should be disambiguated now. - auto namesakes = membersMap.values(userName); + const auto namesakes = membersMap.values(userName); + + // Callers should check they are not adding an existing user once more. + Q_ASSERT(!namesakes.contains(u)); + if (namesakes.size() == 1) emit q->memberAboutToRename(namesakes.front(), namesakes.front()->fullName(q)); @@ -2189,16 +2193,20 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) emit avatarChanged(); return AvatarChange; } - , [this] (const RoomMemberEvent& evt) { + , [this,oldStateEvent] (const RoomMemberEvent& evt) { auto* u = user(evt.userId()); - u->processEvent(evt, this); - if (u == localUser() && memberJoinState(u) == JoinState::Invite + const auto* oldMemberEvent = + static_cast(oldStateEvent); + u->processEvent(evt, this, oldMemberEvent == nullptr); + const auto prevMembership = oldMemberEvent + ? oldMemberEvent->membership() : MembershipType::Leave; + if (u == localUser() && evt.membership() == MembershipType::Invite && evt.isDirect()) connection()->addToDirectChats(this, user(evt.senderId())); - if( evt.membership() == MembershipType::Join ) + if (evt.membership() == MembershipType::Join) { - if (memberJoinState(u) != JoinState::Join) + if (prevMembership != MembershipType::Join) { d->insertMemberIntoMap(u); connect(u, &User::nameAboutToChange, this, @@ -2214,9 +2222,9 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) emit userAdded(u); } } - else if( evt.membership() != MembershipType::Join ) + else if (evt.membership() != MembershipType::Join) { - if (memberJoinState(u) == JoinState::Join) + if (prevMembership == MembershipType::Join) { if (evt.membership() == MembershipType::Invite) qCWarning(MAIN) << "Invalid membership change:" << evt; -- cgit v1.2.3 From 07827998c5ffe495ce83e4b1034d9e016f7296e8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 31 Mar 2019 18:41:12 +0900 Subject: Room::refreshDisplayName() - for debugging purposes only Clients should not need to call this method explicitly. --- lib/room.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 0305cf7b..b0c898fb 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -389,6 +389,11 @@ QString Room::displayName() const return d->displayname; } +void Room::refreshDisplayName() +{ + d->updateDisplayname(); +} + QString Room::topic() const { return d->getCurrentState()->topic(); -- cgit v1.2.3 From 3f449c8773af6183c14b9c40ff1951a565bc1e67 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 31 Mar 2019 18:42:38 +0900 Subject: Room::updateData(): recalculate room name only when state changes occur --- lib/room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index b0c898fb..e69d6de1 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1339,7 +1339,6 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) emit memberListChanged(); roomChanges |= d->setSummary(move(data.summary)); - d->updateDisplayname(); for( auto&& ephemeralEvent: data.ephemeral ) roomChanges |= processEphemeralEvent(move(ephemeralEvent)); @@ -1364,6 +1363,7 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) } if (roomChanges != Change::NoChange) { + d->updateDisplayname(); emit changed(roomChanges); if (!fromCache) connection()->saveRoomState(this); -- cgit v1.2.3 From 7acd18f52da02bcf272b3fe3c8901753bc4adcc9 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 31 Mar 2019 18:44:55 +0900 Subject: Room: track invited users; polish the room naming algorithm It's no more entirely along the spec lines but gives better results with or without lazy-loading, across a wide range of cases. Closes #310. --- lib/room.cpp | 95 +++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 26 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index e69d6de1..a20c1cf0 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -105,6 +105,7 @@ class Room::Private members_map_t membersMap; QList usersTyping; QMultiHash eventIdReadUsers; + QList usersInvited; QList membersLeft; int unreadMessages = 0; bool displayed = false; @@ -1224,7 +1225,6 @@ void Room::Private::removeMemberFromMap(const QString& username, User* u) membersMap.remove(username, u); // If there was one namesake besides the removed user, signal member renaming // for it because it doesn't need to be disambiguated anymore. - // TODO: Think about left users. if (namesake) emit q->memberRenamed(namesake); } @@ -2209,8 +2209,38 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) && evt.isDirect()) connection()->addToDirectChats(this, user(evt.senderId())); - if (evt.membership() == MembershipType::Join) + switch (prevMembership) { + case MembershipType::Invite: + if (evt.membership() != prevMembership) + { + d->usersInvited.removeOne(u); + Q_ASSERT(!d->usersInvited.contains(u)); + } + break; + case MembershipType::Join: + if (evt.membership() == MembershipType::Invite) + qCWarning(MAIN) + << "Invalid membership change from Join to Invite:" + << evt; + if (evt.membership() != prevMembership) + { + d->removeMemberFromMap(u->name(this), u); + emit userRemoved(u); + } + break; + default: + if (evt.membership() == MembershipType::Invite + || evt.membership() == MembershipType::Join) + { + d->membersLeft.removeOne(u); + Q_ASSERT(!d->membersLeft.contains(u)); + } + } + + switch(evt.membership()) + { + case MembershipType::Join: if (prevMembership != MembershipType::Join) { d->insertMemberIntoMap(u); @@ -2226,18 +2256,14 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) }); emit userAdded(u); } - } - else if (evt.membership() != MembershipType::Join) - { - if (prevMembership == MembershipType::Join) - { - if (evt.membership() == MembershipType::Invite) - qCWarning(MAIN) << "Invalid membership change:" << evt; - if (!d->membersLeft.contains(u)) - d->membersLeft.append(u); - d->removeMemberFromMap(u->name(this), u); - emit userRemoved(u); - } + break; + case MembershipType::Invite: + if (!d->usersInvited.contains(u)) + d->usersInvited.push_back(u); + break; + default: + if (!d->membersLeft.contains(u)) + d->membersLeft.append(u); } return MembersChange; } @@ -2418,42 +2444,59 @@ QString Room::Private::calculateDisplayname() const return dispName; // Using m.room.aliases in naming is explicitly discouraged by the spec - //if (!q->aliases().empty() && !q->aliases().at(0).isEmpty()) - // return q->aliases().at(0); // Supplementary code for 3 and 4: build the shortlist of users whose names // will be used to construct the room name. Takes into account MSC688's // "heroes" if available. + const bool localUserIsIn = joinState == JoinState::Join; const bool emptyRoom = membersMap.isEmpty() || (membersMap.size() == 1 && isLocalUser(*membersMap.begin())); - const auto shortlist = - !summary.heroes.omitted() ? buildShortlist(summary.heroes.value()) : - !emptyRoom ? buildShortlist(membersMap) : - buildShortlist(membersLeft); + const bool nonEmptySummary = + !summary.heroes.omitted() && !summary.heroes->empty(); + auto shortlist = nonEmptySummary ? buildShortlist(summary.heroes.value()) : + !emptyRoom ? buildShortlist(membersMap) : + users_shortlist_t { }; + + // When lazy-loading is on, we can rely on the heroes list. + // If it's off, the below code gathers invited and left members. + // NB: including invitations, if any, into naming is a spec extension. + // This kicks in when there's no lazy loading and it's a room with + // the local user as the only member, with more users invited. + if (!shortlist.front() && localUserIsIn) + shortlist = buildShortlist(usersInvited); + + if (!shortlist.front()) // Still empty shortlist; use left members + shortlist = buildShortlist(membersLeft); QStringList names; for (auto u: shortlist) { if (u == nullptr || isLocalUser(u)) break; - names.push_back(q->roomMembername(u)); + // Only disambiguate if the room is not empty + names.push_back(u->displayname(emptyRoom ? nullptr : q)); } - auto usersCountExceptLocal = emptyRoom - ? membersLeft.size() - int(joinState == JoinState::Leave) - : q->joinedCount() - int(joinState == JoinState::Join); + const auto usersCountExceptLocal = + !emptyRoom ? q->joinedCount() - int(joinState == JoinState::Join) : + !usersInvited.empty() ? usersInvited.count() : + membersLeft.size() - int(joinState == JoinState::Leave); if (usersCountExceptLocal > int(shortlist.size())) names << tr("%Ln other(s)", "Used to make a room name from user names: A, B and _N others_", - usersCountExceptLocal); - auto namesList = QLocale().createSeparatedList(names); + usersCountExceptLocal - int(shortlist.size())); + const auto namesList = QLocale().createSeparatedList(names); // 3. Room members if (!emptyRoom) return namesList; + // (Spec extension) Invited users + if (!usersInvited.empty()) + return tr("Empty room (invited: %1)").arg(namesList); + // 4. Users that previously left the room if (membersLeft.size() > 0) return tr("Empty room (was: %1)").arg(namesList); -- cgit v1.2.3 From 7508887564935f70b086ed6fe79bd86757d1fc38 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 3 Apr 2019 20:08:48 +0900 Subject: Room::postFile: initiate uploading the file even before adding a pending event This is to make sure a pending event with file transfer already placed. --- lib/room.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index a20c1cf0..1a63866f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1533,12 +1533,17 @@ QString Room::postFile(const QString& plainText, const QUrl& localPath, { QFileInfo localFile { localPath.toLocalFile() }; Q_ASSERT(localFile.isFile()); + + const auto txnId = connection()->generateTxnId(); // Remote URL will only be known after upload; fill in the local path // to enable the preview while the event is pending. - const auto txnId = d->addAsPending(makeEvent( - plainText, localFile, asGenericFile) - )->transactionId(); uploadFile(txnId, localPath); + { + auto&& event = + makeEvent(plainText, localFile, asGenericFile); + event->setTransactionId(txnId); + d->addAsPending(std::move(event)); + } auto* context = new QObject(this); connect(this, &Room::fileTransferCompleted, context, [context,this,txnId] (const QString& id, QUrl, const QUrl& mxcUri) { -- cgit v1.2.3 From 1c5696e8f5a9ef87a065e2496eecf178e41e75a7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 4 Apr 2019 21:27:38 +0900 Subject: Clean up on clang-tidy/clazy analysis --- lib/room.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 1a63866f..caeeb499 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -168,7 +168,7 @@ class Room::Private //void inviteUser(User* u); // We might get it at some point in time. void insertMemberIntoMap(User* u); - void renameMember(User* u, QString oldName); + void renameMember(User* u, const QString& oldName); void removeMemberFromMap(const QString& username, User* u); // This updates the room displayname field (which is the way a room @@ -185,7 +185,7 @@ class Room::Private void getPreviousContent(int limit = 10); template - const EventT* getCurrentState(QString stateKey = {}) const + const EventT* getCurrentState(const QString& stateKey = {}) const { static const EventT empty; const auto* evt = @@ -236,8 +236,8 @@ class Room::Private * @param placement - position and direction of insertion: Older for * historical messages, Newer for new ones */ - Timeline::difference_type moveEventsToTimeline(RoomEventsRange events, - EventsPlacement placement); + Timeline::size_type moveEventsToTimeline(RoomEventsRange events, + EventsPlacement placement); /** * Remove events from the passed container that are already in the timeline @@ -341,7 +341,7 @@ const QString& Room::id() const QString Room::version() const { const auto v = d->getCurrentState()->version(); - return v.isEmpty() ? "1" : v; + return v.isEmpty() ? QStringLiteral("1") : v; } bool Room::isUnstable() const @@ -546,8 +546,8 @@ Room::Changes Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, { const auto oldUnreadCount = unreadMessages; QElapsedTimer et; et.start(); - unreadMessages = count_if(eagerMarker, timeline.cend(), - std::bind(&Room::Private::isEventNotable, this, _1)); + unreadMessages = int(count_if(eagerMarker, timeline.cend(), + std::bind(&Room::Private::isEventNotable, this, _1))); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Recounting unread messages took" << et; @@ -611,7 +611,7 @@ bool Room::canSwitchVersions() const // TODO, #276: m.room.power_levels const auto* plEvt = - d->currentState.value({"m.room.power_levels", ""}); + d->currentState.value({QStringLiteral("m.room.power_levels"), {}}); if (!plEvt) return true; @@ -621,7 +621,7 @@ bool Room::canSwitchVersions() const .value(localUser()->id()).toInt( plJson.value("users_default"_ls).toInt()); const auto tombstonePowerLevel = - plJson.value("events").toObject() + plJson.value("events"_ls).toObject() .value("m.room.tombstone"_ls).toInt( plJson.value("state_default"_ls).toInt()); return currentUserLevel >= tombstonePowerLevel; @@ -947,7 +947,7 @@ void Room::Private::setTags(TagsMap newTags) } tags = move(newTags); qCDebug(MAIN) << "Room" << q->objectName() << "is tagged with" - << q->tagNames().join(", "); + << q->tagNames().join(QStringLiteral(", ")); emit q->tagsChanged(); } @@ -1196,7 +1196,7 @@ void Room::Private::insertMemberIntoMap(User *u) emit q->memberRenamed(namesakes.front()); } -void Room::Private::renameMember(User* u, QString oldName) +void Room::Private::renameMember(User* u, const QString& oldName) { if (u->name(q) == oldName) { @@ -1234,7 +1234,7 @@ inline auto makeErrorStr(const Event& e, QByteArray msg) return msg.append("; event dump follows:\n").append(e.originalJson()); } -Room::Timeline::difference_type Room::Private::moveEventsToTimeline( +Room::Timeline::size_type Room::Private::moveEventsToTimeline( RoomEventsRange events, EventsPlacement placement) { Q_ASSERT(!events.empty()); @@ -1407,7 +1407,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) return; } it->setDeparted(); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); }); Room::connect(call, &BaseJob::failure, q, std::bind(&Room::Private::onEventSendingFailure, this, txnId, call)); @@ -1423,7 +1423,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) } it->setReachedServer(call->eventId()); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); }); } else onEventSendingFailure(txnId); @@ -1442,7 +1442,7 @@ void Room::Private::onEventSendingFailure(const QString& txnId, BaseJob* call) it->setSendingFailed(call ? call->statusCaption() % ": " % call->errorString() : tr("The call could not be started")); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); } QString Room::retryMessage(const QString& txnId) @@ -2045,7 +2045,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) roomChanges |= q->processStateEvent(*eptr); auto timelineSize = timeline.size(); - auto totalInserted = 0; + size_t totalInserted = 0; for (auto it = events.begin(); it != events.end();) { auto nextPendingPair = findFirstOf(it, events.end(), -- cgit v1.2.3 From edf6a717268d9751f58d256bfe21aab078dfb9f6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Apr 2019 20:32:09 +0900 Subject: Room::processStateEvent: be more careful with signals handling at user renames --- lib/room.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index caeeb499..9e7ff8d2 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1209,7 +1209,6 @@ void Room::Private::renameMember(User* u, const QString& oldName) removeMemberFromMap(oldName, u); insertMemberIntoMap(u); } - emit q->memberRenamed(u); } void Room::Private::removeMemberFromMap(const QString& username, User* u) @@ -2230,6 +2229,8 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) << evt; if (evt.membership() != prevMembership) { + disconnect(u, &User::nameAboutToChange, this, nullptr); + disconnect(u, &User::nameChanged, this, nullptr); d->removeMemberFromMap(u->name(this), u); emit userRemoved(u); } @@ -2257,7 +2258,10 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) connect(u, &User::nameChanged, this, [=] (QString, QString oldName, const Room* context) { if (context == this) + { d->renameMember(u, oldName); + emit memberRenamed(u); + } }); emit userAdded(u); } -- cgit v1.2.3