238 lines
5.3 KiB
Rust
238 lines
5.3 KiB
Rust
// False positive with itertools::process_results
|
|
#![allow (clippy::redundant_closure)]
|
|
|
|
use std::{
|
|
collections::{
|
|
HashMap,
|
|
},
|
|
convert::{TryFrom},
|
|
net::IpAddr,
|
|
path::Path,
|
|
str::FromStr,
|
|
};
|
|
|
|
use crate::{
|
|
errors::ConfigError,
|
|
key_validity::{
|
|
ScraperKey,
|
|
},
|
|
};
|
|
|
|
/// Machine-editable configs.
|
|
/// These are stored in the `data` directory and shouldn't be touched by
|
|
/// humans. `ptth_relay` will re-write them while it's running.
|
|
|
|
pub mod machine_editable {
|
|
use std::{
|
|
collections::BTreeMap,
|
|
path::Path,
|
|
};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use super::file::Server;
|
|
|
|
#[derive (Deserialize, Serialize)]
|
|
pub struct ConfigFile {
|
|
pub servers: Vec <Server>,
|
|
}
|
|
|
|
#[derive (Default)]
|
|
pub struct Config {
|
|
pub servers: BTreeMap <String, Server>,
|
|
}
|
|
|
|
impl ConfigFile {
|
|
pub fn from_file (path: &Path) -> Result <Self, crate::ConfigError>
|
|
{
|
|
let config_s = std::fs::read_to_string (path)?;
|
|
Ok (toml::from_str (&config_s)?)
|
|
}
|
|
|
|
pub async fn save (&self, path: &Path) -> Result <(), crate::ConfigError>
|
|
{
|
|
let s = toml::to_string (self)?;
|
|
// This is way easier in C++ but also not safe
|
|
let mut temp_path = path.file_name ().unwrap ().to_os_string ();
|
|
temp_path.push (".partial");
|
|
let temp_path = path.with_file_name (temp_path);
|
|
tokio::fs::write (&temp_path, &s).await?;
|
|
tokio::fs::rename (&temp_path, path).await?;
|
|
Ok (())
|
|
}
|
|
}
|
|
|
|
impl Config {
|
|
pub fn from_file (path: &Path) -> Result <Self, crate::ConfigError>
|
|
{
|
|
let c = ConfigFile::from_file (path)?;
|
|
|
|
let servers = c.servers.into_iter ()
|
|
.map (|s| (s.name.clone (), s))
|
|
.collect ();
|
|
|
|
Ok (Self {
|
|
servers,
|
|
})
|
|
}
|
|
|
|
pub async fn save (&self, path: &Path) -> Result <(), crate::ConfigError>
|
|
{
|
|
let servers = self.servers.values ()
|
|
.cloned ().into_iter ().collect ();
|
|
|
|
let c = ConfigFile {
|
|
servers,
|
|
};
|
|
c.save (path).await?;
|
|
Ok (())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Config fields as they are loaded from the config file
|
|
|
|
pub mod file {
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::key_validity::{
|
|
BlakeHashWrapper,
|
|
ScraperKey,
|
|
};
|
|
|
|
#[derive (Clone, Debug, Deserialize, Serialize)]
|
|
pub struct Server {
|
|
/// This is duplicated in the hashmap, but it's not a problem
|
|
pub name: String,
|
|
|
|
pub tripcode: BlakeHashWrapper,
|
|
|
|
/// This allows a relay-side rename of servers
|
|
pub display_name: Option <String>,
|
|
}
|
|
|
|
/// Empty
|
|
|
|
#[derive (Deserialize)]
|
|
pub struct DevMode {
|
|
|
|
}
|
|
|
|
/// Config fields that are identical in the file and at runtime
|
|
|
|
#[derive (Default, Deserialize)]
|
|
pub struct Isomorphic {
|
|
#[serde (default)]
|
|
pub enable_scraper_api: bool,
|
|
|
|
/// If any of the `DevMode` fields are used, we are in dev mode
|
|
/// and have to show extra warnings, since auth may be weakened
|
|
pub dev_mode: Option <DevMode>,
|
|
}
|
|
|
|
#[derive (Deserialize)]
|
|
pub struct Config {
|
|
#[serde (flatten)]
|
|
pub iso: Isomorphic,
|
|
|
|
pub address: Option <String>,
|
|
pub port: Option <u16>,
|
|
pub servers: Option <Vec <Server>>,
|
|
|
|
// Adding a DB will take a while, so I'm moving these out of dev mode.
|
|
pub scraper_keys: Option <Vec <ScraperKey>>,
|
|
|
|
pub news_url: Option <String>,
|
|
}
|
|
}
|
|
|
|
/// Config fields as they are used at runtime
|
|
|
|
pub struct Config {
|
|
pub iso: file::Isomorphic,
|
|
|
|
pub address: IpAddr,
|
|
pub port: Option <u16>,
|
|
pub servers: HashMap <String, file::Server>,
|
|
pub scraper_keys: HashMap <String, ScraperKey>,
|
|
pub news_url: Option <String>,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default () -> Self {
|
|
Self {
|
|
iso: Default::default (),
|
|
address: IpAddr::from ([0, 0, 0, 0]),
|
|
port: None,
|
|
servers: Default::default (),
|
|
scraper_keys: Default::default (),
|
|
news_url: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom <file::Config> for Config {
|
|
type Error = ConfigError;
|
|
|
|
fn try_from (f: file::Config) -> Result <Self, Self::Error> {
|
|
let servers = f.servers.unwrap_or_else (|| vec! []);
|
|
let servers = servers.into_iter ().map (|server| Ok::<_, ConfigError> ((server.name.clone (), server)));
|
|
|
|
let servers = itertools::process_results (servers, |i| i.collect ())?;
|
|
|
|
let scraper_keys = f.scraper_keys.unwrap_or_else (|| vec! []);
|
|
let scraper_keys = if f.iso.enable_scraper_api {
|
|
scraper_keys.into_iter ().map (|key| (key.hash.encode_base64 (), key)).collect ()
|
|
}
|
|
else {
|
|
Default::default ()
|
|
};
|
|
|
|
Ok (Self {
|
|
iso: f.iso,
|
|
address: parse_address (f.address.as_ref ().map (|s| &s[..]))?,
|
|
port: f.port,
|
|
servers,
|
|
scraper_keys,
|
|
news_url: f.news_url,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn parse_address (s: Option <&str>) -> Result <IpAddr, ConfigError> {
|
|
Ok (s
|
|
.map (|s| IpAddr::from_str (s))
|
|
.transpose ().map_err (|_| ConfigError::BadServerAddress)?
|
|
.unwrap_or_else (|| IpAddr::from ([0, 0, 0, 0]))
|
|
)
|
|
}
|
|
|
|
impl Config {
|
|
pub async fn from_file (path: &Path) -> Result <Self, ConfigError> {
|
|
let config_s = tokio::fs::read_to_string (path).await?;
|
|
let new_config: file::Config = toml::from_str (&config_s)?;
|
|
|
|
Self::try_from (new_config)
|
|
}
|
|
}
|
|
|
|
#[cfg (test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn ip_address () {
|
|
for (input, expected) in vec! [
|
|
(None, Some (IpAddr::from ([0, 0, 0, 0]))),
|
|
(Some ("bogus"), None),
|
|
(Some ("0.0.0.0"), Some (IpAddr::from ([0, 0, 0, 0]))),
|
|
(Some ("0"), None),
|
|
(Some ("127.0.0.1"), Some (IpAddr::from ([127, 0, 0, 1]))),
|
|
(Some ("10.0.0.1"), Some (IpAddr::from ([10, 0, 0, 1]))),
|
|
].into_iter () {
|
|
let actual = parse_address (input).ok ();
|
|
assert_eq! (actual, expected);
|
|
}
|
|
}
|
|
}
|