use std::collections::*; use std::convert::TryFrom; use std::default::Default; use std::io; use std::path::PathBuf; use futures::prelude::*; use futures::stream::TryStreamExt; use tokio::fs as tokiofs; use tokio::io::{AsyncRead, AsyncReadExt}; use toml; use serde::Deserialize; use matrix_sdk::identifiers::RoomId; // 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), 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, } #[derive(Clone, Debug, Deserialize)] pub struct ConfigFile { acct: Vec, watch: Vec, } #[derive(Clone, Debug, Deserialize)] pub struct ConfigAccount { label: String, homeserver: String, username: String, password: String, } #[derive(Clone, Debug, Deserialize)] pub struct ConfigWatch { sender: String, path: String, destroom: String, } pub async fn find_configs(search_dir: &PathBuf) -> Result, Error> { let mut items: Vec = Vec::new(); let mut rd = tokiofs::read_dir(search_dir).await?; while let Some(dent) = rd.next_entry().await? { items.push(dent); } 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!("warning: error loading config, skipping: {:?}", p); continue; } }; // Ingest the accounts. for a in val.acct { if conf.accounts.contains_key(&a.label) { eprintln!("warning: ignoring duplicate account entry for {}", a.label); continue; } let acct = Account { homeserver: a.homeserver, auth: Auth::UsernamePass(a.username, a.password), device_id: None, display: None, }; conf.accounts.insert(a.label, acct); } // Ingest the watches. for s in val.watch { let sd = SpoolDir { path: PathBuf::from(s.path), send_delay_sec: 0, sender_acct_label: s.sender, dest_room_id: RoomId::try_from(s.destroom.as_str()) .map_err(|_| Error::ParseIdentifier)?, }; conf.spool_dirs.push(sd); } } 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::from_slice(&buf).map_err(|e| { eprintln!("warning: parsing file {:?}", e); Error::ParseFile(path.clone()) }) }