diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2017-12-31 08:53:32 +0900 |
---|---|---|
committer | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2017-12-31 08:53:32 +0900 |
commit | 0b5f0deb773f850d1b905fafc656f67f5b63de24 (patch) | |
tree | b5e0ad5a4558a86d9c95a5d14cc3595e0fa2b5d8 | |
parent | cbfe29b3435fbe47fee268facbe6a82000fce0ad (diff) | |
parent | 2c440249052b0d518fccd953a7dc657f9eed7ab7 (diff) | |
download | libquotient-0b5f0deb773f850d1b905fafc656f67f5b63de24.tar.gz libquotient-0b5f0deb773f850d1b905fafc656f67f5b63de24.zip |
Merge branch 'master' into kitsune-gtad
-rw-r--r-- | .travis.yml | 17 | ||||
-rw-r--r-- | CMakeLists.txt | 16 | ||||
-rw-r--r-- | avatar.cpp | 43 | ||||
-rw-r--r-- | avatar.h | 3 | ||||
-rw-r--r-- | connection.cpp | 35 | ||||
-rw-r--r-- | connection.h | 8 | ||||
-rw-r--r-- | connectiondata.cpp | 32 | ||||
-rw-r--r-- | connectiondata.h | 4 | ||||
-rw-r--r-- | events/event.cpp | 10 | ||||
-rw-r--r-- | events/event.h | 27 | ||||
-rw-r--r-- | jobs/basejob.cpp | 27 | ||||
-rw-r--r-- | jobs/basejob.h | 10 | ||||
-rw-r--r-- | jobs/mediathumbnailjob.cpp | 11 | ||||
-rw-r--r-- | jobs/mediathumbnailjob.h | 6 | ||||
-rw-r--r-- | libqmatrixclient.pri | 11 | ||||
-rw-r--r-- | networkaccessmanager.cpp | 74 | ||||
-rw-r--r-- | networkaccessmanager.h | 49 | ||||
-rw-r--r-- | networksettings.cpp | 31 | ||||
-rw-r--r-- | networksettings.h | 44 | ||||
-rw-r--r-- | room.cpp | 25 | ||||
-rw-r--r-- | room.h | 30 | ||||
-rw-r--r-- | settings.cpp | 32 | ||||
-rw-r--r-- | settings.h | 260 | ||||
-rw-r--r-- | user.cpp | 7 | ||||
-rw-r--r-- | user.h | 3 |
25 files changed, 519 insertions, 296 deletions
diff --git a/.travis.yml b/.travis.yml index 6c08b428..9c7d8a7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,5 @@ language: cpp -os: [ linux, osx ] - -compiler: [ gcc, clang ] - addons: apt: sources: @@ -14,10 +10,17 @@ addons: - qt56base matrix: - exclude: [ { os: osx, compiler: gcc } ] + include: + - os: linux + compiler: gcc + env: [ ENV_EVAL="CC=gcc-5 && CXX=g++-5" ] + - os: linux + compiler: clang + - os: osx + env: [ ENV_EVAL="brew update && brew install qt5 && PATH=/usr/local/opt/qt/bin" ] before_install: -- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; brew install qt5; export PATH="$PATH:/usr/local/opt/qt/bin"; fi +- eval "${ENV_EVAL}" - if [ "$TRAVIS_OS_NAME" = "linux" ]; then . /opt/qt56/bin/qt56-env.sh; fi install: @@ -36,7 +39,7 @@ before_script: script: - cmake --build . --target all - cd .. -- qmake qmc-example.pro && make all +- qmake qmc-example.pro "QMAKE_CC = $CC" "QMAKE_CXX = $CXX" && make all notifications: webhooks: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ead9ec8..1aa06198 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,6 @@ -cmake_minimum_required(VERSION 2.8.11) # Maybe works with even older versions +cmake_minimum_required(VERSION 3.1) -project(qmatrixclient) -enable_language(CXX) +project(qmatrixclient CXX) include(CheckCXXCompilerFlag) @@ -19,16 +18,15 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) "MinSizeRel" "RelWithDebInfo") endif() -# Setup command line parameters for the compiler and linker -foreach (FLAG all pedantic error=return-type) +set(CMAKE_CXX_STANDARD 14) + +foreach (FLAG all "" pedantic extra error=return-type no-unused-parameter no-gnu-zero-variadic-macro-arguments) CHECK_CXX_COMPILER_FLAG("-W${FLAG}" WARN_${FLAG}_SUPPORTED) if ( WARN_${FLAG}_SUPPORTED AND NOT CMAKE_CXX_FLAGS MATCHES "(^| )-W?${FLAG}($| )") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W${FLAG}") - endif ( ) + endif () endforeach () -set(CMAKE_CXX_STANDARD 14) - find_package(Qt5 5.6 REQUIRED Network Gui) get_filename_component(Qt5_Prefix "${Qt5_DIR}/../../../.." ABSOLUTE) @@ -51,6 +49,7 @@ message( STATUS ) # Set up source files set(libqmatrixclient_SRCS + networkaccessmanager.cpp connectiondata.cpp connection.cpp logging.cpp @@ -58,6 +57,7 @@ set(libqmatrixclient_SRCS user.cpp avatar.cpp settings.cpp + networksettings.cpp events/event.cpp events/eventcontent.cpp events/roommessageevent.cpp @@ -22,20 +22,22 @@ #include "events/eventcontent.h" #include "connection.h" +#include <QtGui/QPainter> + using namespace QMatrixClient; class Avatar::Private { public: Private(Connection* c, QIcon di) : _connection(c), _defaultIcon(di) { } - QPixmap get(int width, int height, Avatar::notifier_t notifier); + QImage get(QSize size, Avatar::notifier_t notifier); Connection* _connection; const QIcon _defaultIcon; QUrl _url; - QPixmap _originalPixmap; + QImage _originalImage; - std::vector<QPair<QSize, QPixmap>> _scaledPixmaps; + std::vector<QPair<QSize, QImage>> _scaledImages; QSize _requestedSize; bool _valid = false; @@ -49,22 +51,25 @@ Avatar::Avatar(Connection* connection, QIcon defaultIcon) Avatar::~Avatar() = default; -QPixmap Avatar::get(int width, int height, Avatar::notifier_t notifier) +QImage Avatar::get(int dimension, Avatar::notifier_t notifier) { - return d->get(width, height, notifier); + return d->get({dimension, dimension}, notifier); } -QPixmap Avatar::Private::get(int width, int height, Avatar::notifier_t notifier) +QImage Avatar::get(int width, int height, Avatar::notifier_t notifier) { - QSize size(width, height); + return d->get({width, height}, notifier); +} +QImage Avatar::Private::get(QSize size, Avatar::notifier_t notifier) +{ // FIXME: Alternating between longer-width and longer-height requests // is a sure way to trick the below code into constantly getting another // image from the server because the existing one is alleged unsatisfactory. // This is plain abuse by the client, though; so not critical for now. if( ( !(_valid || _ongoingRequest) - || width > _requestedSize.width() - || height > _requestedSize.height() ) && _url.isValid() ) + || size.width() > _requestedSize.width() + || size.height() > _requestedSize.height() ) && _url.isValid() ) { qCDebug(MAIN) << "Getting avatar from" << _url.toString(); _requestedSize = size; @@ -77,8 +82,9 @@ QPixmap Avatar::Private::get(int width, int height, Avatar::notifier_t notifier) if (_ongoingRequest->status().good()) { _valid = true; - _originalPixmap = _ongoingRequest->scaledThumbnail(_requestedSize); - _scaledPixmaps.clear(); + _originalImage = + _ongoingRequest->scaledThumbnail(_requestedSize); + _scaledImages.clear(); for (auto n: notifiers) n(); } @@ -86,21 +92,22 @@ QPixmap Avatar::Private::get(int width, int height, Avatar::notifier_t notifier) }); } - if( _originalPixmap.isNull() ) + if( _originalImage.isNull() ) { if (_defaultIcon.isNull()) - return _originalPixmap; + return _originalImage; - _originalPixmap = _defaultIcon.pixmap(size); + QPainter p { &_originalImage }; + _defaultIcon.paint(&p, { QPoint(), _defaultIcon.actualSize(size) }); } - for (auto p: _scaledPixmaps) + for (auto p: _scaledImages) if (p.first == size) return p.second; - auto pixmap = _originalPixmap.scaled(size, + auto result = _originalImage.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - _scaledPixmaps.emplace_back(size, pixmap); - return pixmap; + _scaledImages.emplace_back(size, result); + return result; } QUrl Avatar::url() const { return d->_url; } @@ -36,7 +36,8 @@ namespace QMatrixClient using notifier_t = std::function<void()>; - QPixmap get(int w, int h, notifier_t notifier); + QImage get(int dimension, notifier_t notifier); + QImage get(int w, int h, notifier_t notifier); QUrl url() const; bool updateUrl(const QUrl& newUrl); diff --git a/connection.cpp b/connection.cpp index 1f554974..36da838f 100644 --- a/connection.cpp +++ b/connection.cpp @@ -44,15 +44,14 @@ using namespace QMatrixClient; class Connection::Private { public: - explicit Private(const QUrl& serverUrl) - : q(nullptr) - , data(new ConnectionData(serverUrl)) + explicit Private(std::unique_ptr<ConnectionData>&& connection) + : data(move(connection)) { } Q_DISABLE_COPY(Private) Private(Private&&) = delete; Private operator=(Private&&) = delete; - Connection* q; + Connection* q = nullptr; std::unique_ptr<ConnectionData> data; // A complex key below is a pair of room name and whether its // state is Invited. The spec mandates to keep Invited room state @@ -72,7 +71,7 @@ class Connection::Private Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent) - , d(new Private(server)) + , d(std::make_unique<Private>(std::make_unique<ConnectionData>(server))) { d->q = this; // All d initialization should occur before this line } @@ -85,7 +84,6 @@ Connection::~Connection() { qCDebug(MAIN) << "deconstructing connection object for" << d->userId; stopSync(); - delete d; } void Connection::resolveServer(const QString& mxidOrDomain) @@ -125,7 +123,7 @@ void Connection::resolveServer(const QString& mxidOrDomain) dns->setType(QDnsLookup::SRV); dns->setName("_matrix._tcp." + domain); - connect(dns, &QDnsLookup::finished, [this,dns,maybeBaseUrl]() { + connect(dns, &QDnsLookup::finished, [this,dns,&maybeBaseUrl]() { QUrl baseUrl { maybeBaseUrl }; if (dns->error() == QDnsLookup::NoError && dns->serviceRecords().isEmpty()) @@ -151,7 +149,7 @@ void Connection::connectToServer(const QString& user, const QString& password, const QString& deviceId) { checkAndConnect(user, - [=] { + [&] { doConnectToServer(user, password, initialDeviceName, deviceId); }); } @@ -163,12 +161,12 @@ void Connection::doConnectToServer(const QString& user, const QString& password, user, /*medium*/ "", /*address*/ "", password, /*token*/ "", deviceId, initialDeviceName); connect(loginJob, &BaseJob::success, this, - [=] { + [this, loginJob] { d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); }); connect(loginJob, &BaseJob::failure, this, - [=] { + [this, loginJob] { emit loginError(loginJob->errorString()); }); } @@ -178,7 +176,7 @@ void Connection::connectWithToken(const QString& userId, const QString& deviceId) { checkAndConnect(userId, - [=] { d->connectWithToken(userId, accessToken, deviceId); }); + [&] { d->connectWithToken(userId, accessToken, deviceId); }); } void Connection::Private::connectWithToken(const QString& user, @@ -222,7 +220,7 @@ void Connection::checkAndConnect(const QString& userId, void Connection::logout() { auto job = callApi<LogoutJob>(); - connect( job, &LogoutJob::success, this, [=] { + connect( job, &LogoutJob::success, this, [this] { stopSync(); emit loggedOut(); }); @@ -237,13 +235,13 @@ void Connection::sync(int timeout) const QString filter { R"({"room": { "timeline": { "limit": 100 } } })" }; auto job = d->syncJob = callApi<SyncJob>(d->data->lastEvent(), filter, timeout); - connect( job, &SyncJob::success, [=] () { + connect( job, &SyncJob::success, [this, job] { onSyncSuccess(job->takeData()); d->syncJob = nullptr; emit syncDone(); }); connect( job, &SyncJob::retryScheduled, this, &Connection::networkError); - connect( job, &SyncJob::failure, [=] () { + connect( job, &SyncJob::failure, [this, job] { d->syncJob = nullptr; if (job->error() == BaseJob::ContentAccessError) emit loginError(job->errorString()); @@ -285,7 +283,7 @@ JoinRoomJob* Connection::joinRoom(const QString& roomAlias) { auto job = callApi<JoinRoomJob>(roomAlias); connect(job, &JoinRoomJob::success, - this, [=] { provideRoom(job->roomId(), JoinState::Join); }); + this, [this, job] { provideRoom(job->roomId(), JoinState::Join); }); return job; } @@ -325,13 +323,12 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) { auto leaveJob = joinedRoom->leaveRoom(); connect(leaveJob, &BaseJob::success, - this, [=] { forgetJob->start(connectionData()); }); - connect(leaveJob, &BaseJob::failure, - this, [=] { forgetJob->abandon(); }); + this, [this, forgetJob] { forgetJob->start(connectionData()); }); + connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); } else forgetJob->start(connectionData()); - connect(forgetJob, &BaseJob::success, this, [=] + connect(forgetJob, &BaseJob::success, this, [this, &id] { // If the room happens to be in the map (possible in both forms), // delete the found object(s). diff --git a/connection.h b/connection.h index eccde170..8dda2bbf 100644 --- a/connection.h +++ b/connection.h @@ -26,15 +26,13 @@ #include <QtCore/QSize> #include <functional> - -class QDnsLookup; +#include <memory> namespace QMatrixClient { class Room; class User; class RoomEvent; - class ConnectionPrivate; class ConnectionData; class SyncJob; @@ -217,7 +215,7 @@ namespace QMatrixClient void reconnected(); //< Unused; use connected() instead void loggedOut(); void loginError(QString error); - void networkError(size_t nextAttempt, int inMilliseconds); + void networkError(int retriesTaken, int inMilliseconds); void syncDone(); void syncError(QString error); @@ -307,7 +305,7 @@ namespace QMatrixClient private: class Private; - Private* d; + std::unique_ptr<Private> d; /** * A single entry for functions that need to check whether the diff --git a/connectiondata.cpp b/connectiondata.cpp index 70791952..4e9bc77e 100644 --- a/connectiondata.cpp +++ b/connectiondata.cpp @@ -18,26 +18,15 @@ #include "connectiondata.h" +#include "networkaccessmanager.h" #include "logging.h" -#include <QtNetwork/QNetworkAccessManager> - using namespace QMatrixClient; -QNetworkAccessManager* createNam() -{ - auto nam = new QNetworkAccessManager(); - // See #109. Once Qt bearer management gets better, this workaround - // should become unnecessary. - nam->connect(nam, &QNetworkAccessManager::networkAccessibleChanged, - nam, [=] { - nam->setNetworkAccessible(QNetworkAccessManager::Accessible); - }); - return nam; -} - struct ConnectionData::Private { + explicit Private(const QUrl& url) : baseUrl(url) { } + QUrl baseUrl; QByteArray accessToken; QString lastEvent; @@ -48,16 +37,10 @@ struct ConnectionData::Private }; ConnectionData::ConnectionData(QUrl baseUrl) - : d(new Private) -{ - nam(); // Just to ensure NAM is created - d->baseUrl = baseUrl; -} + : d(std::make_unique<Private>(baseUrl)) +{ } -ConnectionData::~ConnectionData() -{ - delete d; -} +ConnectionData::~ConnectionData() = default; QByteArray ConnectionData::accessToken() const { @@ -71,8 +54,7 @@ QUrl ConnectionData::baseUrl() const QNetworkAccessManager* ConnectionData::nam() const { - static auto nam = createNam(); - return nam; + return NetworkAccessManager::instance(); } void ConnectionData::setBaseUrl(QUrl baseUrl) diff --git a/connectiondata.h b/connectiondata.h index 530a52ee..7a2f2e90 100644 --- a/connectiondata.h +++ b/connectiondata.h @@ -20,6 +20,8 @@ #include <QtCore/QUrl> +#include <memory> + class QNetworkAccessManager; namespace QMatrixClient @@ -48,6 +50,6 @@ namespace QMatrixClient private: struct Private; - Private* d; + std::unique_ptr<Private> d; }; } // namespace QMatrixClient diff --git a/events/event.cpp b/events/event.cpp index e74bc114..c7345a13 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -144,3 +144,13 @@ RoomEventPtr _impl::doMakeEvent(const QJsonObject& obj) RoomAvatarEvent, EncryptionEvent, RedactionEvent> (obj, obj["type"].toString()) }; } + +StateEventBase::~StateEventBase() = default; + +bool StateEventBase::repeatsState() const +{ + auto contentJson = originalJsonObject().value("content"); + auto prevContentJson = originalJsonObject().value("unsigned") + .toObject().value("prev_content"); + return contentJson == prevContentJson; +} diff --git a/events/event.h b/events/event.h index d4923f1f..319c3443 100644 --- a/events/event.h +++ b/events/event.h @@ -240,8 +240,23 @@ namespace QMatrixClient RoomEvents::iterator end() { return to; } }; + class StateEventBase: public RoomEvent + { + public: + explicit StateEventBase(Type type, const QJsonObject& obj) + : RoomEvent(obj.contains("state_key") ? type : Type::Unknown, + obj) + { } + explicit StateEventBase(Type type) + : RoomEvent(type) + { } + ~StateEventBase() override = 0; + + virtual bool repeatsState() const; + }; + template <typename ContentT> - class StateEvent: public RoomEvent + class StateEvent: public StateEventBase { public: using content_type = ContentT; @@ -249,19 +264,19 @@ namespace QMatrixClient template <typename... ContentParamTs> explicit StateEvent(Type type, const QJsonObject& obj, ContentParamTs&&... contentParams) - : RoomEvent(obj.contains("state_key") ? type : Type::Unknown, - obj) + : StateEventBase(type, obj) , _content(contentJson(), std::forward<ContentParamTs>(contentParams)...) { - if (obj.contains("prev_content")) + auto unsignedData = obj.value("unsigned").toObject(); + if (unsignedData.contains("prev_content")) _prev.reset(new ContentT( - obj["prev_content"].toObject(), + unsignedData.value("prev_content").toObject(), std::forward<ContentParamTs>(contentParams)...)); } template <typename... ContentParamTs> explicit StateEvent(Type type, ContentParamTs&&... contentParams) - : RoomEvent(type) + : StateEventBase(type) , _content(std::forward<ContentParamTs>(contentParams)...) { } diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index 2f5c381a..405cb9e0 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -52,6 +52,7 @@ class BaseJob::Private { } void sendRequest(); + const JobTimeoutConfig& getCurrentTimeoutConfig() const; const ConnectionData* connection = nullptr; @@ -68,8 +69,10 @@ class BaseJob::Private QTimer timer; QTimer retryTimer; - size_t maxRetries = 3; - size_t retriesTaken = 0; + QVector<JobTimeoutConfig> errorStrategy = + { { 90, 5 }, { 90, 10 }, { 120, 30 } }; + int maxRetries = errorStrategy.size(); + int retriesTaken = 0; LoggingCategory logCat = JOBS; }; @@ -187,7 +190,6 @@ void BaseJob::sendRequest() if (!d->requestQuery.isEmpty()) qCDebug(d->logCat) << " query:" << d->requestQuery.toString(); d->sendRequest(); - connect( d->reply.data(), &QNetworkReply::sslErrors, this, &BaseJob::sslErrors ); connect( d->reply.data(), &QNetworkReply::finished, this, &BaseJob::gotReply ); if (d->reply->isRunning()) { @@ -296,16 +298,19 @@ void BaseJob::finishJob() deleteLater(); } +const JobTimeoutConfig& BaseJob::Private::getCurrentTimeoutConfig() const +{ + return errorStrategy[std::min(retriesTaken, errorStrategy.size() - 1)]; +} + BaseJob::duration_t BaseJob::getCurrentTimeout() const { - static const std::array<int, 4> timeouts = { 90, 90, 120, 120 }; - return timeouts[std::min(d->retriesTaken, timeouts.size() - 1)] * 1000; + return d->getCurrentTimeoutConfig().jobTimeout * 1000; } BaseJob::duration_t BaseJob::getNextRetryInterval() const { - static const std::array<int, 3> intervals = { 5, 10, 30 }; - return intervals[std::min(d->retriesTaken, intervals.size() - 1)] * 1000; + return d->getCurrentTimeoutConfig().nextRetryInterval * 1000; } BaseJob::duration_t BaseJob::millisToRetry() const @@ -364,14 +369,6 @@ void BaseJob::timeout() finishJob(); } -void BaseJob::sslErrors(const QList<QSslError>& errors) -{ - foreach (const QSslError &error, errors) { - qCWarning(d->logCat) << "SSL ERROR" << error.errorString(); - } - d->reply->ignoreSslErrors(); // TODO: insecure! should prompt user first -} - void BaseJob::setLoggingCategory(LoggingCategory lcf) { d->logCat = lcf; diff --git a/jobs/basejob.h b/jobs/basejob.h index 66812774..e3a379fa 100644 --- a/jobs/basejob.h +++ b/jobs/basejob.h @@ -36,6 +36,12 @@ namespace QMatrixClient enum class HttpVerb { Get, Put, Post, Delete }; + struct JobTimeoutConfig + { + int jobTimeout; + int nextRetryInterval; + }; + class BaseJob: public QObject { Q_OBJECT @@ -162,7 +168,7 @@ namespace QMatrixClient * @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(size_t nextAttempt, int inMilliseconds); + void retryScheduled(int nextAttempt, int inMilliseconds); /** * Emitted when the job is finished, in any case. It is used to notify @@ -179,7 +185,6 @@ namespace QMatrixClient * to avoid dangling pointers in your list. * * @param job the job that emitted this signal - * @internal * * @see success, failure */ @@ -262,7 +267,6 @@ namespace QMatrixClient virtual ~BaseJob(); protected slots: void timeout(); - void sslErrors(const QList<QSslError>& errors); private slots: void sendRequest(); diff --git a/jobs/mediathumbnailjob.cpp b/jobs/mediathumbnailjob.cpp index 5945493a..c0d67a63 100644 --- a/jobs/mediathumbnailjob.cpp +++ b/jobs/mediathumbnailjob.cpp @@ -36,19 +36,20 @@ MediaThumbnailJob::MediaThumbnailJob(QUrl url, QSize requestedSize, })) { } -QPixmap MediaThumbnailJob::thumbnail() const +QImage MediaThumbnailJob::thumbnail() const { - return pixmap; + return _thumbnail; } -QPixmap MediaThumbnailJob::scaledThumbnail(QSize toSize) const +QImage MediaThumbnailJob::scaledThumbnail(QSize toSize) const { - return pixmap.scaled(toSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + return _thumbnail.scaled(toSize, + Qt::KeepAspectRatio, Qt::SmoothTransformation); } BaseJob::Status MediaThumbnailJob::parseReply(QByteArray data) { - if( !pixmap.loadFromData(data) ) + if( !_thumbnail.loadFromData(data) ) { qCDebug(JOBS) << "MediaThumbnailJob: could not read image data"; } diff --git a/jobs/mediathumbnailjob.h b/jobs/mediathumbnailjob.h index 292e7f14..f8f36fe9 100644 --- a/jobs/mediathumbnailjob.h +++ b/jobs/mediathumbnailjob.h @@ -32,13 +32,13 @@ namespace QMatrixClient MediaThumbnailJob(QUrl url, QSize requestedSize, ThumbnailType thumbnailType = ThumbnailType::Scale); - QPixmap thumbnail() const; - QPixmap scaledThumbnail(QSize toSize) const; + QImage thumbnail() const; + QImage scaledThumbnail(QSize toSize) const; protected: Status parseReply(QByteArray data) override; private: - QPixmap pixmap; + QImage _thumbnail; }; } // namespace QMatrixClient diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 49442197..8ee3634c 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -1,5 +1,6 @@ QT += network -CONFIG += c++11 warn_on rtti_off +CONFIG += c++14 warn_on rtti_off +QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter INCLUDEPATH += $$PWD @@ -31,7 +32,9 @@ HEADERS += \ $$PWD/jobs/setroomstatejob.h \ $$files($$PWD/jobs/generated/*.h, false) \ $$PWD/logging.h \ - $$PWD/settings.h + $$PWD/settings.h \ + $$PWD/networksettings.h \ + $$PWD/networkaccessmanager.h SOURCES += \ $$PWD/connectiondata.cpp \ @@ -58,4 +61,6 @@ SOURCES += \ $$PWD/jobs/setroomstatejob.cpp \ $$files($$PWD/jobs/generated/*.cpp, false) \ $$PWD/logging.cpp \ - $$PWD/settings.cpp + $$PWD/settings.cpp \ + $$PWD/networksettings.cpp \ + $$PWD/networkaccessmanager.cpp diff --git a/networkaccessmanager.cpp b/networkaccessmanager.cpp new file mode 100644 index 00000000..7fb2f602 --- /dev/null +++ b/networkaccessmanager.cpp @@ -0,0 +1,74 @@ +/****************************************************************************** + * 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 + */ + +#include "networkaccessmanager.h" + +#include <QtNetwork/QNetworkReply> + +using namespace QMatrixClient; + +class NetworkAccessManager::Private +{ + public: + QList<QSslError> ignoredSslErrors; +}; + +NetworkAccessManager::NetworkAccessManager() : d(std::make_unique<Private>()) +{ } + +QList<QSslError> NetworkAccessManager::ignoredSslErrors() const +{ + return d->ignoredSslErrors; +} + +void NetworkAccessManager::addIgnoredSslError(const QSslError& error) +{ + d->ignoredSslErrors << error; +} + +void NetworkAccessManager::clearIgnoredSslErrors() +{ + d->ignoredSslErrors.clear(); +} + +static NetworkAccessManager* createNam() +{ + auto nam = new NetworkAccessManager; + // See #109. Once Qt bearer management gets better, this workaround + // should become unnecessary. + nam->connect(nam, &QNetworkAccessManager::networkAccessibleChanged, + [nam] { nam->setNetworkAccessible(QNetworkAccessManager::Accessible); }); + return nam; +} + +NetworkAccessManager*NetworkAccessManager::instance() +{ + static auto* nam = createNam(); + return nam; +} + +NetworkAccessManager::~NetworkAccessManager() = default; + +QNetworkReply* NetworkAccessManager::createRequest(Operation op, + const QNetworkRequest& request, QIODevice* outgoingData) +{ + auto reply = + QNetworkAccessManager::createRequest(op, request, outgoingData); + reply->ignoreSslErrors(d->ignoredSslErrors); + return reply; +} diff --git a/networkaccessmanager.h b/networkaccessmanager.h new file mode 100644 index 00000000..ea08c591 --- /dev/null +++ b/networkaccessmanager.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * 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 <QtNetwork/QNetworkAccessManager> + +#include <memory> + +namespace QMatrixClient +{ + class NetworkAccessManager : public QNetworkAccessManager + { + Q_OBJECT + public: + NetworkAccessManager(); + ~NetworkAccessManager() override; + + QList<QSslError> ignoredSslErrors() const; + void addIgnoredSslError(const QSslError& error); + void clearIgnoredSslErrors(); + + /** Get a pointer to the singleton */ + static NetworkAccessManager* instance(); + + private: + QNetworkReply * createRequest(Operation op, + const QNetworkRequest &request, + QIODevice *outgoingData = Q_NULLPTR) override; + + class Private; + std::unique_ptr<Private> d; + }; +} // namespace QMatrixClient diff --git a/networksettings.cpp b/networksettings.cpp new file mode 100644 index 00000000..48bd09f3 --- /dev/null +++ b/networksettings.cpp @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (C) 2017 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 + */ + +#include "networksettings.h" + +using namespace QMatrixClient; + +void NetworkSettings::setupApplicationProxy() const +{ + QNetworkProxy::setApplicationProxy( + { proxyType(), proxyHostName(), proxyPort() }); +} + +QMC_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType, "proxy_type", QNetworkProxy::DefaultProxy, setProxyType) +QMC_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname", "", setProxyHostName) +QMC_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1, setProxyPort) diff --git a/networksettings.h b/networksettings.h new file mode 100644 index 00000000..83613060 --- /dev/null +++ b/networksettings.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * Copyright (C) 2017 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 "settings.h" + +#include <QtNetwork/QNetworkProxy> + +Q_DECLARE_METATYPE(QNetworkProxy::ProxyType) + +namespace QMatrixClient { + class NetworkSettings: public SettingsGroup + { + Q_OBJECT + QMC_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) + QMC_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) + QMC_DECLARE_SETTING(quint16, proxyPort, setProxyPort) + Q_PROPERTY(QString proxyHost READ proxyHostName WRITE setProxyHostName) + public: + template <typename... ArgTs> + explicit NetworkSettings(ArgTs... qsettingsArgs) + : SettingsGroup(QStringLiteral("Network"), qsettingsArgs...) + { } + ~NetworkSettings() override = default; + + Q_INVOKABLE void setupApplicationProxy() const; + }; +} @@ -160,6 +160,11 @@ class Room::Private } }; +RoomEventPtr TimelineItem::replaceEvent(RoomEventPtr&& other) +{ + return std::exchange(evt, std::move(other)); +} + Room::Room(Connection* connection, QString id, JoinState initialJoinState) : QObject(connection), d(new Private(connection, id, initialJoinState)) { @@ -209,7 +214,12 @@ QString Room::topic() const return d->topic; } -QPixmap Room::avatar(int width, int height) +QImage Room::avatar(int dimension) +{ + return avatar(dimension, dimension); +} + +QImage Room::avatar(int width, int height) { if (!d->avatar.url().isEmpty()) return d->avatar.get(width, height, [=] { emit avatarChanged(); }); @@ -457,7 +467,7 @@ void Room::Private::removeMemberFromMap(const QString& username, User* u) emit q->memberRenamed(formerNamesakes[0]); } -inline QByteArray makeErrorStr(const Event& e, QByteArray msg) +inline auto makeErrorStr(const Event& e, QByteArray msg) { return msg.append("; event dump follows:\n").append(e.originalJson()); } @@ -499,7 +509,7 @@ void Room::Private::addMember(User *u) { insertMemberIntoMap(u); connect(u, &User::nameChanged, q, - [=] (User* u, const QString& newName) { renameMember(u, newName); }); + bind(&Private::renameMember, this, _1, _2)); emit q->userAdded(u); } } @@ -660,7 +670,7 @@ void Room::Private::getPreviousContent(int limit) { roomMessagesJob = connection->callApi<RoomMessagesJob>(id, prevBatch, limit); - connect( roomMessagesJob, &RoomMessagesJob::result, [=]() { + connect( roomMessagesJob, &RoomMessagesJob::result, [=] { if( !roomMessagesJob->error() ) { addHistoricalMessageEvents(roomMessagesJob->releaseEvents()); @@ -780,8 +790,7 @@ void Room::Private::processRedaction(RoomEventPtr redactionEvent) } auto keepContentKeys = find_if(keepContentKeysMap.begin(), keepContentKeysMap.end(), - [&](const std::pair<EventType,QStringList>& t) - { return ti->type() == t.first; } ); + [&ti](const auto& t) { return ti->type() == t.first; } ); if (keepContentKeys == keepContentKeysMap.end()) { originalJson.remove("content"); @@ -1226,9 +1235,9 @@ bool MemberSorter::operator()(User *u1, User *u2) const { auto n1 = room->roomMembername(u1); auto n2 = room->roomMembername(u2); - if (n1[0] == '@') + if (n1.startsWith('@')) n1.remove(0, 1); - if (n2[0] == '@') + if (n2.startsWith('@')) n2.remove(0, 1); return n1.localeAwareCompare(n2) < 0; } @@ -18,8 +18,9 @@ #pragma once -#include <memory> -#include <deque> +#include "jobs/syncjob.h" +#include "events/roommessageevent.h" +#include "joinstate.h" #include <QtCore/QList> #include <QtCore/QStringList> @@ -27,9 +28,9 @@ #include <QtCore/QJsonObject> #include <QtGui/QPixmap> -#include "jobs/syncjob.h" -#include "events/roommessageevent.h" -#include "joinstate.h" +#include <memory> +#include <deque> +#include <utility> namespace QMatrixClient { @@ -56,11 +57,7 @@ namespace QMatrixClient index_t index() const { return idx; } // Used for event redaction - RoomEventPtr replaceEvent(RoomEventPtr&& other) - { - evt.swap(other); - return move(other); - } + RoomEventPtr replaceEvent(RoomEventPtr&& other); private: RoomEventPtr evt; @@ -111,11 +108,20 @@ namespace QMatrixClient Q_INVOKABLE int timelineSize() const; /** - * Returns a room avatar and requests it from the network if needed + * Returns a square room avatar with the given size and requests it + * from the network if needed + * @return a pixmap with the avatar or a placeholder if there's none + * available yet + */ + Q_INVOKABLE QImage avatar(int dimension); + /** + * Returns a room avatar with the given dimensions and requests it + * from the network if needed * @return a pixmap with the avatar or a placeholder if there's none * available yet */ - Q_INVOKABLE QPixmap avatar(int width, int height); + Q_INVOKABLE QImage avatar(int width, int height); + /** * @brief Produces a disambiguated name for a given user in * the context of the room diff --git a/settings.cpp b/settings.cpp index ac9c091c..bf369c58 100644 --- a/settings.cpp +++ b/settings.cpp @@ -84,15 +84,9 @@ void SettingsGroup::remove(const QString& key) Settings::remove(fullKey);
}
-bool AccountSettings::keepLoggedIn() const
-{
- return value("keep_logged_in", false).toBool();
-}
-
-void AccountSettings::setKeepLoggedIn(bool newSetting)
-{
- setValue("keep_logged_in", newSetting);
-}
+QMC_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", "", setDeviceId)
+QMC_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", "", setDeviceName)
+QMC_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, setKeepLoggedIn)
QUrl AccountSettings::homeserver() const
{
@@ -109,26 +103,6 @@ QString AccountSettings::userId() const return group().section('/', -1);
}
-QString AccountSettings::deviceId() const
-{
- return value("device_id").toString();
-}
-
-void AccountSettings::setDeviceId(const QString& deviceId)
-{
- setValue("device_id", deviceId);
-}
-
-QString AccountSettings::deviceName() const
-{
- return value("device_name").toString();
-}
-
-void AccountSettings::setDeviceName(const QString& deviceName)
-{
- setValue("device_name", deviceName);
-}
-
QString AccountSettings::accessToken() const
{
return value("access_token").toString();
@@ -1,126 +1,134 @@ -/******************************************************************************
- * Copyright (C) 2016 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 <QtCore/QSettings>
-#include <QtCore/QVector>
-#include <QtCore/QUrl>
-
-class QVariant;
-
-namespace QMatrixClient
-{
- class Settings: public QSettings
- {
- Q_OBJECT
- public:
- /**
- * Use this function before creating any Settings objects in order
- * to setup a read-only location where configuration has previously
- * been stored. This will provide an additional fallback in case of
- * renaming the organisation/application.
- */
- static void setLegacyNames(const QString& organizationName,
- const QString& applicationName = {});
-
-#if defined(_MSC_VER) && _MSC_VER < 1900
- // VS 2013 (and probably older) aren't friends with 'using' statements
- // that involve private constructors
- explicit Settings(QObject* parent = 0) : QSettings(parent) { }
-#else
- using QSettings::QSettings;
-#endif
-
- Q_INVOKABLE void setValue(const QString &key,
- const QVariant &value);
- Q_INVOKABLE QVariant value(const QString &key,
- const QVariant &defaultValue = {}) const;
- Q_INVOKABLE bool contains(const QString& key) const;
- Q_INVOKABLE QStringList childGroups() const;
-
- private:
- static QString legacyOrganizationName;
- static QString legacyApplicationName;
-
- protected:
- QSettings legacySettings { legacyOrganizationName,
- legacyApplicationName };
- };
-
- class SettingsGroup: public Settings
- {
- public:
- template <typename... ArgTs>
- explicit SettingsGroup(const QString& path, ArgTs... qsettingsArgs)
- : Settings(qsettingsArgs...)
- , groupPath(path)
- { }
-
- Q_INVOKABLE bool contains(const QString& key) const;
- Q_INVOKABLE QVariant value(const QString &key,
- const QVariant &defaultValue = {}) const;
- Q_INVOKABLE QString group() const;
- Q_INVOKABLE QStringList childGroups() const;
- Q_INVOKABLE void setValue(const QString &key,
- const QVariant &value);
-
- Q_INVOKABLE void remove(const QString& key);
-
- private:
- QString groupPath;
- };
-
- class AccountSettings: public SettingsGroup
- {
- Q_OBJECT
- Q_PROPERTY(QString userId READ userId CONSTANT)
- Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId)
- Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName)
- Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver)
- Q_PROPERTY(bool keepLoggedIn READ keepLoggedIn WRITE setKeepLoggedIn)
- /** \deprecated \sa setToken */
- Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken)
- public:
- template <typename... ArgTs>
- explicit AccountSettings(const QString& accountId, ArgTs... qsettingsArgs)
- : SettingsGroup("Accounts/" + accountId, qsettingsArgs...)
- { }
-
- QString userId() const;
-
- QString deviceId() const;
- void setDeviceId(const QString& deviceId);
-
- QString deviceName() const;
- void setDeviceName(const QString& deviceName);
-
- QUrl homeserver() const;
- void setHomeserver(const QUrl& url);
-
- bool keepLoggedIn() const;
- void setKeepLoggedIn(bool newSetting);
-
- /** \deprecated \sa setToken */
- QString accessToken() const;
- /** \deprecated Storing accessToken in QSettings is unsafe,
- * see QMatrixClient/Quaternion#181 */
- void setAccessToken(const QString& accessToken);
- Q_INVOKABLE void clearAccessToken();
- };
-} // namespace QMatrixClient
+/****************************************************************************** + * Copyright (C) 2016 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 <QtCore/QSettings> +#include <QtCore/QVector> +#include <QtCore/QUrl> + +class QVariant; + +namespace QMatrixClient +{ + class Settings: public QSettings + { + Q_OBJECT + public: + /** + * Use this function before creating any Settings objects in order + * to setup a read-only location where configuration has previously + * been stored. This will provide an additional fallback in case of + * renaming the organisation/application. + */ + static void setLegacyNames(const QString& organizationName, + const QString& applicationName = {}); + +#if defined(_MSC_VER) && _MSC_VER < 1900 + // VS 2013 (and probably older) aren't friends with 'using' statements + // that involve private constructors + explicit Settings(QObject* parent = 0) : QSettings(parent) { } +#else + using QSettings::QSettings; +#endif + + Q_INVOKABLE void setValue(const QString &key, + const QVariant &value); + Q_INVOKABLE QVariant value(const QString &key, + const QVariant &defaultValue = {}) const; + Q_INVOKABLE bool contains(const QString& key) const; + Q_INVOKABLE QStringList childGroups() const; + + private: + static QString legacyOrganizationName; + static QString legacyApplicationName; + + protected: + QSettings legacySettings { legacyOrganizationName, + legacyApplicationName }; + }; + + class SettingsGroup: public Settings + { + public: + template <typename... ArgTs> + explicit SettingsGroup(const QString& path, ArgTs... qsettingsArgs) + : Settings(qsettingsArgs...) + , groupPath(path) + { } + + Q_INVOKABLE bool contains(const QString& key) const; + Q_INVOKABLE QVariant value(const QString &key, + const QVariant &defaultValue = {}) const; + Q_INVOKABLE QString group() const; + Q_INVOKABLE QStringList childGroups() const; + Q_INVOKABLE void setValue(const QString &key, + const QVariant &value); + + Q_INVOKABLE void remove(const QString& key); + + private: + QString groupPath; + }; + +#define QMC_DECLARE_SETTING(type, propname, setter) \ + Q_PROPERTY(type propname READ propname WRITE setter) \ + public: \ + type propname() const; \ + void setter(type newValue); \ + private: + +#define QMC_DEFINE_SETTING(classname, type, propname, qsettingname, defaultValue, setter) \ +type classname::propname() const \ +{ \ + return value(QStringLiteral(qsettingname), defaultValue).value<type>(); \ +} \ +\ +void classname::setter(type newValue) \ +{ \ + setValue(QStringLiteral(qsettingname), newValue); \ +} \ + + class AccountSettings: public SettingsGroup + { + Q_OBJECT + Q_PROPERTY(QString userId READ userId CONSTANT) + QMC_DECLARE_SETTING(QString, deviceId, setDeviceId) + QMC_DECLARE_SETTING(QString, deviceName, setDeviceName) + QMC_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn) + /** \deprecated \sa setAccessToken */ + Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken) + public: + template <typename... ArgTs> + explicit AccountSettings(const QString& accountId, ArgTs... qsettingsArgs) + : SettingsGroup("Accounts/" + accountId, qsettingsArgs...) + { } + + QString userId() const; + + QUrl homeserver() const; + void setHomeserver(const QUrl& url); + + /** \deprecated \sa setToken */ + QString accessToken() const; + /** \deprecated Storing accessToken in QSettings is unsafe, + * see QMatrixClient/Quaternion#181 */ + void setAccessToken(const QString& accessToken); + Q_INVOKABLE void clearAccessToken(); + }; +} // namespace QMatrixClient @@ -95,7 +95,12 @@ Avatar& User::avatarObject() return d->avatar; } -QPixmap User::avatar(int width, int height) +QImage User::avatar(int dimension) +{ + return avatar(dimension, dimension); +} + +QImage User::avatar(int width, int height) { return d->avatar.get(width, height, [=] { emit avatarChanged(this); }); } @@ -54,7 +54,8 @@ namespace QMatrixClient Q_INVOKABLE QString bridged() const; Avatar& avatarObject(); - QPixmap avatar(int requestedWidth, int requestedHeight); + QImage avatar(int dimension); + QImage avatar(int requestedWidth, int requestedHeight); QUrl avatarUrl() const; |