Per-folder "Check all" action + DB backup download
Two small follow-ups to the folder hierarchy that round out the folder-as-a-profile workflow and give the user a one-click way to keep an offsite copy of their library state. Per-folder check - New `POST /api/folders/:id/check` mirrors the existing /api/scheduler/run endpoint but scoped to one folder's members. Each channel's stored DownloadOptions (quality / rate limit / match-filter / extra args) applies just like a scheduled re-check. Returns 409 when downloads are already running. - Web folder-manager modal: each non-empty folder gets a "⬇ Check" button next to Rename / Delete. Status line reports the count of channels queued. - Desktop folder-manager window: same — a small ⬇ icon-button per row, visible only for folders with at least one member. Same options- honouring re-check path as the right-click "Check for new videos" action. - This effectively gives us "Profiles" lite: organize channels into a folder, then trigger a batch re-check whenever you want. DB backup - New `GET /api/backup/db` reads `<channels_root>/yt-offline.db` and streams it back with `Content-Disposition: attachment` so the browser downloads instead of rendering. Filename includes the unix timestamp so downloads don't clobber. Returns 404 when running in in-memory mode (no DB file to dump). - We deliberately don't bundle config.toml + cookies.txt here: config is short to recreate and cookies.txt carries live session credentials that shouldn't fly over the wire unprompted. - Web settings modal gains a "⬇ Download library snapshot" button in a new Backup section, with a hint explaining what's covered (watched / favourites / bookmarks / waiting / channel-options / folders). 55 unit tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
4b0e4b3b07
commit
9d414919cd
3 changed files with 107 additions and 1 deletions
25
src/app.rs
25
src/app.rs
|
|
@ -1762,6 +1762,7 @@ impl App {
|
|||
let mut create_clicked = false;
|
||||
let mut to_rename: Option<(i64, String)> = None;
|
||||
let mut to_delete: Option<(i64, String, usize)> = None;
|
||||
let mut to_check: Option<i64> = None;
|
||||
egui::Window::new("📁 Manage folders")
|
||||
.open(&mut open)
|
||||
.collapsible(false)
|
||||
|
|
@ -1782,9 +1783,14 @@ impl App {
|
|||
if ui.small_button("🗑").on_hover_text("Delete folder").clicked() {
|
||||
to_delete = Some((f.id, f.name.clone(), member_count));
|
||||
}
|
||||
if ui.small_button("✏").on_hover_text("Rename folder").clicked() {
|
||||
if ui.small_button("✏").on_hover_text("Rename (type new name in field below first)").clicked() {
|
||||
to_rename = Some((f.id, f.name.clone()));
|
||||
}
|
||||
if member_count > 0
|
||||
&& ui.small_button("⬇").on_hover_text("Check every channel in this folder for new videos").clicked()
|
||||
{
|
||||
to_check = Some(f.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.separator();
|
||||
|
|
@ -1830,6 +1836,23 @@ impl App {
|
|||
self.status = format!("Type new name into the field below, then click ✏ on '{old_name}' again");
|
||||
}
|
||||
}
|
||||
if let Some(folder_id) = to_check {
|
||||
// Check every member channel for new videos, applying that
|
||||
// channel's own DownloadOptions / quality override just like
|
||||
// a scheduled re-check would.
|
||||
let scheduled: Vec<(String, crate::download_options::DownloadOptions)> =
|
||||
self.library.iter()
|
||||
.filter(|ch| ch.folder_id == Some(folder_id))
|
||||
.map(|ch| (crate::downloader::recheck_url(ch), ch.download_options.clone()))
|
||||
.collect();
|
||||
let count = scheduled.len();
|
||||
for (url, opts) in scheduled {
|
||||
let info = classify_url(&url);
|
||||
let quality = opts.quality.unwrap_or(DownloadQuality::Best);
|
||||
self.downloader.start(url, &info, true, quality, false, Some(&opts));
|
||||
}
|
||||
self.status = format!("Folder check: started {count} channel download{}", if count == 1 { "" } else { "s" });
|
||||
}
|
||||
if let Some((id, name, count)) = to_delete {
|
||||
// Hard delete — member channels revert to Unfiled.
|
||||
if let Err(e) = self.db.delete_folder(id) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue