[feat] use receiver ack to calculate file transfer progress

This commit is contained in:
dijunkun
2025-12-25 18:13:18 +08:00
parent b322181853
commit eea37424c9
9 changed files with 227 additions and 127 deletions

View File

@@ -9,14 +9,13 @@ namespace crossdesk {
namespace {
std::atomic<uint32_t> g_next_file_id{1};
constexpr uint32_t kFileChunkMagic = 0x4A4E544D; // 'JNTM'
} // namespace
uint32_t FileSender::NextFileId() { return g_next_file_id.fetch_add(1); }
int FileSender::SendFile(const std::filesystem::path& path,
const std::string& label, const SendFunc& send,
std::size_t chunk_size) {
std::size_t chunk_size, uint32_t file_id) {
if (!send) {
LOG_ERROR("FileSender::SendFile: send function is empty");
return -1;
@@ -43,10 +42,13 @@ int FileSender::SendFile(const std::filesystem::path& path,
path.string().c_str());
return -1;
}
LOG_INFO("FileSender send file {}, total size {}", path.string().c_str(),
total_size);
LOG_INFO("FileSender send file {}, total size {}, file_id={}",
path.string().c_str(), total_size, file_id);
const uint32_t file_id = NextFileId();
if (file_id == 0) {
file_id = NextFileId();
}
const uint32_t final_file_id = file_id;
uint64_t offset = 0;
bool is_first = true;
std::string file_name = label.empty() ? path.filename().string() : label;
@@ -69,7 +71,7 @@ int FileSender::SendFile(const std::filesystem::path& path,
const std::string* name_ptr = is_first ? &file_name : nullptr;
std::vector<char> chunk = BuildChunk(
file_id, offset, total_size, buffer.data(),
final_file_id, offset, total_size, buffer.data(),
static_cast<uint32_t>(bytes_read), name_ptr, is_first, is_last);
int ret = send(chunk.data(), chunk.size());
@@ -264,6 +266,27 @@ bool FileReceiver::HandleChunk(const FileChunkHeader& header,
ctx.received += static_cast<uint64_t>(payload_size);
}
// Send ACK after processing chunk
if (on_send_ack_) {
FileTransferAck ack{};
ack.magic = kFileAckMagic;
ack.file_id = header.file_id;
ack.acked_offset = header.offset + static_cast<uint64_t>(payload_size);
ack.total_size = header.total_size;
ack.flags = 0;
bool is_last = (header.flags & 0x02) != 0;
if (is_last || ctx.received >= ctx.total_size) {
ack.flags |= 0x01; // completed
}
int ret = on_send_ack_(ack);
if (ret != 0) {
LOG_ERROR("FileReceiver: failed to send ACK for file_id={}, ret={}",
header.file_id, ret);
}
}
bool is_last = (header.flags & 0x02) != 0;
if (is_last || ctx.received >= ctx.total_size) {
ctx.ofs.close();

View File

@@ -17,6 +17,10 @@
namespace crossdesk {
// Magic constants for file transfer protocol
constexpr uint32_t kFileChunkMagic = 0x4A4E544D; // 'JNTM'
constexpr uint32_t kFileAckMagic = 0x4A4E5443; // 'JNTC'
#pragma pack(push, 1)
struct FileChunkHeader {
uint32_t magic; // magic to identify file-transfer chunks
@@ -27,6 +31,14 @@ struct FileChunkHeader {
uint16_t name_len; // filename length (bytes), only set on first chunk
uint8_t flags; // bit0: is_first, bit1: is_last, others reserved
};
struct FileTransferAck {
uint32_t magic; // magic to identify file-transfer ack
uint32_t file_id; // must match FileChunkHeader.file_id
uint64_t acked_offset; // received offset
uint64_t total_size; // total file size
uint32_t flags; // bit0: completed, bit1: error
};
#pragma pack(pop)
class FileSender {
@@ -43,9 +55,11 @@ class FileSender {
// `path` : full path to the local file.
// `label` : logical filename to send (usually path.filename()).
// `send` : callback that pushes one encoded chunk into the data channel.
// `file_id` : file id to use (0 means auto-generate).
// Return 0 on success, <0 on error.
int SendFile(const std::filesystem::path& path, const std::string& label,
const SendFunc& send, std::size_t chunk_size = 64 * 1024);
const SendFunc& send, std::size_t chunk_size = 64 * 1024,
uint32_t file_id = 0);
// build a single encoded chunk buffer according to FileChunkHeader protocol.
static std::vector<char> BuildChunk(uint32_t file_id, uint64_t offset,
@@ -66,6 +80,7 @@ class FileReceiver {
using OnFileComplete =
std::function<void(const std::filesystem::path& saved_path)>;
using OnSendAck = std::function<int(const FileTransferAck& ack)>;
public:
// save to default desktop directory.
@@ -80,6 +95,8 @@ class FileReceiver {
void SetOnFileComplete(OnFileComplete cb) { on_file_complete_ = cb; }
void SetOnSendAck(OnSendAck cb) { on_send_ack_ = cb; }
const std::filesystem::path& OutputDir() const { return output_dir_; }
private:
@@ -92,6 +109,7 @@ class FileReceiver {
std::filesystem::path output_dir_;
std::unordered_map<uint32_t, FileContext> contexts_;
OnFileComplete on_file_complete_ = nullptr;
OnSendAck on_send_ack_ = nullptr;
};
} // namespace crossdesk