diff options
54 files changed, 1372 insertions, 324 deletions
diff --git a/.travis.yml b/.travis.yml index 1b67119d..9c7d8a7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,9 +23,20 @@ before_install: - eval "${ENV_EVAL}" - if [ "$TRAVIS_OS_NAME" = "linux" ]; then . /opt/qt56/bin/qt56-env.sh; fi -script: +install: +- git clone https://github.com/QMatrixClient/matrix-doc.git +- git clone --recursive https://github.com/KitsuneRal/gtad.git +- pushd gtad +- cmake -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} . +- cmake --build . +- popd + +before_script: - mkdir build && cd build -- cmake .. +- cmake -DMATRIX_DOC_PATH="matrix-doc" -DGTAD_PATH="gtad/gtad" -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} .. +- cmake --build . --target update-api + +script: - cmake --build . --target all - cd .. - qmake qmc-example.pro "QMAKE_CC = $CC" "QMAKE_CXX = $CXX" && make all diff --git a/CMakeLists.txt b/CMakeLists.txt index 463bfea7..c0ffc0b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,11 @@ if (CMAKE_BUILD_TYPE) endif(CMAKE_BUILD_TYPE) message( STATUS "Using compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}" ) message( STATUS "Using Qt ${Qt5_VERSION} at ${Qt5_Prefix}" ) +if (MATRIX_DOC_PATH AND GTAD_PATH) + message( STATUS "Generating API stubs enabled" ) + message( STATUS " Using GTAD at ${GTAD_PATH}" ) + message( STATUS " Using CS API files at ${MATRIX_DOC_PATH}/api/client-server" ) +endif () message( STATUS "=============================================================================" ) message( STATUS ) @@ -60,6 +65,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 @@ -68,8 +74,33 @@ set(libqmatrixclient_SRCS jobs/roommessagesjob.cpp jobs/syncjob.cpp jobs/mediathumbnailjob.cpp + jobs/downloadfilejob.cpp ) +set(API_DEF_PATH ${MATRIX_DOC_PATH}/api/client-server/) +file(GLOB_RECURSE API_DEFS RELATIVE ${PROJECT_SOURCE_DIR} + ${API_DEF_PATH}/*.yaml + ${API_DEF_PATH}/definitions/*.yaml + ${MATRIX_DOC_PATH}/event-schemas/schema/* +) +if (MATRIX_DOC_PATH AND GTAD_PATH) + add_custom_target(update-api + ${GTAD_PATH} --config jobs/gtad.yaml --out jobs/generated + ${MATRIX_DOC_PATH}/api/client-server + cas_login_redirect.yaml- cas_login_ticket.yaml- + old_sync.yaml- room_initial_sync.yaml- + sync.yaml- room_state.yaml- + event_context.yaml- joining.yaml- + notifications.yaml- peeking_events.yaml- + pushrules.yaml- rooms.yaml- search.yaml- + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + SOURCES jobs/gtad.yaml + jobs/{{base}}.h.mustache jobs/{{base}}.cpp.mustache + ${API_DEFS} + VERBATIM + ) +endif() + aux_source_directory(jobs/generated libqmatrixclient_job_SRCS) set(example_SRCS examples/qmc-example.cpp) diff --git a/connection.cpp b/connection.cpp index 53a38f0d..935546b1 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 <QtNetwork/QDnsLookup> #include <QtCore/QFile> @@ -187,8 +188,7 @@ void Connection::Private::connectWithToken(const QString& user, data->setToken(accessToken.toLatin1()); data->setDeviceId(deviceId); qCDebug(MAIN) << "Using server" << data->baseUrl() << "by user" - << userId - << "from device" << deviceId; + << userId << "from device" << deviceId; emit q->connected(); } @@ -298,9 +298,24 @@ RoomMessagesJob* Connection::getMessages(Room* room, const QString& from) const return callApi<RoomMessagesJob>(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<MediaThumbnailJob>(idParts.front(), idParts.back(), + requestedSize); +} + MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, QSize requestedSize) const { - return callApi<MediaThumbnailJob>(url, requestedSize); + return getThumbnail(url.authority() + url.path(), requestedSize); } MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, int requestedWidth, @@ -309,6 +324,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<UploadContentJob>(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<GetContentJob>(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<DownloadFileJob>(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 8dda2bbf..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 @@ -174,12 +177,26 @@ 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; + // 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. @@ -297,7 +314,6 @@ namespace QMatrixClient */ Room* provideRoom(const QString& roomId, JoinState joinState); - /** * Completes loading sync data. */ diff --git a/converters.h b/converters.h index 00d1d339..0d7f734e 100644 --- a/converters.h +++ b/converters.h @@ -46,11 +46,7 @@ namespace QMatrixClient inline QJsonValue toJson(const QByteArray& bytes) { -#if QT_VERSION < QT_VERSION_CHECK(5, 3, 0) - return QJsonValue(QLatin1String(bytes.constData())); -#else return QJsonValue(bytes.constData()); -#endif } template <typename T> diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index c92c2944..7fc56287 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -25,7 +25,6 @@ #include <QtNetwork/QNetworkReply> #include <QtCore/QTimer> #include <QtCore/QRegularExpression> -//#include <QtCore/QStringBuilder> #include <array> @@ -46,7 +45,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) @@ -60,10 +59,16 @@ class BaseJob::Private // Contents for the network request HttpVerb verb; QString apiEndpoint; + QHash<QByteArray, QByteArray> 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<QNetworkReply, NetworkReplyDeleter> reply; Status status = Pending; @@ -90,11 +95,16 @@ 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)) + const Query& query, Data&& data, bool needsToken) + : d(new Private(verb, endpoint, query, std::move(data), needsToken)) { setObjectName(name); + setExpectedContentTypes({ "application/json" }); d->timer.setSingleShot(true); connect (&d->timer, &QTimer::timeout, this, &BaseJob::timeout); d->retryTimer.setSingleShot(true); @@ -117,6 +127,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; @@ -132,9 +158,24 @@ const BaseJob::Data& BaseJob::requestData() const return d->requestData; } -void BaseJob::setRequestData(const BaseJob::Data& data) +void BaseJob::setRequestData(Data&& data) +{ + std::swap(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->requestData = data; + d->expectedContentTypes = contentTypes; } void BaseJob::Private::sendRequest() @@ -148,23 +189,26 @@ 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: 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) ); @@ -172,15 +216,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() @@ -194,6 +244,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(); @@ -206,11 +260,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 +295,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 @@ -358,6 +440,7 @@ void BaseJob::setStatus(int code, QString message) void BaseJob::abandon() { + beforeAbandon(d->reply.data()); this->disconnect(); if (d->reply) d->reply->disconnect(this); @@ -374,4 +457,3 @@ void BaseJob::setLoggingCategory(LoggingCategory lcf) { d->logCat = lcf; } - diff --git a/jobs/basejob.h b/jobs/basejob.h index 5fcbbc97..e9e108c6 100644 --- a/jobs/basejob.h +++ b/jobs/basejob.h @@ -18,14 +18,15 @@ #pragma once -#include "logging.h" +#include "../logging.h" +#include "requestdata.h" #include <QtCore/QObject> +#include <QtCore/QUrlQuery> + +// Any job that parses the response will need the below two. #include <QtCore/QJsonDocument> #include <QtCore/QJsonObject> -#include <QtCore/QJsonArray> -#include <QtCore/QUrlQuery> -#include <QtCore/QScopedPointer> class QNetworkReply; class QSslError; @@ -59,6 +60,7 @@ namespace QMatrixClient , ContentAccessError , NotFoundError , IncorrectRequestError + , IncorrectResponseError , UserDefinedError = 200 }; @@ -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 @@ -130,7 +107,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, Data&& data = {}, bool needsToken = true); Status status() const; @@ -213,15 +192,30 @@ namespace QMatrixClient */ void failure(BaseJob*); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + protected: + using headers_t = QHash<QByteArray, QByteArray>; + 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); + void setRequestData(Data&& data); + const QByteArrayList& expectedContentTypes() const; + void addExpectedContentType(const QByteArray& contentType); + 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 @@ -240,11 +234,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 +259,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/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 <QtNetwork/QNetworkReply> +#include <QtCore/QFile> +#include <QtCore/QTemporaryFile> + +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<QFile> targetFile; + QScopedPointer<QFile> 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<qint64>(); + 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<Private> d; + + void beforeStart(const ConnectionData*) override; + void afterStart(const ConnectionData*, + QNetworkReply* reply) override; + void beforeAbandon(QNetworkReply*) override; + Status parseReply(QNetworkReply*) override; + }; +} diff --git a/jobs/generated/administrative_contact.cpp b/jobs/generated/administrative_contact.cpp index 705c5d54..479bee52 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 <QtCore/QStringBuilder> using namespace QMatrixClient; @@ -47,16 +44,13 @@ class GetAccount3PIDsJob::Private GetAccount3PIDsJob::GetAccount3PIDsJob() : BaseJob(HttpVerb::Get, "GetAccount3PIDsJob", - basePath % "/account/3pid", - Query { } - ), d(new Private) -{ } - -GetAccount3PIDsJob::~GetAccount3PIDsJob() + basePath % "/account/3pid") + , d(new Private) { - delete d; } +GetAccount3PIDsJob::~GetAccount3PIDsJob() = default; + const QVector<GetAccount3PIDsJob::ThirdPartyIdentifier>& GetAccount3PIDsJob::threepids() const { return d->threepids; @@ -100,9 +94,7 @@ 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)); @@ -112,8 +104,7 @@ Post3PIDsJob::Post3PIDsJob(const ThreePidCredentials& threePidCreds, bool bind) 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/administrative_contact.h b/jobs/generated/administrative_contact.h index fa6beba9..67563719 100644 --- a/jobs/generated/administrative_contact.h +++ b/jobs/generated/administrative_contact.h @@ -2,7 +2,6 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" @@ -34,13 +33,13 @@ namespace QMatrixClient ~GetAccount3PIDsJob() override; const QVector<ThirdPartyIdentifier>& threepids() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer<Private> d; }; class Post3PIDsJob : public BaseJob diff --git a/jobs/generated/banning.cpp b/jobs/generated/banning.cpp index 96f80ea8..f66b27b6 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" @@ -15,9 +14,7 @@ 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)); @@ -28,9 +25,7 @@ BanJob::BanJob(const QString& roomId, const QString& userId, const QString& reas 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)); diff --git a/jobs/generated/banning.h b/jobs/generated/banning.h index 6db096ee..2d6fbd9b 100644 --- a/jobs/generated/banning.h +++ b/jobs/generated/banning.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient diff --git a/jobs/generated/content-repo.cpp b/jobs/generated/content-repo.cpp new file mode 100644 index 00000000..93aa838c --- /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 <QtNetwork/QNetworkReply> +#include <QtCore/QStringBuilder> + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/media/r0"); + +class UploadContentJob::Private +{ + public: + QString contentUri; +}; + +UploadContentJob::UploadContentJob(QIODevice* 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<QString>(json.value("content_uri")); + return Success; +} + +class GetContentJob::Private +{ + public: + QString contentType; + QString contentDisposition; + QIODevice* 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; +} + +QIODevice* 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; + return Success; +} + +class GetContentOverrideNameJob::Private +{ + public: + QString contentType; + QString contentDisposition; + QIODevice* 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; +} + +QIODevice* 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; + return Success; +} + +class GetContentThumbnailJob::Private +{ + public: + QString contentType; + QIODevice* 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; +} + +QIODevice* GetContentThumbnailJob::content() const +{ + return d->content; +} + +BaseJob::Status GetContentThumbnailJob::parseReply(QNetworkReply* reply) +{ + d->contentType = reply->rawHeader("Content-Type"); + d->content = reply; + 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<double>(json.value("matrix:image:size")); + d->ogImage = fromJson<QString>(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..0796322b --- /dev/null +++ b/jobs/generated/content-repo.h @@ -0,0 +1,101 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "../basejob.h" + +#include <QtCore/QIODevice> + + +namespace QMatrixClient +{ + // Operations + + class UploadContentJob : public BaseJob + { + public: + explicit UploadContentJob(QIODevice* content, const QString& filename = {}, const QString& contentType = {}); + ~UploadContentJob() override; + + const QString& contentUri() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + QScopedPointer<Private> d; + }; + + class GetContentJob : public BaseJob + { + public: + explicit GetContentJob(const QString& serverName, const QString& mediaId); + ~GetContentJob() override; + + const QString& contentType() const; + const QString& contentDisposition() const; + QIODevice* content() const; + + protected: + Status parseReply(QNetworkReply* reply) override; + + private: + class Private; + QScopedPointer<Private> 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; + QIODevice* content() const; + + protected: + Status parseReply(QNetworkReply* reply) override; + + private: + class Private; + QScopedPointer<Private> 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; + QIODevice* content() const; + + protected: + Status parseReply(QNetworkReply* reply) override; + + private: + class Private; + QScopedPointer<Private> 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<Private> 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 <QtCore/QStringBuilder> + +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> + { + CreateRoomJob::Invite3pid operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + CreateRoomJob::Invite3pid result; + result.idServer = + fromJson<QString>(o.value("id_server")); + result.medium = + fromJson<QString>(o.value("medium")); + result.address = + fromJson<QString>(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> + { + CreateRoomJob::StateEvent operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + CreateRoomJob::StateEvent result; + result.type = + fromJson<QString>(o.value("type")); + result.stateKey = + fromJson<QString>(o.value("state_key")); + result.content = + fromJson<QJsonObject>(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<QString>& invite, const QVector<Invite3pid>& invite3pid, const QJsonObject& creationContent, const QVector<StateEvent>& 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<QString>(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..13c9d2c0 --- /dev/null +++ b/jobs/generated/create_room.h @@ -0,0 +1,55 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "../basejob.h" + +#include <QtCore/QJsonObject> +#include <QtCore/QVector> + +#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<QString>& invite = {}, const QVector<Invite3pid>& invite3pid = {}, const QJsonObject& creationContent = {}, const QVector<StateEvent>& initialState = {}, const QString& preset = {}, bool isDirect = {}); + ~CreateRoomJob() override; + + const QString& roomId() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + QScopedPointer<Private> d; + }; +} // namespace QMatrixClient diff --git a/jobs/generated/directory.cpp b/jobs/generated/directory.cpp index dcec75ac..4e61ed74 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" @@ -15,9 +14,7 @@ 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()) @@ -34,16 +31,13 @@ class GetRoomIdByAliasJob::Private GetRoomIdByAliasJob::GetRoomIdByAliasJob(const QString& roomAlias) : BaseJob(HttpVerb::Get, "GetRoomIdByAliasJob", - basePath % "/room/" % roomAlias, - Query { }, Data { }, false - ), d(new Private) -{ } - -GetRoomIdByAliasJob::~GetRoomIdByAliasJob() + basePath % "/room/" % roomAlias, false) + , d(new Private) { - delete d; } +GetRoomIdByAliasJob::~GetRoomIdByAliasJob() = default; + const QString& GetRoomIdByAliasJob::roomId() const { return d->roomId; @@ -64,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/directory.h b/jobs/generated/directory.h index 1dd4e7ed..eeda563b 100644 --- a/jobs/generated/directory.h +++ b/jobs/generated/directory.h @@ -2,13 +2,11 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" #include <QtCore/QVector> -#include <QtCore/QString> namespace QMatrixClient @@ -29,13 +27,13 @@ namespace QMatrixClient const QString& roomId() const; const QVector<QString>& servers() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer<Private> d; }; class DeleteRoomAliasJob : public BaseJob diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp index 5f89adf7..d2ee2107 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" @@ -15,9 +14,7 @@ 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)); diff --git a/jobs/generated/inviting.h b/jobs/generated/inviting.h index 225cb516..eaa884df 100644 --- a/jobs/generated/inviting.h +++ b/jobs/generated/inviting.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp index 86dde629..bf2490b7 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" @@ -15,9 +14,7 @@ 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)); diff --git a/jobs/generated/kicking.h b/jobs/generated/kicking.h index 7c834e45..3814bef7 100644 --- a/jobs/generated/kicking.h +++ b/jobs/generated/kicking.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient diff --git a/jobs/generated/leaving.cpp b/jobs/generated/leaving.cpp index 2cf7fda3..89c110dd 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" @@ -15,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/leaving.h b/jobs/generated/leaving.h index 28ba3d92..cd39b612 100644 --- a/jobs/generated/leaving.h +++ b/jobs/generated/leaving.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient diff --git a/jobs/generated/list_public_rooms.cpp b/jobs/generated/list_public_rooms.cpp index 8a96966f..a2c0e406 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 <QtCore/QStringBuilder> using namespace QMatrixClient; @@ -71,20 +68,20 @@ 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) -{ } - -GetPublicRoomsJob::~GetPublicRoomsJob() -{ - delete d; + 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; + const QVector<GetPublicRoomsJob::PublicRoomsChunk>& GetPublicRoomsJob::chunk() const { return d->chunk; @@ -199,12 +196,13 @@ 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()) @@ -213,10 +211,7 @@ QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, double limit, co setRequestData(_data); } -QueryPublicRoomsJob::~QueryPublicRoomsJob() -{ - delete d; -} +QueryPublicRoomsJob::~QueryPublicRoomsJob() = default; const QVector<QueryPublicRoomsJob::PublicRoomsChunk>& QueryPublicRoomsJob::chunk() const { diff --git a/jobs/generated/list_public_rooms.h b/jobs/generated/list_public_rooms.h index 74dd8626..7dcb8cf7 100644 --- a/jobs/generated/list_public_rooms.h +++ b/jobs/generated/list_public_rooms.h @@ -2,13 +2,11 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" #include <QtCore/QVector> -#include <QtCore/QString> #include "converters.h" @@ -45,13 +43,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<Private> d; }; class QueryPublicRoomsJob : public BaseJob @@ -90,12 +88,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<Private> d; }; } // namespace QMatrixClient diff --git a/jobs/generated/login.cpp b/jobs/generated/login.cpp index 4c159517..a4dab428 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" @@ -24,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)); @@ -47,10 +45,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..3ac955d4 100644 --- a/jobs/generated/login.h +++ b/jobs/generated/login.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient @@ -24,12 +22,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<Private> d; }; } // namespace QMatrixClient diff --git a/jobs/generated/logout.cpp b/jobs/generated/logout.cpp index c250bddf..f7f8eff9 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" @@ -15,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/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..9523ca96 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" @@ -15,9 +14,7 @@ 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()) @@ -33,16 +30,13 @@ class GetDisplayNameJob::Private GetDisplayNameJob::GetDisplayNameJob(const QString& userId) : BaseJob(HttpVerb::Get, "GetDisplayNameJob", - basePath % "/profile/" % userId % "/displayname", - Query { }, Data { }, false - ), d(new Private) -{ } - -GetDisplayNameJob::~GetDisplayNameJob() + basePath % "/profile/" % userId % "/displayname", false) + , d(new Private) { - delete d; } +GetDisplayNameJob::~GetDisplayNameJob() = default; + const QString& GetDisplayNameJob::displayname() const { return d->displayname; @@ -57,9 +51,7 @@ 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()) @@ -75,16 +67,13 @@ class GetAvatarUrlJob::Private GetAvatarUrlJob::GetAvatarUrlJob(const QString& userId) : BaseJob(HttpVerb::Get, "GetAvatarUrlJob", - basePath % "/profile/" % userId % "/avatar_url", - Query { }, Data { }, false - ), d(new Private) -{ } - -GetAvatarUrlJob::~GetAvatarUrlJob() + basePath % "/profile/" % userId % "/avatar_url", false) + , d(new Private) { - delete d; } +GetAvatarUrlJob::~GetAvatarUrlJob() = default; + const QString& GetAvatarUrlJob::avatarUrl() const { return d->avatarUrl; @@ -106,16 +95,13 @@ class GetUserProfileJob::Private GetUserProfileJob::GetUserProfileJob(const QString& userId) : BaseJob(HttpVerb::Get, "GetUserProfileJob", - basePath % "/profile/" % userId, - Query { }, Data { }, false - ), d(new Private) -{ } - -GetUserProfileJob::~GetUserProfileJob() + basePath % "/profile/" % userId, false) + , d(new Private) { - delete d; } +GetUserProfileJob::~GetUserProfileJob() = default; + const QString& GetUserProfileJob::avatarUrl() const { return d->avatarUrl; diff --git a/jobs/generated/profile.h b/jobs/generated/profile.h index 30e858de..1e09791d 100644 --- a/jobs/generated/profile.h +++ b/jobs/generated/profile.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient @@ -27,13 +25,13 @@ namespace QMatrixClient ~GetDisplayNameJob() override; const QString& displayname() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer<Private> d; }; class SetAvatarUrlJob : public BaseJob @@ -49,13 +47,13 @@ namespace QMatrixClient ~GetAvatarUrlJob() override; const QString& avatarUrl() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer<Private> d; }; class GetUserProfileJob : public BaseJob @@ -66,12 +64,12 @@ namespace QMatrixClient const QString& avatarUrl() const; const QString& displayname() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer<Private> d; }; } // namespace QMatrixClient diff --git a/jobs/generated/receipts.cpp b/jobs/generated/receipts.cpp index 2820b583..83c38b6f 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" @@ -15,9 +14,7 @@ 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)); } diff --git a/jobs/generated/receipts.h b/jobs/generated/receipts.h index 6f36d7fc..9eb7a489 100644 --- a/jobs/generated/receipts.h +++ b/jobs/generated/receipts.h @@ -2,13 +2,11 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" #include <QtCore/QJsonObject> -#include <QtCore/QString> namespace QMatrixClient diff --git a/jobs/generated/redaction.cpp b/jobs/generated/redaction.cpp index a9b8ed7e..0da35dfc 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" @@ -21,9 +20,8 @@ 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()) @@ -31,10 +29,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..e3b3ff4f 100644 --- a/jobs/generated/redaction.h +++ b/jobs/generated/redaction.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient @@ -21,12 +19,12 @@ namespace QMatrixClient ~RedactEventJob() override; const QString& eventId() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer<Private> d; }; } // namespace QMatrixClient diff --git a/jobs/generated/third_party_membership.cpp b/jobs/generated/third_party_membership.cpp index 7a2aa4f4..b637d481 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" @@ -15,9 +14,7 @@ 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)); diff --git a/jobs/generated/third_party_membership.h b/jobs/generated/third_party_membership.h index 6c1193ed..c7b5214e 100644 --- a/jobs/generated/third_party_membership.h +++ b/jobs/generated/third_party_membership.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient diff --git a/jobs/generated/typing.cpp b/jobs/generated/typing.cpp index 44bbb131..fa700290 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" @@ -15,9 +14,7 @@ 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)); diff --git a/jobs/generated/typing.h b/jobs/generated/typing.h index e20bca1a..0495ed0a 100644 --- a/jobs/generated/typing.h +++ b/jobs/generated/typing.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient diff --git a/jobs/generated/versions.cpp b/jobs/generated/versions.cpp index 66b31290..938c1d34 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" @@ -21,16 +20,13 @@ class GetVersionsJob::Private GetVersionsJob::GetVersionsJob() : BaseJob(HttpVerb::Get, "GetVersionsJob", - basePath % "/versions", - Query { }, Data { }, false - ), d(new Private) -{ } - -GetVersionsJob::~GetVersionsJob() + basePath % "/versions", false) + , d(new Private) { - delete d; } +GetVersionsJob::~GetVersionsJob() = default; + const QVector<QString>& GetVersionsJob::versions() const { return d->versions; diff --git a/jobs/generated/versions.h b/jobs/generated/versions.h index eab8cf9e..686d7069 100644 --- a/jobs/generated/versions.h +++ b/jobs/generated/versions.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> #include <QtCore/QVector> @@ -22,12 +20,12 @@ namespace QMatrixClient ~GetVersionsJob() override; const QVector<QString>& versions() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer<Private> d; }; } // namespace QMatrixClient diff --git a/jobs/generated/whoami.cpp b/jobs/generated/whoami.cpp index dce091ec..4f7b052c 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" @@ -21,16 +20,13 @@ class GetTokenOwnerJob::Private GetTokenOwnerJob::GetTokenOwnerJob() : BaseJob(HttpVerb::Get, "GetTokenOwnerJob", - basePath % "/account/whoami", - Query { } - ), d(new Private) -{ } - -GetTokenOwnerJob::~GetTokenOwnerJob() + basePath % "/account/whoami") + , d(new Private) { - delete d; } +GetTokenOwnerJob::~GetTokenOwnerJob() = default; + const QString& GetTokenOwnerJob::userId() const { return d->userId; diff --git a/jobs/generated/whoami.h b/jobs/generated/whoami.h index 1b04f337..8e1952da 100644 --- a/jobs/generated/whoami.h +++ b/jobs/generated/whoami.h @@ -2,12 +2,10 @@ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN */ - #pragma once #include "../basejob.h" -#include <QtCore/QString> namespace QMatrixClient @@ -21,12 +19,12 @@ namespace QMatrixClient ~GetTokenOwnerJob() override; const QString& userId() const; - + protected: Status parseJson(const QJsonDocument& data) override; - + private: class Private; - Private* d; + QScopedPointer<Private> d; }; } // namespace QMatrixClient diff --git a/jobs/gtad.yaml b/jobs/gtad.yaml new file mode 100644 index 00000000..f79ce9b6 --- /dev/null +++ b/jobs/gtad.yaml @@ -0,0 +1,84 @@ +preprocess: + "%CLIENT_RELEASE_LABEL%": r0 + "%CLIENT_MAJOR_VERSION%": r0 + # FIXME: the below only fixes C++ compilation but not actual work - the code + # will try to reach out for wrong values in JSON payloads + #"signed:": "signedData:" + #"unsigned:": "unsignedData:" + #"default:": "isDefault:" + +# Structure: +# swaggerType: <targetTypeSpec> +# OR +# swaggerType: +# - swaggerFormat: <targetTypeSpec> +# - /swaggerFormatRegEx/: <targetTypeSpec> +# - //: <targetTypeSpec> # default, if the format doesn't mach anything above +# WHERE +# targetTypeSpec = targetType OR +# { type: targetType, imports: <filename OR [ filenames... ]>, <other attributes...> } +types: + integer: + - int64: qint64 + - int32: qint32 + - //: int + number: + - float: float + - //: double + boolean: bool + string: + - byte: &ByteStream + type: QIODevice* + #initializer: '"{{defaultValue}}"' + #string?: true + imports: <QtCore/QIODevice> + - binary: *ByteStream + - date: + type: QDate + initializer: QDate::fromString("{{defaultValue}}") + avoidCopy?: true + imports: <QtCore/QDate> + - dateTime: + type: QDateTime + initializer: QDateTime::fromString("{{defaultValue}}") + avoidCopy?: true + imports: <QtCore/QDateTime> + - //: + type: QString + initializer: QStringLiteral("{{defaultValue}}") + string?: true + avoidCopy?: true + file: *ByteStream + object: + type: QJsonObject + avoidCopy?: true + imports: <QtCore/QJsonObject> + array: + - /.+/: + type: "QVector<{{1}}>" + avoidCopy?: true + imports: <QtCore/QVector> + - //: { type: QJsonArray, "avoidCopy?": true, imports: <QtCore/QJsonArray> } + schema: + avoidCopy?: true + +#operations: + +env: + _scopeRenderer: "{{scopeCamelCase}}Job::" + _literalQuote: '"' + maybeCrefType: "{{#avoidCopy?}}const {{/avoidCopy?}}{{dataType.name}}{{#avoidCopy?}}&{{/avoidCopy?}}" + qualifiedMaybeCrefType: "{{#avoidCopy?}}const {{/avoidCopy?}}{{dataType.qualifiedName}}{{#avoidCopy?}}&{{/avoidCopy?}}" + initializeDefaultValue: "{{#defaultValue}}{{>initializer}}{{/defaultValue}}{{^defaultValue}}{}{{/defaultValue}}" + paramToString: '{{#string?}}{{nameCamelCase}}{{/string?}}{{^string?}}QString("%1").arg({{nameCamelCase}}){{/string?}}' +# preamble: preamble.mustache + copyrightName: Kitsune Ral + copyrightEmail: <kitsune-ral@users.sf.net> +# imports: { set: } + +templates: +- "{{base}}.h.mustache" +- "{{base}}.cpp.mustache" + +#outFilesList: apifiles.txt + diff --git a/jobs/mediathumbnailjob.cpp b/jobs/mediathumbnailjob.cpp index c0d67a63..ec82f57b 100644 --- a/jobs/mediathumbnailjob.cpp +++ b/jobs/mediathumbnailjob.cpp @@ -17,23 +17,19 @@ */ #include "mediathumbnailjob.h" -#include "util.h" - -#include <QtCore/QDebug> 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(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()) { } QImage MediaThumbnailJob::thumbnail() const @@ -47,9 +43,10 @@ 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) ) + GetContentThumbnailJob::parseReply(reply); + if( !_thumbnail.loadFromData(content()->readAll()) ) { qCDebug(JOBS) << "MediaThumbnailJob: could not read image data"; } diff --git a/jobs/mediathumbnailjob.h b/jobs/mediathumbnailjob.h index f8f36fe9..ef834cd7 100644 --- a/jobs/mediathumbnailjob.h +++ b/jobs/mediathumbnailjob.h @@ -18,25 +18,24 @@ #pragma once -#include "basejob.h" +#include "generated/content-repo.h" #include <QtGui/QPixmap> 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(const QString& serverName, const QString& mediaId, + QSize requestedSize); + MediaThumbnailJob(const QUrl& mxcUri, QSize requestedSize); QImage thumbnail() const; QImage scaledThumbnail(QSize toSize) const; protected: - Status parseReply(QByteArray data) override; + Status parseReply(QNetworkReply* reply) override; private: QImage _thumbnail; diff --git a/jobs/preamble.mustache b/jobs/preamble.mustache new file mode 100644 index 00000000..3ba87d61 --- /dev/null +++ b/jobs/preamble.mustache @@ -0,0 +1,3 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ 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 <QtCore/QByteArray> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> +#include <QtCore/QJsonDocument> +#include <QtCore/QBuffer> + +using namespace QMatrixClient; + +std::unique_ptr<QIODevice> fromData(const QByteArray& data) +{ + auto source = std::make_unique<QBuffer>(); + source->open(QIODevice::WriteOnly); + source->write(data); + source->close(); + return source; +} + +template <typename JsonDataT> +inline std::unique_ptr<QIODevice> 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 <kitsune-ral@users.sf.net> + * + * 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 <memory> + +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<QIODevice>(source)) + { } + RequestData(RequestData&&) = default; + RequestData& operator=(RequestData&&) = default; + ~RequestData(); + + QIODevice* source() const + { + return _source.get(); + } + + private: + std::unique_ptr<QIODevice> _source; + }; +} // namespace QMatrixClient diff --git a/jobs/{{base}}.cpp.mustache b/jobs/{{base}}.cpp.mustache new file mode 100644 index 00000000..34e7faf3 --- /dev/null +++ b/jobs/{{base}}.cpp.mustache @@ -0,0 +1,103 @@ +{{#@filePartial}}preamble{{/@filePartial}} +#include "{{filenameBase}}.h" +{{^allModels}} +#include "converters.h" +{{/allModels}}{{#operations}} +{{#producesNotJson?}}#include <QtNetwork/QNetworkReply> +{{/producesNotJson?}}#include <QtCore/QStringBuilder> +{{/operations}} +using namespace QMatrixClient; +{{#models.model}}{{^trivial?}} +{{qualifiedName}}::operator QJsonValue() const +{ + QJsonObject o; + {{#vars}}o.insert("{{baseName}}", toJson({{nameCamelCase}})); + {{/vars}} + return o; +} + +{{qualifiedName}} FromJson<{{qualifiedName}}>::operator()(QJsonValue jv) +{ + QJsonObject o = jv.toObject(); + {{qualifiedName}} result; + {{#vars}}result.{{nameCamelCase}} = + fromJson<{{dataType.name}}>(o.value("{{baseName}}")); + {{/vars}} + return result; +} +{{/trivial?}}{{/models.model}}{{#operations}} +static const auto basePath = QStringLiteral("{{basePathWithoutHost}}"); +{{# operation}}{{#models.model}}{{^trivial?}} +{{qualifiedName}}::operator QJsonObject() const +{ + QJsonObject o; + {{#vars}}o.insert("{{baseName}}", toJson({{nameCamelCase}})); + {{/vars}} + return o; +} +namespace QMatrixClient +{ + template <> struct FromJson<{{qualifiedName}}> + { + {{qualifiedName}} operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + {{qualifiedName}} result; + {{#vars}}result.{{nameCamelCase}} = + fromJson<{{dataType.qualifiedName}}>(o.value("{{baseName}}")); + {{/vars}} + return result; + } + }; +} // namespace QMatrixClient +{{/ trivial?}}{{/models.model}}{{#responses}}{{#normalResponse?}}{{#allProperties?}} +class {{camelCaseOperationId}}Job::Private +{ + public:{{#allProperties}} + {{dataType.name}} {{paramName}};{{/allProperties}} +}; +{{/ allProperties?}}{{/normalResponse?}}{{/responses}} +{{camelCaseOperationId}}Job::{{camelCaseOperationId}}Job({{#allParams}}{{>maybeCrefType}} {{paramName}}{{#@join}}, {{/@join}}{{/allParams}}) + : BaseJob(HttpVerb::{{#@cap}}{{#@tolower}}{{httpMethod}}{{/@tolower}}{{/@cap}}, "{{camelCaseOperationId}}Job", + basePath{{#pathParts}} % {{_}}{{/pathParts}}{{#skipAuth}}, false{{/skipAuth}}){{#responses}}{{#normalResponse?}}{{#allProperties?}} + , d(new Private){{/allProperties?}}{{/normalResponse?}}{{/responses}} +{ +{{#headerParams?}}{{#headerParams}} setRequestHeader("{{baseName}}", {{paramName}}.toLatin1()); +{{/headerParams}} +{{/headerParams?}}{{! +}}{{#queryParams?}} QUrlQuery _q;{{#queryParams}} +{{^required?}}{{#string?}} if (!{{nameCamelCase}}.isEmpty()) + {{/string?}}{{/required?}} _q.addQueryItem("{{baseName}}", {{>paramToString}});{{/queryParams}} + setRequestQuery(_q); +{{/queryParams?}}{{#bodyParams?}}{{! +}}{{#inlineBody}} setRequestData(Data({{nameCamelCase}}));{{/inlineBody}}{{! +}}{{^inlineBody}} QJsonObject _data;{{#bodyParams}} +{{^required?}}{{#string?}} if (!{{paramName}}.isEmpty()) + {{/string?}}{{/required?}} _data.insert("{{baseName}}", toJson({{paramName}}));{{/bodyParams}} + setRequestData(_data);{{/inlineBody}} +{{/bodyParams?}}{{#producesNotJson?}} setExpectedContentTypes({ {{#produces}}"{{_}}"{{#@join}}, {{/@join}}{{/produces}} }); +{{/producesNotJson?}}}{{!<- mind the actual brace}} +{{# responses}}{{#normalResponse?}}{{#allProperties?}} +{{camelCaseOperationId}}Job::~{{camelCaseOperationId}}Job() = default; +{{# allProperties}} +{{>qualifiedMaybeCrefType}} {{camelCaseOperationId}}Job::{{paramName}}() const +{ + return d->{{paramName}}; +} +{{/ allProperties}}{{#producesNotJson?}} +BaseJob::Status {{camelCaseOperationId}}Job::parseReply(QNetworkReply* reply) +{ + {{#headers}}d->{{paramName}} = reply->rawHeader("{{baseName}}"); {{! We don't check for required headers yet }} + {{/headers}}{{#properties}}d->{{paramName}} = reply;{{/properties}} + return Success; +}{{/ producesNotJson?}}{{^producesNotJson?}} +BaseJob::Status {{camelCaseOperationId}}Job::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + {{# properties}}{{#required?}}if (!json.contains("{{baseName}}")) + return { JsonParseError, + "The key '{{baseName}}' not found in the response" }; + {{/required?}}d->{{paramName}} = fromJson<{{dataType.name}}>(json.value("{{baseName}}")); + {{/ properties}}return Success; +}{{/ producesNotJson?}} +{{/allProperties?}}{{/normalResponse?}}{{/responses}}{{/operation}}{{/operations}} diff --git a/jobs/{{base}}.h.mustache b/jobs/{{base}}.h.mustache new file mode 100644 index 00000000..69e0e6d3 --- /dev/null +++ b/jobs/{{base}}.h.mustache @@ -0,0 +1,60 @@ +{{#@filePartial}}preamble{{/@filePartial}} +#pragma once + +{{#operations}}#include "../basejob.h" +{{/operations}} +{{#imports}}#include {{_}} +{{/imports}} +{{#allModels}}#include "converters.h" +{{/allModels}} +namespace QMatrixClient +{ +{{#models}} // Data structures +{{# model}}{{#trivial?}} + using {{name}} = {{parent.name}}; +{{/ trivial?}}{{^trivial?}} + struct {{name}}{{#parents?}} : {{#parents}}{{name}}{{#@join}}, {{/@join}}{{/parents}}{{/parents?}} + { + {{#vars}}{{dataType.name}} {{nameCamelCase}}; + {{/vars}} + operator QJsonObject() const; + }; + + template <> struct FromJson<{{name}}> + { + {{name}} operator()(QJsonValue jv); + }; +{{/ trivial?}}{{/model}} +{{/models}}{{#operations}} // Operations +{{# operation}} + class {{camelCaseOperationId}}Job : public BaseJob + { + public:{{# models}} + // Inner data structures +{{# model}}{{#trivial?}} + using {{name}} = {{parent.name}}; +{{/ trivial?}}{{^trivial?}} + struct {{name}}{{#parents?}} : {{#parents}}{{name}}{{#@join}}, {{/@join}}{{/parents}}{{/parents?}} + { + {{#vars}}{{dataType.name}} {{nameCamelCase}}; + {{/vars}} + operator QJsonObject() const; + }; +{{/ trivial?}}{{/model}} + // End of inner data structures +{{/models}} + explicit {{camelCaseOperationId}}Job({{#allParams}}{{>maybeCrefType}} {{paramName}}{{^required?}} = {{>initializeDefaultValue}}{{/required?}}{{#@join}}, {{/@join}}{{/allParams}});{{!skip EOL +}}{{# responses}}{{#normalResponse?}}{{#allProperties?}} + ~{{camelCaseOperationId}}Job() override; +{{#allProperties}} + {{>maybeCrefType}} {{paramName}}() const;{{/allProperties}} + + protected: + Status {{#producesNotJson?}}parseReply(QNetworkReply* reply){{/producesNotJson?}}{{^producesNotJson?}}parseJson(const QJsonDocument& data){{/producesNotJson?}} override; + + private: + class Private; + QScopedPointer<Private> d;{{/allProperties?}}{{/normalResponse?}}{{/responses}} + }; +{{/operation}}{{/operations}}{{!skip EOL +}}} // namespace QMatrixClient diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 8ee3634c..9e4cb279 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 \ @@ -34,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 \ @@ -49,6 +51,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 \ @@ -63,4 +66,5 @@ SOURCES += \ $$PWD/logging.cpp \ $$PWD/settings.cpp \ $$PWD/networksettings.cpp \ - $$PWD/networkaccessmanager.cpp + $$PWD/networkaccessmanager.cpp \ + $$PWD/jobs/downloadfilejob.cpp |