aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2022-10-02 02:45:09 +0200
committerLinus Jahn <lnj@kaidan.im>2022-10-02 23:01:13 +0200
commitb871f6d3557c20fc7d1456e5080c33ea90795c84 (patch)
treefda2457ed2728a4cd6d346bce8168523b88c2b15 /src/client
parent6ee26103c5383cef8664d9e7d19c96c83f9a89af (diff)
downloadqxmpp-b871f6d3557c20fc7d1456e5080c33ea90795c84.tar.gz
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.
Diffstat (limited to 'src/client')
-rw-r--r--src/client/QXmppEncryptedHttpFileSharingProvider.cpp88
-rw-r--r--src/client/QXmppEncryptedHttpFileSharingProvider.h12
-rw-r--r--src/client/QXmppFileSharingManager.cpp198
-rw-r--r--src/client/QXmppFileSharingManager.h4
-rw-r--r--src/client/QXmppFileSharingProvider.h52
-rw-r--r--src/client/QXmppHttpFileSharingProvider.cpp218
-rw-r--r--src/client/QXmppHttpFileSharingProvider.h10
-rw-r--r--src/client/QXmppUpload.cpp2
-rw-r--r--src/client/QXmppUpload.h13
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