diff --git a/src/domain/account/entity.rs b/src/domain/account/entity.rs index 910ea7b..bc6a24b 100644 --- a/src/domain/account/entity.rs +++ b/src/domain/account/entity.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Context, Error, Result}; use lettre::transport::smtp::authentication::Credentials as SmtpCredentials; -use log::debug; +use log::{debug, trace}; use std::{convert::TryFrom, env, fs, path::PathBuf}; use crate::{domain::config::entity::Config, output::utils::run_cmd}; @@ -210,6 +210,7 @@ impl<'a> TryFrom<(&'a Config, Option<&str>)> for Account { type Error = Error; fn try_from((config, account_name): (&'a Config, Option<&str>)) -> Result { + debug!("init account `{}`", account_name.unwrap_or("default")); let (name, account) = match account_name { Some("") | None => config .accounts @@ -265,7 +266,7 @@ impl<'a> TryFrom<(&'a Config, Option<&str>)> for Account { .map(|sig| format!("\n{}{}", signature_delim, sig)) .unwrap_or_default(); - Ok(Account { + let account = Account { name, from: account.name.as_ref().unwrap_or(&config.name).to_owned(), downloads_dir, @@ -291,6 +292,9 @@ impl<'a> TryFrom<(&'a Config, Option<&str>)> for Account { smtp_insecure: account.smtp_insecure.unwrap_or_default(), smtp_login: account.smtp_login.to_owned(), smtp_passwd_cmd: account.smtp_passwd_cmd.to_owned(), - }) + }; + + trace!("{:#?}", account); + Ok(account) } } diff --git a/src/domain/config/entity.rs b/src/domain/config/entity.rs index d82c20d..ddf83db 100644 --- a/src/domain/config/entity.rs +++ b/src/domain/config/entity.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Context, Error, Result}; -use log::debug; +use log::{debug, trace}; use serde::Deserialize; use shellexpand; use std::{collections::HashMap, convert::TryFrom, env, fs, path::PathBuf, thread}; @@ -284,12 +284,16 @@ impl Config { } } -impl TryFrom for Config { +impl TryFrom> for Config { type Error = Error; - fn try_from(path: PathBuf) -> Result { - let file_content = fs::read_to_string(path).context("cannot read config file")?; - Ok(toml::from_str(&file_content).context("cannot parse config file")?) + fn try_from(path: Option<&str>) -> Result { + debug!("init config from `{:?}`", path); + let path = path.map(|s| s.into()).unwrap_or(Config::path()?); + let content = fs::read_to_string(path).context("cannot read config file")?; + let config = toml::from_str(&content).context("cannot parse config file")?; + trace!("{:#?}", config); + Ok(config) } } diff --git a/src/domain/imap/service.rs b/src/domain/imap/service.rs index 0b12de7..20bc0f9 100644 --- a/src/domain/imap/service.rs +++ b/src/domain/imap/service.rs @@ -5,7 +5,9 @@ use native_tls::{self, TlsConnector, TlsStream}; use std::{collections::HashSet, convert::TryFrom, iter::FromIterator, net::TcpStream}; use crate::{ - domain::{account::entity::Account, config::entity::Config, msg::entity::Msg}, + domain::{ + account::entity::Account, config::entity::Config, mbox::entity::Mbox, msg::entity::Msg, + }, flag::model::Flags, }; @@ -25,7 +27,7 @@ pub trait ImapServiceInterface { page: &usize, ) -> Result>; fn get_msg(&mut self, uid: &str) -> Result; - fn append_msg(&mut self, mbox: &str, msg: &mut Msg) -> Result<()>; + fn append_msg(&mut self, mbox: &Mbox, msg: &mut Msg) -> Result<()>; fn add_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()>; fn set_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()>; fn remove_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()>; @@ -35,20 +37,11 @@ pub trait ImapServiceInterface { pub struct ImapService<'a> { account: &'a Account, - mbox: &'a str, + mbox: &'a Mbox, sess: Option, } impl<'a> ImapService<'a> { - pub fn new(account: &'a Account, mbox: &'a str) -> Result { - debug!("create new service"); - Ok(Self { - account, - mbox, - sess: None, - }) - } - fn sess(&mut self) -> Result<&mut ImapSession> { if let None = self.sess { debug!("create TLS builder"); @@ -116,8 +109,8 @@ impl<'a> ImapServiceInterface for ImapService<'a> { let mbox = self.mbox.to_owned(); let last_seq = self .sess()? - .select(mbox) - .context(format!("cannot select mailbox `{}`", self.mbox))? + .select(&mbox.name) + .context(format!("cannot select mailbox `{}`", self.mbox.name))? .exists as i64; if last_seq == 0 { @@ -150,8 +143,8 @@ impl<'a> ImapServiceInterface for ImapService<'a> { ) -> Result> { let mbox = self.mbox.to_owned(); self.sess()? - .select(mbox) - .context(format!("cannot select mailbox `{}`", self.mbox))?; + .select(&mbox.name) + .context(format!("cannot select mailbox `{}`", self.mbox.name))?; let begin = page * page_size; let end = begin + (page_size - 1); @@ -160,7 +153,7 @@ impl<'a> ImapServiceInterface for ImapService<'a> { .search(query) .context(format!( "cannot search in `{}` with query `{}`", - self.mbox, query + self.mbox.name, query ))? .iter() .map(|seq| seq.to_string()) @@ -182,8 +175,8 @@ impl<'a> ImapServiceInterface for ImapService<'a> { fn get_msg(&mut self, uid: &str) -> Result { let mbox = self.mbox.to_owned(); self.sess()? - .select(mbox) - .context(format!("cannot select mbox `{}`", self.mbox))?; + .select(&mbox.name) + .context(format!("cannot select mbox `{}`", self.mbox.name))?; match self .sess()? .uid_fetch(uid, "(FLAGS BODY[] ENVELOPE INTERNALDATE)") @@ -195,14 +188,14 @@ impl<'a> ImapServiceInterface for ImapService<'a> { } } - fn append_msg(&mut self, mbox: &str, msg: &mut Msg) -> Result<()> { + fn append_msg(&mut self, mbox: &Mbox, msg: &mut Msg) -> Result<()> { let body = msg.into_bytes()?; let flags: HashSet> = (*msg.flags).clone(); self.sess()? - .append(mbox, &body) + .append(&mbox.name, &body) .flags(flags) .finish() - .context(format!("cannot append message to `{}`", mbox))?; + .context(format!("cannot append message to `{}`", mbox.name))?; Ok(()) } @@ -230,8 +223,8 @@ impl<'a> ImapServiceInterface for ImapService<'a> { let mbox = self.mbox.to_owned(); let flags: String = flags.to_string(); self.sess()? - .select(mbox) - .context(format!("cannot select mbox `{}`", self.mbox))?; + .select(&mbox.name) + .context(format!("cannot select mbox `{}`", self.mbox.name))?; self.sess()? .uid_store(uid_seq, format!("+FLAGS ({})", flags)) .context(format!("cannot add flags `{}`", &flags))?; @@ -263,8 +256,8 @@ impl<'a> ImapServiceInterface for ImapService<'a> { let mbox = self.mbox.to_owned(); let flags: String = flags.to_string(); self.sess()? - .select(mbox) - .context(format!("cannot select mailbox `{}`", self.mbox))?; + .select(&mbox.name) + .context(format!("cannot select mailbox `{}`", self.mbox.name))?; self.sess()? .uid_store(uid_seq, format!("FLAGS ({})", flags)) .context(format!("cannot set flags `{}`", &flags))?; @@ -277,8 +270,8 @@ impl<'a> ImapServiceInterface for ImapService<'a> { let mbox = self.mbox.to_owned(); let flags = flags.to_string(); self.sess()? - .select(mbox) - .context(format!("cannot select mailbox `{}`", self.mbox))?; + .select(&mbox.name) + .context(format!("cannot select mailbox `{}`", self.mbox.name))?; self.sess()? .uid_store(uid_seq, format!("-FLAGS ({})", flags)) .context(format!("cannot remove flags `{}`", &flags))?; @@ -288,17 +281,17 @@ impl<'a> ImapServiceInterface for ImapService<'a> { fn expunge(&mut self) -> Result<()> { self.sess()? .expunge() - .context(format!("cannot expunge `{}`", self.mbox))?; + .context(format!("cannot expunge `{}`", self.mbox.name))?; Ok(()) } fn notify(&mut self, config: &Config, keepalive: u64) -> Result<()> { let mbox = self.mbox.to_owned(); - debug!("examine mailbox: {}", mbox); + debug!("examine mailbox: {}", mbox.name); self.sess()? - .examine(mbox) - .context(format!("cannot examine mailbox `{}`", &self.mbox))?; + .examine(&mbox.name) + .context(format!("cannot examine mailbox `{}`", &self.mbox.name))?; debug!("init messages hashset"); let mut msgs_set: HashSet = @@ -361,12 +354,12 @@ impl<'a> ImapServiceInterface for ImapService<'a> { } fn watch(&mut self, keepalive: u64) -> Result<()> { - debug!("examine mailbox: {}", &self.mbox); + debug!("examine mailbox: {}", &self.mbox.name); let mbox = self.mbox.to_owned(); self.sess()? - .examine(mbox) - .context(format!("cannot examine mailbox `{}`", &self.mbox))?; + .examine(&mbox.name) + .context(format!("cannot examine mailbox `{}`", &self.mbox.name))?; loop { debug!("begin loop"); @@ -396,6 +389,12 @@ impl<'a> ImapServiceInterface for ImapService<'a> { } } -//impl<'a> ImapConnector<'a> { - -//} +impl<'a> From<(&'a Account, &'a Mbox)> for ImapService<'a> { + fn from((account, mbox): (&'a Account, &'a Mbox)) -> Self { + Self { + account, + mbox, + sess: None, + } + } +} diff --git a/src/domain/mbox/entity.rs b/src/domain/mbox/entity.rs index 748c369..1541a16 100644 --- a/src/domain/mbox/entity.rs +++ b/src/domain/mbox/entity.rs @@ -1,11 +1,13 @@ +use anyhow::{anyhow, Error, Result}; use imap::types::NameAttribute; +use log::debug; use serde::{ ser::{self, SerializeSeq}, Serialize, }; -use std::borrow::Cow; use std::collections::HashSet; use std::fmt; +use std::{borrow::Cow, convert::TryFrom}; use crate::ui::table::{Cell, Row, Table}; @@ -99,6 +101,41 @@ pub struct Mbox { pub attributes: Attributes, } +impl Mbox { + pub fn new>(name: S) -> Self { + Self { + name: name.as_ref().to_owned(), + ..Self::default() + } + } +} + +impl Default for Mbox { + fn default() -> Self { + Self { + delim: String::default(), + name: String::default(), + attributes: Attributes::from(&[] as &[NameAttribute]), + } + } +} + +impl fmt::Display for Mbox { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl From<&str> for Mbox { + fn from(mbox: &str) -> Self { + debug!("init mailbox from `{:?}`", mbox); + Self { + name: mbox.to_owned(), + ..Self::default() + } + } +} + impl<'a> From<&'a imap::types::Name> for Mbox { fn from(name: &'a imap::types::Name) -> Self { Self { @@ -109,6 +146,18 @@ impl<'a> From<&'a imap::types::Name> for Mbox { } } +impl TryFrom> for Mbox { + type Error = Error; + + fn try_from(mbox: Option<&str>) -> Result { + debug!("init mailbox from `{:?}`", mbox); + Ok(Self { + name: mbox.ok_or(anyhow!("cannot parse mailbox"))?.to_string(), + ..Self::default() + }) + } +} + impl Table for Mbox { fn head() -> Row { Row::new() diff --git a/src/domain/msg/cli.rs b/src/domain/msg/cli.rs index 4d4b714..f1ca704 100644 --- a/src/domain/msg/cli.rs +++ b/src/domain/msg/cli.rs @@ -20,7 +20,9 @@ use super::{ }; use crate::{ domain::{ - account::entity::Account, imap::service::ImapServiceInterface, mbox::cli::mbox_target_arg, + account::entity::Account, + imap::service::ImapServiceInterface, + mbox::{cli::mbox_target_arg, entity::Mbox}, smtp::service::SmtpServiceInterface, }, flag::model::Flags, @@ -130,7 +132,7 @@ pub fn subcmds<'a>() -> Vec> { pub fn matches( arg_matches: &clap::ArgMatches, - mbox: &str, + mbox: &Mbox, account: &Account, output: &OutputService, imap: &mut ImapService, @@ -578,14 +580,13 @@ fn msg_matches_copy( // fetch the message to be copyied let uid = matches.value_of("uid").unwrap(); debug!("uid: {}", &uid); - let target = matches.value_of("target").unwrap(); - debug!("target: {}", &target); + let target = Mbox::try_from(matches.value_of("target"))?; let mut msg = imap.get_msg(&uid)?; // the message, which will be in the new mailbox doesn't need to be seen msg.flags.insert(Flag::Seen); - imap.append_msg(target, &mut msg)?; + imap.append_msg(&target, &mut msg)?; debug!("message {} successfully copied to folder `{}`", uid, target); @@ -608,13 +609,12 @@ fn msg_matches_move( // fetch the msg which should be moved let uid = matches.value_of("uid").unwrap(); debug!("uid: {}", &uid); - let target = matches.value_of("target").unwrap(); - debug!("target: {}", &target); + let target = Mbox::try_from(matches.value_of("target"))?; let mut msg = imap.get_msg(&uid)?; // create the msg in the target-msgbox msg.flags.insert(Flag::Seen); - imap.append_msg(target, &mut msg)?; + imap.append_msg(&target, &mut msg)?; debug!("message {} successfully moved to folder `{}`", uid, target); output.print(format!( @@ -683,7 +683,8 @@ fn msg_matches_send( - mbox: &str, + mbox: &Mbox, matches: &clap::ArgMatches, imap: &mut ImapService, ) -> Result { @@ -894,7 +895,8 @@ fn msg_interaction { input::remove_draft()?; output.print("Message successfully saved to Drafts")?; diff --git a/src/domain/smtp/service.rs b/src/domain/smtp/service.rs index c5b969f..f750876 100644 --- a/src/domain/smtp/service.rs +++ b/src/domain/smtp/service.rs @@ -7,6 +7,7 @@ use lettre::{ }, Transport, }; +use log::debug; use crate::domain::account::entity::Account; @@ -20,13 +21,6 @@ pub struct SmtpService<'a> { } impl<'a> SmtpService<'a> { - pub fn new(account: &'a Account) -> Result { - Ok(Self { - account, - transport: None, - }) - } - fn transport(&mut self) -> Result<&SmtpTransport> { if let Some(ref transport) = self.transport { Ok(transport) @@ -66,3 +60,13 @@ impl<'a> SmtpServiceInterface for SmtpService<'a> { Ok(()) } } + +impl<'a> From<&'a Account> for SmtpService<'a> { + fn from(account: &'a Account) -> Self { + debug!("init SMTP service"); + Self { + account, + transport: None, + } + } +} diff --git a/src/main.rs b/src/main.rs index c8b23e3..a81a86b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ use anyhow::Result; use clap; use env_logger; -use log::{debug, trace}; -use std::{convert::TryFrom, env, path::PathBuf}; +use std::{convert::TryFrom, env}; use himalaya::{ compl, @@ -11,7 +10,8 @@ use himalaya::{ account::entity::Account, config::entity::Config, imap::{self, service::ImapService}, - mbox, msg, + mbox::{self, entity::Mbox}, + msg, smtp::service::SmtpService, }, flag, @@ -35,13 +35,13 @@ fn create_app<'a>() -> clap::App<'a, 'a> { } fn main() -> Result<()> { + // Init env logger env_logger::init_from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "off"), ); + // TODO: put in a `mailto` module // let raw_args: Vec = env::args().collect(); - - // // This is used if you click on a mailaddress in the webbrowser // if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") { // let config = Config::new(None)?; // let account = config.find_account_by_name(None)?.clone(); @@ -59,41 +59,21 @@ fn main() -> Result<()> { // Check shell completion BEFORE any entity or service initialization. if let Some(compl::arg::Match::Generate(shell)) = compl::arg::matches(&m)? { - let app = create_app(); - compl::handler::generate(shell, app)?; - return Ok(()); + return compl::handler::generate(shell, create_app()); } - let output = OutputService::new(m.value_of("output").unwrap())?; + let mbox = Mbox::try_from(m.value_of("mailbox"))?; + let config = Config::try_from(m.value_of("config"))?; + let account = Account::try_from((&config, m.value_of("account")))?; + let output = OutputService::try_from(m.value_of("output"))?; + let mut imap = ImapService::from((&account, &mbox)); + let mut smtp = SmtpService::from(&account); - debug!("init mbox"); - let mbox = m.value_of("mailbox").unwrap(); - debug!("mbox: {}", mbox); - - let config_path: PathBuf = m - .value_of("config") - .map(|s| s.into()) - .unwrap_or(Config::path()?); - debug!("init config from `{:?}`", config_path); - let config = Config::try_from(config_path.clone())?; - trace!("{:#?}", config); - - let account_name = m.value_of("account"); - debug!("init account `{}`", account_name.unwrap_or("default")); - let account = Account::try_from((&config, account_name))?; - trace!("{:#?}", account); - - debug!("init IMAP service"); - let mut imap = ImapService::new(&account, &mbox)?; - - debug!("init SMTP service"); - let mut smtp = SmtpService::new(&account)?; - - debug!("begin matching"); + // TODO: use same system as compl let _matched = mbox::cli::matches(&m, &output, &mut imap)? || flag::cli::matches(&m, &mut imap)? || imap::cli::matches(&m, &config, &mut imap)? - || msg::cli::matches(&m, mbox, &account, &output, &mut imap, &mut smtp)?; + || msg::cli::matches(&m, &mbox, &account, &output, &mut imap, &mut smtp)?; Ok(()) } diff --git a/src/output/service.rs b/src/output/service.rs index 21c88b0..b05c371 100644 --- a/src/output/service.rs +++ b/src/output/service.rs @@ -1,7 +1,10 @@ use anyhow::{anyhow, Error, Result}; use log::debug; use serde::Serialize; -use std::{fmt, str::FromStr}; +use std::{ + convert::{TryFrom, TryInto}, + fmt, +}; #[derive(Debug, PartialEq)] pub enum OutputFmt { @@ -9,13 +12,15 @@ pub enum OutputFmt { Json, } -impl FromStr for OutputFmt { - type Err = Error; - fn from_str(slice: &str) -> Result { - match slice { - "JSON" | _ if slice.eq_ignore_ascii_case("json") => Ok(Self::Json), - "PLAIN" | _ if slice.eq_ignore_ascii_case("plain") => Ok(Self::Plain), - _ => Err(anyhow!("cannot parse output `{}`", slice)), +impl TryFrom> for OutputFmt { + type Error = Error; + + fn try_from(fmt: Option<&str>) -> Result { + match fmt { + Some(slice) if slice.eq_ignore_ascii_case("json") => Ok(Self::Json), + Some(slice) if slice.eq_ignore_ascii_case("plain") => Ok(Self::Plain), + None => Ok(Self::Plain), + Some(slice) => Err(anyhow!("cannot parse output `{}`", slice)), } } } @@ -55,9 +60,7 @@ pub struct OutputService { impl OutputService { /// Create a new output-handler by setting the given formatting style. pub fn new(slice: &str) -> Result { - debug!("init output service"); - debug!("output: {}", slice); - let fmt = OutputFmt::from_str(slice)?; + let fmt = OutputFmt::try_from(Some(slice))?; Ok(Self { fmt }) } @@ -95,3 +98,14 @@ impl Default for OutputService { } } } + +impl TryFrom> for OutputService { + type Error = Error; + + fn try_from(fmt: Option<&str>) -> Result { + debug!("init output service"); + debug!("output: `{:?}`", fmt); + let fmt = fmt.try_into()?; + Ok(Self { fmt }) + } +}