From 68c0a84440be46cc2d7f7985624ea515ef480bf5 Mon Sep 17 00:00:00 2001 From: cwj3rcb Date: Mon, 16 Feb 2026 14:48:11 -0600 Subject: [PATCH] incomplete downloads, but backup --- src/core/downloads.rs | 62 +++++++++++++++++++++++++++++-------------- src/main.rs | 46 +++++++++++++++++--------------- 2 files changed, 66 insertions(+), 42 deletions(-) diff --git a/src/core/downloads.rs b/src/core/downloads.rs index 183fbe6..c14b87b 100644 --- a/src/core/downloads.rs +++ b/src/core/downloads.rs @@ -104,6 +104,7 @@ pub fn cleanup_temp_dir() -> Result<(), String> { fs::remove_dir_all(&tmp).map_err(|e| format!("remove temp: {e}"))?; } fs::create_dir_all(&tmp).map_err(|e| format!("create temp: {e}"))?; + fs::create_dir_all(tmp.join("jobs")).map_err(|e| format!("create temp/jobs: {e}"))?; Ok(()) } @@ -307,12 +308,7 @@ fn make_download_item_other(item: &JfItem) -> Result { } fn tmp_dir_of_item(it: &DownloadItem) -> Result { - let tmp_path = temp_dir_for(it)?; - let tmp_dir = tmp_path - .parent() - .ok_or_else(|| "bad temp dir".to_string())? - .to_path_buf(); - Ok(tmp_dir) + temp_dir_for(it) } // Find the actual downloaded media file in tmp dir. @@ -560,13 +556,16 @@ fn worker_loop(cfg: Config, state: Arc>, rx: mpsc::Receiver let _ = cleanup_temp_dir(); } + +// Each download gets its own isolated temp folder so deletes can't affect others. +// temp/jobs// fn temp_dir_for(it: &DownloadItem) -> Result { let tmp = temp_root()?; - Ok(tmp.join(&it.final_rel_path)) + Ok(tmp.join("jobs").join(&it.item_id)) } fn delete_temp_for(it: &DownloadItem) -> Result<(), String> { - let dir = tmp_dir_of_item(it)?; + let dir = temp_dir_for(it)?; if dir.exists() { fs::remove_dir_all(&dir).map_err(|e| format!("rm temp: {e}"))?; } @@ -584,11 +583,7 @@ fn run_one_download( // We download into a dedicated temp folder for this item. // Keep your existing folder structure by deriving tmp from final_rel_path. - let tmp_path = temp_dir_for(it)?; - let tmp_dir = tmp_path - .parent() - .ok_or_else(|| "bad temp dir".to_string())? - .to_path_buf(); + let tmp_dir = temp_dir_for(it)?; // Ensure temp dir exists and is empty-ish for robustness if tmp_dir.exists() { @@ -633,28 +628,55 @@ fn run_one_download( Ok(child) } - fn update_progress_for_front(state: &Arc>) { - let (tmp_file, idx) = { + let (tmp_dir, idx) = { let st = match state.lock() { Ok(s) => s, Err(_) => return, }; - if st.queue.is_empty() { return; } + if st.queue.is_empty() { + return; + } let it = st.queue.front().unwrap(); - let tmp_dir = match temp_dir_for(it) { Ok(p) => p.parent().unwrap().to_path_buf(), Err(_) => return }; - (tmp_dir.join(format!("{}.part", it.item_id)), 0usize) + let tmp_dir = match temp_dir_for(it) { + Ok(p) => p, + Err(_) => return, + }; + (tmp_dir, 0usize) }; - if let Ok(meta) = fs::metadata(&tmp_file) { + // Find the largest non-.aria2 file in temp dir (works for partial and final) + let mut best: Option = None; + + if let Ok(rd) = fs::read_dir(&tmp_dir) { + for ent in rd.flatten() { + let p = ent.path(); + if !p.is_file() { + continue; + } + let name = p.file_name().and_then(|s| s.to_str()).unwrap_or(""); + if name.ends_with(".aria2") { + continue; + } + if let Ok(m) = fs::metadata(&p) { + let sz = m.len(); + best = Some(best.map(|b| b.max(sz)).unwrap_or(sz)); + } + } + } + + if let Some(sz) = best { if let Ok(mut st) = state.lock() { if let Some(front) = st.queue.get_mut(idx) { - front.bytes_downloaded = meta.len(); + front.bytes_downloaded = sz; } } } } + + + fn finalize_front(state: &Arc>) -> Result<(), String> { let (it, tmp_dir, final_abs_watch) = { let st = state.lock().map_err(|_| "lock poisoned".to_string())?; diff --git a/src/main.rs b/src/main.rs index 3e7ea69..7129e58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -779,32 +779,34 @@ fn handle_key(app: &mut App, code: KeyCode) { let mut handled_by_downloads = false; if app.downloads_focus { - match code { - KeyCode::Up | KeyCode::Char('k') => { - if app.downloads_selected > 0 { - app.downloads_selected -= 1; - } - handled_by_downloads = true; - } - KeyCode::Down | KeyCode::Char('j') => { - let len = app - .downloads - .as_ref() - .and_then(|dm| dm.state.lock().ok()) - .map(|st| st.queue.len()) - .unwrap_or(0); + if let Some(dm) = &app.downloads { + let idx = app.downloads_selected; - if len > 0 && app.downloads_selected + 1 < len { - app.downloads_selected += 1; - } - handled_by_downloads = true; + if idx == 0 { + dm.send(downloads::DownloadCommand::Cancel { index: 0 }); + app.status = "download: cancel active".into(); + } else { + dm.send(downloads::DownloadCommand::RemoveQueued { index: idx }); + app.status = "download: removed".into(); } - KeyCode::Esc => { - app.downloads_focus = false; - handled_by_downloads = true; + + // Clamp selection immediately to avoid focus navigation glitches + let len = dm + .state + .lock() + .ok() + .map(|st| st.queue.len()) + .unwrap_or(0); + + if len == 0 { + app.downloads_selected = 0; + } else if app.downloads_selected >= len { + app.downloads_selected = len - 1; } - _ => {} + } else { + app.status = "downloads not available".into(); } + return; } // If downloads handled the key, skip normal navigation handling