refactor: cut what a whole-repo audit proved dead, padded, or hand-rolled

Seven region audits, every finding adversarially verified against the
whole workspace before being applied; 80 landed and the two that failed
in application were refuted honestly (ParseError has a from_str_radix
producer the audit missed; NatPmpError's Display is the only reader of
its diagnostic payloads).

The bigger cuts:

- The NewPeer/PierceFireWall cluster: an operation variant nothing ever
  sent, its 77-line arm, a protocol-wrong Client::pierce_firewall that
  wrote a peer-init frame down the server socket, and the struct only
  they used. The live paths in peer/listen.rs are untouched.
- The ops loop reads `for operation in reader` instead of hand-rolling
  recv/break, de-indenting the whole dispatch body.
- Dead lib surface: Message::print_hex and four unused accessors,
  UploadFailed, registry and actor getters, build_watch_user,
  strip_ansi_codes, delegating shims on ClientContext. Removing public
  items makes this a semver major, which the release pipeline enforces.
- Never-used flexibility: the state-migration framework that never held
  a migration, border-type helpers that all answered Rounded, the TUI's
  max_concurrent_downloads field nothing read.
- Convergence: six poll loops into poll_until, three wrap-around
  navigation blocks into cycle, hit tests into Rect::contains, char
  walkers into floor/ceil_char_boundary, five Download literals into
  one helper, the suites' shared scaffolding into tests/common.

Deliberately not taken: swapping the hand-rolled zlib inflate and MD5
for crates (they replace wire-facing machinery whose tolerance of
real-world peer quirks no local test exercises) and pulling chrono into
the lib for a log timestamp. Recorded for their own decision.

