diff options
author | Alexey Rusakov <Kitsune-Ral@users.sf.net> | 2021-09-12 06:16:25 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-12 06:16:25 +0200 |
commit | 41944ac3ee76029201c2108a063ae2b6354f6a22 (patch) | |
tree | 661559b8a51868ae54fbb58104ad49ca8900b2de /lib | |
parent | 9da6b25a26403952e5a76b043076ba302c8d3c30 (diff) | |
parent | bcaab611840a0a2ad284e6f1e7c2f0b4de10222d (diff) | |
download | libquotient-41944ac3ee76029201c2108a063ae2b6354f6a22.tar.gz libquotient-41944ac3ee76029201c2108a063ae2b6354f6a22.zip |
Merge pull request #499 from TobiasFella/mxcnam
Implement the mxc protocol in the NetworkAccessManager
Diffstat (limited to 'lib')
-rw-r--r-- | lib/connection.cpp | 12 | ||||
-rw-r--r-- | lib/connection.h | 2 | ||||
-rw-r--r-- | lib/logging.cpp | 1 | ||||
-rw-r--r-- | lib/logging.h | 1 | ||||
-rw-r--r-- | lib/mxcreply.cpp | 70 | ||||
-rw-r--r-- | lib/mxcreply.h | 29 | ||||
-rw-r--r-- | lib/networkaccessmanager.cpp | 78 | ||||
-rw-r--r-- | lib/networkaccessmanager.h | 5 | ||||
-rw-r--r-- | lib/room.cpp | 11 | ||||
-rw-r--r-- | lib/room.h | 3 |
10 files changed, 208 insertions, 4 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp index 222c3b71..4abf5097 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -13,6 +13,7 @@ #include "room.h" #include "settings.h" #include "user.h" +#include "accountregistry.h" // NB: since Qt 6, moc_connection.cpp needs Room and User fully defined #include "moc_connection.cpp" @@ -258,6 +259,7 @@ Connection::~Connection() { qCDebug(MAIN) << "deconstructing connection object for" << userId(); stopSync(); + AccountRegistry::instance().drop(this); } void Connection::resolveServer(const QString& mxid) @@ -441,6 +443,7 @@ void Connection::Private::completeSetup(const QString& mxId) qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << data->userId() << "from device" << data->deviceId(); + AccountRegistry::instance().add(q); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED @@ -836,6 +839,15 @@ inline auto splitMediaId(const QString& mediaId) return idParts; } +QUrl Connection::makeMediaUrl(QUrl mxcUrl) const +{ + Q_ASSERT(mxcUrl.scheme() == "mxc"); + QUrlQuery q(mxcUrl.query()); + q.addQueryItem(QStringLiteral("user_id"), userId()); + mxcUrl.setQuery(q); + return mxcUrl; +} + MediaThumbnailJob* Connection::getThumbnail(const QString& mediaId, QSize requestedSize, RunningPolicy policy) diff --git a/lib/connection.h b/lib/connection.h index ecbb1a19..1a6ca9b0 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -529,6 +529,8 @@ public Q_SLOTS: void stopSync(); QString nextBatchToken() const; + Q_INVOKABLE QUrl makeMediaUrl(QUrl mxcUrl) const; + virtual MediaThumbnailJob* getThumbnail(const QString& mediaId, QSize requestedSize, RunningPolicy policy = BackgroundRequest); diff --git a/lib/logging.cpp b/lib/logging.cpp index ffcc851c..15eac69d 100644 --- a/lib/logging.cpp +++ b/lib/logging.cpp @@ -17,4 +17,5 @@ LOGGING_CATEGORY(E2EE, "quotient.e2ee") LOGGING_CATEGORY(JOBS, "quotient.jobs") LOGGING_CATEGORY(SYNCJOB, "quotient.jobs.sync") LOGGING_CATEGORY(THUMBNAILJOB, "quotient.jobs.thumbnail") +LOGGING_CATEGORY(NETWORK, "quotient.network") LOGGING_CATEGORY(PROFILER, "quotient.profiler") diff --git a/lib/logging.h b/lib/logging.h index 1d1394e8..7e0da975 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -17,6 +17,7 @@ Q_DECLARE_LOGGING_CATEGORY(E2EE) Q_DECLARE_LOGGING_CATEGORY(JOBS) Q_DECLARE_LOGGING_CATEGORY(SYNCJOB) Q_DECLARE_LOGGING_CATEGORY(THUMBNAILJOB) +Q_DECLARE_LOGGING_CATEGORY(NETWORK) Q_DECLARE_LOGGING_CATEGORY(PROFILER) namespace Quotient { diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp new file mode 100644 index 00000000..0b6643fc --- /dev/null +++ b/lib/mxcreply.cpp @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "mxcreply.h" + +#include "room.h" + +using namespace Quotient; + +class MxcReply::Private +{ +public: + explicit Private(QNetworkReply* r = nullptr) + : m_reply(r) + {} + QNetworkReply* m_reply; +}; + +MxcReply::MxcReply(QNetworkReply* reply) + : d(std::make_unique<Private>(reply)) +{ + reply->setParent(this); + connect(d->m_reply, &QNetworkReply::finished, this, [this]() { + setError(d->m_reply->error(), d->m_reply->errorString()); + setOpenMode(ReadOnly); + Q_EMIT finished(); + }); +} + +MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) + : d(std::make_unique<Private>(reply)) +{ + reply->setParent(this); + connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { + setError(d->m_reply->error(), d->m_reply->errorString()); + setOpenMode(ReadOnly); + emit finished(); + }); +} + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) +#define ERROR_SIGNAL errorOccurred +#else +#define ERROR_SIGNAL error +#endif + +MxcReply::MxcReply() +{ + static const auto BadRequestPhrase = tr("Bad Request"); + QMetaObject::invokeMethod(this, [this]() { + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 400); + setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, + BadRequestPhrase); + setError(QNetworkReply::ProtocolInvalidOperationError, + BadRequestPhrase); + setFinished(true); + emit ERROR_SIGNAL(QNetworkReply::ProtocolInvalidOperationError); + emit finished(); + }, Qt::QueuedConnection); +} + +qint64 MxcReply::readData(char *data, qint64 maxSize) +{ + return d->m_reply->read(data, maxSize); +} + +void MxcReply::abort() +{ + d->m_reply->abort(); +} diff --git a/lib/mxcreply.h b/lib/mxcreply.h new file mode 100644 index 00000000..efaf01c6 --- /dev/null +++ b/lib/mxcreply.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include <QtNetwork/QNetworkReply> +#include <memory> + +namespace Quotient { +class Room; + +class MxcReply : public QNetworkReply +{ +public: + explicit MxcReply(); + explicit MxcReply(QNetworkReply *reply); + MxcReply(QNetworkReply* reply, Room* room, const QString& eventId); + +public Q_SLOTS: + void abort() override; + +protected: + qint64 readData(char *data, qint64 maxSize) override; + +private: + class Private; + std::unique_ptr<Private> d; +}; +} diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index a94ead34..57618329 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -3,18 +3,43 @@ #include "networkaccessmanager.h" +#include "connection.h" +#include "room.h" +#include "accountregistry.h" +#include "mxcreply.h" + #include <QtCore/QCoreApplication> +#include <QtCore/QThreadStorage> +#include <QtCore/QSettings> #include <QtNetwork/QNetworkReply> using namespace Quotient; class NetworkAccessManager::Private { public: + explicit Private(NetworkAccessManager* q) + : q(q) + {} + + QNetworkReply* createImplRequest(Operation op, + const QNetworkRequest& outerRequest, + Connection* connection) + { + Q_ASSERT(outerRequest.url().scheme() == "mxc"); + QNetworkRequest r(outerRequest); + r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2") + .arg(connection->homeserver().toString(), + outerRequest.url().authority() + + outerRequest.url().path()))); + return q->createRequest(op, r); + } + + NetworkAccessManager* q; QList<QSslError> ignoredSslErrors; }; NetworkAccessManager::NetworkAccessManager(QObject* parent) - : QNetworkAccessManager(parent), d(std::make_unique<Private>()) + : QNetworkAccessManager(parent), d(std::make_unique<Private>(this)) {} QList<QSslError> NetworkAccessManager::ignoredSslErrors() const @@ -34,7 +59,7 @@ void NetworkAccessManager::clearIgnoredSslErrors() static NetworkAccessManager* createNam() { - auto nam = new NetworkAccessManager(QCoreApplication::instance()); + auto nam = new NetworkAccessManager(); #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) // See #109; in newer Qt, bearer management is deprecated altogether NetworkAccessManager::connect(nam, @@ -47,8 +72,11 @@ static NetworkAccessManager* createNam() NetworkAccessManager* NetworkAccessManager::instance() { - static auto* nam = createNam(); - return nam; + static QThreadStorage<NetworkAccessManager*> storage; + if(!storage.hasLocalData()) { + storage.setLocalData(createNam()); + } + return storage.localData(); } NetworkAccessManager::~NetworkAccessManager() = default; @@ -56,7 +84,49 @@ NetworkAccessManager::~NetworkAccessManager() = default; QNetworkReply* NetworkAccessManager::createRequest( Operation op, const QNetworkRequest& request, QIODevice* outgoingData) { + const auto& mxcUrl = request.url(); + if (mxcUrl.scheme() == "mxc") { + const QUrlQuery query(mxcUrl.query()); + const auto accountId = query.queryItemValue(QStringLiteral("user_id")); + if (accountId.isEmpty()) { + // Using QSettings here because Quotient::NetworkSettings + // doesn't provide multithreading guarantees + static thread_local QSettings s; + if (!s.value("Network/allow_direct_media_requests").toBool()) { + qCWarning(NETWORK) << "No connection specified"; + return new MxcReply(); + } + // TODO: Make the best effort with a direct unauthenticated request + // to the media server + } else { + auto* const connection = AccountRegistry::instance().get(accountId); + if (!connection) { + qCWarning(NETWORK) << "Connection" << accountId << "not found"; + return new MxcReply(); + } + const auto roomId = query.queryItemValue(QStringLiteral("room_id")); + if (!roomId.isEmpty()) { + auto room = connection->room(roomId); + if (!room) { + qCWarning(NETWORK) << "Room" << roomId << "not found"; + return new MxcReply(); + } + return new MxcReply( + d->createImplRequest(op, request, connection), room, + query.queryItemValue(QStringLiteral("event_id"))); + } + return new MxcReply( + d->createImplRequest(op, request, connection)); + } + } auto reply = QNetworkAccessManager::createRequest(op, request, outgoingData); reply->ignoreSslErrors(d->ignoredSslErrors); return reply; } + +QStringList NetworkAccessManager::supportedSchemesImplementation() const +{ + auto schemes = QNetworkAccessManager::supportedSchemesImplementation(); + schemes += QStringLiteral("mxc"); + return schemes; +} diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 47729a1b..87bc12a1 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -8,6 +8,8 @@ #include <memory> namespace Quotient { +class Room; +class Connection; class NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: @@ -21,6 +23,9 @@ public: /** Get a pointer to the singleton */ static NetworkAccessManager* instance(); +public Q_SLOTS: + QStringList supportedSchemesImplementation() const; + private: QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData = Q_NULLPTR) override; diff --git a/lib/room.cpp b/lib/room.cpp index c6cca2ea..bcbb121b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1116,6 +1116,17 @@ QList<User*> Room::directChatUsers() const return connection()->directChatUsers(this); } +QUrl Room::makeMediaUrl(const QString& eventId, const QUrl& mxcUrl) const +{ + auto url = connection()->makeMediaUrl(mxcUrl); + QUrlQuery q(url.query()); + Q_ASSERT(q.hasQueryItem("user_id")); + q.addQueryItem("room_id", id()); + q.addQueryItem("event_id", eventId); + url.setQuery(q); + return url; +} + QString safeFileName(QString rawName) { return rawName.replace(QRegularExpression("[/\\<>|\"*?:]"), "_"); @@ -458,6 +458,9 @@ public: /// Get the list of users this room is a direct chat with QList<User*> directChatUsers() const; + Q_INVOKABLE QUrl makeMediaUrl(const QString& eventId, + const QUrl &mxcUrl) const; + Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId) const; Q_INVOKABLE QUrl urlToDownload(const QString& eventId) const; |