nautd/webui: scripting, labels, settings, set-location, pause fix
Session checkpoint on webui-plugin: - engine dump (nautctl dump) + engine endgame integration - per-file move locations persistence; torrent-level "Set location" with reset/keep-relative/leave-separate handling + residual prune - Lua: naut.get_labels, define_settings/get_setting (script_host struct) - daemon-owned labels (category+tags) + taxonomy persistence; webui write-through - fix: pausing a completed/seeding torrent now sticks (stop wins over result) - automation tab responsive layout; anime_sort label gating + settings Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6dc711cf57
commit
b633b7d216
40 changed files with 3305 additions and 3267 deletions
|
|
@ -1,4 +1,5 @@
|
|||
/* dht.h - BEP-5 KRPC codec and bounded IPv4 get_peers traversal. */
|
||||
/* dht.h - bounded IPv4 BEP-5 get_peers traversal (KRPC codec from
|
||||
* torrent-tracker; iterative walk + UDP socket in src/discovery). */
|
||||
#ifndef NAUT_DHT_H
|
||||
#define NAUT_DHT_H
|
||||
|
||||
|
|
@ -9,54 +10,6 @@
|
|||
#define NAUT_DHT_MAX_NODES 256
|
||||
#define NAUT_DHT_MAX_PEERS 256
|
||||
|
||||
typedef struct {
|
||||
uint8_t id[NAUT_DHT_ID_LEN];
|
||||
uint8_t ip[4];
|
||||
uint16_t port;
|
||||
} naut_dht_node;
|
||||
|
||||
typedef enum {
|
||||
NAUT_DHT_RESPONSE,
|
||||
NAUT_DHT_ERROR
|
||||
} naut_dht_message_type;
|
||||
|
||||
typedef struct {
|
||||
naut_dht_message_type type;
|
||||
uint8_t transaction[8];
|
||||
size_t transaction_len;
|
||||
uint8_t id[NAUT_DHT_ID_LEN];
|
||||
bool has_id;
|
||||
uint8_t token[64];
|
||||
size_t token_len;
|
||||
naut_dht_node *nodes;
|
||||
size_t num_nodes;
|
||||
naut_peer_addr *peers;
|
||||
size_t num_peers;
|
||||
int error_code;
|
||||
} naut_dht_response;
|
||||
|
||||
naut_err naut_dht_build_ping(const uint8_t *tx, size_t tx_len,
|
||||
const uint8_t id[20],
|
||||
uint8_t **out, size_t *out_len);
|
||||
naut_err naut_dht_build_find_node(const uint8_t *tx, size_t tx_len,
|
||||
const uint8_t id[20],
|
||||
const uint8_t target[20],
|
||||
uint8_t **out, size_t *out_len);
|
||||
naut_err naut_dht_build_get_peers(const uint8_t *tx, size_t tx_len,
|
||||
const uint8_t id[20],
|
||||
const uint8_t info_hash[20],
|
||||
uint8_t **out, size_t *out_len);
|
||||
naut_err naut_dht_build_announce_peer(const uint8_t *tx, size_t tx_len,
|
||||
const uint8_t id[20],
|
||||
const uint8_t info_hash[20],
|
||||
uint16_t port, bool implied_port,
|
||||
const void *token, size_t token_len,
|
||||
uint8_t **out, size_t *out_len);
|
||||
|
||||
naut_err naut_dht_parse_response(const uint8_t *data, size_t len,
|
||||
naut_dht_response *out);
|
||||
void naut_dht_response_free(naut_dht_response *response);
|
||||
|
||||
/* Query bootstrap endpoints ("host:port") and iteratively follow returned
|
||||
* compact nodes until peers are found or the bounded traversal is exhausted. */
|
||||
naut_err naut_dht_get_peers(const char *const *bootstrap, size_t num_bootstrap,
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
/* mse.h - BitTorrent Message Stream Encryption (MSE/PE) transport. */
|
||||
#ifndef NAUT_MSE_H
|
||||
#define NAUT_MSE_H
|
||||
|
||||
#include "naut/common.h"
|
||||
#include "naut/peer.h"
|
||||
#include "naut/rc4.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#define NAUT_MSE_DH_LEN 96
|
||||
|
||||
typedef struct {
|
||||
naut_rc4 send;
|
||||
naut_rc4 recv;
|
||||
bool active;
|
||||
} naut_mse_stream;
|
||||
|
||||
/* ---- sans-IO handshake state machine ------------------------------------- *
|
||||
* The outgoing MSE/PE handshake as a pure state machine over byte buffers — no
|
||||
* sockets — so the same logic drives the blocking apps and the io_uring reactor
|
||||
* (where blocking in a handshake would stall a whole core's worth of peers).
|
||||
*
|
||||
* Drive it like a codec: pump NEED_WRITE bytes out, feed NEED_READ bytes in,
|
||||
* repeat until DONE or ERROR, then call _finish().
|
||||
*
|
||||
* h = naut_mse_handshake_begin(info_hash, peer_id, reserved);
|
||||
* for (;;) switch (naut_mse_handshake_status(h)) {
|
||||
* case NAUT_MSE_HS_NEED_WRITE: pull bytes, write them to the peer; break;
|
||||
* case NAUT_MSE_HS_NEED_READ: read bytes from the peer, feed them; break;
|
||||
* case NAUT_MSE_HS_DONE: naut_mse_handshake_finish(h, ...); goto ok;
|
||||
* case NAUT_MSE_HS_ERROR: ... ; goto err;
|
||||
* }
|
||||
*/
|
||||
typedef enum {
|
||||
NAUT_MSE_HS_NEED_READ,
|
||||
NAUT_MSE_HS_NEED_WRITE,
|
||||
NAUT_MSE_HS_DONE,
|
||||
NAUT_MSE_HS_ERROR,
|
||||
} naut_mse_hs_status;
|
||||
|
||||
typedef struct naut_mse_handshake naut_mse_handshake;
|
||||
|
||||
naut_mse_handshake *naut_mse_handshake_begin(
|
||||
const uint8_t info_hash[20],
|
||||
const uint8_t peer_id[NAUT_PEERID_LEN],
|
||||
uint64_t reserved);
|
||||
void naut_mse_handshake_free(naut_mse_handshake *h);
|
||||
|
||||
naut_mse_hs_status naut_mse_handshake_status(const naut_mse_handshake *h);
|
||||
|
||||
/* Copy pending outgoing bytes into buf (up to cap); returns the count, 0 when
|
||||
* nothing is queued. Call repeatedly until it returns 0. */
|
||||
size_t naut_mse_handshake_pull(naut_mse_handshake *h, uint8_t *buf, size_t cap);
|
||||
|
||||
/* Feed received bytes; *consumed reports how many were absorbed (the rest, if
|
||||
* any, must be re-fed — after DONE that remainder is the start of the encrypted
|
||||
* payload stream). Returns the new status. */
|
||||
naut_mse_hs_status naut_mse_handshake_feed(naut_mse_handshake *h,
|
||||
const uint8_t *data, size_t len,
|
||||
size_t *consumed);
|
||||
|
||||
/* Valid once status is DONE: hand out the negotiated stream and the peer's
|
||||
* decrypted BitTorrent handshake. */
|
||||
naut_err naut_mse_handshake_finish(naut_mse_handshake *h,
|
||||
naut_mse_stream *stream,
|
||||
uint8_t remote_handshake[NAUT_HANDSHAKE_LEN]);
|
||||
|
||||
/* Blocking convenience wrapper over the state machine: perform the whole
|
||||
* outgoing handshake on a blocking socket, offering RC4 only. The BitTorrent
|
||||
* handshake is carried as IA; the peer's decrypted handshake is returned in
|
||||
* remote_handshake. */
|
||||
naut_err naut_mse_client_handshake(
|
||||
int fd,
|
||||
const uint8_t info_hash[20],
|
||||
const uint8_t peer_id[NAUT_PEERID_LEN],
|
||||
uint64_t reserved,
|
||||
naut_mse_stream *stream,
|
||||
uint8_t remote_handshake[NAUT_HANDSHAKE_LEN]);
|
||||
|
||||
/* Stream I/O after a successful handshake. Encryption/decryption is in-place
|
||||
* with connection-owned RC4 state. send_all preserves the caller's buffer. */
|
||||
bool naut_mse_send_all(int fd, naut_mse_stream *stream,
|
||||
const void *data, size_t len);
|
||||
ssize_t naut_mse_recv(int fd, naut_mse_stream *stream,
|
||||
void *data, size_t len);
|
||||
|
||||
#endif /* NAUT_MSE_H */
|
||||
|
|
@ -13,11 +13,17 @@
|
|||
#include "naut/bitfield.h"
|
||||
#include "naut/worker.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct naut_download naut_download;
|
||||
|
||||
naut_download *naut_download_create(const naut_metainfo *mi, naut_storage *st);
|
||||
void naut_download_destroy(naut_download *d);
|
||||
|
||||
/* Scan existing storage and mark SHA-1 verified pieces complete before
|
||||
* requesting from peers. Invalid or missing pieces are left for download. */
|
||||
naut_err naut_download_resume(naut_download *d);
|
||||
|
||||
/* Optional hash offload. Completed-piece SHA-1 jobs run on the worker pool;
|
||||
* naut_download_poll() finalizes verified pieces on the owning engine thread.
|
||||
* The pool must outlive the download. */
|
||||
|
|
@ -90,4 +96,11 @@ uint64_t naut_download_bytes_done(const naut_download *d);
|
|||
size_t naut_download_piece_states(const naut_download *d, uint8_t *out,
|
||||
size_t capacity);
|
||||
|
||||
/* Diagnostic: write a human-readable dump of block-assembly state to `out` —
|
||||
* overall progress plus, for every piece not yet verified, how many of its
|
||||
* blocks have arrived and how many requests are outstanding. Pairs with
|
||||
* engine_dump_torrent() (which covers piece selection across peers) to
|
||||
* investigate pieces that never finish downloading. */
|
||||
void naut_download_dump(const naut_download *d, FILE *out);
|
||||
|
||||
#endif /* NAUT_PIECE_H */
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
/* pipeline.h - adaptive request window based on observed bandwidth-delay product. */
|
||||
#ifndef NAUT_PIPELINE_H
|
||||
#define NAUT_PIPELINE_H
|
||||
|
||||
#include "naut/common.h"
|
||||
|
||||
typedef struct {
|
||||
double rtt_seconds;
|
||||
double bytes_per_second;
|
||||
double last_sample_at;
|
||||
uint32_t depth;
|
||||
uint32_t min_depth;
|
||||
uint32_t max_depth;
|
||||
uint32_t block_size;
|
||||
} naut_pipeline;
|
||||
|
||||
void naut_pipeline_init(naut_pipeline *p, uint32_t block_size,
|
||||
uint32_t min_depth, uint32_t max_depth,
|
||||
uint32_t initial_depth);
|
||||
|
||||
/* Record one completed request. sent_at and received_at are monotonic seconds.
|
||||
* The controller smooths RTT and delivery rate, then targets 2x BDP to absorb
|
||||
* scheduling jitter without allowing an unbounded request window. */
|
||||
void naut_pipeline_on_block(naut_pipeline *p, uint32_t bytes,
|
||||
double sent_at, double received_at);
|
||||
|
||||
uint32_t naut_pipeline_depth(const naut_pipeline *p);
|
||||
|
||||
#endif /* NAUT_PIPELINE_H */
|
||||
|
|
@ -11,6 +11,50 @@ typedef naut_err (*naut_script_move_file_cb)(void *context,
|
|||
uint32_t file_index,
|
||||
const char *destination);
|
||||
|
||||
/* Resolve a torrent's labels for `naut.get_labels(id)`. Returns a heap array of
|
||||
* `*count` heap strings (caller frees each string then the array), or NULL with
|
||||
* *count==0 if the torrent has no labels / is unknown. */
|
||||
typedef char **(*naut_script_labels_cb)(void *context, uint64_t torrent_id,
|
||||
size_t *count);
|
||||
|
||||
/* One user-configurable setting a script declares via naut.define_settings. */
|
||||
typedef struct {
|
||||
const char *key; /* stable identifier read by naut.get_setting */
|
||||
const char *label; /* human label for the web UI form */
|
||||
const char *type; /* "string" | "bool" | "number" */
|
||||
const char *default_value; /* stringified default ("true"/"false" for bool)*/
|
||||
} naut_script_setting_def;
|
||||
|
||||
/* The script (re)declared its settings schema. The host stores it and renders a
|
||||
* form; `defs` is valid only for the duration of the call. */
|
||||
typedef void (*naut_script_define_settings_cb)(void *context,
|
||||
const naut_script_setting_def *defs,
|
||||
size_t count);
|
||||
|
||||
/* Setting value kinds, so the Lua side can push the right type. */
|
||||
typedef enum {
|
||||
NAUT_SETTING_STRING = 0,
|
||||
NAUT_SETTING_BOOL = 1,
|
||||
NAUT_SETTING_NUMBER = 2,
|
||||
} naut_setting_type;
|
||||
|
||||
/* Resolve a setting for `naut.get_setting(key)`: the user-set value if present,
|
||||
* else the declared default. Returns a heap string (caller frees) and sets
|
||||
* *type, or NULL if the key is unknown. */
|
||||
typedef char *(*naut_script_get_setting_cb)(void *context, const char *key,
|
||||
naut_setting_type *type);
|
||||
|
||||
/* Host callbacks the sandboxed script may invoke. Any may be NULL (the matching
|
||||
* naut.* function then reports it is unavailable). `context` is passed back to
|
||||
* each callback. */
|
||||
typedef struct {
|
||||
naut_script_move_file_cb move_file;
|
||||
naut_script_labels_cb labels;
|
||||
naut_script_define_settings_cb define_settings;
|
||||
naut_script_get_setting_cb get_setting;
|
||||
void *context;
|
||||
} naut_script_host;
|
||||
|
||||
typedef struct {
|
||||
uint64_t queued;
|
||||
uint64_t handled;
|
||||
|
|
@ -20,12 +64,12 @@ typedef struct {
|
|||
} naut_script_stats;
|
||||
|
||||
/* script_path is loaded before the worker starts. queue_capacity bounds copied
|
||||
* events and must be non-zero. The VM owns no filesystem or process APIs. */
|
||||
* events and must be non-zero. The VM owns no filesystem or process APIs. `host`
|
||||
* is copied; its callbacks are invoked from the script worker thread. */
|
||||
naut_script *naut_script_create(naut_event_bus *events,
|
||||
const char *script_path,
|
||||
size_t queue_capacity,
|
||||
naut_script_move_file_cb move_file,
|
||||
void *move_context,
|
||||
const naut_script_host *host,
|
||||
naut_err *error);
|
||||
void naut_script_destroy(naut_script *script);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ typedef struct naut_storage naut_storage;
|
|||
typedef struct {
|
||||
bool direct_io;
|
||||
bool preallocate;
|
||||
/* Optional per-file path overrides, e.g. files moved out of `root` on a
|
||||
* prior run. If non-NULL the array has one entry per file: where overrides[i]
|
||||
* is non-NULL the file is opened at that path instead of `root`/<rel-path>,
|
||||
* so a relocated file is picked up in place (no re-download, no placeholder
|
||||
* recreated under `root`). A NULL entry uses the default location. */
|
||||
const char *const *overrides;
|
||||
} naut_storage_opts;
|
||||
|
||||
/* Open (creating + preallocating) all files under `root`. */
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ typedef struct {
|
|||
uint32_t peers_connecting;
|
||||
uint32_t peers_active;
|
||||
uint32_t peers_failed;
|
||||
bool stalled;
|
||||
double elapsed_seconds;
|
||||
uint32_t peer_count;
|
||||
naut_swarm_peer_stats peer_stats[NAUT_SWARM_MAX_PEER_STATS];
|
||||
|
|
@ -78,17 +79,42 @@ typedef void (*naut_swarm_control_cb)(void *context, naut_storage *storage);
|
|||
|
||||
typedef bool (*naut_swarm_stop_cb)(void *context);
|
||||
|
||||
/* Optional: return the desired engine-wide download cap in bytes/sec (0 =
|
||||
* unlimited). Polled on the swarm owner thread; the engine limit is updated
|
||||
* whenever the returned value changes. */
|
||||
typedef uint64_t (*naut_swarm_rate_cb)(void *context);
|
||||
|
||||
/* Optional diagnostics. should_dump is polled on the swarm owner thread; when it
|
||||
* returns true the swarm renders a full engine + piece-assembly state dump and
|
||||
* hands the text to on_dump (also on the owner thread, where engine and download
|
||||
* state can be read safely). Used by `nautctl dump` to investigate why a few
|
||||
* pieces never finish downloading. */
|
||||
typedef bool (*naut_swarm_dump_cb)(void *context);
|
||||
typedef void (*naut_swarm_dump_sink)(void *context, const char *text);
|
||||
|
||||
/* A file's last known on-disk location, from a relocate on a prior run. Passed
|
||||
* back in so the file is reopened in place instead of re-downloaded. */
|
||||
typedef struct {
|
||||
uint32_t file_index;
|
||||
const char *path;
|
||||
} naut_swarm_file_location;
|
||||
|
||||
typedef struct {
|
||||
const char *source; /* .torrent path or magnet URI */
|
||||
const char *output_dir;
|
||||
const char *const *peers; /* optional explicit ip:port endpoints */
|
||||
size_t num_peers;
|
||||
const naut_swarm_file_location *locations; /* optional moved-file locations */
|
||||
size_t num_locations;
|
||||
uint64_t torrent_id;
|
||||
naut_event_bus *events; /* optional */
|
||||
bool keep_alive; /* retain completed storage until stopped */
|
||||
naut_swarm_progress_cb on_progress;
|
||||
naut_swarm_control_cb on_control;
|
||||
naut_swarm_stop_cb should_stop;
|
||||
naut_swarm_rate_cb download_rate; /* optional download throttle provider */
|
||||
naut_swarm_dump_cb should_dump; /* optional state-dump request poll */
|
||||
naut_swarm_dump_sink on_dump; /* optional rendered-dump sink */
|
||||
void *context;
|
||||
} naut_swarm_config;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
/* tracker.h — HTTP and UDP tracker clients (BEP-3/BEP-23, BEP-15).
|
||||
/* tracker.h — HTTP and UDP tracker announce client.
|
||||
*
|
||||
* Split into pure codec (URL building, bencode response parsing, UDP packet
|
||||
* encode/decode — all unit-testable without a socket) and thin blocking fetch
|
||||
* helpers used by the swarm app. HTTPS/TLS is deferred to a later transport
|
||||
* backend; the built-ins in this phase are plaintext HTTP and UDP.
|
||||
* The wire codec (query building, bencode/UDP packet encode+decode) lives in the
|
||||
* sibling `torrent-tracker` library; the implementation here (src/discovery)
|
||||
* owns only the socket glue. HTTPS/TLS is deferred to a later transport backend;
|
||||
* the built-ins are plaintext HTTP and UDP.
|
||||
*/
|
||||
#ifndef NAUT_TRACKER_H
|
||||
#define NAUT_TRACKER_H
|
||||
|
|
@ -37,25 +37,10 @@ typedef struct {
|
|||
|
||||
void naut_tracker_response_free(naut_tracker_response *r);
|
||||
|
||||
/* --- HTTP --- */
|
||||
/* Build the full announce GET URL (base?...params) with percent-encoded binary
|
||||
* info_hash/peer_id. Returns bytes written (excl NUL) or 0 on overflow. */
|
||||
size_t naut_tracker_http_url(const char *base, const naut_announce_req *req,
|
||||
char *out, size_t outsz);
|
||||
/* Parse a bencoded HTTP tracker response body (compact or dict peer list). */
|
||||
naut_err naut_tracker_parse_http(const uint8_t *body, size_t len,
|
||||
naut_tracker_response *out);
|
||||
|
||||
/* --- UDP (BEP-15): pure packet codec --- */
|
||||
#define NAUT_UDP_CONNECT_REQ_LEN 16
|
||||
#define NAUT_UDP_ANNOUNCE_REQ_LEN 98
|
||||
void naut_udp_build_connect(uint8_t out[16], uint32_t txid);
|
||||
naut_err naut_udp_parse_connect(const uint8_t *in, size_t len, uint32_t txid,
|
||||
uint64_t *connection_id);
|
||||
void naut_udp_build_announce(uint8_t out[98], uint64_t connection_id,
|
||||
uint32_t txid, const naut_announce_req *req);
|
||||
naut_err naut_udp_parse_announce(const uint8_t *in, size_t len, uint32_t txid,
|
||||
naut_tracker_response *out);
|
||||
|
||||
/* --- live fetch helpers (blocking) --- */
|
||||
/* HTTP GET the announce URL; fills out. Only http:// (no TLS yet). */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue