diff options
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/QXmppEncryptedHttpFileSharingProvider.cpp | 88 | ||||
| -rw-r--r-- | src/client/QXmppEncryptedHttpFileSharingProvider.h | 12 | ||||
| -rw-r--r-- | src/client/QXmppFileSharingManager.cpp | 198 | ||||
| -rw-r--r-- | src/client/QXmppFileSharingManager.h | 4 | ||||
| -rw-r--r-- | src/client/QXmppFileSharingProvider.h | 52 | ||||
| -rw-r--r-- | src/client/QXmppHttpFileSharingProvider.cpp | 218 | ||||
| -rw-r--r-- | src/client/QXmppHttpFileSharingProvider.h | 10 | ||||
| -rw-r--r-- | src/client/QXmppUpload.cpp | 2 | ||||
| -rw-r--r-- | src/client/QXmppUpload.h | 13 |
9 files changed, 325 insertions, 272 deletions
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<QXmppHttpUpload> &&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<decltype(value)>; - if constexpr (std::is_same_v<T, QUrl>) { - QXmppEncryptedFileSource encryptedSource; - encryptedSource.setKey(key); - encryptedSource.setIv(iv); - encryptedSource.setHttpSources({ QXmppHttpFileSource(value) }); - return std::any(encryptedSource); - } else if constexpr (std::is_same_v<T, Cancelled>) { - return Cancelled {}; - } else if constexpr (std::is_same_v<T, QXmppError>) { - 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<QXmppHttpUpload> 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<QXmppEncryptedHttpFileSharingProviderPrivate>()) { - qRegisterMetaType<QXmpp::Private::UploadResult>(); - Q_ASSERT(client); - d->manager = client->findExtension<QXmppHttpUploadManager>(); - Q_ASSERT(d->manager); d->httpProvider = new QXmppHttpFileSharingProvider(client, netManager); } QXmppEncryptedHttpFileSharingProvider::~QXmppEncryptedHttpFileSharingProvider() = default; -std::shared_ptr<QXmppDownload> QXmppEncryptedHttpFileSharingProvider::downloadFile(const std::any &source, std::unique_ptr<QIODevice> &&target) +auto QXmppEncryptedHttpFileSharingProvider::downloadFile(const std::any &source, + std::unique_ptr<QIODevice> target, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(DownloadResult)> reportFinished) + -> std::shared_ptr<Download> { QXmppEncryptedFileSource encryptedSource; try { @@ -97,12 +60,14 @@ std::shared_ptr<QXmppDownload> QXmppEncryptedHttpFileSharingProvider::downloadFi auto httpSource = encryptedSource.httpSources().front(); auto output = std::make_unique<Encryption::DecryptionDevice>(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<QXmppUpload> QXmppEncryptedHttpFileSharingProvider::uploadFile( - std::unique_ptr<QIODevice> data, - const QXmppFileMetadata &info) +auto QXmppEncryptedHttpFileSharingProvider::uploadFile(std::unique_ptr<QIODevice> data, + const QXmppFileMetadata &, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(UploadResult)> reportFinished) + -> std::shared_ptr<Upload> { auto cipher = Aes256CbcPkcs7; auto key = Encryption::generateKey(cipher); @@ -111,13 +76,24 @@ std::shared_ptr<QXmppUpload> QXmppEncryptedHttpFileSharingProvider::uploadFile( auto encDevice = std::make_unique<Encryption::EncryptionDevice>(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<QXmppSfsEncryptedHttpUpload>(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<UploadResult>(std::move(result), [&](std::any httpSourceAny) { + QXmppEncryptedFileSource encryptedSource; + encryptedSource.setKey(key); + encryptedSource.setIv(iv); + encryptedSource.setHttpSources({ std::any_cast<QXmppHttpFileSource>(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<QIODevice> &&target) -> std::shared_ptr<QXmppDownload> override; - auto uploadFile( - std::unique_ptr<QIODevice> data, - const QXmppFileMetadata &info) -> std::shared_ptr<QXmppUpload> override; + std::unique_ptr<QIODevice> target, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(DownloadResult)> reportFinished) -> std::shared_ptr<Download> override; + + auto uploadFile(std::unique_ptr<QIODevice> source, + const QXmppFileMetadata &info, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(UploadResult)> reportFinished) -> std::shared_ptr<Upload> override; private: std::unique_ptr<QXmppEncryptedHttpFileSharingProviderPrivate> 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 <jbb@kaidan.im> +// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im> // // 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 <any> #include <unordered_map> +#include <utility> #include <QFile> #include <QFileInfo> @@ -45,6 +48,74 @@ static std::vector<HashAlgorithm> 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<QXmppFileSharingProvider::Upload> m_providerUpload; + QFuture<std::shared_ptr<MetadataGeneratorResult>> m_metadataFuture; + QFuture<HashingResultPtr> 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<QXmppFileSharingProvider::Download> m_providerDownload; + quint64 m_bytesReceived = 0; + quint64 m_bytesTotal = 0; + bool m_finished = false; +}; + class QXmppFileSharingManagerPrivate { public: @@ -112,89 +183,105 @@ std::shared_ptr<QXmppUpload> QXmppFileSharingManager::sendFile(std::shared_ptr<Q const QString &filePath, const std::optional<QString> &description) { - std::shared_ptr<QXmppUpload> upload; - QFileInfo fileInfo(filePath); auto metadata = QXmppFileMetadata::fromFileInfo(fileInfo); - if (description) { - metadata.setDescription(description); - } + metadata.setDescription(description); + + auto upload = std::make_shared<UploadImpl>(); + upload->m_metadata = std::move(metadata); auto openFile = [=]() -> std::unique_ptr<QIODevice> { auto device = std::make_unique<QFile>(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<std::any>(uploadResult)) { - auto source = std::get<std::any>(uploadResult); - await(metadataFuture, this, [=](auto &&result) mutable { + upload->m_source = std::get<std::any>(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<QXmppThumbnail> thumbnails; - thumbnails.reserve(result->thumbnails.size()); - QXmppBitsOfBinaryDataList dataBlobs; - dataBlobs.reserve(result->thumbnails.size()); + if (!result->thumbnails.empty()) { + QVector<QXmppThumbnail> 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<QXmppHash> hashes; - const auto hashValue = hashResult->result; - if (std::holds_alternative<Cancelled>(hashValue)) { - Q_EMIT upload->finished(Cancelled {}); - } else if (std::holds_alternative<std::vector<QXmppHash>>(hashValue)) { - auto hashesVector = std::get<std::vector<QXmppHash>>(hashValue); + + await(upload->m_hashesFuture, this, [upload](auto hashResult) mutable { + auto &hashValue = hashResult->result; + if (std::holds_alternative<std::vector<QXmppHash>>(hashValue)) { + const auto &hashesVector = std::get<std::vector<QXmppHash>>(hashValue); + QVector<QXmppHash> 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<Cancelled>(hashValue)) { + upload->reportFinished(Cancelled()); } else if (std::holds_alternative<QXmppError>(hashValue)) { - Q_EMIT upload->finished(std::get<QXmppError>(hashValue)); + upload->reportFinished(std::get<QXmppError>(std::move(hashValue))); } }); }); + } else if (std::holds_alternative<Cancelled>(uploadResult)) { + upload->reportFinished(Cancelled()); } else if (std::holds_alternative<QXmppError>(uploadResult)) { - Q_EMIT upload->finished(std::get<QXmppError>(uploadResult)); + upload->reportFinished(std::get<QXmppError>(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<QXmppUpload> QXmppFileSharingManager::sendFile(std::shared_ptr<Q /// std::shared_ptr<QXmppDownload> QXmppFileSharingManager::downloadFile( const QXmppFileShare &fileShare, - std::unique_ptr<QIODevice> &&output) + std::unique_ptr<QIODevice> output) { - std::shared_ptr<QXmppDownload> download; + auto download = std::make_shared<DownloadImpl>(); + + 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<QString> &description = {}); std::shared_ptr<QXmppDownload> downloadFile(const QXmppFileShare &fileShare, - std::unique_ptr<QIODevice> &&output); + std::unique_ptr<QIODevice> output); private: void internalRegisterProvider(std::type_index, std::shared_ptr<QXmppFileSharingProvider> 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 <jbb@kaidan.im> +// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im> // // SPDX-License-Identifier: LGPL-2.1-or-later #ifndef QXMPPFILESHARINGPROVIDER_H #define QXMPPFILESHARINGPROVIDER_H +#include "QXmppError.h" #include "QXmppGlobal.h" #include <any> +#include <functional> #include <memory> +#include <variant> 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<QXmpp::Success, QXmpp::Cancelled, QXmppError>; + + /// Contains std::any (created file source), QXmpp::Cancelled (manually cancelled) or + /// QXmppError (an error occured while uploading). + using UploadResult = std::variant<std::any /* source */, QXmpp::Cancelled, QXmppError>; + + /// 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<QIODevice> &&target) -> std::shared_ptr<QXmppDownload> = 0; + virtual auto downloadFile(const std::any &source, + std::unique_ptr<QIODevice> target, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(DownloadResult)> reportFinished) -> std::shared_ptr<Download> = 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<QIODevice> source, const QXmppFileMetadata &info) -> std::shared_ptr<QXmppUpload> = 0; + virtual auto uploadFile(std::unique_ptr<QIODevice> source, + const QXmppFileMetadata &info, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(UploadResult)> reportFinished) -> std::shared_ptr<Upload> = 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 <jbb@kaidan.im> +// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im> // // 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 <QMimeDatabase> #include <QNetworkReply> @@ -25,96 +25,6 @@ using namespace QXmpp::Private; /// \since QXmpp 1.5 /// -class QXmppSfsHttpUpload : public QXmppUpload -{ - Q_OBJECT - -public: - QXmppSfsHttpUpload(std::shared_ptr<QXmppHttpUpload> &&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<decltype(value)>; - if constexpr (std::is_same_v<T, QUrl>) { - return std::any(QXmppHttpFileSource(value)); - } else if constexpr (std::is_same_v<T, Cancelled>) { - return Cancelled {}; - } else if constexpr (std::is_same_v<T, QXmppError>) { - 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<QXmppHttpUpload> 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<QXmppHttpFileSharingProviderPrivate>()) { - qRegisterMetaType<QXmpp::Private::UploadResult>(); Q_ASSERT(client); d->manager = client->findExtension<QXmppHttpUploadManager>(); 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<QIODevice> &&target) - -> std::shared_ptr<QXmppDownload> +auto QXmppHttpFileSharingProvider::downloadFile(const std::any &source, + std::unique_ptr<QIODevice> target, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(DownloadResult)> reportFinished) + -> std::shared_ptr<Download> { + 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<QXmppHttpFileSource>(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<QXmppHttpDownload>(); - - 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>(); + 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<QIODevice>(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<QNetworkReply::NetworkError>::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<QXmppFileSharingProvider::Download>(state); } auto QXmppHttpFileSharingProvider::uploadFile(std::unique_ptr<QIODevice> data, - const QXmppFileMetadata &info) - -> std::shared_ptr<QXmppUpload> + const QXmppFileMetadata &info, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(UploadResult)> reportFinished) + -> std::shared_ptr<Upload> { + struct State : Upload + { + ~State() override = default; + + std::shared_ptr<QXmppHttpUpload> upload; + void cancel() override { upload->cancel(); } + }; + Q_ASSERT(d->manager); - auto upload = d->manager->uploadFile( + auto state = std::make_shared<State>(); + 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<QXmppSfsHttpUpload>(std::move(upload)); -} + QObject::connect(state->upload.get(), &QXmppHttpUpload::finished, [state, reportFinished = std::move(reportFinished)](const QXmppHttpUpload::Result &result) mutable { + reportFinished(visitForward<UploadResult>(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<QXmppFileSharingProvider::Upload>(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<QIODevice> &&target) -> std::shared_ptr<QXmppDownload> override; - auto uploadFile(std::unique_ptr<QIODevice> data, - const QXmppFileMetadata &info) -> std::shared_ptr<QXmppUpload> override; + std::unique_ptr<QIODevice> target, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(DownloadResult)> reportFinished) -> std::shared_ptr<Download> override; + auto uploadFile(std::unique_ptr<QIODevice> source, + const QXmppFileMetadata &info, + std::function<void(quint64, quint64)> reportProgress, + std::function<void(UploadResult)> reportFinished) -> std::shared_ptr<Upload> override; private: std::unique_ptr<QXmppHttpFileSharingProviderPrivate> 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 <variant> -namespace QXmpp::Private { -using UploadResult = std::variant<std::any /* source */, QXmpp::Cancelled, QXmppError>; -} - class QXMPP_EXPORT QXmppUpload : public QXmppFileTransfer { Q_OBJECT @@ -30,18 +25,10 @@ public: using Result = std::variant<FileResult, QXmpp::Cancelled, QXmppError>; 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 |
