diff options
Diffstat (limited to 'lib/jobs/basejob.h')
-rw-r--r-- | lib/jobs/basejob.h | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h new file mode 100644 index 00000000..ed630a67 --- /dev/null +++ b/lib/jobs/basejob.h @@ -0,0 +1,303 @@ +/****************************************************************************** + * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de> + * + * 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 "../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> + +class QNetworkReply; +class QSslError; + +namespace QMatrixClient +{ + class ConnectionData; + + enum class HttpVerb { Get, Put, Post, Delete }; + + struct JobTimeoutConfig + { + int jobTimeout; + int nextRetryInterval; + }; + + class BaseJob: public QObject + { + Q_OBJECT + Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries) + public: + /* Just in case, the values are compatible with KJob + * (which BaseJob used to inherit from). */ + enum StatusCode { NoError = 0 // To be compatible with Qt conventions + , Success = 0 + , Pending = 1 + , Abandoned = 50 //< A very brief period between abandoning and object deletion + , ErrorLevel = 100 //< Errors have codes starting from this + , NetworkError = 100 + , JsonParseError + , TimeoutError + , ContentAccessError + , NotFoundError + , IncorrectRequestError + , IncorrectResponseError + , TooManyRequestsError + , UserDefinedError = 200 + }; + + /** + * A simple wrapper around QUrlQuery that allows its creation from + * a list of string pairs + */ + class Query : public QUrlQuery + { + public: + using QUrlQuery::QUrlQuery; + Query() = default; + Query(const std::initializer_list< QPair<QString, QString> >& l) + { + setQueryItems(l); + } + }; + + using Data = RequestData; + + /** + * This structure stores the status of a server call job. The status consists + * of a code, that is described (but not delimited) by the respective enum, + * and a freeform message. + * + * To extend the list of error codes, define an (anonymous) enum + * along the lines of StatusCode, with additional values + * starting at UserDefinedError + */ + class Status + { + public: + Status(StatusCode c) : code(c) { } + Status(int c, QString m) : code(c), message(std::move(m)) { } + + bool good() const { return code < ErrorLevel; } + friend QDebug operator<<(QDebug dbg, const Status& s) + { + QDebug(dbg).noquote().nospace() + << s.code << ": " << s.message; + return dbg; + } + + int code; + QString message; + }; + + using duration_t = int; // milliseconds + + public: + BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, + bool needsToken = true); + BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, + const Query& query, Data&& data = {}, + bool needsToken = true); + + Status status() const; + int error() const; + virtual QString errorString() const; + + int maxRetries() const; + void setMaxRetries(int newMaxRetries); + + Q_INVOKABLE duration_t getCurrentTimeout() const; + Q_INVOKABLE duration_t getNextRetryInterval() const; + Q_INVOKABLE duration_t millisToRetry() const; + + friend QDebug operator<<(QDebug dbg, const BaseJob* j) + { + return dbg << j->objectName(); + } + + public slots: + void start(const ConnectionData* connData); + + /** + * Abandons the result of this job, arrived or unarrived. + * + * This aborts waiting for a reply from the server (if there was + * any pending) and deletes the job object. It is always done quietly + * (as opposed to KJob::kill() that can trigger emitting the result). + */ + void abandon(); + + signals: + /** The job is about to send a network request */ + void aboutToStart(); + + /** The job has sent a network request */ + void started(); + + /** + * The previous network request has failed; the next attempt will + * be done in the specified time + * @param nextAttempt the 1-based number of attempt (will always be more than 1) + * @param inMilliseconds the interval after which the next attempt will be taken + */ + void retryScheduled(int nextAttempt, int inMilliseconds); + + /** + * Emitted when the job is finished, in any case. It is used to notify + * observers that the job is terminated and that progress can be hidden. + * + * This should not be emitted directly by subclasses; + * use finishJob() instead. + * + * In general, to be notified of a job's completion, client code + * should connect to success() and failure() + * rather than finished(), so that kill() is indeed quiet. + * However if you store a list of jobs and they might get killed + * silently, then you must connect to this instead of result(), + * to avoid dangling pointers in your list. + * + * @param job the job that emitted this signal + * + * @see success, failure + */ + void finished(BaseJob* job); + + /** + * Emitted when the job is finished (except when killed). + * + * Use error() to know if the job was finished with error. + * + * @param job the job that emitted this signal + * + * @see success, failure + */ + void result(BaseJob* job); + + /** + * Emitted together with result() but only if there's no error. + */ + void success(BaseJob*); + + /** + * Emitted together with result() if there's an error. + * Same as result(), this won't be emitted in case of kill(). + */ + 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(Data&& data); + const QByteArrayList& expectedContentTypes() const; + void addExpectedContentType(const QByteArray& contentType); + void setExpectedContentTypes(const QByteArrayList& contentTypes); + + /** Construct a URL out of baseUrl, path and query + * The function automatically adds '/' between baseUrl's path and + * \p path if necessary. The query component of \p baseUrl + * is ignored. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& path, + const QUrlQuery& query = {}); + + 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 + * issues such as network errors or access denial. + * Returning anything except NoError/Success prevents + * further parseReply()/parseJson() invocation. + * + * @param reply the reply received from the server + * @return the result of checking the reply + * + * @see gotReply + */ + virtual Status checkReply(QNetworkReply* reply) const; + + /** + * Processes the reply. By default, parses the reply into + * a QJsonDocument and calls parseJson() if it's a valid JSON. + * + * @param reply raw contents of a HTTP reply from the server (without headers) + * + * @see gotReply, parseJson + */ + virtual Status parseReply(QNetworkReply* reply); + + /** + * Processes the JSON document received from the Matrix server. + * By default returns succesful status without analysing the JSON. + * + * @param json valid JSON document received from the server + * + * @see parseReply + */ + virtual Status parseJson(const QJsonDocument&); + + void setStatus(Status s); + void setStatus(int code, QString message); + + // Q_DECLARE_LOGGING_CATEGORY return different function types + // in different versions + using LoggingCategory = decltype(JOBS)*; + void setLoggingCategory(LoggingCategory lcf); + + // Job objects should only be deleted via QObject::deleteLater + ~BaseJob() override; + + protected slots: + void timeout(); + + private slots: + void sendRequest(); + void gotReply(); + + private: + void stop(); + void finishJob(); + + class Private; + QScopedPointer<Private> d; + }; + + inline bool isJobRunning(BaseJob* job) + { + return job && job->error() == BaseJob::Pending; + } +} // namespace QMatrixClient |