New WIP image provider.

This commit is contained in:
Black Hat 2019-01-04 20:17:26 +08:00
parent ec43131a10
commit c46d38e38e
13 changed files with 135 additions and 104 deletions

View File

@ -51,7 +51,7 @@ ColumnLayout {
visible: avatarVisible visible: avatarVisible
hint: author.displayName hint: author.displayName
source: author.avatarUrl source: author.avatarMediaId
} }
Label { Label {

View File

@ -51,7 +51,7 @@ ColumnLayout {
visible: avatarVisible visible: avatarVisible
hint: author.displayName hint: author.displayName
source: author.avatarUrl source: author.avatarMediaId
} }
Label { Label {
@ -74,7 +74,11 @@ ColumnLayout {
id: img id: img
source: "image://mxc/" + (content.thumbnail_url ? content.thumbnail_url : content.url) source: downloaded ? progressInfo.localPath : "image://mxc/" +
(content.info && content.info.thumbnail_info ?
content.thumbnailMediaId : content.mediaId)
sourceSize.width: 200
sourceSize.height: 200
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: OpacityMask {

View File

@ -47,7 +47,7 @@ ColumnLayout {
visible: avatarVisible visible: avatarVisible
hint: author.displayName hint: author.displayName
source: author.avatarUrl source: author.avatarMediaId
} }
Label { Label {
@ -146,7 +146,7 @@ ColumnLayout {
Layout.preferredHeight: 36 Layout.preferredHeight: 36
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
source: replyAuthor ? replyAuthor.avatarUrl : "" source: replyAuthor ? replyAuthor.avatarMediaId : ""
hint: replyAuthor ? replyAuthor.displayName : "H" hint: replyAuthor ? replyAuthor.displayName : "H"
} }

View File

@ -24,7 +24,7 @@ Drawer {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
hint: room ? room.displayName : "No name" hint: room ? room.displayName : "No name"
source: room ? room.avatarUrl : null source: room ? room.avatarMediaId : null
} }
Label { Label {

View File

@ -39,7 +39,7 @@ Control {
id: headerImage id: headerImage
source: currentRoom.avatarUrl source: currentRoom.avatarMediaId
hint: currentRoom ? currentRoom.displayName : "No name" hint: currentRoom ? currentRoom.displayName : "No name"
} }

View File

@ -113,7 +113,7 @@ Item {
Layout.margins: 12 Layout.margins: 12
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
source: root.user ? root.user.avatarUrl : null source: root.user ? root.user.avatarMediaId : null
hint: root.user ? root.user.displayName : "?" hint: root.user ? root.user.displayName : "?"
} }
@ -648,7 +648,7 @@ Item {
visible: !searchField.active visible: !searchField.active
source: root.user ? root.user.avatarUrl : null source: root.user ? root.user.avatarMediaId : null
hint: root.user ? root.user.displayName : "?" hint: root.user ? root.user.displayName : "?"
MouseArea { MouseArea {

View File

@ -62,7 +62,7 @@ Item {
id: roomHeader id: roomHeader
avatar: currentRoom ? currentRoom.avatarUrl : "" avatar: currentRoom ? currentRoom.avatarMediaId : ""
topic: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : "" topic: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : ""
atTop: messageListView.atYBeginning atTop: messageListView.atYBeginning
@ -386,7 +386,7 @@ Item {
Layout.preferredWidth: 24 Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
source: modelData.avatarUrl source: modelData.avatarMediaId
hint: modelData.displayName hint: modelData.displayName
} }
} }

View File

@ -54,7 +54,7 @@ Control {
Layout.preferredWidth: 32 Layout.preferredWidth: 32
Layout.preferredHeight: 32 Layout.preferredHeight: 32
source: replyUser ? replyUser.avatarUrl : "" source: replyUser ? replyUser.avatarMediaId : ""
hint: replyUser ? replyUser.displayName : "No name" hint: replyUser ? replyUser.displayName : "No name"
} }
@ -129,7 +129,7 @@ Control {
width: 20 width: 20
height: 20 height: 20
visible: !isEmoji visible: !isEmoji
source: modelData.avatarUrl || null source: modelData.avatarMediaId || null
} }
Label { Label {
height: parent.height height: parent.height

View File

@ -1,76 +1,87 @@
#include "imageprovider.h" #include "imageprovider.h"
#include <QFile> #include <connection.h>
#include <QMetaObject> #include <jobs/mediathumbnailjob.h>
#include <QStandardPaths>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QWaitCondition> #include <QtCore/QReadWriteLock>
#include "jobs/mediathumbnailjob.h" using QMatrixClient::BaseJob;
using QMatrixClient::Connection;
#include "connection.h" ThumbnailResponse::ThumbnailResponse(Connection* c, QString mediaId,
const QSize& requestedSize)
using QMatrixClient::MediaThumbnailJob; : c(c),
mediaId(std::move(mediaId)),
ImageProvider::ImageProvider(QObject* parent) requestedSize(requestedSize),
: QObject(parent), errorStr("Image request hasn't started") {
QQuickImageProvider( moveToThread(c->thread());
QQmlImageProviderBase::Image, if (requestedSize.isEmpty()) {
QQmlImageProviderBase::ForceAsynchronousImageLoading) {} errorStr.clear();
emit finished();
QImage ImageProvider::requestImage(const QString& id, QSize* pSize, return;
const QSize& requestedSize) { }
if (!id.startsWith("mxc://")) { // Execute a request on the main thread asynchronously
qWarning() << "ImageProvider: won't fetch an invalid id:" << id QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest,
<< "doesn't follow server/mediaId pattern"; Qt::QueuedConnection);
return {};
} }
QUrl mxcUri{id}; void ThumbnailResponse::startRequest() {
// Runs in the main thread, not QML thread
QImage result = image(mxcUri, requestedSize); if (mediaId.count('/') != 1) {
if (result.isNull()) return {}; errorStr =
if (!requestedSize.isEmpty() && result.size() != requestedSize) { QStringLiteral("Media id '%1' doesn't follow server/mediaId pattern")
QImage scaled = result.scaled(requestedSize, Qt::KeepAspectRatio, .arg(mediaId);
Qt::SmoothTransformation); emit finished();
if (pSize != nullptr) *pSize = scaled.size(); return;
return scaled;
}
if (pSize != nullptr) *pSize = result.size();
return result;
} }
QImage ImageProvider::image(const QUrl& mxc, const QSize& size) { QWriteLocker _(&lock);
QUrl tempfilePath = QUrl::fromLocalFile( job = c->getThumbnail(mediaId, requestedSize);
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + // Connect to any possible outcome including abandonment
mxc.fileName() + ".png"); // to make sure the QML thread is not left stuck forever.
QImage cachedImage; connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult);
if (cachedImage.load(tempfilePath.toLocalFile())) {
return cachedImage;
} }
MediaThumbnailJob* job = nullptr; void ThumbnailResponse::prepareResult() {
QReadLocker locker(&m_lock);
QMetaObject::invokeMethod(
m_connection, [=] { return m_connection->getThumbnail(mxc, size); },
Qt::BlockingQueuedConnection, &job);
if (!job) {
qDebug() << "ImageProvider: failed to send a request";
return {};
}
QImage result;
{ {
QWaitCondition condition; // The most compact way to block on a signal QWriteLocker _(&lock);
job->connect(job, &MediaThumbnailJob::finished, job, [&] { Q_ASSERT(job->error() != BaseJob::Pending);
result = job->thumbnail();
condition.wakeAll(); if (job->error() == BaseJob::Success) {
}); image = job->thumbnail();
condition.wait(&m_lock); errorStr.clear();
} else {
errorStr = job->errorString();
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-"
<< errorStr;
}
job = nullptr;
}
emit finished();
} }
result.save(tempfilePath.toLocalFile()); QQuickTextureFactory* ThumbnailResponse::textureFactory() const {
QReadLocker _(&lock);
return result; return QQuickTextureFactory::textureFactoryForImage(image);
}
QString ThumbnailResponse::errorString() const {
QReadLocker _(&lock);
return errorStr;
}
void ThumbnailResponse::cancel() {
QWriteLocker _(&lock);
if (job) {
job->abandon();
job = nullptr;
}
errorStr = "Image request has been cancelled";
}
QQuickImageResponse* ImageProvider::requestImageResponse(
const QString& id, const QSize& requestedSize) {
qDebug() << "ImageProvider: requesting " << id;
return new ThumbnailResponse(m_connection.load(), id, requestedSize);
} }

View File

@ -1,40 +1,61 @@
#ifndef IMAGEPROVIDER_H #ifndef IMAGEPROVIDER_H
#define IMAGEPROVIDER_H #define IMAGEPROVIDER_H
#pragma once
#include <QObject> #include <jobs/mediathumbnailjob.h>
#include <QThreadPool>
#include <QtCore/QAtomicPointer>
#include <QtCore/QReadWriteLock> #include <QtCore/QReadWriteLock>
#include <QtQuick/QQuickImageProvider> #include <QtQuick/QQuickAsyncImageProvider>
#include "connection.h" namespace QMatrixClient {
class Connection;
}
class ImageProvider : public QObject, public QQuickImageProvider { class ThumbnailResponse : public QQuickImageResponse {
public:
ThumbnailResponse(QMatrixClient::Connection* c, QString mediaId,
const QSize& requestedSize);
~ThumbnailResponse() override = default;
void startRequest();
private:
QMatrixClient::Connection* c;
const QString mediaId;
const QSize requestedSize;
QMatrixClient::MediaThumbnailJob* job = nullptr;
QImage image;
QString errorStr;
mutable QReadWriteLock lock;
void prepareResult();
QQuickTextureFactory* textureFactory() const override;
QString errorString() const override;
void cancel() override;
};
class ImageProvider : public QObject, public QQuickAsyncImageProvider {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QMatrixClient::Connection* connection READ connection WRITE Q_PROPERTY(QMatrixClient::Connection* connection READ connection WRITE
setConnection NOTIFY connectionChanged) setConnection NOTIFY connectionChanged)
public: public:
explicit ImageProvider(QObject* parent = nullptr); explicit ImageProvider() : QObject(), QQuickAsyncImageProvider() {}
QImage requestImage(const QString& id, QSize* pSize, QQuickImageResponse* requestImageResponse(
const QSize& requestedSize) override; const QString& id, const QSize& requestedSize) override;
void initializeEngine(QQmlEngine* engine, const char* uri);
QMatrixClient::Connection* connection() { return m_connection; } QMatrixClient::Connection* connection() { return m_connection; }
void setConnection(QMatrixClient::Connection* newConnection) { void setConnection(QMatrixClient::Connection* connection) {
if (m_connection != newConnection) { m_connection.store(connection);
m_connection = newConnection;
emit connectionChanged();
}
} }
signals: signals:
void connectionChanged(); void connectionChanged();
private: private:
QReadWriteLock m_lock; QAtomicPointer<QMatrixClient::Connection> m_connection;
QMatrixClient::Connection* m_connection = nullptr;
QImage image(const QUrl& mxc, const QSize& size);
}; };
#endif // IMAGEPROVIDER_H #endif // IMAGEPROVIDER_H

