From f525984a7b75d52a53637061b5c421720b62e5c8 Mon Sep 17 00:00:00 2001 From: Michel de Graaf Date: Wed, 29 Jul 2026 17:02:27 +0200 Subject: [PATCH] fix(lib): keep a missing download directory as the target, not its parent resolve_download_path treated "not a directory" as "names a file" and fell back to the parent, so a directory that simply did not exist yet redirected the transfer one level up. The CLI and TUI create the folder before downloading and never hit it; a daemon does not, so its transfers landed in ~/Downloads while daemon.status - and the paths download and get report - named ~/Downloads/Soulseek. Green on any machine where the folder already exists, which is how it survived to CI. Only an existing file diverts to the parent now. PartFile::open already creates the directory it streams into. --- soulseek-rs-lib/src/peer/download_peer.rs | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/soulseek-rs-lib/src/peer/download_peer.rs b/soulseek-rs-lib/src/peer/download_peer.rs index 0b4821e..6828743 100644 --- a/soulseek-rs-lib/src/peer/download_peer.rs +++ b/soulseek-rs-lib/src/peer/download_peer.rs @@ -75,10 +75,15 @@ fn extract_filename_from_path(full_path: &str) -> &str { /// Where a download's bytes end up: its configured directory (falling back /// to the parent if it names a file) joined with the remote basename. +/// +/// A directory that does not exist yet stays the target — `PartFile::open` +/// creates it. Treating it as "not a directory" and dodging to the parent +/// made a daemon whose folder was never created download into `~/Downloads` +/// while reporting `~/Downloads/Soulseek`. fn resolve_download_path(download: &Download) -> Result { let mut expanded_path = expand_tilde(&download.download_directory); - if !expanded_path.is_dir() { + if expanded_path.is_file() { expanded_path = expanded_path .parent() .ok_or_else(|| { @@ -527,6 +532,23 @@ mod tests { } } + #[test] + fn a_directory_that_does_not_exist_yet_is_created_not_dodged() { + let dir = scratch_dir("fresh").join("Soulseek"); + + let mut part = PartFile::open(&download_into(&dir, 4)).unwrap(); + part.write(b"data").unwrap(); + let final_path = part.finish().unwrap(); + + assert_eq!( + std::path::Path::new(&final_path).parent(), + Some(dir.as_path()), + "the bytes belong in the configured directory, not its parent" + ); + assert_eq!(std::fs::read(&final_path).unwrap(), b"data"); + let _ = std::fs::remove_dir_all(dir.parent().unwrap()); + } + #[test] fn a_partial_file_sets_the_resume_offset() { let dir = scratch_dir("resume");