Compare commits

...

1 Commits

Author SHA1 Message Date
Tobias Fella
de3d3abc53 Port away from and remove MatrixImageProvider
The functionality was redundant with the custom QNAM extension libQuotient has
2023-12-02 22:30:18 +01:00
23 changed files with 26 additions and 242 deletions

View File

@@ -16,8 +16,6 @@ add_library(neochat STATIC
models/customemojimodel.h
clipboard.cpp
clipboard.h
matriximageprovider.cpp
matriximageprovider.h
models/messageeventmodel.cpp
models/messageeventmodel.h
models/messagefiltermodel.cpp

View File

@@ -5,6 +5,7 @@
#include <QIcon>
#include <QNetworkDiskCache>
#include <QNetworkProxyFactory>
#include <QNetworkReply>
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlContext>
@@ -40,7 +41,6 @@
#include "colorschemer.h"
#include "controller.h"
#include "logger.h"
#include "matriximageprovider.h"
#include "neochatconfig.h"
#include "roommanager.h"
#include "windowcontroller.h"
@@ -247,7 +247,6 @@ int main(int argc, char *argv[])
});
}
engine.addImageProvider(QLatin1String("mxc"), MatrixImageProvider::create(&engine, &engine));
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
engine.load(QUrl(QStringLiteral("qrc:/org/kde/neochat/qml/main.qml")));

View File

@@ -1,127 +0,0 @@
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2019 Kitsune Ral <kitsune-ral@users.sf.net>
// SPDX-License-Identifier: GPL-3.0-only
#include "matriximageprovider.h"
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QStandardPaths>
#include <QThread>
#include <KLocalizedString>
#include "controller.h"
#include "neochatconnection.h"
#include <Quotient/connection.h>
using namespace Quotient;
ThumbnailResponse::ThumbnailResponse(QString id, QSize size, NeoChatConnection *connection)
: mediaId(std::move(id))
, requestedSize(size)
, localFile(QStringLiteral("%1/image_provider/%2-%3x%4.png")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
mediaId,
QString::number(requestedSize.width()),
QString::number(requestedSize.height())))
, m_connection(connection)
, errorStr("Image request hasn't started"_ls)
{
if (requestedSize.isEmpty()) {
requestedSize.setHeight(100);
requestedSize.setWidth(100);
}
if (mediaId.count(QLatin1Char('/')) != 1) {
if (mediaId.startsWith(QLatin1Char('/'))) {
mediaId = mediaId.mid(1);
} else {
errorStr = i18n("Media id '%1' doesn't follow server/mediaId pattern", mediaId);
Q_EMIT finished();
return;
}
}
mediaId = mediaId.split(QLatin1Char('?'))[0];
QImage cachedImage;
if (cachedImage.load(localFile)) {
image = cachedImage;
errorStr.clear();
Q_EMIT finished();
return;
}
if (!m_connection) {
qWarning() << "Current connection is null";
return;
}
// Execute a request on the main thread asynchronously
moveToThread(m_connection->thread());
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, Qt::QueuedConnection);
}
void ThumbnailResponse::startRequest()
{
if (!m_connection) {
return;
}
// Runs in the main thread, not QML thread
Q_ASSERT(QThread::currentThread() == m_connection->thread());
job = m_connection->getThumbnail(mediaId, requestedSize);
// Connect to any possible outcome including abandonment
// to make sure the QML thread is not left stuck forever.
connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult);
}
void ThumbnailResponse::prepareResult()
{
Q_ASSERT(QThread::currentThread() == job->thread());
Q_ASSERT(job->error() != BaseJob::Pending);
{
QWriteLocker _(&lock);
if (job->error() == BaseJob::Success) {
image = job->thumbnail();
QString localPath = QFileInfo(localFile).absolutePath();
QDir dir;
if (!dir.exists(localPath)) {
dir.mkpath(localPath);
}
image.save(localFile);
errorStr.clear();
} else if (job->error() == BaseJob::Abandoned) {
errorStr = i18n("Image request has been cancelled");
// qDebug() << "ThumbnailResponse: cancelled for" << mediaId;
} else {
errorStr = job->errorString();
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-" << errorStr;
}
job = nullptr;
}
Q_EMIT finished();
}
QQuickTextureFactory *ThumbnailResponse::textureFactory() const
{
QReadLocker _(&lock);
return QQuickTextureFactory::textureFactoryForImage(image);
}
QString ThumbnailResponse::errorString() const
{
QReadLocker _(&lock);
return errorStr;
}
QQuickImageResponse *MatrixImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
{
return new ThumbnailResponse(id, requestedSize, m_connection);
}
#include "moc_matriximageprovider.cpp"

View File

