Merge branch 'kitsune-fix-image-provider' into 'develop'

Fix ImageProvider crashes with libQMC 0.5

See merge request b0/spectral!41
This commit is contained in:
Black Hat 2019-03-01 11:27:23 +00:00
commit 7fe4c82bda
2 changed files with 43 additions and 36 deletions

View File

@ -1,42 +1,37 @@
#include "imageprovider.h" #include "imageprovider.h"
#include <connection.h>
#include <jobs/mediathumbnailjob.h>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QReadWriteLock> #include <QtCore/QThread>
using QMatrixClient::BaseJob; using QMatrixClient::BaseJob;
using QMatrixClient::Connection;
ThumbnailResponse::ThumbnailResponse(Connection* c, QString mediaId, ThumbnailResponse::ThumbnailResponse(QMatrixClient::Connection* c,
const QSize& requestedSize) QString mediaId, const QSize& requestedSize)
: c(c), : c(c),
mediaId(std::move(mediaId)), mediaId(std::move(mediaId)),
requestedSize(requestedSize), requestedSize(requestedSize),
errorStr("Image request hasn't started") { errorStr("Image request hasn't started") {
moveToThread(c->thread());
if (requestedSize.isEmpty()) { if (requestedSize.isEmpty()) {
errorStr.clear(); errorStr.clear();
emit finished(); emit finished();
return; return;
} }
if (mediaId.count('/') != 1) {
errorStr =
tr("Media id '%1' doesn't follow server/mediaId pattern")
.arg(mediaId);
emit finished();
return;
}
// Execute a request on the main thread asynchronously // Execute a request on the main thread asynchronously
moveToThread(c->thread());
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest,
Qt::QueuedConnection); Qt::QueuedConnection);
} }
void ThumbnailResponse::startRequest() { void ThumbnailResponse::startRequest() {
// Runs in the main thread, not QML thread // Runs in the main thread, not QML thread
if (mediaId.count('/') != 1) { Q_ASSERT(QThread::currentThread() == c->thread());
errorStr =
QStringLiteral("Media id '%1' doesn't follow server/mediaId pattern")
.arg(mediaId);
emit finished();
return;
}
QWriteLocker _(&lock);
job = c->getThumbnail(mediaId, requestedSize); job = c->getThumbnail(mediaId, requestedSize);
// Connect to any possible outcome including abandonment // Connect to any possible outcome including abandonment
// to make sure the QML thread is not left stuck forever. // to make sure the QML thread is not left stuck forever.
@ -44,13 +39,16 @@ void ThumbnailResponse::startRequest() {
} }
void ThumbnailResponse::prepareResult() { void ThumbnailResponse::prepareResult() {
Q_ASSERT(QThread::currentThread() == job->thread());
Q_ASSERT(job->error() != BaseJob::Pending);
{ {
QWriteLocker _(&lock); QWriteLocker _(&lock);
Q_ASSERT(job->error() != BaseJob::Pending);
if (job->error() == BaseJob::Success) { if (job->error() == BaseJob::Success) {
image = job->thumbnail(); image = job->thumbnail();
errorStr.clear(); errorStr.clear();
} else if (job->error() == BaseJob::Abandoned) {
errorStr = tr("Image request has been cancelled");
qDebug() << "ThumbnailResponse: cancelled for" << mediaId;
} else { } else {
errorStr = job->errorString(); errorStr = job->errorString();
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-" qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-"
@ -61,6 +59,13 @@ void ThumbnailResponse::prepareResult() {
emit finished(); emit finished();
} }
void ThumbnailResponse::doCancel() {
// Runs in the main thread, not QML thread
Q_ASSERT(QThread::currentThread() == job->thread());
if (job)
job->abandon();
}
QQuickTextureFactory* ThumbnailResponse::textureFactory() const { QQuickTextureFactory* ThumbnailResponse::textureFactory() const {
QReadLocker _(&lock); QReadLocker _(&lock);
return QQuickTextureFactory::textureFactoryForImage(image); return QQuickTextureFactory::textureFactoryForImage(image);
@ -72,12 +77,8 @@ QString ThumbnailResponse::errorString() const {
} }
void ThumbnailResponse::cancel() { void ThumbnailResponse::cancel() {
QWriteLocker _(&lock); QMetaObject::invokeMethod(this, &ThumbnailResponse::doCancel,
if (job) { Qt::QueuedConnection);
job->abandon();
job = nullptr;
}
errorStr = "Image request has been cancelled";
} }
QQuickImageResponse* ImageProvider::requestImageResponse( QQuickImageResponse* ImageProvider::requestImageResponse(

View File

@ -2,23 +2,29 @@
#define IMAGEPROVIDER_H #define IMAGEPROVIDER_H
#pragma once #pragma once
#include <jobs/mediathumbnailjob.h>
#include <QThreadPool>
#include <QtCore/QAtomicPointer>
#include <QtCore/QReadWriteLock>
#include <QtQuick/QQuickAsyncImageProvider> #include <QtQuick/QQuickAsyncImageProvider>
#include <connection.h>
#include <jobs/mediathumbnailjob.h>
#include <QtCore/QReadWriteLock>
#include <QtCore/QAtomicPointer>
namespace QMatrixClient { namespace QMatrixClient {
class Connection; class Connection;
} }
class ThumbnailResponse : public QQuickImageResponse { class ThumbnailResponse : public QQuickImageResponse {
Q_OBJECT
public: public:
ThumbnailResponse(QMatrixClient::Connection* c, QString mediaId, ThumbnailResponse(QMatrixClient::Connection* c, QString mediaId,
const QSize& requestedSize); const QSize& requestedSize);
~ThumbnailResponse() override = default; ~ThumbnailResponse() override = default;
private slots:
void startRequest(); void startRequest();
void prepareResult();
void doCancel();
private: private:
QMatrixClient::Connection* c; QMatrixClient::Connection* c;
@ -28,9 +34,8 @@ class ThumbnailResponse : public QQuickImageResponse {
QImage image; QImage image;
QString errorStr; QString errorStr;
mutable QReadWriteLock lock; mutable QReadWriteLock lock; // Guards ONLY these two members above
void prepareResult();
QQuickTextureFactory* textureFactory() const override; QQuickTextureFactory* textureFactory() const override;
QString errorString() const override; QString errorString() const override;
void cancel() override; void cancel() override;
@ -41,7 +46,7 @@ class ImageProvider : public QObject, public QQuickAsyncImageProvider {
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(), QQuickAsyncImageProvider() {} explicit ImageProvider() = default;
QQuickImageResponse* requestImageResponse( QQuickImageResponse* requestImageResponse(
const QString& id, const QSize& requestedSize) override; const QString& id, const QSize& requestedSize) override;
@ -49,6 +54,7 @@ class ImageProvider : public QObject, public QQuickAsyncImageProvider {
QMatrixClient::Connection* connection() { return m_connection; } QMatrixClient::Connection* connection() { return m_connection; }
void setConnection(QMatrixClient::Connection* connection) { void setConnection(QMatrixClient::Connection* connection) {
m_connection.store(connection); m_connection.store(connection);
emit connectionChanged();
} }
signals: signals: