use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, fs::File, io::Read, net::IpAddr, path::PathBuf, }; use thiserror::Error; #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Config { #[serde(default = "Config::default_binding")] pub binding: String, #[serde(default = "Config::default_pinned")] pub pinned: Vec, #[serde(default = "Config::default_targets")] pub targets: HashMap, #[serde(default)] pub info: Info, #[serde(default)] pub theme: Theme, } type Colour = (u8, u8, u8); /// all colours are in rgb #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Theme { #[serde(default = "Theme::default_background")] pub background: Colour, #[serde(default = "Theme::default_background_main")] pub background_main: Colour, #[serde(default = "Theme::default_background_server")] pub background_server: Colour, #[serde(default = "Theme::default_background_button")] pub background_button: Colour, #[serde(default = "Theme::default_text")] pub text: Colour, #[serde(default = "Theme::default_text_secondary")] pub text_secondary: Colour, #[serde(default = "Theme::default_accent_success")] pub accent_success: Colour, #[serde(default = "Theme::default_accent_fail")] pub accent_fail: Colour, #[serde(default = "Theme::default_link")] pub link: Colour, #[serde(default = "Theme::default_link_visited")] pub link_visited: Colour, #[serde(default = "Theme::default_highlight")] pub highlight: Colour, #[serde(default = "Theme::default_highlight_opacity")] pub highlight_opacity: u8, #[serde(default = "Theme::default_fonts")] pub fonts: Vec, } #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Target { #[serde(with = "crate::utils::mac")] pub mac: crate::mac::MacAddress, #[serde(default, with = "crate::utils::ip::option")] pub ip: Option, pub url: Option, } #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Info { #[serde(default = "Info::default_title")] pub title: String, #[serde(default = "Info::default_links", with = "crate::utils::links")] pub links: Vec<(String, String)>, #[serde(default = "Info::default_icon")] pub icon: PathBuf, } impl Config { fn default_binding() -> String { String::from("0.0.0.0:3000") } fn default_pinned() -> Vec { Vec::new() } fn default_targets() -> HashMap { HashMap::new() } } impl Default for Config { fn default() -> Self { Self { binding: Config::default_binding(), theme: Theme::default(), info: Info::default(), pinned: Config::default_pinned(), targets: Config::default_targets(), } } } impl Info { fn default_title() -> String { String::from("Wake on Lan") } fn default_links() -> Vec<(String, String)> { vec![( String::from("https://tangled.org/vielle.dev/wol/"), String::from("vielle.dev/wol"), )] } fn default_icon() -> PathBuf { PathBuf::from("./favicon.ico") } } impl Default for Info { fn default() -> Self { Self { title: Info::default_title(), links: Info::default_links(), icon: Info::default_icon(), } } } impl Theme { fn default_background() -> Colour { (48, 52, 70) } fn default_background_main() -> Colour { (35, 38, 52) } fn default_background_server() -> Colour { (65, 69, 89) } fn default_background_button() -> Colour { (81, 87, 109) } fn default_text() -> Colour { (198, 208, 245) } fn default_text_secondary() -> Colour { (165, 173, 206) } fn default_accent_success() -> Colour { (166, 209, 137) } fn default_accent_fail() -> Colour { (231, 130, 132) } fn default_link() -> Colour { (140, 170, 238) } fn default_link_visited() -> Colour { (202, 158, 230) } fn default_highlight() -> Colour { (148, 156, 187) } fn default_highlight_opacity() -> u8 { 25 } fn default_fonts() -> Vec { vec![String::from("system-ui")] } } impl Default for Theme { fn default() -> Self { Self { background: Theme::default_background(), background_main: Theme::default_background_main(), background_server: Theme::default_background_server(), background_button: Theme::default_background_button(), text: Theme::default_text(), text_secondary: Theme::default_text_secondary(), accent_success: Theme::default_accent_success(), accent_fail: Theme::default_accent_fail(), link: Theme::default_link(), link_visited: Theme::default_link_visited(), highlight: Theme::default_highlight(), highlight_opacity: Theme::default_highlight_opacity(), fonts: Theme::default_fonts(), } } } #[derive(Error, Debug)] pub enum ConfigError { #[error("Io error: {}", .0)] Io(#[from] std::io::Error), #[error("Deserialize error: {}", .0)] Serde(#[from] toml::de::Error), #[error("Unknown item was pinned: {}", .0)] UnknownPin(String), #[error("Item was pinned more than once: {}", .0)] DupePin(String), } impl Config { pub fn load(path: PathBuf) -> Result { let mut file = File::open(path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let config: Self = toml::from_str(&contents)?; // all entries in pinned should be keys of targets let targets = config.targets.keys().collect::>(); if let Some(mismatch) = &config.pinned.iter().find(|x| !targets.contains(x)) { return Err(ConfigError::UnknownPin(mismatch.to_string())); }; let mut uniq = HashSet::::new(); if let Some(dupe) = &config .pinned .iter() .find(move |x| !uniq.insert(x.to_string())) { return Err(ConfigError::DupePin(dupe.to_string())); }; Ok(config) } }