diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-03-31 20:29:02 +0900 |
---|---|---|
committer | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-03-31 20:29:56 +0900 |
commit | a62bc225b8b4c714e7943aad69d84704a03b8015 (patch) | |
tree | df5ff6f4cf878ecd74c14ea41a1d27a46055b415 /lib/avatar.cpp | |
parent | 9d8900197e69e9c0ffaaff6f63a40cb80cf08fb1 (diff) | |
parent | 6a61d3a127db1e253821bfb2ebb7f433bd534c4a (diff) | |
download | libquotient-a62bc225b8b4c714e7943aad69d84704a03b8015.tar.gz libquotient-a62bc225b8b4c714e7943aad69d84704a03b8015.zip |
Merge branch 'kitsune-install-target'
Closes #113.
Diffstat (limited to 'lib/avatar.cpp')
-rw-r--r-- | lib/avatar.cpp | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/lib/avatar.cpp b/lib/avatar.cpp new file mode 100644 index 00000000..1ff2aae1 --- /dev/null +++ b/lib/avatar.cpp @@ -0,0 +1,187 @@ +/****************************************************************************** + * 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 "avatar.h" + +#include "jobs/mediathumbnailjob.h" +#include "events/eventcontent.h" +#include "connection.h" + +#include <QtGui/QPainter> +#include <QtCore/QPointer> + +using namespace QMatrixClient; + +class Avatar::Private +{ + public: + explicit Private(QIcon di, QUrl url = {}) + : _defaultIcon(di), _url(url) + { } + QImage get(Connection* connection, QSize size, + get_callback_t callback) const; + bool upload(UploadContentJob* job, upload_callback_t callback); + + bool checkUrl(QUrl url) const; + + const QIcon _defaultIcon; + QUrl _url; + + // The below are related to image caching, hence mutable + mutable QImage _originalImage; + mutable std::vector<QPair<QSize, QImage>> _scaledImages; + mutable QSize _requestedSize; + mutable bool _bannedUrl = false; + mutable bool _fetched = false; + mutable QPointer<MediaThumbnailJob> _thumbnailRequest = nullptr; + mutable QPointer<BaseJob> _uploadRequest = nullptr; + mutable std::vector<get_callback_t> callbacks; + mutable get_callback_t uploadCallback; +}; + +Avatar::Avatar(QIcon defaultIcon) + : d(std::make_unique<Private>(std::move(defaultIcon))) +{ } + +Avatar::Avatar(QUrl url, QIcon defaultIcon) + : d(std::make_unique<Private>(std::move(defaultIcon), std::move(url))) +{ } + +Avatar::Avatar(Avatar&&) = default; + +Avatar::~Avatar() = default; + +Avatar& Avatar::operator=(Avatar&&) = default; + +QImage Avatar::get(Connection* connection, int dimension, + get_callback_t callback) const +{ + return d->get(connection, {dimension, dimension}, callback); +} + +QImage Avatar::get(Connection* connection, int width, int height, + get_callback_t callback) const +{ + return d->get(connection, {width, height}, callback); +} + +bool Avatar::upload(Connection* connection, const QString& fileName, + upload_callback_t callback) const +{ + if (isJobRunning(d->_uploadRequest)) + return false; + return d->upload(connection->uploadFile(fileName), callback); +} + +bool Avatar::upload(Connection* connection, QIODevice* source, + upload_callback_t callback) const +{ + if (isJobRunning(d->_uploadRequest) || !source->isReadable()) + return false; + return d->upload(connection->uploadContent(source), callback); +} + +QString Avatar::mediaId() const +{ + return d->_url.authority() + d->_url.path(); +} + +QImage Avatar::Private::get(Connection* connection, QSize size, + get_callback_t callback) const +{ + // 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( ( !(_fetched || _thumbnailRequest) + || size.width() > _requestedSize.width() + || size.height() > _requestedSize.height() ) && checkUrl(_url) ) + { + qCDebug(MAIN) << "Getting avatar from" << _url.toString(); + _requestedSize = size; + if (isJobRunning(_thumbnailRequest)) + _thumbnailRequest->abandon(); + callbacks.emplace_back(std::move(callback)); + _thumbnailRequest = connection->getThumbnail(_url, size); + QObject::connect( _thumbnailRequest, &MediaThumbnailJob::success, [this] + { + _fetched = true; + _originalImage = _thumbnailRequest->scaledThumbnail(_requestedSize); + _scaledImages.clear(); + for (auto n: callbacks) + n(); + }); + } + + if( _originalImage.isNull() ) + { + if (_defaultIcon.isNull()) + return _originalImage; + + QPainter p { &_originalImage }; + _defaultIcon.paint(&p, { QPoint(), _defaultIcon.actualSize(size) }); + } + + for (auto p: _scaledImages) + if (p.first == size) + return p.second; + auto result = _originalImage.scaled(size, + Qt::KeepAspectRatio, Qt::SmoothTransformation); + _scaledImages.emplace_back(size, result); + return result; +} + +bool Avatar::Private::upload(UploadContentJob* job, upload_callback_t callback) +{ + _uploadRequest = job; + if (!isJobRunning(_uploadRequest)) + return false; + _uploadRequest->connect(_uploadRequest, &BaseJob::success, + [job,callback] { callback(job->contentUri()); }); + return true; +} + +bool Avatar::Private::checkUrl(QUrl url) const +{ + if (_bannedUrl || url.isEmpty()) + return false; + + // FIXME: Make "mxc" a library-wide constant and maybe even make + // the URL checker a Connection(?) method. + _bannedUrl = !(url.isValid() && + url.scheme() == "mxc" && url.path().count('/') == 1); + if (_bannedUrl) + qCWarning(MAIN) << "Avatar URL is invalid or not mxc-based:" + << url.toDisplayString(); + return !_bannedUrl; +} + +QUrl Avatar::url() const { return d->_url; } + +bool Avatar::updateUrl(const QUrl& newUrl) +{ + if (newUrl == d->_url) + return false; + + d->_url = newUrl; + d->_fetched = false; + if (isJobRunning(d->_thumbnailRequest)) + d->_thumbnailRequest->abandon(); + return true; +} + |