From 1bf8b675e4d0ff0aeade12281001d16ee282cd40 Mon Sep 17 00:00:00 2001 From: NimBold Date: Fri, 31 Jul 2026 21:34:32 +0330 Subject: [PATCH] feat(torrents): add functions to remove orphaned probe directories and cached torrents --- src-tauri/src/torrent.rs | 64 +++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/src-tauri/src/torrent.rs b/src-tauri/src/torrent.rs index b240c07..b27b04d 100644 --- a/src-tauri/src/torrent.rs +++ b/src-tauri/src/torrent.rs @@ -1,6 +1,6 @@ use sha1::{Digest, Sha1}; use std::collections::{BTreeMap, HashSet}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use tauri::Manager; @@ -476,6 +476,10 @@ pub fn remove_orphaned_probe_dirs( app_handle: &tauri::AppHandle, ) -> Result { let root = managed_torrent_storage_root(app_handle)?; + remove_orphaned_probe_dirs_at(&root) +} + +fn remove_orphaned_probe_dirs_at(root: &Path) -> Result { let entries = match std::fs::read_dir(&root) { Ok(entries) => entries, Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(0), @@ -495,9 +499,11 @@ pub fn remove_orphaned_probe_dirs( { continue; } - std::fs::remove_dir_all(entry.path()) - .map_err(|error| format!("could not remove orphaned torrent probe: {error}"))?; - removed += 1; + match std::fs::remove_dir_all(entry.path()) { + Ok(()) => removed += 1, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => return Err(format!("could not remove orphaned torrent probe: {error}")), + } } Ok(removed) } @@ -507,6 +513,13 @@ pub fn remove_orphaned_cached_torrents( retained_ids: &HashSet, ) -> Result { let root = managed_torrent_storage_root(app_handle)?; + remove_orphaned_cached_torrents_at(&root, retained_ids) +} + +fn remove_orphaned_cached_torrents_at( + root: &Path, + retained_ids: &HashSet, +) -> Result { let entries = match std::fs::read_dir(&root) { Ok(entries) => entries, Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(0), @@ -528,9 +541,13 @@ pub fn remove_orphaned_cached_torrents( if retained_ids.contains(id) { continue; } - std::fs::remove_file(path) - .map_err(|error| format!("could not remove orphaned torrent metadata: {error}"))?; - removed += 1; + match std::fs::remove_file(path) { + Ok(()) => removed += 1, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => { + return Err(format!("could not remove orphaned torrent metadata: {error}")); + } + } } Ok(removed) } @@ -731,4 +748,37 @@ mod tests { assert!(parse_torrent_bytes(b"d4:infod6:lengthi5e4:name04:testee").is_err()); assert!(parse_torrent_bytes(b"d4:infod5:filesi1e6:lengthi5e4:name4:testee").is_err()); } + + #[test] + fn removes_only_orphaned_probe_directories() { + let temporary = tempfile::tempdir().expect("temporary torrent storage should exist"); + let root = temporary.path(); + std::fs::create_dir(root.join(".probe-stale")).expect("probe directory should exist"); + std::fs::write(root.join(".probe-file"), b"not a directory") + .expect("probe marker file should exist"); + std::fs::create_dir(root.join("retained-dir")).expect("unrelated directory should exist"); + + assert_eq!(remove_orphaned_probe_dirs_at(root).unwrap(), 1); + assert!(!root.join(".probe-stale").exists()); + assert!(root.join(".probe-file").is_file()); + assert!(root.join("retained-dir").is_dir()); + } + + #[test] + fn removes_unretained_torrent_files_but_preserves_retained_and_unrelated_entries() { + let temporary = tempfile::tempdir().expect("temporary torrent storage should exist"); + let root = temporary.path(); + std::fs::write(root.join("keep-id.torrent"), b"retained") + .expect("retained metadata should exist"); + std::fs::write(root.join("orphan-id.torrent"), b"orphan") + .expect("orphan metadata should exist"); + std::fs::write(root.join("notes.txt"), b"unrelated") + .expect("unrelated file should exist"); + let retained = HashSet::from(["keep-id".to_string()]); + + assert_eq!(remove_orphaned_cached_torrents_at(root, &retained).unwrap(), 1); + assert!(root.join("keep-id.torrent").is_file()); + assert!(!root.join("orphan-id.torrent").exists()); + assert!(root.join("notes.txt").is_file()); + } }