feat: track uploads with progress and cancellation

serve_file streams in chunks, updating a shared byte counter and
aborting with Interrupted when a cancel flag is set. Each served upload
is registered (peer, virtual path, size, live progress, status) and
exposed via Client::uploads(); Client::cancel_upload flags a matching
in-progress transfer. e2e: the download test now also asserts the
uploader recorded a completed upload with the right peer and byte
count.
This commit is contained in:
michel
2026-07-20 20:51:22 +02:00
parent be8197fd31
commit 6238c44312
6 changed files with 225 additions and 8 deletions
+41 -6
View File
@@ -1,7 +1,9 @@
use super::{
Arc, Client, ClientContext, DownloadStatus, RwLock, RwLockExt,
collect_failed_tokens, error, thread,
ActiveUpload, Arc, Client, ClientContext, DownloadStatus, RwLock,
RwLockExt, collect_failed_tokens, error, thread,
};
use crate::types::UploadStatus;
use std::sync::atomic::{AtomicBool, AtomicU64};
impl Client {
/// Consume the upload job for `token` and stream the file to `host:port`
@@ -19,14 +21,47 @@ impl Client {
let Some(job) = ctx.uploads.remove(&token) else {
return;
};
let bytes_sent = Arc::new(AtomicU64::new(0));
let cancel = Arc::new(AtomicBool::new(false));
ctx.active_uploads.insert(
token,
ActiveUpload {
username: job.downloader.clone(),
filename: job.virtual_path.clone(),
size: job.size,
bytes_sent: bytes_sent.clone(),
cancel: cancel.clone(),
status: UploadStatus::InProgress,
},
);
drop(ctx);
let own = own_username.to_string();
let real_path = job.real_path;
let context = client_context.clone();
thread::spawn(move || {
if let Err(e) = crate::peer::upload_peer::serve_file(
&host, port, &own, token, &real_path,
) {
error!("[client] serve {}: {}", real_path.display(), e);
let result = crate::peer::upload_peer::serve_file(
&host,
port,
&own,
token,
&real_path,
&bytes_sent,
&cancel,
);
let status = match &result {
Ok(()) => UploadStatus::Completed,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
UploadStatus::Cancelled
}
Err(e) => {
error!("[client] serve {}: {}", real_path.display(), e);
UploadStatus::Failed(e.to_string())
}
};
if let Ok(mut ctx) = context.write_safe()
&& let Some(upload) = ctx.active_uploads.get_mut(&token)
{
upload.status = status;
}
});
}