@@ -1,80 +0,0 @@
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2019 Kitsune Ral <kitsune-ral@users.sf.net>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <QQuickAsyncImageProvider>
#include <Quotient/jobs/mediathumbnailjob.h>
#include <QReadWriteLock>
class NeoChatConnection;
/**
* @class ThumbnailResponse
*
* A QQuickImageResponse for an mxc image.
*
* @sa QQuickImageResponse
*/
class ThumbnailResponse : public QQuickImageResponse
{
Q_OBJECT
public:
explicit ThumbnailResponse(QString mediaId, QSize requestedSize, NeoChatConnection *m_connection);
~ThumbnailResponse() override = default;
private Q_SLOTS:
void startRequest();
void prepareResult();
private:
QString mediaId;
QSize requestedSize;
const QString localFile;
Quotient::MediaThumbnailJob *job = nullptr;
NeoChatConnection *m_connection;
QImage image;
QString errorStr;
mutable QReadWriteLock lock; // Guards ONLY these two members above
QQuickTextureFactory *textureFactory() const override;
QString errorString() const override;
};
/**
* @class MatrixImageProvider
*
* A QQuickAsyncImageProvider for mxc images.
*
* @sa QQuickAsyncImageProvider
*/
class MatrixImageProvider : public QQuickAsyncImageProvider
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(NeoChatConnection *connection MEMBER m_connection)
public:
static MatrixImageProvider *create(QQmlEngine *engine, QJSEngine *)
{
static MatrixImageProvider *instance = new MatrixImageProvider;
engine->setObjectOwnership(instance, QQmlEngine::CppOwnership);
return instance;
}
/**
* @brief Return a job to provide the image with the given ID.
*
* @sa QQuickAsyncImageProvider::requestImageResponse
*/
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
private:
NeoChatConnection *m_connection = nullptr;
MatrixImageProvider() = default;
};

View File

@@ -141,12 +141,10 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
return QStringLiteral("Unknown User");
}
if (role == AvatarRole) {
auto avatarUrl = user.avatarUrl;
if (avatarUrl.isEmpty()) {
return QString();
if (user.avatarUrl.isEmpty() || user.avatarUrl.scheme() != QStringLiteral("mxc")) {
return QUrl();
}
return avatarUrl.url().remove(0, 6);
return m_connection->makeMediaUrl(user.avatarUrl);
}
if (role == UserIDRole) {
return user.userId;

View File

@@ -452,17 +452,18 @@ QVariantMap NeoChatRoom::getUser(User *user) const
};
}
QString NeoChatRoom::avatarMediaId() const
QUrl NeoChatRoom::avatarMediaId() const
{
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
return avatar;
return connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(avatar)));
}
// Use the first (excluding self) user's avatar for direct chats
const auto dcUsers = directChatUsers();
for (const auto u : dcUsers) {
if (u != localUser()) {
return u->avatarMediaId(this);
const auto &avatar = u->avatarMediaId(this);
return avatar.isEmpty() ? QUrl() : connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(avatar)));
}
}

View File

