librats 0.3.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +405 -405
- package/binding.gyp +96 -95
- package/lib/index.d.ts +522 -522
- package/lib/index.js +82 -82
- package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
- package/native-src/CMakeLists.txt +360 -0
- package/native-src/LICENSE +21 -0
- package/native-src/src/bencode.cpp +485 -0
- package/native-src/src/bencode.h +145 -0
- package/native-src/src/bittorrent.cpp +3682 -0
- package/native-src/src/bittorrent.h +731 -0
- package/native-src/src/dht.cpp +2342 -0
- package/native-src/src/dht.h +501 -0
- package/native-src/src/encrypted_socket.cpp +817 -0
- package/native-src/src/encrypted_socket.h +239 -0
- package/native-src/src/file_transfer.cpp +1808 -0
- package/native-src/src/file_transfer.h +567 -0
- package/native-src/src/fs.cpp +639 -0
- package/native-src/src/fs.h +108 -0
- package/native-src/src/gossipsub.cpp +1137 -0
- package/native-src/src/gossipsub.h +403 -0
- package/native-src/src/ice.cpp +1386 -0
- package/native-src/src/ice.h +328 -0
- package/native-src/src/json.hpp +25526 -0
- package/native-src/src/krpc.cpp +558 -0
- package/native-src/src/krpc.h +145 -0
- package/native-src/src/librats.cpp +2715 -0
- package/native-src/src/librats.h +1729 -0
- package/native-src/src/librats_bittorrent.cpp +167 -0
- package/native-src/src/librats_c.cpp +1317 -0
- package/native-src/src/librats_c.h +237 -0
- package/native-src/src/librats_encryption.cpp +123 -0
- package/native-src/src/librats_file_transfer.cpp +226 -0
- package/native-src/src/librats_gossipsub.cpp +293 -0
- package/native-src/src/librats_ice.cpp +515 -0
- package/native-src/src/librats_logging.cpp +158 -0
- package/native-src/src/librats_mdns.cpp +171 -0
- package/native-src/src/librats_nat.cpp +571 -0
- package/native-src/src/librats_persistence.cpp +815 -0
- package/native-src/src/logger.h +412 -0
- package/native-src/src/mdns.cpp +1178 -0
- package/native-src/src/mdns.h +253 -0
- package/native-src/src/network_utils.cpp +598 -0
- package/native-src/src/network_utils.h +162 -0
- package/native-src/src/noise.cpp +981 -0
- package/native-src/src/noise.h +227 -0
- package/native-src/src/os.cpp +371 -0
- package/native-src/src/os.h +40 -0
- package/native-src/src/rats_export.h +17 -0
- package/native-src/src/sha1.cpp +163 -0
- package/native-src/src/sha1.h +42 -0
- package/native-src/src/socket.cpp +1376 -0
- package/native-src/src/socket.h +309 -0
- package/native-src/src/stun.cpp +484 -0
- package/native-src/src/stun.h +349 -0
- package/native-src/src/threadmanager.cpp +105 -0
- package/native-src/src/threadmanager.h +53 -0
- package/native-src/src/tracker.cpp +1110 -0
- package/native-src/src/tracker.h +268 -0
- package/native-src/src/version.cpp +24 -0
- package/native-src/src/version.h.in +45 -0
- package/native-src/version.rc.in +31 -0
- package/package.json +62 -68
- package/scripts/build-librats.js +241 -194
- package/scripts/postinstall.js +52 -52
- package/scripts/prepare-package.js +187 -91
- package/scripts/verify-installation.js +119 -119
- package/src/librats_node.cpp +1174 -1174
|
@@ -0,0 +1,1808 @@
|
|
|
1
|
+
#include "file_transfer.h"
|
|
2
|
+
#include "librats.h"
|
|
3
|
+
#include "fs.h"
|
|
4
|
+
#include "logger.h"
|
|
5
|
+
#include "sha1.h"
|
|
6
|
+
|
|
7
|
+
// Define logging module for this file
|
|
8
|
+
#define LOG_FILE_TRANSFER_INFO(message) LOG_INFO("filetransfer", message)
|
|
9
|
+
#define LOG_FILE_TRANSFER_ERROR(message) LOG_ERROR("filetransfer", message)
|
|
10
|
+
#define LOG_FILE_TRANSFER_WARN(message) LOG_WARN("filetransfer", message)
|
|
11
|
+
#define LOG_FILE_TRANSFER_DEBUG(message) LOG_DEBUG("filetransfer", message)
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
#include <random>
|
|
14
|
+
#include <iomanip>
|
|
15
|
+
#include <sstream>
|
|
16
|
+
#include <cstring>
|
|
17
|
+
#include <cstdlib>
|
|
18
|
+
|
|
19
|
+
// Optional compression support
|
|
20
|
+
#ifdef LIBRATS_ENABLE_ZLIB
|
|
21
|
+
#include <zlib.h>
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
#ifdef LIBRATS_ENABLE_LZ4
|
|
25
|
+
#include <lz4.h>
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
namespace librats {
|
|
29
|
+
|
|
30
|
+
//=============================================================================
|
|
31
|
+
// DirectoryMetadata Implementation
|
|
32
|
+
//=============================================================================
|
|
33
|
+
|
|
34
|
+
uint64_t DirectoryMetadata::get_total_size() const {
|
|
35
|
+
uint64_t total = 0;
|
|
36
|
+
|
|
37
|
+
// Add size of files in this directory
|
|
38
|
+
for (const auto& file : files) {
|
|
39
|
+
total += file.file_size;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Add size of subdirectories recursively
|
|
43
|
+
for (const auto& subdir : subdirectories) {
|
|
44
|
+
total += subdir.get_total_size();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return total;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
size_t DirectoryMetadata::get_total_file_count() const {
|
|
51
|
+
size_t count = files.size();
|
|
52
|
+
|
|
53
|
+
// Add files from subdirectories recursively
|
|
54
|
+
for (const auto& subdir : subdirectories) {
|
|
55
|
+
count += subdir.get_total_file_count();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return count;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//=============================================================================
|
|
62
|
+
// FileTransferProgress Implementation
|
|
63
|
+
//=============================================================================
|
|
64
|
+
|
|
65
|
+
void FileTransferProgress::update_transfer_rates(uint64_t new_bytes_transferred) {
|
|
66
|
+
auto now = std::chrono::steady_clock::now();
|
|
67
|
+
auto time_diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update);
|
|
68
|
+
|
|
69
|
+
if (time_diff.count() > 0) {
|
|
70
|
+
uint64_t bytes_diff = new_bytes_transferred - bytes_transferred;
|
|
71
|
+
transfer_rate_bps = (static_cast<double>(bytes_diff) * 1000.0) / time_diff.count();
|
|
72
|
+
|
|
73
|
+
// Calculate average rate since start
|
|
74
|
+
auto total_time = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
|
|
75
|
+
if (total_time.count() > 0) {
|
|
76
|
+
average_rate_bps = (static_cast<double>(new_bytes_transferred) * 1000.0) / total_time.count();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Estimate time remaining
|
|
80
|
+
if (transfer_rate_bps > 0 && total_bytes > new_bytes_transferred) {
|
|
81
|
+
uint64_t remaining_bytes = total_bytes - new_bytes_transferred;
|
|
82
|
+
estimated_time_remaining = std::chrono::milliseconds(
|
|
83
|
+
static_cast<int64_t>((remaining_bytes * 1000.0) / transfer_rate_bps)
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
estimated_time_remaining = std::chrono::milliseconds(0);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
bytes_transferred = new_bytes_transferred;
|
|
91
|
+
last_update = now;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//=============================================================================
|
|
95
|
+
// FileTransferManager Implementation
|
|
96
|
+
//=============================================================================
|
|
97
|
+
|
|
98
|
+
FileTransferManager::FileTransferManager(RatsClient& client, const FileTransferConfig& config)
|
|
99
|
+
: client_(client), config_(config), running_(true),
|
|
100
|
+
total_bytes_sent_(0), total_bytes_received_(0),
|
|
101
|
+
total_files_sent_(0), total_files_received_(0) {
|
|
102
|
+
|
|
103
|
+
start_time_ = std::chrono::steady_clock::now();
|
|
104
|
+
initialize();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
FileTransferManager::~FileTransferManager() {
|
|
108
|
+
shutdown();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
void FileTransferManager::initialize() {
|
|
112
|
+
// Ensure temp directory exists
|
|
113
|
+
create_directories(config_.temp_directory.c_str());
|
|
114
|
+
|
|
115
|
+
// Register message handlers with RatsClient
|
|
116
|
+
client_.on("file_transfer_request", [this](const std::string& peer_id, const nlohmann::json& data) {
|
|
117
|
+
handle_transfer_request(peer_id, data);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
client_.on("file_transfer_response", [this](const std::string& peer_id, const nlohmann::json& data) {
|
|
121
|
+
handle_transfer_response(peer_id, data);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
client_.on("file_chunk_metadata", [this](const std::string& peer_id, const nlohmann::json& data) {
|
|
125
|
+
handle_chunk_metadata_message(peer_id, data);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Note: Binary chunk data will be handled through the global binary_data_callback_
|
|
129
|
+
// The FileTransferManager will check for file chunk magic headers in the callback
|
|
130
|
+
|
|
131
|
+
client_.on("file_chunk_ack", [this](const std::string& peer_id, const nlohmann::json& data) {
|
|
132
|
+
handle_chunk_ack_message(peer_id, data);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
client_.on("file_transfer_control", [this](const std::string& peer_id, const nlohmann::json& data) {
|
|
136
|
+
handle_transfer_control(peer_id, data);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
client_.on("file_request", [this](const std::string& peer_id, const nlohmann::json& data) {
|
|
140
|
+
handle_file_request(peer_id, data);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
client_.on("directory_request", [this](const std::string& peer_id, const nlohmann::json& data) {
|
|
144
|
+
handle_directory_request(peer_id, data);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Start worker threads
|
|
148
|
+
for (uint32_t i = 0; i < config_.max_concurrent_chunks; ++i) {
|
|
149
|
+
worker_threads_.emplace_back(&FileTransferManager::worker_thread_loop, this);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Start cleanup thread for pending chunks timeout
|
|
153
|
+
worker_threads_.emplace_back(&FileTransferManager::cleanup_thread_loop, this);
|
|
154
|
+
|
|
155
|
+
LOG_FILE_TRANSFER_INFO("FileTransferManager initialized with " << worker_threads_.size() << " worker threads");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
void FileTransferManager::shutdown() {
|
|
159
|
+
LOG_FILE_TRANSFER_INFO("FileTransferManager stopping...");
|
|
160
|
+
running_.store(false);
|
|
161
|
+
|
|
162
|
+
// Notify all condition variables to wake up waiting threads immediately
|
|
163
|
+
work_condition_.notify_all();
|
|
164
|
+
cleanup_condition_.notify_all();
|
|
165
|
+
throttle_condition_.notify_all();
|
|
166
|
+
|
|
167
|
+
// Join all worker threads
|
|
168
|
+
for (auto& thread : worker_threads_) {
|
|
169
|
+
if (thread.joinable()) {
|
|
170
|
+
thread.join();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
worker_threads_.clear();
|
|
175
|
+
LOG_FILE_TRANSFER_INFO("FileTransferManager stopped");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
void FileTransferManager::worker_thread_loop() {
|
|
179
|
+
while (running_.load()) {
|
|
180
|
+
std::unique_lock<std::mutex> lock(work_mutex_);
|
|
181
|
+
work_condition_.wait(lock, [this] { return !work_queue_.empty() || !running_.load(); });
|
|
182
|
+
|
|
183
|
+
if (!running_.load()) {
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!work_queue_.empty()) {
|
|
188
|
+
std::string transfer_id = work_queue_.front();
|
|
189
|
+
work_queue_.pop();
|
|
190
|
+
lock.unlock();
|
|
191
|
+
|
|
192
|
+
process_transfer(transfer_id);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
void FileTransferManager::cleanup_thread_loop() {
|
|
198
|
+
const auto cleanup_interval = std::chrono::seconds(5);
|
|
199
|
+
const auto pending_timeout = std::chrono::seconds(30);
|
|
200
|
+
|
|
201
|
+
while (running_.load()) {
|
|
202
|
+
// Use condition variable with timeout instead of sleep
|
|
203
|
+
std::unique_lock<std::mutex> lock(cleanup_mutex_);
|
|
204
|
+
cleanup_condition_.wait_for(lock, cleanup_interval, [this] { return !running_.load(); });
|
|
205
|
+
|
|
206
|
+
if (!running_.load()) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Clean up expired pending chunks
|
|
211
|
+
auto now = std::chrono::steady_clock::now();
|
|
212
|
+
std::vector<std::string> expired_peers;
|
|
213
|
+
|
|
214
|
+
{
|
|
215
|
+
std::lock_guard<std::mutex> lock(chunks_mutex_);
|
|
216
|
+
for (auto it = pending_chunks_.begin(); it != pending_chunks_.end();) {
|
|
217
|
+
if (now - it->second.created_at > pending_timeout) {
|
|
218
|
+
LOG_FILE_TRANSFER_WARN("Pending chunk from peer " << it->first <<
|
|
219
|
+
" timed out (transfer: " << it->second.transfer_id <<
|
|
220
|
+
", chunk: " << it->second.chunk_index << ")");
|
|
221
|
+
expired_peers.push_back(it->first);
|
|
222
|
+
it = pending_chunks_.erase(it);
|
|
223
|
+
} else {
|
|
224
|
+
++it;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Send negative acknowledgments for expired chunks
|
|
230
|
+
for (const auto& peer_id : expired_peers) {
|
|
231
|
+
// We don't have direct access to the transfer info here, but the timeout logged above
|
|
232
|
+
// provides sufficient information for debugging
|
|
233
|
+
LOG_FILE_TRANSFER_DEBUG("Cleaned up " << expired_peers.size() << " expired pending chunks");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
void FileTransferManager::set_config(const FileTransferConfig& config) {
|
|
239
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
240
|
+
config_ = config;
|
|
241
|
+
|
|
242
|
+
// Ensure temp directory exists
|
|
243
|
+
create_directories(config_.temp_directory.c_str());
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const FileTransferConfig& FileTransferManager::get_config() const {
|
|
247
|
+
return config_;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
bool FileTransferManager::handle_binary_data(const std::string& peer_id, const std::vector<uint8_t>& binary_data) {
|
|
251
|
+
// Check if this is a file chunk binary message
|
|
252
|
+
const std::string magic = "FTCHUNK";
|
|
253
|
+
|
|
254
|
+
if (binary_data.size() >= magic.length() &&
|
|
255
|
+
std::memcmp(binary_data.data(), magic.c_str(), magic.length()) == 0) {
|
|
256
|
+
|
|
257
|
+
// This is a file transfer chunk - handle it
|
|
258
|
+
handle_chunk_binary_message(peer_id, binary_data);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Not a file transfer chunk
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
void FileTransferManager::set_progress_callback(FileTransferProgressCallback callback) {
|
|
267
|
+
progress_callback_ = callback;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
void FileTransferManager::set_completion_callback(FileTransferCompletedCallback callback) {
|
|
271
|
+
completion_callback_ = callback;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
void FileTransferManager::set_request_callback(FileTransferRequestCallback callback) {
|
|
275
|
+
request_callback_ = callback;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
void FileTransferManager::set_directory_progress_callback(DirectoryTransferProgressCallback callback) {
|
|
279
|
+
directory_progress_callback_ = callback;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
void FileTransferManager::set_file_request_callback(FileRequestCallback callback) {
|
|
283
|
+
file_request_callback_ = callback;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
void FileTransferManager::set_directory_request_callback(DirectoryRequestCallback callback) {
|
|
287
|
+
directory_request_callback_ = callback;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
std::string FileTransferManager::send_file(const std::string& peer_id, const std::string& file_path,
|
|
291
|
+
const std::string& remote_filename) {
|
|
292
|
+
// Validate file
|
|
293
|
+
if (!validate_file_path(file_path, false)) {
|
|
294
|
+
LOG_FILE_TRANSFER_ERROR("Invalid file path: " << file_path);
|
|
295
|
+
return "";
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Get file metadata
|
|
299
|
+
FileMetadata metadata = get_file_metadata(file_path);
|
|
300
|
+
if (metadata.file_size == 0) {
|
|
301
|
+
LOG_FILE_TRANSFER_ERROR("Failed to get metadata for file: " << file_path);
|
|
302
|
+
return "";
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Use custom filename if provided
|
|
306
|
+
if (!remote_filename.empty()) {
|
|
307
|
+
metadata.filename = remote_filename;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return send_file_with_metadata(peer_id, file_path, metadata);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
std::string FileTransferManager::send_file_with_metadata(const std::string& peer_id, const std::string& file_path,
|
|
314
|
+
const FileMetadata& metadata) {
|
|
315
|
+
std::string transfer_id = generate_transfer_id();
|
|
316
|
+
|
|
317
|
+
// Create transfer progress tracking
|
|
318
|
+
auto progress = std::make_shared<FileTransferProgress>();
|
|
319
|
+
progress->transfer_id = transfer_id;
|
|
320
|
+
progress->peer_id = peer_id;
|
|
321
|
+
progress->direction = FileTransferDirection::SENDING;
|
|
322
|
+
progress->status = FileTransferStatus::STARTING;
|
|
323
|
+
progress->filename = metadata.filename;
|
|
324
|
+
progress->local_path = file_path;
|
|
325
|
+
progress->file_size = metadata.file_size;
|
|
326
|
+
progress->total_bytes = metadata.file_size;
|
|
327
|
+
progress->total_chunks = (metadata.file_size + config_.chunk_size - 1) / config_.chunk_size;
|
|
328
|
+
|
|
329
|
+
{
|
|
330
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
331
|
+
active_transfers_[transfer_id] = progress;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Send transfer request to peer
|
|
335
|
+
nlohmann::json request_msg = create_transfer_request_message(metadata, transfer_id);
|
|
336
|
+
client_.send(peer_id, "file_transfer_request", request_msg);
|
|
337
|
+
|
|
338
|
+
LOG_FILE_TRANSFER_INFO("Initiated file transfer request: " << transfer_id << " (" << metadata.filename << " -> " << peer_id << ")");
|
|
339
|
+
return transfer_id;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
std::string FileTransferManager::send_directory(const std::string& peer_id, const std::string& directory_path,
|
|
343
|
+
const std::string& remote_directory_name, bool recursive) {
|
|
344
|
+
// Validate directory
|
|
345
|
+
if (!directory_exists(directory_path)) {
|
|
346
|
+
LOG_FILE_TRANSFER_ERROR("Invalid directory path: " << directory_path);
|
|
347
|
+
return "";
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Get directory metadata
|
|
351
|
+
DirectoryMetadata dir_metadata = get_directory_metadata(directory_path, recursive);
|
|
352
|
+
if (dir_metadata.files.empty() && dir_metadata.subdirectories.empty()) {
|
|
353
|
+
LOG_FILE_TRANSFER_ERROR("Directory is empty: " << directory_path);
|
|
354
|
+
return "";
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Use custom directory name if provided
|
|
358
|
+
if (!remote_directory_name.empty()) {
|
|
359
|
+
dir_metadata.directory_name = remote_directory_name;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
std::string transfer_id = generate_transfer_id();
|
|
363
|
+
|
|
364
|
+
// Create transfer progress tracking for directory
|
|
365
|
+
auto progress = std::make_shared<FileTransferProgress>();
|
|
366
|
+
progress->transfer_id = transfer_id;
|
|
367
|
+
progress->peer_id = peer_id;
|
|
368
|
+
progress->direction = FileTransferDirection::SENDING;
|
|
369
|
+
progress->status = FileTransferStatus::STARTING;
|
|
370
|
+
progress->filename = dir_metadata.directory_name;
|
|
371
|
+
progress->local_path = directory_path;
|
|
372
|
+
progress->total_bytes = dir_metadata.get_total_size();
|
|
373
|
+
progress->file_size = progress->total_bytes;
|
|
374
|
+
|
|
375
|
+
{
|
|
376
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
377
|
+
active_transfers_[transfer_id] = progress;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Store directory metadata for processing
|
|
381
|
+
{
|
|
382
|
+
std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
|
|
383
|
+
active_directory_transfers_[transfer_id] = dir_metadata;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Create directory transfer request message
|
|
387
|
+
nlohmann::json request_msg;
|
|
388
|
+
request_msg["transfer_id"] = transfer_id;
|
|
389
|
+
request_msg["type"] = "directory";
|
|
390
|
+
|
|
391
|
+
// Serialize directory metadata with full file information
|
|
392
|
+
nlohmann::json dir_metadata_json;
|
|
393
|
+
dir_metadata_json["directory_name"] = dir_metadata.directory_name;
|
|
394
|
+
dir_metadata_json["relative_path"] = dir_metadata.relative_path;
|
|
395
|
+
dir_metadata_json["total_size"] = dir_metadata.get_total_size();
|
|
396
|
+
dir_metadata_json["total_files"] = dir_metadata.get_total_file_count();
|
|
397
|
+
|
|
398
|
+
// Serialize file metadata
|
|
399
|
+
nlohmann::json files_json = nlohmann::json::array();
|
|
400
|
+
for (const auto& file_meta : dir_metadata.files) {
|
|
401
|
+
nlohmann::json file_json;
|
|
402
|
+
file_json["filename"] = file_meta.filename;
|
|
403
|
+
file_json["relative_path"] = file_meta.relative_path;
|
|
404
|
+
file_json["file_size"] = file_meta.file_size;
|
|
405
|
+
file_json["last_modified"] = file_meta.last_modified;
|
|
406
|
+
file_json["mime_type"] = file_meta.mime_type;
|
|
407
|
+
file_json["checksum"] = file_meta.checksum;
|
|
408
|
+
files_json.push_back(file_json);
|
|
409
|
+
}
|
|
410
|
+
dir_metadata_json["files"] = files_json;
|
|
411
|
+
|
|
412
|
+
// For now, we'll handle single-level directories (can be extended for nested later)
|
|
413
|
+
dir_metadata_json["subdirectories"] = nlohmann::json::array();
|
|
414
|
+
|
|
415
|
+
request_msg["directory_metadata"] = dir_metadata_json;
|
|
416
|
+
|
|
417
|
+
client_.send(peer_id, "file_transfer_request", request_msg);
|
|
418
|
+
|
|
419
|
+
LOG_FILE_TRANSFER_INFO("Initiated directory transfer request: " << transfer_id << " (" << dir_metadata.directory_name << " -> " << peer_id << ")");
|
|
420
|
+
return transfer_id;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
std::string FileTransferManager::request_file(const std::string& peer_id, const std::string& remote_file_path,
|
|
424
|
+
const std::string& local_path) {
|
|
425
|
+
std::string transfer_id = generate_transfer_id();
|
|
426
|
+
|
|
427
|
+
// Create file request message
|
|
428
|
+
nlohmann::json request_msg;
|
|
429
|
+
request_msg["transfer_id"] = transfer_id;
|
|
430
|
+
request_msg["type"] = "file_request";
|
|
431
|
+
request_msg["remote_path"] = remote_file_path;
|
|
432
|
+
request_msg["local_path"] = local_path;
|
|
433
|
+
|
|
434
|
+
client_.send(peer_id, "file_request", request_msg);
|
|
435
|
+
|
|
436
|
+
LOG_FILE_TRANSFER_INFO("Sent file request: " << transfer_id << " (" << remote_file_path << " from " << peer_id << ")");
|
|
437
|
+
return transfer_id;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
std::string FileTransferManager::request_directory(const std::string& peer_id, const std::string& remote_directory_path,
|
|
441
|
+
const std::string& local_directory_path, bool recursive) {
|
|
442
|
+
std::string transfer_id = generate_transfer_id();
|
|
443
|
+
|
|
444
|
+
// Create directory request message
|
|
445
|
+
nlohmann::json request_msg;
|
|
446
|
+
request_msg["transfer_id"] = transfer_id;
|
|
447
|
+
request_msg["type"] = "directory_request";
|
|
448
|
+
request_msg["remote_path"] = remote_directory_path;
|
|
449
|
+
request_msg["local_path"] = local_directory_path;
|
|
450
|
+
request_msg["recursive"] = recursive;
|
|
451
|
+
|
|
452
|
+
client_.send(peer_id, "directory_request", request_msg);
|
|
453
|
+
|
|
454
|
+
LOG_FILE_TRANSFER_INFO("Sent directory request: " << transfer_id << " (" << remote_directory_path << " from " << peer_id << ")");
|
|
455
|
+
return transfer_id;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
bool FileTransferManager::accept_file_transfer(const std::string& transfer_id, const std::string& local_path) {
|
|
459
|
+
std::lock_guard<std::mutex> lock(pending_mutex_);
|
|
460
|
+
|
|
461
|
+
auto it = pending_transfers_.find(transfer_id);
|
|
462
|
+
if (it == pending_transfers_.end()) {
|
|
463
|
+
LOG_FILE_TRANSFER_ERROR("Transfer not found in pending transfers: " << transfer_id);
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
PendingFileTransfer pending_transfer = it->second;
|
|
468
|
+
pending_transfers_.erase(it);
|
|
469
|
+
|
|
470
|
+
// Create transfer progress tracking
|
|
471
|
+
auto progress = std::make_shared<FileTransferProgress>();
|
|
472
|
+
progress->transfer_id = transfer_id;
|
|
473
|
+
progress->peer_id = pending_transfer.peer_id;
|
|
474
|
+
progress->direction = FileTransferDirection::RECEIVING;
|
|
475
|
+
progress->status = FileTransferStatus::STARTING;
|
|
476
|
+
progress->filename = pending_transfer.metadata.filename;
|
|
477
|
+
progress->local_path = local_path;
|
|
478
|
+
progress->file_size = pending_transfer.metadata.file_size;
|
|
479
|
+
progress->total_bytes = pending_transfer.metadata.file_size;
|
|
480
|
+
progress->total_chunks = (pending_transfer.metadata.file_size + config_.chunk_size - 1) / config_.chunk_size;
|
|
481
|
+
|
|
482
|
+
{
|
|
483
|
+
std::lock_guard<std::mutex> transfers_lock(transfers_mutex_);
|
|
484
|
+
active_transfers_[transfer_id] = progress;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Send acceptance response
|
|
488
|
+
nlohmann::json response_msg = create_transfer_response_message(transfer_id, true);
|
|
489
|
+
client_.send(pending_transfer.peer_id, "file_transfer_response", response_msg);
|
|
490
|
+
|
|
491
|
+
// Add to work queue for processing
|
|
492
|
+
{
|
|
493
|
+
std::lock_guard<std::mutex> work_lock(work_mutex_);
|
|
494
|
+
work_queue_.push(transfer_id);
|
|
495
|
+
}
|
|
496
|
+
work_condition_.notify_one();
|
|
497
|
+
|
|
498
|
+
LOG_FILE_TRANSFER_INFO("Accepted file transfer: " << transfer_id << " (" << pending_transfer.metadata.filename << " from " << pending_transfer.peer_id << " -> " << local_path << ")");
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
bool FileTransferManager::reject_file_transfer(const std::string& transfer_id, const std::string& reason) {
|
|
503
|
+
std::lock_guard<std::mutex> lock(pending_mutex_);
|
|
504
|
+
|
|
505
|
+
auto it = pending_transfers_.find(transfer_id);
|
|
506
|
+
if (it == pending_transfers_.end()) {
|
|
507
|
+
LOG_FILE_TRANSFER_ERROR("Transfer not found in pending transfers: " << transfer_id);
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
PendingFileTransfer pending_transfer = it->second;
|
|
512
|
+
pending_transfers_.erase(it);
|
|
513
|
+
|
|
514
|
+
// Send rejection response
|
|
515
|
+
nlohmann::json response_msg = create_transfer_response_message(transfer_id, false, reason);
|
|
516
|
+
client_.send(pending_transfer.peer_id, "file_transfer_response", response_msg);
|
|
517
|
+
|
|
518
|
+
LOG_FILE_TRANSFER_INFO("Rejected file transfer: " << transfer_id << " (" << pending_transfer.metadata.filename << " from " << pending_transfer.peer_id << ", reason: " << reason << ")");
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
bool FileTransferManager::accept_directory_transfer(const std::string& transfer_id, const std::string& local_path) {
|
|
523
|
+
std::lock_guard<std::mutex> lock(pending_mutex_);
|
|
524
|
+
|
|
525
|
+
auto it = pending_directory_transfers_.find(transfer_id);
|
|
526
|
+
if (it == pending_directory_transfers_.end()) {
|
|
527
|
+
LOG_FILE_TRANSFER_ERROR("Directory transfer not found in pending transfers: " << transfer_id);
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
PendingDirectoryTransfer pending_transfer = it->second;
|
|
532
|
+
pending_directory_transfers_.erase(it);
|
|
533
|
+
|
|
534
|
+
// Create transfer progress tracking
|
|
535
|
+
auto progress = std::make_shared<FileTransferProgress>();
|
|
536
|
+
progress->transfer_id = transfer_id;
|
|
537
|
+
progress->peer_id = pending_transfer.peer_id;
|
|
538
|
+
progress->direction = FileTransferDirection::RECEIVING;
|
|
539
|
+
progress->status = FileTransferStatus::STARTING;
|
|
540
|
+
progress->filename = pending_transfer.metadata.directory_name;
|
|
541
|
+
progress->local_path = local_path;
|
|
542
|
+
progress->total_bytes = pending_transfer.metadata.get_total_size();
|
|
543
|
+
progress->file_size = progress->total_bytes;
|
|
544
|
+
progress->total_chunks = 0; // Will be calculated per file
|
|
545
|
+
|
|
546
|
+
{
|
|
547
|
+
std::lock_guard<std::mutex> transfers_lock(transfers_mutex_);
|
|
548
|
+
active_transfers_[transfer_id] = progress;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Send acceptance response
|
|
552
|
+
nlohmann::json response_msg = create_transfer_response_message(transfer_id, true);
|
|
553
|
+
client_.send(pending_transfer.peer_id, "file_transfer_response", response_msg);
|
|
554
|
+
|
|
555
|
+
// Store directory metadata for processing
|
|
556
|
+
{
|
|
557
|
+
std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
|
|
558
|
+
active_directory_transfers_[transfer_id] = pending_transfer.metadata;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Add to work queue for processing
|
|
562
|
+
{
|
|
563
|
+
std::lock_guard<std::mutex> work_lock(work_mutex_);
|
|
564
|
+
work_queue_.push(transfer_id);
|
|
565
|
+
}
|
|
566
|
+
work_condition_.notify_one();
|
|
567
|
+
|
|
568
|
+
LOG_FILE_TRANSFER_INFO("Accepted directory transfer: " << transfer_id << " (" << pending_transfer.metadata.directory_name << " from " << pending_transfer.peer_id << " -> " << local_path << ")");
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
bool FileTransferManager::reject_directory_transfer(const std::string& transfer_id, const std::string& reason) {
|
|
573
|
+
std::lock_guard<std::mutex> lock(pending_mutex_);
|
|
574
|
+
|
|
575
|
+
auto it = pending_directory_transfers_.find(transfer_id);
|
|
576
|
+
if (it == pending_directory_transfers_.end()) {
|
|
577
|
+
LOG_FILE_TRANSFER_ERROR("Directory transfer not found in pending transfers: " << transfer_id);
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
PendingDirectoryTransfer pending_transfer = it->second;
|
|
582
|
+
pending_directory_transfers_.erase(it);
|
|
583
|
+
|
|
584
|
+
// Send rejection response
|
|
585
|
+
nlohmann::json response_msg = create_transfer_response_message(transfer_id, false, reason);
|
|
586
|
+
client_.send(pending_transfer.peer_id, "file_transfer_response", response_msg);
|
|
587
|
+
|
|
588
|
+
LOG_FILE_TRANSFER_INFO("Rejected directory transfer: " << transfer_id << " (" << pending_transfer.metadata.directory_name << " from " << pending_transfer.peer_id << ", reason: " << reason << ")");
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
bool FileTransferManager::pause_transfer(const std::string& transfer_id) {
|
|
593
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
594
|
+
|
|
595
|
+
auto it = active_transfers_.find(transfer_id);
|
|
596
|
+
if (it == active_transfers_.end()) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
auto& progress = it->second;
|
|
601
|
+
if (progress->status == FileTransferStatus::IN_PROGRESS) {
|
|
602
|
+
progress->status = FileTransferStatus::PAUSED;
|
|
603
|
+
|
|
604
|
+
// Send pause control message
|
|
605
|
+
nlohmann::json control_msg = create_control_message(transfer_id, "pause");
|
|
606
|
+
client_.send(progress->peer_id, "file_transfer_control", control_msg);
|
|
607
|
+
|
|
608
|
+
LOG_FILE_TRANSFER_INFO("Paused transfer: " << transfer_id);
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
bool FileTransferManager::resume_transfer(const std::string& transfer_id) {
|
|
616
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
617
|
+
|
|
618
|
+
auto it = active_transfers_.find(transfer_id);
|
|
619
|
+
if (it == active_transfers_.end()) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
auto& progress = it->second;
|
|
624
|
+
if (progress->status == FileTransferStatus::PAUSED) {
|
|
625
|
+
progress->status = FileTransferStatus::RESUMING;
|
|
626
|
+
|
|
627
|
+
// Add to work queue for processing
|
|
628
|
+
{
|
|
629
|
+
std::lock_guard<std::mutex> work_lock(work_mutex_);
|
|
630
|
+
work_queue_.push(transfer_id);
|
|
631
|
+
}
|
|
632
|
+
work_condition_.notify_one();
|
|
633
|
+
|
|
634
|
+
// Send resume control message
|
|
635
|
+
nlohmann::json control_msg = create_control_message(transfer_id, "resume");
|
|
636
|
+
client_.send(progress->peer_id, "file_transfer_control", control_msg);
|
|
637
|
+
|
|
638
|
+
LOG_FILE_TRANSFER_INFO("Resumed transfer: " << transfer_id);
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
bool FileTransferManager::cancel_transfer(const std::string& transfer_id) {
|
|
646
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
647
|
+
|
|
648
|
+
auto it = active_transfers_.find(transfer_id);
|
|
649
|
+
if (it == active_transfers_.end()) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
auto& progress = it->second;
|
|
654
|
+
progress->status = FileTransferStatus::CANCELLED;
|
|
655
|
+
|
|
656
|
+
// Send cancel control message
|
|
657
|
+
nlohmann::json control_msg = create_control_message(transfer_id, "cancel");
|
|
658
|
+
client_.send(progress->peer_id, "file_transfer_control", control_msg);
|
|
659
|
+
|
|
660
|
+
// Move to completed transfers
|
|
661
|
+
move_to_completed(transfer_id);
|
|
662
|
+
|
|
663
|
+
LOG_FILE_TRANSFER_INFO("Cancelled transfer: " << transfer_id);
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
bool FileTransferManager::retry_transfer(const std::string& transfer_id) {
|
|
668
|
+
// Look in completed transfers for failed ones
|
|
669
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
670
|
+
|
|
671
|
+
auto it = completed_transfers_.find(transfer_id);
|
|
672
|
+
if (it == completed_transfers_.end()) {
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
auto progress = it->second;
|
|
677
|
+
if (progress->status != FileTransferStatus::FAILED) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Reset progress and move back to active
|
|
682
|
+
progress->status = FileTransferStatus::STARTING;
|
|
683
|
+
progress->bytes_transferred = 0;
|
|
684
|
+
progress->chunks_completed = 0;
|
|
685
|
+
progress->retry_count++;
|
|
686
|
+
progress->error_message.clear();
|
|
687
|
+
progress->start_time = std::chrono::steady_clock::now();
|
|
688
|
+
|
|
689
|
+
active_transfers_[transfer_id] = progress;
|
|
690
|
+
completed_transfers_.erase(it);
|
|
691
|
+
|
|
692
|
+
// Add to work queue
|
|
693
|
+
{
|
|
694
|
+
std::lock_guard<std::mutex> work_lock(work_mutex_);
|
|
695
|
+
work_queue_.push(transfer_id);
|
|
696
|
+
}
|
|
697
|
+
work_condition_.notify_one();
|
|
698
|
+
|
|
699
|
+
LOG_FILE_TRANSFER_INFO("Retrying transfer: " << transfer_id);
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
std::shared_ptr<FileTransferProgress> FileTransferManager::get_transfer_progress(const std::string& transfer_id) const {
|
|
704
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
705
|
+
|
|
706
|
+
auto it = active_transfers_.find(transfer_id);
|
|
707
|
+
if (it != active_transfers_.end()) {
|
|
708
|
+
return it->second;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
auto completed_it = completed_transfers_.find(transfer_id);
|
|
712
|
+
if (completed_it != completed_transfers_.end()) {
|
|
713
|
+
return completed_it->second;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return nullptr;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
std::vector<std::shared_ptr<FileTransferProgress>> FileTransferManager::get_active_transfers() const {
|
|
720
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
721
|
+
|
|
722
|
+
std::vector<std::shared_ptr<FileTransferProgress>> transfers;
|
|
723
|
+
transfers.reserve(active_transfers_.size());
|
|
724
|
+
|
|
725
|
+
for (const auto& pair : active_transfers_) {
|
|
726
|
+
transfers.push_back(pair.second);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return transfers;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
std::vector<std::shared_ptr<FileTransferProgress>> FileTransferManager::get_transfer_history(size_t limit) const {
|
|
733
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
734
|
+
|
|
735
|
+
std::vector<std::shared_ptr<FileTransferProgress>> transfers;
|
|
736
|
+
|
|
737
|
+
for (const auto& pair : completed_transfers_) {
|
|
738
|
+
transfers.push_back(pair.second);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Sort by completion time (most recent first)
|
|
742
|
+
std::sort(transfers.begin(), transfers.end(),
|
|
743
|
+
[](const auto& a, const auto& b) {
|
|
744
|
+
return a->last_update > b->last_update;
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
if (limit > 0 && transfers.size() > limit) {
|
|
748
|
+
transfers.resize(limit);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return transfers;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
void FileTransferManager::clear_transfer_history() {
|
|
755
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
756
|
+
completed_transfers_.clear();
|
|
757
|
+
LOG_FILE_TRANSFER_INFO("Cleared transfer history");
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
nlohmann::json FileTransferManager::get_transfer_statistics() const {
|
|
761
|
+
std::lock_guard<std::mutex> stats_lock(stats_mutex_);
|
|
762
|
+
std::lock_guard<std::mutex> transfers_lock(transfers_mutex_);
|
|
763
|
+
|
|
764
|
+
auto now = std::chrono::steady_clock::now();
|
|
765
|
+
auto uptime = std::chrono::duration_cast<std::chrono::seconds>(now - start_time_);
|
|
766
|
+
|
|
767
|
+
nlohmann::json stats;
|
|
768
|
+
stats["uptime_seconds"] = uptime.count();
|
|
769
|
+
stats["total_bytes_sent"] = total_bytes_sent_;
|
|
770
|
+
stats["total_bytes_received"] = total_bytes_received_;
|
|
771
|
+
stats["total_files_sent"] = total_files_sent_;
|
|
772
|
+
stats["total_files_received"] = total_files_received_;
|
|
773
|
+
stats["active_transfers"] = active_transfers_.size();
|
|
774
|
+
stats["completed_transfers"] = completed_transfers_.size();
|
|
775
|
+
|
|
776
|
+
// Calculate success rate
|
|
777
|
+
size_t successful_transfers = 0;
|
|
778
|
+
for (const auto& pair : completed_transfers_) {
|
|
779
|
+
if (pair.second->status == FileTransferStatus::COMPLETED) {
|
|
780
|
+
successful_transfers++;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (!completed_transfers_.empty()) {
|
|
785
|
+
stats["success_rate"] = static_cast<double>(successful_transfers) / completed_transfers_.size();
|
|
786
|
+
} else {
|
|
787
|
+
stats["success_rate"] = 0.0;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Average transfer rate
|
|
791
|
+
if (uptime.count() > 0) {
|
|
792
|
+
stats["average_send_rate_bps"] = static_cast<double>(total_bytes_sent_) / uptime.count();
|
|
793
|
+
stats["average_receive_rate_bps"] = static_cast<double>(total_bytes_received_) / uptime.count();
|
|
794
|
+
} else {
|
|
795
|
+
stats["average_send_rate_bps"] = 0.0;
|
|
796
|
+
stats["average_receive_rate_bps"] = 0.0;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return stats;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Static utility functions
|
|
803
|
+
std::string FileTransferManager::calculate_file_checksum(const std::string& file_path, const std::string& algorithm) {
|
|
804
|
+
if (algorithm == "sha256") {
|
|
805
|
+
// Use a simple SHA1 implementation for now (we can extend this)
|
|
806
|
+
size_t file_size;
|
|
807
|
+
void* file_data = read_file_binary(file_path.c_str(), &file_size);
|
|
808
|
+
if (!file_data) {
|
|
809
|
+
return "";
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
SHA1 sha1;
|
|
813
|
+
sha1.update(reinterpret_cast<const uint8_t*>(file_data), file_size);
|
|
814
|
+
std::string result = sha1.finalize();
|
|
815
|
+
|
|
816
|
+
free_file_buffer(file_data);
|
|
817
|
+
return result;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// For MD5 or other algorithms, we would need additional implementations
|
|
821
|
+
return "";
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
FileMetadata FileTransferManager::get_file_metadata(const std::string& file_path) {
|
|
825
|
+
FileMetadata metadata;
|
|
826
|
+
|
|
827
|
+
try {
|
|
828
|
+
if (!file_or_directory_exists(file_path.c_str())) {
|
|
829
|
+
return metadata;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
metadata.filename = get_filename_from_path(file_path.c_str());
|
|
833
|
+
int64_t file_size = get_file_size(file_path.c_str());
|
|
834
|
+
if (file_size >= 0) {
|
|
835
|
+
metadata.file_size = static_cast<uint64_t>(file_size);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Get last modification time
|
|
839
|
+
metadata.last_modified = get_file_modified_time(file_path.c_str());
|
|
840
|
+
|
|
841
|
+
// Calculate checksum (optional, can be expensive for large files)
|
|
842
|
+
if (metadata.file_size < 100 * 1024 * 1024) { // Only for files < 100MB
|
|
843
|
+
metadata.checksum = calculate_file_checksum(file_path, "sha256");
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Determine MIME type based on extension
|
|
847
|
+
metadata.mime_type = get_mime_type(file_path);
|
|
848
|
+
|
|
849
|
+
} catch (const std::exception& e) {
|
|
850
|
+
LOG_FILE_TRANSFER_ERROR("Failed to get file metadata for " << file_path << ": " << e.what());
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return metadata;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
DirectoryMetadata FileTransferManager::get_directory_metadata(const std::string& directory_path, bool recursive) {
|
|
857
|
+
DirectoryMetadata metadata;
|
|
858
|
+
|
|
859
|
+
try {
|
|
860
|
+
metadata.directory_name = get_filename_from_path(directory_path.c_str());
|
|
861
|
+
|
|
862
|
+
std::vector<DirectoryEntry> entries;
|
|
863
|
+
if (list_directory(directory_path.c_str(), entries)) {
|
|
864
|
+
for (const auto& entry : entries) {
|
|
865
|
+
if (!entry.is_directory) {
|
|
866
|
+
FileMetadata file_meta = get_file_metadata(entry.path);
|
|
867
|
+
file_meta.relative_path = entry.name;
|
|
868
|
+
metadata.files.push_back(file_meta);
|
|
869
|
+
}
|
|
870
|
+
else if (recursive) {
|
|
871
|
+
DirectoryMetadata subdir_meta = get_directory_metadata(entry.path, true);
|
|
872
|
+
subdir_meta.relative_path = entry.name;
|
|
873
|
+
metadata.subdirectories.push_back(subdir_meta);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
} catch (const std::exception& e) {
|
|
879
|
+
LOG_FILE_TRANSFER_ERROR("Failed to get directory metadata for " << directory_path << ": " << e.what());
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return metadata;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
bool FileTransferManager::validate_file_path(const std::string& file_path, bool check_write) {
|
|
886
|
+
return validate_path(file_path.c_str(), check_write);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Private implementation methods
|
|
890
|
+
|
|
891
|
+
std::string FileTransferManager::generate_transfer_id() const {
|
|
892
|
+
std::random_device rd;
|
|
893
|
+
std::mt19937 gen(rd());
|
|
894
|
+
std::uniform_int_distribution<> dis(0, 15);
|
|
895
|
+
|
|
896
|
+
std::stringstream ss;
|
|
897
|
+
ss << std::hex;
|
|
898
|
+
for (int i = 0; i < 32; ++i) {
|
|
899
|
+
ss << dis(gen);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return ss.str();
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
void FileTransferManager::process_transfer(const std::string& transfer_id) {
|
|
906
|
+
// Exit immediately if shutting down
|
|
907
|
+
if (!running_.load()) {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
auto progress = get_transfer_progress(transfer_id);
|
|
912
|
+
if (!progress) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Check again before processing (in case shutdown occurred during progress retrieval)
|
|
917
|
+
if (!running_.load()) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Check if this is a directory transfer
|
|
922
|
+
bool is_directory_transfer = false;
|
|
923
|
+
{
|
|
924
|
+
std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
|
|
925
|
+
is_directory_transfer = active_directory_transfers_.find(transfer_id) != active_directory_transfers_.end();
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (is_directory_transfer) {
|
|
929
|
+
if (progress->direction == FileTransferDirection::SENDING) {
|
|
930
|
+
start_directory_send(transfer_id);
|
|
931
|
+
} else {
|
|
932
|
+
start_directory_receive(transfer_id);
|
|
933
|
+
}
|
|
934
|
+
} else {
|
|
935
|
+
if (progress->direction == FileTransferDirection::SENDING) {
|
|
936
|
+
start_file_send(transfer_id);
|
|
937
|
+
} else {
|
|
938
|
+
start_file_receive(transfer_id);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
void FileTransferManager::start_file_send(const std::string& transfer_id) {
|
|
944
|
+
auto progress = get_transfer_progress(transfer_id);
|
|
945
|
+
if (!progress) {
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
progress->status = FileTransferStatus::IN_PROGRESS;
|
|
950
|
+
update_transfer_progress(transfer_id);
|
|
951
|
+
|
|
952
|
+
// Read file and create chunks
|
|
953
|
+
uint64_t chunk_index = 0;
|
|
954
|
+
uint64_t file_offset = 0;
|
|
955
|
+
|
|
956
|
+
while (file_offset < progress->file_size && running_.load()) {
|
|
957
|
+
// Check if transfer was cancelled or paused
|
|
958
|
+
auto current_progress = get_transfer_progress(transfer_id);
|
|
959
|
+
if (!current_progress || current_progress->status == FileTransferStatus::CANCELLED ||
|
|
960
|
+
current_progress->status == FileTransferStatus::PAUSED) {
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
uint32_t chunk_size = (std::min)(static_cast<uint64_t>(config_.chunk_size),
|
|
965
|
+
progress->file_size - file_offset);
|
|
966
|
+
|
|
967
|
+
FileChunk chunk;
|
|
968
|
+
chunk.transfer_id = transfer_id;
|
|
969
|
+
chunk.chunk_index = chunk_index;
|
|
970
|
+
chunk.total_chunks = progress->total_chunks;
|
|
971
|
+
chunk.chunk_size = chunk_size;
|
|
972
|
+
chunk.file_offset = file_offset;
|
|
973
|
+
chunk.data.resize(chunk_size);
|
|
974
|
+
|
|
975
|
+
// Read chunk data
|
|
976
|
+
if (!read_file_chunk(progress->local_path.c_str(), file_offset, chunk.data.data(), chunk_size)) {
|
|
977
|
+
complete_transfer(transfer_id, false, "Failed to read complete chunk from file");
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Calculate checksum
|
|
982
|
+
if (config_.verify_checksums) {
|
|
983
|
+
chunk.checksum = calculate_chunk_checksum(chunk.data);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Note: Compression removed as requested - only binary chunks needed
|
|
987
|
+
|
|
988
|
+
// Send chunk metadata first
|
|
989
|
+
nlohmann::json metadata_msg = create_chunk_metadata_message(chunk);
|
|
990
|
+
client_.send(progress->peer_id, "file_chunk_metadata", metadata_msg);
|
|
991
|
+
|
|
992
|
+
// Send chunk binary data
|
|
993
|
+
std::vector<uint8_t> binary_msg = create_chunk_binary_message(chunk);
|
|
994
|
+
client_.send_binary_to_peer_id(progress->peer_id, binary_msg, MessageDataType::BINARY);
|
|
995
|
+
|
|
996
|
+
chunk_index++;
|
|
997
|
+
file_offset += chunk_size;
|
|
998
|
+
|
|
999
|
+
// Update progress
|
|
1000
|
+
update_transfer_progress(transfer_id, chunk_size);
|
|
1001
|
+
|
|
1002
|
+
// Throttle sending to avoid overwhelming the peer - use condition variable for fast shutdown
|
|
1003
|
+
if (running_.load()) {
|
|
1004
|
+
std::unique_lock<std::mutex> lock(throttle_mutex_);
|
|
1005
|
+
throttle_condition_.wait_for(lock, std::chrono::milliseconds(10), [this] { return !running_.load(); });
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
LOG_FILE_TRANSFER_INFO("Completed sending file chunks for transfer: " << transfer_id);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
void FileTransferManager::start_file_receive(const std::string& transfer_id) {
|
|
1013
|
+
// Exit immediately if shutting down
|
|
1014
|
+
if (!running_.load()) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
auto progress = get_transfer_progress(transfer_id);
|
|
1019
|
+
if (!progress) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
progress->status = FileTransferStatus::IN_PROGRESS;
|
|
1024
|
+
|
|
1025
|
+
// Create temporary file for receiving
|
|
1026
|
+
if (!create_temp_file(transfer_id, progress->file_size)) {
|
|
1027
|
+
complete_transfer(transfer_id, false, "Failed to create temporary file");
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
update_transfer_progress(transfer_id);
|
|
1032
|
+
LOG_FILE_TRANSFER_INFO("Started receiving file transfer: " << transfer_id);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
void FileTransferManager::start_directory_send(const std::string& transfer_id) {
|
|
1036
|
+
auto progress = get_transfer_progress(transfer_id);
|
|
1037
|
+
if (!progress) {
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
DirectoryMetadata dir_metadata;
|
|
1042
|
+
{
|
|
1043
|
+
std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
|
|
1044
|
+
auto it = active_directory_transfers_.find(transfer_id);
|
|
1045
|
+
if (it == active_directory_transfers_.end()) {
|
|
1046
|
+
complete_transfer(transfer_id, false, "Directory metadata not found");
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
dir_metadata = it->second;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
progress->status = FileTransferStatus::IN_PROGRESS;
|
|
1053
|
+
update_transfer_progress(transfer_id);
|
|
1054
|
+
|
|
1055
|
+
// Send each file in the directory
|
|
1056
|
+
for (const auto& file_metadata : dir_metadata.files) {
|
|
1057
|
+
if (!running_.load()) {
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Check if transfer was cancelled or paused
|
|
1062
|
+
auto current_progress = get_transfer_progress(transfer_id);
|
|
1063
|
+
if (!current_progress || current_progress->status == FileTransferStatus::CANCELLED ||
|
|
1064
|
+
current_progress->status == FileTransferStatus::PAUSED) {
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
std::string full_file_path = combine_paths(progress->local_path, file_metadata.relative_path);
|
|
1069
|
+
|
|
1070
|
+
// Send individual file using existing file transfer logic
|
|
1071
|
+
std::string file_transfer_id = send_file_with_metadata(progress->peer_id, full_file_path, file_metadata);
|
|
1072
|
+
|
|
1073
|
+
if (file_transfer_id.empty()) {
|
|
1074
|
+
complete_transfer(transfer_id, false, "Failed to send file: " + file_metadata.filename);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Wait for individual file transfer to complete
|
|
1079
|
+
// Note: In a real implementation, we might want to track multiple concurrent file transfers
|
|
1080
|
+
// For now, we'll send files sequentially
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
LOG_FILE_TRANSFER_INFO("Completed sending directory transfer: " << transfer_id);
|
|
1084
|
+
complete_transfer(transfer_id, true);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
void FileTransferManager::start_directory_receive(const std::string& transfer_id) {
|
|
1088
|
+
auto progress = get_transfer_progress(transfer_id);
|
|
1089
|
+
if (!progress) {
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
DirectoryMetadata dir_metadata;
|
|
1094
|
+
{
|
|
1095
|
+
std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
|
|
1096
|
+
auto it = active_directory_transfers_.find(transfer_id);
|
|
1097
|
+
if (it == active_directory_transfers_.end()) {
|
|
1098
|
+
complete_transfer(transfer_id, false, "Directory metadata not found");
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
dir_metadata = it->second;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
progress->status = FileTransferStatus::IN_PROGRESS;
|
|
1105
|
+
|
|
1106
|
+
// Create directory structure
|
|
1107
|
+
std::string base_path = progress->local_path;
|
|
1108
|
+
if (!ensure_directory_exists(base_path)) {
|
|
1109
|
+
complete_transfer(transfer_id, false, "Failed to create local directory structure");
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Create subdirectories if needed
|
|
1114
|
+
for (const auto& file_metadata : dir_metadata.files) {
|
|
1115
|
+
std::string file_dir = combine_paths(base_path, get_parent_directory(file_metadata.relative_path.c_str()));
|
|
1116
|
+
if (!file_dir.empty() && !ensure_directory_exists(file_dir)) {
|
|
1117
|
+
complete_transfer(transfer_id, false, "Failed to create subdirectory: " + file_dir);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
update_transfer_progress(transfer_id);
|
|
1123
|
+
LOG_FILE_TRANSFER_INFO("Started receiving directory transfer: " << transfer_id);
|
|
1124
|
+
|
|
1125
|
+
// Note: Individual files will be received as separate file transfer requests
|
|
1126
|
+
// The directory transfer completion will be handled when all files are received
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
bool FileTransferManager::create_temp_file(const std::string& transfer_id, uint64_t file_size) {
|
|
1130
|
+
std::string temp_path = get_temp_file_path(transfer_id, config_.temp_directory);
|
|
1131
|
+
|
|
1132
|
+
try {
|
|
1133
|
+
create_directories(config_.temp_directory.c_str());
|
|
1134
|
+
|
|
1135
|
+
// Create file with pre-allocated size
|
|
1136
|
+
if (!create_file_with_size(temp_path.c_str(), file_size)) {
|
|
1137
|
+
LOG_FILE_TRANSFER_ERROR("Failed to create temp file " << temp_path);
|
|
1138
|
+
return false;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return true;
|
|
1142
|
+
} catch (const std::exception& e) {
|
|
1143
|
+
LOG_FILE_TRANSFER_ERROR("Failed to create temp file " << temp_path << ": " << e.what());
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
std::string FileTransferManager::get_temp_file_path(const std::string& transfer_id, const std::string& temp_dir) {
|
|
1149
|
+
return combine_paths(temp_dir, transfer_id + ".tmp");
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
bool FileTransferManager::ensure_directory_exists(const std::string& directory_path) {
|
|
1153
|
+
return create_directories(directory_path.c_str());
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
std::string FileTransferManager::extract_filename(const std::string& file_path) {
|
|
1157
|
+
return get_filename_from_path(file_path);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
std::string FileTransferManager::get_mime_type(const std::string& file_path) {
|
|
1161
|
+
std::string extension = get_file_extension(file_path);
|
|
1162
|
+
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
|
1163
|
+
|
|
1164
|
+
// Basic MIME type mapping
|
|
1165
|
+
static const std::unordered_map<std::string, std::string> mime_types = {
|
|
1166
|
+
{".txt", "text/plain"},
|
|
1167
|
+
{".json", "application/json"},
|
|
1168
|
+
{".xml", "application/xml"},
|
|
1169
|
+
{".html", "text/html"},
|
|
1170
|
+
{".css", "text/css"},
|
|
1171
|
+
{".js", "application/javascript"},
|
|
1172
|
+
{".pdf", "application/pdf"},
|
|
1173
|
+
{".png", "image/png"},
|
|
1174
|
+
{".jpg", "image/jpeg"},
|
|
1175
|
+
{".jpeg", "image/jpeg"},
|
|
1176
|
+
{".gif", "image/gif"},
|
|
1177
|
+
{".bmp", "image/bmp"},
|
|
1178
|
+
{".svg", "image/svg+xml"},
|
|
1179
|
+
{".mp3", "audio/mpeg"},
|
|
1180
|
+
{".wav", "audio/wav"},
|
|
1181
|
+
{".mp4", "video/mp4"},
|
|
1182
|
+
{".avi", "video/x-msvideo"},
|
|
1183
|
+
{".zip", "application/zip"},
|
|
1184
|
+
{".tar", "application/x-tar"},
|
|
1185
|
+
{".gz", "application/gzip"}
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
auto it = mime_types.find(extension);
|
|
1189
|
+
return (it != mime_types.end()) ? it->second : "application/octet-stream";
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Message handling methods
|
|
1193
|
+
|
|
1194
|
+
void FileTransferManager::handle_transfer_request(const std::string& peer_id, const nlohmann::json& message) {
|
|
1195
|
+
try {
|
|
1196
|
+
std::string transfer_id = message["transfer_id"];
|
|
1197
|
+
|
|
1198
|
+
if (message.contains("directory_metadata")) {
|
|
1199
|
+
// Directory transfer request
|
|
1200
|
+
DirectoryMetadata dir_metadata;
|
|
1201
|
+
auto dir_info = message["directory_metadata"];
|
|
1202
|
+
dir_metadata.directory_name = dir_info["directory_name"];
|
|
1203
|
+
dir_metadata.relative_path = dir_info["relative_path"];
|
|
1204
|
+
|
|
1205
|
+
// Parse files and subdirectories from the metadata
|
|
1206
|
+
if (dir_info.contains("files")) {
|
|
1207
|
+
for (const auto& file_json : dir_info["files"]) {
|
|
1208
|
+
FileMetadata file_meta;
|
|
1209
|
+
file_meta.filename = file_json["filename"];
|
|
1210
|
+
file_meta.relative_path = file_json["relative_path"];
|
|
1211
|
+
file_meta.file_size = file_json["file_size"];
|
|
1212
|
+
file_meta.mime_type = file_json.value("mime_type", "application/octet-stream");
|
|
1213
|
+
file_meta.checksum = file_json.value("checksum", "");
|
|
1214
|
+
file_meta.last_modified = file_json.value("last_modified", 0);
|
|
1215
|
+
dir_metadata.files.push_back(file_meta);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// For now, handle as single directory level (can be expanded for nested later)
|
|
1220
|
+
|
|
1221
|
+
{
|
|
1222
|
+
std::lock_guard<std::mutex> lock(pending_mutex_);
|
|
1223
|
+
PendingDirectoryTransfer pending_transfer;
|
|
1224
|
+
pending_transfer.metadata = dir_metadata;
|
|
1225
|
+
pending_transfer.peer_id = peer_id;
|
|
1226
|
+
pending_directory_transfers_[transfer_id] = pending_transfer;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Call user callback to approve/reject
|
|
1230
|
+
if (request_callback_) {
|
|
1231
|
+
// Create a dummy file metadata for compatibility with existing callback
|
|
1232
|
+
FileMetadata dummy_metadata;
|
|
1233
|
+
dummy_metadata.filename = dir_metadata.directory_name + " (directory)";
|
|
1234
|
+
dummy_metadata.file_size = dir_info.value("total_size", 0);
|
|
1235
|
+
|
|
1236
|
+
bool accepted = request_callback_(peer_id, dummy_metadata, transfer_id);
|
|
1237
|
+
if (accepted) {
|
|
1238
|
+
std::string local_path = "./" + dir_metadata.directory_name;
|
|
1239
|
+
accept_directory_transfer(transfer_id, local_path);
|
|
1240
|
+
} else {
|
|
1241
|
+
reject_directory_transfer(transfer_id, "Rejected by user");
|
|
1242
|
+
}
|
|
1243
|
+
} else {
|
|
1244
|
+
// Auto-reject if no callback is set
|
|
1245
|
+
reject_directory_transfer(transfer_id, "No request handler configured");
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
LOG_FILE_TRANSFER_INFO("Received directory transfer request from " << peer_id << " for " << dir_metadata.directory_name);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Single file transfer request
|
|
1253
|
+
FileMetadata metadata;
|
|
1254
|
+
auto file_info = message["file_metadata"];
|
|
1255
|
+
metadata.filename = file_info["filename"];
|
|
1256
|
+
metadata.file_size = file_info["file_size"];
|
|
1257
|
+
metadata.mime_type = file_info.value("mime_type", "application/octet-stream");
|
|
1258
|
+
metadata.checksum = file_info.value("checksum", "");
|
|
1259
|
+
|
|
1260
|
+
{
|
|
1261
|
+
std::lock_guard<std::mutex> lock(pending_mutex_);
|
|
1262
|
+
PendingFileTransfer pending_transfer;
|
|
1263
|
+
pending_transfer.metadata = metadata;
|
|
1264
|
+
pending_transfer.peer_id = peer_id;
|
|
1265
|
+
pending_transfers_[transfer_id] = pending_transfer;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Call user callback to approve/reject
|
|
1269
|
+
if (request_callback_) {
|
|
1270
|
+
bool accepted = request_callback_(peer_id, metadata, transfer_id);
|
|
1271
|
+
if (accepted) {
|
|
1272
|
+
// We need a way to specify the local path - this should be part of the callback
|
|
1273
|
+
// For now, we'll create a default path
|
|
1274
|
+
std::string local_path = "./" + metadata.filename;
|
|
1275
|
+
accept_file_transfer(transfer_id, local_path);
|
|
1276
|
+
} else {
|
|
1277
|
+
reject_file_transfer(transfer_id, "Rejected by user");
|
|
1278
|
+
}
|
|
1279
|
+
} else {
|
|
1280
|
+
// Auto-reject if no callback is set
|
|
1281
|
+
reject_file_transfer(transfer_id, "No request handler configured");
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
} catch (const std::exception& e) {
|
|
1285
|
+
LOG_FILE_TRANSFER_ERROR("Error handling transfer request: " << e.what());
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
void FileTransferManager::handle_transfer_response(const std::string& peer_id, const nlohmann::json& message) {
|
|
1290
|
+
try {
|
|
1291
|
+
std::string transfer_id = message["transfer_id"];
|
|
1292
|
+
bool accepted = message["accepted"];
|
|
1293
|
+
|
|
1294
|
+
auto progress = get_transfer_progress(transfer_id);
|
|
1295
|
+
if (!progress) {
|
|
1296
|
+
LOG_FILE_TRANSFER_ERROR("Received response for unknown transfer: " << transfer_id);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
if (accepted) {
|
|
1301
|
+
// Start sending file
|
|
1302
|
+
{
|
|
1303
|
+
std::lock_guard<std::mutex> work_lock(work_mutex_);
|
|
1304
|
+
work_queue_.push(transfer_id);
|
|
1305
|
+
}
|
|
1306
|
+
work_condition_.notify_one();
|
|
1307
|
+
|
|
1308
|
+
LOG_FILE_TRANSFER_INFO("Transfer accepted by peer: " << transfer_id);
|
|
1309
|
+
} else {
|
|
1310
|
+
std::string reason = message.value("reason", "No reason provided");
|
|
1311
|
+
complete_transfer(transfer_id, false, "Transfer rejected by peer: " + reason);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
} catch (const std::exception& e) {
|
|
1315
|
+
LOG_FILE_TRANSFER_ERROR("Error handling transfer response: " << e.what());
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
void FileTransferManager::handle_chunk_metadata_message(const std::string& peer_id, const nlohmann::json& message) {
|
|
1320
|
+
try {
|
|
1321
|
+
PendingChunk pending;
|
|
1322
|
+
pending.transfer_id = message["transfer_id"];
|
|
1323
|
+
pending.chunk_index = message["chunk_index"];
|
|
1324
|
+
pending.total_chunks = message["total_chunks"];
|
|
1325
|
+
pending.chunk_size = message["chunk_size"];
|
|
1326
|
+
pending.file_offset = message["file_offset"];
|
|
1327
|
+
pending.checksum = message.value("checksum", "");
|
|
1328
|
+
pending.created_at = std::chrono::steady_clock::now();
|
|
1329
|
+
|
|
1330
|
+
// Store pending chunk metadata waiting for binary data
|
|
1331
|
+
{
|
|
1332
|
+
std::lock_guard<std::mutex> lock(chunks_mutex_);
|
|
1333
|
+
pending_chunks_[peer_id] = pending;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
LOG_FILE_TRANSFER_DEBUG("Stored chunk metadata for transfer " << pending.transfer_id <<
|
|
1337
|
+
", chunk " << pending.chunk_index << " from peer " << peer_id);
|
|
1338
|
+
|
|
1339
|
+
} catch (const std::exception& e) {
|
|
1340
|
+
LOG_FILE_TRANSFER_ERROR("Error handling chunk metadata message: " << e.what());
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
void FileTransferManager::handle_chunk_binary_message(const std::string& peer_id, const std::vector<uint8_t>& binary_data) {
|
|
1345
|
+
try {
|
|
1346
|
+
// Parse binary chunk header and get the chunk data
|
|
1347
|
+
FileChunk chunk;
|
|
1348
|
+
if (!parse_chunk_binary_header(binary_data, chunk)) {
|
|
1349
|
+
LOG_FILE_TRANSFER_ERROR("Failed to parse chunk binary header from peer " << peer_id);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Get pending chunk metadata
|
|
1354
|
+
PendingChunk pending;
|
|
1355
|
+
bool found_pending = false;
|
|
1356
|
+
{
|
|
1357
|
+
std::lock_guard<std::mutex> lock(chunks_mutex_);
|
|
1358
|
+
auto it = pending_chunks_.find(peer_id);
|
|
1359
|
+
if (it != pending_chunks_.end()) {
|
|
1360
|
+
pending = it->second;
|
|
1361
|
+
pending_chunks_.erase(it);
|
|
1362
|
+
found_pending = true;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
if (!found_pending) {
|
|
1367
|
+
LOG_FILE_TRANSFER_ERROR("No pending chunk metadata found for peer " << peer_id);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// Combine metadata with binary data
|
|
1372
|
+
chunk.transfer_id = pending.transfer_id;
|
|
1373
|
+
chunk.chunk_index = pending.chunk_index;
|
|
1374
|
+
chunk.total_chunks = pending.total_chunks;
|
|
1375
|
+
chunk.chunk_size = pending.chunk_size;
|
|
1376
|
+
chunk.file_offset = pending.file_offset;
|
|
1377
|
+
chunk.checksum = pending.checksum;
|
|
1378
|
+
|
|
1379
|
+
// Verify chunk size matches
|
|
1380
|
+
if (chunk.data.size() != pending.chunk_size) {
|
|
1381
|
+
LOG_FILE_TRANSFER_ERROR("Chunk size mismatch: expected " << pending.chunk_size <<
|
|
1382
|
+
", got " << chunk.data.size() << " for transfer " << pending.transfer_id);
|
|
1383
|
+
|
|
1384
|
+
// Send negative acknowledgment
|
|
1385
|
+
nlohmann::json ack_msg = create_chunk_ack_message(chunk.transfer_id, chunk.chunk_index, false, "Size mismatch");
|
|
1386
|
+
client_.send(peer_id, "file_chunk_ack", ack_msg);
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Verify checksum if enabled
|
|
1391
|
+
if (config_.verify_checksums && !chunk.checksum.empty()) {
|
|
1392
|
+
if (!verify_chunk_checksum(chunk)) {
|
|
1393
|
+
LOG_FILE_TRANSFER_ERROR("Chunk checksum verification failed for transfer " << chunk.transfer_id <<
|
|
1394
|
+
", chunk " << chunk.chunk_index);
|
|
1395
|
+
|
|
1396
|
+
// Send negative acknowledgment
|
|
1397
|
+
nlohmann::json ack_msg = create_chunk_ack_message(chunk.transfer_id, chunk.chunk_index, false, "Checksum mismatch");
|
|
1398
|
+
client_.send(peer_id, "file_chunk_ack", ack_msg);
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// Process the received chunk
|
|
1404
|
+
handle_chunk_received(chunk);
|
|
1405
|
+
|
|
1406
|
+
// Send positive acknowledgment
|
|
1407
|
+
nlohmann::json ack_msg = create_chunk_ack_message(chunk.transfer_id, chunk.chunk_index, true);
|
|
1408
|
+
client_.send(peer_id, "file_chunk_ack", ack_msg);
|
|
1409
|
+
|
|
1410
|
+
LOG_FILE_TRANSFER_DEBUG("Successfully processed chunk " << chunk.chunk_index <<
|
|
1411
|
+
" for transfer " << chunk.transfer_id << " from peer " << peer_id);
|
|
1412
|
+
|
|
1413
|
+
} catch (const std::exception& e) {
|
|
1414
|
+
LOG_FILE_TRANSFER_ERROR("Error handling chunk binary message: " << e.what());
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
void FileTransferManager::handle_chunk_ack_message(const std::string& peer_id, const nlohmann::json& message) {
|
|
1419
|
+
try {
|
|
1420
|
+
std::string transfer_id = message["transfer_id"];
|
|
1421
|
+
uint64_t chunk_index = message["chunk_index"];
|
|
1422
|
+
bool success = message["success"];
|
|
1423
|
+
|
|
1424
|
+
handle_chunk_ack(transfer_id, chunk_index, success);
|
|
1425
|
+
|
|
1426
|
+
} catch (const std::exception& e) {
|
|
1427
|
+
LOG_FILE_TRANSFER_ERROR("Error handling chunk ack message: " << e.what());
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
void FileTransferManager::handle_transfer_control(const std::string& peer_id, const nlohmann::json& message) {
|
|
1432
|
+
try {
|
|
1433
|
+
std::string transfer_id = message["transfer_id"];
|
|
1434
|
+
std::string action = message["action"];
|
|
1435
|
+
|
|
1436
|
+
if (action == "pause") {
|
|
1437
|
+
pause_transfer(transfer_id);
|
|
1438
|
+
} else if (action == "resume") {
|
|
1439
|
+
resume_transfer(transfer_id);
|
|
1440
|
+
} else if (action == "cancel") {
|
|
1441
|
+
cancel_transfer(transfer_id);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
} catch (const std::exception& e) {
|
|
1445
|
+
LOG_FILE_TRANSFER_ERROR("Error handling transfer control message: " << e.what());
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// Message creation methods
|
|
1450
|
+
|
|
1451
|
+
nlohmann::json FileTransferManager::create_transfer_request_message(const FileMetadata& metadata, const std::string& transfer_id) {
|
|
1452
|
+
nlohmann::json message;
|
|
1453
|
+
message["transfer_id"] = transfer_id;
|
|
1454
|
+
message["type"] = "file";
|
|
1455
|
+
message["file_metadata"] = {
|
|
1456
|
+
{"filename", metadata.filename},
|
|
1457
|
+
{"file_size", metadata.file_size},
|
|
1458
|
+
{"mime_type", metadata.mime_type},
|
|
1459
|
+
{"checksum", metadata.checksum},
|
|
1460
|
+
{"last_modified", metadata.last_modified}
|
|
1461
|
+
};
|
|
1462
|
+
return message;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
nlohmann::json FileTransferManager::create_transfer_response_message(const std::string& transfer_id, bool accepted, const std::string& reason) {
|
|
1466
|
+
nlohmann::json message;
|
|
1467
|
+
message["transfer_id"] = transfer_id;
|
|
1468
|
+
message["accepted"] = accepted;
|
|
1469
|
+
if (!reason.empty()) {
|
|
1470
|
+
message["reason"] = reason;
|
|
1471
|
+
}
|
|
1472
|
+
return message;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
nlohmann::json FileTransferManager::create_chunk_metadata_message(const FileChunk& chunk) {
|
|
1476
|
+
nlohmann::json message;
|
|
1477
|
+
message["transfer_id"] = chunk.transfer_id;
|
|
1478
|
+
message["chunk_index"] = chunk.chunk_index;
|
|
1479
|
+
message["total_chunks"] = chunk.total_chunks;
|
|
1480
|
+
message["chunk_size"] = chunk.chunk_size;
|
|
1481
|
+
message["file_offset"] = chunk.file_offset;
|
|
1482
|
+
message["checksum"] = chunk.checksum;
|
|
1483
|
+
|
|
1484
|
+
return message;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
std::vector<uint8_t> FileTransferManager::create_chunk_binary_message(const FileChunk& chunk) {
|
|
1488
|
+
// Create binary message with header: "FTCHUNK" + chunk data
|
|
1489
|
+
const std::string magic = "FTCHUNK";
|
|
1490
|
+
std::vector<uint8_t> message;
|
|
1491
|
+
|
|
1492
|
+
// Reserve space for the entire message
|
|
1493
|
+
message.reserve(magic.length() + chunk.data.size());
|
|
1494
|
+
|
|
1495
|
+
// Add magic header
|
|
1496
|
+
message.insert(message.end(), magic.begin(), magic.end());
|
|
1497
|
+
|
|
1498
|
+
// Add chunk data
|
|
1499
|
+
message.insert(message.end(), chunk.data.begin(), chunk.data.end());
|
|
1500
|
+
|
|
1501
|
+
return message;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
nlohmann::json FileTransferManager::create_chunk_ack_message(const std::string& transfer_id, uint64_t chunk_index, bool success, const std::string& error) {
|
|
1505
|
+
nlohmann::json message;
|
|
1506
|
+
message["transfer_id"] = transfer_id;
|
|
1507
|
+
message["chunk_index"] = chunk_index;
|
|
1508
|
+
message["success"] = success;
|
|
1509
|
+
if (!error.empty()) {
|
|
1510
|
+
message["error"] = error;
|
|
1511
|
+
}
|
|
1512
|
+
return message;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
nlohmann::json FileTransferManager::create_control_message(const std::string& transfer_id, const std::string& action, const nlohmann::json& data) {
|
|
1516
|
+
nlohmann::json message;
|
|
1517
|
+
message["transfer_id"] = transfer_id;
|
|
1518
|
+
message["action"] = action;
|
|
1519
|
+
if (!data.empty()) {
|
|
1520
|
+
message["data"] = data;
|
|
1521
|
+
}
|
|
1522
|
+
return message;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// Progress tracking methods
|
|
1526
|
+
|
|
1527
|
+
void FileTransferManager::update_transfer_progress(const std::string& transfer_id, uint64_t bytes_delta) {
|
|
1528
|
+
auto progress = get_transfer_progress(transfer_id);
|
|
1529
|
+
if (!progress) {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
if (bytes_delta > 0) {
|
|
1534
|
+
progress->update_transfer_rates(progress->bytes_transferred + bytes_delta);
|
|
1535
|
+
|
|
1536
|
+
// Update statistics
|
|
1537
|
+
{
|
|
1538
|
+
std::lock_guard<std::mutex> stats_lock(stats_mutex_);
|
|
1539
|
+
if (progress->direction == FileTransferDirection::SENDING) {
|
|
1540
|
+
total_bytes_sent_ += bytes_delta;
|
|
1541
|
+
} else {
|
|
1542
|
+
total_bytes_received_ += bytes_delta;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// Call progress callback
|
|
1548
|
+
if (progress_callback_) {
|
|
1549
|
+
progress_callback_(*progress);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
void FileTransferManager::complete_transfer(const std::string& transfer_id, bool success, const std::string& error_message) {
|
|
1554
|
+
auto progress = get_transfer_progress(transfer_id);
|
|
1555
|
+
if (!progress) {
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
progress->status = success ? FileTransferStatus::COMPLETED : FileTransferStatus::FAILED;
|
|
1560
|
+
progress->error_message = error_message;
|
|
1561
|
+
|
|
1562
|
+
// Update statistics
|
|
1563
|
+
{
|
|
1564
|
+
std::lock_guard<std::mutex> stats_lock(stats_mutex_);
|
|
1565
|
+
if (progress->direction == FileTransferDirection::SENDING) {
|
|
1566
|
+
total_files_sent_++;
|
|
1567
|
+
} else {
|
|
1568
|
+
total_files_received_++;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// Move to completed transfers
|
|
1573
|
+
move_to_completed(transfer_id);
|
|
1574
|
+
|
|
1575
|
+
// Call completion callback
|
|
1576
|
+
if (completion_callback_) {
|
|
1577
|
+
completion_callback_(transfer_id, success, error_message);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
LOG_FILE_TRANSFER_INFO("Transfer " << (success ? "completed" : "failed") << ": " << transfer_id);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
void FileTransferManager::move_to_completed(const std::string& transfer_id) {
|
|
1584
|
+
std::lock_guard<std::mutex> lock(transfers_mutex_);
|
|
1585
|
+
|
|
1586
|
+
auto it = active_transfers_.find(transfer_id);
|
|
1587
|
+
if (it != active_transfers_.end()) {
|
|
1588
|
+
completed_transfers_[transfer_id] = it->second;
|
|
1589
|
+
active_transfers_.erase(it);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// Binary chunk transmission implementation (replaces base64 encoding)
|
|
1594
|
+
//
|
|
1595
|
+
// PERFORMANCE OPTIMIZATIONS:
|
|
1596
|
+
// - Replaced JSON + base64 encoding with direct binary transmission
|
|
1597
|
+
// - Eliminates 33% encoding overhead from base64
|
|
1598
|
+
// - Reduces CPU usage for encoding/decoding
|
|
1599
|
+
// - Minimizes memory allocations for string conversions
|
|
1600
|
+
// - Uses two-phase protocol: metadata (JSON) + binary data (raw)
|
|
1601
|
+
// - Added cleanup thread for timeout handling of split messages
|
|
1602
|
+
//
|
|
1603
|
+
// PROTOCOL CHANGES:
|
|
1604
|
+
// 1. Send file_chunk_metadata (JSON) with transfer info
|
|
1605
|
+
// 2. Send binary data with "FTCHUNK" magic header + raw file data
|
|
1606
|
+
// 3. Receive acknowledgments as before
|
|
1607
|
+
//
|
|
1608
|
+
// This approach provides maximum performance while maintaining protocol robustness.
|
|
1609
|
+
|
|
1610
|
+
void FileTransferManager::handle_chunk_received(const FileChunk& chunk) {
|
|
1611
|
+
// Write chunk to temporary file
|
|
1612
|
+
std::string temp_path = get_temp_file_path(chunk.transfer_id, config_.temp_directory);
|
|
1613
|
+
|
|
1614
|
+
if (!write_file_chunk(temp_path.c_str(), chunk.file_offset, chunk.data.data(), chunk.chunk_size)) {
|
|
1615
|
+
LOG_FILE_TRANSFER_ERROR("Failed to write chunk to temp file: " << temp_path);
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// Update progress
|
|
1620
|
+
update_transfer_progress(chunk.transfer_id, chunk.chunk_size);
|
|
1621
|
+
|
|
1622
|
+
// Check if transfer is complete
|
|
1623
|
+
auto progress = get_transfer_progress(chunk.transfer_id);
|
|
1624
|
+
if (progress) {
|
|
1625
|
+
progress->chunks_completed++;
|
|
1626
|
+
if (progress->chunks_completed == progress->total_chunks) {
|
|
1627
|
+
// Transfer complete - move temp file to final location
|
|
1628
|
+
if (finalize_received_file(chunk.transfer_id, progress->local_path)) {
|
|
1629
|
+
complete_transfer(chunk.transfer_id, true);
|
|
1630
|
+
} else {
|
|
1631
|
+
complete_transfer(chunk.transfer_id, false, "Failed to finalize received file");
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
void FileTransferManager::handle_chunk_ack(const std::string& transfer_id, uint64_t chunk_index, bool success) {
|
|
1638
|
+
auto progress = get_transfer_progress(transfer_id);
|
|
1639
|
+
if (!progress) {
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
if (success) {
|
|
1644
|
+
progress->chunks_completed++;
|
|
1645
|
+
if (progress->chunks_completed == progress->total_chunks) {
|
|
1646
|
+
complete_transfer(transfer_id, true);
|
|
1647
|
+
}
|
|
1648
|
+
} else {
|
|
1649
|
+
// Handle chunk failure - could retry or fail the transfer
|
|
1650
|
+
LOG_FILE_TRANSFER_ERROR("Chunk " << chunk_index << " failed for transfer " << transfer_id);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
bool FileTransferManager::finalize_received_file(const std::string& transfer_id, const std::string& final_path) {
|
|
1655
|
+
std::string temp_path = get_temp_file_path(transfer_id, config_.temp_directory);
|
|
1656
|
+
|
|
1657
|
+
try {
|
|
1658
|
+
// Ensure destination directory exists
|
|
1659
|
+
std::string dest_dir = get_parent_directory(final_path.c_str());
|
|
1660
|
+
if (!dest_dir.empty()) {
|
|
1661
|
+
ensure_directory_exists(dest_dir);
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
// Move temp file to final location
|
|
1665
|
+
if (!rename_file(temp_path.c_str(), final_path.c_str())) {
|
|
1666
|
+
LOG_FILE_TRANSFER_ERROR("Failed to rename temp file to final location: " << temp_path << " -> " << final_path);
|
|
1667
|
+
return false;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
return true;
|
|
1671
|
+
} catch (const std::exception& e) {
|
|
1672
|
+
LOG_FILE_TRANSFER_ERROR("Failed to finalize file " << final_path << ": " << e.what());
|
|
1673
|
+
return false;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
std::string FileTransferManager::calculate_chunk_checksum(const std::vector<uint8_t>& data) {
|
|
1678
|
+
SHA1 sha1;
|
|
1679
|
+
sha1.update(data.data(), data.size());
|
|
1680
|
+
return sha1.finalize();
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
bool FileTransferManager::parse_chunk_binary_header(const std::vector<uint8_t>& binary_data, FileChunk& chunk) {
|
|
1684
|
+
const std::string magic = "FTCHUNK";
|
|
1685
|
+
|
|
1686
|
+
// Check minimum size (magic header + at least some data)
|
|
1687
|
+
if (binary_data.size() < magic.length()) {
|
|
1688
|
+
return false;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// Verify magic header
|
|
1692
|
+
if (std::memcmp(binary_data.data(), magic.c_str(), magic.length()) != 0) {
|
|
1693
|
+
return false;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// Extract chunk data (everything after the magic header)
|
|
1697
|
+
size_t data_start = magic.length();
|
|
1698
|
+
size_t data_size = binary_data.size() - data_start;
|
|
1699
|
+
|
|
1700
|
+
chunk.data.resize(data_size);
|
|
1701
|
+
std::memcpy(chunk.data.data(), binary_data.data() + data_start, data_size);
|
|
1702
|
+
|
|
1703
|
+
return true;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
bool FileTransferManager::verify_chunk_checksum(const FileChunk& chunk) {
|
|
1707
|
+
if (!config_.verify_checksums || chunk.checksum.empty()) {
|
|
1708
|
+
return true;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
std::string calculated = calculate_chunk_checksum(chunk.data);
|
|
1712
|
+
return calculated == chunk.checksum;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
void FileTransferManager::handle_file_request(const std::string& peer_id, const nlohmann::json& message) {
|
|
1716
|
+
try {
|
|
1717
|
+
std::string transfer_id = message["transfer_id"];
|
|
1718
|
+
std::string remote_path = message["remote_path"];
|
|
1719
|
+
|
|
1720
|
+
// Check if file exists and is accessible
|
|
1721
|
+
if (!file_or_directory_exists(remote_path)) {
|
|
1722
|
+
LOG_FILE_TRANSFER_WARN("File request denied - file not found: " << remote_path);
|
|
1723
|
+
nlohmann::json response;
|
|
1724
|
+
response["transfer_id"] = transfer_id;
|
|
1725
|
+
response["accepted"] = false;
|
|
1726
|
+
response["reason"] = "File not found or not accessible";
|
|
1727
|
+
client_.send(peer_id, "file_transfer_response", response);
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// Call user callback to approve/reject
|
|
1732
|
+
if (file_request_callback_) {
|
|
1733
|
+
bool accepted = file_request_callback_(peer_id, remote_path, transfer_id);
|
|
1734
|
+
if (accepted) {
|
|
1735
|
+
// Start file transfer
|
|
1736
|
+
send_file(peer_id, remote_path);
|
|
1737
|
+
LOG_FILE_TRANSFER_INFO("Accepted file request: " << remote_path << " for " << peer_id);
|
|
1738
|
+
} else {
|
|
1739
|
+
nlohmann::json response;
|
|
1740
|
+
response["transfer_id"] = transfer_id;
|
|
1741
|
+
response["accepted"] = false;
|
|
1742
|
+
response["reason"] = "Request rejected by user";
|
|
1743
|
+
client_.send(peer_id, "file_transfer_response", response);
|
|
1744
|
+
LOG_FILE_TRANSFER_INFO("Rejected file request: " << remote_path << " for " << peer_id);
|
|
1745
|
+
}
|
|
1746
|
+
} else {
|
|
1747
|
+
// Auto-reject if no callback is set
|
|
1748
|
+
nlohmann::json response;
|
|
1749
|
+
response["transfer_id"] = transfer_id;
|
|
1750
|
+
response["accepted"] = false;
|
|
1751
|
+
response["reason"] = "No file request handler configured";
|
|
1752
|
+
client_.send(peer_id, "file_transfer_response", response);
|
|
1753
|
+
LOG_FILE_TRANSFER_WARN("Auto-rejected file request - no handler: " << remote_path);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
} catch (const std::exception& e) {
|
|
1757
|
+
LOG_FILE_TRANSFER_ERROR("Error handling file request: " << e.what());
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
void FileTransferManager::handle_directory_request(const std::string& peer_id, const nlohmann::json& message) {
|
|
1762
|
+
try {
|
|
1763
|
+
std::string transfer_id = message["transfer_id"];
|
|
1764
|
+
std::string remote_path = message["remote_path"];
|
|
1765
|
+
bool recursive = message.value("recursive", true);
|
|
1766
|
+
|
|
1767
|
+
// Check if directory exists and is accessible
|
|
1768
|
+
if (!directory_exists(remote_path)) {
|
|
1769
|
+
LOG_FILE_TRANSFER_WARN("Directory request denied - directory not found: " << remote_path);
|
|
1770
|
+
nlohmann::json response;
|
|
1771
|
+
response["transfer_id"] = transfer_id;
|
|
1772
|
+
response["accepted"] = false;
|
|
1773
|
+
response["reason"] = "Directory not found or not accessible";
|
|
1774
|
+
client_.send(peer_id, "file_transfer_response", response);
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
// Call user callback to approve/reject
|
|
1779
|
+
if (directory_request_callback_) {
|
|
1780
|
+
bool accepted = directory_request_callback_(peer_id, remote_path, recursive, transfer_id);
|
|
1781
|
+
if (accepted) {
|
|
1782
|
+
// Start directory transfer
|
|
1783
|
+
send_directory(peer_id, remote_path, "", recursive);
|
|
1784
|
+
LOG_FILE_TRANSFER_INFO("Accepted directory request: " << remote_path << " for " << peer_id);
|
|
1785
|
+
} else {
|
|
1786
|
+
nlohmann::json response;
|
|
1787
|
+
response["transfer_id"] = transfer_id;
|
|
1788
|
+
response["accepted"] = false;
|
|
1789
|
+
response["reason"] = "Request rejected by user";
|
|
1790
|
+
client_.send(peer_id, "file_transfer_response", response);
|
|
1791
|
+
LOG_FILE_TRANSFER_INFO("Rejected directory request: " << remote_path << " for " << peer_id);
|
|
1792
|
+
}
|
|
1793
|
+
} else {
|
|
1794
|
+
// Auto-reject if no callback is set
|
|
1795
|
+
nlohmann::json response;
|
|
1796
|
+
response["transfer_id"] = transfer_id;
|
|
1797
|
+
response["accepted"] = false;
|
|
1798
|
+
response["reason"] = "No directory request handler configured";
|
|
1799
|
+
client_.send(peer_id, "file_transfer_response", response);
|
|
1800
|
+
LOG_FILE_TRANSFER_WARN("Auto-rejected directory request - no handler: " << remote_path);
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
} catch (const std::exception& e) {
|
|
1804
|
+
LOG_FILE_TRANSFER_ERROR("Error handling directory request: " << e.what());
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
} // namespace librats
|