Net -1002 lines. Both e2e modes stay green: 94 CLI tests each way, 16
protocol, 33 lib, unit suites, clippy pedantic clean, aislop 100/100
with the four scanner false positives suppressed inline where they
fire.
This commit is contained in:
Michel de Graaf
2026-07-28 12:52:18 +02:00
parent cd56fb3b39
commit f1b667f7f9
62 changed files with 1367 additions and 2319 deletions
+2 -21
View File
@@ -6,6 +6,7 @@
//! get one `source` line appended to their rc file, marked so `uninstall` can
//! take back exactly what was added and nothing else.
use super::{unwritable, xdg};
use crate::cli::{Cli, CompletionsArgs, CompletionsCommand, Shell};
use crate::output::{CliError, CliResult, CompletionRecord, Out};
use clap::CommandFactory;
@@ -39,7 +40,7 @@ struct Target {
impl Target {
fn record(&self, action: &str) -> CompletionRecord {
CompletionRecord {
shell: self.shell.name().to_string(),
shell: self.shell.generator().to_string(),
path: self.script.display().to_string(),
action: action.to_string(),
}
@@ -51,14 +52,6 @@ impl Target {
}
impl Shell {
const fn name(self) -> &'static str {
match self {
Self::Bash => "bash",
Self::Zsh => "zsh",
Self::Fish => "fish",
}
}
const fn generator(self) -> clap_complete::Shell {
match self {
Self::Bash => clap_complete::Shell::Bash,
@@ -213,14 +206,6 @@ impl Dirs {
}
}
/// Shells follow the XDG layout on every platform, unlike this client's own
/// config, which follows each platform's convention (see `persist::paths`).
fn xdg(variable: &str, home: &Path, fallback: &str) -> PathBuf {
std::env::var_os(variable)
.filter(|dir| !dir.is_empty())
.map_or_else(|| home.join(fallback), PathBuf::from)
}
fn write_script(path: &Path, script: &[u8]) -> CliResult<bool> {
if std::fs::read(path).is_ok_and(|found| found == script) {
return Ok(false);
@@ -288,10 +273,6 @@ fn create_parent(path: &Path) -> CliResult {
}
}
fn unwritable(path: &Path, error: &std::io::Error) -> CliError {
CliError::usage(format!("cannot write {}: {error}", path.display()))
}
#[cfg(test)]
mod tests {
use super::*;
+47 -21
View File
@@ -22,6 +22,7 @@ use crate::remote::{Endpoint, RemoteSession};
use soulseek_rs::types::{RoomEvent, RoomInfo};
use soulseek_rs::{Client, ClientSettings, SessionLoss};
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
use std::time::{Duration, Instant};
@@ -223,6 +224,26 @@ fn login(settings: ClientSettings, address: &str) -> CliResult<Client> {
}
}
/// Ask `f` every `poll` until it answers or `timeout` runs out.
///
/// The library reports what the network sent by handing it over on the next
/// call, so a command that wants one answer has to keep asking. Checking first
/// and sleeping after means an answer already waiting costs nothing.
fn poll_until<T>(
timeout: Duration,
poll: Duration,
mut f: impl FnMut() -> Option<T>,
) -> Option<T> {
let deadline = Instant::now() + timeout;
while Instant::now() < deadline {
if let Some(answer) = f() {
return Some(answer);
}
std::thread::sleep(poll);
}
None
}
/// Ask for the room list and wait for the server's reply.
///
/// Events already queued are discarded first: servers push a room list at
@@ -235,21 +256,15 @@ pub fn await_room_list(
let _ = client.take_room_events();
client.request_room_list().ok()?;
let deadline = Instant::now() + timeout;
while Instant::now() < deadline {
let listed =
client.take_room_events().into_iter().find_map(
|event| match event {
RoomEvent::List(rooms) => Some(rooms),
_ => None,
},
);
if listed.is_some() {
return listed;
}
std::thread::sleep(poll);
}
None
poll_until(timeout, poll, || {
client
.take_room_events()
.into_iter()
.find_map(|event| match event {
RoomEvent::List(rooms) => Some(rooms),
_ => None,
})
})
}
/// Wait until the server has answered something we asked for after `send`.
@@ -295,12 +310,10 @@ fn dispatch(ctx: &Ctx, command: Commands) -> CliResult {
}
Commands::User(args) => peer::user(ctx, &args),
Commands::Whoami => peer::whoami(ctx),
// These need no account, so main.rs answers them before asking for
// credentials; the arms exist only to keep the match exhaustive.
Commands::Portmap => portmap(&ctx.out, ctx.settings.listen_port),
// These need the config file as well as a session, so main.rs runs
// them where the store is in scope.
Commands::Serve(_)
// main.rs runs these itself: some need no account, the rest need the
// config store in scope. The arms only keep the match exhaustive.
Commands::Portmap
| Commands::Serve(_)
| Commands::Daemon(_)
| Commands::Wish(_)
| Commands::Config(_)
@@ -317,6 +330,19 @@ fn listen_span(duration: u64, follow: bool) -> Option<Duration> {
(!follow).then(|| Duration::from_secs(duration))
}
/// Where shells and coding agents keep their configuration: the XDG layout on
/// every platform, unlike this client's own config, which follows each
/// platform's convention (see `persist::paths`).
fn xdg(variable: &str, home: &Path, fallback: &str) -> PathBuf {
std::env::var_os(variable)
.filter(|dir| !dir.is_empty())
.map_or_else(|| home.join(fallback), PathBuf::from)
}
fn unwritable(path: &Path, error: &std::io::Error) -> CliError {
CliError::usage(format!("cannot write {}: {error}", path.display()))
}
/// Probe automatic port mapping. A router that does not answer is a negative
/// answer, not a crash, so it exits 4 the way `grep` exits 1 on no match.
pub fn portmap(out: &Out, port: u16) -> CliResult {
+7 -23
View File
@@ -47,14 +47,9 @@ fn own_privileges(client: &dyn crate::api::SessionApi) -> Option<u32> {
if client.check_privileges().is_err() {
return None;
}
let deadline = Instant::now() + PRIVILEGE_TIMEOUT;
while Instant::now() < deadline {
if let Some(seconds) = client.own_privilege_seconds() {
return Some(seconds);
}
std::thread::sleep(Duration::from_millis(50));
}
None
super::poll_until(PRIVILEGE_TIMEOUT, Duration::from_millis(50), || {
client.own_privilege_seconds()
})
}
pub fn user(ctx: &Ctx, args: &UserArgs) -> CliResult {
@@ -141,7 +136,7 @@ pub fn serve(
.warn("the share index is empty — peers will find nothing");
}
let span = (!args.follow).then(|| Duration::from_secs(args.duration));
let span = super::listen_span(args.duration, args.follow);
ctx.out.status(&match span {
Some(span) => format!("online for {}s…", span.as_secs()),
None => "online until interrupted…".to_string(),
@@ -155,20 +150,10 @@ pub fn serve(
// served well inside one poll, and "it had to wait" is precisely what a
// transfer log exists to report.
for queued in session.client.take_upload_events() {
let record = upload_record(&queued);
let key = (record.user.clone(), record.path.clone());
if reported.get(&key) != Some(&record) {
ctx.out.emit(&record);
reported.insert(key, record);
}
emit_changed(&mut reported, upload_record(&queued), &ctx.out);
}
for upload in session.client.uploads() {
let record = upload_record(&upload);
let key = (record.user.clone(), record.path.clone());
if reported.get(&key) != Some(&record) {
ctx.out.emit(&record);
reported.insert(key, record);
}
emit_changed(&mut reported, upload_record(&upload), &ctx.out);
}
if let Some(sweeper) = sweeper.as_mut() {
sweeper.tick(ctx, session.client.as_ref());
@@ -211,8 +196,7 @@ fn upload_record(upload: &UploadInfo) -> UploadRecord {
}
/// Emit `record` only when it differs from the last one reported for that
/// (user, file). Exposed for tests; the serve loop inlines the same rule.
#[cfg(test)]
/// (user, file).
fn emit_changed(
reported: &mut HashMap<(String, String), UploadRecord>,
record: UploadRecord,
+2 -14
View File
@@ -4,6 +4,7 @@
//! inside it and is written out on demand. Installing over an older copy is
//! what updating means, which is why there is no separate verb for it.
use super::{unwritable, xdg};
use crate::cli::{SkillsArgs, SkillsCommand};
use crate::output::{CliError, CliResult, Out, SkillRecord};
use directories::UserDirs;
@@ -122,7 +123,7 @@ fn targets(args: &SkillsArgs) -> CliResult<Vec<Target>> {
)
})?;
let found = detected(&home, &config_home(&home));
let found = detected(&home, &xdg("XDG_CONFIG_HOME", &home, ".config"));
if found.is_empty() {
return Err(CliError::no_results(
"no coding agent found in your home directory; name a skills \
@@ -149,15 +150,6 @@ fn detected(home: &Path, config_home: &Path) -> Vec<Target> {
.collect()
}
/// Where an agent keeps its own configuration, which is the XDG location on
/// every platform — unlike this client's config, which follows each platform's
/// own convention (see `persist::paths`).
fn config_home(home: &Path) -> PathBuf {
std::env::var_os("XDG_CONFIG_HOME")
.filter(|dir| !dir.is_empty())
.map_or_else(|| home.join(".config"), PathBuf::from)
}
fn current(file: &Path) -> bool {
std::fs::read_to_string(file).is_ok_and(|found| found == SKILL)
}
@@ -167,10 +159,6 @@ fn ours(file: &Path) -> bool {
.is_ok_and(|found| found.contains(FRONTMATTER_NAME))
}
fn unwritable(path: &Path, error: &std::io::Error) -> CliError {
CliError::usage(format!("cannot write {}: {error}", path.display()))
}
#[cfg(test)]
mod tests {
use super::*;
+13 -21
View File
@@ -39,17 +39,13 @@ pub fn fetch_listing(
CliError::connection(format!("cannot ask {user} for a listing: {e}"))
})?;
let deadline = Instant::now() + timeout;
while Instant::now() < deadline {
if let Some(directories) = client.take_browse_result(user) {
return Ok(directories);
}
std::thread::sleep(POLL);
}
Err(CliError::timeout(format!(
"{user} did not send a file list within {}s",
timeout.as_secs()
)))
super::poll_until(timeout, POLL, || client.take_browse_result(user))
.ok_or_else(|| {
CliError::timeout(format!(
"{user} did not send a file list within {}s",
timeout.as_secs()
))
})
}
pub fn browse(ctx: &Ctx, args: &BrowseArgs) -> CliResult {
@@ -143,22 +139,18 @@ pub fn room_users(ctx: &Ctx, room: &str, timeout: Duration) -> CliResult {
ctx.out
.status(&format!("waiting for {room}'s member list…"));
let deadline = Instant::now() + timeout;
let mut members = Vec::new();
while Instant::now() < deadline && members.is_empty() {
members = session.client.room_members(room);
if members.is_empty() {
std::thread::sleep(POLL);
}
}
let listed = super::poll_until(timeout, POLL, || {
let members = session.client.room_members(room);
(!members.is_empty()).then_some(members)
});
let _ = session.client.leave_room(room);
if members.is_empty() {
let Some(members) = listed else {
return Err(CliError::timeout(format!(
"{room} sent no member list within {}s",
timeout.as_secs()
)));
}
};
for user in members {
ctx.out.emit(&RoomMemberRecord {
room: room.to_string(),
+16 -24
View File
@@ -310,13 +310,9 @@ fn collect(
})
};
let deadline = Instant::now() + timeout;
while Instant::now() < deadline {
if client.get_search_results_count(query) > 0 {
break;
}
std::thread::sleep(Duration::from_millis(100));
}
super::poll_until(timeout, Duration::from_millis(100), || {
(client.get_search_results_count(query) > 0).then_some(())
});
cancel.store(true, Ordering::Relaxed);
let _ = worker.join();
Ok(client.get_search_results(query))
@@ -334,29 +330,26 @@ fn dedupe(
download_dir: &str,
out: &Out,
) -> Vec<Request> {
let mut paths: HashMap<String, String> = HashMap::new();
let mut targets: HashMap<PathBuf, String> = HashMap::new();
let mut targets: HashMap<PathBuf, Request> = HashMap::new();
let mut kept = Vec::new();
for request in requests {
if let Some(user) = paths.get(&request.path) {
out.warn(&format!(
"skipping {} from {}: same remote path already queued from \
{user}",
request.path, request.user
));
continue;
}
let target = local_path(download_dir, &request.path);
if let Some(other) = targets.get(&target) {
if let Some(queued) = targets.get(&target) {
let collision = if queued.path == request.path {
format!("same remote path already queued from {}", queued.user)
} else {
format!(
"would overwrite the file already queued from {}",
queued.user
)
};
out.warn(&format!(
"skipping {} from {}: would overwrite the file already queued \
from {other}",
"skipping {} from {}: {collision}",
request.path, request.user
));
continue;
}
paths.insert(request.path.clone(), request.user.clone());
targets.insert(target, request.user.clone());
targets.insert(target, request.clone());
kept.push(request);
}
kept
@@ -617,9 +610,8 @@ fn read_requests(
}
/// `Ok(None)` for a blank line, `Err` for something unusable.
// aislop-ignore-next-line complexity/function-too-long -- scanner bug, not a long function: the `'{'` char literal below reads as an opening brace, so aislop's counter never closes and measures to EOF. It is 34 lines.
// aislop-ignore-next-line complexity/function-too-long -- scanner bug, not a long function: the `'{'` char literal below reads as an opening brace, so aislop's counter never closes and measures to EOF. It is 33 lines.
fn parse_request(line: &str) -> Result<Option<Request>, String> {
let line = line.trim_end_matches(['\r', '\n']);
if line.trim().is_empty() {
return Ok(None);
}
+1 -10
View File
@@ -68,22 +68,13 @@ pub fn run(ctx: &Ctx, store: &Store, args: &WishRunArgs) -> CliResult {
/// How to narrow and order what a sweep prints — the parts of `wish run`'s flags
/// that outlive the command, so `serve --wishlist` can sweep without inventing
/// a set of command-line arguments it never received.
#[derive(Default)]
struct Sweep {
filter: transfer::Filter,
sort: SortKey,
limit: usize,
}
impl Default for Sweep {
fn default() -> Self {
Self {
filter: transfer::Filter::default(),
sort: SortKey::Best,
limit: 0,
}
}
}
impl From<&WishRunArgs> for Sweep {
fn from(args: &WishRunArgs) -> Self {
Self {