View File

@ -14,10 +14,6 @@
#include "utils.h" #include "utils.h"
static QString parseAvatarUrl(QUrl url) {
return url.host() + "/" + url.path();
}
QHash<int, QByteArray> MessageEventModel::roleNames() const { QHash<int, QByteArray> MessageEventModel::roleNames() const {
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[EventTypeRole] = "eventType"; roles[EventTypeRole] = "eventType";

View File

@ -82,7 +82,6 @@ void RoomListModel::connectRoomSignals(SpectralRoom* room) {
if (event->isStateEvent()) return; if (event->isStateEvent()) return;
User* sender = room->user(event->senderId()); User* sender = room->user(event->senderId());
if (sender == room->localUser()) return; if (sender == room->localUser()) return;
QUrl _url = room->avatarUrl();
emit newMessage( emit newMessage(
room->id(), event->id(), room->displayName(), room->id(), event->id(), room->displayName(),
sender->displayname(), utils::eventToString(*event), sender->displayname(), utils::eventToString(*event),
@ -151,7 +150,7 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const {
} }
SpectralRoom* room = m_rooms.at(index.row()); SpectralRoom* room = m_rooms.at(index.row());
if (role == NameRole) return room->displayName(); if (role == NameRole) return room->displayName();
if (role == AvatarRole) return room->avatarUrl(); if (role == AvatarRole) return room->avatarMediaId();
if (role == TopicRole) return room->topic(); if (role == TopicRole) return room->topic();
if (role == CategoryRole) { if (role == CategoryRole) {
if (room->joinState() == JoinState::Invite) return RoomType::Invited; if (room->joinState() == JoinState::Invite) return RoomType::Invited;

View File

@ -69,7 +69,7 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const {
return user->id(); return user->id();
} }
if (role == AvatarRole) { if (role == AvatarRole) {
return user->avatarUrl(); return user->avatarMediaId();
} }
return QVariant(); return QVariant();