incomplete downloads, but backup
This commit is contained in:
@@ -104,6 +104,7 @@ pub fn cleanup_temp_dir() -> Result<(), String> {
|
|||||||
fs::remove_dir_all(&tmp).map_err(|e| format!("remove temp: {e}"))?;
|
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).map_err(|e| format!("create temp: {e}"))?;
|
||||||
|
fs::create_dir_all(tmp.join("jobs")).map_err(|e| format!("create temp/jobs: {e}"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,12 +308,7 @@ fn make_download_item_other(item: &JfItem) -> Result<DownloadItem, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn tmp_dir_of_item(it: &DownloadItem) -> Result<PathBuf, String> {
|
fn tmp_dir_of_item(it: &DownloadItem) -> Result<PathBuf, String> {
|
||||||
let tmp_path = temp_dir_for(it)?;
|
temp_dir_for(it)
|
||||||
let tmp_dir = tmp_path
|
|
||||||
.parent()
|
|
||||||
.ok_or_else(|| "bad temp dir".to_string())?
|
|
||||||
.to_path_buf();
|
|
||||||
Ok(tmp_dir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the actual downloaded media file in tmp dir.
|
// Find the actual downloaded media file in tmp dir.
|
||||||
@@ -560,13 +556,16 @@ fn worker_loop(cfg: Config, state: Arc<Mutex<DownloadState>>, rx: mpsc::Receiver
|
|||||||
let _ = cleanup_temp_dir();
|
let _ = cleanup_temp_dir();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Each download gets its own isolated temp folder so deletes can't affect others.
|
||||||
|
// temp/jobs/<item_id>/
|
||||||
fn temp_dir_for(it: &DownloadItem) -> Result<PathBuf, String> {
|
fn temp_dir_for(it: &DownloadItem) -> Result<PathBuf, String> {
|
||||||
let tmp = temp_root()?;
|
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> {
|
fn delete_temp_for(it: &DownloadItem) -> Result<(), String> {
|
||||||
let dir = tmp_dir_of_item(it)?;
|
let dir = temp_dir_for(it)?;
|
||||||
if dir.exists() {
|
if dir.exists() {
|
||||||
fs::remove_dir_all(&dir).map_err(|e| format!("rm temp: {e}"))?;
|
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.
|
// We download into a dedicated temp folder for this item.
|
||||||
// Keep your existing folder structure by deriving tmp from final_rel_path.
|
// Keep your existing folder structure by deriving tmp from final_rel_path.
|
||||||
let tmp_path = temp_dir_for(it)?;
|
let tmp_dir = temp_dir_for(it)?;
|
||||||
let tmp_dir = tmp_path
|
|
||||||
.parent()
|
|
||||||
.ok_or_else(|| "bad temp dir".to_string())?
|
|
||||||
.to_path_buf();
|
|
||||||
|
|
||||||
// Ensure temp dir exists and is empty-ish for robustness
|
// Ensure temp dir exists and is empty-ish for robustness
|
||||||
if tmp_dir.exists() {
|
if tmp_dir.exists() {
|
||||||
@@ -633,28 +628,55 @@ fn run_one_download(
|
|||||||
Ok(child)
|
Ok(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn update_progress_for_front(state: &Arc<Mutex<DownloadState>>) {
|
fn update_progress_for_front(state: &Arc<Mutex<DownloadState>>) {
|
||||||
let (tmp_file, idx) = {
|
let (tmp_dir, idx) = {
|
||||||
let st = match state.lock() {
|
let st = match state.lock() {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
if st.queue.is_empty() { return; }
|
if st.queue.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let it = st.queue.front().unwrap();
|
let it = st.queue.front().unwrap();
|
||||||
let tmp_dir = match temp_dir_for(it) { Ok(p) => p.parent().unwrap().to_path_buf(), Err(_) => return };
|
let tmp_dir = match temp_dir_for(it) {
|
||||||
(tmp_dir.join(format!("{}.part", it.item_id)), 0usize)
|
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<u64> = 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 Ok(mut st) = state.lock() {
|
||||||
if let Some(front) = st.queue.get_mut(idx) {
|
if let Some(front) = st.queue.get_mut(idx) {
|
||||||
front.bytes_downloaded = meta.len();
|
front.bytes_downloaded = sz;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn finalize_front(state: &Arc<Mutex<DownloadState>>) -> Result<(), String> {
|
fn finalize_front(state: &Arc<Mutex<DownloadState>>) -> Result<(), String> {
|
||||||
let (it, tmp_dir, final_abs_watch) = {
|
let (it, tmp_dir, final_abs_watch) = {
|
||||||
let st = state.lock().map_err(|_| "lock poisoned".to_string())?;
|
let st = state.lock().map_err(|_| "lock poisoned".to_string())?;
|
||||||
|
|||||||
42
src/main.rs
42
src/main.rs
@@ -779,32 +779,34 @@ fn handle_key(app: &mut App, code: KeyCode) {
|
|||||||
let mut handled_by_downloads = false;
|
let mut handled_by_downloads = false;
|
||||||
|
|
||||||
if app.downloads_focus {
|
if app.downloads_focus {
|
||||||
match code {
|
if let Some(dm) = &app.downloads {
|
||||||
KeyCode::Up | KeyCode::Char('k') => {
|
let idx = app.downloads_selected;
|
||||||
if app.downloads_selected > 0 {
|
|
||||||
app.downloads_selected -= 1;
|
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();
|
||||||
}
|
}
|
||||||
handled_by_downloads = true;
|
|
||||||
}
|
// Clamp selection immediately to avoid focus navigation glitches
|
||||||
KeyCode::Down | KeyCode::Char('j') => {
|
let len = dm
|
||||||
let len = app
|
.state
|
||||||
.downloads
|
.lock()
|
||||||
.as_ref()
|
.ok()
|
||||||
.and_then(|dm| dm.state.lock().ok())
|
|
||||||
.map(|st| st.queue.len())
|
.map(|st| st.queue.len())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
if len > 0 && app.downloads_selected + 1 < len {
|
if len == 0 {
|
||||||
app.downloads_selected += 1;
|
app.downloads_selected = 0;
|
||||||
|
} else if app.downloads_selected >= len {
|
||||||
|
app.downloads_selected = len - 1;
|
||||||
}
|
}
|
||||||
handled_by_downloads = true;
|
} else {
|
||||||
}
|
app.status = "downloads not available".into();
|
||||||
KeyCode::Esc => {
|
|
||||||
app.downloads_focus = false;
|
|
||||||
handled_by_downloads = true;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If downloads handled the key, skip normal navigation handling
|
// If downloads handled the key, skip normal navigation handling
|
||||||
|
|||||||
Reference in New Issue
Block a user