use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use log::{debug, warn};
use serde::{Deserialize, Serialize};
const RENDER_CONFIG: &str = "tiny-render.json";
const LIVE_RENDERS: &str = "live-renders.json";
pub fn config_dir() -> PathBuf {
let home = std::env::home_dir().unwrap_or_else(|| PathBuf::from("."));
let dir = home.join(".config").join("tiny-dlna");
if let Err(e) = fs::create_dir_all(&dir) {
warn!("failed to create {}: {e}", dir.display());
}
dir
}
fn read_json<T: for<'de> Deserialize<'de> + Default>(path: &Path) -> T {
match fs::read_to_string(path) {
Ok(text) => serde_json::from_str(&text).unwrap_or_else(|e| {
warn!("ignoring malformed {}: {e}", path.display());
T::default()
}),
Err(e) if e.kind() == io::ErrorKind::NotFound => T::default(),
Err(e) => {
warn!("failed to read {}: {e}", path.display());
T::default()
}
}
}
fn write_json<T: Serialize>(path: &Path, value: &T) -> io::Result<()> {
let mut buf = Vec::new();
let fmt = serde_json::ser::PrettyFormatter::with_indent(b" ");
let mut ser = serde_json::Serializer::with_formatter(&mut buf, fmt);
value.serialize(&mut ser)?;
let tmp = path.with_extension(format!("json.{}.tmp", std::process::id()));
fs::write(&tmp, &buf)?;
fs::rename(&tmp, path)
}
#[derive(Serialize, Deserialize, Default)]
struct RenderConfig {
#[serde(rename = "UUID", default)]
uuid: String,
}
pub fn render_uuid(port: u16) -> String {
let path = config_dir().join(RENDER_CONFIG);
let mut config: RenderConfig = read_json(&path);
if config.uuid.is_empty() {
let full = uuid::Uuid::new_v4().to_string();
config.uuid = full
.rsplit_once('-')
.map_or(full.as_str(), |(base, _)| base)
.to_string();
if let Err(e) = write_json(&path, &config) {
warn!("failed to save {}: {e}", path.display());
}
}
format!("{}-{port}", config.uuid)
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct LiveRender {
pub name: String,
pub port: u16,
pub uuid: String,
}
#[derive(Serialize, Deserialize, Default)]
struct LiveRenders {
#[serde(default)]
renders: Vec<LiveRender>,
}
fn live_renders_path() -> PathBuf {
config_dir().join(LIVE_RENDERS)
}
pub fn register_render(uuid: &str, name: &str, port: u16) {
let path = live_renders_path();
let mut data: LiveRenders = read_json(&path);
if data.renders.iter().any(|r| r.uuid == uuid) {
return;
}
data.renders.push(LiveRender {
name: name.into(),
port,
uuid: uuid.into(),
});
match write_json(&path, &data) {
Ok(()) => debug!("registered render {uuid}"),
Err(e) => warn!("failed to save {}: {e}", path.display()),
}
}
pub fn unregister_render(uuid: &str) {
let path = live_renders_path();
let mut data: LiveRenders = read_json(&path);
let before = data.renders.len();
data.renders.retain(|r| r.uuid != uuid);
if data.renders.len() == before {
return;
}
match write_json(&path, &data) {
Ok(()) => debug!("unregistered render {uuid}"),
Err(e) => warn!("failed to save {}: {e}", path.display()),
}
}
pub fn live_render_ports() -> Vec<u16> {
let data: LiveRenders = read_json(&live_renders_path());
data.renders.into_iter().map(|r| r.port).collect()
}