static-site-server-rs/src/server_routes_scan.rs

236 lines
9.1 KiB
Rust

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<StaticHttpServerRoute>,
pub graph: HashMap<String, Vec<StaticHttpServerRoute>>,
pub subdomains: HashMap<String, Vec<StaticHttpServerRoute>>,
}
impl ServerRoutesScan {
fn new(
routes_date: u128,
routes: Vec<StaticHttpServerRoute>,
graph: HashMap<String, Vec<StaticHttpServerRoute>>,
subdomains: HashMap<String, Vec<StaticHttpServerRoute>>,
) -> 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<Self> {
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<Self> {
let mut routes: Vec<StaticHttpServerRoute> = 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<StaticHttpServerRoute> = match serde_yaml::from_str(
site_definition_string.as_str(),
) {
Ok(routes_) => routes_,
Err(_) => {
eprintln!(
"WARNING: {:?} does not deserialize into a Vec<StaticHttpServerRoutes> 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<String, Vec<StaticHttpServerRoute>> = 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<String, Vec<StaticHttpServerRoute>> = 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,
))
}
}