use std::collections::*; use std::convert::TryFrom; use std::default::Default; use std::io; use std::path::PathBuf; use futures::prelude::*; use ruma_identifiers::{self, RoomId}; use tokio::fs as tokiofs; use tokio::prelude::*; use toml; // FIXME Some of these error types are only used in the main module. #[derive(Debug)] pub enum Error { // TODO Reconcile these different parse errors. ParseFile(PathBuf), ParseToml(toml::de::Error), ParseMalformed, ParseIdentifier, Io(io::Error), } impl From for Error { fn from(f: io::Error) -> Self { Self::Io(f) } } impl From for Error { fn from(f: toml::de::Error) -> Self { Self::ParseToml(f) } } #[derive(Default, Debug)] pub struct Config { pub accounts: HashMap, pub spool_dirs: Vec, } #[derive(Clone, Debug)] pub struct Account { pub homeserver: String, pub display: Option, pub device_id: Option, pub auth: Auth, } #[derive(Clone, Debug)] pub enum Auth { UsernamePass(String, String), } #[derive(Debug)] pub struct SpoolDir { pub path: PathBuf, pub send_delay_sec: u32, pub sender_acct_label: String, pub dest_room_id: RoomId, } pub async fn find_configs(search_dir: &PathBuf) -> Result, Error> { let items: Vec = tokiofs::read_dir(search_dir).await?.try_collect().await?; Ok(items .into_iter() .filter(|de| { de.file_name() .to_str() .map(|s| s.ends_with(".toml")) .unwrap_or(false) }) .map(|e| e.path()) .collect()) } pub async fn parse_configs(paths: Vec) -> Result { let mut conf = Config::default(); for p in &paths { println!("Reading config: {}", p.to_str().unwrap_or("[non-UTF-8]")); let val = match load_toml(p).await { Ok(t) => t, Err(_) => { println!("Error loading config, skipping"); continue; } }; match parse_toml(&val) { Ok((accts, spools)) => { conf.accounts.extend(accts); conf.spool_dirs.extend(spools); } Err(_) => { println!("Error processing config, skipping"); continue; } } } Ok(conf) } async fn load_toml(path: &PathBuf) -> Result { let mut buf = Vec::new(); let mut f = tokiofs::File::open(path).await?; let _ = f.read_to_end(&mut buf).await?; toml::de::from_slice(buf.as_slice()).map_err(|_| Error::ParseFile(path.clone())) } fn parse_toml(val: &toml::Value) -> Result<(Vec<(String, Account)>, Vec), Error> { use toml::Value::*; let mut accts = Vec::new(); let mut spools = Vec::new(); match val { Table(tab) => { let acct_ents = tab.get("acct").cloned(); let watch_ents = tab.get("watch").cloned(); match acct_ents { Some(Array(entries)) => { for acct in &entries { accts.push(parse_acct_entry(&acct)?); } } // TODO Some(_) => {} _ => {} } match watch_ents { Some(Array(entries)) => { for wd in &entries { spools.push(parse_watch_entry(&wd)?); } } // TODO Some(_) => {} _ => {} } } _ => {} } Ok((accts, spools)) } fn parse_acct_entry(ent: &toml::Value) -> Result<(String, Account), Error> { use toml::Value::*; type StdString = ::std::string::String; let label = ent.get("label"); let homeserver = ent.get("homeserver"); let display = ent.get("display"); let dev_id = ent.get("deviceid"); let username = ent.get("username"); let password = ent.get("password"); // This is gross and I don't like it, but ok. match (label, homeserver, username, password) { (Some(String(l)), Some(String(s)), Some(String(u)), Some(String(p))) => { let auth = Auth::UsernamePass(u.clone(), p.clone()); Ok(( l.clone(), Account { homeserver: s.clone(), display: display .cloned() .map(|v| v.try_into::()) .transpose()?, device_id: dev_id .cloned() .map(|v| v.try_into::()) .transpose()?, auth: auth, }, )) } _ => Err(Error::ParseMalformed), } } fn parse_watch_entry(ent: &toml::Value) -> Result { use toml::Value::*; let sender = ent.get("sender"); let path = ent.get("path"); let dest = ent.get("destroom"); let delay = ent.get("delay"); // Again this is gross, but whatever. match (path, sender, dest) { (Some(String(p)), Some(String(s)), Some(String(d))) => Ok(SpoolDir { path: PathBuf::from(p.clone()), send_delay_sec: delay .cloned() .map(|v| v.try_into::()) .transpose()? .unwrap_or(0), sender_acct_label: s.clone(), dest_room_id: RoomId::try_from(d.as_str()).map_err(|_| Error::ParseIdentifier)?, }), _ => Err(Error::ParseMalformed), } }