use std::{ collections::HashMap, io::{self, Write}, path::PathBuf, time::SystemTime, }; use serde::{Deserialize, Serialize}; use crate::{server_config::ServerConfig, static_http_server_route::StaticHttpServerRoute}; #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct ServerRoutesScan { pub routes_date: u128, pub routes: Vec, pub graph: HashMap>, pub subdomains: HashMap>, } impl ServerRoutesScan { fn new( routes_date: u128, routes: Vec, graph: HashMap>, subdomains: HashMap>, ) -> Self { Self { routes_date, routes, graph, subdomains, } } pub fn check_for_changes_mut(&mut self, config: &ServerConfig) { match self.check_for_changes(config) { Ok(new) => { self.routes_date = new.routes_date; self.routes = new.routes; self.graph = new.graph; self.subdomains = new.subdomains; } Err(err) if err.kind() == io::ErrorKind::AddrInUse => (), Err(err) => { self.routes_date = u128::default(); self.routes = Vec::default(); self.graph = HashMap::default(); self.subdomains = HashMap::default(); eprintln!("Warn: Error on adapt_from_disk_changes: {:?}", err) } } } pub fn check_for_changes(&self, config: &ServerConfig) -> io::Result { let routes_disk_date = std::fs::metadata(&config.routes) .ok() .or_else(|| { std::fs::File::create(&config.routes) .and_then(|mut file| file.write_all(b"")) .and_then(|_| std::fs::metadata(&config.routes)) .ok() }) .and_then(|metadata| metadata.modified().ok()) .and_then(|systime| systime.duration_since(SystemTime::UNIX_EPOCH).ok()) .map(|duration| duration.as_micros()) .unwrap_or(0u128); if routes_disk_date == 0 || self.routes_date == 0 || self.routes_date != routes_disk_date { self.adapt_from_disk_changes(config) } else { Err(io::ErrorKind::AddrInUse.into()) } } fn adapt_from_disk_changes(&self, config: &ServerConfig) -> io::Result { let mut routes: Vec = Vec::new(); std::fs::create_dir_all(config.sites.clone())?; for dir_entry_result in config.sites.read_dir()? { let dir_entry: std::fs::DirEntry = dir_entry_result?; if !dir_entry.metadata()?.is_dir() { eprintln!( "WARNING: {:?} is not a directory! Ignoring...", dir_entry.path() ); continue; } let site_definition = dir_entry.path().join("site.yaml"); if !(match site_definition.metadata() { Ok(metadata) => metadata.is_file(), Err(_) => false, }) { eprintln!("WARNING: {:?} is not a file! Ignoring...", site_definition); continue; } let site_definition_string: String = match std::fs::read_to_string(&site_definition) { Ok(site_definition_) => site_definition_, Err(_) => { eprintln!( "WARNING: {:?} does not have valid text! Ignoring...", site_definition, ); continue; } }; let mut route_vec: Vec = match serde_yaml::from_str( site_definition_string.as_str(), ) { Ok(routes_) => routes_, Err(_) => { eprintln!( "WARNING: {:?} does not deserialize into a Vec struct! Ignoring...", site_definition, ); continue; } }; let site_files_cand = dir_entry.path().join("files"); let site_files_try = site_files_cand.canonicalize(); let site_files = site_files_try.unwrap_or(site_files_cand); if !(site_files .metadata() .map(|metadata| metadata.is_dir()) .unwrap_or(false)) { eprintln!( "WARNING: {:?} is not a directory! Is likely to only return 404s...", site_files ); } for route in route_vec.iter_mut() { if config.is_on_dev || !route.only_on_dev { if route.path == PathBuf::from("") || route.path == PathBuf::from("/") || route .path .canonicalize() .and_then(|pb| PathBuf::from(".").canonicalize().map(|cd| cd == pb)) .unwrap_or(true) || route .path .canonicalize() .map(|pb| pb == site_files) .unwrap_or(true) { route.path.clone_from(&site_files); } else { route.path = route .path .canonicalize() .unwrap_or_else(|_| site_files.clone()); } // if route.template_path == PathBuf::from("") || route.template_path == PathBuf::from("/") || route .template_path .canonicalize() .and_then(|pb| PathBuf::from(".").canonicalize().map(|cd| cd == pb)) .unwrap_or(true) || route .template_path .canonicalize() .map(|pb| pb == site_files) .unwrap_or(true) { route.template_path.clone_from(&site_files); } else { route.template_path = route .template_path .canonicalize() .unwrap_or_else(|_| site_files.clone()); } // let on_404 = route.path.join(route.on_404.clone()); if on_404.is_file() { route.on_404 = on_404; } else { route.on_404 = PathBuf::default(); } if !route.prefix.starts_with('/') { route.prefix = format!("/{}", route.prefix); } if !route.prefix.ends_with('/') { route.prefix = format!("{}/", route.prefix); } routes.append(&mut vec![route.clone()]); } } } let mut graph: HashMap> = HashMap::new(); for route in routes.iter() { if !graph.contains_key(&route.domain) { graph.insert(route.domain.clone(), Vec::new()); } graph .get_mut(&route.domain) .map(|graph_vec| { graph_vec.push(route.clone()); }) .ok_or(std::io::ErrorKind::NotFound)?; } let mut graph2: HashMap> = HashMap::new(); for ge in graph.iter() { let mut vector = ge.1.clone(); vector.sort_by(|x, y| y.prefix.len().cmp(&x.prefix.len())); graph2.insert(ge.0.clone(), vector); } let new_graph = graph2 .clone() .into_iter() .map(|(k, v)| (k.trim_start_matches('*').to_owned(), v)) .collect(); let new_subdomains = graph2 .clone() .into_iter() .filter(|(k, _)| k.starts_with('*')) .map(|(k, v)| (k.trim_start_matches('*').to_owned(), v)) .collect(); std::fs::File::create(&config.routes)?.write_all( serde_yaml::to_string(&graph) .map_err(|_| std::io::ErrorKind::InvalidInput)? .as_bytes(), )?; let new_routes_date = std::fs::metadata(&config.routes)? .modified()? .duration_since(SystemTime::UNIX_EPOCH) .map_err(|_| std::io::ErrorKind::InvalidData)? .as_micros(); Ok(Self::new( new_routes_date, routes, new_graph, new_subdomains, )) } }