From 68f167995e7ba71a6f2e556a7a0eab3d234e2d1a Mon Sep 17 00:00:00 2001 From: Jonah BrĂ¼chert Date: Fri, 9 Sep 2022 23:15:10 +0200 Subject: Implement XEP-0448: Stateless File Sharing This adds a file sharing manager that is capable of using multiple back ends. Currently implemented are a normal HTTP File Upload backend and an encrypted HTTP File Upload. Jingle File Transfer could be implemented later. Co-authored-by: Linus Jahn --- src/client/QXmppFileSharingManager.cpp | 210 +++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 src/client/QXmppFileSharingManager.cpp (limited to 'src/client/QXmppFileSharingManager.cpp') diff --git a/src/client/QXmppFileSharingManager.cpp b/src/client/QXmppFileSharingManager.cpp new file mode 100644 index 00000000..2daf980a --- /dev/null +++ b/src/client/QXmppFileSharingManager.cpp @@ -0,0 +1,210 @@ +// SPDX-FileCopyrightText: 2022 Jonah BrĂ¼chert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppFileSharingManager.h" + +#include "QXmppBitsOfBinaryContentId.h" +#include "QXmppBitsOfBinaryData.h" +#include "QXmppClient.h" +#include "QXmppDownload.h" +#include "QXmppFileMetadata.h" +#include "QXmppFileShare.h" +#include "QXmppFutureUtils_p.h" +#include "QXmppHashing_p.h" +#include "QXmppThumbnail.h" +#include "QXmppUpload.h" +#include "QXmppUploadRequestManager.h" + +#include + +#include +#include +#include +#include +#include +#include + +using namespace QXmpp; +using namespace QXmpp::Private; + +/// +/// \class QXmppFileSharingProvider +/// +/// Base class for Stateless File Sharing providers +/// +/// A provider defines the way that files can be uploaded and downloaded. +/// +/// An example is the QXmppHttpFileSharingProvider, which uses HTTP File Upload. +/// +/// \since QXmpp 1.5 +/// + +/// +/// \class QXmppFileSharingManager +/// +/// The file sharing manager allows to easily send and retrieve files in a chat. +/// +/// \since QXmpp 1.5 +/// + +QXmppFileSharingManager::QXmppFileSharingManager() + : m_metadataGenerator([](std::unique_ptr &&) -> QFuture> { + return makeReadyFuture(std::make_shared()); + }) +{ +} + +/// +/// \typedef QXmppFileSharingManager::MetadataGenerator +/// +/// The function signature of a metadata generator function +/// + +/// +/// \brief Register a function that is called when metadata needs to be gererated for a file. +/// +/// The function is passed a QIODevice, so if you need the path of the file on disk, +/// you can dynamically cast it to a QFile and access the fileName. +/// When doing that, make sure to check the result, +/// as in the future this function might be passed other QIODevices than QFile. +/// +void QXmppFileSharingManager::setMetadataGenerator(MetadataGenerator &&generator) +{ + m_metadataGenerator = std::move(generator); +} + +/// +/// \brief Upload a file in a way that it can be attached to a message. +/// \param provider The provider class decides how the file is uploaded +/// \param filePath Path to a file that should be uploaded +/// \param description Optional description of the file +/// \return An object that allows to track the progress of the upload. +/// Once the upload is finished, the finished signal is emitted on it. +/// +std::shared_ptr QXmppFileSharingManager::sendFile(std::shared_ptr provider, + const QString &filePath, + const std::optional &description) +{ + std::shared_ptr upload; + + QFileInfo fileInfo(filePath); + auto metadata = QXmppFileMetadata::fromFileInfo(fileInfo); + if (description) { + metadata.setDescription(description); + } + + auto openFile = [=]() -> std::unique_ptr { + auto device = std::make_unique(fileInfo.absoluteFilePath()); + if (!device->open(QIODevice::ReadOnly)) { + Q_EMIT upload->finished(QXmppError::fromIoDevice(*device)); + } + return device; + }; + + auto metadataFuture = m_metadataGenerator(openFile()); + auto hashesFuture = calculateHashes(openFile(), { HashAlgorithm::Sha256 }); + + upload = provider->uploadFile(openFile(), metadata); + upload->metadata = metadata; + + connect(upload.get(), &QXmppUpload::uploadFinished, this, [=](auto &&uploadResult) { + if (std::holds_alternative(uploadResult)) { + auto source = std::get(uploadResult); + await(metadataFuture, this, [=](auto &&result) mutable { + if (result->dimensions) { + upload->metadata.setWidth(result->dimensions->width()); + upload->metadata.setHeight(result->dimensions->height()); + } + if (result->length) { + upload->metadata.setLength(*result->length); + } + + QVector thumbnails; + thumbnails.reserve(result->thumbnails.size()); + QXmppBitsOfBinaryDataList dataBlobs; + dataBlobs.reserve(result->thumbnails.size()); + + for (const auto &metadataThumb : result->thumbnails) { + auto bobData = QXmppBitsOfBinaryData::fromByteArray(metadataThumb.data); + bobData.setContentType(metadataThumb.mimeType); + + QXmppThumbnail thumbnail; + thumbnail.setHeight(metadataThumb.height); + thumbnail.setWidth(metadataThumb.width); + thumbnail.setMediaType(metadataThumb.mimeType); + thumbnail.setUri(bobData.cid().toCidUrl()); + + thumbnails.append(std::move(thumbnail)); + dataBlobs.append(std::move(bobData)); + } + upload->metadata.setThumbnails(thumbnails); + + await(hashesFuture, this, [=, dataBlobs = std::move(dataBlobs)](const auto &hashResult) mutable { + QVector hashes; + const auto hashValue = hashResult->result; + if (std::holds_alternative(hashValue)) { + Q_EMIT upload->finished(Cancelled {}); + } else if (std::holds_alternative>(hashValue)) { + auto hashesVector = std::get>(hashValue); + std::transform(hashesVector.begin(), hashesVector.end(), + std::back_inserter(hashes), [](auto &&hash) { + return hash; + }); + + upload->metadata.setHashes(hashes); + + QXmppFileShare fs; + fs.setMetadata(upload->metadata); + fs.addSource(std::move(source)); + + Q_EMIT upload->finished(QXmppUpload::FileResult { fs, std::move(dataBlobs) }); + } else if (std::holds_alternative(hashValue)) { + Q_EMIT upload->finished(std::get(hashValue)); + } + }); + }); + } else if (std::holds_alternative(uploadResult)) { + Q_EMIT upload->finished(std::get(uploadResult)); + } + }); + + return upload; +} + +/// +/// \brief Download a file from a QXmppFileShare +/// +/// \warning This function currently does not check the hash of the downloaded file. +/// +/// Make sure to register the provider +/// that handles the sources used in this file share before calling this function. +/// +/// \param fileShare The file share object which you want to download +/// \param output An open QIODevice that the data should be written into. +/// In most cases, a QFile +/// \return An object that allows to track the progress of the download. +/// +std::shared_ptr QXmppFileSharingManager::downloadFile( + const QXmppFileShare &fileShare, + std::unique_ptr &&output) +{ + std::shared_ptr download; + fileShare.visitSources([&](const std::any &source) { + std::type_index index(source.type()); + try { + auto provider = m_providers.at(index); + download = provider->downloadFile(source, std::move(output)); + return true; + } catch (const std::out_of_range &) { + return false; + } + }); + + return download; +} + +void QXmppFileSharingManager::internalRegisterProvider(std::type_index index, std::shared_ptr provider) +{ + m_providers.insert_or_assign(index, provider); +} -- cgit v1.2.3