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/QXmppDownload.cpp | 26 +++ src/client/QXmppDownload.h | 23 +++ .../QXmppEncryptedHttpFileSharingProvider.cpp | 123 ++++++++++++ src/client/QXmppEncryptedHttpFileSharingProvider.h | 33 ++++ src/client/QXmppFileSharingManager.cpp | 210 +++++++++++++++++++++ src/client/QXmppFileSharingManager.h | 77 ++++++++ src/client/QXmppFileSharingProvider.h | 52 +++++ src/client/QXmppFileTransfer.cpp | 43 +++++ src/client/QXmppFileTransfer.h | 31 +++ src/client/QXmppHttpFileSharingProvider.cpp | 207 ++++++++++++++++++++ src/client/QXmppHttpFileSharingProvider.h | 40 ++++ src/client/QXmppHttpUploadManager.cpp | 9 +- src/client/QXmppUpload.cpp | 47 +++++ src/client/QXmppUpload.h | 47 +++++ 14 files changed, 961 insertions(+), 7 deletions(-) create mode 100644 src/client/QXmppDownload.cpp create mode 100644 src/client/QXmppDownload.h create mode 100644 src/client/QXmppEncryptedHttpFileSharingProvider.cpp create mode 100644 src/client/QXmppEncryptedHttpFileSharingProvider.h create mode 100644 src/client/QXmppFileSharingManager.cpp create mode 100644 src/client/QXmppFileSharingManager.h create mode 100644 src/client/QXmppFileSharingProvider.h create mode 100644 src/client/QXmppFileTransfer.cpp create mode 100644 src/client/QXmppFileTransfer.h create mode 100644 src/client/QXmppHttpFileSharingProvider.cpp create mode 100644 src/client/QXmppHttpFileSharingProvider.h create mode 100644 src/client/QXmppUpload.cpp create mode 100644 src/client/QXmppUpload.h (limited to 'src/client') diff --git a/src/client/QXmppDownload.cpp b/src/client/QXmppDownload.cpp new file mode 100644 index 00000000..0ece1906 --- /dev/null +++ b/src/client/QXmppDownload.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppDownload.h" + +/// +/// \class QXmppDownload +/// +/// \brief Provides progress of stateless file sharing uploads. +/// +/// \since QXmpp 1.5 +/// + +/// +/// \typedef QXmppDownload::Result +/// +/// \brief Contains QXmpp::Success (successfully finished), QXmpp::Cancelled (manually cancelled) +/// or QXmppError (an error occured while downloading). +/// + +/// +/// \fn QXmppDownload::finished +/// +/// Emitted when the download has finished. +/// diff --git a/src/client/QXmppDownload.h b/src/client/QXmppDownload.h new file mode 100644 index 00000000..3256aaea --- /dev/null +++ b/src/client/QXmppDownload.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPDOWNLOAD_H +#define QXMPPDOWNLOAD_H + +#include "QXmppError.h" +#include "QXmppFileTransfer.h" + +#include + +class QXMPP_EXPORT QXmppDownload : public QXmppFileTransfer +{ + Q_OBJECT +public: + using Result = std::variant; + Q_SIGNAL void finished(QXmppDownload::Result); +}; + +Q_DECLARE_METATYPE(QXmppDownload::Result); + +#endif // QXMPPDOWNLOAD_H diff --git a/src/client/QXmppEncryptedHttpFileSharingProvider.cpp b/src/client/QXmppEncryptedHttpFileSharingProvider.cpp new file mode 100644 index 00000000..b6e82621 --- /dev/null +++ b/src/client/QXmppEncryptedHttpFileSharingProvider.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppEncryptedHttpFileSharingProvider.h" + +#include "QXmppClient.h" +#include "QXmppFileEncryption.h" +#include "QXmppHttpUploadManager.h" +#include "QXmppUpload.h" +#include "QXmppUtils.h" + +#include "QcaInitializer_p.h" +#include + +using namespace QXmpp; +using namespace QXmpp::Private; + +class QXmppSfsEncryptedHttpUpload : public QXmppUpload +{ + Q_OBJECT + +public: + QXmppSfsEncryptedHttpUpload(std::shared_ptr &&httpUpload, const QByteArray &key, const QByteArray &iv) + : inner(std::move(httpUpload)) + { + connect(inner.get(), &QXmppHttpUpload::finished, this, [=](const QXmppHttpUpload::Result &result) { + Q_EMIT uploadFinished(std::visit([=](auto &&value) -> QXmpp::Private::UploadResult { + using T = std::decay_t; + if constexpr (std::is_same_v) { + QXmppEncryptedFileSource encryptedSource; + encryptedSource.setKey(key); + encryptedSource.setIv(iv); + encryptedSource.setHttpSources({ QXmppHttpFileSource(value) }); + return std::any(encryptedSource); + } else if constexpr (std::is_same_v) { + return Cancelled {}; + } else if constexpr (std::is_same_v) { + return value; + } + }, + result)); + }); + } + + float progress() override { return inner->progress(); } + void cancel() override { inner->cancel(); } + bool isFinished() override { return inner->isFinished(); } + quint64 bytesTransferred() override { return inner->bytesSent(); } + quint64 bytesTotal() override { return inner->bytesTotal(); } + +private: + std::shared_ptr inner; +}; + +/// +/// \class QXmppEncryptedHttpFileSharingProvider +/// +/// Support for storing files encrypted on an HTTP server +/// +/// \since QXmpp 1.5 +/// + +class QXmppEncryptedHttpFileSharingProviderPrivate +{ +public: + QXmpp::Private::QcaInitializer init; + QXmppHttpUploadManager *manager; + QXmppHttpFileSharingProvider *httpProvider; +}; + +/// +/// \brief Create a new QXmppEncryptedHttpFileSharingProvider +/// \param client +/// \param netManager QNetworkAccessManager that can be reused all over your application. +/// +QXmppEncryptedHttpFileSharingProvider::QXmppEncryptedHttpFileSharingProvider(QXmppClient *client, QNetworkAccessManager *netManager) + : d(std::make_unique()) +{ + qRegisterMetaType(); + Q_ASSERT(client); + d->manager = client->findExtension(); + Q_ASSERT(d->manager); + d->httpProvider = new QXmppHttpFileSharingProvider(client, netManager); +} + +QXmppEncryptedHttpFileSharingProvider::~QXmppEncryptedHttpFileSharingProvider() = default; + +std::shared_ptr QXmppEncryptedHttpFileSharingProvider::downloadFile(const std::any &source, std::unique_ptr &&target) +{ + QXmppEncryptedFileSource encryptedSource; + try { + encryptedSource = std::any_cast(source); + } catch (const std::bad_any_cast &) { + qFatal("QXmppEncryptedHttpFileSharingProvider::downloadFile can only handle QXmppEncryptedFileSource sources"); + } + + auto httpSource = encryptedSource.httpSources().front(); + auto output = std::make_unique(std::move(target), encryptedSource.cipher(), encryptedSource.iv(), encryptedSource.key()); + return d->httpProvider->downloadFile(httpSource, std::move(output)); +} + +std::shared_ptr QXmppEncryptedHttpFileSharingProvider::uploadFile( + std::unique_ptr data, + const QXmppFileMetadata &info) +{ + auto cipher = Aes256CbcPkcs7; + auto key = Encryption::generateKey(cipher); + auto iv = Encryption::generateInitializationVector(cipher); + + auto encDevice = std::make_unique(std::move(data), cipher, key, iv); + auto encryptedSize = encDevice->size(); + + auto upload = d->manager->uploadFile( + std::move(encDevice), + QXmppUtils::generateStanzaHash(10), + QMimeDatabase().mimeTypeForName("application/octet-stream"), + encryptedSize); + + return std::make_shared(std::move(upload), key, iv); +} + +#include "QXmppEncryptedHttpFileSharingProvider.moc" diff --git a/src/client/QXmppEncryptedHttpFileSharingProvider.h b/src/client/QXmppEncryptedHttpFileSharingProvider.h new file mode 100644 index 00000000..6c43d8cd --- /dev/null +++ b/src/client/QXmppEncryptedHttpFileSharingProvider.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPENCRYPTEDHTTPFILESHARINGPROVIDER_H +#define QXMPPENCRYPTEDHTTPFILESHARINGPROVIDER_H + +#include "QXmppEncryptedFileSource.h" +#include "QXmppHttpFileSharingProvider.h" + +class QXmppEncryptedHttpFileSharingProviderPrivate; + +class QXMPP_EXPORT QXmppEncryptedHttpFileSharingProvider : public QXmppFileSharingProvider +{ +public: + /// \cond + using SourceType = QXmppEncryptedFileSource; + /// \endcond + + QXmppEncryptedHttpFileSharingProvider(QXmppClient *client, QNetworkAccessManager *netManager); + ~QXmppEncryptedHttpFileSharingProvider() override; + + auto downloadFile(const std::any &source, + std::unique_ptr &&target) -> std::shared_ptr override; + auto uploadFile( + std::unique_ptr data, + const QXmppFileMetadata &info) -> std::shared_ptr override; + +private: + std::unique_ptr d; +}; + +#endif // QXMPPENCRYPTEDHTTPFILESHARINGPROVIDER_H 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); +} diff --git a/src/client/QXmppFileSharingManager.h b/src/client/QXmppFileSharingManager.h new file mode 100644 index 00000000..a7f4c5dd --- /dev/null +++ b/src/client/QXmppFileSharingManager.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPFILESHARINGMANAGER_H +#define QXMPPFILESHARINGMANAGER_H + +#include "QXmppClientExtension.h" +#include "QXmppFileShare.h" +#include "QXmppFileSharingProvider.h" +#include "QXmppGlobal.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +class QIODevice; +class QXmppFileMetadata; + +class QXMPP_EXPORT QXmppFileSharingManager : public QXmppClientExtension +{ +public: + struct MetadataThumbnail + { + uint32_t width; + uint32_t height; + QByteArray data; + QMimeType mimeType; + }; + + struct MetadataGeneratorResult + { + std::optional dimensions; + std::optional length; + QVector thumbnails; + std::unique_ptr dataDevice; + }; + + using MetadataGenerator = std::function>(std::unique_ptr)>; + + QXmppFileSharingManager(); + + void setMetadataGenerator(MetadataGenerator &&generator); + + /// + /// \brief Register a provider for automatic downloads + /// \param manager A shared_ptr to a QXmppFileSharingProvider subclass + /// + template + void registerProivder(std::shared_ptr manager) + { + std::type_index index(typeid(typename ProviderType::SourceType)); + internalRegisterProvider(index, manager); + } + + std::shared_ptr sendFile(std::shared_ptr provider, + const QString &filePath, + const std::optional &description = {}); + + std::shared_ptr downloadFile(const QXmppFileShare &fileShare, + std::unique_ptr &&output); + +private: + void internalRegisterProvider(std::type_index, std::shared_ptr provider); + + MetadataGenerator m_metadataGenerator; + std::unordered_map> m_providers; +}; + +#endif // QXMPPFILESHARINGMANAGER_H diff --git a/src/client/QXmppFileSharingProvider.h b/src/client/QXmppFileSharingProvider.h new file mode 100644 index 00000000..cde719db --- /dev/null +++ b/src/client/QXmppFileSharingProvider.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPFILESHARINGPROVIDER_H +#define QXMPPFILESHARINGPROVIDER_H + +#include "QXmppGlobal.h" + +#include +#include + +class QIODevice; +class QXmppFileMetadata; +class QXmppUpload; +class QXmppDownload; + +/// +/// \brief The interface of a provider for the FileSharingManager +/// +/// To use it, implement all the pure virtual functions, +/// and add a using declaration for the type of source you want to handle. +/// ``` +/// using SourceType = QXmppHttpFileSource; +/// ``` +/// +class QXMPP_EXPORT QXmppFileSharingProvider +{ +public: + /// \cond + virtual ~QXmppFileSharingProvider() = default; + /// \endcond + + /// + /// \brief Handles the download of files for this provider + /// \param source A type-erased source object. The provider will only ever have to handle its own sources, + /// so this can be safely casted to a concrete type. + /// \param target QIODevice into which the received data should be written + /// \return A subclass of QXmppDownload + /// + virtual auto downloadFile(const std::any &source, std::unique_ptr &&target) -> std::shared_ptr = 0; + + /// + /// \brief Handles the upload of a file for this provider + /// \param source A QIODevice from which data for uploading should be read. + /// \param info Metadata of the file + /// \return A subclass of QXmppUpload + /// + virtual auto uploadFile(std::unique_ptr source, const QXmppFileMetadata &info) -> std::shared_ptr = 0; +}; + +#endif // QXMPPFILESHARINGPROVIDER_H diff --git a/src/client/QXmppFileTransfer.cpp b/src/client/QXmppFileTransfer.cpp new file mode 100644 index 00000000..f26c3a89 --- /dev/null +++ b/src/client/QXmppFileTransfer.cpp @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2022 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppFileTransfer.h" + +/// +/// \class QXmppFileTransfer +/// +/// \brief Provides progress information about ongoing file uploads and downloads. +/// +/// \since QXmpp 1.5 +/// + +/// +/// \fn QXmppFileTransfer::cancel() +/// +/// \brief Cancels the file transfer. finished() will be emitted. +/// + +/// +/// \fn QXmppFileTransfer::isFinished() +/// +/// \brief Returns whether the file transfer is finished. +/// + +/// +/// \fn QXmppFileTransfer::bytesTransferred() +/// +/// \brief Returns the number of bytes that have been uploaded or downloaded. +/// + +/// +/// \fn QXmppFileTransfer::bytesTotal() +/// +/// \brief Returns the number of bytes that totally need to be transferred. +/// + +/// +/// \fn QXmppFileTransfer::progressChanged() +/// +/// \brief Emitted when new bytes have been transferred. +/// diff --git a/src/client/QXmppFileTransfer.h b/src/client/QXmppFileTransfer.h new file mode 100644 index 00000000..30a91aa9 --- /dev/null +++ b/src/client/QXmppFileTransfer.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPFILETRANSFER_H +#define QXMPPFILETRANSFER_H + +#include "QXmppGlobal.h" + +#include + +class QXMPP_EXPORT QXmppFileTransfer : public QObject +{ + Q_OBJECT + /// Progress of the file transfer between 0.0 and 1.0. + Q_PROPERTY(float progress READ progress NOTIFY progressChanged) + +public: + /// Returns the current progress between 0.0 and 1.0. + virtual float progress() = 0; + virtual void cancel() = 0; + virtual bool isFinished() = 0; + virtual quint64 bytesTransferred() = 0; + virtual quint64 bytesTotal() = 0; + + // TODO consider adding speed getter + + Q_SIGNAL void progressChanged(); +}; + +#endif // QXMPPFILETRANSFER_H diff --git a/src/client/QXmppHttpFileSharingProvider.cpp b/src/client/QXmppHttpFileSharingProvider.cpp new file mode 100644 index 00000000..5c3e356c --- /dev/null +++ b/src/client/QXmppHttpFileSharingProvider.cpp @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppHttpFileSharingProvider.h" + +#include "QXmppClient.h" +#include "QXmppDownload.h" +#include "QXmppHttpUploadManager.h" +#include "QXmppUpload.h" +#include "QXmppUtils.h" +#include "QXmppUtils_p.h" + +#include +#include + +using namespace QXmpp; +using namespace QXmpp::Private; + +/// +/// \class QXmppHttpFileSharingProvider +/// +/// A file sharing provider that uses HTTP File Upload to upload the file. +/// +/// \since QXmpp 1.5 +/// + +class QXmppSfsHttpUpload : public QXmppUpload +{ + Q_OBJECT + +public: + QXmppSfsHttpUpload(std::shared_ptr &&httpUpload) + : inner(std::move(httpUpload)) + { + connect(inner.get(), &QXmppHttpUpload::finished, this, [this](const QXmppHttpUpload::Result &result) { + Q_EMIT uploadFinished(std::visit([](auto &&value) -> QXmpp::Private::UploadResult { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::any(QXmppHttpFileSource(value)); + } else if constexpr (std::is_same_v) { + return Cancelled {}; + } else if constexpr (std::is_same_v) { + return value; + } + }, + result)); + }); + connect(inner.get(), &QXmppHttpUpload::progressChanged, + this, &QXmppSfsHttpUpload::progressChanged); + } + + float progress() override { return inner->progress(); } + void cancel() override { inner->cancel(); } + bool isFinished() override { return inner->isFinished(); } + quint64 bytesTransferred() override { return inner->bytesSent(); } + quint64 bytesTotal() override { return inner->bytesTotal(); } + +private: + std::shared_ptr inner; +}; + +class QXmppHttpDownload : public QXmppDownload +{ + float progress() override + { + return calculateProgress(m_bytesSent, m_bytesTotal); + } + + void cancel() override + { + m_aborted = true; + reply->abort(); + } + + bool isFinished() override + { + return m_isFinished; + } + + quint64 bytesTransferred() override + { + return m_bytesSent; + } + + quint64 bytesTotal() override + { + return m_bytesTotal; + } + +public: + void reportProgress(quint64 bytesSent, quint64 bytesTotal) + { + m_bytesSent = bytesSent; + m_bytesTotal = bytesTotal; + Q_EMIT progressChanged(); + } + + void reportFinished(Result &&result) + { + Q_EMIT finished(result); + m_isFinished = true; + } + + [[nodiscard]] bool aborted() const + { + return m_aborted; + } + +private: + QNetworkReply *reply = nullptr; + quint64 m_bytesSent = 0; + quint64 m_bytesTotal = 0; + bool m_isFinished = false; + bool m_aborted = false; +}; + +class QXmppHttpFileSharingProviderPrivate +{ +public: + QXmppHttpUploadManager *manager; + QNetworkAccessManager *netManager; +}; + +/// +/// \brief Create a QXmppHttpFileSharingProvider +/// \param client +/// \param netManager QNetworkAccessManager that can be reused all over your application. +/// +QXmppHttpFileSharingProvider::QXmppHttpFileSharingProvider(QXmppClient *client, QNetworkAccessManager *netManager) + : d(std::make_unique()) +{ + qRegisterMetaType(); + Q_ASSERT(client); + d->manager = client->findExtension(); + Q_ASSERT(d->manager); + d->netManager = netManager; +} + +QXmppHttpFileSharingProvider::~QXmppHttpFileSharingProvider() = default; + +auto QXmppHttpFileSharingProvider::downloadFile(const std::any &source, std::unique_ptr &&target) + -> std::shared_ptr +{ + QXmppHttpFileSource httpSource; + try { + httpSource = std::any_cast(source); + } catch (const std::bad_any_cast &) { + qFatal("QXmppHttpFileSharingProvider::downloadFile can only handle QXmppHttpFileSharingProvider sources"); + } + + auto *reply = d->netManager->get(QNetworkRequest(httpSource.url())); + + auto download = std::make_shared(); + + QObject::connect(reply, &QNetworkReply::finished, [=]() { + if (reply->error() != QNetworkReply::NoError) { + download->reportFinished(QXmppError::fromIoDevice(*reply)); + } else if (download->aborted()) { + download->reportFinished(Cancelled()); + } else { + download->reportFinished(Success()); + } + reply->deleteLater(); + }); + + QObject::connect(reply, &QNetworkReply::readyRead, [file = std::move(target), reply]() { + file->write(reply->readAll()); + }); + + QObject::connect(reply, &QNetworkReply::downloadProgress, [=](qint64 bytesReceived, qint64 bytesTotal) { + download->reportProgress(bytesReceived, bytesTotal); + }); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + QObject::connect(reply, &QNetworkReply::errorOccurred, +#else + QObject::connect(reply, QOverload::of(&QNetworkReply::error), +#endif + [download, reply]() { + download->reportFinished(QXmppError::fromNetworkReply(*reply)); + }); + + QObject::connect(reply, &QNetworkReply::uploadProgress, [download](qint64 sent, qint64 total) { + quint64 sentBytes = sent < 0 ? 0 : quint64(sent); + quint64 totalBytes = total < 0 ? 0 : quint64(total); + download->reportProgress(sentBytes, totalBytes); + }); + + return download; +} + +auto QXmppHttpFileSharingProvider::uploadFile(std::unique_ptr data, + const QXmppFileMetadata &info) + -> std::shared_ptr +{ + Q_ASSERT(d->manager); + auto upload = d->manager->uploadFile( + std::move(data), + QXmppUtils::generateStanzaHash(10), + info.mediaType().value_or(QMimeDatabase().mimeTypeForName("application/octet-stream")), + info.size() ? info.size().value() : -1); + + return std::make_shared(std::move(upload)); +} + +#include "QXmppHttpFileSharingProvider.moc" diff --git a/src/client/QXmppHttpFileSharingProvider.h b/src/client/QXmppHttpFileSharingProvider.h new file mode 100644 index 00000000..748c1277 --- /dev/null +++ b/src/client/QXmppHttpFileSharingProvider.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPHTTPFILESHARINGPROVIDER_H +#define QXMPPHTTPFILESHARINGPROVIDER_H + +#include "QXmppFileSharingProvider.h" +#include "QXmppHttpFileSource.h" + +#include +#include + +class QXmppClient; +class QIODevice; +class QXmppHttpUploadManager; +class QNetworkAccessManager; + +class QXmppHttpFileSharingProviderPrivate; + +class QXMPP_EXPORT QXmppHttpFileSharingProvider : public QXmppFileSharingProvider +{ +public: + /// \cond + using SourceType = QXmppHttpFileSource; + /// \endcond + + QXmppHttpFileSharingProvider(QXmppClient *client, QNetworkAccessManager *netManager); + ~QXmppHttpFileSharingProvider() override; + + auto downloadFile(const std::any &source, + std::unique_ptr &&target) -> std::shared_ptr override; + auto uploadFile(std::unique_ptr data, + const QXmppFileMetadata &info) -> std::shared_ptr override; + +private: + std::unique_ptr d; +}; + +#endif // QXMPPHTTPFILESHARINGPROVIDER_H diff --git a/src/client/QXmppHttpUploadManager.cpp b/src/client/QXmppHttpUploadManager.cpp index f6261773..7feac738 100644 --- a/src/client/QXmppHttpUploadManager.cpp +++ b/src/client/QXmppHttpUploadManager.cpp @@ -8,6 +8,7 @@ #include "QXmppFutureUtils_p.h" #include "QXmppHttpUploadIq.h" #include "QXmppUploadRequestManager.h" +#include "QXmppUtils_p.h" #include #include @@ -149,13 +150,7 @@ QXmppHttpUpload::~QXmppHttpUpload() = default; /// float QXmppHttpUpload::progress() const { - if (d->bytesTotal > 0) { - if (d->bytesSent > d->bytesTotal) { - return 1; - } - return float(d->bytesSent) / d->bytesTotal; - } - return 0; + return calculateProgress(d->bytesSent, d->bytesTotal); } /// diff --git a/src/client/QXmppUpload.cpp b/src/client/QXmppUpload.cpp new file mode 100644 index 00000000..e7b876e5 --- /dev/null +++ b/src/client/QXmppUpload.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppUpload.h" + +/// +/// \class QXmppUpload +/// +/// \brief Provides progress of stateless file sharing uploads. +/// +/// \since QXmpp 1.5 +/// + +/// +/// \class QXmppUpload::FileResult +/// +/// \brief Contains QXmppFileShare of the uploaded file and possible data blobs containing +/// referenced thumbnails. +/// + +/// +/// \var QXmppUpload::FileResult::fileShare +/// +/// \brief File share with file metadata and file shares of the uploaded file. +/// + +/// +/// \var QXmppUpload::FileResult::dataBlobs +/// +/// \brief Data blobs of possibly in the metadata referenced thumbnails. +/// +/// The QXmppFileSharingManager may generate file thumbnails. +/// + +/// +/// \typedef QXmppUpload::Result +/// +/// \brief Contains FileResult (successfully finished), QXmpp::Cancelled (manually cancelled) +/// or QXmppError (an error occured while downloading). +/// + +/// +/// \fn QXmppUpload::finished +/// +/// Emitted when the upload has finished. +/// diff --git a/src/client/QXmppUpload.h b/src/client/QXmppUpload.h new file mode 100644 index 00000000..c948496f --- /dev/null +++ b/src/client/QXmppUpload.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPUPLOAD_H +#define QXMPPUPLOAD_H + +#include "QXmppBitsOfBinaryDataList.h" +#include "QXmppError.h" +#include "QXmppFileMetadata.h" +#include "QXmppFileShare.h" +#include "QXmppFileTransfer.h" + +#include + +namespace QXmpp::Private { +using UploadResult = std::variant; +} + +class QXMPP_EXPORT QXmppUpload : public QXmppFileTransfer +{ + Q_OBJECT +public: + struct FileResult + { + QXmppFileShare fileShare; + QXmppBitsOfBinaryDataList dataBlobs; + }; + + using Result = std::variant; + Q_SIGNAL void finished(QXmppUpload::Result); + +protected: + /// \cond + Q_SIGNAL void uploadFinished(QXmpp::Private::UploadResult); + /// \endcond + +private: + QXmppFileMetadata metadata; + + friend class QXmppFileSharingManager; +}; + +Q_DECLARE_METATYPE(QXmppUpload::Result); +Q_DECLARE_METATYPE(QXmpp::Private::UploadResult); + +#endif // QXMPPUPLOAD_H -- cgit v1.2.3