Skip to main content

tiny_dlna/
config.rs

1//! Files under `~/.config/tiny-dlna/`: the render UUID and the list of
2//! renders currently running on this machine (shared by all processes so a
3//! single SSDP listener can answer for every render).
4
5use 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
38/// Write via a temp file + rename so concurrent readers never see a partial file.
39fn 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
55/// The render's UUID: a per-machine random base plus the HTTP port, so that
56/// several renders on one machine get distinct but stable IDs.
57pub 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}