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/connection.cpp | 3 +-- lib/room.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 26b40c03..59aca025 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -271,8 +271,7 @@ void Connection::reloadCapabilities() Q_ASSERT(!d->capabilities.roomVersions.omitted()); emit capabilitiesLoaded(); for (auto* r: d->roomMap) - if (r->joinState() == JoinState::Join && r->successorId().isEmpty()) - r->checkVersion(); + r->checkVersion(); }); } 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 ced57b074d0dc4e6e582a4bddca7a07e41d2063f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 12 Mar 2019 21:16:54 +0900 Subject: Make Room::prettyPrint Q_INVOKABLE So that it can be called from QML code --- lib/room.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/room.h b/lib/room.h index f4ecef42..f7add412 100644 --- a/lib/room.h +++ b/lib/room.h @@ -365,7 +365,7 @@ namespace QMatrixClient * in the future, it will also linkify room aliases, mxids etc. * using the room context. */ - QString prettyPrint(const QString& plainText) const; + Q_INVOKABLE QString prettyPrint(const QString& plainText) const; MemberSorter memberSorter() const; -- cgit v1.2.3 From b6b4b5e82c8984c0c7e29c4ccf5c2d25cda563f9 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 13 Mar 2019 18:07:03 +0900 Subject: Room: make notificationCount, highlightCount Q_PROPERTYs Closes #299. Due to restrictions for the NOTIFY signal notificationCountChanged and highlightCountChanged no more carry Room* as a parameter, breaking back-compatibility. --- lib/room.cpp | 8 ++++---- lib/room.h | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index f2e03e94..a4946b65 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -815,7 +815,7 @@ void Room::resetNotificationCount() if( d->notificationCount == 0 ) return; d->notificationCount = 0; - emit notificationCountChanged(this); + emit notificationCountChanged(); } int Room::highlightCount() const @@ -828,7 +828,7 @@ void Room::resetHighlightCount() if( d->highlightCount == 0 ) return; d->highlightCount = 0; - emit highlightCountChanged(this); + emit highlightCountChanged(); } void Room::switchVersion(QString newVersion) @@ -1343,12 +1343,12 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) if( data.highlightCount != d->highlightCount ) { d->highlightCount = data.highlightCount; - emit highlightCountChanged(this); + emit highlightCountChanged(); } if( data.notificationCount != d->notificationCount ) { d->notificationCount = data.notificationCount; - emit notificationCountChanged(this); + emit notificationCountChanged(); } if (roomChanges != Change::NoChange) { diff --git a/lib/room.h b/lib/room.h index f7add412..352c098b 100644 --- a/lib/room.h +++ b/lib/room.h @@ -108,6 +108,8 @@ namespace QMatrixClient Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE markMessagesAsRead NOTIFY readMarkerMoved) Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY unreadMessagesChanged) Q_PROPERTY(int unreadCount READ unreadCount NOTIFY unreadMessagesChanged) + Q_PROPERTY(int highlightCount READ highlightCount NOTIFY highlightCountChanged RESET resetHighlightCount) + Q_PROPERTY(int notificationCount READ notificationCount NOTIFY notificationCountChanged RESET resetNotificationCount) Q_PROPERTY(QStringList tagNames READ tagNames NOTIFY tagsChanged) Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY tagsChanged) Q_PROPERTY(bool isLowPriority READ isLowPriority NOTIFY tagsChanged) @@ -517,8 +519,8 @@ namespace QMatrixClient void joinStateChanged(JoinState oldState, JoinState newState); void typingChanged(); - void highlightCountChanged(Room* room); - void notificationCountChanged(Room* room); + void highlightCountChanged(); + void notificationCountChanged(); void displayedChanged(bool displayed); void firstDisplayedEventChanged(); -- cgit v1.2.3 From d9452a731a1983d9b5d3f8cd419b09299c30996e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 13 Mar 2019 18:10:36 +0900 Subject: Bump API_VERSION to 0.6 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2d8c218..e01f1f6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,7 +145,7 @@ add_library(QMatrixClient ${libqmatrixclient_SRCS} ${libqmatrixclient_job_SRCS} ${libqmatrixclient_csdef_SRCS} ${libqmatrixclient_cswellknown_SRCS} ${libqmatrixclient_asdef_SRCS} ${libqmatrixclient_isdef_SRCS}) -set(API_VERSION "0.5") +set(API_VERSION "0.6") set_property(TARGET QMatrixClient PROPERTY VERSION "${API_VERSION}.0") set_property(TARGET QMatrixClient PROPERTY SOVERSION ${API_VERSION} ) set_property(TARGET QMatrixClient PROPERTY -- cgit v1.2.3 From d6cddf3e016a4fe3e906e2ab815a1e8b775c284c 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/jobs/basejob.cpp | 2 +- lib/room.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 8c3381ae..f738ce7a 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -186,7 +186,7 @@ QUrl BaseJob::makeRequestUrl(QUrl baseUrl, if (!pathBase.endsWith('/') && !path.startsWith('/')) pathBase.push_back('/'); - baseUrl.setPath( pathBase + path ); + baseUrl.setPath(pathBase + path, QUrl::TolerantMode); baseUrl.setQuery(query); return baseUrl; } diff --git a/lib/room.cpp b/lib/room.cpp index a4946b65..7494917d 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 266f4b8a82fa437bbd49c8e3d85313677e068699 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 21 Mar 2019 15:41:06 +0900 Subject: Room::displayName: fix NOTIFY signal for Q_PROPERTY --- lib/room.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/room.h b/lib/room.h index 352c098b..85c67bfb 100644 --- a/lib/room.h +++ b/lib/room.h @@ -88,7 +88,7 @@ namespace QMatrixClient Q_PROPERTY(QString name READ name NOTIFY namesChanged) Q_PROPERTY(QStringList aliases READ aliases NOTIFY namesChanged) Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) - Q_PROPERTY(QString displayName READ displayName NOTIFY namesChanged) + Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged) Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) -- cgit v1.2.3 From 01d9f7b3f1785034503497798fb732ee6ee5fba3 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 23 Mar 2019 15:51:04 +0900 Subject: Update to the latest CS API definitions No breaking changes; GetAccountDataJob/GetAccountDataPerRoomJob added. --- lib/csapi/account-data.cpp | 28 ++++++++++++++++++++ lib/csapi/account-data.h | 66 ++++++++++++++++++++++++++++++++++++++++++---- lib/csapi/capabilities.h | 8 +++--- lib/csapi/room_upgrades.h | 4 +-- 4 files changed, 94 insertions(+), 12 deletions(-) diff --git a/lib/csapi/account-data.cpp b/lib/csapi/account-data.cpp index 5021c73a..96b32a92 100644 --- a/lib/csapi/account-data.cpp +++ b/lib/csapi/account-data.cpp @@ -21,6 +21,20 @@ SetAccountDataJob::SetAccountDataJob(const QString& userId, const QString& type, setRequestData(Data(toJson(content))); } +QUrl GetAccountDataJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& type) +{ + return BaseJob::makeRequestUrl(std::move(baseUrl), + basePath % "/user/" % userId % "/account_data/" % type); +} + +static const auto GetAccountDataJobName = QStringLiteral("GetAccountDataJob"); + +GetAccountDataJob::GetAccountDataJob(const QString& userId, const QString& type) + : BaseJob(HttpVerb::Get, GetAccountDataJobName, + basePath % "/user/" % userId % "/account_data/" % type) +{ +} + static const auto SetAccountDataPerRoomJobName = QStringLiteral("SetAccountDataPerRoomJob"); SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, const QString& roomId, const QString& type, const QJsonObject& content) @@ -30,3 +44,17 @@ SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, const setRequestData(Data(toJson(content))); } +QUrl GetAccountDataPerRoomJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& roomId, const QString& type) +{ + return BaseJob::makeRequestUrl(std::move(baseUrl), + basePath % "/user/" % userId % "/rooms/" % roomId % "/account_data/" % type); +} + +static const auto GetAccountDataPerRoomJobName = QStringLiteral("GetAccountDataPerRoomJob"); + +GetAccountDataPerRoomJob::GetAccountDataPerRoomJob(const QString& userId, const QString& roomId, const QString& type) + : BaseJob(HttpVerb::Get, GetAccountDataPerRoomJobName, + basePath % "/user/" % userId % "/rooms/" % roomId % "/account_data/" % type) +{ +} + diff --git a/lib/csapi/account-data.h b/lib/csapi/account-data.h index f3656a14..b067618f 100644 --- a/lib/csapi/account-data.h +++ b/lib/csapi/account-data.h @@ -22,8 +22,8 @@ namespace QMatrixClient public: /*! Set some account_data for the user. * \param userId - * The id of the user to set account_data for. The access token must be - * authorized to make requests for this user id. + * The ID of the user to set account_data for. The access token must be + * authorized to make requests for this user ID. * \param type * The event type of the account_data to set. Custom types should be * namespaced to avoid clashes. @@ -33,6 +33,33 @@ namespace QMatrixClient explicit SetAccountDataJob(const QString& userId, const QString& type, const QJsonObject& content = {}); }; + /// Get some account_data for the user. + /// + /// Get some account_data for the client. This config is only visible to the user + /// that set the account_data. + class GetAccountDataJob : public BaseJob + { + public: + /*! Get some account_data for the user. + * \param userId + * The ID of the user to get account_data for. The access token must be + * authorized to make requests for this user ID. + * \param type + * The event type of the account_data to get. Custom types should be + * namespaced to avoid clashes. + */ + explicit GetAccountDataJob(const QString& userId, const QString& type); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetAccountDataJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& type); + + }; + /// Set some account_data for the user. /// /// Set some account_data for the client on a given room. This config is only @@ -43,10 +70,10 @@ namespace QMatrixClient public: /*! Set some account_data for the user. * \param userId - * The id of the user to set account_data for. The access token must be - * authorized to make requests for this user id. + * The ID of the user to set account_data for. The access token must be + * authorized to make requests for this user ID. * \param roomId - * The id of the room to set account_data on. + * The ID of the room to set account_data on. * \param type * The event type of the account_data to set. Custom types should be * namespaced to avoid clashes. @@ -55,4 +82,33 @@ namespace QMatrixClient */ explicit SetAccountDataPerRoomJob(const QString& userId, const QString& roomId, const QString& type, const QJsonObject& content = {}); }; + + /// Get some account_data for the user. + /// + /// Get some account_data for the client on a given room. This config is only + /// visible to the user that set the account_data. + class GetAccountDataPerRoomJob : public BaseJob + { + public: + /*! Get some account_data for the user. + * \param userId + * The ID of the user to set account_data for. The access token must be + * authorized to make requests for this user ID. + * \param roomId + * The ID of the room to get account_data for. + * \param type + * The event type of the account_data to get. Custom types should be + * namespaced to avoid clashes. + */ + explicit GetAccountDataPerRoomJob(const QString& userId, const QString& roomId, const QString& type); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetAccountDataPerRoomJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& roomId, const QString& type); + + }; } // namespace QMatrixClient diff --git a/lib/csapi/capabilities.h b/lib/csapi/capabilities.h index 39e2f4d1..06a8bf0d 100644 --- a/lib/csapi/capabilities.h +++ b/lib/csapi/capabilities.h @@ -39,8 +39,8 @@ namespace QMatrixClient QHash available; }; - /// Gets information about the server's supported feature set - /// and other relevant capabilities. + /// The custom capabilities the server supports, using the + /// Java package naming convention. struct Capabilities { /// Capability to indicate if the user can change their password. @@ -68,8 +68,8 @@ namespace QMatrixClient // Result properties - /// Gets information about the server's supported feature set - /// and other relevant capabilities. + /// The custom capabilities the server supports, using the + /// Java package naming convention. const Capabilities& capabilities() const; protected: diff --git a/lib/csapi/room_upgrades.h b/lib/csapi/room_upgrades.h index 6f712f10..4da5941a 100644 --- a/lib/csapi/room_upgrades.h +++ b/lib/csapi/room_upgrades.h @@ -13,9 +13,7 @@ namespace QMatrixClient /// Upgrades a room to a new room version. /// - /// Upgrades the given room to a particular room version, migrating as much - /// data as possible over to the new room. See the `room_upgrades <#room-upgrades>`_ - /// module for more information on what this entails. + /// Upgrades the given room to a particular room version. class UpgradeRoomJob : public BaseJob { public: -- cgit v1.2.3 From 9ba481f2c8e7f1db6144ece7119d8cc314c57bc5 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/events/eventcontent.cpp | 6 ++++++ lib/events/eventcontent.h | 2 ++ lib/room.cpp | 9 ++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 9a5e872c..77f756cd 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -50,6 +50,12 @@ FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } +bool FileInfo::isValid() const +{ + return url.scheme() == "mxc" + && (url.authority() + url.path()).count('/') == 1; +} + void FileInfo::fillInfoJson(QJsonObject* infoJson) const { Q_ASSERT(infoJson); diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 0588c0e2..ab31a75d 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -94,6 +94,8 @@ namespace QMatrixClient FileInfo(const QUrl& u, const QJsonObject& infoJson, const QString& originalFilename = {}); + bool isValid() const; + void fillInfoJson(QJsonObject* infoJson) const; /** diff --git a/lib/room.cpp b/lib/room.cpp index 7494917d..ce7bae04 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 21e5138f6cf1e96d3cac702e2ada2a0148a3ec92 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 24 Mar 2019 18:51:08 +0900 Subject: linkifyUrls(): fix linkification of emails containing "www." Closes #303. --- lib/util.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/util.cpp b/lib/util.cpp index d042aa34..e1f312ee 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -38,14 +38,14 @@ static void linkifyUrls(QString& htmlEscapedText) // comma or dot // Note: outer parentheses are a part of C++ raw string delimiters, not of // the regex (see http://en.cppreference.com/w/cpp/language/string_literal). - // Note2: yet another pair of outer parentheses are \1 in the replacement. + // Note2: the next-outer parentheses are \N in the replacement. static const QRegularExpression FullUrlRegExp(QStringLiteral( - R"(((www\.(?!\.)|(https?|ftp|magnet)://)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))" + R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp|magnet)://)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))" ), RegExpOptions); // email address: // [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] static const QRegularExpression EmailAddressRegExp(QStringLiteral( - R"((mailto:)?(\b(\w|\.|-)+@(\w|\.|-)+\.\w+\b))" + R"(\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))" ), RegExpOptions); // An interim liberal implementation of // https://matrix.org/docs/spec/appendices.html#identifier-grammar @@ -53,7 +53,7 @@ static void linkifyUrls(QString& htmlEscapedText) R"((^|[^<>/])([!#@][-a-z0-9_=/.]{1,252}:[-.a-z0-9]+))" ), RegExpOptions); - // NOTE: htmlEscapedText is already HTML-escaped! No literal <,>,& + // NOTE: htmlEscapedText is already HTML-escaped! No literal <,>,&," htmlEscapedText.replace(EmailAddressRegExp, QStringLiteral(R"(\1\2)")); -- cgit v1.2.3 From e855085835909549aa866ed968e24902eb378b5a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 17 Mar 2019 09:03:34 +0900 Subject: RoomMemberEvent: sanitize user display names MemberEventContent::displayName() will strip away Unicode text direction override characters. Direct access to JSON can still provide "raw" data. --- lib/events/roommemberevent.cpp | 2 +- lib/util.cpp | 10 +++++++++- lib/util.h | 7 ++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index a5ac3c5f..6da76526 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -52,7 +52,7 @@ using namespace QMatrixClient; MemberEventContent::MemberEventContent(const QJsonObject& json) : membership(fromJson(json["membership"_ls])) , isDirect(json["is_direct"_ls].toBool()) - , displayName(json["displayname"_ls].toString()) + , displayName(sanitized(json["displayname"_ls].toString())) , avatarUrl(json["avatar_url"_ls].toString()) { } diff --git a/lib/util.cpp b/lib/util.cpp index e1f312ee..8d16cfc8 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -63,10 +63,18 @@ static void linkifyUrls(QString& htmlEscapedText) QStringLiteral(R"(\1\2)")); } +QString QMatrixClient::sanitized(const QString& plainText) +{ + auto text = plainText; + text.remove(QChar(0x202e)); + text.remove(QChar(0x202d)); + return text; +} + QString QMatrixClient::prettyPrint(const QString& plainText) { auto pt = QStringLiteral("") + - plainText.toHtmlEscaped() + QStringLiteral(""); + plainText.toHtmlEscaped() + QStringLiteral(""); pt.replace('\n', QStringLiteral("
")); linkifyUrls(pt); diff --git a/lib/util.h b/lib/util.h index f7f646da..beb3c697 100644 --- a/lib/util.h +++ b/lib/util.h @@ -296,7 +296,12 @@ namespace QMatrixClient return std::make_pair(last, sLast); } - /** Pretty-prints plain text into HTML + /** Sanitize the text before showing in HTML + * This does toHtmlEscaped() and removes Unicode BiDi marks. + */ + QString sanitized(const QString& plainText); + + /** Pretty-print plain text into HTML * This includes HTML escaping of <,>,",& and URLs linkification. */ QString prettyPrint(const QString& plainText); -- cgit v1.2.3 From e2dea5a7e263707c283b63f0c31b4fae8399db3b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 17 Mar 2019 18:58:49 +0900 Subject: User: strip RLO/LRO markers on renaming as well Continuation of work on #545. --- lib/user.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/user.cpp b/lib/user.cpp index eec41957..93cfbffd 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -264,8 +264,9 @@ void User::updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl, void User::rename(const QString& newName) { - auto job = connection()->callApi(id(), newName); - connect(job, &BaseJob::success, this, [=] { updateName(newName); }); + const auto actualNewName = sanitized(newName); + connect(connection()->callApi(id(), actualNewName), + &BaseJob::success, this, [=] { updateName(actualNewName); }); } void User::rename(const QString& newName, const Room* r) @@ -279,10 +280,11 @@ void User::rename(const QString& newName, const Room* r) } Q_ASSERT_X(r->memberJoinState(this) == JoinState::Join, __FUNCTION__, "Attempt to rename a user that's not a room member"); + const auto actualNewName = sanitized(newName); MemberEventContent evtC; - evtC.displayName = newName; - auto job = r->setMemberState(id(), RoomMemberEvent(move(evtC))); - connect(job, &BaseJob::success, this, [=] { updateName(newName, r); }); + evtC.displayName = actualNewName; + connect(r->setMemberState(id(), RoomMemberEvent(move(evtC))), + &BaseJob::success, this, [=] { updateName(actualNewName, r); }); } bool User::setAvatar(const QString& fileName) -- cgit v1.2.3 From adcea5868d45610be0539af3e1cfc15f8495815c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 24 Mar 2019 19:09:48 +0900 Subject: Expose linkifyUrls() into library API for future use --- lib/util.cpp | 11 ++++++----- lib/util.h | 5 ++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/util.cpp b/lib/util.cpp index 8d16cfc8..fe6286f3 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -29,16 +29,17 @@ static const auto RegExpOptions = | QRegularExpression::UseUnicodePropertiesOption; // Converts all that looks like a URL into HTML links -static void linkifyUrls(QString& htmlEscapedText) +void QMatrixClient::linkifyUrls(QString& htmlEscapedText) { + // Note: outer parentheses are a part of C++ raw string delimiters, not of + // the regex (see http://en.cppreference.com/w/cpp/language/string_literal). + // Note2: the next-outer parentheses are \N in the replacement. + + // generic url: // regexp is originally taken from Konsole (https://github.com/KDE/konsole) - // full url: // protocolname:// or www. followed by anything other than whitespaces, // <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, ), :, // comma or dot - // Note: outer parentheses are a part of C++ raw string delimiters, not of - // the regex (see http://en.cppreference.com/w/cpp/language/string_literal). - // Note2: the next-outer parentheses are \N in the replacement. static const QRegularExpression FullUrlRegExp(QStringLiteral( R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp|magnet)://)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))" ), RegExpOptions); diff --git a/lib/util.h b/lib/util.h index beb3c697..f08c1c95 100644 --- a/lib/util.h +++ b/lib/util.h @@ -296,13 +296,16 @@ namespace QMatrixClient return std::make_pair(last, sLast); } + /** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */ + void linkifyUrls(QString& htmlEscapedText); + /** Sanitize the text before showing in HTML * This does toHtmlEscaped() and removes Unicode BiDi marks. */ QString sanitized(const QString& plainText); /** Pretty-print plain text into HTML - * This includes HTML escaping of <,>,",& and URLs linkification. + * This includes HTML escaping of <,>,",& and calling linkifyUrls() */ QString prettyPrint(const QString& plainText); -- cgit v1.2.3 From c8becd7ba500802bbb23b986108ad965d4f31df5 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(+) diff --git a/lib/room.cpp b/lib/room.cpp index ce7bae04..18dacbaa 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 d1efa846ee2e454b56dc77db403e40f1d9dfc7cd Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 26 Mar 2019 12:00:14 +0900 Subject: Room::switchVersion(): refuse to switch a version if a tombstone is already there Closes #306. --- lib/room.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 18dacbaa..2930875f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -836,10 +836,17 @@ void Room::resetHighlightCount() void Room::switchVersion(QString newVersion) { - auto* job = connection()->callApi(id(), newVersion); - connect(job, &BaseJob::failure, this, [this,job] { - emit upgradeFailed(job->errorString()); - }); + if (!successorId().isEmpty()) + { + Q_ASSERT(!successorId().isEmpty()); + emit upgradeFailed(tr("The room is already upgraded")); + } + if (auto* job = connection()->callApi(id(), newVersion)) + connect(job, &BaseJob::failure, this, [this,job] { + emit upgradeFailed(job->errorString()); + }); + else + emit upgradeFailed(tr("Couldn't initiate upgrade")); } bool Room::hasAccountData(const QString& type) const -- cgit v1.2.3 From fe3141f7bc3d2c92dcfe8ee5b3eb8d3ca6f0ab17 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 27 Mar 2019 19:06:34 +0900 Subject: Connection::domain() --- lib/connection.cpp | 5 +++++ lib/connection.h | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 59aca025..07c24c92 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -788,6 +788,11 @@ QUrl Connection::homeserver() const return d->data->baseUrl(); } +QString Connection::domain() const +{ + return d->userId.section(':', 1); +} + Room* Connection::room(const QString& roomId, JoinStates states) const { Room* room = d->roomMap.value({roomId, false}, nullptr); diff --git a/lib/connection.h b/lib/connection.h index b22d63da..ea5be51a 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -104,6 +104,7 @@ namespace QMatrixClient Q_PROPERTY(QByteArray accessToken READ accessToken NOTIFY stateChanged) Q_PROPERTY(QString defaultRoomVersion READ defaultRoomVersion NOTIFY capabilitiesLoaded) Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) + Q_PROPERTY(QString domain READ domain NOTIFY homeserverChanged) Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged) Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged) @@ -241,7 +242,10 @@ namespace QMatrixClient /** Get the full list of users known to this account */ QMap users() const; + /** Get the base URL of the homeserver to connect to */ QUrl homeserver() const; + /** Get the domain name used for ids/aliases on the server */ + QString domain() const; /** Find a room by its id and a mask of applicable states */ Q_INVOKABLE Room* room(const QString& roomId, JoinStates states = JoinState::Invite|JoinState::Join) const; -- cgit v1.2.3 From 57cf115d713bf02d0ba97a7a1f2e3874be8080e3 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 27 Mar 2019 19:09:28 +0900 Subject: qmc-example: add a couple homeserver data sanity checks --- examples/qmc-example.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 9d6f2f39..bd9190b9 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -101,6 +101,8 @@ QMCTest::QMCTest(Connection* conn, QString testRoomName, QString source) void QMCTest::setupAndRun() { + Q_ASSERT(!c->homeserver().isEmpty() && c->homeserver().isValid()); + Q_ASSERT(c->domain() == c->userId().section(':', 1)); cout << "Connected, server: " << c->homeserver().toDisplayString().toStdString() << endl; cout << "Access token: " << c->accessToken().toStdString() << endl; -- cgit v1.2.3 From 01c5a35398a55dfc4a30e466aeb13419387555d3 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(-) diff --git a/lib/room.cpp b/lib/room.cpp index 2930875f..19658be0 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1966,10 +1966,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)) @@ -2170,7 +2170,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 a28892ca3b40a32556ee7615116c322f6b2a4ae5 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 ++++++++++++++++-------- lib/user.cpp | 13 ++++++------- lib/user.h | 6 +++++- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 19658be0..789800c6 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1184,7 +1184,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)); @@ -2196,16 +2200,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, @@ -2221,9 +2229,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; diff --git a/lib/user.cpp b/lib/user.cpp index 93cfbffd..c373a067 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -379,19 +379,18 @@ QUrl User::avatarUrl(const Room* room) const return avatarObject(room).url(); } -void User::processEvent(const RoomMemberEvent& event, const Room* room) +void User::processEvent(const RoomMemberEvent& event, const Room* room, + bool firstMention) { Q_ASSERT(room); + + if (firstMention) + ++d->totalRooms; + if (event.membership() != MembershipType::Invite && event.membership() != MembershipType::Join) return; - auto aboutToEnter = room->memberJoinState(this) == JoinState::Leave && - (event.membership() == MembershipType::Join || - event.membership() == MembershipType::Invite); - if (aboutToEnter) - ++d->totalRooms; - auto newName = event.displayName(); // `bridged` value uses the same notification signal as the name; // it is assumed that first setting of the bridge occurs together with diff --git a/lib/user.h b/lib/user.h index 0023b44a..7c9ed55f 100644 --- a/lib/user.h +++ b/lib/user.h @@ -105,7 +105,11 @@ namespace QMatrixClient QString avatarMediaId(const Room* room = nullptr) const; QUrl avatarUrl(const Room* room = nullptr) const; - void processEvent(const RoomMemberEvent& event, const Room* r); + /// This method is for internal use and should not be called + /// from client code + // FIXME: Move it away to private in lib 0.6 + void processEvent(const RoomMemberEvent& event, const Room* r, + bool firstMention); public slots: /** Set a new name in the global user profile */ -- cgit v1.2.3 From e2b30ad0dc6f5a369cbd1dce3f9c92ef5d801dae Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 29 Mar 2019 13:30:03 +0900 Subject: Connection: make sure to mark rooms supposed to be direct chats as such Closes #305. Relies on correct tracking of Invite membership from the previous commit. --- lib/connection.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 07c24c92..c09de979 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -609,8 +609,17 @@ CreateRoomJob* Connection::createRoom(RoomVisibility visibility, : QStringLiteral("private"), alias, name, topic, invites, invite3pids, roomVersion, creationContent, initialState, presetName, isDirect); - connect(job, &BaseJob::success, this, [this,job] { - emit createdRoom(provideRoom(job->roomId(), JoinState::Join)); + connect(job, &BaseJob::success, this, [this,job,invites,isDirect] { + auto* room = provideRoom(job->roomId(), JoinState::Join); + if (!room) + { + Q_ASSERT_X(room, "Connection::createRoom", "Failed to create a room"); + return; + } + emit createdRoom(room); + if (isDirect) + for (const auto& i: invites) + addToDirectChats(room, user(i)); }); return job; } @@ -1161,6 +1170,9 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) emit leftRoom(room, prevInvite); if (prevInvite) { + const auto dcUsers = prevInvite->directChatUsers(); + for (auto* u: dcUsers) + addToDirectChats(room, u); qCDebug(MAIN) << "Deleting Invite state for room" << prevInvite->id(); emit prevInvite->beforeDestruction(prevInvite); prevInvite->deleteLater(); -- cgit v1.2.3 From 91e70b1242e6e40170285bd29fdfd7c82fd45691 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 30 Mar 2019 20:52:43 +0900 Subject: User::nameForRoom(): null hint is not a hint This caused the library to erroneously believe that users with no representation in other rooms have no display name even if that display name is provided for the given room. --- lib/user.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/user.cpp b/lib/user.cpp index c373a067..951ad87d 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -82,7 +82,8 @@ class User::Private QString User::Private::nameForRoom(const Room* r, const QString& hint) const { // If the hint is accurate, this function is O(1) instead of O(n) - if (hint == mostUsedName || otherNames.contains(hint, r)) + if (!hint.isNull() + && (hint == mostUsedName || otherNames.contains(hint, r))) return hint; return otherNames.key(r, mostUsedName); } -- cgit v1.2.3 From 27ba75b458d2418051b170eb811ab88c00c336db 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 +++++ lib/room.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/lib/room.cpp b/lib/room.cpp index 789800c6..bf51584d 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(); diff --git a/lib/room.h b/lib/room.h index 85c67bfb..8440543e 100644 --- a/lib/room.h +++ b/lib/room.h @@ -410,6 +410,9 @@ namespace QMatrixClient void setAliases(const QStringList& aliases); void setTopic(const QString& newTopic); + /// You shouldn't normally call this method; it's here for debugging + void refreshDisplayName(); + void getPreviousContent(int limit = 10); void inviteToRoom(const QString& memberId); -- cgit v1.2.3 From 1cc9a93bb403ba50aa80524bc9d9dae44241941e 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(-) diff --git a/lib/room.cpp b/lib/room.cpp index bf51584d..746dd688 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1346,7 +1346,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)); @@ -1371,6 +1370,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 2a53784faa9b776406900db0a7a750c8963ced92 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(-) diff --git a/lib/room.cpp b/lib/room.cpp index 746dd688..b64ed3a1 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; @@ -1231,7 +1232,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); } @@ -2216,8 +2216,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); @@ -2233,18 +2263,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; } @@ -2425,42 +2451,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 3ea5915d864040dd7f1b1cdffcacd67850a644bf Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 31 Mar 2019 18:45:11 +0900 Subject: CMakeLists.txt: slightly better version management --- CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e01f1f6c..e4642f3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,7 +146,8 @@ add_library(QMatrixClient ${libqmatrixclient_SRCS} ${libqmatrixclient_cswellknown_SRCS} ${libqmatrixclient_asdef_SRCS} ${libqmatrixclient_isdef_SRCS}) set(API_VERSION "0.6") -set_property(TARGET QMatrixClient PROPERTY VERSION "${API_VERSION}.0") +set(FULL_VERSION "${API_VERSION}.0") +set_property(TARGET QMatrixClient PROPERTY VERSION "${FULL_VERSION}") set_property(TARGET QMatrixClient PROPERTY SOVERSION ${API_VERSION} ) set_property(TARGET QMatrixClient PROPERTY INTERFACE_QMatrixClient_MAJOR_VERSION ${API_VERSION}) @@ -174,10 +175,12 @@ install(DIRECTORY lib/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h") include(CMakePackageConfigHelpers) +# NB: SameMajorVersion doesn't really work yet, as we're within 0.x trail. +# Maybe consider jumping the gun and releasing 1.0, as semver advises? write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/QMatrixClient/QMatrixClientConfigVersion.cmake" - VERSION ${API_VERSION} - COMPATIBILITY AnyNewerVersion + VERSION ${FULL_VERSION} + COMPATIBILITY SameMajorVersion ) export(PACKAGE QMatrixClient) -- cgit v1.2.3 From 008207f178475531c0a8a86b2192c09d7134ad0e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 3 Apr 2019 19:33:24 +0900 Subject: room.h: more doc-comments --- lib/room.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/room.h b/lib/room.h index 8440543e..db4336ea 100644 --- a/lib/room.h +++ b/lib/room.h @@ -358,8 +358,28 @@ namespace QMatrixClient Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId) const; Q_INVOKABLE QUrl urlToDownload(const QString& eventId) const; + + /// Get a file name for downloading for a given event id + /*! + * The event MUST be RoomMessageEvent and have content + * for downloading. \sa RoomMessageEvent::hasContent + */ Q_INVOKABLE QString fileNameToDownload(const QString& eventId) const; + + /// Get information on file upload/download + /*! + * \param id uploads are identified by the corresponding event's + * transactionId (because uploads are done before + * the event is even sent), while downloads are using + * the normal event id for identifier. + */ Q_INVOKABLE FileTransferInfo fileTransferInfo(const QString& id) const; + + /// Get the URL to the actual file source in a unified way + /*! + * For uploads it will return a URL to a local file; for downloads + * the URL will be taken from the corresponding room event. + */ Q_INVOKABLE QUrl fileSource(const QString& id) const; /** Pretty-prints plain text into HTML -- cgit v1.2.3 From 8035ec464ce797abab5e464c2cfb485f7fa14d06 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(-) diff --git a/lib/room.cpp b/lib/room.cpp index b64ed3a1..587b598d 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1540,12 +1540,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 432e7fd7107d8260e0016a1adcd8d94263dc1044 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/avatar.cpp | 2 +- lib/connection.cpp | 25 +++++++++++++++---------- lib/connectiondata.cpp | 6 +++--- lib/events/event.cpp | 3 ++- lib/events/stateevent.cpp | 2 +- lib/jobs/basejob.cpp | 2 +- lib/jobs/downloadfilejob.cpp | 5 +++-- lib/jobs/mediathumbnailjob.cpp | 2 +- lib/networkaccessmanager.cpp | 3 ++- lib/networksettings.cpp | 2 +- lib/room.cpp | 32 ++++++++++++++++---------------- lib/settings.cpp | 21 ++++++++++++--------- lib/settings.h | 2 +- lib/syncdata.cpp | 8 ++++---- lib/user.cpp | 14 ++++++++------ lib/util.cpp | 2 +- 16 files changed, 72 insertions(+), 59 deletions(-) diff --git a/lib/avatar.cpp b/lib/avatar.cpp index c0ef3cba..9279ef9d 100644 --- a/lib/avatar.cpp +++ b/lib/avatar.cpp @@ -191,7 +191,7 @@ bool Avatar::Private::checkUrl(const QUrl& url) const } QString Avatar::Private::localFile() const { - static const auto cachePath = cacheLocation("avatars"); + static const auto cachePath = cacheLocation(QStringLiteral("avatars")); return cachePath % _url.authority() % '_' % _url.fileName() % ".png"; } diff --git a/lib/connection.cpp b/lib/connection.cpp index c09de979..5ed72616 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -717,8 +717,8 @@ void Connection::doInDirectChat(User* u, CreateRoomJob* Connection::createDirectChat(const QString& userId, const QString& topic, const QString& name) { - return createRoom(UnpublishRoom, "", name, topic, {userId}, - "trusted_private_chat", {}, true); + return createRoom(UnpublishRoom, {}, name, topic, {userId}, + QStringLiteral("trusted_private_chat"), {}, true); } ForgetRoomJob* Connection::forgetRoom(const QString& id) @@ -964,7 +964,8 @@ QHash> Connection::tagsToRooms() const QHash> result; for (auto* r: qAsConst(d->roomMap)) { - for (const auto& tagName: r->tagNames()) + const auto& tagNames = r->tagNames(); + for (const auto& tagName: tagNames) result[tagName].push_back(r); } for (auto it = result.begin(); it != result.end(); ++it) @@ -979,9 +980,12 @@ QStringList Connection::tagNames() const { QStringList tags ({FavouriteTag}); for (auto* r: qAsConst(d->roomMap)) - for (const auto& tag: r->tagNames()) + { + const auto& tagNames = r->tagNames(); + for (const auto& tag: tagNames) if (tag != LowPriorityTag && !tags.contains(tag)) tags.push_back(tag); + } tags.push_back(LowPriorityTag); return tags; } @@ -1264,18 +1268,19 @@ void Connection::saveState() const { QJsonObject rooms; QJsonObject inviteRooms; - for (const auto* i : roomMap()) // Pass on rooms in Leave state + const auto& rs = roomMap(); // Pass on rooms in Leave state + for (const auto* i : rs) (i->joinState() == JoinState::Invite ? inviteRooms : rooms) .insert(i->id(), QJsonValue::Null); QJsonObject roomObj; if (!rooms.isEmpty()) - roomObj.insert("join", rooms); + roomObj.insert(QStringLiteral("join"), rooms); if (!inviteRooms.isEmpty()) - roomObj.insert("invite", inviteRooms); + roomObj.insert(QStringLiteral("invite"), inviteRooms); - rootObj.insert("next_batch", d->data->lastEvent()); - rootObj.insert("rooms", roomObj); + rootObj.insert(QStringLiteral("next_batch"), d->data->lastEvent()); + rootObj.insert(QStringLiteral("rooms"), roomObj); } { QJsonArray accountDataEvents { @@ -1285,7 +1290,7 @@ void Connection::saveState() const accountDataEvents.append( basicEventJson(e.first, e.second->contentJson())); - rootObj.insert("account_data", + rootObj.insert(QStringLiteral("account_data"), QJsonObject {{ QStringLiteral("events"), accountDataEvents }}); } diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index eb516ef7..91cda09f 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.cpp @@ -25,7 +25,7 @@ using namespace QMatrixClient; struct ConnectionData::Private { - explicit Private(const QUrl& url) : baseUrl(url) { } + explicit Private(QUrl url) : baseUrl(std::move(url)) { } QUrl baseUrl; QByteArray accessToken; @@ -37,7 +37,7 @@ struct ConnectionData::Private }; ConnectionData::ConnectionData(QUrl baseUrl) - : d(std::make_unique(baseUrl)) + : d(std::make_unique(std::move(baseUrl))) { } ConnectionData::~ConnectionData() = default; @@ -98,7 +98,7 @@ QString ConnectionData::lastEvent() const void ConnectionData::setLastEvent(QString identifier) { - d->lastEvent = identifier; + d->lastEvent = std::move(identifier); } QByteArray ConnectionData::generateTxnId() const diff --git a/lib/events/event.cpp b/lib/events/event.cpp index c98dfbb6..6505d89a 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -38,7 +38,8 @@ event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId) QString EventTypeRegistry::getMatrixType(event_type_t typeId) { - return typeId < get().eventTypes.size() ? get().eventTypes[typeId] : ""; + return typeId < get().eventTypes.size() + ? get().eventTypes[typeId] : QString(); } Event::Event(Type type, const QJsonObject& json) diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index e96614d2..a84f302b 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -27,7 +27,7 @@ using namespace QMatrixClient; RoomEvent::factory_t::addMethod( [] (const QJsonObject& json, const QString& matrixType) -> StateEventPtr { - if (!json.contains("state_key")) + if (!json.contains("state_key"_ls)) return nullptr; if (auto e = StateEventBase::factory_t::make(json, matrixType)) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index f738ce7a..f521cc4b 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -430,7 +430,7 @@ BaseJob::Status BaseJob::doCheckReply(QNetworkReply* reply) const BaseJob::Status BaseJob::parseReply(QNetworkReply* reply) { d->rawResponse = reply->readAll(); - QJsonParseError error; + QJsonParseError error { 0, QJsonParseError::MissingObject }; const auto& json = QJsonDocument::fromJson(d->rawResponse, &error); if( error.error == QJsonParseError::NoError ) return parseJson(json); diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 2bf9dd8f..672a7b2d 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -22,7 +22,8 @@ class DownloadFileJob::Private QUrl DownloadFileJob::makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri) { - return makeRequestUrl(baseUrl, mxcUri.authority(), mxcUri.path().mid(1)); + return makeRequestUrl( + std::move(baseUrl), mxcUri.authority(), mxcUri.path().mid(1)); } DownloadFileJob::DownloadFileJob(const QString& serverName, @@ -31,7 +32,7 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) { - setObjectName("DownloadFileJob"); + setObjectName(QStringLiteral("DownloadFileJob")); } QString DownloadFileJob::targetFileName() const diff --git a/lib/jobs/mediathumbnailjob.cpp b/lib/jobs/mediathumbnailjob.cpp index aeb49839..edb9b156 100644 --- a/lib/jobs/mediathumbnailjob.cpp +++ b/lib/jobs/mediathumbnailjob.cpp @@ -59,5 +59,5 @@ BaseJob::Status MediaThumbnailJob::parseReply(QNetworkReply* reply) if( _thumbnail.loadFromData(data()->readAll()) ) return Success; - return { IncorrectResponseError, "Could not read image data" }; + return { IncorrectResponseError, QStringLiteral("Could not read image data") }; } diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 89967a8a..7d9cb360 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -29,7 +29,8 @@ class NetworkAccessManager::Private QList ignoredSslErrors; }; -NetworkAccessManager::NetworkAccessManager(QObject* parent) : d(std::make_unique()) +NetworkAccessManager::NetworkAccessManager(QObject* parent) + : QNetworkAccessManager(parent), d(std::make_unique()) { } QList NetworkAccessManager::ignoredSslErrors() const diff --git a/lib/networksettings.cpp b/lib/networksettings.cpp index 48bd09f3..6ff2bc1f 100644 --- a/lib/networksettings.cpp +++ b/lib/networksettings.cpp @@ -27,5 +27,5 @@ void NetworkSettings::setupApplicationProxy() const } QMC_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType, "proxy_type", QNetworkProxy::DefaultProxy, setProxyType) -QMC_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname", "", setProxyHostName) +QMC_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname", {}, setProxyHostName) QMC_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1, setProxyPort) diff --git a/lib/room.cpp b/lib/room.cpp index 587b598d..f031273a 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; @@ -954,7 +954,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(); } @@ -1203,7 +1203,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) { @@ -1241,7 +1241,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()); @@ -1414,7 +1414,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)); @@ -1430,7 +1430,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); @@ -1449,7 +1449,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) @@ -2052,7 +2052,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(), diff --git a/lib/settings.cpp b/lib/settings.cpp index 852e19cb..124d7042 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -84,18 +84,21 @@ void SettingsGroup::remove(const QString& key) Settings::remove(fullKey); } -QMC_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", "", setDeviceId) -QMC_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", "", setDeviceName) +QMC_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", {}, setDeviceId) +QMC_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, setDeviceName) QMC_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, setKeepLoggedIn) +static const auto HomeserverKey = QStringLiteral("homeserver"); +static const auto AccessTokenKey = QStringLiteral("access_token"); + QUrl AccountSettings::homeserver() const { - return QUrl::fromUserInput(value("homeserver").toString()); + return QUrl::fromUserInput(value(HomeserverKey).toString()); } void AccountSettings::setHomeserver(const QUrl& url) { - setValue("homeserver", url.toString()); + setValue(HomeserverKey, url.toString()); } QString AccountSettings::userId() const @@ -105,19 +108,19 @@ QString AccountSettings::userId() const QString AccountSettings::accessToken() const { - return value("access_token").toString(); + return value(AccessTokenKey).toString(); } void AccountSettings::setAccessToken(const QString& accessToken) { qCWarning(MAIN) << "Saving access_token to QSettings is insecure." " Developers, please save access_token separately."; - setValue("access_token", accessToken); + setValue(AccessTokenKey, accessToken); } void AccountSettings::clearAccessToken() { - legacySettings.remove("access_token"); - legacySettings.remove("device_id"); // Force the server to re-issue it - remove("access_token"); + legacySettings.remove(AccessTokenKey); + legacySettings.remove(QStringLiteral("device_id")); // Force the server to re-issue it + remove(AccessTokenKey); } diff --git a/lib/settings.h b/lib/settings.h index 0b3ecaff..759bda35 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -119,7 +119,7 @@ type classname::propname() const \ \ void classname::setter(type newValue) \ { \ - setValue(QStringLiteral(qsettingname), newValue); \ + setValue(QStringLiteral(qsettingname), std::move(newValue)); \ } \ class AccountSettings: public SettingsGroup diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index f55d4396..21517884 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -72,7 +72,7 @@ void JsonObjectConverter::fillFrom(const QJsonObject& jo, { fromJson(jo["m.joined_member_count"_ls], rs.joinedMemberCount); fromJson(jo["m.invited_member_count"_ls], rs.invitedMemberCount); - fromJson(jo["m.heroes"], rs.heroes); + fromJson(jo["m.heroes"_ls], rs.heroes); } template @@ -85,7 +85,7 @@ SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_, const QJsonObject& room_) : roomId(roomId_) , joinState(joinState_) - , summary(fromJson(room_["summary"])) + , summary(fromJson(room_["summary"_ls])) , state(load(room_, joinState == JoinState::Invite ? "invite_state"_ls : "state"_ls)) { @@ -121,8 +121,8 @@ SyncData::SyncData(const QString& cacheFileName) QFileInfo cacheFileInfo { cacheFileName }; auto json = loadJson(cacheFileName); auto requiredVersion = std::get<0>(cacheVersion()); - auto actualVersion = json.value("cache_version").toObject() - .value("major").toInt(); + auto actualVersion = json.value("cache_version"_ls).toObject() + .value("major"_ls).toInt(); if (actualVersion == requiredVersion) parseJson(json, cacheFileInfo.absolutePath() + '/'); else diff --git a/lib/user.cpp b/lib/user.cpp index 951ad87d..17db5760 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -59,7 +59,7 @@ class User::Private QMultiHash otherNames; Avatar mostUsedAvatar { makeAvatar({}) }; std::vector otherAvatars; - auto otherAvatar(QUrl url) + auto otherAvatar(const QUrl& url) { return std::find_if(otherAvatars.begin(), otherAvatars.end(), [&url] (const auto& av) { return av.url() == url; }); @@ -69,7 +69,7 @@ class User::Private mutable int totalRooms = 0; QString nameForRoom(const Room* r, const QString& hint = {}) const; - void setNameForRoom(const Room* r, QString newName, QString oldName); + void setNameForRoom(const Room* r, QString newName, const QString& oldName); QUrl avatarUrlForRoom(const Room* r, const QUrl& hint = {}) const; void setAvatarForRoom(const Room* r, const QUrl& newUrl, const QUrl& oldUrl); @@ -91,7 +91,7 @@ QString User::Private::nameForRoom(const Room* r, const QString& hint) const static constexpr int MIN_JOINED_ROOMS_TO_LOG = 20; void User::Private::setNameForRoom(const Room* r, QString newName, - QString oldName) + const QString& oldName) { Q_ASSERT(oldName != newName); Q_ASSERT(oldName == mostUsedName || otherNames.contains(oldName, r)); @@ -118,7 +118,8 @@ void User::Private::setNameForRoom(const Room* r, QString newName, et.start(); } - for (auto* r1: connection->roomMap()) + const auto& roomMap = connection->roomMap(); + for (auto* r1: roomMap) if (nameForRoom(r1) == mostUsedName) otherNames.insert(mostUsedName, r1); @@ -178,7 +179,8 @@ void User::Private::setAvatarForRoom(const Room* r, const QUrl& newUrl, auto nextMostUsedIt = otherAvatar(newUrl); Q_ASSERT(nextMostUsedIt != otherAvatars.end()); std::swap(mostUsedAvatar, *nextMostUsedIt); - for (const auto* r1: connection->roomMap()) + const auto& roomMap = connection->roomMap(); + for (const auto* r1: roomMap) if (avatarUrlForRoom(r1) == nextMostUsedIt->url()) avatarsToRooms.insert(nextMostUsedIt->url(), r1); @@ -399,7 +401,7 @@ void User::processEvent(const RoomMemberEvent& event, const Room* room, // exceptionally rare (the only reasonable case being that the bridge // changes the naming convention). For the same reason room-specific // bridge tags are not supported at all. - QRegularExpression reSuffix(" \\((IRC|Gitter|Telegram)\\)$"); + QRegularExpression reSuffix(QStringLiteral(" \\((IRC|Gitter|Telegram)\\)$")); auto match = reSuffix.match(newName); if (match.hasMatch()) { diff --git a/lib/util.cpp b/lib/util.cpp index fe6286f3..c3e21c8e 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -157,7 +157,7 @@ static_assert(!is_callable_v>, "Test non-function object"); // "Test returns<> with static member function"); template -QString ft(T&&); +QString ft(T&&) { return {}; } static_assert(std::is_same)>, QString&&>(), "Test function templates"); -- cgit v1.2.3 From a10a06052aa3f85f937844cb72f8775712e4299b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 4 Apr 2019 21:29:57 +0900 Subject: Update README.md and CONTRIBUTING.md (attn: LGPL v3 coming) [skip ci] --- CONTRIBUTING.md | 55 +++++++++++++++++++++++++++++++++---------------------- README.md | 17 ++++++++++++++--- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b534c32..56bc9d91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,17 +86,18 @@ a commit without a DCO is an accident and the DCO still applies. --> ### License -Unless a contributor explicitly specifies otherwise, we assume that all -contributed code is released under [the same license as libQMatrixClient itself](./COPYING), -which is LGPL v2.1 as of the time of this writing. +Unless a contributor explicitly specifies otherwise, we assume contributors +to agree that all contributed code is released either under *LGPL v2.1 or later*. +This is more than just [LGPL v2.1 libQMatrixClient now uses](./COPYING) +because the project plans to switch to LGPL v3 for library code in the near future. Any components proposed for reuse should have a license that permits releasing -a derivative work under LGPL v2.1. Moreover, the license of a proposed component -should be approved by OSI, no exceptions. +a derivative work under *LGPL v2.1 or later* or LGPL v3. Moreover, the license of +a proposed component should be approved by OSI, no exceptions. ## Vulnerability reporting (security issues) @@ -110,7 +111,7 @@ In any of these two options, _indicate that you have such information_ By default, we will give credit to anyone who reports a vulnerability in a responsible way so that we can fix it before public disclosure. If you want -to remain anonymous or pseudonymous instead, please let us know that; we will +to remain anonymous or pseudonymous instead, please let us know; we will gladly respect your wishes. If you provide a fix as a PR, you have no way to remain anonymous (and you also disclose the vulnerability thereby) so this is not the right way, unless the vulnerability is already made public. @@ -156,12 +157,12 @@ The code should strive to be DRY (don't repeat yourself), clear, and obviously c ### Generated C++ code for CS API The code in lib/csapi, lib/identity and lib/application-service, although -it resides in Git, is actually generated from the official Matrix -Swagger/OpenAPI definition files. If you're unhappy with something in these -directories and want to improve the code, you have to understand the way these -files are produced and setup some additional tooling. The shortest possible -procedure resembling the below text can be found in .travis.yml (our Travis CI -configuration actually regenerates those files upon every build). +it resides in Git, is actually generated from (a soft fork of) the official +Matrix Swagger/OpenAPI definition files. If you're unhappy with something in +these directories and want to improve the code, you have to understand the way +these files are produced and setup some additional tooling. The shortest +possible procedure resembling the below text can be found in .travis.yml +(our Travis CI configuration actually regenerates those files upon every build). The generating sequence only works with CMake atm; patches to enable it with qmake are (you guessed it) very welcome. @@ -209,16 +210,23 @@ Instead of relying on the event structure definition in the OpenAPI files, `gtad ### Library API and doc-comments -Whenever you add a new call to the library API that you expect to be used from client code, you must supply a proper doc-comment along with the call. Doxygen (with backslashes) style is preferred. You can find that some parts of the code still use JavaDoc (with @'s) style; feel free to replace it with Doxygen backslashes and if that bothers you. Some parts are not even documented; adding doc-comments to them is highly encouraged. +Whenever you add a new call to the library API that you expect to be used from client code, you must supply a proper doc-comment along with the call. Doxygen (with backslashes) style is preferred. You can find that some parts of the code still use JavaDoc (with @'s) style; feel free to replace it with Doxygen backslashes if that bothers you. Some parts are not even documented; adding doc-comments to them is highly encouraged. -Calls, data structures and other symbols not intended for use by clients should _not_ be exposed in (public) .h files, unless they are necessary to declare other public symbols. In particular, this involves private members (functions, typedefs, or variables) in public classes; use pimpl idiom to hide implementation details as much as possible. +Calls, data structures and other symbols not intended for use by clients +should _not_ be exposed in (public) .h files, unless they are necessary +to declare other public symbols. In particular, this involves private members +(functions, typedefs, or variables) in public classes; use pimpl idiom to hide +implementation details as much as possible. `_impl` namespace is reserved for +definitions that should not be used by clients and are not covered by +API guarantees. Note: As of now, all header files of libQMatrixClient are considered public; this may change eventually. ### Qt-flavoured C++ -This is our primary language. We don't have a particular code style _as of yet_ -but some rules-of-thumb are below: +This is our primary language. A particular code style is not enforced _yet_ but +[the PR imposing the common code style](https://github.com/QMatrixClient/libqmatrixclient/pull/295) +is planned to arrive in version 0.6. * 4-space indents, no tabs, no trailing spaces, no last empty lines. If you spot the code abusing these - we'll thank you for fixing it. * Prefer keeping lines within 80 characters. @@ -260,9 +268,12 @@ but some rules-of-thumb are below: ### Automated tests -There's no testing framework as of now; either Catch or Qt Test or both will be used eventually. However, as a stopgap measure, qmc-example is used for automated end-to-end testing. +There's no testing framework as of now; either Catch or Qt Test or both will +be used eventually. -Any significant addition to the library API should be accompanied by a respective test in qmc-example. To add a test you should: +As a stopgap measure, qmc-example is used for automated functional testing. +Therefore, any significant addition to the library API should be accompanied +by a respective test in qmc-example. To add a test you should: - Add a new private slot to the `QMCTest` class. - Add to the beginning of the slot the line `running.push_back("Test name");`. - Add test logic to the slot, using `QMC_CHECK` macro to assert the test outcome. ALL (even failing) branches should conclude with a QMC_CHECK invocation, unless you intend to have a "DID NOT FINISH" message in the logs under certain conditions. @@ -310,7 +321,7 @@ In Qt Creator, the following line can be used with the Clang code model ### Continuous Integration -We use Travis CI to check buildability and smoke-testing on Linux (GCC, Clang) and MacOS (Clang), and AppVeyor CI to build on Windows (MSVC). Every PR will go through these, and you'll see the traffic lights from them on the PR page. Failure on any platform will most likely entail a request to you for a fix before merging a PR. +We use Travis CI to check buildability and smoke-testing on Linux (GCC, Clang) and MacOS (Clang), and AppVeyor CI to build on Windows (MSVC). Every PR will go through these, and you'll see the traffic lights from them on the PR page. If your PR fails on any platform double-check that it's not your code causing it - and fix it if it is. ### Other tools @@ -323,7 +334,7 @@ Qt Creator, in addition, knows about clazy, an even deeper Qt-aware static analysis tool. Even level 1 clazy eats away CPU but produces some very relevant and unobvious notices, such as possible unintended copying of a Qt container, or unguarded null pointers. You can use this time to time (see Analyze menu in -Qt Creator) instead of loading your machine with deep runtime analysis. +Qt Creator) instead of hogging your machine with deep analysis as you type. ## Git commit messages @@ -343,7 +354,7 @@ When writing git commit messages, try to follow the guidelines in C++ is unfortunately not very coherent about SDK/package management, and we try to keep building the library as easy as possible. Because of that we are very conservative about adding dependencies to libQMatrixClient. That relates to additional Qt components and even more to other libraries. Fortunately, even the Qt components now in use (Qt Core and Network) are very feature-rich and provide plenty of ready-made stuff. -Regardless of the above paragraph (and as mentioned earlier in the text), we're now looking at possible options for automated testing, so PRs onboarding a test framework will be considered with much gratitude. +Regardless of the above paragraph (and as mentioned earlier in the text), we're now looking at possible options for futures and automated testing, so PRs onboarding those will be considered with much gratitude. Some cases need additional explanation: * Before rolling out your own super-optimised container or algorithm written @@ -367,4 +378,4 @@ Some cases need additional explanation: ## Attribution -This text is largely based on CONTRIBUTING.md from CII Best Practices Badge project, which is a collective work of its contributors (many thanks!). The text itself is licensed under CC-BY-4.0. +This text is based on CONTRIBUTING.md from CII Best Practices Badge project, which is a collective work of its contributors (many thanks!). The text itself is licensed under CC-BY-4.0. diff --git a/README.md b/README.md index ab275a35..857543e1 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,19 @@ You can find authors of libQMatrixClient in the Matrix room: [#qmatrixclient:mat You can also file issues at [the project's issue tracker](https://github.com/QMatrixClient/libqmatrixclient/issues). If you have what looks like a security issue, please see respective instructions in CONTRIBUTING.md. ## Building and usage -So far the library is typically used as a git submodule of another project (such as Quaternion); however it can be built separately (either as a static or as a dynamic library). As of version 0.2, the library can be installed and CMake package config files are provided; projects can use `find_package(QMatrixClient)` to setup their code with the installed library files. PRs to enable the same for qmake are most welcome. - -The source code is hosted at GitHub: https://github.com/QMatrixClient/libqmatrixclient - checking out a certain commit or tag from GitHub (rather than downloading the archive) is the recommended way for one-off building. If you want to hack on the library as a part of another project (e.g. you are working on Quaternion but need to do some changes to the library code), you're advised to make a recursive check out of that project (in this case, Quaternion) and update the library submodule to its master branch. +So far the library is typically used as a git submodule of another project +(such as Quaternion); however it can be built separately (either as a static or +as a dynamic library). After installing the library the CMake package becomes +available for `find_package(QMatrixClient)` to setup the client code with +the installed library files. PRs to enable the same for qmake are most welcome. + +[The source code is hosted at GitHub](https://github.com/QMatrixClient/libqmatrixclient) - +checking out a certain commit or tag (rather than downloading the archive) is +the recommended way for one-off building. If you want to hack on the library +as a part of another project (e.g. you are working on Quaternion but need +to do some changes to the library code), you're advised to make a recursive +check out of that project (in this case, Quaternion) and update +the library submodule to its master branch. Tags consisting of digits and periods represent released versions; tags ending with `-betaN` or `-rcN` mark pre-releases. If/when packaging pre-releases, it is advised to replace a dash with a tilde. @@ -28,6 +38,7 @@ Tags consisting of digits and periods represent released versions; tags ending w - For Ubuntu flavours - zesty or later (or a derivative) is good enough out of the box; older ones will need PPAs at least for a newer Qt; in particular, if you have xenial you're advised to add Kubuntu Backports PPA for it - a Git client to check out this repo - Qt 5 (either Open Source or Commercial), version 5.6 or higher + (5.9 or higher is strongly recommended) - a build configuration tool: - CMake (from your package management system or [the official website](https://cmake.org/download/)) - or qmake (comes with Qt) -- cgit v1.2.3 From c36341512560cedf063279848fa43c44b146161b 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(-) diff --git a/lib/room.cpp b/lib/room.cpp index f031273a..e363767a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1216,7 +1216,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) @@ -2237,6 +2236,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); } @@ -2264,7 +2265,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 From 5fcbbe4939f5bd9c64fc399d0d7ec43044f41c0c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Apr 2019 20:55:29 +0900 Subject: CMakeLists.txt: use project() to set the version --- CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e4642f3a..ca597469 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.1) -project(qmatrixclient CXX) +set(API_VERSION "0.6") +project(qmatrixclient VERSION "${API_VERSION}.0" LANGUAGES CXX) option(QMATRIXCLIENT_INSTALL_EXAMPLE "install qmc-example application" ON) @@ -58,6 +59,7 @@ message( STATUS ) message( STATUS "=============================================================================" ) message( STATUS " libqmatrixclient Build Information" ) message( STATUS "=============================================================================" ) +message( STATUS "Version: ${PROJECT_VERSION}, API version: ${API_VERSION}") if (CMAKE_BUILD_TYPE) message( STATUS "Build type: ${CMAKE_BUILD_TYPE}") endif(CMAKE_BUILD_TYPE) @@ -145,9 +147,7 @@ add_library(QMatrixClient ${libqmatrixclient_SRCS} ${libqmatrixclient_job_SRCS} ${libqmatrixclient_csdef_SRCS} ${libqmatrixclient_cswellknown_SRCS} ${libqmatrixclient_asdef_SRCS} ${libqmatrixclient_isdef_SRCS}) -set(API_VERSION "0.6") -set(FULL_VERSION "${API_VERSION}.0") -set_property(TARGET QMatrixClient PROPERTY VERSION "${FULL_VERSION}") +set_property(TARGET QMatrixClient PROPERTY VERSION "${PROJECT_VERSION}") set_property(TARGET QMatrixClient PROPERTY SOVERSION ${API_VERSION} ) set_property(TARGET QMatrixClient PROPERTY INTERFACE_QMatrixClient_MAJOR_VERSION ${API_VERSION}) @@ -179,7 +179,6 @@ include(CMakePackageConfigHelpers) # Maybe consider jumping the gun and releasing 1.0, as semver advises? write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/QMatrixClient/QMatrixClientConfigVersion.cmake" - VERSION ${FULL_VERSION} COMPATIBILITY SameMajorVersion ) -- cgit v1.2.3 From af55d9a0f23ed48c7dcec26efe6b01fb44a8c4fc Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Apr 2019 21:48:21 +0900 Subject: .gitignore: ignore project files of Qt Creator pre-releases as well --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5d8126f4..944c894d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ build .kdev4 # Qt Creator project file -*.user +*.user* # qmake derivatives Makefile* -- cgit v1.2.3 From a6df4183d6da324f2c2329f21d732071b3337cb8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 15 Apr 2019 20:29:00 +0900 Subject: BaseJob: fix a possible crash upon logout See https://github.com/QMatrixClient/Quaternion/issues/566 for details. --- lib/jobs/basejob.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index f521cc4b..a79d0e03 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -600,10 +600,25 @@ QUrl BaseJob::errorUrl() const void BaseJob::setStatus(Status s) { + // The crash that led to this code has been reported in + // https://github.com/QMatrixClient/Quaternion/issues/566 - basically, + // when cleaning up childrent of a deleted Connection, there's a chance + // of pending jobs being abandoned, calling setStatus(Abandoned). + // There's nothing wrong with this; however, the safety check for + // cleartext access tokens below uses d->connection - which is a dangling + // pointer. + // To alleviate that, a stricter condition is applied, that for Abandoned + // and to-be-Abandoned jobs the status message will be disregarded entirely. + // For 0.6 we might rectify the situation by making d->connection + // a QPointer<> (and derive ConnectionData from QObject, respectively). + if (d->status.code == Abandoned || s.code == Abandoned) + s.message.clear(); + if (d->status == s) return; - if (d->connection && !d->connection->accessToken().isEmpty()) + if (!s.message.isEmpty() + && d->connection && !d->connection->accessToken().isEmpty()) s.message.replace(d->connection->accessToken(), "(REDACTED)"); if (!s.good()) qCWarning(d->logCat) << this << "status" << s; -- cgit v1.2.3 From 8a3bb2d5b27824890482fa7185bbc1fcda9dd49c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 16 Apr 2019 16:21:53 +0900 Subject: BaseJob: preserve the calculated error code if JSON error code is unknown Resetting the code to IncorrectRequestError has been a part of the cause for the incorrect Quaternion behaviour on expired tokens. --- lib/jobs/basejob.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index a79d0e03..0d9b9f10 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -334,8 +334,7 @@ void BaseJob::gotReply() tr("Requested room version: %1") .arg(json.value("room_version").toString()); } else if (!json.isEmpty()) // Not localisable on the client side - setStatus(IncorrectRequestError, - json.value("error"_ls).toString()); + setStatus(d->status.code, json.value("error"_ls).toString()); } } -- cgit v1.2.3 From 1103c1fa00d99c4e090f76fd07a384032f79f0c1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 16 Apr 2019 16:22:54 +0900 Subject: Connection::logout: ignore ContentAccessError Closes #316. --- lib/connection.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 5ed72616..fa358e17 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -321,11 +321,14 @@ void Connection::checkAndConnect(const QString& userId, void Connection::logout() { auto job = callApi(); - connect( job, &LogoutJob::success, this, [this] { - stopSync(); - d->data->setToken({}); - emit stateChanged(); - emit loggedOut(); + connect( job, &LogoutJob::finished, this, [job,this] { + if (job->status().good() || job->error() == BaseJob::ContentAccessError) + { + stopSync(); + d->data->setToken({}); + emit stateChanged(); + emit loggedOut(); + } }); } -- cgit v1.2.3 From 05c9984fd1d369397fbf3c9cad67413906e817cc Mon Sep 17 00:00:00 2001 From: Black Hat Date: Wed, 24 Apr 2019 21:18:24 +0800 Subject: Make ignore-user related method Q_INVOKABLE. --- lib/connection.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/connection.h b/lib/connection.h index ea5be51a..42e678ff 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -219,10 +219,10 @@ namespace QMatrixClient QList directChatUsers(const Room* room) const; /** Check whether a particular user is in the ignore list */ - bool isIgnored(const User* user) const; + Q_INVOKABLE bool isIgnored(const User* user) const; /** Get the whole list of ignored users */ - IgnoredUsersList ignoredUsers() const; + Q_INVOKABLE IgnoredUsersList ignoredUsers() const; /** Add the user to the ignore list * The change signal is emitted synchronously, without waiting @@ -230,14 +230,14 @@ namespace QMatrixClient * * \sa ignoredUsersListChanged */ - void addToIgnoredUsers(const User* user); + Q_INVOKABLE void addToIgnoredUsers(const User* user); /** Remove the user from the ignore list */ /** Similar to adding, the change signal is emitted synchronously. * * \sa ignoredUsersListChanged */ - void removeFromIgnoredUsers(const User* user); + Q_INVOKABLE void removeFromIgnoredUsers(const User* user); /** Get the full list of users known to this account */ QMap users() const; -- cgit v1.2.3 From 0378e830c40a1d7302a8108d460940b6cd15201e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 3 May 2019 22:56:42 +0900 Subject: Connection::stateCacheDir Same as stateCachePath but returns QDir. --- lib/connection.cpp | 16 +++++++++++++--- lib/connection.h | 28 ++++++++++++++++++---------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index fa358e17..d75d8e56 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -133,6 +133,10 @@ class Connection::Private packAndSendAccountData( makeEvent(std::forward(content))); } + QString topLevelStatePath() const + { + return q->stateCacheDir().filePath("state.json"); + } }; Connection::Connection(const QUrl& server, QObject* parent) @@ -1232,7 +1236,8 @@ void Connection::saveRoomState(Room* r) const if (!d->cacheState) return; - QFile outRoomFile { stateCachePath() % SyncData::fileNameForRoom(r->id()) }; + QFile outRoomFile { + stateCacheDir().filePath(SyncData::fileNameForRoom(r->id())) }; if (outRoomFile.open(QFile::WriteOnly)) { QJsonDocument json { r->toJson() }; @@ -1253,7 +1258,7 @@ void Connection::saveState() const QElapsedTimer et; et.start(); - QFile outFile { stateCachePath() % "state.json" }; + QFile outFile { d->topLevelStatePath() }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(MAIN) << "Error opening" << outFile.fileName() @@ -1313,7 +1318,7 @@ void Connection::loadState() QElapsedTimer et; et.start(); - SyncData sync { stateCachePath() % "state.json" }; + SyncData sync { d->topLevelStatePath() }; if (sync.nextBatch().isEmpty()) // No token means no cache by definition return; @@ -1330,6 +1335,11 @@ void Connection::loadState() } QString Connection::stateCachePath() const +{ + return stateCacheDir().path() % '/'; +} + +QDir Connection::stateCacheDir() const { auto safeUserId = userId(); safeUserId.replace(':', '_'); diff --git a/lib/connection.h b/lib/connection.h index 42e678ff..2ff27ea6 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -305,8 +306,8 @@ namespace QMatrixClient * Call this before first sync to load from previously saved file. * * \param fromFile A local path to read the state from. Uses QUrl - * to be QML-friendly. Empty parameter means using a path - * defined by stateCachePath(). + * to be QML-friendly. Empty parameter means saving to the directory + * defined by stateCachePath() / stateCacheDir(). */ Q_INVOKABLE void loadState(); /** @@ -315,23 +316,30 @@ namespace QMatrixClient * loadState() on a next run of the client. * * \param toFile A local path to save the state to. Uses QUrl to be - * QML-friendly. Empty parameter means using a path defined by - * stateCachePath(). + * QML-friendly. Empty parameter means saving to the directory + * defined by stateCachePath() / stateCacheDir(). */ Q_INVOKABLE void saveState() const; /// This method saves the current state of a single room. void saveRoomState(Room* r) const; + /// Get the default directory path to save the room state to + /** \sa stateCacheDir */ + Q_INVOKABLE QString stateCachePath() const; + + /// Get the default directory to save the room state to /** - * The default path to store the cached room state, defined as - * follows: + * This function returns the default directory to store the cached + * room state, defined as follows: + * \code * QStandardPaths::writeableLocation(QStandardPaths::CacheLocation) + _safeUserId + "_state.json" - * where `_safeUserId` is userId() with `:` (colon) replaced with - * `_` (underscore) - * /see loadState(), saveState() + * \endcode + * where `_safeUserId` is userId() with `:` (colon) replaced by + * `_` (underscore), as colons are reserved characters on Windows. + * \sa loadState, saveState, stateCachePath */ - Q_INVOKABLE QString stateCachePath() const; + QDir stateCacheDir() const; bool cacheState() const; void setCacheState(bool newValue); -- cgit v1.2.3 From 2d39b1e51e500fa17788630cd0e6a57a451eb475 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 4 May 2019 08:34:18 +0900 Subject: Room::allHistoryLoaded --- lib/room.cpp | 5 +++++ lib/room.h | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/room.cpp b/lib/room.cpp index e363767a..2ce37acc 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -370,6 +370,11 @@ const Room::PendingEvents& Room::pendingEvents() const return d->unsyncedEvents; } +bool Room::allHistoryLoaded() const +{ + return !d->timeline.empty() && is(*d->timeline.front()); +} + QString Room::name() const { return d->getCurrentState()->name(); diff --git a/lib/room.h b/lib/room.h index db4336ea..055da3da 100644 --- a/lib/room.h +++ b/lib/room.h @@ -110,6 +110,7 @@ namespace QMatrixClient Q_PROPERTY(int unreadCount READ unreadCount NOTIFY unreadMessagesChanged) Q_PROPERTY(int highlightCount READ highlightCount NOTIFY highlightCountChanged RESET resetHighlightCount) Q_PROPERTY(int notificationCount READ notificationCount NOTIFY notificationCountChanged RESET resetNotificationCount) + Q_PROPERTY(bool allHistoryLoaded READ allHistoryLoaded NOTIFY addedMessages STORED false) Q_PROPERTY(QStringList tagNames READ tagNames NOTIFY tagsChanged) Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY tagsChanged) Q_PROPERTY(bool isLowPriority READ isLowPriority NOTIFY tagsChanged) @@ -228,6 +229,14 @@ namespace QMatrixClient const Timeline& messageEvents() const; const PendingEvents& pendingEvents() const; + + /// Check whether all historical messages are already loaded + /** + * \return true if the "oldest" event in the timeline is + * a room creation event and there's no further history + * to load; false otherwise + */ + bool allHistoryLoaded() const; /** * A convenience method returning the read marker to the position * before the "oldest" event; same as messageEvents().crend() -- cgit v1.2.3 From 346adee1810109f4b7b14298e55d29a44c076a66 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 13 May 2019 20:41:04 +0900 Subject: prettyPrint(): First linkify, than add more tags Otherwise the linkification gets confused by HTML tags being already there and doesn't linkify what has to be linkified if that occurs at the beginning of the message. --- lib/util.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/util.cpp b/lib/util.cpp index c3e21c8e..0248e521 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -74,12 +74,11 @@ QString QMatrixClient::sanitized(const QString& plainText) QString QMatrixClient::prettyPrint(const QString& plainText) { - auto pt = QStringLiteral("") + - plainText.toHtmlEscaped() + QStringLiteral(""); - pt.replace('\n', QStringLiteral("
")); - + auto pt = plainText.toHtmlEscaped(); linkifyUrls(pt); - return pt; + pt.replace('\n', QStringLiteral("
")); + return QStringLiteral("") + pt + + QStringLiteral(""); } QString QMatrixClient::cacheLocation(const QString& dirName) -- cgit v1.2.3 From 3c253ed025246a34d849d14aac6feaee672d7e63 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 13 May 2019 20:42:50 +0900 Subject: linkifyUrls(): be more conservative in parsing serverparts Closes #321. --- lib/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.cpp b/lib/util.cpp index 0248e521..883db2ea 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -51,7 +51,7 @@ void QMatrixClient::linkifyUrls(QString& htmlEscapedText) // An interim liberal implementation of // https://matrix.org/docs/spec/appendices.html#identifier-grammar static const QRegularExpression MxIdRegExp(QStringLiteral( - R"((^|[^<>/])([!#@][-a-z0-9_=/.]{1,252}:[-.a-z0-9]+))" + R"((^|[^<>/])([!#@][-a-z0-9_=/.]{1,252}:(?:\w|\.|-)+\.\w+(?::\d{1,5})?))" ), RegExpOptions); // NOTE: htmlEscapedText is already HTML-escaped! No literal <,>,&," -- cgit v1.2.3 From 1a034626bcbe064ebe0ada8cdfe1a47f2d82e477 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 13 May 2019 21:43:54 +0900 Subject: sanitized(): add object replacement character (0xfffc) to the blacklist --- lib/util.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/util.cpp b/lib/util.cpp index 883db2ea..4e17d2f9 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -57,18 +57,19 @@ void QMatrixClient::linkifyUrls(QString& htmlEscapedText) // NOTE: htmlEscapedText is already HTML-escaped! No literal <,>,&," htmlEscapedText.replace(EmailAddressRegExp, - QStringLiteral(R"(\1\2)")); + QStringLiteral(R"(\1\2)")); htmlEscapedText.replace(FullUrlRegExp, - QStringLiteral(R"(\1)")); + QStringLiteral(R"(\1)")); htmlEscapedText.replace(MxIdRegExp, - QStringLiteral(R"(\1\2)")); + QStringLiteral(R"(\1\2)")); } QString QMatrixClient::sanitized(const QString& plainText) { auto text = plainText; - text.remove(QChar(0x202e)); - text.remove(QChar(0x202d)); + text.remove(QChar(0x202e)); // RLO + text.remove(QChar(0x202d)); // LRO + text.remove(QChar(0xfffc)); // Object replacement character return text; } -- cgit v1.2.3 From 74fa9bc64128d88939259ccb2ba4dca51571559a Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Wed, 6 Mar 2019 22:13:05 +0300 Subject: Provide a colour code for the user Contributes to #296 --- lib/user.cpp | 28 +++++++++++++++++++++++++++- lib/user.h | 11 +++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/user.cpp b/lib/user.cpp index eec41957..b13f98b4 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -33,6 +33,9 @@ #include #include +#include +#include + #include using namespace QMatrixClient; @@ -47,8 +50,21 @@ class User::Private return Avatar(move(url)); } + qreal makeHueF(QString userId) + { + QByteArray hash = QCryptographicHash::hash(userId.toUtf8(), + QCryptographicHash::Sha1); + QDataStream dataStream(qToLittleEndian(hash).left(2)); + dataStream.setByteOrder(QDataStream::LittleEndian); + quint16 hashValue; + dataStream >> hashValue; + qreal hueF = static_cast(hashValue)/std::numeric_limits::max(); + Q_ASSERT((0 <= hueF) && (hueF <= 1)); + return hueF; + } + Private(QString userId, Connection* connection) - : userId(move(userId)), connection(connection) + : userId(move(userId)), connection(connection), hueF(makeHueF(userId)) { } QString userId; @@ -57,6 +73,7 @@ class User::Private QString bridged; QString mostUsedName; QMultiHash otherNames; + qreal hueF; Avatar mostUsedAvatar { makeAvatar({}) }; std::vector otherAvatars; auto otherAvatar(QUrl url) @@ -219,6 +236,11 @@ bool User::isGuest() const return *it == ':'; } +int User::hue() const +{ + return int(hueF()*359); +} + QString User::name(const Room* room) const { return d->nameForRoom(room); @@ -424,3 +446,7 @@ void User::processEvent(const RoomMemberEvent& event, const Room* room) updateAvatarUrl(event.avatarUrl(), d->avatarUrlForRoom(room), room); } } + +qreal User::hueF() const { + return d->hueF; +} diff --git a/lib/user.h b/lib/user.h index 0023b44a..af1abfa2 100644 --- a/lib/user.h +++ b/lib/user.h @@ -33,6 +33,8 @@ namespace QMatrixClient Q_OBJECT Q_PROPERTY(QString id READ id CONSTANT) Q_PROPERTY(bool isGuest READ isGuest CONSTANT) + Q_PROPERTY(int hue READ hue CONSTANT) + Q_PROPERTY(qreal hueF READ hueF CONSTANT) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString displayName READ displayname NOTIFY nameChanged STORED false) Q_PROPERTY(QString fullName READ fullName NOTIFY nameChanged STORED false) @@ -95,6 +97,15 @@ namespace QMatrixClient */ bool isGuest() const; + /** Hue color component of this user based on id. + * The implementation is based on XEP-0392: + * https://xmpp.org/extensions/xep-0392.html + * Naming and ranges are the same as QColor's hue methods: + * https://doc.qt.io/qt-5/qcolor.html#integer-vs-floating-point-precision + */ + int hue() const; + qreal hueF() const; + const Avatar& avatarObject(const Room* room = nullptr) const; Q_INVOKABLE QImage avatar(int dimension, const Room* room = nullptr); Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight, -- cgit v1.2.3 From d6f39dcb0de69322479f287514a8c36afcb3fe7b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 14 May 2019 15:29:56 +0900 Subject: User::Private::makeHueF: Fix trying to use the moved value --- lib/user.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/user.cpp b/lib/user.cpp index fdb82a38..7f3f11f6 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -50,21 +50,23 @@ class User::Private return Avatar(move(url)); } - qreal makeHueF(QString userId) + qreal makeHueF() { + Q_ASSERT(!userId.isEmpty()); QByteArray hash = QCryptographicHash::hash(userId.toUtf8(), QCryptographicHash::Sha1); QDataStream dataStream(qToLittleEndian(hash).left(2)); dataStream.setByteOrder(QDataStream::LittleEndian); quint16 hashValue; dataStream >> hashValue; - qreal hueF = static_cast(hashValue)/std::numeric_limits::max(); + const auto hueF = + qreal(hashValue)/std::numeric_limits::max(); Q_ASSERT((0 <= hueF) && (hueF <= 1)); return hueF; } Private(QString userId, Connection* connection) - : userId(move(userId)), connection(connection), hueF(makeHueF(userId)) + : userId(move(userId)), connection(connection), hueF(makeHueF()) { } QString userId; -- cgit v1.2.3 From 44ce399ab59204864b8c0c70d9b92e73e7e0135a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 17 May 2019 21:19:32 +0900 Subject: Minor code refactoring --- lib/events/accountdataevents.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index a99d85ac..ffee5ba6 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -85,7 +85,7 @@ namespace QMatrixClient toJson(std::move(content)) } }) \ { } \ auto _ContentKey() const \ - { return fromJson(contentJson()[#_ContentKey##_ls]); } \ + { return content(#_ContentKey##_ls); } \ }; \ REGISTER_EVENT_TYPE(_Name) \ // End of macro -- cgit v1.2.3 From 9ca07298008d891250ffc79771bc5ef8c97950cb Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 17 May 2019 21:20:30 +0900 Subject: Drop unneeded #include --- lib/avatar.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/avatar.cpp b/lib/avatar.cpp index 9279ef9d..0d849eae 100644 --- a/lib/avatar.cpp +++ b/lib/avatar.cpp @@ -1,5 +1,3 @@ -#include - /****************************************************************************** * Copyright (C) 2017 Kitsune Ral * -- cgit v1.2.3 From 7374cef74ab92dd2165831c73db51c7dfa4a3511 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 18 May 2019 21:48:04 +0900 Subject: event.h: add doc-comments; deprecate ptrCast() --- lib/events/event.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/events/event.h b/lib/events/event.h index d7ac4292..b7bbd83e 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -32,19 +32,23 @@ namespace QMatrixClient template using event_ptr_tt = std::unique_ptr; + /// Unwrap a plain pointer from a smart pointer template - inline EventT* rawPtr(const event_ptr_tt& ptr) // unwrap + inline EventT* rawPtr(const event_ptr_tt& ptr) { return ptr.get(); } + /// Unwrap a plain pointer and downcast it to the specified type template inline TargetEventT* weakPtrCast(const event_ptr_tt& ptr) { return static_cast(rawPtr(ptr)); } + /// Re-wrap a smart pointer to base into a smart pointer to derived template + [[deprecated("Consider using eventCast() or visit() instead")]] inline event_ptr_tt ptrCast(event_ptr_tt&& ptr) { return unique_ptr_cast(ptr); -- cgit v1.2.3 From 1033f3b4249b8b0224ba725b220338e6c9621084 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 18 May 2019 21:50:08 +0900 Subject: Connection::onSyncSuccess(): fix using after move() Also rewrite the account data piece with visit(). --- lib/connection.cpp | 116 +++++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index d75d8e56..cd0d96f7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -411,63 +411,67 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { // Let UI update itself after updating each room QCoreApplication::processEvents(); } - for (auto&& accountEvent: data.takeAccountData()) - { - if (is(*accountEvent)) - { - const auto usersToDCs = ptrCast(move(accountEvent)) - ->usersToDirectChats(); - DirectChatsMap removals = - erase_if(d->directChats, [&usersToDCs] (auto it) { - return !usersToDCs.contains(it.key()->id(), it.value()); - }); - erase_if(d->directChatUsers, [&usersToDCs] (auto it) { - return !usersToDCs.contains(it.value()->id(), it.key()); - }); - if (MAIN().isDebugEnabled()) - for (auto it = removals.begin(); it != removals.end(); ++it) - qCDebug(MAIN) << it.value() - << "is no more a direct chat with" << it.key()->id(); - - DirectChatsMap additions; - for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it) - { - if (auto* u = user(it.key())) - { - if (!d->directChats.contains(u, it.value())) - { - Q_ASSERT(!d->directChatUsers.contains(it.value(), u)); - additions.insert(u, it.value()); - d->directChats.insert(u, it.value()); - d->directChatUsers.insert(it.value(), u); - qCDebug(MAIN) << "Marked room" << it.value() + // After running this loop, the account data events not saved in + // d->accountData (see the end of the loop body) are auto-cleaned away + for (auto& eventPtr: data.takeAccountData()) + visit(*eventPtr, + [this](const DirectChatEvent& dce) { + const auto& usersToDCs = dce.usersToDirectChats(); + DirectChatsMap removals = + erase_if(d->directChats, [&usersToDCs](auto it) { + return !usersToDCs.contains(it.key()->id(), + it.value()); + }); + erase_if(d->directChatUsers, [&usersToDCs](auto it) { + return !usersToDCs.contains(it.value()->id(), it.key()); + }); + if (MAIN().isDebugEnabled()) + for (auto it = removals.begin(); it != removals.end(); + ++it) + qCDebug(MAIN) << it.value() + << "is no more a direct chat with" + << it.key()->id(); + + DirectChatsMap additions; + for (auto it = usersToDCs.begin(); it != usersToDCs.end(); + ++it) { + if (auto* u = user(it.key())) { + if (!d->directChats.contains(u, it.value())) { + Q_ASSERT(!d->directChatUsers.contains(it.value(), + u)); + additions.insert(u, it.value()); + d->directChats.insert(u, it.value()); + d->directChatUsers.insert(it.value(), u); + qCDebug(MAIN) + << "Marked room" << it.value() << "as a direct chat with" << u->id(); - } - } else - qCWarning(MAIN) - << "Couldn't get a user object for" << it.key(); - } - if (!additions.isEmpty() || !removals.isEmpty()) - emit directChatsListChanged(additions, removals); - - continue; - } - if (is(*accountEvent)) - qCDebug(MAIN) << "Users ignored by" << d->userId << "updated:" - << QStringList::fromSet(ignoredUsers()).join(','); - - auto& currentData = d->accountData[accountEvent->matrixType()]; - // A polymorphic event-specific comparison might be a bit more - // efficient; maaybe do it another day - if (!currentData || - currentData->contentJson() != accountEvent->contentJson()) - { - currentData = std::move(accountEvent); - qCDebug(MAIN) << "Updated account data of type" - << currentData->matrixType(); - emit accountDataChanged(currentData->matrixType()); - } - } + } + } else + qCWarning(MAIN) << "Couldn't get a user object for" + << it.key(); + } + if (!additions.isEmpty() || !removals.isEmpty()) + emit directChatsListChanged(additions, removals); + }, + // catch-all, passing eventPtr for a possible take-over + [this, &eventPtr](const Event& accountEvent) { + if (is(accountEvent)) + qCDebug(MAIN) + << "Users ignored by" << d->userId << "updated:" + << QStringList::fromSet(ignoredUsers()).join(','); + + auto& currentData = d->accountData[accountEvent.matrixType()]; + // A polymorphic event-specific comparison might be a bit more + // efficient; maaybe do it another day + if (!currentData + || currentData->contentJson() + != accountEvent.contentJson()) { + currentData = std::move(eventPtr); + qCDebug(MAIN) << "Updated account data of type" + << currentData->matrixType(); + emit accountDataChanged(currentData->matrixType()); + } + }); } void Connection::stopSync() -- cgit v1.2.3 From bb6d33182269425d98f7aa4e22aa5478cff57550 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 19 May 2019 08:47:23 +0900 Subject: Move a doc-comment to its place --- lib/connection.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/connection.h b/lib/connection.h index 2ff27ea6..018c0459 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -96,9 +96,6 @@ namespace QMatrixClient class Connection: public QObject { Q_OBJECT - /** Whether or not the rooms state should be cached locally - * \sa loadState(), saveState() - */ Q_PROPERTY(User* localUser READ user NOTIFY stateChanged) Q_PROPERTY(QString localUserId READ userId NOTIFY stateChanged) Q_PROPERTY(QString deviceId READ deviceId NOTIFY stateChanged) @@ -341,6 +338,9 @@ namespace QMatrixClient */ QDir stateCacheDir() const; + /** Whether or not the rooms state should be cached locally + * \sa loadState(), saveState() + */ bool cacheState() const; void setCacheState(bool newValue); -- cgit v1.2.3 From 626b2711a49e581babafd179ce362a1c88db8b85 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 19 May 2019 08:54:00 +0900 Subject: Connection: use QScopedPointer instead of unique_ptr While theoretically less robust (no equivalent of make_unique), QScopedPointer is navigable in Qt Creator debug views, unlike unique_ptr. Of course this will eventually be fixed; but given that inability to create an owning pointer object means sure abnormal termination of our code shortly afterwards, having make_unique in this particular case doesn't help in any way at all; so unique_ptr has zero advantages over QScopedPointer in this setting. --- lib/connection.cpp | 3 ++- lib/connection.h | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index cd0d96f7..e3da3c16 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -141,7 +141,7 @@ class Connection::Private Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent) - , d(std::make_unique(std::make_unique(server))) + , d(new Private(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line } @@ -294,6 +294,7 @@ void Connection::Private::connectWithToken(const QString& user, q->user(); // Creates a User object for the local user data->setToken(accessToken.toLatin1()); data->setDeviceId(deviceId); + q->setObjectName(userId % '/' % deviceId); qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); diff --git a/lib/connection.h b/lib/connection.h index 018c0459..cc2feed8 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -29,7 +29,6 @@ #include #include -#include namespace QMatrixClient { @@ -744,7 +743,7 @@ namespace QMatrixClient private: class Private; - std::unique_ptr d; + QScopedPointer d; /** * A single entry for functions that need to check whether the -- cgit v1.2.3 From 4ae7fe50e5aae28dee3d207b0ca02207c46bb781 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 19 May 2019 15:35:52 +0900 Subject: Improve wording in a comment --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index e3da3c16..98cd1ef1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -80,8 +80,8 @@ class Connection::Private std::unique_ptr data; // A complex key below is a pair of room name and whether its // state is Invited. The spec mandates to keep Invited room state - // separately so we should, e.g., keep objects for Invite and - // Leave state of the same room. + // separately; specifically, we should keep objects for Invite and + // Leave state of the same room if the two happen to co-exist. QHash, Room*> roomMap; // Mapping from aliases to room ids, as per the last sync QHash roomAliasMap; -- cgit v1.2.3 From 89e0cec42fe9538cbd170a9dc3fcf6dd18b5ac02 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 20 May 2019 07:46:03 +0900 Subject: Connection: Fix a race condition in direct chats handling upon initial sync Closes #323. --- lib/connection.cpp | 129 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 54 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 98cd1ef1..a7eae30f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -73,8 +73,7 @@ class Connection::Private : data(move(connection)) { } Q_DISABLE_COPY(Private) - Private(Private&&) = delete; - Private operator=(Private&&) = delete; + DISABLE_MOVE(Private) Connection* q = nullptr; std::unique_ptr data; @@ -91,6 +90,10 @@ class Connection::Private QMap userMap; DirectChatsMap directChats; DirectChatUsersMap directChatUsers; + // The below two variables track local changes between sync completions. + // See also: https://github.com/QMatrixClient/libqmatrixclient/wiki/Handling-direct-chat-events + DirectChatsMap dcLocalAdditions; + DirectChatsMap dcLocalRemovals; std::unordered_map accountData; QString userId; int syncLoopTimeout = -1; @@ -107,8 +110,6 @@ class Connection::Private void connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId); - void broadcastDirectChatUpdates(const DirectChatsMap& additions, - const DirectChatsMap& removals); template EventT* unpackAccountData() const @@ -378,6 +379,20 @@ void Connection::syncLoop(int timeout) syncLoopIteration(); // initial sync to start the loop } +QJsonObject toJson(const Connection::DirectChatsMap& directChats) +{ + QJsonObject json; + for (auto it = directChats.begin(); it != directChats.end();) + { + QJsonArray roomIds; + const auto* user = it.key(); + for (; it != directChats.end() && it.key() == user; ++it) + roomIds.append(*it); + json.insert(user->id(), roomIds); + } + return json; +} + void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { d->data->setLastEvent(data.nextBatch()); for (auto&& roomData: data.takeRoomData()) @@ -414,33 +429,43 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { } // After running this loop, the account data events not saved in // d->accountData (see the end of the loop body) are auto-cleaned away - for (auto& eventPtr: data.takeAccountData()) + for (auto& eventPtr : data.takeAccountData()) + { visit(*eventPtr, [this](const DirectChatEvent& dce) { + // See https://github.com/QMatrixClient/libqmatrixclient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); - DirectChatsMap removals = - erase_if(d->directChats, [&usersToDCs](auto it) { - return !usersToDCs.contains(it.key()->id(), - it.value()); - }); - erase_if(d->directChatUsers, [&usersToDCs](auto it) { - return !usersToDCs.contains(it.value()->id(), it.key()); + DirectChatsMap remoteRemovals = + erase_if(d->directChats, [&usersToDCs, this](auto it) { + return !(usersToDCs.contains(it.key()->id(), it.value()) + || d->dcLocalAdditions.contains(it.key(), + it.value())); + }); + erase_if(d->directChatUsers, [&remoteRemovals](auto it) { + return remoteRemovals.contains(it.value(), it.key()); + }); + // Remove from dcLocalRemovals what the server already has. + erase_if(d->dcLocalRemovals, [&remoteRemovals](auto it) { + return remoteRemovals.contains(it.key(), it.value()); }); if (MAIN().isDebugEnabled()) - for (auto it = removals.begin(); it != removals.end(); - ++it) - qCDebug(MAIN) << it.value() - << "is no more a direct chat with" - << it.key()->id(); - - DirectChatsMap additions; + for (auto it = remoteRemovals.begin(); + it != remoteRemovals.end(); ++it) { + qCDebug(MAIN) + << it.value() << "is no more a direct chat with" + << it.key()->id(); + } + + DirectChatsMap remoteAdditions; for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it) { if (auto* u = user(it.key())) { - if (!d->directChats.contains(u, it.value())) { - Q_ASSERT(!d->directChatUsers.contains(it.value(), - u)); - additions.insert(u, it.value()); + if (!d->directChats.contains(u, it.value()) + && !d->dcLocalRemovals.contains(u, it.value())) + { + Q_ASSERT( + !d->directChatUsers.contains(it.value(), u)); + remoteAdditions.insert(u, it.value()); d->directChats.insert(u, it.value()); d->directChatUsers.insert(it.value(), u); qCDebug(MAIN) @@ -451,8 +476,12 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { qCWarning(MAIN) << "Couldn't get a user object for" << it.key(); } - if (!additions.isEmpty() || !removals.isEmpty()) - emit directChatsListChanged(additions, removals); + // Remove from dcLocalAdditions what the server already has. + erase_if(d->dcLocalAdditions, [&remoteAdditions](auto it) { + return remoteAdditions.contains(it.key(), it.value()); + }); + if (!remoteAdditions.isEmpty() || !remoteRemovals.isEmpty()) + emit directChatsListChanged(remoteAdditions, remoteRemovals); }, // catch-all, passing eventPtr for a possible take-over [this, &eventPtr](const Event& accountEvent) { @@ -473,6 +502,16 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { emit accountDataChanged(currentData->matrixType()); } }); + } + if (!d->dcLocalAdditions.isEmpty() || !d->dcLocalRemovals.isEmpty()) { + qDebug(MAIN) << "Sending updated direct chats to the server:" + << d->dcLocalRemovals.size() << "removal(s)," + << d->dcLocalAdditions.size() << "addition(s)"; + callApi(d->userId, QStringLiteral("m.direct"), + toJson(d->directChats)); + d->dcLocalAdditions.clear(); + d->dcLocalRemovals.clear(); + } } void Connection::stopSync() @@ -667,8 +706,8 @@ void Connection::doInDirectChat(User* u, { Q_ASSERT(u); const auto& userId = u->id(); - // There can be more than one DC; find the first valid, and delete invalid - // (left/forgotten) ones along the way. + // There can be more than one DC; find the first valid (existing and + // not left), and delete inexistent (forgotten?) ones along the way. DirectChatsMap removals; for (auto it = d->directChats.find(u); it != d->directChats.end() && it.key() == u; ++it) @@ -705,6 +744,8 @@ void Connection::doInDirectChat(User* u, << roomId << "is not valid and will be discarded"; // Postpone actual deletion until we finish iterating d->directChats. removals.insert(it.key(), it.value()); + // Add to the list of updates to send to the server upon the next sync. + d->dcLocalRemovals.insert(it.key(), it.value()); } if (!removals.isEmpty()) { @@ -714,7 +755,7 @@ void Connection::doInDirectChat(User* u, d->directChatUsers.remove(it.value(), const_cast(it.key())); // FIXME } - d->broadcastDirectChatUpdates({}, removals); + emit directChatsListChanged({}, removals); } auto j = createDirectChat(userId); @@ -1015,28 +1056,6 @@ Connection::DirectChatsMap Connection::directChats() const return d->directChats; } -QJsonObject toJson(const Connection::DirectChatsMap& directChats) -{ - QJsonObject json; - for (auto it = directChats.begin(); it != directChats.end();) - { - QJsonArray roomIds; - const auto* user = it.key(); - for (; it != directChats.end() && it.key() == user; ++it) - roomIds.append(*it); - json.insert(user->id(), roomIds); - } - return json; -} - -void Connection::Private::broadcastDirectChatUpdates(const DirectChatsMap& additions, - const DirectChatsMap& removals) -{ - q->callApi(userId, QStringLiteral("m.direct"), - toJson(directChats)); - emit q->directChatsListChanged(additions, removals); -} - void Connection::addToDirectChats(const Room* room, User* user) { Q_ASSERT(room != nullptr && user != nullptr); @@ -1045,8 +1064,8 @@ void Connection::addToDirectChats(const Room* room, User* user) Q_ASSERT(!d->directChatUsers.contains(room->id(), user)); d->directChats.insert(user, room->id()); d->directChatUsers.insert(room->id(), user); - DirectChatsMap additions { { user, room->id() } }; - d->broadcastDirectChatUpdates(additions, {}); + d->dcLocalAdditions.insert(user, room->id()); + emit directChatsListChanged({ { user, room->id() } }, {}); } void Connection::removeFromDirectChats(const QString& roomId, User* user) @@ -1059,15 +1078,17 @@ void Connection::removeFromDirectChats(const QString& roomId, User* user) DirectChatsMap removals; if (user != nullptr) { - removals.insert(user, roomId); d->directChats.remove(user, roomId); d->directChatUsers.remove(roomId, user); + removals.insert(user, roomId); + d->dcLocalRemovals.insert(user, roomId); } else { removals = erase_if(d->directChats, [&roomId] (auto it) { return it.value() == roomId; }); d->directChatUsers.remove(roomId); + d->dcLocalRemovals += removals; } - d->broadcastDirectChatUpdates({}, removals); + emit directChatsListChanged({}, removals); } bool Connection::isDirectChat(const QString& roomId) const -- cgit v1.2.3 From 1040e73ed3d4bdd38d573a6fe7cdf6f177e354d3 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 25 May 2019 20:21:54 +0900 Subject: SECURITY.md Moved over text about vulnerabilities reporting and extended it with more details on supported versions and commitments. --- SECURITY.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..9bb46715 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,32 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| master | :white_check_mark: | +| 0.5.x | :white_check_mark: | +| older | :x: | + +## Reporting a Vulnerability + +If you find a significant vulnerability, or evidence of one, use either of the following contacts: +- send an email to [Kitsune Ral](mailto:Kitsune-Ral@users.sf.net); or +- reach out in Matrix to #kitsune:matrix.org (if you can, switch encryption on). + +In any of these two options, indicate that you have such information (do not share it yet), and we'll tell you the next steps. + +By default, we will give credit to anyone who reports a vulnerability in a responsible way so that we can fix it before public disclosure. +If you want to remain anonymous or pseudonymous instead, please let us know; we will gladly respect your wishes. +If you provide a fix as a PR, you have no way to remain anonymous; you also thereby lay out the vulnerability itself +so this is NOT the right way for undisclosed vulnerabilities, whether or not you want to stay incognito. + +## Timeline and commitments + +Initial reaction to the message about a vulnerability (see above) will be no more than 5 days. From the moment of the private report or +public disclosure (if it hasn't been reported earlier in private) of each vulnerability, we take effort to fix it on priority before +any other issues. In case of vulnerabilities with [CVSS 2.0](https://nvd.nist.gov/cvss.cfm) score of 4.0 and higher the commitment is +to provide a workaround within 30 days and a full fix within 60 days after the specific information on the vulnerability has been +reported to the project by any means (in private or in public). For vulnerabilities with lower score there is no commitment on the timeline, +only prioritisation. The full fix doesn't imply that all software functionality remains accessible (in the worst case +the vulnerable functionality may be disabled or removed to prevent the attack). -- cgit v1.2.3 From 0e2df34b31bc5c3d76ee4cc60f02892600890508 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 25 May 2019 20:23:40 +0900 Subject: SECURITY.md: minor edits --- SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 9bb46715..086258bd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,7 +12,7 @@ If you find a significant vulnerability, or evidence of one, use either of the following contacts: - send an email to [Kitsune Ral](mailto:Kitsune-Ral@users.sf.net); or -- reach out in Matrix to #kitsune:matrix.org (if you can, switch encryption on). +- reach out in Matrix to [@kitsune:matrix.org](https://matrix.to/#/@kitsune:matrix.org) (if you can, switch encryption on). In any of these two options, indicate that you have such information (do not share it yet), and we'll tell you the next steps. @@ -25,7 +25,7 @@ so this is NOT the right way for undisclosed vulnerabilities, whether or not you Initial reaction to the message about a vulnerability (see above) will be no more than 5 days. From the moment of the private report or public disclosure (if it hasn't been reported earlier in private) of each vulnerability, we take effort to fix it on priority before -any other issues. In case of vulnerabilities with [CVSS 2.0](https://nvd.nist.gov/cvss.cfm) score of 4.0 and higher the commitment is +any other issues. In case of vulnerabilities with [CVSS v2](https://nvd.nist.gov/cvss.cfm) score of 4.0 and higher the commitment is to provide a workaround within 30 days and a full fix within 60 days after the specific information on the vulnerability has been reported to the project by any means (in private or in public). For vulnerabilities with lower score there is no commitment on the timeline, only prioritisation. The full fix doesn't imply that all software functionality remains accessible (in the worst case -- cgit v1.2.3 From 4fdc2af2505da55d384be7d312540d1cbd79aaf8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 25 May 2019 20:33:53 +0900 Subject: Refer to SECURITY.md from the rest of documentation [skip ci] --- CONTRIBUTING.md | 17 +---------------- README.md | 8 ++++++-- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 56bc9d91..37dfa77e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,22 +99,7 @@ Any components proposed for reuse should have a license that permits releasing a derivative work under *LGPL v2.1 or later* or LGPL v3. Moreover, the license of a proposed component should be approved by OSI, no exceptions. -## Vulnerability reporting (security issues) - -If you find a significant vulnerability, or evidence of one, -use either of the following contacts: -* send an email to Kitsune Ral [Kitsune-Ral@users.sf.net](mailto:Kitsune-Ral@users.sf.net) -* reach out in Matrix to #kitsune:matrix.org (if you can, switch encryption **on**) - -In any of these two options, _indicate that you have such information_ -(do not share the information yet), and we'll tell you the next steps. - -By default, we will give credit to anyone who reports a vulnerability in -a responsible way so that we can fix it before public disclosure. If you want -to remain anonymous or pseudonymous instead, please let us know; we will -gladly respect your wishes. If you provide a fix as a PR, you have no way -to remain anonymous (and you also disclose the vulnerability thereby) so this -is not the right way, unless the vulnerability is already made public. +## Vulnerability reporting (security issues) - see [SECURITY.md](./SECURITY.md) ## Documentation changes diff --git a/README.md b/README.md index 857543e1..787fa0df 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,13 @@ libQMatrixClient is a Qt5-based library to make IM clients for the [Matrix](https://matrix.org) protocol. It is the backbone of [Quaternion](https://github.com/QMatrixClient/Quaternion), [Spectral](https://matrix.org/docs/projects/client/spectral.html) and some other projects. ## Contacts -You can find authors of libQMatrixClient in the Matrix room: [#qmatrixclient:matrix.org](https://matrix.to/#/#qmatrixclient:matrix.org). +You can find authors of libQMatrixClient in the Matrix room: +[#qmatrixclient:matrix.org](https://matrix.to/#/#qmatrixclient:matrix.org). -You can also file issues at [the project's issue tracker](https://github.com/QMatrixClient/libqmatrixclient/issues). If you have what looks like a security issue, please see respective instructions in CONTRIBUTING.md. +You can also file issues at +[the project's issue tracker](https://github.com/QMatrixClient/libqmatrixclient/issues). +If you find what looks like a security issue, please use instructions +in SECURITY.md. ## Building and usage So far the library is typically used as a git submodule of another project -- cgit v1.2.3 From 7ea51059b86d8124c077ed84ad3b91770af8d610 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 2 Jun 2019 13:22:11 +0900 Subject: lib/room.*: Spelling fixes in comments [ci skip] --- lib/room.cpp | 13 +++++++------ lib/room.h | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 2ce37acc..9042130a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -75,7 +75,8 @@ enum EventsPlacement : int { Older = -1, Newer = 1 }; class Room::Private { public: - /** Map of user names to users. User names potentially duplicate, hence a multi-hashmap. */ + /// Map of user names to users + /** User names potentially duplicate, hence QMultiHash. */ using members_map_t = QMultiHash; Private(Connection* c, QString id_, JoinState initialJoinState) @@ -515,7 +516,7 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to) if(newUnreadMessages > 0) { - // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count + // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (unreadMessages < 0) unreadMessages = 0; @@ -556,7 +557,7 @@ Room::Changes Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Recounting unread messages took" << et; - // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count + // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (unreadMessages == 0) unreadMessages = -1; @@ -612,7 +613,7 @@ void Room::markAllMessagesAsRead() bool Room::canSwitchVersions() const { if (!successorId().isEmpty()) - return false; // Noone can upgrade a room that's already upgraded + return false; // No one can upgrade a room that's already upgraded // TODO, #276: m.room.power_levels const auto* plEvt = @@ -1235,7 +1236,7 @@ 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. + // for it because it doesn't need to be disambiguated any more. if (namesake) emit q->memberRenamed(namesake); } @@ -1354,7 +1355,7 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) for( auto&& ephemeralEvent: data.ephemeral ) roomChanges |= processEphemeralEvent(move(ephemeralEvent)); - // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count + // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages) { qCDebug(MAIN) << "Setting unread_count to" << data.unreadCount; diff --git a/lib/room.h b/lib/room.h index 055da3da..d4a1b959 100644 --- a/lib/room.h +++ b/lib/room.h @@ -322,7 +322,7 @@ namespace QMatrixClient bool hasAccountData(const QString& type) const; /** Get a generic account data event of the given type - * This returns a generic hashmap for any room account data event + * This returns a generic hash map for any room account data event * stored on the server. Tags and read markers cannot be retrieved * using this method _yet_. */ @@ -516,7 +516,7 @@ namespace QMatrixClient /** A common signal for various kinds of changes in the room * Aside from all changes in the room state - * @param changes a set of flags describing what changes occured + * @param changes a set of flags describing what changes occurred * upon the last sync * \sa StateChange */ @@ -524,7 +524,7 @@ namespace QMatrixClient /** * \brief The room name, the canonical alias or other aliases changed * - * Not triggered when displayname changes. + * Not triggered when display name changes. */ void namesChanged(Room* room); void displaynameAboutToChange(Room* room); @@ -581,7 +581,7 @@ namespace QMatrixClient /// The room's version stability may have changed void stabilityUpdated(QString recommendedDefault, QStringList stableVersions); - /// This room has been upgraded and won't receive updates anymore + /// This room has been upgraded and won't receive updates any more void upgraded(QString serverMessage, Room* successor); /// An attempted room upgrade has failed void upgradeFailed(QString errorMessage); -- cgit v1.2.3 From e8718d8b1d61d6f70e61da0436f793a461e83bd7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 7 Jun 2019 19:23:55 +0900 Subject: BaseJob::StatusCode: add Error-less synonyms; officially deprecate JsonParseError --- lib/jobs/basejob.cpp | 2 -- lib/jobs/basejob.h | 34 +++++++++++++++++++++------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 0d9b9f10..34fc0f57 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -555,8 +555,6 @@ QString BaseJob::statusCaption() const return tr("Request was abandoned"); case NetworkError: return tr("Network problems"); - case JsonParseError: - return tr("Response could not be parsed"); case TimeoutError: return tr("Request timed out"); case ContentAccessError: diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 4c1c7706..4ee63adb 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -46,28 +46,36 @@ namespace QMatrixClient Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT) Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries) public: - /* Just in case, the values are compatible with KJob - * (which BaseJob used to inherit from). */ enum StatusCode { NoError = 0 // To be compatible with Qt conventions , Success = 0 , Pending = 1 , WarningLevel = 20 - , UnexpectedResponseTypeWarning = 21 + , UnexpectedResponseType = 21 + , UnexpectedResponseTypeWarning = UnexpectedResponseType , Abandoned = 50 //< A very brief period between abandoning and object deletion , ErrorLevel = 100 //< Errors have codes starting from this , NetworkError = 100 - , JsonParseError // TODO: Merge into IncorrectResponseError - , TimeoutError + , Timeout + , TimeoutError = Timeout , ContentAccessError , NotFoundError - , IncorrectRequestError - , IncorrectResponseError - , TooManyRequestsError - , RequestNotImplementedError - , UnsupportedRoomVersionError - , NetworkAuthRequiredError - , UserConsentRequiredError - , UserDefinedError = 200 + , IncorrectRequest + , IncorrectRequestError = IncorrectRequest + , IncorrectResponse + , IncorrectResponseError = IncorrectResponse + , JsonParseError //< deprecated; Use IncorrectResponse instead + = IncorrectResponse + , TooManyRequests + , TooManyRequestsError = TooManyRequests + , RequestNotImplemented + , RequestNotImplementedError = RequestNotImplemented + , UnsupportedRoomVersion + , UnsupportedRoomVersionError = UnsupportedRoomVersion + , NetworkAuthRequired + , NetworkAuthRequiredError = NetworkAuthRequired + , UserConsentRequired + , UserConsentRequiredError = UserConsentRequired + , UserDefinedError = 256 }; /** -- cgit v1.2.3 From 10c4be59438ac614a01800fb44e351e7039fe6e6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 7 Jun 2019 10:37:41 +0900 Subject: *.cpp.mustache: switch from now-deprecated JsonParseError to IncorrectResponse --- lib/csapi/{{base}}.cpp.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/csapi/{{base}}.cpp.mustache b/lib/csapi/{{base}}.cpp.mustache index ff888d76..010f9116 100644 --- a/lib/csapi/{{base}}.cpp.mustache +++ b/lib/csapi/{{base}}.cpp.mustache @@ -115,7 +115,7 @@ BaseJob::Status {{camelCaseOperationId}}Job::parseJson(const QJsonDocument& data {{#inlineResponse}} fromJson(data, d->{{paramName}}); {{/inlineResponse}}{{^inlineResponse}} auto json = data.object(); {{#properties}}{{#required?}} if (!json.contains("{{baseName}}"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key '{{baseName}}' not found in the response" }; {{/required?}} fromJson(json.value("{{baseName}}"_ls), d->{{paramName}}); {{/properties}}{{/inlineResponse}} return Success; -- cgit v1.2.3 From 93f0c8fe89f448d1d58caa757573f17102369471 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 7 Jun 2019 19:16:33 +0900 Subject: Generated files in csapi/: switch from now-deprecated JsonParserError to IncorrectResponse --- lib/csapi/capabilities.cpp | 2 +- lib/csapi/content-repo.cpp | 2 +- lib/csapi/create_room.cpp | 2 +- lib/csapi/filter.cpp | 2 +- lib/csapi/joining.cpp | 4 ++-- lib/csapi/keys.cpp | 2 +- lib/csapi/list_joined_rooms.cpp | 2 +- lib/csapi/notifications.cpp | 2 +- lib/csapi/openid.cpp | 8 ++++---- lib/csapi/presence.cpp | 2 +- lib/csapi/pushrules.cpp | 6 +++--- lib/csapi/registration.cpp | 2 +- lib/csapi/room_upgrades.cpp | 2 +- lib/csapi/search.cpp | 2 +- lib/csapi/users.cpp | 4 ++-- lib/csapi/versions.cpp | 2 +- lib/csapi/whoami.cpp | 2 +- 17 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/csapi/capabilities.cpp b/lib/csapi/capabilities.cpp index 210423f5..fb506784 100644 --- a/lib/csapi/capabilities.cpp +++ b/lib/csapi/capabilities.cpp @@ -76,7 +76,7 @@ BaseJob::Status GetCapabilitiesJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("capabilities"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'capabilities' not found in the response" }; fromJson(json.value("capabilities"_ls), d->capabilities); return Success; diff --git a/lib/csapi/content-repo.cpp b/lib/csapi/content-repo.cpp index 22223985..7e490604 100644 --- a/lib/csapi/content-repo.cpp +++ b/lib/csapi/content-repo.cpp @@ -50,7 +50,7 @@ BaseJob::Status UploadContentJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("content_uri"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'content_uri' not found in the response" }; fromJson(json.value("content_uri"_ls), d->contentUri); return Success; diff --git a/lib/csapi/create_room.cpp b/lib/csapi/create_room.cpp index 448547ae..3101152a 100644 --- a/lib/csapi/create_room.cpp +++ b/lib/csapi/create_room.cpp @@ -77,7 +77,7 @@ BaseJob::Status CreateRoomJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("room_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'room_id' not found in the response" }; fromJson(json.value("room_id"_ls), d->roomId); return Success; diff --git a/lib/csapi/filter.cpp b/lib/csapi/filter.cpp index 982e60b5..9f412d53 100644 --- a/lib/csapi/filter.cpp +++ b/lib/csapi/filter.cpp @@ -39,7 +39,7 @@ BaseJob::Status DefineFilterJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("filter_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'filter_id' not found in the response" }; fromJson(json.value("filter_id"_ls), d->filterId); return Success; diff --git a/lib/csapi/joining.cpp b/lib/csapi/joining.cpp index 00d930fa..544f442f 100644 --- a/lib/csapi/joining.cpp +++ b/lib/csapi/joining.cpp @@ -57,7 +57,7 @@ BaseJob::Status JoinRoomByIdJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("room_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'room_id' not found in the response" }; fromJson(json.value("room_id"_ls), d->roomId); return Success; @@ -124,7 +124,7 @@ BaseJob::Status JoinRoomJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("room_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'room_id' not found in the response" }; fromJson(json.value("room_id"_ls), d->roomId); return Success; diff --git a/lib/csapi/keys.cpp b/lib/csapi/keys.cpp index 6c16a8a3..5bbc1aab 100644 --- a/lib/csapi/keys.cpp +++ b/lib/csapi/keys.cpp @@ -42,7 +42,7 @@ BaseJob::Status UploadKeysJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("one_time_key_counts"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'one_time_key_counts' not found in the response" }; fromJson(json.value("one_time_key_counts"_ls), d->oneTimeKeyCounts); return Success; diff --git a/lib/csapi/list_joined_rooms.cpp b/lib/csapi/list_joined_rooms.cpp index 85a9cae4..297a5ae0 100644 --- a/lib/csapi/list_joined_rooms.cpp +++ b/lib/csapi/list_joined_rooms.cpp @@ -44,7 +44,7 @@ BaseJob::Status GetJoinedRoomsJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("joined_rooms"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'joined_rooms' not found in the response" }; fromJson(json.value("joined_rooms"_ls), d->joinedRooms); return Success; diff --git a/lib/csapi/notifications.cpp b/lib/csapi/notifications.cpp index c00b7cb0..5d3bdb47 100644 --- a/lib/csapi/notifications.cpp +++ b/lib/csapi/notifications.cpp @@ -80,7 +80,7 @@ BaseJob::Status GetNotificationsJob::parseJson(const QJsonDocument& data) auto json = data.object(); fromJson(json.value("next_token"_ls), d->nextToken); if (!json.contains("notifications"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'notifications' not found in the response" }; fromJson(json.value("notifications"_ls), d->notifications); return Success; diff --git a/lib/csapi/openid.cpp b/lib/csapi/openid.cpp index b27fe0b8..03d24790 100644 --- a/lib/csapi/openid.cpp +++ b/lib/csapi/openid.cpp @@ -57,19 +57,19 @@ BaseJob::Status RequestOpenIdTokenJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("access_token"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'access_token' not found in the response" }; fromJson(json.value("access_token"_ls), d->accessToken); if (!json.contains("token_type"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'token_type' not found in the response" }; fromJson(json.value("token_type"_ls), d->tokenType); if (!json.contains("matrix_server_name"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'matrix_server_name' not found in the response" }; fromJson(json.value("matrix_server_name"_ls), d->matrixServerName); if (!json.contains("expires_in"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'expires_in' not found in the response" }; fromJson(json.value("expires_in"_ls), d->expiresIn); return Success; diff --git a/lib/csapi/presence.cpp b/lib/csapi/presence.cpp index 024d7a34..210ee0ae 100644 --- a/lib/csapi/presence.cpp +++ b/lib/csapi/presence.cpp @@ -74,7 +74,7 @@ BaseJob::Status GetPresenceJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("presence"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'presence' not found in the response" }; fromJson(json.value("presence"_ls), d->presence); fromJson(json.value("last_active_ago"_ls), d->lastActiveAgo); diff --git a/lib/csapi/pushrules.cpp b/lib/csapi/pushrules.cpp index b91d18f7..9b5b7cd1 100644 --- a/lib/csapi/pushrules.cpp +++ b/lib/csapi/pushrules.cpp @@ -44,7 +44,7 @@ BaseJob::Status GetPushRulesJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("global"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'global' not found in the response" }; fromJson(json.value("global"_ls), d->global); return Success; @@ -152,7 +152,7 @@ BaseJob::Status IsPushRuleEnabledJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("enabled"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'enabled' not found in the response" }; fromJson(json.value("enabled"_ls), d->enabled); return Success; @@ -201,7 +201,7 @@ BaseJob::Status GetPushRuleActionsJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("actions"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'actions' not found in the response" }; fromJson(json.value("actions"_ls), d->actions); return Success; diff --git a/lib/csapi/registration.cpp b/lib/csapi/registration.cpp index 5dc9c1e5..76741a50 100644 --- a/lib/csapi/registration.cpp +++ b/lib/csapi/registration.cpp @@ -74,7 +74,7 @@ BaseJob::Status RegisterJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("user_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'user_id' not found in the response" }; fromJson(json.value("user_id"_ls), d->userId); fromJson(json.value("access_token"_ls), d->accessToken); diff --git a/lib/csapi/room_upgrades.cpp b/lib/csapi/room_upgrades.cpp index f58fd675..f80c3aba 100644 --- a/lib/csapi/room_upgrades.cpp +++ b/lib/csapi/room_upgrades.cpp @@ -41,7 +41,7 @@ BaseJob::Status UpgradeRoomJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("replacement_room"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'replacement_room' not found in the response" }; fromJson(json.value("replacement_room"_ls), d->replacementRoom); return Success; diff --git a/lib/csapi/search.cpp b/lib/csapi/search.cpp index a5f83c79..ad2c34a3 100644 --- a/lib/csapi/search.cpp +++ b/lib/csapi/search.cpp @@ -164,7 +164,7 @@ BaseJob::Status SearchJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("search_categories"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'search_categories' not found in the response" }; fromJson(json.value("search_categories"_ls), d->searchCategories); return Success; diff --git a/lib/csapi/users.cpp b/lib/csapi/users.cpp index 97d8962d..0d867145 100644 --- a/lib/csapi/users.cpp +++ b/lib/csapi/users.cpp @@ -63,11 +63,11 @@ BaseJob::Status SearchUserDirectoryJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("results"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'results' not found in the response" }; fromJson(json.value("results"_ls), d->results); if (!json.contains("limited"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'limited' not found in the response" }; fromJson(json.value("limited"_ls), d->limited); return Success; diff --git a/lib/csapi/versions.cpp b/lib/csapi/versions.cpp index 6ee6725d..4b7c4ced 100644 --- a/lib/csapi/versions.cpp +++ b/lib/csapi/versions.cpp @@ -50,7 +50,7 @@ BaseJob::Status GetVersionsJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("versions"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'versions' not found in the response" }; fromJson(json.value("versions"_ls), d->versions); fromJson(json.value("unstable_features"_ls), d->unstableFeatures); diff --git a/lib/csapi/whoami.cpp b/lib/csapi/whoami.cpp index aebdf5d3..ce024c33 100644 --- a/lib/csapi/whoami.cpp +++ b/lib/csapi/whoami.cpp @@ -44,7 +44,7 @@ BaseJob::Status GetTokenOwnerJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("user_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'user_id' not found in the response" }; fromJson(json.value("user_id"_ls), d->userId); return Success; -- cgit v1.2.3