1use std::fs;
6use std::io;
7use std::path::{Path, PathBuf};
8
9use log::{debug, warn};
10use serde::{Deserialize, Serialize};
11
12const RENDER_CONFIG: &str = "tiny-render.json";
13const LIVE_RENDERS: &str = "live-renders.json";
14
15pub fn config_dir() -> PathBuf {
16 let home = std::env::home_dir().unwrap_or_else(|| PathBuf::from("."));
17 let dir = home.join(".config").join("tiny-dlna");
18 if let Err(e) = fs::create_dir_all(&dir) {
19 warn!("failed to create {}: {e}", dir.display());
20 }
21 dir
22}
23
24fn read_json<T: for<'de> Deserialize<'de> + Default>(path: &Path) -> T {
25 match fs::read_to_string(path) {
26 Ok(text) => serde_json::from_str(&text).unwrap_or_else(|e| {
27 warn!("ignoring malformed {}: {e}", path.display());
28 T::default()
29 }),
30 Err(e) if e.kind() == io::ErrorKind::NotFound => T::default(),
31 Err(e) => {
32 warn!("failed to read {}: {e}", path.display());
33 T::default()
34 }
35 }
36}
37
38fn write_json<T: Serialize>(path: &Path, value: &T) -> io::Result<()> {
40 let mut buf = Vec::new();
41 let fmt = serde_json::ser::PrettyFormatter::with_indent(b" ");
42 let mut ser = serde_json::Serializer::with_formatter(&mut buf, fmt);
43 value.serialize(&mut ser)?;
44 let tmp = path.with_extension(format!("json.{}.tmp", std::process::id()));
45 fs::write(&tmp, &buf)?;
46 fs::rename(&tmp, path)
47}
48
49#[derive(Serialize, Deserialize, Default)]
50struct RenderConfig {
51 #[serde(rename = "UUID", default)]
52 uuid: String,
53}
54
55pub fn render_uuid(port: u16) -> String {
58 let path = config_dir().join(RENDER_CONFIG);
59 let mut config: RenderConfig = read_json(&path);
60 if config.uuid.is_empty() {
61 let full = uuid::Uuid::new_v4().to_string();
62 config.uuid = full
63 .rsplit_once('-')
64 .map_or(full.as_str(), |(base, _)| base)
65 .to_string();
66 if let Err(e) = write_json(&path, &config) {
67 warn!("failed to save {}: {e}", path.display());
68 }
69 }
70 format!("{}-{port}", config.uuid)
71}
72
73#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
74pub struct LiveRender {
75 pub name: String,
76 pub port: u16,
77 pub uuid: String,
78}
79
80#[derive(Serialize, Deserialize, Default)]
81struct LiveRenders {
82 #[serde(default)]
83 renders: Vec<LiveRender>,
84}
85
86fn live_renders_path() -> PathBuf {
87 config_dir().join(LIVE_RENDERS)
88}
89
90pub fn register_render(uuid: &str, name: &str, port: u16) {
91 let path = live_renders_path();
92 let mut data: LiveRenders = read_json(&path);
93 if data.renders.iter().any(|r| r.uuid == uuid) {
94 return;
95 }
96 data.renders.push(LiveRender {
97 name: name.into(),
98 port,
99 uuid: uuid.into(),
100 });
101 match write_json(&path, &data) {
102 Ok(()) => debug!("registered render {uuid}"),
103 Err(e) => warn!("failed to save {}: {e}", path.display()),
104 }
105}
106
107pub fn unregister_render(uuid: &str) {
108 let path = live_renders_path();
109 let mut data: LiveRenders = read_json(&path);
110 let before = data.renders.len();
111 data.renders.retain(|r| r.uuid != uuid);
112 if data.renders.len() == before {
113 return;
114 }
115 match write_json(&path, &data) {
116 Ok(()) => debug!("unregistered render {uuid}"),
117 Err(e) => warn!("failed to save {}: {e}", path.display()),
118 }
119}
120
121pub fn live_render_ports() -> Vec<u16> {
122 let data: LiveRenders = read_json(&live_renders_path());
123 data.renders.into_iter().map(|r| r.port).collect()
124}