offline mode almost there

This commit is contained in:
2026-02-16 18:38:23 -06:00
parent 81d75446fd
commit d805e666e9
3 changed files with 255 additions and 26 deletions
+92
View File
@@ -71,6 +71,98 @@ pub struct DownloadManager {
pub state: Arc<Mutex<DownloadState>>,
}
#[derive(Clone, Debug)]
pub struct LocalEntry {
pub item_id: String,
pub title: String,
pub path: PathBuf,
}
/// Scan downloads root for marker files and return local playable entries.
/// Title is taken from marker JSON if present, else from the media filename stem.
pub fn scan_local_entries() -> Vec<LocalEntry> {
fn walk(dir: &Path, out: &mut Vec<LocalEntry>) {
let rd = match std::fs::read_dir(dir) {
Ok(r) => r,
Err(_) => return,
};
for ent in rd.flatten() {
let p = ent.path();
if p.is_dir() {
walk(&p, out);
continue;
}
let name = match p.file_name().and_then(|s| s.to_str()) {
Some(s) => s,
None => continue,
};
if !name.ends_with(DOWNLOAD_MARKER_EXT) {
continue;
}
let raw = match std::fs::read_to_string(&p) {
Ok(s) => s,
Err(_) => continue,
};
let v: serde_json::Value = match serde_json::from_str(raw.trim()) {
Ok(v) => v,
Err(_) => continue,
};
let item_id = match v.get("item_id").and_then(|x| x.as_str()) {
Some(s) if !s.is_empty() => s.to_string(),
_ => continue,
};
// derive media path: "<media>.<DOWNLOAD_MARKER_EXT>" -> "<media>"
let suffix = format!(".{DOWNLOAD_MARKER_EXT}");
if !name.ends_with(&suffix) {
continue;
}
let media_name = &name[..name.len() - suffix.len()];
let media_path = p.with_file_name(media_name);
if !media_path.exists() {
continue;
}
// title: prefer json field if you ever add it, else from filename
let title = v
.get("title")
.and_then(|x| x.as_str())
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| {
media_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or(media_name)
.to_string()
});
out.push(LocalEntry {
item_id,
title,
path: media_path,
});
}
}
let mut out = Vec::new();
if let Ok(root) = downloads_root() {
walk(&root, &mut out);
}
// Stable sort: title
out.sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase()));
out
}
impl DownloadManager {
pub fn start(cfg: Config) -> Self {
let state = Arc::new(Mutex::new(DownloadState::default()));