[feat] support multi-file transfer, refs #45

This commit is contained in:
dijunkun
2025-12-28 18:19:26 +08:00
parent eea37424c9
commit a3e564f160
8 changed files with 2835 additions and 2479 deletions

View File

@@ -12,6 +12,7 @@
#include "device_controller_factory.h"
#include "fa_regular_400.h"
#include "fa_solid_900.h"
#include "file_transfer.h"
#include "layout_relative.h"
#include "localization.h"
#include "platform.h"
@@ -1544,6 +1545,166 @@ void Render::CleanSubStreamWindowProperties(
}
}
void Render::StartFileTransfer(std::shared_ptr<SubStreamWindowProperties> props,
const std::filesystem::path& file_path,
const std::string& file_label) {
if (!props || !props->peer_) {
LOG_ERROR("StartFileTransfer: invalid props or peer");
return;
}
bool expected = false;
if (!props->file_sending_.compare_exchange_strong(expected, true)) {
// Already sending, this should not happen if called correctly
LOG_WARN(
"StartFileTransfer called but file_sending_ is already true, "
"file should have been queued: {}",
file_path.filename().string().c_str());
return;
}
auto peer = props->peer_;
auto props_weak = std::weak_ptr<SubStreamWindowProperties>(props);
Render* render_ptr = this;
std::thread([peer, file_path, file_label, props_weak, render_ptr]() {
auto props_locked = props_weak.lock();
if (!props_locked) {
return;
}
std::error_code ec;
uint64_t total_size = std::filesystem::file_size(file_path, ec);
if (ec) {
LOG_ERROR("Failed to get file size: {}", ec.message().c_str());
props_locked->file_sending_ = false;
return;
}
props_locked->file_sent_bytes_ = 0;
props_locked->file_total_bytes_ = total_size;
props_locked->file_send_rate_bps_ = 0;
props_locked->file_transfer_window_visible_ = true;
{
std::lock_guard<std::mutex> lock(props_locked->file_transfer_mutex_);
props_locked->file_send_start_time_ = std::chrono::steady_clock::now();
props_locked->file_send_last_update_time_ =
props_locked->file_send_start_time_;
props_locked->file_send_last_bytes_ = 0;
}
LOG_INFO(
"File transfer started: {} ({} bytes), file_sending_={}, "
"total_bytes_={}",
file_path.filename().string(), total_size,
props_locked->file_sending_.load(),
props_locked->file_total_bytes_.load());
FileSender sender;
uint32_t file_id = FileSender::NextFileId();
{
std::lock_guard<std::shared_mutex> lock(
render_ptr->file_id_to_props_mutex_);
render_ptr->file_id_to_props_[file_id] = props_weak;
}
props_locked->current_file_id_ = file_id;
// Update file transfer list: mark as sending
// Find the queued file that matches the exact file path
{
std::lock_guard<std::mutex> lock(props_locked->file_transfer_list_mutex_);
for (auto& info : props_locked->file_transfer_list_) {
if (info.file_path == file_path &&
info.status ==
SubStreamWindowProperties::FileTransferStatus::Queued) {
info.status = SubStreamWindowProperties::FileTransferStatus::Sending;
info.file_id = file_id;
info.file_size = total_size;
info.sent_bytes = 0;
break;
}
}
}
props_locked->file_transfer_window_visible_ = true;
// Progress will be updated via ACK from receiver
int ret = sender.SendFile(
file_path, file_path.filename().string(),
[peer, file_label](const char* buf, size_t sz) -> int {
return SendReliableDataFrame(peer, buf, sz, file_label.c_str());
},
64 * 1024, file_id);
// file_sending_ should remain true until we receive the final ACK from
// receiver
auto props_locked_final = props_weak.lock();
if (props_locked_final) {
// On error, set file_sending_ to false immediately to allow next file
if (ret != 0) {
props_locked_final->file_sending_ = false;
props_locked_final->file_transfer_window_visible_ = false;
props_locked_final->file_sent_bytes_ = 0;
props_locked_final->file_total_bytes_ = 0;
props_locked_final->file_send_rate_bps_ = 0;
props_locked_final->current_file_id_ = 0;
// Unregister file_id mapping on error
{
std::lock_guard<std::shared_mutex> lock(
render_ptr->file_id_to_props_mutex_);
render_ptr->file_id_to_props_.erase(file_id);
}
// Update file transfer list: mark as failed
{
std::lock_guard<std::mutex> lock(
props_locked_final->file_transfer_list_mutex_);
for (auto& info : props_locked_final->file_transfer_list_) {
if (info.file_id == file_id) {
info.status =
SubStreamWindowProperties::FileTransferStatus::Failed;
break;
}
}
}
LOG_ERROR("FileSender::SendFile failed for [{}], ret={}",
file_path.string().c_str(), ret);
render_ptr->ProcessFileQueue(props_locked_final);
}
}
}).detach();
}
void Render::ProcessFileQueue(
std::shared_ptr<SubStreamWindowProperties> props) {
if (!props) {
return;
}
if (props->file_sending_.load()) {
return;
}
SubStreamWindowProperties::QueuedFile queued_file;
{
std::lock_guard<std::mutex> lock(props->file_queue_mutex_);
if (props->file_send_queue_.empty()) {
return;
}
queued_file = props->file_send_queue_.front();
props->file_send_queue_.pop();
}
LOG_INFO("Processing next file in queue: {}",
queued_file.file_path.string().c_str());
StartFileTransfer(props, queued_file.file_path, queued_file.file_label);
}
void Render::UpdateRenderRect() {
// std::shared_lock lock(client_properties_mutex_);
for (auto& [_, props] : client_properties_) {