From 0571ba1fb1948a6cc050230a85201291ababbf04 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 7 Jun 2021 07:55:23 +0200 Subject: Connection::joinRoom() shouldn't enforce room state This is an adjustment to the earlier fix of #471: if a join is immediately followed by a leave (e.g. from another client/bot - you can't do it programmatically from libQuotient) the sync may bring the room already in the Leave state; therefore `joinRoom` should not impose the state but rather ask `provideRoom` to create a `Join` room - just as it's designed when passed an empty `joinState`. --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index a384783c..55067bb7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -800,14 +800,14 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { - auto job = callApi(roomAlias, serverNames); - // Upon completion, ensure a room object in Join state is created - // (or it might already be there due to a sync completing earlier). - // finished() is used here instead of success() to overtake clients - // that may add their own slots to finished(). + auto* const job = callApi(roomAlias, serverNames); + // Upon completion, ensure a room object is created in case it hasn't come + // with a sync yet. If the room object is not there, provideRoom() will + // create it in Join state. finished() is used here instead of success() + // to overtake clients that may add their own slots to finished(). connect(job, &BaseJob::finished, this, [this, job] { if (job->status().good()) - provideRoom(job->roomId(), JoinState::Join); + provideRoom(job->roomId()); }); return job; } -- cgit v1.2.3 From dcbb2cfd0e238f788105d7d249f8aac6ad0823e4 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 11 Jun 2021 17:46:58 +0200 Subject: CMakeLists: require at least Qt 5.12; add Qt 6 support --- CMakeLists.txt | 23 +++++++++++++++++++---- autotests/CMakeLists.txt | 2 +- quotest/CMakeLists.txt | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 39b1b03a..9b53a53a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,9 +72,21 @@ message(STATUS " Header files will be installed to ${CMAKE_INSTALL_PREFIX}/${${ # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) -find_package(Qt5 5.9 REQUIRED Core Network Gui Multimedia Test) -get_filename_component(Qt5_Prefix "${Qt5_DIR}/../../../.." ABSOLUTE) -message(STATUS "Using Qt ${Qt5_VERSION} at ${Qt5_Prefix}") +option(BUILD_WITH_QT6 "Build Quotient with Qt 6" OFF) + +if (NOT BUILD_WITH_QT6) + # Use Qt5 by default + find_package(Qt5 5.12 QUIET COMPONENTS Core) +endif() +if (NOT Qt5Core_FOUND OR BUILD_WITH_Qt6) + find_package(Qt6 6.2 REQUIRED Core Network Gui Test) # TODO: Multimedia + set(Qt Qt6) +else() + find_package(Qt5 5.12 REQUIRED Core Network Gui Multimedia Test) + set(Qt Qt5) +endif() +get_filename_component($Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) +message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") if (${PROJECT_NAME}_ENABLE_E2EE) if ((NOT DEFINED USE_INTREE_LIBQOLM OR USE_INTREE_LIBQOLM) @@ -279,7 +291,10 @@ if (${PROJECT_NAME}_ENABLE_E2EE) target_link_libraries(${PROJECT_NAME} QtOlm) set(FIND_DEPS "find_dependency(QtOlm)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Network Qt5::Gui Qt5::Multimedia) +target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) +if (Qt STREQUAL Qt5) # Qt 6 hasn't got Multimedia component as yet + target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) +endif() configure_file(${PROJECT_NAME}.pc.in ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc @ONLY NEWLINE_STYLE UNIX) diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 07f1f046..282ab036 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -7,7 +7,7 @@ include(CMakeParseArguments) function(QUOTIENT_ADD_TEST) cmake_parse_arguments(ARG "" "NAME" "" ${ARGN}) add_executable(${ARG_NAME} ${ARG_NAME}.cpp) - target_link_libraries(${ARG_NAME} Qt5::Core Qt5::Test Quotient) + target_link_libraries(${ARG_NAME} ${Qt}::Core ${Qt}::Test Quotient) add_test(NAME ${ARG_NAME} COMMAND ${ARG_NAME}) endfunction() diff --git a/quotest/CMakeLists.txt b/quotest/CMakeLists.txt index 29c53fae..bf9af796 100644 --- a/quotest/CMakeLists.txt +++ b/quotest/CMakeLists.txt @@ -5,7 +5,7 @@ set(quotest_SRCS quotest.cpp) add_executable(quotest ${quotest_SRCS}) -target_link_libraries(quotest PRIVATE Qt5::Core Qt5::Test ${PROJECT_NAME}) +target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${PROJECT_NAME}) option(${PROJECT_NAME}_INSTALL_TESTS "install quotest (former qmc-example) application" ON) add_feature_info(InstallQuotest ${PROJECT_NAME}_INSTALL_TESTS -- cgit v1.2.3 From c18a591b484db451eb084ec4f1f17057813800df Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 20:51:17 +0200 Subject: Adjust to new moc/QMetaType requirements See https://www.qt.io/blog/whats-new-in-qmetatype-qvariant#qmetatype-knows-your-properties-and-methods-types --- lib/connection.cpp | 3 +++ lib/room.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 55067bb7..b3006084 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -14,6 +14,9 @@ #include "settings.h" #include "user.h" +// NB: since Qt 6, moc_connection.cpp needs Room and User fully defined +#include "moc_connection.cpp" + #include "csapi/account-data.h" #include "csapi/capabilities.h" #include "csapi/joining.h" diff --git a/lib/room.cpp b/lib/room.cpp index fadcea17..abaf50b7 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -16,6 +16,9 @@ #include "syncdata.h" #include "user.h" +// NB: since Qt 6, moc_room.cpp needs User fully defined +#include "moc_room.cpp" + #include "csapi/account-data.h" #include "csapi/banning.h" #include "csapi/inviting.h" -- cgit v1.2.3 From ff171e91877048f132955abaa617a26c63632bdf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 20:56:41 +0200 Subject: connection.cpp: erase_if -> remove_if erase_if is now also provided by Qt; doing pretty much the same thing, the Qt implementation only returns the number of removed entries instead of returning a collection of them, however. Worth admitting at this point that the function in connection.cpp has never had the semantics of STL's erase_if() and doesn't quite have the semantics of remove_if() either; but at least it's closer to remove_if(). --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index b3006084..e076957a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -58,7 +58,7 @@ using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() template -HashT erase_if(HashT& hashMap, Pred pred) +HashT remove_if(HashT& hashMap, Pred pred) { HashT removals; for (auto it = hashMap.begin(); it != hashMap.end();) { @@ -668,16 +668,16 @@ void Connection::Private::consumeAccountData(Events&& accountDataEvents) // https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); DirectChatsMap remoteRemovals = - erase_if(directChats, [&usersToDCs, this](auto it) { + remove_if(directChats, [&usersToDCs, this](auto it) { return !( usersToDCs.contains(it.key()->id(), it.value()) || dcLocalAdditions.contains(it.key(), it.value())); }); - erase_if(directChatUsers, [&remoteRemovals](auto it) { + remove_if(directChatUsers, [&remoteRemovals](auto it) { return remoteRemovals.contains(it.value(), it.key()); }); // Remove from dcLocalRemovals what the server already has. - erase_if(dcLocalRemovals, [&remoteRemovals](auto it) { + remove_if(dcLocalRemovals, [&remoteRemovals](auto it) { return remoteRemovals.contains(it.key(), it.value()); }); if (MAIN().isDebugEnabled()) @@ -705,7 +705,7 @@ void Connection::Private::consumeAccountData(Events&& accountDataEvents) << "Couldn't get a user object for" << it.key(); } // Remove from dcLocalAdditions what the server already has. - erase_if(dcLocalAdditions, [&remoteAdditions](auto it) { + remove_if(dcLocalAdditions, [&remoteAdditions](auto it) { return remoteAdditions.contains(it.key(), it.value()); }); if (!remoteAdditions.isEmpty() || !remoteRemovals.isEmpty()) @@ -1388,7 +1388,7 @@ void Connection::removeFromDirectChats(const QString& roomId, User* user) removals.insert(user, roomId); d->dcLocalRemovals.insert(user, roomId); } else { - removals = erase_if(d->directChats, + removals = remove_if(d->directChats, [&roomId](auto it) { return it.value() == roomId; }); d->directChatUsers.remove(roomId); d->dcLocalRemovals += removals; -- cgit v1.2.3 From 84d6295f859ee600d7aa3860767030bdc78914ba Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 20:57:54 +0200 Subject: uri.cpp, room.*: Get rid of QStringRefs --- lib/room.cpp | 8 ++++++-- lib/room.h | 2 +- lib/uri.cpp | 10 +++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index abaf50b7..c314fc72 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2962,12 +2962,16 @@ bool MemberSorter::operator()(User* u1, User* u2) const return operator()(u1, room->disambiguatedMemberName(u2->id())); } -bool MemberSorter::operator()(User* u1, const QString& u2name) const +bool MemberSorter::operator()(User* u1, QStringView u2name) const { auto n1 = room->disambiguatedMemberName(u1->id()); if (n1.startsWith('@')) n1.remove(0, 1); - auto n2 = u2name.midRef(u2name.startsWith('@') ? 1 : 0); + const auto n2 = u2name.mid(u2name.startsWith('@') ? 1 : 0) +#if QT_VERSION_MAJOR < 6 + .toString() // Qt 5 doesn't have QStringView::localeAwareCompare +#endif + ; return n1.localeAwareCompare(n2) < 0; } diff --git a/lib/room.h b/lib/room.h index a8275ce9..26d0121e 100644 --- a/lib/room.h +++ b/lib/room.h @@ -754,7 +754,7 @@ public: explicit MemberSorter(const Room* r) : room(r) {} bool operator()(User* u1, User* u2) const; - bool operator()(User* u1, const QString& u2name) const; + bool operator()(User* u1, QStringView u2name) const; template typename ContT::size_type lowerBoundIndex(const ContT& c, const ValT& v) const diff --git a/lib/uri.cpp b/lib/uri.cpp index 291bfcae..d8624796 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -70,7 +70,7 @@ static QString pathSegment(const QUrl& url, int which) encodedPath(url).section('/', which, which).toUtf8()); } -static auto decodeFragmentPart(const QStringRef& part) +static auto decodeFragmentPart(QStringView part) { return QUrl::fromPercentEncoding(part.toLatin1()).toUtf8(); } @@ -98,7 +98,7 @@ Uri::Uri(QUrl url) : QUrl(std::move(url)) if (scheme() == "matrix") { // Check sanity as per https://github.com/matrix-org/matrix-doc/pull/2312 const auto& urlPath = encodedPath(*this); - const auto& splitPath = urlPath.splitRef('/'); + const auto& splitPath = urlPath.split('/'); switch (splitPath.size()) { case 2: break; @@ -128,9 +128,9 @@ Uri::Uri(QUrl url) : QUrl(std::move(url)) // so force QUrl to decode everything. auto f = fragment(QUrl::EncodeUnicode); if (auto&& m = MatrixToUrlRE.match(f); m.hasMatch()) - *this = Uri { decodeFragmentPart(m.capturedRef("main")), - decodeFragmentPart(m.capturedRef("sec")), - decodeFragmentPart(m.capturedRef("query")) }; + *this = Uri { decodeFragmentPart(m.capturedView(u"main")), + decodeFragmentPart(m.capturedView(u"sec")), + decodeFragmentPart(m.capturedView(u"query")) }; } } -- cgit v1.2.3 From 620c2fe55b327e555477ed29bd670ddc6b9023d1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 21:01:38 +0200 Subject: Make RequestData compile again This fixes reliance on QIODevice being magically available for std::unique_ptr<> by indirect inclusion. Since Qt 6 this inclusion no more happens, time to #include explicitly. --- lib/jobs/requestdata.cpp | 5 +++++ lib/jobs/requestdata.h | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/jobs/requestdata.cpp b/lib/jobs/requestdata.cpp index 047e2920..2c001ccc 100644 --- a/lib/jobs/requestdata.cpp +++ b/lib/jobs/requestdata.cpp @@ -3,6 +3,7 @@ #include "requestdata.h" +#include #include #include #include @@ -31,4 +32,8 @@ RequestData::RequestData(const QJsonObject& jo) : _source(fromJson(jo)) {} RequestData::RequestData(const QJsonArray& ja) : _source(fromJson(ja)) {} +RequestData::RequestData(QIODevice* source) + : _source(std::unique_ptr(source)) +{} + RequestData::~RequestData() = default; diff --git a/lib/jobs/requestdata.h b/lib/jobs/requestdata.h index 4958e0f9..21657631 100644 --- a/lib/jobs/requestdata.h +++ b/lib/jobs/requestdata.h @@ -24,8 +24,7 @@ public: RequestData(const QByteArray& a = {}); RequestData(const QJsonObject& jo); RequestData(const QJsonArray& ja); - RequestData(QIODevice* source) : _source(std::unique_ptr(source)) - {} + RequestData(QIODevice* source); RequestData(RequestData&&) = default; RequestData& operator=(RequestData&&) = default; ~RequestData(); -- cgit v1.2.3 From 67ea5b45701e6bd5bf244039dc60a134d67a4cab Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 13 Jun 2021 14:08:52 +0200 Subject: Disable the piece depending on Qt Multimedia for Qt 6 Waiting for the Multimedia arrival in Qt 6.2. --- lib/events/roommessageevent.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 31c0fd9e..3f6e475d 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -10,7 +10,9 @@ #include #include #include -#include +#if QT_VERSION_MAJOR < 6 +# include +#endif using namespace Quotient; using namespace EventContent; @@ -149,7 +151,11 @@ TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) // done by starting to play the file. Left for a future implementation. if (mimeTypeName.startsWith("video/")) return new VideoContent(localUrl, file.size(), mimeType, +#if QT_VERSION_MAJOR < 6 QMediaResource(localUrl).resolution(), +#else + {}, +#endif file.fileName()); if (mimeTypeName.startsWith("audio/")) -- cgit v1.2.3 From b8a78fe7a2370697eea4517ab000130fe1180715 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 13 Jun 2021 14:10:47 +0200 Subject: Exclude code no more needed with Qt6 --- lib/converters.h | 4 +++- lib/settings.cpp | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/converters.h b/lib/converters.h index e07b6ee4..af6c0192 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -221,14 +221,16 @@ template struct JsonConverter> : public JsonArrayConverter> {}; +#if QT_VERSION_MAJOR < 6 // QVector is an alias of QList in Qt6 but not in Qt 5 template struct JsonConverter> : public JsonArrayConverter> {}; +#endif template struct JsonConverter> : public JsonArrayConverter> {}; template <> -struct JsonConverter : public JsonConverter> { +struct JsonConverter : public JsonArrayConverter { static auto dump(const QStringList& sl) { return QJsonArray::fromStringList(sl); diff --git a/lib/settings.cpp b/lib/settings.cpp index 703f4320..1d36db27 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -21,7 +21,9 @@ void Settings::setLegacyNames(const QString& organizationName, Settings::Settings(QObject* parent) : QSettings(parent) { +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) setIniCodec("UTF-8"); +#endif } void Settings::setValue(const QString& key, const QVariant& value) -- cgit v1.2.3 From beb3a135a336dca654d967b88ea06a7457fbbec1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 22:31:25 +0200 Subject: EncryptionEvent: fix "too perfect forwarding" Now that QMetaType introspects into types, it reveals hidden problems (which is very nice of it). --- lib/events/encryptionevent.h | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index f9bbab12..65ee4187 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -40,6 +40,7 @@ public: // default value : StateEvent(typeId(), obj) {} + EncryptionEvent(EncryptionEvent&&) = delete; template EncryptionEvent(ArgTs&&... contentArgs) : StateEvent(typeId(), matrixTypeId(), QString(), -- cgit v1.2.3 From faf0122db53e4d010ec1f9f00f40feedd6786e71 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 22:43:33 +0200 Subject: Uri: Fix ambiguity around QChar constructors QChar now accepts more types for construction, and that unraveled concatenation of a Type/SecondaryType character with a QString. To fix it, give the compiler a hint by casting to the enum's underlying type (which also nicely documents that we _actually_ switch from enum to character type). --- lib/uri.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/uri.cpp b/lib/uri.cpp index d8624796..c8843dda 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -186,14 +186,18 @@ QString Uri::primaryId() const if (primaryType_ == Empty || primaryType_ == Invalid) return {}; - const auto& idStem = pathSegment(*this, 1); - return idStem.isEmpty() ? idStem : primaryType_ + idStem; + auto idStem = pathSegment(*this, 1); + if (!idStem.isEmpty()) + idStem.push_front(char(primaryType_)); + return idStem; } QString Uri::secondaryId() const { - const auto& idStem = pathSegment(*this, 3); - return idStem.isEmpty() ? idStem : secondaryType() + idStem; + auto idStem = pathSegment(*this, 3); + if (!idStem.isEmpty()) + idStem.push_front(char(secondaryType())); + return idStem; } static const auto ActionKey = QStringLiteral("action"); -- cgit v1.2.3 From fad6ac5fdee53c349c69afd0ad5e57f340228c6b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 22:45:15 +0200 Subject: Generate a moc file for quotient_common.h Previously Q_NAMESPACE did not require its own moc, somehow blending into others; now it does. --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b53a53a..dfb60ea6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ endif() if (NOT Qt5Core_FOUND OR BUILD_WITH_Qt6) find_package(Qt6 6.2 REQUIRED Core Network Gui Test) # TODO: Multimedia set(Qt Qt6) + qt6_wrap_cpp(lib_SRCS lib/quotient_common.h) else() find_package(Qt5 5.12 REQUIRED Core Network Gui Multimedia Test) set(Qt Qt5) @@ -124,7 +125,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) endif () # Set up source files -set(lib_SRCS +list(APPEND lib_SRCS lib/networkaccessmanager.cpp lib/connectiondata.cpp lib/connection.cpp -- cgit v1.2.3 From f4255f83bf5f4bfe03dc7518a89b378d3238c940 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 23:24:19 +0200 Subject: CI: Use Qt 5.12, as required from now --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24681460..00e17f42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v2.11.1 with: - version: '5.9.9' + version: '5.12.10' cached: ${{ steps.cache-qt.outputs.cache-hit }} - name: Install Ninja (macOS) -- cgit v1.2.3 From ab269f183fdc6f611559d69475543089088b4712 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 13 Jun 2021 09:41:28 +0200 Subject: CI: version Qt cache --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00e17f42..ed251bc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: matrix: os: [ubuntu-18.04, macos-10.15] compiler: [ GCC, Clang ] + qt-version: [ '5.12.10' ] # Not using binary values here, to make the job captions more readable e2ee: [ '', 'E2EE' ] update-api: [ '', 'update-api' ] @@ -37,12 +38,12 @@ jobs: uses: actions/cache@v2 with: path: ${{ runner.workspace }}/Qt - key: ${{ runner.os }}-QtCache + key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache - name: Install Qt uses: jurplel/install-qt-action@v2.11.1 with: - version: '5.12.10' + version: ${{ matrix.qt-version }} cached: ${{ steps.cache-qt.outputs.cache-hit }} - name: Install Ninja (macOS) -- cgit v1.2.3 From 55845602abf09bc5ccee2032a21b95b245d3cedd Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 13 Jun 2021 14:23:46 +0200 Subject: BaseJob: FollowRedirectsAttribute -> RedirectPolicyAttribute The latter obsoleted the former since Qt 5.9, actually. --- lib/jobs/basejob.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 48c2996d..c27c6a89 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -288,7 +288,8 @@ void BaseJob::Private::sendRequest() req.setRawHeader("Authorization", QByteArray("Bearer ") + connection->accessToken()); req.setAttribute(QNetworkRequest::BackgroundRequestAttribute, inBackground); - req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, + QNetworkRequest::NoLessSafeRedirectPolicy); req.setMaximumRedirectsAllowed(10); req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); req.setAttribute( -- cgit v1.2.3 From 21db98f32bc7e87685c7bd813945cbba75ec0fb7 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 13 Jun 2021 17:06:41 +0200 Subject: AppVeyor: disable E2EE building, drop older Qt E2EE will be remade anyway so building it now makes little sense. --- .appveyor.yml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 69a58ba0..fa031ed8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,16 +9,6 @@ environment: - QTDIR: C:\Qt\5.13\msvc2017_64 # Fresh Qt, 64-bit VCVARS: "vcvars64.bat" PLATFORM: - - QTDIR: C:\Qt\5.9\msvc2017_64 # Oldest supported Qt, 64-bit, E2EE - VCVARS: "vcvars64.bat" - QMAKE_E2EE_ARGS: '"DEFINES += Quotient_E2EE_ENABLED USE_INTREE_LIBQOLM" "INCLUDEPATH += olm/include" "LIBS += -Lbuild/olm"' - CMAKE_E2EE_ARGS: '-DQuotient_ENABLE_E2EE=ON -DOlm_DIR=build/olm' - PLATFORM: - - QTDIR: C:\Qt\5.13\msvc2017_64 # Fresh Qt, 64-bit, E2EE - VCVARS: "vcvars64.bat" - QMAKE_E2EE_ARGS: '"DEFINES += Quotient_E2EE_ENABLED USE_INTREE_LIBQOLM" "INCLUDEPATH += olm/include" "LIBS += -Lbuild/olm"' - CMAKE_E2EE_ARGS: '-DQuotient_ENABLE_E2EE=ON -DOlm_DIR=build/olm' - PLATFORM: init: - call "%QTDIR%\bin\qtenv2.bat" @@ -28,12 +18,9 @@ init: before_build: - git submodule update --init --recursive -- git clone https://gitlab.matrix.org/matrix-org/olm.git -- cmake %CMAKE_ARGS% -Holm -Bbuild/olm -- cmake --build build/olm build_script: -- cmake %CMAKE_ARGS% %CMAKE_E2EE_ARGS% -H. -Bbuild +- cmake %CMAKE_ARGS% -H. -Bbuild - cmake --build build #after_build: -- cgit v1.2.3 From 083f62f58bc525d761969133e12a859de9b29648 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 13 Jun 2021 17:11:53 +0200 Subject: CMakeLists: require explicit BUILD_WITH_QT6 for Qt 6 It's not there, it's experimental - people should know what they are doing. --- CMakeLists.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dfb60ea6..d930bbf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,13 +72,9 @@ message(STATUS " Header files will be installed to ${CMAKE_INSTALL_PREFIX}/${${ # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) -option(BUILD_WITH_QT6 "Build Quotient with Qt 6" OFF) +option(BUILD_WITH_QT6 "Build Quotient with Qt 6 (EXPERIMENTAL)" OFF) -if (NOT BUILD_WITH_QT6) - # Use Qt5 by default - find_package(Qt5 5.12 QUIET COMPONENTS Core) -endif() -if (NOT Qt5Core_FOUND OR BUILD_WITH_Qt6) +if (BUILD_WITH_QT6) find_package(Qt6 6.2 REQUIRED Core Network Gui Test) # TODO: Multimedia set(Qt Qt6) qt6_wrap_cpp(lib_SRCS lib/quotient_common.h) -- cgit v1.2.3 From fefa95b86c31f3b7e46fc28327b23050cbadedb9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 23 Jun 2021 13:41:44 +0200 Subject: Update to the new matrix-doc layout and tooling Among things affecting Quotient, the update involved moving API files from api/ to data/api/, adding extensions to event schema files, and switching from ReStructured Text to Markdown as a lightweight markup language. This commit updates the build system and GTAD configuration to accommodate for these. The build system is also more robust now in choosing whether the update-api target should be provided. Previously the target was provided whenever GTAD_PATH and MATRIX_DOC_PATH were specified, even if they did not point to anything valid. CMake now checks that MATRIX_DOC_PATH is an actual directory and that GTAD_PATH points to an actual file. # Conflicts: # CMakeLists.txt --- CMakeLists.txt | 26 ++++++++++++++++++-------- gtad/gtad.yaml | 4 ++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d930bbf2..555ffa96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,16 +174,26 @@ set(FULL_CSAPI_DIR lib/${CSAPI_DIR}) set(ASAPI_DEF_DIR application-service/definitions) set(ISAPI_DEF_DIR identity/definitions) -if (GTAD_PATH) +if (GTAD_PATH AND MATRIX_DOC_PATH) get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" REALPATH) + if (EXISTS ${ABS_GTAD_PATH}) + get_filename_component(ABS_API_DEF_PATH "${MATRIX_DOC_PATH}/data/api" REALPATH) + if (NOT IS_DIRECTORY ${ABS_API_DEF_PATH}) + # Check the old place of API files + get_filename_component(ABS_API_DEF_PATH "${MATRIX_DOC_PATH}/api" REALPATH) + endif () + if (IS_DIRECTORY ${ABS_API_DEF_PATH}) + set(API_GENERATION_ENABLED 1) + else () + message( WARNING "${MATRIX_DOC_PATH} doesn't seem to point to a valid matrix-doc repo; disabling API stubs generation") + endif () + else (EXISTS ${ABS_GTAD_PATH}) + message( WARNING "${GTAD_PATH} doesn't exist; disabling API stubs generation") + endif () endif () -if (MATRIX_DOC_PATH) - get_filename_component(ABS_API_DEF_PATH "${MATRIX_DOC_PATH}/api" REALPATH) -endif () -if (ABS_GTAD_PATH AND ABS_API_DEF_PATH) +if (API_GENERATION_ENABLED) message( STATUS "Using GTAD at ${ABS_GTAD_PATH}" ) - message( STATUS "Using API files at ${ABS_API_DEF_PATH}" ) - set(API_GENERATION_ENABLED 1) + message( STATUS "Found API files at ${ABS_API_DEF_PATH}" ) if (NOT CLANG_FORMAT) set(CLANG_FORMAT clang-format) endif() @@ -225,7 +235,7 @@ if (ABS_GTAD_PATH AND ABS_API_DEF_PATH) VERBATIM ) add_custom_target(update-api DEPENDS generate-unformatted-api) - if (ABS_CLANG_FORMAT) + if (EXISTS ${ABS_CLANG_FORMAT}) set(CLANG_FORMAT_ARGS -i -sort-includes ${CLANG_FORMAT_ARGS}) # FIXME: the list of files should be produced after GTAD has run. # For now it's produced at CMake invocation. If file() hasn't found diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml index adf5024a..d68cc8a0 100644 --- a/gtad/gtad.yaml +++ b/gtad/gtad.yaml @@ -85,7 +85,7 @@ analyzer: { type: RoomEventPtr, imports: "events/eventloader.h" } - /event.yaml$/: { type: EventPtr, imports: "events/eventloader.h" } - - /m\.room\.member$/: void # Skip resolving; see EventsArray<> below + - /m\.room\.member/: void # Skip resolving; see EventsArray<> below - '/^(\./)?definitions/request_email_validation.yaml$/': title: EmailValidationData - '/^(\./)?definitions/request_msisdn_validation.yaml$/': @@ -109,7 +109,7 @@ analyzer: - /^Notification|Result$/: type: "std::vector<{{1}}>" imports: "events/eventloader.h" - - /m\.room\.member$/: # Only used in an array (see also above) + - /m\.room\.member/: # Only used in an array (see also above) type: "EventsArray" imports: "events/roommemberevent.h" - /state_event.yaml$/: StateEvents -- cgit v1.2.3 From 653932bb30a4c3111dc1f99a641a011f10f21edd Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 23 Jun 2021 18:59:51 +0200 Subject: Require ClangFormat 10 or newer, if used --- .clang-format | 31 ++++++++++++++----------------- CONTRIBUTING.md | 6 +++--- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/.clang-format b/.clang-format index 4510d46e..72b67488 100644 --- a/.clang-format +++ b/.clang-format @@ -23,16 +23,17 @@ AlignAfterOpenBracket: Align #AlignConsecutiveAssignments: false #AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left -AlignOperands: true +AlignOperands: true # 'Align' since ClangFormat 11 #AlignTrailingComments: false #AllowAllArgumentsOnNextLine: true #AllowAllConstructorInitializersOnNextLine: true #AllowAllParametersOfDeclarationOnNextLine: true -#AllowShortBlocksOnASingleLine: false # 'Empty' since ClangFormat 10 +#AllowShortEnumsOnASingleLine: true +#AllowShortBlocksOnASingleLine: Empty #AllowShortCaseLabelsOnASingleLine: false #AllowShortFunctionsOnASingleLine: All #AllowShortLambdasOnASingleLine: All -#AllowShortIfStatementsOnASingleLine: false # 'Never' since ClangFormat 10 +#AllowShortIfStatementsOnASingleLine: Never #AllowShortLoopsOnASingleLine: false #AlwaysBreakAfterDefinitionReturnType: None # deprecated #AlwaysBreakAfterReturnType: None @@ -43,7 +44,7 @@ AlwaysBreakTemplateDeclarations: Yes BraceWrapping: AfterCaseLabel: false AfterClass: false - AfterControlStatement: false + AfterControlStatement: MultiLine AfterEnum: false AfterFunction: true AfterNamespace: false @@ -61,22 +62,18 @@ BreakBeforeBraces: Custom #BreakBeforeInheritanceComma: false #BreakInheritanceList: BeforeColon #BreakBeforeTernaryOperators: true -#BreakConstructorInitializersBeforeComma: false +#BreakConstructorInitializersBeforeComma: false # deprecated? #BreakConstructorInitializers: BeforeComma #BreakStringLiterals: true ColumnLimit: 80 -CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: true +#CompactNamespaces: false +#ConstructorInitializerAllOnOneLineOrOnePerLine: false #ConstructorInitializerIndentWidth: 4 #ContinuationIndentWidth: 4 Cpp11BracedListStyle: false #DeriveLineEnding: true #DerivePointerAlignment: false FixNamespaceComments: true -ForEachMacros: - - foreach - - Q_FOREACH - - forever IncludeBlocks: Regroup IncludeCategories: - Regex: '^ Date: Wed, 23 Jun 2021 19:12:26 +0200 Subject: *.mustache: Drop a stray leading end-of-line An SPDX comment in the source code did not collapse entirely. --- gtad/data.h.mustache | 3 +-- gtad/operation.cpp.mustache | 3 +-- gtad/operation.h.mustache | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/gtad/data.h.mustache b/gtad/data.h.mustache index a2193380..1b511262 100644 --- a/gtad/data.h.mustache +++ b/gtad/data.h.mustache @@ -1,8 +1,7 @@ {{! SPDX-FileCopyrightText: 2020 Kitsune Ral SPDX-License-Identifier: LGPL-2.1-or-later -}} -{{>preamble}} +}}{{>preamble}} #pragma once #include "converters.h" diff --git a/gtad/operation.cpp.mustache b/gtad/operation.cpp.mustache index 1d0ae476..5bbc45ec 100644 --- a/gtad/operation.cpp.mustache +++ b/gtad/operation.cpp.mustache @@ -1,8 +1,7 @@ {{! SPDX-FileCopyrightText: 2020 Kitsune Ral SPDX-License-Identifier: LGPL-2.1-or-later -}} -{{>preamble}} +}}{{>preamble}} #include "{{filenameBase}}.h" #include diff --git a/gtad/operation.h.mustache b/gtad/operation.h.mustache index 135eee55..f91dc66c 100644 --- a/gtad/operation.h.mustache +++ b/gtad/operation.h.mustache @@ -1,8 +1,7 @@ {{! SPDX-FileCopyrightText: 2020 Kitsune Ral SPDX-License-Identifier: LGPL-2.1-or-later -}} -{{>preamble}} +}}{{>preamble}} #pragma once #include "jobs/basejob.h" -- cgit v1.2.3 From 0d4315008374d9a4dfb11f934875b1a16670ec74 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 23 Jun 2021 19:12:38 +0200 Subject: Re-generate API files --- lib/application-service/definitions/protocol.h | 9 +- lib/csapi/account-data.h | 4 +- lib/csapi/admin.h | 5 +- lib/csapi/administrative_contact.h | 65 ++++--- lib/csapi/appservice_room_directory.cpp | 4 +- lib/csapi/appservice_room_directory.h | 14 +- lib/csapi/banning.cpp | 4 +- lib/csapi/banning.h | 10 +- lib/csapi/content-repo.h | 74 +++++--- lib/csapi/create_room.h | 210 ++++++++++----------- lib/csapi/cross_signing.cpp | 34 ++++ lib/csapi/cross_signing.h | 72 +++++++ lib/csapi/definitions/cross_signing_key.h | 47 +++++ lib/csapi/definitions/device_keys.h | 8 +- lib/csapi/definitions/event_filter.h | 8 +- lib/csapi/definitions/openid_token.h | 4 +- lib/csapi/definitions/public_rooms_response.h | 8 + lib/csapi/definitions/push_condition.h | 17 +- lib/csapi/definitions/push_rule.h | 4 +- lib/csapi/definitions/request_email_validation.h | 5 +- lib/csapi/definitions/request_msisdn_validation.h | 5 +- lib/csapi/definitions/request_token_response.h | 6 +- lib/csapi/definitions/room_event_filter.h | 25 +-- lib/csapi/definitions/sync_filter.h | 8 +- lib/csapi/definitions/third_party_signed.h | 2 +- lib/csapi/definitions/user_identifier.h | 5 +- lib/csapi/device_management.h | 11 +- lib/csapi/directory.h | 25 +-- lib/csapi/event_context.h | 34 ++-- lib/csapi/filter.h | 12 +- lib/csapi/inviting.cpp | 4 +- lib/csapi/inviting.h | 16 +- lib/csapi/joining.cpp | 8 +- lib/csapi/joining.h | 42 +++-- lib/csapi/keys.h | 73 +++++-- lib/csapi/kicking.h | 9 +- lib/csapi/knocking.cpp | 28 +++ lib/csapi/knocking.h | 58 ++++++ lib/csapi/leaving.cpp | 15 +- lib/csapi/leaving.h | 11 +- lib/csapi/list_joined_rooms.h | 2 +- lib/csapi/list_public_rooms.h | 22 ++- lib/csapi/login.h | 48 +++-- lib/csapi/logout.h | 11 +- lib/csapi/message_pagination.h | 54 +++--- lib/csapi/notifications.h | 15 +- lib/csapi/openid.h | 14 +- lib/csapi/peeking_events.h | 29 ++- lib/csapi/presence.h | 12 +- lib/csapi/profile.h | 16 +- lib/csapi/pusher.h | 38 ++-- lib/csapi/pushrules.h | 42 +++-- lib/csapi/read_markers.h | 2 +- lib/csapi/receipts.h | 4 +- lib/csapi/redaction.h | 14 +- lib/csapi/registration.h | 147 +++++++-------- lib/csapi/report_content.cpp | 6 +- lib/csapi/report_content.h | 3 +- lib/csapi/room_send.h | 10 +- lib/csapi/room_state.h | 33 ++-- lib/csapi/rooms.h | 28 +-- lib/csapi/search.h | 24 +-- lib/csapi/sso_login_redirect.cpp | 24 +++ lib/csapi/sso_login_redirect.h | 36 +++- lib/csapi/tags.h | 4 +- lib/csapi/third_party_membership.h | 15 +- lib/csapi/to_device.cpp | 2 +- lib/csapi/to_device.h | 2 +- lib/csapi/typing.h | 6 +- lib/csapi/users.h | 9 +- lib/csapi/versions.h | 10 +- lib/csapi/voip.h | 5 +- lib/csapi/wellknown.h | 2 +- lib/csapi/whoami.h | 20 +- .../definitions/request_email_validation.h | 6 +- .../definitions/request_msisdn_validation.h | 8 +- 76 files changed, 1119 insertions(+), 607 deletions(-) create mode 100644 lib/csapi/cross_signing.cpp create mode 100644 lib/csapi/cross_signing.h create mode 100644 lib/csapi/definitions/cross_signing_key.h create mode 100644 lib/csapi/knocking.cpp create mode 100644 lib/csapi/knocking.h diff --git a/lib/application-service/definitions/protocol.h b/lib/application-service/definitions/protocol.h index 6aee9c57..213dbf19 100644 --- a/lib/application-service/definitions/protocol.h +++ b/lib/application-service/definitions/protocol.h @@ -40,7 +40,7 @@ struct ProtocolInstance { /// provided at the higher level Protocol object. QString icon; - /// Preset values for ``fields`` the client may use to search by. + /// Preset values for `fields` the client may use to search by. QJsonObject fields; /// A unique identifier across all instances. @@ -81,10 +81,9 @@ struct ThirdPartyProtocol { /// A content URI representing an icon for the third party protocol. QString icon; - /// The type definitions for the fields defined in the ``user_fields`` and - /// ``location_fields``. Each entry in those arrays MUST have an entry here. - /// The - /// ``string`` key for this object is field name itself. + /// The type definitions for the fields defined in the `user_fields` and + /// `location_fields`. Each entry in those arrays MUST have an entry here. + /// The `string` key for this object is field name itself. /// /// May be an empty object if no fields are defined. QHash fieldTypes; diff --git a/lib/csapi/account-data.h b/lib/csapi/account-data.h index 9a31596f..0c760e80 100644 --- a/lib/csapi/account-data.h +++ b/lib/csapi/account-data.h @@ -12,7 +12,7 @@ namespace Quotient { * * Set some account_data for the client. This config is only visible to the user * that set the account_data. The config will be synced to clients in the - * top-level ``account_data``. + * top-level `account_data`. */ class SetAccountDataJob : public BaseJob { public: @@ -65,7 +65,7 @@ public: * * Set some account_data for the client on a given room. This config is only * visible to the user that set the account_data. The config will be synced to - * clients in the per-room ``account_data``. + * clients in the per-room `account_data`. */ class SetAccountDataPerRoomJob : public BaseJob { public: diff --git a/lib/csapi/admin.h b/lib/csapi/admin.h index 570bf24a..d4fe639b 100644 --- a/lib/csapi/admin.h +++ b/lib/csapi/admin.h @@ -74,7 +74,10 @@ public: // Result properties /// The Matrix user ID of the user. - QString userId() const { return loadFromJson("user_id"_ls); } + QString userId() const + { + return loadFromJson("user_id"_ls); + } /// Each key is an identifier for one of the user's devices. QHash devices() const diff --git a/lib/csapi/administrative_contact.h b/lib/csapi/administrative_contact.h index 1966d533..e436971d 100644 --- a/lib/csapi/administrative_contact.h +++ b/lib/csapi/administrative_contact.h @@ -93,14 +93,14 @@ struct JsonObjectConverter { * * Adds contact information to the user's account. * - * This endpoint is deprecated in favour of the more specific ``/3pid/add`` - * and ``/3pid/bind`` endpoints. + * This endpoint is deprecated in favour of the more specific `/3pid/add` + * and `/3pid/bind` endpoints. * - * .. Note:: - * Previously this endpoint supported a ``bind`` parameter. This parameter - * has been removed, making this endpoint behave as though it was ``false``. - * This results in this endpoint being an equivalent to ``/3pid/bind`` rather - * than dual-purpose. + * **Note:** + * Previously this endpoint supported a `bind` parameter. This parameter + * has been removed, making this endpoint behave as though it was `false`. + * This results in this endpoint being an equivalent to `/3pid/bind` rather + * than dual-purpose. */ class Post3PIDsJob : public BaseJob { public: @@ -144,7 +144,8 @@ struct JsonObjectConverter { /*! \brief Adds contact information to the user's account. * - * This API endpoint uses the `User-Interactive Authentication API`_. + * This API endpoint uses the [User-Interactive Authentication + * API](/client-server-api/#user-interactive-authentication-api). * * Adds contact information to the user's account. Homeservers should use 3PIDs * added through this endpoint for password resets instead of relying on the @@ -206,7 +207,7 @@ public: * Removes a third party identifier from the user's account. This might not * cause an unbind of the identifier from the identity server. * - * Unlike other endpoints, this endpoint does not take an ``id_access_token`` + * Unlike other endpoints, this endpoint does not take an `id_access_token` * parameter because the homeserver is expected to sign the request to the * identity server instead. */ @@ -222,9 +223,9 @@ public: * * \param idServer * The identity server to unbind from. If not provided, the homeserver - * MUST use the ``id_server`` the identifier was added through. If the - * homeserver does not know the original ``id_server``, it MUST return - * a ``id_server_unbind_result`` of ``no-support``. + * MUST use the `id_server` the identifier was added through. If the + * homeserver does not know the original `id_server`, it MUST return + * a `id_server_unbind_result` of `no-support`. */ explicit Delete3pidFromAccountJob(const QString& medium, const QString& address, @@ -233,8 +234,8 @@ public: // Result properties /// An indicator as to whether or not the homeserver was able to unbind - /// the 3PID from the identity server. ``success`` indicates that the - /// indentity server has unbound the identifier whereas ``no-support`` + /// the 3PID from the identity server. `success` indicates that the + /// indentity server has unbound the identifier whereas `no-support` /// indicates that the identity server refuses to support the request /// or the homeserver was not able to determine an identity server to /// unbind from. @@ -249,7 +250,7 @@ public: * Removes a user's third party identifier from the provided identity server * without removing it from the homeserver. * - * Unlike other endpoints, this endpoint does not take an ``id_access_token`` + * Unlike other endpoints, this endpoint does not take an `id_access_token` * parameter because the homeserver is expected to sign the request to the * identity server instead. */ @@ -265,9 +266,9 @@ public: * * \param idServer * The identity server to unbind from. If not provided, the homeserver - * MUST use the ``id_server`` the identifier was added through. If the - * homeserver does not know the original ``id_server``, it MUST return - * a ``id_server_unbind_result`` of ``no-support``. + * MUST use the `id_server` the identifier was added through. If the + * homeserver does not know the original `id_server`, it MUST return + * a `id_server_unbind_result` of `no-support`. */ explicit Unbind3pidFromAccountJob(const QString& medium, const QString& address, @@ -276,8 +277,8 @@ public: // Result properties /// An indicator as to whether or not the identity server was able to unbind - /// the 3PID. ``success`` indicates that the identity server has unbound the - /// identifier whereas ``no-support`` indicates that the identity server + /// the 3PID. `success` indicates that the identity server has unbound the + /// identifier whereas `no-support` indicates that the identity server /// refuses to support the request or the homeserver was not able to /// determine an identity server to unbind from. QString idServerUnbindResult() const @@ -293,7 +294,9 @@ public: * already associated with an account on this homeserver. This API should * be used to request validation tokens when adding an email address to an * account. This API's parameters and response are identical to that of - * the |/register/email/requestToken|_ endpoint. The homeserver should validate + * the + * [`/register/email/requestToken`](/client-server-api/#post_matrixclientr0registeremailrequesttoken) + * endpoint. The homeserver should validate * the email itself, either by sending a validation email itself or by using * a service it has control over. */ @@ -307,9 +310,11 @@ public: * already associated with an account on this homeserver. This API should * be used to request validation tokens when adding an email address to an * account. This API's parameters and response are identical to that of - * the |/register/email/requestToken|_ endpoint. The homeserver should - * validate the email itself, either by sending a validation email itself or - * by using a service it has control over. + * the + * [`/register/email/requestToken`](/client-server-api/#post_matrixclientr0registeremailrequesttoken) + * endpoint. The homeserver should validate + * the email itself, either by sending a validation email itself or by + * using a service it has control over. */ explicit RequestTokenTo3PIDEmailJob(const EmailValidationData& body); @@ -331,7 +336,9 @@ public: * already associated with an account on this homeserver. This API should * be used to request validation tokens when adding a phone number to an * account. This API's parameters and response are identical to that of - * the |/register/msisdn/requestToken|_ endpoint. The homeserver should validate + * the + * [`/register/msisdn/requestToken`](/client-server-api/#post_matrixclientr0registermsisdnrequesttoken) + * endpoint. The homeserver should validate * the phone number itself, either by sending a validation message itself or by * using a service it has control over. */ @@ -345,9 +352,11 @@ public: * already associated with an account on this homeserver. This API should * be used to request validation tokens when adding a phone number to an * account. This API's parameters and response are identical to that of - * the |/register/msisdn/requestToken|_ endpoint. The homeserver should - * validate the phone number itself, either by sending a validation message - * itself or by using a service it has control over. + * the + * [`/register/msisdn/requestToken`](/client-server-api/#post_matrixclientr0registermsisdnrequesttoken) + * endpoint. The homeserver should validate + * the phone number itself, either by sending a validation message itself + * or by using a service it has control over. */ explicit RequestTokenTo3PIDMSISDNJob(const MsisdnValidationData& body); diff --git a/lib/csapi/appservice_room_directory.cpp b/lib/csapi/appservice_room_directory.cpp index e8ec55bf..4d87e4af 100644 --- a/lib/csapi/appservice_room_directory.cpp +++ b/lib/csapi/appservice_room_directory.cpp @@ -8,10 +8,10 @@ using namespace Quotient; -UpdateAppserviceRoomDirectoryVsibilityJob::UpdateAppserviceRoomDirectoryVsibilityJob( +UpdateAppserviceRoomDirectoryVisibilityJob::UpdateAppserviceRoomDirectoryVisibilityJob( const QString& networkId, const QString& roomId, const QString& visibility) : BaseJob(HttpVerb::Put, - QStringLiteral("UpdateAppserviceRoomDirectoryVsibilityJob"), + QStringLiteral("UpdateAppserviceRoomDirectoryVisibilityJob"), QStringLiteral("/_matrix/client/r0") % "/directory/list/appservice/" % networkId % "/" % roomId) { diff --git a/lib/csapi/appservice_room_directory.h b/lib/csapi/appservice_room_directory.h index 3fa02a07..56a69592 100644 --- a/lib/csapi/appservice_room_directory.h +++ b/lib/csapi/appservice_room_directory.h @@ -17,11 +17,11 @@ namespace Quotient { * This API is similar to the room directory visibility API used by clients * to update the homeserver's more general room directory. * - * This API requires the use of an application service access token - * (``as_token``) instead of a typical client's access_token. This API cannot be - * invoked by users who are not identified as application services. + * This API requires the use of an application service access token (`as_token`) + * instead of a typical client's access_token. This API cannot be invoked by + * users who are not identified as application services. */ -class UpdateAppserviceRoomDirectoryVsibilityJob : public BaseJob { +class UpdateAppserviceRoomDirectoryVisibilityJob : public BaseJob { public: /*! \brief Updates a room's visibility in the application service's room * directory. @@ -38,9 +38,9 @@ public: * Whether the room should be visible (public) in the directory * or not (private). */ - explicit UpdateAppserviceRoomDirectoryVsibilityJob(const QString& networkId, - const QString& roomId, - const QString& visibility); + explicit UpdateAppserviceRoomDirectoryVisibilityJob( + const QString& networkId, const QString& roomId, + const QString& visibility); }; } // namespace Quotient diff --git a/lib/csapi/banning.cpp b/lib/csapi/banning.cpp index 8a8ec664..8e0add1a 100644 --- a/lib/csapi/banning.cpp +++ b/lib/csapi/banning.cpp @@ -19,12 +19,14 @@ BanJob::BanJob(const QString& roomId, const QString& userId, setRequestData(std::move(_data)); } -UnbanJob::UnbanJob(const QString& roomId, const QString& userId) +UnbanJob::UnbanJob(const QString& roomId, const QString& userId, + const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("UnbanJob"), QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/unban") { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); + addParam(_data, QStringLiteral("reason"), reason); setRequestData(std::move(_data)); } diff --git a/lib/csapi/banning.h b/lib/csapi/banning.h index 37ae91ee..7a9697d3 100644 --- a/lib/csapi/banning.h +++ b/lib/csapi/banning.h @@ -30,7 +30,8 @@ public: * * \param reason * The reason the user has been banned. This will be supplied as the - * ``reason`` on the target's updated `m.room.member`_ event. + * `reason` on the target's updated + * [`m.room.member`](/client-server-api/#mroommember) event. */ explicit BanJob(const QString& roomId, const QString& userId, const QString& reason = {}); @@ -54,8 +55,13 @@ public: * * \param userId * The fully qualified user ID of the user being unbanned. + * + * \param reason + * Optional reason to be included as the `reason` on the subsequent + * membership event. */ - explicit UnbanJob(const QString& roomId, const QString& userId); + explicit UnbanJob(const QString& roomId, const QString& userId, + const QString& reason = {}); }; } // namespace Quotient diff --git a/lib/csapi/content-repo.h b/lib/csapi/content-repo.h index ed67485c..a41453b2 100644 --- a/lib/csapi/content-repo.h +++ b/lib/csapi/content-repo.h @@ -32,7 +32,8 @@ public: // Result properties - /// The `MXC URI`_ to the uploaded content. + /// The [MXC URI](/client-server-api/#matrix-content-mxc-uris) to the + /// uploaded content. QString contentUri() const { return loadFromJson("content_uri"_ls); @@ -47,10 +48,10 @@ public: /*! \brief Download content from the content repository. * * \param serverName - * The server name from the ``mxc://`` URI (the authoritory component) + * The server name from the `mxc://` URI (the authoritory component) * * \param mediaId - * The media ID from the ``mxc://`` URI (the path component) + * The media ID from the `mxc://` URI (the path component) * * \param allowRemote * Indicates to the server that it should not attempt to fetch the media @@ -71,7 +72,10 @@ public: // Result properties /// The content type of the file that was previously uploaded. - QString contentType() const { return reply()->rawHeader("Content-Type"); } + QString contentType() const + { + return reply()->rawHeader("Content-Type"); + } /// The name of the file that was previously uploaded, if set. QString contentDisposition() const @@ -80,7 +84,10 @@ public: } /// The content that was previously uploaded. - QIODevice* data() { return reply(); } + QIODevice* data() + { + return reply(); + } }; /*! \brief Download content from the content repository overriding the file name @@ -95,13 +102,13 @@ public: * name * * \param serverName - * The server name from the ``mxc://`` URI (the authoritory component) + * The server name from the `mxc://` URI (the authoritory component) * * \param mediaId - * The media ID from the ``mxc://`` URI (the path component) + * The media ID from the `mxc://` URI (the path component) * * \param fileName - * A filename to give in the ``Content-Disposition`` header. + * A filename to give in the `Content-Disposition` header. * * \param allowRemote * Indicates to the server that it should not attempt to fetch the media @@ -125,9 +132,12 @@ public: // Result properties /// The content type of the file that was previously uploaded. - QString contentType() const { return reply()->rawHeader("Content-Type"); } + QString contentType() const + { + return reply()->rawHeader("Content-Type"); + } - /// The ``fileName`` requested or the name of the file that was previously + /// The `fileName` requested or the name of the file that was previously /// uploaded, if set. QString contentDisposition() const { @@ -135,23 +145,27 @@ public: } /// The content that was previously uploaded. - QIODevice* data() { return reply(); } + QIODevice* data() + { + return reply(); + } }; /*! \brief Download a thumbnail of content from the content repository * * Download a thumbnail of content from the content repository. - * See the `thumbnailing <#thumbnails>`_ section for more information. + * See the [Thumbnails](/client-server-api/#thumbnails) section for more + * information. */ class GetContentThumbnailJob : public BaseJob { public: /*! \brief Download a thumbnail of content from the content repository * * \param serverName - * The server name from the ``mxc://`` URI (the authoritory component) + * The server name from the `mxc://` URI (the authoritory component) * * \param mediaId - * The media ID from the ``mxc://`` URI (the path component) + * The media ID from the `mxc://` URI (the path component) * * \param width * The *desired* width of the thumbnail. The actual thumbnail may be @@ -162,8 +176,8 @@ public: * larger than the size specified. * * \param method - * The desired resizing method. See the `thumbnailing <#thumbnails>`_ - * section for more information. + * The desired resizing method. See the + * [Thumbnails](/client-server-api/#thumbnails) section for more information. * * \param allowRemote * Indicates to the server that it should not attempt to fetch @@ -188,10 +202,16 @@ public: // Result properties /// The content type of the thumbnail. - QString contentType() const { return reply()->rawHeader("Content-Type"); } + QString contentType() const + { + return reply()->rawHeader("Content-Type"); + } /// A thumbnail of the requested content. - QIODevice* data() { return reply(); } + QIODevice* data() + { + return reply(); + } }; /*! \brief Get information about a URL for a client @@ -199,11 +219,11 @@ public: * Get information about a URL for the client. Typically this is called when a * client sees a URL in a message and wants to render a preview for the user. * - * .. Note:: - * Clients should consider avoiding this endpoint for URLs posted in encrypted - * rooms. Encrypted rooms often contain more sensitive information the users - * do not want to share with the homeserver, and this can mean that the URLs - * being shared should also not be shared with the homeserver. + * **Note:** + * Clients should consider avoiding this endpoint for URLs posted in encrypted + * rooms. Encrypted rooms often contain more sensitive information the users + * do not want to share with the homeserver, and this can mean that the URLs + * being shared should also not be shared with the homeserver. */ class GetUrlPreviewJob : public BaseJob { public: @@ -235,8 +255,12 @@ public: return loadFromJson>("matrix:image:size"_ls); } - /// An `MXC URI`_ to the image. Omitted if there is no image. - QString ogImage() const { return loadFromJson("og:image"_ls); } + /// An [MXC URI](/client-server-api/#matrix-content-mxc-uris) to the image. + /// Omitted if there is no image. + QString ogImage() const + { + return loadFromJson("og:image"_ls); + } }; /*! \brief Get the configuration for the content repository. diff --git a/lib/csapi/create_room.h b/lib/csapi/create_room.h index 6a718ff4..8c6af7d4 100644 --- a/lib/csapi/create_room.h +++ b/lib/csapi/create_room.h @@ -16,47 +16,42 @@ namespace Quotient { * the new room, including checking power levels for each event. It MUST * apply the events implied by the request in the following order: * - * 1. The ``m.room.create`` event itself. Must be the first event in the + * 1. The `m.room.create` event itself. Must be the first event in the * room. * - * 2. An ``m.room.member`` event for the creator to join the room. This is + * 2. An `m.room.member` event for the creator to join the room. This is * needed so the remaining events can be sent. * - * 3. A default ``m.room.power_levels`` event, giving the room creator + * 3. A default `m.room.power_levels` event, giving the room creator * (and not other members) permission to send state events. Overridden - * by the ``power_level_content_override`` parameter. + * by the `power_level_content_override` parameter. * - * 4. Events set by the ``preset``. Currently these are the - * ``m.room.join_rules``, - * ``m.room.history_visibility``, and ``m.room.guest_access`` state events. + * 4. Events set by the `preset`. Currently these are the `m.room.join_rules`, + * `m.room.history_visibility`, and `m.room.guest_access` state events. * - * 5. Events listed in ``initial_state``, in the order that they are + * 5. Events listed in `initial_state`, in the order that they are * listed. * - * 6. Events implied by ``name`` and ``topic`` (``m.room.name`` and - * ``m.room.topic`` state events). + * 6. Events implied by `name` and `topic` (`m.room.name` and `m.room.topic` + * state events). * - * 7. Invite events implied by ``invite`` and ``invite_3pid`` (``m.room.member`` - * with - * ``membership: invite`` and ``m.room.third_party_invite``). + * 7. Invite events implied by `invite` and `invite_3pid` (`m.room.member` with + * `membership: invite` and `m.room.third_party_invite`). * * The available presets do the following with respect to room state: * - * ======================== ============== ====================== - * ================ ========= Preset ``join_rules`` - * ``history_visibility`` ``guest_access`` Other - * ======================== ============== ====================== - * ================ ========= - * ``private_chat`` ``invite`` ``shared`` ``can_join`` - * ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` All - * invitees are given the same power level as the room creator. - * ``public_chat`` ``public`` ``shared`` ``forbidden`` - * ======================== ============== ====================== - * ================ ========= + * | Preset | `join_rules` | `history_visibility` | + * `guest_access` | Other | + * |------------------------|--------------|----------------------|----------------|-------| + * | `private_chat` | `invite` | `shared` | `can_join` + * | | | `trusted_private_chat` | `invite` | `shared` | + * `can_join` | All invitees are given the same power level as the room + * creator. | | `public_chat` | `public` | `shared` | + * `forbidden` | | * - * The server will create a ``m.room.create`` event in the room with the + * The server will create a `m.room.create` event in the room with the * requesting user as the creator, alongside other keys provided in the - * ``creation_content``. + * `creation_content`. */ class CreateRoomJob : public BaseJob { public: @@ -68,50 +63,44 @@ public: /// the new room, including checking power levels for each event. It MUST /// apply the events implied by the request in the following order: /// - /// 1. The ``m.room.create`` event itself. Must be the first event in the + /// 1. The `m.room.create` event itself. Must be the first event in the /// room. /// - /// 2. An ``m.room.member`` event for the creator to join the room. This is + /// 2. An `m.room.member` event for the creator to join the room. This is /// needed so the remaining events can be sent. /// - /// 3. A default ``m.room.power_levels`` event, giving the room creator + /// 3. A default `m.room.power_levels` event, giving the room creator /// (and not other members) permission to send state events. Overridden - /// by the ``power_level_content_override`` parameter. + /// by the `power_level_content_override` parameter. /// - /// 4. Events set by the ``preset``. Currently these are the - /// ``m.room.join_rules``, - /// ``m.room.history_visibility``, and ``m.room.guest_access`` state - /// events. + /// 4. Events set by the `preset`. Currently these are the + /// `m.room.join_rules`, + /// `m.room.history_visibility`, and `m.room.guest_access` state events. /// - /// 5. Events listed in ``initial_state``, in the order that they are + /// 5. Events listed in `initial_state`, in the order that they are /// listed. /// - /// 6. Events implied by ``name`` and ``topic`` (``m.room.name`` and - /// ``m.room.topic`` + /// 6. Events implied by `name` and `topic` (`m.room.name` and `m.room.topic` /// state events). /// - /// 7. Invite events implied by ``invite`` and ``invite_3pid`` - /// (``m.room.member`` with - /// ``membership: invite`` and ``m.room.third_party_invite``). + /// 7. Invite events implied by `invite` and `invite_3pid` (`m.room.member` + /// with + /// `membership: invite` and `m.room.third_party_invite`). /// /// The available presets do the following with respect to room state: /// - /// ======================== ============== ====================== - /// ================ ========= - /// Preset ``join_rules`` ``history_visibility`` - /// ``guest_access`` Other - /// ======================== ============== ====================== - /// ================ ========= - /// ``private_chat`` ``invite`` ``shared`` ``can_join`` - /// ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` All - /// invitees are given the same power level as the room creator. - /// ``public_chat`` ``public`` ``shared`` ``forbidden`` - /// ======================== ============== ====================== - /// ================ ========= + /// | Preset | `join_rules` | `history_visibility` | + /// `guest_access` | Other | + /// |------------------------|--------------|----------------------|----------------|-------| + /// | `private_chat` | `invite` | `shared` | + /// `can_join` | | | `trusted_private_chat` | `invite` | + /// `shared` | `can_join` | All invitees are given the same + /// power level as the room creator. | | `public_chat` | `public` + /// | `shared` | `forbidden` | | /// - /// The server will create a ``m.room.create`` event in the room with the + /// The server will create a `m.room.create` event in the room with the /// requesting user as the creator, alongside other keys provided in the - /// ``creation_content``. + /// `creation_content`. struct Invite3pid { /// The hostname+port of the identity server which should be used for /// third party identifier lookups. @@ -121,7 +110,7 @@ public: /// r0.5-compatible clients and this specification version. QString idAccessToken; /// The kind of address being passed in the address field, for example - /// ``email``. + /// `email`. QString medium; /// The invitee's third party identifier. QString address; @@ -133,50 +122,44 @@ public: /// the new room, including checking power levels for each event. It MUST /// apply the events implied by the request in the following order: /// - /// 1. The ``m.room.create`` event itself. Must be the first event in the + /// 1. The `m.room.create` event itself. Must be the first event in the /// room. /// - /// 2. An ``m.room.member`` event for the creator to join the room. This is + /// 2. An `m.room.member` event for the creator to join the room. This is /// needed so the remaining events can be sent. /// - /// 3. A default ``m.room.power_levels`` event, giving the room creator + /// 3. A default `m.room.power_levels` event, giving the room creator /// (and not other members) permission to send state events. Overridden - /// by the ``power_level_content_override`` parameter. + /// by the `power_level_content_override` parameter. /// - /// 4. Events set by the ``preset``. Currently these are the - /// ``m.room.join_rules``, - /// ``m.room.history_visibility``, and ``m.room.guest_access`` state - /// events. + /// 4. Events set by the `preset`. Currently these are the + /// `m.room.join_rules`, + /// `m.room.history_visibility`, and `m.room.guest_access` state events. /// - /// 5. Events listed in ``initial_state``, in the order that they are + /// 5. Events listed in `initial_state`, in the order that they are /// listed. /// - /// 6. Events implied by ``name`` and ``topic`` (``m.room.name`` and - /// ``m.room.topic`` + /// 6. Events implied by `name` and `topic` (`m.room.name` and `m.room.topic` /// state events). /// - /// 7. Invite events implied by ``invite`` and ``invite_3pid`` - /// (``m.room.member`` with - /// ``membership: invite`` and ``m.room.third_party_invite``). + /// 7. Invite events implied by `invite` and `invite_3pid` (`m.room.member` + /// with + /// `membership: invite` and `m.room.third_party_invite`). /// /// The available presets do the following with respect to room state: /// - /// ======================== ============== ====================== - /// ================ ========= - /// Preset ``join_rules`` ``history_visibility`` - /// ``guest_access`` Other - /// ======================== ============== ====================== - /// ================ ========= - /// ``private_chat`` ``invite`` ``shared`` ``can_join`` - /// ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` All - /// invitees are given the same power level as the room creator. - /// ``public_chat`` ``public`` ``shared`` ``forbidden`` - /// ======================== ============== ====================== - /// ================ ========= + /// | Preset | `join_rules` | `history_visibility` | + /// `guest_access` | Other | + /// |------------------------|--------------|----------------------|----------------|-------| + /// | `private_chat` | `invite` | `shared` | + /// `can_join` | | | `trusted_private_chat` | `invite` | + /// `shared` | `can_join` | All invitees are given the same + /// power level as the room creator. | | `public_chat` | `public` + /// | `shared` | `forbidden` | | /// - /// The server will create a ``m.room.create`` event in the room with the + /// The server will create a `m.room.create` event in the room with the /// requesting user as the creator, alongside other keys provided in the - /// ``creation_content``. + /// `creation_content`. struct StateEvent { /// The type of event to send. QString type; @@ -191,12 +174,12 @@ public: /*! \brief Create a new room * * \param visibility - * A ``public`` visibility indicates that the room will be shown - * in the published room list. A ``private`` visibility will hide + * A `public` visibility indicates that the room will be shown + * in the published room list. A `private` visibility will hide * the room from the published room list. Rooms default to - * ``private`` visibility if this key is not included. NB: This - * should not be confused with ``join_rules`` which also uses the - * word ``public``. + * `private` visibility if this key is not included. NB: This + * should not be confused with `join_rules` which also uses the + * word `public`. * * \param roomAliasName * The desired room alias **local part**. If this is included, a @@ -204,20 +187,20 @@ public: * room. The alias will belong on the *same* homeserver which * created the room. For example, if this was set to "foo" and * sent to the homeserver "example.com" the complete room alias - * would be ``#foo:example.com``. + * would be `#foo:example.com`. * * The complete room alias will become the canonical alias for * the room. * * \param name - * If this is included, an ``m.room.name`` event will be sent + * If this is included, an `m.room.name` event will be sent * into the room to indicate the name of the room. See Room - * Events for more information on ``m.room.name``. + * Events for more information on `m.room.name`. * * \param topic - * If this is included, an ``m.room.topic`` event will be sent + * If this is included, an `m.room.topic` event will be sent * into the room to indicate the topic for the room. See Room - * Events for more information on ``m.room.topic``. + * Events for more information on `m.room.topic`. * * \param invite * A list of user IDs to invite to the room. This will tell the @@ -230,14 +213,14 @@ public: * \param roomVersion * The room version to set for the room. If not provided, the homeserver * is to use its configured default. If provided, the homeserver will return - * a 400 error with the errcode ``M_UNSUPPORTED_ROOM_VERSION`` if it does - * not support the room version. + * a 400 error with the errcode `M_UNSUPPORTED_ROOM_VERSION` if it does not + * support the room version. * * \param creationContent - * Extra keys, such as ``m.federate``, to be added to the content - * of the `m.room.create`_ event. The server will clobber the following - * keys: ``creator``, ``room_version``. Future versions of the - * specification may allow the server to clobber other keys. + * Extra keys, such as `m.federate`, to be added to the content + * of the [`m.room.create`](client-server-api/#mroomcreate) event. The + * server will clobber the following keys: `creator`, `room_version`. Future + * versions of the specification may allow the server to clobber other keys. * * \param initialState * A list of state events to set in the new room. This allows @@ -245,28 +228,30 @@ public: * room. The expected format of the state events are an object * with type, state_key and content keys set. * - * Takes precedence over events set by ``preset``, but gets - * overriden by ``name`` and ``topic`` keys. + * Takes precedence over events set by `preset`, but gets + * overriden by `name` and `topic` keys. * * \param preset * Convenience parameter for setting various default state events * based on a preset. * - * If unspecified, the server should use the ``visibility`` to determine - * which preset to use. A visbility of ``public`` equates to a preset of - * ``public_chat`` and ``private`` visibility equates to a preset of - * ``private_chat``. + * If unspecified, the server should use the `visibility` to determine + * which preset to use. A visbility of `public` equates to a preset of + * `public_chat` and `private` visibility equates to a preset of + * `private_chat`. * * \param isDirect - * This flag makes the server set the ``is_direct`` flag on the - * ``m.room.member`` events sent to the users in ``invite`` and - * ``invite_3pid``. See `Direct Messaging`_ for more information. + * This flag makes the server set the `is_direct` flag on the + * `m.room.member` events sent to the users in `invite` and + * `invite_3pid`. See [Direct + * Messaging](/client-server-api/#direct-messaging) for more information. * * \param powerLevelContentOverride * The power level content to override in the default power level * event. This object is applied on top of the generated - * `m.room.power_levels`_ event content prior to it being sent to the room. - * Defaults to overriding nothing. + * [`m.room.power_levels`](client-server-api/#mroompower_levels) + * event content prior to it being sent to the room. Defaults to + * overriding nothing. */ explicit CreateRoomJob(const QString& visibility = {}, const QString& roomAliasName = {}, @@ -283,7 +268,10 @@ public: // Result properties /// The created room's ID. - QString roomId() const { return loadFromJson("room_id"_ls); } + QString roomId() const + { + return loadFromJson("room_id"_ls); + } }; template <> diff --git a/lib/csapi/cross_signing.cpp b/lib/csapi/cross_signing.cpp new file mode 100644 index 00000000..9bfc026a --- /dev/null +++ b/lib/csapi/cross_signing.cpp @@ -0,0 +1,34 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#include "cross_signing.h" + +#include + +using namespace Quotient; + +UploadCrossSigningKeysJob::UploadCrossSigningKeysJob( + const Omittable& masterKey, + const Omittable& selfSigningKey, + const Omittable& userSigningKey) + : BaseJob(HttpVerb::Post, QStringLiteral("UploadCrossSigningKeysJob"), + QStringLiteral("/_matrix/client/r0") + % "/keys/device_signing/upload") +{ + QJsonObject _data; + addParam(_data, QStringLiteral("master_key"), masterKey); + addParam(_data, QStringLiteral("self_signing_key"), + selfSigningKey); + addParam(_data, QStringLiteral("user_signing_key"), + userSigningKey); + setRequestData(std::move(_data)); +} + +UploadCrossSigningSignaturesJob::UploadCrossSigningSignaturesJob( + const QHash>& signatures) + : BaseJob(HttpVerb::Post, QStringLiteral("UploadCrossSigningSignaturesJob"), + QStringLiteral("/_matrix/client/r0") % "/keys/signatures/upload") +{ + setRequestData(Data(toJson(signatures))); +} diff --git a/lib/csapi/cross_signing.h b/lib/csapi/cross_signing.h new file mode 100644 index 00000000..2ab65e06 --- /dev/null +++ b/lib/csapi/cross_signing.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "csapi/definitions/cross_signing_key.h" + +#include "jobs/basejob.h" + +namespace Quotient { + +/*! \brief Upload cross-signing keys. + * + * Publishes cross-signing keys for the user. + * + * This API endpoint uses the [User-Interactive Authentication + * API](/client-server-api/#user-interactive-authentication-api). + */ +class UploadCrossSigningKeysJob : public BaseJob { +public: + /*! \brief Upload cross-signing keys. + * + * \param masterKey + * Optional. The user\'s master key. + * + * \param selfSigningKey + * Optional. The user\'s self-signing key. Must be signed by + * the accompanying master key, or by the user\'s most recently + * uploaded master key if no master key is included in the + * request. + * + * \param userSigningKey + * Optional. The user\'s user-signing key. Must be signed by + * the accompanying master key, or by the user\'s most recently + * uploaded master key if no master key is included in the + * request. + */ + explicit UploadCrossSigningKeysJob( + const Omittable& masterKey = none, + const Omittable& selfSigningKey = none, + const Omittable& userSigningKey = none); +}; + +/*! \brief Upload cross-signing signatures. + * + * Publishes cross-signing signatures for the user. The request body is a + * map from user ID to key ID to signed JSON object. + */ +class UploadCrossSigningSignaturesJob : public BaseJob { +public: + /*! \brief Upload cross-signing signatures. + * + * \param signatures + * The signatures to be published. + */ + explicit UploadCrossSigningSignaturesJob( + const QHash>& signatures = {}); + + // Result properties + + /// A map from user ID to key ID to an error for any signatures + /// that failed. If a signature was invalid, the `errcode` will + /// be set to `M_INVALID_SIGNATURE`. + QHash> failures() const + { + return loadFromJson>>( + "failures"_ls); + } +}; + +} // namespace Quotient diff --git a/lib/csapi/definitions/cross_signing_key.h b/lib/csapi/definitions/cross_signing_key.h new file mode 100644 index 00000000..0cec8161 --- /dev/null +++ b/lib/csapi/definitions/cross_signing_key.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "converters.h" + +namespace Quotient { +/// Cross signing key +struct CrossSigningKey { + /// The ID of the user the key belongs to. + QString userId; + + /// What the key is used for. + QStringList usage; + + /// The public key. The object must have exactly one property, whose name + /// is in the form `:`, and whose + /// value is the unpadded base64 public key. + QHash keys; + + /// Signatures of the key, calculated using the process described at + /// [Signing JSON](/appendices/#signing-json). Optional for the master key. + /// Other keys must be signed by the user\'s master key. + QJsonObject signatures; +}; + +template <> +struct JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const CrossSigningKey& pod) + { + addParam<>(jo, QStringLiteral("user_id"), pod.userId); + addParam<>(jo, QStringLiteral("usage"), pod.usage); + addParam<>(jo, QStringLiteral("keys"), pod.keys); + addParam(jo, QStringLiteral("signatures"), pod.signatures); + } + static void fillFrom(const QJsonObject& jo, CrossSigningKey& pod) + { + fromJson(jo.value("user_id"_ls), pod.userId); + fromJson(jo.value("usage"_ls), pod.usage); + fromJson(jo.value("keys"_ls), pod.keys); + fromJson(jo.value("signatures"_ls), pod.signatures); + } +}; + +} // namespace Quotient diff --git a/lib/csapi/definitions/device_keys.h b/lib/csapi/definitions/device_keys.h index 3065f218..84ecefae 100644 --- a/lib/csapi/definitions/device_keys.h +++ b/lib/csapi/definitions/device_keys.h @@ -21,15 +21,15 @@ struct DeviceKeys { QStringList algorithms; /// Public identity keys. The names of the properties should be in the - /// format ``:``. The keys themselves should be + /// format `:`. The keys themselves should be /// encoded as specified by the key algorithm. QHash keys; /// Signatures for the device key object. A map from user ID, to a map from - /// ``:`` to the signature. + /// `:` to the signature. /// - /// The signature is calculated using the process described at `Signing - /// JSON`_. + /// The signature is calculated using the process described at [Signing + /// JSON](/appendices/#signing-json). QHash> signatures; }; diff --git a/lib/csapi/definitions/event_filter.h b/lib/csapi/definitions/event_filter.h index 67497412..c55d4f92 100644 --- a/lib/csapi/definitions/event_filter.h +++ b/lib/csapi/definitions/event_filter.h @@ -14,13 +14,13 @@ struct EventFilter { /// A list of sender IDs to exclude. If this list is absent then no senders /// are excluded. A matching sender will be excluded even if it is listed in - /// the ``'senders'`` filter. + /// the `'senders'` filter. QStringList notSenders; /// A list of event types to exclude. If this list is absent then no event /// types are excluded. A matching type will be excluded even if it is - /// listed in the ``'types'`` filter. A '*' can be used as a wildcard to - /// match any sequence of characters. + /// listed in the `'types'` filter. A '*' can be used as a wildcard to match + /// any sequence of characters. QStringList notTypes; /// A list of senders IDs to include. If this list is absent then all @@ -28,7 +28,7 @@ struct EventFilter { QStringList senders; /// A list of event types to include. If this list is absent then all event - /// types are included. A ``'*'`` can be used as a wildcard to match any + /// types are included. A `'*'` can be used as a wildcard to match any /// sequence of characters. QStringList types; }; diff --git a/lib/csapi/definitions/openid_token.h b/lib/csapi/definitions/openid_token.h index 5e68c376..3c447321 100644 --- a/lib/csapi/definitions/openid_token.h +++ b/lib/csapi/definitions/openid_token.h @@ -11,10 +11,10 @@ namespace Quotient { struct OpenidToken { /// An access token the consumer may use to verify the identity of /// the person who generated the token. This is given to the federation - /// API ``GET /openid/userinfo`` to verify the user's identity. + /// API `GET /openid/userinfo` to verify the user's identity. QString accessToken; - /// The string ``Bearer``. + /// The string `Bearer`. QString tokenType; /// The homeserver domain the consumer should use when attempting to diff --git a/lib/csapi/definitions/public_rooms_response.h b/lib/csapi/definitions/public_rooms_response.h index 8f30e607..34b447d2 100644 --- a/lib/csapi/definitions/public_rooms_response.h +++ b/lib/csapi/definitions/public_rooms_response.h @@ -37,6 +37,12 @@ struct PublicRoomsChunk { /// The URL for the room's avatar, if one is set. QString avatarUrl; + + /// The room's join rule. When not present, the room is assumed to + /// be `public`. Note that rooms with `invite` join rules are not + /// expected here, but rooms with `knock` rules are given their + /// near-public nature. + QString joinRule; }; template <> @@ -54,6 +60,7 @@ struct JsonObjectConverter { addParam<>(jo, QStringLiteral("world_readable"), pod.worldReadable); addParam<>(jo, QStringLiteral("guest_can_join"), pod.guestCanJoin); addParam(jo, QStringLiteral("avatar_url"), pod.avatarUrl); + addParam(jo, QStringLiteral("join_rule"), pod.joinRule); } static void fillFrom(const QJsonObject& jo, PublicRoomsChunk& pod) { @@ -66,6 +73,7 @@ struct JsonObjectConverter { fromJson(jo.value("world_readable"_ls), pod.worldReadable); fromJson(jo.value("guest_can_join"_ls), pod.guestCanJoin); fromJson(jo.value("avatar_url"_ls), pod.avatarUrl); + fromJson(jo.value("join_rule"_ls), pod.joinRule); } }; diff --git a/lib/csapi/definitions/push_condition.h b/lib/csapi/definitions/push_condition.h index a6decf1b..ce66d075 100644 --- a/lib/csapi/definitions/push_condition.h +++ b/lib/csapi/definitions/push_condition.h @@ -9,26 +9,27 @@ namespace Quotient { struct PushCondition { - /// The kind of condition to apply. See `conditions <#conditions>`_ for - /// more information on the allowed kinds and how they work. + /// The kind of condition to apply. See + /// [conditions](/client-server-api/#conditions) for more information on the + /// allowed kinds and how they work. QString kind; - /// Required for ``event_match`` conditions. The dot-separated field of the + /// Required for `event_match` conditions. The dot-separated field of the /// event to match. /// - /// Required for ``sender_notification_permission`` conditions. The field in + /// Required for `sender_notification_permission` conditions. The field in /// the power level event the user needs a minimum power level for. Fields - /// must be specified under the ``notifications`` property in the power - /// level event's ``content``. + /// must be specified under the `notifications` property in the power level + /// event's `content`. QString key; - /// Required for ``event_match`` conditions. The glob-style pattern to + /// Required for `event_match` conditions. The glob-style pattern to /// match against. Patterns with no special glob characters should be /// treated as having asterisks prepended and appended when testing the /// condition. QString pattern; - /// Required for ``room_member_count`` conditions. A decimal integer + /// Required for `room_member_count` conditions. A decimal integer /// optionally prefixed by one of, ==, <, >, >= or <=. A prefix of < matches /// rooms where the member count is strictly less than the given number and /// so forth. If no prefix is present, this parameter defaults to ==. diff --git a/lib/csapi/definitions/push_rule.h b/lib/csapi/definitions/push_rule.h index 43749bae..135537c1 100644 --- a/lib/csapi/definitions/push_rule.h +++ b/lib/csapi/definitions/push_rule.h @@ -25,10 +25,10 @@ struct PushRule { /// The conditions that must hold true for an event in order for a rule to /// be applied to an event. A rule with no conditions always matches. Only - /// applicable to ``underride`` and ``override`` rules. + /// applicable to `underride` and `override` rules. QVector conditions; - /// The glob-style pattern to match against. Only applicable to ``content`` + /// The glob-style pattern to match against. Only applicable to `content` /// rules. QString pattern; }; diff --git a/lib/csapi/definitions/request_email_validation.h b/lib/csapi/definitions/request_email_validation.h index ab34862e..b1781e27 100644 --- a/lib/csapi/definitions/request_email_validation.h +++ b/lib/csapi/definitions/request_email_validation.h @@ -16,15 +16,14 @@ struct EmailValidationData : RequestEmailValidation { /// 3PID verification. /// /// This parameter is deprecated with a plan to be removed in a future - /// specification version for ``/account/password`` and ``/register`` - /// requests. + /// specification version for `/account/password` and `/register` requests. QString idServer; /// An access token previously registered with the identity server. Servers /// can treat this as optional to distinguish between r0.5-compatible /// clients and this specification version. /// - /// Required if an ``id_server`` is supplied. + /// Required if an `id_server` is supplied. QString idAccessToken; }; diff --git a/lib/csapi/definitions/request_msisdn_validation.h b/lib/csapi/definitions/request_msisdn_validation.h index 8539cd98..4600b48c 100644 --- a/lib/csapi/definitions/request_msisdn_validation.h +++ b/lib/csapi/definitions/request_msisdn_validation.h @@ -16,15 +16,14 @@ struct MsisdnValidationData : RequestMsisdnValidation { /// 3PID verification. /// /// This parameter is deprecated with a plan to be removed in a future - /// specification version for ``/account/password`` and ``/register`` - /// requests. + /// specification version for `/account/password` and `/register` requests. QString idServer; /// An access token previously registered with the identity server. Servers /// can treat this as optional to distinguish between r0.5-compatible /// clients and this specification version. /// - /// Required if an ``id_server`` is supplied. + /// Required if an `id_server` is supplied. QString idAccessToken; }; diff --git a/lib/csapi/definitions/request_token_response.h b/lib/csapi/definitions/request_token_response.h index 00222839..f9981100 100644 --- a/lib/csapi/definitions/request_token_response.h +++ b/lib/csapi/definitions/request_token_response.h @@ -10,20 +10,20 @@ namespace Quotient { struct RequestTokenResponse { /// The session ID. Session IDs are opaque strings that must consist - /// entirely of the characters ``[0-9a-zA-Z.=_-]``. Their length must not + /// entirely of the characters `[0-9a-zA-Z.=_-]`. Their length must not /// exceed 255 characters and they must not be empty. QString sid; /// An optional field containing a URL where the client must submit the /// validation token to, with identical parameters to the Identity Service - /// API's ``POST /validate/email/submitToken`` endpoint (without the + /// API's `POST /validate/email/submitToken` endpoint (without the /// requirement for an access token). The homeserver must send this token to /// the user (if applicable), who should then be prompted to provide it to /// the client. /// /// If this field is not present, the client can assume that verification /// will happen without the client's involvement provided the homeserver - /// advertises this specification version in the ``/versions`` response + /// advertises this specification version in the `/versions` response /// (ie: r0.5.0). QString submitUrl; }; diff --git a/lib/csapi/definitions/room_event_filter.h b/lib/csapi/definitions/room_event_filter.h index 11e87fde..91caf667 100644 --- a/lib/csapi/definitions/room_event_filter.h +++ b/lib/csapi/definitions/room_event_filter.h @@ -11,30 +11,31 @@ namespace Quotient { struct RoomEventFilter : EventFilter { - /// If ``true``, enables lazy-loading of membership events. See - /// `Lazy-loading room members <#lazy-loading-room-members>`_ - /// for more information. Defaults to ``false``. + /// If `true`, enables lazy-loading of membership events. See + /// [Lazy-loading room + /// members](/client-server-api/#lazy-loading-room-members) for more + /// information. Defaults to `false`. Omittable lazyLoadMembers; - /// If ``true``, sends all membership events for all events, even if they - /// have already been sent to the client. Does not apply unless - /// ``lazy_load_members`` is ``true``. See `Lazy-loading room members - /// <#lazy-loading-room-members>`_ for more information. Defaults to - /// ``false``. + /// If `true`, sends all membership events for all events, even if they have + /// already been sent to the client. Does not apply unless + /// `lazy_load_members` is `true`. See [Lazy-loading room + /// members](/client-server-api/#lazy-loading-room-members) for more + /// information. Defaults to `false`. Omittable includeRedundantMembers; /// A list of room IDs to exclude. If this list is absent then no rooms are /// excluded. A matching room will be excluded even if it is listed in the - /// ``'rooms'`` filter. + /// `'rooms'` filter. QStringList notRooms; /// A list of room IDs to include. If this list is absent then all rooms are /// included. QStringList rooms; - /// If ``true``, includes only events with a ``url`` key in their content. - /// If ``false``, excludes those events. If omitted, ``url`` key is not - /// considered for filtering. + /// If `true`, includes only events with a `url` key in their content. If + /// `false`, excludes those events. If omitted, `url` key is not considered + /// for filtering. Omittable containsUrl; }; diff --git a/lib/csapi/definitions/sync_filter.h b/lib/csapi/definitions/sync_filter.h index 9c8f08d5..62e17962 100644 --- a/lib/csapi/definitions/sync_filter.h +++ b/lib/csapi/definitions/sync_filter.h @@ -14,13 +14,13 @@ namespace Quotient { struct RoomFilter { /// A list of room IDs to exclude. If this list is absent then no rooms are /// excluded. A matching room will be excluded even if it is listed in the - /// ``'rooms'`` filter. This filter is applied before the filters in - /// ``ephemeral``, ``state``, ``timeline`` or ``account_data`` + /// `'rooms'` filter. This filter is applied before the filters in + /// `ephemeral`, `state`, `timeline` or `account_data` QStringList notRooms; /// A list of room IDs to include. If this list is absent then all rooms are - /// included. This filter is applied before the filters in ``ephemeral``, - /// ``state``, ``timeline`` or ``account_data`` + /// included. This filter is applied before the filters in `ephemeral`, + /// `state`, `timeline` or `account_data` QStringList rooms; /// The events that aren't recorded in the room history, e.g. typing and diff --git a/lib/csapi/definitions/third_party_signed.h b/lib/csapi/definitions/third_party_signed.h index 526545d0..7097bda4 100644 --- a/lib/csapi/definitions/third_party_signed.h +++ b/lib/csapi/definitions/third_party_signed.h @@ -7,7 +7,7 @@ #include "converters.h" namespace Quotient { -/// A signature of an ``m.third_party_invite`` token to prove that this user +/// A signature of an `m.third_party_invite` token to prove that this user /// owns a third party identity which has been invited to the room. struct ThirdPartySigned { /// The Matrix ID of the user who issued the invite. diff --git a/lib/csapi/definitions/user_identifier.h b/lib/csapi/definitions/user_identifier.h index dadf6f97..cb585a6a 100644 --- a/lib/csapi/definitions/user_identifier.h +++ b/lib/csapi/definitions/user_identifier.h @@ -9,8 +9,9 @@ namespace Quotient { /// Identification information for a user struct UserIdentifier { - /// The type of identification. See `Identifier types`_ for supported - /// values and additional property descriptions. + /// The type of identification. See [Identifier + /// types](/client-server-api/#identifier-types) for supported values and + /// additional property descriptions. QString type; /// Identification information for a user diff --git a/lib/csapi/device_management.h b/lib/csapi/device_management.h index 47dc7ec8..e2acea18 100644 --- a/lib/csapi/device_management.h +++ b/lib/csapi/device_management.h @@ -59,7 +59,10 @@ public: // Result properties /// Device information - Device device() const { return fromJson(jsonData()); } + Device device() const + { + return fromJson(jsonData()); + } }; /*! \brief Update a device @@ -83,7 +86,8 @@ public: /*! \brief Delete a device * - * This API endpoint uses the `User-Interactive Authentication API`_. + * This API endpoint uses the [User-Interactive Authentication + * API](/client-server-api/#user-interactive-authentication-api). * * Deletes the given device, and invalidates any access token associated with it. */ @@ -104,7 +108,8 @@ public: /*! \brief Bulk deletion of devices * - * This API endpoint uses the `User-Interactive Authentication API`_. + * This API endpoint uses the [User-Interactive Authentication + * API](/client-server-api/#user-interactive-authentication-api). * * Deletes the given devices, and invalidates any access token associated with * them. diff --git a/lib/csapi/directory.h b/lib/csapi/directory.h index 9b109aad..00215cae 100644 --- a/lib/csapi/directory.h +++ b/lib/csapi/directory.h @@ -51,7 +51,10 @@ public: // Result properties /// The room ID for this room alias. - QString roomId() const { return loadFromJson("room_id"_ls); } + QString roomId() const + { + return loadFromJson("room_id"_ls); + } /// A list of servers that are aware of this room alias. QStringList servers() const @@ -68,13 +71,13 @@ public: * instance that room aliases can only be deleted by their creator or a server * administrator. * - * .. Note:: - * Servers may choose to update the ``alt_aliases`` for the - * ``m.room.canonical_alias`` state event in the room when an alias is removed. + * **Note:** + * Servers may choose to update the `alt_aliases` for the + * `m.room.canonical_alias` state event in the room when an alias is removed. * Servers which choose to update the canonical alias event are recommended to, * in addition to their other relevant permission checks, delete the alias and * return a successful response even if the user does not have permission to - * update the ``m.room.canonical_alias`` event. + * update the `m.room.canonical_alias` event. */ class DeleteRoomAliasJob : public BaseJob { public: @@ -99,18 +102,18 @@ public: * given room. * * This endpoint can be called by users who are in the room (external - * users receive an ``M_FORBIDDEN`` error response). If the room's - * ``m.room.history_visibility`` maps to ``world_readable``, any + * users receive an `M_FORBIDDEN` error response). If the room's + * `m.room.history_visibility` maps to `world_readable`, any * user can call this endpoint. * * Servers may choose to implement additional access control checks here, * such as allowing server administrators to view aliases regardless of * membership. * - * .. Note:: - * Clients are recommended not to display this list of aliases prominently - * as they are not curated, unlike those listed in the - * ``m.room.canonical_alias`` state event. + * **Note:** + * Clients are recommended not to display this list of aliases prominently + * as they are not curated, unlike those listed in the `m.room.canonical_alias` + * state event. */ class GetLocalAliasesJob : public BaseJob { public: diff --git a/lib/csapi/event_context.h b/lib/csapi/event_context.h index d82d16ab..6a49769f 100644 --- a/lib/csapi/event_context.h +++ b/lib/csapi/event_context.h @@ -16,8 +16,8 @@ namespace Quotient { * surrounding an event. * * *Note*: This endpoint supports lazy-loading of room member events. See - * `Lazy-loading room members <#lazy-loading-room-members>`_ for more - * information. + * [Lazy-loading room members](/client-server-api/#lazy-loading-room-members) + * for more information. */ class GetEventContextJob : public BaseJob { public: @@ -33,13 +33,13 @@ public: * The maximum number of events to return. Default: 10. * * \param filter - * A JSON ``RoomEventFilter`` to filter the returned events with. The - * filter is only applied to ``events_before``, ``events_after``, and - * ``state``. It is not applied to the ``event`` itself. The filter may - * be applied before or/and after the ``limit`` parameter - whichever the + * A JSON `RoomEventFilter` to filter the returned events with. The + * filter is only applied to `events_before`, `events_after`, and + * `state`. It is not applied to the `event` itself. The filter may + * be applied before or/and after the `limit` parameter - whichever the * homeserver prefers. * - * See `Filtering <#filtering>`_ for more information. + * See [Filtering](/client-server-api/#filtering) for more information. */ explicit GetEventContextJob(const QString& roomId, const QString& eventId, Omittable limit = none, @@ -58,10 +58,16 @@ public: // Result properties /// A token that can be used to paginate backwards with. - QString begin() const { return loadFromJson("start"_ls); } + QString begin() const + { + return loadFromJson("start"_ls); + } /// A token that can be used to paginate forwards with. - QString end() const { return loadFromJson("end"_ls); } + QString end() const + { + return loadFromJson("end"_ls); + } /// A list of room events that happened just before the /// requested event, in reverse-chronological order. @@ -71,7 +77,10 @@ public: } /// Details of the requested event. - RoomEventPtr event() { return takeFromJson("event"_ls); } + RoomEventPtr event() + { + return takeFromJson("event"_ls); + } /// A list of room events that happened just after the /// requested event, in chronological order. @@ -81,7 +90,10 @@ public: } /// The state of the room at the last event returned. - StateEvents state() { return takeFromJson("state"_ls); } + StateEvents state() + { + return takeFromJson("state"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/filter.h b/lib/csapi/filter.h index f07b489c..7e9e14ee 100644 --- a/lib/csapi/filter.h +++ b/lib/csapi/filter.h @@ -32,10 +32,13 @@ public: // Result properties /// The ID of the filter that was created. Cannot start - /// with a ``{`` as this character is used to determine + /// with a `{` as this character is used to determine /// if the filter provided is inline JSON or a previously /// declared filter by homeservers on some APIs. - QString filterId() const { return loadFromJson("filter_id"_ls); } + QString filterId() const + { + return loadFromJson("filter_id"_ls); + } }; /*! \brief Download a filter @@ -64,7 +67,10 @@ public: // Result properties /// The filter definition. - Filter filter() const { return fromJson(jsonData()); } + Filter filter() const + { + return fromJson(jsonData()); + } }; } // namespace Quotient diff --git a/lib/csapi/inviting.cpp b/lib/csapi/inviting.cpp index 01620f9e..1e2554f4 100644 --- a/lib/csapi/inviting.cpp +++ b/lib/csapi/inviting.cpp @@ -8,12 +8,14 @@ using namespace Quotient; -InviteUserJob::InviteUserJob(const QString& roomId, const QString& userId) +InviteUserJob::InviteUserJob(const QString& roomId, const QString& userId, + const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("InviteUserJob"), QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/invite") { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); + addParam(_data, QStringLiteral("reason"), reason); setRequestData(std::move(_data)); } diff --git a/lib/csapi/inviting.h b/lib/csapi/inviting.h index 59a61b89..eb13cc95 100644 --- a/lib/csapi/inviting.h +++ b/lib/csapi/inviting.h @@ -9,13 +9,12 @@ namespace Quotient { /*! \brief Invite a user to participate in a particular room. - * - * .. _invite-by-user-id-endpoint: * * *Note that there are two forms of this API, which are documented separately. * This version of the API requires that the inviter knows the Matrix * identifier of the invitee. The other is documented in the* - * `third party invites section`_. + * [third party invites + * section](/client-server-api/#post_matrixclientr0roomsroomidinvite-1). * * This API invites a user to participate in a particular room. * They do not start participating in the room until they actually join the @@ -25,9 +24,7 @@ namespace Quotient { * join that room. * * If the user was invited to the room, the homeserver will append a - * ``m.room.member`` event to the room. - * - * .. _third party invites section: `invite-by-third-party-id-endpoint`_ + * `m.room.member` event to the room. */ class InviteUserJob : public BaseJob { public: @@ -38,8 +35,13 @@ public: * * \param userId * The fully qualified user ID of the invitee. + * + * \param reason + * Optional reason to be included as the `reason` on the subsequent + * membership event. */ - explicit InviteUserJob(const QString& roomId, const QString& userId); + explicit InviteUserJob(const QString& roomId, const QString& userId, + const QString& reason = {}); }; } // namespace Quotient diff --git a/lib/csapi/joining.cpp b/lib/csapi/joining.cpp index 4761e949..998d0b42 100644 --- a/lib/csapi/joining.cpp +++ b/lib/csapi/joining.cpp @@ -9,13 +9,15 @@ using namespace Quotient; JoinRoomByIdJob::JoinRoomByIdJob( - const QString& roomId, const Omittable& thirdPartySigned) + const QString& roomId, const Omittable& thirdPartySigned, + const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("JoinRoomByIdJob"), QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/join") { QJsonObject _data; addParam(_data, QStringLiteral("third_party_signed"), thirdPartySigned); + addParam(_data, QStringLiteral("reason"), reason); setRequestData(std::move(_data)); addExpectedKey("room_id"); } @@ -29,7 +31,8 @@ auto queryToJoinRoom(const QStringList& serverName) JoinRoomJob::JoinRoomJob(const QString& roomIdOrAlias, const QStringList& serverName, - const Omittable& thirdPartySigned) + const Omittable& thirdPartySigned, + const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("JoinRoomJob"), QStringLiteral("/_matrix/client/r0") % "/join/" % roomIdOrAlias, queryToJoinRoom(serverName)) @@ -37,6 +40,7 @@ JoinRoomJob::JoinRoomJob(const QString& roomIdOrAlias, QJsonObject _data; addParam(_data, QStringLiteral("third_party_signed"), thirdPartySigned); + addParam(_data, QStringLiteral("reason"), reason); setRequestData(std::move(_data)); addExpectedKey("room_id"); } diff --git a/lib/csapi/joining.h b/lib/csapi/joining.h index dd936f92..6dcd1351 100644 --- a/lib/csapi/joining.h +++ b/lib/csapi/joining.h @@ -13,7 +13,7 @@ namespace Quotient { /*! \brief Start the requesting user participating in a particular room. * * *Note that this API requires a room ID, not alias.* - * ``/join/{roomIdOrAlias}`` *exists if you have a room alias.* + * `/join/{roomIdOrAlias}` *exists if you have a room alias.* * * This API starts a user participating in a particular room, if that user * is allowed to participate in that room. After this call, the client is @@ -21,7 +21,9 @@ namespace Quotient { * events associated with the room until the user leaves the room. * * After a user has joined a room, the room will appear as an entry in the - * response of the |/initialSync|_ and |/sync|_ APIs. + * response of the + * [`/initialSync`](/client-server-api/#get_matrixclientr0initialsync) and + * [`/sync`](/client-server-api/#get_matrixclientr0sync) APIs. */ class JoinRoomByIdJob : public BaseJob { public: @@ -32,23 +34,31 @@ public: * * \param thirdPartySigned * If supplied, the homeserver must verify that it matches a pending - * ``m.room.third_party_invite`` event in the room, and perform + * `m.room.third_party_invite` event in the room, and perform * key validity checking if required by the event. + * + * \param reason + * Optional reason to be included as the `reason` on the subsequent + * membership event. */ explicit JoinRoomByIdJob( const QString& roomId, - const Omittable& thirdPartySigned = none); + const Omittable& thirdPartySigned = none, + const QString& reason = {}); // Result properties /// The joined room ID. - QString roomId() const { return loadFromJson("room_id"_ls); } + QString roomId() const + { + return loadFromJson("room_id"_ls); + } }; /*! \brief Start the requesting user participating in a particular room. * * *Note that this API takes either a room ID or alias, unlike* - * ``/room/{roomId}/join``. + * `/room/{roomId}/join`. * * This API starts a user participating in a particular room, if that user * is allowed to participate in that room. After this call, the client is @@ -56,7 +66,9 @@ public: * events associated with the room until the user leaves the room. * * After a user has joined a room, the room will appear as an entry in the - * response of the |/initialSync|_ and |/sync|_ APIs. + * response of the + * [`/initialSync`](/client-server-api/#get_matrixclientr0initialsync) and + * [`/sync`](/client-server-api/#get_matrixclientr0sync) APIs. */ class JoinRoomJob : public BaseJob { public: @@ -70,18 +82,26 @@ public: * must be participating in the room. * * \param thirdPartySigned - * If a ``third_party_signed`` was supplied, the homeserver must verify - * that it matches a pending ``m.room.third_party_invite`` event in the + * If a `third_party_signed` was supplied, the homeserver must verify + * that it matches a pending `m.room.third_party_invite` event in the * room, and perform key validity checking if required by the event. + * + * \param reason + * Optional reason to be included as the `reason` on the subsequent + * membership event. */ explicit JoinRoomJob( const QString& roomIdOrAlias, const QStringList& serverName = {}, - const Omittable& thirdPartySigned = none); + const Omittable& thirdPartySigned = none, + const QString& reason = {}); // Result properties /// The joined room ID. - QString roomId() const { return loadFromJson("room_id"_ls); } + QString roomId() const + { + return loadFromJson("room_id"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index 8f6c8cc9..53ba6495 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -4,6 +4,7 @@ #pragma once +#include "csapi/definitions/cross_signing_key.h" #include "csapi/definitions/device_keys.h" #include "jobs/basejob.h" @@ -25,8 +26,8 @@ public: * \param oneTimeKeys * One-time public keys for "pre-key" messages. The names of * the properties should be in the format - * ``:``. The format of the key is determined - * by the `key algorithm <#key-algorithms>`_. + * `:`. The format of the key is determined + * by the [key algorithm](/client-server-api/#key-algorithms). * * May be absent if no new one-time keys are required. */ @@ -98,7 +99,7 @@ public: /// /// If the homeserver could be reached, but the user or device /// was unknown, no failure is recorded. Instead, the corresponding - /// user or device is missing from the ``device_keys`` result. + /// user or device is missing from the `device_keys` result. QHash failures() const { return loadFromJson>("failures"_ls); @@ -107,13 +108,45 @@ public: /// Information on the queried devices. A map from user ID, to a /// map from device ID to device information. For each device, /// the information returned will be the same as uploaded via - /// ``/keys/upload``, with the addition of an ``unsigned`` + /// `/keys/upload`, with the addition of an `unsigned` /// property. QHash> deviceKeys() const { return loadFromJson>>( "device_keys"_ls); } + + /// Information on the master cross-signing keys of the queried users. + /// A map from user ID, to master key information. For each key, the + /// information returned will be the same as uploaded via + /// `/keys/device_signing/upload`, along with the signatures + /// uploaded via `/keys/signatures/upload` that the requesting user + /// is allowed to see. + QHash masterKeys() const + { + return loadFromJson>("master_keys"_ls); + } + + /// Information on the self-signing keys of the queried users. A map + /// from user ID, to self-signing key information. For each key, the + /// information returned will be the same as uploaded via + /// `/keys/device_signing/upload`. + QHash selfSigningKeys() const + { + return loadFromJson>( + "self_signing_keys"_ls); + } + + /// Information on the user-signing key of the user making the + /// request, if they queried their own device information. A map + /// from user ID, to user-signing key information. The + /// information returned will be the same as uploaded via + /// `/keys/device_signing/upload`. + QHash userSigningKeys() const + { + return loadFromJson>( + "user_signing_keys"_ls); + } }; template <> @@ -163,18 +196,17 @@ public: /// /// If the homeserver could be reached, but the user or device /// was unknown, no failure is recorded. Instead, the corresponding - /// user or device is missing from the ``one_time_keys`` result. + /// user or device is missing from the `one_time_keys` result. QHash failures() const { return loadFromJson>("failures"_ls); } /// One-time keys for the queried devices. A map from user ID, to a - /// map from devices to a map from ``:`` to the key - /// object. + /// map from devices to a map from `:` to the key object. /// - /// See the `key algorithms <#key-algorithms>`_ section for information - /// on the Key Object format. + /// See the [key algorithms](/client-server-api/#key-algorithms) section for + /// information on the Key Object format. QHash> oneTimeKeys() const { return loadFromJson>>( @@ -190,26 +222,28 @@ public: * The server should include in the results any users who: * * * currently share a room with the calling user (ie, both users have - * membership state ``join``); *and* + * membership state `join`); *and* * * added new device identity keys or removed an existing device with - * identity keys, between ``from`` and ``to``. + * identity keys, between `from` and `to`. */ class GetKeysChangesJob : public BaseJob { public: /*! \brief Query users with recent device key updates. * * \param from - * The desired start point of the list. Should be the ``next_batch`` field - * from a response to an earlier call to |/sync|. Users who have not + * The desired start point of the list. Should be the `next_batch` field + * from a response to an earlier call to + * [`/sync`](/client-server-api/#get_matrixclientr0sync). Users who have not * uploaded new device identity keys since this point, nor deleted * existing devices with identity keys since then, will be excluded * from the results. * * \param to - * The desired end point of the list. Should be the ``next_batch`` - * field from a recent call to |/sync| - typically the most recent - * such call. This may be used by the server as a hint to check its - * caches are up to date. + * The desired end point of the list. Should be the `next_batch` + * field from a recent call to + * [`/sync`](/client-server-api/#get_matrixclientr0sync) - typically the + * most recent such call. This may be used by the server as a hint to check + * its caches are up to date. */ explicit GetKeysChangesJob(const QString& from, const QString& to); @@ -233,7 +267,10 @@ public: /// The Matrix User IDs of all users who may have left all /// the end-to-end encrypted rooms they previously shared /// with the user. - QStringList left() const { return loadFromJson("left"_ls); } + QStringList left() const + { + return loadFromJson("left"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/kicking.h b/lib/csapi/kicking.h index 2645a54f..11018368 100644 --- a/lib/csapi/kicking.h +++ b/lib/csapi/kicking.h @@ -15,10 +15,10 @@ namespace Quotient { * The caller must have the required power level in order to perform this * operation. * - * Kicking a user adjusts the target member's membership state to be ``leave`` - * with an optional ``reason``. Like with other membership changes, a user can + * Kicking a user adjusts the target member's membership state to be `leave` + * with an optional `reason`. Like with other membership changes, a user can * directly adjust the target member's state by making a request to - * ``/rooms//state/m.room.member/``. + * `/rooms//state/m.room.member/`. */ class KickJob : public BaseJob { public: @@ -32,7 +32,8 @@ public: * * \param reason * The reason the user has been kicked. This will be supplied as the - * ``reason`` on the target's updated `m.room.member`_ event. + * `reason` on the target's updated + * [`m.room.member`](/client-server-api/#mroommember) event. */ explicit KickJob(const QString& roomId, const QString& userId, const QString& reason = {}); diff --git a/lib/csapi/knocking.cpp b/lib/csapi/knocking.cpp new file mode 100644 index 00000000..4b561300 --- /dev/null +++ b/lib/csapi/knocking.cpp @@ -0,0 +1,28 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#include "knocking.h" + +#include + +using namespace Quotient; + +auto queryToKnockRoom(const QStringList& serverName) +{ + BaseJob::Query _q; + addParam(_q, QStringLiteral("server_name"), serverName); + return _q; +} + +KnockRoomJob::KnockRoomJob(const QString& roomIdOrAlias, + const QStringList& serverName, const QString& reason) + : BaseJob(HttpVerb::Post, QStringLiteral("KnockRoomJob"), + QStringLiteral("/_matrix/client/r0") % "/knock/" % roomIdOrAlias, + queryToKnockRoom(serverName)) +{ + QJsonObject _data; + addParam(_data, QStringLiteral("reason"), reason); + setRequestData(std::move(_data)); + addExpectedKey("room_id"); +} diff --git a/lib/csapi/knocking.h b/lib/csapi/knocking.h new file mode 100644 index 00000000..607b55a9 --- /dev/null +++ b/lib/csapi/knocking.h @@ -0,0 +1,58 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "jobs/basejob.h" + +namespace Quotient { + +/*! \brief Knock on a room, requesting permission to join. + * + * *Note that this API takes either a room ID or alias, unlike other membership + * APIs.* + * + * This API "knocks" on the room to ask for permission to join, if the user + * is allowed to knock on the room. Acceptance of the knock happens out of + * band from this API, meaning that the client will have to watch for updates + * regarding the acceptance/rejection of the knock. + * + * If the room history settings allow, the user will still be able to see + * history of the room while being in the "knock" state. The user will have + * to accept the invitation to join the room (acceptance of knock) to see + * messages reliably. See the `/join` endpoints for more information about + * history visibility to the user. + * + * The knock will appear as an entry in the response of the + * [`/sync`](/client-server-api/#get_matrixclientr0sync) API. + */ +class KnockRoomJob : public BaseJob { +public: + /*! \brief Knock on a room, requesting permission to join. + * + * \param roomIdOrAlias + * The room identifier or alias to knock upon. + * + * \param serverName + * The servers to attempt to knock on the room through. One of the servers + * must be participating in the room. + * + * \param reason + * Optional reason to be included as the `reason` on the subsequent + * membership event. + */ + explicit KnockRoomJob(const QString& roomIdOrAlias, + const QStringList& serverName = {}, + const QString& reason = {}); + + // Result properties + + /// The knocked room ID. + QString roomId() const + { + return loadFromJson("room_id"_ls); + } +}; + +} // namespace Quotient diff --git a/lib/csapi/leaving.cpp b/lib/csapi/leaving.cpp index 8bd170bf..f4c5f120 100644 --- a/lib/csapi/leaving.cpp +++ b/lib/csapi/leaving.cpp @@ -8,18 +8,15 @@ using namespace Quotient; -QUrl LeaveRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) -{ - return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/rooms/" % roomId % "/leave"); -} - -LeaveRoomJob::LeaveRoomJob(const QString& roomId) +LeaveRoomJob::LeaveRoomJob(const QString& roomId, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("LeaveRoomJob"), QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/leave") -{} +{ + QJsonObject _data; + addParam(_data, QStringLiteral("reason"), reason); + setRequestData(std::move(_data)); +} QUrl ForgetRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { diff --git a/lib/csapi/leaving.h b/lib/csapi/leaving.h index 1bea7e41..2e402d16 100644 --- a/lib/csapi/leaving.h +++ b/lib/csapi/leaving.h @@ -28,15 +28,12 @@ public: * * \param roomId * The room identifier to leave. - */ - explicit LeaveRoomJob(const QString& roomId); - - /*! \brief Construct a URL without creating a full-fledged job object * - * This function can be used when a URL for LeaveRoomJob - * is necessary but the job itself isn't. + * \param reason + * Optional reason to be included as the `reason` on the subsequent + * membership event. */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); + explicit LeaveRoomJob(const QString& roomId, const QString& reason = {}); }; /*! \brief Stop the requesting user remembering about a particular room. diff --git a/lib/csapi/list_joined_rooms.h b/lib/csapi/list_joined_rooms.h index 1034aa7b..59a24a49 100644 --- a/lib/csapi/list_joined_rooms.h +++ b/lib/csapi/list_joined_rooms.h @@ -26,7 +26,7 @@ public: // Result properties - /// The ID of each room in which the user has ``joined`` membership. + /// The ID of each room in which the user has `joined` membership. QStringList joinedRooms() const { return loadFromJson("joined_rooms"_ls); diff --git a/lib/csapi/list_public_rooms.h b/lib/csapi/list_public_rooms.h index b0cb79d2..1c73c0af 100644 --- a/lib/csapi/list_public_rooms.h +++ b/lib/csapi/list_public_rooms.h @@ -111,12 +111,18 @@ public: /// A pagination token for the response. The absence of this token /// means there are no more results to fetch and the client should /// stop paginating. - QString nextBatch() const { return loadFromJson("next_batch"_ls); } + QString nextBatch() const + { + return loadFromJson("next_batch"_ls); + } /// A pagination token that allows fetching previous results. The /// absence of this token means there are no results before this /// batch, i.e. this is the first batch. - QString prevBatch() const { return loadFromJson("prev_batch"_ls); } + QString prevBatch() const + { + return loadFromJson("prev_batch"_ls); + } /// An estimate on the total number of public rooms, if the /// server has an estimate. @@ -170,7 +176,7 @@ public: * * \param thirdPartyInstanceId * The specific third party network/protocol to request from the - * homeserver. Can only be used if ``include_all_networks`` is false. + * homeserver. Can only be used if `include_all_networks` is false. */ explicit QueryPublicRoomsJob(const QString& server = {}, Omittable limit = none, @@ -190,12 +196,18 @@ public: /// A pagination token for the response. The absence of this token /// means there are no more results to fetch and the client should /// stop paginating. - QString nextBatch() const { return loadFromJson("next_batch"_ls); } + QString nextBatch() const + { + return loadFromJson("next_batch"_ls); + } /// A pagination token that allows fetching previous results. The /// absence of this token means there are no results before this /// batch, i.e. this is the first batch. - QString prevBatch() const { return loadFromJson("prev_batch"_ls); } + QString prevBatch() const + { + return loadFromJson("prev_batch"_ls); + } /// An estimate on the total number of public rooms, if the /// server has an estimate. diff --git a/lib/csapi/login.h b/lib/csapi/login.h index a406fc79..ce783af2 100644 --- a/lib/csapi/login.h +++ b/lib/csapi/login.h @@ -14,17 +14,17 @@ namespace Quotient { /*! \brief Get the supported login types to authenticate users * * Gets the homeserver's supported login types to authenticate users. Clients - * should pick one of these and supply it as the ``type`` when logging in. + * should pick one of these and supply it as the `type` when logging in. */ class GetLoginFlowsJob : public BaseJob { public: // Inner data structures /// Gets the homeserver's supported login types to authenticate users. - /// Clients should pick one of these and supply it as the ``type`` when + /// Clients should pick one of these and supply it as the `type` when /// logging in. struct LoginFlow { - /// The login type. This is supplied as the ``type`` when + /// The login type. This is supplied as the `type` when /// logging in. QString type; }; @@ -64,13 +64,14 @@ struct JsonObjectConverter { * Authenticates the user, and issues an access token they can * use to authorize themself in subsequent requests. * - * If the client does not supply a ``device_id``, the server must + * If the client does not supply a `device_id`, the server must * auto-generate one. * - * The returned access token must be associated with the ``device_id`` + * The returned access token must be associated with the `device_id` * supplied by the client or generated by the server. The server may * invalidate any access token previously associated with that device. See - * `Relationship between access tokens and devices`_. + * [Relationship between access tokens and + * devices](/client-server-api/#relationship-between-access-tokens-and-devices). */ class LoginJob : public BaseJob { public: @@ -83,30 +84,33 @@ public: * Authenticates the user, and issues an access token they can * use to authorize themself in subsequent requests. * - * If the client does not supply a ``device_id``, the server must + * If the client does not supply a `device_id`, the server must * auto-generate one. * - * The returned access token must be associated with the ``device_id`` + * The returned access token must be associated with the `device_id` * supplied by the client or generated by the server. The server may * invalidate any access token previously associated with that device. See - * `Relationship between access tokens and devices`_. + * [Relationship between access tokens and + * devices](/client-server-api/#relationship-between-access-tokens-and-devices). * * \param password - * Required when ``type`` is ``m.login.password``. The user's + * Required when `type` is `m.login.password`. The user's * password. * * \param token - * Required when ``type`` is ``m.login.token``. Part of `Token-based`_ - * login. + * Required when `type` is `m.login.token`. Part of Token-based login. * * \param deviceId * ID of the client device. If this does not correspond to a - * known client device, a new device will be created. The server - * will auto-generate a device_id if this is not specified. + * known client device, a new device will be created. The given + * device ID must not be the same as a + * [cross-signing](/client-server-api/#cross-signing) key ID. + * The server will auto-generate a device_id + * if this is not specified. * * \param initialDeviceDisplayName * A display name to assign to the newly-created device. Ignored - * if ``device_id`` corresponds to a known device. + * if `device_id` corresponds to a known device. */ explicit LoginJob(const QString& type, const Omittable& identifier = none, @@ -117,7 +121,10 @@ public: // Result properties /// The fully-qualified Matrix ID for the account. - QString userId() const { return loadFromJson("user_id"_ls); } + QString userId() const + { + return loadFromJson("user_id"_ls); + } /// An access token for the account. /// This access token can then be used to authorize other requests. @@ -130,8 +137,8 @@ public: /// been registered. /// /// **Deprecated**. Clients should extract the server_name from - /// ``user_id`` (by splitting at the first colon) if they require - /// it. Note also that ``homeserver`` is not spelt this way. + /// `user_id` (by splitting at the first colon) if they require + /// it. Note also that `homeserver` is not spelt this way. QString homeServer() const { return loadFromJson("home_server"_ls); @@ -139,7 +146,10 @@ public: /// ID of the logged-in device. Will be the same as the /// corresponding parameter in the request, if one was specified. - QString deviceId() const { return loadFromJson("device_id"_ls); } + QString deviceId() const + { + return loadFromJson("device_id"_ls); + } /// Optional client configuration provided by the server. If present, /// clients SHOULD use the provided object to reconfigure themselves, diff --git a/lib/csapi/logout.h b/lib/csapi/logout.h index 78f14e40..2e4c2692 100644 --- a/lib/csapi/logout.h +++ b/lib/csapi/logout.h @@ -12,7 +12,8 @@ namespace Quotient { * * Invalidates an existing access token, so that it can no longer be used for * authorization. The device associated with the access token is also deleted. - * `Device keys <#device-keys>`_ for the device are deleted alongside the device. + * [Device keys](/client-server-api/#device-keys) for the device are deleted + * alongside the device. */ class LogoutJob : public BaseJob { public: @@ -31,10 +32,12 @@ public: * * Invalidates all access tokens for a user, so that they can no longer be used * for authorization. This includes the access token that made this request. All - * devices for the user are also deleted. `Device keys <#device-keys>`_ for the - * device are deleted alongside the device. + * devices for the user are also deleted. [Device + * keys](/client-server-api/#device-keys) for the device are deleted alongside + * the device. * - * This endpoint does not use the `User-Interactive Authentication API`_ because + * This endpoint does not use the [User-Interactive Authentication + * API](/client-server-api/#user-interactive-authentication-api) because * User-Interactive Authentication is designed to protect against attacks where * the someone gets hold of a single access token then takes over the account. * This endpoint invalidates all access tokens for the user, including the token diff --git a/lib/csapi/message_pagination.h b/lib/csapi/message_pagination.h index 286d4237..020ef543 100644 --- a/lib/csapi/message_pagination.h +++ b/lib/csapi/message_pagination.h @@ -15,8 +15,8 @@ namespace Quotient { * pagination query parameters to paginate history in the room. * * *Note*: This endpoint supports lazy-loading of room member events. See - * `Lazy-loading room members <#lazy-loading-room-members>`_ for more - * information. + * [Lazy-loading room members](/client-server-api/#lazy-loading-room-members) + * for more information. */ class GetRoomEventsJob : public BaseJob { public: @@ -27,8 +27,8 @@ public: * * \param from * The token to start returning events from. This token can be obtained - * from a ``prev_batch`` token returned for each room by the sync API, - * or from a ``start`` or ``end`` token returned by a previous request + * from a `prev_batch` token returned for each room by the sync API, + * or from a `start` or `end` token returned by a previous request * to this endpoint. * * \param dir @@ -36,8 +36,8 @@ public: * * \param to * The token to stop returning events at. This token can be obtained from - * a ``prev_batch`` token returned for each room by the sync endpoint, - * or from a ``start`` or ``end`` token returned by a previous request to + * a `prev_batch` token returned for each room by the sync endpoint, + * or from a `start` or `end` token returned by a previous request to * this endpoint. * * \param limit @@ -64,29 +64,41 @@ public: // Result properties - /// The token the pagination starts from. If ``dir=b`` this will be - /// the token supplied in ``from``. - QString begin() const { return loadFromJson("start"_ls); } + /// The token the pagination starts from. If `dir=b` this will be + /// the token supplied in `from`. + QString begin() const + { + return loadFromJson("start"_ls); + } - /// The token the pagination ends at. If ``dir=b`` this token should + /// The token the pagination ends at. If `dir=b` this token should /// be used again to request even earlier events. - QString end() const { return loadFromJson("end"_ls); } + QString end() const + { + return loadFromJson("end"_ls); + } - /// A list of room events. The order depends on the ``dir`` parameter. - /// For ``dir=b`` events will be in reverse-chronological order, - /// for ``dir=f`` in chronological order, so that events start - /// at the ``from`` point. - RoomEvents chunk() { return takeFromJson("chunk"_ls); } + /// A list of room events. The order depends on the `dir` parameter. + /// For `dir=b` events will be in reverse-chronological order, + /// for `dir=f` in chronological order, so that events start + /// at the `from` point. + RoomEvents chunk() + { + return takeFromJson("chunk"_ls); + } - /// A list of state events relevant to showing the ``chunk``. For example, if - /// ``lazy_load_members`` is enabled in the filter then this may contain - /// the membership events for the senders of events in the ``chunk``. + /// A list of state events relevant to showing the `chunk`. For example, if + /// `lazy_load_members` is enabled in the filter then this may contain + /// the membership events for the senders of events in the `chunk`. /// - /// Unless ``include_redundant_members`` is ``true``, the server + /// Unless `include_redundant_members` is `true`, the server /// may remove membership events which would have already been /// sent to the client in prior calls to this endpoint, assuming /// the membership of those members has not changed. - StateEvents state() { return takeFromJson("state"_ls); } + StateEvents state() + { + return takeFromJson("state"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/notifications.h b/lib/csapi/notifications.h index ff499c7a..0cc165ce 100644 --- a/lib/csapi/notifications.h +++ b/lib/csapi/notifications.h @@ -22,7 +22,7 @@ public: /// user has been, or would have been notified about. struct Notification { /// The action(s) to perform when the conditions for this rule are met. - /// See `Push Rules: API`_. + /// See [Push Rules: API](/client-server-api/#push-rules-api). QVector actions; /// The Event object for the event that triggered the notification. EventPtr event; @@ -35,7 +35,7 @@ public: QString roomId; /// The unix timestamp at which the event notification was sent, /// in milliseconds. - int ts; + qint64 ts; }; // Construction/destruction @@ -49,7 +49,7 @@ public: * Limit on the number of events to return in this request. * * \param only - * Allows basic filtering of events returned. Supply ``highlight`` + * Allows basic filtering of events returned. Supply `highlight` * to return only events where the notification had the highlight * tweak set. */ @@ -68,10 +68,13 @@ public: // Result properties - /// The token to supply in the ``from`` param of the next - /// ``/notifications`` request in order to request more + /// The token to supply in the `from` param of the next + /// `/notifications` request in order to request more /// events. If this is absent, there are no more results. - QString nextToken() const { return loadFromJson("next_token"_ls); } + QString nextToken() const + { + return loadFromJson("next_token"_ls); + } /// The list of events that triggered notifications. std::vector notifications() diff --git a/lib/csapi/openid.h b/lib/csapi/openid.h index efb5f623..88218c20 100644 --- a/lib/csapi/openid.h +++ b/lib/csapi/openid.h @@ -18,7 +18,7 @@ namespace Quotient { * OpenID. * * The access token generated is only valid for the OpenID API. It cannot - * be used to request another OpenID access token or call ``/sync``, for + * be used to request another OpenID access token or call `/sync`, for * example. */ class RequestOpenIdTokenJob : public BaseJob { @@ -38,11 +38,15 @@ public: // Result properties /// OpenID token information. This response is nearly compatible with the - /// response documented in the `OpenID Connect 1.0 Specification - /// `_ - /// with the only difference being the lack of an ``id_token``. Instead, + /// response documented in the + /// [OpenID Connect 1.0 + /// Specification](http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse) + /// with the only difference being the lack of an `id_token`. Instead, /// the Matrix homeserver's name is provided. - OpenidToken tokenData() const { return fromJson(jsonData()); } + OpenidToken tokenData() const + { + return fromJson(jsonData()); + } }; } // namespace Quotient diff --git a/lib/csapi/peeking_events.h b/lib/csapi/peeking_events.h index cecd9f2d..1eee880f 100644 --- a/lib/csapi/peeking_events.h +++ b/lib/csapi/peeking_events.h @@ -13,12 +13,12 @@ namespace Quotient { * * This will listen for new events related to a particular room and return * them to the caller. This will block until an event is received, or until - * the ``timeout`` is reached. + * the `timeout` is reached. * - * This API is the same as the normal ``/events`` endpoint, but can be + * This API is the same as the normal `/events` endpoint, but can be * called by users who have not joined the room. * - * Note that the normal ``/events`` endpoint has been deprecated. This + * Note that the normal `/events` endpoint has been deprecated. This * API will also be deprecated at some point, but its replacement is not * yet known. */ @@ -51,16 +51,25 @@ public: // Result properties - /// A token which correlates to the first value in ``chunk``. This - /// is usually the same token supplied to ``from=``. - QString begin() const { return loadFromJson("start"_ls); } + /// A token which correlates to the first value in `chunk`. This + /// is usually the same token supplied to `from=`. + QString begin() const + { + return loadFromJson("start"_ls); + } - /// A token which correlates to the last value in ``chunk``. This - /// token should be used in the next request to ``/events``. - QString end() const { return loadFromJson("end"_ls); } + /// A token which correlates to the last value in `chunk`. This + /// token should be used in the next request to `/events`. + QString end() const + { + return loadFromJson("end"_ls); + } /// An array of events. - RoomEvents chunk() { return takeFromJson("chunk"_ls); } + RoomEvents chunk() + { + return takeFromJson("chunk"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/presence.h b/lib/csapi/presence.h index a885bf4f..c817ad9f 100644 --- a/lib/csapi/presence.h +++ b/lib/csapi/presence.h @@ -12,7 +12,7 @@ namespace Quotient { * * This API sets the given user's presence state. When setting the status, * the activity time is updated to reflect that activity; the client does - * not need to specify the ``last_active_ago`` field. You cannot set the + * not need to specify the `last_active_ago` field. You cannot set the * presence state of another user. */ class SetPresenceJob : public BaseJob { @@ -55,7 +55,10 @@ public: // Result properties /// This user's presence. - QString presence() const { return loadFromJson("presence"_ls); } + QString presence() const + { + return loadFromJson("presence"_ls); + } /// The length of time in milliseconds since an action was performed /// by this user. @@ -65,7 +68,10 @@ public: } /// The state message for this user if one was set. - QString statusMsg() const { return loadFromJson("status_msg"_ls); } + QString statusMsg() const + { + return loadFromJson("status_msg"_ls); + } /// Whether the user is currently active Omittable currentlyActive() const diff --git a/lib/csapi/profile.h b/lib/csapi/profile.h index 3858fab2..3cda34f8 100644 --- a/lib/csapi/profile.h +++ b/lib/csapi/profile.h @@ -11,7 +11,7 @@ namespace Quotient { /*! \brief Set the user's display name. * * This API sets the given user's display name. You must have permission to - * set this user's display name, e.g. you need to have their ``access_token``. + * set this user's display name, e.g. you need to have their `access_token`. */ class SetDisplayNameJob : public BaseJob { public: @@ -61,7 +61,7 @@ public: /*! \brief Set the user's avatar URL. * * This API sets the given user's avatar URL. You must have permission to - * set this user's avatar URL, e.g. you need to have their ``access_token``. + * set this user's avatar URL, e.g. you need to have their `access_token`. */ class SetAvatarUrlJob : public BaseJob { public: @@ -101,7 +101,10 @@ public: // Result properties /// The user's avatar URL if they have set one, otherwise not present. - QString avatarUrl() const { return loadFromJson("avatar_url"_ls); } + QString avatarUrl() const + { + return loadFromJson("avatar_url"_ls); + } }; /*! \brief Get this user's profile information. @@ -109,7 +112,7 @@ public: * Get the combined profile information for this user. This API may be used * to fetch the user's own profile information or other users; either * locally or on remote homeservers. This API may return keys which are not - * limited to ``displayname`` or ``avatar_url``. + * limited to `displayname` or `avatar_url`. */ class GetUserProfileJob : public BaseJob { public: @@ -130,7 +133,10 @@ public: // Result properties /// The user's avatar URL if they have set one, otherwise not present. - QString avatarUrl() const { return loadFromJson("avatar_url"_ls); } + QString avatarUrl() const + { + return loadFromJson("avatar_url"_ls); + } /// The user's display name if they have set one, otherwise not present. QString displayname() const diff --git a/lib/csapi/pusher.h b/lib/csapi/pusher.h index ae0050d2..13c9ec25 100644 --- a/lib/csapi/pusher.h +++ b/lib/csapi/pusher.h @@ -19,7 +19,7 @@ public: /// A dictionary of information for the pusher implementation /// itself. struct PusherData { - /// Required if ``kind`` is ``http``. The URL to use to send + /// Required if `kind` is `http`. The URL to use to send /// notifications to. QString url; /// The format to use when sending notifications to the Push @@ -29,11 +29,11 @@ public: /// Gets all currently active pushers for the authenticated user. struct Pusher { - /// This is a unique identifier for this pusher. See ``/set`` for + /// This is a unique identifier for this pusher. See `/set` for /// more detail. /// Max length, 512 bytes. QString pushkey; - /// The kind of pusher. ``"http"`` is a pusher that + /// The kind of pusher. `"http"` is a pusher that /// sends HTTP pokes. QString kind; /// This is a reverse-DNS style identifier for the application. @@ -104,27 +104,27 @@ struct JsonObjectConverter { /*! \brief Modify a pusher for this user on the homeserver. * - * This endpoint allows the creation, modification and deletion of `pushers`_ - * for this user ID. The behaviour of this endpoint varies depending on the - * values in the JSON body. + * This endpoint allows the creation, modification and deletion of + * [pushers](/client-server-api/#push-notifications) for this user ID. The + * behaviour of this endpoint varies depending on the values in the JSON body. */ class PostPusherJob : public BaseJob { public: // Inner data structures /// A dictionary of information for the pusher implementation - /// itself. If ``kind`` is ``http``, this should contain ``url`` + /// itself. If `kind` is `http`, this should contain `url` /// which is the URL to use to send notifications to. struct PusherData { - /// Required if ``kind`` is ``http``. The URL to use to send + /// Required if `kind` is `http`. The URL to use to send /// notifications to. MUST be an HTTPS URL with a path of - /// ``/_matrix/push/v1/notify``. + /// `/_matrix/push/v1/notify`. QString url; /// The format to send notifications in to Push Gateways if the - /// ``kind`` is ``http``. The details about what fields the + /// `kind` is `http`. The details about what fields the /// homeserver should send to the push gateway are defined in the - /// `Push Gateway Specification`_. Currently the only format - /// available is 'event_id_only'. + /// [Push Gateway Specification](/push-gateway-api/). Currently the only + /// format available is 'event_id_only'. QString format; }; @@ -140,13 +140,13 @@ public: * client has no such concept, use any unique identifier. * Max length, 512 bytes. * - * If the ``kind`` is ``"email"``, this is the email address to + * If the `kind` is `"email"`, this is the email address to * send notifications to. * * \param kind - * The kind of pusher to configure. ``"http"`` makes a pusher that - * sends HTTP pokes. ``"email"`` makes a pusher that emails the - * user with unread notifications. ``null`` deletes the pusher. + * The kind of pusher to configure. `"http"` makes a pusher that + * sends HTTP pokes. `"email"` makes a pusher that emails the + * user with unread notifications. `null` deletes the pusher. * * \param appId * This is a reverse-DNS style identifier for the application. @@ -154,7 +154,7 @@ public: * different platform versions get different app identifiers. * Max length, 64 chars. * - * If the ``kind`` is ``"email"``, this is ``"m.email"``. + * If the `kind` is `"email"`, this is `"m.email"`. * * \param appDisplayName * A string that will allow the user to identify what application @@ -170,7 +170,7 @@ public: * * \param data * A dictionary of information for the pusher implementation - * itself. If ``kind`` is ``http``, this should contain ``url`` + * itself. If `kind` is `http`, this should contain `url` * which is the URL to use to send notifications to. * * \param profileTag @@ -182,7 +182,7 @@ public: * given pushkey and App ID in addition to any others with * different user IDs. Otherwise, the homeserver must remove any * other pushers with the same App ID and pushkey for different - * users. The default is ``false``. + * users. The default is `false`. */ explicit PostPusherJob(const QString& pushkey, const QString& kind, const QString& appId, const QString& appDisplayName, diff --git a/lib/csapi/pushrules.h b/lib/csapi/pushrules.h index 1c6d5c2d..90d2ce79 100644 --- a/lib/csapi/pushrules.h +++ b/lib/csapi/pushrules.h @@ -15,9 +15,9 @@ namespace Quotient { /*! \brief Retrieve all push rulesets. * * Retrieve all push rulesets for this user. Clients can "drill-down" on - * the rulesets by suffixing a ``scope`` to this path e.g. - * ``/pushrules/global/``. This will return a subset of this data under the - * specified key e.g. the ``global`` key. + * the rulesets by suffixing a `scope` to this path e.g. + * `/pushrules/global/`. This will return a subset of this data under the + * specified key e.g. the `global` key. */ class GetPushRulesJob : public BaseJob { public: @@ -49,7 +49,7 @@ public: /*! \brief Retrieve a push rule. * * \param scope - * ``global`` to specify global rules. + * `global` to specify global rules. * * \param kind * The kind of rule @@ -71,8 +71,11 @@ public: // Result properties /// The specific push rule. This will also include keys specific to the - /// rule itself such as the rule's ``actions`` and ``conditions`` if set. - PushRule pushRule() const { return fromJson(jsonData()); } + /// rule itself such as the rule's `actions` and `conditions` if set. + PushRule pushRule() const + { + return fromJson(jsonData()); + } }; /*! \brief Delete a push rule. @@ -84,7 +87,7 @@ public: /*! \brief Delete a push rule. * * \param scope - * ``global`` to specify global rules. + * `global` to specify global rules. * * \param kind * The kind of rule @@ -117,7 +120,7 @@ public: /*! \brief Add or change a push rule. * * \param scope - * ``global`` to specify global rules. + * `global` to specify global rules. * * \param kind * The kind of rule @@ -129,7 +132,7 @@ public: * The action(s) to perform when the conditions for this rule are met. * * \param before - * Use 'before' with a ``rule_id`` as its value to make the new rule the + * Use 'before' with a `rule_id` as its value to make the new rule the * next-most important rule with respect to the given user defined rule. * It is not possible to add a rule relative to a predefined server rule. * @@ -141,10 +144,10 @@ public: * \param conditions * The conditions that must hold true for an event in order for a * rule to be applied to an event. A rule with no conditions - * always matches. Only applicable to ``underride`` and ``override`` rules. + * always matches. Only applicable to `underride` and `override` rules. * * \param pattern - * Only applicable to ``content`` rules. The glob-style pattern to match + * Only applicable to `content` rules. The glob-style pattern to match * against. */ explicit SetPushRuleJob(const QString& scope, const QString& kind, @@ -165,8 +168,8 @@ public: /*! \brief Get whether a push rule is enabled * * \param scope - * Either ``global`` or ``device/`` to specify global - * rules or device rules for the given ``profile_tag``. + * Either `global` or `device/` to specify global + * rules or device rules for the given `profile_tag`. * * \param kind * The kind of rule @@ -188,7 +191,10 @@ public: // Result properties /// Whether the push rule is enabled or not. - bool enabled() const { return loadFromJson("enabled"_ls); } + bool enabled() const + { + return loadFromJson("enabled"_ls); + } }; /*! \brief Enable or disable a push rule. @@ -200,7 +206,7 @@ public: /*! \brief Enable or disable a push rule. * * \param scope - * ``global`` to specify global rules. + * `global` to specify global rules. * * \param kind * The kind of rule @@ -224,8 +230,8 @@ public: /*! \brief The actions for a push rule * * \param scope - * Either ``global`` or ``device/`` to specify global - * rules or device rules for the given ``profile_tag``. + * Either `global` or `device/` to specify global + * rules or device rules for the given `profile_tag`. * * \param kind * The kind of rule @@ -263,7 +269,7 @@ public: /*! \brief Set the actions for a push rule. * * \param scope - * ``global`` to specify global rules. + * `global` to specify global rules. * * \param kind * The kind of rule diff --git a/lib/csapi/read_markers.h b/lib/csapi/read_markers.h index 0e122c63..00a2aa0d 100644 --- a/lib/csapi/read_markers.h +++ b/lib/csapi/read_markers.h @@ -26,7 +26,7 @@ public: * * \param mRead * The event ID to set the read receipt location at. This is - * equivalent to calling ``/receipt/m.read/$elsewhere:example.org`` + * equivalent to calling `/receipt/m.read/$elsewhere:example.org` * and is provided here to save that extra call. */ explicit SetReadMarkerJob(const QString& roomId, const QString& mFullyRead, diff --git a/lib/csapi/receipts.h b/lib/csapi/receipts.h index 1fac0acf..7ac093cd 100644 --- a/lib/csapi/receipts.h +++ b/lib/csapi/receipts.h @@ -27,8 +27,8 @@ public: * The event ID to acknowledge up to. * * \param receipt - * Extra receipt information to attach to ``content`` if any. The - * server will automatically set the ``ts`` field. + * Extra receipt information to attach to `content` if any. The + * server will automatically set the `ts` field. */ explicit PostReceiptJob(const QString& roomId, const QString& receiptType, const QString& eventId, diff --git a/lib/csapi/redaction.h b/lib/csapi/redaction.h index 541e433a..f12e6b71 100644 --- a/lib/csapi/redaction.h +++ b/lib/csapi/redaction.h @@ -15,9 +15,12 @@ namespace Quotient { * * This cannot be undone. * - * Users may redact their own events, and any user with a power level - * greater than or equal to the ``redact`` power level of the room may - * redact events there. + * Any user with a power level greater than or equal to the `m.room.redaction` + * event power level may send redaction events in the room. If the user's power + * level greater is also greater than or equal to the `redact` power level + * of the room, the user may redact events sent by other users. + * + * Server administrators may redact events sent by users on their server. */ class RedactEventJob : public BaseJob { public: @@ -43,7 +46,10 @@ public: // Result properties /// A unique identifier for the event. - QString eventId() const { return loadFromJson("event_id"_ls); } + QString eventId() const + { + return loadFromJson("event_id"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/registration.h b/lib/csapi/registration.h index 6864fd47..0ad8b101 100644 --- a/lib/csapi/registration.h +++ b/lib/csapi/registration.h @@ -15,8 +15,9 @@ namespace Quotient { /*! \brief Register for an account on this homeserver. * - * This API endpoint uses the `User-Interactive Authentication API`_, except in - * the cases where a guest account is being registered. + * This API endpoint uses the [User-Interactive Authentication + * API](/client-server-api/#user-interactive-authentication-api), except in the + * cases where a guest account is being registered. * * Register for an account on this homeserver. * @@ -31,45 +32,46 @@ namespace Quotient { * If registration is successful, this endpoint will issue an access token * the client can use to authorize itself in subsequent requests. * - * If the client does not supply a ``device_id``, the server must + * If the client does not supply a `device_id`, the server must * auto-generate one. * * The server SHOULD register an account with a User ID based on the - * ``username`` provided, if any. Note that the grammar of Matrix User ID + * `username` provided, if any. Note that the grammar of Matrix User ID * localparts is restricted, so the server MUST either map the provided - * ``username`` onto a ``user_id`` in a logical manner, or reject - * ``username``\s which do not comply to the grammar, with - * ``M_INVALID_USERNAME``. + * `username` onto a `user_id` in a logical manner, or reject + * `username`\s which do not comply to the grammar, with + * `M_INVALID_USERNAME`. * * Matrix clients MUST NOT assume that localpart of the registered - * ``user_id`` matches the provided ``username``. + * `user_id` matches the provided `username`. * - * The returned access token must be associated with the ``device_id`` + * The returned access token must be associated with the `device_id` * supplied by the client or generated by the server. The server may * invalidate any access token previously associated with that device. See - * `Relationship between access tokens and devices`_. + * [Relationship between access tokens and + * devices](/client-server-api/#relationship-between-access-tokens-and-devices). * * When registering a guest account, all parameters in the request body - * with the exception of ``initial_device_display_name`` MUST BE ignored - * by the server. The server MUST pick a ``device_id`` for the account + * with the exception of `initial_device_display_name` MUST BE ignored + * by the server. The server MUST pick a `device_id` for the account * regardless of input. * * Any user ID returned by this API must conform to the grammar given in the - * `Matrix specification <../appendices.html#user-identifiers>`_. + * [Matrix specification](/appendices/#user-identifiers). */ class RegisterJob : public BaseJob { public: /*! \brief Register for an account on this homeserver. * * \param kind - * The kind of account to register. Defaults to ``user``. + * The kind of account to register. Defaults to `user`. * * \param auth * Additional authentication information for the * user-interactive authentication API. Note that this * information is *not* used to define how the registered user * should be authenticated, but is instead used to - * authenticate the ``register`` call itself. + * authenticate the `register` call itself. * * \param username * The basis for the localpart of the desired Matrix ID. If omitted, @@ -85,10 +87,10 @@ public: * * \param initialDeviceDisplayName * A display name to assign to the newly-created device. Ignored - * if ``device_id`` corresponds to a known device. + * if `device_id` corresponds to a known device. * * \param inhibitLogin - * If true, an ``access_token`` and ``device_id`` should not be + * If true, an `access_token` and `device_id` should not be * returned from this call, therefore preventing an automatic * login. Defaults to false. */ @@ -105,12 +107,15 @@ public: /// The fully-qualified Matrix user ID (MXID) that has been registered. /// /// Any user ID returned by this API must conform to the grammar given in - /// the `Matrix specification <../appendices.html#user-identifiers>`_. - QString userId() const { return loadFromJson("user_id"_ls); } + /// the [Matrix specification](/appendices/#user-identifiers). + QString userId() const + { + return loadFromJson("user_id"_ls); + } /// An access token for the account. /// This access token can then be used to authorize other requests. - /// Required if the ``inhibit_login`` option is false. + /// Required if the `inhibit_login` option is false. QString accessToken() const { return loadFromJson("access_token"_ls); @@ -120,8 +125,8 @@ public: /// been registered. /// /// **Deprecated**. Clients should extract the server_name from - /// ``user_id`` (by splitting at the first colon) if they require - /// it. Note also that ``homeserver`` is not spelt this way. + /// `user_id` (by splitting at the first colon) if they require + /// it. Note also that `homeserver` is not spelt this way. QString homeServer() const { return loadFromJson("home_server"_ls); @@ -129,8 +134,11 @@ public: /// ID of the registered device. Will be the same as the /// corresponding parameter in the request, if one was specified. - /// Required if the ``inhibit_login`` option is false. - QString deviceId() const { return loadFromJson("device_id"_ls); } + /// Required if the `inhibit_login` option is false. + QString deviceId() const + { + return loadFromJson("device_id"_ls); + } }; /*! \brief Begins the validation process for an email to be used during @@ -201,9 +209,9 @@ public: * * Changes the password for an account on this homeserver. * - * This API endpoint uses the `User-Interactive Authentication API`_ to - * ensure the user changing the password is actually the owner of the - * account. + * This API endpoint uses the [User-Interactive Authentication + * API](/client-server-api/#user-interactive-authentication-api) to ensure the + * user changing the password is actually the owner of the account. * * An access token should be submitted to this endpoint if the client has * an active session. @@ -224,8 +232,8 @@ public: * Whether the user's other access tokens, and their associated devices, * should be revoked if the request succeeds. * - * When ``false``, the server can still take advantage of `the soft logout - * method <#soft-logout>`_ for the user's remaining devices. + * When `false`, the server can still take advantage of the [soft logout + * method](/client-server-api/#soft-logout) for the user's remaining devices. * * \param auth * Additional authentication information for the user-interactive @@ -242,23 +250,18 @@ public: * The homeserver must check that the given email address **is * associated** with an account on this homeserver. This API should be * used to request validation tokens when authenticating for the - * ``/account/password`` endpoint. + * `/account/password` endpoint. * * This API's parameters and response are identical to that of the - * |/register/email/requestToken|_ endpoint, except that - * ``M_THREEPID_NOT_FOUND`` may be returned if no account matching the + * [`/register/email/requestToken`](/client-server-api/#post_matrixclientr0registeremailrequesttoken) + * endpoint, except that + * `M_THREEPID_NOT_FOUND` may be returned if no account matching the * given email address could be found. The server may instead send an * email to the given address prompting the user to create an account. - * ``M_THREEPID_IN_USE`` may not be returned. + * `M_THREEPID_IN_USE` may not be returned. * * The homeserver should validate the email itself, either by sending a * validation email itself or by using a service it has control over. - * - * - * .. |/register/email/requestToken| replace:: ``/register/email/requestToken`` - * - * .. _/register/email/requestToken: - * #post-matrix-client-r0-register-email-requesttoken */ class RequestTokenToResetPasswordEmailJob : public BaseJob { public: @@ -269,24 +272,18 @@ public: * The homeserver must check that the given email address **is * associated** with an account on this homeserver. This API should be * used to request validation tokens when authenticating for the - * ``/account/password`` endpoint. + * `/account/password` endpoint. * * This API's parameters and response are identical to that of the - * |/register/email/requestToken|_ endpoint, except that - * ``M_THREEPID_NOT_FOUND`` may be returned if no account matching the + * [`/register/email/requestToken`](/client-server-api/#post_matrixclientr0registeremailrequesttoken) + * endpoint, except that + * `M_THREEPID_NOT_FOUND` may be returned if no account matching the * given email address could be found. The server may instead send an * email to the given address prompting the user to create an account. - * ``M_THREEPID_IN_USE`` may not be returned. + * `M_THREEPID_IN_USE` may not be returned. * * The homeserver should validate the email itself, either by sending a * validation email itself or by using a service it has control over. - * - * - * .. |/register/email/requestToken| replace:: - * ``/register/email/requestToken`` - * - * .. _/register/email/requestToken: - * #post-matrix-client-r0-register-email-requesttoken */ explicit RequestTokenToResetPasswordEmailJob(const EmailValidationData& body); @@ -305,22 +302,18 @@ public: * The homeserver must check that the given phone number **is * associated** with an account on this homeserver. This API should be * used to request validation tokens when authenticating for the - * ``/account/password`` endpoint. + * `/account/password` endpoint. * * This API's parameters and response are identical to that of the - * |/register/msisdn/requestToken|_ endpoint, except that - * ``M_THREEPID_NOT_FOUND`` may be returned if no account matching the + * [`/register/msisdn/requestToken`](/client-server-api/#post_matrixclientr0registermsisdnrequesttoken) + * endpoint, except that + * `M_THREEPID_NOT_FOUND` may be returned if no account matching the * given phone number could be found. The server may instead send the SMS * to the given phone number prompting the user to create an account. - * ``M_THREEPID_IN_USE`` may not be returned. + * `M_THREEPID_IN_USE` may not be returned. * * The homeserver should validate the phone number itself, either by sending a * validation message itself or by using a service it has control over. - * - * .. |/register/msisdn/requestToken| replace:: ``/register/msisdn/requestToken`` - * - * .. _/register/msisdn/requestToken: - * #post-matrix-client-r0-register-email-requesttoken */ class RequestTokenToResetPasswordMSISDNJob : public BaseJob { public: @@ -331,23 +324,18 @@ public: * The homeserver must check that the given phone number **is * associated** with an account on this homeserver. This API should be * used to request validation tokens when authenticating for the - * ``/account/password`` endpoint. + * `/account/password` endpoint. * * This API's parameters and response are identical to that of the - * |/register/msisdn/requestToken|_ endpoint, except that - * ``M_THREEPID_NOT_FOUND`` may be returned if no account matching the + * [`/register/msisdn/requestToken`](/client-server-api/#post_matrixclientr0registermsisdnrequesttoken) + * endpoint, except that + * `M_THREEPID_NOT_FOUND` may be returned if no account matching the * given phone number could be found. The server may instead send the SMS * to the given phone number prompting the user to create an account. - * ``M_THREEPID_IN_USE`` may not be returned. + * `M_THREEPID_IN_USE` may not be returned. * * The homeserver should validate the phone number itself, either by sending * a validation message itself or by using a service it has control over. - * - * .. |/register/msisdn/requestToken| replace:: - * ``/register/msisdn/requestToken`` - * - * .. _/register/msisdn/requestToken: - * #post-matrix-client-r0-register-email-requesttoken */ explicit RequestTokenToResetPasswordMSISDNJob( const MsisdnValidationData& body); @@ -366,7 +354,8 @@ public: * Deactivate the user's account, removing all ability for the user to * login again. * - * This API endpoint uses the `User-Interactive Authentication API`_. + * This API endpoint uses the [User-Interactive Authentication + * API](/client-server-api/#user-interactive-authentication-api). * * An access token should be submitted to this endpoint if the client has * an active session. @@ -374,7 +363,7 @@ public: * The homeserver may change the flows available depending on whether a * valid access token is provided. * - * Unlike other endpoints, this endpoint does not take an ``id_access_token`` + * Unlike other endpoints, this endpoint does not take an `id_access_token` * parameter because the homeserver is expected to sign the request to the * identity server instead. */ @@ -388,11 +377,11 @@ public: * * \param idServer * The identity server to unbind all of the user's 3PIDs from. - * If not provided, the homeserver MUST use the ``id_server`` + * If not provided, the homeserver MUST use the `id_server` * that was originally use to bind each identifier. If the - * homeserver does not know which ``id_server`` that was, - * it must return an ``id_server_unbind_result`` of - * ``no-support``. + * homeserver does not know which `id_server` that was, + * it must return an `id_server_unbind_result` of + * `no-support`. */ explicit DeactivateAccountJob(const Omittable& auth = none, const QString& idServer = {}); @@ -400,12 +389,12 @@ public: // Result properties /// An indicator as to whether or not the homeserver was able to unbind - /// the user's 3PIDs from the identity server(s). ``success`` indicates + /// the user's 3PIDs from the identity server(s). `success` indicates /// that all identifiers have been unbound from the identity server while - /// ``no-support`` indicates that one or more identifiers failed to unbind + /// `no-support` indicates that one or more identifiers failed to unbind /// due to the identity server refusing the request or the homeserver /// being unable to determine an identity server to unbind from. This - /// must be ``success`` if the homeserver has no identifiers to unbind + /// must be `success` if the homeserver has no identifiers to unbind /// for the user. QString idServerUnbindResult() const { @@ -447,7 +436,7 @@ public: // Result properties /// A flag to indicate that the username is available. This should always - /// be ``true`` when the server replies with 200 OK. + /// be `true` when the server replies with 200 OK. Omittable available() const { return loadFromJson>("available"_ls); diff --git a/lib/csapi/report_content.cpp b/lib/csapi/report_content.cpp index 0a41625f..ea906380 100644 --- a/lib/csapi/report_content.cpp +++ b/lib/csapi/report_content.cpp @@ -9,13 +9,13 @@ using namespace Quotient; ReportContentJob::ReportContentJob(const QString& roomId, const QString& eventId, - int score, const QString& reason) + Omittable score, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("ReportContentJob"), QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/report/" % eventId) { QJsonObject _data; - addParam<>(_data, QStringLiteral("score"), score); - addParam<>(_data, QStringLiteral("reason"), reason); + addParam(_data, QStringLiteral("score"), score); + addParam(_data, QStringLiteral("reason"), reason); setRequestData(std::move(_data)); } diff --git a/lib/csapi/report_content.h b/lib/csapi/report_content.h index 375e1829..e401c2e1 100644 --- a/lib/csapi/report_content.h +++ b/lib/csapi/report_content.h @@ -31,7 +31,8 @@ public: * The reason the content is being reported. May be blank. */ explicit ReportContentJob(const QString& roomId, const QString& eventId, - int score, const QString& reason); + Omittable score = none, + const QString& reason = {}); }; } // namespace Quotient diff --git a/lib/csapi/room_send.h b/lib/csapi/room_send.h index 39460aca..a9e7ca13 100644 --- a/lib/csapi/room_send.h +++ b/lib/csapi/room_send.h @@ -16,7 +16,7 @@ namespace Quotient { * * The body of the request should be the content object of the event; the * fields in this object will vary depending on the type of event. See - * `Room Events`_ for the m. event specification. + * [Room Events](/client-server-api/#room-events) for the m. event specification. */ class SendMessageJob : public BaseJob { public: @@ -40,7 +40,8 @@ public: * * The body of the request should be the content object of the event; the * fields in this object will vary depending on the type of event. See - * `Room Events`_ for the m. event specification. + * [Room Events](/client-server-api/#room-events) for the m. event + * specification. */ explicit SendMessageJob(const QString& roomId, const QString& eventType, const QString& txnId, const QJsonObject& body = {}); @@ -48,7 +49,10 @@ public: // Result properties /// A unique identifier for the event. - QString eventId() const { return loadFromJson("event_id"_ls); } + QString eventId() const + { + return loadFromJson("event_id"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/room_state.h b/lib/csapi/room_state.h index 447605ff..99eb4fc9 100644 --- a/lib/csapi/room_state.h +++ b/lib/csapi/room_state.h @@ -9,23 +9,21 @@ namespace Quotient { /*! \brief Send a state event to the given room. - * - * .. For backwards compatibility with older links... - * .. _`put-matrix-client-r0-rooms-roomid-state-eventtype`: * * State events can be sent using this endpoint. These events will be - * overwritten if ````, ```` and ```` all + * overwritten if ``, `` and `` all * match. * * Requests to this endpoint **cannot use transaction IDs** - * like other ``PUT`` paths because they cannot be differentiated from the - * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. + * like other `PUT` paths because they cannot be differentiated from the + * `state_key`. Furthermore, `POST` is unsupported on state paths. * * The body of the request should be the content object of the event; the * fields in this object will vary depending on the type of event. See - * `Room Events`_ for the ``m.`` event specification. + * [Room Events](/client-server-api/#room-events) for the `m.` event + * specification. * - * If the event type being sent is ``m.room.canonical_alias`` servers + * If the event type being sent is `m.room.canonical_alias` servers * SHOULD ensure that any new aliases being listed in the event are valid * per their grammar/syntax and that they point to the room ID where the * state event is to be sent. Servers do not validate aliases which are @@ -46,22 +44,20 @@ public: * an empty string, the trailing slash on this endpoint is optional. * * \param body - * .. For backwards compatibility with older links... - * .. _`put-matrix-client-r0-rooms-roomid-state-eventtype`: - * * State events can be sent using this endpoint. These events will be - * overwritten if ````, ```` and ```` all + * overwritten if ``, `` and `` all * match. * * Requests to this endpoint **cannot use transaction IDs** - * like other ``PUT`` paths because they cannot be differentiated from the - * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. + * like other `PUT` paths because they cannot be differentiated from the + * `state_key`. Furthermore, `POST` is unsupported on state paths. * * The body of the request should be the content object of the event; the * fields in this object will vary depending on the type of event. See - * `Room Events`_ for the ``m.`` event specification. + * [Room Events](/client-server-api/#room-events) for the `m.` event + * specification. * - * If the event type being sent is ``m.room.canonical_alias`` servers + * If the event type being sent is `m.room.canonical_alias` servers * SHOULD ensure that any new aliases being listed in the event are valid * per their grammar/syntax and that they point to the room ID where the * state event is to be sent. Servers do not validate aliases which are @@ -75,7 +71,10 @@ public: // Result properties /// A unique identifier for the event. - QString eventId() const { return loadFromJson("event_id"_ls); } + QString eventId() const + { + return loadFromJson("event_id"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/rooms.h b/lib/csapi/rooms.h index f0bfa349..179d7a27 100644 --- a/lib/csapi/rooms.h +++ b/lib/csapi/rooms.h @@ -12,7 +12,7 @@ namespace Quotient { /*! \brief Get a single event by event ID. * - * Get a single event based on ``roomId/eventId``. You must have permission to + * Get a single event based on `roomId/eventId`. You must have permission to * retrieve this event e.g. by being a member in the room for this event. */ class GetOneRoomEventJob : public BaseJob { @@ -38,13 +38,14 @@ public: // Result properties /// The full event. - EventPtr event() { return fromJson(jsonData()); } + EventPtr event() + + { + return fromJson(jsonData()); + } }; /*! \brief Get the state identified by the type and key. - * - * .. For backwards compatibility with older links... - * .. _`get-matrix-client-r0-rooms-roomid-state-eventtype`: * * Looks up the contents of a state event in a room. If the user is * joined to the room then the state is taken from the current @@ -102,7 +103,11 @@ public: // Result properties /// The current state of the room - StateEvents events() { return fromJson(jsonData()); } + StateEvents events() + + { + return fromJson(jsonData()); + } }; /*! \brief Get the m.room.member events for the room. @@ -118,16 +123,15 @@ public: * * \param at * The point in time (pagination token) to return members for in the room. - * This token can be obtained from a ``prev_batch`` token returned for + * This token can be obtained from a `prev_batch` token returned for * each room by the sync API. Defaults to the current state of the room, * as determined by the server. * * \param membership * The kind of membership to filter for. Defaults to no filtering if - * unspecified. When specified alongside ``not_membership``, the two + * unspecified. When specified alongside `not_membership`, the two * parameters create an 'or' condition: either the membership *is* - * the same as ``membership`` **or** *is not* the same as - * ``not_membership``. + * the same as `membership` **or** *is not* the same as `not_membership`. * * \param notMembership * The kind of membership to exclude from the results. Defaults to no @@ -162,7 +166,7 @@ public: * room. The current user must be in the room for it to work, unless it is an * Application Service in which case any of the AS's users must be in the room. * This API is primarily for Application Services and should be faster to - * respond than ``/members`` as it can be implemented more efficiently on the + * respond than `/members` as it can be implemented more efficiently on the * server. */ class GetJoinedMembersByRoomJob : public BaseJob { @@ -173,7 +177,7 @@ public: /// the room. The current user must be in the room for it to work, unless it /// is an Application Service in which case any of the AS's users must be in /// the room. This API is primarily for Application Services and should be - /// faster to respond than ``/members`` as it can be implemented more + /// faster to respond than `/members` as it can be implemented more /// efficiently on the server. struct RoomMember { /// The display name of the user this object is representing. diff --git a/lib/csapi/search.h b/lib/csapi/search.h index c009ded6..b56d9154 100644 --- a/lib/csapi/search.h +++ b/lib/csapi/search.h @@ -23,15 +23,15 @@ public: /// returned are included in the response. struct IncludeEventContext { /// How many events before the result are - /// returned. By default, this is ``5``. + /// returned. By default, this is `5`. Omittable beforeLimit; /// How many events after the result are - /// returned. By default, this is ``5``. + /// returned. By default, this is `5`. Omittable afterLimit; /// Requests that the server returns the /// historic profile information for the users /// that sent the events that were returned. - /// By default, this is ``false``. + /// By default, this is `false`. Omittable includeProfile; }; @@ -54,10 +54,10 @@ public: QString searchTerm; /// The keys to search. Defaults to all. QStringList keys; - /// This takes a `filter`_. + /// This takes a [filter](/client-server-api/#filtering). RoomEventFilter filter; /// The order in which to search for results. - /// By default, this is ``"rank"``. + /// By default, this is `"rank"`. QString orderBy; /// Configures whether any context for the events /// returned are included in the response. @@ -93,7 +93,7 @@ public: /// The historic profile information of the /// users that sent the events returned. /// - /// The ``string`` key is the user ID for which + /// The `string` key is the user ID for which /// the profile belongs to. QHash profileInfo; /// Events just before the result. @@ -139,15 +139,15 @@ public: std::vector results; /// The current state for every room in the results. /// This is included if the request had the - /// ``include_state`` key set with a value of ``true``. + /// `include_state` key set with a value of `true`. /// - /// The ``string`` key is the room ID for which the ``State - /// Event`` array belongs to. + /// The `string` key is the room ID for which the `State + /// Event` array belongs to. UnorderedMap state; /// Any groups that were requested. /// - /// The outer ``string`` key is the group key requested (eg: ``room_id`` - /// or ``sender``). The inner ``string`` key is the grouped value (eg: + /// The outer `string` key is the group key requested (eg: `room_id` + /// or `sender`). The inner `string` key is the grouped value (eg: /// a room's ID or a user's ID). QHash> groups; /// Token that can be used to get the next batch of @@ -172,7 +172,7 @@ public: * * \param nextBatch * The point to return events from. If given, this should be a - * ``next_batch`` result from a previous call to this endpoint. + * `next_batch` result from a previous call to this endpoint. */ explicit SearchJob(const Categories& searchCategories, const QString& nextBatch = {}); diff --git a/lib/csapi/sso_login_redirect.cpp b/lib/csapi/sso_login_redirect.cpp index 85a18560..e3d39807 100644 --- a/lib/csapi/sso_login_redirect.cpp +++ b/lib/csapi/sso_login_redirect.cpp @@ -28,3 +28,27 @@ RedirectToSSOJob::RedirectToSSOJob(const QString& redirectUrl) QStringLiteral("/_matrix/client/r0") % "/login/sso/redirect", queryToRedirectToSSO(redirectUrl), {}, false) {} + +auto queryToRedirectToIdP(const QString& redirectUrl) +{ + BaseJob::Query _q; + addParam<>(_q, QStringLiteral("redirectUrl"), redirectUrl); + return _q; +} + +QUrl RedirectToIdPJob::makeRequestUrl(QUrl baseUrl, const QString& idpId, + const QString& redirectUrl) +{ + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/login/sso/redirect/" % idpId, + queryToRedirectToIdP(redirectUrl)); +} + +RedirectToIdPJob::RedirectToIdPJob(const QString& idpId, + const QString& redirectUrl) + : BaseJob(HttpVerb::Get, QStringLiteral("RedirectToIdPJob"), + QStringLiteral("/_matrix/client/r0") % "/login/sso/redirect/" + % idpId, + queryToRedirectToIdP(redirectUrl), {}, false) +{} diff --git a/lib/csapi/sso_login_redirect.h b/lib/csapi/sso_login_redirect.h index d6330e38..ade1eb7d 100644 --- a/lib/csapi/sso_login_redirect.h +++ b/lib/csapi/sso_login_redirect.h @@ -13,7 +13,9 @@ namespace Quotient { * A web-based Matrix client should instruct the user's browser to * navigate to this endpoint in order to log in via SSO. * - * The server MUST respond with an HTTP redirect to the SSO interface. + * The server MUST respond with an HTTP redirect to the SSO interface, + * or present a page which lets the user select an IdP to continue + * with in the event multiple are supported by the server. */ class RedirectToSSOJob : public BaseJob { public: @@ -33,4 +35,36 @@ public: static QUrl makeRequestUrl(QUrl baseUrl, const QString& redirectUrl); }; +/*! \brief Redirect the user's browser to the SSO interface for an IdP. + * + * This endpoint is the same as `/login/sso/redirect`, though with an + * IdP ID from the original `identity_providers` array to inform the + * server of which IdP the client/user would like to continue with. + * + * The server MUST respond with an HTTP redirect to the SSO interface + * for that IdP. + */ +class RedirectToIdPJob : public BaseJob { +public: + /*! \brief Redirect the user's browser to the SSO interface for an IdP. + * + * \param idpId + * The `id` of the IdP from the `m.login.sso` `identity_providers` + * array denoting the user's selection. + * + * \param redirectUrl + * URI to which the user will be redirected after the homeserver has + * authenticated the user with SSO. + */ + explicit RedirectToIdPJob(const QString& idpId, const QString& redirectUrl); + + /*! \brief Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for RedirectToIdPJob + * is necessary but the job itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& idpId, + const QString& redirectUrl); +}; + } // namespace Quotient diff --git a/lib/csapi/tags.h b/lib/csapi/tags.h index a815d9b3..a854531a 100644 --- a/lib/csapi/tags.h +++ b/lib/csapi/tags.h @@ -18,7 +18,7 @@ public: /// List the tags set by a user on a room. struct Tag { - /// A number in a range ``[0,1]`` describing a relative + /// A number in a range `[0,1]` describing a relative /// position of the room under the given tag. Omittable order; /// List the tags set by a user on a room. @@ -83,7 +83,7 @@ public: * The tag to add. * * \param order - * A number in a range ``[0,1]`` describing a relative + * A number in a range `[0,1]` describing a relative * position of the room under the given tag. * * \param additionalProperties diff --git a/lib/csapi/third_party_membership.h b/lib/csapi/third_party_membership.h index 55cab370..a424678f 100644 --- a/lib/csapi/third_party_membership.h +++ b/lib/csapi/third_party_membership.h @@ -9,15 +9,14 @@ namespace Quotient { /*! \brief Invite a user to participate in a particular room. - * - * .. _invite-by-third-party-id-endpoint: * * *Note that there are two forms of this API, which are documented separately. * This version of the API does not require that the inviter know the Matrix * identifier of the invitee, and instead relies on third party identifiers. * The homeserver uses an identity server to perform the mapping from * third party identifier to a Matrix identifier. The other is documented in - * the* `joining rooms section`_. + * the* [joining rooms + * section](/client-server-api/#post_matrixclientr0roomsroomidinvite). * * This API invites a user to participate in a particular room. * They do not start participating in the room until they actually join the @@ -27,7 +26,7 @@ namespace Quotient { * join that room. * * If the identity server did know the Matrix user identifier for the - * third party identifier, the homeserver will append a ``m.room.member`` + * third party identifier, the homeserver will append a `m.room.member` * event to the room. * * If the identity server does not know a Matrix user identifier for the @@ -35,7 +34,7 @@ namespace Quotient { * which can be accepted upon providing proof of ownership of the third * party identifier. This is achieved by the identity server generating a * token, which it gives to the inviting homeserver. The homeserver will - * add an ``m.room.third_party_invite`` event into the graph for the room, + * add an `m.room.third_party_invite` event into the graph for the room, * containing that token. * * When the invitee binds the invited third party identifier to a Matrix @@ -51,9 +50,7 @@ namespace Quotient { * - The matrix user ID who invited them to the room * * If a token is requested from the identity server, the homeserver will - * append a ``m.room.third_party_invite`` event to the room. - * - * .. _joining rooms section: `invite-by-user-id-endpoint`_ + * append a `m.room.third_party_invite` event to the room. */ class InviteBy3PIDJob : public BaseJob { public: @@ -73,7 +70,7 @@ public: * * \param medium * The kind of address being passed in the address field, for example - * ``email``. + * `email`. * * \param address * The invitee's third party identifier. diff --git a/lib/csapi/to_device.cpp b/lib/csapi/to_device.cpp index 28c4115a..3775174d 100644 --- a/lib/csapi/to_device.cpp +++ b/lib/csapi/to_device.cpp @@ -16,6 +16,6 @@ SendToDeviceJob::SendToDeviceJob( % eventType % "/" % txnId) { QJsonObject _data; - addParam(_data, QStringLiteral("messages"), messages); + addParam<>(_data, QStringLiteral("messages"), messages); setRequestData(std::move(_data)); } diff --git a/lib/csapi/to_device.h b/lib/csapi/to_device.h index f5d69d65..7a237195 100644 --- a/lib/csapi/to_device.h +++ b/lib/csapi/to_device.h @@ -32,7 +32,7 @@ public: */ explicit SendToDeviceJob( const QString& eventType, const QString& txnId, - const QHash>& messages = {}); + const QHash>& messages); }; } // namespace Quotient diff --git a/lib/csapi/typing.h b/lib/csapi/typing.h index 2c953949..64a310d0 100644 --- a/lib/csapi/typing.h +++ b/lib/csapi/typing.h @@ -11,8 +11,8 @@ namespace Quotient { /*! \brief Informs the server that the user has started or stopped typing. * * This tells the server that the user is typing for the next N - * milliseconds where N is the value specified in the ``timeout`` key. - * Alternatively, if ``typing`` is ``false``, it tells the server that the + * milliseconds where N is the value specified in the `timeout` key. + * Alternatively, if `typing` is `false`, it tells the server that the * user has stopped typing. */ class SetTypingJob : public BaseJob { @@ -26,7 +26,7 @@ public: * The room in which the user is typing. * * \param typing - * Whether the user is typing or not. If ``false``, the ``timeout`` + * Whether the user is typing or not. If `false`, the `timeout` * key can be omitted. * * \param timeout diff --git a/lib/csapi/users.h b/lib/csapi/users.h index 6fc26f57..772a6365 100644 --- a/lib/csapi/users.h +++ b/lib/csapi/users.h @@ -19,7 +19,7 @@ namespace Quotient { * * The search is performed case-insensitively on user IDs and display * names preferably using a collation determined based upon the - * ``Accept-Language`` header provided in the request, if present. + * `Accept-Language` header provided in the request, if present. */ class SearchUserDirectoryJob : public BaseJob { public: @@ -34,7 +34,7 @@ public: /// /// The search is performed case-insensitively on user IDs and display /// names preferably using a collation determined based upon the - /// ``Accept-Language`` header provided in the request, if present. + /// `Accept-Language` header provided in the request, if present. struct User { /// The user's matrix user ID. QString userId; @@ -66,7 +66,10 @@ public: } /// Indicates if the result list has been truncated by the limit. - bool limited() const { return loadFromJson("limited"_ls); } + bool limited() const + { + return loadFromJson("limited"_ls); + } }; template <> diff --git a/lib/csapi/versions.h b/lib/csapi/versions.h index 828a7eb9..896e2ea9 100644 --- a/lib/csapi/versions.h +++ b/lib/csapi/versions.h @@ -12,14 +12,14 @@ namespace Quotient { * * Gets the versions of the specification supported by the server. * - * Values will take the form ``rX.Y.Z``. + * Values will take the form `rX.Y.Z`. * - * Only the latest ``Z`` value will be reported for each supported ``X.Y`` - * value. i.e. if the server implements ``r0.0.0``, ``r0.0.1``, and ``r1.2.0``, - * it will report ``r0.0.1`` and ``r1.2.0``. + * Only the latest `Z` value will be reported for each supported `X.Y` value. + * i.e. if the server implements `r0.0.0`, `r0.0.1`, and `r1.2.0`, it will + * report `r0.0.1` and `r1.2.0`. * * The server may additionally advertise experimental features it supports - * through ``unstable_features``. These features should be namespaced and + * through `unstable_features`. These features should be namespaced and * may optionally include version information within their name if desired. * Features listed here are not for optionally toggling parts of the Matrix * specification and should only be used to advertise support for a feature diff --git a/lib/csapi/voip.h b/lib/csapi/voip.h index 087ebbbd..85ab8b41 100644 --- a/lib/csapi/voip.h +++ b/lib/csapi/voip.h @@ -28,7 +28,10 @@ public: // Result properties /// The TURN server credentials. - QJsonObject data() const { return fromJson(jsonData()); } + QJsonObject data() const + { + return fromJson(jsonData()); + } }; } // namespace Quotient diff --git a/lib/csapi/wellknown.h b/lib/csapi/wellknown.h index b21d9fc7..c707d232 100644 --- a/lib/csapi/wellknown.h +++ b/lib/csapi/wellknown.h @@ -14,7 +14,7 @@ namespace Quotient { * * Gets discovery information about the domain. The file may include * additional keys, which MUST follow the Java package naming convention, - * e.g. ``com.example.myapp.property``. This ensures property names are + * e.g. `com.example.myapp.property`. This ensures property names are * suitably namespaced for each application and reduces the risk of * clashes. * diff --git a/lib/csapi/whoami.h b/lib/csapi/whoami.h index af8f1e8a..203742c9 100644 --- a/lib/csapi/whoami.h +++ b/lib/csapi/whoami.h @@ -14,8 +14,8 @@ namespace Quotient { * * Note that, as with the rest of the Client-Server API, * Application Services may masquerade as users within their - * namespace by giving a ``user_id`` query parameter. In this - * situation, the server should verify that the given ``user_id`` + * namespace by giving a `user_id` query parameter. In this + * situation, the server should verify that the given `user_id` * is registered by the appservice, and return it in the response * body. */ @@ -33,8 +33,20 @@ public: // Result properties - /// The user id that owns the access token. - QString userId() const { return loadFromJson("user_id"_ls); } + /// The user ID that owns the access token. + QString userId() const + { + return loadFromJson("user_id"_ls); + } + + /// Device ID associated with the access token. If no device + /// is associated with the access token (such as in the case + /// of application services) then this field can be omitted. + /// Otherwise this is required. + QString deviceId() const + { + return loadFromJson("device_id"_ls); + } }; } // namespace Quotient diff --git a/lib/identity/definitions/request_email_validation.h b/lib/identity/definitions/request_email_validation.h index 079da953..87549505 100644 --- a/lib/identity/definitions/request_email_validation.h +++ b/lib/identity/definitions/request_email_validation.h @@ -11,16 +11,16 @@ namespace Quotient { struct RequestEmailValidation { /// A unique string generated by the client, and used to identify the /// validation attempt. It must be a string consisting of the characters - /// ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it + /// `[0-9a-zA-Z.=_-]`. Its length must not exceed 255 characters and it /// must not be empty. QString clientSecret; /// The email address to validate. QString email; - /// The server will only send an email if the ``send_attempt`` + /// The server will only send an email if the `send_attempt` /// is a number greater than the most recent one which it has seen, - /// scoped to that ``email`` + ``client_secret`` pair. This is to + /// scoped to that `email` + `client_secret` pair. This is to /// avoid repeatedly sending the same email in the case of request /// retries between the POSTing user and the identity server. /// The client should increment this value if they desire a new diff --git a/lib/identity/definitions/request_msisdn_validation.h b/lib/identity/definitions/request_msisdn_validation.h index a29fd0de..d2ea463f 100644 --- a/lib/identity/definitions/request_msisdn_validation.h +++ b/lib/identity/definitions/request_msisdn_validation.h @@ -11,20 +11,20 @@ namespace Quotient { struct RequestMsisdnValidation { /// A unique string generated by the client, and used to identify the /// validation attempt. It must be a string consisting of the characters - /// ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it + /// `[0-9a-zA-Z.=_-]`. Its length must not exceed 255 characters and it /// must not be empty. QString clientSecret; /// The two-letter uppercase ISO-3166-1 alpha-2 country code that the - /// number in ``phone_number`` should be parsed as if it were dialled from. + /// number in `phone_number` should be parsed as if it were dialled from. QString country; /// The phone number to validate. QString phoneNumber; - /// The server will only send an SMS if the ``send_attempt`` is a + /// The server will only send an SMS if the `send_attempt` is a /// number greater than the most recent one which it has seen, - /// scoped to that ``country`` + ``phone_number`` + ``client_secret`` + /// scoped to that `country` + `phone_number` + `client_secret` /// triple. This is to avoid repeatedly sending the same SMS in /// the case of request retries between the POSTing user and the /// identity server. The client should increment this value if -- cgit v1.2.3 From 466fe1a43389ac1d7408d32eee3e85e8b074b4bd Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 23 Jun 2021 20:16:04 +0200 Subject: Step to Ubuntu 20.04; drop (old) E2EE code building --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed251bc3..20100e5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,17 +16,15 @@ jobs: fail-fast: false max-parallel: 1 matrix: - os: [ubuntu-18.04, macos-10.15] + os: [ubuntu-20.04, macos-10.15] compiler: [ GCC, Clang ] qt-version: [ '5.12.10' ] # Not using binary values here, to make the job captions more readable - e2ee: [ '', 'E2EE' ] + e2ee: [ '' ] update-api: [ '', 'update-api' ] exclude: - os: macos-10.15 compiler: GCC - - e2ee: '' # Somewhat reduce the number of combinations to check - update-api: 'update-api' steps: - uses: actions/checkout@v2 -- cgit v1.2.3 From ac8a4487e0254386462c43bfc0aeef8412854117 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 28 Jun 2021 20:32:14 +0200 Subject: gtad.yaml: update for use with GTAD pre-0.8 --- gtad/gtad.yaml | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml index d68cc8a0..e56c394d 100644 --- a/gtad/gtad.yaml +++ b/gtad/gtad.yaml @@ -77,28 +77,31 @@ analyzer: +on: - object: &QJsonObject { type: QJsonObject } - $ref: - - +set: { moveOnly: } + - +set: + moveOnly: + imports: '"events/eventloader.h"' +on: - - /state_event.yaml$/: - { type: StateEventPtr, imports: "events/eventloader.h" } - - /room_event.yaml$/: - { type: RoomEventPtr, imports: "events/eventloader.h" } - - /event.yaml$/: - { type: EventPtr, imports: "events/eventloader.h" } + - /state_event.yaml$/: StateEventPtr + - /room_event.yaml$/: RoomEventPtr + - /event.yaml$/: EventPtr - /m\.room\.member/: void # Skip resolving; see EventsArray<> below - - '/^(\./)?definitions/request_email_validation.yaml$/': - title: EmailValidationData - - '/^(\./)?definitions/request_msisdn_validation.yaml$/': - title: MsisdnValidationData - - /_filter.yaml$/: # Event/RoomEventFilters do NOT need Omittable<> - - /public_rooms_response.yaml$/: { _inline: true } - - //: *UseOmittable # Also apply "avoidCopy" to all other ref'ed types + - +set: + # This renderer actually applies to all $ref things + _importRenderer: '"{{#segments}}{{_}}{{#_join}}/{{/_join}}{{/segments}}.h"' + +on: + - '/^(\./)?definitions/request_email_validation.yaml$/': + title: EmailValidationData + - '/^(\./)?definitions/request_msisdn_validation.yaml$/': + title: MsisdnValidationData + - /_filter.yaml$/: # Event/RoomEventFilters do NOT need Omittable<> + - /public_rooms_response.yaml$/: { _inline: true } + - //: *UseOmittable # Also apply "avoidCopy" to all other ref'ed types - schema: - getTurnServer<: *QJsonObject # It's used as an opaque JSON object - PublicRoomResponse: { _inline: true } # - defineFilter>: &Filter # Force folding into a structure # type: Filter -# imports: "csapi/definitions/sync_filter.h" +# imports: '"csapi/definitions/sync_filter.h"' # - getFilter<: *Filter - RoomFilter: # A structure inside Filter, same story as with *_filter.yaml - //: *UseOmittable @@ -108,10 +111,10 @@ analyzer: +on: - /^Notification|Result$/: type: "std::vector<{{1}}>" - imports: "events/eventloader.h" + imports: '"events/eventloader.h"' - /m\.room\.member/: # Only used in an array (see also above) type: "EventsArray" - imports: "events/roommemberevent.h" + imports: '"events/roommemberevent.h"' - /state_event.yaml$/: StateEvents - /room_event.yaml$/: RoomEvents - /event.yaml$/: Events @@ -134,7 +137,6 @@ mustache: # _quote: '"' # Common quote for left and right # _leftQuote: '"' # _rightQuote: '"' -# _joinChar: ',' # The character used by {{_join}} - not working yet _comment: '//' copyrightName: Kitsune Ral copyrightEmail: -- cgit v1.2.3 From 7f4ef8b303c36831a9172a1fc60888e906fc9b93 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 30 Jun 2021 20:10:40 +0200 Subject: Add a commented out delimiter override example --- gtad/gtad.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml index e56c394d..928a1495 100644 --- a/gtad/gtad.yaml +++ b/gtad/gtad.yaml @@ -132,6 +132,7 @@ analyzer: #operations: mustache: +# delimiter: '%| |%' # or something else instead of '{{ }}' constants: # Syntax elements used by GTAD # _quote: '"' # Common quote for left and right -- cgit v1.2.3 From b64bfa8f72084d9d9397001a735e985a4bf94e56 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 2 Jul 2021 10:33:59 +0200 Subject: Abandon BaseJob::Query - Mustache template --- gtad/operation.cpp.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtad/operation.cpp.mustache b/gtad/operation.cpp.mustache index 5bbc45ec..f34c9280 100644 --- a/gtad/operation.cpp.mustache +++ b/gtad/operation.cpp.mustache @@ -13,7 +13,7 @@ using namespace Quotient; auto queryTo{{camelCaseOperationId}}( {{#queryParams}}{{>joinedParamDef}}{{/queryParams}}) { - BaseJob::Query _q;{{#queryParams}} + QUrlQuery _q;{{#queryParams}} addParam<{{^required?}}IfNotEmpty{{/required?}}>(_q, QStringLiteral("{{baseName}}"), {{paramName}});{{/queryParams}} return _q; -- cgit v1.2.3 From 7aa6e7c300779f652558397bcb7bb3b726d30cb9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 2 Jul 2021 10:34:16 +0200 Subject: Abandon BaseJob::Query - generated API files --- lib/csapi/content-repo.cpp | 10 +++++----- lib/csapi/event_context.cpp | 2 +- lib/csapi/joining.cpp | 2 +- lib/csapi/keys.cpp | 2 +- lib/csapi/knocking.cpp | 2 +- lib/csapi/list_public_rooms.cpp | 4 ++-- lib/csapi/message_pagination.cpp | 2 +- lib/csapi/notifications.cpp | 2 +- lib/csapi/peeking_events.cpp | 2 +- lib/csapi/pushrules.cpp | 2 +- lib/csapi/registration.cpp | 4 ++-- lib/csapi/rooms.cpp | 2 +- lib/csapi/search.cpp | 2 +- lib/csapi/sso_login_redirect.cpp | 4 ++-- lib/csapi/third_party_lookup.cpp | 8 ++++---- 15 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/csapi/content-repo.cpp b/lib/csapi/content-repo.cpp index 7ae89739..e913bfd1 100644 --- a/lib/csapi/content-repo.cpp +++ b/lib/csapi/content-repo.cpp @@ -10,7 +10,7 @@ using namespace Quotient; auto queryToUploadContent(const QString& filename) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("filename"), filename); return _q; } @@ -28,7 +28,7 @@ UploadContentJob::UploadContentJob(QIODevice* content, const QString& filename, auto queryToGetContent(bool allowRemote) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("allow_remote"), allowRemote); return _q; } @@ -55,7 +55,7 @@ GetContentJob::GetContentJob(const QString& serverName, const QString& mediaId, auto queryToGetContentOverrideName(bool allowRemote) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("allow_remote"), allowRemote); return _q; } @@ -88,7 +88,7 @@ GetContentOverrideNameJob::GetContentOverrideNameJob(const QString& serverName, auto queryToGetContentThumbnail(int width, int height, const QString& method, bool allowRemote) { - BaseJob::Query _q; + QUrlQuery _q; addParam<>(_q, QStringLiteral("width"), width); addParam<>(_q, QStringLiteral("height"), height); addParam(_q, QStringLiteral("method"), method); @@ -124,7 +124,7 @@ GetContentThumbnailJob::GetContentThumbnailJob(const QString& serverName, auto queryToGetUrlPreview(const QString& url, Omittable ts) { - BaseJob::Query _q; + QUrlQuery _q; addParam<>(_q, QStringLiteral("url"), url); addParam(_q, QStringLiteral("ts"), ts); return _q; diff --git a/lib/csapi/event_context.cpp b/lib/csapi/event_context.cpp index d2a5f522..3f4cd61e 100644 --- a/lib/csapi/event_context.cpp +++ b/lib/csapi/event_context.cpp @@ -10,7 +10,7 @@ using namespace Quotient; auto queryToGetEventContext(Omittable limit, const QString& filter) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("limit"), limit); addParam(_q, QStringLiteral("filter"), filter); return _q; diff --git a/lib/csapi/joining.cpp b/lib/csapi/joining.cpp index 998d0b42..f5266f0b 100644 --- a/lib/csapi/joining.cpp +++ b/lib/csapi/joining.cpp @@ -24,7 +24,7 @@ JoinRoomByIdJob::JoinRoomByIdJob( auto queryToJoinRoom(const QStringList& serverName) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("server_name"), serverName); return _q; } diff --git a/lib/csapi/keys.cpp b/lib/csapi/keys.cpp index 34ab47c9..ba5d8e12 100644 --- a/lib/csapi/keys.cpp +++ b/lib/csapi/keys.cpp @@ -47,7 +47,7 @@ ClaimKeysJob::ClaimKeysJob( auto queryToGetKeysChanges(const QString& from, const QString& to) { - BaseJob::Query _q; + QUrlQuery _q; addParam<>(_q, QStringLiteral("from"), from); addParam<>(_q, QStringLiteral("to"), to); return _q; diff --git a/lib/csapi/knocking.cpp b/lib/csapi/knocking.cpp index 4b561300..788bb378 100644 --- a/lib/csapi/knocking.cpp +++ b/lib/csapi/knocking.cpp @@ -10,7 +10,7 @@ using namespace Quotient; auto queryToKnockRoom(const QStringList& serverName) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("server_name"), serverName); return _q; } diff --git a/lib/csapi/list_public_rooms.cpp b/lib/csapi/list_public_rooms.cpp index 415d816c..a4bcb934 100644 --- a/lib/csapi/list_public_rooms.cpp +++ b/lib/csapi/list_public_rooms.cpp @@ -38,7 +38,7 @@ SetRoomVisibilityOnDirectoryJob::SetRoomVisibilityOnDirectoryJob( auto queryToGetPublicRooms(Omittable limit, const QString& since, const QString& server) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("limit"), limit); addParam(_q, QStringLiteral("since"), since); addParam(_q, QStringLiteral("server"), server); @@ -66,7 +66,7 @@ GetPublicRoomsJob::GetPublicRoomsJob(Omittable limit, const QString& since, auto queryToQueryPublicRooms(const QString& server) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("server"), server); return _q; } diff --git a/lib/csapi/message_pagination.cpp b/lib/csapi/message_pagination.cpp index 855c051f..441e4dea 100644 --- a/lib/csapi/message_pagination.cpp +++ b/lib/csapi/message_pagination.cpp @@ -12,7 +12,7 @@ auto queryToGetRoomEvents(const QString& from, const QString& to, const QString& dir, Omittable limit, const QString& filter) { - BaseJob::Query _q; + QUrlQuery _q; addParam<>(_q, QStringLiteral("from"), from); addParam(_q, QStringLiteral("to"), to); addParam<>(_q, QStringLiteral("dir"), dir); diff --git a/lib/csapi/notifications.cpp b/lib/csapi/notifications.cpp index a479d500..a38e46f5 100644 --- a/lib/csapi/notifications.cpp +++ b/lib/csapi/notifications.cpp @@ -11,7 +11,7 @@ using namespace Quotient; auto queryToGetNotifications(const QString& from, Omittable limit, const QString& only) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("from"), from); addParam(_q, QStringLiteral("limit"), limit); addParam(_q, QStringLiteral("only"), only); diff --git a/lib/csapi/peeking_events.cpp b/lib/csapi/peeking_events.cpp index 70a5b6f3..ad2f9afe 100644 --- a/lib/csapi/peeking_events.cpp +++ b/lib/csapi/peeking_events.cpp @@ -11,7 +11,7 @@ using namespace Quotient; auto queryToPeekEvents(const QString& from, Omittable timeout, const QString& roomId) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("from"), from); addParam(_q, QStringLiteral("timeout"), timeout); addParam(_q, QStringLiteral("room_id"), roomId); diff --git a/lib/csapi/pushrules.cpp b/lib/csapi/pushrules.cpp index 86165744..ab7d0038 100644 --- a/lib/csapi/pushrules.cpp +++ b/lib/csapi/pushrules.cpp @@ -57,7 +57,7 @@ DeletePushRuleJob::DeletePushRuleJob(const QString& scope, const QString& kind, auto queryToSetPushRule(const QString& before, const QString& after) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("before"), before); addParam(_q, QStringLiteral("after"), after); return _q; diff --git a/lib/csapi/registration.cpp b/lib/csapi/registration.cpp index 33f61265..38649e63 100644 --- a/lib/csapi/registration.cpp +++ b/lib/csapi/registration.cpp @@ -10,7 +10,7 @@ using namespace Quotient; auto queryToRegister(const QString& kind) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("kind"), kind); return _q; } @@ -106,7 +106,7 @@ DeactivateAccountJob::DeactivateAccountJob( auto queryToCheckUsernameAvailability(const QString& username) { - BaseJob::Query _q; + QUrlQuery _q; addParam<>(_q, QStringLiteral("username"), username); return _q; } diff --git a/lib/csapi/rooms.cpp b/lib/csapi/rooms.cpp index 724d941f..3dd87021 100644 --- a/lib/csapi/rooms.cpp +++ b/lib/csapi/rooms.cpp @@ -58,7 +58,7 @@ GetRoomStateJob::GetRoomStateJob(const QString& roomId) auto queryToGetMembersByRoom(const QString& at, const QString& membership, const QString& notMembership) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("at"), at); addParam(_q, QStringLiteral("membership"), membership); addParam(_q, QStringLiteral("not_membership"), notMembership); diff --git a/lib/csapi/search.cpp b/lib/csapi/search.cpp index 5649d52a..05ad871e 100644 --- a/lib/csapi/search.cpp +++ b/lib/csapi/search.cpp @@ -10,7 +10,7 @@ using namespace Quotient; auto queryToSearch(const QString& nextBatch) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("next_batch"), nextBatch); return _q; } diff --git a/lib/csapi/sso_login_redirect.cpp b/lib/csapi/sso_login_redirect.cpp index e3d39807..92601b4d 100644 --- a/lib/csapi/sso_login_redirect.cpp +++ b/lib/csapi/sso_login_redirect.cpp @@ -10,7 +10,7 @@ using namespace Quotient; auto queryToRedirectToSSO(const QString& redirectUrl) { - BaseJob::Query _q; + QUrlQuery _q; addParam<>(_q, QStringLiteral("redirectUrl"), redirectUrl); return _q; } @@ -31,7 +31,7 @@ RedirectToSSOJob::RedirectToSSOJob(const QString& redirectUrl) auto queryToRedirectToIdP(const QString& redirectUrl) { - BaseJob::Query _q; + QUrlQuery _q; addParam<>(_q, QStringLiteral("redirectUrl"), redirectUrl); return _q; } diff --git a/lib/csapi/third_party_lookup.cpp b/lib/csapi/third_party_lookup.cpp index baf1fab5..93687a76 100644 --- a/lib/csapi/third_party_lookup.cpp +++ b/lib/csapi/third_party_lookup.cpp @@ -36,7 +36,7 @@ GetProtocolMetadataJob::GetProtocolMetadataJob(const QString& protocol) auto queryToQueryLocationByProtocol(const QString& searchFields) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("searchFields"), searchFields); return _q; } @@ -61,7 +61,7 @@ QueryLocationByProtocolJob::QueryLocationByProtocolJob( auto queryToQueryUserByProtocol(const QString& fields) { - BaseJob::Query _q; + QUrlQuery _q; addParam(_q, QStringLiteral("fields..."), fields); return _q; } @@ -86,7 +86,7 @@ QueryUserByProtocolJob::QueryUserByProtocolJob(const QString& protocol, auto queryToQueryLocationByAlias(const QString& alias) { - BaseJob::Query _q; + QUrlQuery _q; addParam<>(_q, QStringLiteral("alias"), alias); return _q; } @@ -107,7 +107,7 @@ QueryLocationByAliasJob::QueryLocationByAliasJob(const QString& alias) auto queryToQueryUserByID(const QString& userid) { - BaseJob::Query _q; + QUrlQuery _q; addParam<>(_q, QStringLiteral("userid"), userid); return _q; } -- cgit v1.2.3 From 74dda96855b6245b09a8f7eb0512b4457b18a6b7 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 2 Jul 2021 10:35:45 +0200 Subject: Actually delete BaseJob::Query It was a tiny wrapper around QUrlQuery to facilitate creation from an initializer list - however, Mustache templates long changed to not actually used that additional constructor. --- lib/jobs/basejob.cpp | 4 ++-- lib/jobs/basejob.h | 16 +--------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index c27c6a89..9a7b9b5e 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -175,11 +175,11 @@ public: BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, bool needsToken) - : BaseJob(verb, name, endpoint, Query {}, Data {}, needsToken) + : BaseJob(verb, name, endpoint, QUrlQuery {}, Data {}, needsToken) {} BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, - const Query& query, Data&& data, bool needsToken) + const QUrlQuery &query, Data&& data, bool needsToken) : d(new Private(verb, endpoint, query, std::move(data), needsToken)) { setObjectName(name); diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index ca91a781..d33d542e 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -72,20 +72,6 @@ public: }; Q_ENUM(StatusCode) - /** - * A simple wrapper around QUrlQuery that allows its creation from - * a list of string pairs - */ - class Query : public QUrlQuery { - public: - using QUrlQuery::QUrlQuery; - Query() = default; - Query(const std::initializer_list>& l) - { - setQueryItems(l); - } - }; - using Data = RequestData; /*! @@ -139,7 +125,7 @@ public: BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, bool needsToken = true); BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, - const Query& query, Data&& data = {}, bool needsToken = true); + const QUrlQuery& query, Data&& data = {}, bool needsToken = true); QUrl requestUrl() const; bool isBackground() const; -- cgit v1.2.3 From adb6725c04918d1323b4fc4e3a8cba91ae1dde60 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 10 Jul 2021 18:38:55 +0200 Subject: Fix Room::processAccountDataEvent() return value (cherry picked from commit 7b65051e959968fe538f40c975d85757cfcc7df7) (cherry picked from commit 9edfefe9b209583d18ce92e7ffd73e8aa1f3ef1e) --- lib/room.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index c314fc72..0984bd96 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2766,9 +2766,9 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) qCDebug(STATE) << "Updated account data of type" << currentData->matrixType(); emit accountDataChanged(currentData->matrixType()); - return Change::AccountDataChange; + changes |= Change::AccountDataChange; } - return Change::NoChange; + return changes; } template -- cgit v1.2.3 From 9a5fa623c17f5644da7cdb459ade86bc8e1cdbf3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 16 Jul 2021 20:02:00 +0200 Subject: Officially drop Qt Multimedia with Qt 6 Closes #483. --- CMakeLists.txt | 14 +++++++------- lib/events/roommessageevent.cpp | 6 ++---- lib/events/roommessageevent.h | 4 ++++ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 555ffa96..285862df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,14 +75,14 @@ set(CMAKE_AUTOMOC ON) option(BUILD_WITH_QT6 "Build Quotient with Qt 6 (EXPERIMENTAL)" OFF) if (BUILD_WITH_QT6) - find_package(Qt6 6.2 REQUIRED Core Network Gui Test) # TODO: Multimedia - set(Qt Qt6) - qt6_wrap_cpp(lib_SRCS lib/quotient_common.h) + set(QtMinVersion "6.0") else() - find_package(Qt5 5.12 REQUIRED Core Network Gui Multimedia Test) - set(Qt Qt5) + set(QtMinVersion "5.12") + set(QtExtraModules "Multimedia") # See #483 endif() -get_filename_component($Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) +string(REGEX REPLACE "^(.).*" "Qt\\1" Qt ${QtMinVersion}) # makes "Qt5" or "Qt6" +find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModules}) +get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") if (${PROJECT_NAME}_ENABLE_E2EE) @@ -299,7 +299,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) set(FIND_DEPS "find_dependency(QtOlm)") # For QuotientConfig.cmake.in endif() target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) -if (Qt STREQUAL Qt5) # Qt 6 hasn't got Multimedia component as yet +if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) endif() diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 3f6e475d..71f85363 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -135,6 +135,7 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, MsgType msgType, : RoomMessageEvent(plainBody, msgTypeToJson(msgType), content) {} +#if QT_VERSION_MAJOR < 6 TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) { auto filePath = file.absoluteFilePath(); @@ -151,11 +152,7 @@ TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) // done by starting to play the file. Left for a future implementation. if (mimeTypeName.startsWith("video/")) return new VideoContent(localUrl, file.size(), mimeType, -#if QT_VERSION_MAJOR < 6 QMediaResource(localUrl).resolution(), -#else - {}, -#endif file.fileName()); if (mimeTypeName.startsWith("audio/")) @@ -172,6 +169,7 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, : rawMsgTypeForFile(file), contentFromFile(file, asGenericFile)) {} +#endif RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj), _content(nullptr) diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 8303ce4e..7bcda2ba 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -42,8 +42,12 @@ public: explicit RoomMessageEvent(const QString& plainBody, MsgType msgType = MsgType::Text, EventContent::TypedBase* content = nullptr); +#if QT_VERSION_MAJOR < 6 + [[deprecated("Create an EventContent object on the client side" + " and pass it to other constructors")]] // explicit RoomMessageEvent(const QString& plainBody, const QFileInfo& file, bool asGenericFile = false); +#endif explicit RoomMessageEvent(const QJsonObject& obj); MsgType msgtype() const; -- cgit v1.2.3 From 0cb9a462945b1a9dd30979e9b97415dff16816e0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 16 Jul 2021 20:03:49 +0200 Subject: UriResolver: fix clang-tidy warnings --- lib/uriresolver.cpp | 2 ++ lib/uriresolver.h | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/uriresolver.cpp b/lib/uriresolver.cpp index 287e0552..681e3842 100644 --- a/lib/uriresolver.cpp +++ b/lib/uriresolver.cpp @@ -8,6 +8,8 @@ using namespace Quotient; +UriResolverBase::~UriResolverBase() = default; + UriResolveResult UriResolverBase::visitResource(Connection* account, const Uri& uri) { diff --git a/lib/uriresolver.h b/lib/uriresolver.h index f290e58b..ff97324a 100644 --- a/lib/uriresolver.h +++ b/lib/uriresolver.h @@ -42,23 +42,29 @@ public: UriResolveResult visitResource(Connection* account, const Uri& uri); protected: + virtual ~UriResolverBase() = 0; + /// Called by visitResource() when the passed URI identifies a Matrix user /*! * \return IncorrectAction if the action is not correct or not supported; * UriResolved if it is accepted; other values are disallowed */ - virtual UriResolveResult visitUser(User* user, const QString& action) + virtual UriResolveResult visitUser(User* user [[maybe_unused]], + const QString& action [[maybe_unused]]) { return IncorrectAction; } /// Called by visitResource() when the passed URI identifies a room or /// an event in a room - virtual void visitRoom(Room* room, const QString& eventId) {} + virtual void visitRoom(Room* room [[maybe_unused]], + const QString& eventId [[maybe_unused]]) + {} /// Called by visitResource() when the passed URI has `action() == "join"` /// and identifies a room that the user defined by the Connection argument /// is not a member of - virtual void joinRoom(Connection* account, const QString& roomAliasOrId, - const QStringList& viaServers = {}) + virtual void joinRoom(Connection* account [[maybe_unused]], + const QString& roomAliasOrId [[maybe_unused]], + const QStringList& viaServers [[maybe_unused]] = {}) {} /// Called by visitResource() when the passed URI has `type() == NonMatrix` /*! @@ -67,7 +73,10 @@ protected: * `return QDesktopServices::openUrl(url);` but it's strongly advised to * ask for a user confirmation beforehand. */ - virtual bool visitNonMatrix(const QUrl& url) { return false; } + virtual bool visitNonMatrix(const QUrl& url [[maybe_unused]]) + { + return false; + } }; /*! \brief Resolve the resource and invoke an action on it, via function objects -- cgit v1.2.3 From e9dace0e22a2aa3727bc842a30b4ad504029c0fc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 16 Jul 2021 20:09:42 +0200 Subject: Log thumbnail requests in their own category As pointed out by one of users, thumbnail requests produce quite a bit of logging traffic, so it's better to manage them separately. --- README.md | 10 +++++----- lib/jobs/mediathumbnailjob.cpp | 8 ++++++-- lib/logging.cpp | 1 + lib/logging.h | 1 + 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c993c31e..d5aae543 100644 --- a/README.md +++ b/README.md @@ -196,11 +196,11 @@ libQuotient uses Qt's logging categories to make switching certain types of logg quotient..= ``` where -- `` is one of: `main`, `jobs`, `jobs.sync`, `events`, `events.state` - (covering both the "usual" room state and account data), `events.messages`, - `events.ephemeral`, `e2ee` and `profiler` (you can always find the full list - in `lib/logging.cpp`) -- `` is one of `debug`, `info`, and `warning` +- `` is one of: `main`, `jobs`, `jobs.sync`, `jobs.thumbnail`, + `events`, `events.state` (covering both the "usual" room state and account + data), `events.messages`, `events.ephemeral`, `e2ee` and `profiler` (you can + always find the full list in `lib/logging.cpp`); +- `` is one of `debug`, `info`, and `warning`; - `` is either `true` or `false`. `*` can be used as a wildcard for any part between two dots, and semicolon is used for a separator. Latter statements override former ones, so if you want to switch on all debug logs except `jobs` you can set diff --git a/lib/jobs/mediathumbnailjob.cpp b/lib/jobs/mediathumbnailjob.cpp index 7dbf4ab3..6fe8ef26 100644 --- a/lib/jobs/mediathumbnailjob.cpp +++ b/lib/jobs/mediathumbnailjob.cpp @@ -17,13 +17,17 @@ MediaThumbnailJob::MediaThumbnailJob(const QString& serverName, const QString& mediaId, QSize requestedSize) : GetContentThumbnailJob(serverName, mediaId, requestedSize.width(), requestedSize.height(), "scale") -{} +{ + setLoggingCategory(THUMBNAILJOB); +} MediaThumbnailJob::MediaThumbnailJob(const QUrl& mxcUri, QSize requestedSize) : MediaThumbnailJob(mxcUri.authority(), mxcUri.path().mid(1), // sans leading '/' requestedSize) -{} +{ + setLoggingCategory(THUMBNAILJOB); +} QImage MediaThumbnailJob::thumbnail() const { return _thumbnail; } diff --git a/lib/logging.cpp b/lib/logging.cpp index af229684..ffcc851c 100644 --- a/lib/logging.cpp +++ b/lib/logging.cpp @@ -16,4 +16,5 @@ LOGGING_CATEGORY(EPHEMERAL, "quotient.events.ephemeral") LOGGING_CATEGORY(E2EE, "quotient.e2ee") LOGGING_CATEGORY(JOBS, "quotient.jobs") LOGGING_CATEGORY(SYNCJOB, "quotient.jobs.sync") +LOGGING_CATEGORY(THUMBNAILJOB, "quotient.jobs.thumbnail") LOGGING_CATEGORY(PROFILER, "quotient.profiler") diff --git a/lib/logging.h b/lib/logging.h index 432ed16f..264215e1 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -16,6 +16,7 @@ Q_DECLARE_LOGGING_CATEGORY(EPHEMERAL) Q_DECLARE_LOGGING_CATEGORY(E2EE) Q_DECLARE_LOGGING_CATEGORY(JOBS) Q_DECLARE_LOGGING_CATEGORY(SYNCJOB) +Q_DECLARE_LOGGING_CATEGORY(THUMBNAILJOB) Q_DECLARE_LOGGING_CATEGORY(PROFILER) namespace Quotient { -- cgit v1.2.3 From 680d51c4686ab5645405a0ca9631b71a567eaefa Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 16 Jul 2021 20:09:59 +0200 Subject: SyncData::parseJson(): minor optimisation --- lib/syncdata.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index adcba5cd..232f3694 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -171,13 +171,19 @@ void SyncData::parseJson(const QJsonObject& json, const QString& baseDir) deviceOneTimeKeysCount_); auto rooms = json.value("rooms"_ls).toObject(); - JoinStates::Int ii = 1; // ii is used to make a JoinState value auto totalRooms = 0; auto totalEvents = 0; - for (size_t i = 0; i < JoinStateStrings.size(); ++i, ii <<= 1) { + // The first comparison shortcuts the loop when not all states are there + // in the response (anything except "join" is only occasional, and "join" + // intentionally comes first in the enum). + for (size_t i = 0; + static_cast(i) < rooms.size() && i < JoinStateStrings.size(); ++i) + { + // This assumes that MemberState values go over powers of 2: 1,2,4,... + const auto joinState = JoinState(1U << i); const auto rs = rooms.value(JoinStateStrings[i]).toObject(); // We have a Qt container on the right and an STL one on the left - roomData.reserve(static_cast(rs.size())); + roomData.reserve(roomData.size() + static_cast(rs.size())); for (auto roomIt = rs.begin(); roomIt != rs.end(); ++roomIt) { auto roomJson = roomIt->isObject() @@ -187,7 +193,7 @@ void SyncData::parseJson(const QJsonObject& json, const QString& baseDir) unresolvedRoomIds.push_back(roomIt.key()); continue; } - roomData.emplace_back(roomIt.key(), JoinState(ii), roomJson); + roomData.emplace_back(roomIt.key(), joinState, roomJson); const auto& r = roomData.back(); totalEvents += r.state.size() + r.ephemeral.size() + r.accountData.size() + r.timeline.size(); -- cgit v1.2.3 From d85c63ffaf776683a74d0a20dcdb76f7ade8461b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 16 Jul 2021 22:11:27 +0200 Subject: User::rename(): don't discard the current state Closes #481. --- lib/user.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/user.cpp b/lib/user.cpp index 7933c5d9..6cc9e161 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -121,11 +121,11 @@ void User::rename(const QString& newName, const Room* r) rename(newName); return; } - Q_ASSERT_X(r->memberJoinState(this) == JoinState::Join, __FUNCTION__, + // #481: take the current state and update it with the new name + auto evtC = r->getCurrentState(id())->content(); + Q_ASSERT_X(evtC.membership == MembershipType::Join, __FUNCTION__, "Attempt to rename a user that's not a room member"); - const auto actualNewName = sanitized(newName); - MemberEventContent evtC; - evtC.displayName = actualNewName; + evtC.displayName = sanitized(newName); r->setState(id(), move(evtC)); // The state will be updated locally after it arrives with sync } -- cgit v1.2.3 From e3bdbc84ec5ada04e436dba9067d902e2c6c030a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 14 Jul 2021 14:28:12 +0200 Subject: CMakeLists: fixed potential linking errors around quotient_common.h quotient_common.h has Q_NAMESPACE but no own compilation unit, and moc was not called on it either - using metaobject data on an enumeration defined in that file leads to a linking error due to sharedMetaObject not being defined. The fix makes so that the file is detected by automoc with the respective definition being generated. Cherry-picked from a83ec900 (0.6.x branch). --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 285862df..deb50aea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,11 @@ endif () # Set up source files list(APPEND lib_SRCS + # This .h is special in that it declares a Q_NAMESPACE but has no .cpp + # where staticMetaObject for that namespace would be defined; passing it + # to add_library (see below) puts it on the automoc radar, producing + # a compilation unit with the needed definition. + lib/quotient_common.h lib/networkaccessmanager.cpp lib/connectiondata.cpp lib/connection.cpp -- cgit v1.2.3 From 004ebf8d5ba095ca1b11e30d86cedc2ff8c0cfe7 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 18:22:28 +0200 Subject: Room::postFile(): adjust to the changed RoomMessageEvent API 9a5fa623 dropped one of RoomMessageEvent constructors for Qt 6 in order to address #483 - breaking the build with Qt 6 along the way, as Room::postFile() relied on that constructor. This commit changes Room::postFile() in turn, deprecating the current signature and adding a new one that accepts an EventContent object rather than a path to a file. In order to achieve that, FileInfo and ImageInfo classes have gained new constructors that accept QFileInfo instead of the legacy series of parameters, streamlining usage of EventContent structures. --- lib/events/eventcontent.cpp | 51 +++++++++++++++------ lib/events/eventcontent.h | 23 ++++++---- lib/events/roomavatarevent.h | 4 +- lib/room.cpp | 105 ++++++++++++++++++++++++++----------------- lib/room.h | 5 +++ quotest/quotest.cpp | 9 ++-- 6 files changed, 128 insertions(+), 69 deletions(-) diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index b249b160..1f28f195 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -5,10 +5,13 @@ #include "converters.h" #include "util.h" +#include "logging.h" #include +#include using namespace Quotient::EventContent; +using std::move; QJsonObject Base::toJson() const { @@ -17,22 +20,37 @@ QJsonObject Base::toJson() const return o; } -FileInfo::FileInfo(const QUrl& u, qint64 payloadSize, const QMimeType& mimeType, - const QString& originalFilename) +FileInfo::FileInfo(const QFileInfo &fi) + : mimeType(QMimeDatabase().mimeTypeForFile(fi)) + , url(QUrl::fromLocalFile(fi.filePath())) + , payloadSize(fi.size()) + , originalName(fi.fileName()) +{ + Q_ASSERT(fi.isFile()); +} + +FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, + QString originalFilename) : mimeType(mimeType) - , url(u) + , url(move(u)) , payloadSize(payloadSize) - , originalName(originalFilename) -{} + , originalName(move(originalFilename)) +{ + if (!isValid()) + qCWarning(MESSAGES) + << "To client developers: using FileInfo(QUrl, qint64, ...) " + "constructor for non-mxc resources is deprecated since Quotient " + "0.7; for local resources, use FileInfo(QFileInfo) instead"; +} -FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename) +FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + QString originalFilename) : originalInfoJson(infoJson) , mimeType( QMimeDatabase().mimeTypeForName(infoJson["mimetype"_ls].toString())) - , url(u) + , url(move(mxcUrl)) , payloadSize(fromJson(infoJson["size"_ls])) - , originalName(originalFilename) + , originalName(move(originalFilename)) { if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); @@ -53,14 +71,19 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("mimetype"), mimeType.name()); } -ImageInfo::ImageInfo(const QUrl& u, qint64 fileSize, QMimeType mimeType, - const QSize& imageSize, const QString& originalFilename) - : FileInfo(u, fileSize, mimeType, originalFilename), imageSize(imageSize) +ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) + : FileInfo(fi), imageSize(imageSize) +{} + +ImageInfo::ImageInfo(const QUrl& mxcUrl, qint64 fileSize, const QMimeType& type, + QSize imageSize, const QString& originalFilename) + : FileInfo(mxcUrl, fileSize, type, originalFilename) + , imageSize(imageSize) {} -ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson, +ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, const QString& originalFilename) - : FileInfo(u, infoJson, originalFilename) + : FileInfo(mxcUrl, infoJson, originalFilename) , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) {} diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 60d1f7b7..78c5b287 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -10,7 +10,8 @@ #include #include #include -#include + +class QFileInfo; namespace Quotient { namespace EventContent { @@ -73,11 +74,13 @@ namespace EventContent { */ class FileInfo { public: - explicit FileInfo(const QUrl& u, qint64 payloadSize = -1, + FileInfo() = default; + explicit FileInfo(const QFileInfo& fi); + explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1, const QMimeType& mimeType = {}, - const QString& originalFilename = {}); - FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}); + QString originalFilename = {}); + FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + QString originalFilename = {}); bool isValid() const; @@ -113,10 +116,12 @@ namespace EventContent { */ class ImageInfo : public FileInfo { public: - explicit ImageInfo(const QUrl& u, qint64 fileSize = -1, - QMimeType mimeType = {}, const QSize& imageSize = {}, + ImageInfo() = default; + explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); + explicit ImageInfo(const QUrl& mxcUrl, qint64 fileSize = -1, + const QMimeType& type = {}, QSize imageSize = {}, const QString& originalFilename = {}); - ImageInfo(const QUrl& u, const QJsonObject& infoJson, + ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, const QString& originalFilename = {}); void fillInfoJson(QJsonObject* infoJson) const; @@ -134,7 +139,7 @@ namespace EventContent { */ class Thumbnail : public ImageInfo { public: - Thumbnail() : ImageInfo(QUrl()) {} // To allow empty thumbnails + Thumbnail() = default; // Allow empty thumbnails Thumbnail(const QJsonObject& infoJson); Thumbnail(const ImageInfo& info) : ImageInfo(info) {} using ImageInfo::ImageInfo; diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index a4257895..3fa11a0f 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -20,12 +20,12 @@ public: : StateEvent(typeId(), matrixTypeId(), QString(), avatar) {} // A replica of EventContent::ImageInfo constructor - explicit RoomAvatarEvent(const QUrl& u, qint64 fileSize = -1, + explicit RoomAvatarEvent(const QUrl& mxcUrl, qint64 fileSize = -1, QMimeType mimeType = {}, const QSize& imageSize = {}, const QString& originalFilename = {}) : RoomAvatarEvent(EventContent::ImageContent { - u, fileSize, mimeType, imageSize, originalFilename }) + mxcUrl, fileSize, mimeType, imageSize, originalFilename }) {} QUrl url() const { return content().url; } diff --git a/lib/room.cpp b/lib/room.cpp index 0984bd96..6b729b8f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -306,6 +306,8 @@ public: return sendEvent(makeEvent(std::forward(eventArgs)...)); } + QString doPostFile(RoomEventPtr &&msgEvent, const QUrl &localUrl); + RoomEvent* addAsPending(RoomEventPtr&& event); QString doSendEvent(const RoomEvent* pEvent); @@ -1756,57 +1758,80 @@ QString Room::postReaction(const QString& eventId, const QString& key) return d->sendEvent(EventRelation::annotate(eventId, key)); } -QString Room::postFile(const QString& plainText, const QUrl& localPath, - bool asGenericFile) +QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) { - QFileInfo localFile { localPath.toLocalFile() }; - Q_ASSERT(localFile.isFile()); - - const auto txnId = - d->addAsPending( - makeEvent(plainText, localFile, asGenericFile)) - ->transactionId(); + const auto txnId = addAsPending(move(msgEvent))->transactionId(); // Remote URL will only be known after upload; fill in the local path // to enable the preview while the event is pending. - uploadFile(txnId, localPath); + q->uploadFile(txnId, localUrl); // Below, the upload job is used as a context object to clean up connections - const auto& transferJob = d->fileTransfers.value(txnId).job; - connect(this, &Room::fileTransferCompleted, transferJob, - [this, txnId](const QString& id, const QUrl&, const QUrl& mxcUri) { - if (id == txnId) { - auto it = findPendingEvent(txnId); - if (it != d->unsyncedEvents.end()) { - it->setFileUploaded(mxcUri); - emit pendingEventChanged( - int(it - d->unsyncedEvents.begin())); - d->doSendEvent(it->get()); - } else { - // Normally in this situation we should instruct - // the media server to delete the file; alas, there's no - // API specced for that. - qCWarning(MAIN) << "File uploaded to" << mxcUri - << "but the event referring to it was " - "cancelled"; - } + const auto& transferJob = fileTransfers.value(txnId).job; + connect(q, &Room::fileTransferCompleted, transferJob, + [this, txnId](const QString& tId, const QUrl&, const QUrl& mxcUri) { + if (tId != txnId) + return; + + const auto it = q->findPendingEvent(txnId); + if (it != unsyncedEvents.end()) { + it->setFileUploaded(mxcUri); + emit q->pendingEventChanged( + int(it - unsyncedEvents.begin())); + doSendEvent(it->get()); + } else { + // Normally in this situation we should instruct + // the media server to delete the file; alas, there's no + // API specced for that. + qCWarning(MAIN) << "File uploaded to" << mxcUri + << "but the event referring to it was " + "cancelled"; } }); - connect(this, &Room::fileTransferCancelled, transferJob, - [this, txnId](const QString& id) { - if (id == txnId) { - auto it = findPendingEvent(txnId); - if (it != d->unsyncedEvents.end()) { - const auto idx = int(it - d->unsyncedEvents.begin()); - emit pendingEventAboutToDiscard(idx); - // See #286 on why iterator may not be valid here. - d->unsyncedEvents.erase(d->unsyncedEvents.begin() + idx); - emit pendingEventDiscarded(); - } - } + connect(q, &Room::fileTransferCancelled, transferJob, + [this, txnId](const QString& tId) { + if (tId != txnId) + return; + + const auto it = q->findPendingEvent(txnId); + if (it == unsyncedEvents.end()) + return; + + const auto idx = int(it - unsyncedEvents.begin()); + emit q->pendingEventAboutToDiscard(idx); + // See #286 on why `it` may not be valid here. + unsyncedEvents.erase(unsyncedEvents.begin() + idx); + emit q->pendingEventDiscarded(); }); return txnId; } +QString Room::postFile(const QString& plainText, + EventContent::TypedBase* content) +{ + Q_ASSERT(content != nullptr && content->fileInfo() != nullptr); + const auto* const fileInfo = content->fileInfo(); + Q_ASSERT(fileInfo != nullptr); + QFileInfo localFile { fileInfo->url.toLocalFile() }; + Q_ASSERT(localFile.isFile()); + + return d->doPostFile( + makeEvent( + plainText, RoomMessageEvent::rawMsgTypeForFile(localFile), content), + fileInfo->url); +} + +#if QT_VERSION_MAJOR < 6 +QString Room::postFile(const QString& plainText, const QUrl& localPath, + bool asGenericFile) +{ + QFileInfo localFile { localPath.toLocalFile() }; + Q_ASSERT(localFile.isFile()); + return d->doPostFile(makeEvent(plainText, localFile, + asGenericFile), + localPath); +} +#endif + QString Room::postEvent(RoomEvent* event) { return d->sendEvent(RoomEventPtr(event)); diff --git a/lib/room.h b/lib/room.h index 26d0121e..d71bff9c 100644 --- a/lib/room.h +++ b/lib/room.h @@ -549,8 +549,13 @@ public Q_SLOTS: QString postHtmlText(const QString& plainText, const QString& html); /// Send a reaction on a given event with a given key QString postReaction(const QString& eventId, const QString& key); + + QString postFile(const QString& plainText, EventContent::TypedBase* content); +#if QT_VERSION_MAJOR < 6 + /// \deprecated Use postFile(QString, MessageEventType, EventContent) instead QString postFile(const QString& plainText, const QUrl& localPath, bool asGenericFile = false); +#endif /** Post a pre-created room message event * * Takes ownership of the event, deleting it once the matching one diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index a0914c72..5646d54d 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -397,15 +397,16 @@ TEST_IMPL(sendFile) } tf->write("Test"); tf->close(); + QFileInfo tfi { *tf }; // QFileInfo::fileName brings only the file name; QFile::fileName brings // the full path - const auto tfName = QFileInfo(*tf).fileName(); + const auto tfName = tfi.fileName(); clog << "Sending file " << tfName.toStdString() << endl; - const auto txnId = - targetRoom->postFile("Test file", QUrl::fromLocalFile(tf->fileName())); + const auto txnId = targetRoom->postFile( + "Test file", new EventContent::FileContent(tfi)); if (!validatePendingEvent(txnId)) { clog << "Invalid pending event right after submitting" << endl; - delete tf; + tf->deleteLater(); FAIL_TEST(); } -- cgit v1.2.3 From 86fe0a8f0682c0122439d53cc96b4b742a69ffcf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 18:24:39 +0200 Subject: Make EventContent::TypedBase() constructor protected TypedBase is an abstract class; constructing it doesn't make sense. But even if it were not abstract, it's not supposed to be instantiated. --- lib/events/eventcontent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 78c5b287..b3a5f280 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -153,13 +153,13 @@ namespace EventContent { class TypedBase : public Base { public: - explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} virtual QMimeType type() const = 0; virtual const FileInfo* fileInfo() const { return nullptr; } virtual FileInfo* fileInfo() { return nullptr; } virtual const Thumbnail* thumbnailInfo() const { return nullptr; } protected: + explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} using Base::Base; }; -- cgit v1.2.3 From 6d24915e4bdd56dbdace8358297ee9d2d9aa83a0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 18:53:57 +0200 Subject: Revert previous commit Q_DECLARE_METATYPE is really unhappy about types without a public default constructor. --- lib/events/eventcontent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index b3a5f280..78c5b287 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -153,13 +153,13 @@ namespace EventContent { class TypedBase : public Base { public: + explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} virtual QMimeType type() const = 0; virtual const FileInfo* fileInfo() const { return nullptr; } virtual FileInfo* fileInfo() { return nullptr; } virtual const Thumbnail* thumbnailInfo() const { return nullptr; } protected: - explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} using Base::Base; }; -- cgit v1.2.3 From 110190d48a80a471e6d10d048602390b35e7ed07 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 18:24:39 +0200 Subject: Re-apply the previous commit and actually fix the breakage Ok, it was stupid to delete #include in 004ebf8d and then to expect that Qt macros would still work, given that I don't use QObject. In my defense I can only say that with Qt 6 it still compiled. --- lib/events/eventcontent.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 78c5b287..1d81bd72 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -10,6 +10,7 @@ #include #include #include +#include class QFileInfo; @@ -153,13 +154,13 @@ namespace EventContent { class TypedBase : public Base { public: - explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} virtual QMimeType type() const = 0; virtual const FileInfo* fileInfo() const { return nullptr; } virtual FileInfo* fileInfo() { return nullptr; } virtual const Thumbnail* thumbnailInfo() const { return nullptr; } protected: + explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} using Base::Base; }; -- cgit v1.2.3 From c05b5c2b79f9ab301fee587ee781b9c8e18b8a2f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 16 Jul 2021 20:03:06 +0200 Subject: MembershipType -> Membership, also used for JoinState Instead of being defined independently, JoinState now uses values from the Membership enumeration (former MemberEventContent::MembershipType) that was moved to quotient_common.h for that purpose. Both enumerations gained a Q_FLAG_NS decoration and operator<< overrides that strip "Quotient::" prefix when dumping member/join state values to the log - obviating toCString(JoinState) along the way. Quotient::MembershipType alias is deprecated from now. --- CMakeLists.txt | 6 +--- lib/connection.cpp | 4 +-- lib/connection.h | 1 - lib/events/roommemberevent.cpp | 65 ++++++++++++++++++++-------------------- lib/events/roommemberevent.h | 26 ++++++++-------- lib/joinstate.h | 32 -------------------- lib/quotient_common.cpp | 45 ++++++++++++++++++++++++++++ lib/quotient_common.h | 67 +++++++++++++++++++++++++++++++++++++----- lib/room.cpp | 49 ++++++++++++++++-------------- lib/room.h | 9 +++++- lib/syncdata.h | 2 +- lib/user.cpp | 2 +- 12 files changed, 190 insertions(+), 118 deletions(-) delete mode 100644 lib/joinstate.h create mode 100644 lib/quotient_common.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index deb50aea..49105389 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,11 +122,7 @@ endif () # Set up source files list(APPEND lib_SRCS - # This .h is special in that it declares a Q_NAMESPACE but has no .cpp - # where staticMetaObject for that namespace would be defined; passing it - # to add_library (see below) puts it on the automoc radar, producing - # a compilation unit with the needed definition. - lib/quotient_common.h + lib/quotient_common.cpp lib/networkaccessmanager.cpp lib/connectiondata.cpp lib/connection.cpp diff --git a/lib/connection.cpp b/lib/connection.cpp index e076957a..7dd04aaa 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -640,7 +640,7 @@ void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, } qWarning(MAIN) << "Room" << roomData.roomId << "has just been forgotten but /sync returned it in" - << toCString(roomData.joinState) + << roomData.joinState << "state - suspiciously fast turnaround"; } if (auto* r = q->provideRoom(roomData.roomId, roomData.joinState)) { @@ -1356,7 +1356,7 @@ void Connection::Private::removeRoom(const QString& roomId) for (auto f : { false, true }) if (auto r = roomMap.take({ roomId, f })) { qCDebug(MAIN) << "Room" << r->objectName() << "in state" - << toCString(r->joinState()) << "will be deleted"; + << r->joinState() << "will be deleted"; emit r->beforeDestruction(r); r->deleteLater(); } diff --git a/lib/connection.h b/lib/connection.h index 0d22d01f..a7a071f3 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -6,7 +6,6 @@ #pragma once #include "ssosession.h" -#include "joinstate.h" #include "qt_connection_util.h" #include "quotient_common.h" diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 9634ca3a..8a6bddd8 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -7,27 +7,26 @@ #include "converters.h" #include "logging.h" -#include - -static const std::array membershipStrings = { - { QStringLiteral("invite"), QStringLiteral("join"), QStringLiteral("knock"), - QStringLiteral("leave"), QStringLiteral("ban") } -}; +#include namespace Quotient { template <> -struct JsonConverter { - static MembershipType load(const QJsonValue& jv) +struct JsonConverter { + static Membership load(const QJsonValue& jv) { - const auto& membershipString = jv.toString(); - for (auto it = membershipStrings.begin(); it != membershipStrings.end(); - ++it) - if (membershipString == *it) - return MembershipType(it - membershipStrings.begin()); - - if (!membershipString.isEmpty()) - qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; - return MembershipType::Undefined; + const auto& ms = jv.toString(); + if (ms.isEmpty()) + { + qCWarning(EVENTS) << "Empty member state:" << ms; + return Membership::Invalid; + } + const auto it = + std::find(MembershipStrings.begin(), MembershipStrings.end(), ms); + if (it != MembershipStrings.end()) + return Membership(1U << (it - MembershipStrings.begin())); + + qCWarning(EVENTS) << "Unknown Membership value: " << ms; + return Membership::Invalid; } }; } // namespace Quotient @@ -35,7 +34,7 @@ struct JsonConverter { using namespace Quotient; MemberEventContent::MemberEventContent(const QJsonObject& json) - : membership(fromJson(json["membership"_ls])) + : membership(fromJson(json["membership"_ls])) , isDirect(json["is_direct"_ls].toBool()) , displayName(fromJson>(json["displayname"_ls])) , avatarUrl(fromJson>(json["avatar_url"_ls])) @@ -48,10 +47,12 @@ MemberEventContent::MemberEventContent(const QJsonObject& json) void MemberEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); - Q_ASSERT_X(membership != MembershipType::Undefined, __FUNCTION__, - "The key 'membership' must be explicit in MemberEventContent"); - if (membership != MembershipType::Undefined) - o->insert(QStringLiteral("membership"), membershipStrings[membership]); + if (membership != Membership::Invalid) + o->insert( + QStringLiteral("membership"), + MembershipStrings[qCountTrailingZeroBits( + std::underlying_type_t(membership)) + + 1]); if (displayName) o->insert(QStringLiteral("displayname"), *displayName); if (avatarUrl && avatarUrl->isValid()) @@ -67,37 +68,37 @@ bool RoomMemberEvent::changesMembership() const bool RoomMemberEvent::isInvite() const { - return membership() == MembershipType::Invite && changesMembership(); + return membership() == Membership::Invite && changesMembership(); } bool RoomMemberEvent::isRejectedInvite() const { - return membership() == MembershipType::Leave && prevContent() - && prevContent()->membership == MembershipType::Invite; + return membership() == Membership::Leave && prevContent() + && prevContent()->membership == Membership::Invite; } bool RoomMemberEvent::isJoin() const { - return membership() == MembershipType::Join && changesMembership(); + return membership() == Membership::Join && changesMembership(); } bool RoomMemberEvent::isLeave() const { - return membership() == MembershipType::Leave && prevContent() + return membership() == Membership::Leave && prevContent() && prevContent()->membership != membership() - && prevContent()->membership != MembershipType::Ban - && prevContent()->membership != MembershipType::Invite; + && prevContent()->membership != Membership::Ban + && prevContent()->membership != Membership::Invite; } bool RoomMemberEvent::isBan() const { - return membership() == MembershipType::Ban && changesMembership(); + return membership() == Membership::Ban && changesMembership(); } bool RoomMemberEvent::isUnban() const { - return membership() == MembershipType::Leave && prevContent() - && prevContent()->membership == MembershipType::Ban; + return membership() == Membership::Leave && prevContent() + && prevContent()->membership == Membership::Ban; } bool RoomMemberEvent::isRename() const diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index f2fbe689..f3047159 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -7,23 +7,21 @@ #include "eventcontent.h" #include "stateevent.h" +#include "quotient_common.h" namespace Quotient { class MemberEventContent : public EventContent::Base { public: - enum MembershipType : unsigned char { - Invite = 0, - Join, - Knock, - Leave, - Ban, - Undefined - }; + using MembershipType + [[deprecated("Use Quotient::Membership instead")]] = Membership; - explicit MemberEventContent(MembershipType mt = Join) : membership(mt) {} + explicit MemberEventContent(Membership ms = Membership::Join) + : membership(ms) + {} explicit MemberEventContent(const QJsonObject& json); - MembershipType membership; + Membership membership; + /// (Only for invites) Whether the invite is to a direct chat bool isDirect = false; Omittable displayName; Omittable avatarUrl; @@ -33,15 +31,15 @@ protected: void fillJson(QJsonObject* o) const override; }; -using MembershipType = MemberEventContent::MembershipType; +using MembershipType [[deprecated("Use Membership instead")]] = Membership; class RoomMemberEvent : public StateEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) - using MembershipType = MemberEventContent::MembershipType; - Q_ENUM(MembershipType) + using MembershipType + [[deprecated("Use Quotient::Membership instead")]] = Membership; explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} @@ -65,7 +63,7 @@ public: : StateEvent(type, fullJson) {} - MembershipType membership() const { return content().membership; } + Membership membership() const { return content().membership; } QString userId() const { return stateKey(); } bool isDirect() const { return content().isDirect; } Omittable newDisplayName() const { return content().displayName; } diff --git a/lib/joinstate.h b/lib/joinstate.h deleted file mode 100644 index 805ce73a..00000000 --- a/lib/joinstate.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: 2016 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include - -#include - -namespace Quotient { -enum class JoinState : unsigned int { - Join = 0x1, - Invite = 0x2, - Leave = 0x4, -}; - -Q_DECLARE_FLAGS(JoinStates, JoinState) - -// We cannot use Q_ENUM outside of a Q_OBJECT and besides, we want -// to use strings that match respective JSON keys. -static const std::array JoinStateStrings { { "join", "invite", - "leave" } }; - -inline const char* toCString(JoinState js) -{ - size_t state = size_t(js), index = 0; - while (state >>= 1u) - ++index; - return JoinStateStrings[index]; -} -} // namespace Quotient -Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates) diff --git a/lib/quotient_common.cpp b/lib/quotient_common.cpp new file mode 100644 index 00000000..805070d1 --- /dev/null +++ b/lib/quotient_common.cpp @@ -0,0 +1,45 @@ +#include "quotient_common.h" + +#include + +using namespace Quotient; + +template +inline QDebug suppressScopeAndDump(QDebug dbg, Enum e) +{ + // Suppress "Quotient::" prefix + QDebugStateSaver _dss(dbg); + dbg.setVerbosity(QDebug::MinimumVerbosity); + return qt_QMetaEnum_debugOperator(dbg, std::underlying_type_t(e), + qt_getEnumMetaObject(e), + qt_getEnumName(e)); +} + +template +inline QDebug suppressScopeAndDump(QDebug dbg, const QFlags& f) +{ + // Suppress "Quotient::" prefix + QDebugStateSaver _dss(dbg); + dbg.setVerbosity(QDebug::MinimumVerbosity); + return qt_QMetaEnum_flagDebugOperator_helper(dbg, f); +} + +QDebug operator<<(QDebug dbg, Membership m) +{ + return suppressScopeAndDump(dbg, m); +} + +QDebug operator<<(QDebug dbg, MembershipMask mm) +{ + return suppressScopeAndDump(dbg, mm) << ")"; +} + +QDebug operator<<(QDebug dbg, JoinState js) +{ + return suppressScopeAndDump(dbg, js); +} + +QDebug operator<<(QDebug dbg, JoinStates jss) +{ + return suppressScopeAndDump(dbg, jss) << ")"; +} diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 22fdbe94..789128cf 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -8,15 +8,62 @@ namespace Quotient { Q_NAMESPACE -/** Enumeration with flags defining the network job running policy - * So far only background/foreground flags are available. - * - * \sa Connection::callApi, Connection::run - */ +// TODO: code like this should be generated from the CS API definition + +//! \brief Membership states +//! +//! These are used for member events. The names here are case-insensitively +//! equal to state names used on the wire. +//! \sa MemberEventContent, RoomMemberEvent +enum class Membership : unsigned int { + // Specific power-of-2 values (1,2,4,...) are important here as syncdata.cpp + // depends on that, as well as Join being the first in line + Invalid = 0x0, + Join = 0x1, + Leave = 0x2, + Invite = 0x4, + Knock = 0x8, + Ban = 0x10, + Undefined = Invalid +}; +Q_DECLARE_FLAGS(MembershipMask, Membership) +Q_FLAG_NS(MembershipMask) + +constexpr inline std::array MembershipStrings = { + // The order MUST be the same as the order in the original enum + "join", "leave", "invite", "knock", "ban" +}; + +//! \brief Local user join-state names +//! +//! This represents a subset of Membership values that may arrive as the local +//! user's state grouping for the sync response. +//! \sa SyncData +enum class JoinState : std::underlying_type_t { + Invalid = std::underlying_type_t(Membership::Invalid), + Join = std::underlying_type_t(Membership::Join), + Leave = std::underlying_type_t(Membership::Leave), + Invite = std::underlying_type_t(Membership::Invite), + Knock = std::underlying_type_t(Membership::Knock), +}; +Q_DECLARE_FLAGS(JoinStates, JoinState) +Q_FLAG_NS(JoinStates) + +constexpr inline std::array JoinStateStrings { + MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], + MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ +}; + +//! \brief Network job running policy flags +//! +//! So far only background/foreground flags are available. +//! \sa Connection::callApi, Connection::run enum RunningPolicy { ForegroundRequest = 0x0, BackgroundRequest = 0x1 }; Q_ENUM_NS(RunningPolicy) +//! \brief The result of URI resolution using UriResolver +//! \sa UriResolver enum UriResolveResult : short { StillResolving = -1, UriResolved = 0, @@ -28,5 +75,11 @@ enum UriResolveResult : short { Q_ENUM_NS(UriResolveResult) } // namespace Quotient -/// \deprecated Use namespace Quotient instead -namespace QMatrixClient = Quotient; +Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) +Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates) + +class QDebug; +QDebug operator<<(QDebug dbg, Quotient::Membership m); +QDebug operator<<(QDebug dbg, Quotient::MembershipMask m); +QDebug operator<<(QDebug dbg, Quotient::JoinState js); +QDebug operator<<(QDebug dbg, Quotient::JoinStates js); diff --git a/lib/room.cpp b/lib/room.cpp index 6b729b8f..54c67c2f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -460,7 +460,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) emit baseStateLoaded(); return this == r; // loadedRoomState fires only once per room }); - qCDebug(STATE) << "New" << toCString(initialJoinState) << "Room:" << id; + qCDebug(STATE) << "New" << initialJoinState << "Room:" << id; } Room::~Room() { delete d; } @@ -603,6 +603,11 @@ JoinState Room::memberJoinState(User* user) const : JoinState::Leave; } +Membership Room::memberState(User* user) const +{ + return d->getCurrentState(user->id())->membership(); +} + JoinState Room::joinState() const { return d->joinState; } void Room::setJoinState(JoinState state) @@ -611,8 +616,8 @@ void Room::setJoinState(JoinState state) if (state == oldState) return; d->joinState = state; - qCDebug(STATE) << "Room" << id() << "changed state: " << int(oldState) - << "->" << int(state); + qCDebug(STATE) << "Room" << id() << "changed state: " << oldState + << "->" << state; emit changed(Change::JoinStateChange); emit joinStateChanged(oldState, state); } @@ -2513,16 +2518,16 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return false; // Stay low and hope for the best... } const auto prevMembership = oldRme ? oldRme->membership() - : MembershipType::Leave; + : Membership::Leave; switch (prevMembership) { - case MembershipType::Invite: + case Membership::Invite: if (rme.membership() != prevMembership) { d->usersInvited.removeOne(u); Q_ASSERT(!d->usersInvited.contains(u)); } break; - case MembershipType::Join: - if (rme.membership() == MembershipType::Join) { + case Membership::Join: + if (rme.membership() == Membership::Join) { // rename/avatar change or no-op if (rme.newDisplayName()) { emit memberAboutToRename(u, *rme.newDisplayName()); @@ -2536,7 +2541,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return false; } } else { - if (rme.membership() == MembershipType::Invite) + if (rme.membership() == Membership::Invite) qCWarning(MAIN) << "Membership change from Join to Invite:" << rme; // whatever the new membership, it's no more Join @@ -2544,16 +2549,16 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) emit userRemoved(u); } break; - case MembershipType::Ban: - case MembershipType::Knock: - case MembershipType::Leave: - if (rme.membership() == MembershipType::Invite - || rme.membership() == MembershipType::Join) { + case Membership::Ban: + case Membership::Knock: + case Membership::Leave: + if (rme.membership() == Membership::Invite + || rme.membership() == Membership::Join) { d->membersLeft.removeOne(u); Q_ASSERT(!d->membersLeft.contains(u)); } break; - case MembershipType::Undefined: + case Membership::Undefined: ; // A warning will be dropped in the post-processing block below } return true; @@ -2636,10 +2641,10 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) static_cast(oldStateEvent); const auto prevMembership = oldMemberEvent ? oldMemberEvent->membership() - : MembershipType::Leave; + : Membership::Leave; switch (evt.membership()) { - case MembershipType::Join: - if (prevMembership != MembershipType::Join) { + case Membership::Join: + if (prevMembership != Membership::Join) { d->insertMemberIntoMap(u); emit userAdded(u); } else { @@ -2651,19 +2656,19 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) emit memberAvatarChanged(u); } break; - case MembershipType::Invite: + case Membership::Invite: if (!d->usersInvited.contains(u)) d->usersInvited.push_back(u); if (u == localUser() && evt.isDirect()) connection()->addToDirectChats(this, user(evt.senderId())); break; - case MembershipType::Knock: - case MembershipType::Ban: - case MembershipType::Leave: + case Membership::Knock: + case Membership::Ban: + case Membership::Leave: if (!d->membersLeft.contains(u)) d->membersLeft.append(u); break; - case MembershipType::Undefined: + case Membership::Undefined: qCWarning(MEMBERS) << "Ignored undefined membership type"; } return MembersChange; diff --git a/lib/room.h b/lib/room.h index d71bff9c..cdbfe58f 100644 --- a/lib/room.h +++ b/lib/room.h @@ -11,7 +11,7 @@ #include "connection.h" #include "eventitem.h" -#include "joinstate.h" +#include "quotient_common.h" #include "csapi/message_pagination.h" @@ -245,6 +245,8 @@ public: /** * \brief Check the join state of a given user in this room * + * \deprecated Use memberState and check against a mask + * * \note Banned and invited users are not tracked separately for now (Leave * will be returned for them). * @@ -252,6 +254,11 @@ public: */ Q_INVOKABLE Quotient::JoinState memberJoinState(Quotient::User* user) const; + //! \brief Check the join state of a given user in this room + //! + //! \return the given user's state with respect to the room + Q_INVOKABLE Quotient::Membership memberState(User* user) const; + //! \brief Get a display name (without disambiguation) for the given member //! //! \sa safeMemberName, htmlSafeMemberName diff --git a/lib/syncdata.h b/lib/syncdata.h index e69bac17..0153bfd6 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -3,7 +3,7 @@ #pragma once -#include "joinstate.h" +#include "quotient_common.h" #include "events/stateevent.h" diff --git a/lib/user.cpp b/lib/user.cpp index 6cc9e161..c97e33a4 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -123,7 +123,7 @@ void User::rename(const QString& newName, const Room* r) } // #481: take the current state and update it with the new name auto evtC = r->getCurrentState(id())->content(); - Q_ASSERT_X(evtC.membership == MembershipType::Join, __FUNCTION__, + Q_ASSERT_X(evtC.membership == Membership::Join, __FUNCTION__, "Attempt to rename a user that's not a room member"); evtC.displayName = sanitized(newName); r->setState(id(), move(evtC)); -- cgit v1.2.3 From e05402045261a404ad5d8add63b82672d3d9aebb Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 17 Jul 2021 13:47:19 +0200 Subject: Fix building with Qt 5.12 --- lib/quotient_common.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/quotient_common.cpp b/lib/quotient_common.cpp index 805070d1..5d7a3027 100644 --- a/lib/quotient_common.cpp +++ b/lib/quotient_common.cpp @@ -9,7 +9,7 @@ inline QDebug suppressScopeAndDump(QDebug dbg, Enum e) { // Suppress "Quotient::" prefix QDebugStateSaver _dss(dbg); - dbg.setVerbosity(QDebug::MinimumVerbosity); + dbg.setVerbosity(0 /* QDebug::MinimumVerbosity since Qt 5.13 */); return qt_QMetaEnum_debugOperator(dbg, std::underlying_type_t(e), qt_getEnumMetaObject(e), qt_getEnumName(e)); @@ -20,7 +20,7 @@ inline QDebug suppressScopeAndDump(QDebug dbg, const QFlags& f) { // Suppress "Quotient::" prefix QDebugStateSaver _dss(dbg); - dbg.setVerbosity(QDebug::MinimumVerbosity); + dbg.setVerbosity(0 /* QDebug::MinimumVerbosity since Qt 5.13 */); return qt_QMetaEnum_flagDebugOperator_helper(dbg, f); } -- cgit v1.2.3 From 3c28d13c1a61999e7c3141f3ca08b5b734bd160c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 19:00:07 +0200 Subject: Introduce to_array() to fix building on macOS A previous incarnation, make_array, existed in basejob.cpp before. The new direction taken by C++20 is to either deduce the array (but the used Apple standard library doesn't have deduction guides yet) or to use to_array() that converts a C array to std::array. This latter option is taken here, with to_array() defined in quotient_common.h until we move over to C++20. --- lib/jobs/basejob.cpp | 13 +++---------- lib/quotient_common.h | 27 ++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 9a7b9b5e..400a9243 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -5,6 +5,7 @@ #include "basejob.h" #include "connectiondata.h" +#include "quotient_common.h" #include #include @@ -15,8 +16,6 @@ #include #include -#include - using namespace Quotient; using std::chrono::seconds, std::chrono::milliseconds; using namespace std::chrono_literals; @@ -63,12 +62,6 @@ QDebug BaseJob::Status::dumpToLog(QDebug dbg) const return dbg << ": " << message; } -template -constexpr auto make_array(Ts&&... items) -{ - return std::array, sizeof...(Ts)>({items...}); -} - class BaseJob::Private { public: struct JobTimeoutConfig { @@ -163,8 +156,8 @@ public: { // FIXME: use std::array {} when Apple stdlib gets deduction guides for it static const auto verbs = - make_array(QStringLiteral("GET"), QStringLiteral("PUT"), - QStringLiteral("POST"), QStringLiteral("DELETE")); + to_array({ QStringLiteral("GET"), QStringLiteral("PUT"), + QStringLiteral("POST"), QStringLiteral("DELETE") }); const auto verbWord = verbs.at(size_t(verb)); return verbWord % ' ' % (reply ? reply->url().toString(QUrl::RemoveQuery) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 789128cf..037d5ded 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -8,6 +8,24 @@ namespace Quotient { Q_NAMESPACE +namespace impl { + template + constexpr std::array, N> + to_array_impl(T (&&a)[N], std::index_sequence) + { + return { {std::move(a[I])...} }; + } +} +// std::array {} needs explicit template parameters on macOS because +// Apple stdlib doesn't have deduction guides for std::array; to alleviate that, +// to_array() is borrowed from C++20 (thanks to cppreference for the possible +// implementation: https://en.cppreference.com/w/cpp/container/array/to_array) +template +constexpr auto to_array(T (&& items)[N]) +{ + return impl::to_array_impl(std::move(items), std::make_index_sequence{}); +} + // TODO: code like this should be generated from the CS API definition //! \brief Membership states @@ -29,10 +47,9 @@ enum class Membership : unsigned int { Q_DECLARE_FLAGS(MembershipMask, Membership) Q_FLAG_NS(MembershipMask) -constexpr inline std::array MembershipStrings = { +constexpr inline auto MembershipStrings = to_array( // The order MUST be the same as the order in the original enum - "join", "leave", "invite", "knock", "ban" -}; + { "join", "leave", "invite", "knock", "ban" }); //! \brief Local user join-state names //! @@ -49,10 +66,10 @@ enum class JoinState : std::underlying_type_t { Q_DECLARE_FLAGS(JoinStates, JoinState) Q_FLAG_NS(JoinStates) -constexpr inline std::array JoinStateStrings { +constexpr inline auto JoinStateStrings = to_array({ MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ -}; +}); //! \brief Network job running policy flags //! -- cgit v1.2.3 From e4a08bc431be9a2b680a4cd70f2ceda07c99b7bf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 20:10:35 +0200 Subject: Fix bit rot in comments --- lib/events/eventcontent.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 1d81bd72..40ec3a49 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -49,13 +49,14 @@ namespace EventContent { // but specific aggregation structure is altered. See doc comments to // each type for the list of available attributes. - // A quick classes inheritance structure follows: + // A quick classes inheritance structure follows (the definitions are + // spread across eventcontent.h and roommessageevent.h): // FileInfo - // FileContent : UrlBasedContent - // AudioContent : UrlBasedContent + // FileContent : UrlWithThumbnailContent + // AudioContent : PlayableContent> // ImageInfo : FileInfo + imageSize attribute - // ImageContent : UrlBasedContent - // VideoContent : UrlBasedContent + // ImageContent : UrlWithThumbnailContent + // VideoContent : PlayableContent> /** * A base/mixin class for structures representing an "info" object for -- cgit v1.2.3 From 2187f26ebdc9edf7b3cbfa1d208c03c4384f4135 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 20:56:33 +0200 Subject: Add a missing #include Without this, it compiles on Linux but on macOS and Windows. --- lib/quotient_common.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 037d5ded..d225ad63 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -5,6 +5,8 @@ #include +#include + namespace Quotient { Q_NAMESPACE -- cgit v1.2.3 From f340d73ae5dac0d0cfee732aabbd5222c7be16dd Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 24 Jul 2021 19:19:04 +0200 Subject: Make operator ""_ls constexpr --- lib/util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.h b/lib/util.h index cb0ff44a..7f09de28 100644 --- a/lib/util.h +++ b/lib/util.h @@ -196,7 +196,7 @@ inline auto wrap_in_function(FnT&& f) return typename function_traits::function_type(std::forward(f)); } -inline auto operator"" _ls(const char* s, std::size_t size) +inline constexpr auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); } -- cgit v1.2.3 From cddf3c6a2ab7481e5816ca7632b9f919efa0ac40 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 24 Jul 2021 20:56:50 +0200 Subject: Wrap SyncRoomData counters into Omittables Also: introduce a merge(T1&, const Omittable&) that does pretty much the same as Omittable::merge(const Omittable&) except it works on non-omittables as the left/first operand. The change removes the need for a clumsy -2 fallback in unreadCount, and makes the logic loading those counters cleaner along the way. --- lib/room.cpp | 16 +++++++--------- lib/syncdata.cpp | 12 ++++++------ lib/syncdata.h | 6 +++--- lib/util.h | 49 ++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 54c67c2f..076fd8c8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1570,20 +1570,18 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) roomChanges |= processEphemeralEvent(move(ephemeralEvent)); // See https://github.com/quotient-im/libQuotient/wiki/unread_count - if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages) { - qCDebug(MESSAGES) << "Setting unread_count to" << data.unreadCount; - d->unreadMessages = data.unreadCount; + if (merge(d->unreadMessages, data.unreadCount)) { + qCDebug(MESSAGES) << "Loaded unread_count:" << *data.unreadCount // + << "in" << objectName(); emit unreadMessagesChanged(this); } - if (data.highlightCount != d->highlightCount) { - d->highlightCount = data.highlightCount; + if (merge(d->highlightCount, data.highlightCount)) emit highlightCountChanged(); - } - if (data.notificationCount != d->notificationCount) { - d->notificationCount = data.notificationCount; + + if (merge(d->notificationCount, data.notificationCount)) emit notificationCountChanged(); - } + if (roomChanges != Change::NoChange) { d->updateDisplayname(); emit changed(roomChanges); diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index 232f3694..d3c270b5 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -90,13 +90,13 @@ SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_, } const auto unreadJson = room_.value("unread_notifications"_ls).toObject(); - unreadCount = unreadJson.value(UnreadCountKey).toInt(-2); - highlightCount = unreadJson.value("highlight_count"_ls).toInt(); - notificationCount = unreadJson.value("notification_count"_ls).toInt(); - if (highlightCount > 0 || notificationCount > 0) + fromJson(unreadJson.value(UnreadCountKey), unreadCount); + fromJson(unreadJson.value("highlight_count"_ls), highlightCount); + fromJson(unreadJson.value("notification_count"_ls), notificationCount); + if (highlightCount.has_value() || notificationCount.has_value()) qCDebug(SYNCJOB) << "Room" << roomId_ - << "has highlights:" << highlightCount - << "and notifications:" << notificationCount; + << "has highlights:" << *highlightCount + << "and notifications:" << *notificationCount; } SyncData::SyncData(const QString& cacheFileName) diff --git a/lib/syncdata.h b/lib/syncdata.h index 0153bfd6..b0e31726 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -48,9 +48,9 @@ public: bool timelineLimited; QString timelinePrevBatch; - int unreadCount; - int highlightCount; - int notificationCount; + Omittable unreadCount; + Omittable highlightCount; + Omittable notificationCount; SyncRoomData(const QString& roomId, JoinState joinState_, const QJsonObject& room_); diff --git a/lib/util.h b/lib/util.h index 7f09de28..78fc9ab7 100644 --- a/lib/util.h +++ b/lib/util.h @@ -30,6 +30,13 @@ struct HashQ { template using UnorderedMap = std::unordered_map>; +namespace _impl { + template + constexpr inline auto IsOmittableValue = false; + template + constexpr inline auto IsOmittable = IsOmittableValue>; +} + constexpr auto none = std::nullopt; /** `std::optional` with tweaks @@ -107,18 +114,18 @@ public: return !this->has_value(); } - /// Merge the value from another Omittable - /// \return true if \p other is not omitted and the value of - /// the current Omittable was different (or omitted); - /// in other words, if the current Omittable has changed; - /// false otherwise + //! Merge the value from another Omittable + //! \return true if \p other is not omitted and the value of + //! the current Omittable was different (or omitted), + //! in other words, if the current Omittable has changed; + //! false otherwise template auto merge(const Omittable& other) - -> std::enable_if_t::value, bool> + -> std::enable_if_t, bool> { if (!other || (this->has_value() && **this == *other)) return false; - *this = other; + emplace(*other); return true; } @@ -132,6 +139,34 @@ public: value_type& operator*() && { return base_type::operator*(); } }; +namespace _impl { + template + constexpr inline auto IsOmittableValue> = true; +} + +template +inline auto merge(Omittable& lhs, T2&& rhs) +{ + return lhs.merge(std::forward(rhs)); +} + +//! \brief Merge the value from an Omittable +//! This is an adaptation of Omittable::merge() to the case when the value +//! on the left hand side is not an Omittable. +//! \return true if \p rhs is not omitted and the \p lhs value was different, +//! in other words, if \p lhs has changed; +//! false otherwise +template +inline auto merge(T1& lhs, const Omittable& rhs) + -> std::enable_if_t + && std::is_convertible_v, bool> +{ + if (!rhs || lhs == *rhs) + return false; + lhs = *rhs; + return true; +} + namespace _impl { template struct fn_traits {}; -- cgit v1.2.3 From 4ef72d95868451b7e53bd49a873648332a121130 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 28 Jul 2021 03:33:24 +0200 Subject: setFirst/LastDisplayedEvent(): warn about unloaded events --- lib/room.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/room.cpp b/lib/room.cpp index 076fd8c8..10e827d7 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -909,6 +909,11 @@ void Room::setFirstDisplayedEventId(const QString& eventId) if (d->firstDisplayedEventId == eventId) return; + if (!eventId.isEmpty() && findInTimeline(eventId) == historyEdge()) + qCWarning(MESSAGES) + << eventId + << "is marked as first displayed but doesn't seem to be loaded"; + d->firstDisplayedEventId = eventId; emit firstDisplayedEventChanged(); } @@ -931,6 +936,12 @@ void Room::setLastDisplayedEventId(const QString& eventId) if (d->lastDisplayedEventId == eventId) return; + const auto marker = findInTimeline(eventId); + if (!eventId.isEmpty() && marker == historyEdge()) + qCWarning(MESSAGES) + << eventId + << "is marked as last displayed but doesn't seem to be loaded"; + d->lastDisplayedEventId = eventId; emit lastDisplayedEventChanged(); } -- cgit v1.2.3 From 000b57306afe450c21df3aa95313567614c34516 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 28 Jul 2021 15:08:05 +0200 Subject: .clang-format: revert BraceWrapping.AfterControlStatement It triggers a bug in libformat that prevents AllowShortFunctionsOnASingleLine to do its job: https://bugs.llvm.org/show_bug.cgi?id=47936 --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 72b67488..3eafee44 100644 --- a/.clang-format +++ b/.clang-format @@ -44,7 +44,7 @@ AlwaysBreakTemplateDeclarations: Yes BraceWrapping: AfterCaseLabel: false AfterClass: false - AfterControlStatement: MultiLine + AfterControlStatement: Never # Switch to MultiLine, once https://bugs.llvm.org/show_bug.cgi?id=47936 is fixed AfterEnum: false AfterFunction: true AfterNamespace: false -- cgit v1.2.3 From eed5a6f127ec3bb1553ac629457f196d8893665a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 26 Jul 2021 08:19:29 +0200 Subject: Room::usersAtEventId(): switch from QMultiHash to QHash of QSets While slightly more complex for updating, this allows COW to kick in in the read accessor; using QSet instead of QList also provides better consistency guarantees. For QML both are converted to an Array-like collection since Qt 5.15; Qt 5.12 turns QSet<> in a QVariantList, according to the documentation, which is quite reasonable too. --- lib/room.cpp | 13 ++++++++----- lib/room.h | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 10e827d7..13589ee9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -121,7 +121,7 @@ public: int notificationCount = 0; members_map_t membersMap; QList usersTyping; - QMultiHash eventIdReadUsers; + QHash> eventIdReadUsers; QList usersInvited; QList membersLeft; int unreadMessages = 0; @@ -627,8 +627,11 @@ Room::Changes Room::Private::setLastReadEvent(User* u, QString eventId) auto& storedId = lastReadEventIds[u]; if (storedId == eventId) return Change::NoChange; - eventIdReadUsers.remove(storedId, u); - eventIdReadUsers.insert(eventId, u); + auto& oldEventReadUsers = eventIdReadUsers[storedId]; + oldEventReadUsers.remove(u); + if (oldEventReadUsers.isEmpty()) + eventIdReadUsers.remove(storedId); + eventIdReadUsers[eventId].insert(u); swap(storedId, eventId); emit q->lastReadEventChanged(u); emit q->readMarkerForUserMoved(u, eventId, storedId); @@ -965,9 +968,9 @@ QString Room::readMarkerEventId() const return d->lastReadEventIds.value(localUser()); } -QList Room::usersAtEventId(const QString& eventId) +QSet Room::usersAtEventId(const QString& eventId) { - return d->eventIdReadUsers.values(eventId); + return d->eventIdReadUsers.value(eventId); } int Room::notificationCount() const { return d->notificationCount; } diff --git a/lib/room.h b/lib/room.h index cdbfe58f..fa7b6e6d 100644 --- a/lib/room.h +++ b/lib/room.h @@ -364,7 +364,7 @@ public: rev_iter_t readMarker(const User* user) const; rev_iter_t readMarker() const; QString readMarkerEventId() const; - QList usersAtEventId(const QString& eventId); + QSet usersAtEventId(const QString& eventId); /** * \brief Mark the event with uptoEventId as read * -- cgit v1.2.3 From ed1b034089dfc1948acaa80a5200cb0df28d0c1c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 26 Jul 2021 22:36:44 +0200 Subject: SyncData: minor update to the cache version The minor component is now updated in .cpp, not in .h. --- lib/syncdata.cpp | 7 ++++++- lib/syncdata.h | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index d3c270b5..3e0eff17 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -103,7 +103,7 @@ SyncData::SyncData(const QString& cacheFileName) { QFileInfo cacheFileInfo { cacheFileName }; auto json = loadJson(cacheFileName); - auto requiredVersion = std::get<0>(cacheVersion()); + auto requiredVersion = MajorCacheVersion; auto actualVersion = json.value("cache_version"_ls).toObject().value("major"_ls).toInt(); if (actualVersion == requiredVersion) @@ -128,6 +128,11 @@ Events&& SyncData::takeAccountData() { return std::move(accountData); } Events&& SyncData::takeToDeviceEvents() { return std::move(toDeviceEvents); } +std::pair SyncData::cacheVersion() +{ + return { MajorCacheVersion, 1 }; +} + QJsonObject SyncData::loadJson(const QString& fileName) { QFile roomFile { fileName }; diff --git a/lib/syncdata.h b/lib/syncdata.h index b0e31726..b869a541 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -87,7 +87,8 @@ public: QStringList unresolvedRooms() const { return unresolvedRoomIds; } - static std::pair cacheVersion() { return { 11, 0 }; } + static constexpr int MajorCacheVersion = 11; + static std::pair cacheVersion(); static QString fileNameForRoom(QString roomId); private: -- cgit v1.2.3 From 277f43defe3fa55ff32fe53952c6331a81d65a20 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 28 Jul 2021 03:32:02 +0200 Subject: Room: fix the read markers/receipts confusion This turns the design changes laid out in #464 comments to code, as of 0.6.x branch (0.7 API will be introduced separately): - readMarker() now returns the fully read marker, unlike readMarker(User*) that returns a read receipt, even when called for the local user. - Private::setLastReadEvent() -> setLastReadReceipt(), incorporating the "promotion" logic from promoteReadReceipt(). - The above makes promoteReadReceipt() unneeded; the remaining piece of logic that recalculates the number of unread messages is put to its own method - Private::recalculateUnreadCount(). - Private::updateUnreadCount() is only slightly refreshed, continues to use the fully read marker position (as it used to). - Now that read receipts and fully read markers are managed separately, Private::setLastReadReceipt() has got its counterpart, Private::setFullyReadMarker(); both only update their respective markers locally (emitting signals as needed), without interaction with the homeserver. - Private::markMessagesAsRead() now delegates updating the fully read marker to setFullyReadMarker() and on top of that sends the new fully read marker to the homeserver. - Private::serverReadMarker -> fullyReadUntilEventId (to be really clear what it stores). - The hand-written PostReadMarkersJob is replaced with the generated SetReadMarkerJob that does the same thing (and can update the read receipt on top). --- lib/room.cpp | 341 +++++++++++++++++++++++++++++++---------------------------- 1 file changed, 180 insertions(+), 161 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 13589ee9..ec3a9532 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -129,7 +129,7 @@ public: QString firstDisplayedEventId; QString lastDisplayedEventId; QHash lastReadEventIds; - QString serverReadMarker; + QString fullyReadUntilEventId; TagsMap tags; UnorderedMap accountData; QString prevBatch; @@ -290,11 +290,12 @@ public: */ void dropDuplicateEvents(RoomEvents& events) const; - Changes setLastReadEvent(User* u, QString eventId); - void updateUnreadCount(const rev_iter_t& from, const rev_iter_t& to); - Changes promoteReadMarker(User* u, const rev_iter_t& newMarker, bool force = false); - - Changes markMessagesAsRead(rev_iter_t upToMarker); + void setLastReadReceipt(User* u, rev_iter_t newMarker, + QString newEvtId = {}); + Changes setFullyReadMarker(const QString &eventId); + Changes updateUnreadCount(const rev_iter_t& from, const rev_iter_t& to); + Changes recalculateUnreadCount(bool force = false); + void markMessagesAsRead(const rev_iter_t &upToMarker); void getAllMembers(); @@ -622,142 +623,172 @@ void Room::setJoinState(JoinState state) emit joinStateChanged(oldState, state); } -Room::Changes Room::Private::setLastReadEvent(User* u, QString eventId) +void Room::Private::setLastReadReceipt(User* u, rev_iter_t newMarker, + QString newEvtId) { + if (!u) { + Q_ASSERT(u != nullptr); // For Debug builds + qCCritical(MAIN) << "Empty user, skipping read receipt registration"; + return; // For Release builds + } + if (q->memberJoinState(u) != JoinState::Join) { + qCWarning(MAIN) << "Won't record read receipt for non-member" << u->id(); + return; + } + + if (newMarker == timeline.crend() && !newEvtId.isEmpty()) + newMarker = q->findInTimeline(newEvtId); + if (newMarker != timeline.crend()) { + // NB: with reverse iterators, timeline history >= sync edge + if (newMarker >= q->readMarker(u)) + return; + + // Try to auto-promote the read marker over the user's own messages + // (switch to direct iterators for that). + const auto eagerMarker = find_if(newMarker.base(), timeline.cend(), + [=](const TimelineItem& ti) { + return ti->senderId() != u->id(); + }) + - 1; + newEvtId = (*eagerMarker)->id(); + if (eagerMarker != newMarker.base() - 1) // &*(rIt.base() - 1) === &*rIt + qCDebug(EPHEMERAL) << "Auto-promoted read receipt for" << u->id() + << "to" << newEvtId; + } + auto& storedId = lastReadEventIds[u]; - if (storedId == eventId) - return Change::NoChange; + if (storedId == newEvtId) + return; + // Finally make the change auto& oldEventReadUsers = eventIdReadUsers[storedId]; oldEventReadUsers.remove(u); if (oldEventReadUsers.isEmpty()) eventIdReadUsers.remove(storedId); - eventIdReadUsers[eventId].insert(u); - swap(storedId, eventId); + eventIdReadUsers[newEvtId].insert(u); + swap(storedId, newEvtId); // Now newEvtId actually stores the old eventId emit q->lastReadEventChanged(u); - emit q->readMarkerForUserMoved(u, eventId, storedId); - if (isLocalUser(u)) { - if (storedId != serverReadMarker) - connection->callApi(BackgroundRequest, id, - storedId); - emit q->readMarkerMoved(eventId, storedId); - return Change::ReadMarkerChange; - } - return Change::NoChange; + if (!isLocalUser(u)) + emit q->readMarkerForUserMoved(u, newEvtId, storedId); } -void Room::Private::updateUnreadCount(const rev_iter_t& from, - const rev_iter_t& to) +Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, + const rev_iter_t& to) { Q_ASSERT(from >= timeline.crbegin() && from <= timeline.crend()); Q_ASSERT(to >= from && to <= timeline.crend()); - // Catch a special case when the last read event id refers to an event - // that has just arrived. In this case we should recalculate - // unreadMessages and might need to promote the read marker further - // over local-origin messages. - auto readMarker = q->readMarker(); - if (readMarker == timeline.crend() && q->allHistoryLoaded()) - --readMarker; // Read marker not found in the timeline, initialise it - if (readMarker >= from && readMarker < to) { - promoteReadMarker(q->localUser(), readMarker, true); - return; - } - - Q_ASSERT(to <= readMarker); + auto fullyReadMarker = q->readMarker(); + if (fullyReadMarker < from) + return NoChange; // What's arrived is already fully read + + if (fullyReadMarker == timeline.crend() && q->allHistoryLoaded()) + --fullyReadMarker; // No read marker in the whole room, initialise it + if (fullyReadMarker < to) { + // Catch a special case when the last fully read event id refers to an + // event that has just arrived. In this case we should recalculate + // unreadMessages to get an exact number instead of an estimation + // (see https://github.com/quotient-im/libQuotient/wiki/unread_count). + // For the same reason (switching from the estimation to the exact + // number) this branch always emits unreadMessagesChanged() and returns + // UnreadNotifsChange, even if the estimation luckily matched the exact + // result. + return recalculateUnreadCount(true); + } + + // Fully read marker is somewhere beyond the most historical message from + // the arrived batch - add up newly arrived messages to the current counter, + // instead of a complete recalculation. + Q_ASSERT(to <= fullyReadMarker); QElapsedTimer et; et.start(); const auto newUnreadMessages = - count_if(from, to, std::bind(&Room::Private::isEventNotable, this, _1)); + count_if(from, to, + std::bind(&Room::Private::isEventNotable, this, _1)); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Counting gained unread messages took" << et; - if (newUnreadMessages > 0) { - // See https://github.com/quotient-im/libQuotient/wiki/unread_count - if (unreadMessages < 0) - unreadMessages = 0; - - unreadMessages += newUnreadMessages; - qCDebug(MESSAGES) << "Room" << q->objectName() << "has gained" - << newUnreadMessages << "unread message(s)," - << (q->readMarker() == timeline.crend() - ? "in total at least" - : "in total") - << unreadMessages << "unread message(s)"; - emit q->unreadMessagesChanged(q); - } -} + if (newUnreadMessages == 0) + return NoChange; -Room::Changes Room::Private::promoteReadMarker(User* u, - const rev_iter_t& newMarker, - bool force) -{ - Q_ASSERT_X(u, __FUNCTION__, "User* should not be nullptr"); - Q_ASSERT(newMarker >= timeline.crbegin() && newMarker <= timeline.crend()); + // See https://github.com/quotient-im/libQuotient/wiki/unread_count + if (unreadMessages < 0) + unreadMessages = 0; + + unreadMessages += newUnreadMessages; + qCDebug(MESSAGES) << "Room" << q->objectName() << "has gained" + << newUnreadMessages << "unread message(s)," + << (q->readMarker() == timeline.crend() + ? "in total at least" + : "in total") + << unreadMessages << "unread message(s)"; + emit q->unreadMessagesChanged(q); + return UnreadNotifsChange; +} + +Room::Changes Room::Private::recalculateUnreadCount(bool force) +{ + // The recalculation logic assumes that the fully read marker points at + // a specific position in the timeline + Q_ASSERT(q->readMarker() != timeline.crend()); + const auto oldUnreadCount = unreadMessages; + QElapsedTimer et; + et.start(); + unreadMessages = + int(count_if(timeline.crbegin(), q->readMarker(), + [this](const auto& ti) { return isEventNotable(ti); })); + if (et.nsecsElapsed() > profilerMinNsecs() / 10) + qCDebug(PROFILER) << "Recounting unread messages took" << et; - const auto prevMarker = q->readMarker(u); - if (!force && prevMarker <= newMarker) // Remember, we deal with reverse - // iterators - return Change::NoChange; + // See https://github.com/quotient-im/libQuotient/wiki/unread_count + if (unreadMessages == 0) + unreadMessages = -1; - Q_ASSERT(newMarker < timeline.crend()); + if (!force && unreadMessages == oldUnreadCount) + return NoChange; - // Try to auto-promote the read marker over the user's own messages - // (switch to direct iterators for that). - auto eagerMarker = - find_if(newMarker.base(), timeline.cend(), [=](const TimelineItem& ti) { - return ti->senderId() != u->id(); - }); + if (unreadMessages == -1) + qCDebug(MESSAGES) + << "Room" << displayname << "has no more unread messages"; + else + qCDebug(MESSAGES) << "Room" << displayname << "still has" + << unreadMessages << "unread message(s)"; + emit q->unreadMessagesChanged(q); + return UnreadNotifsChange; +} - auto changes = setLastReadEvent(u, (*(eagerMarker - 1))->id()); - if (isLocalUser(u)) { - const auto oldUnreadCount = unreadMessages; - QElapsedTimer et; - et.start(); - unreadMessages = - int(count_if(eagerMarker, timeline.cend(), - [this](const auto& ti) { return isEventNotable(ti); })); - if (et.nsecsElapsed() > profilerMinNsecs() / 10) - qCDebug(PROFILER) << "Recounting unread messages took" << et; +Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) +{ + if (fullyReadUntilEventId == eventId) + return NoChange; - // See https://github.com/quotient-im/libQuotient/wiki/unread_count - if (unreadMessages == 0) - unreadMessages = -1; + const auto prevFullyReadId = std::exchange(fullyReadUntilEventId, eventId); + qCDebug(MESSAGES) << "Fully read marker in" << q->objectName() // + << "moved to" << fullyReadUntilEventId; + emit q->readMarkerMoved(prevFullyReadId, fullyReadUntilEventId); - if (force || unreadMessages != oldUnreadCount) { - if (unreadMessages == -1) { - qCDebug(MESSAGES) - << "Room" << displayname << "has no more unread messages"; - } else - qCDebug(MESSAGES) << "Room" << displayname << "still has" - << unreadMessages << "unread message(s)"; - emit q->unreadMessagesChanged(q); - changes |= Change::UnreadNotifsChange; - } + Changes changes = ReadMarkerChange; + if (const auto rm = q->readMarker(); rm != timeline.crend()) { + // Pull read receipt if it's behind + if (auto rr = q->readMarker(q->localUser()); rr > rm) + setLastReadReceipt(q->localUser(), rm); + + changes |= recalculateUnreadCount(); } return changes; } -Room::Changes Room::Private::markMessagesAsRead(rev_iter_t upToMarker) +void Room::Private::markMessagesAsRead(const rev_iter_t &upToMarker) { - const auto prevMarker = q->readMarker(); - auto changes = promoteReadMarker(q->localUser(), upToMarker); - if (prevMarker != upToMarker) - qCDebug(MESSAGES) << "Marked messages as read until" << *q->readMarker(); - - // We shouldn't send read receipts for the local user's own messages - so - // search earlier messages for the latest message not from the local user - // until the previous last-read message, whichever comes first. - for (; upToMarker < prevMarker; ++upToMarker) { - if ((*upToMarker)->senderId() != q->localUser()->id()) { - connection->callApi(BackgroundRequest, - id, QStringLiteral("m.read"), - QUrl::toPercentEncoding( - (*upToMarker)->id())); - break; - } + if (upToMarker < q->readMarker()) { + setFullyReadMarker((*upToMarker)->id()); + // Assuming that if a read receipt was sent on a newer event, it will + // stay there instead of "un-reading" notifications/mentions from + // m.fully_read to m.read + connection->callApi(BackgroundRequest, id, + fullyReadUntilEventId, + fullyReadUntilEventId); } - return changes; } void Room::markMessagesAsRead(QString uptoEventId) @@ -961,11 +992,14 @@ Room::rev_iter_t Room::readMarker(const User* user) const return findInTimeline(d->lastReadEventIds.value(user)); } -Room::rev_iter_t Room::readMarker() const { return readMarker(localUser()); } +Room::rev_iter_t Room::readMarker() const +{ + return findInTimeline(d->fullyReadUntilEventId); +} QString Room::readMarkerEventId() const { - return d->lastReadEventIds.value(localUser()); + return d->fullyReadUntilEventId; } QSet Room::usersAtEventId(const QString& eventId) @@ -2436,24 +2470,21 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) << timeline.back(); // The first event in the just-added batch (referred to by `from`) - // defines whose read marker can possibly be promoted any further over + // defines whose read receipt can possibly be promoted any further over // the same author's events newly arrived. Others will need explicit - // read receipts from the server (or, for the local user, - // markMessagesAsRead() invocation) to promote their read markers over + // read receipts from the server - or, for the local user, calling + // setLastDisplayedEventId() - to promote their read receipts over // the new message events. - if (const auto senderId = (*from)->senderId(); !senderId.isEmpty()) { - auto* const firstWriter = q->user(senderId); - if (q->readMarker(firstWriter) != timeline.crend()) { - roomChanges |= - promoteReadMarker(firstWriter, rev_iter_t(from) - 1); - qCDebug(MESSAGES) - << "Auto-promoted read marker for" << senderId - << "to" << *q->readMarker(firstWriter); - } + auto* const firstWriter = q->user((*from)->senderId()); + setLastReadReceipt(firstWriter, rev_iter_t(from + 1)); + if (firstWriter == q->localUser() && q->readMarker().base() == from) { + // If the local user's message(s) is/are first in the batch + // and the fully read marker was right before it, promote + // the fully read marker to the same event as the read receipt. + roomChanges |= + setFullyReadMarker(lastReadEventIds.value(firstWriter)); } - - updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); - roomChanges |= Change::UnreadNotifsChange; + roomChanges |= updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); } Q_ASSERT(timeline.size() == timelineSize + totalInserted); @@ -2498,8 +2529,11 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) emit q->updatedEvent(relation.eventId); } } - if (from <= q->readMarker()) - updateUnreadCount(from, timeline.crend()); + updateUnreadCount(from, timeline.crend()); + // When there are no unread messages and the read marker is within the + // known timeline, unreadMessages == -1 + // (see https://github.com/quotient-im/libQuotient/wiki/unread_count). + Q_ASSERT(unreadMessages != 0 || q->readMarker() == timeline.crend()); Q_ASSERT(timeline.size() == timelineSize + insertedSize); if (insertedSize > 9 || et.nsecsElapsed() >= profilerMinNsecs()) @@ -2723,7 +2757,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) if (auto* evt = eventCast(event)) { d->usersTyping.clear(); for (const auto& userId : evt->users()) { - auto u = user(userId); + auto* const u = user(userId); if (memberJoinState(u) == JoinState::Join) d->usersTyping.append(u); } @@ -2746,30 +2780,21 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) << p.receipts.size() << "users"; } const auto newMarker = findInTimeline(p.evtId); - if (newMarker != timelineEdge()) { - for (const Receipt& r : p.receipts) { - if (r.userId == connection()->userId()) - continue; // FIXME, #185 - auto u = user(r.userId); - if (memberJoinState(u) == JoinState::Join) - changes |= d->promoteReadMarker(u, newMarker); - } - } else { - qCDebug(EPHEMERAL) << "Event" << p.evtId - << "not found; saving read receipts anyway"; - // If the event is not found (most likely, because it's too old - // and hasn't been fetched from the server yet), but there is - // a previous marker for a user, keep the previous marker. - // Otherwise, blindly store the event id for this user. - for (const Receipt& r : p.receipts) { - if (r.userId == connection()->userId()) - continue; // FIXME, #185 - auto u = user(r.userId); - if (memberJoinState(u) == JoinState::Join - && readMarker(u) == timelineEdge()) - changes |= d->setLastReadEvent(u, p.evtId); + if (newMarker == historyEdge()) + qCDebug(EPHEMERAL) << "Event of the read receipt(s) is not " + "found; saving them anyway"; + for (const Receipt& r : p.receipts) + if (auto* const u = user(r.userId); + memberJoinState(u) == JoinState::Join) { + // If the event is not found (most likely, because it's + // too old and hasn't been fetched from the server yet) + // but there is a previous marker for a user, keep + // the previous marker because read receipts are not + // supposed to move backwards. Otherwise, blindly + // store the event id for this user and update the read + // marker when/if the event is fetched later on. + d->setLastReadReceipt(u, newMarker, p.evtId); } - } } if (eventsWithReceipts.size() > 3 || totalReceipts > 10 || et.nsecsElapsed() >= profilerMinNsecs()) @@ -2789,15 +2814,9 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) changes |= Change::TagsChange; } - if (auto* evt = eventCast(event)) { - auto readEventId = evt->event_id(); - qCDebug(STATE) << "Server-side read marker at" << readEventId; - d->serverReadMarker = readEventId; - const auto newMarker = findInTimeline(readEventId); - changes |= newMarker != timelineEdge() - ? d->markMessagesAsRead(newMarker) - : d->setLastReadEvent(localUser(), readEventId); - } + if (auto* evt = eventCast(event)) + changes |= d->setFullyReadMarker(evt->event_id()); + // For all account data events auto& currentData = d->accountData[event->matrixType()]; // A polymorphic event-specific comparison might be a bit more -- cgit v1.2.3 From 70035f204345b09305fbb487208708e69bd79a53 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 29 Jul 2021 21:59:03 +0200 Subject: Formatting --- lib/room.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/room.h b/lib/room.h index cdbfe58f..a1cb6261 100644 --- a/lib/room.h +++ b/lib/room.h @@ -181,12 +181,10 @@ public: QString name() const; /// Room aliases defined on the current user's server /// \sa remoteAliases, setLocalAliases - [[deprecated("Use aliases()")]] - QStringList localAliases() const; + [[deprecated("Use aliases()")]] QStringList localAliases() const; /// Room aliases defined on other servers /// \sa localAliases - [[deprecated("Use aliases()")]] - QStringList remoteAliases() const; + [[deprecated("Use aliases()")]] QStringList remoteAliases() const; QString canonicalAlias() const; QStringList altAliases() const; QStringList aliases() const; @@ -271,7 +269,8 @@ public: */ Q_INVOKABLE QString roomMembername(const Quotient::User* u) const; /*! - * \brief Get a disambiguated name for a user with this id in the room context + * \brief Get a disambiguated name for a user with this id in the room + * context * * \deprecated use safeMemberName() instead */ @@ -339,9 +338,13 @@ public: const char* relType) const; const RoomCreateEvent* creation() const - { return getCurrentState(); } + { + return getCurrentState(); + } const RoomTombstoneEvent* tombstone() const - { return getCurrentState(); } + { + return getCurrentState(); + } bool displayed() const; /// Mark the room as currently displayed to the user -- cgit v1.2.3 From 42f20f827565a2bdd9e1373b7ee2c408991b9d44 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 30 Jul 2021 08:07:59 +0200 Subject: .clang-format: add DEFINE_EVENT_TYPEID to StatementMacros --- .clang-format | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-format b/.clang-format index 3eafee44..6e13223e 100644 --- a/.clang-format +++ b/.clang-format @@ -133,6 +133,7 @@ Standard: c++17 StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION + - DEFINE_EVENT_TYPEID TabWidth: 4 #UseCRLF: false #UseTab: Never -- cgit v1.2.3 From e0d9125de7ac132c2a54152015687abbe5e73193 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 30 Jul 2021 08:10:48 +0200 Subject: Room: drop 0.6 deprecations; deprecate RoomAliasEvent Namely memberCount(), localAliases(), remoteAliases(), timelineEdge(). --- lib/events/simplestateevents.h | 6 ++++-- lib/room.cpp | 30 +++++------------------------- lib/room.h | 14 ++------------ 3 files changed, 11 insertions(+), 39 deletions(-) diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index d6261a8f..3bac54e6 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -56,8 +56,10 @@ namespace EventContent { DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) -class RoomAliasesEvent - : public StateEvent> { +class [[deprecated( + "m.room.aliases events are deprecated by the Matrix spec; use" + " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")]] // +RoomAliasesEvent : public StateEvent> { public: DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent) explicit RoomAliasesEvent(const QJsonObject& obj) diff --git a/lib/room.cpp b/lib/room.cpp index 10e827d7..f223e0b9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -540,21 +540,6 @@ QStringList Room::altAliases() const return d->getCurrentState()->altAliases(); } -QStringList Room::localAliases() const -{ - return d->getCurrentState( - connection()->domain()) - ->aliases(); -} - -QStringList Room::remoteAliases() const -{ - QStringList result; - for (const auto& s : std::as_const(d->aliasServers)) - result += d->getCurrentState(s)->aliases(); - return result; -} - QString Room::canonicalAlias() const { return d->getCurrentState()->alias(); @@ -793,8 +778,6 @@ Room::Timeline::const_iterator Room::syncEdge() const return d->timeline.cend(); } -Room::rev_iter_t Room::timelineEdge() const { return historyEdge(); } - TimelineItem::index_t Room::minTimelineIndex() const { return d->timeline.empty() ? 0 : d->timeline.front().index(); @@ -813,7 +796,7 @@ bool Room::isValidIndex(TimelineItem::index_t timelineIndex) const Room::rev_iter_t Room::findInTimeline(TimelineItem::index_t index) const { - return timelineEdge() + return historyEdge() - (isValidIndex(index) ? index - minTimelineIndex() + 1 : 0); } @@ -1279,8 +1262,6 @@ QStringList Room::htmlSafeMemberNames() const return res; } -int Room::memberCount() const { return d->membersMap.size(); } - int Room::timelineSize() const { return int(d->timeline.size()); } bool Room::usesEncryption() const @@ -2153,8 +2134,7 @@ RoomEventPtr makeRedacted(const RoomEvent& target, { QStringLiteral("ban"), QStringLiteral("events"), QStringLiteral("events_default"), QStringLiteral("kick"), QStringLiteral("redact"), QStringLiteral("state_default"), - QStringLiteral("users"), QStringLiteral("users_default") } }, - { RoomAliasesEvent::typeId(), { QStringLiteral("aliases") } } + QStringLiteral("users"), QStringLiteral("users_default") } } // , { RoomJoinRules::typeId(), { QStringLiteral("join_rule") } } // , { RoomHistoryVisibility::typeId(), // { QStringLiteral("history_visibility") } } @@ -2743,7 +2723,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) << p.receipts.size() << "users"; } const auto newMarker = findInTimeline(p.evtId); - if (newMarker != timelineEdge()) { + if (newMarker != historyEdge()) { for (const Receipt& r : p.receipts) { if (r.userId == connection()->userId()) continue; // FIXME, #185 @@ -2763,7 +2743,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) continue; // FIXME, #185 auto u = user(r.userId); if (memberJoinState(u) == JoinState::Join - && readMarker(u) == timelineEdge()) + && readMarker(u) == historyEdge()) changes |= d->setLastReadEvent(u, p.evtId); } } @@ -2791,7 +2771,7 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) qCDebug(STATE) << "Server-side read marker at" << readEventId; d->serverReadMarker = readEventId; const auto newMarker = findInTimeline(readEventId); - changes |= newMarker != timelineEdge() + changes |= newMarker != historyEdge() ? d->markMessagesAsRead(newMarker) : d->setLastReadEvent(localUser(), readEventId); } diff --git a/lib/room.h b/lib/room.h index a1cb6261..0ab18ef7 100644 --- a/lib/room.h +++ b/lib/room.h @@ -93,7 +93,6 @@ class Room : public QObject { Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages) Q_PROPERTY(QStringList memberNames READ safeMemberNames NOTIFY memberListChanged) - Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged) Q_PROPERTY(int joinedCount READ joinedCount NOTIFY memberListChanged) Q_PROPERTY(int invitedCount READ invitedCount NOTIFY memberListChanged) Q_PROPERTY(int totalMemberCount READ totalMemberCount NOTIFY memberListChanged) @@ -179,14 +178,9 @@ public: Room* successor(JoinStates statesFilter = JoinState::Invite | JoinState::Join) const; QString name() const; - /// Room aliases defined on the current user's server - /// \sa remoteAliases, setLocalAliases - [[deprecated("Use aliases()")]] QStringList localAliases() const; - /// Room aliases defined on other servers - /// \sa localAliases - [[deprecated("Use aliases()")]] QStringList remoteAliases() const; QString canonicalAlias() const; QStringList altAliases() const; + //! Get a list of both canonical and alternative aliases QStringList aliases() const; QString displayName() const; QString topic() const; @@ -202,8 +196,6 @@ public: QStringList memberNames() const; QStringList safeMemberNames() const; QStringList htmlSafeMemberNames() const; - [[deprecated("Use joinedCount(), invitedCount(), totalMemberCount()")]] - int memberCount() const; int timelineSize() const; bool usesEncryption() const; RoomEventPtr decryptMessage(const EncryptedEvent& encryptedEvent); @@ -320,8 +312,6 @@ public: * arrived event; same as messageEvents().cend() */ Timeline::const_iterator syncEdge() const; - /// \deprecated Use historyEdge instead - rev_iter_t timelineEdge() const; Q_INVOKABLE Quotient::TimelineItem::index_t minTimelineIndex() const; Q_INVOKABLE Quotient::TimelineItem::index_t maxTimelineIndex() const; Q_INVOKABLE bool @@ -387,7 +377,7 @@ public: * events (non-redacted message events from users other than local) * are counted. * - * In a case when readMarker() == timelineEdge() (the local read + * In a case when readMarker() == historyEdge() (the local read * marker is beyond the local timeline) only the bottom limit of * the unread messages number can be estimated (and even that may * be slightly off due to, e.g., redactions of events not loaded -- cgit v1.2.3 From 02eac9e9ff573d85677f900288e82473f5296ee3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 30 Jul 2021 08:12:09 +0200 Subject: Use a better type in makeRedacted --- lib/room.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index f223e0b9..47a0ec94 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2127,7 +2127,7 @@ RoomEventPtr makeRedacted(const RoomEvent& target, QStringLiteral("membership") }; // clang-format on - std::vector> keepContentKeysMap { + static const std::pair keepContentKeysMap[] { { RoomMemberEvent::typeId(), { QStringLiteral("membership") } }, { RoomCreateEvent::typeId(), { QStringLiteral("creator") } }, { RoomPowerLevelsEvent::typeId(), @@ -2146,9 +2146,9 @@ RoomEventPtr makeRedacted(const RoomEvent& target, ++it; } auto keepContentKeys = - find_if(keepContentKeysMap.begin(), keepContentKeysMap.end(), + find_if(begin(keepContentKeysMap), end(keepContentKeysMap), [&target](const auto& t) { return target.type() == t.first; }); - if (keepContentKeys == keepContentKeysMap.end()) { + if (keepContentKeys == end(keepContentKeysMap)) { originalJson.remove(ContentKeyL); originalJson.remove(PrevContentKeyL); } else { -- cgit v1.2.3 From 7ee1681d7640b7e7683f7bb40bf768704a48832c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 30 Jul 2021 08:22:13 +0200 Subject: Clean up after the previous commit RoomAliasesEvent is no more even registered (meaning that the library will load m.room.aliases as unknown state events); quotest code updated to use historyEdge() instead of timelineEdge(). --- lib/events/simplestateevents.h | 1 - quotest/quotest.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 3bac54e6..cf1bfbba 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -72,5 +72,4 @@ public: QString server() const { return stateKey(); } QStringList aliases() const { return content().value; } }; -REGISTER_EVENT_TYPE(RoomAliasesEvent) } // namespace Quotient diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 5646d54d..ec7d4dcb 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -557,7 +557,7 @@ bool TestSuite::checkRedactionOutcome(const QByteArray& thisTest, // redacted at the next sync, or the nearest sync completes with // the unredacted event but the next one brings redaction. auto it = targetRoom->findInTimeline(evtIdToRedact); - if (it == targetRoom->timelineEdge()) + if (it == targetRoom->historyEdge()) return false; // Waiting for the next sync if ((*it)->isRedacted()) { -- cgit v1.2.3 From 62c829cc8de8a870c08926c41331f2766e766f37 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 29 Jul 2021 21:58:56 +0200 Subject: Quotient::ReadReceipt; deprecated readMarker() It's now possible to get receipts along with their timestamps by calling Room::lastReadReceipt(). Together this new method, fullyReadMarker(), and lastFullyReadEventId() deprecate readMarker() overloads and readMarkerEventId() respectively. lastFullyReadEventId is also a Q_PROPERTY (deprecating readMarkerEventId); readMarkerMoved() signal is deprecated by fullyReadMarkerMoved(), while readMarkerForUserMoved() is deprecated in favour of existing lastReadEventChanged(). --- lib/room.cpp | 79 ++++++++++++++++++++++++++++++++++-------------------------- lib/room.h | 39 +++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index ec3a9532..9a571127 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -128,7 +128,7 @@ public: bool displayed = false; QString firstDisplayedEventId; QString lastDisplayedEventId; - QHash lastReadEventIds; + QHash lastReadReceipts; QString fullyReadUntilEventId; TagsMap tags; UnorderedMap accountData; @@ -291,7 +291,7 @@ public: void dropDuplicateEvents(RoomEvents& events) const; void setLastReadReceipt(User* u, rev_iter_t newMarker, - QString newEvtId = {}); + ReadReceipt newReceipt = {}); Changes setFullyReadMarker(const QString &eventId); Changes updateUnreadCount(const rev_iter_t& from, const rev_iter_t& to); Changes recalculateUnreadCount(bool force = false); @@ -624,7 +624,7 @@ void Room::setJoinState(JoinState state) } void Room::Private::setLastReadReceipt(User* u, rev_iter_t newMarker, - QString newEvtId) + ReadReceipt newReceipt) { if (!u) { Q_ASSERT(u != nullptr); // For Debug builds @@ -636,11 +636,12 @@ void Room::Private::setLastReadReceipt(User* u, rev_iter_t newMarker, return; } - if (newMarker == timeline.crend() && !newEvtId.isEmpty()) - newMarker = q->findInTimeline(newEvtId); + auto& storedReceipt = lastReadReceipts[u]; + if (newMarker == timeline.crend() && !newReceipt.eventId.isEmpty()) + newMarker = q->findInTimeline(newReceipt.eventId); if (newMarker != timeline.crend()) { // NB: with reverse iterators, timeline history >= sync edge - if (newMarker >= q->readMarker(u)) + if (newMarker >= q->findInTimeline(storedReceipt.eventId)) return; // Try to auto-promote the read marker over the user's own messages @@ -650,25 +651,26 @@ void Room::Private::setLastReadReceipt(User* u, rev_iter_t newMarker, return ti->senderId() != u->id(); }) - 1; - newEvtId = (*eagerMarker)->id(); + newReceipt.eventId = (*eagerMarker)->id(); if (eagerMarker != newMarker.base() - 1) // &*(rIt.base() - 1) === &*rIt qCDebug(EPHEMERAL) << "Auto-promoted read receipt for" << u->id() - << "to" << newEvtId; + << "to" << newReceipt.eventId; } - auto& storedId = lastReadEventIds[u]; - if (storedId == newEvtId) + if (storedReceipt == newReceipt) return; // Finally make the change - auto& oldEventReadUsers = eventIdReadUsers[storedId]; + auto& oldEventReadUsers = eventIdReadUsers[storedReceipt.eventId]; oldEventReadUsers.remove(u); if (oldEventReadUsers.isEmpty()) - eventIdReadUsers.remove(storedId); - eventIdReadUsers[newEvtId].insert(u); - swap(storedId, newEvtId); // Now newEvtId actually stores the old eventId + eventIdReadUsers.remove(storedReceipt.eventId); + eventIdReadUsers[newReceipt.eventId].insert(u); + swap(storedReceipt, newReceipt); // Now newReceipt actually stores the old receipt emit q->lastReadEventChanged(u); + // TODO: remove in 0.8 if (!isLocalUser(u)) - emit q->readMarkerForUserMoved(u, newEvtId, storedId); + emit q->readMarkerForUserMoved(u, newReceipt.eventId, + storedReceipt.eventId); } Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, @@ -677,7 +679,7 @@ Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, Q_ASSERT(from >= timeline.crbegin() && from <= timeline.crend()); Q_ASSERT(to >= from && to <= timeline.crend()); - auto fullyReadMarker = q->readMarker(); + auto fullyReadMarker = q->fullyReadMarker(); if (fullyReadMarker < from) return NoChange; // What's arrived is already fully read @@ -718,7 +720,7 @@ Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, unreadMessages += newUnreadMessages; qCDebug(MESSAGES) << "Room" << q->objectName() << "has gained" << newUnreadMessages << "unread message(s)," - << (q->readMarker() == timeline.crend() + << (q->fullyReadMarker() == timeline.crend() ? "in total at least" : "in total") << unreadMessages << "unread message(s)"; @@ -730,12 +732,12 @@ Room::Changes Room::Private::recalculateUnreadCount(bool force) { // The recalculation logic assumes that the fully read marker points at // a specific position in the timeline - Q_ASSERT(q->readMarker() != timeline.crend()); + Q_ASSERT(q->fullyReadMarker() != timeline.crend()); const auto oldUnreadCount = unreadMessages; QElapsedTimer et; et.start(); unreadMessages = - int(count_if(timeline.crbegin(), q->readMarker(), + int(count_if(timeline.crbegin(), q->fullyReadMarker(), [this](const auto& ti) { return isEventNotable(ti); })); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Recounting unread messages took" << et; @@ -765,22 +767,22 @@ Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) const auto prevFullyReadId = std::exchange(fullyReadUntilEventId, eventId); qCDebug(MESSAGES) << "Fully read marker in" << q->objectName() // << "moved to" << fullyReadUntilEventId; + emit q->fullyReadMarkerMoved(prevFullyReadId, fullyReadUntilEventId); + // TODO: Remove in 0.8 emit q->readMarkerMoved(prevFullyReadId, fullyReadUntilEventId); Changes changes = ReadMarkerChange; - if (const auto rm = q->readMarker(); rm != timeline.crend()) { + if (const auto rm = q->fullyReadMarker(); rm != timeline.crend()) { // Pull read receipt if it's behind - if (auto rr = q->readMarker(q->localUser()); rr > rm) - setLastReadReceipt(q->localUser(), rm); - - changes |= recalculateUnreadCount(); + setLastReadReceipt(q->localUser(), rm); + changes |= recalculateUnreadCount(); // TODO: updateUnreadCount()? } return changes; } void Room::Private::markMessagesAsRead(const rev_iter_t &upToMarker) { - if (upToMarker < q->readMarker()) { + if (upToMarker < q->fullyReadMarker()) { setFullyReadMarker((*upToMarker)->id()); // Assuming that if a read receipt was sent on a newer event, it will // stay there instead of "un-reading" notifications/mentions from @@ -989,17 +991,23 @@ void Room::setLastDisplayedEvent(TimelineItem::index_t index) Room::rev_iter_t Room::readMarker(const User* user) const { Q_ASSERT(user); - return findInTimeline(d->lastReadEventIds.value(user)); + return findInTimeline(lastReadReceipt(user->id()).eventId); } -Room::rev_iter_t Room::readMarker() const +Room::rev_iter_t Room::readMarker() const { return fullyReadMarker(); } + +QString Room::readMarkerEventId() const { return lastFullyReadEventId(); } + +ReadReceipt Room::lastReadReceipt(const QString& userId) const { - return findInTimeline(d->fullyReadUntilEventId); + return d->lastReadReceipts.value(user(userId)); } -QString Room::readMarkerEventId() const +QString Room::lastFullyReadEventId() const { return d->fullyReadUntilEventId; } + +Room::rev_iter_t Room::fullyReadMarker() const { - return d->fullyReadUntilEventId; + return findInTimeline(d->fullyReadUntilEventId); } QSet Room::usersAtEventId(const QString& eventId) @@ -2477,12 +2485,14 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // the new message events. auto* const firstWriter = q->user((*from)->senderId()); setLastReadReceipt(firstWriter, rev_iter_t(from + 1)); - if (firstWriter == q->localUser() && q->readMarker().base() == from) { + if (firstWriter == q->localUser() + && q->fullyReadMarker().base() == from) // + { // If the local user's message(s) is/are first in the batch // and the fully read marker was right before it, promote // the fully read marker to the same event as the read receipt. roomChanges |= - setFullyReadMarker(lastReadEventIds.value(firstWriter)); + setFullyReadMarker(lastReadReceipts.value(firstWriter).eventId); } roomChanges |= updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); } @@ -2533,7 +2543,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) // When there are no unread messages and the read marker is within the // known timeline, unreadMessages == -1 // (see https://github.com/quotient-im/libQuotient/wiki/unread_count). - Q_ASSERT(unreadMessages != 0 || q->readMarker() == timeline.crend()); + Q_ASSERT(unreadMessages != 0 || q->fullyReadMarker() == timeline.crend()); Q_ASSERT(timeline.size() == timelineSize + insertedSize); if (insertedSize > 9 || et.nsecsElapsed() >= profilerMinNsecs()) @@ -2793,7 +2803,8 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) // supposed to move backwards. Otherwise, blindly // store the event id for this user and update the read // marker when/if the event is fetched later on. - d->setLastReadReceipt(u, newMarker, p.evtId); + d->setLastReadReceipt(u, newMarker, + { p.evtId, r.timestamp }); } } if (eventsWithReceipts.size() > 3 || totalReceipts > 10 diff --git a/lib/room.h b/lib/room.h index fa7b6e6d..4833bd27 100644 --- a/lib/room.h +++ b/lib/room.h @@ -71,6 +71,28 @@ public: bool failed() const { return status == Failed; } }; +//! \brief Data structure for a room member's read receipt +//! \sa Room::lastReadReceipt +class ReadReceipt { + Q_GADGET + Q_PROPERTY(QString eventId MEMBER eventId CONSTANT) + Q_PROPERTY(QDateTime timestamp MEMBER timestamp CONSTANT) +public: + QString eventId; + QDateTime timestamp; + + bool operator==(const ReadReceipt& other) + { + return eventId == other.eventId && timestamp == other.timestamp; + } + bool operator!=(const ReadReceipt& other) { return !operator==(other); } +}; +inline void swap(ReadReceipt& lhs, ReadReceipt& rhs) +{ + swap(lhs.eventId, rhs.eventId); + swap(lhs.timestamp, rhs.timestamp); +} + class Room : public QObject { Q_OBJECT Q_PROPERTY(Connection* connection READ connection CONSTANT) @@ -104,9 +126,11 @@ class Room : public QObject { setFirstDisplayedEventId NOTIFY firstDisplayedEventChanged) Q_PROPERTY(QString lastDisplayedEventId READ lastDisplayedEventId WRITE setLastDisplayedEventId NOTIFY lastDisplayedEventChanged) - + //! \deprecated since 0.7 Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE markMessagesAsRead NOTIFY readMarkerMoved) + Q_PROPERTY(QString lastFullyReadEventId READ lastFullyReadEventId WRITE + markMessagesAsRead NOTIFY fullyReadMarkerMoved) Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY unreadMessagesChanged) Q_PROPERTY(int unreadCount READ unreadCount NOTIFY unreadMessagesChanged) @@ -361,9 +385,18 @@ public: void setLastDisplayedEventId(const QString& eventId); void setLastDisplayedEvent(TimelineItem::index_t index); + [[deprecated("Use lastReadReceipt() to get m.read receipt or" + " fullyReadMarker() to get m.fully_read marker")]] // rev_iter_t readMarker(const User* user) const; + [[deprecated("Use lastReadReceipt() to get m.read receipt or" + " fullyReadMarker() to get m.fully_read marker")]] // rev_iter_t readMarker() const; + [[deprecated("Use lastReadReceipt() to get m.read receipt or" + " fullyReadMarker() to get m.fully_read marker")]] // QString readMarkerEventId() const; + ReadReceipt lastReadReceipt(const QString& userId) const; + QString lastFullyReadEventId() const; + rev_iter_t fullyReadMarker() const; QSet usersAtEventId(const QString& eventId); /** * \brief Mark the event with uptoEventId as read @@ -704,7 +737,10 @@ Q_SIGNALS: void firstDisplayedEventChanged(); void lastDisplayedEventChanged(); void lastReadEventChanged(Quotient::User* user); + void fullyReadMarkerMoved(QString fromEventId, QString toEventId); + //! \deprecated since 0.7 - use fullyReadMarkerMoved void readMarkerMoved(QString fromEventId, QString toEventId); + //! \deprecated since 0.7 - use lastReadEventChanged void readMarkerForUserMoved(Quotient::User* user, QString fromEventId, QString toEventId); void unreadMessagesChanged(Quotient::Room* room); @@ -779,4 +815,5 @@ private: }; } // namespace Quotient Q_DECLARE_METATYPE(Quotient::FileTransferInfo) +Q_DECLARE_METATYPE(Quotient::ReadReceipt) Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::Room::Changes) -- cgit v1.2.3 From 2975b435f1ae198187183b883b5e666c7659e8fc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 30 Jul 2021 23:25:01 +0200 Subject: Room::setReadReceipt() --- lib/room.cpp | 9 +++++++++ lib/room.h | 7 ++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/room.cpp b/lib/room.cpp index c7f29b6d..649b1ed2 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -765,6 +765,15 @@ Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) return changes; } +void Room::setReadReceipt(const QString& atEventId) +{ + d->setLastReadReceipt(localUser(), historyEdge(), + { atEventId, QDateTime::currentDateTime() }); + connection()->callApi(BackgroundRequest, id(), + QStringLiteral("m.read"), + QUrl::toPercentEncoding(atEventId)); +} + void Room::Private::markMessagesAsRead(const rev_iter_t &upToMarker) { if (upToMarker < q->fullyReadMarker()) { diff --git a/lib/room.h b/lib/room.h index 9ef553df..afe223c5 100644 --- a/lib/room.h +++ b/lib/room.h @@ -626,7 +626,12 @@ public Q_SLOTS: void downloadFile(const QString& eventId, const QUrl& localFilename = {}); void cancelFileTransfer(const QString& id); - /// Mark all messages in the room as read + //! \brief Set a given event as last read and post a read receipt on it + //! + //! Does nothing if the event is behind the current read receipt. + //! \sa lastReadReceipt, markMessagesAsRead, markAllMessagesAsRead + void setReadReceipt(const QString& atEventId); + //! Put the fully-read marker at the latest message in the room void markAllMessagesAsRead(); /// Switch the room's version (aka upgrade) -- cgit v1.2.3 From 6d558fa9c12b52fe3cc47cc143d0a758c4ea929a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 31 Jul 2021 08:30:44 +0200 Subject: Add/update doc-comments on the new/updated methods [skip ci] --- lib/room.h | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/lib/room.h b/lib/room.h index afe223c5..a8c3cac2 100644 --- a/lib/room.h +++ b/lib/room.h @@ -378,30 +378,85 @@ public: void setLastDisplayedEventId(const QString& eventId); void setLastDisplayedEvent(TimelineItem::index_t index); + //! \brief Obtain a read receipt of any user + //! + //! \deprecated Use lastReadReceipt or fullyReadMarker instead + //! Historically, readMarker was returning a "converged" read marker + //! representing both the read receipt and the fully read marker, as + //! Quotient managed them together. Since 0.6.8, a single-argument call of + //! readMarker returns the last read receipt position (for any room member) + //! and a call without arguments returns the last _fully read_ position, + //! to provide access to both positions separately while maintaining API + //! stability guarantees. 0.7 has separate methods to return read receipts + //! and the fully read marker - use them instead. + //! \sa lastReadReceipt [[deprecated("Use lastReadReceipt() to get m.read receipt or" " fullyReadMarker() to get m.fully_read marker")]] // rev_iter_t readMarker(const User* user) const; + //! \brief Obtain the local user's fully-read marker + //! \deprecated Use fullyReadMarker instead + //! See the documentation for the single-argument overload + //! \sa fullyReadMarker [[deprecated("Use lastReadReceipt() to get m.read receipt or" " fullyReadMarker() to get m.fully_read marker")]] // rev_iter_t readMarker() const; + //! \brief Get the event id for the local user's fully-read marker + //! \deprecated Use lastFullyReadEventId instead + //! See the readMarker documentation [[deprecated("Use lastReadReceipt() to get m.read receipt or" " fullyReadMarker() to get m.fully_read marker")]] // QString readMarkerEventId() const; + + //! \brief Get the latest read receipt from a user + //! + //! The user id must be valid. A read receipt with an empty event id + //! is returned if the user id is valid but there was no read receipt + //! from them. + //! \sa usersAtEventId ReadReceipt lastReadReceipt(const QString& userId) const; + + //! \brief Get the latest event id marked as fully read + //! + //! This can be either the event id pointed to by the actual latest + //! m.fully_read event, or the latest event id marked locally as fully read + //! if markMessagesAsRead or markAllMessagesAsRead has been called and + //! the homeserver didn't return an updated m.fully_read event yet. + //! \sa markMessagesAsRead, markAllMessagesAsRead, fullyReadMarker QString lastFullyReadEventId() const; + + //! \brief Get the iterator to the latest timeline item marked as fully read + //! + //! This method calls findInTimeline on the result of lastFullyReadEventId. + //! If the fully read marker turns out to be outside the timeline (because + //! the event marked as fully read is too far back in the history) the + //! returned value will be equal to historyEdge. + //! + //! Be sure to read the caveats on iterators returned by findInTimeline. + //! \sa lastFullyReadEventId, findInTimeline rev_iter_t fullyReadMarker() const; + + //! \brief Get users whose latest read receipts point to the event + //! + //! This method is for cases when you need to show users who have read + //! an event. Calling it on inexistent or empty event id will return + //! an empty set. + //! \sa lastReadReceipt QSet usersAtEventId(const QString& eventId); - /** - * \brief Mark the event with uptoEventId as read - * - * Finds in the timeline and marks as read the event with - * the specified id; also posts a read receipt to the server either - * for this message or, if it's from the local user, for - * the nearest non-local message before. uptoEventId must be non-empty. - */ - void markMessagesAsRead(QString uptoEventId); - /// Check whether there are unread messages in the room + //! + //! \brief Mark the event with uptoEventId as fully read + //! + //! Marks the event with the specified id as fully read locally and also + //! sends an update to m.fully_read account data to the server either + //! for this message or, if it's from the local user, for + //! the nearest non-local message before. uptoEventId must point to a known + //! event in the timeline; the method will do nothing if the event is behind + //! the current m.fully_read marker or is not loaded, to prevent + //! accidentally trying to move the marker back in the timeline. + //! \sa markAllMessagesAsRead, fullyReadMarker + Q_INVOKABLE void markMessagesAsRead(QString uptoEventId); + + //! Check whether there are unread messages in the room bool hasUnreadMessages() const; /** Get the number of unread messages in the room -- cgit v1.2.3 From 5c3f853a04a0c1a2b360391a9f27e7c0fd9f42bd Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 31 Jul 2021 08:32:35 +0200 Subject: Room: Mark dependent Q_PROPERTYs as STORED false hasUnreadMessages is derived from unreadCount; isFavourite/isLowPriority effectively depend on tagNames. --- lib/room.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/room.h b/lib/room.h index 0ab18ef7..52ba2eab 100644 --- a/lib/room.h +++ b/lib/room.h @@ -107,7 +107,7 @@ class Room : public QObject { Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE markMessagesAsRead NOTIFY readMarkerMoved) Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY - unreadMessagesChanged) + unreadMessagesChanged STORED false) Q_PROPERTY(int unreadCount READ unreadCount NOTIFY unreadMessagesChanged) Q_PROPERTY(int highlightCount READ highlightCount NOTIFY highlightCountChanged RESET resetHighlightCount) @@ -116,8 +116,8 @@ class Room : public QObject { 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) + Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY tagsChanged STORED false) + Q_PROPERTY(bool isLowPriority READ isLowPriority NOTIFY tagsChanged STORED false) Q_PROPERTY(GetRoomEventsJob* eventsHistoryJob READ eventsHistoryJob NOTIFY eventsHistoryJobChanged) -- cgit v1.2.3 From ea1e849f617f62b3d209b2019e0daa3c6bed50f0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 2 Aug 2021 09:02:31 +0200 Subject: More doc-comments --- lib/events/roomevent.h | 4 ++++ lib/events/roommessageevent.h | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index fea509c0..3abd56c0 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -37,6 +37,10 @@ public: } QString roomId() const; QString senderId() const; + //! \brief Determine whether the event has been replaced + //! + //! \return true if this event has been overridden by another event + //! with `"rel_type": "m.replace"`; false otherwise bool isReplaced() const; QString replacedBy() const; bool isRedacted() const { return bool(_redactedBecause); } diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 7bcda2ba..88d3b74c 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -62,9 +62,26 @@ public: _content.data()); } QMimeType mimeType() const; + //! \brief Determine whether the message has text content + //! + //! \return true, if the message type is one of m.text, m.notice, m.emote, + //! or the message type is unspecified (in which case plainBody() + //! can still be examined); false otherwise bool hasTextContent() const; + //! \brief Determine whether the message has a file/attachment + //! + //! \return true, if the message has a data structure corresponding to + //! a file (such as m.file or m.audio); false otherwise bool hasFileContent() const; + //! \brief Determine whether the message has a thumbnail + //! + //! \return true, if the message has a data structure corresponding to + //! a thumbnail (the message type may be one for visual content, + //! such as m.image, or generic binary content, i.e. m.file); + //! false otherwise bool hasThumbnail() const; + //! \brief Obtain id of an event replaced by the current one + //! \sa RoomEvent::isReplaced, RoomEvent::replacedBy QString replacedEvent() const; static QString rawMsgTypeForUrl(const QUrl& url); -- cgit v1.2.3 From 72643d2f90aa929ec5e44159f717057fdad56cbd Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 2 Aug 2021 09:04:06 +0200 Subject: Fix lack of percent encoding in User::load() Users with slashes in their ids do it at their own peril of course but to encode the id in the URL is a good thing in any case. Too bad it's pretty invisible and has to be dealt with case by case, instead of GTAD magically sticking QUrl::toPercentEncoding() where appropriate in the generated code. --- lib/user.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/user.cpp b/lib/user.cpp index c97e33a4..04afed2b 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -65,7 +65,8 @@ User::~User() = default; void User::load() { - auto *profileJob = connection()->callApi(id()); + auto* profileJob = + connection()->callApi(QUrl::toPercentEncoding(id())); connect(profileJob, &BaseJob::result, this, [this, profileJob] { d->defaultName = profileJob->displayname(); d->defaultAvatar = Avatar(QUrl(profileJob->avatarUrl())); -- cgit v1.2.3 From 1f52b5a2da9bce4d25f4c897370e58c8b6d56ba1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 Aug 2021 11:08:57 +0200 Subject: README.md: replace "PRs welcome" with a merge chance badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5aae543..05c629f2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![](https://img.shields.io/cii/percentage/1023.svg?label=CII%20best%20practices)](https://bestpractices.coreinfrastructure.org/projects/1023/badge) ![](https://img.shields.io/github/commit-activity/y/quotient-im/libQuotient.svg) [![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/quotient-im/libQuotient.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/quotient-im/libQuotient/context:cpp) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) +[![merge-chance-badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fmerge-chance.info%2Fbadge%3Frepo%3Dquotient-im/libquotient)](https://merge-chance.info/target?repo=quotient-im/libquotient) The Quotient project aims to produce a Qt5-based SDK to develop applications for [Matrix](https://matrix.org). libQuotient is a library that enables client -- cgit v1.2.3 From f7cbefe5ad626ae1798a5d7bb7546e89ad336acd Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 Aug 2021 16:55:06 +0200 Subject: API files: reformat after .clang-format change See 000b5730. --- lib/csapi/admin.h | 5 +---- lib/csapi/content-repo.h | 35 +++++++---------------------------- lib/csapi/create_room.h | 5 +---- lib/csapi/device_management.h | 5 +---- lib/csapi/directory.h | 5 +---- lib/csapi/event_context.h | 20 ++++---------------- lib/csapi/filter.h | 10 ++-------- lib/csapi/joining.h | 10 ++-------- lib/csapi/keys.h | 5 +---- lib/csapi/knocking.h | 5 +---- lib/csapi/list_public_rooms.h | 20 ++++---------------- lib/csapi/login.h | 10 ++-------- lib/csapi/message_pagination.h | 20 ++++---------------- lib/csapi/notifications.h | 5 +---- lib/csapi/openid.h | 5 +---- lib/csapi/peeking_events.h | 15 +++------------ lib/csapi/presence.h | 10 ++-------- lib/csapi/profile.h | 10 ++-------- lib/csapi/pushrules.h | 10 ++-------- lib/csapi/redaction.h | 5 +---- lib/csapi/registration.h | 10 ++-------- lib/csapi/room_send.h | 5 +---- lib/csapi/room_state.h | 5 +---- lib/csapi/rooms.h | 12 ++---------- lib/csapi/users.h | 5 +---- lib/csapi/voip.h | 5 +---- lib/csapi/whoami.h | 10 ++-------- 27 files changed, 53 insertions(+), 214 deletions(-) diff --git a/lib/csapi/admin.h b/lib/csapi/admin.h index d4fe639b..570bf24a 100644 --- a/lib/csapi/admin.h +++ b/lib/csapi/admin.h @@ -74,10 +74,7 @@ public: // Result properties /// The Matrix user ID of the user. - QString userId() const - { - return loadFromJson("user_id"_ls); - } + QString userId() const { return loadFromJson("user_id"_ls); } /// Each key is an identifier for one of the user's devices. QHash devices() const diff --git a/lib/csapi/content-repo.h b/lib/csapi/content-repo.h index a41453b2..f3d7309a 100644 --- a/lib/csapi/content-repo.h +++ b/lib/csapi/content-repo.h @@ -72,10 +72,7 @@ public: // Result properties /// The content type of the file that was previously uploaded. - QString contentType() const - { - return reply()->rawHeader("Content-Type"); - } + QString contentType() const { return reply()->rawHeader("Content-Type"); } /// The name of the file that was previously uploaded, if set. QString contentDisposition() const @@ -84,10 +81,7 @@ public: } /// The content that was previously uploaded. - QIODevice* data() - { - return reply(); - } + QIODevice* data() { return reply(); } }; /*! \brief Download content from the content repository overriding the file name @@ -132,10 +126,7 @@ public: // Result properties /// The content type of the file that was previously uploaded. - QString contentType() const - { - return reply()->rawHeader("Content-Type"); - } + QString contentType() const { return reply()->rawHeader("Content-Type"); } /// The `fileName` requested or the name of the file that was previously /// uploaded, if set. @@ -145,10 +136,7 @@ public: } /// The content that was previously uploaded. - QIODevice* data() - { - return reply(); - } + QIODevice* data() { return reply(); } }; /*! \brief Download a thumbnail of content from the content repository @@ -202,16 +190,10 @@ public: // Result properties /// The content type of the thumbnail. - QString contentType() const - { - return reply()->rawHeader("Content-Type"); - } + QString contentType() const { return reply()->rawHeader("Content-Type"); } /// A thumbnail of the requested content. - QIODevice* data() - { - return reply(); - } + QIODevice* data() { return reply(); } }; /*! \brief Get information about a URL for a client @@ -257,10 +239,7 @@ public: /// An [MXC URI](/client-server-api/#matrix-content-mxc-uris) to the image. /// Omitted if there is no image. - QString ogImage() const - { - return loadFromJson("og:image"_ls); - } + QString ogImage() const { return loadFromJson("og:image"_ls); } }; /*! \brief Get the configuration for the content repository. diff --git a/lib/csapi/create_room.h b/lib/csapi/create_room.h index 8c6af7d4..81dfbffc 100644 --- a/lib/csapi/create_room.h +++ b/lib/csapi/create_room.h @@ -268,10 +268,7 @@ public: // Result properties /// The created room's ID. - QString roomId() const - { - return loadFromJson("room_id"_ls); - } + QString roomId() const { return loadFromJson("room_id"_ls); } }; template <> diff --git a/lib/csapi/device_management.h b/lib/csapi/device_management.h index e2acea18..7fb69873 100644 --- a/lib/csapi/device_management.h +++ b/lib/csapi/device_management.h @@ -59,10 +59,7 @@ public: // Result properties /// Device information - Device device() const - { - return fromJson(jsonData()); - } + Device device() const { return fromJson(jsonData()); } }; /*! \brief Update a device diff --git a/lib/csapi/directory.h b/lib/csapi/directory.h index 00215cae..93a31595 100644 --- a/lib/csapi/directory.h +++ b/lib/csapi/directory.h @@ -51,10 +51,7 @@ public: // Result properties /// The room ID for this room alias. - QString roomId() const - { - return loadFromJson("room_id"_ls); - } + QString roomId() const { return loadFromJson("room_id"_ls); } /// A list of servers that are aware of this room alias. QStringList servers() const diff --git a/lib/csapi/event_context.h b/lib/csapi/event_context.h index 6a49769f..4e50edf3 100644 --- a/lib/csapi/event_context.h +++ b/lib/csapi/event_context.h @@ -58,16 +58,10 @@ public: // Result properties /// A token that can be used to paginate backwards with. - QString begin() const - { - return loadFromJson("start"_ls); - } + QString begin() const { return loadFromJson("start"_ls); } /// A token that can be used to paginate forwards with. - QString end() const - { - return loadFromJson("end"_ls); - } + QString end() const { return loadFromJson("end"_ls); } /// A list of room events that happened just before the /// requested event, in reverse-chronological order. @@ -77,10 +71,7 @@ public: } /// Details of the requested event. - RoomEventPtr event() - { - return takeFromJson("event"_ls); - } + RoomEventPtr event() { return takeFromJson("event"_ls); } /// A list of room events that happened just after the /// requested event, in chronological order. @@ -90,10 +81,7 @@ public: } /// The state of the room at the last event returned. - StateEvents state() - { - return takeFromJson("state"_ls); - } + StateEvents state() { return takeFromJson("state"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/filter.h b/lib/csapi/filter.h index 7e9e14ee..01bec36b 100644 --- a/lib/csapi/filter.h +++ b/lib/csapi/filter.h @@ -35,10 +35,7 @@ public: /// with a `{` as this character is used to determine /// if the filter provided is inline JSON or a previously /// declared filter by homeservers on some APIs. - QString filterId() const - { - return loadFromJson("filter_id"_ls); - } + QString filterId() const { return loadFromJson("filter_id"_ls); } }; /*! \brief Download a filter @@ -67,10 +64,7 @@ public: // Result properties /// The filter definition. - Filter filter() const - { - return fromJson(jsonData()); - } + Filter filter() const { return fromJson(jsonData()); } }; } // namespace Quotient diff --git a/lib/csapi/joining.h b/lib/csapi/joining.h index 6dcd1351..d0199b11 100644 --- a/lib/csapi/joining.h +++ b/lib/csapi/joining.h @@ -49,10 +49,7 @@ public: // Result properties /// The joined room ID. - QString roomId() const - { - return loadFromJson("room_id"_ls); - } + QString roomId() const { return loadFromJson("room_id"_ls); } }; /*! \brief Start the requesting user participating in a particular room. @@ -98,10 +95,7 @@ public: // Result properties /// The joined room ID. - QString roomId() const - { - return loadFromJson("room_id"_ls); - } + QString roomId() const { return loadFromJson("room_id"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index 53ba6495..7db09e8d 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -267,10 +267,7 @@ public: /// The Matrix User IDs of all users who may have left all /// the end-to-end encrypted rooms they previously shared /// with the user. - QStringList left() const - { - return loadFromJson("left"_ls); - } + QStringList left() const { return loadFromJson("left"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/knocking.h b/lib/csapi/knocking.h index 607b55a9..1108cb64 100644 --- a/lib/csapi/knocking.h +++ b/lib/csapi/knocking.h @@ -49,10 +49,7 @@ public: // Result properties /// The knocked room ID. - QString roomId() const - { - return loadFromJson("room_id"_ls); - } + QString roomId() const { return loadFromJson("room_id"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/list_public_rooms.h b/lib/csapi/list_public_rooms.h index 1c73c0af..963c8b56 100644 --- a/lib/csapi/list_public_rooms.h +++ b/lib/csapi/list_public_rooms.h @@ -111,18 +111,12 @@ public: /// A pagination token for the response. The absence of this token /// means there are no more results to fetch and the client should /// stop paginating. - QString nextBatch() const - { - return loadFromJson("next_batch"_ls); - } + QString nextBatch() const { return loadFromJson("next_batch"_ls); } /// A pagination token that allows fetching previous results. The /// absence of this token means there are no results before this /// batch, i.e. this is the first batch. - QString prevBatch() const - { - return loadFromJson("prev_batch"_ls); - } + QString prevBatch() const { return loadFromJson("prev_batch"_ls); } /// An estimate on the total number of public rooms, if the /// server has an estimate. @@ -196,18 +190,12 @@ public: /// A pagination token for the response. The absence of this token /// means there are no more results to fetch and the client should /// stop paginating. - QString nextBatch() const - { - return loadFromJson("next_batch"_ls); - } + QString nextBatch() const { return loadFromJson("next_batch"_ls); } /// A pagination token that allows fetching previous results. The /// absence of this token means there are no results before this /// batch, i.e. this is the first batch. - QString prevBatch() const - { - return loadFromJson("prev_batch"_ls); - } + QString prevBatch() const { return loadFromJson("prev_batch"_ls); } /// An estimate on the total number of public rooms, if the /// server has an estimate. diff --git a/lib/csapi/login.h b/lib/csapi/login.h index ce783af2..b35db1eb 100644 --- a/lib/csapi/login.h +++ b/lib/csapi/login.h @@ -121,10 +121,7 @@ public: // Result properties /// The fully-qualified Matrix ID for the account. - QString userId() const - { - return loadFromJson("user_id"_ls); - } + QString userId() const { return loadFromJson("user_id"_ls); } /// An access token for the account. /// This access token can then be used to authorize other requests. @@ -146,10 +143,7 @@ public: /// ID of the logged-in device. Will be the same as the /// corresponding parameter in the request, if one was specified. - QString deviceId() const - { - return loadFromJson("device_id"_ls); - } + QString deviceId() const { return loadFromJson("device_id"_ls); } /// Optional client configuration provided by the server. If present, /// clients SHOULD use the provided object to reconfigure themselves, diff --git a/lib/csapi/message_pagination.h b/lib/csapi/message_pagination.h index 020ef543..363e4d99 100644 --- a/lib/csapi/message_pagination.h +++ b/lib/csapi/message_pagination.h @@ -66,26 +66,17 @@ public: /// The token the pagination starts from. If `dir=b` this will be /// the token supplied in `from`. - QString begin() const - { - return loadFromJson("start"_ls); - } + QString begin() const { return loadFromJson("start"_ls); } /// The token the pagination ends at. If `dir=b` this token should /// be used again to request even earlier events. - QString end() const - { - return loadFromJson("end"_ls); - } + QString end() const { return loadFromJson("end"_ls); } /// A list of room events. The order depends on the `dir` parameter. /// For `dir=b` events will be in reverse-chronological order, /// for `dir=f` in chronological order, so that events start /// at the `from` point. - RoomEvents chunk() - { - return takeFromJson("chunk"_ls); - } + RoomEvents chunk() { return takeFromJson("chunk"_ls); } /// A list of state events relevant to showing the `chunk`. For example, if /// `lazy_load_members` is enabled in the filter then this may contain @@ -95,10 +86,7 @@ public: /// may remove membership events which would have already been /// sent to the client in prior calls to this endpoint, assuming /// the membership of those members has not changed. - StateEvents state() - { - return takeFromJson("state"_ls); - } + StateEvents state() { return takeFromJson("state"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/notifications.h b/lib/csapi/notifications.h index 0cc165ce..0c38fe6b 100644 --- a/lib/csapi/notifications.h +++ b/lib/csapi/notifications.h @@ -71,10 +71,7 @@ public: /// The token to supply in the `from` param of the next /// `/notifications` request in order to request more /// events. If this is absent, there are no more results. - QString nextToken() const - { - return loadFromJson("next_token"_ls); - } + QString nextToken() const { return loadFromJson("next_token"_ls); } /// The list of events that triggered notifications. std::vector notifications() diff --git a/lib/csapi/openid.h b/lib/csapi/openid.h index 88218c20..0be39c8c 100644 --- a/lib/csapi/openid.h +++ b/lib/csapi/openid.h @@ -43,10 +43,7 @@ public: /// Specification](http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse) /// with the only difference being the lack of an `id_token`. Instead, /// the Matrix homeserver's name is provided. - OpenidToken tokenData() const - { - return fromJson(jsonData()); - } + OpenidToken tokenData() const { return fromJson(jsonData()); } }; } // namespace Quotient diff --git a/lib/csapi/peeking_events.h b/lib/csapi/peeking_events.h index 1eee880f..885ff340 100644 --- a/lib/csapi/peeking_events.h +++ b/lib/csapi/peeking_events.h @@ -53,23 +53,14 @@ public: /// A token which correlates to the first value in `chunk`. This /// is usually the same token supplied to `from=`. - QString begin() const - { - return loadFromJson("start"_ls); - } + QString begin() const { return loadFromJson("start"_ls); } /// A token which correlates to the last value in `chunk`. This /// token should be used in the next request to `/events`. - QString end() const - { - return loadFromJson("end"_ls); - } + QString end() const { return loadFromJson("end"_ls); } /// An array of events. - RoomEvents chunk() - { - return takeFromJson("chunk"_ls); - } + RoomEvents chunk() { return takeFromJson("chunk"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/presence.h b/lib/csapi/presence.h index c817ad9f..4ab50e25 100644 --- a/lib/csapi/presence.h +++ b/lib/csapi/presence.h @@ -55,10 +55,7 @@ public: // Result properties /// This user's presence. - QString presence() const - { - return loadFromJson("presence"_ls); - } + QString presence() const { return loadFromJson("presence"_ls); } /// The length of time in milliseconds since an action was performed /// by this user. @@ -68,10 +65,7 @@ public: } /// The state message for this user if one was set. - QString statusMsg() const - { - return loadFromJson("status_msg"_ls); - } + QString statusMsg() const { return loadFromJson("status_msg"_ls); } /// Whether the user is currently active Omittable currentlyActive() const diff --git a/lib/csapi/profile.h b/lib/csapi/profile.h index 3cda34f8..8bbe4f8c 100644 --- a/lib/csapi/profile.h +++ b/lib/csapi/profile.h @@ -101,10 +101,7 @@ public: // Result properties /// The user's avatar URL if they have set one, otherwise not present. - QString avatarUrl() const - { - return loadFromJson("avatar_url"_ls); - } + QString avatarUrl() const { return loadFromJson("avatar_url"_ls); } }; /*! \brief Get this user's profile information. @@ -133,10 +130,7 @@ public: // Result properties /// The user's avatar URL if they have set one, otherwise not present. - QString avatarUrl() const - { - return loadFromJson("avatar_url"_ls); - } + QString avatarUrl() const { return loadFromJson("avatar_url"_ls); } /// The user's display name if they have set one, otherwise not present. QString displayname() const diff --git a/lib/csapi/pushrules.h b/lib/csapi/pushrules.h index 90d2ce79..a5eb48f0 100644 --- a/lib/csapi/pushrules.h +++ b/lib/csapi/pushrules.h @@ -72,10 +72,7 @@ public: /// The specific push rule. This will also include keys specific to the /// rule itself such as the rule's `actions` and `conditions` if set. - PushRule pushRule() const - { - return fromJson(jsonData()); - } + PushRule pushRule() const { return fromJson(jsonData()); } }; /*! \brief Delete a push rule. @@ -191,10 +188,7 @@ public: // Result properties /// Whether the push rule is enabled or not. - bool enabled() const - { - return loadFromJson("enabled"_ls); - } + bool enabled() const { return loadFromJson("enabled"_ls); } }; /*! \brief Enable or disable a push rule. diff --git a/lib/csapi/redaction.h b/lib/csapi/redaction.h index f12e6b71..f0db9f9f 100644 --- a/lib/csapi/redaction.h +++ b/lib/csapi/redaction.h @@ -46,10 +46,7 @@ public: // Result properties /// A unique identifier for the event. - QString eventId() const - { - return loadFromJson("event_id"_ls); - } + QString eventId() const { return loadFromJson("event_id"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/registration.h b/lib/csapi/registration.h index 0ad8b101..c1614f20 100644 --- a/lib/csapi/registration.h +++ b/lib/csapi/registration.h @@ -108,10 +108,7 @@ public: /// /// Any user ID returned by this API must conform to the grammar given in /// the [Matrix specification](/appendices/#user-identifiers). - QString userId() const - { - return loadFromJson("user_id"_ls); - } + QString userId() const { return loadFromJson("user_id"_ls); } /// An access token for the account. /// This access token can then be used to authorize other requests. @@ -135,10 +132,7 @@ public: /// ID of the registered device. Will be the same as the /// corresponding parameter in the request, if one was specified. /// Required if the `inhibit_login` option is false. - QString deviceId() const - { - return loadFromJson("device_id"_ls); - } + QString deviceId() const { return loadFromJson("device_id"_ls); } }; /*! \brief Begins the validation process for an email to be used during diff --git a/lib/csapi/room_send.h b/lib/csapi/room_send.h index a9e7ca13..96f5beca 100644 --- a/lib/csapi/room_send.h +++ b/lib/csapi/room_send.h @@ -49,10 +49,7 @@ public: // Result properties /// A unique identifier for the event. - QString eventId() const - { - return loadFromJson("event_id"_ls); - } + QString eventId() const { return loadFromJson("event_id"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/room_state.h b/lib/csapi/room_state.h index 99eb4fc9..f95af223 100644 --- a/lib/csapi/room_state.h +++ b/lib/csapi/room_state.h @@ -71,10 +71,7 @@ public: // Result properties /// A unique identifier for the event. - QString eventId() const - { - return loadFromJson("event_id"_ls); - } + QString eventId() const { return loadFromJson("event_id"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/rooms.h b/lib/csapi/rooms.h index 179d7a27..51af2c65 100644 --- a/lib/csapi/rooms.h +++ b/lib/csapi/rooms.h @@ -38,11 +38,7 @@ public: // Result properties /// The full event. - EventPtr event() - - { - return fromJson(jsonData()); - } + EventPtr event() { return fromJson(jsonData()); } }; /*! \brief Get the state identified by the type and key. @@ -103,11 +99,7 @@ public: // Result properties /// The current state of the room - StateEvents events() - - { - return fromJson(jsonData()); - } + StateEvents events() { return fromJson(jsonData()); } }; /*! \brief Get the m.room.member events for the room. diff --git a/lib/csapi/users.h b/lib/csapi/users.h index 772a6365..eab18f6c 100644 --- a/lib/csapi/users.h +++ b/lib/csapi/users.h @@ -66,10 +66,7 @@ public: } /// Indicates if the result list has been truncated by the limit. - bool limited() const - { - return loadFromJson("limited"_ls); - } + bool limited() const { return loadFromJson("limited"_ls); } }; template <> diff --git a/lib/csapi/voip.h b/lib/csapi/voip.h index 85ab8b41..087ebbbd 100644 --- a/lib/csapi/voip.h +++ b/lib/csapi/voip.h @@ -28,10 +28,7 @@ public: // Result properties /// The TURN server credentials. - QJsonObject data() const - { - return fromJson(jsonData()); - } + QJsonObject data() const { return fromJson(jsonData()); } }; } // namespace Quotient diff --git a/lib/csapi/whoami.h b/lib/csapi/whoami.h index 203742c9..319f82c5 100644 --- a/lib/csapi/whoami.h +++ b/lib/csapi/whoami.h @@ -34,19 +34,13 @@ public: // Result properties /// The user ID that owns the access token. - QString userId() const - { - return loadFromJson("user_id"_ls); - } + QString userId() const { return loadFromJson("user_id"_ls); } /// Device ID associated with the access token. If no device /// is associated with the access token (such as in the case /// of application services) then this field can be omitted. /// Otherwise this is required. - QString deviceId() const - { - return loadFromJson("device_id"_ls); - } + QString deviceId() const { return loadFromJson("device_id"_ls); } }; } // namespace Quotient -- cgit v1.2.3 From f81aa4d723577ce30518424510e45ef39ff0e29e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 Aug 2021 18:59:33 +0200 Subject: Drop an out-of-date comment BaseJob::loadFromJson() does just fine without QStringViews. [skip ci] --- lib/jobs/basejob.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index d33d542e..7ce4b808 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -180,7 +180,7 @@ public: * If there's no top-level JSON object in the response or if there's * no node with the key \p keyName, \p defaultValue is returned. */ - template // Waiting for QStringViews... + template T loadFromJson(const StrT& keyName, T&& defaultValue = {}) const { const auto& jv = jsonData().value(keyName); -- cgit v1.2.3 From 35ce036407e1865402d01070e9680a9cda4f361c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 Aug 2021 22:06:42 +0200 Subject: converters.h: (actually) enable QUrl; drop unused types QUrl can now be converted even with QT_NO_URL_CAST_FROM_STRING; and it can also be put to queries. QByteArray did not really need conversion in JSON context; and QJsonObject is/was never used in queries. --- lib/converters.h | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index af6c0192..cc6378e4 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -151,10 +151,17 @@ struct JsonConverter { }; template <> -struct JsonConverter : JsonConverter { - static auto dump(const QUrl& url) // Override on top of that for QString +struct JsonConverter { + static auto load(const QJsonValue& jv) + { + // QT_NO_URL_CAST_FROM_STRING makes this a bit more verbose + QUrl url; + url.setUrl(jv.toString()); + return url; + } + static auto dump(const QUrl& url) { - return JsonConverter::dump(url.toString(QUrl::FullyEncoded)); + return url.toString(QUrl::FullyEncoded); } }; @@ -163,15 +170,6 @@ struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toArray(); } }; -template <> -struct JsonConverter { - static QString dump(const QByteArray& ba) { return ba.constData(); } - static auto load(const QJsonValue& jv) - { - return fromJson(jv).toLatin1(); - } -}; - template <> struct JsonConverter { static QJsonValue dump(const QVariant& v); @@ -304,16 +302,15 @@ namespace _impl { q.addQueryItem(k, v ? QStringLiteral("true") : QStringLiteral("false")); } - inline void addTo(QUrlQuery& q, const QString& k, const QStringList& vals) + inline void addTo(QUrlQuery& q, const QString& k, const QUrl& v) { - for (const auto& v : vals) - q.addQueryItem(k, v); + q.addQueryItem(k, v.toEncoded()); } - inline void addTo(QUrlQuery& q, const QString&, const QJsonObject& vals) + inline void addTo(QUrlQuery& q, const QString& k, const QStringList& vals) { - for (auto it = vals.begin(); it != vals.end(); ++it) - q.addQueryItem(it.key(), it.value().toString()); + for (const auto& v : vals) + q.addQueryItem(k, v); } // This one is for types that don't have isEmpty() and for all types -- cgit v1.2.3 From 08d5cef8bbbef65c961370d4a9e2c48a6d7281f1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 Aug 2021 22:08:11 +0200 Subject: gtad.yaml: use QUrl where API uses 'format: uri' --- gtad/gtad.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml index 928a1495..58e1909c 100644 --- a/gtad/gtad.yaml +++ b/gtad/gtad.yaml @@ -68,6 +68,9 @@ analyzer: - dateTime: type: QDateTime initializer: QDateTime::fromString("{{defaultValue}}") + - uri: + type: QUrl + initializer: QUrl::fromEncoded("{{defaultValue}}") - //: &QString type: QString initializer: QStringLiteral("{{defaultValue}}") -- cgit v1.2.3 From bf6303c41264d913ca049009034aa948464b8f30 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 Aug 2021 22:14:06 +0200 Subject: User::avatar: add const --- lib/user.cpp | 6 +++--- lib/user.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/user.cpp b/lib/user.cpp index 04afed2b..a7e0efd9 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -197,18 +197,18 @@ const Avatar& User::avatarObject(const Room* room) const return d->otherAvatars.try_emplace(mediaId, url).first->second; } -QImage User::avatar(int dimension, const Room* room) +QImage User::avatar(int dimension, const Room* room) const { return avatar(dimension, dimension, room); } -QImage User::avatar(int width, int height, const Room* room) +QImage User::avatar(int width, int height, const Room* room) const { return avatar(width, height, room, [] {}); } QImage User::avatar(int width, int height, const Room* room, - const Avatar::get_callback_t& callback) + const Avatar::get_callback_t& callback) const { return avatarObject(room).get(connection(), width, height, callback); } diff --git a/lib/user.h b/lib/user.h index e4560843..4ff62951 100644 --- a/lib/user.h +++ b/lib/user.h @@ -99,11 +99,11 @@ public: */ const Avatar& avatarObject(const Room* room = nullptr) const; Q_INVOKABLE QImage avatar(int dimension, - const Quotient::Room* room = nullptr); + const Quotient::Room* room = nullptr) const; Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight, - const Quotient::Room* room = nullptr); + const Quotient::Room* room = nullptr) const; QImage avatar(int width, int height, const Room* room, - const Avatar::get_callback_t& callback); + const Avatar::get_callback_t& callback) const; QString avatarMediaId(const Room* room = nullptr) const; QUrl avatarUrl(const Room* room = nullptr) const; -- cgit v1.2.3 From bd649c591fa020fde0bd56a63c13025097b831ae Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 Aug 2021 22:12:46 +0200 Subject: Update generated files This uses API definition files from https://github.com/matrix-org/matrix-doc/pull/3236, and additionally makes uploadFile>content_uri to have 'format: uri' (as suggested in the PR review). Only use this commit with the next one; alone it breaks the build. --- lib/csapi/content-repo.cpp | 6 +++--- lib/csapi/content-repo.h | 11 ++++------- lib/csapi/definitions/public_rooms_response.h | 2 +- lib/csapi/definitions/request_token_response.h | 2 +- lib/csapi/definitions/wellknown/homeserver.h | 2 +- lib/csapi/definitions/wellknown/identity_server.h | 2 +- lib/csapi/profile.cpp | 2 +- lib/csapi/profile.h | 6 +++--- lib/csapi/pusher.h | 4 ++-- lib/csapi/rooms.h | 2 +- lib/csapi/search.h | 2 +- lib/csapi/users.h | 2 +- 12 files changed, 20 insertions(+), 23 deletions(-) diff --git a/lib/csapi/content-repo.cpp b/lib/csapi/content-repo.cpp index e913bfd1..00bc9706 100644 --- a/lib/csapi/content-repo.cpp +++ b/lib/csapi/content-repo.cpp @@ -122,7 +122,7 @@ GetContentThumbnailJob::GetContentThumbnailJob(const QString& serverName, setExpectedContentTypes({ "image/jpeg", "image/png" }); } -auto queryToGetUrlPreview(const QString& url, Omittable ts) +auto queryToGetUrlPreview(const QUrl& url, Omittable ts) { QUrlQuery _q; addParam<>(_q, QStringLiteral("url"), url); @@ -130,7 +130,7 @@ auto queryToGetUrlPreview(const QString& url, Omittable ts) return _q; } -QUrl GetUrlPreviewJob::makeRequestUrl(QUrl baseUrl, const QString& url, +QUrl GetUrlPreviewJob::makeRequestUrl(QUrl baseUrl, const QUrl& url, Omittable ts) { return BaseJob::makeRequestUrl(std::move(baseUrl), @@ -139,7 +139,7 @@ QUrl GetUrlPreviewJob::makeRequestUrl(QUrl baseUrl, const QString& url, queryToGetUrlPreview(url, ts)); } -GetUrlPreviewJob::GetUrlPreviewJob(const QString& url, Omittable ts) +GetUrlPreviewJob::GetUrlPreviewJob(const QUrl& url, Omittable ts) : BaseJob(HttpVerb::Get, QStringLiteral("GetUrlPreviewJob"), QStringLiteral("/_matrix/media/r0") % "/preview_url", queryToGetUrlPreview(url, ts)) diff --git a/lib/csapi/content-repo.h b/lib/csapi/content-repo.h index f3d7309a..28409f5c 100644 --- a/lib/csapi/content-repo.h +++ b/lib/csapi/content-repo.h @@ -34,10 +34,7 @@ public: /// The [MXC URI](/client-server-api/#matrix-content-mxc-uris) to the /// uploaded content. - QString contentUri() const - { - return loadFromJson("content_uri"_ls); - } + QUrl contentUri() const { return loadFromJson("content_uri"_ls); } }; /*! \brief Download content from the content repository. @@ -219,14 +216,14 @@ public: * return a newer version if it does not have the requested version * available. */ - explicit GetUrlPreviewJob(const QString& url, Omittable ts = none); + explicit GetUrlPreviewJob(const QUrl& url, Omittable ts = none); /*! \brief Construct a URL without creating a full-fledged job object * * This function can be used when a URL for GetUrlPreviewJob * is necessary but the job itself isn't. */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& url, + static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& url, Omittable ts = none); // Result properties @@ -239,7 +236,7 @@ public: /// An [MXC URI](/client-server-api/#matrix-content-mxc-uris) to the image. /// Omitted if there is no image. - QString ogImage() const { return loadFromJson("og:image"_ls); } + QUrl ogImage() const { return loadFromJson("og:image"_ls); } }; /*! \brief Get the configuration for the content repository. diff --git a/lib/csapi/definitions/public_rooms_response.h b/lib/csapi/definitions/public_rooms_response.h index 34b447d2..2938b4ec 100644 --- a/lib/csapi/definitions/public_rooms_response.h +++ b/lib/csapi/definitions/public_rooms_response.h @@ -36,7 +36,7 @@ struct PublicRoomsChunk { bool guestCanJoin; /// The URL for the room's avatar, if one is set. - QString avatarUrl; + QUrl avatarUrl; /// The room's join rule. When not present, the room is assumed to /// be `public`. Note that rooms with `invite` join rules are not diff --git a/lib/csapi/definitions/request_token_response.h b/lib/csapi/definitions/request_token_response.h index f9981100..d5fbbadb 100644 --- a/lib/csapi/definitions/request_token_response.h +++ b/lib/csapi/definitions/request_token_response.h @@ -25,7 +25,7 @@ struct RequestTokenResponse { /// will happen without the client's involvement provided the homeserver /// advertises this specification version in the `/versions` response /// (ie: r0.5.0). - QString submitUrl; + QUrl submitUrl; }; template <> diff --git a/lib/csapi/definitions/wellknown/homeserver.h b/lib/csapi/definitions/wellknown/homeserver.h index 5cfaca24..b7db4182 100644 --- a/lib/csapi/definitions/wellknown/homeserver.h +++ b/lib/csapi/definitions/wellknown/homeserver.h @@ -10,7 +10,7 @@ namespace Quotient { /// Used by clients to discover homeserver information. struct HomeserverInformation { /// The base URL for the homeserver for client-server connections. - QString baseUrl; + QUrl baseUrl; }; template <> diff --git a/lib/csapi/definitions/wellknown/identity_server.h b/lib/csapi/definitions/wellknown/identity_server.h index 3bd07bd1..885e3d34 100644 --- a/lib/csapi/definitions/wellknown/identity_server.h +++ b/lib/csapi/definitions/wellknown/identity_server.h @@ -10,7 +10,7 @@ namespace Quotient { /// Used by clients to discover identity server information. struct IdentityServerInformation { /// The base URL for the identity server for client-server connections. - QString baseUrl; + QUrl baseUrl; }; template <> diff --git a/lib/csapi/profile.cpp b/lib/csapi/profile.cpp index 8436b8e6..745fa488 100644 --- a/lib/csapi/profile.cpp +++ b/lib/csapi/profile.cpp @@ -33,7 +33,7 @@ GetDisplayNameJob::GetDisplayNameJob(const QString& userId) false) {} -SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QString& avatarUrl) +SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QUrl& avatarUrl) : BaseJob(HttpVerb::Put, QStringLiteral("SetAvatarUrlJob"), QStringLiteral("/_matrix/client/r0") % "/profile/" % userId % "/avatar_url") diff --git a/lib/csapi/profile.h b/lib/csapi/profile.h index 8bbe4f8c..7f9c9e95 100644 --- a/lib/csapi/profile.h +++ b/lib/csapi/profile.h @@ -73,7 +73,7 @@ public: * \param avatarUrl * The new avatar URL for this user. */ - explicit SetAvatarUrlJob(const QString& userId, const QString& avatarUrl); + explicit SetAvatarUrlJob(const QString& userId, const QUrl& avatarUrl); }; /*! \brief Get the user's avatar URL. @@ -101,7 +101,7 @@ public: // Result properties /// The user's avatar URL if they have set one, otherwise not present. - QString avatarUrl() const { return loadFromJson("avatar_url"_ls); } + QUrl avatarUrl() const { return loadFromJson("avatar_url"_ls); } }; /*! \brief Get this user's profile information. @@ -130,7 +130,7 @@ public: // Result properties /// The user's avatar URL if they have set one, otherwise not present. - QString avatarUrl() const { return loadFromJson("avatar_url"_ls); } + QUrl avatarUrl() const { return loadFromJson("avatar_url"_ls); } /// The user's display name if they have set one, otherwise not present. QString displayname() const diff --git a/lib/csapi/pusher.h b/lib/csapi/pusher.h index 13c9ec25..622b0df6 100644 --- a/lib/csapi/pusher.h +++ b/lib/csapi/pusher.h @@ -21,7 +21,7 @@ public: struct PusherData { /// Required if `kind` is `http`. The URL to use to send /// notifications to. - QString url; + QUrl url; /// The format to use when sending notifications to the Push /// Gateway. QString format; @@ -119,7 +119,7 @@ public: /// Required if `kind` is `http`. The URL to use to send /// notifications to. MUST be an HTTPS URL with a path of /// `/_matrix/push/v1/notify`. - QString url; + QUrl url; /// The format to send notifications in to Push Gateways if the /// `kind` is `http`. The details about what fields the /// homeserver should send to the push gateway are defined in the diff --git a/lib/csapi/rooms.h b/lib/csapi/rooms.h index 51af2c65..2620582b 100644 --- a/lib/csapi/rooms.h +++ b/lib/csapi/rooms.h @@ -175,7 +175,7 @@ public: /// The display name of the user this object is representing. QString displayName; /// The mxc avatar url of the user this object is representing. - QString avatarUrl; + QUrl avatarUrl; }; // Construction/destruction diff --git a/lib/csapi/search.h b/lib/csapi/search.h index b56d9154..3d02752a 100644 --- a/lib/csapi/search.h +++ b/lib/csapi/search.h @@ -81,7 +81,7 @@ public: /// Performs a full text search across different categories. QString displayname; /// Performs a full text search across different categories. - QString avatarUrl; + QUrl avatarUrl; }; /// Context for result, if requested. diff --git a/lib/csapi/users.h b/lib/csapi/users.h index eab18f6c..ec186592 100644 --- a/lib/csapi/users.h +++ b/lib/csapi/users.h @@ -41,7 +41,7 @@ public: /// The display name of the user, if one exists. QString displayName; /// The avatar url, as an MXC, if one exists. - QString avatarUrl; + QUrl avatarUrl; }; // Construction/destruction -- cgit v1.2.3 From e5a760371a158bec6a70353b96614611adecc4bc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 Aug 2021 22:13:42 +0200 Subject: Update non-generated code to work with QUrls in CS API --- lib/avatar.h | 2 +- lib/user.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/avatar.h b/lib/avatar.h index be125c17..37e1eeef 100644 --- a/lib/avatar.h +++ b/lib/avatar.h @@ -21,7 +21,7 @@ public: Avatar& operator=(Avatar&&); using get_callback_t = std::function; - using upload_callback_t = std::function; + using upload_callback_t = std::function; QImage get(Connection* connection, int dimension, get_callback_t callback) const; diff --git a/lib/user.cpp b/lib/user.cpp index 04afed2b..797576db 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -135,17 +135,17 @@ template inline bool User::doSetAvatar(SourceT&& source) { return d->defaultAvatar.upload( - connection(), source, [this](const QString& contentUri) { + connection(), source, [this](const QUrl& contentUri) { auto* j = connection()->callApi(id(), contentUri); connect(j, &BaseJob::success, this, - [this, newUrl = QUrl(contentUri)] { - if (newUrl == d->defaultAvatar.url()) { - d->defaultAvatar.updateUrl(newUrl); + [this, contentUri] { + if (contentUri == d->defaultAvatar.url()) { + d->defaultAvatar.updateUrl(contentUri); emit defaultAvatarChanged(); } else qCWarning(MAIN) << "User" << id() << "already has avatar URL set to" - << newUrl.toDisplayString(); + << contentUri.toDisplayString(); }); }); } @@ -162,7 +162,7 @@ bool User::setAvatar(QIODevice* source) void User::removeAvatar() { - connection()->callApi(id(), ""); + connection()->callApi(id(), QUrl()); } void User::requestDirectChat() { connection()->requestDirectChat(this); } -- cgit v1.2.3 From 8093ea85347588431c0ddbf6e178a4db5f51b909 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 1 Aug 2021 17:19:23 +0200 Subject: Enhanced logging for read receipts --- lib/room.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 649b1ed2..e7d4e137 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -617,7 +617,8 @@ void Room::Private::setLastReadReceipt(User* u, rev_iter_t newMarker, return; // For Release builds } if (q->memberJoinState(u) != JoinState::Join) { - qCWarning(MAIN) << "Won't record read receipt for non-member" << u->id(); + qCWarning(EPHEMERAL) + << "Won't record read receipt for non-member" << u->id(); return; } @@ -626,8 +627,11 @@ void Room::Private::setLastReadReceipt(User* u, rev_iter_t newMarker, newMarker = q->findInTimeline(newReceipt.eventId); if (newMarker != timeline.crend()) { // NB: with reverse iterators, timeline history >= sync edge - if (newMarker >= q->findInTimeline(storedReceipt.eventId)) + if (newMarker >= q->findInTimeline(storedReceipt.eventId)) { + qCDebug(EPHEMERAL) << "The new read receipt for" << u->id() + << "is at or behind the old one, skipping"; return; + } // Try to auto-promote the read marker over the user's own messages // (switch to direct iterators for that). @@ -651,6 +655,8 @@ void Room::Private::setLastReadReceipt(User* u, rev_iter_t newMarker, eventIdReadUsers.remove(storedReceipt.eventId); eventIdReadUsers[newReceipt.eventId].insert(u); swap(storedReceipt, newReceipt); // Now newReceipt actually stores the old receipt + qCDebug(EPHEMERAL) << "The new read receipt for" << u->id() << "is at" + << storedReceipt.eventId; emit q->lastReadEventChanged(u); // TODO: remove in 0.8 if (!isLocalUser(u)) -- cgit v1.2.3 From c79dcb673b6f2888faa4b62cd53689fde9f7ea10 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 1 Aug 2021 17:22:42 +0200 Subject: Room::toJson(): save the last local user's read receipt Read receipts are entangled with counting unread messages, and saving them also helps in not sending receipts for too old events. Other users' read receipts are still treated as truly ephemeral. --- lib/room.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/room.cpp b/lib/room.cpp index e7d4e137..1c84ece0 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -3003,6 +3003,32 @@ QJsonObject Room::Private::toJson() const { QStringLiteral("events"), accountDataEvents } }); } + if (const auto& readReceiptEventId = + lastReadReceipts.value(q->localUser()).eventId; + !readReceiptEventId.isEmpty()) // + { + // Okay, that's a mouthful; but basically, it's simply placing an m.read + // event in the 'ephemeral' section of the cached sync payload. + // See also receiptevent.* and m.read example in the spec. + // Only the local user's read receipt is saved - others' are really + // considered ephemeral but this one is useful in understanding where + // the user is in the timeline before any history is loaded. + result.insert( + QStringLiteral("ephemeral"), + QJsonObject { + { QStringLiteral("events"), + QJsonArray { QJsonObject { + { TypeKey, ReceiptEvent::matrixTypeId() }, + { ContentKey, + QJsonObject { + { readReceiptEventId, + QJsonObject { + { QStringLiteral("m.read"), + QJsonObject { + { connection->userId(), + QJsonObject {} } } } } } } } } } } }); + } + QJsonObject unreadNotifObj { { SyncRoomData::UnreadCountKey, unreadMessages } }; -- cgit v1.2.3 From 4f08c88d234119c2a76874ebd2b1433b81992427 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 1 Aug 2021 17:47:56 +0200 Subject: Room::setDisplayed(): Don't reset unread counters Closes #489. --- lib/room.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 1c84ece0..8462902f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -924,11 +924,8 @@ void Room::setDisplayed(bool displayed) d->displayed = displayed; emit displayedChanged(displayed); - if (displayed) { - resetHighlightCount(); - resetNotificationCount(); + if (displayed) d->getAllMembers(); - } } QString Room::firstDisplayedEventId() const { return d->firstDisplayedEventId; } -- cgit v1.2.3 From 5276e4321c6b3348fc5b1d3d16f93131d9676e76 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 24 Jul 2021 21:06:13 +0200 Subject: Room::Private::sync/historyEdge() Move Room::sync/historyEdge() implementation to Room::Private, so that internal logic could use the same readable shortcuts without q-> prefixes, instead of timeline.crend() and timeline.cend() that are much less readable. --- lib/room.cpp | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 47a0ec94..9fe70678 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -101,8 +101,8 @@ public: UnorderedMap baseState; /// State event stubs - events without content, just type and state key static decltype(baseState) stubbedState; - /// The state of the room at timeline position after-maxTimelineIndex() - /// \sa Room::syncEdge + /// The state of the room at syncEdge() + /// \sa syncEdge QHash currentState; /// Servers with aliases for this room except the one of the local user /// \sa Room::remoteAliases @@ -198,6 +198,8 @@ public: /// A point in the timeline corresponding to baseState rev_iter_t timelineBase() const { return q->findInTimeline(-1); } + rev_iter_t historyEdge() const { return timeline.crend(); } + Timeline::const_iterator syncEdge() const { return timeline.cend(); } void getPreviousContent(int limit = 10, const QString &filter = {}); @@ -638,7 +640,7 @@ void Room::Private::updateUnreadCount(const rev_iter_t& from, // unreadMessages and might need to promote the read marker further // over local-origin messages. auto readMarker = q->readMarker(); - if (readMarker == timeline.crend() && q->allHistoryLoaded()) + if (readMarker == historyEdge() && q->allHistoryLoaded()) --readMarker; // Read marker not found in the timeline, initialise it if (readMarker >= from && readMarker < to) { promoteReadMarker(q->localUser(), readMarker, true); @@ -682,7 +684,7 @@ Room::Changes Room::Private::promoteReadMarker(User* u, // iterators return Change::NoChange; - Q_ASSERT(newMarker < timeline.crend()); + Q_ASSERT(newMarker < historyEdge()); // Try to auto-promote the read marker over the user's own messages // (switch to direct iterators for that). @@ -697,7 +699,7 @@ Room::Changes Room::Private::promoteReadMarker(User* u, QElapsedTimer et; et.start(); unreadMessages = - int(count_if(eagerMarker, timeline.cend(), + int(count_if(eagerMarker, syncEdge(), [this](const auto& ti) { return isEventNotable(ti); })); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Recounting unread messages took" << et; @@ -771,12 +773,9 @@ bool Room::hasUnreadMessages() const { return unreadCount() >= 0; } int Room::unreadCount() const { return d->unreadMessages; } -Room::rev_iter_t Room::historyEdge() const { return d->timeline.crend(); } +Room::rev_iter_t Room::historyEdge() const { return d->historyEdge(); } -Room::Timeline::const_iterator Room::syncEdge() const -{ - return d->timeline.cend(); -} +Room::Timeline::const_iterator Room::syncEdge() const { return d->syncEdge(); } TimelineItem::index_t Room::minTimelineIndex() const { @@ -855,7 +854,7 @@ void Room::Private::getAllMembers() // the full members list was requested. if (!timeline.empty()) for (auto it = q->findInTimeline(nextIndex).base(); - it != timeline.cend(); ++it) + it != syncEdge(); ++it) if (is(**it)) roomChanges |= q->processStateEvent(**it); if (roomChanges & MembersChange) @@ -2357,7 +2356,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) emit q->aboutToAddNewMessages(eventsSpan); auto insertedSize = moveEventsToTimeline(eventsSpan, Newer); totalInserted += insertedSize; - auto firstInserted = timeline.cend() - insertedSize; + auto firstInserted = syncEdge() - insertedSize; q->onAddNewTimelineEvents(firstInserted); emit q->addedMessages(firstInserted->index(), timeline.back().index()); @@ -2387,20 +2386,20 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) unsyncedEvents.erase(unsyncedEvents.begin() + pendingEvtIdx); if (auto insertedSize = moveEventsToTimeline({ remoteEcho, it }, Newer)) { totalInserted += insertedSize; - q->onAddNewTimelineEvents(timeline.cend() - insertedSize); + q->onAddNewTimelineEvents(syncEdge() - insertedSize); } emit q->pendingEventMerged(); } // Events merged and transferred from `events` to `timeline` now. - const auto from = timeline.cend() - totalInserted; + const auto from = syncEdge() - totalInserted; if (q->supportsCalls()) - for (auto it = from; it != timeline.cend(); ++it) + for (auto it = from; it != syncEdge(); ++it) if (const auto* evt = it->viewAs()) emit q->callEvent(q, evt); if (totalInserted > 0) { - for (auto it = from; it != timeline.cend(); ++it) { + for (auto it = from; it != syncEdge(); ++it) { if (const auto* reaction = it->viewAs()) { const auto& relation = reaction->relation(); relations[{ relation.eventId, relation.type }] << reaction; @@ -2420,7 +2419,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // the new message events. if (const auto senderId = (*from)->senderId(); !senderId.isEmpty()) { auto* const firstWriter = q->user(senderId); - if (q->readMarker(firstWriter) != timeline.crend()) { + if (q->readMarker(firstWriter) != historyEdge()) { roomChanges |= promoteReadMarker(firstWriter, rev_iter_t(from) - 1); qCDebug(MESSAGES) @@ -2461,14 +2460,14 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) emit q->aboutToAddHistoricalMessages(events); const auto insertedSize = moveEventsToTimeline(events, Older); - const auto from = timeline.crend() - insertedSize; + const auto from = historyEdge() - insertedSize; qCDebug(STATE) << "Room" << displayname << "received" << insertedSize << "past events; the oldest event is now" << timeline.front(); q->onAddHistoricalTimelineEvents(from); emit q->addedMessages(timeline.front().index(), from->index()); - for (auto it = from; it != timeline.crend(); ++it) { + for (auto it = from; it != historyEdge(); ++it) { if (const auto* reaction = it->viewAs()) { const auto& relation = reaction->relation(); relations[{ relation.eventId, relation.type }] << reaction; @@ -2476,7 +2475,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) } } if (from <= q->readMarker()) - updateUnreadCount(from, timeline.crend()); + updateUnreadCount(from, historyEdge()); Q_ASSERT(timeline.size() == timelineSize + insertedSize); if (insertedSize > 9 || et.nsecsElapsed() >= profilerMinNsecs()) -- cgit v1.2.3 From f46e19924a1c0a5ac3f1d2a489f8017a5e143fda Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 10 Aug 2021 15:23:08 +0200 Subject: Turn EventStatus from a class to a namespace This wrapper only exists for an enum inside of it and dates back to times when Qt meta-object system did not support free-standing enums. --- lib/eventitem.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/eventitem.h b/lib/eventitem.h index 1986ba77..a5f381f0 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -11,9 +11,9 @@ namespace Quotient { class StateEventBase; -class EventStatus { - Q_GADGET -public: +namespace EventStatus { + Q_NAMESPACE + /** Special marks an event can assume * * This is used to hint at a special status of some events in UI. @@ -32,8 +32,8 @@ public: Hidden = 0x100, //< The event should not be shown in the timeline }; Q_DECLARE_FLAGS(Status, Code) - Q_FLAG(Status) -}; + Q_FLAG_NS(Status) +} // namespace EventStatus class EventItemBase { public: @@ -148,4 +148,4 @@ inline QDebug& operator<<(QDebug& d, const TimelineItem& ti) return d; } } // namespace Quotient -Q_DECLARE_METATYPE(Quotient::EventStatus) +//Q_DECLARE_METATYPE(Quotient::EventStatus) -- cgit v1.2.3 From 14d896c9d659d3d3c4f9c4d6eeb50e59a2a002a9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 21 Aug 2021 11:40:45 +0200 Subject: Cleanup --- lib/eventitem.h | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/eventitem.h b/lib/eventitem.h index a5f381f0..a70a3c3e 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -148,4 +148,3 @@ inline QDebug& operator<<(QDebug& d, const TimelineItem& ti) return d; } } // namespace Quotient -//Q_DECLARE_METATYPE(Quotient::EventStatus) -- cgit v1.2.3 From 71384a49c3a053e715241172d9d9893bb1742e6b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 19:37:09 +0200 Subject: Mustache: avoid BaseJob::Data It's about to be deprecated in the next commits. --- gtad/operation.cpp.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtad/operation.cpp.mustache b/gtad/operation.cpp.mustache index f34c9280..7f692e4a 100644 --- a/gtad/operation.cpp.mustache +++ b/gtad/operation.cpp.mustache @@ -36,7 +36,7 @@ QUrl {{camelCaseOperationId}}Job::makeRequestUrl(QUrl baseUrl{{#allParams?}}, { {{#headerParams}} setRequestHeader("{{baseName}}", {{paramName}}.toLatin1()); {{/headerParams}}{{#inlineBody}}{{^propertyMap}}{{^bodyParams?}} - setRequestData(Data({{#consumesNonJson?}}{{nameCamelCase}}{{/consumesNonJson? + setRequestData(RequestData({{#consumesNonJson?}}{{nameCamelCase}}{{/consumesNonJson? }}{{^consumesNonJson?}}toJson({{nameCamelCase}}){{/consumesNonJson?}})); {{/bodyParams?}}{{/propertyMap}}{{/inlineBody }}{{^consumesNonJson?}}{{#bodyParams?}} -- cgit v1.2.3 From ed24065f2e9b8fce059c54137c04b790c6ce4fd1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:05:41 +0200 Subject: Regenerate API files --- lib/csapi/account-data.cpp | 4 ++-- lib/csapi/administrative_contact.cpp | 4 ++-- lib/csapi/content-repo.cpp | 2 +- lib/csapi/cross_signing.cpp | 2 +- lib/csapi/filter.cpp | 2 +- lib/csapi/openid.cpp | 2 +- lib/csapi/receipts.cpp | 2 +- lib/csapi/registration.cpp | 8 ++++---- lib/csapi/room_send.cpp | 2 +- lib/csapi/room_state.cpp | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/csapi/account-data.cpp b/lib/csapi/account-data.cpp index 6a40e908..80deb8f1 100644 --- a/lib/csapi/account-data.cpp +++ b/lib/csapi/account-data.cpp @@ -14,7 +14,7 @@ SetAccountDataJob::SetAccountDataJob(const QString& userId, const QString& type, QStringLiteral("/_matrix/client/r0") % "/user/" % userId % "/account_data/" % type) { - setRequestData(Data(toJson(content))); + setRequestData(RequestData(toJson(content))); } QUrl GetAccountDataJob::makeRequestUrl(QUrl baseUrl, const QString& userId, @@ -39,7 +39,7 @@ SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, QStringLiteral("/_matrix/client/r0") % "/user/" % userId % "/rooms/" % roomId % "/account_data/" % type) { - setRequestData(Data(toJson(content))); + setRequestData(RequestData(toJson(content))); } QUrl GetAccountDataPerRoomJob::makeRequestUrl(QUrl baseUrl, diff --git a/lib/csapi/administrative_contact.cpp b/lib/csapi/administrative_contact.cpp index fa4f475a..04360299 100644 --- a/lib/csapi/administrative_contact.cpp +++ b/lib/csapi/administrative_contact.cpp @@ -89,7 +89,7 @@ RequestTokenTo3PIDEmailJob::RequestTokenTo3PIDEmailJob( % "/account/3pid/email/requestToken", false) { - setRequestData(Data(toJson(body))); + setRequestData(RequestData(toJson(body))); } RequestTokenTo3PIDMSISDNJob::RequestTokenTo3PIDMSISDNJob( @@ -99,5 +99,5 @@ RequestTokenTo3PIDMSISDNJob::RequestTokenTo3PIDMSISDNJob( % "/account/3pid/msisdn/requestToken", false) { - setRequestData(Data(toJson(body))); + setRequestData(RequestData(toJson(body))); } diff --git a/lib/csapi/content-repo.cpp b/lib/csapi/content-repo.cpp index 00bc9706..2d82437b 100644 --- a/lib/csapi/content-repo.cpp +++ b/lib/csapi/content-repo.cpp @@ -22,7 +22,7 @@ UploadContentJob::UploadContentJob(QIODevice* content, const QString& filename, queryToUploadContent(filename)) { setRequestHeader("Content-Type", contentType.toLatin1()); - setRequestData(Data(content)); + setRequestData(RequestData(content)); addExpectedKey("content_uri"); } diff --git a/lib/csapi/cross_signing.cpp b/lib/csapi/cross_signing.cpp index 9bfc026a..ed2b15c0 100644 --- a/lib/csapi/cross_signing.cpp +++ b/lib/csapi/cross_signing.cpp @@ -30,5 +30,5 @@ UploadCrossSigningSignaturesJob::UploadCrossSigningSignaturesJob( : BaseJob(HttpVerb::Post, QStringLiteral("UploadCrossSigningSignaturesJob"), QStringLiteral("/_matrix/client/r0") % "/keys/signatures/upload") { - setRequestData(Data(toJson(signatures))); + setRequestData(RequestData(toJson(signatures))); } diff --git a/lib/csapi/filter.cpp b/lib/csapi/filter.cpp index bb3a893f..6b8863cc 100644 --- a/lib/csapi/filter.cpp +++ b/lib/csapi/filter.cpp @@ -13,7 +13,7 @@ DefineFilterJob::DefineFilterJob(const QString& userId, const Filter& filter) QStringLiteral("/_matrix/client/r0") % "/user/" % userId % "/filter") { - setRequestData(Data(toJson(filter))); + setRequestData(RequestData(toJson(filter))); addExpectedKey("filter_id"); } diff --git a/lib/csapi/openid.cpp b/lib/csapi/openid.cpp index 3941e9c0..0447db79 100644 --- a/lib/csapi/openid.cpp +++ b/lib/csapi/openid.cpp @@ -14,5 +14,5 @@ RequestOpenIdTokenJob::RequestOpenIdTokenJob(const QString& userId, QStringLiteral("/_matrix/client/r0") % "/user/" % userId % "/openid/request_token") { - setRequestData(Data(toJson(body))); + setRequestData(RequestData(toJson(body))); } diff --git a/lib/csapi/receipts.cpp b/lib/csapi/receipts.cpp index 00d1c28a..47b18174 100644 --- a/lib/csapi/receipts.cpp +++ b/lib/csapi/receipts.cpp @@ -15,5 +15,5 @@ PostReceiptJob::PostReceiptJob(const QString& roomId, const QString& receiptType QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/receipt/" % receiptType % "/" % eventId) { - setRequestData(Data(toJson(receipt))); + setRequestData(RequestData(toJson(receipt))); } diff --git a/lib/csapi/registration.cpp b/lib/csapi/registration.cpp index 38649e63..c3617bfc 100644 --- a/lib/csapi/registration.cpp +++ b/lib/csapi/registration.cpp @@ -44,7 +44,7 @@ RequestTokenToRegisterEmailJob::RequestTokenToRegisterEmailJob( % "/register/email/requestToken", false) { - setRequestData(Data(toJson(body))); + setRequestData(RequestData(toJson(body))); } RequestTokenToRegisterMSISDNJob::RequestTokenToRegisterMSISDNJob( @@ -54,7 +54,7 @@ RequestTokenToRegisterMSISDNJob::RequestTokenToRegisterMSISDNJob( % "/register/msisdn/requestToken", false) { - setRequestData(Data(toJson(body))); + setRequestData(RequestData(toJson(body))); } ChangePasswordJob::ChangePasswordJob(const QString& newPassword, @@ -78,7 +78,7 @@ RequestTokenToResetPasswordEmailJob::RequestTokenToResetPasswordEmailJob( % "/account/password/email/requestToken", false) { - setRequestData(Data(toJson(body))); + setRequestData(RequestData(toJson(body))); } RequestTokenToResetPasswordMSISDNJob::RequestTokenToResetPasswordMSISDNJob( @@ -89,7 +89,7 @@ RequestTokenToResetPasswordMSISDNJob::RequestTokenToResetPasswordMSISDNJob( % "/account/password/msisdn/requestToken", false) { - setRequestData(Data(toJson(body))); + setRequestData(RequestData(toJson(body))); } DeactivateAccountJob::DeactivateAccountJob( diff --git a/lib/csapi/room_send.cpp b/lib/csapi/room_send.cpp index 63986c56..9fd8cb96 100644 --- a/lib/csapi/room_send.cpp +++ b/lib/csapi/room_send.cpp @@ -14,6 +14,6 @@ SendMessageJob::SendMessageJob(const QString& roomId, const QString& eventType, QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/send/" % eventType % "/" % txnId) { - setRequestData(Data(toJson(body))); + setRequestData(RequestData(toJson(body))); addExpectedKey("event_id"); } diff --git a/lib/csapi/room_state.cpp b/lib/csapi/room_state.cpp index e18108ac..37e897fa 100644 --- a/lib/csapi/room_state.cpp +++ b/lib/csapi/room_state.cpp @@ -16,6 +16,6 @@ SetRoomStateWithKeyJob::SetRoomStateWithKeyJob(const QString& roomId, QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/state/" % eventType % "/" % stateKey) { - setRequestData(Data(toJson(body))); + setRequestData(RequestData(toJson(body))); addExpectedKey("event_id"); } -- cgit v1.2.3 From 40548a7995147f3f99212928ae27047de7a79618 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:10:49 +0200 Subject: Deprecate BaseJob::Data The grand plan is to get rid of `BaseJob` and turn job invocations to function calls returning `QFuture`. `RequestData` will stay though, feeding data into those calls. --- lib/jobs/basejob.cpp | 17 ++++++++++------- lib/jobs/basejob.h | 10 ++++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 400a9243..239cef28 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -71,8 +71,8 @@ public: // Using an idiom from clang-tidy: // http://clang.llvm.org/extra/clang-tidy/checks/modernize-pass-by-value.html - Private(HttpVerb v, QString endpoint, const QUrlQuery& q, Data&& data, - bool nt) + Private(HttpVerb v, QString endpoint, const QUrlQuery& q, + RequestData&& data, bool nt) : verb(v) , apiEndpoint(std::move(endpoint)) , requestQuery(q) @@ -109,7 +109,7 @@ public: QString apiEndpoint; QHash requestHeaders; QUrlQuery requestQuery; - Data requestData; + RequestData requestData; bool needsToken; bool inBackground = false; @@ -168,11 +168,11 @@ public: BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, bool needsToken) - : BaseJob(verb, name, endpoint, QUrlQuery {}, Data {}, needsToken) + : BaseJob(verb, name, endpoint, QUrlQuery {}, RequestData {}, needsToken) {} BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, - const QUrlQuery &query, Data&& data, bool needsToken) + const QUrlQuery& query, RequestData&& data, bool needsToken) : d(new Private(verb, endpoint, query, std::move(data), needsToken)) { setObjectName(name); @@ -224,9 +224,12 @@ void BaseJob::setRequestQuery(const QUrlQuery& query) d->requestQuery = query; } -const BaseJob::Data& BaseJob::requestData() const { return d->requestData; } +const RequestData& BaseJob::requestData() const { return d->requestData; } -void BaseJob::setRequestData(Data&& data) { std::swap(d->requestData, data); } +void BaseJob::setRequestData(RequestData&& data) +{ + std::swap(d->requestData, data); +} const QByteArrayList& BaseJob::expectedContentTypes() const { diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 7ce4b808..7750fb8b 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -72,7 +72,8 @@ public: }; Q_ENUM(StatusCode) - using Data = RequestData; + using Data Q_DECL_DEPRECATED_X("Use Quotient::RequestData instead") // + = RequestData; /*! * This structure stores the status of a server call job. The status @@ -125,7 +126,8 @@ public: BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, bool needsToken = true); BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, - const QUrlQuery& query, Data&& data = {}, bool needsToken = true); + const QUrlQuery& query, RequestData&& data = {}, + bool needsToken = true); QUrl requestUrl() const; bool isBackground() const; @@ -330,8 +332,8 @@ protected: void setRequestHeaders(const headers_t& headers); const QUrlQuery& query() const; void setRequestQuery(const QUrlQuery& query); - const Data& requestData() const; - void setRequestData(Data&& data); + const RequestData& requestData() const; + void setRequestData(RequestData&& data); const QByteArrayList& expectedContentTypes() const; void addExpectedContentType(const QByteArray& contentType); void setExpectedContentTypes(const QByteArrayList& contentTypes); -- cgit v1.2.3 From c50420a0f2df7a7bf291312c38ac43e2c9f58141 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:15:04 +0200 Subject: Drop QMatrixClient namespace alias --- lib/avatar.h | 2 -- lib/jobs/requestdata.h | 2 -- lib/logging.h | 2 -- 3 files changed, 6 deletions(-) diff --git a/lib/avatar.h b/lib/avatar.h index 37e1eeef..d4634aea 100644 --- a/lib/avatar.h +++ b/lib/avatar.h @@ -42,5 +42,3 @@ private: std::unique_ptr d; }; } // namespace Quotient -/// \deprecated Use namespace Quotient instead -namespace QMatrixClient = Quotient; diff --git a/lib/jobs/requestdata.h b/lib/jobs/requestdata.h index 21657631..4f05e5ff 100644 --- a/lib/jobs/requestdata.h +++ b/lib/jobs/requestdata.h @@ -35,5 +35,3 @@ private: std::unique_ptr _source; }; } // namespace Quotient -/// \deprecated Use namespace Quotient instead -namespace QMatrixClient = Quotient; diff --git a/lib/logging.h b/lib/logging.h index 264215e1..1d1394e8 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -67,8 +67,6 @@ inline qint64 profilerMinNsecs() * 1000; } } // namespace Quotient -/// \deprecated Use namespace Quotient instead -namespace QMatrixClient = Quotient; inline QDebug operator<<(QDebug debug_object, const QElapsedTimer& et) { -- cgit v1.2.3 From c26015503aa0fbca37abdfc4870ac94bb7befeee Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:19:15 +0200 Subject: Drop other stuff deprecated pre- or early 0.6 BaseJob: StatusCode::JsonParseError Connection: resolved() and reconnected() signals; roomMap(); postReceipt() User: bridged() and rawName() ConnectionData: setHost() and setPort() StateEventBase: prev_content() --- lib/connection.cpp | 21 --------------------- lib/connection.h | 45 --------------------------------------------- lib/connectiondata.cpp | 12 ------------ lib/connectiondata.h | 4 ---- lib/events/stateevent.h | 4 ---- lib/jobs/basejob.h | 2 -- lib/user.cpp | 4 ---- lib/user.h | 18 +----------------- 8 files changed, 1 insertion(+), 109 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 7dd04aaa..222c3b71 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -314,8 +314,6 @@ void Connection::resolveServer(const QString& mxid) setHomeserver(maybeBaseUrl); } Q_ASSERT(d->loginFlowsJob != nullptr); // Ensured by setHomeserver() - connect(d->loginFlowsJob, &BaseJob::success, this, - &Connection::resolved); connect(d->loginFlowsJob, &BaseJob::failure, this, [this] { qCWarning(MAIN) << "Homeserver base URL sanity check failed"; emit resolveError(tr("The homeserver doesn't seem to be working")); @@ -795,11 +793,6 @@ void Connection::stopSync() QString Connection::nextBatchToken() const { return d->data->lastEvent(); } -PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) -{ - return callApi(room->id(), "m.read", event->id()); -} - JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { @@ -1239,20 +1232,6 @@ int Connection::millisToReconnect() const return d->syncJob ? d->syncJob->millisToRetry() : 0; } -QHash, Room*> Connection::roomMap() const -{ - // Copy-on-write-and-remove-elements is faster than copying elements one by - // one. - QHash, Room*> roomMap = d->roomMap; - for (auto it = roomMap.begin(); it != roomMap.end();) { - if (it.value()->joinState() == JoinState::Leave) - it = roomMap.erase(it); - else - ++it; - } - return roomMap; -} - QVector Connection::allRooms() const { QVector result; diff --git a/lib/connection.h b/lib/connection.h index a7a071f3..ecbb1a19 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -138,15 +138,6 @@ public: explicit Connection(const QUrl& server, QObject* parent = nullptr); ~Connection() override; - /// Get all Invited and Joined rooms - /*! - * \return a hashmap from a composite key - room name and whether - * it's an Invite rather than Join - to room pointers - * \sa allRooms, rooms, roomsWithTag - */ - [[deprecated("Use allRooms(), roomsWithTag() or rooms(joinStates) instead")]] - QHash, Room*> roomMap() const; - /// Get all rooms known within this Connection /*! * This includes Invite, Join and Leave rooms, in no particular order. @@ -524,30 +515,12 @@ public Q_SLOTS: */ void assumeIdentity(const QString& mxId, const QString& accessToken, const QString& deviceId); - /*! \deprecated Use loginWithPassword instead */ - void connectToServer(const QString& userId, const QString& password, - const QString& initialDeviceName, - const QString& deviceId = {}) - { - loginWithPassword(userId, password, initialDeviceName, deviceId); - } - /*! \deprecated - * Use assumeIdentity() if you have an access token or - * loginWithToken() if you have a login token. - */ - void connectWithToken(const QString& userId, const QString& accessToken, - const QString& deviceId) - { - assumeIdentity(userId, accessToken, deviceId); - } /// Explicitly request capabilities from the server void reloadCapabilities(); /// Find out if capabilites are still loading from the server bool loadingCapabilities() const; - /** @deprecated Use stopSync() instead */ - void disconnectFromServer() { stopSync(); } void logout(); void sync(int timeout = -1); @@ -662,24 +635,7 @@ public Q_SLOTS: /** \deprecated Do not use this directly, use Room::leaveRoom() instead */ virtual LeaveRoomJob* leaveRoom(Room* room); - // Old API that will be abolished any time soon. DO NOT USE. - - /** @deprecated Use callApi() or Room::postReceipt() instead - */ - virtual PostReceiptJob* postReceipt(Room* room, RoomEvent* event); - Q_SIGNALS: - /** - * @deprecated - * This was a signal resulting from a successful resolveServer(). - * Since Connection now provides setHomeserver(), the HS URL - * may change even without resolveServer() invocation. Use - * loginFLowsChanged() instead of resolved(). You can also use - * loginWith*() and assumeIdentity() without the HS URL set in - * advance (i.e. without calling resolveServer), as they trigger - * server name resolution from MXID if the server URL is not valid. - */ - void resolved(); void resolveError(QString error); void homeserverChanged(QUrl baseUrl); @@ -687,7 +643,6 @@ Q_SIGNALS: void capabilitiesLoaded(); void connected(); - void reconnected(); //< \deprecated Use connected() instead void loggedOut(); /** Login data or state have changed * diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index e54d909b..87ad4577 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.cpp @@ -118,18 +118,6 @@ void ConnectionData::setToken(QByteArray token) d->accessToken = std::move(token); } -void ConnectionData::setHost(QString host) -{ - d->baseUrl.setHost(host); - qCDebug(MAIN) << "updated baseUrl to" << d->baseUrl; -} - -void ConnectionData::setPort(int port) -{ - d->baseUrl.setPort(port); - qCDebug(MAIN) << "updated baseUrl to" << d->baseUrl; -} - const QString& ConnectionData::deviceId() const { return d->deviceId; } const QString& ConnectionData::userId() const { return d->userId; } diff --git a/lib/connectiondata.h b/lib/connectiondata.h index 7dd96f26..e16a2dac 100644 --- a/lib/connectiondata.h +++ b/lib/connectiondata.h @@ -31,10 +31,6 @@ public: void setBaseUrl(QUrl baseUrl); void setToken(QByteArray accessToken); - [[deprecated("Use setBaseUrl() instead")]] - void setHost(QString host); - [[deprecated("Use setBaseUrl() instead")]] - void setPort(int port); void setDeviceId(const QString& deviceId); void setUserId(const QString& userId); void setNeedsToken(const QString& requestName); diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 1415f709..bc414a5f 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -100,10 +100,6 @@ public: visitor(_content); editJson()[ContentKeyL] = _content.toJson(); } - [[deprecated("Use prevContent instead")]] const ContentT* prev_content() const - { - return prevContent(); - } const ContentT* prevContent() const { return _prev ? &_prev->content : nullptr; diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 7750fb8b..835cd822 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -52,8 +52,6 @@ public: IncorrectRequestError = IncorrectRequest, IncorrectResponse, IncorrectResponseError = IncorrectResponse, - JsonParseError //< \deprecated Use IncorrectResponse instead - = IncorrectResponse, TooManyRequests, TooManyRequestsError = TooManyRequests, RateLimited = TooManyRequests, diff --git a/lib/user.cpp b/lib/user.cpp index a4abed37..60920a59 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -93,8 +93,6 @@ QString User::name(const Room* room) const return room ? room->memberName(id()) : d->defaultName; } -QString User::rawName(const Room* room) const { return name(room); } - void User::rename(const QString& newName) { const auto actualNewName = sanitized(newName); @@ -185,8 +183,6 @@ QString User::fullName(const Room* room) const return displayName.isEmpty() ? id() : (displayName % " (" % id() % ')'); } -QString User::bridged() const { return {}; } - const Avatar& User::avatarObject(const Room* room) const { if (!room) diff --git a/lib/user.h b/lib/user.h index 4ff62951..78b72bf2 100644 --- a/lib/user.h +++ b/lib/user.h @@ -22,7 +22,6 @@ class User : public QObject { Q_PROPERTY(QString name READ name NOTIFY defaultNameChanged) Q_PROPERTY(QString displayName READ displayname NOTIFY defaultNameChanged STORED false) Q_PROPERTY(QString fullName READ fullName NOTIFY defaultNameChanged STORED false) - Q_PROPERTY(QString bridgeName READ bridged NOTIFY defaultNameChanged STORED false) Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY defaultAvatarChanged STORED false) Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY defaultAvatarChanged) public: @@ -40,18 +39,10 @@ public: * This may be empty if the user didn't choose the name or cleared * it. If the user is bridged, the bridge postfix (such as '(IRC)') * is stripped out. No disambiguation for the room is done. - * \sa displayName, rawName + * \sa displayName */ QString name(const Room* room = nullptr) const; - /** Get the user name along with the bridge postfix - * This function is similar to name() but appends the bridge postfix - * (such as '(IRC)') to the user name. No disambiguation is done. - * \sa name, displayName - */ - [[deprecated("Bridge postfixes exist no more, use name() instead")]] - QString rawName(const Room* room = nullptr) const; - /** Get the displayed user name * When \p room is null, this method returns result of name() if * the name is non-empty; otherwise it returns user id. @@ -70,13 +61,6 @@ public: */ QString fullName(const Room* room = nullptr) const; - /** - * Returns the name of bridge the user is connected from or empty. - */ - [[deprecated("Bridged status is no more supported; this always returns" - " an empty string")]] - QString bridged() const; - /** Whether the user is a guest * As of now, the function relies on the convention used in Synapse * that guests and only guests have all-numeric IDs. This may or -- cgit v1.2.3 From 776feaa5eeaffe78773aec9a4431fef147fa95c4 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:21:06 +0200 Subject: Use Q_DECL_DEPRECATED_X instead of \deprecated doc-comment This still works with older moc yet produces actual warnings when compiling C++ code. --- lib/room.cpp | 4 ++-- lib/room.h | 22 +++++++--------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 9fe70678..398b3ec8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1236,7 +1236,7 @@ QList Room::membersLeft() const { return d->membersLeft; } QList Room::users() const { return d->membersMap.values(); } -[[deprecated]] QStringList Room::memberNames() const +QStringList Room::memberNames() const { return safeMemberNames(); } @@ -1507,7 +1507,7 @@ QString Room::disambiguatedMemberName(const QString& mxId) const QString Room::safeMemberName(const QString& userId) const { - return sanitized(roomMembername(userId)); + return sanitized(disambiguatedMemberName(userId)); } QString Room::htmlSafeMemberName(const QString& userId) const diff --git a/lib/room.h b/lib/room.h index 52ba2eab..de0c7504 100644 --- a/lib/room.h +++ b/lib/room.h @@ -192,7 +192,7 @@ public: QList membersLeft() const; Q_INVOKABLE QList users() const; - [[deprecated("Use safeMemberNames() or htmlSafeMemberNames() instead")]] + Q_DECL_DEPRECATED_X("Use safeMemberNames() or htmlSafeMemberNames() instead") // QStringList memberNames() const; QStringList safeMemberNames() const; QStringList htmlSafeMemberNames() const; @@ -235,13 +235,12 @@ public: /** * \brief Check the join state of a given user in this room * - * \deprecated Use memberState and check against a mask - * * \note Banned and invited users are not tracked separately for now (Leave * will be returned for them). * * \return Join if the user is a room member; Leave otherwise */ + Q_DECL_DEPRECATED_X("Use memberState and check against the mask") // Q_INVOKABLE Quotient::JoinState memberJoinState(Quotient::User* user) const; //! \brief Check the join state of a given user in this room @@ -254,18 +253,11 @@ public: //! \sa safeMemberName, htmlSafeMemberName Q_INVOKABLE QString memberName(const QString& mxId) const; - /*! - * \brief Get a disambiguated name for the given user in the room context - * - * \deprecated use safeMemberName() instead - */ + //! \brief Get a disambiguated name for the given user in the room context + Q_DECL_DEPRECATED_X("Use safeMemberName() instead") Q_INVOKABLE QString roomMembername(const Quotient::User* u) const; - /*! - * \brief Get a disambiguated name for a user with this id in the room - * context - * - * \deprecated use safeMemberName() instead - */ + //! \brief Get a disambiguated name for a user with this id in the room + Q_DECL_DEPRECATED_X("Use safeMemberName() instead") Q_INVOKABLE QString roomMembername(const QString& userId) const; /*! @@ -552,7 +544,7 @@ public Q_SLOTS: QString postFile(const QString& plainText, EventContent::TypedBase* content); #if QT_VERSION_MAJOR < 6 - /// \deprecated Use postFile(QString, MessageEventType, EventContent) instead + Q_DECL_DEPRECATED_X("Use postFile(QString, MessageEventType, EventContent)") // QString postFile(const QString& plainText, const QUrl& localPath, bool asGenericFile = false); #endif -- cgit v1.2.3 From 395f3758e810fed6cd276a356fd5f0e955c5477e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:22:36 +0200 Subject: Settings: no more setToken() and accessToken(); deprecate clearAccessToken() Access tokens should be stored with Qt Keychain that's about to come; and these methods were deprecated since before 0.5. --- lib/settings.cpp | 13 ------------- lib/settings.h | 8 +------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/lib/settings.cpp b/lib/settings.cpp index 1d36db27..ed9082b0 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -126,19 +126,6 @@ void AccountSettings::setHomeserver(const QUrl& url) QString AccountSettings::userId() const { return group().section('/', -1); } -QString AccountSettings::accessToken() const -{ - return value(AccessTokenKey).toString(); -} - -void AccountSettings::setAccessToken(const QString& accessToken) -{ - qCWarning(MAIN) << "Saving access_token to QSettings is insecure." - " Developers, do it manually or contribute to share " - "QtKeychain logic to libQuotient."; - setValue(AccessTokenKey, accessToken); -} - void AccountSettings::clearAccessToken() { legacySettings.remove(AccessTokenKey); diff --git a/lib/settings.h b/lib/settings.h index 84c54802..3f4dc123 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -131,8 +131,6 @@ class AccountSettings : public SettingsGroup { QTNT_DECLARE_SETTING(QString, deviceId, setDeviceId) QTNT_DECLARE_SETTING(QString, deviceName, setDeviceName) QTNT_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn) - /** \deprecated \sa setAccessToken */ - Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken) Q_PROPERTY(QByteArray encryptionAccountPickle READ encryptionAccountPickle WRITE setEncryptionAccountPickle) public: @@ -147,11 +145,7 @@ public: QUrl homeserver() const; void setHomeserver(const QUrl& url); - /** \deprecated \sa setToken */ - QString accessToken() const; - /** \deprecated Storing accessToken in QSettings is unsafe, - * see quotient-im/Quaternion#181 */ - void setAccessToken(const QString& accessToken); + Q_DECL_DEPRECATED_X("Access tokens are not stored in QSettings any more") Q_INVOKABLE void clearAccessToken(); QByteArray encryptionAccountPickle(); -- cgit v1.2.3 From fd42d1dbd29800ef53ab9997c948f39a92aa8bff Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:23:42 +0200 Subject: RoomEvent: drop timestamp() Use originTimestamp(); the corresponding Q_PROPERTY was not renamed (in error) so it is now. --- lib/events/roomevent.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 3abd56c0..3174764f 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -14,7 +14,9 @@ class RedactionEvent; class RoomEvent : public Event { Q_GADGET Q_PROPERTY(QString id READ id) - Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT) + //! \deprecated Use originTimestamp instead + Q_PROPERTY(QDateTime timestamp READ originTimestamp CONSTANT) + Q_PROPERTY(QDateTime originTimestamp READ originTimestamp CONSTANT) Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString senderId READ senderId CONSTANT) Q_PROPERTY(QString redactionReason READ redactionReason) @@ -32,9 +34,6 @@ public: QString id() const; QDateTime originTimestamp() const; - [[deprecated("Use originTimestamp()")]] QDateTime timestamp() const { - return originTimestamp(); - } QString roomId() const; QString senderId() const; //! \brief Determine whether the event has been replaced -- cgit v1.2.3 From 93cee89f9a99e51275ac9cd304180499b0543eea Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:45:53 +0200 Subject: Fix building with MSVC --- lib/jobs/basejob.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 835cd822..29443c53 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -70,7 +70,10 @@ public: }; Q_ENUM(StatusCode) - using Data Q_DECL_DEPRECATED_X("Use Quotient::RequestData instead") // + using Data +#ifndef Q_CC_MSVC + Q_DECL_DEPRECATED_X("Use Quotient::RequestData instead") +#endif = RequestData; /*! -- cgit v1.2.3 From 3cbc13a33c81a75e18c415bd31cc2156461ffa1f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:58:18 +0200 Subject: User::isGuest(): fix a clazy warning --- lib/user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/user.cpp b/lib/user.cpp index a4abed37..2706b2c0 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -82,7 +82,7 @@ bool User::isGuest() const Q_ASSERT(!d->id.isEmpty() && d->id.startsWith('@')); auto it = std::find_if_not(d->id.cbegin() + 1, d->id.cend(), [](QChar c) { return c.isDigit(); }); - Q_ASSERT(it != d->id.end()); + Q_ASSERT(it != d->id.cend()); return *it == ':'; } -- cgit v1.2.3 From 6953e55361f600a591c08b9cd287a350230b3ef8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 23 Aug 2021 08:42:49 +0200 Subject: Room: isMember(); memberState() only needs user id Room::memberJoinState() was only used to check if the user has joined the room (it couldn't be used for anything else), meaning that its best replacement is actually not memberState() but isMember() introduced hereby. It's also better to pass user ids instead of User objects to memberState() and isMember() since that is enough to check membership. # Conflicts: # lib/room.cpp # lib/room.h --- lib/room.cpp | 29 +++++++++++++++++------------ lib/room.h | 7 +++++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 398b3ec8..45f1af53 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -590,9 +590,14 @@ JoinState Room::memberJoinState(User* user) const : JoinState::Leave; } -Membership Room::memberState(User* user) const +Membership Room::memberState(const QString& userId) const { - return d->getCurrentState(user->id())->membership(); + return d->getCurrentState(userId)->membership(); +} + +bool Room::isMember(const QString& userId) const +{ + return memberState(userId) == Membership::Join; } JoinState Room::joinState() const { return d->joinState; } @@ -2698,11 +2703,10 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) et.start(); if (auto* evt = eventCast(event)) { d->usersTyping.clear(); - for (const auto& userId : evt->users()) { - auto u = user(userId); - if (memberJoinState(u) == JoinState::Join) - d->usersTyping.append(u); - } + for (const auto& userId : evt->users()) + if (isMember(userId)) + d->usersTyping.append(user(userId)); + if (evt->users().size() > 3 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) << "*** Room::processEphemeralEvent(typing):" << evt->users().size() << "users," << et; @@ -2726,9 +2730,9 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) for (const Receipt& r : p.receipts) { if (r.userId == connection()->userId()) continue; // FIXME, #185 - auto u = user(r.userId); - if (memberJoinState(u) == JoinState::Join) - changes |= d->promoteReadMarker(u, newMarker); + if (isMember(r.userId)) + changes |= + d->promoteReadMarker(user(r.userId), newMarker); } } else { qCDebug(EPHEMERAL) << "Event" << p.evtId @@ -2740,9 +2744,10 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) for (const Receipt& r : p.receipts) { if (r.userId == connection()->userId()) continue; // FIXME, #185 + if (!isMember(r.userId)) + continue; auto u = user(r.userId); - if (memberJoinState(u) == JoinState::Join - && readMarker(u) == historyEdge()) + if (readMarker(u) == historyEdge()) changes |= d->setLastReadEvent(u, p.evtId); } } diff --git a/lib/room.h b/lib/room.h index de0c7504..179295d0 100644 --- a/lib/room.h +++ b/lib/room.h @@ -240,13 +240,16 @@ public: * * \return Join if the user is a room member; Leave otherwise */ - Q_DECL_DEPRECATED_X("Use memberState and check against the mask") // + Q_DECL_DEPRECATED_X("Use isMember() instead") Q_INVOKABLE Quotient::JoinState memberJoinState(Quotient::User* user) const; //! \brief Check the join state of a given user in this room //! //! \return the given user's state with respect to the room - Q_INVOKABLE Quotient::Membership memberState(User* user) const; + Q_INVOKABLE Quotient::Membership memberState(const QString& userId) const; + + //! Check whether a user with the given id is a member of the room + Q_INVOKABLE bool isMember(const QString& userId) const; //! \brief Get a display name (without disambiguation) for the given member //! -- cgit v1.2.3 From 8d21c8a6579ad9c63e331ffb42e3ed81b9c73caf Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 24 Aug 2021 23:59:48 +0200 Subject: Add AccountRegistry Basic session management class; Created from Quaternion's AccountRegistry and NeoChat's AccountListModel. The connections can be accessed by the user's id, this technically limits it to one connection for each matrix account. --- CMakeLists.txt | 1 + lib/accountregistry.cpp | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/accountregistry.h | 45 +++++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 lib/accountregistry.cpp create mode 100644 lib/accountregistry.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 49105389..3c6e7548 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,6 +140,7 @@ list(APPEND lib_SRCS lib/util.cpp lib/encryptionmanager.cpp lib/eventitem.cpp + lib/accountregistry.cpp lib/events/event.cpp lib/events/roomevent.cpp lib/events/stateevent.cpp diff --git a/lib/accountregistry.cpp b/lib/accountregistry.cpp new file mode 100644 index 00000000..3a022f14 --- /dev/null +++ b/lib/accountregistry.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Kitsune Ral +// SPDX-FileCopyrightText: Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "accountregistry.h" + +#include "connection.h" + +using namespace Quotient; + +void AccountRegistry::add(Connection* c) +{ + if (m_accounts.contains(c)) + return; + beginInsertRows(QModelIndex(), m_accounts.size(), m_accounts.size()); + m_accounts += c; + endInsertRows(); +} + +void AccountRegistry::drop(Connection* c) +{ + beginRemoveRows(QModelIndex(), m_accounts.indexOf(c), m_accounts.indexOf(c)); + m_accounts.removeOne(c); + endRemoveRows(); + Q_ASSERT(!m_accounts.contains(c)); +} + +bool AccountRegistry::isLoggedIn(const QString &userId) const +{ + return std::any_of(m_accounts.cbegin(), m_accounts.cend(), + [&userId](Connection* a) { return a->userId() == userId; }); +} + +bool AccountRegistry::contains(Connection *c) const +{ + return m_accounts.contains(c); +} + +AccountRegistry::AccountRegistry() +{} + +QVariant AccountRegistry::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return {}; + } + + if (index.row() >= m_accounts.count()) { + return {}; + } + + auto account = m_accounts[index.row()]; + + if (role == ConnectionRole) { + return QVariant::fromValue(account); + } + + return {}; +} + +int AccountRegistry::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return m_accounts.count(); +} + +QHash AccountRegistry::roleNames() const +{ + return {{ConnectionRole, "connection"}}; +} + +bool AccountRegistry::isEmpty() const +{ + return m_accounts.isEmpty(); +} + +int AccountRegistry::count() const +{ + return m_accounts.count(); +} + +const QVector AccountRegistry::accounts() const +{ + return m_accounts; +} + +Connection* AccountRegistry::get(const QString& userId) +{ + for (const auto &connection : m_accounts) { + if(connection->userId() == userId) { + return connection; + } + } + return nullptr; +} \ No newline at end of file diff --git a/lib/accountregistry.h b/lib/accountregistry.h new file mode 100644 index 00000000..e87da3e8 --- /dev/null +++ b/lib/accountregistry.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2020 Kitsune Ral +// SPDX-FileCopyrightText: Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include + +namespace Quotient { +class Connection; + +class AccountRegistry : public QAbstractListModel { + Q_OBJECT +public: + enum EventRoles { + ConnectionRole = Qt::UserRole + 1, + }; + + static AccountRegistry &instance() { + static AccountRegistry _instance; + return _instance; + } + + const QVector accounts() const; + void add(Connection* a); + void drop(Connection* a); + bool isLoggedIn(const QString& userId) const; + bool isEmpty() const; + int count() const; + bool contains(Connection*) const; + Connection* get(const QString& userId); + + [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + [[nodiscard]] QHash roleNames() const override; + +private: + AccountRegistry(); + + QVector m_accounts; +}; +} \ No newline at end of file -- cgit v1.2.3 From f005f3b69651bd6d6f58879804e1281f6c08177a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 30 Aug 2021 12:33:56 +0200 Subject: SettingsGroup, AccountSettings: simplify constructors The parameter packs intended to pass organisation/application names to QSettings never worked that way since Quotient::Settings didn't have that parameter pack in its constructor. On the other hand, setting organisation/application name using static methods before constructing the first settings object has been working just fine so far. If someone needs to create a settings object with customised org/app name for some reason (sneaking settings from other apps?), those can be brought back in a working manner and without breaking API/ABI even. --- lib/settings.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/settings.h b/lib/settings.h index 3f4dc123..efd0d714 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -78,9 +78,8 @@ protected: class SettingsGroup : public Settings { public: - template - explicit SettingsGroup(QString path, ArgTs&&... qsettingsArgs) - : Settings(std::forward(qsettingsArgs)...) + explicit SettingsGroup(QString path, QObject* parent = nullptr) + : Settings(parent) , groupPath(std::move(path)) {} @@ -134,10 +133,8 @@ class AccountSettings : public SettingsGroup { Q_PROPERTY(QByteArray encryptionAccountPickle READ encryptionAccountPickle WRITE setEncryptionAccountPickle) public: - template - explicit AccountSettings(const QString& accountId, ArgTs&&... qsettingsArgs) - : SettingsGroup("Accounts/" + accountId, - std::forward(qsettingsArgs)...) + explicit AccountSettings(const QString& accountId, QObject* parent = nullptr) + : SettingsGroup("Accounts/" + accountId, parent) {} QString userId() const; -- cgit v1.2.3 From 06a8ef6ebed5962117121486059ba46dc7f6d4f9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 30 Aug 2021 12:35:27 +0200 Subject: Room: displayNameForHtml This is useful for cases when the room display name is returned to QML that doesn't have an equivalent of QString::toHtmlEscaped(). --- lib/room.cpp | 5 +++++ lib/room.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/lib/room.cpp b/lib/room.cpp index 45f1af53..890da13d 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -549,6 +549,11 @@ QString Room::canonicalAlias() const QString Room::displayName() const { return d->displayname; } +QString Room::displayNameForHtml() const +{ + return displayName().toHtmlEscaped(); +} + void Room::refreshDisplayName() { d->updateDisplayname(); } QString Room::topic() const diff --git a/lib/room.h b/lib/room.h index 179295d0..d3a7466d 100644 --- a/lib/room.h +++ b/lib/room.h @@ -85,6 +85,7 @@ class Room : public QObject { Q_PROPERTY(QStringList altAliases READ altAliases NOTIFY namesChanged) Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged) + Q_PROPERTY(QString displayNameForHtml READ displayNameForHtml NOTIFY displaynameChanged) Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) @@ -183,6 +184,7 @@ public: //! Get a list of both canonical and alternative aliases QStringList aliases() const; QString displayName() const; + QString displayNameForHtml() const; QString topic() const; QString avatarMediaId() const; QUrl avatarUrl() const; -- cgit v1.2.3 From e2de07628f61c565ac8c85fa3aae84a5fa6feba3 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 15:06:46 +0200 Subject: Add functions and macros to query for Quotient's version --- CMakeLists.txt | 4 ++++ lib/util.cpp | 20 ++++++++++++++++++++ lib/util.h | 5 +++++ 3 files changed, 29 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c6e7548..aca1f982 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -275,6 +275,10 @@ file(GLOB_RECURSE api_SRCS ${add_CONFIGURE_DEPENDS} ${FULL_CSAPI_DIR}/*.cpp) add_library(${PROJECT_NAME} ${lib_SRCS} ${api_SRCS}) target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_JAVA_STYLE_ITERATORS QT_NO_URL_CAST_FROM_STRING QT_NO_CAST_TO_ASCII) + +target_compile_definitions(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} + ${PROJECT_NAME}_VERSION_MINOR=${PROJECT_VERSION_MINOR} ${PROJECT_NAME}_VERSION_PATCH=${PROJECT_VERSION_PATCH} + ${PROJECT_NAME}_VERSION_STRING=\"${PROJECT_VERSION}\") if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.16.0" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL GNU) # https://bugzilla.redhat.com/show_bug.cgi?id=1721553 target_precompile_headers(${PROJECT_NAME} PRIVATE lib/converters.h) diff --git a/lib/util.cpp b/lib/util.cpp index 904bfd5a..3de1d169 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -119,6 +119,26 @@ QString Quotient::serverPart(const QString& mxId) return parser.match(mxId).captured(1); } +QString Quotient::versionString() +{ + return QStringLiteral(Quotient_VERSION_STRING); +} + +int Quotient::majorVersion() +{ + return Quotient_VERSION_MAJOR; +} + +int Quotient::minorVersion() +{ + return Quotient_VERSION_MINOR; +} + +int Quotient::patchVersion() +{ + return Quotient_VERSION_PATCH; +} + // Tests for function_traits<> using namespace Quotient; diff --git a/lib/util.h b/lib/util.h index 78fc9ab7..5bfe6841 100644 --- a/lib/util.h +++ b/lib/util.h @@ -318,4 +318,9 @@ qreal stringToHueF(const QString& s); /** Extract the serverpart from MXID */ QString serverPart(const QString& mxId); + +QString versionString(); +int majorVersion(); +int minorVersion(); +int patchVersion(); } // namespace Quotient -- cgit v1.2.3 From 6be4436494506f2fa5dbbdc633577e059a422bfe Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 24 Aug 2021 14:04:49 +0200 Subject: Add Windows, CodeQL Windows and CodeQL snippets picked from Quaternion --- .github/workflows/ci.yml | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20100e5f..87bb5149 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,13 +18,21 @@ jobs: matrix: os: [ubuntu-20.04, macos-10.15] compiler: [ GCC, Clang ] + platform: [ '' ] qt-version: [ '5.12.10' ] + qt-arch: [ '' ] # Not using binary values here, to make the job captions more readable e2ee: [ '' ] update-api: [ '', 'update-api' ] exclude: - os: macos-10.15 compiler: GCC + include: + - os: windows-2019 + compiler: MSVC + platform: x64 + qt-version: '5.12.10' + qt-arch: win64_msvc2017_64 steps: - uses: actions/checkout@v2 @@ -36,15 +44,16 @@ jobs: uses: actions/cache@v2 with: path: ${{ runner.workspace }}/Qt - key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache + key: ${{ runner.os }}${{ matrix.platform }}-Qt${{ matrix.qt-version }}-cache - name: Install Qt uses: jurplel/install-qt-action@v2.11.1 with: version: ${{ matrix.qt-version }} + arch: ${{ matrix.qt-arch }} cached: ${{ steps.cache-qt.outputs.cache-hit }} - - name: Install Ninja (macOS) + - name: Install Ninja (macOS/Windows) if: ${{ !startsWith(matrix.os, 'ubuntu') }} uses: seanmiddleditch/gha-setup-ninja@v3 @@ -60,9 +69,13 @@ jobs: if [ -n "${{ matrix.update-api }}" ]; then VERSION_POSTFIX='-9'; fi echo "CC=gcc$VERSION_POSTFIX" >>$GITHUB_ENV echo "CXX=g++$VERSION_POSTFIX" >>$GITHUB_ENV - else + elif [[ '${{ matrix.compiler }}' == 'Clang' ]]; then echo "CC=clang" >>$GITHUB_ENV echo "CXX=clang++" >>$GITHUB_ENV + if [[ '${{ runner.os }}' == 'Linux' ]]; then + # Do CodeQL analysis on one of Linux branches + echo "CODEQL_ANALYSIS=true" >>$GITHUB_ENV + fi fi if grep -q 'refs/tags' <<<'${{ github.ref }}'; then VERSION="$(git describe --tags)" @@ -76,6 +89,12 @@ jobs: -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV cmake -E make_directory ${{ runner.workspace }}/build + - name: Setup MSVC environment + uses: ilammy/msvc-dev-cmd@v1 + if: matrix.compiler == 'MSVC' + with: + arch: ${{ matrix.platform }} + - name: Build and install olm if: matrix.e2ee run: | @@ -97,6 +116,16 @@ jobs: -DGTAD_PATH=${{ runner.workspace }}/gtad/gtad" \ >>$GITHUB_ENV echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN and API files regeneration" >>$GITHUB_ENV + + - name: Initialize CodeQL tools + if: env.CODEQL_ANALYSIS + uses: github/codeql-action/init@v1 + with: + languages: cpp + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Configure libQuotient run: cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} @@ -117,3 +146,7 @@ jobs: run: | [[ -z "$TEST_USER" ]] || $VALGRIND build/quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 5 # quotest is supposed to finish within 3 minutes, actually + + - name: Perform CodeQL analysis + if: env.CODEQL_ANALYSIS + uses: github/codeql-action/analyze@v1 -- cgit v1.2.3 From c813a084b6dc8206a9c7305bbb236e23edcb49e5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 24 Aug 2021 18:38:45 +0200 Subject: Fix building with MSVC Turned out it was broken, and I was looking the other way. --- lib/jobs/basejob.cpp | 4 ++-- lib/quotient_common.h | 31 +++++++++++++------------------ 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 239cef28..6346db9d 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -156,8 +156,8 @@ public: { // FIXME: use std::array {} when Apple stdlib gets deduction guides for it static const auto verbs = - to_array({ QStringLiteral("GET"), QStringLiteral("PUT"), - QStringLiteral("POST"), QStringLiteral("DELETE") }); + make_array(QStringLiteral("GET"), QStringLiteral("PUT"), + QStringLiteral("POST"), QStringLiteral("DELETE")); const auto verbWord = verbs.at(size_t(verb)); return verbWord % ' ' % (reply ? reply->url().toString(QUrl::RemoveQuery) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index d225ad63..bb2e6a6b 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -10,22 +10,17 @@ namespace Quotient { Q_NAMESPACE -namespace impl { - template - constexpr std::array, N> - to_array_impl(T (&&a)[N], std::index_sequence) - { - return { {std::move(a[I])...} }; - } -} // std::array {} needs explicit template parameters on macOS because -// Apple stdlib doesn't have deduction guides for std::array; to alleviate that, -// to_array() is borrowed from C++20 (thanks to cppreference for the possible -// implementation: https://en.cppreference.com/w/cpp/container/array/to_array) -template -constexpr auto to_array(T (&& items)[N]) +// Apple stdlib doesn't have deduction guides for std::array. C++20 has +// to_array() but that can't be borrowed, this time because of MSVC: +// https://developercommunity.visualstudio.com/t/vc-ice-p1-initc-line-3652-from-stdto-array/1464038 +// Therefore a simpler (but also slightly more wobbly - it resolves the element +// type using std::common_type<>) make_array facility is implemented here. +template +constexpr auto make_array(Ts&&... items) { - return impl::to_array_impl(std::move(items), std::make_index_sequence{}); + return std::array, sizeof...(items)>( + { std::forward(items)... }); } // TODO: code like this should be generated from the CS API definition @@ -49,9 +44,9 @@ enum class Membership : unsigned int { Q_DECLARE_FLAGS(MembershipMask, Membership) Q_FLAG_NS(MembershipMask) -constexpr inline auto MembershipStrings = to_array( +constexpr inline auto MembershipStrings = make_array( // The order MUST be the same as the order in the original enum - { "join", "leave", "invite", "knock", "ban" }); + "join", "leave", "invite", "knock", "ban"); //! \brief Local user join-state names //! @@ -68,10 +63,10 @@ enum class JoinState : std::underlying_type_t { Q_DECLARE_FLAGS(JoinStates, JoinState) Q_FLAG_NS(JoinStates) -constexpr inline auto JoinStateStrings = to_array({ +constexpr inline auto JoinStateStrings = make_array( MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ -}); +); //! \brief Network job running policy flags //! -- cgit v1.2.3 From 66795dbc90e5013eb5ddf941f7e462053a3d5229 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 24 Aug 2021 19:29:27 +0200 Subject: Fix bin path differences between POSIX and Windows --- .github/workflows/ci.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87bb5149..9d985fca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,7 +128,14 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Configure libQuotient - run: cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} + run: | + if [[ '${{ runner.os }}' == 'Windows' ]]; then + BIN_DIR=. + else + BIN_DIR=bin + fi + echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV + cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} - name: Regenerate API code if: matrix.update-api @@ -137,7 +144,7 @@ jobs: - name: Build and install libQuotient run: | cmake --build build --target install - ls ~/.local/bin/quotest + ls ~/.local/$BIN_DIR/quotest - name: Run tests env: -- cgit v1.2.3 From e150cdaf11800220f2cac0b6b348ebf8583514b0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 1 Sep 2021 14:10:28 +0200 Subject: Add update-api on Windows pipeline --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d985fca..c90037ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,12 @@ jobs: platform: x64 qt-version: '5.12.10' qt-arch: win64_msvc2017_64 + - os: windows-2019 + compiler: MSVC + platform: x64 + qt-version: '5.12.10' + qt-arch: win64_msvc2017_64 + update-api: update-api steps: - uses: actions/checkout@v2 -- cgit v1.2.3 From 339db5a0e3e458f12beeec57116514e88f8ab354 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 1 Sep 2021 15:34:31 +0200 Subject: Change GTAD/matrix-doc paths `${{ runner.workspace }}` is, unfortunately, not portable to Windows. --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c90037ea..670153eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Build and install olm if: matrix.e2ee run: | - cd ${{ runner.workspace }} + cd .. git clone https://gitlab.matrix.org/matrix-org/olm.git cmake -S olm -B olm/build $CMAKE_ARGS cmake --build olm/build --target install @@ -113,13 +113,13 @@ jobs: - name: Pull CS API and build GTAD if: matrix.update-api run: | - cd ${{ runner.workspace }} + cd .. git clone https://github.com/matrix-org/matrix-doc.git git clone --recursive https://github.com/KitsuneRal/gtad.git - cmake -S gtad -B gtad $CMAKE_ARGS + cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF cmake --build gtad - echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=${{ runner.workspace }}/matrix-doc \ - -DGTAD_PATH=${{ runner.workspace }}/gtad/gtad" \ + echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ + -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ >>$GITHUB_ENV echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN and API files regeneration" >>$GITHUB_ENV -- cgit v1.2.3 From db9dd0ea43f2140e0df72d05861939ee1cb98053 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 1 Sep 2021 17:06:41 +0200 Subject: CMakeLists: fix resolving gtad on Windows --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49105389..5e5c1932 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,7 +176,7 @@ set(ASAPI_DEF_DIR application-service/definitions) set(ISAPI_DEF_DIR identity/definitions) if (GTAD_PATH AND MATRIX_DOC_PATH) - get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" REALPATH) + get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" PROGRAM PROGRAM_ARGS GTAD_ARGS) if (EXISTS ${ABS_GTAD_PATH}) get_filename_component(ABS_API_DEF_PATH "${MATRIX_DOC_PATH}/data/api" REALPATH) if (NOT IS_DIRECTORY ${ABS_API_DEF_PATH}) @@ -227,6 +227,7 @@ if (API_GENERATION_ENABLED) old_sync.yaml- room_initial_sync.yaml- # deprecated key_backup.yaml- # immature and buggy in terms of API definition sync.yaml- # we have a better handcrafted implementation + ${GTAD_ARGS} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/lib SOURCES gtad/gtad.yaml gtad/data.h.mustache -- cgit v1.2.3 From 87d05252acc4ed94d24150eabb2dd1a1f700f46f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 1 Sep 2021 17:08:48 +0200 Subject: CMakeLists: allow to pass clang-format options in CLANG_FORMAT This supersedes passing clang-format options in a separate CLANG_FORMAT_ARGS CMake variable. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e5c1932..22951faa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,7 +198,7 @@ if (API_GENERATION_ENABLED) if (NOT CLANG_FORMAT) set(CLANG_FORMAT clang-format) endif() - get_filename_component(ABS_CLANG_FORMAT "${CLANG_FORMAT}" PROGRAM) + get_filename_component(ABS_CLANG_FORMAT "${CLANG_FORMAT}" PROGRAM PROGRAM_ARGS CLANG_FORMAT_ARGS) if (ABS_CLANG_FORMAT) set(API_FORMATTING_ENABLED 1) message( STATUS "clang-format is at ${ABS_CLANG_FORMAT}") -- cgit v1.2.3 From 92efa87eff1b35bc857633af20ea8987d926dd5e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 1 Sep 2021 21:51:24 +0200 Subject: Drop .appveyor.yml --- .appveyor.yml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index fa031ed8..00000000 --- a/.appveyor.yml +++ /dev/null @@ -1,37 +0,0 @@ -image: Visual Studio 2017 - -environment: - CMAKE_ARGS: '-G "NMake Makefiles JOM" -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo' - matrix: - - QTDIR: C:\Qt\5.13\msvc2017 # Fresh Qt, 32-bit - VCVARS: "vcvars32.bat" - PLATFORM: x86 - - QTDIR: C:\Qt\5.13\msvc2017_64 # Fresh Qt, 64-bit - VCVARS: "vcvars64.bat" - PLATFORM: - -init: -- call "%QTDIR%\bin\qtenv2.bat" -- set PATH=C:\Qt\Tools\QtCreator\bin;%PATH% -- call "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\%VCVARS%" %PLATFORM% -- cd /D "%APPVEYOR_BUILD_FOLDER%" - -before_build: -- git submodule update --init --recursive - -build_script: -- cmake %CMAKE_ARGS% -H. -Bbuild -- cmake --build build - -#after_build: -#- cmake --build build --target install -#- 7z a quotient.zip "%DEPLOY_DIR%\" - -# Uncomment this to connect to the AppVeyor build worker -#on_finish: -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) - -test: off - -#artifacts: -#- path: quotient.zip -- cgit v1.2.3 From f14b04157a9cacaf60f815f164939cc42dad04da Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Sep 2021 19:42:24 +0200 Subject: Require CMake 3.16; drop qmake; use C++20 and newer compilers Also, refresh the documentation a bit. --- CMakeLists.txt | 30 +++++++------- CONTRIBUTING.md | 2 +- README.md | 125 +++++++++++++++++++++++++------------------------------- 3 files changed, 72 insertions(+), 85 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 62fa43de..c0a39932 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.16) if (POLICY CMP0092) cmake_policy(SET CMP0092 NEW) endif() @@ -207,15 +207,14 @@ if (API_GENERATION_ENABLED) message( STATUS "${CLANG_FORMAT} is NOT FOUND; API files won't be reformatted") endif () - if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.12.0") - # We use globbing with CONFIGURE_DEPENDS to produce two file lists: - # one of all API files for clang-format and another of just .cpp - # files to supply for library source files. Since we expect these - # file lists to only change due to GTAD invocation, we only use - # CONFIGURE_DEPENDS when pre-requisites to update API are met. - # Read comments next to each file(GLOB_RECURSE) for caveats. - set(add_CONFIGURE_DEPENDS "CONFIGURE_DEPENDS") - endif() + # We use globbing with CONFIGURE_DEPENDS to produce two file lists: + # one of all API files for clang-format and another of just .cpp + # files to supply for library source files. Since we expect these + # file lists to only change due to GTAD invocation, we only use + # CONFIGURE_DEPENDS when pre-requisites to update API are met. + # Read comments next to each file(GLOB_RECURSE) for caveats. + set(add_CONFIGURE_DEPENDS "CONFIGURE_DEPENDS") + set(FULL_CSAPI_SRC_DIR ${ABS_API_DEF_PATH}/client-server) file(GLOB_RECURSE API_DEFS RELATIVE ${PROJECT_SOURCE_DIR} ${FULL_CSAPI_SRC_DIR}/*.yaml @@ -280,14 +279,12 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_JAVA_STYLE_ITERATORS QT target_compile_definitions(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} ${PROJECT_NAME}_VERSION_MINOR=${PROJECT_VERSION_MINOR} ${PROJECT_NAME}_VERSION_PATCH=${PROJECT_VERSION_PATCH} ${PROJECT_NAME}_VERSION_STRING=\"${PROJECT_VERSION}\") -if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.16.0" - AND NOT CMAKE_CXX_COMPILER_ID STREQUAL GNU) # https://bugzilla.redhat.com/show_bug.cgi?id=1721553 - target_precompile_headers(${PROJECT_NAME} PRIVATE lib/converters.h) -endif () if (${PROJECT_NAME}_ENABLE_E2EE) target_compile_definitions(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}_E2EE_ENABLED) endif() set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 20 + CXX_EXTENSIONS OFF VERSION "${PROJECT_VERSION}" SOVERSION ${API_VERSION} INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${API_VERSION} @@ -295,8 +292,13 @@ set_target_properties(${PROJECT_NAME} PROPERTIES set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION) +# C++17 required, C++20 desired (see above) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) +if (NOT CMAKE_CXX_COMPILER_ID STREQUAL GNU) # https://bugzilla.redhat.com/show_bug.cgi?id=1721553 + target_precompile_headers(${PROJECT_NAME} PRIVATE lib/converters.h) +endif () + target_include_directories(${PROJECT_NAME} PUBLIC $ $ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4fdab602..c9296df1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -408,7 +408,7 @@ I (Kitsune) will be very glad to help you out. The types map in `gtad.yaml` is the central switchboard when it comes to matching OpenAPI types with C++ (and Qt) ones. It uses the following type attributes aside from pretty obvious "imports:": * `avoidCopy` - this attribute defines whether a const ref should be used instead of a value. For basic types like int this is obviously unnecessary; but compound types like `QVector` should rather be taken by reference when possible. * `moveOnly` - some types are not copyable at all and must be moved instead (an obvious example is anything "tainted" with a member of type `std::unique_ptr<>`). The template will use `T&&` instead of `T` or `const T&` to pass such types around. -* `useOmittable` - wrap types that have no value with "null" semantics (i.e. number types and custom-defined data structures) into a special `Omittable<>` template defined in `converters.h` - a substitute for `std::optional` from C++17 (we're still at C++14 yet). +* `useOmittable` - wrap types that have no value with "null" semantics (i.e. number types and custom-defined data structures) into a special `Omittable<>` template defined in `converters.h`, a drop-in upgrade over `std::optional`. * `omittedValue` - an alternative for `useOmittable`, just provide a value used for an omitted parameter. This is used for bool parameters which normally are considered false if omitted (or they have an explicit default value, passed in the "official" GTAD's `defaultValue` variable). * `initializer` - this is a _partial_ (see GTAD and Mustache documentation for explanations but basically it's a variable that is a Mustache template itself) that specifies how exactly a default value should be passed to the parameter. E.g., the default value for a `QString` parameter is enclosed into `QStringLiteral`. diff --git a/README.md b/README.md index 05c629f2..71f2d04c 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,7 @@ The Quotient project aims to produce a Qt5-based SDK to develop applications for [Matrix](https://matrix.org). libQuotient is a library that enables client applications. It is the backbone of [Quaternion](https://github.com/quotient-im/Quaternion), -[Spectral](https://matrix.org/docs/projects/client/spectral.html) and -other projects. +[NeoChat](https://matrix.org/docs/projects/client/neo-chat) and other projects. Versions 0.5.x and older use the previous name - libQMatrixClient. ## Contacts @@ -38,61 +37,62 @@ and bundle it with your application. ### Pre-requisites - A recent Linux, macOS or Windows system (desktop versions are known to work; mobile operating systems where Qt is available might work too) - - Recent enough Linux examples: Debian Buster; Fedora 28; openSUSE Leap 15; - Ubuntu Bionic Beaver. -- Qt 5 (either Open Source or Commercial), 5.9 or higher; - 5.12 is recommended, especially if you use qmake -- A build configuration tool (CMake is recommended, qmake is supported): - - CMake 3.10 or newer (from your package management system or - [the official website](https://cmake.org/download/)) - - or qmake (comes with Qt) -- A C++ toolchain with _reasonably complete_ C++17 support: - - GCC 7 (Windows, Linux, macOS), Clang 6 (Linux), Apple Clang 10 (macOS) - and Visual Studio 2017 (Windows) are the oldest officially supported. -- Any build system that works with CMake and/or qmake should be fine: - GNU Make, ninja (any platform), NMake, jom (Windows) are known to work. + - Recent enough Linux examples: Debian Bullseye; Fedora 33; openSUSE Leap 15.3; + Ubuntu Focal Fossa. +- Qt 5 (either Open Source or Commercial), 5.12 or higher +- CMake 3.16 or newer (from your package management system or + [the official website](https://cmake.org/download/)) +- A C++ toolchain with complete (as much as possible) C++17 and basic C++20: + - GCC 10 (Windows, Linux, macOS), Clang 11 (Linux), Apple Clang 12 (macOS) + and Visual Studio 2019 (Windows) are the oldest officially supported. +- Any build system that works with CMake should be fine: + GNU Make and ninja on any platform, NMake and jom on Windows are known to work. + Ninja is recommended. #### Linux -Just install things from the list above using your preferred package manager. If your Qt package base is fine-grained you might want to run cmake/qmake and look at error messages. The library is entirely offscreen (QtCore and QtNetwork are essential) but it also depends on QtGui in order to handle avatar thumbnails. +Just install things from the list above using your preferred package manager. +If your Qt package base is fine-grained you might want to run cmake and look +at error messages. The library is entirely offscreen but aside from QtCore and +QtNetwork it also depends on QtGui in order to handle avatar thumbnails. #### macOS -`brew install qt5` should get you a recent Qt5. If you plan to use CMake, you will need to tell it about the path to Qt by passing `-DCMAKE_PREFIX_PATH=$(brew --prefix qt5)` +`brew install qt5` should get you a recent Qt5. You may need to pass +`-DCMAKE_PREFIX_PATH=$(brew --prefix qt5)` to make it aware of the Qt location. #### Windows -Install Qt5, using their official installer; if you plan to build with CMake, -make sure to tick the CMake box in the list of installed components. - -The commands in further sections imply that cmake/qmake is in your PATH, -otherwise you have to prepend those commands with actual paths. As an option -it's a good idea to run a `qtenv2.bat` script that can be found in -`C:\Qt\\\bin` (assuming you installed Qt to `C:\Qt`); -the only thing it does is adding necessary paths to PATH. You might not want -to run that script on system startup but it's very handy to setup -the environment before building. For CMake you can alternatively point -`CMAKE_PREFIX_PATH` to your Qt installation and leave PATH unchanged; but -in that case you'll have to supply the full path to CMake when calling it. +Install Qt5 using their official installer; make sure to tick the CMake box +in the list of installed components unless you already have it installed. + +The commands in further sections imply that cmake is in your PATH, otherwise +you have to prepend those commands with actual paths. It's a good idea to run +a `qtenv2.bat` script that can be found in `C:\Qt\\\bin` +(assuming you installed Qt to `C:\Qt`) if you're building from the command line; +the script adds necessary paths to PATH. You might not want to run that script +on system startup but it's very handy to setup the environment before building. +Alternatively you can add the Qt path to `CMAKE_PREFIX_PATH` and leave PATH +unchanged. ### Using the library -If you use CMake, `find_package(Quotient)` sets up the client code to use -libQuotient, assuming the library development files are installed. There's no -documented procedure to use a preinstalled library with qmake; consider -introducing a submodule in your source tree and build it along with the rest -of the application for now. Note also that qmake is considered for phase-out -in Qt 6 so you should probably think of moving over to CMake eventually. +If you're just starting a project using libQuotient from scratch, you can copy +`quotest/CMakeLists.txt` to your project and change `quotest` to your +project name. If you already have an existing CMakeLists.txt, you need to insert +a `find_package(Quotient REQUIRED)` line to an appropriate place in it (use +`find_package(Quotient)` if libQuotient is not a hard dependency for you) and +then add `Quotient` to your `target_link_libraries()` line. Building with dynamic linkage is only tested on Linux at the moment and is a recommended way of linking your application with libQuotient on this platform. Static linkage is the default on Windows/macOS; feel free to experiment with dynamic linking and submit PRs if you get reusable results. -[Quotest](quotest), the test application that comes with libQuotient, includes -most common use cases such as sending messages, uploading files, -setting room state etc.; for more extensive usage check out the source code -of [Quaternion](https://github.com/quotient-im/Quaternion) -(the reference client of Quotient) or [Spectral](https://gitlab.com/b0/spectral). - -To ease the first step, `quotest/CMakeLists.txt` is a good starting point -for your own CMake-based project using libQuotient. +As for the actual API usage, a (very basic) overview can be found at +[the respective wiki page](https://github.com/quotient-im/libQuotient/wiki/libQuotient-overview). +Beyond that, looking at [Quotest](quotest) - the test application that comes +with libQuotient - may help you with most common use cases such as sending +messages, uploading files, setting room state etc. For more extensive usage +feel free to check out (and copy, with appropriate attribution) the source code +of [Quaternion](https://github.com/quotient-im/Quaternion) (the reference client +for libQuotient) or [NeoChat](https://invent.kde.org/network/neochat). ## Building the library [The source code is at GitHub](https://github.com/quotient-im/libQuotient). @@ -101,28 +101,29 @@ along with submodules is strongly recommended. 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), it makes sense to make a recursive check out of that project (in this case, Quaternion) -and update the library submodule (also recursively) to its master branch. +and update the library submodule (also recursively) within the appropriate +branch. Be mindful of API compatibility restrictions: e.g., Quaternion 0.0.95 +will not build with the master branch of libQuotient. 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. -### CMake-based -In the root directory of the project sources: +The following commands issued in the root directory of the project sources: ```shell script mkdir build_dir cd build_dir cmake .. # [-D=...], see below cmake --build . --target all ``` -This will get you the compiled library in `build_dir` inside your project -sources. Static builds are tested on all supported platforms, building -the library as a shared object (aka dynamic library) is supported on Linux -and macOS but is very likely to be broken on Windows. - -The first CMake invocation configures the build. You can pass CMake variables, -such as `-DCMAKE_PREFIX_PATH="path1;path2;..."` and -`-DCMAKE_INSTALL_PREFIX=path` here if needed. +will get you a compiled library in `build_dir` inside your project sources. +Static builds are tested on all supported platforms, building the library as +a shared object (aka dynamic library) is supported on Linux and macOS but is +very likely to be broken on Windows. + +The first CMake invocation above configures the build. You can pass CMake +variables (such as `-DCMAKE_PREFIX_PATH="path1;path2;..."` and +`-DCMAKE_INSTALL_PREFIX=path`) here if needed. [CMake documentation](https://cmake.org/cmake/help/latest/index.html) (pick the CMake version at the top of the page that you use) describes the standard variables coming with CMake. On top of them, Quotient introduces: @@ -160,22 +161,6 @@ with the _installed_ library. Installation of the `quotest` binary along with the rest of the library can be skipped by setting `Quotient_INSTALL_TESTS` to `OFF`. -### qmake-based -The library provides a .pri file with an intention to be included from a bigger project's .pro file. As a starting point you can use `quotest.pro` that will build a minimal example of library usage for you. In the root directory of the project sources: -```shell script -qmake quotest.pro -make all -``` -This will get you `debug/quotest` and `release/quotest` -console executables that login to the Matrix server at matrix.org with -credentials of your choosing (pass the username and password as arguments), -run a sync long-polling loop and do some tests of the library API. Note that -qmake didn't really know about C++17 until Qt 5.12 so if your Qt is older -you may have quite a bit of warnings during the compilation process. - -Installing the standalone library with qmake is not implemented yet; PRs are -welcome though. - ## Troubleshooting #### Building fails -- cgit v1.2.3 From ee678fac0ee09c6262c7a39b2b1072660e6db9ca Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Sep 2021 22:18:14 +0200 Subject: CI experiment: requires GCC 10 and Clang 11 --- .github/workflows/ci.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 670153eb..47e31d55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,16 +72,17 @@ jobs: - name: Setup build environment run: | if [ "${{ matrix.compiler }}" == "GCC" ]; then - if [ -n "${{ matrix.update-api }}" ]; then VERSION_POSTFIX='-9'; fi - echo "CC=gcc$VERSION_POSTFIX" >>$GITHUB_ENV - echo "CXX=g++$VERSION_POSTFIX" >>$GITHUB_ENV + CXX_VERSION_POSTFIX='-10' + echo "CC=gcc$CXX_VERSION_POSTFIX" >>$GITHUB_ENV + echo "CXX=g++$CXX_VERSION_POSTFIX" >>$GITHUB_ENV elif [[ '${{ matrix.compiler }}' == 'Clang' ]]; then - echo "CC=clang" >>$GITHUB_ENV - echo "CXX=clang++" >>$GITHUB_ENV if [[ '${{ runner.os }}' == 'Linux' ]]; then + CXX_VERSION_POSTFIX='-11' # Do CodeQL analysis on one of Linux branches echo "CODEQL_ANALYSIS=true" >>$GITHUB_ENV fi + echo "CC=clang$CXX_VERSION_POSTFIX" >>$GITHUB_ENV + echo "CXX=clang++$CXX_VERSION_POSTFIX" >>$GITHUB_ENV fi if grep -q 'refs/tags' <<<'${{ github.ref }}'; then VERSION="$(git describe --tags)" -- cgit v1.2.3 From 2736eb8ff2e95daf6ec8b2616d70381054d1a238 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Sep 2021 06:54:29 +0200 Subject: CONTRIBUTING.md: update code conventions to C++20 --- CONTRIBUTING.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9296df1..3eeac68c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,18 +155,20 @@ just don't bankrupt us with it. Refactoring is welcome. ### Code style and formatting -As of Quotient 0.6, the C++ standard for newly written code is C++17, with a few -restrictions, notably: +As of Quotient 0.6, the C++ standard for newly written code is C++17 with C++20 +compatibility and a few restrictions, notably: * standard library's _deduction guides_ cannot be used to lighten up syntax in template instantiation, i.e. you have to still write - `std::array { 1, 2 }` instead of `std::array { 1, 2 }` or use helpers - like `std::make_pair` - once we move over to the later Apple toolchain, this - will be no more necessary. -* enumerators and slots cannot have `[[attributes]]` because moc of Qt 5.9 - chokes on them. This will be lifted when we move on to Qt 5.12 for the oldest - supported version. -* things from `std::filesystem` cannot be used until we push the oldest - required g++/libc to version 8. + `std::array { 1, 2 }` instead of `std::array { 1, 2 }` (or use + `Quotient::make_array` helper from `util.h`), use `std::make_pair` to create + pairs etc. - once we move over to the later Apple toolchain, this will be + no more necessary; +* enumerators and slots cannot have `[[attributes]]` because moc from Qt 5.12 + chokes on them - this will be lifted when we move on to Qt 5.13 for the oldest + supported version, in the meantime use `Q_DECL_DEPRECATED` and similar Qt + macros - they expand to nothing when the code is passed to moc. +* explicit lists in lambda captures are preferred over `[=]`; note that C++20 + deprecates implicit `this` capture in `[=]`. The code style is defined by `.clang-format`, and in general, all C++ files should follow it. Files with minor deviations from the defined style are still -- cgit v1.2.3 From df66314bb47f1a824f35e9d5c7764305a4f103cb Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Sep 2021 07:20:27 +0200 Subject: Tacitly allow CMake 3.13 to keep LGTM working Also: drop olm from the LGTM build environment, it's of no use there for now. --- .lgtm.yml | 18 ++++++++---------- CMakeLists.txt | 9 +++++++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.lgtm.yml b/.lgtm.yml index f6dfb229..308675a8 100644 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -6,15 +6,13 @@ path_classifiers: extraction: cpp: prepare: - packages: # Assuming package base of cosmic - - ninja-build - - qt5-default + packages: # Assuming package base of eoan - qtmultimedia5-dev - after_prepare: - - git clone https://gitlab.matrix.org/matrix-org/olm.git - - pushd olm - - cmake . -Bbuild -GNinja - - cmake --build build - - popd +# after_prepare: +# - git clone https://gitlab.matrix.org/matrix-org/olm.git +# - pushd olm +# - cmake . -Bbuild -GNinja +# - cmake --build build +# - popd configure: - command: "cmake . -GNinja -DOlm_DIR=olm/build" + command: "cmake . -GNinja" # -DOlm_DIR=olm/build" diff --git a/CMakeLists.txt b/CMakeLists.txt index c0a39932..7043a653 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,6 @@ -cmake_minimum_required(VERSION 3.16) +# Officially CMake 3.16+ is needed but LGTM.com still sits on eoan that only +# has CMake 3.13 +cmake_minimum_required(VERSION 3.13) if (POLICY CMP0092) cmake_policy(SET CMP0092 NEW) endif() @@ -295,7 +297,10 @@ set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY # C++17 required, C++20 desired (see above) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) -if (NOT CMAKE_CXX_COMPILER_ID STREQUAL GNU) # https://bugzilla.redhat.com/show_bug.cgi?id=1721553 +# TODO: Bump the CMake requirement and drop the version check here once +# LGTM upgrades +if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.16.0" + AND NOT CMAKE_CXX_COMPILER_ID STREQUAL GNU) # https://bugzilla.redhat.com/show_bug.cgi?id=1721553 target_precompile_headers(${PROJECT_NAME} PRIVATE lib/converters.h) endif () -- cgit v1.2.3 From 4bab0f2ef2c68b478d669f90557d6bef6332e823 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 21:47:10 +0200 Subject: Implement the mxc protocol in the NetworkAccessManager Allows images to be loaded using the NetworkAccessManager instead of an ImageProvider --- CMakeLists.txt | 1 + lib/mxcreply.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++ lib/mxcreply.h | 29 +++++++++++++++++++++++ lib/networkaccessmanager.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++ lib/networkaccessmanager.h | 6 +++++ 5 files changed, 146 insertions(+) create mode 100644 lib/mxcreply.cpp create mode 100644 lib/mxcreply.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c6e7548..178602da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ list(APPEND lib_SRCS lib/encryptionmanager.cpp lib/eventitem.cpp lib/accountregistry.cpp + lib/mxcreply.cpp lib/events/event.cpp lib/events/roomevent.cpp lib/events/stateevent.cpp diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp new file mode 100644 index 00000000..49ebe603 --- /dev/null +++ b/lib/mxcreply.cpp @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "mxcreply.h" + +#include +#include "connection.h" +#include "room.h" +#include "networkaccessmanager.h" +#include "events/stickerevent.h" + +using namespace Quotient; + +class MxcReply::Private +{ +public: + QNetworkReply *m_reply = nullptr; +}; + +MxcReply::MxcReply(QNetworkReply* reply) +{ + reply->setParent(this); + d->m_reply = reply; + connect(d->m_reply, &QNetworkReply::finished, this, [this]() { + setError(d->m_reply->error(), d->m_reply->errorString()); + Q_EMIT finished(); + }); +} + +MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) + : d(std::make_unique()) +{ + reply->setParent(this); + d->m_reply = reply; + connect(d->m_reply, &QNetworkReply::finished, this, [this, eventId]() { + setError(d->m_reply->error(), d->m_reply->errorString()); + Q_EMIT finished(); + }); +} + +bool MxcReply::isSequential() const +{ + return true; +} + +qint64 MxcReply::readData(char *data, qint64 maxSize) +{ + return d->m_reply->read(data, maxSize); +} + +void MxcReply::abort() +{ + d->m_reply->abort(); +} \ No newline at end of file diff --git a/lib/mxcreply.h b/lib/mxcreply.h new file mode 100644 index 00000000..26dea2d0 --- /dev/null +++ b/lib/mxcreply.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include + +namespace Quotient { +class Room; +class Connection; +class MxcReply : public QNetworkReply +{ +public: + MxcReply(QNetworkReply* reply, Room* room, const QString &eventId); + MxcReply(QNetworkReply* reply); + + bool isSequential() const override; + +public slots: + void abort() override; + +protected: + qint64 readData(char *data, qint64 maxSize) override; +private: + class Private; + std::unique_ptr d; +}; +} \ No newline at end of file diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index a94ead34..e4132957 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -5,6 +5,11 @@ #include #include +#include "accountregistry.h" +#include "mxcreply.h" +#include "connection.h" + +#include "room.h" using namespace Quotient; @@ -56,7 +61,58 @@ NetworkAccessManager::~NetworkAccessManager() = default; QNetworkReply* NetworkAccessManager::createRequest( Operation op, const QNetworkRequest& request, QIODevice* outgoingData) { + if(request.url().scheme() == QStringLiteral("mxc")) { + const auto fragment = request.url().fragment(); + const auto fragmentParts = fragment.split(QLatin1Char('/')); + const auto mediaId = request.url().toString(QUrl::RemoveScheme | QUrl::RemoveFragment); + if(fragmentParts.size() == 3) { + auto connection = AccountRegistry::instance().get(fragmentParts[0]); + if(!connection) { + qWarning() << "Connection not found"; + return nullptr; + } + auto room = connection->room(fragmentParts[1]); + if(!room) { + qWarning() << "Room not found"; + return nullptr; + } + QNetworkRequest r(request); + r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2").arg(connection->homeserver().toString(), mediaId))); + auto reply = createRequest(QNetworkAccessManager::GetOperation, r); + return new MxcReply(reply, room, fragmentParts[2]); + } else if(fragmentParts.size() == 1) { + auto connection = AccountRegistry::instance().get(fragment); + if(!connection) { + qWarning() << "Connection not found"; + return nullptr; + } + QNetworkRequest r(request); + r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2").arg(connection->homeserver().toString(), mediaId))); + auto reply = createRequest(QNetworkAccessManager::GetOperation, r); + return new MxcReply(reply); + } else { + qWarning() << "Invalid request"; + return nullptr; + } + } auto reply = QNetworkAccessManager::createRequest(op, request, outgoingData); reply->ignoreSslErrors(d->ignoredSslErrors); return reply; } + +QStringList NetworkAccessManager::supportedSchemesImplementation() const +{ + auto schemes = QNetworkAccessManager::supportedSchemesImplementation(); + schemes += QStringLiteral("mxc"); + return schemes; +} + +QUrl NetworkAccessManager::urlForRoomEvent(Room *room, const QString &eventId, const QString &mediaId) +{ + return QUrl(QStringLiteral("mxc:%1#%2/%3/%4").arg(mediaId, room->connection()->userId(), room->id(), eventId)); +} + +QUrl NetworkAccessManager::urlForFile(Connection *connection, const QString &mediaId) +{ + return QUrl(QStringLiteral("mxc:%1#%2").arg(mediaId, connection->userId())); +} \ No newline at end of file diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 47729a1b..5d262f98 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -8,6 +8,8 @@ #include namespace Quotient { +class Room; +class Connection; class NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: @@ -21,6 +23,10 @@ public: /** Get a pointer to the singleton */ static NetworkAccessManager* instance(); +public Q_SLOTS: + QStringList supportedSchemesImplementation() const; + QUrl urlForRoomEvent(Room *room, const QString &eventId, const QString &mediaId); + QUrl urlForFile(Connection *connection, const QString &mediaId); private: QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData = Q_NULLPTR) override; -- cgit v1.2.3 From 67186252eac4f3890d5abae31e74efee956b2f5d Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 5 Sep 2021 22:52:41 +0200 Subject: Create a NAM for each thread --- lib/networkaccessmanager.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index e4132957..7368de7e 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "accountregistry.h" #include "mxcreply.h" #include "connection.h" @@ -52,8 +53,11 @@ static NetworkAccessManager* createNam() NetworkAccessManager* NetworkAccessManager::instance() { - static auto* nam = createNam(); - return nam; + static QThreadStorage storage; + if(!storage.hasLocalData()) { + storage.setLocalData(createNam()); + } + return storage.localData(); } NetworkAccessManager::~NetworkAccessManager() = default; -- cgit v1.2.3 From 6d3bc1667e084fa2fc7b2c547374d2c62a29e8df Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Sep 2021 16:17:23 +0200 Subject: Fix showing non-animated Images --- lib/mxcreply.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 49ebe603..f389ac85 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -23,6 +23,7 @@ MxcReply::MxcReply(QNetworkReply* reply) d->m_reply = reply; connect(d->m_reply, &QNetworkReply::finished, this, [this]() { setError(d->m_reply->error(), d->m_reply->errorString()); + setOpenMode(ReadOnly); Q_EMIT finished(); }); } @@ -34,6 +35,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) d->m_reply = reply; connect(d->m_reply, &QNetworkReply::finished, this, [this, eventId]() { setError(d->m_reply->error(), d->m_reply->errorString()); + setOpenMode(ReadOnly); Q_EMIT finished(); }); } -- cgit v1.2.3 From 9e548e0625a819052cd10d5c4bf129dde649a6a4 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 9 Sep 2021 19:27:57 +0200 Subject: Straighten up file transfer cancellation There was a mess with fileTransferCancelled(); it was only emitted when a download (but not an upload) was cancelled; besides, in case of downloads a file transfer info structure was getting deleted whereas uploads left a file transfer in Cancelled status. This all now converges on: - fileTransferFailed() for both failures and cancellations (to simplify slot connection, and also to follow the practice in, e.g., Qt Network). - the file transfer info structure is kept around in Cancelled status, following the logic used for failures. There's no particular cleanup which may become a problem if one uploads and cancels many times (download file transfers are keyed to event ids, mitigating the problem); this will be fixed in another commit. Closes #503. Closes #504. --- lib/room.cpp | 18 ++++++++++++------ lib/room.h | 3 ++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 890da13d..c6cca2ea 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1708,6 +1708,12 @@ QString Room::retryMessage(const QString& txnId) return d->doSendEvent(it->event()); } +// Lambda defers actual tr() invocation to the moment when translations are +// initialised +const auto FileTransferCancelledMsg = [] { + return Room::tr("File transfer cancelled"); +}; + void Room::discardMessage(const QString& txnId) { auto it = std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(), @@ -1722,7 +1728,7 @@ void Room::discardMessage(const QString& txnId) if (isJobPending(transferIt->job)) { transferIt->status = FileTransferInfo::Cancelled; transferIt->job->abandon(); - emit fileTransferFailed(txnId, tr("File upload cancelled")); + emit fileTransferFailed(txnId, FileTransferCancelledMsg()); } else if (transferIt->status == FileTransferInfo::Completed) { qCWarning(MAIN) << "File for transaction" << txnId @@ -1790,7 +1796,7 @@ QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) "cancelled"; } }); - connect(q, &Room::fileTransferCancelled, transferJob, + connect(q, &Room::fileTransferFailed, transferJob, [this, txnId](const QString& tId) { if (tId != txnId) return; @@ -2079,16 +2085,16 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) void Room::cancelFileTransfer(const QString& id) { - const auto it = d->fileTransfers.constFind(id); - if (it == d->fileTransfers.cend()) { + const auto it = d->fileTransfers.find(id); + if (it == d->fileTransfers.end()) { qCWarning(MAIN) << "No information on file transfer" << id << "in room" << d->id; return; } if (isJobPending(it->job)) it->job->abandon(); - d->fileTransfers.remove(id); - emit fileTransferCancelled(id); + it->status = FileTransferInfo::Cancelled; + emit fileTransferFailed(id, FileTransferCancelledMsg()); } void Room::Private::dropDuplicateEvents(RoomEvents& events) const diff --git a/lib/room.h b/lib/room.h index d3a7466d..4e55dbaf 100644 --- a/lib/room.h +++ b/lib/room.h @@ -712,7 +712,8 @@ Q_SIGNALS: void fileTransferProgress(QString id, qint64 progress, qint64 total); void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl); void fileTransferFailed(QString id, QString errorMessage = {}); - void fileTransferCancelled(QString id); + // fileTransferCancelled() is no more here; use fileTransferFailed() and + // check the transfer status instead void callEvent(Quotient::Room* room, const Quotient::RoomEvent* event); -- cgit v1.2.3 From c1015d78fc90972a87b02a7863a9148c446c94b1 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Fri, 10 Sep 2021 17:36:11 +0200 Subject: Update lib/networkaccessmanager.cpp Co-authored-by: Alexey Rusakov --- lib/networkaccessmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 7368de7e..9f423cc3 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -69,7 +69,7 @@ QNetworkReply* NetworkAccessManager::createRequest( const auto fragment = request.url().fragment(); const auto fragmentParts = fragment.split(QLatin1Char('/')); const auto mediaId = request.url().toString(QUrl::RemoveScheme | QUrl::RemoveFragment); - if(fragmentParts.size() == 3) { + if (fragmentParts.size() == 3) { auto connection = AccountRegistry::instance().get(fragmentParts[0]); if(!connection) { qWarning() << "Connection not found"; -- cgit v1.2.3 From 3b383a6dcb75531ca7efcaa4afa28b92dbe15e3e Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Fri, 10 Sep 2021 17:36:19 +0200 Subject: Update lib/networkaccessmanager.cpp Co-authored-by: Alexey Rusakov --- lib/networkaccessmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 9f423cc3..710ade4e 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -53,7 +53,7 @@ static NetworkAccessManager* createNam() NetworkAccessManager* NetworkAccessManager::instance() { - static QThreadStorage storage; + static QThreadStorage storage; if(!storage.hasLocalData()) { storage.setLocalData(createNam()); } -- cgit v1.2.3 From 8dfa505066a03cc8450527699634fda71cbd8915 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 10 Sep 2021 17:55:20 +0200 Subject: Return a failed MxcReply on invalid requests --- lib/mxcreply.cpp | 15 +++++++++++++++ lib/mxcreply.h | 1 + lib/networkaccessmanager.cpp | 8 ++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index f389ac85..7819367e 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -4,6 +4,7 @@ #include "mxcreply.h" #include +#include #include "connection.h" #include "room.h" #include "networkaccessmanager.h" @@ -40,6 +41,20 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) }); } +MxcReply::MxcReply() +{ + QTimer::singleShot(0, this, [this](){ + setError(QNetworkReply::ProtocolInvalidOperationError, QStringLiteral("Invalid Request")); + setFinished(true); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + Q_EMIT errorOccurred(QNetworkReply::ProtocolInvalidOperationError); +#else + Q_EMIT error(QNetworkReply::ProtocolInvalidOperationError); +#endif + Q_EMIT finished(); + }); +} + bool MxcReply::isSequential() const { return true; diff --git a/lib/mxcreply.h b/lib/mxcreply.h index 26dea2d0..ac3ac4f4 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -14,6 +14,7 @@ class MxcReply : public QNetworkReply public: MxcReply(QNetworkReply* reply, Room* room, const QString &eventId); MxcReply(QNetworkReply* reply); + MxcReply(); bool isSequential() const override; diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 710ade4e..f37e26b6 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -73,12 +73,12 @@ QNetworkReply* NetworkAccessManager::createRequest( auto connection = AccountRegistry::instance().get(fragmentParts[0]); if(!connection) { qWarning() << "Connection not found"; - return nullptr; + return new MxcReply(); } auto room = connection->room(fragmentParts[1]); if(!room) { qWarning() << "Room not found"; - return nullptr; + return new MxcReply(); } QNetworkRequest r(request); r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2").arg(connection->homeserver().toString(), mediaId))); @@ -88,7 +88,7 @@ QNetworkReply* NetworkAccessManager::createRequest( auto connection = AccountRegistry::instance().get(fragment); if(!connection) { qWarning() << "Connection not found"; - return nullptr; + return new MxcReply(); } QNetworkRequest r(request); r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2").arg(connection->homeserver().toString(), mediaId))); @@ -96,7 +96,7 @@ QNetworkReply* NetworkAccessManager::createRequest( return new MxcReply(reply); } else { qWarning() << "Invalid request"; - return nullptr; + return new MxcReply(); } } auto reply = QNetworkAccessManager::createRequest(op, request, outgoingData); -- cgit v1.2.3 From 4106a073d03b4ba9176da24c6b169c5fc2c79fb4 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 10 Sep 2021 22:10:16 +0200 Subject: Percent-encode all the things --- lib/networkaccessmanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index f37e26b6..dc1c139c 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -70,7 +70,7 @@ QNetworkReply* NetworkAccessManager::createRequest( const auto fragmentParts = fragment.split(QLatin1Char('/')); const auto mediaId = request.url().toString(QUrl::RemoveScheme | QUrl::RemoveFragment); if (fragmentParts.size() == 3) { - auto connection = AccountRegistry::instance().get(fragmentParts[0]); + auto connection = AccountRegistry::instance().get(QUrl::fromPercentEncoding(fragmentParts[0].toLatin1())); if(!connection) { qWarning() << "Connection not found"; return new MxcReply(); @@ -83,7 +83,7 @@ QNetworkReply* NetworkAccessManager::createRequest( QNetworkRequest r(request); r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2").arg(connection->homeserver().toString(), mediaId))); auto reply = createRequest(QNetworkAccessManager::GetOperation, r); - return new MxcReply(reply, room, fragmentParts[2]); + return new MxcReply(reply, room, QUrl::fromPercentEncoding(fragmentParts[2].toLatin1())); } else if(fragmentParts.size() == 1) { auto connection = AccountRegistry::instance().get(fragment); if(!connection) { @@ -113,10 +113,10 @@ QStringList NetworkAccessManager::supportedSchemesImplementation() const QUrl NetworkAccessManager::urlForRoomEvent(Room *room, const QString &eventId, const QString &mediaId) { - return QUrl(QStringLiteral("mxc:%1#%2/%3/%4").arg(mediaId, room->connection()->userId(), room->id(), eventId)); + return QUrl(QStringLiteral("mxc:%1#%2/%3/%4").arg(mediaId, QString(QUrl::toPercentEncoding(room->connection()->userId())), room->id(), QString(QUrl::toPercentEncoding(eventId)))); } QUrl NetworkAccessManager::urlForFile(Connection *connection, const QString &mediaId) { - return QUrl(QStringLiteral("mxc:%1#%2").arg(mediaId, connection->userId())); + return QUrl(QStringLiteral("mxc:%1#%2").arg(mediaId, QString(QUrl::toPercentEncoding(connection->userId())))); } \ No newline at end of file -- cgit v1.2.3 From df46414a4e16d608049610935aeabab222e06d72 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 10 Sep 2021 21:53:22 +0200 Subject: Add "quotient.network" logging category --- lib/logging.cpp | 1 + lib/logging.h | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/logging.cpp b/lib/logging.cpp index ffcc851c..15eac69d 100644 --- a/lib/logging.cpp +++ b/lib/logging.cpp @@ -17,4 +17,5 @@ LOGGING_CATEGORY(E2EE, "quotient.e2ee") LOGGING_CATEGORY(JOBS, "quotient.jobs") LOGGING_CATEGORY(SYNCJOB, "quotient.jobs.sync") LOGGING_CATEGORY(THUMBNAILJOB, "quotient.jobs.thumbnail") +LOGGING_CATEGORY(NETWORK, "quotient.network") LOGGING_CATEGORY(PROFILER, "quotient.profiler") diff --git a/lib/logging.h b/lib/logging.h index 1d1394e8..7e0da975 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -17,6 +17,7 @@ Q_DECLARE_LOGGING_CATEGORY(E2EE) Q_DECLARE_LOGGING_CATEGORY(JOBS) Q_DECLARE_LOGGING_CATEGORY(SYNCJOB) Q_DECLARE_LOGGING_CATEGORY(THUMBNAILJOB) +Q_DECLARE_LOGGING_CATEGORY(NETWORK) Q_DECLARE_LOGGING_CATEGORY(PROFILER) namespace Quotient { -- cgit v1.2.3 From 2bf18a64d236c2364e12d4c2f1a9464cc6a2ebf9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 10 Sep 2021 22:38:10 +0200 Subject: Move URL creation to Room/Connection; use query instead of fragment The query is easier to manipulate; and the original mxc URL is not used for the real network request anyway. --- lib/connection.cpp | 9 ++++ lib/connection.h | 2 + lib/networkaccessmanager.cpp | 97 ++++++++++++++++++++++++-------------------- lib/networkaccessmanager.h | 3 +- lib/room.cpp | 11 +++++ lib/room.h | 3 ++ 6 files changed, 80 insertions(+), 45 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 222c3b71..51946b2f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -836,6 +836,15 @@ inline auto splitMediaId(const QString& mediaId) return idParts; } +QUrl Connection::makeMediaUrl(QUrl mxcUrl) const +{ + Q_ASSERT(mxcUrl.scheme() == "mxc"); + QUrlQuery q(mxcUrl.query()); + q.addQueryItem(QStringLiteral("user_id"), userId()); + mxcUrl.setQuery(q); + return mxcUrl; +} + MediaThumbnailJob* Connection::getThumbnail(const QString& mediaId, QSize requestedSize, RunningPolicy policy) diff --git a/lib/connection.h b/lib/connection.h index ecbb1a19..1a6ca9b0 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -529,6 +529,8 @@ public Q_SLOTS: void stopSync(); QString nextBatchToken() const; + Q_INVOKABLE QUrl makeMediaUrl(QUrl mxcUrl) const; + virtual MediaThumbnailJob* getThumbnail(const QString& mediaId, QSize requestedSize, RunningPolicy policy = BackgroundRequest); diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index dc1c139c..3b0dc92b 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -3,24 +3,43 @@ #include "networkaccessmanager.h" -#include -#include -#include +#include "connection.h" +#include "room.h" #include "accountregistry.h" #include "mxcreply.h" -#include "connection.h" -#include "room.h" +#include +#include +#include +#include using namespace Quotient; class NetworkAccessManager::Private { public: + explicit Private(NetworkAccessManager* q) + : q(q) + {} + + QNetworkReply* createImplRequest(Operation op, + const QNetworkRequest& outerRequest, + Connection* connection) + { + Q_ASSERT(outerRequest.url().scheme() == "mxc"); + QNetworkRequest r(outerRequest); + r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2") + .arg(connection->homeserver().toString(), + outerRequest.url().authority() + + outerRequest.url().path()))); + return q->createRequest(op, r); + } + + NetworkAccessManager* q; QList ignoredSslErrors; }; NetworkAccessManager::NetworkAccessManager(QObject* parent) - : QNetworkAccessManager(parent), d(std::make_unique()) + : QNetworkAccessManager(parent), d(std::make_unique(this)) {} QList NetworkAccessManager::ignoredSslErrors() const @@ -54,6 +73,8 @@ static NetworkAccessManager* createNam() NetworkAccessManager* NetworkAccessManager::instance() { static QThreadStorage storage; + // FIXME: createNam() returns an object parented to + // QCoreApplication::instance() that lives in the main thread if(!storage.hasLocalData()) { storage.setLocalData(createNam()); } @@ -65,38 +86,38 @@ NetworkAccessManager::~NetworkAccessManager() = default; QNetworkReply* NetworkAccessManager::createRequest( Operation op, const QNetworkRequest& request, QIODevice* outgoingData) { - if(request.url().scheme() == QStringLiteral("mxc")) { - const auto fragment = request.url().fragment(); - const auto fragmentParts = fragment.split(QLatin1Char('/')); - const auto mediaId = request.url().toString(QUrl::RemoveScheme | QUrl::RemoveFragment); - if (fragmentParts.size() == 3) { - auto connection = AccountRegistry::instance().get(QUrl::fromPercentEncoding(fragmentParts[0].toLatin1())); - if(!connection) { - qWarning() << "Connection not found"; + const auto& mxcUrl = request.url(); + if (mxcUrl.scheme() == "mxc") { + const QUrlQuery query(mxcUrl.query()); + const auto accountId = query.queryItemValue(QStringLiteral("user_id")); + if (accountId.isEmpty()) { + // Using QSettings here because Quotient::NetworkSettings + // doesn't provide multithreading guarantees + static thread_local QSettings s; + if (!s.value("Network/allow_direct_media_requests").toBool()) { return new MxcReply(); } - auto room = connection->room(fragmentParts[1]); - if(!room) { - qWarning() << "Room not found"; + // TODO: Make the best effort with a direct unauthenticated request + // to the media server + } else { + auto* const connection = AccountRegistry::instance().get(accountId); + if (!connection) { + qCWarning(NETWORK) << "Connection not found"; return new MxcReply(); } - QNetworkRequest r(request); - r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2").arg(connection->homeserver().toString(), mediaId))); - auto reply = createRequest(QNetworkAccessManager::GetOperation, r); - return new MxcReply(reply, room, QUrl::fromPercentEncoding(fragmentParts[2].toLatin1())); - } else if(fragmentParts.size() == 1) { - auto connection = AccountRegistry::instance().get(fragment); - if(!connection) { - qWarning() << "Connection not found"; - return new MxcReply(); + const auto roomId = query.queryItemValue(QStringLiteral("room_id")); + if (!roomId.isEmpty()) { + auto room = connection->room(roomId); + if (!room) { + qCWarning(NETWORK) << "Room not found"; + return new MxcReply(); + } + return new MxcReply( + d->createImplRequest(op, request, connection), room, + query.queryItemValue(QStringLiteral("event_id"))); } - QNetworkRequest r(request); - r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2").arg(connection->homeserver().toString(), mediaId))); - auto reply = createRequest(QNetworkAccessManager::GetOperation, r); - return new MxcReply(reply); - } else { - qWarning() << "Invalid request"; - return new MxcReply(); + return new MxcReply( + d->createImplRequest(op, request, connection)); } } auto reply = QNetworkAccessManager::createRequest(op, request, outgoingData); @@ -110,13 +131,3 @@ QStringList NetworkAccessManager::supportedSchemesImplementation() const schemes += QStringLiteral("mxc"); return schemes; } - -QUrl NetworkAccessManager::urlForRoomEvent(Room *room, const QString &eventId, const QString &mediaId) -{ - return QUrl(QStringLiteral("mxc:%1#%2/%3/%4").arg(mediaId, QString(QUrl::toPercentEncoding(room->connection()->userId())), room->id(), QString(QUrl::toPercentEncoding(eventId)))); -} - -QUrl NetworkAccessManager::urlForFile(Connection *connection, const QString &mediaId) -{ - return QUrl(QStringLiteral("mxc:%1#%2").arg(mediaId, QString(QUrl::toPercentEncoding(connection->userId())))); -} \ No newline at end of file diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 5d262f98..87bc12a1 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -25,8 +25,7 @@ public: public Q_SLOTS: QStringList supportedSchemesImplementation() const; - QUrl urlForRoomEvent(Room *room, const QString &eventId, const QString &mediaId); - QUrl urlForFile(Connection *connection, const QString &mediaId); + private: QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData = Q_NULLPTR) override; diff --git a/lib/room.cpp b/lib/room.cpp index 890da13d..72b37f62 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1116,6 +1116,17 @@ QList Room::directChatUsers() const return connection()->directChatUsers(this); } +QUrl Room::makeMediaUrl(const QString& eventId, const QUrl& mxcUrl) const +{ + auto url = connection()->makeMediaUrl(mxcUrl); + QUrlQuery q(url.query()); + Q_ASSERT(q.hasQueryItem("user_id")); + q.addQueryItem("room_id", id()); + q.addQueryItem("event_id", eventId); + url.setQuery(q); + return url; +} + QString safeFileName(QString rawName) { return rawName.replace(QRegularExpression("[/\\<>|\"*?:]"), "_"); diff --git a/lib/room.h b/lib/room.h index d3a7466d..9daca076 100644 --- a/lib/room.h +++ b/lib/room.h @@ -458,6 +458,9 @@ public: /// Get the list of users this room is a direct chat with QList directChatUsers() const; + Q_INVOKABLE QUrl makeMediaUrl(const QString& eventId, + const QUrl &mxcUrl) const; + Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId) const; Q_INVOKABLE QUrl urlToDownload(const QString& eventId) const; -- cgit v1.2.3 From 4babd9b2f1ba1d8c8c58c2f728cc4875ecf144c7 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 11 Sep 2021 11:14:04 +0200 Subject: Don't parent NAM to QCoreApplication QThreadStorage accepts ownership over stored objects. --- lib/networkaccessmanager.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 3b0dc92b..d35b2ec8 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -59,7 +59,7 @@ void NetworkAccessManager::clearIgnoredSslErrors() static NetworkAccessManager* createNam() { - auto nam = new NetworkAccessManager(QCoreApplication::instance()); + auto nam = new NetworkAccessManager(); #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) // See #109; in newer Qt, bearer management is deprecated altogether NetworkAccessManager::connect(nam, @@ -73,8 +73,6 @@ static NetworkAccessManager* createNam() NetworkAccessManager* NetworkAccessManager::instance() { static QThreadStorage storage; - // FIXME: createNam() returns an object parented to - // QCoreApplication::instance() that lives in the main thread if(!storage.hasLocalData()) { storage.setLocalData(createNam()); } -- cgit v1.2.3 From 6597866ead7a3eb03cfcbbd99b547de1bb72867e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 11 Sep 2021 13:05:40 +0200 Subject: Further tweaks to MxcReply - QNetworkReply::isSequential() already returns `true`, there's no need to overload it again. - Use `Q_SLOTS` instead of `slots` because it's an external library interface and clients may use other libraries using `slots` identifier; - Use `emit` instead of `Q_EMIT` because this is a part of internal implementation and if we ever use a library that has an `emit` identifier, a massive search-replace will be in order anyway. - Use `QMetaObject::invokeMethod()` with a queued connection as a clearer way to achieve the same goal as `QTimer::singleShot(0, ...)`. --- lib/mxcreply.cpp | 37 +++++++++++++++++-------------------- lib/mxcreply.h | 15 +++++++-------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 7819367e..daa4af9a 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -3,12 +3,7 @@ #include "mxcreply.h" -#include -#include -#include "connection.h" #include "room.h" -#include "networkaccessmanager.h" -#include "events/stickerevent.h" using namespace Quotient; @@ -34,30 +29,32 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) { reply->setParent(this); d->m_reply = reply; - connect(d->m_reply, &QNetworkReply::finished, this, [this, eventId]() { + connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { setError(d->m_reply->error(), d->m_reply->errorString()); setOpenMode(ReadOnly); - Q_EMIT finished(); + emit finished(); }); } -MxcReply::MxcReply() -{ - QTimer::singleShot(0, this, [this](){ - setError(QNetworkReply::ProtocolInvalidOperationError, QStringLiteral("Invalid Request")); - setFinished(true); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - Q_EMIT errorOccurred(QNetworkReply::ProtocolInvalidOperationError); +#define ERROR_SIGNAL errorOccurred #else - Q_EMIT error(QNetworkReply::ProtocolInvalidOperationError); +#define ERROR_SIGNAL error #endif - Q_EMIT finished(); - }); -} -bool MxcReply::isSequential() const +MxcReply::MxcReply() { - return true; + static const auto BadRequestPhrase = tr("Bad Request"); + QMetaObject::invokeMethod(this, [this]() { + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 400); + setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, + BadRequestPhrase); + setError(QNetworkReply::ProtocolInvalidOperationError, + BadRequestPhrase); + setFinished(true); + emit ERROR_SIGNAL(QNetworkReply::ProtocolInvalidOperationError); + emit finished(); + }, Qt::QueuedConnection); } qint64 MxcReply::readData(char *data, qint64 maxSize) @@ -68,4 +65,4 @@ qint64 MxcReply::readData(char *data, qint64 maxSize) void MxcReply::abort() { d->m_reply->abort(); -} \ No newline at end of file +} diff --git a/lib/mxcreply.h b/lib/mxcreply.h index ac3ac4f4..efaf01c6 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -8,23 +8,22 @@ namespace Quotient { class Room; -class Connection; + class MxcReply : public QNetworkReply { public: - MxcReply(QNetworkReply* reply, Room* room, const QString &eventId); - MxcReply(QNetworkReply* reply); - MxcReply(); - - bool isSequential() const override; + explicit MxcReply(); + explicit MxcReply(QNetworkReply *reply); + MxcReply(QNetworkReply* reply, Room* room, const QString& eventId); -public slots: +public Q_SLOTS: void abort() override; protected: qint64 readData(char *data, qint64 maxSize) override; + private: class Private; std::unique_ptr d; }; -} \ No newline at end of file +} -- cgit v1.2.3 From 9da6b25a26403952e5a76b043076ba302c8d3c30 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 11 Sep 2021 20:35:00 +0200 Subject: BaseJob: deprecate endpoint accessors; query returns an object To provide more room for internal changes in BaseJob. --- lib/jobs/basejob.cpp | 24 ++++++++++-------------- lib/jobs/basejob.h | 4 +++- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 6346db9d..85066024 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -194,12 +194,12 @@ QUrl BaseJob::requestUrl() const { return d->reply ? d->reply->url() : QUrl(); } bool BaseJob::isBackground() const { return d->inBackground; } -const QString& BaseJob::apiEndpoint() const { return d->apiEndpoint; } +//const QString& BaseJob::apiEndpoint() const { return d->apiUrl.path(); } -void BaseJob::setApiEndpoint(const QString& apiEndpoint) -{ - d->apiEndpoint = apiEndpoint; -} +//void BaseJob::setApiEndpoint(const QString& apiEndpoint) +//{ +// d->apiEndpoint = apiEndpoint; +//} const BaseJob::headers_t& BaseJob::requestHeaders() const { @@ -217,7 +217,7 @@ void BaseJob::setRequestHeaders(const BaseJob::headers_t& headers) d->requestHeaders = headers; } -const QUrlQuery& BaseJob::query() const { return d->requestQuery; } +QUrlQuery BaseJob::query() const { return d->requestQuery; } void BaseJob::setRequestQuery(const QUrlQuery& query) { @@ -262,14 +262,10 @@ QNetworkReply* BaseJob::reply() { return d->reply.data(); } QUrl BaseJob::makeRequestUrl(QUrl baseUrl, const QString& path, const QUrlQuery& query) { - auto pathBase = baseUrl.path(); - // QUrl::adjusted(QUrl::StripTrailingSlashes) doesn't help with root '/' - while (pathBase.endsWith('/')) - pathBase.chop(1); - if (!path.startsWith('/')) // Normally API files do start with '/' - pathBase.push_back('/'); // so this shouldn't be needed these days - - baseUrl.setPath(pathBase + path, QUrl::TolerantMode); + // Make sure the added path is relative even if it's not (the official + // API definitions have the leading slash though it's not really correct). + baseUrl = baseUrl.resolved( + QUrl(path.mid(path.startsWith('/')), QUrl::TolerantMode)); baseUrl.setQuery(query); return baseUrl; } diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 29443c53..663c121c 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -325,13 +325,15 @@ Q_SIGNALS: protected: using headers_t = QHash; + Q_DECL_DEPRECATED_X("Deprecated due to being unused") const QString& apiEndpoint() const; + Q_DECL_DEPRECATED_X("Deprecated due to being unused") void setApiEndpoint(const QString& apiEndpoint); const headers_t& requestHeaders() const; void setRequestHeader(const headers_t::key_type& headerName, const headers_t::mapped_type& headerValue); void setRequestHeaders(const headers_t& headers); - const QUrlQuery& query() const; + QUrlQuery query() const; void setRequestQuery(const QUrlQuery& query); const RequestData& requestData() const; void setRequestData(RequestData&& data); -- cgit v1.2.3 From 0209be8305aa38722a3d25593ae71fbb3ac05e52 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 11 Sep 2021 16:18:35 +0200 Subject: Add convenience function for activating encryption and fix EncryptionEvent constructor --- lib/events/encryptionevent.cpp | 8 ++++++++ lib/events/encryptionevent.h | 4 +--- lib/room.cpp | 9 +++++++++ lib/room.h | 6 ++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 490a5e8a..aa05a96e 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -39,6 +39,14 @@ EncryptionEventContent::EncryptionEventContent(const QJsonObject& json) , rotationPeriodMsgs(json[RotationPeriodMsgsKeyL].toInt(100)) {} +EncryptionEventContent::EncryptionEventContent(EncryptionType et) + : encryption(et) +{ + if(encryption != Undefined) { + algorithm = encryptionStrings[encryption]; + } +} + void EncryptionEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 65ee4187..14439fcc 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -12,9 +12,7 @@ class EncryptionEventContent : public EventContent::Base { public: enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; - explicit EncryptionEventContent(EncryptionType et = Undefined) - : encryption(et) - {} + explicit EncryptionEventContent(EncryptionType et = Undefined); explicit EncryptionEventContent(const QJsonObject& json); EncryptionType encryption; diff --git a/lib/room.cpp b/lib/room.cpp index c6cca2ea..ced313a4 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -3009,3 +3009,12 @@ bool MemberSorter::operator()(User* u1, QStringView u2name) const return n1.localeAwareCompare(n2) < 0; } + +void Room::activateEncryption() +{ + if(usesEncryption()) { + qCWarning(E2EE) << "Room" << objectName() << "is already encrypted"; + return; + } + setState(EncryptionEventContent::MegolmV1AesSha2); +} diff --git a/lib/room.h b/lib/room.h index 4e55dbaf..c18d0b36 100644 --- a/lib/room.h +++ b/lib/room.h @@ -604,6 +604,12 @@ public Q_SLOTS: void answerCall(const QString& callId, const QString& sdp); void hangupCall(const QString& callId); + /** + * Activates encryption for this room. + * Warning: Cannot be undone + */ + void activateEncryption(); + Q_SIGNALS: /// Initial set of state events has been loaded /** -- cgit v1.2.3 From 693b5da2920f173a9e3f723b845d35a7b4aa9823 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 12 Sep 2021 04:35:43 +0200 Subject: Add a download test to quotest --- quotest/CMakeLists.txt | 3 ++- quotest/quotest.cpp | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/quotest/CMakeLists.txt b/quotest/CMakeLists.txt index bf9af796..59334e30 100644 --- a/quotest/CMakeLists.txt +++ b/quotest/CMakeLists.txt @@ -4,8 +4,9 @@ set(quotest_SRCS quotest.cpp) +find_package(${Qt} COMPONENTS Concurrent) add_executable(quotest ${quotest_SRCS}) -target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${PROJECT_NAME}) +target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${Qt}::Concurrent ${PROJECT_NAME}) option(${PROJECT_NAME}_INSTALL_TESTS "install quotest (former qmc-example) application" ON) add_feature_info(InstallQuotest ${PROJECT_NAME}_INSTALL_TESTS diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index ec7d4dcb..3f886676 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -5,6 +5,7 @@ #include "room.h" #include "user.h" #include "uriresolver.h" +#include "networkaccessmanager.h" #include "csapi/joining.h" #include "csapi/leaving.h" @@ -20,6 +21,8 @@ #include #include #include +#include +#include #include #include @@ -435,6 +438,27 @@ TEST_IMPL(sendFile) return false; } +bool testDownload(const QUrl& url) +{ + // Move out actual test from the multithreaded code + // to help debugging + auto results = + QtConcurrent::blockingMapped(QVector { 1, 2, 3 }, [url](int) { + QEventLoop el; + auto reply = + NetworkAccessManager::instance()->get(QNetworkRequest(url)); + QObject::connect( + reply, &QNetworkReply::finished, &el, [&el] { el.exit(); }, + Qt::QueuedConnection); + el.exec(); + return reply->error(); + }); + return std::all_of(results.cbegin(), results.cend(), + [](QNetworkReply::NetworkError ne) { + return ne == QNetworkReply::NoError; + }); +} + bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, const QString& txnId, const QString& fileName) @@ -465,14 +489,15 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, return visit( *evt, [&](const RoomMessageEvent& e) { - // TODO: actually try to download it to check, e.g., #366 - // (and #368 would help to test against bad file names). + // TODO: check #366 once #368 is implemented FINISH_TEST( !e.id().isEmpty() - && pendingEvents[size_t(pendingIdx)]->transactionId() - == txnId - && e.hasFileContent() - && e.content()->fileInfo()->originalName == fileName); + && pendingEvents[size_t(pendingIdx)]->transactionId() + == txnId + && e.hasFileContent() + && e.content()->fileInfo()->originalName == fileName + && testDownload(targetRoom->connection()->makeMediaUrl( + e.content()->fileInfo()->url))); }, [this, thisTest](const RoomEvent&) { FAIL_TEST(); }); }); -- cgit v1.2.3 From 2c61b463fa0608626a58aed79ebecb3bbd8c41d3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 12 Sep 2021 04:36:13 +0200 Subject: NAM::createRequest(): more logging --- lib/networkaccessmanager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index d35b2ec8..57618329 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -93,6 +93,7 @@ QNetworkReply* NetworkAccessManager::createRequest( // doesn't provide multithreading guarantees static thread_local QSettings s; if (!s.value("Network/allow_direct_media_requests").toBool()) { + qCWarning(NETWORK) << "No connection specified"; return new MxcReply(); } // TODO: Make the best effort with a direct unauthenticated request @@ -100,14 +101,14 @@ QNetworkReply* NetworkAccessManager::createRequest( } else { auto* const connection = AccountRegistry::instance().get(accountId); if (!connection) { - qCWarning(NETWORK) << "Connection not found"; + qCWarning(NETWORK) << "Connection" << accountId << "not found"; return new MxcReply(); } const auto roomId = query.queryItemValue(QStringLiteral("room_id")); if (!roomId.isEmpty()) { auto room = connection->room(roomId); if (!room) { - qCWarning(NETWORK) << "Room not found"; + qCWarning(NETWORK) << "Room" << roomId << "not found"; return new MxcReply(); } return new MxcReply( -- cgit v1.2.3 From 77b69eb370e1cdbc33e44121f4f8483e19cad7d8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 12 Sep 2021 04:37:08 +0200 Subject: MxcReply: make sure to create a Private object --- lib/mxcreply.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index daa4af9a..0b6643fc 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -10,13 +10,16 @@ using namespace Quotient; class MxcReply::Private { public: - QNetworkReply *m_reply = nullptr; + explicit Private(QNetworkReply* r = nullptr) + : m_reply(r) + {} + QNetworkReply* m_reply; }; MxcReply::MxcReply(QNetworkReply* reply) + : d(std::make_unique(reply)) { reply->setParent(this); - d->m_reply = reply; connect(d->m_reply, &QNetworkReply::finished, this, [this]() { setError(d->m_reply->error(), d->m_reply->errorString()); setOpenMode(ReadOnly); @@ -25,10 +28,9 @@ MxcReply::MxcReply(QNetworkReply* reply) } MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) - : d(std::make_unique()) + : d(std::make_unique(reply)) { reply->setParent(this); - d->m_reply = reply; connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { setError(d->m_reply->error(), d->m_reply->errorString()); setOpenMode(ReadOnly); -- cgit v1.2.3 From 06a4fbb5c0ad0fadba1e5924f73d067850a78312 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 12 Sep 2021 04:38:23 +0200 Subject: Connection: update AccountRegistry Clients don't need to do it themselves. --- lib/connection.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 51946b2f..4abf5097 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -13,6 +13,7 @@ #include "room.h" #include "settings.h" #include "user.h" +#include "accountregistry.h" // NB: since Qt 6, moc_connection.cpp needs Room and User fully defined #include "moc_connection.cpp" @@ -258,6 +259,7 @@ Connection::~Connection() { qCDebug(MAIN) << "deconstructing connection object for" << userId(); stopSync(); + AccountRegistry::instance().drop(this); } void Connection::resolveServer(const QString& mxid) @@ -441,6 +443,7 @@ void Connection::Private::completeSetup(const QString& mxId) qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << data->userId() << "from device" << data->deviceId(); + AccountRegistry::instance().add(q); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED -- cgit v1.2.3 From 167509514587aa22d837b42a8d30d7c1128e0a45 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 12 Sep 2021 04:52:41 +0200 Subject: Fix building with older Qt --- quotest/quotest.cpp | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 3f886676..31a0b6d6 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -438,21 +438,30 @@ TEST_IMPL(sendFile) return false; } +// Can be replaced with a lambda once QtConcurrent is able to resolve return +// types from lambda invocations (Qt 6 can, not sure about earlier) +struct DownloadRunner { + QUrl url; + + using result_type = QNetworkReply::NetworkError; + + QNetworkReply::NetworkError operator()(int) const { + QEventLoop el; + auto reply = NetworkAccessManager::instance()->get(QNetworkRequest(url)); + QObject::connect( + reply, &QNetworkReply::finished, &el, [&el] { el.exit(); }, + Qt::QueuedConnection); + el.exec(); + return reply->error(); + } +}; + bool testDownload(const QUrl& url) { // Move out actual test from the multithreaded code // to help debugging - auto results = - QtConcurrent::blockingMapped(QVector { 1, 2, 3 }, [url](int) { - QEventLoop el; - auto reply = - NetworkAccessManager::instance()->get(QNetworkRequest(url)); - QObject::connect( - reply, &QNetworkReply::finished, &el, [&el] { el.exit(); }, - Qt::QueuedConnection); - el.exec(); - return reply->error(); - }); + auto results = QtConcurrent::blockingMapped(QVector { 1, 2, 3 }, + DownloadRunner { url }); return std::all_of(results.cbegin(), results.cend(), [](QNetworkReply::NetworkError ne) { return ne == QNetworkReply::NoError; -- cgit v1.2.3 From bcaab611840a0a2ad284e6f1e7c2f0b4de10222d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 12 Sep 2021 05:22:53 +0200 Subject: Fix a memory leak in DownloadRunner --- quotest/quotest.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 31a0b6d6..4142c718 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -445,11 +445,14 @@ struct DownloadRunner { using result_type = QNetworkReply::NetworkError; - QNetworkReply::NetworkError operator()(int) const { + QNetworkReply::NetworkError operator()(int) const + { QEventLoop el; - auto reply = NetworkAccessManager::instance()->get(QNetworkRequest(url)); + QScopedPointer reply { + NetworkAccessManager::instance()->get(QNetworkRequest(url)) + }; QObject::connect( - reply, &QNetworkReply::finished, &el, [&el] { el.exit(); }, + reply.data(), &QNetworkReply::finished, &el, [&el] { el.exit(); }, Qt::QueuedConnection); el.exec(); return reply->error(); -- cgit v1.2.3 From 59c9ca720093f2931c2eee1c0d5806d7e2e0c85f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 13 Sep 2021 16:05:46 +0200 Subject: Add room types to RoomCreateEvent --- lib/events/roomcreateevent.cpp | 21 +++++++++++++++++++++ lib/events/roomcreateevent.h | 2 ++ lib/quotient_common.h | 10 ++++++++++ 3 files changed, 33 insertions(+) diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index 6558bade..ff93041c 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -5,6 +5,22 @@ using namespace Quotient; +template <> +struct Quotient::JsonConverter { + static RoomType load(const QJsonValue& jv) + { + const auto& roomTypeString = jv.toString(); + for (auto it = RoomTypeStrings.begin(); it != RoomTypeStrings.end(); + ++it) + if (roomTypeString == *it) + return RoomType(it - RoomTypeStrings.begin()); + + if (!roomTypeString.isEmpty()) + qCWarning(EVENTS) << "Unknown Room Type: " << roomTypeString; + return RoomType::Undefined; + } +}; + bool RoomCreateEvent::isFederated() const { return fromJson(contentJson()["m.federate"_ls]); @@ -26,3 +42,8 @@ bool RoomCreateEvent::isUpgrade() const { return contentJson().contains("predecessor"_ls); } + +RoomType RoomCreateEvent::roomType() const +{ + return fromJson(contentJson()["type"_ls]); +} diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 05e623ed..b3ad287c 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -4,6 +4,7 @@ #pragma once #include "stateevent.h" +#include "quotient_common.h" namespace Quotient { class RoomCreateEvent : public StateEventBase { @@ -24,6 +25,7 @@ public: QString version() const; Predecessor predecessor() const; bool isUpgrade() const; + RoomType roomType() const; }; REGISTER_EVENT_TYPE(RoomCreateEvent) } // namespace Quotient diff --git a/lib/quotient_common.h b/lib/quotient_common.h index bb2e6a6b..4444a111 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -88,6 +88,16 @@ enum UriResolveResult : short { }; Q_ENUM_NS(UriResolveResult) +enum RoomType { + Space, + Undefined, +}; +Q_ENUM_NS(RoomType); + +constexpr inline auto RoomTypeStrings = make_array( + "m.space" +); + } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates) -- cgit v1.2.3 From fe9425f313e7c172095ff9355743427337b7ea78 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 13 Sep 2021 19:14:19 +0200 Subject: Add the encryptedfile to the eventcontent --- lib/events/encryptedfile.h | 88 +++++++++++++++++++++++++++++++++++++++++ lib/events/eventcontent.cpp | 20 +++++++--- lib/events/eventcontent.h | 17 ++++++-- lib/events/roomavatarevent.h | 2 +- lib/events/roommessageevent.cpp | 8 ++-- 5 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 lib/events/encryptedfile.h diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h new file mode 100644 index 00000000..24ac9de1 --- /dev/null +++ b/lib/events/encryptedfile.h @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPl-2.1-or-later + +#pragma once + +#include "converters.h" + +namespace Quotient { +/** + * JSON Web Key object as specified in + * https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes + * The only currently relevant member is `k`, the rest needs to be set to the defaults specified in the spec. + */ +struct JWK +{ + Q_GADGET + Q_PROPERTY(QString kty MEMBER kty CONSTANT) + Q_PROPERTY(QStringList keyOps MEMBER keyOps CONSTANT) + Q_PROPERTY(QString alg MEMBER alg CONSTANT) + Q_PROPERTY(QString k MEMBER k CONSTANT) + Q_PROPERTY(bool ext MEMBER ext CONSTANT) + +public: + QString kty; + QStringList keyOps; + QString alg; + QString k; + bool ext; +}; + +struct EncryptedFile +{ + Q_GADGET + Q_PROPERTY(QUrl url MEMBER url CONSTANT) + Q_PROPERTY(JWK key MEMBER key CONSTANT) + Q_PROPERTY(QString iv MEMBER iv CONSTANT) + Q_PROPERTY(QHash hashes MEMBER hashes CONSTANT) + Q_PROPERTY(QString v MEMBER v CONSTANT) + +public: + QUrl url; + JWK key; + QString iv; + QHash hashes; + QString v; +}; + +template <> +struct JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const EncryptedFile& pod) + { + addParam<>(jo, QStringLiteral("url"), pod.url); + addParam<>(jo, QStringLiteral("key"), pod.key); + addParam<>(jo, QStringLiteral("iv"), pod.iv); + addParam<>(jo, QStringLiteral("hashes"), pod.hashes); + addParam<>(jo, QStringLiteral("v"), pod.v); + } + static void fillFrom(const QJsonObject& jo, EncryptedFile& pod) + { + fromJson(jo.value("url"_ls), pod.url); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("iv"_ls), pod.iv); + fromJson(jo.value("hashes"_ls), pod.hashes); + fromJson(jo.value("v"_ls), pod.v); + } +}; + +template <> +struct JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const JWK& pod) + { + addParam<>(jo, QStringLiteral("kty"), pod.kty); + addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); + addParam<>(jo, QStringLiteral("alg"), pod.alg); + addParam<>(jo, QStringLiteral("k"), pod.k); + addParam<>(jo, QStringLiteral("ext"), pod.ext); + } + static void fillFrom(const QJsonObject& jo, JWK& pod) + { + fromJson(jo.value("kty"_ls), pod.kty); + fromJson(jo.value("key_ops"_ls), pod.keyOps); + fromJson(jo.value("alg"_ls), pod.alg); + fromJson(jo.value("k"_ls), pod.k); + fromJson(jo.value("ext"_ls), pod.ext); + } +}; +} // namespace Quotient diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 1f28f195..22878d4c 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -30,11 +30,12 @@ FileInfo::FileInfo(const QFileInfo &fi) } FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, - QString originalFilename) + Omittable file, QString originalFilename) : mimeType(mimeType) , url(move(u)) , payloadSize(payloadSize) , originalName(move(originalFilename)) + , file(file) { if (!isValid()) qCWarning(MESSAGES) @@ -44,6 +45,7 @@ FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, } FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + const Omittable &file, QString originalFilename) : originalInfoJson(infoJson) , mimeType( @@ -51,7 +53,11 @@ FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, , url(move(mxcUrl)) , payloadSize(fromJson(infoJson["size"_ls])) , originalName(move(originalFilename)) + , file(file) { + if(url.isEmpty() && file.has_value()) { + url = file->url; + } if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } @@ -76,14 +82,15 @@ ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) {} ImageInfo::ImageInfo(const QUrl& mxcUrl, qint64 fileSize, const QMimeType& type, - QSize imageSize, const QString& originalFilename) - : FileInfo(mxcUrl, fileSize, type, originalFilename) + QSize imageSize, const Omittable &file, const QString& originalFilename) + : FileInfo(mxcUrl, fileSize, type, file, originalFilename) , imageSize(imageSize) {} ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, + const Omittable &file, const QString& originalFilename) - : FileInfo(mxcUrl, infoJson, originalFilename) + : FileInfo(mxcUrl, infoJson, file, originalFilename) , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) {} @@ -96,9 +103,10 @@ void ImageInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("h"), imageSize.height()); } -Thumbnail::Thumbnail(const QJsonObject& infoJson) +Thumbnail::Thumbnail(const QJsonObject& infoJson, const Omittable &file) : ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()), - infoJson["thumbnail_info"_ls].toObject()) + infoJson["thumbnail_info"_ls].toObject(), + file) {} void Thumbnail::fillInfoJson(QJsonObject* infoJson) const diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 40ec3a49..f609a603 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -12,6 +12,8 @@ #include #include +#include "encryptedfile.h" + class QFileInfo; namespace Quotient { @@ -80,8 +82,10 @@ namespace EventContent { explicit FileInfo(const QFileInfo& fi); explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1, const QMimeType& mimeType = {}, + Omittable file = none, QString originalFilename = {}); FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + const Omittable &file, QString originalFilename = {}); bool isValid() const; @@ -103,6 +107,7 @@ namespace EventContent { QUrl url; qint64 payloadSize; QString originalName; + Omittable file = none; }; template @@ -122,8 +127,10 @@ namespace EventContent { explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); explicit ImageInfo(const QUrl& mxcUrl, qint64 fileSize = -1, const QMimeType& type = {}, QSize imageSize = {}, + const Omittable &file = none, const QString& originalFilename = {}); ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, + const Omittable &encryptedFile, const QString& originalFilename = {}); void fillInfoJson(QJsonObject* infoJson) const; @@ -142,7 +149,7 @@ namespace EventContent { class Thumbnail : public ImageInfo { public: Thumbnail() = default; // Allow empty thumbnails - Thumbnail(const QJsonObject& infoJson); + Thumbnail(const QJsonObject& infoJson, const Omittable &file = none); Thumbnail(const ImageInfo& info) : ImageInfo(info) {} using ImageInfo::ImageInfo; @@ -182,7 +189,7 @@ namespace EventContent { explicit UrlBasedContent(const QJsonObject& json) : TypedBase(json) , InfoT(QUrl(json["url"].toString()), json["info"].toObject(), - json["filename"].toString()) + fromJson>(json["file"]), json["filename"].toString()) { // A small hack to facilitate links creation in QML. originalJson.insert("mediaId", InfoT::mediaId()); @@ -196,7 +203,11 @@ namespace EventContent { void fillJson(QJsonObject* json) const override { Q_ASSERT(json); - json->insert("url", InfoT::url.toString()); + if (!InfoT::file.has_value()) { + json->insert("url", InfoT::url.toString()); + } else { + json->insert("file", Quotient::toJson(*InfoT::file)); + } if (!InfoT::originalName.isEmpty()) json->insert("filename", InfoT::originalName); json->insert("info", toInfoJson(*this)); diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index 3fa11a0f..8618ba31 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -25,7 +25,7 @@ public: const QSize& imageSize = {}, const QString& originalFilename = {}) : RoomAvatarEvent(EventContent::ImageContent { - mxcUrl, fileSize, mimeType, imageSize, originalFilename }) + mxcUrl, fileSize, mimeType, imageSize, none, originalFilename }) {} QUrl url() const { return content().url; } diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 71f85363..9b46594e 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -145,21 +145,21 @@ TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) auto mimeTypeName = mimeType.name(); if (mimeTypeName.startsWith("image/")) return new ImageContent(localUrl, file.size(), mimeType, - QImageReader(filePath).size(), + QImageReader(filePath).size(), none, file.fileName()); // duration can only be obtained asynchronously and can only be reliably // done by starting to play the file. Left for a future implementation. if (mimeTypeName.startsWith("video/")) return new VideoContent(localUrl, file.size(), mimeType, - QMediaResource(localUrl).resolution(), + QMediaResource(localUrl).resolution(), none, file.fileName()); if (mimeTypeName.startsWith("audio/")) - return new AudioContent(localUrl, file.size(), mimeType, + return new AudioContent(localUrl, file.size(), mimeType, none, file.fileName()); } - return new FileContent(localUrl, file.size(), mimeType, file.fileName()); + return new FileContent(localUrl, file.size(), mimeType, none, file.fileName()); } RoomMessageEvent::RoomMessageEvent(const QString& plainBody, -- cgit v1.2.3 From 9f43d34c590a825504b72be7f6b238d0ff2c915a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 12 Sep 2021 04:39:44 +0200 Subject: Use C++ instead of commenting --- quotest/quotest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 4142c718..3a77eb01 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -51,7 +51,7 @@ private: QByteArrayList running {}, succeeded {}, failed {}; }; -using TestToken = QByteArray; // return value of QMetaMethod::name +using TestToken = decltype(std::declval().name()); Q_DECLARE_METATYPE(TestToken) // For now, the token itself is the test name but that may change. -- cgit v1.2.3 From 92024f20fd8cfe4adb586b1d477969e45ba9ab4d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Sep 2021 08:41:06 +0200 Subject: SyncData: drop a shortcut that led to ignoring invites Fixes #510. --- lib/syncdata.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index d3c270b5..4edc9564 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -173,12 +173,7 @@ void SyncData::parseJson(const QJsonObject& json, const QString& baseDir) auto rooms = json.value("rooms"_ls).toObject(); auto totalRooms = 0; auto totalEvents = 0; - // The first comparison shortcuts the loop when not all states are there - // in the response (anything except "join" is only occasional, and "join" - // intentionally comes first in the enum). - for (size_t i = 0; - static_cast(i) < rooms.size() && i < JoinStateStrings.size(); ++i) - { + for (size_t i = 0; i < JoinStateStrings.size(); ++i) { // This assumes that MemberState values go over powers of 2: 1,2,4,... const auto joinState = JoinState(1U << i); const auto rs = rooms.value(JoinStateStrings[i]).toObject(); -- cgit v1.2.3 From 4c3efb82b9f47ca92973089413da994619ddeb5c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 03:18:39 +0200 Subject: AccountRegistry: minor code cleanup --- lib/accountregistry.cpp | 5 ++--- lib/accountregistry.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/accountregistry.cpp b/lib/accountregistry.cpp index 3a022f14..a292ed45 100644 --- a/lib/accountregistry.cpp +++ b/lib/accountregistry.cpp @@ -36,8 +36,7 @@ bool AccountRegistry::contains(Connection *c) const return m_accounts.contains(c); } -AccountRegistry::AccountRegistry() -{} +AccountRegistry::AccountRegistry() = default; QVariant AccountRegistry::data(const QModelIndex &index, int role) const { @@ -95,4 +94,4 @@ Connection* AccountRegistry::get(const QString& userId) } } return nullptr; -} \ No newline at end of file +} diff --git a/lib/accountregistry.h b/lib/accountregistry.h index e87da3e8..5efda459 100644 --- a/lib/accountregistry.h +++ b/lib/accountregistry.h @@ -42,4 +42,4 @@ private: QVector m_accounts; }; -} \ No newline at end of file +} -- cgit v1.2.3 From bd71b075e699ab4dda92d72bac77cbfeb6217625 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 3 Oct 2021 19:43:03 +0200 Subject: prettyPrint(): tighten up Matrix identifier regex It was too permissive on characters before the identifier and also allowed the domain name to start on dash, which should not occur. Closes #512. --- lib/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.cpp b/lib/util.cpp index 3de1d169..8067f561 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -44,7 +44,7 @@ void Quotient::linkifyUrls(QString& htmlEscapedText) // https://matrix.org/docs/spec/appendices.html#identifier-grammar static const QRegularExpression MxIdRegExp( QStringLiteral( - R"((^|[^<>/])([!#@][-a-z0-9_=#/.]{1,252}:(?:\w|\.|-)+\.\w+(?::\d{1,5})?))"), + R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"), RegExpOptions); Q_ASSERT(FullUrlRegExp.isValid() && EmailAddressRegExp.isValid() && MxIdRegExp.isValid()); -- cgit v1.2.3 From 062d534a0024959117e310f8c2a964434acb9fa0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 09:53:33 +0200 Subject: Add tests for prettyPrint() --- quotest/quotest.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 3a77eb01..44d82adf 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -108,6 +108,7 @@ private slots: TEST_DECL(addAndRemoveTag) TEST_DECL(markDirectChat) TEST_DECL(visitResources) + TEST_DECL(prettyPrintTests) // Add more tests above here public: @@ -140,7 +141,7 @@ private: // connectUntil() to break the QMetaObject::Connection upon finishing the test // item. #define FINISH_TEST(Condition) \ - return (finishTest(thisTest, Condition, __FILE__, __LINE__), true) + return (finishTest(thisTest, (Condition), __FILE__, __LINE__), true) #define FAIL_TEST() FINISH_TEST(false) @@ -824,6 +825,52 @@ TEST_IMPL(visitResources) FINISH_TEST(true); } +bool checkPrettyPrint( + std::initializer_list> tests) +{ + bool result = true; + for (const auto& [test, etalon] : tests) { + const auto is = prettyPrint(test).toStdString(); + const auto shouldBe = std::string("") + + etalon + ""; + if (is == shouldBe) + continue; + clog << is << " != " << shouldBe << endl; + result = false; + } + return result; +} + +TEST_IMPL(prettyPrintTests) +{ + const bool prettyPrintTestResult = checkPrettyPrint( + { { "https://www.matrix.org", + R"(https://www.matrix.org)" }, +// { "www.matrix.org", // Doesn't work yet +// R"(www.matrix.org)" }, + { "smb://somewhere/file", "smb://somewhere/file" }, // Disallowed scheme + { "https:/something", "https:/something" }, // Malformed URL + { "https://matrix.to/#/!roomid:example.org", + R"(https://matrix.to/#/!roomid:example.org)" }, + { "https://matrix.to/#/@user_id:example.org", + R"(https://matrix.to/#/@user_id:example.org)" }, + { "https://matrix.to/#/#roomalias:example.org", + R"(https://matrix.to/#/#roomalias:example.org)" }, + { "https://matrix.to/#/##ircroomalias:example.org", + R"(https://matrix.to/#/##ircroomalias:example.org)" }, + { "me@example.org", + R"(me@example.org)" }, + { "mailto:me@example.org", + R"(mailto:me@example.org)" }, + { "!room_id:example.org", + R"(!room_id:example.org)" }, + { "@user_id:example.org", + R"(@user_id:example.org)" }, + { "#room_alias:example.org", + R"(#room_alias:example.org)" } }); + FINISH_TEST(prettyPrintTestResult); +} + void TestManager::conclude() { // Clean up the room (best effort) -- cgit v1.2.3 From c49de691291147233f24c9db17c0c1a3e2b73dde Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 09:53:53 +0200 Subject: Further tighten the linkifier in prettyPrint() --- lib/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.cpp b/lib/util.cpp index 8067f561..993152dd 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -33,7 +33,7 @@ void Quotient::linkifyUrls(QString& htmlEscapedText) // comma or dot static const QRegularExpression FullUrlRegExp( QStringLiteral( - R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp|magnet|matrix):(//)?)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"), + R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp):(//)?\w|(magnet|matrix):)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"), RegExpOptions); // email address: // [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] -- cgit v1.2.3 From 73c1c8747b0c00524239724bb7cf00776448c5c7 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 09:54:45 +0200 Subject: quotient_common.h: remove a stray semicolon --- lib/quotient_common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 4444a111..13bf7246 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -92,7 +92,7 @@ enum RoomType { Space, Undefined, }; -Q_ENUM_NS(RoomType); +Q_ENUM_NS(RoomType) constexpr inline auto RoomTypeStrings = make_array( "m.space" -- cgit v1.2.3 From c16813c5209f0421ec773a98cf935a2eb2ea3d7c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 11:05:10 +0200 Subject: Move away wrap_in_function to private interface This has always been merely a workaround to enable connectUntil/connectSingleShot and was never intended to be used elsewhere, let alone in clients. --- lib/qt_connection_util.h | 17 ++++++++++++++--- lib/util.h | 12 ------------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index c6fa037a..9370d2eb 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -73,6 +73,17 @@ namespace _impl { }), connType); } + + // TODO: get rid of it as soon as Apple Clang gets proper deduction guides + // for std::function<> + // ...or consider using QtPrivate magic used by QObject::connect() + // ...for inspiration, also check a possible std::not_fn implementation + // at https://en.cppreference.com/w/cpp/utility/functional/not_fn + template + inline auto wrap_in_function(FnT&& f) + { + return typename function_traits::function_type(std::forward(f)); + } } // namespace _impl /*! \brief Create a connection that self-disconnects when its "slot" returns true @@ -90,7 +101,7 @@ inline auto connectUntil(SenderT* sender, SignalT signal, ContextT* context, const FunctorT& slot, Qt::ConnectionType connType = Qt::AutoConnection) { - return _impl::connectUntil(sender, signal, context, wrap_in_function(slot), + return _impl::connectUntil(sender, signal, context, _impl::wrap_in_function(slot), connType); } @@ -101,7 +112,7 @@ inline auto connectSingleShot(SenderT* sender, SignalT signal, Qt::ConnectionType connType = Qt::AutoConnection) { return _impl::connectSingleShot( - sender, signal, context, wrap_in_function(slot), connType); + sender, signal, context, _impl::wrap_in_function(slot), connType); } // Specialisation for usual Qt slots passed as pointers-to-members. @@ -114,7 +125,7 @@ inline auto connectSingleShot(SenderT* sender, SignalT signal, { // TODO: when switching to C++20, use std::bind_front() instead return _impl::connectSingleShot(sender, signal, receiver, - wrap_in_function( + _impl::wrap_in_function( [receiver, slot](const ArgTs&... args) { (receiver->*slot)(args...); }), diff --git a/lib/util.h b/lib/util.h index 5bfe6841..c6171b91 100644 --- a/lib/util.h +++ b/lib/util.h @@ -219,18 +219,6 @@ template using fn_arg_t = std::tuple_element_t::arg_types>; -// TODO: get rid of it as soon as Apple Clang gets proper deduction guides -// for std::function<> -// ...or consider using QtPrivate magic used by QObject::connect() -// since wrap_in_function() is actually made for qt_connection_util.h -// ...for inspiration, also check a possible std::not_fn implementation at -// https://en.cppreference.com/w/cpp/utility/functional/not_fn -template -inline auto wrap_in_function(FnT&& f) -{ - return typename function_traits::function_type(std::forward(f)); -} - inline constexpr auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); -- cgit v1.2.3 From 677c73263f434d1564695a8128d75fefd1a7b50b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 11:06:05 +0200 Subject: Drop old compatibility code libQuotient 0.7 really requires Qt 5.12, nothing earlier will work. --- lib/qt_connection_util.h | 5 ----- lib/util.cpp | 3 --- 2 files changed, 8 deletions(-) diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 9370d2eb..ffefb2a2 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -19,12 +19,7 @@ namespace _impl { decorated_slot_tt decoratedSlot, Qt::ConnectionType connType) { - // See https://bugreports.qt.io/browse/QTBUG-60339 -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - auto pc = std::make_shared(); -#else auto pc = std::make_unique(); -#endif auto& c = *pc; // Resolve a reference before pc is moved to lambda // Perfect forwarding doesn't work through signal-slot connections - diff --git a/lib/util.cpp b/lib/util.cpp index 993152dd..2dfb09a6 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -14,9 +14,6 @@ static const auto RegExpOptions = QRegularExpression::CaseInsensitiveOption -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - | QRegularExpression::OptimizeOnFirstUsageOption // Default since 5.12 -#endif | QRegularExpression::UseUnicodePropertiesOption; // Converts all that looks like a URL into HTML links -- cgit v1.2.3 From df5606ebd360d753b6261133254408aadbbb7f7f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 11:07:47 +0200 Subject: Make connectSingleShot() a tiny wrapper on Qt 6 Qt 6 has Qt::SingleShotConnection; connectSingleShot remains just for the sake of compatibility across Qt 5 and Qt 6. If you target Qt 6 only, feel free to use the Qt facility directly. --- lib/qt_connection_util.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index ffefb2a2..46294499 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -106,6 +106,11 @@ inline auto connectSingleShot(SenderT* sender, SignalT signal, ContextT* context, const FunctorT& slot, Qt::ConnectionType connType = Qt::AutoConnection) { +#if QT_VERSION_MAJOR >= 6 + return QObject::connect(sender, signal, context, slot, + Qt::ConnectionType(connType + | Qt::SingleShotConnection)); +#else return _impl::connectSingleShot( sender, signal, context, _impl::wrap_in_function(slot), connType); } @@ -125,6 +130,7 @@ inline auto connectSingleShot(SenderT* sender, SignalT signal, (receiver->*slot)(args...); }), connType); +#endif } /*! \brief A guard pointer that disconnects an interested object upon destruction -- cgit v1.2.3 From 7c11f7fddbcc98e4b3b92060c475799d7518624c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 18:26:47 +0200 Subject: gtad.yaml: make _rightQuote example less trivial --- gtad/gtad.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml index 58e1909c..ee8a43fe 100644 --- a/gtad/gtad.yaml +++ b/gtad/gtad.yaml @@ -140,7 +140,7 @@ mustache: # Syntax elements used by GTAD # _quote: '"' # Common quote for left and right # _leftQuote: '"' -# _rightQuote: '"' +# _rightQuote: '"_ls' _comment: '//' copyrightName: Kitsune Ral copyrightEmail: -- cgit v1.2.3 From 96f31d7d8ed1c9ab905c24ac039079aea622f4dc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 18:39:21 +0200 Subject: BaseJob: percent-encode variable path parts This is meant to spare clients from having to percent-encode room aliases, v3 event ids etc. that happen to hit the endpoint path. It is unfair to expect clients to do that since they are not supposed to care about the shape of CS API, which parameter should be encoded in which way. The trick (together with the slightly updated GTAD configuration) is to percent-encode parts that happen to be QStrings and not `const char[]`'s while passing all constant parts as plain C character literals. This also allows to make it more certain that the path is correctly encoded by passing and storing QByteArray's wherever the path is already encoded, and only use QStrings (next to const char arrays) before that. Since the change alters the API contract (even if that contract was crappy), some crude detection of percent-encoded stuff on input is inserted; if input is already percent-encoded, a warning is put to the logs, alerting developers about the change. --- gtad/gtad.yaml | 4 ++-- gtad/operation.cpp.mustache | 2 -- lib/jobs/basejob.cpp | 51 +++++++++++++++++++++++++++++++-------------- lib/jobs/basejob.h | 21 ++++++++++++++++--- lib/jobs/syncjob.cpp | 2 +- 5 files changed, 56 insertions(+), 24 deletions(-) diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml index ee8a43fe..943ac013 100644 --- a/gtad/gtad.yaml +++ b/gtad/gtad.yaml @@ -190,8 +190,8 @@ mustache: joinedParamDef: "{{>maybeCrefType}} {{paramName}}{{>cjoin}}" passPathAndMaybeQuery: >- - QStringLiteral("{{basePathWithoutHost}}") - {{#pathParts}} % {{_}}{{/pathParts}}{{#queryParams?}}, + makePath("{{basePathWithoutHost}}"{{#pathParts}}, + {{_}}{{/pathParts}}){{#queryParams?}}, queryTo{{camelCaseOperationId}}( {{#queryParams}}{{paramName}}{{>cjoin}}{{/queryParams}}){{/queryParams?}} diff --git a/gtad/operation.cpp.mustache b/gtad/operation.cpp.mustache index 7f692e4a..3d26ec73 100644 --- a/gtad/operation.cpp.mustache +++ b/gtad/operation.cpp.mustache @@ -4,8 +4,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later }}{{>preamble}} #include "{{filenameBase}}.h" -#include - using namespace Quotient; {{#operations}}{{#operation}} {{#queryParams?}} diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 85066024..73762e4f 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -71,7 +71,7 @@ public: // Using an idiom from clang-tidy: // http://clang.llvm.org/extra/clang-tidy/checks/modernize-pass-by-value.html - Private(HttpVerb v, QString endpoint, const QUrlQuery& q, + Private(HttpVerb v, QByteArray endpoint, const QUrlQuery& q, RequestData&& data, bool nt) : verb(v) , apiEndpoint(std::move(endpoint)) @@ -106,7 +106,7 @@ public: // Contents for the network request HttpVerb verb; - QString apiEndpoint; + QByteArray apiEndpoint; QHash requestHeaders; QUrlQuery requestQuery; RequestData requestData; @@ -166,14 +166,36 @@ public: } }; -BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, +inline bool isHex(QChar c) +{ + return c.isDigit() || (c >= u'A' && c <= u'F') || (c >= u'a' && c <= u'f'); +} + +QByteArray BaseJob::encodeIfParam(const QString& paramPart) +{ + const auto percentIndex = paramPart.indexOf('%'); + if (percentIndex != -1 && paramPart.size() > percentIndex + 2 + && isHex(paramPart[percentIndex + 1]) + && isHex(paramPart[percentIndex + 2])) { + qCWarning(JOBS) + << "Developers, upfront percent-encoding of job parameters is " + "deprecated since libQuotient 0.7; the string involved is" + << paramPart; + return QUrl(paramPart, QUrl::TolerantMode).toEncoded(); + } + return QUrl::toPercentEncoding(paramPart); +} + +BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, bool needsToken) - : BaseJob(verb, name, endpoint, QUrlQuery {}, RequestData {}, needsToken) + : BaseJob(verb, name, std::move(endpoint), QUrlQuery {}, RequestData {}, + needsToken) {} -BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, +BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, const QUrlQuery& query, RequestData&& data, bool needsToken) - : d(new Private(verb, endpoint, query, std::move(data), needsToken)) + : d(new Private(verb, std::move(endpoint), query, std::move(data), + needsToken)) { setObjectName(name); connect(&d->timer, &QTimer::timeout, this, &BaseJob::timeout); @@ -194,13 +216,6 @@ QUrl BaseJob::requestUrl() const { return d->reply ? d->reply->url() : QUrl(); } bool BaseJob::isBackground() const { return d->inBackground; } -//const QString& BaseJob::apiEndpoint() const { return d->apiUrl.path(); } - -//void BaseJob::setApiEndpoint(const QString& apiEndpoint) -//{ -// d->apiEndpoint = apiEndpoint; -//} - const BaseJob::headers_t& BaseJob::requestHeaders() const { return d->requestHeaders; @@ -259,13 +274,17 @@ const QNetworkReply* BaseJob::reply() const { return d->reply.data(); } QNetworkReply* BaseJob::reply() { return d->reply.data(); } -QUrl BaseJob::makeRequestUrl(QUrl baseUrl, const QString& path, +QUrl BaseJob::makeRequestUrl(QUrl baseUrl, const QByteArray& encodedPath, const QUrlQuery& query) { // Make sure the added path is relative even if it's not (the official // API definitions have the leading slash though it's not really correct). - baseUrl = baseUrl.resolved( - QUrl(path.mid(path.startsWith('/')), QUrl::TolerantMode)); + const auto pathUrl = + QUrl::fromEncoded(encodedPath.mid(encodedPath.startsWith('/')), + QUrl::StrictMode); + Q_ASSERT_X(pathUrl.isValid(), __FUNCTION__, + qPrintable(pathUrl.errorString())); + baseUrl = baseUrl.resolved(pathUrl); baseUrl.setQuery(query); return baseUrl; } diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 663c121c..81455307 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -9,6 +9,7 @@ #include "../converters.h" #include +#include class QNetworkReply; class QSslError; @@ -23,6 +24,14 @@ class BaseJob : public QObject { Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT) Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries) Q_PROPERTY(int statusCode READ error NOTIFY statusChanged) + + static QByteArray encodeIfParam(const QString& paramPart); + template + static inline auto encodeIfParam(const char (&constPart)[N]) + { + return constPart; + } + public: /*! The status code of a job * @@ -70,6 +79,12 @@ public: }; Q_ENUM(StatusCode) + template + static QByteArray makePath(StrTs&&... parts) + { + return (QByteArray() % ... % encodeIfParam(parts)); + } + using Data #ifndef Q_CC_MSVC Q_DECL_DEPRECATED_X("Use Quotient::RequestData instead") @@ -124,9 +139,9 @@ public: }; public: - BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, + BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, bool needsToken = true); - BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, + BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, const QUrlQuery& query, RequestData&& data = {}, bool needsToken = true); @@ -352,7 +367,7 @@ protected: * The function ensures exactly one '/' between the path component of * \p baseUrl and \p path. The query component of \p baseUrl is ignored. */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& path, + static QUrl makeRequestUrl(QUrl baseUrl, const QByteArray &encodedPath, const QUrlQuery& query = {}); /*! Prepares the job for execution diff --git a/lib/jobs/syncjob.cpp b/lib/jobs/syncjob.cpp index 59a34ef3..9b1b46f0 100644 --- a/lib/jobs/syncjob.cpp +++ b/lib/jobs/syncjob.cpp @@ -10,7 +10,7 @@ static size_t jobId = 0; SyncJob::SyncJob(const QString& since, const QString& filter, int timeout, const QString& presence) : BaseJob(HttpVerb::Get, QStringLiteral("SyncJob-%1").arg(++jobId), - QStringLiteral("_matrix/client/r0/sync")) + "_matrix/client/r0/sync") { setLoggingCategory(SYNCJOB); QUrlQuery query; -- cgit v1.2.3 From 1fd25fe944b67c55435ed4d4d8fd1cbb0989bb5f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 18:40:48 +0200 Subject: Regenerate CS API files upon the previous commit --- lib/csapi/account-data.cpp | 28 ++++++++-------- lib/csapi/admin.cpp | 8 ++--- lib/csapi/administrative_contact.cpp | 27 +++++++-------- lib/csapi/appservice_room_directory.cpp | 6 ++-- lib/csapi/banning.cpp | 7 ++-- lib/csapi/capabilities.cpp | 9 ++--- lib/csapi/content-repo.cpp | 41 ++++++++++------------- lib/csapi/create_room.cpp | 4 +-- lib/csapi/cross_signing.cpp | 7 ++-- lib/csapi/device_management.cpp | 19 +++++------ lib/csapi/directory.cpp | 26 ++++++--------- lib/csapi/event_context.cpp | 11 +++--- lib/csapi/filter.cpp | 13 +++----- lib/csapi/inviting.cpp | 5 +-- lib/csapi/joining.cpp | 6 ++-- lib/csapi/keys.cpp | 14 ++++---- lib/csapi/kicking.cpp | 4 +-- lib/csapi/knocking.cpp | 4 +-- lib/csapi/leaving.cpp | 12 +++---- lib/csapi/list_joined_rooms.cpp | 9 ++--- lib/csapi/list_public_rooms.cpp | 20 +++++------ lib/csapi/login.cpp | 9 ++--- lib/csapi/logout.cpp | 14 +++----- lib/csapi/message_pagination.cpp | 7 ++-- lib/csapi/notifications.cpp | 8 ++--- lib/csapi/openid.cpp | 6 ++-- lib/csapi/peeking_events.cpp | 7 ++-- lib/csapi/presence.cpp | 12 +++---- lib/csapi/profile.cpp | 28 ++++++---------- lib/csapi/pusher.cpp | 9 ++--- lib/csapi/pushrules.cpp | 59 +++++++++++++++------------------ lib/csapi/read_markers.cpp | 5 +-- lib/csapi/receipts.cpp | 6 ++-- lib/csapi/redaction.cpp | 6 ++-- lib/csapi/registration.cpp | 28 +++++++--------- lib/csapi/report_content.cpp | 6 ++-- lib/csapi/room_send.cpp | 6 ++-- lib/csapi/room_state.cpp | 6 ++-- lib/csapi/room_upgrades.cpp | 5 +-- lib/csapi/rooms.cpp | 41 ++++++++++------------- lib/csapi/search.cpp | 4 +-- lib/csapi/sso_login_redirect.cpp | 15 ++++----- lib/csapi/tags.cpp | 24 ++++++-------- lib/csapi/third_party_lookup.cpp | 41 ++++++++++------------- lib/csapi/third_party_membership.cpp | 5 +-- lib/csapi/to_device.cpp | 6 ++-- lib/csapi/typing.cpp | 6 ++-- lib/csapi/users.cpp | 4 +-- lib/csapi/versions.cpp | 7 ++-- lib/csapi/voip.cpp | 9 ++--- lib/csapi/wellknown.cpp | 7 ++-- lib/csapi/whoami.cpp | 9 ++--- 52 files changed, 265 insertions(+), 420 deletions(-) diff --git a/lib/csapi/account-data.cpp b/lib/csapi/account-data.cpp index 80deb8f1..09fc8d40 100644 --- a/lib/csapi/account-data.cpp +++ b/lib/csapi/account-data.cpp @@ -4,15 +4,13 @@ #include "account-data.h" -#include - using namespace Quotient; SetAccountDataJob::SetAccountDataJob(const QString& userId, const QString& type, const QJsonObject& content) : BaseJob(HttpVerb::Put, QStringLiteral("SetAccountDataJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/account_data/" % type) + makePath("/_matrix/client/r0", "/user/", userId, "/account_data/", + type)) { setRequestData(RequestData(toJson(content))); } @@ -21,14 +19,14 @@ QUrl GetAccountDataJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& type) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") % "/user/" - % userId % "/account_data/" % type); + makePath("/_matrix/client/r0", "/user/", + userId, "/account_data/", type)); } GetAccountDataJob::GetAccountDataJob(const QString& userId, const QString& type) : BaseJob(HttpVerb::Get, QStringLiteral("GetAccountDataJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/account_data/" % type) + makePath("/_matrix/client/r0", "/user/", userId, "/account_data/", + type)) {} SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, @@ -36,8 +34,8 @@ SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, const QString& type, const QJsonObject& content) : BaseJob(HttpVerb::Put, QStringLiteral("SetAccountDataPerRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/rooms/" % roomId % "/account_data/" % type) + makePath("/_matrix/client/r0", "/user/", userId, "/rooms/", + roomId, "/account_data/", type)) { setRequestData(RequestData(toJson(content))); } @@ -48,15 +46,15 @@ QUrl GetAccountDataPerRoomJob::makeRequestUrl(QUrl baseUrl, const QString& type) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/user/" % userId % "/rooms/" % roomId - % "/account_data/" % type); + makePath("/_matrix/client/r0", "/user/", + userId, "/rooms/", roomId, + "/account_data/", type)); } GetAccountDataPerRoomJob::GetAccountDataPerRoomJob(const QString& userId, const QString& roomId, const QString& type) : BaseJob(HttpVerb::Get, QStringLiteral("GetAccountDataPerRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/rooms/" % roomId % "/account_data/" % type) + makePath("/_matrix/client/r0", "/user/", userId, "/rooms/", + roomId, "/account_data/", type)) {} diff --git a/lib/csapi/admin.cpp b/lib/csapi/admin.cpp index 9619c441..81dd0624 100644 --- a/lib/csapi/admin.cpp +++ b/lib/csapi/admin.cpp @@ -4,18 +4,16 @@ #include "admin.h" -#include - using namespace Quotient; QUrl GetWhoIsJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/admin/whois/" % userId); + makePath("/_matrix/client/r0", + "/admin/whois/", userId)); } GetWhoIsJob::GetWhoIsJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetWhoIsJob"), - QStringLiteral("/_matrix/client/r0") % "/admin/whois/" % userId) + makePath("/_matrix/client/r0", "/admin/whois/", userId)) {} diff --git a/lib/csapi/administrative_contact.cpp b/lib/csapi/administrative_contact.cpp index 04360299..589c9fc1 100644 --- a/lib/csapi/administrative_contact.cpp +++ b/lib/csapi/administrative_contact.cpp @@ -4,25 +4,22 @@ #include "administrative_contact.h" -#include - using namespace Quotient; QUrl GetAccount3PIDsJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/account/3pid"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), makePath("/_matrix/client/r0", "/account/3pid")); } GetAccount3PIDsJob::GetAccount3PIDsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetAccount3PIDsJob"), - QStringLiteral("/_matrix/client/r0") % "/account/3pid") + makePath("/_matrix/client/r0", "/account/3pid")) {} Post3PIDsJob::Post3PIDsJob(const ThreePidCredentials& threePidCreds) : BaseJob(HttpVerb::Post, QStringLiteral("Post3PIDsJob"), - QStringLiteral("/_matrix/client/r0") % "/account/3pid") + makePath("/_matrix/client/r0", "/account/3pid")) { QJsonObject _data; addParam<>(_data, QStringLiteral("three_pid_creds"), threePidCreds); @@ -32,7 +29,7 @@ Post3PIDsJob::Post3PIDsJob(const ThreePidCredentials& threePidCreds) Add3PIDJob::Add3PIDJob(const QString& clientSecret, const QString& sid, const Omittable& auth) : BaseJob(HttpVerb::Post, QStringLiteral("Add3PIDJob"), - QStringLiteral("/_matrix/client/r0") % "/account/3pid/add") + makePath("/_matrix/client/r0", "/account/3pid/add")) { QJsonObject _data; addParam(_data, QStringLiteral("auth"), auth); @@ -44,7 +41,7 @@ Add3PIDJob::Add3PIDJob(const QString& clientSecret, const QString& sid, Bind3PIDJob::Bind3PIDJob(const QString& clientSecret, const QString& idServer, const QString& idAccessToken, const QString& sid) : BaseJob(HttpVerb::Post, QStringLiteral("Bind3PIDJob"), - QStringLiteral("/_matrix/client/r0") % "/account/3pid/bind") + makePath("/_matrix/client/r0", "/account/3pid/bind")) { QJsonObject _data; addParam<>(_data, QStringLiteral("client_secret"), clientSecret); @@ -58,7 +55,7 @@ Delete3pidFromAccountJob::Delete3pidFromAccountJob(const QString& medium, const QString& address, const QString& idServer) : BaseJob(HttpVerb::Post, QStringLiteral("Delete3pidFromAccountJob"), - QStringLiteral("/_matrix/client/r0") % "/account/3pid/delete") + makePath("/_matrix/client/r0", "/account/3pid/delete")) { QJsonObject _data; addParam(_data, QStringLiteral("id_server"), idServer); @@ -72,7 +69,7 @@ Unbind3pidFromAccountJob::Unbind3pidFromAccountJob(const QString& medium, const QString& address, const QString& idServer) : BaseJob(HttpVerb::Post, QStringLiteral("Unbind3pidFromAccountJob"), - QStringLiteral("/_matrix/client/r0") % "/account/3pid/unbind") + makePath("/_matrix/client/r0", "/account/3pid/unbind")) { QJsonObject _data; addParam(_data, QStringLiteral("id_server"), idServer); @@ -85,8 +82,8 @@ Unbind3pidFromAccountJob::Unbind3pidFromAccountJob(const QString& medium, RequestTokenTo3PIDEmailJob::RequestTokenTo3PIDEmailJob( const EmailValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenTo3PIDEmailJob"), - QStringLiteral("/_matrix/client/r0") - % "/account/3pid/email/requestToken", + makePath("/_matrix/client/r0", + "/account/3pid/email/requestToken"), false) { setRequestData(RequestData(toJson(body))); @@ -95,8 +92,8 @@ RequestTokenTo3PIDEmailJob::RequestTokenTo3PIDEmailJob( RequestTokenTo3PIDMSISDNJob::RequestTokenTo3PIDMSISDNJob( const MsisdnValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenTo3PIDMSISDNJob"), - QStringLiteral("/_matrix/client/r0") - % "/account/3pid/msisdn/requestToken", + makePath("/_matrix/client/r0", + "/account/3pid/msisdn/requestToken"), false) { setRequestData(RequestData(toJson(body))); diff --git a/lib/csapi/appservice_room_directory.cpp b/lib/csapi/appservice_room_directory.cpp index 4d87e4af..40d784c6 100644 --- a/lib/csapi/appservice_room_directory.cpp +++ b/lib/csapi/appservice_room_directory.cpp @@ -4,16 +4,14 @@ #include "appservice_room_directory.h" -#include - using namespace Quotient; UpdateAppserviceRoomDirectoryVisibilityJob::UpdateAppserviceRoomDirectoryVisibilityJob( const QString& networkId, const QString& roomId, const QString& visibility) : BaseJob(HttpVerb::Put, QStringLiteral("UpdateAppserviceRoomDirectoryVisibilityJob"), - QStringLiteral("/_matrix/client/r0") - % "/directory/list/appservice/" % networkId % "/" % roomId) + makePath("/_matrix/client/r0", "/directory/list/appservice/", + networkId, "/", roomId)) { QJsonObject _data; addParam<>(_data, QStringLiteral("visibility"), visibility); diff --git a/lib/csapi/banning.cpp b/lib/csapi/banning.cpp index 8e0add1a..472128bb 100644 --- a/lib/csapi/banning.cpp +++ b/lib/csapi/banning.cpp @@ -4,14 +4,12 @@ #include "banning.h" -#include - using namespace Quotient; BanJob::BanJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("BanJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/ban") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/ban")) { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); @@ -22,8 +20,7 @@ BanJob::BanJob(const QString& roomId, const QString& userId, UnbanJob::UnbanJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("UnbanJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/unban") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/unban")) { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); diff --git a/lib/csapi/capabilities.cpp b/lib/csapi/capabilities.cpp index 33a53cad..bc21e462 100644 --- a/lib/csapi/capabilities.cpp +++ b/lib/csapi/capabilities.cpp @@ -4,20 +4,17 @@ #include "capabilities.h" -#include - using namespace Quotient; QUrl GetCapabilitiesJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/capabilities"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), makePath("/_matrix/client/r0", "/capabilities")); } GetCapabilitiesJob::GetCapabilitiesJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetCapabilitiesJob"), - QStringLiteral("/_matrix/client/r0") % "/capabilities") + makePath("/_matrix/client/r0", "/capabilities")) { addExpectedKey("capabilities"); } diff --git a/lib/csapi/content-repo.cpp b/lib/csapi/content-repo.cpp index 2d82437b..6d1e38b6 100644 --- a/lib/csapi/content-repo.cpp +++ b/lib/csapi/content-repo.cpp @@ -4,8 +4,6 @@ #include "content-repo.h" -#include - using namespace Quotient; auto queryToUploadContent(const QString& filename) @@ -18,7 +16,7 @@ auto queryToUploadContent(const QString& filename) UploadContentJob::UploadContentJob(QIODevice* content, const QString& filename, const QString& contentType) : BaseJob(HttpVerb::Post, QStringLiteral("UploadContentJob"), - QStringLiteral("/_matrix/media/r0") % "/upload", + makePath("/_matrix/media/r0", "/upload"), queryToUploadContent(filename)) { setRequestHeader("Content-Type", contentType.toLatin1()); @@ -37,17 +35,16 @@ QUrl GetContentJob::makeRequestUrl(QUrl baseUrl, const QString& serverName, const QString& mediaId, bool allowRemote) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/media/r0") - % "/download/" % serverName % "/" - % mediaId, + makePath("/_matrix/media/r0", "/download/", + serverName, "/", mediaId), queryToGetContent(allowRemote)); } GetContentJob::GetContentJob(const QString& serverName, const QString& mediaId, bool allowRemote) : BaseJob(HttpVerb::Get, QStringLiteral("GetContentJob"), - QStringLiteral("/_matrix/media/r0") % "/download/" % serverName - % "/" % mediaId, + makePath("/_matrix/media/r0", "/download/", serverName, "/", + mediaId), queryToGetContent(allowRemote), {}, false) { setExpectedContentTypes({ "*/*" }); @@ -67,9 +64,9 @@ QUrl GetContentOverrideNameJob::makeRequestUrl(QUrl baseUrl, bool allowRemote) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/media/r0") - % "/download/" % serverName % "/" - % mediaId % "/" % fileName, + makePath("/_matrix/media/r0", "/download/", + serverName, "/", mediaId, "/", + fileName), queryToGetContentOverrideName(allowRemote)); } @@ -78,8 +75,8 @@ GetContentOverrideNameJob::GetContentOverrideNameJob(const QString& serverName, const QString& fileName, bool allowRemote) : BaseJob(HttpVerb::Get, QStringLiteral("GetContentOverrideNameJob"), - QStringLiteral("/_matrix/media/r0") % "/download/" % serverName - % "/" % mediaId % "/" % fileName, + makePath("/_matrix/media/r0", "/download/", serverName, "/", + mediaId, "/", fileName), queryToGetContentOverrideName(allowRemote), {}, false) { setExpectedContentTypes({ "*/*" }); @@ -104,8 +101,7 @@ QUrl GetContentThumbnailJob::makeRequestUrl(QUrl baseUrl, { return BaseJob::makeRequestUrl( std::move(baseUrl), - QStringLiteral("/_matrix/media/r0") % "/thumbnail/" % serverName % "/" - % mediaId, + makePath("/_matrix/media/r0", "/thumbnail/", serverName, "/", mediaId), queryToGetContentThumbnail(width, height, method, allowRemote)); } @@ -114,8 +110,8 @@ GetContentThumbnailJob::GetContentThumbnailJob(const QString& serverName, int height, const QString& method, bool allowRemote) : BaseJob(HttpVerb::Get, QStringLiteral("GetContentThumbnailJob"), - QStringLiteral("/_matrix/media/r0") % "/thumbnail/" % serverName - % "/" % mediaId, + makePath("/_matrix/media/r0", "/thumbnail/", serverName, "/", + mediaId), queryToGetContentThumbnail(width, height, method, allowRemote), {}, false) { @@ -134,25 +130,24 @@ QUrl GetUrlPreviewJob::makeRequestUrl(QUrl baseUrl, const QUrl& url, Omittable ts) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/media/r0") - % "/preview_url", + makePath("/_matrix/media/r0", + "/preview_url"), queryToGetUrlPreview(url, ts)); } GetUrlPreviewJob::GetUrlPreviewJob(const QUrl& url, Omittable ts) : BaseJob(HttpVerb::Get, QStringLiteral("GetUrlPreviewJob"), - QStringLiteral("/_matrix/media/r0") % "/preview_url", + makePath("/_matrix/media/r0", "/preview_url"), queryToGetUrlPreview(url, ts)) {} QUrl GetConfigJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/media/r0") - % "/config"); + makePath("/_matrix/media/r0", "/config")); } GetConfigJob::GetConfigJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetConfigJob"), - QStringLiteral("/_matrix/media/r0") % "/config") + makePath("/_matrix/media/r0", "/config")) {} diff --git a/lib/csapi/create_room.cpp b/lib/csapi/create_room.cpp index a94f9951..9aaef87f 100644 --- a/lib/csapi/create_room.cpp +++ b/lib/csapi/create_room.cpp @@ -4,8 +4,6 @@ #include "create_room.h" -#include - using namespace Quotient; CreateRoomJob::CreateRoomJob(const QString& visibility, @@ -18,7 +16,7 @@ CreateRoomJob::CreateRoomJob(const QString& visibility, const QString& preset, Omittable isDirect, const QJsonObject& powerLevelContentOverride) : BaseJob(HttpVerb::Post, QStringLiteral("CreateRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/createRoom") + makePath("/_matrix/client/r0", "/createRoom")) { QJsonObject _data; addParam(_data, QStringLiteral("visibility"), visibility); diff --git a/lib/csapi/cross_signing.cpp b/lib/csapi/cross_signing.cpp index ed2b15c0..1fa0e949 100644 --- a/lib/csapi/cross_signing.cpp +++ b/lib/csapi/cross_signing.cpp @@ -4,8 +4,6 @@ #include "cross_signing.h" -#include - using namespace Quotient; UploadCrossSigningKeysJob::UploadCrossSigningKeysJob( @@ -13,8 +11,7 @@ UploadCrossSigningKeysJob::UploadCrossSigningKeysJob( const Omittable& selfSigningKey, const Omittable& userSigningKey) : BaseJob(HttpVerb::Post, QStringLiteral("UploadCrossSigningKeysJob"), - QStringLiteral("/_matrix/client/r0") - % "/keys/device_signing/upload") + makePath("/_matrix/client/r0", "/keys/device_signing/upload")) { QJsonObject _data; addParam(_data, QStringLiteral("master_key"), masterKey); @@ -28,7 +25,7 @@ UploadCrossSigningKeysJob::UploadCrossSigningKeysJob( UploadCrossSigningSignaturesJob::UploadCrossSigningSignaturesJob( const QHash>& signatures) : BaseJob(HttpVerb::Post, QStringLiteral("UploadCrossSigningSignaturesJob"), - QStringLiteral("/_matrix/client/r0") % "/keys/signatures/upload") + makePath("/_matrix/client/r0", "/keys/signatures/upload")) { setRequestData(RequestData(toJson(signatures))); } diff --git a/lib/csapi/device_management.cpp b/lib/csapi/device_management.cpp index eac9a545..da6dbc76 100644 --- a/lib/csapi/device_management.cpp +++ b/lib/csapi/device_management.cpp @@ -4,38 +4,35 @@ #include "device_management.h" -#include - using namespace Quotient; QUrl GetDevicesJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/devices"); + makePath("/_matrix/client/r0", "/devices")); } GetDevicesJob::GetDevicesJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetDevicesJob"), - QStringLiteral("/_matrix/client/r0") % "/devices") + makePath("/_matrix/client/r0", "/devices")) {} QUrl GetDeviceJob::makeRequestUrl(QUrl baseUrl, const QString& deviceId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/devices/" % deviceId); + makePath("/_matrix/client/r0", "/devices/", + deviceId)); } GetDeviceJob::GetDeviceJob(const QString& deviceId) : BaseJob(HttpVerb::Get, QStringLiteral("GetDeviceJob"), - QStringLiteral("/_matrix/client/r0") % "/devices/" % deviceId) + makePath("/_matrix/client/r0", "/devices/", deviceId)) {} UpdateDeviceJob::UpdateDeviceJob(const QString& deviceId, const QString& displayName) : BaseJob(HttpVerb::Put, QStringLiteral("UpdateDeviceJob"), - QStringLiteral("/_matrix/client/r0") % "/devices/" % deviceId) + makePath("/_matrix/client/r0", "/devices/", deviceId)) { QJsonObject _data; addParam(_data, QStringLiteral("display_name"), displayName); @@ -45,7 +42,7 @@ UpdateDeviceJob::UpdateDeviceJob(const QString& deviceId, DeleteDeviceJob::DeleteDeviceJob(const QString& deviceId, const Omittable& auth) : BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), - QStringLiteral("/_matrix/client/r0") % "/devices/" % deviceId) + makePath("/_matrix/client/r0", "/devices/", deviceId)) { QJsonObject _data; addParam(_data, QStringLiteral("auth"), auth); @@ -55,7 +52,7 @@ DeleteDeviceJob::DeleteDeviceJob(const QString& deviceId, DeleteDevicesJob::DeleteDevicesJob(const QStringList& devices, const Omittable& auth) : BaseJob(HttpVerb::Post, QStringLiteral("DeleteDevicesJob"), - QStringLiteral("/_matrix/client/r0") % "/delete_devices") + makePath("/_matrix/client/r0", "/delete_devices")) { QJsonObject _data; addParam<>(_data, QStringLiteral("devices"), devices); diff --git a/lib/csapi/directory.cpp b/lib/csapi/directory.cpp index 25ea82e2..b351b4ef 100644 --- a/lib/csapi/directory.cpp +++ b/lib/csapi/directory.cpp @@ -4,14 +4,11 @@ #include "directory.h" -#include - using namespace Quotient; SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, const QString& roomId) : BaseJob(HttpVerb::Put, QStringLiteral("SetRoomAliasJob"), - QStringLiteral("/_matrix/client/r0") % "/directory/room/" - % roomAlias) + makePath("/_matrix/client/r0", "/directory/room/", roomAlias)) { QJsonObject _data; addParam<>(_data, QStringLiteral("room_id"), roomId); @@ -21,41 +18,38 @@ SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, const QString& roomId QUrl GetRoomIdByAliasJob::makeRequestUrl(QUrl baseUrl, const QString& roomAlias) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/directory/room/" % roomAlias); + makePath("/_matrix/client/r0", + "/directory/room/", roomAlias)); } GetRoomIdByAliasJob::GetRoomIdByAliasJob(const QString& roomAlias) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomIdByAliasJob"), - QStringLiteral("/_matrix/client/r0") % "/directory/room/" - % roomAlias, + makePath("/_matrix/client/r0", "/directory/room/", roomAlias), false) {} QUrl DeleteRoomAliasJob::makeRequestUrl(QUrl baseUrl, const QString& roomAlias) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/directory/room/" % roomAlias); + makePath("/_matrix/client/r0", + "/directory/room/", roomAlias)); } DeleteRoomAliasJob::DeleteRoomAliasJob(const QString& roomAlias) : BaseJob(HttpVerb::Delete, QStringLiteral("DeleteRoomAliasJob"), - QStringLiteral("/_matrix/client/r0") % "/directory/room/" - % roomAlias) + makePath("/_matrix/client/r0", "/directory/room/", roomAlias)) {} QUrl GetLocalAliasesJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/rooms/" % roomId % "/aliases"); + makePath("/_matrix/client/r0", "/rooms/", + roomId, "/aliases")); } GetLocalAliasesJob::GetLocalAliasesJob(const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("GetLocalAliasesJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/aliases") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/aliases")) { addExpectedKey("aliases"); } diff --git a/lib/csapi/event_context.cpp b/lib/csapi/event_context.cpp index 3f4cd61e..877838e2 100644 --- a/lib/csapi/event_context.cpp +++ b/lib/csapi/event_context.cpp @@ -4,8 +4,6 @@ #include "event_context.h" -#include - using namespace Quotient; auto queryToGetEventContext(Omittable limit, const QString& filter) @@ -22,9 +20,8 @@ QUrl GetEventContextJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& filter) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/rooms/" % roomId % "/context/" - % eventId, + makePath("/_matrix/client/r0", "/rooms/", + roomId, "/context/", eventId), queryToGetEventContext(limit, filter)); } @@ -33,7 +30,7 @@ GetEventContextJob::GetEventContextJob(const QString& roomId, Omittable limit, const QString& filter) : BaseJob(HttpVerb::Get, QStringLiteral("GetEventContextJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/context/" % eventId, + makePath("/_matrix/client/r0", "/rooms/", roomId, "/context/", + eventId), queryToGetEventContext(limit, filter)) {} diff --git a/lib/csapi/filter.cpp b/lib/csapi/filter.cpp index 6b8863cc..38c68be7 100644 --- a/lib/csapi/filter.cpp +++ b/lib/csapi/filter.cpp @@ -4,14 +4,11 @@ #include "filter.h" -#include - using namespace Quotient; DefineFilterJob::DefineFilterJob(const QString& userId, const Filter& filter) : BaseJob(HttpVerb::Post, QStringLiteral("DefineFilterJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/filter") + makePath("/_matrix/client/r0", "/user/", userId, "/filter")) { setRequestData(RequestData(toJson(filter))); addExpectedKey("filter_id"); @@ -21,12 +18,12 @@ QUrl GetFilterJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& filterId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") % "/user/" - % userId % "/filter/" % filterId); + makePath("/_matrix/client/r0", "/user/", + userId, "/filter/", filterId)); } GetFilterJob::GetFilterJob(const QString& userId, const QString& filterId) : BaseJob(HttpVerb::Get, QStringLiteral("GetFilterJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/filter/" % filterId) + makePath("/_matrix/client/r0", "/user/", userId, "/filter/", + filterId)) {} diff --git a/lib/csapi/inviting.cpp b/lib/csapi/inviting.cpp index 1e2554f4..39d24611 100644 --- a/lib/csapi/inviting.cpp +++ b/lib/csapi/inviting.cpp @@ -4,15 +4,12 @@ #include "inviting.h" -#include - using namespace Quotient; InviteUserJob::InviteUserJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("InviteUserJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/invite") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/invite")) { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); diff --git a/lib/csapi/joining.cpp b/lib/csapi/joining.cpp index f5266f0b..373c1c6a 100644 --- a/lib/csapi/joining.cpp +++ b/lib/csapi/joining.cpp @@ -4,15 +4,13 @@ #include "joining.h" -#include - using namespace Quotient; JoinRoomByIdJob::JoinRoomByIdJob( const QString& roomId, const Omittable& thirdPartySigned, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("JoinRoomByIdJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/join") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/join")) { QJsonObject _data; addParam(_data, QStringLiteral("third_party_signed"), @@ -34,7 +32,7 @@ JoinRoomJob::JoinRoomJob(const QString& roomIdOrAlias, const Omittable& thirdPartySigned, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("JoinRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/join/" % roomIdOrAlias, + makePath("/_matrix/client/r0", "/join/", roomIdOrAlias), queryToJoinRoom(serverName)) { QJsonObject _data; diff --git a/lib/csapi/keys.cpp b/lib/csapi/keys.cpp index ba5d8e12..d6bd2fab 100644 --- a/lib/csapi/keys.cpp +++ b/lib/csapi/keys.cpp @@ -4,14 +4,12 @@ #include "keys.h" -#include - using namespace Quotient; UploadKeysJob::UploadKeysJob(const Omittable& deviceKeys, const QHash& oneTimeKeys) : BaseJob(HttpVerb::Post, QStringLiteral("UploadKeysJob"), - QStringLiteral("/_matrix/client/r0") % "/keys/upload") + makePath("/_matrix/client/r0", "/keys/upload")) { QJsonObject _data; addParam(_data, QStringLiteral("device_keys"), deviceKeys); @@ -23,7 +21,7 @@ UploadKeysJob::UploadKeysJob(const Omittable& deviceKeys, QueryKeysJob::QueryKeysJob(const QHash& deviceKeys, Omittable timeout, const QString& token) : BaseJob(HttpVerb::Post, QStringLiteral("QueryKeysJob"), - QStringLiteral("/_matrix/client/r0") % "/keys/query") + makePath("/_matrix/client/r0", "/keys/query")) { QJsonObject _data; addParam(_data, QStringLiteral("timeout"), timeout); @@ -36,7 +34,7 @@ ClaimKeysJob::ClaimKeysJob( const QHash>& oneTimeKeys, Omittable timeout) : BaseJob(HttpVerb::Post, QStringLiteral("ClaimKeysJob"), - QStringLiteral("/_matrix/client/r0") % "/keys/claim") + makePath("/_matrix/client/r0", "/keys/claim")) { QJsonObject _data; addParam(_data, QStringLiteral("timeout"), timeout); @@ -57,13 +55,13 @@ QUrl GetKeysChangesJob::makeRequestUrl(QUrl baseUrl, const QString& from, const QString& to) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/keys/changes", + makePath("/_matrix/client/r0", + "/keys/changes"), queryToGetKeysChanges(from, to)); } GetKeysChangesJob::GetKeysChangesJob(const QString& from, const QString& to) : BaseJob(HttpVerb::Get, QStringLiteral("GetKeysChangesJob"), - QStringLiteral("/_matrix/client/r0") % "/keys/changes", + makePath("/_matrix/client/r0", "/keys/changes"), queryToGetKeysChanges(from, to)) {} diff --git a/lib/csapi/kicking.cpp b/lib/csapi/kicking.cpp index 7de5ce01..433e592c 100644 --- a/lib/csapi/kicking.cpp +++ b/lib/csapi/kicking.cpp @@ -4,14 +4,12 @@ #include "kicking.h" -#include - using namespace Quotient; KickJob::KickJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("KickJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/kick") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/kick")) { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); diff --git a/lib/csapi/knocking.cpp b/lib/csapi/knocking.cpp index 788bb378..73e13e6e 100644 --- a/lib/csapi/knocking.cpp +++ b/lib/csapi/knocking.cpp @@ -4,8 +4,6 @@ #include "knocking.h" -#include - using namespace Quotient; auto queryToKnockRoom(const QStringList& serverName) @@ -18,7 +16,7 @@ auto queryToKnockRoom(const QStringList& serverName) KnockRoomJob::KnockRoomJob(const QString& roomIdOrAlias, const QStringList& serverName, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("KnockRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/knock/" % roomIdOrAlias, + makePath("/_matrix/client/r0", "/knock/", roomIdOrAlias), queryToKnockRoom(serverName)) { QJsonObject _data; diff --git a/lib/csapi/leaving.cpp b/lib/csapi/leaving.cpp index f4c5f120..0e5386be 100644 --- a/lib/csapi/leaving.cpp +++ b/lib/csapi/leaving.cpp @@ -4,14 +4,11 @@ #include "leaving.h" -#include - using namespace Quotient; LeaveRoomJob::LeaveRoomJob(const QString& roomId, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("LeaveRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/leave") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/leave")) { QJsonObject _data; addParam(_data, QStringLiteral("reason"), reason); @@ -21,12 +18,11 @@ LeaveRoomJob::LeaveRoomJob(const QString& roomId, const QString& reason) QUrl ForgetRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/rooms/" % roomId % "/forget"); + makePath("/_matrix/client/r0", "/rooms/", + roomId, "/forget")); } ForgetRoomJob::ForgetRoomJob(const QString& roomId) : BaseJob(HttpVerb::Post, QStringLiteral("ForgetRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/forget") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/forget")) {} diff --git a/lib/csapi/list_joined_rooms.cpp b/lib/csapi/list_joined_rooms.cpp index 8d7e267f..22ba04da 100644 --- a/lib/csapi/list_joined_rooms.cpp +++ b/lib/csapi/list_joined_rooms.cpp @@ -4,20 +4,17 @@ #include "list_joined_rooms.h" -#include - using namespace Quotient; QUrl GetJoinedRoomsJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/joined_rooms"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), makePath("/_matrix/client/r0", "/joined_rooms")); } GetJoinedRoomsJob::GetJoinedRoomsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetJoinedRoomsJob"), - QStringLiteral("/_matrix/client/r0") % "/joined_rooms") + makePath("/_matrix/client/r0", "/joined_rooms")) { addExpectedKey("joined_rooms"); } diff --git a/lib/csapi/list_public_rooms.cpp b/lib/csapi/list_public_rooms.cpp index a4bcb934..25f8da5c 100644 --- a/lib/csapi/list_public_rooms.cpp +++ b/lib/csapi/list_public_rooms.cpp @@ -4,31 +4,27 @@ #include "list_public_rooms.h" -#include - using namespace Quotient; QUrl GetRoomVisibilityOnDirectoryJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/directory/list/room/" % roomId); + makePath("/_matrix/client/r0", + "/directory/list/room/", roomId)); } GetRoomVisibilityOnDirectoryJob::GetRoomVisibilityOnDirectoryJob( const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomVisibilityOnDirectoryJob"), - QStringLiteral("/_matrix/client/r0") % "/directory/list/room/" - % roomId, + makePath("/_matrix/client/r0", "/directory/list/room/", roomId), false) {} SetRoomVisibilityOnDirectoryJob::SetRoomVisibilityOnDirectoryJob( const QString& roomId, const QString& visibility) : BaseJob(HttpVerb::Put, QStringLiteral("SetRoomVisibilityOnDirectoryJob"), - QStringLiteral("/_matrix/client/r0") % "/directory/list/room/" - % roomId) + makePath("/_matrix/client/r0", "/directory/list/room/", roomId)) { QJsonObject _data; addParam(_data, QStringLiteral("visibility"), visibility); @@ -50,15 +46,15 @@ QUrl GetPublicRoomsJob::makeRequestUrl(QUrl baseUrl, Omittable limit, const QString& server) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/publicRooms", + makePath("/_matrix/client/r0", + "/publicRooms"), queryToGetPublicRooms(limit, since, server)); } GetPublicRoomsJob::GetPublicRoomsJob(Omittable limit, const QString& since, const QString& server) : BaseJob(HttpVerb::Get, QStringLiteral("GetPublicRoomsJob"), - QStringLiteral("/_matrix/client/r0") % "/publicRooms", + makePath("/_matrix/client/r0", "/publicRooms"), queryToGetPublicRooms(limit, since, server), {}, false) { addExpectedKey("chunk"); @@ -78,7 +74,7 @@ QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, Omittable includeAllNetworks, const QString& thirdPartyInstanceId) : BaseJob(HttpVerb::Post, QStringLiteral("QueryPublicRoomsJob"), - QStringLiteral("/_matrix/client/r0") % "/publicRooms", + makePath("/_matrix/client/r0", "/publicRooms"), queryToQueryPublicRooms(server)) { QJsonObject _data; diff --git a/lib/csapi/login.cpp b/lib/csapi/login.cpp index a5bac9ea..71fd93c5 100644 --- a/lib/csapi/login.cpp +++ b/lib/csapi/login.cpp @@ -4,20 +4,17 @@ #include "login.h" -#include - using namespace Quotient; QUrl GetLoginFlowsJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/login"); + makePath("/_matrix/client/r0", "/login")); } GetLoginFlowsJob::GetLoginFlowsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetLoginFlowsJob"), - QStringLiteral("/_matrix/client/r0") % "/login", false) + makePath("/_matrix/client/r0", "/login"), false) {} LoginJob::LoginJob(const QString& type, @@ -26,7 +23,7 @@ LoginJob::LoginJob(const QString& type, const QString& deviceId, const QString& initialDeviceDisplayName) : BaseJob(HttpVerb::Post, QStringLiteral("LoginJob"), - QStringLiteral("/_matrix/client/r0") % "/login", false) + makePath("/_matrix/client/r0", "/login"), false) { QJsonObject _data; addParam<>(_data, QStringLiteral("type"), type); diff --git a/lib/csapi/logout.cpp b/lib/csapi/logout.cpp index 9583b8ec..e8083e31 100644 --- a/lib/csapi/logout.cpp +++ b/lib/csapi/logout.cpp @@ -4,30 +4,26 @@ #include "logout.h" -#include - using namespace Quotient; QUrl LogoutJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/logout"); + makePath("/_matrix/client/r0", "/logout")); } LogoutJob::LogoutJob() : BaseJob(HttpVerb::Post, QStringLiteral("LogoutJob"), - QStringLiteral("/_matrix/client/r0") % "/logout") + makePath("/_matrix/client/r0", "/logout")) {} QUrl LogoutAllJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/logout/all"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), makePath("/_matrix/client/r0", "/logout/all")); } LogoutAllJob::LogoutAllJob() : BaseJob(HttpVerb::Post, QStringLiteral("LogoutAllJob"), - QStringLiteral("/_matrix/client/r0") % "/logout/all") + makePath("/_matrix/client/r0", "/logout/all")) {} diff --git a/lib/csapi/message_pagination.cpp b/lib/csapi/message_pagination.cpp index 441e4dea..1a93b75b 100644 --- a/lib/csapi/message_pagination.cpp +++ b/lib/csapi/message_pagination.cpp @@ -4,8 +4,6 @@ #include "message_pagination.h" -#include - using namespace Quotient; auto queryToGetRoomEvents(const QString& from, const QString& to, @@ -28,7 +26,7 @@ QUrl GetRoomEventsJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, { return BaseJob::makeRequestUrl( std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/messages", + makePath("/_matrix/client/r0", "/rooms/", roomId, "/messages"), queryToGetRoomEvents(from, to, dir, limit, filter)); } @@ -36,7 +34,6 @@ GetRoomEventsJob::GetRoomEventsJob(const QString& roomId, const QString& from, const QString& dir, const QString& to, Omittable limit, const QString& filter) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomEventsJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/messages", + makePath("/_matrix/client/r0", "/rooms/", roomId, "/messages"), queryToGetRoomEvents(from, to, dir, limit, filter)) {} diff --git a/lib/csapi/notifications.cpp b/lib/csapi/notifications.cpp index a38e46f5..1e523c6f 100644 --- a/lib/csapi/notifications.cpp +++ b/lib/csapi/notifications.cpp @@ -4,8 +4,6 @@ #include "notifications.h" -#include - using namespace Quotient; auto queryToGetNotifications(const QString& from, Omittable limit, @@ -23,8 +21,8 @@ QUrl GetNotificationsJob::makeRequestUrl(QUrl baseUrl, const QString& from, const QString& only) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/notifications", + makePath("/_matrix/client/r0", + "/notifications"), queryToGetNotifications(from, limit, only)); } @@ -32,7 +30,7 @@ GetNotificationsJob::GetNotificationsJob(const QString& from, Omittable limit, const QString& only) : BaseJob(HttpVerb::Get, QStringLiteral("GetNotificationsJob"), - QStringLiteral("/_matrix/client/r0") % "/notifications", + makePath("/_matrix/client/r0", "/notifications"), queryToGetNotifications(from, limit, only)) { addExpectedKey("notifications"); diff --git a/lib/csapi/openid.cpp b/lib/csapi/openid.cpp index 0447db79..5c93a2d7 100644 --- a/lib/csapi/openid.cpp +++ b/lib/csapi/openid.cpp @@ -4,15 +4,13 @@ #include "openid.h" -#include - using namespace Quotient; RequestOpenIdTokenJob::RequestOpenIdTokenJob(const QString& userId, const QJsonObject& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestOpenIdTokenJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/openid/request_token") + makePath("/_matrix/client/r0", "/user/", userId, + "/openid/request_token")) { setRequestData(RequestData(toJson(body))); } diff --git a/lib/csapi/peeking_events.cpp b/lib/csapi/peeking_events.cpp index ad2f9afe..eb5d22fa 100644 --- a/lib/csapi/peeking_events.cpp +++ b/lib/csapi/peeking_events.cpp @@ -4,8 +4,6 @@ #include "peeking_events.h" -#include - using namespace Quotient; auto queryToPeekEvents(const QString& from, Omittable timeout, @@ -22,14 +20,13 @@ QUrl PeekEventsJob::makeRequestUrl(QUrl baseUrl, const QString& from, Omittable timeout, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/events", + makePath("/_matrix/client/r0", "/events"), queryToPeekEvents(from, timeout, roomId)); } PeekEventsJob::PeekEventsJob(const QString& from, Omittable timeout, const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("PeekEventsJob"), - QStringLiteral("/_matrix/client/r0") % "/events", + makePath("/_matrix/client/r0", "/events"), queryToPeekEvents(from, timeout, roomId)) {} diff --git a/lib/csapi/presence.cpp b/lib/csapi/presence.cpp index 58d0d157..4f77c466 100644 --- a/lib/csapi/presence.cpp +++ b/lib/csapi/presence.cpp @@ -4,15 +4,12 @@ #include "presence.h" -#include - using namespace Quotient; SetPresenceJob::SetPresenceJob(const QString& userId, const QString& presence, const QString& statusMsg) : BaseJob(HttpVerb::Put, QStringLiteral("SetPresenceJob"), - QStringLiteral("/_matrix/client/r0") % "/presence/" % userId - % "/status") + makePath("/_matrix/client/r0", "/presence/", userId, "/status")) { QJsonObject _data; addParam<>(_data, QStringLiteral("presence"), presence); @@ -23,14 +20,13 @@ SetPresenceJob::SetPresenceJob(const QString& userId, const QString& presence, QUrl GetPresenceJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/presence/" % userId % "/status"); + makePath("/_matrix/client/r0", "/presence/", + userId, "/status")); } GetPresenceJob::GetPresenceJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetPresenceJob"), - QStringLiteral("/_matrix/client/r0") % "/presence/" % userId - % "/status") + makePath("/_matrix/client/r0", "/presence/", userId, "/status")) { addExpectedKey("presence"); } diff --git a/lib/csapi/profile.cpp b/lib/csapi/profile.cpp index 745fa488..64ac84ca 100644 --- a/lib/csapi/profile.cpp +++ b/lib/csapi/profile.cpp @@ -4,15 +4,12 @@ #include "profile.h" -#include - using namespace Quotient; SetDisplayNameJob::SetDisplayNameJob(const QString& userId, const QString& displayname) : BaseJob(HttpVerb::Put, QStringLiteral("SetDisplayNameJob"), - QStringLiteral("/_matrix/client/r0") % "/profile/" % userId - % "/displayname") + makePath("/_matrix/client/r0", "/profile/", userId, "/displayname")) { QJsonObject _data; addParam<>(_data, QStringLiteral("displayname"), displayname); @@ -22,21 +19,19 @@ SetDisplayNameJob::SetDisplayNameJob(const QString& userId, QUrl GetDisplayNameJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/profile/" % userId % "/displayname"); + makePath("/_matrix/client/r0", "/profile/", + userId, "/displayname")); } GetDisplayNameJob::GetDisplayNameJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetDisplayNameJob"), - QStringLiteral("/_matrix/client/r0") % "/profile/" % userId - % "/displayname", + makePath("/_matrix/client/r0", "/profile/", userId, "/displayname"), false) {} SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QUrl& avatarUrl) : BaseJob(HttpVerb::Put, QStringLiteral("SetAvatarUrlJob"), - QStringLiteral("/_matrix/client/r0") % "/profile/" % userId - % "/avatar_url") + makePath("/_matrix/client/r0", "/profile/", userId, "/avatar_url")) { QJsonObject _data; addParam<>(_data, QStringLiteral("avatar_url"), avatarUrl); @@ -46,25 +41,24 @@ SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QUrl& avatarUrl) QUrl GetAvatarUrlJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/profile/" % userId % "/avatar_url"); + makePath("/_matrix/client/r0", "/profile/", + userId, "/avatar_url")); } GetAvatarUrlJob::GetAvatarUrlJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetAvatarUrlJob"), - QStringLiteral("/_matrix/client/r0") % "/profile/" % userId - % "/avatar_url", + makePath("/_matrix/client/r0", "/profile/", userId, "/avatar_url"), false) {} QUrl GetUserProfileJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/profile/" % userId); + makePath("/_matrix/client/r0", "/profile/", + userId)); } GetUserProfileJob::GetUserProfileJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetUserProfileJob"), - QStringLiteral("/_matrix/client/r0") % "/profile/" % userId, false) + makePath("/_matrix/client/r0", "/profile/", userId), false) {} diff --git a/lib/csapi/pusher.cpp b/lib/csapi/pusher.cpp index 028022c5..ef4b3767 100644 --- a/lib/csapi/pusher.cpp +++ b/lib/csapi/pusher.cpp @@ -4,20 +4,17 @@ #include "pusher.h" -#include - using namespace Quotient; QUrl GetPushersJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/pushers"); + makePath("/_matrix/client/r0", "/pushers")); } GetPushersJob::GetPushersJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetPushersJob"), - QStringLiteral("/_matrix/client/r0") % "/pushers") + makePath("/_matrix/client/r0", "/pushers")) {} PostPusherJob::PostPusherJob(const QString& pushkey, const QString& kind, @@ -26,7 +23,7 @@ PostPusherJob::PostPusherJob(const QString& pushkey, const QString& kind, const QString& lang, const PusherData& data, const QString& profileTag, Omittable append) : BaseJob(HttpVerb::Post, QStringLiteral("PostPusherJob"), - QStringLiteral("/_matrix/client/r0") % "/pushers/set") + makePath("/_matrix/client/r0", "/pushers/set")) { QJsonObject _data; addParam<>(_data, QStringLiteral("pushkey"), pushkey); diff --git a/lib/csapi/pushrules.cpp b/lib/csapi/pushrules.cpp index ab7d0038..0d840788 100644 --- a/lib/csapi/pushrules.cpp +++ b/lib/csapi/pushrules.cpp @@ -4,20 +4,17 @@ #include "pushrules.h" -#include - using namespace Quotient; QUrl GetPushRulesJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/pushrules"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), makePath("/_matrix/client/r0", "/pushrules")); } GetPushRulesJob::GetPushRulesJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetPushRulesJob"), - QStringLiteral("/_matrix/client/r0") % "/pushrules") + makePath("/_matrix/client/r0", "/pushrules")) { addExpectedKey("global"); } @@ -26,16 +23,15 @@ QUrl GetPushRuleJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/pushrules/" % scope % "/" % kind - % "/" % ruleId); + makePath("/_matrix/client/r0", "/pushrules/", + scope, "/", kind, "/", ruleId)); } GetPushRuleJob::GetPushRuleJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, QStringLiteral("GetPushRuleJob"), - QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" - % kind % "/" % ruleId) + makePath("/_matrix/client/r0", "/pushrules/", scope, "/", kind, + "/", ruleId)) {} QUrl DeletePushRuleJob::makeRequestUrl(QUrl baseUrl, const QString& scope, @@ -43,16 +39,15 @@ QUrl DeletePushRuleJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& ruleId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/pushrules/" % scope % "/" % kind - % "/" % ruleId); + makePath("/_matrix/client/r0", "/pushrules/", + scope, "/", kind, "/", ruleId)); } DeletePushRuleJob::DeletePushRuleJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Delete, QStringLiteral("DeletePushRuleJob"), - QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" - % kind % "/" % ruleId) + makePath("/_matrix/client/r0", "/pushrules/", scope, "/", kind, + "/", ruleId)) {} auto queryToSetPushRule(const QString& before, const QString& after) @@ -70,8 +65,8 @@ SetPushRuleJob::SetPushRuleJob(const QString& scope, const QString& kind, const QVector& conditions, const QString& pattern) : BaseJob(HttpVerb::Put, QStringLiteral("SetPushRuleJob"), - QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" - % kind % "/" % ruleId, + makePath("/_matrix/client/r0", "/pushrules/", scope, "/", kind, + "/", ruleId), queryToSetPushRule(before, after)) { QJsonObject _data; @@ -86,17 +81,17 @@ QUrl IsPushRuleEnabledJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& ruleId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/pushrules/" % scope % "/" % kind - % "/" % ruleId % "/enabled"); + makePath("/_matrix/client/r0", "/pushrules/", + scope, "/", kind, "/", ruleId, + "/enabled")); } IsPushRuleEnabledJob::IsPushRuleEnabledJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, QStringLiteral("IsPushRuleEnabledJob"), - QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" - % kind % "/" % ruleId % "/enabled") + makePath("/_matrix/client/r0", "/pushrules/", scope, "/", kind, + "/", ruleId, "/enabled")) { addExpectedKey("enabled"); } @@ -105,8 +100,8 @@ SetPushRuleEnabledJob::SetPushRuleEnabledJob(const QString& scope, const QString& kind, const QString& ruleId, bool enabled) : BaseJob(HttpVerb::Put, QStringLiteral("SetPushRuleEnabledJob"), - QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" - % kind % "/" % ruleId % "/enabled") + makePath("/_matrix/client/r0", "/pushrules/", scope, "/", kind, + "/", ruleId, "/enabled")) { QJsonObject _data; addParam<>(_data, QStringLiteral("enabled"), enabled); @@ -118,17 +113,17 @@ QUrl GetPushRuleActionsJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& ruleId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/pushrules/" % scope % "/" % kind - % "/" % ruleId % "/actions"); + makePath("/_matrix/client/r0", "/pushrules/", + scope, "/", kind, "/", ruleId, + "/actions")); } GetPushRuleActionsJob::GetPushRuleActionsJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, QStringLiteral("GetPushRuleActionsJob"), - QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" - % kind % "/" % ruleId % "/actions") + makePath("/_matrix/client/r0", "/pushrules/", scope, "/", kind, + "/", ruleId, "/actions")) { addExpectedKey("actions"); } @@ -138,8 +133,8 @@ SetPushRuleActionsJob::SetPushRuleActionsJob(const QString& scope, const QString& ruleId, const QVector& actions) : BaseJob(HttpVerb::Put, QStringLiteral("SetPushRuleActionsJob"), - QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" - % kind % "/" % ruleId % "/actions") + makePath("/_matrix/client/r0", "/pushrules/", scope, "/", kind, + "/", ruleId, "/actions")) { QJsonObject _data; addParam<>(_data, QStringLiteral("actions"), actions); diff --git a/lib/csapi/read_markers.cpp b/lib/csapi/read_markers.cpp index 39e4d148..f2edb71e 100644 --- a/lib/csapi/read_markers.cpp +++ b/lib/csapi/read_markers.cpp @@ -4,16 +4,13 @@ #include "read_markers.h" -#include - using namespace Quotient; SetReadMarkerJob::SetReadMarkerJob(const QString& roomId, const QString& mFullyRead, const QString& mRead) : BaseJob(HttpVerb::Post, QStringLiteral("SetReadMarkerJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/read_markers") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/read_markers")) { QJsonObject _data; addParam<>(_data, QStringLiteral("m.fully_read"), mFullyRead); diff --git a/lib/csapi/receipts.cpp b/lib/csapi/receipts.cpp index 47b18174..401c3bfe 100644 --- a/lib/csapi/receipts.cpp +++ b/lib/csapi/receipts.cpp @@ -4,16 +4,14 @@ #include "receipts.h" -#include - using namespace Quotient; PostReceiptJob::PostReceiptJob(const QString& roomId, const QString& receiptType, const QString& eventId, const QJsonObject& receipt) : BaseJob(HttpVerb::Post, QStringLiteral("PostReceiptJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/receipt/" % receiptType % "/" % eventId) + makePath("/_matrix/client/r0", "/rooms/", roomId, "/receipt/", + receiptType, "/", eventId)) { setRequestData(RequestData(toJson(receipt))); } diff --git a/lib/csapi/redaction.cpp b/lib/csapi/redaction.cpp index 91497064..acf1b0e4 100644 --- a/lib/csapi/redaction.cpp +++ b/lib/csapi/redaction.cpp @@ -4,15 +4,13 @@ #include "redaction.h" -#include - using namespace Quotient; RedactEventJob::RedactEventJob(const QString& roomId, const QString& eventId, const QString& txnId, const QString& reason) : BaseJob(HttpVerb::Put, QStringLiteral("RedactEventJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/redact/" % eventId % "/" % txnId) + makePath("/_matrix/client/r0", "/rooms/", roomId, "/redact/", + eventId, "/", txnId)) { QJsonObject _data; addParam(_data, QStringLiteral("reason"), reason); diff --git a/lib/csapi/registration.cpp b/lib/csapi/registration.cpp index c3617bfc..153abcee 100644 --- a/lib/csapi/registration.cpp +++ b/lib/csapi/registration.cpp @@ -4,8 +4,6 @@ #include "registration.h" -#include - using namespace Quotient; auto queryToRegister(const QString& kind) @@ -22,7 +20,7 @@ RegisterJob::RegisterJob(const QString& kind, const QString& initialDeviceDisplayName, Omittable inhibitLogin) : BaseJob(HttpVerb::Post, QStringLiteral("RegisterJob"), - QStringLiteral("/_matrix/client/r0") % "/register", + makePath("/_matrix/client/r0", "/register"), queryToRegister(kind), {}, false) { QJsonObject _data; @@ -40,8 +38,7 @@ RegisterJob::RegisterJob(const QString& kind, RequestTokenToRegisterEmailJob::RequestTokenToRegisterEmailJob( const EmailValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenToRegisterEmailJob"), - QStringLiteral("/_matrix/client/r0") - % "/register/email/requestToken", + makePath("/_matrix/client/r0", "/register/email/requestToken"), false) { setRequestData(RequestData(toJson(body))); @@ -50,8 +47,7 @@ RequestTokenToRegisterEmailJob::RequestTokenToRegisterEmailJob( RequestTokenToRegisterMSISDNJob::RequestTokenToRegisterMSISDNJob( const MsisdnValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenToRegisterMSISDNJob"), - QStringLiteral("/_matrix/client/r0") - % "/register/msisdn/requestToken", + makePath("/_matrix/client/r0", "/register/msisdn/requestToken"), false) { setRequestData(RequestData(toJson(body))); @@ -61,7 +57,7 @@ ChangePasswordJob::ChangePasswordJob(const QString& newPassword, bool logoutDevices, const Omittable& auth) : BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), - QStringLiteral("/_matrix/client/r0") % "/account/password") + makePath("/_matrix/client/r0", "/account/password")) { QJsonObject _data; addParam<>(_data, QStringLiteral("new_password"), newPassword); @@ -74,8 +70,8 @@ RequestTokenToResetPasswordEmailJob::RequestTokenToResetPasswordEmailJob( const EmailValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenToResetPasswordEmailJob"), - QStringLiteral("/_matrix/client/r0") - % "/account/password/email/requestToken", + makePath("/_matrix/client/r0", + "/account/password/email/requestToken"), false) { setRequestData(RequestData(toJson(body))); @@ -85,8 +81,8 @@ RequestTokenToResetPasswordMSISDNJob::RequestTokenToResetPasswordMSISDNJob( const MsisdnValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenToResetPasswordMSISDNJob"), - QStringLiteral("/_matrix/client/r0") - % "/account/password/msisdn/requestToken", + makePath("/_matrix/client/r0", + "/account/password/msisdn/requestToken"), false) { setRequestData(RequestData(toJson(body))); @@ -95,7 +91,7 @@ RequestTokenToResetPasswordMSISDNJob::RequestTokenToResetPasswordMSISDNJob( DeactivateAccountJob::DeactivateAccountJob( const Omittable& auth, const QString& idServer) : BaseJob(HttpVerb::Post, QStringLiteral("DeactivateAccountJob"), - QStringLiteral("/_matrix/client/r0") % "/account/deactivate") + makePath("/_matrix/client/r0", "/account/deactivate")) { QJsonObject _data; addParam(_data, QStringLiteral("auth"), auth); @@ -115,13 +111,13 @@ QUrl CheckUsernameAvailabilityJob::makeRequestUrl(QUrl baseUrl, const QString& username) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/register/available", + makePath("/_matrix/client/r0", + "/register/available"), queryToCheckUsernameAvailability(username)); } CheckUsernameAvailabilityJob::CheckUsernameAvailabilityJob(const QString& username) : BaseJob(HttpVerb::Get, QStringLiteral("CheckUsernameAvailabilityJob"), - QStringLiteral("/_matrix/client/r0") % "/register/available", + makePath("/_matrix/client/r0", "/register/available"), queryToCheckUsernameAvailability(username), {}, false) {} diff --git a/lib/csapi/report_content.cpp b/lib/csapi/report_content.cpp index ea906380..0a76d5b8 100644 --- a/lib/csapi/report_content.cpp +++ b/lib/csapi/report_content.cpp @@ -4,15 +4,13 @@ #include "report_content.h" -#include - using namespace Quotient; ReportContentJob::ReportContentJob(const QString& roomId, const QString& eventId, Omittable score, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("ReportContentJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/report/" % eventId) + makePath("/_matrix/client/r0", "/rooms/", roomId, "/report/", + eventId)) { QJsonObject _data; addParam(_data, QStringLiteral("score"), score); diff --git a/lib/csapi/room_send.cpp b/lib/csapi/room_send.cpp index 9fd8cb96..f80f9300 100644 --- a/lib/csapi/room_send.cpp +++ b/lib/csapi/room_send.cpp @@ -4,15 +4,13 @@ #include "room_send.h" -#include - using namespace Quotient; SendMessageJob::SendMessageJob(const QString& roomId, const QString& eventType, const QString& txnId, const QJsonObject& body) : BaseJob(HttpVerb::Put, QStringLiteral("SendMessageJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/send/" % eventType % "/" % txnId) + makePath("/_matrix/client/r0", "/rooms/", roomId, "/send/", + eventType, "/", txnId)) { setRequestData(RequestData(toJson(body))); addExpectedKey("event_id"); diff --git a/lib/csapi/room_state.cpp b/lib/csapi/room_state.cpp index 37e897fa..f6d2e6ec 100644 --- a/lib/csapi/room_state.cpp +++ b/lib/csapi/room_state.cpp @@ -4,8 +4,6 @@ #include "room_state.h" -#include - using namespace Quotient; SetRoomStateWithKeyJob::SetRoomStateWithKeyJob(const QString& roomId, @@ -13,8 +11,8 @@ SetRoomStateWithKeyJob::SetRoomStateWithKeyJob(const QString& roomId, const QString& stateKey, const QJsonObject& body) : BaseJob(HttpVerb::Put, QStringLiteral("SetRoomStateWithKeyJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/state/" % eventType % "/" % stateKey) + makePath("/_matrix/client/r0", "/rooms/", roomId, "/state/", + eventType, "/", stateKey)) { setRequestData(RequestData(toJson(body))); addExpectedKey("event_id"); diff --git a/lib/csapi/room_upgrades.cpp b/lib/csapi/room_upgrades.cpp index e3791b08..d4129cfb 100644 --- a/lib/csapi/room_upgrades.cpp +++ b/lib/csapi/room_upgrades.cpp @@ -4,14 +4,11 @@ #include "room_upgrades.h" -#include - using namespace Quotient; UpgradeRoomJob::UpgradeRoomJob(const QString& roomId, const QString& newVersion) : BaseJob(HttpVerb::Post, QStringLiteral("UpgradeRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/upgrade") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/upgrade")) { QJsonObject _data; addParam<>(_data, QStringLiteral("new_version"), newVersion); diff --git a/lib/csapi/rooms.cpp b/lib/csapi/rooms.cpp index 3dd87021..5310aa32 100644 --- a/lib/csapi/rooms.cpp +++ b/lib/csapi/rooms.cpp @@ -4,24 +4,21 @@ #include "rooms.h" -#include - using namespace Quotient; QUrl GetOneRoomEventJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& eventId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/rooms/" % roomId % "/event/" - % eventId); + makePath("/_matrix/client/r0", "/rooms/", + roomId, "/event/", eventId)); } GetOneRoomEventJob::GetOneRoomEventJob(const QString& roomId, const QString& eventId) : BaseJob(HttpVerb::Get, QStringLiteral("GetOneRoomEventJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/event/" % eventId) + makePath("/_matrix/client/r0", "/rooms/", roomId, "/event/", + eventId)) {} QUrl GetRoomStateWithKeyJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, @@ -29,30 +26,29 @@ QUrl GetRoomStateWithKeyJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& stateKey) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/rooms/" % roomId % "/state/" - % eventType % "/" % stateKey); + makePath("/_matrix/client/r0", "/rooms/", + roomId, "/state/", eventType, "/", + stateKey)); } GetRoomStateWithKeyJob::GetRoomStateWithKeyJob(const QString& roomId, const QString& eventType, const QString& stateKey) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomStateWithKeyJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/state/" % eventType % "/" % stateKey) + makePath("/_matrix/client/r0", "/rooms/", roomId, "/state/", + eventType, "/", stateKey)) {} QUrl GetRoomStateJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/rooms/" % roomId % "/state"); + makePath("/_matrix/client/r0", "/rooms/", + roomId, "/state")); } GetRoomStateJob::GetRoomStateJob(const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomStateJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/state") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/state")) {} auto queryToGetMembersByRoom(const QString& at, const QString& membership, @@ -72,7 +68,7 @@ QUrl GetMembersByRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, { return BaseJob::makeRequestUrl( std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/members", + makePath("/_matrix/client/r0", "/rooms/", roomId, "/members"), queryToGetMembersByRoom(at, membership, notMembership)); } @@ -81,8 +77,7 @@ GetMembersByRoomJob::GetMembersByRoomJob(const QString& roomId, const QString& membership, const QString& notMembership) : BaseJob(HttpVerb::Get, QStringLiteral("GetMembersByRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/members", + makePath("/_matrix/client/r0", "/rooms/", roomId, "/members"), queryToGetMembersByRoom(at, membership, notMembership)) {} @@ -90,12 +85,12 @@ QUrl GetJoinedMembersByRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/rooms/" % roomId % "/joined_members"); + makePath("/_matrix/client/r0", "/rooms/", + roomId, "/joined_members")); } GetJoinedMembersByRoomJob::GetJoinedMembersByRoomJob(const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("GetJoinedMembersByRoomJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/joined_members") + makePath("/_matrix/client/r0", "/rooms/", roomId, + "/joined_members")) {} diff --git a/lib/csapi/search.cpp b/lib/csapi/search.cpp index 05ad871e..295dd1cc 100644 --- a/lib/csapi/search.cpp +++ b/lib/csapi/search.cpp @@ -4,8 +4,6 @@ #include "search.h" -#include - using namespace Quotient; auto queryToSearch(const QString& nextBatch) @@ -18,7 +16,7 @@ auto queryToSearch(const QString& nextBatch) SearchJob::SearchJob(const Categories& searchCategories, const QString& nextBatch) : BaseJob(HttpVerb::Post, QStringLiteral("SearchJob"), - QStringLiteral("/_matrix/client/r0") % "/search", + makePath("/_matrix/client/r0", "/search"), queryToSearch(nextBatch)) { QJsonObject _data; diff --git a/lib/csapi/sso_login_redirect.cpp b/lib/csapi/sso_login_redirect.cpp index 92601b4d..871d6ff6 100644 --- a/lib/csapi/sso_login_redirect.cpp +++ b/lib/csapi/sso_login_redirect.cpp @@ -4,8 +4,6 @@ #include "sso_login_redirect.h" -#include - using namespace Quotient; auto queryToRedirectToSSO(const QString& redirectUrl) @@ -18,14 +16,14 @@ auto queryToRedirectToSSO(const QString& redirectUrl) QUrl RedirectToSSOJob::makeRequestUrl(QUrl baseUrl, const QString& redirectUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/login/sso/redirect", + makePath("/_matrix/client/r0", + "/login/sso/redirect"), queryToRedirectToSSO(redirectUrl)); } RedirectToSSOJob::RedirectToSSOJob(const QString& redirectUrl) : BaseJob(HttpVerb::Get, QStringLiteral("RedirectToSSOJob"), - QStringLiteral("/_matrix/client/r0") % "/login/sso/redirect", + makePath("/_matrix/client/r0", "/login/sso/redirect"), queryToRedirectToSSO(redirectUrl), {}, false) {} @@ -40,15 +38,14 @@ QUrl RedirectToIdPJob::makeRequestUrl(QUrl baseUrl, const QString& idpId, const QString& redirectUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/login/sso/redirect/" % idpId, + makePath("/_matrix/client/r0", + "/login/sso/redirect/", idpId), queryToRedirectToIdP(redirectUrl)); } RedirectToIdPJob::RedirectToIdPJob(const QString& idpId, const QString& redirectUrl) : BaseJob(HttpVerb::Get, QStringLiteral("RedirectToIdPJob"), - QStringLiteral("/_matrix/client/r0") % "/login/sso/redirect/" - % idpId, + makePath("/_matrix/client/r0", "/login/sso/redirect/", idpId), queryToRedirectToIdP(redirectUrl), {}, false) {} diff --git a/lib/csapi/tags.cpp b/lib/csapi/tags.cpp index dc22dc18..f717de6e 100644 --- a/lib/csapi/tags.cpp +++ b/lib/csapi/tags.cpp @@ -4,30 +4,28 @@ #include "tags.h" -#include - using namespace Quotient; QUrl GetRoomTagsJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") % "/user/" - % userId % "/rooms/" % roomId % "/tags"); + makePath("/_matrix/client/r0", "/user/", + userId, "/rooms/", roomId, "/tags")); } GetRoomTagsJob::GetRoomTagsJob(const QString& userId, const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomTagsJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/rooms/" % roomId % "/tags") + makePath("/_matrix/client/r0", "/user/", userId, "/rooms/", + roomId, "/tags")) {} SetRoomTagJob::SetRoomTagJob(const QString& userId, const QString& roomId, const QString& tag, Omittable order, const QVariantHash& additionalProperties) : BaseJob(HttpVerb::Put, QStringLiteral("SetRoomTagJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/rooms/" % roomId % "/tags/" % tag) + makePath("/_matrix/client/r0", "/user/", userId, "/rooms/", + roomId, "/tags/", tag)) { QJsonObject _data; fillJson(_data, additionalProperties); @@ -39,14 +37,14 @@ QUrl DeleteRoomTagJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& roomId, const QString& tag) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/user/" % userId % "/rooms/" % roomId - % "/tags/" % tag); + makePath("/_matrix/client/r0", "/user/", + userId, "/rooms/", roomId, "/tags/", + tag)); } DeleteRoomTagJob::DeleteRoomTagJob(const QString& userId, const QString& roomId, const QString& tag) : BaseJob(HttpVerb::Delete, QStringLiteral("DeleteRoomTagJob"), - QStringLiteral("/_matrix/client/r0") % "/user/" % userId - % "/rooms/" % roomId % "/tags/" % tag) + makePath("/_matrix/client/r0", "/user/", userId, "/rooms/", + roomId, "/tags/", tag)) {} diff --git a/lib/csapi/third_party_lookup.cpp b/lib/csapi/third_party_lookup.cpp index 93687a76..4c930668 100644 --- a/lib/csapi/third_party_lookup.cpp +++ b/lib/csapi/third_party_lookup.cpp @@ -4,34 +4,31 @@ #include "third_party_lookup.h" -#include - using namespace Quotient; QUrl GetProtocolsJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/thirdparty/protocols"); + makePath("/_matrix/client/r0", + "/thirdparty/protocols")); } GetProtocolsJob::GetProtocolsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetProtocolsJob"), - QStringLiteral("/_matrix/client/r0") % "/thirdparty/protocols") + makePath("/_matrix/client/r0", "/thirdparty/protocols")) {} QUrl GetProtocolMetadataJob::makeRequestUrl(QUrl baseUrl, const QString& protocol) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/thirdparty/protocol/" % protocol); + makePath("/_matrix/client/r0", + "/thirdparty/protocol/", protocol)); } GetProtocolMetadataJob::GetProtocolMetadataJob(const QString& protocol) : BaseJob(HttpVerb::Get, QStringLiteral("GetProtocolMetadataJob"), - QStringLiteral("/_matrix/client/r0") % "/thirdparty/protocol/" - % protocol) + makePath("/_matrix/client/r0", "/thirdparty/protocol/", protocol)) {} auto queryToQueryLocationByProtocol(const QString& searchFields) @@ -46,16 +43,15 @@ QUrl QueryLocationByProtocolJob::makeRequestUrl(QUrl baseUrl, const QString& searchFields) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/thirdparty/location/" % protocol, + makePath("/_matrix/client/r0", + "/thirdparty/location/", protocol), queryToQueryLocationByProtocol(searchFields)); } QueryLocationByProtocolJob::QueryLocationByProtocolJob( const QString& protocol, const QString& searchFields) : BaseJob(HttpVerb::Get, QStringLiteral("QueryLocationByProtocolJob"), - QStringLiteral("/_matrix/client/r0") % "/thirdparty/location/" - % protocol, + makePath("/_matrix/client/r0", "/thirdparty/location/", protocol), queryToQueryLocationByProtocol(searchFields)) {} @@ -71,16 +67,15 @@ QUrl QueryUserByProtocolJob::makeRequestUrl(QUrl baseUrl, const QString& fields) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/thirdparty/user/" % protocol, + makePath("/_matrix/client/r0", + "/thirdparty/user/", protocol), queryToQueryUserByProtocol(fields)); } QueryUserByProtocolJob::QueryUserByProtocolJob(const QString& protocol, const QString& fields) : BaseJob(HttpVerb::Get, QStringLiteral("QueryUserByProtocolJob"), - QStringLiteral("/_matrix/client/r0") % "/thirdparty/user/" - % protocol, + makePath("/_matrix/client/r0", "/thirdparty/user/", protocol), queryToQueryUserByProtocol(fields)) {} @@ -94,14 +89,14 @@ auto queryToQueryLocationByAlias(const QString& alias) QUrl QueryLocationByAliasJob::makeRequestUrl(QUrl baseUrl, const QString& alias) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/thirdparty/location", + makePath("/_matrix/client/r0", + "/thirdparty/location"), queryToQueryLocationByAlias(alias)); } QueryLocationByAliasJob::QueryLocationByAliasJob(const QString& alias) : BaseJob(HttpVerb::Get, QStringLiteral("QueryLocationByAliasJob"), - QStringLiteral("/_matrix/client/r0") % "/thirdparty/location", + makePath("/_matrix/client/r0", "/thirdparty/location"), queryToQueryLocationByAlias(alias)) {} @@ -115,13 +110,13 @@ auto queryToQueryUserByID(const QString& userid) QUrl QueryUserByIDJob::makeRequestUrl(QUrl baseUrl, const QString& userid) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/thirdparty/user", + makePath("/_matrix/client/r0", + "/thirdparty/user"), queryToQueryUserByID(userid)); } QueryUserByIDJob::QueryUserByIDJob(const QString& userid) : BaseJob(HttpVerb::Get, QStringLiteral("QueryUserByIDJob"), - QStringLiteral("/_matrix/client/r0") % "/thirdparty/user", + makePath("/_matrix/client/r0", "/thirdparty/user"), queryToQueryUserByID(userid)) {} diff --git a/lib/csapi/third_party_membership.cpp b/lib/csapi/third_party_membership.cpp index fda772d2..59275e41 100644 --- a/lib/csapi/third_party_membership.cpp +++ b/lib/csapi/third_party_membership.cpp @@ -4,16 +4,13 @@ #include "third_party_membership.h" -#include - using namespace Quotient; InviteBy3PIDJob::InviteBy3PIDJob(const QString& roomId, const QString& idServer, const QString& idAccessToken, const QString& medium, const QString& address) : BaseJob(HttpVerb::Post, QStringLiteral("InviteBy3PIDJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/invite") + makePath("/_matrix/client/r0", "/rooms/", roomId, "/invite")) { QJsonObject _data; addParam<>(_data, QStringLiteral("id_server"), idServer); diff --git a/lib/csapi/to_device.cpp b/lib/csapi/to_device.cpp index 3775174d..628e8314 100644 --- a/lib/csapi/to_device.cpp +++ b/lib/csapi/to_device.cpp @@ -4,16 +4,14 @@ #include "to_device.h" -#include - using namespace Quotient; SendToDeviceJob::SendToDeviceJob( const QString& eventType, const QString& txnId, const QHash>& messages) : BaseJob(HttpVerb::Put, QStringLiteral("SendToDeviceJob"), - QStringLiteral("/_matrix/client/r0") % "/sendToDevice/" - % eventType % "/" % txnId) + makePath("/_matrix/client/r0", "/sendToDevice/", eventType, "/", + txnId)) { QJsonObject _data; addParam<>(_data, QStringLiteral("messages"), messages); diff --git a/lib/csapi/typing.cpp b/lib/csapi/typing.cpp index 8e214053..c9673118 100644 --- a/lib/csapi/typing.cpp +++ b/lib/csapi/typing.cpp @@ -4,15 +4,13 @@ #include "typing.h" -#include - using namespace Quotient; SetTypingJob::SetTypingJob(const QString& userId, const QString& roomId, bool typing, Omittable timeout) : BaseJob(HttpVerb::Put, QStringLiteral("SetTypingJob"), - QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId - % "/typing/" % userId) + makePath("/_matrix/client/r0", "/rooms/", roomId, "/typing/", + userId)) { QJsonObject _data; addParam<>(_data, QStringLiteral("typing"), typing); diff --git a/lib/csapi/users.cpp b/lib/csapi/users.cpp index a0279d7e..48b727f0 100644 --- a/lib/csapi/users.cpp +++ b/lib/csapi/users.cpp @@ -4,14 +4,12 @@ #include "users.h" -#include - using namespace Quotient; SearchUserDirectoryJob::SearchUserDirectoryJob(const QString& searchTerm, Omittable limit) : BaseJob(HttpVerb::Post, QStringLiteral("SearchUserDirectoryJob"), - QStringLiteral("/_matrix/client/r0") % "/user_directory/search") + makePath("/_matrix/client/r0", "/user_directory/search")) { QJsonObject _data; addParam<>(_data, QStringLiteral("search_term"), searchTerm); diff --git a/lib/csapi/versions.cpp b/lib/csapi/versions.cpp index 9003e27f..a1efc33e 100644 --- a/lib/csapi/versions.cpp +++ b/lib/csapi/versions.cpp @@ -4,20 +4,17 @@ #include "versions.h" -#include - using namespace Quotient; QUrl GetVersionsJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client") - % "/versions"); + makePath("/_matrix/client", "/versions")); } GetVersionsJob::GetVersionsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetVersionsJob"), - QStringLiteral("/_matrix/client") % "/versions", false) + makePath("/_matrix/client", "/versions"), false) { addExpectedKey("versions"); } diff --git a/lib/csapi/voip.cpp b/lib/csapi/voip.cpp index 43170057..c748ad94 100644 --- a/lib/csapi/voip.cpp +++ b/lib/csapi/voip.cpp @@ -4,18 +4,15 @@ #include "voip.h" -#include - using namespace Quotient; QUrl GetTurnServerJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/voip/turnServer"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), makePath("/_matrix/client/r0", "/voip/turnServer")); } GetTurnServerJob::GetTurnServerJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetTurnServerJob"), - QStringLiteral("/_matrix/client/r0") % "/voip/turnServer") + makePath("/_matrix/client/r0", "/voip/turnServer")) {} diff --git a/lib/csapi/wellknown.cpp b/lib/csapi/wellknown.cpp index 1aa0a90b..0b441279 100644 --- a/lib/csapi/wellknown.cpp +++ b/lib/csapi/wellknown.cpp @@ -4,18 +4,15 @@ #include "wellknown.h" -#include - using namespace Quotient; QUrl GetWellknownJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/.well-known") - % "/matrix/client"); + makePath("/.well-known", "/matrix/client")); } GetWellknownJob::GetWellknownJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetWellknownJob"), - QStringLiteral("/.well-known") % "/matrix/client", false) + makePath("/.well-known", "/matrix/client"), false) {} diff --git a/lib/csapi/whoami.cpp b/lib/csapi/whoami.cpp index 73f0298e..ed8a9817 100644 --- a/lib/csapi/whoami.cpp +++ b/lib/csapi/whoami.cpp @@ -4,20 +4,17 @@ #include "whoami.h" -#include - using namespace Quotient; QUrl GetTokenOwnerJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - QStringLiteral("/_matrix/client/r0") - % "/account/whoami"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), makePath("/_matrix/client/r0", "/account/whoami")); } GetTokenOwnerJob::GetTokenOwnerJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetTokenOwnerJob"), - QStringLiteral("/_matrix/client/r0") % "/account/whoami") + makePath("/_matrix/client/r0", "/account/whoami")) { addExpectedKey("user_id"); } -- cgit v1.2.3 From f89285a3bd7c093622be966823d0ae1254822905 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 4 Oct 2021 18:48:12 +0200 Subject: Room: use more modern Connection API --- lib/room.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index fb65fd84..a923bf9a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1180,9 +1180,8 @@ QUrl Room::urlToThumbnail(const QString& eventId) const if (event->hasThumbnail()) { auto* thumbnail = event->content()->thumbnailInfo(); Q_ASSERT(thumbnail != nullptr); - return MediaThumbnailJob::makeRequestUrl(connection()->homeserver(), - thumbnail->url, - thumbnail->imageSize); + return connection()->getUrlForApi( + thumbnail->url, thumbnail->imageSize); } qCDebug(MAIN) << "Event" << eventId << "has no thumbnail"; return {}; @@ -1193,8 +1192,7 @@ QUrl Room::urlToDownload(const QString& eventId) const if (auto* event = d->getEventWithFile(eventId)) { auto* fileInfo = event->content()->fileInfo(); Q_ASSERT(fileInfo != nullptr); - return DownloadFileJob::makeRequestUrl(connection()->homeserver(), - fileInfo->url); + return connection()->getUrlForApi(fileInfo->url); } return {}; } -- cgit v1.2.3 From 67da887e864d292608e7132388f518596374af34 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 5 Oct 2021 03:43:22 +0200 Subject: BaseJob::StatusCode: officially deprecate most *Error enumerators --- lib/connection.cpp | 8 ++++---- lib/jobs/basejob.cpp | 42 +++++++++++++++++++++--------------------- lib/jobs/basejob.h | 34 ++++++++++++++++------------------ 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 4abf5097..093362ab 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -288,7 +288,7 @@ void Connection::resolveServer(const QString& mxid) if (d->resolverJob->error() == BaseJob::Abandoned) return; - if (d->resolverJob->error() != BaseJob::NotFoundError) { + if (d->resolverJob->error() != BaseJob::NotFound) { if (!d->resolverJob->status().good()) { qCWarning(MAIN) << "Fetching .well-known file failed, FAIL_PROMPT"; @@ -401,7 +401,7 @@ void Connection::reloadCapabilities() " disabling version upgrade recommendations to reduce noise"; }); connect(d->capabilitiesJob, &BaseJob::failure, this, [this] { - if (d->capabilitiesJob->error() == BaseJob::IncorrectRequestError) + if (d->capabilitiesJob->error() == BaseJob::IncorrectRequest) qCDebug(MAIN) << "Server doesn't support /capabilities;" " version upgrade recommendations won't be issued"; }); @@ -1058,7 +1058,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) connect(leaveJob, &BaseJob::result, this, [this, leaveJob, forgetJob, room] { if (leaveJob->error() == BaseJob::Success - || leaveJob->error() == BaseJob::NotFoundError) { + || leaveJob->error() == BaseJob::NotFound) { run(forgetJob); // If the matching /sync response hasn't arrived yet, // mark the room for explicit deletion @@ -1077,7 +1077,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) connect(forgetJob, &BaseJob::result, this, [this, id, forgetJob] { // Leave room in case of success, or room not known by server if (forgetJob->error() == BaseJob::Success - || forgetJob->error() == BaseJob::NotFoundError) + || forgetJob->error() == BaseJob::NotFound) d->removeRoom(id); // Delete the room from roomMap else qCWarning(MAIN).nospace() << "Error forgetting room " << id << ": " diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 73762e4f..a7921c61 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -24,7 +24,7 @@ BaseJob::StatusCode BaseJob::Status::fromHttpCode(int httpCode) { // Based on https://en.wikipedia.org/wiki/List_of_HTTP_status_codes if (httpCode / 10 == 41) // 41x errors - return httpCode == 410 ? IncorrectRequestError : NotFoundError; + return httpCode == 410 ? IncorrectRequest : NotFound; switch (httpCode) { case 401: return Unauthorised; @@ -32,19 +32,19 @@ BaseJob::StatusCode BaseJob::Status::fromHttpCode(int httpCode) case 403: case 407: // clang-format on return ContentAccessError; case 404: - return NotFoundError; + return NotFound; // clang-format off case 400: case 405: case 406: case 426: case 428: case 505: // clang-format on case 494: // Unofficial nginx "Request header too large" case 497: // Unofficial nginx "HTTP request sent to HTTPS port" - return IncorrectRequestError; + return IncorrectRequest; case 429: - return TooManyRequestsError; + return TooManyRequests; case 501: case 510: - return RequestNotImplementedError; + return RequestNotImplemented; case 511: - return NetworkAuthRequiredError; + return NetworkAuthRequired; default: return NetworkError; } @@ -365,7 +365,7 @@ void BaseJob::initiate(ConnectionData* connData, bool inBackground) qCCritical(d->logCat) << "Developers, ensure the Connection is valid before using it"; Q_ASSERT(false); - setStatus(IncorrectRequestError, tr("Invalid server connection")); + setStatus(IncorrectRequest, tr("Invalid server connection")); } // The status is no good, finalise QTimer::singleShot(0, this, &BaseJob::finishJob); @@ -528,7 +528,7 @@ BaseJob::Status BaseJob::prepareError() // of if's below will fall through to `return NoError` at the end const auto& errorJson = jsonData(); const auto errCode = errorJson.value("errcode"_ls).toString(); - if (error() == TooManyRequestsError || errCode == "M_LIMIT_EXCEEDED") { + if (error() == TooManyRequests || errCode == "M_LIMIT_EXCEEDED") { QString msg = tr("Too many requests"); int64_t retryAfterMs = errorJson.value("retry_after_ms"_ls).toInt(-1); if (retryAfterMs >= 0) @@ -538,16 +538,16 @@ BaseJob::Status BaseJob::prepareError() d->connection->limitRate(milliseconds(retryAfterMs)); - return { TooManyRequestsError, msg }; + return { TooManyRequests, msg }; } if (errCode == "M_CONSENT_NOT_GIVEN") { d->errorUrl = QUrl(errorJson.value("consent_uri"_ls).toString()); - return { UserConsentRequiredError }; + return { UserConsentRequired }; } if (errCode == "M_UNSUPPORTED_ROOM_VERSION" || errCode == "M_INCOMPATIBLE_ROOM_VERSION") - return { UnsupportedRoomVersionError, + return { UnsupportedRoomVersion, errorJson.contains("room_version"_ls) ? tr("Requested room version: %1") .arg(errorJson.value("room_version"_ls).toString()) @@ -729,27 +729,27 @@ QString BaseJob::statusCaption() const return tr("Request was abandoned"); case NetworkError: return tr("Network problems"); - case TimeoutError: + case Timeout: return tr("Request timed out"); case Unauthorised: return tr("Unauthorised request"); case ContentAccessError: return tr("Access error"); - case NotFoundError: + case NotFound: return tr("Not found"); - case IncorrectRequestError: + case IncorrectRequest: return tr("Invalid request"); - case IncorrectResponseError: + case IncorrectResponse: return tr("Response could not be parsed"); - case TooManyRequestsError: + case TooManyRequests: return tr("Too many requests"); - case RequestNotImplementedError: + case RequestNotImplemented: return tr("Function not implemented by the server"); - case NetworkAuthRequiredError: + case NetworkAuthRequired: return tr("Network authentication required"); - case UserConsentRequiredError: + case UserConsentRequired: return tr("User consent required"); - case UnsupportedRoomVersionError: + case UnsupportedRoomVersion: return tr("The server does not support the needed room version"); default: return tr("Request failed"); @@ -811,7 +811,7 @@ void BaseJob::abandon() void BaseJob::timeout() { - setStatus(TimeoutError, "The job has timed out"); + setStatus(Timeout, "The job has timed out"); finishJob(); } diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 81455307..e0910a26 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -33,6 +33,10 @@ class BaseJob : public QObject { } public: +#define WITH_DEPRECATED_ERROR_VERSION(Recommended) \ + Recommended, Recommended##Error Q_DECL_ENUMERATOR_DEPRECATED_X( \ + "Use " #Recommended) = Recommended + /*! The status code of a job * * Every job is created in Unprepared status; upon calling prepare() @@ -43,7 +47,7 @@ public: */ enum StatusCode { Success = 0, - NoError = Success, // To be compatible with Qt conventions + NoError = Success, Pending = 1, WarningLevel = 20, //< Warnings have codes starting from this UnexpectedResponseType = 21, @@ -52,26 +56,18 @@ public: Abandoned = 50, //< A tiny period between abandoning and object deletion ErrorLevel = 100, //< Errors have codes starting from this NetworkError = 101, - Timeout, - TimeoutError = Timeout, + WITH_DEPRECATED_ERROR_VERSION(Timeout), Unauthorised, ContentAccessError, - NotFoundError, - IncorrectRequest, - IncorrectRequestError = IncorrectRequest, - IncorrectResponse, - IncorrectResponseError = IncorrectResponse, - TooManyRequests, - TooManyRequestsError = TooManyRequests, + WITH_DEPRECATED_ERROR_VERSION(NotFound), + WITH_DEPRECATED_ERROR_VERSION(IncorrectRequest), + WITH_DEPRECATED_ERROR_VERSION(IncorrectResponse), + WITH_DEPRECATED_ERROR_VERSION(TooManyRequests), RateLimited = TooManyRequests, - RequestNotImplemented, - RequestNotImplementedError = RequestNotImplemented, - UnsupportedRoomVersion, - UnsupportedRoomVersionError = UnsupportedRoomVersion, - NetworkAuthRequired, - NetworkAuthRequiredError = NetworkAuthRequired, - UserConsentRequired, - UserConsentRequiredError = UserConsentRequired, + WITH_DEPRECATED_ERROR_VERSION(RequestNotImplemented), + WITH_DEPRECATED_ERROR_VERSION(UnsupportedRoomVersion), + WITH_DEPRECATED_ERROR_VERSION(NetworkAuthRequired), + WITH_DEPRECATED_ERROR_VERSION(UserConsentRequired), CannotLeaveRoom, UserDeactivated, FileError, @@ -79,6 +75,8 @@ public: }; Q_ENUM(StatusCode) +#undef WITH_DEPRECATED_ERROR_VERSION + template static QByteArray makePath(StrTs&&... parts) { -- cgit v1.2.3 From 9cfd6ad6a4651280a12099d1a92432f07fc5aae0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 5 Oct 2021 03:44:18 +0200 Subject: BaseJob: refresh error handling - BaseJob::prepareError() slightly updated to get the current status instead of checking the returned value outside in gotReply() - BaseJob::gotReply() no more reports on 429 Too Many Requests twice (the first time with dubious "Too Many Requests: Unknown error") --- lib/jobs/basejob.cpp | 46 +++++++++++++++++++++++----------------------- lib/jobs/basejob.h | 6 ++++-- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index a7921c61..971fea7b 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -415,42 +415,42 @@ BaseJob::Status BaseJob::Private::parseJson() void BaseJob::gotReply() { - setStatus(checkReply(reply())); - - if (status().good() - && d->expectedContentTypes == QByteArrayList { "application/json" }) { + // Defer actually updating the status until it's finalised + auto statusSoFar = checkReply(reply()); + if (statusSoFar.good() + && d->expectedContentTypes == QByteArrayList { "application/json" }) // + { d->rawResponse = reply()->readAll(); - setStatus(d->parseJson()); - if (status().good() && !expectedKeys().empty()) { + statusSoFar = d->parseJson(); + if (statusSoFar.good() && !expectedKeys().empty()) { const auto& responseObject = jsonData(); QByteArrayList missingKeys; for (const auto& k: expectedKeys()) if (!responseObject.contains(k)) missingKeys.push_back(k); if (!missingKeys.empty()) - setStatus(IncorrectResponse, tr("Required JSON keys missing: ") - + missingKeys.join()); + statusSoFar = { IncorrectResponse, + tr("Required JSON keys missing: ") + + missingKeys.join() }; } + setStatus(statusSoFar); if (!status().good()) // Bad JSON in a "good" reply: bail out return; - } // else { + } // If the endpoint expects anything else than just (API-related) JSON // reply()->readAll() is not performed and the whole reply processing // is left to derived job classes: they may read it piecemeal or customise // per content type in prepareResult(), or even have read it already // (see, e.g., DownloadFileJob). - // } - - if (status().good()) + if (statusSoFar.good()) { setStatus(prepareResult()); - else { - d->rawResponse = reply()->readAll(); - qCDebug(d->logCat).noquote() - << "Error body (truncated if long):" << rawDataSample(500); - // Parse the error payload and update the status if needed - if (const auto newStatus = prepareError(); !newStatus.good()) - setStatus(newStatus); + return; } + + d->rawResponse = reply()->readAll(); + qCDebug(d->logCat).noquote() + << "Error body (truncated if long):" << rawDataSample(500); + setStatus(prepareError(statusSoFar)); } bool checkContentType(const QByteArray& type, const QByteArrayList& patterns) @@ -515,7 +515,7 @@ BaseJob::Status BaseJob::checkReply(const QNetworkReply* reply) const BaseJob::Status BaseJob::prepareResult() { return Success; } -BaseJob::Status BaseJob::prepareError() +BaseJob::Status BaseJob::prepareError(Status currentStatus) { // Try to make sense of the error payload but be prepared for all kinds // of unexpected stuff (raw HTML, plain text, foreign JSON among those) @@ -525,7 +525,7 @@ BaseJob::Status BaseJob::prepareError() // By now, if d->parseJson() above succeeded then jsonData() will return // a valid JSON object - or an empty object otherwise (in which case most - // of if's below will fall through to `return NoError` at the end + // of if's below will fall through retaining the current status) const auto& errorJson = jsonData(); const auto errCode = errorJson.value("errcode"_ls).toString(); if (error() == TooManyRequests || errCode == "M_LIMIT_EXCEEDED") { @@ -560,9 +560,9 @@ BaseJob::Status BaseJob::prepareError() // Not localisable on the client side if (errorJson.contains("error"_ls)) // Keep the code, update the message - return { d->status.code, errorJson.value("error"_ls).toString() }; + return { currentStatus.code, errorJson.value("error"_ls).toString() }; - return NoError; // Retain the status if the error payload is not recognised + return currentStatus; // The error payload is not recognised } QJsonValue BaseJob::takeValueFromJson(const QString& key) diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index e0910a26..119d7cce 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -399,10 +399,12 @@ protected: * was not good (usually because of an unsuccessful HTTP code). * The base implementation assumes Matrix JSON error object in the body; * overrides are strongly recommended to call it for all stock Matrix - * responses as early as possible but in addition can process custom errors, + * responses as early as possible and only then process custom errors, * with JSON or non-JSON payload. + * + * \return updated (if necessary) job status */ - virtual Status prepareError(); + virtual Status prepareError(Status currentStatus); /*! \brief Get direct access to the JSON response object in the job * -- cgit v1.2.3 From 107471447a62663eaf97b4b982d8c3f3e1b3364e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 5 Oct 2021 03:44:43 +0200 Subject: Connection: fix C++20 warnings --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 093362ab..1fe0d2d0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -341,7 +341,7 @@ void Connection::loginWithPassword(const QString& userId, const QString& initialDeviceName, const QString& deviceId) { - d->checkAndConnect(userId, [=] { + d->checkAndConnect(userId, [=,this] { d->loginToServer(LoginFlows::Password.type, makeUserIdentifier(userId), password, /*token*/ "", deviceId, initialDeviceName); }, LoginFlows::Password); @@ -1716,7 +1716,7 @@ void Connection::getTurnServers() { auto job = callApi(); connect(job, &GetTurnServerJob::success, this, - [=] { emit turnServersChanged(job->data()); }); + [this,job] { emit turnServersChanged(job->data()); }); } const QString Connection::SupportedRoomVersion::StableTag = -- cgit v1.2.3 From 7b516cdf0b987e542b1e4cd4556ecb2bfbde3ff9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 5 Oct 2021 03:52:19 +0200 Subject: Quotest: return non-zero when things go really wrong ...such as stuck login or failure to join the room. Closes #496. --- quotest/quotest.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 44d82adf..d006c7fb 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -245,7 +245,7 @@ void TestManager::setupAndRun() clog << "Sync " << ++i << " complete" << endl; if (auto* r = testSuite->room()) { clog << "Test room timeline size = " << r->timelineSize(); - if (r->pendingEvents().empty()) + if (!r->pendingEvents().empty()) clog << ", pending size = " << r->pendingEvents().size(); clog << endl; } @@ -939,10 +939,22 @@ void TestManager::conclude() void TestManager::finalize() { + if (!c->isUsable() || !c->isLoggedIn()) { + clog << "No usable connection reached" << endl; + QCoreApplication::exit(-2); + return; // NB: QCoreApplication::exit() does return to the caller + } clog << "Logging out" << endl; c->logout(); - connect(c, &Connection::loggedOut, this, - [this] { QCoreApplication::exit(failed.size() + running.size()); }, + connect( + c, &Connection::loggedOut, this, + [this] { + QCoreApplication::exit(!testSuite ? -3 + : succeeded.empty() && failed.empty() + && running.empty() + ? -4 + : failed.size() + running.size()); + }, Qt::QueuedConnection); } -- cgit v1.2.3 From fe77279cf0f7de006db40a828cc2fb7261e8e0bd Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 10 Oct 2021 00:07:44 +0200 Subject: Keep the reply when replacing an event --- lib/room.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/room.cpp b/lib/room.cpp index a923bf9a..e1d41fc3 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2251,8 +2251,13 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) RoomEventPtr makeReplaced(const RoomEvent& target, const RoomMessageEvent& replacement) { + const auto &targetReply = target.contentJson()["m.relates_to"].toObject(); + auto newContent = replacement.contentJson().value("m.new_content"_ls).toObject(); + if (!targetReply.empty()) { + newContent["m.relates_to"] = targetReply; + } auto originalJson = target.originalJsonObject(); - originalJson[ContentKeyL] = replacement.contentJson().value("m.new_content"_ls); + originalJson[ContentKeyL] = newContent; auto unsignedData = originalJson.take(UnsignedKeyL).toObject(); auto relations = unsignedData.take("m.relations"_ls).toObject(); -- cgit v1.2.3 From 762be1e73d6f5021e63fc9a4fea273951b3fb113 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 10 Oct 2021 22:26:08 +0200 Subject: ReadReceipt::operator==/!=: Add const where it's due --- lib/room.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/room.h b/lib/room.h index ca9f36cb..73f28a6e 100644 --- a/lib/room.h +++ b/lib/room.h @@ -81,11 +81,14 @@ public: QString eventId; QDateTime timestamp; - bool operator==(const ReadReceipt& other) + bool operator==(const ReadReceipt& other) const { return eventId == other.eventId && timestamp == other.timestamp; } - bool operator!=(const ReadReceipt& other) { return !operator==(other); } + bool operator!=(const ReadReceipt& other) const + { + return !operator==(other); + } }; inline void swap(ReadReceipt& lhs, ReadReceipt& rhs) { -- cgit v1.2.3 From 5c0346f3a700e6af31463490b7af3382b86e09d5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 8 Sep 2021 05:47:10 +0200 Subject: Room: actually initialise read marker when needed This fixes the `q->readMarker() != historyEdge()` assertion failure occuring in recalculateUnreadCount() when new events from sync arrive to a room with no read marker and all history loaded. --- lib/room.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index a2b99039..ea8df286 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -686,23 +686,24 @@ Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, if (fullyReadMarker < from) return NoChange; // What's arrived is already fully read + // If there's no read marker in the whole room, initialise it if (fullyReadMarker == historyEdge() && q->allHistoryLoaded()) - --fullyReadMarker; // No read marker in the whole room, initialise it - if (fullyReadMarker < to) { - // Catch a special case when the last fully read event id refers to an - // event that has just arrived. In this case we should recalculate - // unreadMessages to get an exact number instead of an estimation - // (see https://github.com/quotient-im/libQuotient/wiki/unread_count). - // For the same reason (switching from the estimation to the exact - // number) this branch always emits unreadMessagesChanged() and returns - // UnreadNotifsChange, even if the estimation luckily matched the exact - // result. + return setFullyReadMarker(timeline.front()->id()); + + // Catch a special case when the last fully read event id refers to an + // event that has just arrived. In this case we should recalculate + // unreadMessages to get an exact number instead of an estimation + // (see https://github.com/quotient-im/libQuotient/wiki/unread_count). + // For the same reason (switching from the estimation to the exact + // number) this branch always emits unreadMessagesChanged() and returns + // UnreadNotifsChange, even if the estimation luckily matched the exact + // result. + if (fullyReadMarker < to) return recalculateUnreadCount(true); - } - // Fully read marker is somewhere beyond the most historical message from - // the arrived batch - add up newly arrived messages to the current counter, - // instead of a complete recalculation. + // At this point the fully read marker is somewhere beyond the "oldest" + // message from the arrived batch - add up newly arrived messages to + // the current counter, instead of a complete recalculation. Q_ASSERT(to <= fullyReadMarker); QElapsedTimer et; @@ -769,7 +770,7 @@ Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) const auto prevFullyReadId = std::exchange(fullyReadUntilEventId, eventId); qCDebug(MESSAGES) << "Fully read marker in" << q->objectName() // - << "moved to" << fullyReadUntilEventId; + << "set to" << fullyReadUntilEventId; emit q->fullyReadMarkerMoved(prevFullyReadId, fullyReadUntilEventId); // TODO: Remove in 0.8 emit q->readMarkerMoved(prevFullyReadId, fullyReadUntilEventId); -- cgit v1.2.3 From a1e885177bd9421a8ede4bec870a8fdc8b89a693 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 11 Oct 2021 16:02:21 +0200 Subject: formatJson(QDebug): Drop some very old cruft --- lib/logging.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/logging.h b/lib/logging.h index 7e0da975..5a3ef6ea 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -37,11 +37,7 @@ using QDebugManip = QDebug (*)(QDebug); */ inline QDebug formatJson(QDebug debug_object) { -#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) - return debug_object; -#else return debug_object.noquote(); -#endif } /** -- cgit v1.2.3 From fc0fdf2ed6006c11ffd47675fabb1232721c5e7d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 12 Oct 2021 09:26:09 +0200 Subject: RoomMemberEvent::is*(): fix comparison against Omittable Closes #514. --- lib/events/roommemberevent.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 8a6bddd8..469dbb32 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -103,16 +103,14 @@ bool RoomMemberEvent::isUnban() const bool RoomMemberEvent::isRename() const { - auto prevName = prevContent() && prevContent()->displayName - ? *prevContent()->displayName - : QString(); - return newDisplayName() != prevName; + return prevContent() && prevContent()->displayName + ? newDisplayName() != *prevContent()->displayName + : newDisplayName().has_value(); } bool RoomMemberEvent::isAvatarUpdate() const { - auto prevAvatarUrl = prevContent() && prevContent()->avatarUrl - ? *prevContent()->avatarUrl - : QUrl(); - return newAvatarUrl() != prevAvatarUrl; + return prevContent() && prevContent()->avatarUrl + ? newAvatarUrl() != *prevContent()->avatarUrl + : newAvatarUrl().has_value(); } -- cgit v1.2.3 From fd915b0865bde5741ceb1dd1e76a99d25b8c63fd Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 12 Oct 2021 18:01:25 +0200 Subject: Omittable: add a deduction guide Just for completeness, not really needed anywhere yet. --- lib/util.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/util.h b/lib/util.h index c6171b91..9c146100 100644 --- a/lib/util.h +++ b/lib/util.h @@ -138,6 +138,8 @@ public: const value_type& operator*() const& { return base_type::operator*(); } value_type& operator*() && { return base_type::operator*(); } }; +template +Omittable(T&&) -> Omittable; namespace _impl { template -- cgit v1.2.3 From de164d60f2561fb0ad142b5d4fd31ff9817c2725 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 12 Oct 2021 19:17:42 +0200 Subject: Make sure to expose both the flags type and the underlying enum See also https://bugreports.qt.io/browse/QTBUG-82295. --- lib/quotient_common.h | 22 ++++++++++++++++++---- lib/room.h | 3 +-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 13bf7246..e1e14a14 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -7,6 +7,22 @@ #include +// See https://bugreports.qt.io/browse/QTBUG-82295 - despite the comment that +// Q_FLAG[_NS] "should" be applied to the enum only, Qt doesn't allow to wrap +// a flag type into a QVariant then. The macros below define Q_FLAG_NS and on +// top of that add a part of Q_ENUM() that enables the metatype data but goes +// under the moc radar to avoid double registration of the same data in the map +// defined in moc_*.cpp +#define QUO_DECLARE_FLAGS(Flags, Enum) \ + Q_DECLARE_FLAGS(Flags, Enum) \ + Q_ENUM_IMPL(Enum) \ + Q_FLAG(Flags) + +#define QUO_DECLARE_FLAGS_NS(Flags, Enum) \ + Q_DECLARE_FLAGS(Flags, Enum) \ + Q_ENUM_NS_IMPL(Enum) \ + Q_FLAG_NS(Flags) + namespace Quotient { Q_NAMESPACE @@ -41,8 +57,7 @@ enum class Membership : unsigned int { Ban = 0x10, Undefined = Invalid }; -Q_DECLARE_FLAGS(MembershipMask, Membership) -Q_FLAG_NS(MembershipMask) +QUO_DECLARE_FLAGS_NS(MembershipMask, Membership) constexpr inline auto MembershipStrings = make_array( // The order MUST be the same as the order in the original enum @@ -60,8 +75,7 @@ enum class JoinState : std::underlying_type_t { Invite = std::underlying_type_t(Membership::Invite), Knock = std::underlying_type_t(Membership::Knock), }; -Q_DECLARE_FLAGS(JoinStates, JoinState) -Q_FLAG_NS(JoinStates) +QUO_DECLARE_FLAGS_NS(JoinStates, JoinState) constexpr inline auto JoinStateStrings = make_array( MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], diff --git a/lib/room.h b/lib/room.h index c4d94c02..b43217ae 100644 --- a/lib/room.h +++ b/lib/room.h @@ -148,8 +148,7 @@ public: OtherChange = 0x8000, AnyChange = 0xFFFF }; - Q_DECLARE_FLAGS(Changes, Change) - Q_FLAG(Changes) + QUO_DECLARE_FLAGS(Changes, Change) Room(Connection* connection, QString id, JoinState initialJoinState); ~Room() override; -- cgit v1.2.3 From e633f9ed1558fe1e8aa026af2932a1d06b8beadb Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 13 Oct 2021 10:00:15 +0200 Subject: CMakeLists: don't report that update-api is enabled when it's actually not add_feature_info() treats unset variable as "no change" rather than "false", which may lead to a confusing build configuration report when GTAD_PATH and/or MATRIX_DOC_PATH and/or CLANG_FORMAT have been there before but were removed since. --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bae833c3..30bab53a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,8 @@ set(FULL_CSAPI_DIR lib/${CSAPI_DIR}) set(ASAPI_DEF_DIR application-service/definitions) set(ISAPI_DEF_DIR identity/definitions) +set(API_GENERATION_ENABLED 0) +set(API_FORMATTING_ENABLED 0) if (GTAD_PATH AND MATRIX_DOC_PATH) get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" PROGRAM PROGRAM_ARGS GTAD_ARGS) if (EXISTS ${ABS_GTAD_PATH}) @@ -264,8 +266,10 @@ if (API_GENERATION_ENABLED) endif() add_feature_info(EnableApiCodeGeneration "${API_GENERATION_ENABLED}" "build target update-api") -add_feature_info(EnableApiFormatting "${API_FORMATTING_ENABLED}" - "formatting of generated API files with clang-format") +if (API_GENERATION_ENABLED) + add_feature_info(EnableApiFormatting "${API_FORMATTING_ENABLED}" + "formatting of generated API files with clang-format") +endif() # Make no mistake: CMake cannot run gtad first and then populate the list of # resulting api_SRCS files. In other words, placing the below statement after -- cgit v1.2.3 From f620284bac23bb86c329863c53f3699845bf606f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 13 Oct 2021 10:16:39 +0200 Subject: CMakeLists: more robust GTAD_PATH detection After switching over to get_filename_component(PROGRAM) paths with ~ (home directory) were no more resolved. They are again. --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30bab53a..34200548 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,7 +182,9 @@ set(ISAPI_DEF_DIR identity/definitions) set(API_GENERATION_ENABLED 0) set(API_FORMATTING_ENABLED 0) if (GTAD_PATH AND MATRIX_DOC_PATH) - get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" PROGRAM PROGRAM_ARGS GTAD_ARGS) + # REALPATH resolves ~ (home directory) while PROGRAM doesn't + get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" REALPATH) + get_filename_component(ABS_GTAD_PATH "${ABS_GTAD_PATH}" PROGRAM PROGRAM_ARGS GTAD_ARGS) if (EXISTS ${ABS_GTAD_PATH}) get_filename_component(ABS_API_DEF_PATH "${MATRIX_DOC_PATH}/data/api" REALPATH) if (NOT IS_DIRECTORY ${ABS_API_DEF_PATH}) @@ -195,7 +197,7 @@ if (GTAD_PATH AND MATRIX_DOC_PATH) message( WARNING "${MATRIX_DOC_PATH} doesn't seem to point to a valid matrix-doc repo; disabling API stubs generation") endif () else (EXISTS ${ABS_GTAD_PATH}) - message( WARNING "${GTAD_PATH} doesn't exist; disabling API stubs generation") + message( WARNING "${GTAD_PATH} is not executable; disabling API stubs generation") endif () endif () if (API_GENERATION_ENABLED) -- cgit v1.2.3 From b7de9c00b79ef0d187190aa5d126bff7503f471f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 13 Oct 2021 19:55:40 +0200 Subject: connection.h: more doc-comments --- lib/connection.h | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/connection.h b/lib/connection.h index 1a6ca9b0..05a3bb7f 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -479,10 +479,23 @@ public: } public Q_SLOTS: - /** Set the homeserver base URL */ + /// \brief Set the homeserver base URL and retrieve its login flows + /// + /// \sa LoginFlowsJob, loginFlows, loginFlowsChanged, homeserverChanged void setHomeserver(const QUrl& baseUrl); - /** Determine and set the homeserver from MXID */ + /// \brief Determine and set the homeserver from MXID + /// + /// This attempts to resolve the homeserver by requesting + /// .well-known/matrix/client record from the server taken from the MXID + /// serverpart. If there is no record found, the serverpart itself is + /// attempted as the homeserver base URL; if the record is there but + /// is malformed (e.g., the homeserver base URL cannot be found in it) + /// resolveError() is emitted and further processing stops. Otherwise, + /// setHomeserver is called, preparing the Connection object for the login + /// attempt. + /// \param mxid user Matrix ID, such as @someone:example.org + /// \sa setHomeserver, homeserverChanged, loginFlowsChanged, resolveError void resolveServer(const QString& mxid); /** \brief Log in using a username and password pair @@ -638,6 +651,11 @@ public Q_SLOTS: virtual LeaveRoomJob* leaveRoom(Room* room); Q_SIGNALS: + /// \brief Initial server resolution has failed + /// + /// This signal is emitted when resolveServer() did not manage to resolve + /// the homeserver using its .well-known/client record or otherwise. + /// \sa resolveServer void resolveError(QString error); void homeserverChanged(QUrl baseUrl); -- cgit v1.2.3 From 0aa70ed9b3cff3c9e0dbbea9beb419d840e42341 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 13 Oct 2021 19:57:01 +0200 Subject: Connection::resolveServer(): don't connect to loginFlowsJob Checking whether any login flows are available is a good enough measure of the homeserver actual workability. Closes #515. --- lib/connection.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 1fe0d2d0..2ad10694 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -316,10 +316,6 @@ void Connection::resolveServer(const QString& mxid) setHomeserver(maybeBaseUrl); } Q_ASSERT(d->loginFlowsJob != nullptr); // Ensured by setHomeserver() - connect(d->loginFlowsJob, &BaseJob::failure, this, [this] { - qCWarning(MAIN) << "Homeserver base URL sanity check failed"; - emit resolveError(tr("The homeserver doesn't seem to be working")); - }); }); } @@ -478,10 +474,11 @@ void Connection::Private::checkAndConnect(const QString& userId, connectFn(); else emit q->loginError( + tr("Unsupported login flow"), tr("The homeserver at %1 does not support" " the login flow '%2'") - .arg(data->baseUrl().toDisplayString()), - flow->type); + .arg(data->baseUrl().toDisplayString(), + flow->type)); }); else connectSingleShot(q, &Connection::homeserverChanged, q, connectFn); -- cgit v1.2.3 From 9ee0efbf1d7a431c370b13e1d49c903165d474fe Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 17 Oct 2021 20:32:46 +0200 Subject: Room: qualify signal parameters --- lib/room.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/room.h b/lib/room.h index b43217ae..55dde2ee 100644 --- a/lib/room.h +++ b/lib/room.h @@ -623,11 +623,11 @@ Q_SIGNALS: */ void baseStateLoaded(); void eventsHistoryJobChanged(); - void aboutToAddHistoricalMessages(RoomEventsRange events); - void aboutToAddNewMessages(RoomEventsRange events); + void aboutToAddHistoricalMessages(Quotient::RoomEventsRange events); + void aboutToAddNewMessages(Quotient::RoomEventsRange events); void addedMessages(int fromIndex, int toIndex); /// The event is about to be appended to the list of pending events - void pendingEventAboutToAdd(RoomEvent* event); + void pendingEventAboutToAdd(Quotient::RoomEvent* event); /// An event has been appended to the list of pending events void pendingEventAdded(); /// The remote echo has arrived with the sync and will be merged -- cgit v1.2.3 From 965f0e94f3f8c98ccb704b1d5abdeac1efc699cc Mon Sep 17 00:00:00 2001 From: Smitty Date: Sun, 7 Nov 2021 17:08:53 -0500 Subject: Add method to get all state events in a room It is useful to be able to get all of the state events that have occured in a room, instead of needing to use Room::getCurrentState, which filters based on the event type and state key. --- lib/room.cpp | 10 ++++++++++ lib/room.h | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/lib/room.cpp b/lib/room.cpp index e1d41fc3..3b00d2d9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -224,6 +224,11 @@ public: return evt; } + const QHash stateEvents() const + { + return currentState; + } + template const EventT* getCurrentState(const QString& stateKey = {}) const { @@ -1293,6 +1298,11 @@ const StateEventBase* Room::getCurrentState(const QString& evtType, return d->getCurrentState({ evtType, stateKey }); } +const QHash Room::stateEvents() const +{ + return d->stateEvents(); +} + RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED diff --git a/lib/room.h b/lib/room.h index 55dde2ee..452ea306 100644 --- a/lib/room.h +++ b/lib/room.h @@ -508,6 +508,12 @@ public: Q_INVOKABLE const Quotient::StateEventBase* getCurrentState(const QString& evtType, const QString& stateKey = {}) const; + /// Get all state events in the room. + /*! This method returns all known state events that have occured in + * the room, as a mapping from the event type and state key to value. + */ + Q_INVOKABLE const QHash stateEvents() const; + /// Get a state event with the given event type and state key /*! This is a typesafe overload that accepts a C++ event type instead of * its Matrix name. -- cgit v1.2.3 From 061de37889b0fa4bf8baae1f11693950297418c5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 8 Nov 2021 14:46:56 +0100 Subject: Q_DISABLE_MOVE/COPY_MOVE; QT_IGNORE_DEPRECATIONS DISABLE_MOVE is no more; instead, the library provides Q_DISABLE_MOVE (and also Q_DISABLE_COPY_MOVE while at it) for Qt pre-5.13 that don't have it yet. Same for QT_IGNORE_DEPRECATIONS - it only arrived in 5.15 but all the building pieces existed prior so libQuotient has it regardless of the Qt version used for building. --- lib/connection.cpp | 2 -- lib/events/event.h | 3 +-- lib/util.h | 28 ++++++++++++++++++++++++---- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 2ad10694..75966731 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -77,8 +77,6 @@ public: explicit Private(std::unique_ptr&& connection) : data(move(connection)) {} - Q_DISABLE_COPY(Private) - DISABLE_MOVE(Private) Connection* q = nullptr; std::unique_ptr data; diff --git a/lib/events/event.h b/lib/events/event.h index f8f8311d..78853ced 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -76,8 +76,7 @@ public: private: EventTypeRegistry() = default; - Q_DISABLE_COPY(EventTypeRegistry) - DISABLE_MOVE(EventTypeRegistry) + Q_DISABLE_COPY_MOVE(EventTypeRegistry) static EventTypeRegistry& get() { diff --git a/lib/util.h b/lib/util.h index 9c146100..13efb94b 100644 --- a/lib/util.h +++ b/lib/util.h @@ -12,10 +12,30 @@ #include #include -// Along the lines of Q_DISABLE_COPY - the upstream version comes in Qt 5.13 -#define DISABLE_MOVE(_ClassName) \ - _ClassName(_ClassName&&) Q_DECL_EQ_DELETE; \ - _ClassName& operator=(_ClassName&&) Q_DECL_EQ_DELETE; +#ifndef Q_DISABLE_MOVE +// Q_DISABLE_MOVE was introduced in Q_VERSION_CHECK(5,13,0) +# define Q_DISABLE_MOVE(_ClassName) \ + _ClassName(_ClassName&&) Q_DECL_EQ_DELETE; \ + _ClassName& operator=(_ClassName&&) Q_DECL_EQ_DELETE; +#endif + +#ifndef Q_DISABLE_COPY_MOVE +#define Q_DISABLE_COPY_MOVE(Class) \ + Q_DISABLE_COPY(Class) \ + Q_DISABLE_MOVE(Class) +#endif + +#define DISABLE_MOVE(_ClassName) \ +static_assert(false, "Use Q_DISABLE_MOVE instead; Quotient enables it across all used versions of Qt"); + +#ifndef QT_IGNORE_DEPRECATIONS +// QT_IGNORE_DEPRECATIONS was introduced in Q_VERSION_CHECK(5,15,0) +# define QT_IGNORE_DEPRECATIONS(statement) \ + QT_WARNING_PUSH \ + QT_WARNING_DISABLE_DEPRECATED \ + statement \ + QT_WARNING_POP +#endif namespace Quotient { /// An equivalent of std::hash for QTypes to enable std::unordered_map -- cgit v1.2.3 From 48400a8aa28537c1bedc095b5c4c08f677cca0f5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 8 Nov 2021 14:47:18 +0100 Subject: Drop unused #include --- lib/room.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/room.cpp b/lib/room.cpp index e1d41fc3..4e7b29d8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -54,7 +54,6 @@ #include #include -#include #include #include #include // for efficient string concats (operator%) -- cgit v1.2.3 From 38291a7b442906b638ce0d34a429c72089e80f6d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 8 Nov 2021 19:27:46 +0100 Subject: Room::Change: deprecate AccountDataChange, ReadMarkerChange These usually don't affect the room outlooks in the room list in any way; so can be merged into OtherChange instead. Also: OtherChanges synonym has been added, implying that there might be more than one change behind a single "Other" flag. --- lib/room.cpp | 7 +++++-- lib/room.h | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 4e7b29d8..6bccc405 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -633,7 +633,9 @@ Room::Changes Room::Private::setLastReadEvent(User* u, QString eventId) connection->callApi(BackgroundRequest, id, storedId); emit q->readMarkerMoved(eventId, storedId); - return Change::ReadMarkerChange; + // TODO: Drop ReadMarkerChange in 0.8 + return QT_IGNORE_DEPRECATIONS(Change::ReadMarkerChange) + | Change::OtherChange; } return Change::NoChange; } @@ -2813,7 +2815,8 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) qCDebug(STATE) << "Updated account data of type" << currentData->matrixType(); emit accountDataChanged(currentData->matrixType()); - changes |= Change::AccountDataChange; + // TODO: Drop AccountDataChange in 0.8 + QT_IGNORE_DEPRECATIONS(changes |= AccountDataChange|OtherChange); } return changes; } diff --git a/lib/room.h b/lib/room.h index 55dde2ee..873c47d3 100644 --- a/lib/room.h +++ b/lib/room.h @@ -142,10 +142,13 @@ public: TagsChange = 0x40, MembersChange = 0x80, /* = 0x100, */ - AccountDataChange = 0x200, + AccountDataChange Q_DECL_ENUMERATOR_DEPRECATED_X( + "AccountDataChange will be merged into OtherChange in 0.8") = 0x200, SummaryChange = 0x400, - ReadMarkerChange = 0x800, + ReadMarkerChange Q_DECL_ENUMERATOR_DEPRECATED_X( + "ReadMarkerChange will be merged into OtherChange in 0.8") = 0x800, OtherChange = 0x8000, + OtherChanges = OtherChange, AnyChange = 0xFFFF }; QUO_DECLARE_FLAGS(Changes, Change) -- cgit v1.2.3 From 65bb0b5fcf029df7a9bfa0b7b7b7e3203fd7862f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 9 Nov 2021 13:59:47 +0100 Subject: DECL_DEPRECATED_ENUMERATOR A handy macro that introduces an enumerator with a respective Q_DECL_DEPRECATED_X recommending the substitution. --- lib/jobs/basejob.h | 6 +++--- lib/quotient_common.h | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 119d7cce..ddf243ed 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -7,6 +7,7 @@ #include "requestdata.h" #include "../logging.h" #include "../converters.h" +#include "../quotient_common.h" #include #include @@ -33,9 +34,8 @@ class BaseJob : public QObject { } public: -#define WITH_DEPRECATED_ERROR_VERSION(Recommended) \ - Recommended, Recommended##Error Q_DECL_ENUMERATOR_DEPRECATED_X( \ - "Use " #Recommended) = Recommended +#define WITH_DEPRECATED_ERROR_VERSION(Recommended) \ + Recommended, DECL_DEPRECATED_ENUMERATOR(Recommended##Error, Recommended) /*! The status code of a job * diff --git a/lib/quotient_common.h b/lib/quotient_common.h index e1e14a14..3d8ace67 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -23,6 +23,9 @@ Q_ENUM_NS_IMPL(Enum) \ Q_FLAG_NS(Flags) +#define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ + Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended + namespace Quotient { Q_NAMESPACE -- cgit v1.2.3 From 29195491c25bcba50b78a30c779db07913f87d3a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 11 Nov 2021 13:01:58 +0100 Subject: Update comments around read receipts code --- lib/room.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index ceb8d111..4d11878f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2498,12 +2498,6 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) << totalInserted << "new events; the last event is now" << timeline.back(); - // The first event in the just-added batch (referred to by `from`) - // defines whose read receipt can possibly be promoted any further over - // the same author's events newly arrived. Others will need explicit - // read receipts from the server - or, for the local user, calling - // setLastDisplayedEventId() - to promote their read receipts over - // the new message events. auto* const firstWriter = q->user((*from)->senderId()); setLastReadReceipt(firstWriter, rev_iter_t(from + 1)); if (firstWriter == q->localUser() @@ -2813,15 +2807,14 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) if (newMarker == historyEdge()) qCDebug(EPHEMERAL) << "Event of the read receipt(s) is not " "found; saving them anyway"; + // If the event is not found (most likely, because it's too old and + // hasn't been fetched from the server yet) but there is a previous + // marker for a user, keep the previous marker because read receipts + // are not supposed to move backwards. Otherwise, blindly store + // the event id for this user and update the read marker when/if + // the event is fetched later on. for (const Receipt& r : p.receipts) if (isMember(r.userId)) { - // If the event is not found (most likely, because it's - // too old and hasn't been fetched from the server yet) - // but there is a previous marker for a user, keep - // the previous marker because read receipts are not - // supposed to move backwards. Otherwise, blindly - // store the event id for this user and update the read - // marker when/if the event is fetched later on. d->setLastReadReceipt(user(r.userId), newMarker, { p.evtId, r.timestamp }); } -- cgit v1.2.3 From a4f34202b47f91f7fdfbe2006b2eae10b9b8eeac Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 11 Nov 2021 14:35:54 +0100 Subject: Fix a few quirks in converters.h --- lib/converters.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index cc6378e4..2df18b03 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -242,12 +242,11 @@ struct JsonObjectConverter> { for (const auto& e : s) json.insert(toJson(e), QJsonObject {}); } - static auto fillFrom(const QJsonObject& json, QSet& s) + static void fillFrom(const QJsonObject& json, QSet& s) { s.reserve(s.size() + json.size()); for (auto it = json.begin(); it != json.end(); ++it) s.insert(it.key()); - return s; } }; @@ -260,7 +259,7 @@ struct HashMapFromJson { } static void fillFrom(const QJsonObject& jo, HashMapT& h) { - h.reserve(jo.size()); + h.reserve(h.size() + jo.size()); for (auto it = jo.begin(); it != jo.end(); ++it) h[it.key()] = fromJson(it.value()); } -- cgit v1.2.3 From fb30d455e2dbeed8c242cfbeb153f834b0358f11 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 12 Nov 2021 11:10:30 +0100 Subject: Fix building with GCC It didn't like using QT_IGNORE_DEPRECATIONS inside a statement. --- lib/room.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 6bccc405..c11f7990 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -633,9 +633,12 @@ Room::Changes Room::Private::setLastReadEvent(User* u, QString eventId) connection->callApi(BackgroundRequest, id, storedId); emit q->readMarkerMoved(eventId, storedId); - // TODO: Drop ReadMarkerChange in 0.8 - return QT_IGNORE_DEPRECATIONS(Change::ReadMarkerChange) - | Change::OtherChange; + + // NB: GCC (at least 10) only accepts QT_IGNORE_DEPRECATIONS around + // a statement, not within a statement + QT_IGNORE_DEPRECATIONS( // TODO: Drop ReadMarkerChange in 0.8 + return Change::ReadMarkerChange | Change::OtherChange; + ) } return Change::NoChange; } @@ -2816,7 +2819,9 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) << currentData->matrixType(); emit accountDataChanged(currentData->matrixType()); // TODO: Drop AccountDataChange in 0.8 - QT_IGNORE_DEPRECATIONS(changes |= AccountDataChange|OtherChange); + // NB: GCC (at least 10) only accepts QT_IGNORE_DEPRECATIONS around + // a statement, not within a statement + QT_IGNORE_DEPRECATIONS(changes |= AccountDataChange | OtherChange;) } return changes; } -- cgit v1.2.3 From 05e71a72fdc6da3fb319edc67b723999285c9657 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 11 Nov 2021 14:35:54 +0100 Subject: Fix a few quirks in converters.h --- lib/converters.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index cc6378e4..2df18b03 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -242,12 +242,11 @@ struct JsonObjectConverter> { for (const auto& e : s) json.insert(toJson(e), QJsonObject {}); } - static auto fillFrom(const QJsonObject& json, QSet& s) + static void fillFrom(const QJsonObject& json, QSet& s) { s.reserve(s.size() + json.size()); for (auto it = json.begin(); it != json.end(); ++it) s.insert(it.key()); - return s; } }; @@ -260,7 +259,7 @@ struct HashMapFromJson { } static void fillFrom(const QJsonObject& jo, HashMapT& h) { - h.reserve(jo.size()); + h.reserve(h.size() + jo.size()); for (auto it = jo.begin(); it != jo.end(); ++it) h[it.key()] = fromJson(it.value()); } -- cgit v1.2.3 From f2bf3f203965c51824e8681427798f7a09784ce3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 11 Nov 2021 22:18:18 +0100 Subject: Make ReceiptEvent constructible from content Makes the Room::P::toJson() code more readable. --- lib/converters.h | 7 +++++-- lib/events/receiptevent.cpp | 29 +++++++++++++++++++++++++---- lib/events/receiptevent.h | 5 +++-- lib/room.cpp | 27 +++++++-------------------- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index 2df18b03..d9a68bfb 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -134,7 +134,10 @@ struct JsonConverter : public TrivialJsonDumper { template <> struct JsonConverter { - static auto dump(const QDateTime& val) = delete; // not provided yet + static auto dump(const QDateTime& val) + { + return val.isValid() ? val.toMSecsSinceEpoch() : QJsonValue(); + } static auto load(const QJsonValue& jv) { return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); @@ -143,7 +146,7 @@ struct JsonConverter { template <> struct JsonConverter { - static auto dump(const QDate& val) = delete; // not provided yet + static auto dump(const QDate& val) { return toJson(QDateTime(val)); } static auto load(const QJsonValue& jv) { return fromJson(jv).date(); diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index 4185d92d..72dbf2e3 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -25,6 +25,27 @@ Example of a Receipt Event: using namespace Quotient; +// The library loads the event-ids-to-receipts JSON map into a vector because +// map lookups are not used and vectors are massively faster. Same goes for +// de-/serialization of ReceiptsForEvent::receipts. +// (XXX: would this be generally preferred across CS API JSON maps?..) +QJsonObject toJson(const EventsWithReceipts& ewrs) +{ + QJsonObject json; + for (const auto& e : ewrs) { + QJsonObject receiptsJson; + for (const auto& r : e.receipts) + receiptsJson.insert(r.userId, + QJsonObject { { "ts"_ls, toJson(r.timestamp) } }); + json.insert(e.evtId, QJsonObject { { "m.read"_ls, receiptsJson } }); + } + return json; +} + +ReceiptEvent::ReceiptEvent(const EventsWithReceipts &ewrs) + : Event(typeId(), matrixTypeId(), toJson(ewrs)) +{} + EventsWithReceipts ReceiptEvent::eventsWithReceipts() const { EventsWithReceipts result; @@ -39,14 +60,14 @@ EventsWithReceipts ReceiptEvent::eventsWithReceipts() const } const auto reads = eventIt.value().toObject().value("m.read"_ls).toObject(); - QVector receipts; - receipts.reserve(reads.size()); + QVector usersAtEvent; + usersAtEvent.reserve(reads.size()); for (auto userIt = reads.begin(); userIt != reads.end(); ++userIt) { const auto user = userIt.value().toObject(); - receipts.push_back( + usersAtEvent.push_back( { userIt.key(), fromJson(user["ts"_ls]) }); } - result.push_back({ eventIt.key(), std::move(receipts) }); + result.push_back({ eventIt.key(), std::move(usersAtEvent) }); } return result; } diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index 4feec9ea..9683deef 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -9,19 +9,20 @@ #include namespace Quotient { -struct Receipt { +struct UserTimestamp { QString userId; QDateTime timestamp; }; struct ReceiptsForEvent { QString evtId; - QVector receipts; + QVector receipts; }; using EventsWithReceipts = QVector; class ReceiptEvent : public Event { public: DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) + explicit ReceiptEvent(const EventsWithReceipts& ewrs); explicit ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) {} EventsWithReceipts eventsWithReceipts() const; diff --git a/lib/room.cpp b/lib/room.cpp index 4d11878f..58950aac 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2813,7 +2813,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) // are not supposed to move backwards. Otherwise, blindly store // the event id for this user and update the read marker when/if // the event is fetched later on. - for (const Receipt& r : p.receipts) + for (const auto& r : p.receipts) if (isMember(r.userId)) { d->setLastReadReceipt(user(r.userId), newMarker, { p.evtId, r.timestamp }); @@ -3021,30 +3021,17 @@ QJsonObject Room::Private::toJson() const { QStringLiteral("events"), accountDataEvents } }); } - if (const auto& readReceiptEventId = - lastReadReceipts.value(q->localUser()).eventId; - !readReceiptEventId.isEmpty()) // + if (const auto& readReceipt = q->lastReadReceipt(connection->userId()); + !readReceipt.eventId.isEmpty()) // { - // Okay, that's a mouthful; but basically, it's simply placing an m.read - // event in the 'ephemeral' section of the cached sync payload. - // See also receiptevent.* and m.read example in the spec. - // Only the local user's read receipt is saved - others' are really - // considered ephemeral but this one is useful in understanding where - // the user is in the timeline before any history is loaded. result.insert( QStringLiteral("ephemeral"), QJsonObject { { QStringLiteral("events"), - QJsonArray { QJsonObject { - { TypeKey, ReceiptEvent::matrixTypeId() }, - { ContentKey, - QJsonObject { - { readReceiptEventId, - QJsonObject { - { QStringLiteral("m.read"), - QJsonObject { - { connection->userId(), - QJsonObject {} } } } } } } } } } } }); + QJsonArray { ReceiptEvent({ { readReceipt.eventId, + { { connection->userId(), + readReceipt.timestamp } } } }) + .fullJson() } } }); } QJsonObject unreadNotifObj { { SyncRoomData::UnreadCountKey, -- cgit v1.2.3 From d8c9f0d5802f8b1c3ef362964dac5c8d60a61871 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 15 Nov 2021 23:03:09 +0100 Subject: Port away from deprecated upfront percent encoding --- lib/room.cpp | 3 +-- lib/user.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index c11f7990..c2c4ba78 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -750,8 +750,7 @@ Room::Changes Room::Private::markMessagesAsRead(rev_iter_t upToMarker) if ((*upToMarker)->senderId() != q->localUser()->id()) { connection->callApi(BackgroundRequest, id, QStringLiteral("m.read"), - QUrl::toPercentEncoding( - (*upToMarker)->id())); + (*upToMarker)->id()); break; } } diff --git a/lib/user.cpp b/lib/user.cpp index 88549e5d..7da71dba 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -66,7 +66,7 @@ User::~User() = default; void User::load() { auto* profileJob = - connection()->callApi(QUrl::toPercentEncoding(id())); + connection()->callApi(id()); connect(profileJob, &BaseJob::result, this, [this, profileJob] { d->defaultName = profileJob->displayname(); d->defaultAvatar = Avatar(QUrl(profileJob->avatarUrl())); -- cgit v1.2.3 From cb7b9e8d04f060893a5ffb8cfa22c627c7dbe507 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 16 Nov 2021 00:53:39 +0100 Subject: Port away from implicit 'this' captures in lambdas Deprecated with C++20 --- lib/room.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index c11f7990..bef06dfe 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -572,13 +572,13 @@ QImage Room::avatar(int width, int height) { if (!d->avatar.url().isEmpty()) return d->avatar.get(connection(), width, height, - [=] { emit avatarChanged(); }); + [this] { emit avatarChanged(); }); // Use the first (excluding self) user's avatar for direct chats const auto dcUsers = directChatUsers(); for (auto* u : dcUsers) if (u != localUser()) - return u->avatar(width, height, this, [=] { emit avatarChanged(); }); + return u->avatar(width, height, this, [this] { emit avatarChanged(); }); return {}; } @@ -861,7 +861,7 @@ void Room::Private::getAllMembers() allMembersJob = connection->callApi( id, connection->nextBatchToken(), "join"); auto nextIndex = timeline.empty() ? 0 : timeline.back().index() + 1; - connect(allMembersJob, &BaseJob::success, q, [=] { + connect(allMembersJob, &BaseJob::success, q, [this, nextIndex] { Q_ASSERT(timeline.empty() || nextIndex <= q->maxTimelineIndex() + 1); auto roomChanges = updateStateFrom(allMembersJob->chunk()); // Replay member events that arrived after the point for which @@ -1973,7 +1973,7 @@ void Room::Private::getPreviousContent(int limit, const QString &filter) eventsHistoryJob = connection->callApi(id, prevBatch, "b", "", limit, filter); emit q->eventsHistoryJobChanged(); - connect(eventsHistoryJob, &BaseJob::success, q, [=] { + connect(eventsHistoryJob, &BaseJob::success, q, [this] { prevBatch = eventsHistoryJob->end(); addHistoricalMessageEvents(eventsHistoryJob->chunk()); }); -- cgit v1.2.3 From edb63528e6f3048045f70eb6a48412917bdbea0b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 15 Nov 2021 21:37:04 +0100 Subject: Bind read receipts to userIds, not to User* values This reduces the surface interacting with the User class that eventually will be split into LocalUser (most part) and RoomMember (a tiny wrapper around the member data in a given room, used almost everywhere in Room where User currently is). Also: dropped a log message when the new receipt is at or behind the old one as it causes a lot of noise in the logs. --- lib/room.cpp | 163 +++++++++++++++++++++++++++++++++-------------------------- lib/room.h | 12 ++++- 2 files changed, 100 insertions(+), 75 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 58950aac..ee76a85c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -120,14 +120,14 @@ public: int notificationCount = 0; members_map_t membersMap; QList usersTyping; - QHash> eventIdReadUsers; + QHash> eventIdReadUsers; QList usersInvited; QList membersLeft; int unreadMessages = 0; bool displayed = false; QString firstDisplayedEventId; QString lastDisplayedEventId; - QHash lastReadReceipts; + QHash lastReadReceipts; QString fullyReadUntilEventId; TagsMap tags; UnorderedMap accountData; @@ -291,7 +291,7 @@ public: */ void dropDuplicateEvents(RoomEvents& events) const; - void setLastReadReceipt(User* u, rev_iter_t newMarker, + bool setLastReadReceipt(const QString& userId, rev_iter_t newMarker, ReadReceipt newReceipt = {}); Changes setFullyReadMarker(const QString &eventId); Changes updateUnreadCount(const rev_iter_t& from, const rev_iter_t& to); @@ -619,60 +619,62 @@ void Room::setJoinState(JoinState state) emit joinStateChanged(oldState, state); } -void Room::Private::setLastReadReceipt(User* u, rev_iter_t newMarker, +bool Room::Private::setLastReadReceipt(const QString& userId, + rev_iter_t newMarker, ReadReceipt newReceipt) { - if (!u) { - Q_ASSERT(u != nullptr); // For Debug builds - qCCritical(MAIN) << "Empty user, skipping read receipt registration"; - return; // For Release builds - } - if (!q->isMember(u->id())) { - qCWarning(EPHEMERAL) - << "Won't record read receipt for non-member" << u->id(); - return; - } - - auto& storedReceipt = lastReadReceipts[u]; - if (newMarker == timeline.crend() && !newReceipt.eventId.isEmpty()) + if (newMarker == historyEdge() && !newReceipt.eventId.isEmpty()) newMarker = q->findInTimeline(newReceipt.eventId); - if (newMarker != timeline.crend()) { - // NB: with reverse iterators, timeline history >= sync edge - if (newMarker >= q->findInTimeline(storedReceipt.eventId)) { - qCDebug(EPHEMERAL) << "The new read receipt for" << u->id() - << "is at or behind the old one, skipping"; - return; - } - + if (newMarker != historyEdge()) { // Try to auto-promote the read marker over the user's own messages // (switch to direct iterators for that). - const auto eagerMarker = find_if(newMarker.base(), timeline.cend(), + const auto eagerMarker = find_if(newMarker.base(), syncEdge(), [=](const TimelineItem& ti) { - return ti->senderId() != u->id(); - }) - - 1; - newReceipt.eventId = (*eagerMarker)->id(); - if (eagerMarker != newMarker.base() - 1) // &*(rIt.base() - 1) === &*rIt - qCDebug(EPHEMERAL) << "Auto-promoted read receipt for" << u->id() + return ti->senderId() != userId; + }); + // eagerMarker is now just after the desired event for newMarker + if (eagerMarker != newMarker.base()) { + qCDebug(EPHEMERAL) << "Auto-promoted read receipt for" << userId << "to" << newReceipt.eventId; - } + newMarker = rev_iter_t(eagerMarker); + } + // Fill newReceipt with the event (and, if needed, timestamp) from + // eagerMarker + newReceipt.eventId = (eagerMarker - 1)->event()->id(); + if (newReceipt.timestamp.isNull()) + newReceipt.timestamp = QDateTime::currentDateTime(); + } + auto& storedReceipt = + lastReadReceipts[userId]; // clazy:exclude=detaching-member + const auto prevEventId = storedReceipt.eventId; + // NB: with reverse iterators, timeline history >= sync edge + if (newMarker >= q->findInTimeline(prevEventId)) + return false; - if (storedReceipt == newReceipt) - return; // Finally make the change - auto& oldEventReadUsers = eventIdReadUsers[storedReceipt.eventId]; - oldEventReadUsers.remove(u); - if (oldEventReadUsers.isEmpty()) - eventIdReadUsers.remove(storedReceipt.eventId); - eventIdReadUsers[newReceipt.eventId].insert(u); - swap(storedReceipt, newReceipt); // Now newReceipt actually stores the old receipt - qCDebug(EPHEMERAL) << "The new read receipt for" << u->id() << "is at" + + auto oldEventReadUsersIt = + eventIdReadUsers.find(prevEventId); // clazy:exclude=detaching-member + if (oldEventReadUsersIt != eventIdReadUsers.end()) { + oldEventReadUsersIt->remove(userId); + if (oldEventReadUsersIt->isEmpty()) + eventIdReadUsers.erase(oldEventReadUsersIt); + } + eventIdReadUsers[newReceipt.eventId].insert(userId); + storedReceipt = move(newReceipt); + qCDebug(EPHEMERAL) << "The new read receipt for" << userId << "is now at" << storedReceipt.eventId; - emit q->lastReadEventChanged(u); + + // TODO: use Room::member() when it becomes a thing and only emit signals + // for actual members, not just any user + const auto member = q->user(userId); + Q_ASSERT(member != nullptr); + emit q->lastReadEventChanged(member); // TODO: remove in 0.8 - if (!isLocalUser(u)) - emit q->readMarkerForUserMoved(u, newReceipt.eventId, + if (!isLocalUser(member)) + emit q->readMarkerForUserMoved(member, prevEventId, storedReceipt.eventId); + return true; } Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, @@ -777,7 +779,7 @@ Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) Changes changes = ReadMarkerChange; if (const auto rm = q->fullyReadMarker(); rm != historyEdge()) { // Pull read receipt if it's behind - setLastReadReceipt(q->localUser(), rm); + setLastReadReceipt(connection->userId(), rm); changes |= recalculateUnreadCount(); // TODO: updateUnreadCount()? } return changes; @@ -785,8 +787,13 @@ Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) void Room::setReadReceipt(const QString& atEventId) { - d->setLastReadReceipt(localUser(), historyEdge(), - { atEventId, QDateTime::currentDateTime() }); + if (!d->setLastReadReceipt(localUser()->id(), historyEdge(), + { atEventId, QDateTime::currentDateTime() })) { + qCDebug(EPHEMERAL) << "The new read receipt for" << localUser()->id() + << "in" << objectName() + << "is at or behind the old one, skipping"; + return; + } connection()->callApi(BackgroundRequest, id(), QStringLiteral("m.read"), QUrl::toPercentEncoding(atEventId)); @@ -1004,7 +1011,7 @@ QString Room::readMarkerEventId() const { return lastFullyReadEventId(); } ReadReceipt Room::lastReadReceipt(const QString& userId) const { - return d->lastReadReceipts.value(user(userId)); + return d->lastReadReceipts.value(userId); } QString Room::lastFullyReadEventId() const { return d->fullyReadUntilEventId; } @@ -1014,11 +1021,21 @@ Room::rev_iter_t Room::fullyReadMarker() const return findInTimeline(d->fullyReadUntilEventId); } -QSet Room::usersAtEventId(const QString& eventId) +QSet Room::userIdsAtEvent(const QString& eventId) { return d->eventIdReadUsers.value(eventId); } +QSet Room::usersAtEventId(const QString& eventId) +{ + const auto& userIds = d->eventIdReadUsers.value(eventId); + QSet users; + users.reserve(userIds.size()); + for (const auto& uId : userIds) + users.insert(user(uId)); + return users; +} + int Room::notificationCount() const { return d->notificationCount; } void Room::resetNotificationCount() @@ -2498,17 +2515,16 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) << totalInserted << "new events; the last event is now" << timeline.back(); - auto* const firstWriter = q->user((*from)->senderId()); - setLastReadReceipt(firstWriter, rev_iter_t(from + 1)); - if (firstWriter == q->localUser() + const auto& firstWriterId = (*from)->senderId(); + setLastReadReceipt(firstWriterId, rev_iter_t(from + 1)); + // If the local user's message(s) is/are first in the batch + // and the fully read marker was right before it, promote + // the fully read marker to the same event as the read receipt. + if (firstWriterId == connection->userId() && q->fullyReadMarker().base() == from) // - { - // If the local user's message(s) is/are first in the batch - // and the fully read marker was right before it, promote - // the fully read marker to the same event as the read receipt. roomChanges |= - setFullyReadMarker(lastReadReceipts.value(firstWriter).eventId); - } + setFullyReadMarker(q->lastReadReceipt(firstWriterId).eventId); + roomChanges |= updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); } @@ -2781,6 +2797,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) et.start(); if (auto* evt = eventCast(event)) { d->usersTyping.clear(); + d->usersTyping.reserve(evt->users().size()); // Assume all are members for (const auto& userId : evt->users()) if (isMember(userId)) d->usersTyping.append(user(userId)); @@ -2795,29 +2812,29 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) const auto& eventsWithReceipts = evt->eventsWithReceipts(); for (const auto& p : eventsWithReceipts) { totalReceipts += p.receipts.size(); - { - if (p.receipts.size() == 1) - qCDebug(EPHEMERAL) << "Marking" << p.evtId << "as read for" - << p.receipts[0].userId; - else - qCDebug(EPHEMERAL) << "Marking" << p.evtId << "as read for" - << p.receipts.size() << "users"; - } const auto newMarker = findInTimeline(p.evtId); if (newMarker == historyEdge()) qCDebug(EPHEMERAL) << "Event of the read receipt(s) is not " - "found; saving them anyway"; + "found; saving anyway"; // If the event is not found (most likely, because it's too old and // hasn't been fetched from the server yet) but there is a previous // marker for a user, keep the previous marker because read receipts // are not supposed to move backwards. Otherwise, blindly store // the event id for this user and update the read marker when/if // the event is fetched later on. - for (const auto& r : p.receipts) - if (isMember(r.userId)) { - d->setLastReadReceipt(user(r.userId), newMarker, - { p.evtId, r.timestamp }); - } + const auto updatedCount = std::count_if( + p.receipts.cbegin(), p.receipts.cend(), + [this, &newMarker, &evtId = p.evtId](const auto& r) -> bool { + return d->setLastReadReceipt(r.userId, newMarker, + { evtId, r.timestamp }); + }); + + if (p.receipts.size() > 1) + qCDebug(EPHEMERAL) << p.evtId << "marked as read for" + << updatedCount << "user(s)"; + if (updatedCount < p.receipts.size()) + qCDebug(EPHEMERAL) << p.receipts.size() - updatedCount + << "receipts were skipped"; } if (eventsWithReceipts.size() > 3 || totalReceipts > 10 || et.nsecsElapsed() >= profilerMinNsecs()) diff --git a/lib/room.h b/lib/room.h index 260510e6..e4d33c4e 100644 --- a/lib/room.h +++ b/lib/room.h @@ -442,10 +442,18 @@ public: //! This method is for cases when you need to show users who have read //! an event. Calling it on inexistent or empty event id will return //! an empty set. - //! \sa lastReadReceipt + //! \note The returned list may contain ids resolving to users that are + //! not loaded as room members yet (in particular, if members are not + //! yet lazy-loaded). For now this merely means that the user's + //! room-specific name and avatar will not be there; but generally + //! it's recommended to ensure that all room members are loaded + //! before operating on the result of this function. + //! \sa lastReadReceipt, allMembersLoaded + QSet userIdsAtEvent(const QString& eventId); + + [[deprecated("Use userIdsAtEvent instead")]] QSet usersAtEventId(const QString& eventId); - //! //! \brief Mark the event with uptoEventId as fully read //! //! Marks the event with the specified id as fully read locally and also -- cgit v1.2.3 From 7b633ba257fc8643ef8cc2ef724f3b6ac9e186ba Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 18 Oct 2021 05:42:12 +0200 Subject: Room: doc-comments cleanup [skip ci] --- lib/room.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/room.h b/lib/room.h index e4d33c4e..24025a88 100644 --- a/lib/room.h +++ b/lib/room.h @@ -381,8 +381,8 @@ public: void setLastDisplayedEvent(TimelineItem::index_t index); //! \brief Obtain a read receipt of any user + //! \deprecated Use lastReadReceipt or fullyReadMarker instead. //! - //! \deprecated Use lastReadReceipt or fullyReadMarker instead //! Historically, readMarker was returning a "converged" read marker //! representing both the read receipt and the fully read marker, as //! Quotient managed them together. Since 0.6.8, a single-argument call of @@ -397,16 +397,19 @@ public: rev_iter_t readMarker(const User* user) const; //! \brief Obtain the local user's fully-read marker //! \deprecated Use fullyReadMarker instead - //! See the documentation for the single-argument overload + //! + //! See the documentation for the single-argument overload. //! \sa fullyReadMarker [[deprecated("Use lastReadReceipt() to get m.read receipt or" " fullyReadMarker() to get m.fully_read marker")]] // rev_iter_t readMarker() const; //! \brief Get the event id for the local user's fully-read marker //! \deprecated Use lastFullyReadEventId instead + //! //! See the readMarker documentation [[deprecated("Use lastReadReceipt() to get m.read receipt or" - " fullyReadMarker() to get m.fully_read marker")]] // + " lastFullyReadEventId() to get an event id that" + " m.fully_read marker points to")]] // QString readMarkerEventId() const; //! \brief Get the latest read receipt from a user -- cgit v1.2.3 From b472fc355dc5ce70391ca2b9bc8da35b973ae3a3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 8 Nov 2021 21:03:37 +0100 Subject: Room: lastLocalReadReceipt(), localReadReceiptMarker() To simplify retrieval of the local m.read receipt and the marker for it. --- lib/room.cpp | 10 ++++++++++ lib/room.h | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index ee76a85c..273a5753 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1014,6 +1014,16 @@ ReadReceipt Room::lastReadReceipt(const QString& userId) const return d->lastReadReceipts.value(userId); } +ReadReceipt Room::lastLocalReadReceipt() const +{ + return d->lastReadReceipts.value(localUser()->id()); +} + +Room::rev_iter_t Room::localReadReceiptMarker() const +{ + return findInTimeline(lastLocalReadReceipt().eventId); +} + QString Room::lastFullyReadEventId() const { return d->fullyReadUntilEventId; } Room::rev_iter_t Room::fullyReadMarker() const diff --git a/lib/room.h b/lib/room.h index 24025a88..d94de51c 100644 --- a/lib/room.h +++ b/lib/room.h @@ -400,8 +400,7 @@ public: //! //! See the documentation for the single-argument overload. //! \sa fullyReadMarker - [[deprecated("Use lastReadReceipt() to get m.read receipt or" - " fullyReadMarker() to get m.fully_read marker")]] // + [[deprecated("Use localReadReceiptMarker() or fullyReadMarker()")]] // rev_iter_t readMarker() const; //! \brief Get the event id for the local user's fully-read marker //! \deprecated Use lastFullyReadEventId instead @@ -420,6 +419,19 @@ public: //! \sa usersAtEventId ReadReceipt lastReadReceipt(const QString& userId) const; + //! \brief Get the latest read receipt from the local user + //! + //! This is a shortcut for lastReadReceipt(localUserId). + //! \sa lastReadReceipt + ReadReceipt lastLocalReadReceipt() const; + + //! \brief Find the timeline item the local read receipt is at + //! + //! This is a shortcut for \code + //! room->findInTimeline(room->lastLocalReadReceipt().eventId); + //! \endcode + rev_iter_t localReadReceiptMarker() const; + //! \brief Get the latest event id marked as fully read //! //! This can be either the event id pointed to by the actual latest -- cgit v1.2.3 From d97195d3c67dcf08d727a2a65863b99982c6b24e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 10 Jul 2021 18:37:16 +0200 Subject: Room: refactoring around logging (esp. profile logs) --- lib/room.cpp | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index a06a8d2a..54d63138 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -266,8 +266,8 @@ public: } if (events.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) - << "*** Room::Private::updateStateFrom():" << events.size() - << "event(s)," << et; + << "Updated" << q->objectName() << "room state from" + << events.size() << "event(s) in" << et; } return changes; } @@ -713,7 +713,8 @@ Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, count_if(from, to, std::bind(&Room::Private::isEventNotable, this, _1)); if (et.nsecsElapsed() > profilerMinNsecs() / 10) - qCDebug(PROFILER) << "Counting gained unread messages took" << et; + qCDebug(PROFILER) << "Counting gained unread messages in" + << q->objectName() << "took" << et; if (newUnreadMessages == 0) return NoChange; @@ -745,7 +746,8 @@ Room::Changes Room::Private::recalculateUnreadCount(bool force) int(count_if(timeline.crbegin(), q->fullyReadMarker(), [this](const auto& ti) { return isEventNotable(ti); })); if (et.nsecsElapsed() > profilerMinNsecs() / 10) - qCDebug(PROFILER) << "Recounting unread messages took" << et; + qCDebug(PROFILER) << "Recounting unread messages in" << q->objectName() + << "took" << et; // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (unreadMessages == 0) @@ -1634,21 +1636,13 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) setJoinState(data.joinState); Changes roomChanges = Change::NoChange; - QElapsedTimer et; - et.start(); for (auto&& event : data.accountData) roomChanges |= processAccountDataEvent(move(event)); roomChanges |= d->updateStateFrom(data.state); + // The order of calculation is important - don't merge these lines! + roomChanges |= d->addNewMessageEvents(move(data.timeline)); - if (!data.timeline.empty()) { - et.restart(); - roomChanges |= d->addNewMessageEvents(move(data.timeline)); - if (data.timeline.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) - << "*** Room::addNewMessageEvents():" << data.timeline.size() - << "event(s)," << et; - } if (roomChanges & TopicChange) emit topicChanged(); @@ -2403,6 +2397,8 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) if (events.empty()) return Change::NoChange; + QElapsedTimer et; + et.start(); { // Pre-process redactions and edits so that events that get // redacted/replaced in the same batch landed in the timeline already @@ -2539,6 +2535,9 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) } Q_ASSERT(timeline.size() == timelineSize + totalInserted); + if (totalInserted > 9 || et.nsecsElapsed() >= profilerMinNsecs()) + qCDebug(PROFILER) << "Added" << totalInserted << "new event(s) to" + << q->objectName() << "in" << et; return roomChanges; } @@ -2583,13 +2582,13 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) updateUnreadCount(from, historyEdge()); // When there are no unread messages and the read marker is within the // known timeline, unreadMessages == -1 - // (see https://github.com/quotient-im/libQuotient/wiki/unread_count). + // (see https://github.com/quotient-im/libQuotient/wiki/Developer-notes#2-saving-unread-event-counts). Q_ASSERT(unreadMessages != 0 || q->fullyReadMarker() == historyEdge()); Q_ASSERT(timeline.size() == timelineSize + insertedSize); if (insertedSize > 9 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::addHistoricalMessageEvents():" - << insertedSize << "event(s)," << et; + qCDebug(PROFILER) << "Added" << insertedSize << "historical event(s) to" + << q->objectName() << "in" << et; } Room::Changes Room::processStateEvent(const RoomEvent& e) @@ -2813,8 +2812,9 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) d->usersTyping.append(user(userId)); if (evt->users().size() > 3 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::processEphemeralEvent(typing):" - << evt->users().size() << "users," << et; + qCDebug(PROFILER) + << "Processing typing events from" << evt->users().size() + << "user(s) in" << objectName() << "took" << et; emit typingChanged(); } if (auto* evt = eventCast(event)) { @@ -2848,10 +2848,9 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) } if (eventsWithReceipts.size() > 3 || totalReceipts > 10 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) - << "*** Room::processEphemeralEvent(receipts):" - << eventsWithReceipts.size() << "event(s) with" - << totalReceipts << "receipt(s)," << et; + qCDebug(PROFILER) << "Processing" << totalReceipts + << "receipt(s) on" << eventsWithReceipts.size() + << "event(s) in" << objectName() << "took" << et; } return changes; } @@ -3075,7 +3074,8 @@ QJsonObject Room::Private::toJson() const result.insert(QStringLiteral("unread_notifications"), unreadNotifObj); if (et.elapsed() > 30) - qCDebug(PROFILER) << "Room::toJson() for" << displayname << "took" << et; + qCDebug(PROFILER) << "Room::toJson() for" << q->objectName() << "took" + << et; return result; } -- cgit v1.2.3 From bcf7f7e6408872d8315e5c69829d7ce790e4820a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 17 Nov 2021 20:27:10 +0100 Subject: Fix QDateTime(QDate) deprecation warnings --- lib/converters.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/converters.h b/lib/converters.h index d9a68bfb..8ec0fa81 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -146,7 +146,15 @@ struct JsonConverter { template <> struct JsonConverter { - static auto dump(const QDate& val) { return toJson(QDateTime(val)); } + static auto dump(const QDate& val) { + return toJson( +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QDateTime(val) +#else + val.startOfDay() +#endif + ); + } static auto load(const QJsonValue& jv) { return fromJson(jv).date(); -- cgit v1.2.3 From 76b6238af16a1ccd284831ce42ec9e2cb1fba2c5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 18 Nov 2021 17:33:05 +0100 Subject: Make Room::Changes an enum class; simplify enumerators This enumeration sees very limited (if any) use outside Quotient; and though this change will surely break code using it the fix is very straightforward and quick. --- lib/room.cpp | 70 +++++++++++++++++++++++++++++------------------------------- lib/room.h | 39 ++++++++++++++++----------------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 54d63138..a376238e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -251,15 +251,14 @@ public: template Changes updateStateFrom(EventArrayT&& events) { - Changes changes = NoChange; + Changes changes {}; if (!events.empty()) { QElapsedTimer et; et.start(); for (auto&& eptr : events) { const auto& evt = *eptr; Q_ASSERT(evt.isStateEvent()); - auto change = q->processStateEvent(evt); - if (change != NoChange) { + if (auto change = q->processStateEvent(evt); change) { changes |= change; baseState[{ evt.matrixType(), evt.stateKey() }] = move(eptr); } @@ -615,7 +614,6 @@ void Room::setJoinState(JoinState state) d->joinState = state; qCDebug(STATE) << "Room" << id() << "changed state: " << oldState << "->" << state; - emit changed(Change::JoinStateChange); emit joinStateChanged(oldState, state); } @@ -685,7 +683,7 @@ Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, auto fullyReadMarker = q->fullyReadMarker(); if (fullyReadMarker < from) - return NoChange; // What's arrived is already fully read + return Change::None; // What's arrived is already fully read // If there's no read marker in the whole room, initialise it if (fullyReadMarker == historyEdge() && q->allHistoryLoaded()) @@ -717,7 +715,7 @@ Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, << q->objectName() << "took" << et; if (newUnreadMessages == 0) - return NoChange; + return Change::None; // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (unreadMessages < 0) @@ -731,7 +729,7 @@ Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, : "in total") << unreadMessages << "unread message(s)"; emit q->unreadMessagesChanged(q); - return UnreadNotifsChange; + return Change::UnreadNotifs; } Room::Changes Room::Private::recalculateUnreadCount(bool force) @@ -754,7 +752,7 @@ Room::Changes Room::Private::recalculateUnreadCount(bool force) unreadMessages = -1; if (!force && unreadMessages == oldUnreadCount) - return NoChange; + return Change::None; if (unreadMessages == -1) qCDebug(MESSAGES) @@ -763,13 +761,13 @@ Room::Changes Room::Private::recalculateUnreadCount(bool force) qCDebug(MESSAGES) << "Room" << displayname << "still has" << unreadMessages << "unread message(s)"; emit q->unreadMessagesChanged(q); - return UnreadNotifsChange; + return Change::UnreadNotifs; } Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) { if (fullyReadUntilEventId == eventId) - return NoChange; + return Change::None; const auto prevFullyReadId = std::exchange(fullyReadUntilEventId, eventId); qCDebug(MESSAGES) << "Fully read marker in" << q->objectName() // @@ -778,7 +776,7 @@ Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) // TODO: Remove in 0.8 emit q->readMarkerMoved(prevFullyReadId, fullyReadUntilEventId); - Changes changes = ReadMarkerChange; + QT_IGNORE_DEPRECATIONS(Changes changes = Change::ReadMarker;) if (const auto rm = q->fullyReadMarker(); rm != historyEdge()) { // Pull read receipt if it's behind setLastReadReceipt(connection->userId(), rm); @@ -927,7 +925,7 @@ void Room::Private::getAllMembers() it != syncEdge(); ++it) if (is(**it)) roomChanges |= q->processStateEvent(**it); - if (roomChanges & MembersChange) + if (roomChanges & Change::Members) emit q->memberListChanged(); emit q->allMembersLoaded(); }); @@ -1442,11 +1440,11 @@ GetRoomEventsJob* Room::eventsHistoryJob() const { return d->eventsHistoryJob; } Room::Changes Room::Private::setSummary(RoomSummary&& newSummary) { if (!summary.merge(newSummary)) - return Change::NoChange; + return Change::None; qCDebug(STATE).nospace().noquote() << "Updated room summary for " << q->objectName() << ": " << summary; emit q->memberListChanged(); - return Change::SummaryChange; + return Change::Summary; } void Room::Private::insertMemberIntoMap(User* u) @@ -1635,7 +1633,7 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) d->prevBatch = data.timelinePrevBatch; setJoinState(data.joinState); - Changes roomChanges = Change::NoChange; + Changes roomChanges {}; for (auto&& event : data.accountData) roomChanges |= processAccountDataEvent(move(event)); @@ -1643,13 +1641,13 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) // The order of calculation is important - don't merge these lines! roomChanges |= d->addNewMessageEvents(move(data.timeline)); - if (roomChanges & TopicChange) + if (roomChanges & Change::Topic) emit topicChanged(); - if (roomChanges & (NameChange | AliasesChange)) + if (roomChanges & (Change::Name | Change::Aliases)) emit namesChanged(this); - if (roomChanges & MembersChange) + if (roomChanges & Change::Members) emit memberListChanged(); roomChanges |= d->setSummary(move(data.summary)); @@ -1670,7 +1668,7 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) if (merge(d->notificationCount, data.notificationCount)) emit notificationCountChanged(); - if (roomChanges != Change::NoChange) { + if (roomChanges) { d->updateDisplayname(); emit changed(roomChanges); if (!fromCache) @@ -2395,7 +2393,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) { dropDuplicateEvents(events); if (events.empty()) - return Change::NoChange; + return Change::None; QElapsedTimer et; et.start(); @@ -2448,7 +2446,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // clients historically expect. This may eventually change though if we // postulate that the current state is only current between syncs but not // within a sync. - Changes roomChanges = Change::NoChange; + Changes roomChanges {}; for (const auto& eptr : events) roomChanges |= q->processStateEvent(*eptr); @@ -2594,7 +2592,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) Room::Changes Room::processStateEvent(const RoomEvent& e) { if (!e.isStateEvent()) - return NoChange; + return Change::None; // Find a value (create an empty one if necessary) and get a reference // to it. Can't use getCurrentState<>() because it (creates and) returns @@ -2682,7 +2680,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) , true); // By default, go forward with the state change // clang-format on if (!proceed) - return NoChange; + return Change::None; // Change the state const auto* const oldStateEvent = @@ -2700,7 +2698,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // clang-format off const auto result = visit(e , [] (const RoomNameEvent&) { - return NameChange; + return Change::Name; } , [this, oldStateEvent] (const RoomCanonicalAliasEvent& cae) { // clang-format on @@ -2719,16 +2717,16 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) newAliases.push_front(cae.alias()); connection()->updateRoomAliases(id(), previousAltAliases, newAliases); - return AliasesChange; + return Change::Aliases; // clang-format off } , [] (const RoomTopicEvent&) { - return TopicChange; + return Change::Topic; } , [this] (const RoomAvatarEvent& evt) { if (d->avatar.updateUrl(evt.url())) emit avatarChanged(); - return AvatarChange; + return Change::Avatar; } , [this,oldStateEvent] (const RoomMemberEvent& evt) { // clang-format on @@ -2767,14 +2765,14 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) case Membership::Undefined: qCWarning(MEMBERS) << "Ignored undefined membership type"; } - return MembersChange; + return Change::Members; // clang-format off } , [this] (const EncryptionEvent&) { // As encryption can only be switched on once, emit the signal here // instead of aggregating and emitting in updateData() emit encryption(); - return OtherChange; + return Change::Other; } , [this] (const RoomTombstoneEvent& evt) { const auto successorId = evt.successorRoomId(); @@ -2790,18 +2788,18 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return true; }); - return OtherChange; + return Change::Other; // clang-format off } - , OtherChange); + , Change::Other); // clang-format on - Q_ASSERT(result != NoChange); + Q_ASSERT(result != Change::None); return result; } Room::Changes Room::processEphemeralEvent(EventPtr&& event) { - Changes changes = NoChange; + Changes changes {}; QElapsedTimer et; et.start(); if (auto* evt = eventCast(event)) { @@ -2857,10 +2855,10 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) Room::Changes Room::processAccountDataEvent(EventPtr&& event) { - Changes changes = NoChange; + Changes changes {}; if (auto* evt = eventCast(event)) { d->setTags(evt->tags()); - changes |= Change::TagsChange; + changes |= Change::Tags; } if (auto* evt = eventCast(event)) @@ -2879,7 +2877,7 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) // TODO: Drop AccountDataChange in 0.8 // NB: GCC (at least 10) only accepts QT_IGNORE_DEPRECATIONS around // a statement, not within a statement - QT_IGNORE_DEPRECATIONS(changes |= AccountDataChange | OtherChange;) + QT_IGNORE_DEPRECATIONS(changes |= Change::AccountData | Change::Other;) } return changes; } diff --git a/lib/room.h b/lib/room.h index d94de51c..8a544e82 100644 --- a/lib/room.h +++ b/lib/room.h @@ -157,26 +157,27 @@ public: using rev_iter_t = Timeline::const_reverse_iterator; using timeline_iter_t = Timeline::const_iterator; - enum Change : uint { - NoChange = 0x0, - NameChange = 0x1, - AliasesChange = 0x2, - CanonicalAliasChange = AliasesChange, - TopicChange = 0x4, - UnreadNotifsChange = 0x8, - AvatarChange = 0x10, - JoinStateChange = 0x20, - TagsChange = 0x40, - MembersChange = 0x80, + enum class Change : uint { + None = 0x0, + Name = 0x1, + Aliases = 0x2, + CanonicalAlias = Aliases, + Topic = 0x4, + UnreadNotifs = 0x8, + Avatar = 0x10, + JoinState = 0x20, + Tags = 0x40, + Members = 0x80, /* = 0x100, */ - AccountDataChange Q_DECL_ENUMERATOR_DEPRECATED_X( - "AccountDataChange will be merged into OtherChange in 0.8") = 0x200, - SummaryChange = 0x400, - ReadMarkerChange Q_DECL_ENUMERATOR_DEPRECATED_X( - "ReadMarkerChange will be merged into OtherChange in 0.8") = 0x800, - OtherChange = 0x8000, - OtherChanges = OtherChange, - AnyChange = 0xFFFF + AccountData Q_DECL_ENUMERATOR_DEPRECATED_X( + "Change::AccountData will be merged into Change::Other in 0.8") = + 0x200, + Summary = 0x400, + ReadMarker Q_DECL_ENUMERATOR_DEPRECATED_X( + "Change::ReadMarker will be merged into Change::Other in 0.8") = + 0x800, + Other = 0x8000, + Any = 0xFFFF }; QUO_DECLARE_FLAGS(Changes, Change) -- cgit v1.2.3 From d6cf6b32cdd2843c40fc696accd8a6456f1ea15c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 19 Nov 2021 12:45:50 +0100 Subject: Make enum values logging more terse() By default enum class values are logged along with the qualifier; this may or may not be desirable in a given setting. For JoinState(s) and Membership(Mask) operator<< was overloaded to implicitly suppress qualification; however, this is both overly sweeping and uses Qt's internal API for the backend. Instead, a new QDebug manipulator, terse(), is introduced, that does the same as those operator<< overloads but on a per-invocation basis. This makes it slightly more verbose to log enums but makes the QDebug reconfiguration explicit and doesn't require to produce new overloads every time a new enum ends up in logs. And it's built entirely on the published Qt API, reusing the QDebugManip framework that Quotient already has. Also: operator<<(QDebug, QDebugManip) has been moved out of the namespace to fix lookup issues when there's no prior `using namespace Quotient`. --- CMakeLists.txt | 2 +- lib/connection.cpp | 4 ++-- lib/logging.h | 31 +++++++++++++++++++++---------- lib/quotient_common.cpp | 45 --------------------------------------------- lib/quotient_common.h | 6 ------ lib/room.cpp | 4 ++-- 6 files changed, 26 insertions(+), 66 deletions(-) delete mode 100644 lib/quotient_common.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 34200548..3814bc7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,7 @@ endif () # Set up source files list(APPEND lib_SRCS - lib/quotient_common.cpp + lib/quotient_common.h lib/networkaccessmanager.cpp lib/connectiondata.cpp lib/connection.cpp diff --git a/lib/connection.cpp b/lib/connection.cpp index 75966731..e65fdac4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -636,7 +636,7 @@ void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, } qWarning(MAIN) << "Room" << roomData.roomId << "has just been forgotten but /sync returned it in" - << roomData.joinState + << terse << roomData.joinState << "state - suspiciously fast turnaround"; } if (auto* r = q->provideRoom(roomData.roomId, roomData.joinState)) { @@ -1341,7 +1341,7 @@ void Connection::Private::removeRoom(const QString& roomId) { for (auto f : { false, true }) if (auto r = roomMap.take({ roomId, f })) { - qCDebug(MAIN) << "Room" << r->objectName() << "in state" + qCDebug(MAIN) << "Room" << r->objectName() << "in state" << terse << r->joinState() << "will be deleted"; emit r->beforeDestruction(r); r->deleteLater(); diff --git a/lib/logging.h b/lib/logging.h index 5a3ef6ea..5bf050a9 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -40,17 +40,15 @@ inline QDebug formatJson(QDebug debug_object) return debug_object.noquote(); } -/** - * @brief A helper operator to facilitate usage of formatJson (and possibly - * other manipulators) - * - * @param debug_object to output the json to - * @param qdm a QDebug manipulator - * @return a copy of debug_object that has its mode altered by qdm - */ -inline QDebug operator<<(QDebug debug_object, QDebugManip qdm) +//! Suppress full qualification of enums/QFlags when logging +inline QDebug terse(QDebug dbg) { - return qdm(debug_object); + return +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + dbg.setVerbosity(0), dbg; +#else + dbg.verbosity(QDebug::MinimumVerbosity); +#endif } inline qint64 profilerMinNsecs() @@ -65,6 +63,19 @@ inline qint64 profilerMinNsecs() } } // namespace Quotient +/** + * @brief A helper operator to facilitate usage of formatJson (and possibly + * other manipulators) + * + * @param debug_object to output the json to + * @param qdm a QDebug manipulator + * @return a copy of debug_object that has its mode altered by qdm + */ +inline QDebug operator<<(QDebug debug_object, Quotient::QDebugManip qdm) +{ + return qdm(debug_object); +} + inline QDebug operator<<(QDebug debug_object, const QElapsedTimer& et) { auto val = et.nsecsElapsed() / 1000; diff --git a/lib/quotient_common.cpp b/lib/quotient_common.cpp deleted file mode 100644 index 5d7a3027..00000000 --- a/lib/quotient_common.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "quotient_common.h" - -#include - -using namespace Quotient; - -template -inline QDebug suppressScopeAndDump(QDebug dbg, Enum e) -{ - // Suppress "Quotient::" prefix - QDebugStateSaver _dss(dbg); - dbg.setVerbosity(0 /* QDebug::MinimumVerbosity since Qt 5.13 */); - return qt_QMetaEnum_debugOperator(dbg, std::underlying_type_t(e), - qt_getEnumMetaObject(e), - qt_getEnumName(e)); -} - -template -inline QDebug suppressScopeAndDump(QDebug dbg, const QFlags& f) -{ - // Suppress "Quotient::" prefix - QDebugStateSaver _dss(dbg); - dbg.setVerbosity(0 /* QDebug::MinimumVerbosity since Qt 5.13 */); - return qt_QMetaEnum_flagDebugOperator_helper(dbg, f); -} - -QDebug operator<<(QDebug dbg, Membership m) -{ - return suppressScopeAndDump(dbg, m); -} - -QDebug operator<<(QDebug dbg, MembershipMask mm) -{ - return suppressScopeAndDump(dbg, mm) << ")"; -} - -QDebug operator<<(QDebug dbg, JoinState js) -{ - return suppressScopeAndDump(dbg, js); -} - -QDebug operator<<(QDebug dbg, JoinStates jss) -{ - return suppressScopeAndDump(dbg, jss) << ")"; -} diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 3d8ace67..969ebe90 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -118,9 +118,3 @@ constexpr inline auto RoomTypeStrings = make_array( } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates) - -class QDebug; -QDebug operator<<(QDebug dbg, Quotient::Membership m); -QDebug operator<<(QDebug dbg, Quotient::MembershipMask m); -QDebug operator<<(QDebug dbg, Quotient::JoinState js); -QDebug operator<<(QDebug dbg, Quotient::JoinStates js); diff --git a/lib/room.cpp b/lib/room.cpp index a376238e..a2ec228a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -461,7 +461,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) emit baseStateLoaded(); return this == r; // loadedRoomState fires only once per room }); - qCDebug(STATE) << "New" << initialJoinState << "Room:" << id; + qCDebug(STATE) << "New" << terse << initialJoinState << "Room:" << id; } Room::~Room() { delete d; } @@ -612,7 +612,7 @@ void Room::setJoinState(JoinState state) if (state == oldState) return; d->joinState = state; - qCDebug(STATE) << "Room" << id() << "changed state: " << oldState + qCDebug(STATE) << "Room" << id() << "changed state: " << terse << oldState << "->" << state; emit joinStateChanged(oldState, state); } -- cgit v1.2.3 From 2d1cf137d7380a15673826bce00e71461fbc7446 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 19 Nov 2021 12:46:00 +0100 Subject: Cleanup --- lib/events/roommemberevent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 469dbb32..b0bc7bcb 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -17,7 +17,7 @@ struct JsonConverter { const auto& ms = jv.toString(); if (ms.isEmpty()) { - qCWarning(EVENTS) << "Empty member state:" << ms; + qCWarning(EVENTS) << "Empty membership state"; return Membership::Invalid; } const auto it = -- cgit v1.2.3 From 71b7f05e42f93c3da590b6f7f55658a81b607c0e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 19 Nov 2021 17:17:30 +0100 Subject: Add continue-on-error for regenerated API files The current upstream API definitions are expected to fail the test. --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47e31d55..f22ea6d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,7 +122,7 @@ jobs: echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ >>$GITHUB_ENV - echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN and API files regeneration" >>$GITHUB_ENV + echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with API files regeneration" >>$GITHUB_ENV - name: Initialize CodeQL tools if: env.CODEQL_ANALYSIS @@ -154,6 +154,7 @@ jobs: ls ~/.local/$BIN_DIR/quotest - name: Run tests + continue-on-error: ${{ matrix.update-api != '' }} # the current upstream API definitions are expected to fail the test env: TEST_USER: ${{ secrets.TEST_USER }} TEST_PWD: ${{ secrets.TEST_PWD }} -- cgit v1.2.3 From 02fa42b8ac5694485c7f6dedc62a873e97da2d35 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 19 Nov 2021 18:57:36 +0100 Subject: continue-on-error on the job level Continue on the step level marks the whole job as successful which is not really accurate. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f22ea6d2..269e487c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ defaults: jobs: CI: runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.update-api != '' }} # the current upstream API definitions are expected to fail the test strategy: fail-fast: false max-parallel: 1 @@ -154,7 +155,6 @@ jobs: ls ~/.local/$BIN_DIR/quotest - name: Run tests - continue-on-error: ${{ matrix.update-api != '' }} # the current upstream API definitions are expected to fail the test env: TEST_USER: ${{ secrets.TEST_USER }} TEST_PWD: ${{ secrets.TEST_PWD }} -- cgit v1.2.3 From bf5f209d2d237301c65cc0973f1707b9386f3110 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 21 Nov 2021 06:55:09 +0100 Subject: Room: isEventNotable, notificationFor, checkForNotifications Room::isEventNotable has been moved out from Room::Private and made compliant with MSC2654. The concept of Room::checkForNotifications is taken from Quaternion where a method with the same name has been in QuaternionRoom for a long time - however, actual body is a stub for now, always returning { Notification::None } (Quaternion's implementation is too crude to be taken to the library). Now we really need a pushrules processor to fill this method with something reasonably good. Internally the library now calls checkForNotifications() on every event added to the timeline, filling up the events-to-notifications map because it is anticipated that calculation of notifications can be rather resource-intensive and should only be done once for a given event. Finally, Room::notificationsFor is an accessor into the mentioned map, standing next to isEventNotable (but unlike isEventNotable, it's not virtual; checkForNotifications is). --- lib/room.cpp | 43 ++++++++++++++++++++++++++++++------------- lib/room.h | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index a2ec228a..67f65472 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -116,6 +116,7 @@ public: QHash, RelatedEvents> relations; QString displayname; Avatar avatar; + QHash notifications; int highlightCount = 0; int notificationCount = 0; members_map_t membersMap; @@ -241,13 +242,6 @@ public: // return EventT::content_type() // } - bool isEventNotable(const TimelineItem& ti) const - { - return !ti->isRedacted() && ti->senderId() != connection->userId() - && is(*ti) - && ti.viewAs()->replacedEvent().isEmpty(); - } - template Changes updateStateFrom(EventArrayT&& events) { @@ -709,7 +703,7 @@ Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, et.start(); const auto newUnreadMessages = count_if(from, to, - std::bind(&Room::Private::isEventNotable, this, _1)); + std::bind(&Room::isEventNotable, q, _1)); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Counting gained unread messages in" << q->objectName() << "took" << et; @@ -742,7 +736,7 @@ Room::Changes Room::Private::recalculateUnreadCount(bool force) et.start(); unreadMessages = int(count_if(timeline.crbegin(), q->fullyReadMarker(), - [this](const auto& ti) { return isEventNotable(ti); })); + [this](const auto& ti) { return q->isEventNotable(ti); })); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Recounting unread messages in" << q->objectName() << "took" << et; @@ -837,6 +831,28 @@ bool Room::canSwitchVersions() const return true; } +bool Room::isEventNotable(const TimelineItem &ti) const +{ + const auto& evt = *ti; + const auto* rme = ti.viewAs(); + return !evt.isRedacted() + && (is(evt) || is(evt) + || is(evt) || is(evt) + || (rme && rme->msgtype() != MessageEventType::Notice + && rme->replacedEvent().isEmpty())) + && evt.senderId() != localUser()->id(); +} + +Notification Room::notificationFor(const TimelineItem &ti) const +{ + return d->notifications.value(ti->id()); +} + +Notification Room::checkForNotifications(const TimelineItem &ti) +{ + return { Notification::None }; +} + bool Room::hasUnreadMessages() const { return unreadCount() >= 0; } int Room::unreadCount() const { return d->unreadMessages; } @@ -1548,11 +1564,12 @@ Room::Private::moveEventsToTimeline(RoomEventsRange events, !eventsIndex.contains(eId), __FUNCTION__, makeErrorStr(*e, "Event is already in the timeline; " "incoming events were not properly deduplicated")); - if (placement == Older) - timeline.emplace_front(move(e), --index); - else - timeline.emplace_back(move(e), ++index); + const auto& ti = placement == Older + ? timeline.emplace_front(move(e), --index) + : timeline.emplace_back(move(e), ++index); eventsIndex.insert(eId, index); + if (auto n = q->checkForNotifications(ti); n.type != Notification::None) + notifications.insert(e->id(), n); Q_ASSERT(q->findInTimeline(eId)->event()->id() == eId); } const auto insertedSize = (index - baseIndex) * placement; diff --git a/lib/room.h b/lib/room.h index 8a544e82..124c8cb4 100644 --- a/lib/room.h +++ b/lib/room.h @@ -96,6 +96,18 @@ inline void swap(ReadReceipt& lhs, ReadReceipt& rhs) swap(lhs.timestamp, rhs.timestamp); } +struct Notification +{ + enum Type { None = 0, Basic, Highlight }; + Q_ENUM(Notification) + + Type type = None; + +private: + Q_GADGET + Q_PROPERTY(Type type MEMBER type CONSTANT) +}; + class Room : public QObject { Q_OBJECT Q_PROPERTY(Connection* connection READ connection CONSTANT) @@ -482,6 +494,29 @@ public: //! \sa markAllMessagesAsRead, fullyReadMarker Q_INVOKABLE void markMessagesAsRead(QString uptoEventId); + //! \brief Determine whether an event should be counted as unread + //! + //! The criteria of including an event in unread counters are described in + //! [MSC2654](https://github.com/matrix-org/matrix-doc/pull/2654); according + //! to these, the event should be counted as unread (or, in libQuotient + //! parlance, is "notable") if it is: + //! - either + //! - a message event that is not m.notice, or + //! - a state event with type being one of: + //! `m.room.topic`, `m.room.name`, `m.room.avatar`, `m.room.tombstone`; + //! - neither redacted, nor an edit (redactions cause the redacted event + //! to stop being notable, while edits are not notable themselves while + //! the original event usually is); + //! - from a non-local user (events from other devices of the local + //! user are not notable). + virtual bool isEventNotable(const TimelineItem& ti) const; + + //! \brief Get notification details for an event + //! + //! This allows to get details on the kind of notification that should + //! generated for \p evt. + Notification notificationFor(const TimelineItem& ti) const; + //! Check whether there are unread messages in the room bool hasUnreadMessages() const; @@ -873,6 +908,7 @@ protected: {} virtual QJsonObject toJson() const; virtual void updateData(SyncRoomData&& data, bool fromCache = false); + virtual Notification checkForNotifications(const TimelineItem& ti); private: friend class Connection; -- cgit v1.2.3 From 96f3daf7a2c4ec875904c11350c93612265e2eed Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 21 Nov 2021 06:03:49 +0100 Subject: SyncData: support MSC2654; partiallyReadCount Since MSC2654's unread count is counted from the m.read receipt, and the course is to follow the spec's terminology and use "unread count" for the number of notable events since m.read, this required to move the existing number of notable events since m.fully_read to another field, henceforth called partiallyReadCount. At the same time, SyncData::notificationCount is dropped completely since MSC2654 claims to supersede it. Also: Room::resetNotificationCount() and Room::resetHighlightCount() are deprecated, as these never worked properly overwriting values that can be calculated or sourced from the server, only for these values to be set back again the next time the room is updated from /sync. --- lib/room.cpp | 33 ++++++++++++++++----------------- lib/room.h | 24 ++++++++++++++++++------ lib/syncdata.cpp | 44 ++++++++++++++++++++++---------------------- lib/syncdata.h | 15 +++++++++------ 4 files changed, 65 insertions(+), 51 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 67f65472..bc686962 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -117,7 +117,7 @@ public: QString displayname; Avatar avatar; QHash notifications; - int highlightCount = 0; + qsizetype serverHighlightCount = 0; int notificationCount = 0; members_map_t membersMap; QList usersTyping; @@ -1072,13 +1072,13 @@ void Room::resetNotificationCount() emit notificationCountChanged(); } -int Room::highlightCount() const { return d->highlightCount; } +qsizetype Room::highlightCount() const { return d->serverHighlightCount; } void Room::resetHighlightCount() { - if (d->highlightCount == 0) + if (d->serverHighlightCount == 0) return; - d->highlightCount = 0; + d->serverHighlightCount = 0; emit highlightCountChanged(); } @@ -1673,16 +1673,16 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) roomChanges |= processEphemeralEvent(move(ephemeralEvent)); // See https://github.com/quotient-im/libQuotient/wiki/unread_count - if (merge(d->unreadMessages, data.unreadCount)) { - qCDebug(MESSAGES) << "Loaded unread_count:" << *data.unreadCount // - << "in" << objectName(); + if (merge(d->unreadMessages, data.partiallyReadCount)) { + qCDebug(MESSAGES) << "Loaded partially read count:" + << *data.partiallyReadCount << "in" << objectName(); emit unreadMessagesChanged(this); } - if (merge(d->highlightCount, data.highlightCount)) + if (merge(d->serverHighlightCount, data.highlightCount)) emit highlightCountChanged(); - if (merge(d->notificationCount, data.notificationCount)) + if (merge(d->notificationCount, data.unreadCount)) emit notificationCountChanged(); if (roomChanges) { @@ -3077,16 +3077,15 @@ QJsonObject Room::Private::toJson() const .fullJson() } } }); } - QJsonObject unreadNotifObj { { SyncRoomData::UnreadCountKey, - unreadMessages } }; + QJsonObject unreadNotifObj { { PartiallyReadCountKey, unreadMessages } }; - if (highlightCount > 0) - unreadNotifObj.insert(QStringLiteral("highlight_count"), highlightCount); - if (notificationCount > 0) - unreadNotifObj.insert(QStringLiteral("notification_count"), - notificationCount); + if (serverHighlightCount > 0) + unreadNotifObj.insert(HighlightCountKey, serverHighlightCount); + + result.insert(UnreadNotificationsKey, unreadNotifObj); - result.insert(QStringLiteral("unread_notifications"), unreadNotifObj); + if (notificationCount > 0) + unreadNotifObj.insert(NewUnreadCountKey, notificationCount); if (et.elapsed() > 30) qCDebug(PROFILER) << "Room::toJson() for" << q->objectName() << "took" diff --git a/lib/room.h b/lib/room.h index 124c8cb4..a75311fb 100644 --- a/lib/room.h +++ b/lib/room.h @@ -149,10 +149,10 @@ class Room : public QObject { Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY unreadMessagesChanged STORED false) 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(qsizetype highlightCount READ highlightCount + NOTIFY highlightCountChanged) + Q_PROPERTY(qsizetype notificationCount READ notificationCount + NOTIFY notificationCountChanged) Q_PROPERTY(bool allHistoryLoaded READ allHistoryLoaded NOTIFY addedMessages STORED false) Q_PROPERTY(QStringList tagNames READ tagNames NOTIFY tagsChanged) @@ -538,9 +538,21 @@ public: */ int unreadCount() const; - Q_INVOKABLE int notificationCount() const; + //! \brief Get the number of notifications since the last read receipt + //! + //! \sa lastLocalReadReceipt + qsizetype notificationCount() const; + + //! \deprecated Use setReadReceipt() to drive changes in notification count Q_INVOKABLE void resetNotificationCount(); - Q_INVOKABLE int highlightCount() const; + + //! \brief Get the number of highlights since the last read receipt + //! + //! As of 0.7, this is defined by the homeserver as Quotient doesn't process + //! push rules. + qsizetype highlightCount() const; + + //! \deprecated Use setReadReceipt() to drive changes in highlightCount Q_INVOKABLE void resetHighlightCount(); /** Check whether the room has account data of the given type diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index e86d3100..396e77eb 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -10,9 +10,6 @@ using namespace Quotient; -const QString SyncRoomData::UnreadCountKey = - QStringLiteral("x-quotient.unread_count"); - bool RoomSummary::isEmpty() const { return !joinedMemberCount && !invitedMemberCount && !heroes; @@ -64,23 +61,23 @@ inline EventsArrayT load(const QJsonObject& batches, StrT keyName) return fromJson(batches[keyName].toObject().value("events"_ls)); } -SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_, - const QJsonObject& room_) - : roomId(roomId_) - , joinState(joinState_) - , summary(fromJson(room_["summary"_ls])) - , state(load(room_, joinState == JoinState::Invite +SyncRoomData::SyncRoomData(QString roomId_, JoinState joinState, + const QJsonObject& roomJson) + : roomId(std::move(roomId_)) + , joinState(joinState) + , summary(fromJson(roomJson["summary"_ls])) + , state(load(roomJson, joinState == JoinState::Invite ? "invite_state"_ls : "state"_ls)) { switch (joinState) { case JoinState::Join: - ephemeral = load(room_, "ephemeral"_ls); + ephemeral = load(roomJson, "ephemeral"_ls); [[fallthrough]]; case JoinState::Leave: { - accountData = load(room_, "account_data"_ls); - timeline = load(room_, "timeline"_ls); - const auto timelineJson = room_.value("timeline"_ls).toObject(); + accountData = load(roomJson, "account_data"_ls); + timeline = load(roomJson, "timeline"_ls); + const auto timelineJson = roomJson.value("timeline"_ls).toObject(); timelineLimited = timelineJson.value("limited"_ls).toBool(); timelinePrevBatch = timelineJson.value("prev_batch"_ls).toString(); @@ -89,14 +86,17 @@ SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_, default: /* nothing on top of state */; } - const auto unreadJson = room_.value("unread_notifications"_ls).toObject(); - fromJson(unreadJson.value(UnreadCountKey), unreadCount); - fromJson(unreadJson.value("highlight_count"_ls), highlightCount); - fromJson(unreadJson.value("notification_count"_ls), notificationCount); - if (highlightCount.has_value() || notificationCount.has_value()) - qCDebug(SYNCJOB) << "Room" << roomId_ - << "has highlights:" << *highlightCount - << "and notifications:" << *notificationCount; + const auto unreadJson = roomJson.value(UnreadNotificationsKey).toObject(); + + fromJson(unreadJson.value(PartiallyReadCountKey), partiallyReadCount); + if (!partiallyReadCount.has_value()) + fromJson(unreadJson.value("x-quotient.unread_count"_ls), + partiallyReadCount); + + fromJson(roomJson.value(NewUnreadCountKey), unreadCount); + if (!unreadCount.has_value()) + fromJson(unreadJson.value("notification_count"_ls), unreadCount); + fromJson(unreadJson.value(HighlightCountKey), highlightCount); } SyncData::SyncData(const QString& cacheFileName) @@ -130,7 +130,7 @@ Events&& SyncData::takeToDeviceEvents() { return std::move(toDeviceEvents); } std::pair SyncData::cacheVersion() { - return { MajorCacheVersion, 1 }; + return { MajorCacheVersion, 2 }; } QJsonObject SyncData::loadJson(const QString& fileName) diff --git a/lib/syncdata.h b/lib/syncdata.h index b869a541..36d2e0bf 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -8,6 +8,12 @@ #include "events/stateevent.h" namespace Quotient { + +constexpr auto UnreadNotificationsKey = "unread_notifications"_ls; +constexpr auto PartiallyReadCountKey = "x-quotient.since_fully_read_count"_ls; +constexpr auto NewUnreadCountKey = "org.matrix.msc2654.unread_count"_ls; +constexpr auto HighlightCountKey = "highlight_count"_ls; + /// Room summary, as defined in MSC688 /** * Every member of this structure is an Omittable; as per the MSC, only @@ -29,7 +35,6 @@ struct RoomSummary { }; QDebug operator<<(QDebug dbg, const RoomSummary& rs); - template <> struct JsonObjectConverter { static void dumpTo(QJsonObject& jo, const RoomSummary& rs); @@ -48,16 +53,14 @@ public: bool timelineLimited; QString timelinePrevBatch; + Omittable partiallyReadCount; Omittable unreadCount; Omittable highlightCount; - Omittable notificationCount; - SyncRoomData(const QString& roomId, JoinState joinState_, - const QJsonObject& room_); + SyncRoomData(QString roomId, JoinState joinState, + const QJsonObject& roomJson); SyncRoomData(SyncRoomData&&) = default; SyncRoomData& operator=(SyncRoomData&&) = default; - - static const QString UnreadCountKey; }; // QVector cannot work with non-copyable objects, std::vector can. -- cgit v1.2.3 From e7babe6715672a358f7cc8b90d5df27e21a1b3e8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 21 Nov 2021 07:03:48 +0100 Subject: Room::Private::postprocessChanges() This makes updating display name and emission of necessary signals including Room::changed() more systematic when it has to occur outside of updateData() flow - e.g. when loading all members. --- lib/room.cpp | 85 +++++++++++++++++++++++++++++++++++++----------------------- lib/room.h | 2 +- 2 files changed, 54 insertions(+), 33 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index bc686962..1df5dc71 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -267,6 +267,8 @@ public: Changes addNewMessageEvents(RoomEvents&& events); void addHistoricalMessageEvents(RoomEvents&& events); + void postprocessChanges(Changes changes, bool saveState = true); + /** Move events into the timeline * * Insert events into the timeline, either new or historical. @@ -793,28 +795,36 @@ void Room::setReadReceipt(const QString& atEventId) QUrl::toPercentEncoding(atEventId)); } -void Room::Private::markMessagesAsRead(const rev_iter_t &upToMarker) +bool Room::Private::markMessagesAsRead(const rev_iter_t &upToMarker) { - if (upToMarker < q->fullyReadMarker()) { - setFullyReadMarker((*upToMarker)->id()); - // Assuming that if a read receipt was sent on a newer event, it will - // stay there instead of "un-reading" notifications/mentions from - // m.fully_read to m.read + if (setFullyReadMarker(upToMarker->event()->id())) { + // The assumption below is that if a read receipt was sent on a newer + // event, the homeserver will keep it there instead of reverting to + // m.fully_read connection->callApi(BackgroundRequest, id, fullyReadUntilEventId, fullyReadUntilEventId); + return true; } + if (upToMarker != q->historyEdge()) + qCDebug(MESSAGES) << "Event" << *upToMarker << "in" << q->objectName() + << "is behind the current fully read marker at" + << *q->fullyReadMarker() + << "- won't move fully read marker back in timeline"; + else + qCWarning(MESSAGES) << "Cannot mark an unknown event in" + << q->objectName() << "as fully read"; + return false; } -void Room::markMessagesAsRead(QString uptoEventId) +void Room::markMessagesAsRead(const QString& uptoEventId) { d->markMessagesAsRead(findInTimeline(uptoEventId)); } void Room::markAllMessagesAsRead() { - if (!d->timeline.empty()) - d->markMessagesAsRead(d->timeline.crbegin()); + d->markMessagesAsRead(d->timeline.crbegin()); } bool Room::canSwitchVersions() const @@ -941,8 +951,7 @@ void Room::Private::getAllMembers() it != syncEdge(); ++it) if (is(**it)) roomChanges |= q->processStateEvent(**it); - if (roomChanges & Change::Members) - emit q->memberListChanged(); + postprocessChanges(roomChanges); emit q->allMembersLoaded(); }); } @@ -1459,7 +1468,6 @@ Room::Changes Room::Private::setSummary(RoomSummary&& newSummary) return Change::None; qCDebug(STATE).nospace().noquote() << "Updated room summary for " << q->objectName() << ": " << summary; - emit q->memberListChanged(); return Change::Summary; } @@ -1651,27 +1659,23 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) setJoinState(data.joinState); Changes roomChanges {}; - for (auto&& event : data.accountData) - roomChanges |= processAccountDataEvent(move(event)); - + // The order of calculation is important - don't merge the lines! roomChanges |= d->updateStateFrom(data.state); - // The order of calculation is important - don't merge these lines! + roomChanges |= d->setSummary(move(data.summary)); roomChanges |= d->addNewMessageEvents(move(data.timeline)); + for (auto&& ephemeralEvent : data.ephemeral) + roomChanges |= processEphemeralEvent(move(ephemeralEvent)); + + for (auto&& event : data.accountData) + roomChanges |= processAccountDataEvent(move(event)); + if (roomChanges & Change::Topic) emit topicChanged(); if (roomChanges & (Change::Name | Change::Aliases)) emit namesChanged(this); - if (roomChanges & Change::Members) - emit memberListChanged(); - - roomChanges |= d->setSummary(move(data.summary)); - - for (auto&& ephemeralEvent : data.ephemeral) - roomChanges |= processEphemeralEvent(move(ephemeralEvent)); - // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (merge(d->unreadMessages, data.partiallyReadCount)) { qCDebug(MESSAGES) << "Loaded partially read count:" @@ -1685,12 +1689,25 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) if (merge(d->notificationCount, data.unreadCount)) emit notificationCountChanged(); - if (roomChanges) { - d->updateDisplayname(); - emit changed(roomChanges); - if (!fromCache) - connection()->saveRoomState(this); - } + d->postprocessChanges(roomChanges, !fromCache); +} + +void Room::Private::postprocessChanges(Changes changes, bool saveState) +{ + if (!changes) + return; + + if (changes & Change::Members) + emit q->memberListChanged(); + + if (changes + & (Change::Name | Change::Aliases | Change::Members | Change::Summary)) + updateDisplayname(); + + qCDebug(MAIN) << terse << changes << "in" << q->objectName(); + emit q->changed(changes); + if (saveState) + connection->saveRoomState(q); } RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event) @@ -2566,6 +2583,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) if (events.empty()) return; + Changes changes {}; // In case of lazy-loading new members may be loaded with historical // messages. Also, the cache doesn't store events with empty content; // so when such events show up in the timeline they should be properly @@ -2574,7 +2592,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) const auto& e = *eptr; if (e.isStateEvent() && !currentState.contains({ e.matrixType(), e.stateKey() })) { - q->processStateEvent(e); + changes |= q->processStateEvent(e); } } @@ -2594,7 +2612,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) emit q->updatedEvent(relation.eventId); } } - updateUnreadCount(from, historyEdge()); + changes |= updateUnreadCount(from, historyEdge()); // When there are no unread messages and the read marker is within the // known timeline, unreadMessages == -1 // (see https://github.com/quotient-im/libQuotient/wiki/Developer-notes#2-saving-unread-event-counts). @@ -2604,6 +2622,9 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) if (insertedSize > 9 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) << "Added" << insertedSize << "historical event(s) to" << q->objectName() << "in" << et; + + if (changes) + postprocessChanges(changes); } Room::Changes Room::processStateEvent(const RoomEvent& e) diff --git a/lib/room.h b/lib/room.h index a75311fb..c228f6c9 100644 --- a/lib/room.h +++ b/lib/room.h @@ -492,7 +492,7 @@ public: //! the current m.fully_read marker or is not loaded, to prevent //! accidentally trying to move the marker back in the timeline. //! \sa markAllMessagesAsRead, fullyReadMarker - Q_INVOKABLE void markMessagesAsRead(QString uptoEventId); + Q_INVOKABLE void markMessagesAsRead(const QString& uptoEventId); //! \brief Determine whether an event should be counted as unread //! -- cgit v1.2.3 From b2f9b212c78bc9dd7c69f6a2d1f94376adb488e3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 21 Nov 2021 06:55:16 +0100 Subject: EventStats and Room::partiallyRead/unreadStats() This introduces a new API to count unread events that would allow to obtain those unread and highlight counts since either the fully read marker (Room::partiallyReadStats) or the last read receipt (Room::unreadStats). Element uses the read receipt as the anchor to count unread numbers, while Quaternion historically used the fully read marker for that (with the pre-0.7 library sticking the two markers to each other). From now on the meaning of "unread" in Quotient is aligned with that of the spec and Element, and "partially read" means events between the fully read marker and the local read receipt; the design allows client authors to use either or both counting strategies as they see fit. Respectively, Room::P::setFullyReadMarker() updates partially-read statistics, while Room::P::setLastReadReceipt(), when called on a local user, updates unread statistics. Room::notificationCount() and Room::highlightCount() maintain their previous meaning as the counters since the last read receipt; Room::notificationCount() counts unread events locally, falling back to the value from the above-mentioned key defined by MSC2654, and if that is not there, further to `unread_notifications/notification_count` defined in the current spec. Room::highlightCount(), however, is still taken from the homeserver, not from Room::unreadStats().highlightCount. --- CMakeLists.txt | 1 + lib/eventstats.cpp | 98 ++++++++++++++ lib/eventstats.h | 107 +++++++++++++++ lib/room.cpp | 386 ++++++++++++++++++++++++++++++++++------------------- lib/room.h | 116 ++++++++++++---- 5 files changed, 544 insertions(+), 164 deletions(-) create mode 100644 lib/eventstats.cpp create mode 100644 lib/eventstats.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3814bc7e..eaf662cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ list(APPEND lib_SRCS lib/avatar.cpp lib/uri.cpp lib/uriresolver.cpp + lib/eventstats.cpp lib/syncdata.cpp lib/settings.cpp lib/networksettings.cpp diff --git a/lib/eventstats.cpp b/lib/eventstats.cpp new file mode 100644 index 00000000..9fa7f5ff --- /dev/null +++ b/lib/eventstats.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2021 Quotient contributors +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "eventstats.h" + +using namespace Quotient; + +EventStats EventStats::fromRange(const Room* room, const Room::rev_iter_t& from, + const Room::rev_iter_t& to, + const EventStats& init) +{ + Q_ASSERT(to <= room->historyEdge()); + Q_ASSERT(from >= Room::rev_iter_t(room->syncEdge())); + Q_ASSERT(from <= to); + QElapsedTimer et; + et.start(); + const auto result = + accumulate(from, to, init, + [room](EventStats acc, const TimelineItem& ti) { + acc.notableCount += room->isEventNotable(ti); + acc.highlightCount += room->notificationFor(ti).type + == Notification::Highlight; + return acc; + }); + if (et.nsecsElapsed() > profilerMinNsecs() / 10) + qCDebug(PROFILER).nospace() + << "Event statistics collection over index range [" << from->index() + << "," << (to - 1)->index() << "] took " << et; + return result; +} + +EventStats EventStats::fromMarker(const Room* room, + const EventStats::marker_t& marker) +{ + const auto s = fromRange(room, marker_t(room->syncEdge()), marker, + { 0, 0, marker == room->historyEdge() }); + Q_ASSERT(s.isValidFor(room, marker)); + return s; +} + +EventStats EventStats::fromCachedCounters(Omittable notableCount, + Omittable highlightCount) +{ + const auto hCount = std::max(0, highlightCount.value_or(0)); + if (!notableCount.has_value()) + return { 0, hCount, true }; + auto nCount = notableCount.value_or(0); + return { std::max(0, nCount), hCount, nCount != -1 }; +} + +bool EventStats::updateOnMarkerMove(const Room* room, const marker_t& oldMarker, + const marker_t& newMarker) +{ + if (newMarker == oldMarker) + return false; + + // Double-check consistency between the old marker and the old stats + Q_ASSERT(isValidFor(room, oldMarker)); + Q_ASSERT(oldMarker > newMarker); + + // A bit of optimisation: only calculate the difference if the marker moved + // less than half the remaining timeline ahead; otherwise, recalculation + // over the remaining timeline will very likely be faster. + if (oldMarker != room->historyEdge() + && oldMarker - newMarker < newMarker - marker_t(room->syncEdge())) { + const auto removedStats = fromRange(room, newMarker, oldMarker); + Q_ASSERT(notableCount >= removedStats.notableCount + && highlightCount >= removedStats.highlightCount); + notableCount -= removedStats.notableCount; + highlightCount -= removedStats.highlightCount; + return removedStats.notableCount > 0 || removedStats.highlightCount > 0; + } + + const auto newStats = EventStats::fromMarker(room, newMarker); + if (!isEstimate && newStats == *this) + return false; + *this = newStats; + return true; +} + +bool EventStats::isValidFor(const Room* room, const marker_t& marker) const +{ + const auto markerAtHistoryEdge = marker == room->historyEdge(); + // Either markerAtHistoryEdge and isEstimate are in the same state, or it's + // a special case of no notable events and the marker at history edge + // (then isEstimate can assume any value). + return markerAtHistoryEdge == isEstimate + || (markerAtHistoryEdge && notableCount == 0); +} + +QDebug Quotient::operator<<(QDebug dbg, const EventStats& es) +{ + QDebugStateSaver _(dbg); + dbg.nospace() << es.notableCount << '/' << es.highlightCount; + if (es.isEstimate) + dbg << " (estimated)"; + return dbg; +} diff --git a/lib/eventstats.h b/lib/eventstats.h new file mode 100644 index 00000000..9be83377 --- /dev/null +++ b/lib/eventstats.h @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2021 Quotient contributors +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "room.h" + +namespace Quotient { + +//! \brief Counters of unread events and highlights with a precision flag +//! +//! This structure contains a static snapshot with values of unread counters +//! returned by Room::partiallyReadStats and Room::unreadStats (properties +//! or methods). +//! +//! \note It's just a simple grouping of counters and is not automatically +//! updated from the room as subsequent syncs arrive. +//! \sa Room::unreadStats, Room::partiallyReadStats, Room::isEventNotable +struct EventStats { + Q_GADGET + Q_PROPERTY(qsizetype notableCount MEMBER notableCount CONSTANT) + Q_PROPERTY(qsizetype highlightCount MEMBER highlightCount CONSTANT) + Q_PROPERTY(bool isEstimate MEMBER isEstimate CONSTANT) +public: + //! The number of "notable" events in an events range + //! \sa Room::isEventNotable + qsizetype notableCount = 0; + qsizetype highlightCount = 0; + //! \brief Whether the counter values above are exact + //! + //! This is false when the end marker (m.read receipt or m.fully_read) used + //! to collect the stats points to an event loaded locally and the counters + //! can therefore be calculated exactly using the locally available segment + //! of the timeline; true when the marker points to an event outside of + //! the local timeline (in which case the estimation is made basing on + //! the data supplied by the homeserver as well as counters saved from + //! the previous run of the client). + bool isEstimate = true; + + bool operator==(const EventStats& rhs) const& = default; + + //! \brief Check whether the event statistics are empty + //! + //! Empty statistics have notable and highlight counters of zero and + //! isEstimate set to false. + Q_INVOKABLE bool empty() const + { + return notableCount == 0 && !isEstimate && highlightCount == 0; + } + + using marker_t = Room::rev_iter_t; + + //! \brief Build event statistics on a range of events + //! + //! This is a factory that returns an EventStats instance with counts of + //! notable and highlighted events between \p from and \p to reverse + //! timeline iterators; the \p init parameter allows to override + //! the initial statistics object and start from other values. + static EventStats fromRange(const Room* room, const marker_t& from, + const marker_t& to, + const EventStats& init = { 0, 0, false }); + + //! \brief Build event statistics on a range from sync edge to marker + //! + //! This is mainly a shortcut for \code + //! fromRange(room, marker_t(room->syncEdge()), marker) + //! \endcode except that it also sets isEstimate to true if (and only if) + //! to == room->historyEdge(). + static EventStats fromMarker(const Room* room, const marker_t& marker); + + //! \brief Loads a statistics object from the cached counters + //! + //! Sets isEstimate to `true` unless both notableCount and highlightCount + //! are equal to -1. + static EventStats fromCachedCounters(Omittable notableCount, + Omittable highlightCount = none); + + //! \brief Update statistics when a read marker moves down the timeline + //! + //! Removes events between oldMarker and newMarker from statistics + //! calculation if \p oldMarker points to an existing event in the timeline, + //! or recalculates the statistics entirely if \p oldMarker points + //! to room->historyEdge(). Always results in exact statistics + //! (isEstimate == false. + //! \param oldMarker Must point correspond to the _current_ statistics + //! isEstimate state, i.e. it should point to + //! room->historyEdge() if isEstimate == true, or + //! to a valid position within the timeline otherwise + //! \param newMarker Must point to a valid position in the timeline (not to + //! room->historyEdge() that is equal to or closer to + //! the sync edge than \p oldMarker + //! \return true if either notableCount or highlightCount changed, or if + //! the statistics was completely recalculated; false otherwise + bool updateOnMarkerMove(const Room* room, const marker_t& oldMarker, + const marker_t& newMarker); + + //! \brief Validate the statistics object against the given marker + //! + //! Checks whether the statistics object data are valid for a given marker. + //! No stats recalculation takes place, only isEstimate and zero-ness + //! of notableCount are checked. + bool isValidFor(const Room* room, const marker_t& marker) const; +}; + +QDebug operator<<(QDebug dbg, const EventStats& es); + +} diff --git a/lib/room.cpp b/lib/room.cpp index 1df5dc71..8bad9084 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -15,6 +15,7 @@ #include "e2ee.h" #include "syncdata.h" #include "user.h" +#include "eventstats.h" // NB: since Qt 6, moc_room.cpp needs User fully defined #include "moc_room.cpp" @@ -118,13 +119,14 @@ public: Avatar avatar; QHash notifications; qsizetype serverHighlightCount = 0; - int notificationCount = 0; + // Starting up with estimate event statistics as there's zero knowledge + // about the timeline. + EventStats partiallyReadStats {}, unreadStats {}; members_map_t membersMap; QList usersTyping; QHash> eventIdReadUsers; QList usersInvited; QList membersLeft; - int unreadMessages = 0; bool displayed = false; QString firstDisplayedEventId; QString lastDisplayedEventId; @@ -267,6 +269,7 @@ public: Changes addNewMessageEvents(RoomEvents&& events); void addHistoricalMessageEvents(RoomEvents&& events); + Changes updateStatsFromSyncData(const SyncRoomData &data, bool fromCache); void postprocessChanges(Changes changes, bool saveState = true); /** Move events into the timeline @@ -286,12 +289,12 @@ public: */ void dropDuplicateEvents(RoomEvents& events) const; - bool setLastReadReceipt(const QString& userId, rev_iter_t newMarker, - ReadReceipt newReceipt = {}); + Changes setLastReadReceipt(const QString& userId, rev_iter_t newMarker, + ReadReceipt newReceipt = {}, + bool deferStatsUpdate = false); Changes setFullyReadMarker(const QString &eventId); - Changes updateUnreadCount(const rev_iter_t& from, const rev_iter_t& to); - Changes recalculateUnreadCount(bool force = false); - void markMessagesAsRead(const rev_iter_t &upToMarker); + Changes updateStats(const rev_iter_t& from, const rev_iter_t& to); + bool markMessagesAsRead(const rev_iter_t& upToMarker); void getAllMembers(); @@ -613,9 +616,10 @@ void Room::setJoinState(JoinState state) emit joinStateChanged(oldState, state); } -bool Room::Private::setLastReadReceipt(const QString& userId, - rev_iter_t newMarker, - ReadReceipt newReceipt) +Room::Changes Room::Private::setLastReadReceipt(const QString& userId, + rev_iter_t newMarker, + ReadReceipt newReceipt, + bool deferStatsUpdate) { if (newMarker == historyEdge() && !newReceipt.eventId.isEmpty()) newMarker = q->findInTimeline(newReceipt.eventId); @@ -643,10 +647,11 @@ bool Room::Private::setLastReadReceipt(const QString& userId, const auto prevEventId = storedReceipt.eventId; // NB: with reverse iterators, timeline history >= sync edge if (newMarker >= q->findInTimeline(prevEventId)) - return false; + return Change::None; // Finally make the change + Changes changes = Change::Other; auto oldEventReadUsersIt = eventIdReadUsers.find(prevEventId); // clazy:exclude=detaching-member if (oldEventReadUsersIt != eventIdReadUsers.end()) { @@ -663,21 +668,43 @@ bool Room::Private::setLastReadReceipt(const QString& userId, // for actual members, not just any user const auto member = q->user(userId); Q_ASSERT(member != nullptr); + if (isLocalUser(member) && !deferStatsUpdate) { + if (unreadStats.updateOnMarkerMove(q, q->findInTimeline(prevEventId), + newMarker)) { + qCDebug(MESSAGES) + << "Updated unread event statistics in" << q->objectName() + << "after moving the local read receipt:" << unreadStats; + changes |= Change::UnreadStats; + } + Q_ASSERT(unreadStats.isValidFor(q, newMarker)); // post-check + } emit q->lastReadEventChanged(member); // TODO: remove in 0.8 if (!isLocalUser(member)) emit q->readMarkerForUserMoved(member, prevEventId, storedReceipt.eventId); - return true; + return changes; } -Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, - const rev_iter_t& to) +Room::Changes Room::Private::updateStats(const rev_iter_t& from, + const rev_iter_t& to) { Q_ASSERT(from >= timeline.crbegin() && from <= timeline.crend()); Q_ASSERT(to >= from && to <= timeline.crend()); - auto fullyReadMarker = q->fullyReadMarker(); + const auto fullyReadMarker = q->fullyReadMarker(); + auto readReceiptMarker = q->localReadReceiptMarker(); + Changes changes = Change::None; + // Correct the read receipt to never be behind the fully read marker + if (readReceiptMarker > fullyReadMarker + && setLastReadReceipt(connection->userId(), fullyReadMarker, {}, true)) { + changes |= Change::Other; + readReceiptMarker = q->localReadReceiptMarker(); + qCInfo(MESSAGES) << "The local m.read receipt was behind m.fully_read " + "marker - it's now corrected to be at index" + << readReceiptMarker->index(); + } + if (fullyReadMarker < from) return Change::None; // What's arrived is already fully read @@ -685,79 +712,70 @@ Room::Changes Room::Private::updateUnreadCount(const rev_iter_t& from, if (fullyReadMarker == historyEdge() && q->allHistoryLoaded()) return setFullyReadMarker(timeline.front()->id()); - // Catch a special case when the last fully read event id refers to an - // event that has just arrived. In this case we should recalculate - // unreadMessages to get an exact number instead of an estimation - // (see https://github.com/quotient-im/libQuotient/wiki/unread_count). - // For the same reason (switching from the estimation to the exact - // number) this branch always emits unreadMessagesChanged() and returns - // UnreadNotifsChange, even if the estimation luckily matched the exact - // result. - if (fullyReadMarker < to) - return recalculateUnreadCount(true); - - // At this point the fully read marker is somewhere beyond the "oldest" - // message from the arrived batch - add up newly arrived messages to - // the current counter, instead of a complete recalculation. - Q_ASSERT(to <= fullyReadMarker); + // Catch a case when the id in the last fully read marker or the local read + // receipt refers to an event that has just arrived. In this case either + // one (unreadStats) or both statistics should be recalculated to get + // an exact number instead of an estimation (see documentation on + // EventStats::isEstimate). For the same reason (switching from the + // estimate to the exact number) this branch forces returning + // Change::UnreadStats and also possibly Change::PartiallyReadStats, even if + // the estimation luckily matched the exact result. + if (readReceiptMarker < to || changes /*i.e. read receipt was corrected*/) { + unreadStats = EventStats::fromMarker(q, readReceiptMarker); + Q_ASSERT(!unreadStats.isEstimate); + qCDebug(MESSAGES).nospace() << "Recalculated unread event statistics in" + << q->objectName() << ": " << unreadStats; + changes |= Change::UnreadStats; + if (fullyReadMarker < to) { + // Add up to unreadStats instead of counting same events again + partiallyReadStats = EventStats::fromRange(q, readReceiptMarker, + q->fullyReadMarker(), + unreadStats); + Q_ASSERT(!partiallyReadStats.isEstimate); + + qCDebug(MESSAGES).nospace() + << "Recalculated partially read event statistics in " + << q->objectName() << ": " << partiallyReadStats; + return Change::PartiallyReadStats | Change::UnreadStats; + } + } - QElapsedTimer et; - et.start(); - const auto newUnreadMessages = - count_if(from, to, - std::bind(&Room::isEventNotable, q, _1)); - if (et.nsecsElapsed() > profilerMinNsecs() / 10) - qCDebug(PROFILER) << "Counting gained unread messages in" - << q->objectName() << "took" << et; - - if (newUnreadMessages == 0) - return Change::None; + // As of here, at least the fully read marker (but maybe also read receipt) + // points to somewhere beyond the "oldest" message from the arrived batch - + // add up newly arrived messages to the current stats, instead of a complete + // recalculation. + Q_ASSERT(fullyReadMarker >= to); - // See https://github.com/quotient-im/libQuotient/wiki/unread_count - if (unreadMessages < 0) - unreadMessages = 0; + const auto newStats = EventStats::fromRange(q, from, to); + Q_ASSERT(!newStats.isEstimate); + if (newStats.notableCount == 0 || newStats.highlightCount == 0) + return changes; - unreadMessages += newUnreadMessages; - qCDebug(MESSAGES) << "Room" << q->objectName() << "has gained" - << newUnreadMessages << "unread message(s)," - << (q->fullyReadMarker() == timeline.crend() - ? "in total at least" - : "in total") - << unreadMessages << "unread message(s)"; - emit q->unreadMessagesChanged(q); - return Change::UnreadNotifs; -} + const auto doAddStats = [this, &changes, newStats](EventStats& s, + const rev_iter_t& marker, + Change c) { + s.notableCount += newStats.notableCount; + s.highlightCount += newStats.highlightCount; + if (!s.isEstimate) + s.isEstimate = marker == historyEdge(); + changes |= c; + }; -Room::Changes Room::Private::recalculateUnreadCount(bool force) -{ - // The recalculation logic assumes that the fully read marker points at - // a specific position in the timeline - Q_ASSERT(q->fullyReadMarker() != timeline.crend()); - const auto oldUnreadCount = unreadMessages; - QElapsedTimer et; - et.start(); - unreadMessages = - int(count_if(timeline.crbegin(), q->fullyReadMarker(), - [this](const auto& ti) { return q->isEventNotable(ti); })); - if (et.nsecsElapsed() > profilerMinNsecs() / 10) - qCDebug(PROFILER) << "Recounting unread messages in" << q->objectName() - << "took" << et; - - // See https://github.com/quotient-im/libQuotient/wiki/unread_count - if (unreadMessages == 0) - unreadMessages = -1; - - if (!force && unreadMessages == oldUnreadCount) - return Change::None; + doAddStats(partiallyReadStats, fullyReadMarker, Change::PartiallyReadStats); + if (readReceiptMarker >= to) { + // readReceiptMarker < to branch shouldn't have been entered + Q_ASSERT(!changes.testFlag(Change::UnreadStats)); + doAddStats(unreadStats, readReceiptMarker, Change::UnreadStats); + } + qCDebug(MESSAGES) << "Room" << q->objectName() << "has gained" << newStats + << "notable/highlighted event(s); total statistics:" + << partiallyReadStats << "since the fully read marker," + << unreadStats << "since read receipt"; - if (unreadMessages == -1) - qCDebug(MESSAGES) - << "Room" << displayname << "has no more unread messages"; - else - qCDebug(MESSAGES) << "Room" << displayname << "still has" - << unreadMessages << "unread message(s)"; - emit q->unreadMessagesChanged(q); - return Change::UnreadNotifs; + // Check invariants + Q_ASSERT(partiallyReadStats.isValidFor(q, fullyReadMarker)); + Q_ASSERT(unreadStats.isValidFor(q, readReceiptMarker)); + return changes; } Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) @@ -765,45 +783,59 @@ Room::Changes Room::Private::setFullyReadMarker(const QString& eventId) if (fullyReadUntilEventId == eventId) return Change::None; + const auto prevReadMarker = q->fullyReadMarker(); + const auto newReadMarker = q->findInTimeline(eventId); + if (newReadMarker > prevReadMarker) + return Change::None; + const auto prevFullyReadId = std::exchange(fullyReadUntilEventId, eventId); qCDebug(MESSAGES) << "Fully read marker in" << q->objectName() // << "set to" << fullyReadUntilEventId; - emit q->fullyReadMarkerMoved(prevFullyReadId, fullyReadUntilEventId); - // TODO: Remove in 0.8 - emit q->readMarkerMoved(prevFullyReadId, fullyReadUntilEventId); - QT_IGNORE_DEPRECATIONS(Changes changes = Change::ReadMarker;) + QT_IGNORE_DEPRECATIONS(Changes changes = Change::ReadMarker|Change::Other;) if (const auto rm = q->fullyReadMarker(); rm != historyEdge()) { - // Pull read receipt if it's behind - setLastReadReceipt(connection->userId(), rm); - changes |= recalculateUnreadCount(); // TODO: updateUnreadCount()? + // Pull read receipt if it's behind, and update statistics + changes |= setLastReadReceipt(connection->userId(), rm); + if (partiallyReadStats.updateOnMarkerMove(q, prevReadMarker, rm)) { + changes |= Change::PartiallyReadStats; + qCDebug(MESSAGES) + << "Updated partially read event statistics in" + << q->objectName() + << "after moving m.fully_read marker: " << partiallyReadStats; + } + Q_ASSERT(partiallyReadStats.isValidFor(q, rm)); // post-check } + emit q->fullyReadMarkerMoved(prevFullyReadId, fullyReadUntilEventId); + // TODO: Remove in 0.8 + emit q->readMarkerMoved(prevFullyReadId, fullyReadUntilEventId); return changes; } void Room::setReadReceipt(const QString& atEventId) { - if (!d->setLastReadReceipt(localUser()->id(), historyEdge(), - { atEventId, QDateTime::currentDateTime() })) { + if (const auto changes = d->setLastReadReceipt(localUser()->id(), + historyEdge(), + { atEventId })) { + connection()->callApi(BackgroundRequest, id(), + QStringLiteral("m.read"), + QUrl::toPercentEncoding(atEventId)); + d->postprocessChanges(changes); + } else qCDebug(EPHEMERAL) << "The new read receipt for" << localUser()->id() << "in" << objectName() << "is at or behind the old one, skipping"; - return; - } - connection()->callApi(BackgroundRequest, id(), - QStringLiteral("m.read"), - QUrl::toPercentEncoding(atEventId)); } bool Room::Private::markMessagesAsRead(const rev_iter_t &upToMarker) { - if (setFullyReadMarker(upToMarker->event()->id())) { + if (const auto changes = setFullyReadMarker(upToMarker->event()->id())) { // The assumption below is that if a read receipt was sent on a newer // event, the homeserver will keep it there instead of reverting to // m.fully_read connection->callApi(BackgroundRequest, id, fullyReadUntilEventId, fullyReadUntilEventId); + postprocessChanges(changes); return true; } if (upToMarker != q->historyEdge()) @@ -863,9 +895,18 @@ Notification Room::checkForNotifications(const TimelineItem &ti) return { Notification::None }; } -bool Room::hasUnreadMessages() const { return unreadCount() >= 0; } +bool Room::hasUnreadMessages() const { return !d->partiallyReadStats.empty(); } -int Room::unreadCount() const { return d->unreadMessages; } +int countFromStats(const EventStats& s) +{ + return s.empty() ? -1 : int(s.notableCount); +} + +int Room::unreadCount() const { return countFromStats(partiallyReadStats()); } + +EventStats Room::partiallyReadStats() const { return d->partiallyReadStats; } + +EventStats Room::unreadStats() const { return d->unreadStats; } Room::rev_iter_t Room::historyEdge() const { return d->historyEdge(); } @@ -1071,13 +1112,16 @@ QSet Room::usersAtEventId(const QString& eventId) return users; } -int Room::notificationCount() const { return d->notificationCount; } +qsizetype Room::notificationCount() const +{ + return d->unreadStats.notableCount; +} void Room::resetNotificationCount() { - if (d->notificationCount == 0) + if (d->unreadStats.notableCount == 0) return; - d->notificationCount = 0; + d->unreadStats.notableCount = 0; emit notificationCountChanged(); } @@ -1652,6 +1696,72 @@ QUrl Room::memberAvatarUrl(const QString &mxId) const : QUrl(); } +Room::Changes Room::Private::updateStatsFromSyncData(const SyncRoomData& data, + bool fromCache) +{ + Changes changes {}; + if (fromCache) { + // Initial load of cached statistics + partiallyReadStats = + EventStats::fromCachedCounters(data.partiallyReadCount); + unreadStats = EventStats::fromCachedCounters(data.unreadCount, + data.highlightCount); + // Migrate from lib 0.6: -1 in the old unread counter overrides 0 + // (which loads to an estimate) in notification_count. Next caching will + // save -1 in both places, completing the migration. + if (data.unreadCount == 0 && data.partiallyReadCount == -1) + unreadStats.isEstimate = false; + changes |= Change::PartiallyReadStats | Change::UnreadStats; + qCDebug(MESSAGES) << "Loaded" << q->objectName() + << "event statistics from cache:" << partiallyReadStats + << "since m.fully_read," << unreadStats + << "since m.read"; + } else if (timeline.empty()) { + // In absence of actual events use statistics from the homeserver + if (merge(unreadStats.notableCount, data.unreadCount)) + changes |= Change::PartiallyReadStats; + if (merge(unreadStats.highlightCount, data.highlightCount)) + changes |= Change::UnreadStats; + unreadStats.isEstimate = !data.unreadCount.has_value() + || *data.unreadCount > 0; + qCDebug(MESSAGES) + << "Using server-side unread event statistics while the" + << q->objectName() << "timeline is empty:" << unreadStats; + } + bool correctedStats = false; + if (unreadStats.highlightCount > partiallyReadStats.highlightCount) { + correctedStats = true; + partiallyReadStats.highlightCount = unreadStats.highlightCount; + partiallyReadStats.isEstimate |= unreadStats.isEstimate; + } + if (unreadStats.notableCount > partiallyReadStats.notableCount) { + correctedStats = true; + partiallyReadStats.notableCount = unreadStats.notableCount; + partiallyReadStats.isEstimate |= unreadStats.isEstimate; + } + if (!unreadStats.isEstimate && partiallyReadStats.isEstimate) { + correctedStats = true; + partiallyReadStats.isEstimate = true; + } + if (correctedStats) + qCDebug(MESSAGES) << "Partially read event statistics in" + << q->objectName() << "were adjusted to" + << partiallyReadStats + << "to be consistent with the m.read receipt"; + Q_ASSERT(partiallyReadStats.isValidFor(q, q->fullyReadMarker())); + Q_ASSERT(unreadStats.isValidFor(q, q->localReadReceiptMarker())); + + // TODO: Once the library learns to count highlights, drop + // serverHighlightCount and only use the server-side counter when + // the timeline is empty (see the code above). + if (merge(serverHighlightCount, data.highlightCount)) { + qCDebug(MESSAGES) << "Updated highlights number in" << q->objectName() + << "to" << serverHighlightCount; + changes |= Change::Highlights; + } + return changes; +} + void Room::updateData(SyncRoomData&& data, bool fromCache) { if (d->prevBatch.isEmpty()) @@ -1670,25 +1780,14 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) for (auto&& event : data.accountData) roomChanges |= processAccountDataEvent(move(event)); + roomChanges |= d->updateStatsFromSyncData(data, fromCache); + if (roomChanges & Change::Topic) emit topicChanged(); if (roomChanges & (Change::Name | Change::Aliases)) emit namesChanged(this); - // See https://github.com/quotient-im/libQuotient/wiki/unread_count - if (merge(d->unreadMessages, data.partiallyReadCount)) { - qCDebug(MESSAGES) << "Loaded partially read count:" - << *data.partiallyReadCount << "in" << objectName(); - emit unreadMessagesChanged(this); - } - - if (merge(d->serverHighlightCount, data.highlightCount)) - emit highlightCountChanged(); - - if (merge(d->notificationCount, data.unreadCount)) - emit notificationCountChanged(); - d->postprocessChanges(roomChanges, !fromCache); } @@ -1704,6 +1803,17 @@ void Room::Private::postprocessChanges(Changes changes, bool saveState) & (Change::Name | Change::Aliases | Change::Members | Change::Summary)) updateDisplayname(); + if (changes & Change::PartiallyReadStats) { + emit q->unreadMessagesChanged(q); // TODO: remove in 0.8 + emit q->partiallyReadStatsChanged(); + } + + if (changes & Change::UnreadStats) + emit q->unreadStatsChanged(); + + if (changes & Change::Highlights) + emit q->highlightCountChanged(); + qCDebug(MAIN) << terse << changes << "in" << q->objectName(); emit q->changed(changes); if (saveState) @@ -2553,17 +2663,16 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) << totalInserted << "new events; the last event is now" << timeline.back(); - const auto& firstWriterId = (*from)->senderId(); - setLastReadReceipt(firstWriterId, rev_iter_t(from + 1)); + roomChanges |= updateStats(timeline.crbegin(), rev_iter_t(from)); + // If the local user's message(s) is/are first in the batch // and the fully read marker was right before it, promote // the fully read marker to the same event as the read receipt. + const auto& firstWriterId = (*from)->senderId(); if (firstWriterId == connection->userId() - && q->fullyReadMarker().base() == from) // + && q->fullyReadMarker().base() == from) roomChanges |= setFullyReadMarker(q->lastReadReceipt(firstWriterId).eventId); - - roomChanges |= updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); } Q_ASSERT(timeline.size() == timelineSize + totalInserted); @@ -2612,17 +2721,12 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) emit q->updatedEvent(relation.eventId); } } - changes |= updateUnreadCount(from, historyEdge()); - // When there are no unread messages and the read marker is within the - // known timeline, unreadMessages == -1 - // (see https://github.com/quotient-im/libQuotient/wiki/Developer-notes#2-saving-unread-event-counts). - Q_ASSERT(unreadMessages != 0 || q->fullyReadMarker() == historyEdge()); - Q_ASSERT(timeline.size() == timelineSize + insertedSize); if (insertedSize > 9 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) << "Added" << insertedSize << "historical event(s) to" << q->objectName() << "in" << et; + changes |= updateStats(from, historyEdge()); if (changes) postprocessChanges(changes); } @@ -2860,8 +2964,9 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) totalReceipts += p.receipts.size(); const auto newMarker = findInTimeline(p.evtId); if (newMarker == historyEdge()) - qCDebug(EPHEMERAL) << "Event of the read receipt(s) is not " - "found; saving anyway"; + qCDebug(EPHEMERAL) + << "Event" << p.evtId + << "is not found; saving read receipt(s) anyway"; // If the event is not found (most likely, because it's too old and // hasn't been fetched from the server yet) but there is a previous // marker for a user, keep the previous marker because read receipts @@ -2870,9 +2975,12 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) // the event is fetched later on. const auto updatedCount = std::count_if( p.receipts.cbegin(), p.receipts.cend(), - [this, &newMarker, &evtId = p.evtId](const auto& r) -> bool { - return d->setLastReadReceipt(r.userId, newMarker, - { evtId, r.timestamp }); + [this, &changes, &newMarker, &evtId = p.evtId](const auto& r) { + const auto change = + d->setLastReadReceipt(r.userId, newMarker, + { evtId, r.timestamp }); + changes |= change; + return change.testFlag(Change::Any); }); if (p.receipts.size() > 1) @@ -3098,15 +3206,11 @@ QJsonObject Room::Private::toJson() const .fullJson() } } }); } - QJsonObject unreadNotifObj { { PartiallyReadCountKey, unreadMessages } }; - - if (serverHighlightCount > 0) - unreadNotifObj.insert(HighlightCountKey, serverHighlightCount); - - result.insert(UnreadNotificationsKey, unreadNotifObj); - - if (notificationCount > 0) - unreadNotifObj.insert(NewUnreadCountKey, notificationCount); + result.insert(UnreadNotificationsKey, + QJsonObject { { PartiallyReadCountKey, + countFromStats(partiallyReadStats) }, + { HighlightCountKey, serverHighlightCount } }); + result.insert(NewUnreadCountKey, countFromStats(unreadStats)); if (et.elapsed() > 30) qCDebug(PROFILER) << "Room::toJson() for" << q->objectName() << "took" diff --git a/lib/room.h b/lib/room.h index c228f6c9..2f46e3a8 100644 --- a/lib/room.h +++ b/lib/room.h @@ -79,7 +79,7 @@ class ReadReceipt { Q_PROPERTY(QDateTime timestamp MEMBER timestamp CONSTANT) public: QString eventId; - QDateTime timestamp; + QDateTime timestamp = {}; bool operator==(const ReadReceipt& other) const { @@ -96,6 +96,8 @@ inline void swap(ReadReceipt& lhs, ReadReceipt& rhs) swap(lhs.timestamp, rhs.timestamp); } +struct EventStats; + struct Notification { enum Type { None = 0, Basic, Highlight }; @@ -146,13 +148,18 @@ class Room : public QObject { markMessagesAsRead NOTIFY readMarkerMoved) Q_PROPERTY(QString lastFullyReadEventId READ lastFullyReadEventId WRITE markMessagesAsRead NOTIFY fullyReadMarkerMoved) + //! \deprecated since 0.7 Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY - unreadMessagesChanged STORED false) - Q_PROPERTY(int unreadCount READ unreadCount NOTIFY unreadMessagesChanged) + partiallyReadStatsChanged STORED false) + //! \deprecated since 0.7 + Q_PROPERTY(int unreadCount READ unreadCount NOTIFY partiallyReadStatsChanged + STORED false) Q_PROPERTY(qsizetype highlightCount READ highlightCount NOTIFY highlightCountChanged) Q_PROPERTY(qsizetype notificationCount READ notificationCount NOTIFY notificationCountChanged) + Q_PROPERTY(EventStats partiallyReadStats READ partiallyReadStats NOTIFY partiallyReadStatsChanged) + Q_PROPERTY(EventStats unreadStats READ unreadStats NOTIFY unreadStatsChanged) Q_PROPERTY(bool allHistoryLoaded READ allHistoryLoaded NOTIFY addedMessages STORED false) Q_PROPERTY(QStringList tagNames READ tagNames NOTIFY tagsChanged) @@ -175,12 +182,13 @@ public: Aliases = 0x2, CanonicalAlias = Aliases, Topic = 0x4, - UnreadNotifs = 0x8, + PartiallyReadStats = 0x8, + DECL_DEPRECATED_ENUMERATOR(UnreadNotifs, PartiallyReadStats), Avatar = 0x10, JoinState = 0x20, Tags = 0x40, Members = 0x80, - /* = 0x100, */ + UnreadStats = 0x100, AccountData Q_DECL_ENUMERATOR_DEPRECATED_X( "Change::AccountData will be merged into Change::Other in 0.8") = 0x200, @@ -188,6 +196,7 @@ public: ReadMarker Q_DECL_ENUMERATOR_DEPRECATED_X( "Change::ReadMarker will be merged into Change::Other in 0.8") = 0x800, + Highlights = 0x1000, Other = 0x8000, Any = 0xFFFF }; @@ -509,6 +518,7 @@ public: //! the original event usually is); //! - from a non-local user (events from other devices of the local //! user are not notable). + //! \sa partiallyReadStats, unreadStats virtual bool isEventNotable(const TimelineItem& ti) const; //! \brief Get notification details for an event @@ -517,30 +527,84 @@ public: //! generated for \p evt. Notification notificationFor(const TimelineItem& ti) const; - //! Check whether there are unread messages in the room + //! \brief Get event statistics since the fully read marker + //! + //! This call returns a structure containing: + //! - the number of notable unread events since the fully read marker; + //! depending on the fully read marker state with respect to the local + //! timeline, this number may be either exact or estimated + //! (see EventStats::isEstimate); + //! - the number of highlights (TODO). + //! + //! Note that this is different from the unread count defined by MSC2654 + //! and from the notification/highlight numbers defined by the spec in that + //! it counts events since the fully read marker, not since the last + //! read receipt position. + //! + //! As E2EE is not supported in the library, the returned result will always + //! be an estimate (isEstimate == true) for encrypted rooms; + //! moreover, since the library doesn't know how to tackle push rules yet + //! the number of highlights returned here will always be zero (there's no + //! good substitute for that now). + //! + //! \sa isEventNotable, fullyReadMarker, unreadStats, EventStats + EventStats partiallyReadStats() const; + + //! \brief Get event statistics since the last read receipt + //! + //! This call returns a structure that contains the following three numbers, + //! all counted on the timeline segment between the event pointed to by + //! the m.fully_read marker and the sync edge: + //! - the number of unread events - depending on the read receipt state + //! with respect to the local timeline, this number may be either precise + //! or estimated (see EventStats::isEstimate); + //! - the number of highlights (TODO). + //! + //! As E2EE is not supported in the library, the returned result will always + //! be an estimate (isEstimate == true) for encrypted rooms; + //! moreover, since the library doesn't know how to tackle push rules yet + //! the number of highlights returned here will always be zero - use + //! highlightCount() for now. + //! + //! \sa isEventNotable, lastLocalReadReceipt, partiallyReadStats, + //! highlightCount + EventStats unreadStats() const; + + [[deprecated( + "Use partiallyReadStats/unreadStats() and EventStats::empty()")]] bool hasUnreadMessages() const; - /** Get the number of unread messages in the room - * Depending on the read marker state, this call may return either - * a precise or an estimate number of unread events. Only "notable" - * events (non-redacted message events from users other than local) - * are counted. - * - * In a case when readMarker() == historyEdge() (the local read - * marker is beyond the local timeline) only the bottom limit of - * the unread messages number can be estimated (and even that may - * be slightly off due to, e.g., redactions of events not loaded - * to the local timeline). - * - * If all messages are read, this function will return -1 (_not_ 0, - * as zero may mean "zero or more unread messages" in a situation - * when the read marker is outside the local timeline. - */ + //! \brief Get the number of notable events since the fully read marker + //! + //! \deprecated Since 0.7 there are two ways to count unread events: since + //! the fully read marker (used by libQuotient pre-0.7) and since the last + //! read receipt (as used by most of Matrix ecosystem, including the spec + //! and MSCs). This function currently returns a value derived from + //! partiallyReadStats() for compatibility with libQuotient 0.6; it will be + //! removed due to ambiguity. Use unreadStats() to obtain the spec-compliant + //! count of unread events and the highlight count; partiallyReadStats() to + //! obtain the unread events count since the fully read marker. + //! + //! \return -1 (_not 0_) when all messages are known to have been fully read, + //! i.e. the fully read marker points to _the latest notable_ event + //! loaded in the local timeline (which may be different from + //! the latest event in the local timeline as that might not be + //! notable); + //! 0 when there may be unread messages but the current local + //! timeline doesn't have any notable ones (often but not always + //! because it's entirely empty yet); + //! a positive integer when there is (or estimated to be) a number + //! of unread notable events as described above. + //! + //! \sa partiallyReadStats, unreadStats + [[deprecated("Use partiallyReadStats() or unreadStats() instead")]] // int unreadCount() const; //! \brief Get the number of notifications since the last read receipt //! - //! \sa lastLocalReadReceipt + //! This is the same as unreadStats().notableCount. + //! + //! \sa unreadStats, lastLocalReadReceipt qsizetype notificationCount() const; //! \deprecated Use setReadReceipt() to drive changes in notification count @@ -550,6 +614,8 @@ public: //! //! As of 0.7, this is defined by the homeserver as Quotient doesn't process //! push rules. + //! + //! \sa unreadStats, lastLocalReadReceipt qsizetype highlightCount() const; //! \deprecated Use setReadReceipt() to drive changes in highlightCount @@ -878,7 +944,11 @@ Q_SIGNALS: //! \deprecated since 0.7 - use lastReadEventChanged void readMarkerForUserMoved(Quotient::User* user, QString fromEventId, QString toEventId); + //! \deprecated since 0.7 - use either partiallyReadStatsChanged + //! or unreadStatsChanged void unreadMessagesChanged(Quotient::Room* room); + void partiallyReadStatsChanged(); + void unreadStatsChanged(); void accountDataAboutToChange(QString type); void accountDataChanged(QString type); -- cgit v1.2.3 From 9e5c9ff3ef0eeff8abd0bc3508d9152506e27e30 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 20 Nov 2021 11:42:53 +0100 Subject: Document Room::Change and its enumerators --- lib/room.h | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/lib/room.h b/lib/room.h index 2f46e3a8..706fb17f 100644 --- a/lib/room.h +++ b/lib/room.h @@ -176,28 +176,45 @@ public: using rev_iter_t = Timeline::const_reverse_iterator; using timeline_iter_t = Timeline::const_iterator; + //! \brief Room changes that can be tracked using Room::changed() signal + //! + //! This enumeration lists kinds of changes that can be tracked with + //! a "cumulative" changed() signal instead of using individual signals for + //! each change. Specific enumerators mention these individual signals. + //! \sa changed enum class Change : uint { - None = 0x0, - Name = 0x1, - Aliases = 0x2, + None = 0x0, //< No changes occurred in the room + Name = 0x1, //< \sa namesChanged, displaynameChanged + Aliases = 0x2, //< \sa namesChanged, displaynameChanged CanonicalAlias = Aliases, - Topic = 0x4, - PartiallyReadStats = 0x8, + Topic = 0x4, //< \sa topicChanged + PartiallyReadStats = 0x8, //< \sa partiallyReadStatsChanged DECL_DEPRECATED_ENUMERATOR(UnreadNotifs, PartiallyReadStats), - Avatar = 0x10, - JoinState = 0x20, - Tags = 0x40, + Avatar = 0x10, //< \sa avatarChanged + JoinState = 0x20, //< \sa joinStateChanged + Tags = 0x40, //< \sa tagsChanged + //! \sa userAdded, userRemoved, memberRenamed, memberListChanged, + //! displaynameChanged Members = 0x80, - UnreadStats = 0x100, + UnreadStats = 0x100, //< \sa unreadStatsChanged AccountData Q_DECL_ENUMERATOR_DEPRECATED_X( "Change::AccountData will be merged into Change::Other in 0.8") = 0x200, - Summary = 0x400, + Summary = 0x400, //< \sa summaryChanged, displaynameChanged ReadMarker Q_DECL_ENUMERATOR_DEPRECATED_X( "Change::ReadMarker will be merged into Change::Other in 0.8") = 0x800, - Highlights = 0x1000, + Highlights = 0x1000, //< \sa highlightCountChanged + //! A catch-all value that covers changes not listed above (such as + //! encryption turned on or the room having been upgraded), as well as + //! changes in the room state that the library is not aware of (e.g., + //! custom state events) and m.read/m.fully_read position changes. + //! \sa encryptionChanged, upgraded, accountDataChanged Other = 0x8000, + //! This is intended to test a Change/Changes value for non-emptiness; + //! testFlag(Change::Any) or adding & Change::Any has + //! the same meaning as !testFlag(Change::None) or adding + //! != Change::None. Any = 0xFFFF }; QUO_DECLARE_FLAGS(Changes, Change) @@ -931,12 +948,14 @@ Q_SIGNALS: Quotient::JoinState newState); void typingChanged(); - void highlightCountChanged(); - void notificationCountChanged(); + void highlightCountChanged(); //< \sa highlightCount + void notificationCountChanged(); //< \sa notificationCount void displayedChanged(bool displayed); void firstDisplayedEventChanged(); void lastDisplayedEventChanged(); + //! The event that m.read receipt points to has changed + //! \sa lastReadReceipt void lastReadEventChanged(Quotient::User* user); void fullyReadMarkerMoved(QString fromEventId, QString toEventId); //! \deprecated since 0.7 - use fullyReadMarkerMoved -- cgit v1.2.3 From 52e640bce5a8931330fa6d653212e524e7baa2eb Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 20 Nov 2021 11:42:21 +0100 Subject: Minor brushup in the comment to QUO_DECLARE_FLAGS [skip ci] --- lib/quotient_common.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 969ebe90..d91c3d17 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -9,10 +9,10 @@ // See https://bugreports.qt.io/browse/QTBUG-82295 - despite the comment that // Q_FLAG[_NS] "should" be applied to the enum only, Qt doesn't allow to wrap -// a flag type into a QVariant then. The macros below define Q_FLAG_NS and on -// top of that add a part of Q_ENUM() that enables the metatype data but goes -// under the moc radar to avoid double registration of the same data in the map -// defined in moc_*.cpp +// a flag type into a QVariant then. The macros below define Q_FLAG[_NS] and on +// top of that add Q_ENUM[_NS]_IMPL which is a part of Q_ENUM() macro that +// enables the metatype data but goes under the moc radar to avoid double +// registration of the same data in the map defined in moc_*.cpp #define QUO_DECLARE_FLAGS(Flags, Enum) \ Q_DECLARE_FLAGS(Flags, Enum) \ Q_ENUM_IMPL(Enum) \ -- cgit v1.2.3 From ffb1f978c377e0442bed9eab17727932d59fc55a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 21 Nov 2021 12:24:11 +0100 Subject: Log the hexadecimal value of Changes too, just in case --- lib/room.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/room.cpp b/lib/room.cpp index 8bad9084..c166254e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1814,7 +1814,11 @@ void Room::Private::postprocessChanges(Changes changes, bool saveState) if (changes & Change::Highlights) emit q->highlightCountChanged(); - qCDebug(MAIN) << terse << changes << "in" << q->objectName(); + qCDebug(MAIN) << terse << changes << "= hex" << +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + Qt:: +#endif + hex << uint(changes) << "in" << q->objectName(); emit q->changed(changes); if (saveState) connection->saveRoomState(q); -- cgit v1.2.3 From e16ed41a6a28a43919126a85f7a802c5b2e091b6 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 21 Nov 2021 15:29:57 +0100 Subject: CI: Nicer and more detailed logging for quotest --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 269e487c..dd862a0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -158,6 +158,8 @@ jobs: env: TEST_USER: ${{ secrets.TEST_USER }} TEST_PWD: ${{ secrets.TEST_PWD }} + QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' + QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' run: | [[ -z "$TEST_USER" ]] || $VALGRIND build/quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 5 # quotest is supposed to finish within 3 minutes, actually -- cgit v1.2.3 From a2cc707107464fd98fc8a33afde3ed29f8cd9526 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 21 Nov 2021 21:48:54 +0100 Subject: CI: Restrict workflow concurrency; tighten job timeout --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 269e487c..1b6d9e52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,8 @@ defaults: run: shell: bash +concurrency: ci-${{ github.ref }} + jobs: CI: runs-on: ${{ matrix.os }} @@ -160,7 +162,7 @@ jobs: TEST_PWD: ${{ secrets.TEST_PWD }} run: | [[ -z "$TEST_USER" ]] || $VALGRIND build/quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" - timeout-minutes: 5 # quotest is supposed to finish within 3 minutes, actually + timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually - name: Perform CodeQL analysis if: env.CODEQL_ANALYSIS -- cgit v1.2.3 From 59ab0d5e04152e9657bdb47a6c9e860f4864be20 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 22 Nov 2021 17:58:54 +0100 Subject: Fix a crash on trying to mark unknown events as fully read --- lib/room.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index c166254e..e8d9b1bf 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -828,7 +828,10 @@ void Room::setReadReceipt(const QString& atEventId) bool Room::Private::markMessagesAsRead(const rev_iter_t &upToMarker) { - if (const auto changes = setFullyReadMarker(upToMarker->event()->id())) { + if (upToMarker == q->historyEdge()) + qCWarning(MESSAGES) << "Cannot mark an unknown event in" + << q->objectName() << "as fully read"; + else if (const auto changes = setFullyReadMarker(upToMarker->event()->id())) { // The assumption below is that if a read receipt was sent on a newer // event, the homeserver will keep it there instead of reverting to // m.fully_read @@ -837,15 +840,11 @@ bool Room::Private::markMessagesAsRead(const rev_iter_t &upToMarker) fullyReadUntilEventId); postprocessChanges(changes); return true; - } - if (upToMarker != q->historyEdge()) + } else qCDebug(MESSAGES) << "Event" << *upToMarker << "in" << q->objectName() << "is behind the current fully read marker at" << *q->fullyReadMarker() << "- won't move fully read marker back in timeline"; - else - qCWarning(MESSAGES) << "Cannot mark an unknown event in" - << q->objectName() << "as fully read"; return false; } -- cgit v1.2.3 From 2fdcb290c7c5249896e3f8b542df16e248487f34 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 22 Nov 2021 22:36:11 +0100 Subject: Fix stupid false-negatives from Room::updateStats() --- lib/room.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index e8d9b1bf..3090cb7b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -736,7 +736,7 @@ Room::Changes Room::Private::updateStats(const rev_iter_t& from, qCDebug(MESSAGES).nospace() << "Recalculated partially read event statistics in " << q->objectName() << ": " << partiallyReadStats; - return Change::PartiallyReadStats | Change::UnreadStats; + return changes | Change::PartiallyReadStats; } } @@ -748,7 +748,7 @@ Room::Changes Room::Private::updateStats(const rev_iter_t& from, const auto newStats = EventStats::fromRange(q, from, to); Q_ASSERT(!newStats.isEstimate); - if (newStats.notableCount == 0 || newStats.highlightCount == 0) + if (newStats.empty()) return changes; const auto doAddStats = [this, &changes, newStats](EventStats& s, -- cgit v1.2.3 From c57d6de40fb790a4920a9c8ff235511860d68f32 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 24 Nov 2021 09:50:34 +0100 Subject: Don't require C++20 from the clients just yet --- lib/eventstats.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/eventstats.h b/lib/eventstats.h index 9be83377..77c661a7 100644 --- a/lib/eventstats.h +++ b/lib/eventstats.h @@ -37,7 +37,14 @@ public: //! the previous run of the client). bool isEstimate = true; - bool operator==(const EventStats& rhs) const& = default; + // TODO: replace with = default once C++20 becomes a requirement on clients + bool operator==(const EventStats& rhs) const + { + return notableCount == rhs.notableCount + && highlightCount == rhs.highlightCount + && isEstimate == rhs.isEstimate; + } + bool operator!=(const EventStats& rhs) const { return !operator==(rhs); } //! \brief Check whether the event statistics are empty //! -- cgit v1.2.3 From f6155d62740a88b020273ba623c816f7b9805772 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 13:44:39 +0100 Subject: Drop Q_GADGET from most uncopyable classes; other minor cleanup Q_GADGET is generally used to enable two things outside of QObject: Q_PROPERTY/Q_INVOKABLE and Q_ENUM/Q_FLAG. While the latter can be used in its own right in QML, the former requires Q_GADGET instances to be passed to QML by value, which is not really possible with uncopyable/unassignable classes. Bottom line is that Q_PROPERTY in anything derived from Quotient::Event is not viable, making Q_GADGET macro useless unless there's a Q_ENUM/Q_FLAG (as is the case with RoomMessageEvent, e.g.). --- lib/eventitem.h | 5 +---- lib/events/encryptedevent.h | 2 -- lib/events/event.h | 3 --- lib/events/roomevent.h | 41 +++++++++++++---------------------------- lib/events/roommessageevent.h | 4 ---- lib/events/stateevent.h | 2 -- lib/quotient_common.h | 1 - 7 files changed, 14 insertions(+), 44 deletions(-) diff --git a/lib/eventitem.h b/lib/eventitem.h index a70a3c3e..0ab1a01d 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -9,7 +9,6 @@ #include namespace Quotient { -class StateEventBase; namespace EventStatus { Q_NAMESPACE @@ -31,8 +30,7 @@ namespace EventStatus { Replaced = 0x10, //< The event has been replaced Hidden = 0x100, //< The event should not be shown in the timeline }; - Q_DECLARE_FLAGS(Status, Code) - Q_FLAG_NS(Status) + Q_ENUM_NS(Code) } // namespace EventStatus class EventItemBase { @@ -106,7 +104,6 @@ inline const CallEventBase* EventItemBase::viewAs() const } class PendingEventItem : public EventItemBase { - Q_GADGET public: using EventItemBase::EventItemBase; diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index eb7123eb..598829cd 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -7,7 +7,6 @@ #include "roomevent.h" namespace Quotient { -class Room; /* * While the specification states: * @@ -27,7 +26,6 @@ class Room; * one and doesn't add new restrictions, just provides additional features. */ class EncryptedEvent : public RoomEvent { - Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent) diff --git a/lib/events/event.h b/lib/events/event.h index 78853ced..2ed5de5d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -195,9 +195,6 @@ inline auto registerEventType() // === Event === class Event { - Q_GADGET - Q_PROPERTY(Type type READ type CONSTANT) - Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT) public: using Type = event_type_t; using factory_t = EventFactory; diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 3174764f..a6cd84ca 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -12,16 +12,6 @@ class RedactionEvent; /** This class corresponds to m.room.* events */ class RoomEvent : public Event { - Q_GADGET - Q_PROPERTY(QString id READ id) - //! \deprecated Use originTimestamp instead - Q_PROPERTY(QDateTime timestamp READ originTimestamp CONSTANT) - Q_PROPERTY(QDateTime originTimestamp READ originTimestamp CONSTANT) - Q_PROPERTY(QString roomId READ roomId CONSTANT) - Q_PROPERTY(QString senderId READ senderId CONSTANT) - Q_PROPERTY(QString redactionReason READ redactionReason) - Q_PROPERTY(bool isRedacted READ isRedacted) - Q_PROPERTY(QString transactionId READ transactionId WRITE setTransactionId) public: using factory_t = EventFactory; @@ -51,28 +41,23 @@ public: QString transactionId() const; QString stateKey() const; + //! \brief Fill the pending event object with the room id void setRoomId(const QString& roomId); + //! \brief Fill the pending event object with the sender id void setSender(const QString& senderId); - - /** - * Sets the transaction id for locally created events. This should be - * done before the event is exposed to any code using the respective - * Q_PROPERTY. - * - * \param txnId - transaction id, normally obtained from - * Connection::generateTxnId() - */ + //! \brief Fill the pending event object with the transaction id + //! \param txnId - transaction id, normally obtained from + //! Connection::generateTxnId() void setTransactionId(const QString& txnId); - /** - * Sets event id for locally created events - * - * When a new event is created locally, it has no server id yet. - * This function allows to add the id once the confirmation from - * the server is received. There should be no id set previously - * in the event. It's the responsibility of the code calling addId() - * to notify clients that use Q_PROPERTY(id) about its change - */ + //! \brief Add an event id to locally created events after they are sent + //! + //! When a new event is created locally, it has no id; the homeserver + //! assigns it once the event is sent. This function allows to add the id + //! once the confirmation from the server is received. There should be no id + //! set previously in the event. It's the responsibility of the code calling + //! addId() to notify clients about the change; there's no signal or + //! callback for that in RoomEvent. void addId(const QString& newId); protected: diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 88d3b74c..56597ddc 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -18,10 +18,6 @@ namespace MessageEventContent = EventContent; // Back-compatibility */ class RoomMessageEvent : public RoomEvent { Q_GADGET - Q_PROPERTY(QString msgType READ rawMsgtype CONSTANT) - Q_PROPERTY(QString plainBody READ plainBody CONSTANT) - Q_PROPERTY(QMimeType mimeType READ mimeType STORED false CONSTANT) - Q_PROPERTY(const EventContent::TypedBase* content READ content CONSTANT) public: DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index bc414a5f..b0aa9907 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -18,8 +18,6 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, } class StateEventBase : public RoomEvent { - Q_GADGET - Q_PROPERTY(QString stateKey READ stateKey CONSTANT) public: using factory_t = EventFactory; diff --git a/lib/quotient_common.h b/lib/quotient_common.h index d91c3d17..0e3e2a40 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -90,7 +90,6 @@ constexpr inline auto JoinStateStrings = make_array( //! So far only background/foreground flags are available. //! \sa Connection::callApi, Connection::run enum RunningPolicy { ForegroundRequest = 0x0, BackgroundRequest = 0x1 }; - Q_ENUM_NS(RunningPolicy) //! \brief The result of URI resolution using UriResolver -- cgit v1.2.3 From f4a0acf818c4c89d132b2ec96d47c5817b106149 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 13:46:58 +0100 Subject: Drop #include "logging.h" from event.h Makes compilation a tad lighter. --- lib/events/callanswerevent.cpp | 1 + lib/events/callhangupevent.cpp | 1 + lib/events/callinviteevent.cpp | 1 + lib/events/encryptedevent.cpp | 11 ++++++++--- lib/events/encryptedevent.h | 14 ++++---------- lib/events/encryptionevent.cpp | 1 + lib/events/event.cpp | 11 ++++++++--- lib/events/event.h | 5 +++-- lib/events/reactionevent.cpp | 1 + lib/events/roomcreateevent.cpp | 1 + lib/events/roomkeyevent.cpp | 1 + lib/syncdata.cpp | 1 + 12 files changed, 31 insertions(+), 18 deletions(-) diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index be83d9d0..24144585 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "callanswerevent.h" +#include "logging.h" /* m.call.answer diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp index 43bc4db0..537ace75 100644 --- a/lib/events/callhangupevent.cpp +++ b/lib/events/callhangupevent.cpp @@ -6,6 +6,7 @@ */ #include "callhangupevent.h" +#include "logging.h" /* m.call.hangup diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 5ea54662..ce4fb101 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "callinviteevent.h" +#include "logging.h" /* m.call.invite diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 0290f973..ebf733ac 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" +#include "logging.h" using namespace Quotient; @@ -25,8 +26,12 @@ EncryptedEvent::EncryptedEvent(QByteArray ciphertext, const QString& senderKey, }) {} -EncryptedEvent::EncryptedEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj) +QString EncryptedEvent::algorithm() const { - qCDebug(E2EE) << "Encrypted event from" << senderId(); + const auto algo = content(AlgorithmKeyL); + if (!SupportedAlgorithms.contains(algo)) { + qCWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + } + return algo; } diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 598829cd..d2b71785 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -37,17 +37,11 @@ public: /* In case with Megolm, device_id and session_id are required */ explicit EncryptedEvent(QByteArray ciphertext, const QString& senderKey, const QString& deviceId, const QString& sessionId); - explicit EncryptedEvent(const QJsonObject& obj); + explicit EncryptedEvent(const QJsonObject& obj) + : RoomEvent(typeId(), obj) + {} - QString algorithm() const - { - QString algo = content(AlgorithmKeyL); - if (!SupportedAlgorithms.contains(algo)) { - qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo - << "is not supported"; - } - return algo; - } + QString algorithm() const; QByteArray ciphertext() const { return content(CiphertextKeyL).toLatin1(); diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index aa05a96e..9eb48844 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptionevent.h" +#include "logging.h" #include "e2ee.h" diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 3d66ab55..305dd454 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -9,15 +9,20 @@ using namespace Quotient; +void Quotient::logFactorySetup(event_mtype_t eventTypeId) +{ + qCDebug(EVENTS) << "Adding factory method for" << eventTypeId; +} + event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId) { const auto id = get().eventTypes.size(); get().eventTypes.push_back(matrixTypeId); if (strncmp(matrixTypeId, "", 1) == 0) - qDebug(EVENTS) << "Initialized unknown event type with id" << id; + qCDebug(EVENTS) << "Initialized unknown event type with id" << id; else - qDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" - << id; + qCDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" + << id; return id; } diff --git a/lib/events/event.h b/lib/events/event.h index 2ed5de5d..e9963eae 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -4,7 +4,6 @@ #pragma once #include "converters.h" -#include "logging.h" namespace Quotient { // === event_ptr_tt<> and type casting facilities === @@ -162,6 +161,8 @@ private: } }; +void logFactorySetup(event_mtype_t eventTypeId); + /** Add a type to its default factory * Adds a standard factory method (via makeEvent<>) for a given * type to EventT::factory_t factory class so that it can be @@ -174,7 +175,7 @@ private: template inline auto setupFactory() { - qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); + logFactorySetup(EventT::matrixTypeId()); return EventT::factory_t::addMethod([](const QJsonObject& json, const QString& jsonMatrixType) { return EventT::matrixTypeId() == jsonMatrixType ? makeEvent(json) diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp index b53fffd6..bb62f0e0 100644 --- a/lib/events/reactionevent.cpp +++ b/lib/events/reactionevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "reactionevent.h" +#include "logging.h" using namespace Quotient; diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index ff93041c..e119696f 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "roomcreateevent.h" +#include "logging.h" using namespace Quotient; diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp index 332be3f7..dbcb35bd 100644 --- a/lib/events/roomkeyevent.cpp +++ b/lib/events/roomkeyevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "roomkeyevent.h" +#include "logging.h" using namespace Quotient; diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index 396e77eb..f9fabdea 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -4,6 +4,7 @@ #include "syncdata.h" #include "events/eventloader.h" +#include "logging.h" #include #include -- cgit v1.2.3 From b3d62050befbb1c526f03e4356f3263d197c45f2 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 14:02:49 +0100 Subject: Event: deprecate originalJson[Object]() The "original JSON" wording is misleading: the returned JSON can be and is routinely edited as a part of event construction, redaction, editing. Also, originalJson() name is misleading in that it returns a stringified (in a very specific way) JSON and not an object. You have to call fullJson() to get the object, and originalJsonObject(), confusingly, returns exactly the same thing but as a value rather than as a reference. The original intention of keeping originalJsonObject() was to make it Q_INVOKABLE or use it as an accessor for a Q_PROPERTY. unfortunately, this was never really practical as discussed in the previous commit. All that implies that clients have to handle passing event JSON to QML themselves, in the form they prefer (as an object or a string). The added complexity is negligible though; on the other hand, there's added flexibility in, e.g., choosing a compact instead of default JSON layout or even generate a highlighted JSON representation. --- lib/events/event.h | 3 +++ lib/room.cpp | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index e9963eae..52e3d025 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -210,7 +210,10 @@ public: Type type() const { return _type; } QString matrixType() const; + [[deprecated("Use fullJson() and stringify it with QJsonDocument::toJson() " + "or by other means")]] QByteArray originalJson() const; + [[deprecated("Use fullJson() instead")]] // QJsonObject originalJsonObject() const { return fullJson(); } const QJsonObject& fullJson() const { return _json; } diff --git a/lib/room.cpp b/lib/room.cpp index 3090cb7b..aa00025c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1589,7 +1589,8 @@ void Room::Private::removeMemberFromMap(User* u) inline auto makeErrorStr(const Event& e, QByteArray msg) { - return msg.append("; event dump follows:\n").append(e.originalJson()); + return msg.append("; event dump follows:\n") + .append(QJsonDocument(e.fullJson()).toJson()); } Room::Timeline::size_type @@ -2363,7 +2364,7 @@ void Room::Private::dropDuplicateEvents(RoomEvents& events) const RoomEventPtr makeRedacted(const RoomEvent& target, const RedactionEvent& redaction) { - auto originalJson = target.originalJsonObject(); + auto originalJson = target.fullJson(); // clang-format off static const QStringList keepKeys { EventIdKey, TypeKey, RoomIdKey, SenderKey, StateKeyKey, @@ -2409,7 +2410,7 @@ RoomEventPtr makeRedacted(const RoomEvent& target, originalJson.insert(ContentKey, content); } auto unsignedData = originalJson.take(UnsignedKeyL).toObject(); - unsignedData[RedactedCauseKeyL] = redaction.originalJsonObject(); + unsignedData[RedactedCauseKeyL] = redaction.fullJson(); originalJson.insert(QStringLiteral("unsigned"), unsignedData); return loadEvent(originalJson); @@ -2479,7 +2480,7 @@ RoomEventPtr makeReplaced(const RoomEvent& target, if (!targetReply.empty()) { newContent["m.relates_to"] = targetReply; } - auto originalJson = target.originalJsonObject(); + auto originalJson = target.fullJson(); originalJson[ContentKeyL] = newContent; auto unsignedData = originalJson.take(UnsignedKeyL).toObject(); -- cgit v1.2.3 From 4ba795556721a88d2ac258d9095a46de21d77011 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 16:07:25 +0100 Subject: Revert "Drop #include "logging.h" from event.h" Doesn't really help build times, instead breaking the build on older Qt. --- lib/events/callanswerevent.cpp | 1 - lib/events/callhangupevent.cpp | 1 - lib/events/callinviteevent.cpp | 1 - lib/events/encryptedevent.cpp | 11 +++-------- lib/events/encryptedevent.h | 14 ++++++++++---- lib/events/encryptionevent.cpp | 1 - lib/events/event.cpp | 11 +++-------- lib/events/event.h | 5 ++--- lib/events/reactionevent.cpp | 1 - lib/events/roomcreateevent.cpp | 1 - lib/events/roomkeyevent.cpp | 1 - lib/syncdata.cpp | 1 - 12 files changed, 18 insertions(+), 31 deletions(-) diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index 24144585..be83d9d0 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "callanswerevent.h" -#include "logging.h" /* m.call.answer diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp index 537ace75..43bc4db0 100644 --- a/lib/events/callhangupevent.cpp +++ b/lib/events/callhangupevent.cpp @@ -6,7 +6,6 @@ */ #include "callhangupevent.h" -#include "logging.h" /* m.call.hangup diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index ce4fb101..5ea54662 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "callinviteevent.h" -#include "logging.h" /* m.call.invite diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index ebf733ac..0290f973 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" -#include "logging.h" using namespace Quotient; @@ -26,12 +25,8 @@ EncryptedEvent::EncryptedEvent(QByteArray ciphertext, const QString& senderKey, }) {} -QString EncryptedEvent::algorithm() const +EncryptedEvent::EncryptedEvent(const QJsonObject& obj) + : RoomEvent(typeId(), obj) { - const auto algo = content(AlgorithmKeyL); - if (!SupportedAlgorithms.contains(algo)) { - qCWarning(MAIN) << "The EncryptedEvent's algorithm" << algo - << "is not supported"; - } - return algo; + qCDebug(E2EE) << "Encrypted event from" << senderId(); } diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index d2b71785..598829cd 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -37,11 +37,17 @@ public: /* In case with Megolm, device_id and session_id are required */ explicit EncryptedEvent(QByteArray ciphertext, const QString& senderKey, const QString& deviceId, const QString& sessionId); - explicit EncryptedEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj) - {} + explicit EncryptedEvent(const QJsonObject& obj); - QString algorithm() const; + QString algorithm() const + { + QString algo = content(AlgorithmKeyL); + if (!SupportedAlgorithms.contains(algo)) { + qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + } + return algo; + } QByteArray ciphertext() const { return content(CiphertextKeyL).toLatin1(); diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 9eb48844..aa05a96e 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptionevent.h" -#include "logging.h" #include "e2ee.h" diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 305dd454..3d66ab55 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -9,20 +9,15 @@ using namespace Quotient; -void Quotient::logFactorySetup(event_mtype_t eventTypeId) -{ - qCDebug(EVENTS) << "Adding factory method for" << eventTypeId; -} - event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId) { const auto id = get().eventTypes.size(); get().eventTypes.push_back(matrixTypeId); if (strncmp(matrixTypeId, "", 1) == 0) - qCDebug(EVENTS) << "Initialized unknown event type with id" << id; + qDebug(EVENTS) << "Initialized unknown event type with id" << id; else - qCDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" - << id; + qDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" + << id; return id; } diff --git a/lib/events/event.h b/lib/events/event.h index 52e3d025..89efb7f8 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -4,6 +4,7 @@ #pragma once #include "converters.h" +#include "logging.h" namespace Quotient { // === event_ptr_tt<> and type casting facilities === @@ -161,8 +162,6 @@ private: } }; -void logFactorySetup(event_mtype_t eventTypeId); - /** Add a type to its default factory * Adds a standard factory method (via makeEvent<>) for a given * type to EventT::factory_t factory class so that it can be @@ -175,7 +174,7 @@ void logFactorySetup(event_mtype_t eventTypeId); template inline auto setupFactory() { - logFactorySetup(EventT::matrixTypeId()); + qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); return EventT::factory_t::addMethod([](const QJsonObject& json, const QString& jsonMatrixType) { return EventT::matrixTypeId() == jsonMatrixType ? makeEvent(json) diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp index bb62f0e0..b53fffd6 100644 --- a/lib/events/reactionevent.cpp +++ b/lib/events/reactionevent.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "reactionevent.h" -#include "logging.h" using namespace Quotient; diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index e119696f..ff93041c 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "roomcreateevent.h" -#include "logging.h" using namespace Quotient; diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp index dbcb35bd..332be3f7 100644 --- a/lib/events/roomkeyevent.cpp +++ b/lib/events/roomkeyevent.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "roomkeyevent.h" -#include "logging.h" using namespace Quotient; diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index f9fabdea..396e77eb 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -4,7 +4,6 @@ #include "syncdata.h" #include "events/eventloader.h" -#include "logging.h" #include #include -- cgit v1.2.3 From 429b3adaf921dd7534a04fccae1bc3c1801d2e40 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 17:27:23 +0100 Subject: Room::processEphemeralEvents: Fix updatedCount always being zero Trying to test bits with Changes::testFlag(Change::Any) was a bad idea. Along the way: made logging in setLastReadReceipt() refer to the actual timeline item when possible. --- lib/room.cpp | 24 +++++++++++++++++------- lib/room.h | 7 ++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index aa00025c..68095412 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -632,9 +632,9 @@ Room::Changes Room::Private::setLastReadReceipt(const QString& userId, }); // eagerMarker is now just after the desired event for newMarker if (eagerMarker != newMarker.base()) { - qCDebug(EPHEMERAL) << "Auto-promoted read receipt for" << userId - << "to" << newReceipt.eventId; newMarker = rev_iter_t(eagerMarker); + qCDebug(EPHEMERAL) << "Auto-promoted read receipt for" << userId + << "to" << *newMarker; } // Fill newReceipt with the event (and, if needed, timestamp) from // eagerMarker @@ -645,8 +645,11 @@ Room::Changes Room::Private::setLastReadReceipt(const QString& userId, auto& storedReceipt = lastReadReceipts[userId]; // clazy:exclude=detaching-member const auto prevEventId = storedReceipt.eventId; - // NB: with reverse iterators, timeline history >= sync edge - if (newMarker >= q->findInTimeline(prevEventId)) + // Check that either the new marker is actually "newer" than the current one + // or, if both markers are at historyEdge(), event ids are different. + // NB: with reverse iterators, timeline history edge >= sync edge + if (prevEventId == newReceipt.eventId + || newMarker > q->findInTimeline(prevEventId)) return Change::None; // Finally make the change @@ -661,8 +664,15 @@ Room::Changes Room::Private::setLastReadReceipt(const QString& userId, } eventIdReadUsers[newReceipt.eventId].insert(userId); storedReceipt = move(newReceipt); - qCDebug(EPHEMERAL) << "The new read receipt for" << userId << "is now at" - << storedReceipt.eventId; + + { + auto dbg = qDebug(EPHEMERAL); // This trick needs qDebug, not qCDebug + dbg << "The new read receipt for" << userId << "is now at"; + if (newMarker == historyEdge()) + dbg << storedReceipt.eventId; + else + dbg << *newMarker; + } // TODO: use Room::member() when it becomes a thing and only emit signals // for actual members, not just any user @@ -2984,7 +2994,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) d->setLastReadReceipt(r.userId, newMarker, { evtId, r.timestamp }); changes |= change; - return change.testFlag(Change::Any); + return change & Change::Any; }); if (p.receipts.size() > 1) diff --git a/lib/room.h b/lib/room.h index 706fb17f..65217290 100644 --- a/lib/room.h +++ b/lib/room.h @@ -212,9 +212,10 @@ public: //! \sa encryptionChanged, upgraded, accountDataChanged Other = 0x8000, //! This is intended to test a Change/Changes value for non-emptiness; - //! testFlag(Change::Any) or adding & Change::Any has - //! the same meaning as !testFlag(Change::None) or adding - //! != Change::None. + //! adding & Change::Any has the same meaning as + //! !testFlag(Change::None) or adding != Change::None + //! \note testFlag(Change::Any) tests that _all_ bits are on and + //! will always return false. Any = 0xFFFF }; QUO_DECLARE_FLAGS(Changes, Change) -- cgit v1.2.3 From 38eaa61b9fa8e0a04baa847824bb1e1280395a57 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 17:33:46 +0100 Subject: Fix crashing on invalid member and encryption events The problem is in Room::processStateEvent(): after potentially-inserting-nullptr into currentState, pre-check failure (that may occur on member and trigger events for now) leaves that nullptr in the hash map. Basically anything that uses currentState (e.g., Room::toJson) assumes that currentState has no nullptrs - which leads to either an assertion failure, or nullptr dereferencing. The fix removes the nullptr placeholder if the pre-checks failed. --- lib/room.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/room.cpp b/lib/room.cpp index 68095412..284eacd1 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2835,8 +2835,11 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) } , true); // By default, go forward with the state change // clang-format on - if (!proceed) + if (!proceed) { + if (!curStateEvent) // Remove the empty placeholder if one was created + d->currentState.remove({ e.matrixType(), e.stateKey() }); return Change::None; + } // Change the state const auto* const oldStateEvent = -- cgit v1.2.3 From 9701f24ac0214e4d8bd7fe1d6d4fa542d75d3d18 Mon Sep 17 00:00:00 2001 From: Smitty Date: Fri, 26 Nov 2021 20:25:58 -0500 Subject: Add Room::{stateEventsOfType,currentState} This is useful for implementing Spaces support, where all events of type `m.space.child` are needed, and we don't know their state keys in advance. --- lib/room.cpp | 20 ++++++++++++++++---- lib/room.h | 9 ++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 3b00d2d9..7e0806a7 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -224,9 +224,15 @@ public: return evt; } - const QHash stateEvents() const + QVector stateEventsOfType(const QString& evtType) const { - return currentState; + QVector vals = QVector(); + for (const auto* val : currentState) { + if (val->matrixType() == evtType) { + vals.append(val); + } + } + return vals; } template @@ -1298,9 +1304,15 @@ const StateEventBase* Room::getCurrentState(const QString& evtType, return d->getCurrentState({ evtType, stateKey }); } -const QHash Room::stateEvents() const +const QVector +Room::stateEventsOfType(const QString& evtType) const +{ + return d->stateEventsOfType(evtType); +} + +const QHash& Room::currentState() const { - return d->stateEvents(); + return d->currentState; } RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) diff --git a/lib/room.h b/lib/room.h index 452ea306..51538b8f 100644 --- a/lib/room.h +++ b/lib/room.h @@ -512,7 +512,14 @@ public: /*! This method returns all known state events that have occured in * the room, as a mapping from the event type and state key to value. */ - Q_INVOKABLE const QHash stateEvents() const; + const QHash& currentState() const; + + /// Get all state events in the room of a certain type. + /*! This method returns all known state events that have occured in + * the room of the given type. + */ + Q_INVOKABLE const QVector + stateEventsOfType(const QString& evtType) const; /// Get a state event with the given event type and state key /*! This is a typesafe overload that accepts a C++ event type instead of -- cgit v1.2.3 From c476df5a306383c164b974234c1574f76abb98b8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 27 Nov 2021 18:43:57 +0100 Subject: Add issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 29 +++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/change-request.md | 20 ++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/change-request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..637cb4b3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** + + +**To Reproduce** +Steps to reproduce the behaviour, and the description of the actual result: +1. +2. +3. + +**Expected behavior** + + +**Is it environment-specific?** + + - OS: [e.g. Windows 11] + - Version of the library [e.g. 0.6.10] + - Linkage: static, dynamic, any + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/change-request.md b/.github/ISSUE_TEMPLATE/change-request.md new file mode 100644 index 00000000..09deeaa6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/change-request.md @@ -0,0 +1,20 @@ +--- +name: Change request +about: Suggest an idea or improvement for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your request related to a problem?** + + +**Describe the suggested change/improvement you'd like** + + +**Describe alternatives you've considered** + + +**Additional context** + -- cgit v1.2.3 From 1903a8e26893a3f332ba39643aa8da5fdaf1536b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 27 Nov 2021 18:45:03 +0100 Subject: Delete ISSUE_TEMPLATE.md New issue templates reside under .github/ --- ISSUE_TEMPLATE.md | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 1e3f172b..00000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,48 +0,0 @@ - - -### Description - -Describe here the problem that you are experiencing, or the feature -you are requesting. - -### Steps to reproduce - -- For bugs, list the steps -- that reproduce the bug -- using hyphens as bullet points - -Describe how what happens differs from what you expected. - -libQuotient-based clients either have a log file or dump log -to the standard output. If you can identify any log snippets relevant -to your issue, please include those here (please be careful to remove -any personal or private data): - -### Version information - - - -- **The client application**: - -- **libQuotient version if you know it**: - -- **Qt version**: - -- **Install method**: - -- **Platform**: - -- cgit v1.2.3 From 0a46049ef26270933ecf6fea7395b03e6aee783e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 27 Nov 2021 10:17:51 +0100 Subject: Add SonarCloud analysis to CI --- .github/workflows/ci.yml | 3 +- .github/workflows/sonar.yml | 113 ++++++++++++++++++++++++++++++++++++++++++++ sonar-project.properties | 11 +++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/sonar.yml create mode 100644 sonar-project.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 714473b0..47e55421 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,7 @@ jobs: steps: - uses: actions/checkout@v2 with: + fetch-depth: 0 submodules: ${{ matrix.e2ee != '' }} - name: Cache Qt @@ -126,7 +127,7 @@ jobs: -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ >>$GITHUB_ENV echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with API files regeneration" >>$GITHUB_ENV - + - name: Initialize CodeQL tools if: env.CODEQL_ANALYSIS uses: github/codeql-action/init@v1 diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 00000000..76db59c9 --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,113 @@ +name: Sonar + +on: + push: + pull_request: + types: [opened, reopened] + +defaults: + run: + shell: bash + +jobs: + SonarCloud: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + qt-version: [ '5.12.10' ] + e2ee: [ '', 'e2ee' ] + update-api: [ '', 'update-api' ] + + env: + SONAR_SCANNER_VERSION: 4.6.2.2472 + SONAR_SERVER_URL: "https://sonarcloud.io" + BUILD_WRAPPER_OUT_DIR: build/sonar + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: ${{ matrix.e2ee != '' }} + + - name: Cache Qt + id: cache-qt + uses: actions/cache@v2 + with: + path: ${{ runner.workspace }}/Qt + key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache + + - name: Install Qt + uses: jurplel/install-qt-action@v2.11.1 + with: + version: ${{ matrix.qt-version }} +# arch: ${{ matrix.qt-arch }} # Only Windows needs that + cached: ${{ steps.cache-qt.outputs.cache-hit }} + + - name: Install Ninja + uses: seanmiddleditch/gha-setup-ninja@v3 + + - name: Setup build environment + run: | + echo "CC=gcc-10" >>$GITHUB_ENV + echo "CXX=g++-10" >>$GITHUB_ENV + mkdir -p $HOME/.sonar + echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ + -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV + cmake -E make_directory ${{ runner.workspace }}/build + + - name: Build and install olm + if: matrix.e2ee + run: | + cd .. + git clone https://gitlab.matrix.org/matrix-org/olm.git + cmake -S olm -B olm/build $CMAKE_ARGS + cmake --build olm/build --target install + + - name: Pull CS API and build GTAD + if: matrix.update-api + run: | + cd .. + git clone https://github.com/quotient-im/matrix-doc.git + git clone --recursive https://github.com/KitsuneRal/gtad.git + cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF + cmake --build gtad + echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ + -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ + >>$GITHUB_ENV + + - name: Download and set up Sonar Cloud tools + run: | + pushd $HOME/.sonar + curl -sSLo build-wrapper.zip $SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip + unzip -o build-wrapper.zip + echo "BUILD_WRAPPER=$HOME/.sonar/build-wrapper-linux-x86/build-wrapper-linux* --out-dir $BUILD_WRAPPER_OUT_DIR" >>$GITHUB_ENV + curl -sSLo sonar-scanner.zip \ + https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip + unzip -o sonar-scanner.zip + popd + + - name: Configure libQuotient + run: | + if [[ '${{ runner.os }}' == 'Windows' ]]; then + BIN_DIR=. + else + BIN_DIR=bin + fi + echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV + cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} + + - name: Regenerate API code + if: matrix.update-api + run: cmake --build build --target update-api + + - name: Build libQuotient + run: | + $BUILD_WRAPPER cmake --build build --target all + + - name: Run sonar-scanner + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + $HOME/.sonar/sonar-scanner*/bin/sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..96e98985 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,11 @@ +sonar.projectKey=quotient-im_libQuotient +sonar.organization=quotient-im + +# This is the name and version displayed in the SonarCloud UI. +sonar.projectName=libQuotient +sonar.projectVersion=0.7 + +sonar.sources=lib + +# Encoding of the source code. Default is default system encoding +#sonar.sourceEncoding=UTF-8 -- cgit v1.2.3 From 0425fd280e7eee7a4c9bcf18f79910f181322c42 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 27 Nov 2021 20:14:53 +0100 Subject: Event::content() -> contentPart() There's a clash between Event::content() (a template function) and RoomMessageEvent::content() (plain member). Out of these two, the name more fits to the RME's member function - strictly speaking, Event::content() retrieves a part of content, and so is renamed. In addition, contentPart() defaults to QJsonValue now, which is pretty intuitive (the function returns values from a JSON object) and allows to implement more elaborate logic such as if (const auto v = contentPart<>("key"_ls); v.isObject()) { // foo } else if (v.isString()) { // bar } else { // boo } --- lib/events/accountdataevents.h | 2 +- lib/events/callanswerevent.h | 4 ++-- lib/events/callcandidatesevent.h | 6 +++--- lib/events/callinviteevent.h | 4 ++-- lib/events/encryptedevent.h | 12 ++++++------ lib/events/event.h | 15 +++++++++++---- lib/events/reactionevent.h | 2 +- lib/events/redactionevent.h | 2 +- lib/events/roomcreateevent.cpp | 8 ++++---- lib/events/roomevent.h | 4 ++-- lib/events/roomkeyevent.h | 8 ++++---- lib/events/roommessageevent.cpp | 4 ++-- lib/events/roomtombstoneevent.cpp | 4 ++-- lib/events/stickerevent.cpp | 2 +- lib/events/typingevent.cpp | 2 +- lib/room.cpp | 4 ++-- 16 files changed, 45 insertions(+), 38 deletions(-) diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 8cea0ec8..7715d3b8 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -63,7 +63,7 @@ using TagsMap = QHash; {} \ auto _ContentKey() const \ { \ - return content(#_ContentKey##_ls); \ + return contentPart(#_ContentKey##_ls); \ } \ }; \ REGISTER_EVENT_TYPE(_Name) \ diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 6132cb44..4c01c941 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -19,11 +19,11 @@ public: int lifetime() const { - return content("lifetime"_ls); + return contentPart("lifetime"_ls); } // FIXME: Omittable<>? QString sdp() const { - return contentJson()["answer"_ls].toObject().value("sdp"_ls).toString(); + return contentPart("answer"_ls).value("sdp"_ls).toString(); } }; diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index c2ccac3b..74c38f2c 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -25,17 +25,17 @@ public: QJsonArray candidates() const { - return content("candidates"_ls); + return contentPart("candidates"_ls); } QString callId() const { - return content("call_id"); + return contentPart("call_id"); } int version() const { - return content("version"); + return contentPart("version"); } }; diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index d3454c4f..80b7d651 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -18,11 +18,11 @@ public: int lifetime() const { - return content("lifetime"_ls); + return contentPart("lifetime"_ls); } // FIXME: Omittable<>? QString sdp() const { - return contentJson()["offer"_ls].toObject().value("sdp"_ls).toString(); + return contentPart("offer"_ls).value("sdp"_ls).toString(); } }; diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 598829cd..de89a7c6 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -41,7 +41,7 @@ public: QString algorithm() const { - QString algo = content(AlgorithmKeyL); + QString algo = contentPart(AlgorithmKeyL); if (!SupportedAlgorithms.contains(algo)) { qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo << "is not supported"; @@ -50,17 +50,17 @@ public: } QByteArray ciphertext() const { - return content(CiphertextKeyL).toLatin1(); + return contentPart(CiphertextKeyL).toLatin1(); } QJsonObject ciphertext(const QString& identityKey) const { - return content(CiphertextKeyL).value(identityKey).toObject(); + return contentPart(CiphertextKeyL).value(identityKey).toObject(); } - QString senderKey() const { return content(SenderKeyKeyL); } + QString senderKey() const { return contentPart(SenderKeyKeyL); } /* device_id and session_id are required with Megolm */ - QString deviceId() const { return content(DeviceIdKeyL); } - QString sessionId() const { return content(SessionIdKeyL); } + QString deviceId() const { return contentPart(DeviceIdKeyL); } + QString sessionId() const { return contentPart(SessionIdKeyL); } }; REGISTER_EVENT_TYPE(EncryptedEvent) diff --git a/lib/events/event.h b/lib/events/event.h index 89efb7f8..024e45ef 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -224,18 +224,25 @@ public: const QJsonObject contentJson() const; const QJsonObject unsignedJson() const; - template - T content(const QString& key) const + template + const T contentPart(const QString& key) const { return fromJson(contentJson()[key]); } - template - T content(QLatin1String key) const + template + const T contentPart(QLatin1String key) const { return fromJson(contentJson()[key]); } + template + [[deprecated("Use contentPart() to get a part of the event content")]] // + T content(const QString& key) const + { + return contentPart(key); + } + friend QDebug operator<<(QDebug dbg, const Event& e) { QDebugStateSaver _dss { dbg }; diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index 777905f2..5a2b98c4 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -47,7 +47,7 @@ public: explicit ReactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {} EventRelation relation() const { - return content(QStringLiteral("m.relates_to")); + return contentPart("m.relates_to"_ls); } }; REGISTER_EVENT_TYPE(ReactionEvent) diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index ed560331..be20bf52 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -17,7 +17,7 @@ public: { return fullJson()["redacts"_ls].toString(); } - QString reason() const { return contentJson()["reason"_ls].toString(); } + QString reason() const { return contentPart("reason"_ls); } }; REGISTER_EVENT_TYPE(RedactionEvent) } // namespace Quotient diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index ff93041c..bb6de648 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -23,17 +23,17 @@ struct Quotient::JsonConverter { bool RoomCreateEvent::isFederated() const { - return fromJson(contentJson()["m.federate"_ls]); + return contentPart("m.federate"_ls); } QString RoomCreateEvent::version() const { - return fromJson(contentJson()["room_version"_ls]); + return contentPart("room_version"_ls); } RoomCreateEvent::Predecessor RoomCreateEvent::predecessor() const { - const auto predJson = contentJson()["predecessor"_ls].toObject(); + const auto predJson = contentPart("predecessor"_ls); return { fromJson(predJson[RoomIdKeyL]), fromJson(predJson[EventIdKeyL]) }; } @@ -45,5 +45,5 @@ bool RoomCreateEvent::isUpgrade() const RoomType RoomCreateEvent::roomType() const { - return fromJson(contentJson()["type"_ls]); + return contentPart("type"_ls); } diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index a6cd84ca..7f13f6f2 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -78,8 +78,8 @@ public: ~CallEventBase() override = default; bool isCallEvent() const override { return true; } - QString callId() const { return content("call_id"_ls); } - int version() const { return content("version"_ls); } + QString callId() const { return contentPart("call_id"_ls); } + int version() const { return contentPart("version"_ls); } }; } // namespace Quotient Q_DECLARE_METATYPE(Quotient::RoomEvent*) diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 14e80324..d021fbec 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -13,10 +13,10 @@ public: explicit RoomKeyEvent(const QJsonObject& obj); - QString algorithm() const { return content("algorithm"_ls); } - QString roomId() const { return content(RoomIdKeyL); } - QString sessionId() const { return content("session_id"_ls); } - QString sessionKey() const { return content("session_key"_ls); } + QString algorithm() const { return contentPart("algorithm"_ls); } + QString roomId() const { return contentPart(RoomIdKeyL); } + QString sessionId() const { return contentPart("session_id"_ls); } + QString sessionKey() const { return contentPart("session_key"_ls); } }; REGISTER_EVENT_TYPE(RoomKeyEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 9b46594e..2b7b4166 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -204,12 +204,12 @@ RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const QString RoomMessageEvent::rawMsgtype() const { - return contentJson()[MsgTypeKeyL].toString(); + return contentPart(MsgTypeKeyL); } QString RoomMessageEvent::plainBody() const { - return contentJson()[BodyKeyL].toString(); + return contentPart(BodyKeyL); } QMimeType RoomMessageEvent::mimeType() const diff --git a/lib/events/roomtombstoneevent.cpp b/lib/events/roomtombstoneevent.cpp index 080d269c..2c3492d6 100644 --- a/lib/events/roomtombstoneevent.cpp +++ b/lib/events/roomtombstoneevent.cpp @@ -7,10 +7,10 @@ using namespace Quotient; QString RoomTombstoneEvent::serverMessage() const { - return fromJson(contentJson()["body"_ls]); + return contentPart("body"_ls); } QString RoomTombstoneEvent::successorRoomId() const { - return fromJson(contentJson()["replacement_room"_ls]); + return contentPart("replacement_room"_ls); } diff --git a/lib/events/stickerevent.cpp b/lib/events/stickerevent.cpp index ea4dff3f..628fd154 100644 --- a/lib/events/stickerevent.cpp +++ b/lib/events/stickerevent.cpp @@ -12,7 +12,7 @@ StickerEvent::StickerEvent(const QJsonObject &obj) QString StickerEvent::body() const { - return content("body"_ls); + return contentPart("body"_ls); } const EventContent::ImageContent &StickerEvent::image() const diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp index e97e978f..7e5d7ee6 100644 --- a/lib/events/typingevent.cpp +++ b/lib/events/typingevent.cpp @@ -7,5 +7,5 @@ using namespace Quotient; QStringList TypingEvent::users() const { - return fromJson(contentJson()["user_ids"_ls]); + return contentPart("user_ids"_ls); } diff --git a/lib/room.cpp b/lib/room.cpp index fac24e5e..8f430ccd 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2507,8 +2507,8 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) RoomEventPtr makeReplaced(const RoomEvent& target, const RoomMessageEvent& replacement) { - const auto &targetReply = target.contentJson()["m.relates_to"].toObject(); - auto newContent = replacement.contentJson().value("m.new_content"_ls).toObject(); + const auto& targetReply = target.contentPart("m.relates_to"); + auto newContent = replacement.contentPart("m.new_content"_ls); if (!targetReply.empty()) { newContent["m.relates_to"] = targetReply; } -- cgit v1.2.3 From 102ed8b661e6ca66d754de0d102713d930fb224d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 27 Nov 2021 22:29:11 +0100 Subject: Code cleanup --- lib/room.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 8f430ccd..6e6d7f11 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -228,12 +228,11 @@ public: QVector stateEventsOfType(const QString& evtType) const { - QVector vals = QVector(); - for (const auto* val : currentState) { - if (val->matrixType() == evtType) { - vals.append(val); - } - } + auto vals = QVector(); + for (auto it = currentState.cbegin(); it != currentState.cend(); ++it) + if (it.key().first == evtType) + vals.append(it.value()); + return vals; } -- cgit v1.2.3 From ce4e878040946b963b30c4768847e2be24dd77fa Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 27 Nov 2021 22:53:02 +0100 Subject: basicEventJson(): dismiss with the template Given that QJsonObject only accepts QStrings in the list constructor, the template is useless cruft. --- lib/events/event.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index 024e45ef..733fadd8 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -48,11 +48,10 @@ static const auto PrevContentKeyL = "prev_content"_ls; static const auto StateKeyKeyL = "state_key"_ls; /// Make a minimal correct Matrix event JSON -template -inline QJsonObject basicEventJson(StrT matrixType, const QJsonObject& content) +inline QJsonObject basicEventJson(const QString& matrixType, + const QJsonObject& content) { - return { { TypeKey, std::forward(matrixType) }, - { ContentKey, content } }; + return { { TypeKey, matrixType }, { ContentKey, content } }; } // === Event types and event types registry === -- cgit v1.2.3 From ab91944cc71a72699d0168b7c472326e59319477 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 11:19:08 +0100 Subject: Event::unsignedPart() Similar to contentPart() - apparently there are enough places across the code that would benefit from it. --- lib/events/event.h | 21 +++++++++++---------- lib/events/roomevent.cpp | 10 +++++----- lib/events/stateevent.cpp | 10 +++++----- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index 733fadd8..e45fecca 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -221,18 +221,11 @@ public: // different types, we're implementing it per-event type. const QJsonObject contentJson() const; - const QJsonObject unsignedJson() const; - template - const T contentPart(const QString& key) const + template + const T contentPart(KeyT&& key) const { - return fromJson(contentJson()[key]); - } - - template - const T contentPart(QLatin1String key) const - { - return fromJson(contentJson()[key]); + return fromJson(contentJson()[std::forward(key)]); } template @@ -242,6 +235,14 @@ public: return contentPart(key); } + const QJsonObject unsignedJson() const; + + template + const T unsignedPart(KeyT&& key) const + { + return fromJson(unsignedJson()[std::forward(key)]); + } + friend QDebug operator<<(QDebug dbg, const Event& e) { QDebugStateSaver _dss { dbg }; diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 4fec9d2b..fb921af6 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -19,7 +19,7 @@ RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, RoomEvent::RoomEvent(Type type, const QJsonObject& json) : Event(type, json) { - if (const auto redaction = unsignedJson()[RedactedCauseKeyL]; + if (const auto redaction = unsignedPart(RedactedCauseKeyL); redaction.isObject()) _redactedBecause = makeEvent(redaction.toObject()); } @@ -45,14 +45,14 @@ QString RoomEvent::senderId() const bool RoomEvent::isReplaced() const { - return unsignedJson()["m.relations"_ls].toObject().contains("m.replace"); + return unsignedPart("m.relations"_ls).contains("m.replace"); } QString RoomEvent::replacedBy() const { // clang-format off - return unsignedJson()["m.relations"_ls].toObject() - .value("m.replace").toObject() + return unsignedPart("m.relations"_ls) + .value("m.replace"_ls).toObject() .value(EventIdKeyL).toString(); // clang-format on } @@ -64,7 +64,7 @@ QString RoomEvent::redactionReason() const QString RoomEvent::transactionId() const { - return unsignedJson()["transaction_id"_ls].toString(); + return unsignedPart("transaction_id"_ls); } QString RoomEvent::stateKey() const diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 42fc9054..efe011a0 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -28,22 +28,22 @@ StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, bool StateEventBase::repeatsState() const { - const auto prevContentJson = unsignedJson().value(PrevContentKeyL); + const auto prevContentJson = unsignedPart(PrevContentKeyL); return fullJson().value(ContentKeyL) == prevContentJson; } QString StateEventBase::replacedState() const { - return unsignedJson().value("replaces_state"_ls).toString(); + return unsignedPart("replaces_state"_ls); } void StateEventBase::dumpTo(QDebug dbg) const { if (!stateKey().isEmpty()) dbg << '<' << stateKey() << "> "; - if (unsignedJson().contains(PrevContentKeyL)) - dbg << QJsonDocument(unsignedJson()[PrevContentKeyL].toObject()) - .toJson(QJsonDocument::Compact) + if (const auto prevContentJson = unsignedPart(PrevContentKeyL); + !prevContentJson.isEmpty()) + dbg << QJsonDocument(prevContentJson).toJson(QJsonDocument::Compact) << " -> "; RoomEvent::dumpTo(dbg); } -- cgit v1.2.3 From e12a7aac030aaf53122da4b1bd55f3a97325f624 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 15:44:41 +0100 Subject: Fix CI breakage caused by the previous commit --- lib/converters.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/converters.h b/lib/converters.h index 8ec0fa81..f6d56527 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -62,6 +62,8 @@ inline T fromJson(const QJsonValue& jv) return JsonConverter::load(jv); } +inline QJsonValue fromJson(const QJsonValue& jv) { return jv; } + template inline T fromJson(const QJsonDocument& jd) { -- cgit v1.2.3 From 080af15c8aa7cc51f97c924ba4678083b7f8da8f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 16:04:42 +0100 Subject: One more small thing to actually fix CI breakage It's might look weird; but without making fromJson() a specialisation it becomes an overload next to an implicit specialisation of the template function defined just above, and then loses to that specialisation because it (also) has the perfect match. (would be great if the compiler shaded the implicit specialisation in such cases - alas it's not how the standard works.) --- lib/converters.h | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/converters.h b/lib/converters.h index f6d56527..84fab798 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -62,6 +62,7 @@ inline T fromJson(const QJsonValue& jv) return JsonConverter::load(jv); } +template<> inline QJsonValue fromJson(const QJsonValue& jv) { return jv; } template -- cgit v1.2.3 From ca1ba482b50c41425bd0a540c7bb68406d10e552 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 16:42:41 +0100 Subject: Don't std::move when the callee doesn't support it In both fixed cases the callee accepts a const reference, which makes std::move() useless. Static analyzers apparently missed them because the cases are inside a macro. --- lib/events/accountdataevents.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 7715d3b8..e5101d20 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -55,11 +55,11 @@ using TagsMap = QHash; public: \ using content_type = _ContentType; \ DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - explicit _Name(QJsonObject obj) : Event(typeId(), std::move(obj)) {} \ - explicit _Name(_ContentType content) \ + explicit _Name(const QJsonObject& obj) : Event(typeId(), obj) {} \ + explicit _Name(const content_type& content) \ : Event(typeId(), matrixTypeId(), \ - QJsonObject { { QStringLiteral(#_ContentKey), \ - toJson(std::move(content)) } }) \ + QJsonObject { \ + { QStringLiteral(#_ContentKey), toJson(content) } }) \ {} \ auto _ContentKey() const \ { \ -- cgit v1.2.3 From 8a769cddcd1a063dd9400518ff65c0b1f1aec1b4 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 16:53:51 +0100 Subject: Comment on const return types in event.h Proper linters recognise that the returned types are not primitive, while people might still be confused a bit. --- lib/events/event.cpp | 3 --- lib/events/event.h | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 3d66ab55..96be717c 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -46,14 +46,11 @@ QString Event::matrixType() const { return fullJson()[TypeKeyL].toString(); } QByteArray Event::originalJson() const { return QJsonDocument(_json).toJson(); } -// On const below: this is to catch accidental attempts to change event JSON -// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::contentJson() const { return fullJson()[ContentKeyL].toObject(); } -// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::unsignedJson() const { return fullJson()[UnsignedKeyL].toObject(); diff --git a/lib/events/event.h b/lib/events/event.h index e45fecca..998a386c 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -220,6 +220,9 @@ public: // a "content" object; but since its structure is different for // different types, we're implementing it per-event type. + // NB: const return types below are meant to catch accidental attempts + // to change event JSON (e.g., consider contentJson()["inexistentKey"]). + const QJsonObject contentJson() const; template -- cgit v1.2.3 From efd07e0f728d08ff76975c7b053104ab05c48799 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 16:54:52 +0100 Subject: CMakeLists: drop obsolete -W from the warnings list Turns out it's been deprecated by -Wextra since before Quotient existed. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaf662cc..aa3b9c98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ if (MSVC) /wd4464 /wd4505 /wd4514 /wd4571 /wd4619 /wd4623 /wd4625 /wd4626 /wd4706 /wd4710 /wd4774 /wd4820 /wd4946 /wd5026 /wd5027) else() - foreach (FLAG Wall W Wpedantic Wextra Werror=return-type Wno-unused-parameter + foreach (FLAG Wall Wpedantic Wextra Werror=return-type Wno-unused-parameter Wno-gnu-zero-variadic-macro-arguments fvisibility-inlines-hidden) CHECK_CXX_COMPILER_FLAG("-${FLAG}" COMPILER_${FLAG}_SUPPORTED) if ( COMPILER_${FLAG}_SUPPORTED AND -- cgit v1.2.3 From 121c2a08b25d79bcfd09c050d72c6ea7cc7720f9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 17:21:01 +0100 Subject: Simplify converters.* There was a lot of excess redirection in fromJson() and toJson() with most of JsonConverter<> specialisations being unnecessary boilerplate. These have been replaced by overloads for toJson() and explicit specialisations for fromJson() wherever possible without breaking the conversion logic. --- lib/converters.cpp | 11 +++-- lib/converters.h | 126 +++++++++++++++++++++++++---------------------------- 2 files changed, 64 insertions(+), 73 deletions(-) diff --git a/lib/converters.cpp b/lib/converters.cpp index 4338e8ed..444ca4f6 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -5,24 +5,23 @@ #include -using namespace Quotient; - -QJsonValue JsonConverter::dump(const QVariant& v) +QJsonValue Quotient::JsonConverter::dump(const QVariant& v) { return QJsonValue::fromVariant(v); } -QVariant JsonConverter::load(const QJsonValue& jv) +QVariant Quotient::JsonConverter::load(const QJsonValue& jv) { return jv.toVariant(); } -QJsonObject JsonConverter::dump(const QVariantHash& vh) +QJsonObject Quotient::toJson(const QVariantHash& vh) { return QJsonObject::fromVariantHash(vh); } -QVariantHash JsonConverter::load(const QJsonValue& jv) +template<> +QVariantHash Quotient::fromJson(const QJsonValue& jv) { return jv.toObject().toVariantHash(); } diff --git a/lib/converters.h b/lib/converters.h index 84fab798..90349019 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -24,6 +24,21 @@ struct JsonObjectConverter { static void fillFrom(const QJsonObject& jo, T& pod) { pod = T(jo); } }; +//! \brief The switchboard for extra conversion algorithms behind from/toJson +//! +//! This template is mainly intended for partial conversion specialisations +//! since from/toJson are functions and cannot be partially specialised; +//! another case for JsonConverter is to insulate types that can be constructed +//! from basic types - namely, QVariant and QUrl can be directly constructed +//! from QString and having an overload or specialisation for those leads to +//! ambiguity between these and QJsonValue. For trivial (converting +//! QJsonObject/QJsonValue) and most simple cases such as primitive types or +//! QString this class is not needed. +//! +//! Do NOT call the functions of this class directly unless you know what you're +//! doing; and do not try to specialise basic things unless you're really sure +//! that they are not supported and it's not feasible to support those by means +//! of overloading toJson() and specialising fromJson(). template struct JsonConverter { static QJsonObject dump(const T& pod) @@ -42,13 +57,17 @@ struct JsonConverter { static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } }; -template +template >> inline auto toJson(const T& pod) +// -> can return anything from which QJsonValue or, in some cases, QJsonDocument +// is constructible { return JsonConverter::dump(pod); } inline auto toJson(const QJsonObject& jo) { return jo; } +inline auto toJson(const QJsonValue& jv) { return jv; } template inline void fillJson(QJsonObject& json, const T& data) @@ -98,80 +117,64 @@ inline void fillFromJson(const QJsonValue& jv, T& pod) // JsonConverter<> specialisations -template -struct TrivialJsonDumper { - // Works for: QJsonValue (and all things it can consume), - // QJsonObject, QJsonArray - static auto dump(const T& val) { return val; } -}; +template<> +inline bool fromJson(const QJsonValue& jv) { return jv.toBool(); } template <> -struct JsonConverter : public TrivialJsonDumper { - static auto load(const QJsonValue& jv) { return jv.toBool(); } -}; +inline int fromJson(const QJsonValue& jv) { return jv.toInt(); } template <> -struct JsonConverter : public TrivialJsonDumper { - static auto load(const QJsonValue& jv) { return jv.toInt(); } -}; +inline double fromJson(const QJsonValue& jv) { return jv.toDouble(); } template <> -struct JsonConverter : public TrivialJsonDumper { - static auto load(const QJsonValue& jv) { return jv.toDouble(); } -}; +inline float fromJson(const QJsonValue& jv) { return float(jv.toDouble()); } template <> -struct JsonConverter : public TrivialJsonDumper { - static auto load(const QJsonValue& jv) { return float(jv.toDouble()); } -}; +inline qint64 fromJson(const QJsonValue& jv) { return qint64(jv.toDouble()); } template <> -struct JsonConverter : public TrivialJsonDumper { - static auto load(const QJsonValue& jv) { return qint64(jv.toDouble()); } -}; +inline QString fromJson(const QJsonValue& jv) { return jv.toString(); } template <> -struct JsonConverter : public TrivialJsonDumper { - static auto load(const QJsonValue& jv) { return jv.toString(); } -}; +inline QJsonArray fromJson(const QJsonValue& jv) { return jv.toArray(); } template <> -struct JsonConverter { - static auto dump(const QDateTime& val) - { - return val.isValid() ? val.toMSecsSinceEpoch() : QJsonValue(); - } - static auto load(const QJsonValue& jv) - { - return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); - } -}; +inline QJsonArray fromJson(const QJsonDocument& jd) { return jd.array(); } +inline QJsonValue toJson(const QDateTime& val) +{ + return val.isValid() ? val.toMSecsSinceEpoch() : QJsonValue(); +} template <> -struct JsonConverter { - static auto dump(const QDate& val) { - return toJson( +inline QDateTime fromJson(const QJsonValue& jv) +{ + return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); +} + +inline QJsonValue toJson(const QDate& val) { + return toJson( #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - QDateTime(val) + QDateTime(val) #else - val.startOfDay() + val.startOfDay() #endif - ); - } - static auto load(const QJsonValue& jv) - { - return fromJson(jv).date(); - } -}; + ); +} +template <> +inline QDate fromJson(const QJsonValue& jv) +{ + return fromJson(jv).date(); +} + +// Insulate QVariant and QUrl conversions into JsonConverter so that they don't +// interfere with toJson(const QJsonValue&) over QString, since both types are +// constructible from QString (even if QUrl requires explicit construction). template <> struct JsonConverter { static auto load(const QJsonValue& jv) { - // QT_NO_URL_CAST_FROM_STRING makes this a bit more verbose - QUrl url; - url.setUrl(jv.toString()); - return url; + return QUrl(jv.toString()); } static auto dump(const QUrl& url) { @@ -179,11 +182,6 @@ struct JsonConverter { } }; -template <> -struct JsonConverter : public TrivialJsonDumper { - static auto load(const QJsonValue& jv) { return jv.toArray(); } -}; - template <> struct JsonConverter { static QJsonValue dump(const QVariant& v); @@ -206,15 +204,11 @@ struct JsonConverter> { template struct JsonArrayConverter { - static void dumpTo(QJsonArray& ar, const VectorT& vals) - { - for (const auto& v : vals) - ar.push_back(toJson(v)); - } static auto dump(const VectorT& vals) { QJsonArray ja; - dumpTo(ja, vals); + for (const auto& v : vals) + ja.push_back(toJson(v)); return ja; } static auto load(const QJsonArray& ja) @@ -254,7 +248,7 @@ struct JsonObjectConverter> { static void dumpTo(QJsonObject& json, const QSet& s) { for (const auto& e : s) - json.insert(toJson(e), QJsonObject {}); + json.insert(e, QJsonObject {}); } static void fillFrom(const QJsonObject& json, QSet& s) { @@ -287,11 +281,9 @@ template struct JsonObjectConverter> : public HashMapFromJson> {}; +QJsonObject toJson(const QVariantHash& vh); template <> -struct JsonConverter { - static QJsonObject dump(const QVariantHash& vh); - static QVariantHash load(const QJsonValue& jv); -}; +QVariantHash fromJson(const QJsonValue& jv); // Conditional insertion into a QJsonObject -- cgit v1.2.3 From d7f96a88710235c7c8648a7216bb5ad7d779889a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 29 Nov 2021 10:58:07 +0100 Subject: Track room stubbed state size in logs --- lib/room.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/room.cpp b/lib/room.cpp index 6e6d7f11..4d60570d 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -217,6 +217,7 @@ public: evtKey.second)); qCDebug(STATE) << "A new stub event created for key {" << evtKey.first << evtKey.second << "}"; + qCDebug(STATE) << "Stubbed state size:" << stubbedState.size(); } evt = stubbedState[evtKey].get(); Q_ASSERT(evt); -- cgit v1.2.3 From ae0ad49f36e8ba5983839581302ed16ddbd75d5f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 14:04:49 +0100 Subject: visit(Event, ...) -> switchOnType() It has not much to do with the Visitor design pattern; also, std::visit() has different conventions on the order of parameters. --- lib/connection.cpp | 4 ++-- lib/events/event.h | 58 ++++++++++++++++++++++++++++++----------------------- lib/room.cpp | 9 +++++---- quotest/quotest.cpp | 4 ++-- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index e65fdac4..25219def 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -659,7 +659,7 @@ void Connection::Private::consumeAccountData(Events&& accountDataEvents) // After running this loop, the account data events not saved in // accountData (see the end of the loop body) are auto-cleaned away for (auto&& eventPtr: accountDataEvents) { - visit(*eventPtr, + switchOnType(*eventPtr, [this](const DirectChatEvent& dce) { // https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); @@ -760,7 +760,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) << ee.senderId(); // encryptionManager->updateDeviceKeys(); - visit(*sessionDecryptMessage(ee), + switchOnType(*sessionDecryptMessage(ee), [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); diff --git a/lib/events/event.h b/lib/events/event.h index 998a386c..ce737280 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -290,7 +290,7 @@ using Events = EventsArray; } \ // End of macro -// === is<>(), eventCast<>() and visit<>() === +// === is<>(), eventCast<>() and switchOnType<>() === template inline bool is(const Event& e) @@ -312,12 +312,12 @@ inline auto eventCast(const BasePtrT& eptr) : nullptr; } -// A single generic catch-all visitor +// A trivial generic catch-all "switch" template -inline auto visit(const BaseEventT& event, FnT&& visitor) - -> decltype(visitor(event)) +inline auto switchOnType(const BaseEventT& event, FnT&& fn) + -> decltype(fn(event)) { - return visitor(event); + return fn(event); } namespace _impl { @@ -328,52 +328,60 @@ namespace _impl { && !std::is_same_v>>; } -// A single type-specific void visitor +// A trivial type-specific "switch" for a void function template -inline auto visit(const BaseT& event, FnT&& visitor) +inline auto switchOnType(const BaseT& event, FnT&& fn) -> std::enable_if_t<_impl::needs_downcast && std::is_void_v>> { using event_type = fn_arg_t; if (is>(event)) - visitor(static_cast(event)); + fn(static_cast(event)); } -// A single type-specific non-void visitor with an optional default value -// non-voidness is guarded by defaultValue type +// A trivial type-specific "switch" for non-void functions with an optional +// default value; non-voidness is guarded by defaultValue type template -inline auto visit(const BaseT& event, FnT&& visitor, - fn_return_t&& defaultValue = {}) +inline auto switchOnType(const BaseT& event, FnT&& fn, + fn_return_t&& defaultValue = {}) -> std::enable_if_t<_impl::needs_downcast, fn_return_t> { using event_type = fn_arg_t; if (is>(event)) - return visitor(static_cast(event)); - return std::forward>(defaultValue); + return fn(static_cast(event)); + return std::move(defaultValue); } -// A chain of 2 or more visitors +// A switch for a chain of 2 or more functions template -inline std::common_type_t, fn_return_t> visit( - const BaseT& event, FnT1&& visitor1, FnT2&& visitor2, - FnTs&&... visitors) +inline std::common_type_t, fn_return_t> +switchOnType(const BaseT& event, FnT1&& fn1, FnT2&& fn2, FnTs&&... fns) { using event_type1 = fn_arg_t; if (is>(event)) - return visitor1(static_cast(event)); - return visit(event, std::forward(visitor2), - std::forward(visitors)...); + return fn1(static_cast(event)); + return switchOnType(event, std::forward(fn2), + std::forward(fns)...); } -// A facility overload that calls void-returning visit() on each event +template +[[deprecated("The new name for visit() is switchOnType()")]] // +inline std::common_type_t...> +visit(const BaseT& event, FnTs&&... fns) +{ + return switchOnType(event, std::forward(fns)...); +} + + // A facility overload that calls void-returning switchOnType() on each event // over a range of event pointers +// TODO: replace with ranges::for_each once all standard libraries have it template -inline auto visitEach(RangeT&& events, FnTs&&... visitors) +inline auto visitEach(RangeT&& events, FnTs&&... fns) -> std::enable_if_t(visitors)...))>> + decltype(switchOnType(**begin(events), std::forward(fns)...))>> { for (auto&& evtPtr: events) - visit(*evtPtr, std::forward(visitors)...); + switchOnType(*evtPtr, std::forward(fns)...); } } // namespace Quotient Q_DECLARE_METATYPE(Quotient::Event*) diff --git a/lib/room.cpp b/lib/room.cpp index 4d60570d..b3438e08 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2779,7 +2779,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) auto& curStateEvent = d->currentState[{ e.matrixType(), e.stateKey() }]; // Prepare for the state change // clang-format off - const bool proceed = visit(e + const bool proceed = switchOnType(e , [this, curStateEvent](const RoomMemberEvent& rme) { // clang-format on auto* oldRme = static_cast(curStateEvent); @@ -2877,7 +2877,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // Update internal structures as per the change and work out the return value // clang-format off - const auto result = visit(e + const auto result = switchOnType(e , [] (const RoomNameEvent&) { return Change::Name; } @@ -2885,7 +2885,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // clang-format on setObjectName(cae.alias().isEmpty() ? d->id : cae.alias()); const auto* oldCae = - static_cast(oldStateEvent); + static_cast(oldStateEvent); QStringList previousAltAliases {}; if (oldCae) { previousAltAliases = oldCae->altAliases(); @@ -2897,7 +2897,8 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) if (!cae.alias().isEmpty()) newAliases.push_front(cae.alias()); - connection()->updateRoomAliases(id(), previousAltAliases, newAliases); + connection()->updateRoomAliases(id(), previousAltAliases, + newAliases); return Change::Aliases; // clang-format off } diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index d006c7fb..764d5dfd 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -498,8 +498,8 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, clog << "File event " << txnId.toStdString() << " arrived in the timeline" << endl; - // This part tests visit() - return visit( + // This part tests switchOnType() + return switchOnType( *evt, [&](const RoomMessageEvent& e) { // TODO: check #366 once #368 is implemented -- cgit v1.2.3 From dc08fb9dfd474023084de9ce86f29f177ca52fdc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 15:24:44 +0100 Subject: Improve function_traits<>; split out from util.* Quotient::function_traits<> did not support member functions in a proper way (i.e. the way std::invoke_result<> treats them, with the function's owning class represented as the first parameter). Now that I gained the skill and understanding in function_traits<> somewhat wicked machinery, I could properly support member functions. Overloads and generic lambdas are not supported but maybe we'll get to those one day. --- CMakeLists.txt | 2 ++ lib/events/event.h | 1 + lib/function_traits.cpp | 53 +++++++++++++++++++++++++++ lib/function_traits.h | 93 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/qt_connection_util.h | 2 +- lib/util.cpp | 32 ----------------- lib/util.h | 52 --------------------------- 7 files changed, 150 insertions(+), 85 deletions(-) create mode 100644 lib/function_traits.cpp create mode 100644 lib/function_traits.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aa3b9c98..ca92699c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,8 @@ endif () # Set up source files list(APPEND lib_SRCS lib/quotient_common.h + lib/function_traits.h + lib/function_traits.cpp lib/networkaccessmanager.cpp lib/connectiondata.cpp lib/connection.cpp diff --git a/lib/events/event.h b/lib/events/event.h index ce737280..4d4bb16b 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -5,6 +5,7 @@ #include "converters.h" #include "logging.h" +#include "function_traits.h" namespace Quotient { // === event_ptr_tt<> and type casting facilities === diff --git a/lib/function_traits.cpp b/lib/function_traits.cpp new file mode 100644 index 00000000..4ff427e4 --- /dev/null +++ b/lib/function_traits.cpp @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "function_traits.h" + +// Tests for function_traits<> + +using namespace Quotient; + +int f_(); +static_assert(std::is_same, int>::value, + "Test fn_return_t<>"); + +void f1_(int, float); +static_assert(std::is_same, float>::value, + "Test fn_arg_t<>"); + +struct Fo { + int operator()(); + static constexpr auto l = [] { return 0.0f; }; + bool memFn(); + void constMemFn() const&; + double field; + const double field2; +}; +static_assert(std::is_same_v, int>, + "Test return type of function object"); +static_assert(std::is_same_v, float>, + "Test return type of lambda"); +static_assert(std::is_same_v, Fo>, + "Test first argument type of member function"); +static_assert(std::is_same_v, bool>, + "Test return type of member function"); +static_assert(std::is_same_v, const Fo&>, + "Test first argument type of const member function"); +static_assert(std::is_void_v>, + "Test return type of const member function"); +static_assert(std::is_same_v, double&>, + "Test return type of a class member"); +static_assert(std::is_same_v, const double&>, + "Test return type of a const class member"); + +struct Fo1 { + void operator()(int); +}; +static_assert(std::is_same, int>(), + "Test fn_arg_t defaulting to first argument"); + +template +static void ft(const std::vector&); +static_assert( + std::is_same)>, const std::vector&>(), + "Test function templates"); diff --git a/lib/function_traits.h b/lib/function_traits.h new file mode 100644 index 00000000..83b8e425 --- /dev/null +++ b/lib/function_traits.h @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include + +namespace Quotient { + +namespace _impl { + template + struct fn_traits {}; +} + +/// Determine traits of an arbitrary function/lambda/functor +/*! + * Doesn't work with generic lambdas and function objects that have + * operator() overloaded. + * \sa + * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765 + */ +template +struct function_traits + : public _impl::fn_traits> {}; + +// Specialisation for a function +template +struct function_traits { + using return_type = ReturnT; + using arg_types = std::tuple; + // See also the comment for wrap_in_function() in qt_connection_util.h + using function_type = std::function; +}; + +namespace _impl { + template + struct fn_object_traits; + + // Specialisation for a lambda function + template + struct fn_object_traits + : function_traits {}; + + // Specialisation for a const lambda function + template + struct fn_object_traits + : function_traits {}; + + // Specialisation for function objects with (non-overloaded) operator() + // (this includes non-generic lambdas) + template + struct fn_traits + : public fn_object_traits {}; + + // Specialisation for a member function in a non-functor class + template + struct fn_traits + : function_traits {}; + + // Specialisation for a const member function + template + struct fn_traits + : function_traits {}; + + // Specialisation for a constref member function + template + struct fn_traits + : function_traits {}; + + // Specialisation for a prvalue member function + template + struct fn_traits + : function_traits {}; + + // Specialisation for a pointer-to-member + template + struct fn_traits + : function_traits {}; + + // Specialisation for a const pointer-to-member + template + struct fn_traits + : function_traits {}; +} // namespace _impl + +template +using fn_return_t = typename function_traits::return_type; + +template +using fn_arg_t = + std::tuple_element_t::arg_types>; + +} // namespace Quotient diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 46294499..86593cc8 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -3,7 +3,7 @@ #pragma once -#include "util.h" +#include "function_traits.h" #include diff --git a/lib/util.cpp b/lib/util.cpp index 2dfb09a6..03ebf325 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -135,35 +135,3 @@ int Quotient::patchVersion() { return Quotient_VERSION_PATCH; } - -// Tests for function_traits<> - -using namespace Quotient; - -int f_(); -static_assert(std::is_same, int>::value, - "Test fn_return_t<>"); - -void f1_(int, QString); -static_assert(std::is_same, QString>::value, - "Test fn_arg_t<>"); - -struct Fo { - int operator()(); - static constexpr auto l = [] { return 0.0f; }; -}; -static_assert(std::is_same, int>::value, - "Test return type of function object"); -static_assert(std::is_same, float>::value, - "Test return type of lambda"); - -struct Fo1 { - void operator()(int); -}; -static_assert(std::is_same, int>(), - "Test fn_arg_t defaulting to first argument"); - -template -static QString ft(T&&); -static_assert(std::is_same)>, QString&&>(), - "Test function templates"); diff --git a/lib/util.h b/lib/util.h index 13efb94b..97f0ecbc 100644 --- a/lib/util.h +++ b/lib/util.h @@ -189,58 +189,6 @@ inline auto merge(T1& lhs, const Omittable& rhs) return true; } -namespace _impl { - template - struct fn_traits {}; -} - -/// Determine traits of an arbitrary function/lambda/functor -/*! - * Doesn't work with generic lambdas and function objects that have - * operator() overloaded. - * \sa - * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765 - */ -template -struct function_traits - : public _impl::fn_traits> {}; - -// Specialisation for a function -template -struct function_traits { - using return_type = ReturnT; - using arg_types = std::tuple; - // Doesn't (and there's no plan to make it) work for "classic" - // member functions (i.e. outside of functors). - // See also the comment for wrap_in_function() below - using function_type = std::function; -}; - -namespace _impl { - // Specialisation for function objects with (non-overloaded) operator() - // (this includes non-generic lambdas) - template - struct fn_traits - : public fn_traits {}; - - // Specialisation for a member function - template - struct fn_traits - : function_traits {}; - - // Specialisation for a const member function - template - struct fn_traits - : function_traits {}; -} // namespace _impl - -template -using fn_return_t = typename function_traits::return_type; - -template -using fn_arg_t = - std::tuple_element_t::arg_types>; - inline constexpr auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); -- cgit v1.2.3 From c3a0515ac2ead7b2d0653be3517836cd31be480e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 21:18:30 +0100 Subject: Cleanup on Sonar issues --- lib/converters.h | 4 ++-- lib/function_traits.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index 90349019..9c3d5749 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -27,8 +27,8 @@ struct JsonObjectConverter { //! \brief The switchboard for extra conversion algorithms behind from/toJson //! //! This template is mainly intended for partial conversion specialisations -//! since from/toJson are functions and cannot be partially specialised; -//! another case for JsonConverter is to insulate types that can be constructed +//! since from/toJson are functions and cannot be partially specialised. +//! Another case for JsonConverter is to insulate types that can be constructed //! from basic types - namely, QVariant and QUrl can be directly constructed //! from QString and having an overload or specialisation for those leads to //! ambiguity between these and QJsonValue. For trivial (converting diff --git a/lib/function_traits.cpp b/lib/function_traits.cpp index 4ff427e4..20bcf30e 100644 --- a/lib/function_traits.cpp +++ b/lib/function_traits.cpp @@ -8,11 +8,11 @@ using namespace Quotient; int f_(); -static_assert(std::is_same, int>::value, +static_assert(std::is_same_v, int>, "Test fn_return_t<>"); void f1_(int, float); -static_assert(std::is_same, float>::value, +static_assert(std::is_same_v, float>, "Test fn_arg_t<>"); struct Fo { @@ -43,7 +43,7 @@ static_assert(std::is_same_v, const double&>, struct Fo1 { void operator()(int); }; -static_assert(std::is_same, int>(), +static_assert(std::is_same_v, int>, "Test fn_arg_t defaulting to first argument"); template -- cgit v1.2.3 From 3b5d5f65c5eb10997362f9f9509fe2161eaa335c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Dec 2021 15:07:22 +0100 Subject: CMakeLists.txt: add .h files to sources This is driven by the change in the way Qt Creator 6 works with CMake (see https://www.qt.io/blog/qt-creator-6-cmake-update) that basically requires you to explicitly add header files as source files. While this obviously added to the size of the source files list, it also drove dropping the repeated file(GLOB_RECURSE ... CONFIGURE_DEPENDS) call which added a considerable speedbump to the beginning of each build (now that call is just one). --- CMakeLists.txt | 131 ++++++++++++++++++++++++++------------------------------- 1 file changed, 60 insertions(+), 71 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca92699c..d1dcc106 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,54 +125,53 @@ endif () # Set up source files list(APPEND lib_SRCS lib/quotient_common.h - lib/function_traits.h - lib/function_traits.cpp - lib/networkaccessmanager.cpp - lib/connectiondata.cpp - lib/connection.cpp - lib/ssosession.cpp - lib/logging.cpp - lib/room.cpp - lib/user.cpp - lib/avatar.cpp - lib/uri.cpp - lib/uriresolver.cpp - lib/eventstats.cpp - lib/syncdata.cpp - lib/settings.cpp - lib/networksettings.cpp - lib/converters.cpp - lib/util.cpp - lib/encryptionmanager.cpp - lib/eventitem.cpp - lib/accountregistry.cpp - lib/mxcreply.cpp - lib/events/event.cpp - lib/events/roomevent.cpp - lib/events/stateevent.cpp - lib/events/eventcontent.cpp - lib/events/roomcreateevent.cpp - lib/events/roomtombstoneevent.cpp - lib/events/roommessageevent.cpp - lib/events/roommemberevent.cpp - lib/events/roompowerlevelsevent.cpp - lib/events/typingevent.cpp - lib/events/receiptevent.cpp - lib/events/reactionevent.cpp - lib/events/callanswerevent.cpp - lib/events/callcandidatesevent.cpp - lib/events/callhangupevent.cpp - lib/events/callinviteevent.cpp - lib/events/directchatevent.cpp - lib/events/encryptionevent.cpp - lib/events/encryptedevent.cpp - lib/events/roomkeyevent.cpp - lib/events/stickerevent.cpp - lib/jobs/requestdata.cpp - lib/jobs/basejob.cpp - lib/jobs/syncjob.cpp - lib/jobs/mediathumbnailjob.cpp - lib/jobs/downloadfilejob.cpp + lib/function_traits.h lib/function_traits.cpp + lib/networkaccessmanager.h lib/networkaccessmanager.cpp + lib/connectiondata.h lib/connectiondata.cpp + lib/connection.h lib/connection.cpp + lib/ssosession.h lib/ssosession.cpp + lib/logging.h lib/logging.cpp + lib/room.h lib/room.cpp + lib/user.h lib/user.cpp + lib/avatar.h lib/avatar.cpp + lib/uri.h lib/uri.cpp + lib/uriresolver.h lib/uriresolver.cpp + lib/eventstats.h lib/eventstats.cpp + lib/syncdata.h lib/syncdata.cpp + lib/settings.h lib/settings.cpp + lib/networksettings.h lib/networksettings.cpp + lib/converters.h lib/converters.cpp + lib/util.h lib/util.cpp + lib/encryptionmanager.h lib/encryptionmanager.cpp + lib/eventitem.h lib/eventitem.cpp + lib/accountregistry.h lib/accountregistry.cpp + lib/mxcreply.h lib/mxcreply.cpp + lib/events/event.h lib/events/event.cpp + lib/events/roomevent.h lib/events/roomevent.cpp + lib/events/stateevent.h lib/events/stateevent.cpp + lib/events/eventcontent.h lib/events/eventcontent.cpp + lib/events/roomcreateevent.h lib/events/roomcreateevent.cpp + lib/events/roomtombstoneevent.h lib/events/roomtombstoneevent.cpp + lib/events/roommessageevent.h lib/events/roommessageevent.cpp + lib/events/roommemberevent.h lib/events/roommemberevent.cpp + lib/events/roompowerlevelsevent.h lib/events/roompowerlevelsevent.cpp + lib/events/typingevent.h lib/events/typingevent.cpp + lib/events/receiptevent.h lib/events/receiptevent.cpp + lib/events/reactionevent.h lib/events/reactionevent.cpp + lib/events/callanswerevent.h lib/events/callanswerevent.cpp + lib/events/callcandidatesevent.h lib/events/callcandidatesevent.cpp + lib/events/callhangupevent.h lib/events/callhangupevent.cpp + lib/events/callinviteevent.h lib/events/callinviteevent.cpp + lib/events/directchatevent.h lib/events/directchatevent.cpp + lib/events/encryptionevent.h lib/events/encryptionevent.cpp + lib/events/encryptedevent.h lib/events/encryptedevent.cpp + lib/events/roomkeyevent.h lib/events/roomkeyevent.cpp + lib/events/stickerevent.h lib/events/stickerevent.cpp + lib/jobs/requestdata.h lib/jobs/requestdata.cpp + lib/jobs/basejob.h lib/jobs/basejob.cpp + lib/jobs/syncjob.h lib/jobs/syncjob.cpp + lib/jobs/mediathumbnailjob.h lib/jobs/mediathumbnailjob.cpp + lib/jobs/downloadfilejob.h lib/jobs/downloadfilejob.cpp ) # Configure API files generation @@ -217,21 +216,13 @@ if (API_GENERATION_ENABLED) message( STATUS "${CLANG_FORMAT} is NOT FOUND; API files won't be reformatted") endif () - # We use globbing with CONFIGURE_DEPENDS to produce two file lists: - # one of all API files for clang-format and another of just .cpp - # files to supply for library source files. Since we expect these - # file lists to only change due to GTAD invocation, we only use - # CONFIGURE_DEPENDS when pre-requisites to update API are met. - # Read comments next to each file(GLOB_RECURSE) for caveats. - set(add_CONFIGURE_DEPENDS "CONFIGURE_DEPENDS") - set(FULL_CSAPI_SRC_DIR ${ABS_API_DEF_PATH}/client-server) file(GLOB_RECURSE API_DEFS RELATIVE ${PROJECT_SOURCE_DIR} ${FULL_CSAPI_SRC_DIR}/*.yaml ${ABS_API_DEF_PATH}/${ASAPI_DEF_DIR}/*.yaml ${ABS_API_DEF_PATH}/${ISAPI_DEF_DIR}/*.yaml ) - add_custom_target(generate-unformatted-api + add_custom_target(update-api ${ABS_GTAD_PATH} --config ../gtad/gtad.yaml --out ${CSAPI_DIR} ${FULL_CSAPI_SRC_DIR} old_sync.yaml- room_initial_sync.yaml- # deprecated @@ -271,21 +262,19 @@ if (API_GENERATION_ENABLED) endif() add_feature_info(EnableApiCodeGeneration "${API_GENERATION_ENABLED}" "build target update-api") -if (API_GENERATION_ENABLED) - add_feature_info(EnableApiFormatting "${API_FORMATTING_ENABLED}" - "formatting of generated API files with clang-format") -endif() - -# Make no mistake: CMake cannot run gtad first and then populate the list of -# resulting api_SRCS files. In other words, placing the below statement after -# the block above will not lead to CMake magically reconfiguring the build -# after building the update-api target. CONFIGURE_DEPENDS somewhat helps, -# at least forcing the build system to reevaluate the glob before building -# the next target. Otherwise, you have to watch out if gtad has created -# new files and if it has, re-run cmake. -file(GLOB_RECURSE api_SRCS ${add_CONFIGURE_DEPENDS} ${FULL_CSAPI_DIR}/*.cpp) -add_library(${PROJECT_NAME} ${lib_SRCS} ${api_SRCS}) +# Produce the list of all Matrix API files for building the library. When this +# list changes (normally after calling GTAD), CONFIGURE_DEPENDS will force +# the build system to call CMake again. Checking for the glob change slows down +# each build (even if the target does not involve API generation). It would be +# ideal if GTAD could compare the initial (saved somewhere) and the generated +# file list itself and write down to some .cmake file if those are different, +# which would trigger the reconfiguration specifically before the next build. +# For now CONFIGURE_DEPENDS is the best approximation of that. +file(GLOB_RECURSE api_ALL_SRCS CONFIGURE_DEPENDS + ${FULL_CSAPI_DIR}/*.* lib/${ASAPI_DEF_DIR}/*.* lib/${ISAPI_DEF_DIR}/*.*) + +add_library(${PROJECT_NAME} ${lib_SRCS} ${api_ALL_SRCS}) target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_JAVA_STYLE_ITERATORS QT_NO_URL_CAST_FROM_STRING QT_NO_CAST_TO_ASCII) target_compile_definitions(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} -- cgit v1.2.3 From dc458f3952b0005a0feb63fbebed11ae9ad5a062 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Dec 2021 15:11:32 +0100 Subject: Rely on GTAD 0.9 that calls clang-format itself There was that ugly workaround in CMakeLists.txt to produce the list of files to be formatted and making a separate build target for clang-format. As GTAD 0.9 calls clang-format itself this workaround is no more necessary; generate-unformatted-api and format-api build targets are now gone, and clang-format is no more one-build-system-configuration behind GTAD on the list of files to handle. --- CMakeLists.txt | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d1dcc106..8d9be635 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,7 +182,6 @@ set(ASAPI_DEF_DIR application-service/definitions) set(ISAPI_DEF_DIR identity/definitions) set(API_GENERATION_ENABLED 0) -set(API_FORMATTING_ENABLED 0) if (GTAD_PATH AND MATRIX_DOC_PATH) # REALPATH resolves ~ (home directory) while PROGRAM doesn't get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" REALPATH) @@ -209,11 +208,8 @@ if (API_GENERATION_ENABLED) set(CLANG_FORMAT clang-format) endif() get_filename_component(ABS_CLANG_FORMAT "${CLANG_FORMAT}" PROGRAM PROGRAM_ARGS CLANG_FORMAT_ARGS) - if (ABS_CLANG_FORMAT) - set(API_FORMATTING_ENABLED 1) - message( STATUS "clang-format is at ${ABS_CLANG_FORMAT}") - else () - message( STATUS "${CLANG_FORMAT} is NOT FOUND; API files won't be reformatted") + if (NOT ABS_CLANG_FORMAT) + message( WARNING "${CLANG_FORMAT} is NOT FOUND; API files won't be formatted") endif () set(FULL_CSAPI_SRC_DIR ${ABS_API_DEF_PATH}/client-server) @@ -237,28 +233,6 @@ if (API_GENERATION_ENABLED) ${API_DEFS} VERBATIM ) - add_custom_target(update-api DEPENDS generate-unformatted-api) - if (EXISTS ${ABS_CLANG_FORMAT}) - set(CLANG_FORMAT_ARGS -i -sort-includes ${CLANG_FORMAT_ARGS}) - # FIXME: the list of files should be produced after GTAD has run. - # For now it's produced at CMake invocation. If file() hasn't found - # any files at that moment, clang-format will be called with an empty - # list of files and choke. Taking outfiles.txt from GTAD could be - # an option but this, too, must be done during the build stage, and - # there's no crossplatform way to use the contents of a file as - # input for a build target command. - file(GLOB_RECURSE api_ALL_SRCS ${add_CONFIGURE_DEPENDS} - ${FULL_CSAPI_DIR}/*.* - lib/${ASAPI_DEF_DIR}/*.* - lib/${ISAPI_DEF_DIR}/*.*) - if (api_ALL_SRCS) - add_custom_target(format-api - ${ABS_CLANG_FORMAT} ${CLANG_FORMAT_ARGS} ${api_ALL_SRCS} - DEPENDS generate-unformatted-api - VERBATIM) - add_dependencies(update-api format-api) - endif() - endif() endif() add_feature_info(EnableApiCodeGeneration "${API_GENERATION_ENABLED}" "build target update-api") -- cgit v1.2.3 From 0d212bdb7849d12582d906a03935cb6f51767e3b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Dec 2021 21:22:46 +0100 Subject: Add a couple of bare .h files to CMakeLists.txt These were missing from the commit introducing .h files to sources, highlighting the reason why .h files should be there... --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d9be635..2762df6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,13 +149,16 @@ list(APPEND lib_SRCS lib/events/event.h lib/events/event.cpp lib/events/roomevent.h lib/events/roomevent.cpp lib/events/stateevent.h lib/events/stateevent.cpp + lib/events/simplestateevents.h lib/events/eventcontent.h lib/events/eventcontent.cpp lib/events/roomcreateevent.h lib/events/roomcreateevent.cpp lib/events/roomtombstoneevent.h lib/events/roomtombstoneevent.cpp lib/events/roommessageevent.h lib/events/roommessageevent.cpp lib/events/roommemberevent.h lib/events/roommemberevent.cpp + lib/events/roomavatarevent.h lib/events/roompowerlevelsevent.h lib/events/roompowerlevelsevent.cpp lib/events/typingevent.h lib/events/typingevent.cpp + lib/events/accountdataevents.h lib/events/receiptevent.h lib/events/receiptevent.cpp lib/events/reactionevent.h lib/events/reactionevent.cpp lib/events/callanswerevent.h lib/events/callanswerevent.cpp -- cgit v1.2.3 From 776d05bf98a5dd9e484d5a0e651c71fa95498689 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Dec 2021 21:36:32 +0100 Subject: Cleanup; drop an unused RoomAliasesEvent constructor Also, RoomAliasesEvent is to be completely gone after 0.7. --- lib/events/accountdataevents.h | 5 ++--- lib/events/simplestateevents.h | 12 ++---------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index e5101d20..9cf77be3 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -4,7 +4,6 @@ #pragma once #include "event.h" -#include "eventcontent.h" namespace Quotient { constexpr const char* FavouriteTag = "m.favourite"; @@ -16,12 +15,12 @@ struct TagRecord { order_type order; - TagRecord(order_type order = none) : order(std::move(order)) {} + TagRecord(order_type order = none) : order(order) {} bool operator<(const TagRecord& other) const { // Per The Spec, rooms with no order should be after those with order, - // against optional<>::operator<() convention. + // against std::optional<>::operator<() convention. return order && (!other.order || *order < *other.order); } }; diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index cf1bfbba..9ce78609 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -8,8 +8,7 @@ namespace Quotient { namespace EventContent { template - class SimpleContent { - public: + struct SimpleContent { using value_type = T; // The constructor is templated to enable perfect forwarding @@ -25,11 +24,8 @@ namespace EventContent { return { { key, Quotient::toJson(value) } }; } - public: T value; - - protected: - QString key; + const QString key; }; } // namespace EventContent @@ -65,10 +61,6 @@ public: explicit RoomAliasesEvent(const QJsonObject& obj) : StateEvent(typeId(), obj, QStringLiteral("aliases")) {} - RoomAliasesEvent(const QString& server, const QStringList& aliases) - : StateEvent(typeId(), matrixTypeId(), server, - QStringLiteral("aliases"), aliases) - {} QString server() const { return stateKey(); } QStringList aliases() const { return content().value; } }; -- cgit v1.2.3 From 7981c654c0cd53f6e100ecae25882246a9de5d6e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 5 Dec 2021 21:47:25 +0100 Subject: Drop 'qmc-example' from one last(?) place --- quotest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quotest/CMakeLists.txt b/quotest/CMakeLists.txt index 59334e30..8d67b6f1 100644 --- a/quotest/CMakeLists.txt +++ b/quotest/CMakeLists.txt @@ -8,7 +8,7 @@ find_package(${Qt} COMPONENTS Concurrent) add_executable(quotest ${quotest_SRCS}) target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${Qt}::Concurrent ${PROJECT_NAME}) -option(${PROJECT_NAME}_INSTALL_TESTS "install quotest (former qmc-example) application" ON) +option(${PROJECT_NAME}_INSTALL_TESTS "install quotest application" ON) add_feature_info(InstallQuotest ${PROJECT_NAME}_INSTALL_TESTS "the library functional test suite") -- cgit v1.2.3 From 9ddb72611f5575ccd5d3b68734a9d40d8ef2befc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 6 Dec 2021 07:17:17 +0100 Subject: Add execution of autotests to CI --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47e55421..ec6b671c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,7 +154,7 @@ jobs: - name: Build and install libQuotient run: | - cmake --build build --target install + cmake --build build --target all install ls ~/.local/$BIN_DIR/quotest - name: Run tests @@ -164,6 +164,8 @@ jobs: QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' run: | + cd build + ctest --output-on-failure [[ -z "$TEST_USER" ]] || $VALGRIND build/quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually -- cgit v1.2.3 From ac0ce7da24b4e567a34863db3261f755674d8337 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 6 Dec 2021 22:42:33 +0100 Subject: autotests/: don't instantiate QApplication Those tests don't even need an event loop. --- autotests/callcandidateseventtest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autotests/callcandidateseventtest.cpp b/autotests/callcandidateseventtest.cpp index 1a69cb66..0d5a543b 100644 --- a/autotests/callcandidateseventtest.cpp +++ b/autotests/callcandidateseventtest.cpp @@ -53,5 +53,5 @@ void TestCallCandidatesEvent::fromJson() QStringLiteral("candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0")); } -QTEST_MAIN(TestCallCandidatesEvent) +QTEST_APPLESS_MAIN(TestCallCandidatesEvent) #include "callcandidateseventtest.moc" -- cgit v1.2.3 From 449456c2c86f56e9294294e03436d3aa0ff089f5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 7 Dec 2021 09:43:00 +0100 Subject: Fix quotest invocation; use working-directory --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec6b671c..9eefa43f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: | sudo apt-get -qq install ninja-build valgrind - echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=quotest/.valgrind.supp" >>$GITHUB_ENV + echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=${{ runner.workspace }}/quotest/.valgrind.supp" >>$GITHUB_ENV - name: Setup build environment run: | @@ -108,8 +108,8 @@ jobs: - name: Build and install olm if: matrix.e2ee + working-directory: '..' run: | - cd .. git clone https://gitlab.matrix.org/matrix-org/olm.git cmake -S olm -B olm/build $CMAKE_ARGS cmake --build olm/build --target install @@ -117,8 +117,8 @@ jobs: - name: Pull CS API and build GTAD if: matrix.update-api + working-directory: '..' run: | - cd .. git clone https://github.com/matrix-org/matrix-doc.git git clone --recursive https://github.com/KitsuneRal/gtad.git cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF @@ -163,10 +163,10 @@ jobs: TEST_PWD: ${{ secrets.TEST_PWD }} QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' + working-directory: build run: | - cd build ctest --output-on-failure - [[ -z "$TEST_USER" ]] || $VALGRIND build/quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" + [[ -z "$TEST_USER" ]] || $VALGRIND quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually - name: Perform CodeQL analysis -- cgit v1.2.3 From cb7c57953024a23fc7c20db75f3a15f81bb62bd3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 7 Dec 2021 14:43:27 +0100 Subject: Fix valgrind invocation failure It turned out that, confusingly, ${{ runner.workspace }} refers to the directory above $GITHUB_WORKSPACE, which is why the previous commit ended up with valgrind not finding its suppressions. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eefa43f..6fdb337a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: | sudo apt-get -qq install ninja-build valgrind - echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=${{ runner.workspace }}/quotest/.valgrind.supp" >>$GITHUB_ENV + echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=$GITHUB_WORKSPACE/quotest/.valgrind.supp" >>$GITHUB_ENV - name: Setup build environment run: | -- cgit v1.2.3 From 0128b8a296b1be7e7702ec525539614d47dd7471 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 7 Dec 2021 14:43:39 +0100 Subject: CI: Put all build directories to ${{ runner.workspace }}/build --- .github/workflows/ci.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fdb337a..2e023230 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,6 +99,7 @@ jobs: echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV cmake -E make_directory ${{ runner.workspace }}/build + echo "BUILD_PATH=${{ runner.workspace }}/build/libQuotient" >>$GITHUB_ENV - name: Setup MSVC environment uses: ilammy/msvc-dev-cmd@v1 @@ -108,23 +109,23 @@ jobs: - name: Build and install olm if: matrix.e2ee - working-directory: '..' + working-directory: ${{ runner.workspace }} run: | git clone https://gitlab.matrix.org/matrix-org/olm.git - cmake -S olm -B olm/build $CMAKE_ARGS - cmake --build olm/build --target install + cmake -S olm -B build/olm $CMAKE_ARGS + cmake --build build/olm --target install echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with E2EE" >>$GITHUB_ENV - name: Pull CS API and build GTAD if: matrix.update-api - working-directory: '..' + working-directory: ${{ runner.workspace }} run: | git clone https://github.com/matrix-org/matrix-doc.git git clone --recursive https://github.com/KitsuneRal/gtad.git - cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - cmake --build gtad - echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ - -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ + cmake -S gtad -B build/gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF + cmake --build build/gtad + echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=${{ runner.workspace }}/matrix-doc \ + -DGTAD_PATH=${{ runner.workspace }}/build/gtad/gtad" \ >>$GITHUB_ENV echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with API files regeneration" >>$GITHUB_ENV @@ -146,15 +147,15 @@ jobs: BIN_DIR=bin fi echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV - cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} + cmake -S $GITHUB_WORKSPACE -B $BUILD_PATH $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} - name: Regenerate API code if: matrix.update-api - run: cmake --build build --target update-api + run: cmake --build ../build/libQuotient --target update-api - name: Build and install libQuotient run: | - cmake --build build --target all install + cmake --build $BUILD_PATH --target all install ls ~/.local/$BIN_DIR/quotest - name: Run tests @@ -163,9 +164,9 @@ jobs: TEST_PWD: ${{ secrets.TEST_PWD }} QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' - working-directory: build + working-directory: ../build/libQuotient run: | - ctest --output-on-failure + ctest --test-dir $BUILD_PATH --output-on-failure [[ -z "$TEST_USER" ]] || $VALGRIND quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually -- cgit v1.2.3 From bd728a91f69382a052d07582788fcecec8b0f35e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 7 Dec 2021 19:00:04 +0100 Subject: Test installed quotest This covers Quotient_INSTALL_TESTS setting. --- .github/workflows/ci.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e023230..9d286da6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,8 +98,16 @@ jobs: echo "QUOTEST_ORIGIN=$VERSION @ ${{ runner.os }}/${{ matrix.compiler }}" >>$GITHUB_ENV echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV + + if [[ '${{ runner.os }}' != 'Windows' ]]; then + BIN_DIR=/bin + fi + echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV + echo "~/.local$BIN_DIR" >>$GITHUB_PATH + cmake -E make_directory ${{ runner.workspace }}/build echo "BUILD_PATH=${{ runner.workspace }}/build/libQuotient" >>$GITHUB_ENV + echo "LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH" >>$GITHUB_ENV - name: Setup MSVC environment uses: ilammy/msvc-dev-cmd@v1 @@ -141,13 +149,8 @@ jobs: - name: Configure libQuotient run: | - if [[ '${{ runner.os }}' == 'Windows' ]]; then - BIN_DIR=. - else - BIN_DIR=bin - fi - echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV - cmake -S $GITHUB_WORKSPACE -B $BUILD_PATH $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} + cmake -S $GITHUB_WORKSPACE -B $BUILD_PATH $CMAKE_ARGS \ + -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} -DQuotient_INSTALL_TESTS=ON - name: Regenerate API code if: matrix.update-api @@ -164,10 +167,10 @@ jobs: TEST_PWD: ${{ secrets.TEST_PWD }} QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' - working-directory: ../build/libQuotient run: | ctest --test-dir $BUILD_PATH --output-on-failure - [[ -z "$TEST_USER" ]] || $VALGRIND quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" + [[ -z "$TEST_USER" ]] || \ + $VALGRIND quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually - name: Perform CodeQL analysis -- cgit v1.2.3 From c585227b3724666e3cf7aab3ab53d6f5930e7218 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 9 Dec 2021 21:07:52 +0100 Subject: Fix CI failure on macOS CMAKE_INSTALL_RPATH_USE_LINK_PATH is more universal than setting LD_LIBRARY_PATH Also: drop an extra slash in the path to installed quotest. --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d286da6..c78a5981 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,7 +97,8 @@ jobs: fi echo "QUOTEST_ORIGIN=$VERSION @ ${{ runner.os }}/${{ matrix.compiler }}" >>$GITHUB_ENV echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV + -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local\ + -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" >>$GITHUB_ENV if [[ '${{ runner.os }}' != 'Windows' ]]; then BIN_DIR=/bin @@ -107,7 +108,6 @@ jobs: cmake -E make_directory ${{ runner.workspace }}/build echo "BUILD_PATH=${{ runner.workspace }}/build/libQuotient" >>$GITHUB_ENV - echo "LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH" >>$GITHUB_ENV - name: Setup MSVC environment uses: ilammy/msvc-dev-cmd@v1 @@ -159,7 +159,7 @@ jobs: - name: Build and install libQuotient run: | cmake --build $BUILD_PATH --target all install - ls ~/.local/$BIN_DIR/quotest + ls ~/.local$BIN_DIR/quotest - name: Run tests env: -- cgit v1.2.3 From e5256e0b1e4c43ce96d99d1b82ca5d98a1baded6 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 17 Dec 2021 08:07:07 +0100 Subject: RoomMemberEvent: fix an off-by-one error Also: extended quotest to cover member renames, not just user profile renames. --- lib/events/roommemberevent.cpp | 8 +++----- quotest/quotest.cpp | 35 +++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index b0bc7bcb..3141f6b5 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -48,11 +48,9 @@ void MemberEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); if (membership != Membership::Invalid) - o->insert( - QStringLiteral("membership"), - MembershipStrings[qCountTrailingZeroBits( - std::underlying_type_t(membership)) - + 1]); + o->insert(QStringLiteral("membership"), + MembershipStrings[qCountTrailingZeroBits( + std::underlying_type_t(membership))]); if (displayName) o->insert(QStringLiteral("displayname"), *displayName); if (avatarUrl && avatarUrl->isValid()) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 764d5dfd..8703efb2 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -214,6 +214,7 @@ TestManager::TestManager(int& argc, char** argv) // Big countdown watchdog QTimer::singleShot(180000, this, [this] { + clog << "Time is up, stopping the session"; if (testSuite) conclude(); else @@ -537,14 +538,32 @@ TEST_IMPL(setTopic) TEST_IMPL(changeName) { - auto* const localUser = connection()->user(); - const auto& newName = connection()->generateTxnId(); // See setTopic() - clog << "Renaming the user to " << newName.toStdString() << endl; - localUser->rename(newName); - connectUntil(localUser, &User::defaultNameChanged, this, - [this, thisTest, localUser, newName] { - FINISH_TEST(localUser->name() == newName); - }); + connectSingleShot(targetRoom, &Room::allMembersLoaded, this, [this, thisTest] { + auto* const localUser = connection()->user(); + const auto& newName = connection()->generateTxnId(); // See setTopic() + clog << "Renaming the user to " << newName.toStdString() + << " in the target room" << endl; + localUser->rename(newName, targetRoom); + connectUntil(targetRoom, &Room::memberRenamed, this, + [this, thisTest, localUser, newName](const User* u) { + if (localUser != u) + return false; + if (localUser->name(targetRoom) != newName) + FAIL_TEST(); + + clog + << "Member rename successful, renaming the account" + << endl; + const auto newN = newName.mid(0, 5); + localUser->rename(newN); + connectUntil(localUser, &User::defaultNameChanged, + this, [this, thisTest, localUser, newN] { + targetRoom->localUser()->rename({}); + FINISH_TEST(localUser->name() == newN); + }); + return true; + }); + }); return false; } -- cgit v1.2.3 From 24a562cc64e8fac6c55108ae2b7b6997ecdd2010 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 19 Dec 2021 10:43:33 +0100 Subject: Quotest: add a missing \n in the output --- quotest/quotest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 8703efb2..7bd9c5c3 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -214,7 +214,7 @@ TestManager::TestManager(int& argc, char** argv) // Big countdown watchdog QTimer::singleShot(180000, this, [this] { - clog << "Time is up, stopping the session"; + clog << "Time is up, stopping the session\n"; if (testSuite) conclude(); else -- cgit v1.2.3 From 487e7f5ef75a5a5a4d732027d0b7705fdffb71ca Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 20 Dec 2021 16:18:35 +0100 Subject: Add event_loader.h to CMakeLists Another forgotten header file. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2762df6a..7675daad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,7 @@ list(APPEND lib_SRCS lib/accountregistry.h lib/accountregistry.cpp lib/mxcreply.h lib/mxcreply.cpp lib/events/event.h lib/events/event.cpp + lib/events/eventloader.h lib/events/roomevent.h lib/events/roomevent.cpp lib/events/stateevent.h lib/events/stateevent.cpp lib/events/simplestateevents.h -- cgit v1.2.3 From b989383165b648269a231d1febadc8150676d5cf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 21 Dec 2021 18:40:47 +0100 Subject: Don't chain RoomEvent to Event factory any more Objects derived from Event are not room events (in the spec sense) and never occur in the same arrays as room events; therefore this chaining has always been superfluous. --- lib/events/roomevent.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index fb921af6..b728e0bf 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -9,9 +9,6 @@ using namespace Quotient; -[[maybe_unused]] static auto roomEventTypeInitialised = - Event::factory_t::chainFactory(); - RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, const QJsonObject& contentJson) : Event(type, matrixType, contentJson) -- cgit v1.2.3 From 231c4d723eb53f3ea5f641b743d198584840a963 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Dec 2021 13:44:39 +0100 Subject: Simplify the code around EventFactory<> The former code assumed that EventFactory<> is just a class-level shell for a bunch of functions and a static data member that only exists to allow specialisations to occur for the whole group together. On top of that, setupFactory() and registerEventType() strived to protect this group from double registration coming from static variables in an anonymous namespace produced by REGISTER_EVENT_TYPE. The whole thing is now de-static-ed: resolving the factory now relies on class-static Event/RoomEvent/StateEventBase::factory variables instead of factory_t type aliases; and REGISTER_EVENT_TYPE produces non-static inline variables instead, obviating the need of registerEventType/setupFactory kludge. --- lib/events/event.h | 166 +++++++++++++++++++++++-------------------- lib/events/eventloader.h | 18 ++--- lib/events/roomevent.h | 2 +- lib/events/roommemberevent.h | 32 ++++----- lib/events/stateevent.cpp | 15 ---- lib/events/stateevent.h | 18 ++++- 6 files changed, 125 insertions(+), 126 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index 4d4bb16b..e786fb30 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -110,94 +110,90 @@ inline event_type_t typeId() inline event_type_t unknownEventTypeId() { return typeId(); } -// === EventFactory === +// === Event creation facilities === -/** Create an event of arbitrary type from its arguments */ +//! Create an event of arbitrary type from its arguments template inline event_ptr_tt makeEvent(ArgTs&&... args) { return std::make_unique(std::forward(args)...); } -template -class EventFactory { -public: - template - static auto addMethod(FnT&& method) - { - factories().emplace_back(std::forward(method)); - return 0; - } - - /** Chain two type factories - * Adds the factory class of EventT2 (EventT2::factory_t) to - * the list in factory class of EventT1 (EventT1::factory_t) so - * that when EventT1::factory_t::make() is invoked, types of - * EventT2 factory are looked through as well. This is used - * to include RoomEvent types into the more general Event factory, - * and state event types into the RoomEvent factory. - */ - template - static auto chainFactory() - { - return addMethod(&EventT::factory_t::make); - } - - static event_ptr_tt make(const QJsonObject& json, - const QString& matrixType) - { - for (const auto& f : factories()) - if (auto e = f(json, matrixType)) - return e; - return nullptr; - } - -private: - static auto& factories() +namespace _impl { + template + event_ptr_tt makeIfMatches(const QJsonObject& json, + const QString& matrixType) { - using inner_factory_tt = std::function( - const QJsonObject&, const QString&)>; - static std::vector _factories {}; - return _factories; + return QLatin1String(EventT::matrixTypeId()) == matrixType + ? makeEvent(json) + : nullptr; } -}; - -/** Add a type to its default factory - * Adds a standard factory method (via makeEvent<>) for a given - * type to EventT::factory_t factory class so that it can be - * created dynamically from loadEvent<>(). - * - * \tparam EventT the type to enable dynamic creation of - * \return the registered type id - * \sa loadEvent, Event::type - */ -template -inline auto setupFactory() -{ - qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); - return EventT::factory_t::addMethod([](const QJsonObject& json, - const QString& jsonMatrixType) { - return EventT::matrixTypeId() == jsonMatrixType ? makeEvent(json) - : nullptr; - }); -} -template -inline auto registerEventType() -{ - // Initialise exactly once, even if this function is called twice for - // the same type (for whatever reason - you never know the ways of - // static initialisation is done). - static const auto _ = setupFactory(); - return _; // Only to facilitate usage in static initialisation -} + //! \brief A family of event factories to create events from CS API responses + //! + //! Each of these factories, as instantiated by event base types (Event, + //! RoomEvent etc.) is capable of producing an event object derived from + //! \p BaseEventT, using the JSON payload and the event type passed to its + //! make() method. Don't use these directly to make events; use loadEvent() + //! overloads as the frontend for these. Never instantiate new factories + //! outside of base event classes. + //! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, + //! StateEventBase::factory + template + class EventFactory + : private std::vector (*)(const QJsonObject&, + const QString&)> { + // Actual makeIfMatches specialisations will differ in the first + // template parameter but that doesn't affect the function type + public: + explicit EventFactory(const char* name = "") + : name(name) + { + static auto yetToBeConstructed = true; + Q_ASSERT(yetToBeConstructed); + if (!yetToBeConstructed) // For Release builds that pass Q_ASSERT + qCritical(EVENTS) + << "Another EventFactory for the same base type is being " + "created - event creation logic will be splintered"; + yetToBeConstructed = false; + } + EventFactory(const EventFactory&) = delete; + + //! \brief Add a method to create events of a given type + //! + //! Adds a standard factory method (makeIfMatches) for \p EventT so that + //! event objects of this type can be created dynamically by loadEvent. + //! The caller is responsible for ensuring this method is called only + //! once per type. + //! \sa makeIfMatches, loadEvent, Quotient::loadEvent + template + bool addMethod() + { + this->emplace_back(&makeIfMatches); + qDebug(EVENTS) << "Added factory method for" + << EventT::matrixTypeId() << "events;" << this->size() + << "methods in the" << name << "chain by now"; + return true; + } + + auto loadEvent(const QJsonObject& json, const QString& matrixType) + { + for (const auto& f : *this) + if (auto e = f(json, matrixType)) + return e; + return makeEvent(unknownEventTypeId(), json); + } + + const char* const name; + }; +} // namespace _impl // === Event === class Event { public: using Type = event_type_t; - using factory_t = EventFactory; + static inline _impl::EventFactory factory { "Event" }; explicit Event(Type type, const QJsonObject& json); explicit Event(Type type, event_mtype_t matrixType, @@ -272,7 +268,7 @@ template using EventsArray = std::vector>; using Events = EventsArray; -// === Macros used with event class definitions === +// === Facilities for event class definitions === // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). @@ -284,13 +280,27 @@ using Events = EventsArray; // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - namespace { \ - [[maybe_unused]] static const auto _factoryAdded##_Type = \ - registerEventType<_Type>(); \ - } \ +#define REGISTER_EVENT_TYPE(_Type) \ + [[maybe_unused]] inline const auto _factoryAdded##_Type = \ + _Type::factory.addMethod<_Type>(); \ // End of macro +// === Event loading === +// (see also event_loader.h) + +//! \brief Point of customisation to dynamically load events +//! +//! The default specialisation of this calls BaseEventT::factory and if that +//! fails (i.e. returns nullptr) creates an unknown event of BaseEventT. +//! Other specialisations may reuse other factories, add validations common to +//! BaseEventT, and so on +template +event_ptr_tt doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + return BaseEventT::factory.loadEvent(json, matrixType); +} + // === is<>(), eventCast<>() and switchOnType<>() === template diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index 978668f2..fe624d70 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -6,16 +6,6 @@ #include "stateevent.h" namespace Quotient { -namespace _impl { - template - static inline auto loadEvent(const QJsonObject& json, - const QString& matrixType) - { - if (auto e = EventFactory::make(json, matrixType)) - return e; - return makeEvent(unknownEventTypeId(), json); - } -} // namespace _impl /*! Create an event with proper type from a JSON object * @@ -26,7 +16,7 @@ namespace _impl { template inline event_ptr_tt loadEvent(const QJsonObject& fullJson) { - return _impl::loadEvent(fullJson, fullJson[TypeKeyL].toString()); + return doLoadEvent(fullJson, fullJson[TypeKeyL].toString()); } /*! Create an event from a type string and content JSON @@ -39,8 +29,8 @@ template inline event_ptr_tt loadEvent(const QString& matrixType, const QJsonObject& content) { - return _impl::loadEvent(basicEventJson(matrixType, content), - matrixType); + return doLoadEvent(basicEventJson(matrixType, content), + matrixType); } /*! Create a state event from a type string, content JSON and state key @@ -53,7 +43,7 @@ inline StateEventPtr loadStateEvent(const QString& matrixType, const QJsonObject& content, const QString& stateKey = {}) { - return _impl::loadEvent( + return doLoadEvent( basicStateEventJson(matrixType, content, stateKey), matrixType); } diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 7f13f6f2..8be58481 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -13,7 +13,7 @@ class RedactionEvent; /** This class corresponds to m.room.* events */ class RoomEvent : public Event { public: - using factory_t = EventFactory; + static inline _impl::EventFactory factory { "RoomEvent" }; // RedactionEvent is an incomplete type here so we cannot inline // constructors and destructors and we cannot use 'using'. diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index f3047159..0fb464d4 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -49,16 +49,15 @@ public: std::forward(contentArgs)...) {} - /// A special constructor to create unknown RoomMemberEvents - /** - * This is needed in order to use RoomMemberEvent as a "base event - * class" in cases like GetMembersByRoomJob when RoomMemberEvents - * (rather than RoomEvents or StateEvents) are resolved from JSON. - * For such cases loadEvent<> requires an underlying class to be - * constructible with unknownTypeId() instead of its genuine id. - * Don't use it directly. - * \sa GetMembersByRoomJob, loadEvent, unknownTypeId - */ + //! \brief A special constructor to create unknown RoomMemberEvents + //! + //! This is needed in order to use RoomMemberEvent as a "base event class" + //! in cases like GetMembersByRoomJob when RoomMemberEvents (rather than + //! RoomEvents or StateEvents) are resolved from JSON. For such cases + //! loadEvent\<> requires an underlying class to have a specialisation of + //! EventFactory\<> and be constructible with unknownTypeId() instead of + //! its genuine id. Don't use directly. + //! \sa EventFactory, loadEvent, GetMembersByRoomJob RoomMemberEvent(Type type, const QJsonObject& fullJson) : StateEvent(type, fullJson) {} @@ -89,14 +88,13 @@ public: }; template <> -class EventFactory { -public: - static event_ptr_tt make(const QJsonObject& json, - const QString&) - { +inline event_ptr_tt +doLoadEvent(const QJsonObject& json, const QString& matrixType) +{ + if (matrixType == QLatin1String(RoomMemberEvent::matrixTypeId())) return makeEvent(json); - } -}; + return makeEvent(unknownEventTypeId(), json); +} REGISTER_EVENT_TYPE(RoomMemberEvent) } // namespace Quotient diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index efe011a0..9535af56 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -5,21 +5,6 @@ using namespace Quotient; -// Aside from the normal factory to instantiate StateEventBase inheritors -// StateEventBase itself can be instantiated if there's a state_key JSON key -// but the event type is unknown. -[[maybe_unused]] static auto stateEventTypeInitialised = - RoomEvent::factory_t::addMethod( - [](const QJsonObject& json, const QString& matrixType) -> StateEventPtr { - if (!json.contains(StateKeyKeyL)) - return nullptr; - - if (auto e = StateEventBase::factory_t::make(json, matrixType)) - return e; - - return makeEvent(unknownEventTypeId(), json); - }); - StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, const QJsonObject& contentJson) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index b0aa9907..919e8f86 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -19,7 +19,7 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, class StateEventBase : public RoomEvent { public: - using factory_t = EventFactory; + static inline _impl::EventFactory factory { "StateEvent" }; StateEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json) {} @@ -37,6 +37,22 @@ public: using StateEventPtr = event_ptr_tt; using StateEvents = EventsArray; +//! \brief Override RoomEvent factory with that from StateEventBase if JSON has +//! stateKey +//! +//! This means in particular that an event with a type known to RoomEvent but +//! having stateKey set (even to an empty value) will be treated as a state +//! event and most likely end up as unknown (consider, e.g., m.room.message +//! that has stateKey set). +template <> +inline RoomEventPtr doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + if (json.contains(StateKeyKeyL)) + return StateEventBase::factory.loadEvent(json, matrixType); + return RoomEvent::factory.loadEvent(json, matrixType); +} + template <> inline bool is(const Event& e) { -- cgit v1.2.3 From 060af9334049c58767b6457da2d7c07fdb0d171e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Dec 2021 16:13:02 +0100 Subject: StateEventBase: force type to unknown if stateKey is not in JSON --- lib/events/stateevent.cpp | 8 ++++++++ lib/events/stateevent.h | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 9535af56..e53d47d4 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -5,6 +5,14 @@ using namespace Quotient; +StateEventBase::StateEventBase(Type type, const QJsonObject& json) + : RoomEvent(json.contains(StateKeyKeyL) ? type : unknownEventTypeId(), json) +{ + if (Event::type() == unknownEventTypeId() && !json.contains(StateKeyKeyL)) + qWarning(EVENTS) << "Attempt to create a state event with no stateKey -" + "forcing the event type to unknown to avoid damage"; +} + StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, const QJsonObject& contentJson) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 919e8f86..c37965aa 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -21,8 +21,7 @@ class StateEventBase : public RoomEvent { public: static inline _impl::EventFactory factory { "StateEvent" }; - StateEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json) - {} + StateEventBase(Type type, const QJsonObject& json); StateEventBase(Type type, event_mtype_t matrixType, const QString& stateKey = {}, const QJsonObject& contentJson = {}); -- cgit v1.2.3 From 42eaf3671c656088f8d038f83973f0931ad7051c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 02:33:39 +0100 Subject: Sonar: leave just one job There's not much value in analysing the code without E2EE and with E2EE because E2EE is additive; and there's no plan to look close into the generated API code apart from what already ends up being committed. --- .github/workflows/sonar.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 76db59c9..ae96aebc 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -16,8 +16,8 @@ jobs: fail-fast: false matrix: qt-version: [ '5.12.10' ] - e2ee: [ '', 'e2ee' ] - update-api: [ '', 'update-api' ] + e2ee: [ 'e2ee' ] + update-api: [ '' ] env: SONAR_SCANNER_VERSION: 4.6.2.2472 -- cgit v1.2.3 From a08348007dd0a73a40b7b1d755b3affc963b5b80 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Dec 2021 17:31:37 +0100 Subject: Sonar: add coverage analysis --- .github/workflows/sonar.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index ae96aebc..c8ddca66 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -53,7 +53,8 @@ jobs: echo "CXX=g++-10" >>$GITHUB_ENV mkdir -p $HOME/.sonar echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV + -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" \ + -DCMAKE_CXX_FLAGS=--coverage >>$GITHUB_ENV cmake -E make_directory ${{ runner.workspace }}/build - name: Build and install olm @@ -110,4 +111,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | - $HOME/.sonar/sonar-scanner*/bin/sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" + $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ + -Dsonar.host.url="${{ env.SONAR_SERVER_URL }}" \ + -Dsonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" \ + -Dsonar.cfamily.gcov.reportsPath=build/coverage -- cgit v1.2.3 From 0cbbae133d61ccb1fbb41a40660a70c65f65235f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 23 Dec 2021 11:43:01 +0100 Subject: Merge Sonar invocation back to ci.yml For coverage analysis to work, a test run is needed, making the overlap between ci.yaml and sonar.yml quite significant again. Note: 'update-api' option is temporarily dropped from the matrix to speed up the check. If things run fine, 'update-api' will come back. --- .github/workflows/ci.yml | 59 +++++++++++++++++++--- .github/workflows/sonar.yml | 117 -------------------------------------------- 2 files changed, 51 insertions(+), 125 deletions(-) delete mode 100644 .github/workflows/sonar.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c78a5981..01a4468f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,16 +21,21 @@ jobs: matrix: os: [ubuntu-20.04, macos-10.15] compiler: [ GCC, Clang ] - platform: [ '' ] qt-version: [ '5.12.10' ] - qt-arch: [ '' ] # Not using binary values here, to make the job captions more readable e2ee: [ '' ] - update-api: [ '', 'update-api' ] + update-api: [ '' ] #, 'update-api' ] + sonar: [ '' ] + platform: [ '' ] + qt-arch: [ '' ] exclude: - os: macos-10.15 compiler: GCC include: + - os: ubuntu-latest + compiler: GCC + qt-version: '5.12.10' + sonar: 'sonar' - os: windows-2019 compiler: MSVC platform: x64 @@ -43,6 +48,9 @@ jobs: qt-arch: win64_msvc2017_64 update-api: update-api + env: + SONAR_SERVER_URL: 'https://sonarcloud.io' + steps: - uses: actions/checkout@v2 with: @@ -96,9 +104,18 @@ jobs: VERSION="$(git describe --all --contains)-ci${{ github.run_number }}-$(git rev-parse --short HEAD)" fi echo "QUOTEST_ORIGIN=$VERSION @ ${{ runner.os }}/${{ matrix.compiler }}" >>$GITHUB_ENV - echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local\ - -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" >>$GITHUB_ENV + + CMAKE_ARGS="-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DBUILD_SHARED_LIBS=false \ + -DCMAKE_INSTALL_PREFIX=~/.local \ + -DCMAKE_PREFIX_PATH=~/.local \ + -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" + + if [ -n "${{ matrix.sonar }}" ]; then + mkdir -p $HOME/.sonar + CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_CXX_FLAGS=--coverage" + fi + echo "CMAKE_ARGS=$CMAKE_ARGS" >>$GITHUB_ENV if [[ '${{ runner.os }}' != 'Windows' ]]; then BIN_DIR=/bin @@ -115,6 +132,20 @@ jobs: with: arch: ${{ matrix.platform }} + - name: Download and set up Sonar Cloud tools + if: matrix.sonar != '' + env: + SONAR_SCANNER_VERSION: 4.6.2.2472 + run: | + pushd $HOME/.sonar + curl -sSL --remote-name-all \ + $SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip \ + https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip + unzip -o build-wrapper*.zip + echo "BUILD_WRAPPER=$HOME/.sonar/build-wrapper-linux-x86/build-wrapper-linux* --out-dir $BUILD_PATH/sonar" >>$GITHUB_ENV + unzip -o sonar-scanner-cli*.zip + popd + - name: Build and install olm if: matrix.e2ee working-directory: ${{ runner.workspace }} @@ -158,7 +189,8 @@ jobs: - name: Build and install libQuotient run: | - cmake --build $BUILD_PATH --target all install + $BUILD_WRAPPER cmake --build $BUILD_PATH --target all + cmake --build $BUILD_PATH --target install ls ~/.local$BIN_DIR/quotest - name: Run tests @@ -172,7 +204,18 @@ jobs: [[ -z "$TEST_USER" ]] || \ $VALGRIND quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually - + - name: Perform CodeQL analysis if: env.CODEQL_ANALYSIS uses: github/codeql-action/analyze@v1 + + - name: Run sonar-scanner + if: matrix.sonar != '' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ + -Dsonar.host.url="$SONAR_SERVER_URL" \ + -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ + -Dsonar.cfamily.gcov.reportsPath="$BUILD_PATH/coverage" diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml deleted file mode 100644 index c8ddca66..00000000 --- a/.github/workflows/sonar.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Sonar - -on: - push: - pull_request: - types: [opened, reopened] - -defaults: - run: - shell: bash - -jobs: - SonarCloud: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - qt-version: [ '5.12.10' ] - e2ee: [ 'e2ee' ] - update-api: [ '' ] - - env: - SONAR_SCANNER_VERSION: 4.6.2.2472 - SONAR_SERVER_URL: "https://sonarcloud.io" - BUILD_WRAPPER_OUT_DIR: build/sonar - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - submodules: ${{ matrix.e2ee != '' }} - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v2 - with: - path: ${{ runner.workspace }}/Qt - key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache - - - name: Install Qt - uses: jurplel/install-qt-action@v2.11.1 - with: - version: ${{ matrix.qt-version }} -# arch: ${{ matrix.qt-arch }} # Only Windows needs that - cached: ${{ steps.cache-qt.outputs.cache-hit }} - - - name: Install Ninja - uses: seanmiddleditch/gha-setup-ninja@v3 - - - name: Setup build environment - run: | - echo "CC=gcc-10" >>$GITHUB_ENV - echo "CXX=g++-10" >>$GITHUB_ENV - mkdir -p $HOME/.sonar - echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" \ - -DCMAKE_CXX_FLAGS=--coverage >>$GITHUB_ENV - cmake -E make_directory ${{ runner.workspace }}/build - - - name: Build and install olm - if: matrix.e2ee - run: | - cd .. - git clone https://gitlab.matrix.org/matrix-org/olm.git - cmake -S olm -B olm/build $CMAKE_ARGS - cmake --build olm/build --target install - - - name: Pull CS API and build GTAD - if: matrix.update-api - run: | - cd .. - git clone https://github.com/quotient-im/matrix-doc.git - git clone --recursive https://github.com/KitsuneRal/gtad.git - cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - cmake --build gtad - echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ - -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ - >>$GITHUB_ENV - - - name: Download and set up Sonar Cloud tools - run: | - pushd $HOME/.sonar - curl -sSLo build-wrapper.zip $SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip - unzip -o build-wrapper.zip - echo "BUILD_WRAPPER=$HOME/.sonar/build-wrapper-linux-x86/build-wrapper-linux* --out-dir $BUILD_WRAPPER_OUT_DIR" >>$GITHUB_ENV - curl -sSLo sonar-scanner.zip \ - https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip - unzip -o sonar-scanner.zip - popd - - - name: Configure libQuotient - run: | - if [[ '${{ runner.os }}' == 'Windows' ]]; then - BIN_DIR=. - else - BIN_DIR=bin - fi - echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV - cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} - - - name: Regenerate API code - if: matrix.update-api - run: cmake --build build --target update-api - - - name: Build libQuotient - run: | - $BUILD_WRAPPER cmake --build build --target all - - - name: Run sonar-scanner - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ - -Dsonar.host.url="${{ env.SONAR_SERVER_URL }}" \ - -Dsonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" \ - -Dsonar.cfamily.gcov.reportsPath=build/coverage -- cgit v1.2.3 From 669ed9bcae110ca0539876193ec3b8cb9b8a8a18 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 02:35:44 +0100 Subject: Actually do and submit coverage ...instead of hoping the thing will sort itself out because CLion does. --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01a4468f..8596ccd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,7 @@ jobs: if [ -n "${{ matrix.sonar }}" ]; then mkdir -p $HOME/.sonar CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_CXX_FLAGS=--coverage" + echo "COV=gcov$CXX_VERSION_POSTFIX" >>$GITHUB_ENV fi echo "CMAKE_ARGS=$CMAKE_ARGS" >>$GITHUB_ENV @@ -215,7 +216,11 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | + mkdir .coverage && pushd .coverage + find $BUILD_PATH/CMakeFiles/Quotient.dir/lib -name '*.gcda' -print0 \ + | xargs -0 $COV -s $GITHUB_WORKSPACE/lib -pr + popd $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ - -Dsonar.cfamily.gcov.reportsPath="$BUILD_PATH/coverage" + -Dsonar.cfamily.gcov.reportsPath=.coverage -- cgit v1.2.3 From 3fa5925ad6c9ce558dd9726a02614fd6f9c031e5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 02:35:56 +0100 Subject: Analyse in 2 threads --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8596ccd5..47c92ad3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,4 +223,5 @@ jobs: $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ + -Dsonar.cfamily.threads=2 \ -Dsonar.cfamily.gcov.reportsPath=.coverage -- cgit v1.2.3 From e1c19404846b2a7a2f321c662528252e4eeef35e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 06:42:36 +0100 Subject: Don't strip lib from names in .gcov files That apparently confuses Sonar as it fails to match the source files. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47c92ad3..27c65b26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -218,7 +218,7 @@ jobs: run: | mkdir .coverage && pushd .coverage find $BUILD_PATH/CMakeFiles/Quotient.dir/lib -name '*.gcda' -print0 \ - | xargs -0 $COV -s $GITHUB_WORKSPACE/lib -pr + | xargs -0 $COV -s $GITHUB_WORKSPACE -pr popd $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -- cgit v1.2.3 From a207439d165b00b689e37b2759e0ca42bdfb22ae Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 08:00:39 +0100 Subject: Reinstate update-api jobs --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27c65b26..5a3a0708 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: qt-version: [ '5.12.10' ] # Not using binary values here, to make the job captions more readable e2ee: [ '' ] - update-api: [ '' ] #, 'update-api' ] + update-api: [ '', 'update-api' ] sonar: [ '' ] platform: [ '' ] qt-arch: [ '' ] -- cgit v1.2.3 From e4a9132ab5c684be9743823df7c31d12da1e3f3b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 10:40:44 +0100 Subject: CI: Add missing coverage files --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a3a0708..72deb803 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,9 +217,11 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | mkdir .coverage && pushd .coverage - find $BUILD_PATH/CMakeFiles/Quotient.dir/lib -name '*.gcda' -print0 \ + find $BUILD_PATH -name '*.gcda' -print0 \ | xargs -0 $COV -s $GITHUB_WORKSPACE -pr popd + # Drop coverage of the test source code, which is obviously 100% + rm -f quotest* autotests* $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ -- cgit v1.2.3 From 179564eb4c2d5ee59ef129edab39ca1a7cbc4258 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 12:22:59 +0100 Subject: CI: Move a comment outside of the script Comments inside shell scripts apparently break the flimsy GHA machinery. --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72deb803..5cac5874 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -215,12 +215,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + # Coverage of the test source code is not tracked, as it is obviously 100% + # (if it's not, some tests failed and break the build at an earlier stage) run: | mkdir .coverage && pushd .coverage find $BUILD_PATH -name '*.gcda' -print0 \ | xargs -0 $COV -s $GITHUB_WORKSPACE -pr popd - # Drop coverage of the test source code, which is obviously 100% rm -f quotest* autotests* $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -- cgit v1.2.3 From 165e17d4ad289dabdcb6a09ce27c02c6122b7f8a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 13:37:55 +0100 Subject: CI: Fix rm being run in the wrong directory It's been alright with the comment inside the script. --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cac5874..a1b6f0c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -215,14 +215,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - # Coverage of the test source code is not tracked, as it is obviously 100% - # (if it's not, some tests failed and break the build at an earlier stage) run: | mkdir .coverage && pushd .coverage find $BUILD_PATH -name '*.gcda' -print0 \ | xargs -0 $COV -s $GITHUB_WORKSPACE -pr - popd + # Coverage of the test source code is not tracked, as it is always 100% + # (if not, some tests failed and broke the build at an earlier stage) rm -f quotest* autotests* + popd $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ -- cgit v1.2.3 From ae0180acc50de443e98bc1c59dee94f0096df2e0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 15:40:13 +0100 Subject: Prune empty/ish call*event.cpp files --- CMakeLists.txt | 6 +++--- lib/events/callcandidatesevent.cpp | 27 --------------------------- lib/events/callhangupevent.cpp | 36 ------------------------------------ lib/events/callhangupevent.h | 8 ++++++-- 4 files changed, 9 insertions(+), 68 deletions(-) delete mode 100644 lib/events/callcandidatesevent.cpp delete mode 100644 lib/events/callhangupevent.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7675daad..adb5be7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,10 +162,10 @@ list(APPEND lib_SRCS lib/events/accountdataevents.h lib/events/receiptevent.h lib/events/receiptevent.cpp lib/events/reactionevent.h lib/events/reactionevent.cpp - lib/events/callanswerevent.h lib/events/callanswerevent.cpp - lib/events/callcandidatesevent.h lib/events/callcandidatesevent.cpp - lib/events/callhangupevent.h lib/events/callhangupevent.cpp lib/events/callinviteevent.h lib/events/callinviteevent.cpp + lib/events/callcandidatesevent.h + lib/events/callanswerevent.h lib/events/callanswerevent.cpp + lib/events/callhangupevent.h lib/events/directchatevent.h lib/events/directchatevent.cpp lib/events/encryptionevent.h lib/events/encryptionevent.cpp lib/events/encryptedevent.h lib/events/encryptedevent.cpp diff --git a/lib/events/callcandidatesevent.cpp b/lib/events/callcandidatesevent.cpp deleted file mode 100644 index b87c8e9b..00000000 --- a/lib/events/callcandidatesevent.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard -// SPDX-FileCopyrightText: 2018 Josip Delic -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "callcandidatesevent.h" - -/* -m.call.candidates -{ - "age": 242352, - "content": { - "call_id": "12345", - "candidates": [ - { - "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 -43670 typ host generation 0", "sdpMLineIndex": 0, "sdpMid": "audio" - } - ], - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.candidates" -} -*/ diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp deleted file mode 100644 index 43bc4db0..00000000 --- a/lib/events/callhangupevent.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * SPDX-FileCopyrightText: 2018 Josip Delic - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include "callhangupevent.h" - -/* -m.call.hangup -{ - "age": 242352, - "content": { - "call_id": "12345", - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.hangup" -} -*/ - -using namespace Quotient; - -CallHangupEvent::CallHangupEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) -{ - qCDebug(EVENTS) << "Call Hangup event"; -} - -CallHangupEvent::CallHangupEvent(const QString& callId) - : CallEventBase(typeId(), matrixTypeId(), callId, 0) -{} diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index 24382ac2..f3f82833 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -11,8 +11,12 @@ class CallHangupEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) - explicit CallHangupEvent(const QJsonObject& obj); - explicit CallHangupEvent(const QString& callId); + explicit CallHangupEvent(const QJsonObject& obj) + : CallEventBase(typeId(), obj) + {} + explicit CallHangupEvent(const QString& callId) + : CallEventBase(typeId(), matrixTypeId(), callId, 0) + {} }; REGISTER_EVENT_TYPE(CallHangupEvent) -- cgit v1.2.3 From ebff6633ad464991fdffed1841ec6dc5b34296a2 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sat, 25 Dec 2021 23:46:14 +0100 Subject: Make canChangePassword available from QML --- lib/connection.h | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/connection.h b/lib/connection.h index 05a3bb7f..52bf3cea 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -124,6 +124,7 @@ class Connection : public QObject { Q_PROPERTY(bool supportsPasswordAuth READ supportsPasswordAuth NOTIFY loginFlowsChanged STORED false) Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged) Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged) + Q_PROPERTY(bool canChangePassword READ canChangePassword NOTIFY capabilitiesLoaded) public: using UsersToDevicesToEvents = -- cgit v1.2.3 From 53c494f1b9f273395caade35c93e3fb520d083ec Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 27 Dec 2021 20:15:47 +0100 Subject: Quotest: add compile warnings from libQuotient --- quotest/CMakeLists.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/quotest/CMakeLists.txt b/quotest/CMakeLists.txt index 8d67b6f1..cb41141d 100644 --- a/quotest/CMakeLists.txt +++ b/quotest/CMakeLists.txt @@ -8,6 +8,21 @@ find_package(${Qt} COMPONENTS Concurrent) add_executable(quotest ${quotest_SRCS}) target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${Qt}::Concurrent ${PROJECT_NAME}) +if (MSVC) + target_compile_options(quotest PUBLIC /EHsc /W4 + /wd4100 /wd4127 /wd4242 /wd4244 /wd4245 /wd4267 /wd4365 /wd4456 /wd4459 + /wd4464 /wd4505 /wd4514 /wd4571 /wd4619 /wd4623 /wd4625 /wd4626 /wd4706 + /wd4710 /wd4774 /wd4820 /wd4946 /wd5026 /wd5027) +else() + foreach (FLAG W Wall Wpedantic Wextra Wno-unused-parameter Werror=return-type) + CHECK_CXX_COMPILER_FLAG("-${FLAG}" COMPILER_${FLAG}_SUPPORTED) + if (COMPILER_${FLAG}_SUPPORTED AND + NOT CMAKE_CXX_FLAGS MATCHES "(^| )-?${FLAG}($| )") + target_compile_options(quotest PUBLIC -${FLAG}) + endif () + endforeach () +endif() + option(${PROJECT_NAME}_INSTALL_TESTS "install quotest application" ON) add_feature_info(InstallQuotest ${PROJECT_NAME}_INSTALL_TESTS "the library functional test suite") -- cgit v1.2.3 From 58b2501aeecf2c169fd1f583dca292169568b5fa Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 26 Dec 2021 09:29:00 +0100 Subject: Connection: Simplify room/user factory code There's no need to return lambdas where pointers to specialised function templates would work just fine. --- lib/connection.cpp | 4 ++-- lib/connection.h | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 25219def..8d1c80f1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1513,8 +1513,8 @@ room_factory_t Connection::roomFactory() { return _roomFactory; } user_factory_t Connection::userFactory() { return _userFactory; } -room_factory_t Connection::_roomFactory = defaultRoomFactory<>(); -user_factory_t Connection::_userFactory = defaultUserFactory<>(); +room_factory_t Connection::_roomFactory = defaultRoomFactory<>; +user_factory_t Connection::_userFactory = defaultUserFactory<>; QByteArray Connection::generateTxnId() const { diff --git a/lib/connection.h b/lib/connection.h index 52bf3cea..0713af16 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -81,11 +81,9 @@ using user_factory_t = std::function; * \sa Connection::setRoomFactory, Connection::setRoomType */ template -static inline room_factory_t defaultRoomFactory() +auto defaultRoomFactory(Connection* c, const QString& id, JoinState js) { - return [](Connection* c, const QString& id, JoinState js) { - return new T(c, id, js); - }; + return new T(c, id, js); } /** The default factory to create user objects @@ -94,9 +92,9 @@ static inline room_factory_t defaultRoomFactory() * \sa Connection::setUserFactory, Connection::setUserType */ template -static inline user_factory_t defaultUserFactory() +auto defaultUserFactory(Connection* c, const QString& id) { - return [](Connection* c, const QString& id) { return new T(id, c); }; + return new T(id, c); } // Room ids, rather than room pointers, are used in the direct chat @@ -469,14 +467,14 @@ public: template static void setRoomType() { - setRoomFactory(defaultRoomFactory()); + setRoomFactory(defaultRoomFactory); } /// Set the user factory to default with the overriden user type template static void setUserType() { - setUserFactory(defaultUserFactory()); + setUserFactory(defaultUserFactory); } public Q_SLOTS: -- cgit v1.2.3 From 674e984e459375974f619d0e778d43a2cc928dc3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 26 Dec 2021 09:33:14 +0100 Subject: Key* strings: drop 'static'; add 'constexpr' where ok --- lib/events/event.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index e786fb30..8f62872d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -29,24 +29,24 @@ inline TargetEventT* weakPtrCast(const event_ptr_tt& ptr) // === Standard Matrix key names and basicEventJson() === -static const auto TypeKey = QStringLiteral("type"); -static const auto BodyKey = QStringLiteral("body"); -static const auto ContentKey = QStringLiteral("content"); -static const auto EventIdKey = QStringLiteral("event_id"); -static const auto SenderKey = QStringLiteral("sender"); -static const auto RoomIdKey = QStringLiteral("room_id"); -static const auto UnsignedKey = QStringLiteral("unsigned"); -static const auto StateKeyKey = QStringLiteral("state_key"); -static const auto TypeKeyL = "type"_ls; -static const auto BodyKeyL = "body"_ls; -static const auto ContentKeyL = "content"_ls; -static const auto EventIdKeyL = "event_id"_ls; -static const auto SenderKeyL = "sender"_ls; -static const auto RoomIdKeyL = "room_id"_ls; -static const auto UnsignedKeyL = "unsigned"_ls; -static const auto RedactedCauseKeyL = "redacted_because"_ls; -static const auto PrevContentKeyL = "prev_content"_ls; -static const auto StateKeyKeyL = "state_key"_ls; +constexpr auto TypeKeyL = "type"_ls; +constexpr auto BodyKeyL = "body"_ls; +constexpr auto ContentKeyL = "content"_ls; +constexpr auto EventIdKeyL = "event_id"_ls; +constexpr auto SenderKeyL = "sender"_ls; +constexpr auto RoomIdKeyL = "room_id"_ls; +constexpr auto UnsignedKeyL = "unsigned"_ls; +constexpr auto RedactedCauseKeyL = "redacted_because"_ls; +constexpr auto PrevContentKeyL = "prev_content"_ls; +constexpr auto StateKeyKeyL = "state_key"_ls; +const QString TypeKey { TypeKeyL }; +const QString BodyKey { BodyKeyL }; +const QString ContentKey { ContentKeyL }; +const QString EventIdKey { EventIdKeyL }; +const QString SenderKey { SenderKeyL }; +const QString RoomIdKey { RoomIdKeyL }; +const QString UnsignedKey { UnsignedKeyL }; +const QString StateKeyKey { StateKeyKeyL }; /// Make a minimal correct Matrix event JSON inline QJsonObject basicEventJson(const QString& matrixType, -- cgit v1.2.3