From 6dc711cf57f5c9304cc2813aebce9fbbb0d47211 Mon Sep 17 00:00:00 2001 From: ookami125 Date: Wed, 17 Jun 2026 02:23:15 -0400 Subject: [PATCH] Limit active pieces and expose file list --- apps/nautd/main.c | 19 +++++++++++++++++ apps/swarm/main.c | 46 ++++++++++++++++++++++++++++++++++++++++ include/naut/swarm.h | 11 ++++++++++ plugins/webui/webui.c | 6 ++++-- src/piece/piece.c | 8 ++++++- tests/unit/test_picker.c | 30 ++++++++++++++++++++++++++ 6 files changed, 117 insertions(+), 3 deletions(-) diff --git a/apps/nautd/main.c b/apps/nautd/main.c index b03c28b..1c25c79 100644 --- a/apps/nautd/main.c +++ b/apps/nautd/main.c @@ -278,6 +278,25 @@ static json_t *torrent_json(torrent_task *task) { } json_object_set_new(result, "trackers", trackers); } + json_t *files = json_array(); + if (files) { + uint32_t file_count = task->stats.file_count; + if (file_count > NAUT_SWARM_MAX_FILE_STATS) + file_count = NAUT_SWARM_MAX_FILE_STATS; + for (uint32_t i = 0; i < file_count; i++) { + const naut_swarm_file_stats *file = + &task->stats.file_stats[i]; + json_t *item = json_pack( + "{s:s,s:I,s:f,s:i,s:f}", + "name", file->path, + "size", (json_int_t)file->size, + "progress", file->progress, + "priority", file->priority, + "availability", file->availability); + if (item) json_array_append_new(files, item); + } + json_object_set_new(result, "files", files); + } json_object_set_new(result, "elapsed_seconds", json_real(task->stats.elapsed_seconds)); json_object_set_new(result, "pending_moves", diff --git a/apps/swarm/main.c b/apps/swarm/main.c index 16219ea..8317fb8 100644 --- a/apps/swarm/main.c +++ b/apps/swarm/main.c @@ -285,6 +285,51 @@ static void snapshot_tracker_stats(naut_swarm_stats *stats, stats->tracker_stats[i] = trackers[i]; } +static void snapshot_file_stats(naut_swarm_stats *stats, + const naut_download *download, + const naut_metainfo *metainfo) { + if (!stats || !metainfo || !metainfo->files) return; + uint64_t offset = 0; + uint32_t count = 0; + for (size_t i = 0; + i < metainfo->num_files && count < NAUT_SWARM_MAX_FILE_STATS; + i++) { + const naut_file *file = &metainfo->files[i]; + uint64_t size = file->length > 0 ? (uint64_t)file->length : 0; + uint64_t done = 0; + if (download && size > 0) { + uint64_t start = offset; + uint64_t end = offset + size; + uint32_t first = (uint32_t)(start / (uint64_t)metainfo->piece_length); + uint32_t last = (uint32_t)((end - 1) / + (uint64_t)metainfo->piece_length); + for (uint32_t p = first; p <= last; p++) { + if (!naut_download_have(download, p)) continue; + uint64_t piece_start = (uint64_t)p * + (uint64_t)metainfo->piece_length; + uint64_t piece_end = piece_start + + (uint64_t)metainfo->piece_length; + if (piece_end > (uint64_t)metainfo->total_length) + piece_end = (uint64_t)metainfo->total_length; + uint64_t lo = piece_start > start ? piece_start : start; + uint64_t hi = piece_end < end ? piece_end : end; + if (hi > lo) done += hi - lo; + } + } + naut_swarm_file_stats *out = &stats->file_stats[count++]; + memset(out, 0, sizeof(*out)); + snprintf(out->path, sizeof out->path, "%s", + file->path ? file->path : ""); + out->size = size; + out->progress = size ? (double)done / (double)size : 1.0; + if (out->progress > 1.0) out->progress = 1.0; + out->priority = 1; + out->availability = 1.0; + offset += size; + } + stats->file_count = count; +} + static void report_progress(const naut_swarm_config *config, const naut_download *download, const naut_metainfo *metainfo, @@ -308,6 +353,7 @@ static void report_progress(const naut_swarm_config *config, }; snapshot_peer_stats(&stats, peers, npeers, metainfo->num_pieces); snapshot_tracker_stats(&stats, trackers, tracker_count); + snapshot_file_stats(&stats, download, metainfo); if (download) { stats.piece_state_count = (uint32_t)naut_download_piece_states( download, stats.piece_states, NAUT_SWARM_MAX_PIECE_STATS); diff --git a/include/naut/swarm.h b/include/naut/swarm.h index 092547d..82a9407 100644 --- a/include/naut/swarm.h +++ b/include/naut/swarm.h @@ -14,6 +14,7 @@ #define NAUT_SWARM_MAX_PEER_STATS 64 #define NAUT_SWARM_MAX_PIECE_STATS 4000 #define NAUT_SWARM_MAX_TRACKER_STATS 32 +#define NAUT_SWARM_MAX_FILE_STATS 1024 typedef struct { char ip[46]; @@ -40,6 +41,14 @@ typedef struct { char message[128]; } naut_swarm_tracker_stats; +typedef struct { + char path[512]; + uint64_t size; + double progress; + int32_t priority; + double availability; +} naut_swarm_file_stats; + typedef struct { uint64_t total_bytes; uint64_t bytes_done; @@ -54,6 +63,8 @@ typedef struct { naut_swarm_peer_stats peer_stats[NAUT_SWARM_MAX_PEER_STATS]; uint32_t tracker_count; naut_swarm_tracker_stats tracker_stats[NAUT_SWARM_MAX_TRACKER_STATS]; + uint32_t file_count; + naut_swarm_file_stats file_stats[NAUT_SWARM_MAX_FILE_STATS]; uint32_t piece_state_count; uint8_t piece_states[NAUT_SWARM_MAX_PIECE_STATS]; } naut_swarm_stats; diff --git a/plugins/webui/webui.c b/plugins/webui/webui.c index 7e34b43..3c22b8c 100644 --- a/plugins/webui/webui.c +++ b/plugins/webui/webui.c @@ -847,12 +847,14 @@ static json_t *map_torrent(json_t *torrent, bool detail, double dlspeed) { json_t *peers_list = NULL; json_t *hosts = NULL; json_t *source_trackers = json_object_get(torrent, "trackers"); + json_t *source_files = json_object_get(torrent, "files"); hosts = tracker_hosts(source_trackers); if (detail) { trackers = json_is_array(source_trackers) ? json_deep_copy(source_trackers) : json_array(); - files = json_array(); - if (files) + files = json_is_array(source_files) + ? json_deep_copy(source_files) : json_array(); + if (files && json_array_size(files) == 0 && total > 0) json_array_append_new(files, json_pack( "{s:s,s:I,s:f,s:i,s:f}", "name", name, "size", (json_int_t)total, "progress", progress, diff --git a/src/piece/piece.c b/src/piece/piece.c index 80b2e0a..a13e478 100644 --- a/src/piece/piece.c +++ b/src/piece/piece.c @@ -10,6 +10,7 @@ #define ENDGAME_BLOCKS 8 /* switch to endgame when this few remain */ #define ENDGAME_COPIES 2 /* at most two peers race a missing block */ #define PIECE_INFLIGHT_SOFT_CAP 64 +#define ACTIVE_PIECE_SOFT_CAP 64 /* per-piece in-progress state, lazily allocated and freed on completion */ typedef struct { @@ -36,6 +37,7 @@ struct naut_download { naut_bitfield have; uint32_t *avail; /* [num_pieces] swarm availability count */ pstate **ps; /* [num_pieces] in-progress state or NULL */ + uint32_t active_pieces; uint32_t cur_piece; /* sequential cursor for next_request() */ uint32_t pick_cursor; /* rotating start point for rarest-first ties */ @@ -88,6 +90,7 @@ static pstate *ensure_ps(naut_download *d, uint32_t p) { return NULL; } d->ps[p] = s; + d->active_pieces++; return s; } static void free_ps(naut_download *d, uint32_t p) { @@ -95,6 +98,7 @@ static void free_ps(naut_download *d, uint32_t p) { if (!s) return; free(s->recv_bits); free(s->req_count); free(s->buf); free(s); d->ps[p] = NULL; + if (d->active_pieces) d->active_pieces--; } naut_download *naut_download_create(const naut_metainfo *mi, naut_storage *st) { @@ -230,13 +234,15 @@ bool naut_download_pick_for_peer(naut_download *d, const naut_bitfield *peer_hav uint32_t p = (d->pick_cursor + n) % d->num_pieces; if (naut_bitfield_test(&d->have, p) || !d->ps[p]) continue; if (p >= peer_have->nbits || !naut_bitfield_test(peer_have, p)) continue; - if (!d->endgame && + if (!d->endgame && d->active_pieces < ACTIVE_PIECE_SOFT_CAP && piece_inflight(d->ps[p]) >= PIECE_INFLIGHT_SOFT_CAP) continue; uint32_t b = first_unreq(d->ps[p]); if (b != UINT32_MAX) return hand_out(d, p, b, index, begin, length); } /* pass 2: start the rarest new piece the peer has */ + if (!d->endgame && d->active_pieces >= ACTIVE_PIECE_SOFT_CAP) + return false; uint32_t best = UINT32_MAX, best_av = UINT32_MAX; for (uint32_t n = 0; n < d->num_pieces; n++) { uint32_t p = (d->pick_cursor + n) % d->num_pieces; diff --git a/tests/unit/test_picker.c b/tests/unit/test_picker.c index c2e5cce..ae5401f 100644 --- a/tests/unit/test_picker.c +++ b/tests/unit/test_picker.c @@ -90,6 +90,36 @@ int main(void) { naut_download_destroy(d); + /* The picker should not open the entire torrent at once. A large swarm can + * keep many requests in flight, but new-piece fanout is bounded so the + * piece map does not show most pieces "downloading" while few verify. */ + enum { CAP_NP = 80 }; + uint64_t cap_total = (uint64_t)CAP_NP * NAUT_BLOCK; + uint8_t *cap_hashes = malloc(CAP_NP * NAUT_SHA1_LEN); + CHECK(cap_hashes != NULL); + for (int p = 0; p < CAP_NP; p++) + naut_sha1(data, NAUT_BLOCK, cap_hashes + p * NAUT_SHA1_LEN); + naut_file cap_file[1] = { { (char *)"cap.bin", (int64_t)cap_total } }; + naut_metainfo cap_mi; memset(&cap_mi, 0, sizeof cap_mi); + cap_mi.has_v1 = true; cap_mi.num_pieces = CAP_NP; + cap_mi.piece_length = NAUT_BLOCK; cap_mi.total_length = cap_total; + cap_mi.piece_hashes = cap_hashes; cap_mi.files = cap_file; + cap_mi.num_files = 1; cap_mi.name = (char *)"cap"; + d = naut_download_create(&cap_mi, st); + CHECK(d != NULL); + naut_bitfield all; + naut_bitfield_init(&all, CAP_NP); + for (int p = 0; p < CAP_NP; p++) naut_bitfield_set(&all, p); + naut_download_add_bitfield(d, &all); + int opened = 0; + while (naut_download_pick(d, &all, &idx, &begin, &len)) + opened++; + CHECK(opened > 0); + CHECK(opened < CAP_NP); + naut_bitfield_free(&all); + naut_download_destroy(d); + free(cap_hashes); + /* full multi-peer download: alternate peers, all pieces verify */ d = naut_download_create(&mi, st); naut_download_add_bitfield(d, &hb); /* one peer that has everything */