mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-03-28 12:27:21 +08:00
[feat] use receiver ack to calculate file transfer progress
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user