fix(lib): advertise real slot, speed and queue figures in search replies

Every reply to a peer's search claimed a free upload slot, no speed and
an empty queue, whatever the sharer was doing, and searchers rank
sources on exactly those fields. A reply now says whether a slot is
free, the bytes per second of the last completed upload, and how many
uploads are waiting.
This commit is contained in:
Michel de Graaf
2026-09-07 15:16:20 +02:00
parent 049deee5a7
commit 352816cc1e
9 changed files with 114 additions and 13 deletions
+10 -2
View File
@@ -150,6 +150,9 @@ fn build_search_response(
own_username: &str,
token: u32,
query: &str,
free_slot: bool,
speed: u32,
queue_length: u32,
) -> Option<crate::message::Message> {
let matches = shares.search(query);
if matches.is_empty() {
@@ -168,8 +171,9 @@ fn build_search_response(
own_username,
token,
&entries,
1,
0,
u8::from(free_slot),
speed,
queue_length,
))
}
@@ -376,6 +380,9 @@ pub struct ClientContext {
upload_seq: u64,
/// How many uploads may be in flight at once.
upload_slots: usize,
/// Bytes per second of the last completed upload, advertised in search
/// replies; zero until one has finished.
last_upload_speed: u32,
/// Queued-upload states that came and went between two polls of
/// [`Client::uploads`]. A caller sampling that snapshot would otherwise
/// never see a peer that queued and was served inside one poll interval,
@@ -458,6 +465,7 @@ impl ClientContext {
upload_queue: Vec::new(),
upload_seq: 0,
upload_slots: DEFAULT_UPLOAD_SLOTS,
last_upload_speed: 0,
upload_events: Vec::new(),
downloads: DownloadStore::new(),
actor_system,
+3
View File
@@ -593,6 +593,9 @@ impl Client {
&own_username,
token,
&query,
ctx.has_free_upload_slot(),
ctx.last_upload_speed,
ctx.upload_queue.len() as u32,
),
Err(e) => {
error!("[client] IncomingSearch read: {}", e);
+7 -3
View File
@@ -160,8 +160,9 @@ fn build_search_response_matches_shares_and_echoes_token() {
std::fs::write(dir.join("probe_xyzzy.bin"), b"data").unwrap();
let shares = Shares::scan(&dir).unwrap();
let response = build_search_response(&shares, "me", 99, "xyzzy")
.expect("a matching share yields a response");
let response =
build_search_response(&shares, "me", 99, "xyzzy", true, 0, 0)
.expect("a matching share yields a response");
let mut decoded =
crate::message::Message::new_with_data(response.get_buffer());
decoded.set_pointer(8);
@@ -170,7 +171,10 @@ fn build_search_response_matches_shares_and_echoes_token() {
assert_eq!(result.token, 99);
assert!(result.files.iter().any(|f| f.name.contains("probe_xyzzy")));
assert!(build_search_response(&shares, "me", 1, "nomatch").is_none());
assert!(
build_search_response(&shares, "me", 1, "nomatch", true, 0, 0)
.is_none()
);
let _ = std::fs::remove_dir_all(dir);
}
+5 -1
View File
@@ -123,7 +123,7 @@ impl ClientContext {
mut next_token: impl FnMut() -> u32,
) -> (Option<PeerRegistry>, Vec<UploadOffer>) {
let mut offers = Vec::new();
while self.uploads_in_flight() < self.upload_slots {
while self.has_free_upload_slot() {
let Some(index) = next_to_serve(&self.upload_queue) else {
break;
};
@@ -190,6 +190,10 @@ impl ClientContext {
.count()
}
pub(super) fn has_free_upload_slot(&self) -> bool {
self.uploads_in_flight() < self.upload_slots
}
/// The peers still waiting, in the order they will be served, each carrying
/// its own 1-based place.
pub(super) fn queued_uploads(&self) -> Vec<UploadInfo> {
+8 -1
View File
@@ -145,8 +145,9 @@ impl Client {
&bytes_sent,
&cancel,
);
let streamed = result.as_ref().ok().copied();
let status = match &result {
Ok(()) => UploadStatus::Completed,
Ok(_) => UploadStatus::Completed,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
UploadStatus::Cancelled
}
@@ -158,7 +159,13 @@ impl Client {
if let Ok(mut ctx) = context.write_safe()
&& let Some(upload) = ctx.active_uploads.get_mut(&token)
{
let secs = upload.started.elapsed().as_secs_f64();
upload.status = status;
if let Some(bytes) = streamed
&& secs > 0.0
{
ctx.last_upload_speed = (bytes as f64 / secs) as u32;
}
}
// The slot this transfer held is free now, so whoever is next in
// line gets it without waiting for another request to arrive.