From f951ab8a931c68f61b9710a540b2c971bbf502ca Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 3 Jan 2018 12:11:16 +0900 Subject: jobs/generated: Polish formatting; other minor cleanup --- jobs/generated/administrative_contact.cpp | 8 +------- jobs/generated/administrative_contact.h | 8 ++++---- jobs/generated/banning.cpp | 1 - jobs/generated/banning.h | 1 - jobs/generated/directory.cpp | 6 +----- jobs/generated/directory.h | 7 +++---- jobs/generated/inviting.cpp | 1 - jobs/generated/inviting.h | 1 - jobs/generated/kicking.cpp | 1 - jobs/generated/kicking.h | 1 - jobs/generated/leaving.cpp | 1 - jobs/generated/leaving.h | 1 - jobs/generated/list_public_rooms.cpp | 13 ++----------- jobs/generated/list_public_rooms.h | 13 ++++++------- jobs/generated/login.cpp | 6 +----- jobs/generated/login.h | 7 +++---- jobs/generated/logout.cpp | 1 - jobs/generated/logout.h | 1 - jobs/generated/profile.cpp | 16 +++------------- jobs/generated/profile.h | 19 +++++++++---------- jobs/generated/receipts.cpp | 1 - jobs/generated/receipts.h | 1 - jobs/generated/redaction.cpp | 6 +----- jobs/generated/redaction.h | 7 +++---- jobs/generated/third_party_membership.cpp | 1 - jobs/generated/third_party_membership.h | 1 - jobs/generated/typing.cpp | 1 - jobs/generated/typing.h | 1 - jobs/generated/versions.cpp | 6 +----- jobs/generated/versions.h | 7 +++---- jobs/generated/whoami.cpp | 6 +----- jobs/generated/whoami.h | 7 +++---- 32 files changed, 45 insertions(+), 113 deletions(-) diff --git a/jobs/generated/administrative_contact.cpp b/jobs/generated/administrative_contact.cpp index 705c5d54..ca029f58 100644 --- a/jobs/generated/administrative_contact.cpp +++ b/jobs/generated/administrative_contact.cpp @@ -2,11 +2,8 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "administrative_contact.h" -#include "converters.h" - #include using namespace QMatrixClient; @@ -52,10 +49,7 @@ GetAccount3PIDsJob::GetAccount3PIDsJob() ), d(new Private) { } -GetAccount3PIDsJob::~GetAccount3PIDsJob() -{ - delete d; -} +GetAccount3PIDsJob::~GetAccount3PIDsJob() = default; const QVector& GetAccount3PIDsJob::threepids() const { diff --git a/jobs/generated/administrative_contact.h b/jobs/generated/administrative_contact.h index fa6beba9..a5f04781 100644 --- a/jobs/generated/administrative_contact.h +++ b/jobs/generated/administrative_contact.h @@ -2,12 +2,12 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" #include +#include #include "converters.h" @@ -34,13 +34,13 @@ namespace QMatrixClient ~GetAccount3PIDsJob() override; const QVector& threepids() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; class Post3PIDsJob : public BaseJob diff --git a/jobs/generated/banning.cpp b/jobs/generated/banning.cpp index 96f80ea8..d7708cc6 100644 --- a/jobs/generated/banning.cpp +++ b/jobs/generated/banning.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "banning.h" #include "converters.h" diff --git a/jobs/generated/banning.h b/jobs/generated/banning.h index 6db096ee..930020a5 100644 --- a/jobs/generated/banning.h +++ b/jobs/generated/banning.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" diff --git a/jobs/generated/directory.cpp b/jobs/generated/directory.cpp index dcec75ac..1fd1e443 100644 --- a/jobs/generated/directory.cpp +++ b/jobs/generated/directory.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "directory.h" #include "converters.h" @@ -39,10 +38,7 @@ GetRoomIdByAliasJob::GetRoomIdByAliasJob(const QString& roomAlias) ), d(new Private) { } -GetRoomIdByAliasJob::~GetRoomIdByAliasJob() -{ - delete d; -} +GetRoomIdByAliasJob::~GetRoomIdByAliasJob() = default; const QString& GetRoomIdByAliasJob::roomId() const { diff --git a/jobs/generated/directory.h b/jobs/generated/directory.h index 1dd4e7ed..8290a2b5 100644 --- a/jobs/generated/directory.h +++ b/jobs/generated/directory.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" @@ -29,13 +28,13 @@ namespace QMatrixClient const QString& roomId() const; const QVector& servers() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; class DeleteRoomAliasJob : public BaseJob diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp index 5f89adf7..bdf257dd 100644 --- a/jobs/generated/inviting.cpp +++ b/jobs/generated/inviting.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "inviting.h" #include "converters.h" diff --git a/jobs/generated/inviting.h b/jobs/generated/inviting.h index 225cb516..7ed49637 100644 --- a/jobs/generated/inviting.h +++ b/jobs/generated/inviting.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp index 86dde629..3488b387 100644 --- a/jobs/generated/kicking.cpp +++ b/jobs/generated/kicking.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "kicking.h" #include "converters.h" diff --git a/jobs/generated/kicking.h b/jobs/generated/kicking.h index 7c834e45..84d88945 100644 --- a/jobs/generated/kicking.h +++ b/jobs/generated/kicking.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" diff --git a/jobs/generated/leaving.cpp b/jobs/generated/leaving.cpp index 2cf7fda3..604fcc73 100644 --- a/jobs/generated/leaving.cpp +++ b/jobs/generated/leaving.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "leaving.h" #include "converters.h" diff --git a/jobs/generated/leaving.h b/jobs/generated/leaving.h index 28ba3d92..f006ce19 100644 --- a/jobs/generated/leaving.h +++ b/jobs/generated/leaving.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" diff --git a/jobs/generated/list_public_rooms.cpp b/jobs/generated/list_public_rooms.cpp index 8a96966f..d15e9de3 100644 --- a/jobs/generated/list_public_rooms.cpp +++ b/jobs/generated/list_public_rooms.cpp @@ -2,11 +2,8 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "list_public_rooms.h" -#include "converters.h" - #include using namespace QMatrixClient; @@ -80,10 +77,7 @@ GetPublicRoomsJob::GetPublicRoomsJob(double limit, const QString& since, const Q ), d(new Private) { } -GetPublicRoomsJob::~GetPublicRoomsJob() -{ - delete d; -} +GetPublicRoomsJob::~GetPublicRoomsJob() = default; const QVector& GetPublicRoomsJob::chunk() const { @@ -213,10 +207,7 @@ QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, double limit, co setRequestData(_data); } -QueryPublicRoomsJob::~QueryPublicRoomsJob() -{ - delete d; -} +QueryPublicRoomsJob::~QueryPublicRoomsJob() = default; const QVector& QueryPublicRoomsJob::chunk() const { diff --git a/jobs/generated/list_public_rooms.h b/jobs/generated/list_public_rooms.h index 74dd8626..f6467a21 100644 --- a/jobs/generated/list_public_rooms.h +++ b/jobs/generated/list_public_rooms.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" @@ -45,13 +44,13 @@ namespace QMatrixClient const QString& nextBatch() const; const QString& prevBatch() const; double totalRoomCountEstimate() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; class QueryPublicRoomsJob : public BaseJob @@ -90,12 +89,12 @@ namespace QMatrixClient const QString& nextBatch() const; const QString& prevBatch() const; double totalRoomCountEstimate() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; } // namespace QMatrixClient diff --git a/jobs/generated/login.cpp b/jobs/generated/login.cpp index 4c159517..bbfff0da 100644 --- a/jobs/generated/login.cpp +++ b/jobs/generated/login.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "login.h" #include "converters.h" @@ -47,10 +46,7 @@ LoginJob::LoginJob(const QString& type, const QString& user, const QString& medi setRequestData(_data); } -LoginJob::~LoginJob() -{ - delete d; -} +LoginJob::~LoginJob() = default; const QString& LoginJob::userId() const { diff --git a/jobs/generated/login.h b/jobs/generated/login.h index 1c017877..0f68a13b 100644 --- a/jobs/generated/login.h +++ b/jobs/generated/login.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" @@ -24,12 +23,12 @@ namespace QMatrixClient const QString& accessToken() const; const QString& homeServer() const; const QString& deviceId() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; } // namespace QMatrixClient diff --git a/jobs/generated/logout.cpp b/jobs/generated/logout.cpp index c250bddf..b6904070 100644 --- a/jobs/generated/logout.cpp +++ b/jobs/generated/logout.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "logout.h" #include "converters.h" diff --git a/jobs/generated/logout.h b/jobs/generated/logout.h index ae9e54b8..d2b85db5 100644 --- a/jobs/generated/logout.h +++ b/jobs/generated/logout.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" diff --git a/jobs/generated/profile.cpp b/jobs/generated/profile.cpp index 6ec566f7..27bab0b8 100644 --- a/jobs/generated/profile.cpp +++ b/jobs/generated/profile.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "profile.h" #include "converters.h" @@ -38,10 +37,7 @@ GetDisplayNameJob::GetDisplayNameJob(const QString& userId) ), d(new Private) { } -GetDisplayNameJob::~GetDisplayNameJob() -{ - delete d; -} +GetDisplayNameJob::~GetDisplayNameJob() = default; const QString& GetDisplayNameJob::displayname() const { @@ -80,10 +76,7 @@ GetAvatarUrlJob::GetAvatarUrlJob(const QString& userId) ), d(new Private) { } -GetAvatarUrlJob::~GetAvatarUrlJob() -{ - delete d; -} +GetAvatarUrlJob::~GetAvatarUrlJob() = default; const QString& GetAvatarUrlJob::avatarUrl() const { @@ -111,10 +104,7 @@ GetUserProfileJob::GetUserProfileJob(const QString& userId) ), d(new Private) { } -GetUserProfileJob::~GetUserProfileJob() -{ - delete d; -} +GetUserProfileJob::~GetUserProfileJob() = default; const QString& GetUserProfileJob::avatarUrl() const { diff --git a/jobs/generated/profile.h b/jobs/generated/profile.h index 30e858de..9cbf3865 100644 --- a/jobs/generated/profile.h +++ b/jobs/generated/profile.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" @@ -27,13 +26,13 @@ namespace QMatrixClient ~GetDisplayNameJob() override; const QString& displayname() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; class SetAvatarUrlJob : public BaseJob @@ -49,13 +48,13 @@ namespace QMatrixClient ~GetAvatarUrlJob() override; const QString& avatarUrl() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; class GetUserProfileJob : public BaseJob @@ -66,12 +65,12 @@ namespace QMatrixClient const QString& avatarUrl() const; const QString& displayname() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; } // namespace QMatrixClient diff --git a/jobs/generated/receipts.cpp b/jobs/generated/receipts.cpp index 2820b583..1c8bd80f 100644 --- a/jobs/generated/receipts.cpp +++ b/jobs/generated/receipts.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "receipts.h" #include "converters.h" diff --git a/jobs/generated/receipts.h b/jobs/generated/receipts.h index 6f36d7fc..e4065ddb 100644 --- a/jobs/generated/receipts.h +++ b/jobs/generated/receipts.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" diff --git a/jobs/generated/redaction.cpp b/jobs/generated/redaction.cpp index a9b8ed7e..9207f344 100644 --- a/jobs/generated/redaction.cpp +++ b/jobs/generated/redaction.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "redaction.h" #include "converters.h" @@ -31,10 +30,7 @@ RedactEventJob::RedactEventJob(const QString& roomId, const QString& eventId, co setRequestData(_data); } -RedactEventJob::~RedactEventJob() -{ - delete d; -} +RedactEventJob::~RedactEventJob() = default; const QString& RedactEventJob::eventId() const { diff --git a/jobs/generated/redaction.h b/jobs/generated/redaction.h index 600e0daa..0a68f8cd 100644 --- a/jobs/generated/redaction.h +++ b/jobs/generated/redaction.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" @@ -21,12 +20,12 @@ namespace QMatrixClient ~RedactEventJob() override; const QString& eventId() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; } // namespace QMatrixClient diff --git a/jobs/generated/third_party_membership.cpp b/jobs/generated/third_party_membership.cpp index 7a2aa4f4..dbb3ebee 100644 --- a/jobs/generated/third_party_membership.cpp +++ b/jobs/generated/third_party_membership.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "third_party_membership.h" #include "converters.h" diff --git a/jobs/generated/third_party_membership.h b/jobs/generated/third_party_membership.h index 6c1193ed..b1669795 100644 --- a/jobs/generated/third_party_membership.h +++ b/jobs/generated/third_party_membership.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" diff --git a/jobs/generated/typing.cpp b/jobs/generated/typing.cpp index 44bbb131..a6817cb9 100644 --- a/jobs/generated/typing.cpp +++ b/jobs/generated/typing.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "typing.h" #include "converters.h" diff --git a/jobs/generated/typing.h b/jobs/generated/typing.h index e20bca1a..6eb3ddf4 100644 --- a/jobs/generated/typing.h +++ b/jobs/generated/typing.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" diff --git a/jobs/generated/versions.cpp b/jobs/generated/versions.cpp index 66b31290..8ff58365 100644 --- a/jobs/generated/versions.cpp +++ b/jobs/generated/versions.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "versions.h" #include "converters.h" @@ -26,10 +25,7 @@ GetVersionsJob::GetVersionsJob() ), d(new Private) { } -GetVersionsJob::~GetVersionsJob() -{ - delete d; -} +GetVersionsJob::~GetVersionsJob() = default; const QVector& GetVersionsJob::versions() const { diff --git a/jobs/generated/versions.h b/jobs/generated/versions.h index eab8cf9e..a7add8ba 100644 --- a/jobs/generated/versions.h +++ b/jobs/generated/versions.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" @@ -22,12 +21,12 @@ namespace QMatrixClient ~GetVersionsJob() override; const QVector& versions() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; } // namespace QMatrixClient diff --git a/jobs/generated/whoami.cpp b/jobs/generated/whoami.cpp index dce091ec..d4da99d4 100644 --- a/jobs/generated/whoami.cpp +++ b/jobs/generated/whoami.cpp @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #include "whoami.h" #include "converters.h" @@ -26,10 +25,7 @@ GetTokenOwnerJob::GetTokenOwnerJob() ), d(new Private) { } -GetTokenOwnerJob::~GetTokenOwnerJob() -{ - delete d; -} +GetTokenOwnerJob::~GetTokenOwnerJob() = default; const QString& GetTokenOwnerJob::userId() const { diff --git a/jobs/generated/whoami.h b/jobs/generated/whoami.h index 1b04f337..21cb1a17 100644 --- a/jobs/generated/whoami.h +++ b/jobs/generated/whoami.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" @@ -21,12 +20,12 @@ namespace QMatrixClient ~GetTokenOwnerJob() override; const QString& userId() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer d; }; } // namespace QMatrixClient -- cgit v1.2.3 From 986fc27e451b21cdd118e74da9e9ff22e275ef75 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 3 Jan 2018 12:19:25 +0900 Subject: BaseJob, MediaThumbnailJob: Support request and response headers Enable specifying headers in the request and checking/using headers in the response. --- jobs/basejob.cpp | 110 ++++++++++++++++++++++++++++++++++++--------- jobs/basejob.h | 18 ++++++-- jobs/mediathumbnailjob.cpp | 6 +-- jobs/mediathumbnailjob.h | 2 +- 4 files changed, 107 insertions(+), 29 deletions(-) diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index 9df3e430..980814c4 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -60,10 +60,16 @@ class BaseJob::Private // Contents for the network request HttpVerb verb; QString apiEndpoint; + QHash requestHeaders; QUrlQuery requestQuery; Data requestData; bool needsToken; + // There's no use of QMimeType here because we don't want to match + // content types against the known MIME type hierarchy; and at the same + // type QMimeType is of little help with MIME type globs (`text/*` etc.) + QByteArrayList expectedContentTypes; + QScopedPointer reply; Status status = Pending; @@ -117,6 +123,22 @@ void BaseJob::setApiEndpoint(const QString& apiEndpoint) d->apiEndpoint = apiEndpoint; } +const BaseJob::headers_t&BaseJob::requestHeaders() const +{ + return d->requestHeaders; +} + +void BaseJob::setRequestHeader(const headers_t::key_type& headerName, + const headers_t::mapped_type& headerValue) +{ + d->requestHeaders[headerName] = headerValue; +} + +void BaseJob::setRequestHeaders(const BaseJob::headers_t& headers) +{ + d->requestHeaders = headers; +} + const QUrlQuery& BaseJob::query() const { return d->requestQuery; @@ -137,6 +159,21 @@ void BaseJob::setRequestData(const BaseJob::Data& data) d->requestData = data; } +const QByteArrayList& BaseJob::expectedContentTypes() const +{ + return d->expectedContentTypes; +} + +void BaseJob::addExpectedContentType(const QByteArray& contentType) +{ + d->expectedContentTypes << contentType; +} + +void BaseJob::setExpectedContentTypes(const QByteArrayList& contentTypes) +{ + d->expectedContentTypes = contentTypes; +} + void BaseJob::Private::sendRequest() { QUrl url = connection->baseUrl(); @@ -148,13 +185,16 @@ void BaseJob::Private::sendRequest() url.setQuery(requestQuery); QNetworkRequest req {url}; - req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + if (!requestHeaders.contains("Content-Type")) + req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); req.setRawHeader(QByteArray("Authorization"), QByteArray("Bearer ") + connection->accessToken()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); req.setMaximumRedirectsAllowed(10); #endif + for (auto it = requestHeaders.cbegin(); it != requestHeaders.cend(); ++it) + req.setRawHeader(it.key(), it.value()); switch( verb ) { case HttpVerb::Get: @@ -206,11 +246,34 @@ void BaseJob::gotReply() { setStatus(checkReply(d->reply.data())); if (status().good()) - setStatus(parseReply(d->reply->readAll())); + setStatus(parseReply(d->reply.data())); finishJob(); } +bool checkContentType(const QByteArray& type, const QByteArrayList& patterns) +{ + if (patterns.isEmpty()) + return true; + + for (const auto& pattern: patterns) + { + if (pattern.startsWith('*') || type == pattern) // Fast lane + return true; + + auto patternParts = pattern.split('/'); + Q_ASSERT_X(patternParts.size() <= 2, __FUNCTION__, + "BaseJob: Expected content type should have up to two" + " /-separated parts; violating pattern: " + pattern); + + if (type.split('/').front() == patternParts.front() && + patternParts.back() == "*") + return true; // Exact match already went on fast lane + } + + return false; +} + BaseJob::Status BaseJob::checkReply(QNetworkReply* reply) const { qCDebug(d->logCat) << this << "returned from" << reply->url().toDisplayString(); @@ -218,30 +281,35 @@ BaseJob::Status BaseJob::checkReply(QNetworkReply* reply) const qCDebug(d->logCat) << this << "returned" << reply->error(); switch( reply->error() ) { - case QNetworkReply::NoError: - return NoError; - - case QNetworkReply::AuthenticationRequiredError: - case QNetworkReply::ContentAccessDenied: - case QNetworkReply::ContentOperationNotPermittedError: - return { ContentAccessError, reply->errorString() }; - - case QNetworkReply::ProtocolInvalidOperationError: - case QNetworkReply::UnknownContentError: - return { IncorrectRequestError, reply->errorString() }; - - case QNetworkReply::ContentNotFoundError: - return { NotFoundError, reply->errorString() }; - - default: - return { NetworkError, reply->errorString() }; + case QNetworkReply::NoError: + if (checkContentType(reply->rawHeader("Content-Type"), + d->expectedContentTypes)) + return NoError; + else + return { IncorrectResponseError, + "Incorrect content type of the response" }; + + case QNetworkReply::AuthenticationRequiredError: + case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentOperationNotPermittedError: + return { ContentAccessError, reply->errorString() }; + + case QNetworkReply::ProtocolInvalidOperationError: + case QNetworkReply::UnknownContentError: + return { IncorrectRequestError, reply->errorString() }; + + case QNetworkReply::ContentNotFoundError: + return { NotFoundError, reply->errorString() }; + + default: + return { NetworkError, reply->errorString() }; } } -BaseJob::Status BaseJob::parseReply(QByteArray data) +BaseJob::Status BaseJob::parseReply(QNetworkReply* reply) { QJsonParseError error; - QJsonDocument json = QJsonDocument::fromJson(data, &error); + QJsonDocument json = QJsonDocument::fromJson(reply->readAll(), &error); if( error.error == QJsonParseError::NoError ) return parseJson(json); else diff --git a/jobs/basejob.h b/jobs/basejob.h index 2f75c095..1d7f2388 100644 --- a/jobs/basejob.h +++ b/jobs/basejob.h @@ -25,7 +25,6 @@ #include #include #include -#include class QNetworkReply; class QSslError; @@ -59,6 +58,7 @@ namespace QMatrixClient , ContentAccessError , NotFoundError , IncorrectRequestError + , IncorrectResponseError , UserDefinedError = 200 }; @@ -214,12 +214,21 @@ namespace QMatrixClient void failure(BaseJob*); protected: + using headers_t = QHash; + const QString& apiEndpoint() const; 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; void setRequestQuery(const QUrlQuery& query); const Data& requestData() const; void setRequestData(const Data& data); + const QByteArrayList& expectedContentTypes() const; + void addExpectedContentType(const QByteArray& contentType); + void setExpectedContentTypes(const QByteArrayList& contentTypes); virtual void beforeStart(const ConnectionData* connData); @@ -240,11 +249,11 @@ namespace QMatrixClient * Processes the reply. By default, parses the reply into * a QJsonDocument and calls parseJson() if it's a valid JSON. * - * @param data raw contents of a HTTP reply from the server (without headers) + * @param reply raw contents of a HTTP reply from the server (without headers) * * @see gotReply, parseJson */ - virtual Status parseReply(QByteArray data); + virtual Status parseReply(QNetworkReply* reply); /** * Processes the JSON document received from the Matrix server. @@ -265,7 +274,8 @@ namespace QMatrixClient void setLoggingCategory(LoggingCategory lcf); // Job objects should only be deleted via QObject::deleteLater - virtual ~BaseJob(); + ~BaseJob() override; + protected slots: void timeout(); diff --git a/jobs/mediathumbnailjob.cpp b/jobs/mediathumbnailjob.cpp index c0d67a63..9337549e 100644 --- a/jobs/mediathumbnailjob.cpp +++ b/jobs/mediathumbnailjob.cpp @@ -17,8 +17,8 @@ */ #include "mediathumbnailjob.h" -#include "util.h" +#include #include using namespace QMatrixClient; @@ -47,9 +47,9 @@ QImage MediaThumbnailJob::scaledThumbnail(QSize toSize) const Qt::KeepAspectRatio, Qt::SmoothTransformation); } -BaseJob::Status MediaThumbnailJob::parseReply(QByteArray data) +BaseJob::Status MediaThumbnailJob::parseReply(QNetworkReply* reply) { - if( !_thumbnail.loadFromData(data) ) + if( !_thumbnail.loadFromData(reply->readAll()) ) { qCDebug(JOBS) << "MediaThumbnailJob: could not read image data"; } diff --git a/jobs/mediathumbnailjob.h b/jobs/mediathumbnailjob.h index f8f36fe9..2d6853c7 100644 --- a/jobs/mediathumbnailjob.h +++ b/jobs/mediathumbnailjob.h @@ -36,7 +36,7 @@ namespace QMatrixClient QImage scaledThumbnail(QSize toSize) const; protected: - Status parseReply(QByteArray data) override; + Status parseReply(QNetworkReply* reply) override; private: QImage _thumbnail; -- cgit v1.2.3 From b142117d78a2a4ce21e818c62cb7a10cff80af0d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 5 Jan 2018 19:21:54 +0900 Subject: BaseJob: Provide a simplified constructor --- jobs/basejob.cpp | 4 ++++ jobs/basejob.h | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index 980814c4..74031909 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -96,6 +96,10 @@ QDebug QMatrixClient::operator<<(QDebug dbg, const BaseJob::Status& s) << QString(s.message).replace(filter, "\\1 HIDDEN"); } +BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, bool needsToken) + : BaseJob(verb, name, endpoint, Query { }, Data { }, needsToken) +{ } + BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, const Query& query, const Data& data, bool needsToken) : d(new Private(verb, endpoint, query, data, needsToken)) diff --git a/jobs/basejob.h b/jobs/basejob.h index 1d7f2388..1fe3a24d 100644 --- a/jobs/basejob.h +++ b/jobs/basejob.h @@ -130,7 +130,9 @@ namespace QMatrixClient public: BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, - const Query& query = {}, const Data& data = {}, + bool needsToken = true); + BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, + const Query& query, const Data& data = {}, bool needsToken = true); Status status() const; -- cgit v1.2.3 From 7169338dedbf0184da4c971e7cecb6be13b1d129 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 5 Jan 2018 11:01:18 +0900 Subject: jobs/generated: Use BaseJob::addExpectedContentType() --- jobs/generated/administrative_contact.cpp | 2 ++ jobs/generated/banning.cpp | 4 ++++ jobs/generated/directory.cpp | 2 ++ jobs/generated/inviting.cpp | 2 ++ jobs/generated/kicking.cpp | 2 ++ jobs/generated/list_public_rooms.cpp | 2 ++ jobs/generated/login.cpp | 2 ++ jobs/generated/profile.cpp | 4 ++++ jobs/generated/receipts.cpp | 2 ++ jobs/generated/redaction.cpp | 2 ++ jobs/generated/third_party_membership.cpp | 2 ++ jobs/generated/typing.cpp | 2 ++ 12 files changed, 28 insertions(+) diff --git a/jobs/generated/administrative_contact.cpp b/jobs/generated/administrative_contact.cpp index ca029f58..584c447f 100644 --- a/jobs/generated/administrative_contact.cpp +++ b/jobs/generated/administrative_contact.cpp @@ -102,6 +102,8 @@ Post3PIDsJob::Post3PIDsJob(const ThreePidCredentials& threePidCreds, bool bind) _data.insert("three_pid_creds", toJson(threePidCreds)); _data.insert("bind", toJson(bind)); setRequestData(_data); + + addExpectedContentType("application/json"); } RequestTokenTo3PIDJob::RequestTokenTo3PIDJob() diff --git a/jobs/generated/banning.cpp b/jobs/generated/banning.cpp index d7708cc6..b7888062 100644 --- a/jobs/generated/banning.cpp +++ b/jobs/generated/banning.cpp @@ -23,6 +23,8 @@ BanJob::BanJob(const QString& roomId, const QString& userId, const QString& reas if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); setRequestData(_data); + + addExpectedContentType("application/json"); } UnbanJob::UnbanJob(const QString& roomId, const QString& userId) @@ -34,5 +36,7 @@ UnbanJob::UnbanJob(const QString& roomId, const QString& userId) QJsonObject _data; _data.insert("user_id", toJson(userId)); setRequestData(_data); + + addExpectedContentType("application/json"); } diff --git a/jobs/generated/directory.cpp b/jobs/generated/directory.cpp index 1fd1e443..ea6d493f 100644 --- a/jobs/generated/directory.cpp +++ b/jobs/generated/directory.cpp @@ -22,6 +22,8 @@ SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, const QString& roomId if (!roomId.isEmpty()) _data.insert("room_id", toJson(roomId)); setRequestData(_data); + + addExpectedContentType("application/json"); } class GetRoomIdByAliasJob::Private diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp index bdf257dd..6cd67ad7 100644 --- a/jobs/generated/inviting.cpp +++ b/jobs/generated/inviting.cpp @@ -21,5 +21,7 @@ InviteUserJob::InviteUserJob(const QString& roomId, const QString& userId) QJsonObject _data; _data.insert("user_id", toJson(userId)); setRequestData(_data); + + addExpectedContentType("application/json"); } diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp index 3488b387..bf94fe59 100644 --- a/jobs/generated/kicking.cpp +++ b/jobs/generated/kicking.cpp @@ -23,5 +23,7 @@ KickJob::KickJob(const QString& roomId, const QString& userId, const QString& re if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); setRequestData(_data); + + addExpectedContentType("application/json"); } diff --git a/jobs/generated/list_public_rooms.cpp b/jobs/generated/list_public_rooms.cpp index d15e9de3..1e775f1b 100644 --- a/jobs/generated/list_public_rooms.cpp +++ b/jobs/generated/list_public_rooms.cpp @@ -205,6 +205,8 @@ QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, double limit, co _data.insert("since", toJson(since)); _data.insert("filter", toJson(filter)); setRequestData(_data); + + addExpectedContentType("application/json"); } QueryPublicRoomsJob::~QueryPublicRoomsJob() = default; diff --git a/jobs/generated/login.cpp b/jobs/generated/login.cpp index bbfff0da..67d991db 100644 --- a/jobs/generated/login.cpp +++ b/jobs/generated/login.cpp @@ -44,6 +44,8 @@ LoginJob::LoginJob(const QString& type, const QString& user, const QString& medi if (!initialDeviceDisplayName.isEmpty()) _data.insert("initial_device_display_name", toJson(initialDeviceDisplayName)); setRequestData(_data); + + addExpectedContentType("application/json"); } LoginJob::~LoginJob() = default; diff --git a/jobs/generated/profile.cpp b/jobs/generated/profile.cpp index 27bab0b8..b30b9a36 100644 --- a/jobs/generated/profile.cpp +++ b/jobs/generated/profile.cpp @@ -22,6 +22,8 @@ SetDisplayNameJob::SetDisplayNameJob(const QString& userId, const QString& displ if (!displayname.isEmpty()) _data.insert("displayname", toJson(displayname)); setRequestData(_data); + + addExpectedContentType("application/json"); } class GetDisplayNameJob::Private @@ -61,6 +63,8 @@ SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QString& avatarUrl if (!avatarUrl.isEmpty()) _data.insert("avatar_url", toJson(avatarUrl)); setRequestData(_data); + + addExpectedContentType("application/json"); } class GetAvatarUrlJob::Private diff --git a/jobs/generated/receipts.cpp b/jobs/generated/receipts.cpp index 1c8bd80f..20e58ffb 100644 --- a/jobs/generated/receipts.cpp +++ b/jobs/generated/receipts.cpp @@ -19,5 +19,7 @@ PostReceiptJob::PostReceiptJob(const QString& roomId, const QString& receiptType ) { setRequestData(Data(receipt)); + + addExpectedContentType("application/json"); } diff --git a/jobs/generated/redaction.cpp b/jobs/generated/redaction.cpp index 9207f344..218e1aa6 100644 --- a/jobs/generated/redaction.cpp +++ b/jobs/generated/redaction.cpp @@ -28,6 +28,8 @@ RedactEventJob::RedactEventJob(const QString& roomId, const QString& eventId, co if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); setRequestData(_data); + + addExpectedContentType("application/json"); } RedactEventJob::~RedactEventJob() = default; diff --git a/jobs/generated/third_party_membership.cpp b/jobs/generated/third_party_membership.cpp index dbb3ebee..f5ccf123 100644 --- a/jobs/generated/third_party_membership.cpp +++ b/jobs/generated/third_party_membership.cpp @@ -23,5 +23,7 @@ InviteBy3PIDJob::InviteBy3PIDJob(const QString& roomId, const QString& idServer, _data.insert("medium", toJson(medium)); _data.insert("address", toJson(address)); setRequestData(_data); + + addExpectedContentType("application/json"); } diff --git a/jobs/generated/typing.cpp b/jobs/generated/typing.cpp index a6817cb9..05e5fd1e 100644 --- a/jobs/generated/typing.cpp +++ b/jobs/generated/typing.cpp @@ -22,5 +22,7 @@ SetTypingJob::SetTypingJob(const QString& userId, const QString& roomId, bool ty _data.insert("typing", toJson(typing)); _data.insert("timeout", toJson(timeout)); setRequestData(_data); + + addExpectedContentType("application/json"); } -- cgit v1.2.3 From 01806d00977578681a401ad294a957ecec0a3d53 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 5 Jan 2018 19:46:41 +0900 Subject: jobs: expect application/json by default; set query in constructor body; properly convert numbers to strings in query The query should be set in constructor body because there's no reason to pass non-required parameters into the query. As for numbers to strings conversion - there was an attempt to use QJsonValue(a).toString() for that. That doesn't work; QJsonValue does not turn numbers to strings. --- jobs/basejob.cpp | 1 + jobs/generated/administrative_contact.cpp | 21 +++++++----------- jobs/generated/banning.cpp | 12 ++--------- jobs/generated/directory.cpp | 21 +++++++----------- jobs/generated/inviting.cpp | 6 +----- jobs/generated/kicking.cpp | 6 +----- jobs/generated/leaving.cpp | 14 ++++++------ jobs/generated/list_public_rooms.cpp | 32 ++++++++++++++------------- jobs/generated/login.cpp | 7 ++---- jobs/generated/logout.cpp | 7 +++--- jobs/generated/profile.cpp | 36 ++++++++++++------------------- jobs/generated/receipts.cpp | 6 +----- jobs/generated/redaction.cpp | 7 ++---- jobs/generated/third_party_membership.cpp | 6 +----- jobs/generated/typing.cpp | 6 +----- jobs/generated/versions.cpp | 8 +++---- jobs/generated/whoami.cpp | 8 +++---- 17 files changed, 76 insertions(+), 128 deletions(-) diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index 74031909..dddff800 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -105,6 +105,7 @@ BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, : d(new Private(verb, endpoint, query, data, needsToken)) { setObjectName(name); + setExpectedContentTypes({ "application/json" }); d->timer.setSingleShot(true); connect (&d->timer, &QTimer::timeout, this, &BaseJob::timeout); d->retryTimer.setSingleShot(true); diff --git a/jobs/generated/administrative_contact.cpp b/jobs/generated/administrative_contact.cpp index 584c447f..479bee52 100644 --- a/jobs/generated/administrative_contact.cpp +++ b/jobs/generated/administrative_contact.cpp @@ -44,10 +44,10 @@ class GetAccount3PIDsJob::Private GetAccount3PIDsJob::GetAccount3PIDsJob() : BaseJob(HttpVerb::Get, "GetAccount3PIDsJob", - basePath % "/account/3pid", - Query { } - ), d(new Private) -{ } + basePath % "/account/3pid") + , d(new Private) +{ +} GetAccount3PIDsJob::~GetAccount3PIDsJob() = default; @@ -94,22 +94,17 @@ namespace QMatrixClient Post3PIDsJob::Post3PIDsJob(const ThreePidCredentials& threePidCreds, bool bind) : BaseJob(HttpVerb::Post, "Post3PIDsJob", - basePath % "/account/3pid", - Query { } - ) + basePath % "/account/3pid") { QJsonObject _data; _data.insert("three_pid_creds", toJson(threePidCreds)); _data.insert("bind", toJson(bind)); setRequestData(_data); - - addExpectedContentType("application/json"); } RequestTokenTo3PIDJob::RequestTokenTo3PIDJob() : BaseJob(HttpVerb::Post, "RequestTokenTo3PIDJob", - basePath % "/account/3pid/email/requestToken", - Query { }, Data { }, false - ) -{ } + basePath % "/account/3pid/email/requestToken", false) +{ +} diff --git a/jobs/generated/banning.cpp b/jobs/generated/banning.cpp index b7888062..f66b27b6 100644 --- a/jobs/generated/banning.cpp +++ b/jobs/generated/banning.cpp @@ -14,29 +14,21 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); BanJob::BanJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, "BanJob", - basePath % "/rooms/" % roomId % "/ban", - Query { } - ) + basePath % "/rooms/" % roomId % "/ban") { QJsonObject _data; _data.insert("user_id", toJson(userId)); if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); setRequestData(_data); - - addExpectedContentType("application/json"); } UnbanJob::UnbanJob(const QString& roomId, const QString& userId) : BaseJob(HttpVerb::Post, "UnbanJob", - basePath % "/rooms/" % roomId % "/unban", - Query { } - ) + basePath % "/rooms/" % roomId % "/unban") { QJsonObject _data; _data.insert("user_id", toJson(userId)); setRequestData(_data); - - addExpectedContentType("application/json"); } diff --git a/jobs/generated/directory.cpp b/jobs/generated/directory.cpp index ea6d493f..4e61ed74 100644 --- a/jobs/generated/directory.cpp +++ b/jobs/generated/directory.cpp @@ -14,16 +14,12 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0/directory"); SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, const QString& roomId) : BaseJob(HttpVerb::Put, "SetRoomAliasJob", - basePath % "/room/" % roomAlias, - Query { } - ) + basePath % "/room/" % roomAlias) { QJsonObject _data; if (!roomId.isEmpty()) _data.insert("room_id", toJson(roomId)); setRequestData(_data); - - addExpectedContentType("application/json"); } class GetRoomIdByAliasJob::Private @@ -35,10 +31,10 @@ class GetRoomIdByAliasJob::Private GetRoomIdByAliasJob::GetRoomIdByAliasJob(const QString& roomAlias) : BaseJob(HttpVerb::Get, "GetRoomIdByAliasJob", - basePath % "/room/" % roomAlias, - Query { }, Data { }, false - ), d(new Private) -{ } + basePath % "/room/" % roomAlias, false) + , d(new Private) +{ +} GetRoomIdByAliasJob::~GetRoomIdByAliasJob() = default; @@ -62,8 +58,7 @@ BaseJob::Status GetRoomIdByAliasJob::parseJson(const QJsonDocument& data) DeleteRoomAliasJob::DeleteRoomAliasJob(const QString& roomAlias) : BaseJob(HttpVerb::Delete, "DeleteRoomAliasJob", - basePath % "/room/" % roomAlias, - Query { } - ) -{ } + basePath % "/room/" % roomAlias) +{ +} diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp index 6cd67ad7..d2ee2107 100644 --- a/jobs/generated/inviting.cpp +++ b/jobs/generated/inviting.cpp @@ -14,14 +14,10 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); InviteUserJob::InviteUserJob(const QString& roomId, const QString& userId) : BaseJob(HttpVerb::Post, "InviteUserJob", - basePath % "/rooms/" % roomId % "/invite", - Query { } - ) + basePath % "/rooms/" % roomId % "/invite") { QJsonObject _data; _data.insert("user_id", toJson(userId)); setRequestData(_data); - - addExpectedContentType("application/json"); } diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp index bf94fe59..bf2490b7 100644 --- a/jobs/generated/kicking.cpp +++ b/jobs/generated/kicking.cpp @@ -14,16 +14,12 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); KickJob::KickJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, "KickJob", - basePath % "/rooms/" % roomId % "/kick", - Query { } - ) + basePath % "/rooms/" % roomId % "/kick") { QJsonObject _data; _data.insert("user_id", toJson(userId)); if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); setRequestData(_data); - - addExpectedContentType("application/json"); } diff --git a/jobs/generated/leaving.cpp b/jobs/generated/leaving.cpp index 604fcc73..89c110dd 100644 --- a/jobs/generated/leaving.cpp +++ b/jobs/generated/leaving.cpp @@ -14,15 +14,13 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); LeaveRoomJob::LeaveRoomJob(const QString& roomId) : BaseJob(HttpVerb::Post, "LeaveRoomJob", - basePath % "/rooms/" % roomId % "/leave", - Query { } - ) -{ } + basePath % "/rooms/" % roomId % "/leave") +{ +} ForgetRoomJob::ForgetRoomJob(const QString& roomId) : BaseJob(HttpVerb::Post, "ForgetRoomJob", - basePath % "/rooms/" % roomId % "/forget", - Query { } - ) -{ } + basePath % "/rooms/" % roomId % "/forget") +{ +} diff --git a/jobs/generated/list_public_rooms.cpp b/jobs/generated/list_public_rooms.cpp index 1e775f1b..a2c0e406 100644 --- a/jobs/generated/list_public_rooms.cpp +++ b/jobs/generated/list_public_rooms.cpp @@ -68,14 +68,17 @@ class GetPublicRoomsJob::Private GetPublicRoomsJob::GetPublicRoomsJob(double limit, const QString& since, const QString& server) : BaseJob(HttpVerb::Get, "GetPublicRoomsJob", - basePath % "/publicRooms", - Query { - { "limit", toJson(limit).toString() }, - { "since", toJson(since).toString() }, - { "server", toJson(server).toString() } - }, Data { }, false - ), d(new Private) -{ } + basePath % "/publicRooms", false) + , d(new Private) +{ + QUrlQuery _q; + _q.addQueryItem("limit", QString("%1").arg(limit)); + if (!since.isEmpty()) + _q.addQueryItem("since", since); + if (!server.isEmpty()) + _q.addQueryItem("server", server); + setRequestQuery(_q); +} GetPublicRoomsJob::~GetPublicRoomsJob() = default; @@ -193,20 +196,19 @@ class QueryPublicRoomsJob::Private QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, double limit, const QString& since, const Filter& filter) : BaseJob(HttpVerb::Post, "QueryPublicRoomsJob", - basePath % "/publicRooms", - Query { - { "server", toJson(server).toString() } - } - ), d(new Private) + basePath % "/publicRooms") + , d(new Private) { + QUrlQuery _q; + if (!server.isEmpty()) + _q.addQueryItem("server", server); + setRequestQuery(_q); QJsonObject _data; _data.insert("limit", toJson(limit)); if (!since.isEmpty()) _data.insert("since", toJson(since)); _data.insert("filter", toJson(filter)); setRequestData(_data); - - addExpectedContentType("application/json"); } QueryPublicRoomsJob::~QueryPublicRoomsJob() = default; diff --git a/jobs/generated/login.cpp b/jobs/generated/login.cpp index 67d991db..a4dab428 100644 --- a/jobs/generated/login.cpp +++ b/jobs/generated/login.cpp @@ -23,9 +23,8 @@ class LoginJob::Private LoginJob::LoginJob(const QString& type, const QString& user, const QString& medium, const QString& address, const QString& password, const QString& token, const QString& deviceId, const QString& initialDeviceDisplayName) : BaseJob(HttpVerb::Post, "LoginJob", - basePath % "/login", - Query { }, Data { }, false - ), d(new Private) + basePath % "/login", false) + , d(new Private) { QJsonObject _data; _data.insert("type", toJson(type)); @@ -44,8 +43,6 @@ LoginJob::LoginJob(const QString& type, const QString& user, const QString& medi if (!initialDeviceDisplayName.isEmpty()) _data.insert("initial_device_display_name", toJson(initialDeviceDisplayName)); setRequestData(_data); - - addExpectedContentType("application/json"); } LoginJob::~LoginJob() = default; diff --git a/jobs/generated/logout.cpp b/jobs/generated/logout.cpp index b6904070..f7f8eff9 100644 --- a/jobs/generated/logout.cpp +++ b/jobs/generated/logout.cpp @@ -14,8 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); LogoutJob::LogoutJob() : BaseJob(HttpVerb::Post, "LogoutJob", - basePath % "/logout", - Query { } - ) -{ } + basePath % "/logout") +{ +} diff --git a/jobs/generated/profile.cpp b/jobs/generated/profile.cpp index b30b9a36..9523ca96 100644 --- a/jobs/generated/profile.cpp +++ b/jobs/generated/profile.cpp @@ -14,16 +14,12 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); SetDisplayNameJob::SetDisplayNameJob(const QString& userId, const QString& displayname) : BaseJob(HttpVerb::Put, "SetDisplayNameJob", - basePath % "/profile/" % userId % "/displayname", - Query { } - ) + basePath % "/profile/" % userId % "/displayname") { QJsonObject _data; if (!displayname.isEmpty()) _data.insert("displayname", toJson(displayname)); setRequestData(_data); - - addExpectedContentType("application/json"); } class GetDisplayNameJob::Private @@ -34,10 +30,10 @@ class GetDisplayNameJob::Private GetDisplayNameJob::GetDisplayNameJob(const QString& userId) : BaseJob(HttpVerb::Get, "GetDisplayNameJob", - basePath % "/profile/" % userId % "/displayname", - Query { }, Data { }, false - ), d(new Private) -{ } + basePath % "/profile/" % userId % "/displayname", false) + , d(new Private) +{ +} GetDisplayNameJob::~GetDisplayNameJob() = default; @@ -55,16 +51,12 @@ BaseJob::Status GetDisplayNameJob::parseJson(const QJsonDocument& data) SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QString& avatarUrl) : BaseJob(HttpVerb::Put, "SetAvatarUrlJob", - basePath % "/profile/" % userId % "/avatar_url", - Query { } - ) + basePath % "/profile/" % userId % "/avatar_url") { QJsonObject _data; if (!avatarUrl.isEmpty()) _data.insert("avatar_url", toJson(avatarUrl)); setRequestData(_data); - - addExpectedContentType("application/json"); } class GetAvatarUrlJob::Private @@ -75,10 +67,10 @@ class GetAvatarUrlJob::Private GetAvatarUrlJob::GetAvatarUrlJob(const QString& userId) : BaseJob(HttpVerb::Get, "GetAvatarUrlJob", - basePath % "/profile/" % userId % "/avatar_url", - Query { }, Data { }, false - ), d(new Private) -{ } + basePath % "/profile/" % userId % "/avatar_url", false) + , d(new Private) +{ +} GetAvatarUrlJob::~GetAvatarUrlJob() = default; @@ -103,10 +95,10 @@ class GetUserProfileJob::Private GetUserProfileJob::GetUserProfileJob(const QString& userId) : BaseJob(HttpVerb::Get, "GetUserProfileJob", - basePath % "/profile/" % userId, - Query { }, Data { }, false - ), d(new Private) -{ } + basePath % "/profile/" % userId, false) + , d(new Private) +{ +} GetUserProfileJob::~GetUserProfileJob() = default; diff --git a/jobs/generated/receipts.cpp b/jobs/generated/receipts.cpp index 20e58ffb..83c38b6f 100644 --- a/jobs/generated/receipts.cpp +++ b/jobs/generated/receipts.cpp @@ -14,12 +14,8 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); PostReceiptJob::PostReceiptJob(const QString& roomId, const QString& receiptType, const QString& eventId, const QJsonObject& receipt) : BaseJob(HttpVerb::Post, "PostReceiptJob", - basePath % "/rooms/" % roomId % "/receipt/" % receiptType % "/" % eventId, - Query { } - ) + basePath % "/rooms/" % roomId % "/receipt/" % receiptType % "/" % eventId) { setRequestData(Data(receipt)); - - addExpectedContentType("application/json"); } diff --git a/jobs/generated/redaction.cpp b/jobs/generated/redaction.cpp index 218e1aa6..0da35dfc 100644 --- a/jobs/generated/redaction.cpp +++ b/jobs/generated/redaction.cpp @@ -20,16 +20,13 @@ class RedactEventJob::Private RedactEventJob::RedactEventJob(const QString& roomId, const QString& eventId, const QString& txnId, const QString& reason) : BaseJob(HttpVerb::Put, "RedactEventJob", - basePath % "/rooms/" % roomId % "/redact/" % eventId % "/" % txnId, - Query { } - ), d(new Private) + basePath % "/rooms/" % roomId % "/redact/" % eventId % "/" % txnId) + , d(new Private) { QJsonObject _data; if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); setRequestData(_data); - - addExpectedContentType("application/json"); } RedactEventJob::~RedactEventJob() = default; diff --git a/jobs/generated/third_party_membership.cpp b/jobs/generated/third_party_membership.cpp index f5ccf123..b637d481 100644 --- a/jobs/generated/third_party_membership.cpp +++ b/jobs/generated/third_party_membership.cpp @@ -14,16 +14,12 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); InviteBy3PIDJob::InviteBy3PIDJob(const QString& roomId, const QString& idServer, const QString& medium, const QString& address) : BaseJob(HttpVerb::Post, "InviteBy3PIDJob", - basePath % "/rooms/" % roomId % "/invite", - Query { } - ) + basePath % "/rooms/" % roomId % "/invite") { QJsonObject _data; _data.insert("id_server", toJson(idServer)); _data.insert("medium", toJson(medium)); _data.insert("address", toJson(address)); setRequestData(_data); - - addExpectedContentType("application/json"); } diff --git a/jobs/generated/typing.cpp b/jobs/generated/typing.cpp index 05e5fd1e..fa700290 100644 --- a/jobs/generated/typing.cpp +++ b/jobs/generated/typing.cpp @@ -14,15 +14,11 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); SetTypingJob::SetTypingJob(const QString& userId, const QString& roomId, bool typing, int timeout) : BaseJob(HttpVerb::Put, "SetTypingJob", - basePath % "/rooms/" % roomId % "/typing/" % userId, - Query { } - ) + basePath % "/rooms/" % roomId % "/typing/" % userId) { QJsonObject _data; _data.insert("typing", toJson(typing)); _data.insert("timeout", toJson(timeout)); setRequestData(_data); - - addExpectedContentType("application/json"); } diff --git a/jobs/generated/versions.cpp b/jobs/generated/versions.cpp index 8ff58365..938c1d34 100644 --- a/jobs/generated/versions.cpp +++ b/jobs/generated/versions.cpp @@ -20,10 +20,10 @@ class GetVersionsJob::Private GetVersionsJob::GetVersionsJob() : BaseJob(HttpVerb::Get, "GetVersionsJob", - basePath % "/versions", - Query { }, Data { }, false - ), d(new Private) -{ } + basePath % "/versions", false) + , d(new Private) +{ +} GetVersionsJob::~GetVersionsJob() = default; diff --git a/jobs/generated/whoami.cpp b/jobs/generated/whoami.cpp index d4da99d4..4f7b052c 100644 --- a/jobs/generated/whoami.cpp +++ b/jobs/generated/whoami.cpp @@ -20,10 +20,10 @@ class GetTokenOwnerJob::Private GetTokenOwnerJob::GetTokenOwnerJob() : BaseJob(HttpVerb::Get, "GetTokenOwnerJob", - basePath % "/account/whoami", - Query { } - ), d(new Private) -{ } + basePath % "/account/whoami") + , d(new Private) +{ +} GetTokenOwnerJob::~GetTokenOwnerJob() = default; -- cgit v1.2.3 From 4c1d52601be66caafa913171b5d5ed47ee4eb67d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 5 Jan 2018 11:12:48 +0900 Subject: jobs/generated: content-repo.*, create_room.* --- jobs/generated/content-repo.cpp | 212 ++++++++++++++++++++++++++++++++++++++++ jobs/generated/content-repo.h | 102 +++++++++++++++++++ jobs/generated/create_room.cpp | 114 +++++++++++++++++++++ jobs/generated/create_room.h | 56 +++++++++++ 4 files changed, 484 insertions(+) create mode 100644 jobs/generated/content-repo.cpp create mode 100644 jobs/generated/content-repo.h create mode 100644 jobs/generated/create_room.cpp create mode 100644 jobs/generated/create_room.h diff --git a/jobs/generated/content-repo.cpp b/jobs/generated/content-repo.cpp new file mode 100644 index 00000000..ec6683bb --- /dev/null +++ b/jobs/generated/content-repo.cpp @@ -0,0 +1,212 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#include "content-repo.h" + +#include "converters.h" + +#include +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/media/r0"); + +class UploadContentJob::Private +{ + public: + QString contentUri; +}; + +UploadContentJob::UploadContentJob(QByteArray content, const QString& filename, const QString& contentType) + : BaseJob(HttpVerb::Post, "UploadContentJob", + basePath % "/upload") + , d(new Private) +{ + setRequestHeader("Content-Type", contentType.toLatin1()); + + QUrlQuery _q; + if (!filename.isEmpty()) + _q.addQueryItem("filename", filename); + setRequestQuery(_q); + setRequestData(Data(content)); +} + +UploadContentJob::~UploadContentJob() = default; + +const QString& UploadContentJob::contentUri() const +{ + return d->contentUri; +} + +BaseJob::Status UploadContentJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + if (!json.contains("content_uri")) + return { JsonParseError, + "The key 'content_uri' not found in the response" }; + d->contentUri = fromJson(json.value("content_uri")); + return Success; +} + +class GetContentJob::Private +{ + public: + QString contentType; + QString contentDisposition; + QByteArray content; +}; + +GetContentJob::GetContentJob(const QString& serverName, const QString& mediaId) + : BaseJob(HttpVerb::Get, "GetContentJob", + basePath % "/download/" % serverName % "/" % mediaId, false) + , d(new Private) +{ + setExpectedContentTypes({ "*/*" }); +} + +GetContentJob::~GetContentJob() = default; + +const QString& GetContentJob::contentType() const +{ + return d->contentType; +} + +const QString& GetContentJob::contentDisposition() const +{ + return d->contentDisposition; +} + +QByteArray GetContentJob::content() const +{ + return d->content; +} + +BaseJob::Status GetContentJob::parseReply(QNetworkReply* reply) +{ + d->contentType = reply->rawHeader("Content-Type"); + d->contentDisposition = reply->rawHeader("Content-Disposition"); + d->content = reply->readAll(); + return Success; +} + +class GetContentOverrideNameJob::Private +{ + public: + QString contentType; + QString contentDisposition; + QByteArray content; +}; + +GetContentOverrideNameJob::GetContentOverrideNameJob(const QString& serverName, const QString& mediaId, const QString& fileName) + : BaseJob(HttpVerb::Get, "GetContentOverrideNameJob", + basePath % "/download/" % serverName % "/" % mediaId % "/" % fileName, false) + , d(new Private) +{ + setExpectedContentTypes({ "*/*" }); +} + +GetContentOverrideNameJob::~GetContentOverrideNameJob() = default; + +const QString& GetContentOverrideNameJob::contentType() const +{ + return d->contentType; +} + +const QString& GetContentOverrideNameJob::contentDisposition() const +{ + return d->contentDisposition; +} + +QByteArray GetContentOverrideNameJob::content() const +{ + return d->content; +} + +BaseJob::Status GetContentOverrideNameJob::parseReply(QNetworkReply* reply) +{ + d->contentType = reply->rawHeader("Content-Type"); + d->contentDisposition = reply->rawHeader("Content-Disposition"); + d->content = reply->readAll(); + return Success; +} + +class GetContentThumbnailJob::Private +{ + public: + QString contentType; + QByteArray content; +}; + +GetContentThumbnailJob::GetContentThumbnailJob(const QString& serverName, const QString& mediaId, int width, int height, const QString& method) + : BaseJob(HttpVerb::Get, "GetContentThumbnailJob", + basePath % "/thumbnail/" % serverName % "/" % mediaId, false) + , d(new Private) +{ + QUrlQuery _q; + _q.addQueryItem("width", QString("%1").arg(width)); + _q.addQueryItem("height", QString("%1").arg(height)); + if (!method.isEmpty()) + _q.addQueryItem("method", method); + setRequestQuery(_q); + setExpectedContentTypes({ "image/jpeg", "image/png" }); +} + +GetContentThumbnailJob::~GetContentThumbnailJob() = default; + +const QString& GetContentThumbnailJob::contentType() const +{ + return d->contentType; +} + +QByteArray GetContentThumbnailJob::content() const +{ + return d->content; +} + +BaseJob::Status GetContentThumbnailJob::parseReply(QNetworkReply* reply) +{ + d->contentType = reply->rawHeader("Content-Type"); + d->content = reply->readAll(); + return Success; +} + +class GetUrlPreviewJob::Private +{ + public: + double matrixImageSize; + QString ogImage; +}; + +GetUrlPreviewJob::GetUrlPreviewJob(const QString& url, double ts) + : BaseJob(HttpVerb::Get, "GetUrlPreviewJob", + basePath % "/preview_url") + , d(new Private) +{ + QUrlQuery _q; + _q.addQueryItem("url", url); + _q.addQueryItem("ts", QString("%1").arg(ts)); + setRequestQuery(_q); +} + +GetUrlPreviewJob::~GetUrlPreviewJob() = default; + +double GetUrlPreviewJob::matrixImageSize() const +{ + return d->matrixImageSize; +} + +const QString& GetUrlPreviewJob::ogImage() const +{ + return d->ogImage; +} + +BaseJob::Status GetUrlPreviewJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + d->matrixImageSize = fromJson(json.value("matrix:image:size")); + d->ogImage = fromJson(json.value("og:image")); + return Success; +} + diff --git a/jobs/generated/content-repo.h b/jobs/generated/content-repo.h new file mode 100644 index 00000000..1d844651 --- /dev/null +++ b/jobs/generated/content-repo.h @@ -0,0 +1,102 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "../basejob.h" + +#include +#include + + +namespace QMatrixClient +{ + // Operations + + class UploadContentJob : public BaseJob + { + public: + explicit UploadContentJob(QByteArray content, const QString& filename = {}, const QString& contentType = {}); + ~UploadContentJob() override; + + const QString& contentUri() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + QScopedPointer d; + }; + + class GetContentJob : public BaseJob + { + public: + explicit GetContentJob(const QString& serverName, const QString& mediaId); + ~GetContentJob() override; + + const QString& contentType() const; + const QString& contentDisposition() const; + QByteArray content() const; + + protected: + Status parseReply(QNetworkReply* reply) override; + + private: + class Private; + QScopedPointer d; + }; + + class GetContentOverrideNameJob : public BaseJob + { + public: + explicit GetContentOverrideNameJob(const QString& serverName, const QString& mediaId, const QString& fileName); + ~GetContentOverrideNameJob() override; + + const QString& contentType() const; + const QString& contentDisposition() const; + QByteArray content() const; + + protected: + Status parseReply(QNetworkReply* reply) override; + + private: + class Private; + QScopedPointer d; + }; + + class GetContentThumbnailJob : public BaseJob + { + public: + explicit GetContentThumbnailJob(const QString& serverName, const QString& mediaId, int width = {}, int height = {}, const QString& method = {}); + ~GetContentThumbnailJob() override; + + const QString& contentType() const; + QByteArray content() const; + + protected: + Status parseReply(QNetworkReply* reply) override; + + private: + class Private; + QScopedPointer d; + }; + + class GetUrlPreviewJob : public BaseJob + { + public: + explicit GetUrlPreviewJob(const QString& url, double ts = {}); + ~GetUrlPreviewJob() override; + + double matrixImageSize() const; + const QString& ogImage() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + QScopedPointer d; + }; +} // namespace QMatrixClient diff --git a/jobs/generated/create_room.cpp b/jobs/generated/create_room.cpp new file mode 100644 index 00000000..be06873a --- /dev/null +++ b/jobs/generated/create_room.cpp @@ -0,0 +1,114 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#include "create_room.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +CreateRoomJob::Invite3pid::operator QJsonObject() const +{ + QJsonObject o; + o.insert("id_server", toJson(idServer)); + o.insert("medium", toJson(medium)); + o.insert("address", toJson(address)); + + return o; +} +namespace QMatrixClient +{ + template <> struct FromJson + { + CreateRoomJob::Invite3pid operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + CreateRoomJob::Invite3pid result; + result.idServer = + fromJson(o.value("id_server")); + result.medium = + fromJson(o.value("medium")); + result.address = + fromJson(o.value("address")); + + return result; + } + }; +} // namespace QMatrixClient + +CreateRoomJob::StateEvent::operator QJsonObject() const +{ + QJsonObject o; + o.insert("type", toJson(type)); + o.insert("state_key", toJson(stateKey)); + o.insert("content", toJson(content)); + + return o; +} +namespace QMatrixClient +{ + template <> struct FromJson + { + CreateRoomJob::StateEvent operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + CreateRoomJob::StateEvent result; + result.type = + fromJson(o.value("type")); + result.stateKey = + fromJson(o.value("state_key")); + result.content = + fromJson(o.value("content")); + + return result; + } + }; +} // namespace QMatrixClient + +class CreateRoomJob::Private +{ + public: + QString roomId; +}; + +CreateRoomJob::CreateRoomJob(const QString& visibility, const QString& roomAliasName, const QString& name, const QString& topic, const QVector& invite, const QVector& invite3pid, const QJsonObject& creationContent, const QVector& initialState, const QString& preset, bool isDirect) + : BaseJob(HttpVerb::Post, "CreateRoomJob", + basePath % "/createRoom") + , d(new Private) +{ + QJsonObject _data; + if (!visibility.isEmpty()) + _data.insert("visibility", toJson(visibility)); + if (!roomAliasName.isEmpty()) + _data.insert("room_alias_name", toJson(roomAliasName)); + if (!name.isEmpty()) + _data.insert("name", toJson(name)); + if (!topic.isEmpty()) + _data.insert("topic", toJson(topic)); + _data.insert("invite", toJson(invite)); + _data.insert("invite_3pid", toJson(invite3pid)); + _data.insert("creation_content", toJson(creationContent)); + _data.insert("initial_state", toJson(initialState)); + if (!preset.isEmpty()) + _data.insert("preset", toJson(preset)); + _data.insert("is_direct", toJson(isDirect)); + setRequestData(_data); +} + +CreateRoomJob::~CreateRoomJob() = default; + +const QString& CreateRoomJob::roomId() const +{ + return d->roomId; +} + +BaseJob::Status CreateRoomJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + d->roomId = fromJson(json.value("room_id")); + return Success; +} + diff --git a/jobs/generated/create_room.h b/jobs/generated/create_room.h new file mode 100644 index 00000000..a92cb106 --- /dev/null +++ b/jobs/generated/create_room.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "../basejob.h" + +#include +#include +#include + +#include "converters.h" + +namespace QMatrixClient +{ + // Operations + + class CreateRoomJob : public BaseJob + { + public: + // Inner data structures + + struct Invite3pid + { + QString idServer; + QString medium; + QString address; + + operator QJsonObject() const; + }; + + struct StateEvent + { + QString type; + QString stateKey; + QJsonObject content; + + operator QJsonObject() const; + }; + + // End of inner data structures + + explicit CreateRoomJob(const QString& visibility = {}, const QString& roomAliasName = {}, const QString& name = {}, const QString& topic = {}, const QVector& invite = {}, const QVector& invite3pid = {}, const QJsonObject& creationContent = {}, const QVector& initialState = {}, const QString& preset = {}, bool isDirect = {}); + ~CreateRoomJob() override; + + const QString& roomId() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + QScopedPointer d; + }; +} // namespace QMatrixClient -- cgit v1.2.3 From a671d1aeff6bc9c01aa4204e0c05da894c72b603 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 5 Jan 2018 11:17:13 +0900 Subject: MediaThumbnailJob: Rewire to GetContentThumbnailJob; decommission ThumbnailType "crop" thumbnail type didn't seem to be ever used. Once GTAD is able to generate enums, the respective code will show up in GetContentThumbnailJob and this parameter can be reintroduced in MediaThumbnailJob. As of now, just rely on the default "scale" value. --- jobs/mediathumbnailjob.cpp | 20 +++++--------------- jobs/mediathumbnailjob.h | 9 +++------ 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/jobs/mediathumbnailjob.cpp b/jobs/mediathumbnailjob.cpp index 9337549e..d768d253 100644 --- a/jobs/mediathumbnailjob.cpp +++ b/jobs/mediathumbnailjob.cpp @@ -18,22 +18,11 @@ #include "mediathumbnailjob.h" -#include -#include - using namespace QMatrixClient; -MediaThumbnailJob::MediaThumbnailJob(QUrl url, QSize requestedSize, - ThumbnailType thumbnailType) - : BaseJob(HttpVerb::Get, "MediaThumbnailJob", - QStringLiteral("/_matrix/media/v1/thumbnail/%1%2") - .arg(url.host(), url.path()), - Query( - { { "width", QString::number(requestedSize.width()) } - , { "height", QString::number(requestedSize.height()) } - , { "method", - thumbnailType == ThumbnailType::Scale ? "scale" : "crop" } - })) +MediaThumbnailJob::MediaThumbnailJob(QUrl url, QSize requestedSize) + : GetContentThumbnailJob(url.host(), url.path().mid(1), + requestedSize.width(), requestedSize.height()) { } QImage MediaThumbnailJob::thumbnail() const @@ -49,7 +38,8 @@ QImage MediaThumbnailJob::scaledThumbnail(QSize toSize) const BaseJob::Status MediaThumbnailJob::parseReply(QNetworkReply* reply) { - if( !_thumbnail.loadFromData(reply->readAll()) ) + GetContentThumbnailJob::parseReply(reply); + if( !_thumbnail.loadFromData(content()) ) { qCDebug(JOBS) << "MediaThumbnailJob: could not read image data"; } diff --git a/jobs/mediathumbnailjob.h b/jobs/mediathumbnailjob.h index 2d6853c7..66960b75 100644 --- a/jobs/mediathumbnailjob.h +++ b/jobs/mediathumbnailjob.h @@ -18,19 +18,16 @@ #pragma once -#include "basejob.h" +#include "generated/content-repo.h" #include namespace QMatrixClient { - enum class ThumbnailType {Crop, Scale}; - - class MediaThumbnailJob: public BaseJob + class MediaThumbnailJob: public GetContentThumbnailJob { public: - MediaThumbnailJob(QUrl url, QSize requestedSize, - ThumbnailType thumbnailType = ThumbnailType::Scale); + MediaThumbnailJob(QUrl url, QSize requestedSize); QImage thumbnail() const; QImage scaledThumbnail(QSize toSize) const; -- cgit v1.2.3 From 71b9445d7f696cdfabaf05ec7d26a52891dea873 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jan 2018 22:01:26 +0900 Subject: BaseJob::Data -> RequestData; support QIODevice* input/output --- CMakeLists.txt | 1 + jobs/basejob.cpp | 15 +++++------ jobs/basejob.h | 39 ++++++--------------------- jobs/generated/content-repo.cpp | 20 +++++++------- jobs/generated/content-repo.h | 11 ++++---- jobs/mediathumbnailjob.cpp | 13 ++++++--- jobs/mediathumbnailjob.h | 4 ++- jobs/requestdata.cpp | 38 ++++++++++++++++++++++++++ jobs/requestdata.h | 59 +++++++++++++++++++++++++++++++++++++++++ libqmatrixclient.pri | 2 ++ 10 files changed, 143 insertions(+), 59 deletions(-) create mode 100644 jobs/requestdata.cpp create mode 100644 jobs/requestdata.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 463bfea7..a5b24135 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ set(libqmatrixclient_SRCS events/roomavatarevent.cpp events/typingevent.cpp events/receiptevent.cpp + jobs/requestdata.cpp jobs/basejob.cpp jobs/checkauthmethods.cpp jobs/sendeventjob.cpp diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index f508026f..0a81ad5c 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -46,7 +46,7 @@ class BaseJob::Private 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, QUrlQuery q, Data data, bool nt) + Private(HttpVerb v, QString endpoint, QUrlQuery q, Data&& data, bool nt) : verb(v), apiEndpoint(std::move(endpoint)) , requestQuery(std::move(q)), requestData(std::move(data)) , needsToken(nt) @@ -101,8 +101,8 @@ BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, bo { } BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, - const Query& query, const Data& data, bool needsToken) - : d(new Private(verb, endpoint, query, data, needsToken)) + const Query& query, Data&& data, bool needsToken) + : d(new Private(verb, endpoint, query, std::move(data), needsToken)) { setObjectName(name); setExpectedContentTypes({ "application/json" }); @@ -159,9 +159,9 @@ const BaseJob::Data& BaseJob::requestData() const return d->requestData; } -void BaseJob::setRequestData(const BaseJob::Data& data) +void BaseJob::setRequestData(Data&& data) { - d->requestData = data; + std::swap(d->requestData, data); } const QByteArrayList& BaseJob::expectedContentTypes() const @@ -206,10 +206,10 @@ void BaseJob::Private::sendRequest() reply.reset( connection->nam()->get(req) ); break; case HttpVerb::Post: - reply.reset( connection->nam()->post(req, requestData.serialize()) ); + reply.reset( connection->nam()->post(req, requestData.source()) ); break; case HttpVerb::Put: - reply.reset( connection->nam()->put(req, requestData.serialize()) ); + reply.reset( connection->nam()->put(req, requestData.source()) ); break; case HttpVerb::Delete: reply.reset( connection->nam()->deleteResource(req) ); @@ -447,4 +447,3 @@ void BaseJob::setLoggingCategory(LoggingCategory lcf) { d->logCat = lcf; } - diff --git a/jobs/basejob.h b/jobs/basejob.h index 42f5ecc2..6648dc1b 100644 --- a/jobs/basejob.h +++ b/jobs/basejob.h @@ -18,13 +18,15 @@ #pragma once -#include "logging.h" +#include "../logging.h" +#include "requestdata.h" #include +#include + +// Any job that parses the response will need the below two. #include #include -#include -#include class QNetworkReply; class QSslError; @@ -76,33 +78,8 @@ namespace QMatrixClient setQueryItems(l); } }; - /** - * A simple wrapper that represents the request body. - * Provides a unified interface to dump an unstructured byte stream - * as well as JSON (and possibly other structures in the future) to - * a QByteArray consumed by QNetworkAccessManager request methods. - */ - class Data - { - public: - Data() = default; - Data(const QByteArray& a) : _payload(a) { } - Data(const QJsonObject& jo) - : _payload(fromJson(QJsonDocument(jo))) { } - Data(const QJsonArray& ja) - : _payload(fromJson(QJsonDocument(ja))) { } - QByteArray serialize() const - { - return _payload; - } - private: - static QByteArray fromJson(const QJsonDocument& jd) - { - return jd.toJson(QJsonDocument::Compact); - } - QByteArray _payload; - }; + using Data = RequestData; /** * This structure stores the status of a server call job. The status consists @@ -132,7 +109,7 @@ namespace QMatrixClient BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, bool needsToken = true); BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, - const Query& query, const Data& data = {}, + const Query& query, Data&& data = {}, bool needsToken = true); Status status() const; @@ -227,7 +204,7 @@ namespace QMatrixClient const QUrlQuery& query() const; void setRequestQuery(const QUrlQuery& query); const Data& requestData() const; - void setRequestData(const Data& data); + void setRequestData(Data&& data); const QByteArrayList& expectedContentTypes() const; void addExpectedContentType(const QByteArray& contentType); void setExpectedContentTypes(const QByteArrayList& contentTypes); diff --git a/jobs/generated/content-repo.cpp b/jobs/generated/content-repo.cpp index ec6683bb..93aa838c 100644 --- a/jobs/generated/content-repo.cpp +++ b/jobs/generated/content-repo.cpp @@ -19,7 +19,7 @@ class UploadContentJob::Private QString contentUri; }; -UploadContentJob::UploadContentJob(QByteArray content, const QString& filename, const QString& contentType) +UploadContentJob::UploadContentJob(QIODevice* content, const QString& filename, const QString& contentType) : BaseJob(HttpVerb::Post, "UploadContentJob", basePath % "/upload") , d(new Private) @@ -55,7 +55,7 @@ class GetContentJob::Private public: QString contentType; QString contentDisposition; - QByteArray content; + QIODevice* content; }; GetContentJob::GetContentJob(const QString& serverName, const QString& mediaId) @@ -78,7 +78,7 @@ const QString& GetContentJob::contentDisposition() const return d->contentDisposition; } -QByteArray GetContentJob::content() const +QIODevice* GetContentJob::content() const { return d->content; } @@ -87,7 +87,7 @@ BaseJob::Status GetContentJob::parseReply(QNetworkReply* reply) { d->contentType = reply->rawHeader("Content-Type"); d->contentDisposition = reply->rawHeader("Content-Disposition"); - d->content = reply->readAll(); + d->content = reply; return Success; } @@ -96,7 +96,7 @@ class GetContentOverrideNameJob::Private public: QString contentType; QString contentDisposition; - QByteArray content; + QIODevice* content; }; GetContentOverrideNameJob::GetContentOverrideNameJob(const QString& serverName, const QString& mediaId, const QString& fileName) @@ -119,7 +119,7 @@ const QString& GetContentOverrideNameJob::contentDisposition() const return d->contentDisposition; } -QByteArray GetContentOverrideNameJob::content() const +QIODevice* GetContentOverrideNameJob::content() const { return d->content; } @@ -128,7 +128,7 @@ BaseJob::Status GetContentOverrideNameJob::parseReply(QNetworkReply* reply) { d->contentType = reply->rawHeader("Content-Type"); d->contentDisposition = reply->rawHeader("Content-Disposition"); - d->content = reply->readAll(); + d->content = reply; return Success; } @@ -136,7 +136,7 @@ class GetContentThumbnailJob::Private { public: QString contentType; - QByteArray content; + QIODevice* content; }; GetContentThumbnailJob::GetContentThumbnailJob(const QString& serverName, const QString& mediaId, int width, int height, const QString& method) @@ -160,7 +160,7 @@ const QString& GetContentThumbnailJob::contentType() const return d->contentType; } -QByteArray GetContentThumbnailJob::content() const +QIODevice* GetContentThumbnailJob::content() const { return d->content; } @@ -168,7 +168,7 @@ QByteArray GetContentThumbnailJob::content() const BaseJob::Status GetContentThumbnailJob::parseReply(QNetworkReply* reply) { d->contentType = reply->rawHeader("Content-Type"); - d->content = reply->readAll(); + d->content = reply; return Success; } diff --git a/jobs/generated/content-repo.h b/jobs/generated/content-repo.h index 1d844651..0796322b 100644 --- a/jobs/generated/content-repo.h +++ b/jobs/generated/content-repo.h @@ -6,8 +6,7 @@ #include "../basejob.h" -#include -#include +#include namespace QMatrixClient @@ -17,7 +16,7 @@ namespace QMatrixClient class UploadContentJob : public BaseJob { public: - explicit UploadContentJob(QByteArray content, const QString& filename = {}, const QString& contentType = {}); + explicit UploadContentJob(QIODevice* content, const QString& filename = {}, const QString& contentType = {}); ~UploadContentJob() override; const QString& contentUri() const; @@ -38,7 +37,7 @@ namespace QMatrixClient const QString& contentType() const; const QString& contentDisposition() const; - QByteArray content() const; + QIODevice* content() const; protected: Status parseReply(QNetworkReply* reply) override; @@ -56,7 +55,7 @@ namespace QMatrixClient const QString& contentType() const; const QString& contentDisposition() const; - QByteArray content() const; + QIODevice* content() const; protected: Status parseReply(QNetworkReply* reply) override; @@ -73,7 +72,7 @@ namespace QMatrixClient ~GetContentThumbnailJob() override; const QString& contentType() const; - QByteArray content() const; + QIODevice* content() const; protected: Status parseReply(QNetworkReply* reply) override; diff --git a/jobs/mediathumbnailjob.cpp b/jobs/mediathumbnailjob.cpp index d768d253..ec82f57b 100644 --- a/jobs/mediathumbnailjob.cpp +++ b/jobs/mediathumbnailjob.cpp @@ -20,8 +20,15 @@ using namespace QMatrixClient; -MediaThumbnailJob::MediaThumbnailJob(QUrl url, QSize requestedSize) - : GetContentThumbnailJob(url.host(), url.path().mid(1), +MediaThumbnailJob::MediaThumbnailJob(const QString& serverName, + const QString& mediaId, QSize requestedSize) + : GetContentThumbnailJob(serverName, mediaId, + requestedSize.width(), requestedSize.height()) +{ } + +MediaThumbnailJob::MediaThumbnailJob(const QUrl& mxcUri, QSize requestedSize) + : GetContentThumbnailJob(mxcUri.authority(), + mxcUri.path().mid(1), // sans leading '/' requestedSize.width(), requestedSize.height()) { } @@ -39,7 +46,7 @@ QImage MediaThumbnailJob::scaledThumbnail(QSize toSize) const BaseJob::Status MediaThumbnailJob::parseReply(QNetworkReply* reply) { GetContentThumbnailJob::parseReply(reply); - if( !_thumbnail.loadFromData(content()) ) + if( !_thumbnail.loadFromData(content()->readAll()) ) { qCDebug(JOBS) << "MediaThumbnailJob: could not read image data"; } diff --git a/jobs/mediathumbnailjob.h b/jobs/mediathumbnailjob.h index 66960b75..ef834cd7 100644 --- a/jobs/mediathumbnailjob.h +++ b/jobs/mediathumbnailjob.h @@ -27,7 +27,9 @@ namespace QMatrixClient class MediaThumbnailJob: public GetContentThumbnailJob { public: - MediaThumbnailJob(QUrl url, QSize requestedSize); + MediaThumbnailJob(const QString& serverName, const QString& mediaId, + QSize requestedSize); + MediaThumbnailJob(const QUrl& mxcUri, QSize requestedSize); QImage thumbnail() const; QImage scaledThumbnail(QSize toSize) const; diff --git a/jobs/requestdata.cpp b/jobs/requestdata.cpp new file mode 100644 index 00000000..f5516c5f --- /dev/null +++ b/jobs/requestdata.cpp @@ -0,0 +1,38 @@ +#include "requestdata.h" + +#include +#include +#include +#include +#include + +using namespace QMatrixClient; + +std::unique_ptr fromData(const QByteArray& data) +{ + auto source = std::make_unique(); + source->open(QIODevice::WriteOnly); + source->write(data); + source->close(); + return source; +} + +template +inline std::unique_ptr fromJson(const JsonDataT& jdata) +{ + return fromData(QJsonDocument(jdata).toJson(QJsonDocument::Compact)); +} + +RequestData::RequestData(const QByteArray& a) + : _source(fromData(a)) +{ } + +RequestData::RequestData(const QJsonObject& jo) + : _source(fromJson(jo)) +{ } + +RequestData::RequestData(const QJsonArray& ja) + : _source(fromJson(ja)) +{ } + +RequestData::~RequestData() = default; diff --git a/jobs/requestdata.h b/jobs/requestdata.h new file mode 100644 index 00000000..aa03b744 --- /dev/null +++ b/jobs/requestdata.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Copyright (C) 2018 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include + +class QByteArray; +class QJsonObject; +class QJsonArray; +class QJsonDocument; +class QIODevice; + +namespace QMatrixClient +{ + /** + * A simple wrapper that represents the request body. + * Provides a unified interface to dump an unstructured byte stream + * as well as JSON (and possibly other structures in the future) to + * a QByteArray consumed by QNetworkAccessManager request methods. + */ + class RequestData + { + public: + RequestData() = default; + RequestData(const QByteArray& a); + RequestData(const QJsonObject& jo); + RequestData(const QJsonArray& ja); + RequestData(QIODevice* source) + : _source(std::unique_ptr(source)) + { } + RequestData(RequestData&&) = default; + RequestData& operator=(RequestData&&) = default; + ~RequestData(); + + QIODevice* source() const + { + return _source.get(); + } + + private: + std::unique_ptr _source; + }; +} // namespace QMatrixClient diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 8ee3634c..db5de469 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -20,6 +20,7 @@ HEADERS += \ $$PWD/events/typingevent.h \ $$PWD/events/receiptevent.h \ $$PWD/events/redactionevent.h \ + $$PWD/jobs/requestdata.h \ $$PWD/jobs/basejob.h \ $$PWD/jobs/checkauthmethods.h \ $$PWD/jobs/passwordlogin.h \ @@ -49,6 +50,7 @@ SOURCES += \ $$PWD/events/typingevent.cpp \ $$PWD/events/receiptevent.cpp \ $$PWD/events/redactionevent.cpp \ + $$PWD/jobs/requestdata.cpp \ $$PWD/jobs/basejob.cpp \ $$PWD/jobs/checkauthmethods.cpp \ $$PWD/jobs/passwordlogin.cpp \ -- cgit v1.2.3 From 290ddb17189cf4c3a476b72eef13df1d26e8ede0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jan 2018 22:02:42 +0900 Subject: Drop extraneous #include --- jobs/generated/administrative_contact.h | 1 - jobs/generated/banning.h | 1 - jobs/generated/create_room.h | 3 +-- jobs/generated/directory.h | 1 - jobs/generated/inviting.h | 1 - jobs/generated/kicking.h | 1 - jobs/generated/leaving.h | 1 - jobs/generated/list_public_rooms.h | 1 - jobs/generated/login.h | 1 - jobs/generated/profile.h | 1 - jobs/generated/receipts.h | 1 - jobs/generated/redaction.h | 1 - jobs/generated/third_party_membership.h | 1 - jobs/generated/typing.h | 1 - jobs/generated/versions.h | 1 - jobs/generated/whoami.h | 1 - 16 files changed, 1 insertion(+), 17 deletions(-) diff --git a/jobs/generated/administrative_contact.h b/jobs/generated/administrative_contact.h index a5f04781..67563719 100644 --- a/jobs/generated/administrative_contact.h +++ b/jobs/generated/administrative_contact.h @@ -7,7 +7,6 @@ #include "../basejob.h" #include -#include #include "converters.h" diff --git a/jobs/generated/banning.h b/jobs/generated/banning.h index 930020a5..2d6fbd9b 100644 --- a/jobs/generated/banning.h +++ b/jobs/generated/banning.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient diff --git a/jobs/generated/create_room.h b/jobs/generated/create_room.h index a92cb106..13c9d2c0 100644 --- a/jobs/generated/create_room.h +++ b/jobs/generated/create_room.h @@ -6,9 +6,8 @@ #include "../basejob.h" -#include #include -#include +#include #include "converters.h" diff --git a/jobs/generated/directory.h b/jobs/generated/directory.h index 8290a2b5..eeda563b 100644 --- a/jobs/generated/directory.h +++ b/jobs/generated/directory.h @@ -7,7 +7,6 @@ #include "../basejob.h" #include -#include namespace QMatrixClient diff --git a/jobs/generated/inviting.h b/jobs/generated/inviting.h index 7ed49637..eaa884df 100644 --- a/jobs/generated/inviting.h +++ b/jobs/generated/inviting.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient diff --git a/jobs/generated/kicking.h b/jobs/generated/kicking.h index 84d88945..3814bef7 100644 --- a/jobs/generated/kicking.h +++ b/jobs/generated/kicking.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient diff --git a/jobs/generated/leaving.h b/jobs/generated/leaving.h index f006ce19..cd39b612 100644 --- a/jobs/generated/leaving.h +++ b/jobs/generated/leaving.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient diff --git a/jobs/generated/list_public_rooms.h b/jobs/generated/list_public_rooms.h index f6467a21..7dcb8cf7 100644 --- a/jobs/generated/list_public_rooms.h +++ b/jobs/generated/list_public_rooms.h @@ -7,7 +7,6 @@ #include "../basejob.h" #include -#include #include "converters.h" diff --git a/jobs/generated/login.h b/jobs/generated/login.h index 0f68a13b..3ac955d4 100644 --- a/jobs/generated/login.h +++ b/jobs/generated/login.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient diff --git a/jobs/generated/profile.h b/jobs/generated/profile.h index 9cbf3865..1e09791d 100644 --- a/jobs/generated/profile.h +++ b/jobs/generated/profile.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient diff --git a/jobs/generated/receipts.h b/jobs/generated/receipts.h index e4065ddb..9eb7a489 100644 --- a/jobs/generated/receipts.h +++ b/jobs/generated/receipts.h @@ -7,7 +7,6 @@ #include "../basejob.h" #include -#include namespace QMatrixClient diff --git a/jobs/generated/redaction.h b/jobs/generated/redaction.h index 0a68f8cd..e3b3ff4f 100644 --- a/jobs/generated/redaction.h +++ b/jobs/generated/redaction.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient diff --git a/jobs/generated/third_party_membership.h b/jobs/generated/third_party_membership.h index b1669795..c7b5214e 100644 --- a/jobs/generated/third_party_membership.h +++ b/jobs/generated/third_party_membership.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient diff --git a/jobs/generated/typing.h b/jobs/generated/typing.h index 6eb3ddf4..0495ed0a 100644 --- a/jobs/generated/typing.h +++ b/jobs/generated/typing.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient diff --git a/jobs/generated/versions.h b/jobs/generated/versions.h index a7add8ba..686d7069 100644 --- a/jobs/generated/versions.h +++ b/jobs/generated/versions.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include #include diff --git a/jobs/generated/whoami.h b/jobs/generated/whoami.h index 21cb1a17..8e1952da 100644 --- a/jobs/generated/whoami.h +++ b/jobs/generated/whoami.h @@ -6,7 +6,6 @@ #include "../basejob.h" -#include namespace QMatrixClient -- cgit v1.2.3 From b3ad6aa8fe62f461c99ae7728482bd9958e38909 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jan 2018 22:04:28 +0900 Subject: BaseJob: afterStart(), beforeAbandon(), up/downloadProgress() To support the upcoming DownloadFileJob --- jobs/basejob.cpp | 17 ++++++++++++++--- jobs/basejob.h | 6 ++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index 0a81ad5c..720ac560 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -217,15 +217,21 @@ void BaseJob::Private::sendRequest() } } -void BaseJob::beforeStart(const ConnectionData* connData) -{ -} +void BaseJob::beforeStart(const ConnectionData*) +{ } + +void BaseJob::afterStart(const ConnectionData*, QNetworkReply*) +{ } + +void BaseJob::beforeAbandon(QNetworkReply*) +{ } void BaseJob::start(const ConnectionData* connData) { d->connection = connData; beforeStart(connData); sendRequest(); + afterStart(connData, d->reply.data()); } void BaseJob::sendRequest() @@ -239,6 +245,10 @@ void BaseJob::sendRequest() connect( d->reply.data(), &QNetworkReply::finished, this, &BaseJob::gotReply ); if (d->reply->isRunning()) { + connect( d->reply.data(), &QNetworkReply::uploadProgress, + this, &BaseJob::uploadProgress); + connect( d->reply.data(), &QNetworkReply::downloadProgress, + this, &BaseJob::downloadProgress); d->timer.start(getCurrentTimeout()); qCDebug(d->logCat) << this << "request has been sent"; emit started(); @@ -431,6 +441,7 @@ void BaseJob::setStatus(int code, QString message) void BaseJob::abandon() { + beforeAbandon(d->reply.data()); this->disconnect(); if (d->reply) d->reply->disconnect(this); diff --git a/jobs/basejob.h b/jobs/basejob.h index 6648dc1b..e9e108c6 100644 --- a/jobs/basejob.h +++ b/jobs/basejob.h @@ -192,6 +192,9 @@ namespace QMatrixClient */ void failure(BaseJob*); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + protected: using headers_t = QHash; @@ -210,6 +213,9 @@ namespace QMatrixClient void setExpectedContentTypes(const QByteArrayList& contentTypes); virtual void beforeStart(const ConnectionData* connData); + virtual void afterStart(const ConnectionData* connData, + QNetworkReply* reply); + virtual void beforeAbandon(QNetworkReply*); /** * Used by gotReply() to check the received reply for general -- cgit v1.2.3 From 43710d6a5731778e28d907a3a264bcf74550073e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jan 2018 22:07:33 +0900 Subject: DownloadFileJob Instead of exposing a QIODevice as GetContentJob does it gets a filename and saves the incoming payload into it. --- CMakeLists.txt | 1 + jobs/downloadfilejob.cpp | 113 +++++++++++++++++++++++++++++++++++++++++++++++ jobs/downloadfilejob.h | 27 +++++++++++ libqmatrixclient.pri | 6 ++- 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 jobs/downloadfilejob.cpp create mode 100644 jobs/downloadfilejob.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a5b24135..da5bac9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,7 @@ set(libqmatrixclient_SRCS jobs/roommessagesjob.cpp jobs/syncjob.cpp jobs/mediathumbnailjob.cpp + jobs/downloadfilejob.cpp ) aux_source_directory(jobs/generated libqmatrixclient_job_SRCS) diff --git a/jobs/downloadfilejob.cpp b/jobs/downloadfilejob.cpp new file mode 100644 index 00000000..2530e259 --- /dev/null +++ b/jobs/downloadfilejob.cpp @@ -0,0 +1,113 @@ +#include "downloadfilejob.h" + +#include +#include +#include + +using namespace QMatrixClient; + +class DownloadFileJob::Private +{ + public: + Private() : tempFile(new QTemporaryFile()) { } + + explicit Private(const QString& localFilename) + : targetFile(new QFile(localFilename)) + , tempFile(new QFile(targetFile->fileName() + ".qmcdownload")) + { } + + QScopedPointer targetFile; + QScopedPointer tempFile; +}; + +DownloadFileJob::DownloadFileJob(const QString& serverName, + const QString& mediaId, + const QString& localFilename) + : GetContentJob(serverName, mediaId) + , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) +{ + setObjectName("DownloadFileJob"); +} + +QString DownloadFileJob::targetFileName() const +{ + return (d->targetFile ? d->targetFile : d->tempFile)->fileName(); +} + +void DownloadFileJob::beforeStart(const ConnectionData*) +{ + if (d->targetFile && !d->targetFile->open(QIODevice::WriteOnly)) + { + qCWarning(JOBS) << "Couldn't open the file" + << d->targetFile->fileName() << "for writing"; + setStatus(FileError, "Could not open the target file for writing"); + return; + } + if (!d->tempFile->open(QIODevice::WriteOnly)) + { + qCWarning(JOBS) << "Couldn't open the temporary file" + << d->tempFile->fileName() << "for writing"; + setStatus(FileError, "Could not open the temporary download file"); + } + qCDebug(JOBS) << "Downloading to" << d->tempFile->fileName(); +} + +void DownloadFileJob::afterStart(const ConnectionData*, QNetworkReply* reply) +{ + connect(reply, &QNetworkReply::metaDataChanged, this, [this,reply] { + auto sizeHeader = reply->header(QNetworkRequest::ContentLengthHeader); + if (sizeHeader.isValid()) + { + auto targetSize = sizeHeader.value(); + if (targetSize != -1) + if (!d->tempFile->resize(targetSize)) + { + qCWarning(JOBS) << "Failed to allocate" << targetSize + << "bytes for" << d->tempFile->fileName(); + setStatus(FileError, + "Could not reserve disk space for download"); + } + } + }); + connect(reply, &QIODevice::readyRead, this, [this,reply] { + auto bytes = reply->read(reply->bytesAvailable()); + if (bytes.isEmpty()) + { + qCWarning(JOBS) + << "Unexpected empty chunk when downloading from" + << reply->url() << "to" << d->tempFile->fileName(); + } else { + d->tempFile->write(bytes); + } + }); +} + +void DownloadFileJob::beforeAbandon(QNetworkReply*) +{ + if (d->targetFile) + d->targetFile->remove(); + d->tempFile->remove(); +} + +BaseJob::Status DownloadFileJob::parseReply(QNetworkReply*) +{ + if (d->targetFile) + { + d->targetFile->close(); + if (!d->targetFile->remove()) + { + qCWarning(JOBS) << "Failed to remove the target file placeholder"; + return { FileError, "Couldn't finalise the download" }; + } + if (!d->tempFile->rename(d->targetFile->fileName())) + { + qCWarning(JOBS) << "Failed to rename" << d->tempFile->fileName() + << "to" << d->targetFile->fileName(); + return { FileError, "Couldn't finalise the download" }; + } + } + else + d->tempFile->close(); + qCDebug(JOBS) << "Saved a file as" << targetFileName(); + return Success; +} diff --git a/jobs/downloadfilejob.h b/jobs/downloadfilejob.h new file mode 100644 index 00000000..d798506c --- /dev/null +++ b/jobs/downloadfilejob.h @@ -0,0 +1,27 @@ +#pragma once + +#include "generated/content-repo.h" + +namespace QMatrixClient +{ + class DownloadFileJob : public GetContentJob + { + public: + enum { FileError = BaseJob::UserDefinedError + 1 }; + + DownloadFileJob(const QString& serverName, const QString& mediaId, + const QString& localFilename = {}); + + QString targetFileName() const; + + private: + class Private; + QScopedPointer d; + + void beforeStart(const ConnectionData*) override; + void afterStart(const ConnectionData*, + QNetworkReply* reply) override; + void beforeAbandon(QNetworkReply*) override; + Status parseReply(QNetworkReply*) override; + }; +} diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index db5de469..9e4cb279 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -35,7 +35,8 @@ HEADERS += \ $$PWD/logging.h \ $$PWD/settings.h \ $$PWD/networksettings.h \ - $$PWD/networkaccessmanager.h + $$PWD/networkaccessmanager.h \ + $$PWD/jobs/downloadfilejob.h SOURCES += \ $$PWD/connectiondata.cpp \ @@ -65,4 +66,5 @@ SOURCES += \ $$PWD/logging.cpp \ $$PWD/settings.cpp \ $$PWD/networksettings.cpp \ - $$PWD/networkaccessmanager.cpp + $$PWD/networkaccessmanager.cpp \ + $$PWD/jobs/downloadfilejob.cpp -- cgit v1.2.3 From daff357c0631d675606feb4e001c98d1742b0bb0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jan 2018 23:22:49 +0900 Subject: Connection::getThumbnail: Add an overload for QString Connection::getThumbnail(QString,...) is better fitting to retrieve images for QML image providers - one doesn't need to create a QUrl (which if made naively ends up being incorrect) and also doesn't need to stack up "mxc://" before the mediaId. Just call Connection::getThumbnail with the id the QML engine gives you. --- connection.cpp | 17 ++++++++++++++++- connection.h | 5 +++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/connection.cpp b/connection.cpp index 53a38f0d..0086320b 100644 --- a/connection.cpp +++ b/connection.cpp @@ -298,9 +298,24 @@ RoomMessagesJob* Connection::getMessages(Room* room, const QString& from) const return callApi(room->id(), from); } +inline auto splitMediaId(const QString& mediaId) +{ + auto idParts = mediaId.split('/'); + Q_ASSERT_X(idParts.size() == 2, __FUNCTION__, + "mediaId should have a form 'serverName/localMediaId' (without apostrophes)"); + return idParts; +} + +MediaThumbnailJob* Connection::getThumbnail(const QString& mediaId, QSize requestedSize) const +{ + auto idParts = splitMediaId(mediaId); + return callApi(idParts.front(), idParts.back(), + requestedSize); +} + MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, QSize requestedSize) const { - return callApi(url, requestedSize); + return getThumbnail(url.authority() + url.path(), requestedSize); } MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, int requestedWidth, diff --git a/connection.h b/connection.h index 8dda2bbf..1d483fe8 100644 --- a/connection.h +++ b/connection.h @@ -174,8 +174,10 @@ namespace QMatrixClient void sync(int timeout = -1); void stopSync(); - virtual MediaThumbnailJob* getThumbnail(const QUrl& url, + virtual MediaThumbnailJob* getThumbnail(const QString& mediaId, QSize requestedSize) const; + MediaThumbnailJob* getThumbnail(const QUrl& url, + QSize requestedSize) const; MediaThumbnailJob* getThumbnail(const QUrl& url, int requestedWidth, int requestedHeight) const; @@ -297,7 +299,6 @@ namespace QMatrixClient */ Room* provideRoom(const QString& roomId, JoinState joinState); - /** * Completes loading sync data. */ -- cgit v1.2.3 From bb4d1e98f077d29b62efa5f453086fa569698856 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jan 2018 23:31:46 +0900 Subject: Connection: files up/downloading support --- connection.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ connection.h | 15 +++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/connection.cpp b/connection.cpp index 0086320b..2bff6179 100644 --- a/connection.cpp +++ b/connection.cpp @@ -29,6 +29,7 @@ #include "jobs/roommessagesjob.h" #include "jobs/syncjob.h" #include "jobs/mediathumbnailjob.h" +#include "jobs/downloadfilejob.h" #include #include @@ -324,6 +325,47 @@ MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, int requestedWidth, return getThumbnail(url, QSize(requestedWidth, requestedHeight)); } +UploadContentJob* Connection::uploadContent(QIODevice* contentSource, + const QString& filename, const QString& contentType) const +{ + return callApi(contentSource, filename, contentType); +} + +UploadContentJob* Connection::uploadFile(const QString& fileName, + const QString& contentType) +{ + auto sourceFile = new QFile(fileName); + if (sourceFile->open(QIODevice::ReadOnly)) + { + qCWarning(MAIN) << "Couldn't open" << sourceFile->fileName() + << "for reading"; + return nullptr; + } + return uploadContent(sourceFile, QFileInfo(*sourceFile).fileName(), + contentType); +} + +GetContentJob* Connection::getContent(const QString& mediaId) const +{ + auto idParts = splitMediaId(mediaId); + return callApi(idParts.front(), idParts.back()); +} + +GetContentJob* Connection::getContent(const QUrl& url) const +{ + return getContent(url.authority() + url.path()); +} + +DownloadFileJob* Connection::downloadFile(const QUrl& url, + const QString& localFilename) const +{ + auto mediaId = url.authority() + url.path(); + auto idParts = splitMediaId(mediaId); + auto* job = callApi(idParts.front(), idParts.back(), + localFilename); + return job; +} + ForgetRoomJob* Connection::forgetRoom(const QString& id) { // To forget is hard :) First we should ensure the local user is not diff --git a/connection.h b/connection.h index 1d483fe8..79d7d658 100644 --- a/connection.h +++ b/connection.h @@ -41,6 +41,9 @@ namespace QMatrixClient class PostReceiptJob; class MediaThumbnailJob; class JoinRoomJob; + class UploadContentJob; + class GetContentJob; + class DownloadFileJob; class Connection: public QObject { Q_OBJECT @@ -182,6 +185,18 @@ namespace QMatrixClient int requestedWidth, int requestedHeight) const; + // QIODevice* should already be open + virtual UploadContentJob* uploadContent(QIODevice* contentSource, + const QString& filename = {}, + const QString& contentType = {}) const; + virtual UploadContentJob* uploadFile(const QString& fileName, + const QString& contentType = {}); + virtual GetContentJob* getContent(const QString& mediaId) const; + GetContentJob* getContent(const QUrl& url) const; + // If localFilename is empty, a temporary file will be created + virtual DownloadFileJob* downloadFile(const QUrl& url, + const QString& localFilename = {}) const; + virtual JoinRoomJob* joinRoom(const QString& roomAlias); // Old API that will be abolished any time soon. DO NOT USE. -- cgit v1.2.3 From eec3865e536a6c4f95f917eefc2e94c8d0c158b1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jan 2018 23:47:08 +0900 Subject: User: Q_PROPERTYs; setAvatar() --- user.cpp | 46 +++++++++++++++++++++++++++++++++++++++++----- user.h | 25 ++++++++++++++++--------- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/user.cpp b/user.cpp index 6d2a2030..baa7bc45 100644 --- a/user.cpp +++ b/user.cpp @@ -23,9 +23,11 @@ #include "events/event.h" #include "events/roommemberevent.h" #include "jobs/generated/profile.h" +#include "jobs/generated/content-repo.h" #include #include +#include using namespace QMatrixClient; @@ -42,6 +44,9 @@ class User::Private QString bridged; Connection* connection; Avatar avatar; + QPointer avatarUploadJob = nullptr; + + void setAvatar(UploadContentJob* job, User* q); }; User::User(QString userId, Connection* connection) @@ -66,19 +71,51 @@ QString User::name() const void User::updateName(const QString& newName) { const auto oldName = name(); - if (d->name != newName) + if (oldName != newName) { d->name = newName; - emit nameChanged(this, oldName); + emit nameChanged(newName, oldName); } } +void User::updateAvatarUrl(const QUrl& newUrl) +{ + if (d->avatar.updateUrl(newUrl)) + emit avatarChanged(this); +} + void User::rename(const QString& newName) { auto job = d->connection->callApi(id(), newName); connect(job, &BaseJob::success, this, [=] { updateName(newName); }); } +bool User::setAvatar(const QString& fileName) +{ + if (isJobRunning(d->avatarUploadJob)) + return false; + d->setAvatar(d->connection->uploadFile(fileName), this); + return true; +} + +bool User::setAvatar(QIODevice* source) +{ + if (isJobRunning(d->avatarUploadJob) || !source->isReadable()) + return false; + d->setAvatar(d->connection->uploadContent(source), this); + return true; +} + +void User::Private::setAvatar(UploadContentJob* job, User* q) +{ + avatarUploadJob = job; + connect(job, &BaseJob::success, q, [this,q] { + auto* j = connection->callApi( + userId, avatarUploadJob->contentUri()); + connect(j, &BaseJob::success, q, [q] { emit q->avatarChanged(q); }); + }); +} + QString User::displayname() const { if( !d->name.isEmpty() ) @@ -90,7 +127,7 @@ QString User::bridged() const { return d->bridged; } -Avatar& User::avatarObject() +const Avatar& User::avatarObject() { return d->avatar; } @@ -127,7 +164,6 @@ void User::processEvent(Event* event) newName.truncate(match.capturedStart(0)); } updateName(newName); - if (d->avatar.updateUrl(e->avatarUrl())) - emit avatarChanged(this); + updateAvatarUrl(e->avatarUrl()); } } diff --git a/user.h b/user.h index b7d67fb2..91dfdc09 100644 --- a/user.h +++ b/user.h @@ -29,6 +29,10 @@ namespace QMatrixClient class User: public QObject { Q_OBJECT + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString displayName READ displayname NOTIFY nameChanged STORED false) + Q_PROPERTY(QString bridgeName READ bridged NOTIFY nameChanged STORED false) public: User(QString userId, Connection* connection); ~User() override; @@ -36,40 +40,43 @@ namespace QMatrixClient /** * Returns the id of the user */ - Q_INVOKABLE QString id() const; + QString id() const; /** * Returns the name chosen by the user */ - Q_INVOKABLE QString name() const; + QString name() const; /** * Returns the name that should be used to display the user. */ - Q_INVOKABLE QString displayname() const; + QString displayname() const; /** * Returns the name of bridge the user is connected from or empty. */ - Q_INVOKABLE QString bridged() const; + QString bridged() const; - Avatar& avatarObject(); - QImage avatar(int dimension); - QImage avatar(int requestedWidth, int requestedHeight); + const Avatar& avatarObject(); + Q_INVOKABLE QImage avatar(int dimension); + Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight); - QUrl avatarUrl() const; + Q_INVOKABLE QUrl avatarUrl() const; void processEvent(Event* event); public slots: void rename(const QString& newName); + bool setAvatar(const QString& fileName); + bool setAvatar(QIODevice* source); signals: - void nameChanged(User*, QString); + void nameChanged(QString newName, QString oldName); void avatarChanged(User* user); private slots: void updateName(const QString& newName); + void updateAvatarUrl(const QUrl& newUrl); private: class Private; -- cgit v1.2.3 From b9f4b655273481e64d7d7ead6a30dbf85a901063 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 13 Jan 2018 19:42:37 +0900 Subject: Refactor EventContent; allow to easily check files out of message events The whole inheritance/templating structure has been considerably simplified by using a trick with mixin classes; thanks to that, *Info classes are no more templated, they are just mixed together by the almighty UrlBasedContent<> template (but the same can easily be done outside of it, as LocationContent implementation shows). RoomMessageEvent has gained hasFileContent(); it's also possible to easily get a FileInfo core object just by calling msgEvent->content()->fileInfo(). --- events/eventcontent.cpp | 51 ++++++++--- events/eventcontent.h | 212 ++++++++++++++++++++------------------------ events/roommessageevent.cpp | 43 +++++---- events/roommessageevent.h | 34 +++---- 4 files changed, 175 insertions(+), 165 deletions(-) diff --git a/events/eventcontent.cpp b/events/eventcontent.cpp index dcbccf08..271669e2 100644 --- a/events/eventcontent.cpp +++ b/events/eventcontent.cpp @@ -30,27 +30,21 @@ QJsonObject Base::toJson() const return o; } -QJsonObject InfoBase::toInfoJson() const -{ - QJsonObject info; - fillInfoJson(&info); - return info; -} - -void InfoBase::fillInfoJson(QJsonObject*) const { } - FileInfo::FileInfo(const QUrl& u, int payloadSize, const QMimeType& mimeType, const QString& originalFilename) - : InfoBase(mimeType), url(u), payloadSize(payloadSize) + : mimeType(mimeType), url(u), payloadSize(payloadSize) , originalName(originalFilename) { } FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, const QString& originalFilename) - : FileInfo(u, infoJson["size"].toInt(), - QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString()), - originalFilename) + : originalInfoJson(infoJson) + , mimeType(QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString())) + , url(u) + , payloadSize(infoJson["size"].toInt()) + , originalName(originalFilename) { + originalInfoJson.insert("mediaId", url.authority() + url.path()); if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } @@ -61,3 +55,34 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert("size", payloadSize); infoJson->insert("mimetype", mimeType.name()); } + +ImageInfo::ImageInfo(const QUrl& u, int fileSize, QMimeType mimeType, + const QSize& imageSize) + : FileInfo(u, fileSize, mimeType), imageSize(imageSize) +{ } + +ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename) + : FileInfo(u, infoJson, originalFilename) + , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt()) +{ } + +void ImageInfo::fillInfoJson(QJsonObject* infoJson) const +{ + FileInfo::fillInfoJson(infoJson); + infoJson->insert("w", imageSize.width()); + infoJson->insert("h", imageSize.height()); +} + +WithThumbnail::WithThumbnail(const QJsonObject& infoJson) + : thumbnail(infoJson["thumbnail_url"].toString(), + infoJson["thumbnail_info"].toObject()) +{ } + +void WithThumbnail::fillInfoJson(QJsonObject* infoJson) const +{ + infoJson->insert("thumbnail_url", thumbnail.url.toString()); + QJsonObject thumbnailInfoJson; + thumbnail.fillInfoJson(&thumbnailInfoJson); + infoJson->insert("thumbnail_info", thumbnailInfoJson); +} diff --git a/events/eventcontent.h b/events/eventcontent.h index 60437995..b37dc923 100644 --- a/events/eventcontent.h +++ b/events/eventcontent.h @@ -23,7 +23,6 @@ #include "converters.h" -#include #include #include #include @@ -40,28 +39,23 @@ namespace QMatrixClient * a QJsonObject and override fillJson() with an implementation * that will fill the target QJsonObject with stored values. It is * assumed but not required that a content object can also be created - * from plain data. fillJson() should only fill the main JSON object - * but not the "info" subobject if it exists for a certain content type; - * use \p InfoBase to de/serialize "info" parts with an optional URL - * on the top level. + * from plain data. */ class Base { public: + explicit Base (const QJsonObject& o = {}) : originalJson(o) { } virtual ~Base() = default; QJsonObject toJson() const; + public: + QJsonObject originalJson; + protected: virtual void fillJson(QJsonObject* o) const = 0; }; - class TypedBase: public Base - { - public: - virtual QMimeType type() const = 0; - }; - template class SimpleContent: public Base { @@ -74,10 +68,12 @@ namespace QMatrixClient : value(std::forward(value)), key(std::move(keyName)) { } SimpleContent(const QJsonObject& json, QString keyName) - : value(QMatrixClient::fromJson(json[keyName])) + : Base(json) + , value(QMatrixClient::fromJson(json[keyName])) , key(std::move(keyName)) { } + public: T value; protected: @@ -91,44 +87,36 @@ namespace QMatrixClient } }; - /** - * A base class for content types that have an "info" object in their - * JSON representation - * - * These include most multimedia types currently in the CS API spec. - * Derived classes should override fillInfoJson() to fill the "info" - * subobject, BUT NOT the main JSON object. Most but not all "info" - * classes (specifically, those deriving from FileInfo) should also - * have a constructor that accepts two parameters, QUrl and QJsonObject, - * in order to load the URL+info part from JSON. - */ - class InfoBase - { - public: - virtual ~InfoBase() = default; - - QJsonObject toInfoJson() const; - - QMimeType mimeType; - - protected: - InfoBase() = default; - explicit InfoBase(const QMimeType& type) : mimeType(type) { } - - virtual void fillInfoJson(QJsonObject* /*infoJson*/) const = 0; - }; - // The below structures fairly follow CS spec 11.2.1.6. The overall // set of attributes for each content types is a superset of the spec // but specific aggregation structure is altered. See doc comments to // each type for the list of available attributes. + // A quick classes inheritance structure follows: + // FileInfo + // FileContent : UrlBasedContent + // AudioContent : UrlBasedContent + // ImageInfo : FileInfo + imageSize attribute + // ImageContent : UrlBasedContent + // VideoContent : UrlBasedContent + /** - * Base class for content types that consist of a URL along with - * additional information. Most of message types except textual fall - * under this category. + * A base/mixin class for structures representing an "info" object for + * some content types. These include most attachment types currently in + * the CS API spec. + * + * In order to use it in a content class, derive both from TypedBase + * (or Base) and from FileInfo (or its derivative, such as \p ImageInfo) + * and call fillInfoJson() to fill the "info" subobject. Make sure + * to pass an "info" part of JSON to FileInfo constructor, not the whole + * JSON content, as well as contents of "url" (or a similar key) and + * optionally "filename" node from the main JSON content. Assuming you + * don't do unusual things, you should use \p UrlBasedContent<> instead + * of doing multiple inheritance and overriding Base::fillJson() by hand. + * + * This class is not polymorphic. */ - class FileInfo: public InfoBase + class FileInfo { public: explicit FileInfo(const QUrl& u, int payloadSize = -1, @@ -137,111 +125,101 @@ namespace QMatrixClient FileInfo(const QUrl& u, const QJsonObject& infoJson, const QString& originalFilename = {}); + void fillInfoJson(QJsonObject* infoJson) const; + + /** + * \brief Extract media id from the URL + * + * This can be used, e.g., to construct a QML-facing image:// + * URI as follows: + * \code "image://provider/" + info.mediaId() \endcode + */ + QString mediaId() const { return url.authority() + url.path(); } + + public: + QJsonObject originalInfoJson; + QMimeType mimeType; QUrl url; int payloadSize; QString originalName; - - protected: - void fillInfoJson(QJsonObject* infoJson) const override; }; /** - * A base class for image info types: image, thumbnail, video - * - * \tparam InfoT base info class; should derive from \p InfoBase + * A content info class for image content types: image, thumbnail, video */ - template - class ImageInfo : public InfoT + class ImageInfo : public FileInfo { public: explicit ImageInfo(const QUrl& u, int fileSize = -1, QMimeType mimeType = {}, - const QSize& imageSize = {}) - : InfoT(u, fileSize, mimeType), imageSize(imageSize) - { } + const QSize& imageSize = {}); ImageInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}) - : InfoT(u, infoJson, originalFilename) - , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt()) - { } + const QString& originalFilename = {}); - QSize imageSize; + void fillInfoJson(QJsonObject* infoJson) const; - protected: - void fillInfoJson(QJsonObject* infoJson) const override - { - InfoT::fillInfoJson(infoJson); - infoJson->insert("w", imageSize.width()); - infoJson->insert("h", imageSize.height()); - } + public: + QSize imageSize; }; /** - * A base class for an info type that carries a thumbnail + * A mixin class for an info type that carries a thumbnail * - * This class decorates the underlying type, adding ability to save/load - * a thumbnail to/from "info" subobject of the JSON representation of - * event content; namely, "info/thumbnail_url" and "info/thumbnail_info" - * fields are used. - * - * \tparam InfoT base info class; should derive from \p InfoBase + * This class saves/loads a thumbnail to/from "info" subobject of + * the JSON representation of event content; namely, + * "info/thumbnail_url" and "info/thumbnail_info" fields are used. */ - template - class Thumbnailed : public InfoT + class WithThumbnail { public: - template - explicit Thumbnailed(const ImageInfo<>& thumbnail, - ArgTs&&... infoArgs) - : InfoT(std::forward(infoArgs)...) - , thumbnail(thumbnail) - { } - - explicit Thumbnailed(const QJsonObject& infoJson) - : thumbnail(infoJson["thumbnail_url"].toString(), - infoJson["thumbnail_info"].toObject()) + WithThumbnail(const QJsonObject& infoJson); + WithThumbnail(const ImageInfo& info) + : thumbnail(info) { } - Thumbnailed(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}) - : InfoT(u, infoJson, originalFilename) - , thumbnail(infoJson["thumbnail_url"].toString(), - infoJson["thumbnail_info"].toObject()) - { } + /** + * Writes thumbnail information to "thumbnail_info" subobject + * and thumbnail URL to "thumbnail_url" node inside "info". + */ + void fillInfoJson(QJsonObject* infoJson) const; - ImageInfo<> thumbnail; + public: + ImageInfo thumbnail; + }; - protected: - void fillInfoJson(QJsonObject* infoJson) const override - { - InfoT::fillInfoJson(infoJson); - infoJson->insert("thumbnail_url", thumbnail.url.toString()); - infoJson->insert("thumbnail_info", thumbnail.toInfoJson()); - } + class TypedBase: public Base + { + public: + explicit TypedBase(const QJsonObject& o = {}) : Base(o) { } + virtual QMimeType type() const = 0; + virtual const FileInfo* fileInfo() const { return nullptr; } }; /** - * One more facility base class for content types that have a URL and - * additional info + * A base class for content types that have a URL and additional info * - * Types that derive from UrlWith take "url" and, optionally, - * "filename" values from the top-level JSON object and the rest of - * information from the "info" subobject. + * Types that derive from this class template take "url" and, + * optionally, "filename" values from the top-level JSON object and + * the rest of information from the "info" subobject, as defined by + * the parameter type. * - * \tparam InfoT base info class; should derive from \p FileInfo or - * provide a constructor with a compatible signature + * \tparam InfoT base info class + * \tparam InfoMixinTs... additional info mixin classes (e.g. WithThumbnail) */ - template // InfoT : public FileInfo - class UrlWith : public TypedBase, public InfoT + template + class UrlBasedContent : + public TypedBase, public InfoT, public InfoMixinTs... { public: - using InfoT::InfoT; - explicit UrlWith(const QJsonObject& json) - : InfoT(json["url"].toString(), json["info"].toObject(), + explicit UrlBasedContent(const QJsonObject& json) + : TypedBase(json) + , InfoT(json["url"].toString(), json["info"].toObject(), json["filename"].toString()) + , InfoMixinTs(InfoT::originalInfoJson)... { } QMimeType type() const override { return InfoT::mimeType; } + const FileInfo* fileInfo() const override { return this; } protected: void fillJson(QJsonObject* json) const override @@ -250,7 +228,13 @@ namespace QMatrixClient json->insert("url", InfoT::url.toString()); if (!InfoT::originalName.isEmpty()) json->insert("filename", InfoT::originalName); - json->insert("info", InfoT::toInfoJson()); + QJsonObject infoJson; + InfoT::fillInfoJson(&infoJson); + // http://en.cppreference.com/w/cpp/language/parameter_pack#Brace-enclosed_initializers + // Looking forward to C++17 and its folding awesomeness. + int d[] = { (InfoMixinTs::fillInfoJson(&infoJson), 0)... }; + Q_UNUSED(d); + json->insert("info", infoJson); } }; @@ -272,7 +256,7 @@ namespace QMatrixClient * - mimeType * - imageSize */ - using ImageContent = UrlWith>>; + using ImageContent = UrlBasedContent; /** * Content class for m.file @@ -290,6 +274,6 @@ namespace QMatrixClient * - thumbnail.mimeType * - thumbnail.imageSize (QSize for "h" and "w" in JSON) */ - using FileContent = UrlWith>; + using FileContent = UrlBasedContent; } // namespace EventContent } // namespace QMatrixClient diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index bc41abf6..20e81564 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -116,6 +116,11 @@ QMimeType RoomMessageEvent::mimeType() const QMimeDatabase().mimeTypeForName("text/plain"); } +bool RoomMessageEvent::hasFileContent() const +{ + return content() && content()->fileInfo(); +} + QJsonObject RoomMessageEvent::toJson() const { QJsonObject obj = _content ? _content->toJson() : QJsonObject(); @@ -153,43 +158,35 @@ void TextContent::fillJson(QJsonObject* json) const json->insert("formatted_body", body); } -LocationContent::LocationContent(const QString& geoUri, - const ImageInfo<>& thumbnail) - : Thumbnailed<>(thumbnail), geoUri(geoUri) +LocationContent::LocationContent(const QString& geoUri, const ImageInfo& thumbnail) + : WithThumbnail(thumbnail), geoUri(geoUri) { } LocationContent::LocationContent(const QJsonObject& json) - : Thumbnailed<>(json["info"].toObject()) + : TypedBase(json) + , WithThumbnail(json["info"].toObject()) , geoUri(json["geo_uri"].toString()) { } -void LocationContent::fillJson(QJsonObject* o) const -{ - Q_ASSERT(o); - o->insert("geo_uri", geoUri); - o->insert("info", Thumbnailed::toInfoJson()); -} - QMimeType LocationContent::type() const { return QMimeDatabase().mimeTypeForData(geoUri.toLatin1()); } -PlayableInfo::PlayableInfo(const QUrl& u, int fileSize, - const QMimeType& mimeType, int duration, - const QString& originalFilename) - : FileInfo(u, fileSize, mimeType, originalFilename) - , duration(duration) -{ } +void LocationContent::fillJson(QJsonObject* o) const +{ + Q_ASSERT(o); + o->insert("geo_uri", geoUri); + QJsonObject infoJson; + WithThumbnail::fillInfoJson(&infoJson); + o->insert("info", infoJson); +} -PlayableInfo::PlayableInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename) - : FileInfo(u, infoJson, originalFilename) - , duration(infoJson["duration"].toInt()) +WithDuration::WithDuration(const QJsonObject& infoJson) + : duration(infoJson["duration"].toInt()) { } -void PlayableInfo::fillInfoJson(QJsonObject* infoJson) const +void WithDuration::fillInfoJson(QJsonObject* infoJson) const { - FileInfo::fillInfoJson(infoJson); infoJson->insert("duration", duration); } diff --git a/events/roommessageevent.h b/events/roommessageevent.h index eef6b657..6b551b76 100644 --- a/events/roommessageevent.h +++ b/events/roommessageevent.h @@ -32,6 +32,10 @@ namespace QMatrixClient 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(EventContent::TypedBase* content READ content CONSTANT) public: enum class MsgType { @@ -52,9 +56,10 @@ namespace QMatrixClient MsgType msgtype() const; QString rawMsgtype() const { return _msgtype; } const QString& plainBody() const { return _plainBody; } - const EventContent::TypedBase* content() const + EventContent::TypedBase* content() const { return _content.data(); } QMimeType mimeType() const; + bool hasFileContent() const; QJsonObject toJson() const; @@ -107,15 +112,16 @@ namespace QMatrixClient * - thumbnail.mimeType * - thumbnail.imageSize */ - class LocationContent: public TypedBase, public Thumbnailed<> + class LocationContent: public TypedBase, public WithThumbnail { public: LocationContent(const QString& geoUri, - const ImageInfo<>& thumbnail); + const ImageInfo& thumbnail); explicit LocationContent(const QJsonObject& json); QMimeType type() const override; + public: QString geoUri; protected: @@ -123,21 +129,18 @@ namespace QMatrixClient }; /** - * A base class for "playable" info types: audio and video + * A mixin class for info types that include duration: audio and video */ - class PlayableInfo : public FileInfo + class WithDuration { public: - explicit PlayableInfo(const QUrl& u, int fileSize, - const QMimeType& mimeType, int duration, - const QString& originalFilename = {}); - PlayableInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}); + explicit WithDuration(int duration) : duration(duration) { } + WithDuration(const QJsonObject& infoJson); - int duration; + void fillInfoJson(QJsonObject* infoJson) const; - protected: - void fillInfoJson(QJsonObject* infoJson) const override; + public: + int duration; }; /** @@ -159,7 +162,8 @@ namespace QMatrixClient * - mimeType * - imageSize */ - using VideoContent = UrlWith>>; + using VideoContent = + UrlBasedContent; /** * Content class for m.audio @@ -173,6 +177,6 @@ namespace QMatrixClient * - mimeType ("mimetype" in JSON) * - duration */ - using AudioContent = UrlWith; + using AudioContent = UrlBasedContent; } // namespace EventContent } // namespace QMatrixClient -- cgit v1.2.3 From 93b170ceaced07a3de64708094bb68303a3d4440 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 13 Jan 2018 15:37:09 +0900 Subject: RoomEvent-aware file up/downloads along with status tracking Closes #121; closes #122. --- room.cpp | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ room.h | 41 ++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/room.cpp b/room.cpp index 28107228..f6286a7e 100644 --- a/room.cpp +++ b/room.cpp @@ -33,6 +33,7 @@ #include "events/redactionevent.h" #include "jobs/sendeventjob.h" #include "jobs/roommessagesjob.h" +#include "jobs/downloadfilejob.h" #include "avatar.h" #include "connection.h" #include "user.h" @@ -40,9 +41,12 @@ #include #include // for efficient string concats (operator%) #include +#include +#include #include #include +#include using namespace QMatrixClient; using namespace std::placeholders; @@ -90,6 +94,37 @@ class Room::Private QString prevBatch; RoomMessagesJob* roomMessagesJob; + struct FileTransferPrivateInfo + { + QPointer job = nullptr; + QFileInfo localFileInfo { }; + FileTransferInfo::Status status = FileTransferInfo::Started; + qint64 progress = 0; + qint64 total = -1; + + void update(qint64 p, qint64 t) + { + progress = p; total = t; + if (t == 0) + { + t = -1; + if (p == 0) + p = -1; + } + } + }; + void failedTransfer(const QString& tid, const QString& errorMessage = {}) + { + qCWarning(MAIN) << "File transfer failed for id" << tid; + if (!errorMessage.isEmpty()) + qCWarning(MAIN) << "Message:" << errorMessage; + fileTransfers[tid].status = FileTransferInfo::Failed; + emit q->fileTransferFailed(tid, errorMessage); + } + // A map from event/txn ids to information about the long operation; + // used for both download and upload operations + QHash fileTransfers; + // Convenience methods to work with the membersMap and usersLeft. // addMember() and removeMember() emit respective Room:: signals // after a succesful operation. @@ -413,6 +448,30 @@ void Room::resetHighlightCount() emit highlightCountChanged(this); } +FileTransferInfo Room::fileTransferInfo(const QString& id) const +{ + auto infoIt = d->fileTransfers.find(id); + if (infoIt == d->fileTransfers.end()) + return {}; + + // FIXME: Add lib tests to make sure FileTransferInfo::status stays + // consistent with FileTransferInfo::job + + qint64 progress = infoIt->progress; + qint64 total = infoIt->total; + if (total > INT_MAX) + { + // JavaScript doesn't deal with 64-bit integers; scale down if necessary + progress = std::llround(double(progress) / total * INT_MAX); + total = INT_MAX; + } + + return { infoIt->status, int(progress), int(total), + QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()), + QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()) + }; +} + QList< User* > Room::usersTyping() const { return d->usersTyping; @@ -713,6 +772,94 @@ void Room::redactEvent(const QString& eventId, const QString& reason) id(), eventId, connection()->generateTxnId(), reason); } +void Room::uploadFile(const QString& id, const QUrl& localFilename, + const QString& overrideContentType) +{ + Q_ASSERT_X(localFilename.isLocalFile(), __FUNCTION__, + "localFilename should point at a local file"); + auto fileName = localFilename.toLocalFile(); + auto job = connection()->uploadFile(fileName, overrideContentType); + if (isJobRunning(job)) + { + d->fileTransfers.insert(id, { job, fileName }); + connect(job, &BaseJob::uploadProgress, this, + [this,id] (qint64 sent, qint64 total) { + d->fileTransfers[id].update(sent, total); + emit fileTransferProgress(id, sent, total); + }); + connect(job, &BaseJob::success, this, [this,id,localFilename,job] { + d->fileTransfers[id].status = FileTransferInfo::Completed; + emit fileTransferCompleted(id, localFilename, job->contentUri()); + }); + connect(job, &BaseJob::failure, this, + std::bind(&Private::failedTransfer, d, id, job->errorString())); + emit newFileTransfer(id, localFilename); + } else + d->failedTransfer(id); +} + +void Room::downloadFile(const QString& eventId, const QUrl& localFilename) +{ + Q_ASSERT_X(localFilename.isEmpty() || localFilename.isLocalFile(), + __FUNCTION__, "localFilename should point at a local file"); + auto evtIt = findInTimeline(eventId); + if (evtIt == timelineEdge() || + evtIt->event()->type() != EventType::RoomMessage) + { + qCritical() << "Cannot download a file from event" << eventId + << "(there's no such message event in the local timeline)"; + Q_ASSERT(false); + return; + } + auto* event = static_cast(evtIt->event()); + if (!event->hasFileContent()) + { + qCritical() << eventId << "has no file content; nothing to download"; + Q_ASSERT(false); + return; + } + auto* fileInfo = event->content()->fileInfo(); + auto fileName = !localFilename.isEmpty() ? localFilename.toLocalFile() : + !fileInfo->originalName.isEmpty() ? + (QDir::tempPath() + '/' + fileInfo->originalName) : + !event->plainBody().isEmpty() ? + (QDir::tempPath() + '/' + event->plainBody()) : QString(); + auto job = connection()->downloadFile(fileInfo->url, fileName); + if (isJobRunning(job)) + { + d->fileTransfers.insert(eventId, { job, job->targetFileName() }); + connect(job, &BaseJob::downloadProgress, this, + [this,eventId] (qint64 received, qint64 total) { + d->fileTransfers[eventId].update(received, total); + emit fileTransferProgress(eventId, received, total); + }); + connect(job, &BaseJob::success, this, [this,eventId,fileInfo,job] { + d->fileTransfers[eventId].status = FileTransferInfo::Completed; + emit fileTransferCompleted(eventId, fileInfo->url, + QUrl::fromLocalFile(job->targetFileName())); + }); + connect(job, &BaseJob::failure, this, + std::bind(&Private::failedTransfer, d, + eventId, job->errorString())); + } else + d->failedTransfer(eventId); +} + +void Room::cancelFileTransfer(const QString& id) +{ + 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 (isJobRunning(it->job)) + it->job->abandon(); + d->fileTransfers.remove(id); + emit fileTransferCancelled(id); +} + void Room::Private::dropDuplicateEvents(RoomEvents* events) const { if (events->empty()) diff --git a/room.h b/room.h index f863d41b..f5bf0839 100644 --- a/room.h +++ b/room.h @@ -70,6 +70,30 @@ namespace QMatrixClient return d; } + class FileTransferInfo + { + Q_GADGET + Q_PROPERTY(bool active READ active CONSTANT) + Q_PROPERTY(bool completed READ completed CONSTANT) + Q_PROPERTY(bool failed READ failed CONSTANT) + Q_PROPERTY(int progress MEMBER progress CONSTANT) + Q_PROPERTY(int total MEMBER total CONSTANT) + Q_PROPERTY(QUrl localDir MEMBER localDir CONSTANT) + Q_PROPERTY(QUrl localPath MEMBER localPath CONSTANT) + public: + enum Status { None, Started, Completed, Failed }; + Status status = None; + int progress = 0; + int total = -1; + QUrl localDir { }; + QUrl localPath { }; + + bool active() const + { return status == Started || status == Completed; } + bool completed() const { return status == Completed; } + bool failed() const { return status == Failed; } + }; + class Room: public QObject { Q_OBJECT @@ -166,6 +190,8 @@ namespace QMatrixClient Q_INVOKABLE int highlightCount() const; Q_INVOKABLE void resetHighlightCount(); + Q_INVOKABLE FileTransferInfo fileTransferInfo(const QString& id) const; + MemberSorter memberSorter() const; QJsonObject toJson() const; @@ -191,6 +217,13 @@ namespace QMatrixClient void redactEvent(const QString& eventId, const QString& reason = {}); + void uploadFile(const QString& id, const QUrl& localFilename, + const QString& overrideContentType = {}); + // If localFilename is empty a temporary file is created + void downloadFile(const QString& eventId, + const QUrl& localFilename = {}); + void cancelFileTransfer(const QString& id); + /** Mark all messages in the room as read */ void markAllMessagesAsRead(); @@ -219,9 +252,16 @@ namespace QMatrixClient void lastReadEventChanged(User* user); void readMarkerMoved(); void unreadMessagesChanged(Room* room); + void replacedEvent(const RoomEvent* newEvent, const RoomEvent* oldEvent); + void newFileTransfer(QString id, QUrl localFile); + 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); + protected: virtual void processStateEvents(const RoomEvents& events); virtual void processEphemeralEvent(EventPtr event); @@ -253,3 +293,4 @@ namespace QMatrixClient const Room* room; }; } // namespace QMatrixClient +Q_DECLARE_METATYPE(QMatrixClient::FileTransferInfo) -- cgit v1.2.3 From 525be6dce815f88ed9cc97480bff461741665e8f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 14 Jan 2018 18:32:26 +0900 Subject: EventContent: rewrite without mixins MSVC is not good at dealing with type parameter packs of member functions, which is what the whole mixin magic in UrlBasedContent<> relied on. So it's one more level of inheritance instead of mixins now. --- events/eventcontent.cpp | 14 +++++----- events/eventcontent.h | 66 ++++++++++++++++++++++++++++++--------------- events/roommessageevent.cpp | 17 +++--------- events/roommessageevent.h | 28 ++++++++++++------- 4 files changed, 73 insertions(+), 52 deletions(-) diff --git a/events/eventcontent.cpp b/events/eventcontent.cpp index 271669e2..c96da9b3 100644 --- a/events/eventcontent.cpp +++ b/events/eventcontent.cpp @@ -74,15 +74,13 @@ void ImageInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert("h", imageSize.height()); } -WithThumbnail::WithThumbnail(const QJsonObject& infoJson) - : thumbnail(infoJson["thumbnail_url"].toString(), - infoJson["thumbnail_info"].toObject()) +Thumbnail::Thumbnail(const QJsonObject& infoJson) + : ImageInfo(infoJson["thumbnail_url"].toString(), + infoJson["thumbnail_info"].toObject()) { } -void WithThumbnail::fillInfoJson(QJsonObject* infoJson) const +void Thumbnail::fillInfoJson(QJsonObject* infoJson) const { - infoJson->insert("thumbnail_url", thumbnail.url.toString()); - QJsonObject thumbnailInfoJson; - thumbnail.fillInfoJson(&thumbnailInfoJson); - infoJson->insert("thumbnail_info", thumbnailInfoJson); + infoJson->insert("thumbnail_url", url.toString()); + infoJson->insert("thumbnail_info", toInfoJson(*this)); } diff --git a/events/eventcontent.h b/events/eventcontent.h index b37dc923..91e4ca94 100644 --- a/events/eventcontent.h +++ b/events/eventcontent.h @@ -27,6 +27,8 @@ #include #include +#include + namespace QMatrixClient { namespace EventContent @@ -144,6 +146,14 @@ namespace QMatrixClient QString originalName; }; + template + QJsonObject toInfoJson(const InfoT& info) + { + QJsonObject infoJson; + info.fillInfoJson(&infoJson); + return infoJson; + } + /** * A content info class for image content types: image, thumbnail, video */ @@ -163,18 +173,18 @@ namespace QMatrixClient }; /** - * A mixin class for an info type that carries a thumbnail + * An auxiliary class for an info type that carries a thumbnail * * This class saves/loads a thumbnail to/from "info" subobject of * the JSON representation of event content; namely, * "info/thumbnail_url" and "info/thumbnail_info" fields are used. */ - class WithThumbnail + class Thumbnail : public ImageInfo { public: - WithThumbnail(const QJsonObject& infoJson); - WithThumbnail(const ImageInfo& info) - : thumbnail(info) + Thumbnail(const QJsonObject& infoJson); + Thumbnail(const ImageInfo& info) + : ImageInfo(info) { } /** @@ -182,9 +192,6 @@ namespace QMatrixClient * and thumbnail URL to "thumbnail_url" node inside "info". */ void fillInfoJson(QJsonObject* infoJson) const; - - public: - ImageInfo thumbnail; }; class TypedBase: public Base @@ -204,18 +211,18 @@ namespace QMatrixClient * the parameter type. * * \tparam InfoT base info class - * \tparam InfoMixinTs... additional info mixin classes (e.g. WithThumbnail) */ - template - class UrlBasedContent : - public TypedBase, public InfoT, public InfoMixinTs... + template + class UrlBasedContent : public TypedBase, public InfoT { public: + UrlBasedContent(QUrl url, InfoT&& info, QString filename = {}) + : InfoT(url, std::forward(info), filename) + { } explicit UrlBasedContent(const QJsonObject& json) : TypedBase(json) , InfoT(json["url"].toString(), json["info"].toObject(), json["filename"].toString()) - , InfoMixinTs(InfoT::originalInfoJson)... { } QMimeType type() const override { return InfoT::mimeType; } @@ -228,12 +235,29 @@ namespace QMatrixClient json->insert("url", InfoT::url.toString()); if (!InfoT::originalName.isEmpty()) json->insert("filename", InfoT::originalName); - QJsonObject infoJson; - InfoT::fillInfoJson(&infoJson); - // http://en.cppreference.com/w/cpp/language/parameter_pack#Brace-enclosed_initializers - // Looking forward to C++17 and its folding awesomeness. - int d[] = { (InfoMixinTs::fillInfoJson(&infoJson), 0)... }; - Q_UNUSED(d); + json->insert("info", toInfoJson(*this)); + } + }; + + template + class UrlWithThumbnailContent : public UrlBasedContent + { + public: + // TODO: POD constructor + UrlWithThumbnailContent(const QJsonObject& json) + : UrlBasedContent(json) + , thumbnail(InfoT::originalInfoJson) + { } + + public: + Thumbnail thumbnail; + + protected: + void fillJson(QJsonObject* json) const override + { + UrlBasedContent::fillJson(json); + auto infoJson = json->take("info").toObject(); + thumbnail.fillInfoJson(&infoJson); json->insert("info", infoJson); } }; @@ -256,7 +280,7 @@ namespace QMatrixClient * - mimeType * - imageSize */ - using ImageContent = UrlBasedContent; + using ImageContent = UrlWithThumbnailContent; /** * Content class for m.file @@ -274,6 +298,6 @@ namespace QMatrixClient * - thumbnail.mimeType * - thumbnail.imageSize (QSize for "h" and "w" in JSON) */ - using FileContent = UrlBasedContent; + using FileContent = UrlWithThumbnailContent; } // namespace EventContent } // namespace QMatrixClient diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index 20e81564..3c5d10ad 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -159,13 +159,13 @@ void TextContent::fillJson(QJsonObject* json) const } LocationContent::LocationContent(const QString& geoUri, const ImageInfo& thumbnail) - : WithThumbnail(thumbnail), geoUri(geoUri) + : geoUri(geoUri), thumbnail(thumbnail) { } LocationContent::LocationContent(const QJsonObject& json) : TypedBase(json) - , WithThumbnail(json["info"].toObject()) , geoUri(json["geo_uri"].toString()) + , thumbnail(json["info"].toObject()) { } QMimeType LocationContent::type() const @@ -177,16 +177,5 @@ void LocationContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); o->insert("geo_uri", geoUri); - QJsonObject infoJson; - WithThumbnail::fillInfoJson(&infoJson); - o->insert("info", infoJson); -} - -WithDuration::WithDuration(const QJsonObject& infoJson) - : duration(infoJson["duration"].toInt()) -{ } - -void WithDuration::fillInfoJson(QJsonObject* infoJson) const -{ - infoJson->insert("duration", duration); + o->insert("info", toInfoJson(thumbnail)); } diff --git a/events/roommessageevent.h b/events/roommessageevent.h index 6b551b76..867d8880 100644 --- a/events/roommessageevent.h +++ b/events/roommessageevent.h @@ -112,7 +112,7 @@ namespace QMatrixClient * - thumbnail.mimeType * - thumbnail.imageSize */ - class LocationContent: public TypedBase, public WithThumbnail + class LocationContent: public TypedBase { public: LocationContent(const QString& geoUri, @@ -123,21 +123,32 @@ namespace QMatrixClient public: QString geoUri; + Thumbnail thumbnail; protected: void fillJson(QJsonObject* o) const override; }; /** - * A mixin class for info types that include duration: audio and video + * A base class for info types that include duration: audio and video */ - class WithDuration + template + class PlayableContent : public ContentT { public: - explicit WithDuration(int duration) : duration(duration) { } - WithDuration(const QJsonObject& infoJson); + PlayableContent(const QJsonObject& json) + : ContentT(json) + , duration(ContentT::originalInfoJson["duration"].toInt()) + { } - void fillInfoJson(QJsonObject* infoJson) const; + protected: + void fillJson(QJsonObject* json) const override + { + ContentT::fillJson(json); + auto infoJson = json->take("info").toObject(); + infoJson.insert("duration", duration); + json->insert("info", infoJson); + } public: int duration; @@ -162,8 +173,7 @@ namespace QMatrixClient * - mimeType * - imageSize */ - using VideoContent = - UrlBasedContent; + using VideoContent = PlayableContent>; /** * Content class for m.audio @@ -177,6 +187,6 @@ namespace QMatrixClient * - mimeType ("mimetype" in JSON) * - duration */ - using AudioContent = UrlBasedContent; + using AudioContent = PlayableContent>; } // namespace EventContent } // namespace QMatrixClient -- cgit v1.2.3 From 0910bf6c5bbc6c9250023796b2e641f19232c3cd Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 15 Jan 2018 10:01:34 +0900 Subject: One more fix, this time for MSVC 2015 only Making a structure from an initializer list seems to be a problem for it if initializers are defined in the structure. --- room.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/room.cpp b/room.cpp index f6286a7e..d1b098e7 100644 --- a/room.cpp +++ b/room.cpp @@ -96,6 +96,12 @@ class Room::Private struct FileTransferPrivateInfo { +#if defined(_MSC_VER) && _MSC_VER < 1910 + FileTransferPrivateInfo() = default; + FileTransferPrivateInfo(BaseJob* j, QString fileName) + : job(j), localFileInfo(fileName) + { } +#endif QPointer job = nullptr; QFileInfo localFileInfo { }; FileTransferInfo::Status status = FileTransferInfo::Started; @@ -466,10 +472,22 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const total = INT_MAX; } +#if defined(_MSC_VER) && _MSC_VER < 1910 + // A workaround for MSVC 2015 that fails with "error C2440: 'return': + // cannot convert from 'initializer list' to 'QMatrixClient::FileTransferInfo'" + FileTransferInfo fti; + fti.status = infoIt->status; + fti.progress = int(progress); + fti.total = int(total); + fti.localDir = QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()); + fti.localPath = QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()); + return fti; +#else return { infoIt->status, int(progress), int(total), QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()), QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()) }; +#endif } QList< User* > Room::usersTyping() const -- cgit v1.2.3 From 0de5e3b421633bb58805aa4b9d1bf3f07535702c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 15 Jan 2018 10:27:31 +0900 Subject: DownloadFileJob: Be tolerant to request retries --- jobs/downloadfilejob.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jobs/downloadfilejob.cpp b/jobs/downloadfilejob.cpp index 2530e259..06fa3b48 100644 --- a/jobs/downloadfilejob.cpp +++ b/jobs/downloadfilejob.cpp @@ -36,14 +36,15 @@ QString DownloadFileJob::targetFileName() const void DownloadFileJob::beforeStart(const ConnectionData*) { - if (d->targetFile && !d->targetFile->open(QIODevice::WriteOnly)) + if (d->targetFile && !d->targetFile->isReadable() && + !d->targetFile->open(QIODevice::WriteOnly)) { qCWarning(JOBS) << "Couldn't open the file" << d->targetFile->fileName() << "for writing"; setStatus(FileError, "Could not open the target file for writing"); return; } - if (!d->tempFile->open(QIODevice::WriteOnly)) + if (!d->tempFile->isReadable() && !d->tempFile->open(QIODevice::WriteOnly)) { qCWarning(JOBS) << "Couldn't open the temporary file" << d->tempFile->fileName() << "for writing"; -- cgit v1.2.3 From 5d6360b641084cf95a7f30bb760abdb7a73ebd27 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 15 Jan 2018 11:20:48 +0900 Subject: EventContent: provide mediaId in the top-level JSON, not in "info" subobject Given that it's a sidedoor anyway, it should at least be straightforward to use. --- events/eventcontent.cpp | 1 - events/eventcontent.h | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/events/eventcontent.cpp b/events/eventcontent.cpp index c96da9b3..f5974b46 100644 --- a/events/eventcontent.cpp +++ b/events/eventcontent.cpp @@ -44,7 +44,6 @@ FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, , payloadSize(infoJson["size"].toInt()) , originalName(originalFilename) { - originalInfoJson.insert("mediaId", url.authority() + url.path()); if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } diff --git a/events/eventcontent.h b/events/eventcontent.h index 91e4ca94..0733b347 100644 --- a/events/eventcontent.h +++ b/events/eventcontent.h @@ -223,7 +223,10 @@ namespace QMatrixClient : TypedBase(json) , InfoT(json["url"].toString(), json["info"].toObject(), json["filename"].toString()) - { } + { + // A small hack to facilitate links creation in QML. + originalJson.insert("mediaId", InfoT::mediaId()); + } QMimeType type() const override { return InfoT::mimeType; } const FileInfo* fileInfo() const override { return this; } -- cgit v1.2.3 From 038659c6997e5acad6ecf7171bbd3c4ec14b5d3d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 16 Jan 2018 17:00:16 +0900 Subject: EventContent: inject mediaThumbnailId into content original JSON For QML to easily make URLs to thumbnails. --- events/eventcontent.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/events/eventcontent.h b/events/eventcontent.h index 0733b347..4afbaff3 100644 --- a/events/eventcontent.h +++ b/events/eventcontent.h @@ -247,10 +247,14 @@ namespace QMatrixClient { public: // TODO: POD constructor - UrlWithThumbnailContent(const QJsonObject& json) + explicit UrlWithThumbnailContent(const QJsonObject& json) : UrlBasedContent(json) , thumbnail(InfoT::originalInfoJson) - { } + { + // Another small hack, to simplify making a thumbnail link + UrlBasedContent::originalJson.insert( + "thumbnailMediaId", thumbnail.mediaId()); + } public: Thumbnail thumbnail; -- cgit v1.2.3 From ada271e47071681848fdbdecd3aecaa9073091b4 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 17 Jan 2018 12:22:24 +0900 Subject: Room::downloadFile(): make sure temporary file names don't clash Particularly in case when there are two events that have an image with the same file name (image.png). --- room.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/room.cpp b/room.cpp index d1b098e7..bec4cd30 100644 --- a/room.cpp +++ b/room.cpp @@ -837,11 +837,14 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) return; } auto* fileInfo = event->content()->fileInfo(); + auto safeTempPrefix = eventId; + safeTempPrefix.replace(':', '_'); + safeTempPrefix = QDir::tempPath() + '/' + safeTempPrefix + '#'; auto fileName = !localFilename.isEmpty() ? localFilename.toLocalFile() : !fileInfo->originalName.isEmpty() ? - (QDir::tempPath() + '/' + fileInfo->originalName) : + (safeTempPrefix + fileInfo->originalName) : !event->plainBody().isEmpty() ? - (QDir::tempPath() + '/' + event->plainBody()) : QString(); + (safeTempPrefix + event->plainBody()) : QString(); auto job = connection()->downloadFile(fileInfo->url, fileName); if (isJobRunning(job)) { -- cgit v1.2.3 From 0f43e74bcf06b2b09018d564a73aa931098eb790 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 17 Jan 2018 13:00:36 +0900 Subject: Room::getPreviousContent(): Don't skip if the last job just finished working The sequence is: RoomMessagesJob::success -> addHistoricalMessageEvents -> MessageEventModel notification -> QML notification about model reset -> MessageEventModel completes updating -> QML updates from the model but by then scrolling has already stopped at the oldest (just loaded) event -> since there's no momentum, next batch is not fetched. In order to address this, two things are done: in QML, the current position is checked in modelReset() handler; in Room (this commit), prev_batch is updated before historical messages are added (and the model gets notified, respectively), to prevent firing another job with the old prev_batch. --- room.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/room.cpp b/room.cpp index bec4cd30..8ab5b7f5 100644 --- a/room.cpp +++ b/room.cpp @@ -744,17 +744,13 @@ void Room::getPreviousContent(int limit) void Room::Private::getPreviousContent(int limit) { - if( !roomMessagesJob ) + if( !isJobRunning(roomMessagesJob) ) { roomMessagesJob = connection->callApi(id, prevBatch, limit); - connect( roomMessagesJob, &RoomMessagesJob::result, [=] { - if( !roomMessagesJob->error() ) - { - addHistoricalMessageEvents(roomMessagesJob->releaseEvents()); - prevBatch = roomMessagesJob->end(); - } - roomMessagesJob = nullptr; + connect( roomMessagesJob, &RoomMessagesJob::success, [=] { + prevBatch = roomMessagesJob->end(); + addHistoricalMessageEvents(roomMessagesJob->releaseEvents()); }); } } -- cgit v1.2.3 From 936feeedaeda390004d9026ce23324becbcd2c93 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 17 Jan 2018 17:44:56 +0900 Subject: Fix an occasional crash introduced by the previous commit --- room.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/room.cpp b/room.cpp index 8ab5b7f5..6b7484e9 100644 --- a/room.cpp +++ b/room.cpp @@ -63,7 +63,7 @@ class Room::Private Private(Connection* c, QString id_, JoinState initialJoinState) : q(nullptr), connection(c), id(std::move(id_)) , avatar(c), joinState(initialJoinState), unreadMessages(false) - , highlightCount(0), notificationCount(0), roomMessagesJob(nullptr) + , highlightCount(0), notificationCount(0) { } Room* q; @@ -92,7 +92,7 @@ class Room::Private QList membersLeft; QHash lastReadEventIds; QString prevBatch; - RoomMessagesJob* roomMessagesJob; + QPointer roomMessagesJob; struct FileTransferPrivateInfo { -- cgit v1.2.3 From bbb207277a1f8ca1deb69116f20523fcedf05c64 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 17 Jan 2018 20:17:06 +0900 Subject: Fix a mistake reported by Clang static analyzer --- room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/room.cpp b/room.cpp index 6b7484e9..3481f684 100644 --- a/room.cpp +++ b/room.cpp @@ -110,13 +110,13 @@ class Room::Private void update(qint64 p, qint64 t) { - progress = p; total = t; if (t == 0) { t = -1; if (p == 0) p = -1; } + progress = p; total = t; } }; void failedTransfer(const QString& tid, const QString& errorMessage = {}) -- cgit v1.2.3 From 0221eb08bc7b0a58a110cc7a942159afa755aeec Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 18 Jan 2018 08:51:29 +0900 Subject: Move links pretty-printing code from Quaternion to lib This code is useful for all clients, and extensions to pretty-printing can be later added either via making prettyPrint() virtual or even by providing a registry of additional "text transformers" or even "event content renderers" applied to visualise the event. --- room.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ room.h | 5 +++++ 2 files changed, 48 insertions(+) diff --git a/room.cpp b/room.cpp index 3481f684..f8640573 100644 --- a/room.cpp +++ b/room.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -490,6 +491,48 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const #endif } +static const auto RegExpOptions = + QRegularExpression::CaseInsensitiveOption + | QRegularExpression::OptimizeOnFirstUsageOption + | QRegularExpression::UseUnicodePropertiesOption; + +// regexp is originally taken from Konsole (https://github.com/KDE/konsole) +// full url: +// protocolname:// or www. followed by anything other than whitespaces, +// <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, ), :, +// comma or dot +// Note: outer parentheses are a part of C++ raw string delimiters, not of +// the regex (see http://en.cppreference.com/w/cpp/language/string_literal). +static const QRegularExpression FullUrlRegExp(QStringLiteral( + R"(((www\.(?!\.)|[a-z][a-z0-9+.-]*://)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))" + ), RegExpOptions); +// email address: +// [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] +static const QRegularExpression EmailAddressRegExp(QStringLiteral( + R"((mailto:)?(\b(\w|\.|-)+@(\w|\.|-)+\.\w+\b))" + ), RegExpOptions); + +/** Converts all that looks like a URL into HTML links */ +static void linkifyUrls(QString& htmlEscapedText) +{ + // NOTE: htmlEscapedText is already HTML-escaped (no literal <,>,&)! + + htmlEscapedText.replace(EmailAddressRegExp, + QStringLiteral(R"(\1\2)")); + htmlEscapedText.replace(FullUrlRegExp, + QStringLiteral(R"(\1")")); +} + +QString Room::prettyPrint(const QString& plainText) const +{ + auto pt = QStringLiteral("") + + plainText.toHtmlEscaped() + QStringLiteral(""); + pt.replace('\n', "
"); + + linkifyUrls(pt); + return pt; +} + QList< User* > Room::usersTyping() const { return d->usersTyping; diff --git a/room.h b/room.h index f5bf0839..ac540fea 100644 --- a/room.h +++ b/room.h @@ -192,6 +192,11 @@ namespace QMatrixClient Q_INVOKABLE FileTransferInfo fileTransferInfo(const QString& id) const; + /** Pretty-prints plain text into HTML + * This includes HTML escaping of <,>,",& and URLs linkification. + */ + QString prettyPrint(const QString& plainText) const; + MemberSorter memberSorter() const; QJsonObject toJson() const; -- cgit v1.2.3 From ac944a40f12c4aff06af8c01fa36c63643159919 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 18 Jan 2018 14:20:00 +0900 Subject: Room: add properties for shown (room/event), message and members count Closes #151. --- room.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++++----- room.h | 24 +++++++++++++++++++++--- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/room.cpp b/room.cpp index f8640573..65761856 100644 --- a/room.cpp +++ b/room.cpp @@ -63,8 +63,7 @@ class Room::Private Private(Connection* c, QString id_, JoinState initialJoinState) : q(nullptr), connection(c), id(std::move(id_)) - , avatar(c), joinState(initialJoinState), unreadMessages(false) - , highlightCount(0), notificationCount(0) + , avatar(c), joinState(initialJoinState) { } Room* q; @@ -85,12 +84,14 @@ class Room::Private QString topic; Avatar avatar; JoinState joinState; - bool unreadMessages; - int highlightCount; - int notificationCount; + int highlightCount = 0; + int notificationCount = 0; members_map_t membersMap; QList usersTyping; QList membersLeft; + bool unreadMessages = false; + bool displayed = false; + QString lastDisplayedEventId; QHash lastReadEventIds; QString prevBatch; QPointer roomMessagesJob; @@ -214,6 +215,9 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) // See "Accessing the Public Class" section in // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ d->q = this; + connect(this, &Room::userAdded, this, &Room::memberListChanged); + connect(this, &Room::userRemoved, this, &Room::memberListChanged); + connect(this, &Room::memberRenamed, this, &Room::memberListChanged); qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id; } @@ -413,6 +417,45 @@ Room::rev_iter_t Room::findInTimeline(const QString& evtId) const return timelineEdge(); } +bool Room::displayed() const +{ + return d->displayed; +} + +void Room::setDisplayed(bool displayed) +{ + if (d->displayed == displayed) + return; + + d->displayed = displayed; + emit displayedChanged(displayed); + if( displayed ) + { + resetHighlightCount(); + resetNotificationCount(); + } +} + +QString Room::lastDisplayedEventId() const +{ + return d->lastDisplayedEventId; +} + +void Room::setLastDisplayedEventId(const QString& eventId) +{ + if (d->lastDisplayedEventId == eventId) + return; + + d->lastDisplayedEventId = eventId; + emit lastDisplayedEventIdChanged(); +} + +void Room::setLastDisplayedEvent(TimelineItem::index_t index) +{ + Q_ASSERT(isValidIndex(index)); + setLastDisplayedEventId(findInTimeline(index)->event()->id()); +} + Room::rev_iter_t Room::readMarker(const User* user) const { Q_ASSERT(user); diff --git a/room.h b/room.h index ac540fea..33514ac9 100644 --- a/room.h +++ b/room.h @@ -105,6 +105,13 @@ namespace QMatrixClient Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY namesChanged) Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) + Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages) + Q_PROPERTY(QStringList memberNames READ memberNames NOTIFY memberListChanged) + Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged) + + Q_PROPERTY(bool displayed READ displayed WRITE setDisplayed NOTIFY displayedChanged) + Q_PROPERTY(QString lastDisplayedEventId READ lastDisplayedEventId WRITE setLastDisplayedEventId NOTIFY lastDisplayedEventIdChanged) + Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE markMessagesAsRead NOTIFY readMarkerMoved) public: using Timeline = std::deque; @@ -127,9 +134,9 @@ namespace QMatrixClient QList membersLeft() const; Q_INVOKABLE QList users() const; - Q_INVOKABLE QStringList memberNames() const; - Q_INVOKABLE int memberCount() const; - Q_INVOKABLE int timelineSize() const; + QStringList memberNames() const; + int memberCount() const; + int timelineSize() const; /** * Returns a square room avatar with the given size and requests it @@ -170,6 +177,11 @@ namespace QMatrixClient rev_iter_t findInTimeline(TimelineItem::index_t index) const; rev_iter_t findInTimeline(const QString& evtId) const; + bool displayed() const; + void setDisplayed(bool displayed = true); + QString lastDisplayedEventId() const; + void setLastDisplayedEventId(const QString& eventId); + void setLastDisplayedEvent(TimelineItem::index_t index); rev_iter_t readMarker(const User* user) const; rev_iter_t readMarker() const; QString readMarkerEventId() const; @@ -250,10 +262,16 @@ namespace QMatrixClient void userAdded(User* user); void userRemoved(User* user); void memberRenamed(User* user); + void memberListChanged(); + void joinStateChanged(JoinState oldState, JoinState newState); void typingChanged(); + void highlightCountChanged(Room* room); void notificationCountChanged(Room* room); + + void displayedChanged(bool displayed); + void lastDisplayedEventIdChanged(); void lastReadEventChanged(User* user); void readMarkerMoved(); void unreadMessagesChanged(Room* room); -- cgit v1.2.3 From 959b2d023d37713cefaee049b09e09507107e4b0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 18 Jan 2018 17:37:43 +0900 Subject: Typo fix --- room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/room.cpp b/room.cpp index 65761856..68762a8a 100644 --- a/room.cpp +++ b/room.cpp @@ -563,7 +563,7 @@ static void linkifyUrls(QString& htmlEscapedText) htmlEscapedText.replace(EmailAddressRegExp, QStringLiteral(R"(\1\2)")); htmlEscapedText.replace(FullUrlRegExp, - QStringLiteral(R"(\1")")); + QStringLiteral(R"(\1)")); } QString Room::prettyPrint(const QString& plainText) const -- cgit v1.2.3 From 72ff8cf1e77e859de08e343df964f496dc2f9dd8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 20 Jan 2018 14:50:33 +0900 Subject: RoomMessageEvent::hasTextContent() Similar to hasFileContent(), allows to ascertain that an event has TextContent without checking against all possible msgtypes. --- events/roommessageevent.cpp | 7 +++++++ events/roommessageevent.h | 1 + 2 files changed, 8 insertions(+) diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index 3c5d10ad..8c088f21 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -116,6 +116,13 @@ QMimeType RoomMessageEvent::mimeType() const QMimeDatabase().mimeTypeForName("text/plain"); } +bool RoomMessageEvent::hasTextContent() const +{ + return content() && + (msgtype() == MsgType::Text || msgtype() == MsgType::Emote || + msgtype() == MsgType::Notice); // FIXME: Unbind from specific msgtypes +} + bool RoomMessageEvent::hasFileContent() const { return content() && content()->fileInfo(); diff --git a/events/roommessageevent.h b/events/roommessageevent.h index 867d8880..2a5eeb7e 100644 --- a/events/roommessageevent.h +++ b/events/roommessageevent.h @@ -59,6 +59,7 @@ namespace QMatrixClient EventContent::TypedBase* content() const { return _content.data(); } QMimeType mimeType() const; + bool hasTextContent() const; bool hasFileContent() const; QJsonObject toJson() const; -- cgit v1.2.3 From 3bfb1d9736e05fe216c016ae2dbb8eac4ac78048 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 20 Jan 2018 14:52:58 +0900 Subject: Room: store the first displayed event as well; Room::*DisplayedMarker() methods --- room.cpp | 33 ++++++++++++++++++++++++++++++++- room.h | 12 ++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/room.cpp b/room.cpp index 68762a8a..8c6465e2 100644 --- a/room.cpp +++ b/room.cpp @@ -91,6 +91,7 @@ class Room::Private QList membersLeft; bool unreadMessages = false; bool displayed = false; + QString firstDisplayedEventId; QString lastDisplayedEventId; QHash lastReadEventIds; QString prevBatch; @@ -436,18 +437,48 @@ void Room::setDisplayed(bool displayed) } } +QString Room::firstDisplayedEventId() const +{ + return d->firstDisplayedEventId; +} + +Room::rev_iter_t Room::firstDisplayedMarker() const +{ + return findInTimeline(firstDisplayedEventId()); +} + +void Room::setFirstDisplayedEventId(const QString& eventId) +{ + if (d->firstDisplayedEventId == eventId) + return; + + d->firstDisplayedEventId = eventId; + emit firstDisplayedEventChanged(); +} + +void Room::setFirstDisplayedEvent(TimelineItem::index_t index) +{ + Q_ASSERT(isValidIndex(index)); + setFirstDisplayedEventId(findInTimeline(index)->event()->id()); +} + QString Room::lastDisplayedEventId() const { return d->lastDisplayedEventId; } +Room::rev_iter_t Room::lastDisplayedMarker() const +{ + return findInTimeline(lastDisplayedEventId()); +} + void Room::setLastDisplayedEventId(const QString& eventId) { if (d->lastDisplayedEventId == eventId) return; d->lastDisplayedEventId = eventId; - emit lastDisplayedEventIdChanged(); + emit lastDisplayedEventChanged(); } void Room::setLastDisplayedEvent(TimelineItem::index_t index) diff --git a/room.h b/room.h index 33514ac9..2284e8b1 100644 --- a/room.h +++ b/room.h @@ -110,7 +110,8 @@ namespace QMatrixClient Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged) Q_PROPERTY(bool displayed READ displayed WRITE setDisplayed NOTIFY displayedChanged) - Q_PROPERTY(QString lastDisplayedEventId READ lastDisplayedEventId WRITE setLastDisplayedEventId NOTIFY lastDisplayedEventIdChanged) + Q_PROPERTY(QString firstDisplayedEventId READ firstDisplayedEventId WRITE setFirstDisplayedEventId NOTIFY firstDisplayedEventChanged) + Q_PROPERTY(QString lastDisplayedEventId READ lastDisplayedEventId WRITE setLastDisplayedEventId NOTIFY lastDisplayedEventChanged) Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE markMessagesAsRead NOTIFY readMarkerMoved) public: @@ -179,9 +180,15 @@ namespace QMatrixClient bool displayed() const; void setDisplayed(bool displayed = true); + QString firstDisplayedEventId() const; + rev_iter_t firstDisplayedMarker() const; + void setFirstDisplayedEventId(const QString& eventId); + void setFirstDisplayedEvent(TimelineItem::index_t index); QString lastDisplayedEventId() const; + rev_iter_t lastDisplayedMarker() const; void setLastDisplayedEventId(const QString& eventId); void setLastDisplayedEvent(TimelineItem::index_t index); + rev_iter_t readMarker(const User* user) const; rev_iter_t readMarker() const; QString readMarkerEventId() const; @@ -271,7 +278,8 @@ namespace QMatrixClient void notificationCountChanged(Room* room); void displayedChanged(bool displayed); - void lastDisplayedEventIdChanged(); + void firstDisplayedEventChanged(); + void lastDisplayedEventChanged(); void lastReadEventChanged(User* user); void readMarkerMoved(); void unreadMessagesChanged(Room* room); -- cgit v1.2.3 From 766f0cc7743724d830df30eca6899cad20ba1dc8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 22 Jan 2018 10:29:48 +0900 Subject: Room::setLastReadEvent: don't update if the event is the same --- room.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/room.cpp b/room.cpp index 8c6465e2..d27bd5a3 100644 --- a/room.cpp +++ b/room.cpp @@ -302,7 +302,10 @@ void Room::setJoinState(JoinState state) void Room::Private::setLastReadEvent(User* u, const QString& eventId) { - lastReadEventIds.insert(u, eventId); + auto& storedId = lastReadEventIds[u]; + if (storedId == eventId) + return; + storedId = eventId; emit q->lastReadEventChanged(u); if (isLocalUser(u)) emit q->readMarkerMoved(); @@ -1151,7 +1154,7 @@ void Room::Private::checkUnreadMessages(timeline_iter_t from) const auto newUnreadMessages = count_if(from, timeline.cend(), std::bind(&Room::Private::isEventNotable, this, _1)); - // The first event in the just-added batch (referred to by upTo.base()) + // The first event in the just-added batch (referred to by `from`) // defines whose read marker 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, -- cgit v1.2.3 From b8db2e9bf3ae6479157e4419fe27600cccd443ed Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 22 Jan 2018 10:30:21 +0900 Subject: splitMediaId: better assertion failure message --- connection.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/connection.cpp b/connection.cpp index 2bff6179..b6ede0af 100644 --- a/connection.cpp +++ b/connection.cpp @@ -303,7 +303,8 @@ inline auto splitMediaId(const QString& mediaId) { auto idParts = mediaId.split('/'); Q_ASSERT_X(idParts.size() == 2, __FUNCTION__, - "mediaId should have a form 'serverName/localMediaId' (without apostrophes)"); + ("mediaId:" + mediaId + + "doesn't look like \"serverName/localMediaId\"").toLatin1()); return idParts; } -- cgit v1.2.3 From 0ecfd3897cc3264c21f80a121b4b6774ac2d67f7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 22 Jan 2018 11:49:09 +0900 Subject: Room: emit addedMessages() before possible read marker auto-promotion Read marker auto-promotion may be bound to a dataChanged() signal in the client model, while a routine connection for addedMessages() is endInsertRows(). Emitting endInsertRows() after dataChanged() over the same rows has unpredictable consequences for representation of data in those rows - hence the fix. --- room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/room.cpp b/room.cpp index d27bd5a3..ab7f7a8f 100644 --- a/room.cpp +++ b/room.cpp @@ -1141,8 +1141,8 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events) processRedaction(move(r)); if (insertedSize > 0) { - checkUnreadMessages(timeline.cend() - insertedSize); emit q->addedMessages(); + checkUnreadMessages(timeline.cend() - insertedSize); } Q_ASSERT(timeline.size() == timelineSize + insertedSize); -- cgit v1.2.3 From b9b2d97e83ac14798c220d6f6dc4ead1809a9cf7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 24 Jan 2018 11:22:52 +0900 Subject: splitMediaId: fix typos in the assertion failure message --- connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connection.cpp b/connection.cpp index b6ede0af..1257f251 100644 --- a/connection.cpp +++ b/connection.cpp @@ -303,8 +303,8 @@ inline auto splitMediaId(const QString& mediaId) { auto idParts = mediaId.split('/'); Q_ASSERT_X(idParts.size() == 2, __FUNCTION__, - ("mediaId:" + mediaId + - "doesn't look like \"serverName/localMediaId\"").toLatin1()); + ("'" + mediaId + + "' doesn't look like 'serverName/localMediaId'").toLatin1()); return idParts; } -- cgit v1.2.3 From 36ca96ed4ef0e4f63e6925ea8d6e8b7925473fea Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 24 Jan 2018 16:40:04 +0900 Subject: Avatar: Only allow mxc:// links for avatars Otherwise an attempt to use a possibly insecure link goes to Connection::getThumbnail(), leading to an assertion failure in splitMediaId(). See also the discussion in QMatrixClient/Quaternion#265. --- avatar.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/avatar.cpp b/avatar.cpp index a13507fb..d3e9cd29 100644 --- a/avatar.cpp +++ b/avatar.cpp @@ -113,6 +113,13 @@ bool Avatar::updateUrl(const QUrl& newUrl) if (newUrl == d->_url) return false; + // FIXME: Make it a library-wide constant and maybe even make the URL checker + // a Connection(?) method. + if (newUrl.scheme() != "mxc://" || newUrl.path().count('/') != 2) + { + qCWarning(MAIN) << "Malformed avatar URL:" << newUrl.toDisplayString(); + return false; + } d->_url = newUrl; d->_valid = false; return true; -- cgit v1.2.3 From 68d7a22c25014f09c0346cf1a2501b4fd1fd882a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 24 Jan 2018 21:22:12 +0900 Subject: Typo fixes for the previous commit Requires deleting the cache :( --- avatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avatar.cpp b/avatar.cpp index d3e9cd29..44cad4e5 100644 --- a/avatar.cpp +++ b/avatar.cpp @@ -115,7 +115,7 @@ bool Avatar::updateUrl(const QUrl& newUrl) // FIXME: Make it a library-wide constant and maybe even make the URL checker // a Connection(?) method. - if (newUrl.scheme() != "mxc://" || newUrl.path().count('/') != 2) + if (newUrl.scheme() != "mxc" || newUrl.path().count('/') != 1) { qCWarning(MAIN) << "Malformed avatar URL:" << newUrl.toDisplayString(); return false; -- cgit v1.2.3 From b4e699a0076fc7827fa89af33d8774d43eb017ce Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 24 Jan 2018 23:17:23 +0900 Subject: Fix dangling references --- connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connection.cpp b/connection.cpp index 1257f251..f498f613 100644 --- a/connection.cpp +++ b/connection.cpp @@ -150,7 +150,7 @@ void Connection::connectToServer(const QString& user, const QString& password, const QString& deviceId) { checkAndConnect(user, - [&] { + [=] { doConnectToServer(user, password, initialDeviceName, deviceId); }); } @@ -177,7 +177,7 @@ void Connection::connectWithToken(const QString& userId, const QString& deviceId) { checkAndConnect(userId, - [&] { d->connectWithToken(userId, accessToken, deviceId); }); + [=] { d->connectWithToken(userId, accessToken, deviceId); }); } void Connection::Private::connectWithToken(const QString& user, -- cgit v1.2.3 From 52aadc68d73d7e590ab7ec3f8b4382483a50bcf1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 11:10:52 +0900 Subject: Room::Private::toJson: don't save zero numbers --- room.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/room.cpp b/room.cpp index ab7f7a8f..e3e0fb47 100644 --- a/room.cpp +++ b/room.cpp @@ -1499,9 +1499,12 @@ QJsonObject Room::Private::toJson() const } QJsonObject unreadNotificationsObj; - unreadNotificationsObj.insert("highlight_count", highlightCount); - unreadNotificationsObj.insert("notification_count", notificationCount); - result.insert("unread_notifications", unreadNotificationsObj); + if (highlightCount > 0) + unreadNotificationsObj.insert("highlight_count", highlightCount); + if (notificationCount > 0) + unreadNotificationsObj.insert("notification_count", notificationCount); + if (!unreadNotificationsObj.isEmpty()) + result.insert("unread_notifications", unreadNotificationsObj); return result; } -- cgit v1.2.3 From 84e183a70b831d1e18c373099988420f5050254b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 11:11:50 +0900 Subject: BaseJob::checkReply: log job returned status more explicitly --- jobs/basejob.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index 720ac560..2d0d8b1b 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -291,9 +291,10 @@ bool checkContentType(const QByteArray& type, const QByteArrayList& patterns) BaseJob::Status BaseJob::checkReply(QNetworkReply* reply) const { - qCDebug(d->logCat) << this << "returned from" << reply->url().toDisplayString(); - if (reply->error() != QNetworkReply::NoError) - qCDebug(d->logCat) << this << "returned" << reply->error(); + qCDebug(d->logCat) << this << "returned" + << (reply->error() == QNetworkReply::NoError ? + "Success" : reply->errorString()) + << "from" << reply->url().toDisplayString(); switch( reply->error() ) { case QNetworkReply::NoError: -- cgit v1.2.3 From 6ab89ce02e450605cebd432d2113a3115d0cf8c1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 13:21:44 +0900 Subject: Connection::forgetRoom: Support forgetting invites too Closes #157. --- connection.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/connection.cpp b/connection.cpp index f498f613..e182f9d9 100644 --- a/connection.cpp +++ b/connection.cpp @@ -377,17 +377,19 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) // a ForgetRoomJob is created in advance and can be returned in a probably // not-yet-started state (it will start once /leave completes). auto forgetJob = new ForgetRoomJob(id); - auto joinedRoom = d->roomMap.value({id, false}); - if (joinedRoom && joinedRoom->joinState() == JoinState::Join) + auto room = d->roomMap.value({id, false}); + if (!room) + room = d->roomMap.value({id, true}); + if (room && room->joinState() != JoinState::Leave) { - auto leaveJob = joinedRoom->leaveRoom(); + auto leaveJob = room->leaveRoom(); connect(leaveJob, &BaseJob::success, this, [this, forgetJob] { forgetJob->start(connectionData()); }); connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); } else forgetJob->start(connectionData()); - connect(forgetJob, &BaseJob::success, this, [this, &id] + connect(forgetJob, &BaseJob::success, this, [this, id] { // If the room happens to be in the map (possible in both forms), // delete the found object(s). -- cgit v1.2.3 From d1663570003b20be58d4ce87e4b1d26bdf0029a8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 12:26:19 +0900 Subject: Room::toJson: fix invite state caching Causes a cache version upgrade; the old cache stores the state in an incorrect place that won't be supported. Closes #159. --- connection.cpp | 2 +- room.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/connection.cpp b/connection.cpp index f498f613..efe47f86 100644 --- a/connection.cpp +++ b/connection.cpp @@ -551,7 +551,7 @@ void Connection::setHomeserver(const QUrl& url) emit homeserverChanged(homeserver()); } -static constexpr int CACHE_VERSION_MAJOR = 1; +static constexpr int CACHE_VERSION_MAJOR = 2; static constexpr int CACHE_VERSION_MINOR = 0; void Connection::saveState(const QUrl &toFile) const diff --git a/room.cpp b/room.cpp index e3e0fb47..71b7b228 100644 --- a/room.cpp +++ b/room.cpp @@ -1467,7 +1467,9 @@ QJsonObject Room::Private::toJson() const QJsonObject roomStateObj; roomStateObj.insert("events", stateEvents); - result.insert("state", roomStateObj); + result.insert( + joinState == JoinState::Invite ? "invite_state" : "state", + roomStateObj); } if (!q->readMarkerEventId().isEmpty()) -- cgit v1.2.3 From 80f75c9b5c6642e8c02e626f991e2ed620a2dc4f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 14:05:28 +0900 Subject: Connection::provideRoom: assert no empty ids Bonus: slightly more accurate comment in Connection::forgetRoom --- connection.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/connection.cpp b/connection.cpp index e182f9d9..8cdaa3dd 100644 --- a/connection.cpp +++ b/connection.cpp @@ -59,6 +59,7 @@ class Connection::Private // separately so we should, e.g., keep objects for Invite and // Leave state of the same room. QHash, Room*> roomMap; + QVector justForgottenRoomIds; QHash userMap; QString userId; @@ -383,16 +384,17 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) if (room && room->joinState() != JoinState::Leave) { auto leaveJob = room->leaveRoom(); - connect(leaveJob, &BaseJob::success, - this, [this, forgetJob] { forgetJob->start(connectionData()); }); + connect(leaveJob, &BaseJob::success, this, [this, forgetJob, id] { + forgetJob->start(connectionData()); + d->justForgottenRoomIds.push_back(id); + }); connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); } else forgetJob->start(connectionData()); connect(forgetJob, &BaseJob::success, this, [this, id] { - // If the room happens to be in the map (possible in both forms), - // delete the found object(s). + // If the room is in the map (possibly in both forms), delete all forms. for (auto f: {false, true}) if (auto r = d->roomMap.take({ id, f })) { @@ -479,11 +481,7 @@ const ConnectionData* Connection::connectionData() const Room* Connection::provideRoom(const QString& id, JoinState joinState) { // TODO: This whole function is a strong case for a RoomManager class. - if (id.isEmpty()) - { - qCDebug(MAIN) << "Connection::provideRoom() with empty id, doing nothing"; - return nullptr; - } + Q_ASSERT_X(!id.isEmpty(), __FUNCTION__, "Empty room id"); const auto roomKey = qMakePair(id, joinState == JoinState::Invite); auto* room = d->roomMap.value(roomKey, nullptr); -- cgit v1.2.3 From d11b0af7b4a6c572409c0ac979c99a02bfb14761 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 14:06:28 +0900 Subject: Connection: intercept late-coming /sync response on a just forgotten room Closes #160. --- connection.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/connection.cpp b/connection.cpp index 8cdaa3dd..1e7d1edf 100644 --- a/connection.cpp +++ b/connection.cpp @@ -59,7 +59,7 @@ class Connection::Private // separately so we should, e.g., keep objects for Invite and // Leave state of the same room. QHash, Room*> roomMap; - QVector justForgottenRoomIds; + QVector roomIdsToForget; QHash userMap; QString userId; @@ -257,6 +257,21 @@ void Connection::onSyncSuccess(SyncData &&data) { d->data->setLastEvent(data.nextBatch()); for( auto&& roomData: data.takeRoomData() ) { + const auto forgetIdx = d->roomIdsToForget.indexOf(roomData.roomId); + if (forgetIdx != -1) + { + d->roomIdsToForget.removeAt(forgetIdx); + if (roomData.joinState == JoinState::Leave) + { + qDebug(MAIN) << "Room" << roomData.roomId + << "has been forgotten, ignoring /sync response for it"; + continue; + } + qWarning(MAIN) << "Room" << roomData.roomId + << "has just been forgotten but /sync returned it in" + << toCString(roomData.joinState) + << "state - suspiciously fast turnaround"; + } if ( auto* r = provideRoom(roomData.roomId, roomData.joinState) ) r->updateData(std::move(roomData)); } @@ -384,9 +399,12 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) if (room && room->joinState() != JoinState::Leave) { auto leaveJob = room->leaveRoom(); - connect(leaveJob, &BaseJob::success, this, [this, forgetJob, id] { + connect(leaveJob, &BaseJob::success, this, [this, forgetJob, room] { forgetJob->start(connectionData()); - d->justForgottenRoomIds.push_back(id); + // If the matching /sync response hasn't arrived yet, mark the room + // for explicit deletion + if (room->joinState() != JoinState::Leave) + d->roomIdsToForget.push_back(room->id()); }); connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); } -- cgit v1.2.3 From cb54a2a5f9e83a5076eb501e60e88846a4aa28df Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 09:43:11 +0900 Subject: Expose avatar URLs of Room and User as Q_PROPERTY This is needed for QML integration. Closes #155. --- room.cpp | 5 +++++ room.h | 4 ++++ user.h | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/room.cpp b/room.cpp index 71b7b228..1c9063c9 100644 --- a/room.cpp +++ b/room.cpp @@ -262,6 +262,11 @@ QString Room::topic() const return d->topic; } +QUrl Room::avatarUrl() const +{ + return d->avatar.url(); +} + QImage Room::avatar(int dimension) { return avatar(dimension, dimension); diff --git a/room.h b/room.h index 2284e8b1..5b9a1785 100644 --- a/room.h +++ b/room.h @@ -105,6 +105,7 @@ namespace QMatrixClient Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY namesChanged) Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) + Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages) Q_PROPERTY(QStringList memberNames READ memberNames NOTIFY memberListChanged) Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged) @@ -122,6 +123,8 @@ namespace QMatrixClient Room(Connection* connection, QString id, JoinState initialJoinState); ~Room() override; + // Property accessors + Connection* connection() const; User* localUser() const; const QString& id() const; @@ -130,6 +133,7 @@ namespace QMatrixClient QString canonicalAlias() const; QString displayName() const; QString topic() const; + QUrl avatarUrl() const; Q_INVOKABLE JoinState joinState() const; Q_INVOKABLE QList usersTyping() const; QList membersLeft() const; diff --git a/user.h b/user.h index 91dfdc09..3df188a1 100644 --- a/user.h +++ b/user.h @@ -33,6 +33,7 @@ namespace QMatrixClient Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString displayName READ displayname NOTIFY nameChanged STORED false) Q_PROPERTY(QString bridgeName READ bridged NOTIFY nameChanged STORED false) + Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) public: User(QString userId, Connection* connection); ~User() override; @@ -61,7 +62,7 @@ namespace QMatrixClient Q_INVOKABLE QImage avatar(int dimension); Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight); - Q_INVOKABLE QUrl avatarUrl() const; + QUrl avatarUrl() const; void processEvent(Event* event); -- cgit v1.2.3 From 8590054a675bc5d2b07fff1acbb084d67c068113 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 19:42:13 +0900 Subject: StateEvent<>: introduce Prev structure and prevSenderId() accessor Also switch prev_content() from accidental snake case to camel case (old name still provided for compatibility). --- events/event.h | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/events/event.h b/events/event.h index 6ed5ba49..b5a4d94e 100644 --- a/events/event.h +++ b/events/event.h @@ -255,6 +255,21 @@ namespace QMatrixClient virtual bool repeatsState() const; }; + template + struct Prev + { + template + explicit Prev(const QJsonObject& unsignedJson, + ContentParamTs&&... contentParams) + : senderId(unsignedJson.value("prev_sender").toString()) + , content(unsignedJson.value("prev_content").toObject(), + std::forward(contentParams)...) + { } + + QString senderId; + ContentT content; + }; + template class StateEvent: public StateEventBase { @@ -270,9 +285,8 @@ namespace QMatrixClient { auto unsignedData = obj.value("unsigned").toObject(); if (unsignedData.contains("prev_content")) - _prev.reset(new ContentT( - unsignedData.value("prev_content").toObject(), - std::forward(contentParams)...)); + _prev = std::make_unique>(unsignedData, + std::forward(contentParams)...); } template explicit StateEvent(Type type, ContentParamTs&&... contentParams) @@ -283,11 +297,15 @@ namespace QMatrixClient QJsonObject toJson() const { return _content.toJson(); } ContentT content() const { return _content; } - ContentT* prev_content() const { return _prev.data(); } + /** @deprecated Use prevContent instead */ + ContentT* prev_content() const { return prevContent(); } + ContentT* prevContent() const + { return _prev ? &_prev->content : nullptr; } + QString prevSenderId() const { return _prev ? _prev->senderId : ""; } protected: ContentT _content; - QScopedPointer _prev; + std::unique_ptr> _prev; }; } // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::Event*) -- cgit v1.2.3 From 3a913f33853c675a1051460bc36278be20a4c941 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 19:45:08 +0900 Subject: Room, User: const-tighten up the code, set QObject names To make debugging (including QML debugging) more convenient. --- room.cpp | 4 +++- room.h | 3 +-- user.cpp | 7 +++++-- user.h | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/room.cpp b/room.cpp index 1c9063c9..d79363a3 100644 --- a/room.cpp +++ b/room.cpp @@ -213,6 +213,7 @@ RoomEventPtr TimelineItem::replaceEvent(RoomEventPtr&& other) Room::Room(Connection* connection, QString id, JoinState initialJoinState) : QObject(connection), d(new Private(connection, id, initialJoinState)) { + setObjectName(id); // See "Accessing the Public Class" section in // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ d->q = this; @@ -757,7 +758,7 @@ void Room::Private::removeMember(User* u) } } -QString Room::roomMembername(User *u) const +QString Room::roomMembername(const User* u) const { // See the CS spec, section 11.2.2.3 @@ -1239,6 +1240,7 @@ void Room::processStateEvents(const RoomEvents& events) case EventType::RoomCanonicalAlias: { auto aliasEvent = static_cast(event); d->canonicalAlias = aliasEvent->alias(); + setObjectName(d->canonicalAlias); qCDebug(MAIN) << "Room canonical alias updated:" << d->canonicalAlias; emitNamesChanged = true; break; diff --git a/room.h b/room.h index 5b9a1785..d2962d95 100644 --- a/room.h +++ b/room.h @@ -40,7 +40,6 @@ namespace QMatrixClient class MemberSorter; class LeaveRoomJob; class RedactEventJob; - class Room; class TimelineItem { @@ -162,7 +161,7 @@ namespace QMatrixClient * @brief Produces a disambiguated name for a given user in * the context of the room */ - Q_INVOKABLE QString roomMembername(User* u) const; + Q_INVOKABLE QString roomMembername(const User* u) const; /** * @brief Produces a disambiguated name for a user with this id in * the context of the room diff --git a/user.cpp b/user.cpp index baa7bc45..8b662acb 100644 --- a/user.cpp +++ b/user.cpp @@ -51,7 +51,9 @@ class User::Private User::User(QString userId, Connection* connection) : QObject(connection), d(new Private(std::move(userId), connection)) -{ } +{ + setObjectName(userId); +} User::~User() { @@ -74,6 +76,7 @@ void User::updateName(const QString& newName) if (oldName != newName) { d->name = newName; + setObjectName(displayname()); emit nameChanged(newName, oldName); } } @@ -127,7 +130,7 @@ QString User::bridged() const { return d->bridged; } -const Avatar& User::avatarObject() +const Avatar& User::avatarObject() const { return d->avatar; } diff --git a/user.h b/user.h index 3df188a1..4f48f20c 100644 --- a/user.h +++ b/user.h @@ -58,7 +58,7 @@ namespace QMatrixClient */ QString bridged() const; - const Avatar& avatarObject(); + const Avatar& avatarObject() const; Q_INVOKABLE QImage avatar(int dimension); Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight); -- cgit v1.2.3 From 80f7e44e1a9056fc55147718dd2812eb93925ec1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 25 Jan 2018 19:48:08 +0900 Subject: Room, User: expose avatarMediaId(); declare User for the metatype system To make it easy to use User objects and fetch room and user avatars from QML. Closes #155. --- avatar.cpp | 5 +++++ avatar.h | 1 + room.cpp | 9 +++++++-- room.h | 3 +++ user.cpp | 5 +++++ user.h | 3 +++ 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/avatar.cpp b/avatar.cpp index 44cad4e5..040bf9bb 100644 --- a/avatar.cpp +++ b/avatar.cpp @@ -62,6 +62,11 @@ QImage Avatar::get(int width, int height, notifier_t notifier) const return d->get({width, height}, notifier); } +QString Avatar::mediaId() const +{ + return d->_url.authority() + d->_url.path(); +} + QImage Avatar::Private::get(QSize size, Avatar::notifier_t notifier) const { // FIXME: Alternating between longer-width and longer-height requests diff --git a/avatar.h b/avatar.h index 28c16e4d..4d476ea5 100644 --- a/avatar.h +++ b/avatar.h @@ -38,6 +38,7 @@ namespace QMatrixClient QImage get(int dimension, notifier_t notifier) const; QImage get(int w, int h, notifier_t notifier) const; + QString mediaId() const; QUrl url() const; bool updateUrl(const QUrl& newUrl); diff --git a/room.cpp b/room.cpp index d79363a3..bc7c083e 100644 --- a/room.cpp +++ b/room.cpp @@ -263,6 +263,11 @@ QString Room::topic() const return d->topic; } +QString Room::avatarMediaId() const +{ + return d->avatar.mediaId(); +} + QUrl Room::avatarUrl() const { return d->avatar.url(); @@ -284,8 +289,8 @@ QImage Room::avatar(int width, int height) auto theOtherOneIt = d->membersMap.begin(); if (theOtherOneIt.value() == localUser()) ++theOtherOneIt; - return theOtherOneIt.value()->avatarObject() - .get(width, height, [=] { emit avatarChanged(); }); + return (*theOtherOneIt)->avatarObject() + .get(width, height, [=] { emit avatarChanged(); }); } return {}; } diff --git a/room.h b/room.h index d2962d95..b908a763 100644 --- a/room.h +++ b/room.h @@ -104,7 +104,9 @@ namespace QMatrixClient Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY namesChanged) Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) + Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) + Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages) Q_PROPERTY(QStringList memberNames READ memberNames NOTIFY memberListChanged) Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged) @@ -132,6 +134,7 @@ namespace QMatrixClient QString canonicalAlias() const; QString displayName() const; QString topic() const; + QString avatarMediaId() const; QUrl avatarUrl() const; Q_INVOKABLE JoinState joinState() const; Q_INVOKABLE QList usersTyping() const; diff --git a/user.cpp b/user.cpp index 8b662acb..b0890b61 100644 --- a/user.cpp +++ b/user.cpp @@ -145,6 +145,11 @@ QImage User::avatar(int width, int height) return d->avatar.get(width, height, [=] { emit avatarChanged(this); }); } +QString User::avatarMediaId() const +{ + return d->avatar.mediaId(); +} + QUrl User::avatarUrl() const { return d->avatar.url(); diff --git a/user.h b/user.h index 4f48f20c..8a2c53d9 100644 --- a/user.h +++ b/user.h @@ -33,6 +33,7 @@ namespace QMatrixClient Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString displayName READ displayname NOTIFY nameChanged STORED false) Q_PROPERTY(QString bridgeName READ bridged NOTIFY nameChanged STORED false) + Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) public: User(QString userId, Connection* connection); @@ -62,6 +63,7 @@ namespace QMatrixClient Q_INVOKABLE QImage avatar(int dimension); Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight); + QString avatarMediaId() const; QUrl avatarUrl() const; void processEvent(Event* event); @@ -84,3 +86,4 @@ namespace QMatrixClient Private* d; }; } +Q_DECLARE_METATYPE(QMatrixClient::User*) -- cgit v1.2.3