@@ -110,7 +110,7 @@ class NeoChatRoom : public Quotient::Room
/**
* @brief The avatar image to be used for the room.
*/
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
Q_PROPERTY(QUrl avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
/**
* @brief Get a user object for the other person in a direct chat.
@@ -501,7 +501,7 @@ public:
*/
[[nodiscard]] QString subtitleText();
[[nodiscard]] QString avatarMediaId() const;
[[nodiscard]] QUrl avatarMediaId() const;
Quotient::User *directChatRemoteUser() const;

View File

@@ -32,8 +32,7 @@ FormCard.FormCardPage {
implicitHeight: implicitWidth
padding: 0
source: root.connection && root.connection.localUser.avatarMediaId ? ("image://mxc/" + root.connection.localUser.avatarMediaId) : ""
source: root.connection && root.connection.localUser.avatarMediaId ? root.connection.makeMediaUrl("mxc://" + root.connection.localUser.avatarMediaId) : ""
name: root.connection.localUser.displayName
onClicked: {

View File

@@ -38,7 +38,7 @@ FormCard.FormCardPage {
contentItem: RowLayout {
KirigamiComponents.Avatar {
name: accountDelegate.connection.localUser.displayName
source: accountDelegate.connection.localUser.avatarMediaId ? ("image://mxc/" + accountDelegate.connection.localUser.avatarMediaId) : ""
source: accountDelegate.connection.localUser.avatarMediaId ? accountDelegate.connection.makeMediaUrl("mxc://" + accountDelegate.connection.localUser.avatarMediaId) : ""
Layout.rightMargin: Kirigami.Units.largeSpacing
implicitWidth: Kirigami.Units.iconSizes.medium

View File

@@ -33,7 +33,7 @@ QQC2.ItemDelegate {
visible: root.categoryVisible || filterText.length > 0 || Config.mergeRoomList
contentItem: KirigamiComponents.Avatar {
source: root.avatar ? `image://mxc/${root.avatar}` : ""
source: root.avatar
name: root.displayName
sourceSize {

View File

@@ -166,7 +166,7 @@ Loader {
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
id: avatar
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
source: room.avatarMediaId
name: room.displayName
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3

View File

@@ -38,7 +38,7 @@ ColumnLayout {
contentItem: KirigamiComponents.Avatar {
name: root.room ? root.room.displayName : ""
source: root.room ? ("image://mxc/" + root.room.avatarMediaId) : ""
source: root.room ? root.room.avatarMediaId : ""
Rectangle {
visible: root.room.usesEncryption

View File

@@ -35,7 +35,7 @@ FormCard.FormCardPage {
id: avatar
Layout.alignment: Qt.AlignRight
name: room.name
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
source: room.avatarMediaId
implicitWidth: Kirigami.Units.iconSizes.enormous
implicitHeight: Kirigami.Units.iconSizes.enormous
}

View File

@@ -31,7 +31,7 @@ ColumnLayout {
Layout.preferredHeight: Kirigami.Units.iconSizes.large
name: root.room ? root.room.displayName : ""
source: root.room && root.room.avatarMediaId ? ("image://mxc/" + root.room.avatarMediaId) : ""
source: root.room ? root.room.avatarMediaId : ""
Rectangle {
visible: room.usesEncryption

View File

@@ -85,7 +85,7 @@ Kirigami.ScrollablePage {
KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: delegate.avatar ? ("image://mxc/" + delegate.avatar) : ""
source: delegate.avatar
name: delegate.name
}

View File

@@ -179,7 +179,7 @@ FormCard.FormCardPage {
KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: userListItem.avatar ? ("image://" + userListItem.avatar) : ""
source: userListItem.avatar
name: userListItem.name
}

View File

@@ -55,7 +55,7 @@ Delegates.RoundedItemDelegate {
spacing: Kirigami.Units.largeSpacing
Components.Avatar {
source: root.avatar ? "image://mxc/" + root.avatar : ""
source: root.avatar
name: root.displayName
visible: Config.showAvatarInRoomDrawer
implicitHeight: Kirigami.Units.gridUnit + (Config.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2)

View File

@@ -98,7 +98,7 @@ QQC2.Control {
Layout.maximumHeight: width - Kirigami.Units.smallSpacing
text: displayName
source: avatar ? ("image://mxc/" + avatar) : ""
source: spaceDelegate.avatar
onSelected: root.selectedSpaceId = roomId
checked: root.selectedSpaceId === roomId

View File

@@ -92,7 +92,7 @@ Loader {
KirigamiComponents.Avatar {
id: avatar
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
source: room.avatarMediaId
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.alignment: Qt.AlignTop

View File

@@ -76,7 +76,7 @@ Kirigami.ScrollablePage {
KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: delegate.avatar ? ("image://mxc/" + delegate.avatar) : ""
source: delegate.avatar
name: delegate.name
}

View File

@@ -30,7 +30,7 @@ TimelineDelegate {
Layout.preferredHeight: Kirigami.Units.iconSizes.large
name: root.room ? root.room.displayName : ""
source: root.room && root.room.avatarMediaId ? ("image://mxc/" + root.room.avatarMediaId) : ""
source: root.room ? root.room.avatarMediaId : ""
Rectangle {
visible: room.usesEncryption

View File

@@ -63,9 +63,7 @@ RowLayout {
text: i18n("Edit this account")
contentItem: KirigamiComponents.Avatar {
readonly property string mediaId: root.connection.localUser.avatarMediaId
source: mediaId ? ("image://mxc/" + mediaId) : ""
source: root.connection.localUser.avatarMediaId ? root.connection.makeMediaUrl("mxc://" + root.connection.localUser.avatarMediaId) : ""
name: root.connection.localUser.displayName ?? root.connection.localUser.id
}
}
@@ -249,7 +247,7 @@ RowLayout {
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
}
source: userDelegate.connection.localUser.avatarMediaId ? ("image://mxc/" + userDelegate.connection.localUser.avatarMediaId) : ""
source: userDelegate.connection.localUser.avatarMediaId ? userDelegate.connection.makeMediaUrl("mxc://" + userDelegate.connection.localUser.avatarMediaId) : ""
name: userDelegate.connection.localUser.displayName ?? userDelegate.connection.localUser.id
}

View File

@@ -50,7 +50,6 @@ Kirigami.ApplicationWindow {
onConnectionChanged: {
CustomEmojiModel.connection = root.connection
MatrixImageProvider.connection = root.connection
RoomManager.connection = root.connection
SpaceHierarchyCache.connection = root.connection
}
@@ -216,7 +215,6 @@ Kirigami.ApplicationWindow {
Component.onCompleted: {
CustomEmojiModel.connection = root.connection
MatrixImageProvider.connection = root.connection
RoomManager.connection = root.connection
SpaceHierarchyCache.connection = root.connection
WindowController.setBlur(pageStack, Config.blur && !Config.compactLayout);