1use std::fs;
15use std::path::{Path, PathBuf};
16
17use serde_json::Value;
18use xil_core::pyjson::{dumps, Style};
19use xil_core::workspace::derive_paths;
20
21use crate::configs::check_workspace_path;
22use crate::{activity, episodes};
23
24pub fn parsed_path(ep: &str) -> Result<PathBuf, String> {
27 let (slug, tag) = episodes::parse_choice(ep);
28 if slug.is_empty() {
29 return Err("Select an episode first.".into());
30 }
31 if tag.is_empty() {
32 return Err("Select an episode, not a show.".into());
33 }
34 let path = derive_paths(&slug, &tag)["parsed"].clone();
35 check_workspace_path(&path)?;
36 Ok(path)
37}
38
39pub fn load(ep: &str) -> String {
42 let path = match parsed_path(ep) {
43 Ok(p) => p,
44 Err(e) => return format!("// {e}"),
45 };
46 activity::log(&format!("SELECT parsed → {}", path.display()));
47 if !path.exists() {
48 return format!("// File not found: {}", path.display());
49 }
50 fs::read_to_string(&path).unwrap_or_else(|e| format!("// {e}"))
51}
52
53fn backup_path(path: &Path) -> PathBuf {
56 let mut name = path.file_name().unwrap_or_default().to_os_string();
57 name.push(".bak");
58 path.with_file_name(name)
59}
60
61pub fn save(ep: &str, text: &str) -> String {
63 let path = match parsed_path(ep) {
64 Ok(p) => p,
65 Err(e) => return e,
66 };
67 let data: Value = match serde_json::from_str(text) {
68 Ok(d) => d,
69 Err(e) => return format!("Invalid JSON — not saved: {e}"),
70 };
71 if path.exists() {
73 if let Err(e) = fs::copy(&path, backup_path(&path)) {
74 return format!("Backup failed — not saved: {e}");
75 }
76 }
77 if let Some(dir) = path.parent().filter(|d| !d.as_os_str().is_empty()) {
78 if let Err(e) = fs::create_dir_all(dir) {
79 return e.to_string();
80 }
81 }
82 if let Err(e) = fs::write(&path, dumps(&data, Style::INDENT2_UTF8)) {
83 return e.to_string();
84 }
85 activity::log(&format!("SAVE parsed → {}", path.display()));
86 format!("Saved {}", path.display())
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn backup_sits_beside_the_file() {
95 assert_eq!(
96 backup_path(Path::new("/ws/parsed/the413/parsed_S01E01.json")),
97 PathBuf::from("/ws/parsed/the413/parsed_S01E01.json.bak")
98 );
99 }
100
101 #[test]
102 fn a_show_stub_and_an_unsafe_slug_have_no_path() {
103 assert_eq!(
104 parsed_path("mypodcast [show] — My Podcast"),
105 Err("Select an episode, not a show.".into())
106 );
107 assert_eq!(
108 parsed_path("../etc S01E01"),
109 Err("Select an episode first.".into())
110 );
111 assert_eq!(parsed_path(""), Err("Select an episode first.".into()));
112 }
113}