Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #pragma once
18 :
19 : #include "jami/datatransfer_interface.h"
20 : #include "noncopyable.h"
21 :
22 : #include <dhtnet/multiplexed_socket.h>
23 :
24 : #include <fstream>
25 : #include <functional>
26 : #include <memory>
27 : #include <string>
28 :
29 : namespace jami {
30 :
31 : libjami::DataTransferId generateUID(std::mt19937_64& engine);
32 :
33 : /**
34 : * Build the identifier (and on-disk name) of a transferred file from its commit.
35 : * @return "<commitId>_<tid>[.<extension>]", or an empty string when tid is not the decimal
36 : * transfer id generated by the sender, so a forged commit can never name a file.
37 : */
38 : std::string getFileId(const std::string& commitId, const std::string& tid, const std::string& displayName);
39 :
40 : /**
41 : * Check that a file transfer identifier, whether built locally or received from a
42 : * peer, is a plain file name of the form produced by getFileId(): it is joined to
43 : * the conversation data directory so it MUST NOT contain any path component.
44 : */
45 : bool isValidFileId(std::string_view fileId) noexcept;
46 :
47 : class Stream;
48 :
49 : struct IncomingFileInfo
50 : {
51 : libjami::DataTransferId id;
52 : std::shared_ptr<Stream> stream;
53 : };
54 :
55 : struct WaitingRequest
56 : {
57 : std::string fileId;
58 : std::string interactionId;
59 : std::string sha3sum;
60 : std::string path;
61 : std::size_t totalSize;
62 14 : MSGPACK_DEFINE(fileId, interactionId, sha3sum, path, totalSize)
63 : };
64 :
65 : typedef std::function<void(const std::string&)> InternalCompletionCb;
66 : typedef std::function<void()> OnFinishedCb;
67 :
68 : class FileInfo
69 : {
70 : public:
71 : FileInfo(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
72 : const std::string& fileId,
73 : const std::string& interactionId,
74 : const libjami::DataTransferInfo& info);
75 156 : virtual ~FileInfo() {}
76 : virtual void process() = 0;
77 : std::shared_ptr<dhtnet::ChannelSocket> channel() const { return channel_; }
78 0 : libjami::DataTransferInfo info() const { return info_; }
79 : virtual void cancel() = 0;
80 156 : void onFinished(std::function<void(uint32_t)>&& cb) { finishedCb_ = std::move(cb); }
81 : void emit(libjami::DataTransferEventCode code);
82 :
83 : protected:
84 : std::atomic_bool isUserCancelled_ {false};
85 : std::string fileId_ {};
86 : std::string interactionId_ {};
87 : libjami::DataTransferInfo info_ {};
88 : std::shared_ptr<dhtnet::ChannelSocket> channel_ {};
89 : std::function<void(uint32_t)> finishedCb_ {};
90 : };
91 :
92 : class IncomingFile : public FileInfo, public std::enable_shared_from_this<IncomingFile>
93 : {
94 : public:
95 : /** Moves the verified partial file into place; false if the destination is unusable. */
96 : using InstallCb = std::function<bool(const std::filesystem::path& partial)>;
97 :
98 : IncomingFile(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
99 : const libjami::DataTransferInfo& info,
100 : const std::string& fileId,
101 : const std::string& interactionId,
102 : const std::string& sha3Sum,
103 : const std::filesystem::path& temporaryPath);
104 : ~IncomingFile();
105 : void process() override;
106 : void cancel() override;
107 13 : void onInstall(InstallCb&& cb) { installCb_ = std::move(cb); }
108 :
109 : private:
110 : std::mutex streamMtx_;
111 : std::ofstream stream_;
112 : std::string sha3Sum_ {};
113 : std::filesystem::path path_;
114 : InstallCb installCb_ {};
115 : };
116 :
117 : class OutgoingFile : public FileInfo
118 : {
119 : public:
120 : OutgoingFile(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
121 : const std::string& fileId,
122 : const std::string& interactionId,
123 : const libjami::DataTransferInfo& info,
124 : size_t start = 0,
125 : size_t end = 0);
126 : ~OutgoingFile();
127 : void process() override;
128 : void cancel() override;
129 :
130 : private:
131 : std::ifstream stream_;
132 : size_t start_ {0};
133 : size_t end_ {0};
134 : };
135 :
136 : class TransferManager : public std::enable_shared_from_this<TransferManager>
137 : {
138 : public:
139 : enum class WaitResult { waiting, complete, conflict };
140 :
141 : TransferManager(const std::string& accountId,
142 : const std::string& accountUri,
143 : const std::string& to,
144 : const std::mt19937_64& rand);
145 : ~TransferManager();
146 :
147 : /**
148 : * Send a file to a channel
149 : * @param channel channel to use
150 : * @param fileId fileId of the transfer
151 : * @param interactionId interactionId of the transfer
152 : * @param path path of the file
153 : * @param start start offset
154 : * @param end end
155 : */
156 : void transferFile(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
157 : const std::string& fileId,
158 : const std::string& interactionId,
159 : const std::string& path,
160 : size_t start = 0,
161 : size_t end = 0,
162 : OnFinishedCb onFinished = {});
163 :
164 : /**
165 : * Refuse a transfer
166 : * @param id of the transfer
167 : */
168 : bool cancel(const std::string& fileId);
169 :
170 : /**
171 : * Get current transfer info
172 : * @param id of the transfer
173 : * @param total size
174 : * @param path path of the file
175 : * @param progress current progress
176 : * @return if found
177 : */
178 : bool info(const std::string& fileId, std::string& path, int64_t& total, int64_t& progress) const noexcept;
179 :
180 : /**
181 : * Make the transfer index resolve to the expected content, indexing `candidate` if needed.
182 : * @param fileId id of the transfer
183 : * @param candidate file already verified by the caller to hold the expected content
184 : * @param sha3sum expected SHA3-512 digest
185 : * @param total expected file size
186 : * @param independent require independent storage using a hard link or copy
187 : * @return true if the index resolves to the expected file
188 : */
189 : bool indexFile(const std::string& fileId,
190 : const std::filesystem::path& candidate,
191 : const std::string& sha3sum,
192 : std::size_t total,
193 : bool independent = false);
194 :
195 : /**
196 : * Request a file at a destination.
197 : * @param id of the transfer
198 : * @param interactionId linked interaction
199 : * @param sha3sum attended sha3sum
200 : * @param path where the file will be downloaded, the index entry if empty
201 : * @param total total size of the file
202 : * @return complete if the destination already holds the file, conflict if it cannot,
203 : * waiting if the transfer is now waited (and will be automatically accepted)
204 : */
205 : WaitResult waitForTransfer(const std::string& fileId,
206 : const std::string& interactionId,
207 : const std::string& sha3sum,
208 : const std::string& path,
209 : std::size_t total);
210 :
211 : /**
212 : * Handle incoming transfer
213 : * @param id Related id
214 : * @param channel Related channel
215 : * @param start Offset in the file from which the transfer will start.
216 : */
217 : void onIncomingFileTransfer(const std::string& fileId,
218 : const std::shared_ptr<dhtnet::ChannelSocket>& channel,
219 : size_t start);
220 :
221 : /**
222 : * Retrieve path of a file
223 : * @param id
224 : */
225 : std::filesystem::path path(const std::string& fileId) const;
226 :
227 : /**
228 : * Retrieve the private partial-download path for a transfer destination.
229 : */
230 : std::filesystem::path temporaryPath(const std::string& fileId, const std::filesystem::path& destination) const;
231 :
232 : /**
233 : * Retrieve waiting files
234 : * @return waiting list
235 : */
236 : std::vector<WaitingRequest> waitingRequests() const;
237 : bool isWaiting(const std::string& fileId) const;
238 : void onIncomingProfile(const std::shared_ptr<dhtnet::ChannelSocket>& channel, const std::string& sha3Sum = "");
239 :
240 : /**
241 : * @param contactId contact's id
242 : * @return where profile.vcf is stored
243 : */
244 : std::filesystem::path profilePath(const std::string& contactId) const;
245 :
246 : private:
247 169 : std::weak_ptr<TransferManager> weak() { return std::static_pointer_cast<TransferManager>(shared_from_this()); }
248 : bool installIndex(const std::string& fileId,
249 : const std::filesystem::path& candidate,
250 : const std::string& sha3sum,
251 : std::size_t total,
252 : bool independent,
253 : bool verifyCandidate);
254 : /** Make `destination` hold the indexed content. */
255 : bool exportFile(const std::string& fileId,
256 : const std::filesystem::path& destination,
257 : const std::string& sha3sum,
258 : std::size_t total);
259 : /** Install a verified partial download at `destination` and index it. */
260 : bool installTransfer(const std::string& fileId,
261 : const std::filesystem::path& partial,
262 : const std::filesystem::path& destination,
263 : const std::string& sha3sum,
264 : std::size_t total);
265 : NON_COPYABLE(TransferManager);
266 : class Impl;
267 : std::unique_ptr<Impl> pimpl_;
268 : };
269 :
270 : } // namespace jami
|