mirror of https://github.com/Nheko-Reborn/nheko
parent
09c041c8ac
commit
ef068ac2b3
@ -0,0 +1,164 @@ |
|||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "MxcAnimatedImage.h" |
||||||
|
|
||||||
|
#include <QDir> |
||||||
|
#include <QFileInfo> |
||||||
|
#include <QMimeDatabase> |
||||||
|
#include <QQuickWindow> |
||||||
|
#include <QSGImageNode> |
||||||
|
#include <QStandardPaths> |
||||||
|
|
||||||
|
#include "EventAccessors.h" |
||||||
|
#include "Logging.h" |
||||||
|
#include "MatrixClient.h" |
||||||
|
#include "timeline/TimelineModel.h" |
||||||
|
|
||||||
|
void |
||||||
|
MxcAnimatedImage::startDownload() |
||||||
|
{ |
||||||
|
if (!room_) |
||||||
|
return; |
||||||
|
if (eventId_.isEmpty()) |
||||||
|
return; |
||||||
|
|
||||||
|
auto event = room_->eventById(eventId_); |
||||||
|
if (!event) { |
||||||
|
nhlog::ui()->error("Failed to load media for event {}, event not found.", |
||||||
|
eventId_.toStdString()); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
QByteArray mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)).toUtf8(); |
||||||
|
|
||||||
|
animatable_ = QMovie::supportedFormats().contains(mimeType.split('/').back()); |
||||||
|
animatableChanged(); |
||||||
|
|
||||||
|
if (!animatable_) |
||||||
|
return; |
||||||
|
|
||||||
|
QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); |
||||||
|
QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event)); |
||||||
|
|
||||||
|
auto encryptionInfo = mtx::accessors::file(*event); |
||||||
|
|
||||||
|
// If the message is a link to a non mxcUrl, don't download it
|
||||||
|
if (!mxcUrl.startsWith("mxc://")) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix(); |
||||||
|
|
||||||
|
const auto url = mxcUrl.toStdString(); |
||||||
|
const auto name = QString(mxcUrl).remove("mxc://"); |
||||||
|
QFileInfo filename(QString("%1/media_cache/media/%2.%3") |
||||||
|
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) |
||||||
|
.arg(name) |
||||||
|
.arg(suffix)); |
||||||
|
if (QDir::cleanPath(name) != name) { |
||||||
|
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
QDir().mkpath(filename.path()); |
||||||
|
|
||||||
|
QPointer<MxcAnimatedImage> self = this; |
||||||
|
|
||||||
|
auto processBuffer = [this, mimeType, encryptionInfo, self](QIODevice &device) { |
||||||
|
if (!self) |
||||||
|
return; |
||||||
|
|
||||||
|
if (buffer.isOpen()) { |
||||||
|
movie.stop(); |
||||||
|
movie.setDevice(nullptr); |
||||||
|
buffer.close(); |
||||||
|
} |
||||||
|
|
||||||
|
if (encryptionInfo) { |
||||||
|
QByteArray ba = device.readAll(); |
||||||
|
std::string temp(ba.constData(), ba.size()); |
||||||
|
temp = mtx::crypto::to_string( |
||||||
|
mtx::crypto::decrypt_file(temp, encryptionInfo.value())); |
||||||
|
buffer.setData(temp.data(), temp.size()); |
||||||
|
} else { |
||||||
|
buffer.setData(device.readAll()); |
||||||
|
} |
||||||
|
buffer.open(QIODevice::ReadOnly); |
||||||
|
buffer.reset(); |
||||||
|
|
||||||
|
QTimer::singleShot(0, this, [this, mimeType] { |
||||||
|
nhlog::ui()->info("Playing movie with size: {}, {}", |
||||||
|
buffer.bytesAvailable(), |
||||||
|
buffer.isOpen()); |
||||||
|
movie.setFormat(mimeType); |
||||||
|
movie.setDevice(&buffer); |
||||||
|
movie.start(); |
||||||
|
emit loadedChanged(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
if (filename.isReadable()) { |
||||||
|
QFile f(filename.filePath()); |
||||||
|
if (f.open(QIODevice::ReadOnly)) { |
||||||
|
processBuffer(f); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
http::client()->download( |
||||||
|
url, |
||||||
|
[filename, url, processBuffer](const std::string &data, |
||||||
|
const std::string &, |
||||||
|
const std::string &, |
||||||
|
mtx::http::RequestErr err) { |
||||||
|
if (err) { |
||||||
|
nhlog::net()->warn("failed to retrieve media {}: {} {}", |
||||||
|
url, |
||||||
|
err->matrix_error.error, |
||||||
|
static_cast<int>(err->status_code)); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
QFile file(filename.filePath()); |
||||||
|
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) |
||||||
|
return; |
||||||
|
|
||||||
|
QByteArray ba(data.data(), (int)data.size()); |
||||||
|
file.write(ba); |
||||||
|
file.close(); |
||||||
|
|
||||||
|
QBuffer buf(&ba); |
||||||
|
buf.open(QBuffer::ReadOnly); |
||||||
|
processBuffer(buf); |
||||||
|
} catch (const std::exception &e) { |
||||||
|
nhlog::ui()->warn("Error while saving file to: {}", e.what()); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
QSGNode * |
||||||
|
MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) |
||||||
|
{ |
||||||
|
imageDirty = false; |
||||||
|
QSGImageNode *n = static_cast<QSGImageNode *>(oldNode); |
||||||
|
if (!n) |
||||||
|
n = window()->createImageNode(); |
||||||
|
|
||||||
|
// n->setTexture(nullptr);
|
||||||
|
auto img = movie.currentImage(); |
||||||
|
if (!img.isNull()) |
||||||
|
n->setTexture(window()->createTextureFromImage(img)); |
||||||
|
else |
||||||
|
return nullptr; |
||||||
|
|
||||||
|
n->setSourceRect(img.rect()); |
||||||
|
n->setRect(QRect(0, 0, width(), height())); |
||||||
|
n->setFiltering(QSGTexture::Linear); |
||||||
|
n->setMipmapFiltering(QSGTexture::Linear); |
||||||
|
|
||||||
|
return n; |
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <QBuffer> |
||||||
|
#include <QMovie> |
||||||
|
#include <QObject> |
||||||
|
#include <QQuickItem> |
||||||
|
|
||||||
|
class TimelineModel; |
||||||
|
|
||||||
|
// This is an AnimatedImage, that can draw encrypted images
|
||||||
|
class MxcAnimatedImage : public QQuickItem |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED) |
||||||
|
Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged) |
||||||
|
Q_PROPERTY(bool animatable READ animatable NOTIFY animatableChanged) |
||||||
|
Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged) |
||||||
|
public: |
||||||
|
MxcAnimatedImage(QQuickItem *parent = nullptr) |
||||||
|
: QQuickItem(parent) |
||||||
|
{ |
||||||
|
connect(this, &MxcAnimatedImage::eventIdChanged, &MxcAnimatedImage::startDownload); |
||||||
|
connect(this, &MxcAnimatedImage::roomChanged, &MxcAnimatedImage::startDownload); |
||||||
|
connect(&movie, &QMovie::frameChanged, this, &MxcAnimatedImage::newFrame); |
||||||
|
setFlag(QQuickItem::ItemHasContents); |
||||||
|
// setAcceptHoverEvents(true);
|
||||||
|
} |
||||||
|
|
||||||
|
bool animatable() const { return animatable_; } |
||||||
|
bool loaded() const { return buffer.size() > 0; } |
||||||
|
QString eventId() const { return eventId_; } |
||||||
|
TimelineModel *room() const { return room_; } |
||||||
|
void setEventId(QString newEventId) |
||||||
|
{ |
||||||
|
if (eventId_ != newEventId) { |
||||||
|
eventId_ = newEventId; |
||||||
|
emit eventIdChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
void setRoom(TimelineModel *room) |
||||||
|
{ |
||||||
|
if (room_ != room) { |
||||||
|
room_ = room; |
||||||
|
emit roomChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QSGNode *updatePaintNode(QSGNode *oldNode, |
||||||
|
QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override; |
||||||
|
|
||||||
|
signals: |
||||||
|
void roomChanged(); |
||||||
|
void eventIdChanged(); |
||||||
|
void animatableChanged(); |
||||||
|
void loadedChanged(); |
||||||
|
|
||||||
|
private slots: |
||||||
|
void startDownload(); |
||||||
|
void newFrame(int frame) |
||||||
|
{ |
||||||
|
currentFrame = frame; |
||||||
|
imageDirty = true; |
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
TimelineModel *room_ = nullptr; |
||||||
|
QString eventId_; |
||||||
|
QString filename_; |
||||||
|
bool animatable_ = false; |
||||||
|
QBuffer buffer; |
||||||
|
QMovie movie; |
||||||
|
int currentFrame = 0; |
||||||
|
bool imageDirty = true; |
||||||
|
}; |
Loading…
Reference in new issue