aboutsummaryrefslogtreecommitdiff
path: root/lib/avatar.cpp
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2018-03-31 20:29:02 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2018-03-31 20:29:56 +0900
commita62bc225b8b4c714e7943aad69d84704a03b8015 (patch)
treedf5ff6f4cf878ecd74c14ea41a1d27a46055b415 /lib/avatar.cpp
parent9d8900197e69e9c0ffaaff6f63a40cb80cf08fb1 (diff)
parent6a61d3a127db1e253821bfb2ebb7f433bd534c4a (diff)
downloadlibquotient-a62bc225b8b4c714e7943aad69d84704a03b8015.tar.gz
libquotient-a62bc225b8b4c714e7943aad69d84704a03b8015.zip
Merge branch 'kitsune-install-target'
Closes #113.
Diffstat (limited to 'lib/avatar.cpp')
-rw-r--r--lib/avatar.cpp187
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;
+}
+