aboutsummaryrefslogtreecommitdiff
path: root/src/client/QXmppFileSharingManager.cpp
blob: 2daf980a7eee06e3096e1470e97fc5a73b6c1efd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "QXmppFileSharingManager.h"

#include "QXmppBitsOfBinaryContentId.h"
#include "QXmppBitsOfBinaryData.h"
#include "QXmppClient.h"
#include "QXmppDownload.h"
#include "QXmppFileMetadata.h"
#include "QXmppFileShare.h"
#include "QXmppFutureUtils_p.h"
#include "QXmppHashing_p.h"
#include "QXmppThumbnail.h"
#include "QXmppUpload.h"
#include "QXmppUploadRequestManager.h"

#include <any>

#include <QFile>
#include <QFileInfo>
#include <QFutureInterface>
#include <QMimeDatabase>
#include <QNetworkAccessManager>
#include <QNetworkReply>

using namespace QXmpp;
using namespace QXmpp::Private;

///
/// \class QXmppFileSharingProvider
///
/// Base class for Stateless File Sharing providers
///
/// A provider defines the way that files can be uploaded and downloaded.
///
/// An example is the QXmppHttpFileSharingProvider, which uses HTTP File Upload.
///
/// \since QXmpp 1.5
///

///
/// \class QXmppFileSharingManager
///
/// The file sharing manager allows to easily send and retrieve files in a chat.
///
/// \since QXmpp 1.5
///

QXmppFileSharingManager::QXmppFileSharingManager()
    : m_metadataGenerator([](std::unique_ptr<QIODevice> &&) -> QFuture<std::shared_ptr<MetadataGeneratorResult>> {
          return makeReadyFuture(std::make_shared<MetadataGeneratorResult>());
      })
{
}

///
/// \typedef QXmppFileSharingManager::MetadataGenerator
///
/// The function signature of a metadata generator function
///

///
/// \brief Register a function that is called when metadata needs to be gererated for a file.
///
/// The function is passed a QIODevice, so if you need the path of the file on disk,
/// you can dynamically cast it to a QFile and access the fileName.
/// When doing that, make sure to check the result,
/// as in the future this function might be passed other QIODevices than QFile.
///
void QXmppFileSharingManager::setMetadataGenerator(MetadataGenerator &&generator)
{
    m_metadataGenerator = std::move(generator);
}

///
/// \brief Upload a file in a way that it can be attached to a message.
/// \param provider The provider class decides how the file is uploaded
/// \param filePath Path to a file that should be uploaded
/// \param description Optional description of the file
/// \return An object that allows to track the progress of the upload.
///         Once the upload is finished, the finished signal is emitted on it.
///
std::shared_ptr<QXmppUpload> QXmppFileSharingManager::sendFile(std::shared_ptr<QXmppFileSharingProvider> provider,
                                                               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);
    }

    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));
        }
        return device;
    };

    auto metadataFuture = m_metadataGenerator(openFile());
    auto hashesFuture = calculateHashes(openFile(), { HashAlgorithm::Sha256 });

    upload = provider->uploadFile(openFile(), metadata);
    upload->metadata = metadata;

    connect(upload.get(), &QXmppUpload::uploadFinished, this, [=](auto &&uploadResult) {
        if (std::holds_alternative<std::any>(uploadResult)) {
            auto source = std::get<std::any>(uploadResult);
            await(metadataFuture, this, [=](auto &&result) mutable {
                if (result->dimensions) {
                    upload->metadata.setWidth(result->dimensions->width());
                    upload->metadata.setHeight(result->dimensions->height());
                }
                if (result->length) {
                    upload->metadata.setLength(*result->length);
                }

                QVector<QXmppThumbnail> thumbnails;
                thumbnails.reserve(result->thumbnails.size());
                QXmppBitsOfBinaryDataList dataBlobs;
                dataBlobs.reserve(result->thumbnails.size());

                for (const auto &metadataThumb : result->thumbnails) {
                    auto bobData = QXmppBitsOfBinaryData::fromByteArray(metadataThumb.data);
                    bobData.setContentType(metadataThumb.mimeType);

                    QXmppThumbnail thumbnail;
                    thumbnail.setHeight(metadataThumb.height);
                    thumbnail.setWidth(metadataThumb.width);
                    thumbnail.setMediaType(metadataThumb.mimeType);
                    thumbnail.setUri(bobData.cid().toCidUrl());

                    thumbnails.append(std::move(thumbnail));
                    dataBlobs.append(std::move(bobData));
                }
                upload->metadata.setThumbnails(thumbnails);

                await(hashesFuture, this, [=, dataBlobs = std::move(dataBlobs)](const auto &hashResult) mutable {
                    QVector<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);
                        std::transform(hashesVector.begin(), hashesVector.end(),
                                       std::back_inserter(hashes), [](auto &&hash) {
                                           return hash;
                                       });

                        upload->metadata.setHashes(hashes);

                        QXmppFileShare fs;
                        fs.setMetadata(upload->metadata);
                        fs.addSource(std::move(source));

                        Q_EMIT upload->finished(QXmppUpload::FileResult { fs, std::move(dataBlobs) });
                    } else if (std::holds_alternative<QXmppError>(hashValue)) {
                        Q_EMIT upload->finished(std::get<QXmppError>(hashValue));
                    }
                });
            });
        } else if (std::holds_alternative<QXmppError>(uploadResult)) {
            Q_EMIT upload->finished(std::get<QXmppError>(uploadResult));
        }
    });

    return upload;
}

///
/// \brief Download a file from a QXmppFileShare
///
/// \warning This function currently does not check the hash of the downloaded file.
///
/// Make sure to register the provider
/// that handles the sources used in this file share before calling this function.
///
/// \param fileShare The file share object which you want to download
/// \param output An open QIODevice that the data should be written into.
///               In most cases, a QFile
/// \return An object that allows to track the progress of the download.
///
std::shared_ptr<QXmppDownload> QXmppFileSharingManager::downloadFile(
    const QXmppFileShare &fileShare,
    std::unique_ptr<QIODevice> &&output)
{
    std::shared_ptr<QXmppDownload> download;
    fileShare.visitSources([&](const std::any &source) {
        std::type_index index(source.type());
        try {
            auto provider = m_providers.at(index);
            download = provider->downloadFile(source, std::move(output));
            return true;
        } catch (const std::out_of_range &) {
            return false;
        }
    });

    return download;
}

void QXmppFileSharingManager::internalRegisterProvider(std::type_index index, std::shared_ptr<QXmppFileSharingProvider> provider)
{
    m_providers.insert_or_assign(index, provider);
}