#![feature(proc_macro_hygiene, decl_macro)]
#![deny(missing_docs)]
#![allow(clippy::pedantic)]
extern crate rocket;
pub mod api;
#[allow(missing_docs)]
pub mod model;
mod parse;
use std::str::FromStr;
use std::{
collections::HashMap,
fs::{metadata, File},
io::{BufRead, BufReader, Result},
path::{Path, PathBuf},
sync::{Arc, RwLock},
time::SystemTime,
};
pub type ArcMap<T> = Arc<HashMap<u64, T>>;
pub trait ThreadSafeErr: std::error::Error + Sync + Send + 'static {}
pub trait Resource<T: UniqueID> {
fn load(&self) -> Result<Arc<HashMap<u64, T>>>;
}
pub trait UniqueID {
fn id(&self) -> u64;
}
pub trait Query<T> {
fn is_match(&self, t: &T) -> bool;
fn matching_elements<'a>(&self, a: impl IntoIterator<Item = &'a T>) -> Vec<&'a T> {
a.into_iter().filter(|t| self.is_match(t)).collect()
}
}
impl<F: Fn(&T) -> bool, T> Query<T> for F {
fn is_match(&self, t: &T) -> bool {
(self)(t)
}
}
#[derive(Debug)]
pub struct FileResource<T: std::str::FromStr + Clone> {
path: PathBuf,
cached_at: RwLock<std::time::SystemTime>,
cache: RwLock<Arc<HashMap<u64, T>>>,
}
impl<T> FileResource<T>
where
T: FromStr + Clone + UniqueID,
T::Err: ThreadSafeErr,
{
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref().to_owned();
let cached_at = RwLock::new(metadata(&path)?.modified()?);
let cache = RwLock::new(Arc::new(build_cache(&path)?));
Ok(Self { path, cached_at, cache })
}
}
fn build_cache<T>(p: impl AsRef<Path>) -> Result<HashMap<u64, T>>
where
T: FromStr + UniqueID,
T::Err: std::error::Error + Sync + Send + 'static,
{
let mut cache = HashMap::new();
for line_res in BufReader::new(File::open(p)?).lines() {
let line = line_res?;
match line.trim() {
line if line.is_empty() => continue,
line => match line.parse::<T>() {
Ok(t) => cache.insert(t.id(), t),
Err(err) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, Box::new(err))),
},
};
}
Ok(cache)
}
impl<T> Resource<T> for FileResource<T>
where
T: FromStr + Clone + UniqueID + Sync + Send,
T::Err: ThreadSafeErr,
{
fn load(&self) -> Result<Arc<HashMap<u64, T>>> {
let last_modified: SystemTime = metadata(&self.path)?.modified()?;
let need_cache_reload = *self.cached_at.read().expect("thread failure") < last_modified;
if need_cache_reload {
*Arc::make_mut(&mut self.cache.write().unwrap()) = build_cache(&self.path)?;
*self.cached_at.write().unwrap() = last_modified;
};
Ok(self.cache.read().unwrap().clone())
}
}
impl<E: std::error::Error + Sync + Send + 'static> ThreadSafeErr for E {}
impl<T> Resource<T> for &FileResource<T>
where
T: FromStr + Clone + UniqueID + Sync + Send,
T::Err: ThreadSafeErr,
{
fn load(&self) -> Result<Arc<HashMap<u64, T>>> {
let last_modified: SystemTime = metadata(&self.path)?.modified()?;
let need_cache_reload = *self.cached_at.read().expect("thread failure") < last_modified;
if need_cache_reload {
*Arc::make_mut(&mut self.cache.write().unwrap()) = build_cache(&self.path)?;
*self.cached_at.write().unwrap() = last_modified;
};
Ok(self.cache.read().unwrap().clone())
}
}
impl<'r, T> Resource<T> for rocket::State<'r, FileResource<T>>
where
T: Clone + UniqueID + Send + Sync + FromStr,
T::Err: ThreadSafeErr,
{
fn load(&self) -> Result<Arc<HashMap<u64, T>>> {
(*self.inner()).load()
}
}
impl UniqueID for model::User {
fn id(&self) -> u64 {
self.uid
}
}
impl UniqueID for model::Group {
fn id(&self) -> u64 {
self.gid
}
}