You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
6.8 KiB
283 lines
6.8 KiB
//! Server config
|
|
use super::*;
|
|
use std::{
|
|
net::SocketAddr,
|
|
path::Path,
|
|
io,
|
|
borrow::Cow,
|
|
num::NonZeroU64,
|
|
error,
|
|
fmt,
|
|
};
|
|
use tokio::{
|
|
fs::OpenOptions,
|
|
prelude::*,
|
|
time::Duration,
|
|
io::BufReader,
|
|
};
|
|
use ipfilt::IpFilter;
|
|
|
|
pub const DEFAULT_FILE_LOCATION: &'static str = "markov.toml";
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)]
|
|
pub struct Config
|
|
{
|
|
pub bindpoint: String,
|
|
pub file: String,
|
|
pub max_content_length: u64,
|
|
pub max_gen_size: usize,
|
|
pub save_interval_secs: Option<NonZeroU64>,
|
|
pub trust_x_forwarded_for: bool,
|
|
#[serde(default)]
|
|
pub feed_bounds: String,
|
|
#[serde(default)]
|
|
pub filter: FilterConfig,
|
|
#[serde(default)]
|
|
pub writer: WriterConfig,
|
|
#[serde(default)]
|
|
pub mask: IpFilter,
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)]
|
|
pub struct FilterConfig
|
|
{
|
|
#[serde(default)]
|
|
inbound: String,
|
|
#[serde(default)]
|
|
outbound: String,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)]
|
|
pub struct WriterConfig
|
|
{
|
|
pub backlog: usize,
|
|
pub internal_backlog: usize,
|
|
pub capacity: usize,
|
|
pub timeout_ms: Option<u64>,
|
|
pub throttle_ms: Option<u64>,
|
|
}
|
|
|
|
impl Default for WriterConfig
|
|
{
|
|
#[inline]
|
|
fn default() -> Self
|
|
{
|
|
Self {
|
|
backlog: 32,
|
|
internal_backlog: 8,
|
|
capacity: 4,
|
|
timeout_ms: None,
|
|
throttle_ms: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WriterConfig
|
|
{
|
|
fn create_settings(self, bounds: range::DynRange<usize>) -> handle::Settings
|
|
{
|
|
|
|
handle::Settings{
|
|
backlog: self.backlog,
|
|
internal_backlog: self.internal_backlog,
|
|
capacity: self.capacity,
|
|
timeout: self.timeout_ms.map(tokio::time::Duration::from_millis).unwrap_or(handle::DEFAULT_TIMEOUT),
|
|
throttle: self.throttle_ms.map(tokio::time::Duration::from_millis),
|
|
bounds,
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
impl FilterConfig
|
|
{
|
|
fn get_inbound_filter(&self) -> sanitise::filter::Filter
|
|
{
|
|
let filt: sanitise::filter::Filter = self.inbound.parse().unwrap();
|
|
if !filt.is_empty()
|
|
{
|
|
info!("Loaded inbound filter: {:?}", filt.iter().collect::<String>());
|
|
}
|
|
filt
|
|
}
|
|
fn get_outbound_filter(&self) -> sanitise::filter::Filter
|
|
{
|
|
let filt: sanitise::filter::Filter = self.outbound.parse().unwrap();
|
|
if !filt.is_empty()
|
|
{
|
|
info!("Loaded outbound filter: {:?}", filt.iter().collect::<String>());
|
|
}
|
|
filt
|
|
}
|
|
}
|
|
|
|
impl Default for Config
|
|
{
|
|
#[inline]
|
|
fn default() -> Self
|
|
{
|
|
Self {
|
|
bindpoint: SocketAddr::from(([127,0,0,1], 8001)).to_string(),
|
|
file: "chain.dat".to_owned(),
|
|
max_content_length: 1024 * 1024 * 4,
|
|
max_gen_size: 256,
|
|
save_interval_secs: Some(unsafe{NonZeroU64::new_unchecked(2)}),
|
|
trust_x_forwarded_for: false,
|
|
filter: Default::default(),
|
|
feed_bounds: "2..".to_owned(),
|
|
writer: Default::default(),
|
|
mask: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Config
|
|
{
|
|
/// Try to generate a config cache for this instance.
|
|
pub fn try_gen_cache(&self) -> Result<Cache, InvalidConfigError>
|
|
{
|
|
macro_rules! section {
|
|
($name:literal, $expr:expr) => {
|
|
match $expr {
|
|
Ok(v) => Ok(v),
|
|
Err(e) => Err(InvalidConfigError($name, Box::new(e))),
|
|
}
|
|
}
|
|
}
|
|
use std::ops::RangeBounds;
|
|
|
|
let feed_bounds = section!("feed_bounds", self.parse_feed_bounds()).and_then(|bounds| if bounds.contains(&0) {
|
|
Err(InvalidConfigError("feed_bounds", Box::new(opaque_error!("Bounds not allowed to contains 0 (they were `{}`)", bounds))))
|
|
} else {
|
|
Ok(bounds)
|
|
})?;
|
|
Ok(Cache {
|
|
handler_settings: self.writer.create_settings(feed_bounds.clone()),
|
|
feed_bounds,
|
|
inbound_filter: self.filter.get_inbound_filter(),
|
|
outbound_filter: self.filter.get_outbound_filter(),
|
|
})
|
|
}
|
|
/// Try to parse the `feed_bounds`
|
|
fn parse_feed_bounds(&self) -> Result<range::DynRange<usize>, range::ParseError>
|
|
{
|
|
if self.feed_bounds.len() == 0 {
|
|
Ok(feed::DEFAULT_FEED_BOUNDS.into())
|
|
} else {
|
|
self.feed_bounds.parse()
|
|
}
|
|
}
|
|
pub fn save_interval(&self) -> Option<Duration>
|
|
{
|
|
self.save_interval_secs.map(|x| Duration::from_secs(x.into()))
|
|
}
|
|
pub async fn load(from: impl AsRef<Path>) -> io::Result<Self>
|
|
{
|
|
let file = OpenOptions::new()
|
|
.read(true)
|
|
.open(from).await?;
|
|
|
|
let mut buffer= String::new();
|
|
let reader = BufReader::new(file);
|
|
let mut lines = reader.lines();
|
|
while let Some(line) = lines.next_line().await? {
|
|
buffer.push_str(&line[..]);
|
|
buffer.push('\n');
|
|
}
|
|
toml::de::from_str(&buffer[..]).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
|
|
}
|
|
|
|
pub async fn save(&self, to: impl AsRef<Path>) -> io::Result<()>
|
|
{
|
|
let config = toml::ser::to_string_pretty(self).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(to).await?;
|
|
file.write_all(config.as_bytes()).await?;
|
|
file.shutdown().await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Try to load config file specified by args, or default config file
|
|
pub fn load() -> impl futures::future::Future<Output =Option<Config>>
|
|
{
|
|
load_args(std::env::args().skip(1))
|
|
}
|
|
|
|
async fn load_args<I: Iterator<Item=String>>(mut from: I) -> Option<Config>
|
|
{
|
|
let place = if let Some(arg) = from.next() {
|
|
trace!("File {:?} provided", arg);
|
|
Cow::Owned(arg)
|
|
} else {
|
|
warn!("No config file provided. Using default location {:?}", DEFAULT_FILE_LOCATION);
|
|
Cow::Borrowed(DEFAULT_FILE_LOCATION)
|
|
};
|
|
|
|
match Config::load(place.as_ref()).await {
|
|
Ok(cfg) => {
|
|
info!("Loaded config file {:?}", place);
|
|
Some(cfg)
|
|
},
|
|
Err(err) => {
|
|
error!("Failed to load config file from {:?}: {}", place, err);
|
|
None
|
|
},
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct InvalidConfigError(&'static str, Box<dyn error::Error+ 'static>);
|
|
|
|
impl InvalidConfigError
|
|
{
|
|
pub fn field(&self) -> &str
|
|
{
|
|
&self.0[..]
|
|
}
|
|
}
|
|
|
|
impl error::Error for InvalidConfigError
|
|
{
|
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
Some(self.1.as_ref())
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for InvalidConfigError
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
write!(f,"failed to parse field `{}`: {}", self.0, self.1)
|
|
}
|
|
}
|
|
|
|
|
|
/// Caches some parsed config arguments
|
|
#[derive(Clone, PartialEq)]
|
|
pub struct Cache
|
|
{
|
|
pub feed_bounds: range::DynRange<usize>,
|
|
pub inbound_filter: sanitise::filter::Filter,
|
|
pub outbound_filter: sanitise::filter::Filter,
|
|
pub handler_settings: handle::Settings,
|
|
}
|
|
|
|
impl fmt::Debug for Cache
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
f.debug_struct("Cache")
|
|
.field("feed_bounds", &self.feed_bounds)
|
|
.field("inbound_filter", &self.inbound_filter.iter().collect::<String>())
|
|
.field("outbound_filter", &self.outbound_filter.iter().collect::<String>())
|
|
.field("handler_settings", &self.handler_settings)
|
|
.finish()
|
|
}
|
|
}
|
|
|