From b871f6d3557c20fc7d1456e5080c33ea90795c84 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Sun, 2 Oct 2022 02:45:09 +0200 Subject: Generate QXmppUpload/Download only by FileSharingManager Previously all the providers had to subclass the QXmppUpload/Download. It should be much easier to do additional tasks (e.g. hashing after downloading) now because the manager (and not the provider) decides when to emit the finished signal. Making the encrypted source provider able to handle arbitrary unencrypted sources should be possible too. --- .../QXmppEncryptedHttpFileSharingProvider.cpp | 88 +++------ src/client/QXmppEncryptedHttpFileSharingProvider.h | 12 +- src/client/QXmppFileSharingManager.cpp | 198 ++++++++++++++----- src/client/QXmppFileSharingManager.h | 4 +- src/client/QXmppFileSharingProvider.h | 52 ++++- src/client/QXmppHttpFileSharingProvider.cpp | 218 ++++++++------------- src/client/QXmppHttpFileSharingProvider.h | 10 +- src/client/QXmppUpload.cpp | 2 +- src/client/QXmppUpload.h | 13 -- 9 files changed, 325 insertions(+), 272 deletions(-) (limited to 'src/client') diff --git a/src/client/QXmppEncryptedHttpFileSharingProvider.cpp b/src/client/QXmppEncryptedHttpFileSharingProvider.cpp index b6e82621..82aa351e 100644 --- a/src/client/QXmppEncryptedHttpFileSharingProvider.cpp +++ b/src/client/QXmppEncryptedHttpFileSharingProvider.cpp @@ -6,8 +6,9 @@ #include "QXmppClient.h" #include "QXmppFileEncryption.h" +#include "QXmppFileMetadata.h" +#include "QXmppFutureUtils_p.h" #include "QXmppHttpUploadManager.h" -#include "QXmppUpload.h" #include "QXmppUtils.h" #include "QcaInitializer_p.h" @@ -16,43 +17,6 @@ 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 /// @@ -65,7 +29,6 @@ class QXmppEncryptedHttpFileSharingProviderPrivate { public: QXmpp::Private::QcaInitializer init; - QXmppHttpUploadManager *manager; QXmppHttpFileSharingProvider *httpProvider; }; @@ -77,16 +40,16 @@ public: 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) +auto QXmppEncryptedHttpFileSharingProvider::downloadFile(const std::any &source, + std::unique_ptr target, + std::function reportProgress, + std::function reportFinished) + -> std::shared_ptr { QXmppEncryptedFileSource encryptedSource; try { @@ -97,12 +60,14 @@ std::shared_ptr QXmppEncryptedHttpFileSharingProvider::downloadFi 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)); + return d->httpProvider->downloadFile(httpSource, std::move(output), std::move(reportProgress), std::move(reportFinished)); } -std::shared_ptr QXmppEncryptedHttpFileSharingProvider::uploadFile( - std::unique_ptr data, - const QXmppFileMetadata &info) +auto QXmppEncryptedHttpFileSharingProvider::uploadFile(std::unique_ptr data, + const QXmppFileMetadata &, + std::function reportProgress, + std::function reportFinished) + -> std::shared_ptr { auto cipher = Aes256CbcPkcs7; auto key = Encryption::generateKey(cipher); @@ -111,13 +76,24 @@ std::shared_ptr QXmppEncryptedHttpFileSharingProvider::uploadFile( 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); + QXmppFileMetadata metadata; + metadata.setFilename(QXmppUtils::generateStanzaHash(10)); + metadata.setMediaType(QMimeDatabase().mimeTypeForName("application/octet-stream")); + metadata.setSize(encryptedSize); - return std::make_shared(std::move(upload), key, iv); + return d->httpProvider->uploadFile( + std::move(encDevice), + metadata, + std::move(reportProgress), + [=, reportFinished = std::move(reportFinished)](UploadResult result) { + auto encryptedResult = visitForward(std::move(result), [&](std::any httpSourceAny) { + QXmppEncryptedFileSource encryptedSource; + encryptedSource.setKey(key); + encryptedSource.setIv(iv); + encryptedSource.setHttpSources({ std::any_cast(std::move(httpSourceAny)) }); + + return encryptedSource; + }); + reportFinished(std::move(encryptedResult)); + }); } - -#include "QXmppEncryptedHttpFileSharingProvider.moc" diff --git a/src/client/QXmppEncryptedHttpFileSharingProvider.h b/src/client/QXmppEncryptedHttpFileSharingProvider.h index 6c43d8cd..8f018c71 100644 --- a/src/client/QXmppEncryptedHttpFileSharingProvider.h +++ b/src/client/QXmppEncryptedHttpFileSharingProvider.h @@ -21,10 +21,14 @@ public: ~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; + std::unique_ptr target, + std::function reportProgress, + std::function reportFinished) -> std::shared_ptr override; + + auto uploadFile(std::unique_ptr source, + const QXmppFileMetadata &info, + std::function reportProgress, + std::function reportFinished) -> std::shared_ptr override; private: std::unique_ptr d; diff --git a/src/client/QXmppFileSharingManager.cpp b/src/client/QXmppFileSharingManager.cpp index 54e29086..370b3094 100644 --- a/src/client/QXmppFileSharingManager.cpp +++ b/src/client/QXmppFileSharingManager.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2022 Jonah Brüchert +// SPDX-FileCopyrightText: 2022 Linus Jahn // // SPDX-License-Identifier: LGPL-2.1-or-later @@ -15,9 +16,11 @@ #include "QXmppThumbnail.h" #include "QXmppUpload.h" #include "QXmppUploadRequestManager.h" +#include "QXmppUtils_p.h" #include #include +#include #include #include @@ -45,6 +48,74 @@ static std::vector hashAlgorithms() }; } +class UploadImpl : public QXmppUpload +{ + Q_OBJECT +public: + float progress() override { return calculateProgress(m_bytesSent, m_bytesTotal); } + void cancel() override + { + if (m_providerUpload) { + m_providerUpload->cancel(); + } + m_metadataFuture.cancel(); + m_hashesFuture.cancel(); + } + bool isFinished() override { return m_finished; } + quint64 bytesTransferred() override { return m_bytesSent; } + quint64 bytesTotal() override { return m_bytesTotal; } + + void reportFinished(Result result) + { + if (!m_finished) { + m_finished = true; + Q_EMIT finished(std::move(result)); + } + } + + std::shared_ptr m_providerUpload; + QFuture> m_metadataFuture; + QFuture m_hashesFuture; + QXmppFileMetadata m_metadata; + QXmppBitsOfBinaryDataList m_dataBlobs; + std::any m_source; + quint64 m_bytesSent = 0; + quint64 m_bytesTotal = 0; + bool m_finished = false; +}; + +class DownloadImpl : public QXmppDownload +{ + float progress() override { return calculateProgress(m_bytesReceived, m_bytesTotal); } + void cancel() override + { + if (m_providerDownload) { + m_providerDownload->cancel(); + } + } + bool isFinished() override { return m_finished; } + quint64 bytesTransferred() override { return m_bytesReceived; } + quint64 bytesTotal() override { return m_bytesTotal; } + +public: + void reportProgress(quint64 bytesReceived, quint64 bytesTotal) + { + m_bytesReceived = bytesReceived; + m_bytesTotal = bytesTotal; + Q_EMIT progressChanged(); + } + void reportFinished(Result result) + { + m_finished = true; + Q_EMIT finished(std::move(result)); + } + + std::shared_ptr m_providerDownload; + quint64 m_bytesReceived = 0; + quint64 m_bytesTotal = 0; + bool m_finished = false; +}; + class QXmppFileSharingManagerPrivate { public: @@ -112,89 +183,105 @@ std::shared_ptr QXmppFileSharingManager::sendFile(std::shared_ptr &description) { - std::shared_ptr upload; - QFileInfo fileInfo(filePath); auto metadata = QXmppFileMetadata::fromFileInfo(fileInfo); - if (description) { - metadata.setDescription(description); - } + metadata.setDescription(description); + + auto upload = std::make_shared(); + upload->m_metadata = std::move(metadata); auto openFile = [=]() -> std::unique_ptr { auto device = std::make_unique(fileInfo.absoluteFilePath()); if (!device->open(QIODevice::ReadOnly)) { - Q_EMIT upload->finished(QXmppError::fromIoDevice(*device)); + upload->reportFinished(QXmppError::fromIoDevice(*device)); } return device; }; - auto metadataFuture = d->metadataGenerator(openFile()); - auto hashesFuture = calculateHashes(openFile(), hashAlgorithms()); + auto metadataIoDevice = openFile(); + auto hashesIoDevice = openFile(); + auto uploadIoDevice = openFile(); - upload = provider->uploadFile(openFile(), metadata); - upload->metadata = metadata; + if (upload->m_finished) { + // error occurred while opening file + return upload; + } - connect(upload.get(), &QXmppUpload::uploadFinished, this, [=](auto &&uploadResult) { + upload->m_metadataFuture = d->metadataGenerator(std::move(metadataIoDevice)); + upload->m_hashesFuture = calculateHashes(std::move(hashesIoDevice), hashAlgorithms()); + + auto onProgress = [upload](quint64 sent, quint64 total) { + upload->m_bytesSent = sent; + upload->m_bytesTotal = total; + Q_EMIT upload->progressChanged(); + }; + auto onFinished = [this, upload](QXmppFileSharingProvider::UploadResult uploadResult) { + // free memory + upload->m_providerUpload.reset(); if (std::holds_alternative(uploadResult)) { - auto source = std::get(uploadResult); - await(metadataFuture, this, [=](auto &&result) mutable { + upload->m_source = std::get(std::move(uploadResult)); + await(upload->m_metadataFuture, this, [this, upload](auto &&result) mutable { if (result->dimensions) { - upload->metadata.setWidth(result->dimensions->width()); - upload->metadata.setHeight(result->dimensions->height()); + upload->m_metadata.setWidth(result->dimensions->width()); + upload->m_metadata.setHeight(result->dimensions->height()); } if (result->length) { - upload->metadata.setLength(*result->length); + upload->m_metadata.setLength(*result->length); } - QVector thumbnails; - thumbnails.reserve(result->thumbnails.size()); - QXmppBitsOfBinaryDataList dataBlobs; - dataBlobs.reserve(result->thumbnails.size()); + if (!result->thumbnails.empty()) { + QVector thumbnails; + thumbnails.reserve(result->thumbnails.size()); + upload->m_dataBlobs.reserve(result->thumbnails.size()); - for (const auto &metadataThumb : result->thumbnails) { - auto bobData = QXmppBitsOfBinaryData::fromByteArray(metadataThumb.data); - bobData.setContentType(metadataThumb.mimeType); + 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()); + 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)); + thumbnails.append(std::move(thumbnail)); + upload->m_dataBlobs.append(std::move(bobData)); + } + upload->m_metadata.setThumbnails(thumbnails); } - 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); + + await(upload->m_hashesFuture, this, [upload](auto hashResult) mutable { + auto &hashValue = hashResult->result; + if (std::holds_alternative>(hashValue)) { + const auto &hashesVector = std::get>(hashValue); + QVector hashes; + hashes.reserve(hashesVector.size()); std::transform(hashesVector.begin(), hashesVector.end(), std::back_inserter(hashes), [](auto &&hash) { return hash; }); - - upload->metadata.setHashes(hashes); + upload->m_metadata.setHashes(hashes); QXmppFileShare fs; - fs.setMetadata(upload->metadata); - fs.addSource(std::move(source)); + fs.setMetadata(upload->m_metadata); + fs.addSource(upload->m_source); - Q_EMIT upload->finished(QXmppUpload::FileResult { fs, std::move(dataBlobs) }); + upload->reportFinished(QXmppUpload::FileResult { fs, std::move(upload->m_dataBlobs) }); + } else if (std::holds_alternative(hashValue)) { + upload->reportFinished(Cancelled()); } else if (std::holds_alternative(hashValue)) { - Q_EMIT upload->finished(std::get(hashValue)); + upload->reportFinished(std::get(std::move(hashValue))); } }); }); + } else if (std::holds_alternative(uploadResult)) { + upload->reportFinished(Cancelled()); } else if (std::holds_alternative(uploadResult)) { - Q_EMIT upload->finished(std::get(uploadResult)); + upload->reportFinished(std::get(std::move(uploadResult))); } - }); + }; + upload->m_providerUpload = provider->uploadFile(std::move(uploadIoDevice), upload->m_metadata, std::move(onProgress), std::move(onFinished)); return upload; } @@ -213,14 +300,21 @@ std::shared_ptr QXmppFileSharingManager::sendFile(std::shared_ptr QXmppFileSharingManager::downloadFile( const QXmppFileShare &fileShare, - std::unique_ptr &&output) + std::unique_ptr output) { - std::shared_ptr download; + auto download = std::make_shared(); + + auto onProgress = [download](quint64 received, quint64 total) { + download->reportProgress(received, total); + }; + auto onFinished = [download](QXmppFileSharingProvider::DownloadResult result) { + download->reportFinished(std::move(result)); + }; + fileShare.visitSources([&](const std::any &source) { std::type_index index(source.type()); try { - auto provider = d->providers.at(index); - download = provider->downloadFile(source, std::move(output)); + download->m_providerDownload = d->providers.at(index)->downloadFile(source, std::move(output), std::move(onProgress), std::move(onFinished)); return true; } catch (const std::out_of_range &) { return false; @@ -234,3 +328,5 @@ void QXmppFileSharingManager::internalRegisterProvider(std::type_index index, st { d->providers.insert_or_assign(index, provider); } + +#include "QXmppFileSharingManager.moc" diff --git a/src/client/QXmppFileSharingManager.h b/src/client/QXmppFileSharingManager.h index 20a1cdc4..05039f79 100644 --- a/src/client/QXmppFileSharingManager.h +++ b/src/client/QXmppFileSharingManager.h @@ -6,7 +6,6 @@ #define QXMPPFILESHARINGMANAGER_H #include "QXmppClientExtension.h" -#include "QXmppFileShare.h" #include "QXmppFileSharingProvider.h" #include "QXmppGlobal.h" @@ -21,6 +20,7 @@ class QIODevice; class QXmppFileMetadata; +class QXmppFileShare; class QXmppFileSharingManagerPrivate; class QXMPP_EXPORT QXmppFileSharingManager : public QXmppClientExtension @@ -66,7 +66,7 @@ public: const std::optional &description = {}); std::shared_ptr downloadFile(const QXmppFileShare &fileShare, - std::unique_ptr &&output); + std::unique_ptr output); private: void internalRegisterProvider(std::type_index, std::shared_ptr provider); diff --git a/src/client/QXmppFileSharingProvider.h b/src/client/QXmppFileSharingProvider.h index cde719db..02e6252a 100644 --- a/src/client/QXmppFileSharingProvider.h +++ b/src/client/QXmppFileSharingProvider.h @@ -1,14 +1,18 @@ // SPDX-FileCopyrightText: 2022 Jonah Brüchert +// SPDX-FileCopyrightText: 2022 Linus Jahn // // SPDX-License-Identifier: LGPL-2.1-or-later #ifndef QXMPPFILESHARINGPROVIDER_H #define QXMPPFILESHARINGPROVIDER_H +#include "QXmppError.h" #include "QXmppGlobal.h" #include +#include #include +#include class QIODevice; class QXmppFileMetadata; @@ -16,7 +20,7 @@ class QXmppUpload; class QXmppDownload; /// -/// \brief The interface of a provider for the FileSharingManager +/// \brief The interface of a provider for the QXmppFileSharingManager /// /// To use it, implement all the pure virtual functions, /// and add a using declaration for the type of source you want to handle. @@ -27,26 +31,60 @@ class QXmppDownload; class QXMPP_EXPORT QXmppFileSharingProvider { public: + /// Contains QXmpp::Success (successfully finished), QXmpp::Cancelled (manually cancelled) or + /// QXmppError (an error occured while downloading). + using DownloadResult = std::variant; + + /// Contains std::any (created file source), QXmpp::Cancelled (manually cancelled) or + /// QXmppError (an error occured while uploading). + using UploadResult = std::variant; + + /// Used to control ongoing downloads + class Download + { + public: + virtual ~Download() = default; + /// Cancels the download. + virtual void cancel() = 0; + }; + + /// Used to control ongoing uploads + class Upload + { + public: + virtual ~Upload() = default; + /// Cancels the upload. + virtual void cancel() = 0; + }; + /// \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 source A type-erased source object. The provider will only ever have to handle + /// its own sources, so this can safely be casted to the defined source type. /// \param target QIODevice into which the received data should be written - /// \return A subclass of QXmppDownload + /// \param reportProgress Can be called to report received bytes and total bytes + /// \param reportFinished Finalizes the download, no more progress must be reported after this /// - virtual auto downloadFile(const std::any &source, std::unique_ptr &&target) -> std::shared_ptr = 0; + virtual auto downloadFile(const std::any &source, + std::unique_ptr target, + std::function reportProgress, + std::function reportFinished) -> 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 + /// \param reportProgress Can be called to report sent bytes and total bytes + /// \param reportFinished Finalizes the upload, no more progress must be reported after this /// - virtual auto uploadFile(std::unique_ptr source, const QXmppFileMetadata &info) -> std::shared_ptr = 0; + virtual auto uploadFile(std::unique_ptr source, + const QXmppFileMetadata &info, + std::function reportProgress, + std::function reportFinished) -> std::shared_ptr = 0; }; #endif // QXMPPFILESHARINGPROVIDER_H diff --git a/src/client/QXmppHttpFileSharingProvider.cpp b/src/client/QXmppHttpFileSharingProvider.cpp index 8222dab1..4b7cd37e 100644 --- a/src/client/QXmppHttpFileSharingProvider.cpp +++ b/src/client/QXmppHttpFileSharingProvider.cpp @@ -1,15 +1,15 @@ // SPDX-FileCopyrightText: 2022 Jonah Brüchert +// SPDX-FileCopyrightText: 2022 Linus Jahn // // SPDX-License-Identifier: LGPL-2.1-or-later #include "QXmppHttpFileSharingProvider.h" #include "QXmppClient.h" -#include "QXmppDownload.h" +#include "QXmppFileMetadata.h" +#include "QXmppFutureUtils_p.h" #include "QXmppHttpUploadManager.h" -#include "QXmppUpload.h" #include "QXmppUtils.h" -#include "QXmppUtils_p.h" #include #include @@ -25,96 +25,6 @@ using namespace QXmpp::Private; /// \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: @@ -130,7 +40,6 @@ public: QXmppHttpFileSharingProvider::QXmppHttpFileSharingProvider(QXmppClient *client, QNetworkAccessManager *netManager) : d(std::make_unique()) { - qRegisterMetaType(); Q_ASSERT(client); d->manager = client->findExtension(); Q_ASSERT(d->manager); @@ -139,77 +48,116 @@ QXmppHttpFileSharingProvider::QXmppHttpFileSharingProvider(QXmppClient *client, QXmppHttpFileSharingProvider::~QXmppHttpFileSharingProvider() = default; -auto QXmppHttpFileSharingProvider::downloadFile(const std::any &source, std::unique_ptr &&target) - -> std::shared_ptr +auto QXmppHttpFileSharingProvider::downloadFile(const std::any &source, + std::unique_ptr target, + std::function reportProgress, + std::function reportFinished) + -> std::shared_ptr { + struct State : Download + { + ~State() override = default; + + QNetworkReply *reply = nullptr; + bool finished = false; + bool cancelled = false; + + void cancel() override + { + if (!cancelled) { + cancelled = true; + reply->abort(); + } + } + }; + QXmppHttpFileSource httpSource; try { httpSource = std::any_cast(source); } catch (const std::bad_any_cast &) { - qFatal("QXmppHttpFileSharingProvider::downloadFile can only handle QXmppHttpFileSharingProvider sources"); + qFatal("QXmppHttpFileSharingProvider::downloadFile can only handle QXmppHttpFileSource."); } - 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()); + auto state = std::make_shared(); + state->reply = d->netManager->get(QNetworkRequest(httpSource.url())); + + QObject::connect(state->reply, &QNetworkReply::finished, [state, reportFinished = std::move(reportFinished)]() mutable { + Q_ASSERT(state); + + if (!state->finished) { + if (state->reply->error() != QNetworkReply::NoError) { + reportFinished(QXmppError::fromNetworkReply(*state->reply)); + } else if (state->cancelled) { + reportFinished(Cancelled()); + } else { + reportFinished(Success()); + } + state->finished = true; } - reply->deleteLater(); + state->reply->deleteLater(); + + // reduce ref count + state.reset(); }); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - QObject::connect(reply, &QNetworkReply::readyRead, [file = std::move(target), reply]() { + QObject::connect(state->reply, &QNetworkReply::readyRead, [file = std::move(target), reply = state->reply]() { file->write(reply->readAll()); }); #else auto file = std::shared_ptr(std::move(target)); - QObject::connect(reply, &QNetworkReply::readyRead, [file, reply]() { + QObject::connect(state->reply, &QNetworkReply::readyRead, [file, reply = state->reply]() { file->write(reply->readAll()); }); #endif - 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); + QObject::connect(state->reply, &QNetworkReply::downloadProgress, [stateRef = std::weak_ptr(state), reportProgress = std::move(reportProgress)](qint64 bytesReceived, qint64 bytesTotal) { + if (auto state = stateRef.lock()) { + if (!state->finished) { + reportProgress(bytesReceived, bytesTotal); + } + } }); - return download; + return std::dynamic_pointer_cast(state); } auto QXmppHttpFileSharingProvider::uploadFile(std::unique_ptr data, - const QXmppFileMetadata &info) - -> std::shared_ptr + const QXmppFileMetadata &info, + std::function reportProgress, + std::function reportFinished) + -> std::shared_ptr { + struct State : Upload + { + ~State() override = default; + + std::shared_ptr upload; + void cancel() override { upload->cancel(); } + }; + Q_ASSERT(d->manager); - auto upload = d->manager->uploadFile( + auto state = std::make_shared(); + state->upload = d->manager->uploadFile( std::move(data), info.filename().value_or(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)); -} + QObject::connect(state->upload.get(), &QXmppHttpUpload::finished, [state, reportFinished = std::move(reportFinished)](const QXmppHttpUpload::Result &result) mutable { + reportFinished(visitForward(result, [](QUrl url) { + return std::any(QXmppHttpFileSource(std::move(url))); + })); -#include "QXmppHttpFileSharingProvider.moc" + // reduce ref count, so the signal connection doesn't keep the state alive forever + state.reset(); + }); + QObject::connect(state->upload.get(), &QXmppHttpUpload::progressChanged, [stateRef = std::weak_ptr(state), reportProgress = std::move(reportProgress)]() { + if (auto state = stateRef.lock()) { + reportProgress(state->upload->bytesSent(), state->upload->bytesTotal()); + } + }); + + return std::dynamic_pointer_cast(state); +} diff --git a/src/client/QXmppHttpFileSharingProvider.h b/src/client/QXmppHttpFileSharingProvider.h index 748c1277..64329659 100644 --- a/src/client/QXmppHttpFileSharingProvider.h +++ b/src/client/QXmppHttpFileSharingProvider.h @@ -29,9 +29,13 @@ public: ~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; + std::unique_ptr target, + std::function reportProgress, + std::function reportFinished) -> std::shared_ptr override; + auto uploadFile(std::unique_ptr source, + const QXmppFileMetadata &info, + std::function reportProgress, + std::function reportFinished) -> std::shared_ptr override; private: std::unique_ptr d; diff --git a/src/client/QXmppUpload.cpp b/src/client/QXmppUpload.cpp index e7b876e5..cd4a19e4 100644 --- a/src/client/QXmppUpload.cpp +++ b/src/client/QXmppUpload.cpp @@ -37,7 +37,7 @@ /// \typedef QXmppUpload::Result /// /// \brief Contains FileResult (successfully finished), QXmpp::Cancelled (manually cancelled) -/// or QXmppError (an error occured while downloading). +/// or QXmppError (an error occured while uploading). /// /// diff --git a/src/client/QXmppUpload.h b/src/client/QXmppUpload.h index c948496f..ec423bd9 100644 --- a/src/client/QXmppUpload.h +++ b/src/client/QXmppUpload.h @@ -7,16 +7,11 @@ #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 @@ -30,18 +25,10 @@ public: 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