diff --git a/src/ctx.rs b/src/ctx.rs deleted file mode 100644 index ef4c25a4..00000000 --- a/src/ctx.rs +++ /dev/null @@ -1,31 +0,0 @@ -use clap; - -use crate::{domain::config::entity::Config, output::model::Output}; - -/// `Ctx` stands for `Context` and includes the most "important" structs which are used quite often -/// in this crate. -#[derive(Debug, Default, Clone)] -pub struct Ctx<'a> { - pub config: Config, - pub output: Output, - pub mbox: String, - pub arg_matches: clap::ArgMatches<'a>, -} - -impl<'a> Ctx<'a> { - pub fn new( - config: Config, - output: Output, - mbox: S, - arg_matches: clap::ArgMatches<'a>, - ) -> Self { - let mbox = mbox.to_string(); - - Self { - config, - output, - mbox, - arg_matches, - } - } -} diff --git a/src/imap/model.rs b/src/domain/imap.rs similarity index 57% rename from src/imap/model.rs rename to src/domain/imap.rs index aea75e87..093fc69f 100644 --- a/src/imap/model.rs +++ b/src/domain/imap.rs @@ -4,110 +4,204 @@ use log::{debug, trace}; use native_tls::{self, TlsConnector, TlsStream}; use std::{collections::HashSet, convert::TryFrom, iter::FromIterator, net::TcpStream}; -use crate::{ctx::Ctx, domain::account::entity::Account, flag::model::Flags, msg::model::Msg}; +use crate::{domain::account::entity::Account, flag::model::Flags, msg::model::Msg}; -/// A little helper function to create a similiar error output. (to avoid duplicated code) -fn format_err_msg(description: &str, account: &Account) -> String { - format!("{}. Your account settings: \n{:#?}", description, account) +use super::config::entity::Config; + +type ImapSession = imap::Session>; +type ImapMsgs = imap::types::ZeroCopy>; +type ImapMboxes = imap::types::ZeroCopy>; + +pub trait ImapServiceInterface { + fn notify(&mut self, config: &Config, keepalive: u64) -> Result<()>; + fn watch(&mut self, keepalive: u64) -> Result<()>; + fn list_mboxes(&mut self) -> Result; + fn list_msgs(&mut self, page_size: &usize, page: &usize) -> Result>; + fn search_msgs( + &mut self, + query: &str, + page_size: &usize, + page: &usize, + ) -> Result>; + fn get_msg(&mut self, uid: &str) -> Result; + fn append_msg(&mut self, mbox: &str, 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<()>; + fn expunge(&mut self) -> Result<()>; + fn logout(&mut self) -> Result<()>; } -/// The main struct to create a connection to your imap-server. -/// -/// # Example -/// ```no_run -/// use himalaya::imap::model::ImapConnector; -/// use himalaya::config::model::Account; -/// -/// fn main() { -/// let account = Account::default(); -/// let mut imap_conn = ImapConnector::new(&account).unwrap(); -/// -/// // do you stuff with the connection... -/// -/// // Be nice to the server and say 'Bye!' -/// imap_conn.logout(); -/// } -/// ``` -#[derive(Debug)] -pub struct ImapConnector<'a> { - pub account: &'a Account, - pub sess: imap::Session>, +pub struct ImapService<'a> { + account: &'a Account, + mbox: &'a str, + sess: Option, } -impl<'a> ImapConnector<'a> { - /// Creates a new connection with the settings of the given account. - /// - /// Please call the `logout` method below if you don't need the connection anymore! Be nice - /// to the server ;) - pub fn new(account: &'a Account) -> Result { - debug!("create TLS builder"); - let ssl_conn = TlsConnector::builder() - .danger_accept_invalid_certs(account.imap_insecure) - .danger_accept_invalid_hostnames(account.imap_insecure) - .build() - .context(format_err_msg("cannot create TLS connector", account))?; - - debug!("create client"); - let mut client_builder = imap::ClientBuilder::new(&account.imap_host, account.imap_port); - if account.imap_starttls { - debug!("enable STARTTLS"); - client_builder.starttls(); - } - let client = client_builder - .connect(|domain, tcp| Ok(TlsConnector::connect(&ssl_conn, domain, tcp)?)) - .context(format_err_msg("cannot connect to IMAP server", account))?; - - debug!("create session"); - let sess = client - .login(&account.imap_login, &account.imap_passwd()?) - .map_err(|res| res.0) - .context(format_err_msg("cannot login to IMAP server", account))?; - - Ok(Self { account, sess }) +impl<'a> ImapService<'a> { + pub fn new(account: &'a Account, mbox: &'a str) -> Result { + debug!("create new service"); + Ok(Self { + account, + mbox, + sess: None, + }) } - /// Closes the connection. - /// - /// Always call this if you don't need the connection anymore! - pub fn logout(&mut self) { - debug!("logout"); - match self.sess.logout() { - _ => (), + fn sess(&mut self) -> Result<&mut ImapSession> { + if let None = self.sess { + debug!("create TLS builder"); + debug!("insecure: {}", self.account.imap_insecure); + let builder = TlsConnector::builder() + .danger_accept_invalid_certs(self.account.imap_insecure) + .danger_accept_invalid_hostnames(self.account.imap_insecure) + .build() + .context("cannot create TLS connector")?; + + debug!("create client"); + debug!("host: {}", self.account.imap_host); + debug!("port: {}", self.account.imap_port); + debug!("starttls: {}", self.account.imap_starttls); + let mut client_builder = + imap::ClientBuilder::new(&self.account.imap_host, self.account.imap_port); + if self.account.imap_starttls { + client_builder.starttls(); + } + let client = client_builder + .connect(|domain, tcp| Ok(TlsConnector::connect(&builder, domain, tcp)?)) + .context("cannot connect to IMAP server")?; + + debug!("create session"); + debug!("login: {}", self.account.imap_login); + debug!("passwd cmd: {}", self.account.imap_passwd_cmd); + self.sess = Some( + client + .login(&self.account.imap_login, &self.account.imap_passwd()?) + .map_err(|res| res.0) + .context("cannot login to IMAP server")?, + ); + } + + match self.sess { + Some(ref mut sess) => Ok(sess), + None => Err(anyhow!("cannot get IMAP session")), } } - /// Applies the given flags to the msg. - /// - /// # Example - /// ```no_run - /// use himalaya::imap::model::ImapConnector; - /// use himalaya::config::model::Account; - /// use himalaya::flag::model::Flags; - /// use imap::types::Flag; - /// - /// fn main() { - /// let account = Account::default(); - /// let mut imap_conn = ImapConnector::new(&account).unwrap(); - /// let flags = Flags::from(vec![Flag::Seen]); - /// - /// // Mark the message with the UID 42 in the mailbox "rofl" as "Seen" and wipe all other - /// // flags - /// imap_conn.set_flags("rofl", "42", flags).unwrap(); - /// - /// imap_conn.logout(); - /// } - /// ``` - pub fn set_flags(&mut self, mbox: &str, uid_seq: &str, flags: Flags) -> Result<()> { - let flags: String = flags.to_string(); + fn search_new_msgs(&mut self) -> Result> { + let uids: Vec = self + .sess()? + .uid_search("NEW") + .context("cannot search new messages")? + .into_iter() + .collect(); + debug!("found {} new messages", uids.len()); + trace!("uids: {:?}", uids); - self.sess + Ok(uids) + } +} + +impl<'a> ImapServiceInterface for ImapService<'a> { + fn list_mboxes(&mut self) -> Result { + let mboxes = self + .sess()? + .list(Some(""), Some("*")) + .context("cannot list mailboxes")?; + Ok(mboxes) + } + + fn list_msgs(&mut self, page_size: &usize, page: &usize) -> Result> { + let mbox = self.mbox.to_owned(); + let last_seq = self + .sess()? .select(mbox) - .context(format!("cannot select mailbox `{}`", mbox))?; + .context(format!("cannot select mailbox `{}`", self.mbox))? + .exists as i64; - self.sess - .uid_store(uid_seq, format!("FLAGS ({})", flags)) - .context(format!("cannot set flags `{}`", &flags))?; + if last_seq == 0 { + return Ok(None); + } + // TODO: add tests, improve error management when empty page + let range = if page_size > &0 { + let cursor = (page * page_size) as i64; + let begin = 1.max(last_seq - cursor); + let end = begin - begin.min(*page_size as i64) + 1; + format!("{}:{}", begin, end) + } else { + String::from("1:*") + }; + + let fetches = self + .sess()? + .fetch(range, "(UID FLAGS ENVELOPE INTERNALDATE)") + .context("cannot fetch messages")?; + + Ok(Some(fetches)) + } + + fn search_msgs( + &mut self, + query: &str, + page_size: &usize, + page: &usize, + ) -> Result> { + let mbox = self.mbox.to_owned(); + self.sess()? + .select(mbox) + .context(format!("cannot select mailbox `{}`", self.mbox))?; + + let begin = page * page_size; + let end = begin + (page_size - 1); + let uids: Vec = self + .sess()? + .search(query) + .context(format!( + "cannot search in `{}` with query `{}`", + self.mbox, query + ))? + .iter() + .map(|seq| seq.to_string()) + .collect(); + + if uids.is_empty() { + return Ok(None); + } + + let range = uids[begin..end.min(uids.len())].join(","); + let fetches = self + .sess()? + .fetch(&range, "(UID FLAGS ENVELOPE INTERNALDATE)") + .context(format!("cannot fetch range `{}`", &range))?; + + Ok(Some(fetches)) + } + /// Get the message according to the given `mbox` and `uid`. + 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))?; + match self + .sess()? + .uid_fetch(uid, "(FLAGS BODY[] ENVELOPE INTERNALDATE)") + .context("cannot fetch bodies")? + .first() + { + None => Err(anyhow!("cannot find message `{}`", uid)), + Some(fetch) => Ok(Msg::try_from(fetch)?), + } + } + + fn append_msg(&mut self, mbox: &str, msg: &mut Msg) -> Result<()> { + let body = msg.into_bytes()?; + let flags: HashSet> = (*msg.flags).clone(); + self.sess()? + .append(mbox, &body) + .flags(flags) + .finish() + .context(format!("cannot append message to `{}`", mbox))?; Ok(()) } @@ -131,54 +225,79 @@ impl<'a> ImapConnector<'a> { /// imap_conn.logout(); /// } /// ``` - pub fn add_flags(&mut self, mbox: &str, uid_seq: &str, flags: Flags) -> Result<()> { + fn add_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()> { + let mbox = self.mbox.to_owned(); let flags: String = flags.to_string(); - - self.sess + self.sess()? .select(mbox) - .context(format!("cannot select mailbox `{}`", mbox))?; - - self.sess + .context(format!("cannot select mbox `{}`", self.mbox))?; + self.sess()? .uid_store(uid_seq, format!("+FLAGS ({})", flags)) .context(format!("cannot add flags `{}`", &flags))?; + Ok(()) + } + /// Applies the given flags to the msg. + /// + /// # Example + /// ```no_run + /// use himalaya::imap::model::ImapConnector; + /// use himalaya::config::model::Account; + /// use himalaya::flag::model::Flags; + /// use imap::types::Flag; + /// + /// fn main() { + /// let account = Account::default(); + /// let mut imap_conn = ImapConnector::new(&account).unwrap(); + /// let flags = Flags::from(vec![Flag::Seen]); + /// + /// // Mark the message with the UID 42 in the mailbox "rofl" as "Seen" and wipe all other + /// // flags + /// imap_conn.set_flags("rofl", "42", flags).unwrap(); + /// + /// imap_conn.logout(); + /// } + /// ``` + fn set_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()> { + let mbox = self.mbox.to_owned(); + let flags: String = flags.to_string(); + self.sess()? + .select(mbox) + .context(format!("cannot select mailbox `{}`", self.mbox))?; + self.sess()? + .uid_store(uid_seq, format!("FLAGS ({})", flags)) + .context(format!("cannot set flags `{}`", &flags))?; Ok(()) } /// Remove the flags to the message by the given information. Take a look on the example above. /// It's pretty similar. - pub fn remove_flags(&mut self, mbox: &str, uid_seq: &str, flags: Flags) -> Result<()> { + fn remove_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()> { + let mbox = self.mbox.to_owned(); let flags = flags.to_string(); - - self.sess + self.sess()? .select(mbox) - .context(format!("cannot select mailbox `{}`", mbox))?; - - self.sess + .context(format!("cannot select mailbox `{}`", self.mbox))?; + self.sess()? .uid_store(uid_seq, format!("-FLAGS ({})", flags)) .context(format!("cannot remove flags `{}`", &flags))?; - Ok(()) } - fn search_new_msgs(&mut self) -> Result> { - let uids: Vec = self - .sess - .uid_search("NEW") - .context("cannot search new messages")? - .into_iter() - .collect(); - debug!("found {} new messages", uids.len()); - trace!("uids: {:?}", uids); - - Ok(uids) + fn expunge(&mut self) -> Result<()> { + self.sess()? + .expunge() + .context(format!("cannot expunge `{}`", self.mbox))?; + Ok(()) } - pub fn notify(&mut self, ctx: &Ctx, keepalive: u64) -> Result<()> { - debug!("examine mailbox: {}", &ctx.mbox); - self.sess - .examine(&ctx.mbox) - .context(format!("cannot examine mailbox `{}`", &ctx.mbox))?; + fn notify(&mut self, config: &Config, keepalive: u64) -> Result<()> { + let mbox = self.mbox.to_owned(); + + debug!("examine mailbox: {}", mbox); + self.sess()? + .examine(mbox) + .context(format!("cannot examine mailbox `{}`", &self.mbox))?; debug!("init messages hashset"); let mut msgs_set: HashSet = @@ -187,7 +306,7 @@ impl<'a> ImapConnector<'a> { loop { debug!("begin loop"); - self.sess + self.sess()? .idle() .and_then(|mut idle| { idle.set_keepalive(std::time::Duration::new(keepalive, 0)); @@ -214,7 +333,7 @@ impl<'a> ImapConnector<'a> { .collect::>() .join(","); let fetches = self - .sess + .sess()? .uid_fetch(uids, "(ENVELOPE)") .context("cannot fetch new messages enveloppe")?; @@ -225,7 +344,7 @@ impl<'a> ImapConnector<'a> { })?; let subject = msg.headers.subject.clone().unwrap_or_default(); - ctx.config.run_notify_cmd(&subject, &msg.headers.from[0])?; + config.run_notify_cmd(&subject, &msg.headers.from[0])?; debug!("notify message: {}", uid); trace!("message: {:?}", msg); @@ -240,15 +359,17 @@ impl<'a> ImapConnector<'a> { } } - pub fn watch(&mut self, ctx: &Ctx, keepalive: u64) -> Result<()> { - debug!("examine mailbox: {}", &ctx.mbox); - self.sess - .examine(&ctx.mbox) - .context(format!("cannot examine mailbox `{}`", &ctx.mbox))?; + fn watch(&mut self, keepalive: u64) -> Result<()> { + debug!("examine mailbox: {}", &self.mbox); + let mbox = self.mbox.to_owned(); + + self.sess()? + .examine(mbox) + .context(format!("cannot examine mailbox `{}`", &self.mbox))?; loop { debug!("begin loop"); - self.sess + self.sess()? .idle() .and_then(|mut idle| { idle.set_keepalive(std::time::Duration::new(keepalive, 0)); @@ -265,122 +386,15 @@ impl<'a> ImapConnector<'a> { } } - pub fn list_mboxes(&mut self) -> Result>> { - let names = self - .sess - .list(Some(""), Some("*")) - .context("cannot list mailboxes")?; - - Ok(names) - } - - pub fn list_msgs( - &mut self, - mbox: &str, - page_size: &usize, - page: &usize, - ) -> Result>>> { - let last_seq = self - .sess - .select(mbox) - .context(format!("cannot select mailbox `{}`", mbox))? - .exists as i64; - - if last_seq == 0 { - return Ok(None); - } - - // TODO: add tests, improve error management when empty page - let range = if page_size > &0 { - let cursor = (page * page_size) as i64; - let begin = 1.max(last_seq - cursor); - let end = begin - begin.min(*page_size as i64) + 1; - format!("{}:{}", begin, end) - } else { - String::from("1:*") - }; - - let fetches = self - .sess - .fetch(range, "(UID FLAGS ENVELOPE INTERNALDATE)") - .context("cannot fetch messages")?; - - Ok(Some(fetches)) - } - - pub fn search_msgs( - &mut self, - mbox: &str, - query: &str, - page_size: &usize, - page: &usize, - ) -> Result>>> { - self.sess - .select(mbox) - .context(format!("cannot select mailbox `{}`", mbox))?; - - let begin = page * page_size; - let end = begin + (page_size - 1); - let uids: Vec = self - .sess - .search(query) - .context(format!( - "cannot search in `{}` with query `{}`", - mbox, query - ))? - .iter() - .map(|seq| seq.to_string()) - .collect(); - - if uids.is_empty() { - return Ok(None); - } - - let range = uids[begin..end.min(uids.len())].join(","); - let fetches = self - .sess - .fetch(&range, "(UID FLAGS ENVELOPE INTERNALDATE)") - .context(format!("cannot fetch range `{}`", &range))?; - - Ok(Some(fetches)) - } - - /// Get the message according to the given `mbox` and `uid`. - pub fn get_msg(&mut self, mbox: &str, uid: &str) -> Result { - self.sess - .select(mbox) - .context(format!("cannot select mailbox `{}`", mbox))?; - - match self - .sess - .uid_fetch(uid, "(FLAGS BODY[] ENVELOPE INTERNALDATE)") - .context("cannot fetch bodies")? - .first() - { - None => Err(anyhow!(format!("cannot find message `{}`", uid))), - Some(fetch) => Ok(Msg::try_from(fetch)?), - } - } - - /// Append the given `msg` to `mbox`. - pub fn append_msg(&mut self, mbox: &str, msg: &mut Msg) -> Result<()> { - let body = msg.into_bytes()?; - let flags: HashSet> = (*msg.flags).clone(); - - self.sess - .append(mbox, &body) - .flags(flags) - .finish() - .context(format!("cannot append message to `{}`", mbox))?; - - Ok(()) - } - - pub fn expunge(&mut self, mbox: &str) -> Result<()> { - self.sess - .expunge() - .context(format!("cannot expunge `{}`", mbox))?; - + fn logout(&mut self) -> Result<()> { + debug!("logout from IMAP server"); + self.sess()? + .logout() + .context("cannot logout from IMAP server")?; Ok(()) } } + +//impl<'a> ImapConnector<'a> { + +//} diff --git a/src/domain/mod.rs b/src/domain/mod.rs index 6a34d0b3..d6903682 100644 --- a/src/domain/mod.rs +++ b/src/domain/mod.rs @@ -2,4 +2,5 @@ pub mod account; pub mod config; +pub mod imap; pub mod smtp; diff --git a/src/flag/cli.rs b/src/flag/cli.rs index 91655796..2573d34a 100644 --- a/src/flag/cli.rs +++ b/src/flag/cli.rs @@ -2,10 +2,7 @@ use anyhow::Result; use clap; use log::debug; -use crate::{ - ctx::Ctx, domain::account::entity::Account, flag::model::Flags, imap::model::ImapConnector, - msg::cli::uid_arg, -}; +use crate::{domain::imap::ImapServiceInterface, flag::model::Flags, msg::cli::uid_arg}; fn flags_arg<'a>() -> clap::Arg<'a, 'a> { clap::Arg::with_name("flags") @@ -39,8 +36,11 @@ pub fn subcmds<'a>() -> Vec> { )] } -pub fn matches(ctx: &Ctx, account: &Account) -> Result { - if let Some(matches) = ctx.arg_matches.subcommand_matches("set") { +pub fn matches( + arg_matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { + if let Some(matches) = arg_matches.subcommand_matches("set") { debug!("set command matched"); let uid = matches.value_of("uid").unwrap(); @@ -50,14 +50,12 @@ pub fn matches(ctx: &Ctx, account: &Account) -> Result { debug!("flags: {}", flags); let flags = Flags::from(flags); - let mut imap_conn = ImapConnector::new(&account)?; - imap_conn.set_flags(&ctx.mbox, uid, flags)?; - - imap_conn.logout(); + imap.set_flags(uid, flags)?; + imap.logout()?; return Ok(true); } - if let Some(matches) = ctx.arg_matches.subcommand_matches("add") { + if let Some(matches) = arg_matches.subcommand_matches("add") { debug!("add command matched"); let uid = matches.value_of("uid").unwrap(); @@ -67,14 +65,12 @@ pub fn matches(ctx: &Ctx, account: &Account) -> Result { debug!("flags: {}", flags); let flags = Flags::from(flags); - let mut imap_conn = ImapConnector::new(&account)?; - imap_conn.add_flags(&ctx.mbox, uid, flags)?; - - imap_conn.logout(); + imap.add_flags(uid, flags)?; + imap.logout()?; return Ok(true); } - if let Some(matches) = ctx.arg_matches.subcommand_matches("remove") { + if let Some(matches) = arg_matches.subcommand_matches("remove") { debug!("remove command matched"); let uid = matches.value_of("uid").unwrap(); @@ -84,10 +80,8 @@ pub fn matches(ctx: &Ctx, account: &Account) -> Result { debug!("flags: {}", flags); let flags = Flags::from(flags); - let mut imap_conn = ImapConnector::new(&account)?; - imap_conn.remove_flags(&ctx.mbox, uid, flags)?; - - imap_conn.logout(); + imap.remove_flags(uid, flags)?; + imap.logout()?; return Ok(true); } diff --git a/src/imap/cli.rs b/src/imap/cli.rs index c72ba1dd..e05d5383 100644 --- a/src/imap/cli.rs +++ b/src/imap/cli.rs @@ -2,7 +2,7 @@ use anyhow::Result; use clap; use log::debug; -use crate::{ctx::Ctx, domain::account::entity::Account, imap::model::ImapConnector}; +use crate::domain::{config::entity::Config, imap::ImapServiceInterface}; pub fn subcmds<'a>() -> Vec> { vec![ @@ -30,30 +30,30 @@ pub fn subcmds<'a>() -> Vec> { ] } -pub fn matches(ctx: &Ctx, account: &Account) -> Result { - if let Some(matches) = ctx.arg_matches.subcommand_matches("notify") { +pub fn matches( + arg_matches: &clap::ArgMatches, + config: &Config, + imap: &mut ImapService, +) -> Result { + if let Some(matches) = arg_matches.subcommand_matches("notify") { debug!("notify command matched"); let keepalive = clap::value_t_or_exit!(matches.value_of("keepalive"), u64); debug!("keepalive: {}", &keepalive); - let mut imap_conn = ImapConnector::new(&account)?; - imap_conn.notify(&ctx, keepalive)?; - - imap_conn.logout(); + imap.notify(&config, keepalive)?; + imap.logout()?; return Ok(true); } - if let Some(matches) = ctx.arg_matches.subcommand_matches("watch") { + if let Some(matches) = arg_matches.subcommand_matches("watch") { debug!("watch command matched"); let keepalive = clap::value_t_or_exit!(matches.value_of("keepalive"), u64); debug!("keepalive: {}", &keepalive); - let mut imap_conn = ImapConnector::new(&account)?; - imap_conn.watch(&ctx, keepalive)?; - - imap_conn.logout(); + imap.watch(keepalive)?; + imap.logout()?; return Ok(true); } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 92256774..4f773726 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -1,2 +1 @@ pub mod cli; -pub mod model; diff --git a/src/lib.rs b/src/lib.rs index a6ae5ca9..92033d21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,6 @@ pub mod comp; /// Everything which is related to the config files. For example the structure of your config file. pub mod config; -/// A often used-struct to help us to access the most often used structs. -pub mod ctx; - /// A wrapper for representing a flag of a message or mailbox. For example the delete-flag or /// read-flag. pub mod flag; @@ -43,5 +40,4 @@ pub mod msg; pub mod output; pub mod domain; -pub mod infra; pub mod ui; diff --git a/src/main.rs b/src/main.rs index b030cb99..918395fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,11 @@ use std::{convert::TryFrom, env, path::PathBuf}; use himalaya::{ comp, config::cli::config_args, - ctx::Ctx, - domain::{account::entity::Account, config::entity::Config, smtp::SmtpService}, + domain::{ + account::entity::Account, config::entity::Config, imap::ImapService, smtp::SmtpService, + }, flag, imap, mbox, msg, - output::{cli::output_args, model::Output}, + output::{cli::output_args, service::OutputService}, }; fn parse_args<'a>() -> clap::App<'a, 'a> { @@ -57,37 +58,38 @@ fn main() -> Result<()> { return Ok(()); } - let output = Output::new(arg_matches.value_of("output").unwrap()); - debug!("output: {:?}", output); + debug!("init output service"); + let output = OutputService::new(arg_matches.value_of("output").unwrap()); + debug!("output service: {:?}", output); - debug!("init config"); + debug!("init mbox"); + let mbox = arg_matches.value_of("mailbox").unwrap(); + debug!("mbox: {}", mbox); let config_path: PathBuf = arg_matches .value_of("config") .map(|s| s.into()) .unwrap_or(Config::path()?); - debug!("config path: {:?}", config_path); - + debug!("init config from `{:?}`", config_path); let config = Config::try_from(config_path.clone())?; - trace!("config: {:?}", config); + trace!("{:#?}", config); let account_name = arg_matches.value_of("account"); + debug!("init account `{}`", account_name.unwrap_or("default")); let account = Account::try_from((&config, account_name))?; - let smtp_service = SmtpService::new(&account)?; - debug!("account name: {}", account_name.unwrap_or("default")); - trace!("account: {:?}", account); + trace!("{:#?}", account); - let mbox = arg_matches.value_of("mailbox").unwrap().to_string(); - debug!("mailbox: {}", mbox); + debug!("init IMAP service"); + let mut imap = ImapService::new(&account, &mbox)?; - let ctx = Ctx::new(config, output, mbox, arg_matches); - trace!("context: {:?}", ctx); + debug!("init SMTP service"); + let mut smtp = SmtpService::new(&account)?; debug!("begin matching"); - let _matched = mbox::cli::matches(&ctx, &account)? - || flag::cli::matches(&ctx, &account)? - || imap::cli::matches(&ctx, &account)? - || msg::cli::matches(&ctx, &account, smtp_service)?; + let _matched = mbox::cli::matches(&arg_matches, &output, &mut imap)? + || flag::cli::matches(&arg_matches, &mut imap)? + || imap::cli::matches(&arg_matches, &config, &mut imap)? + || msg::cli::matches(&arg_matches, mbox, &account, &output, &mut imap, &mut smtp)?; Ok(()) } diff --git a/src/mbox/cli.rs b/src/mbox/cli.rs index 7dde5eed..30355fd1 100644 --- a/src/mbox/cli.rs +++ b/src/mbox/cli.rs @@ -3,7 +3,9 @@ use clap; use log::{debug, trace}; use crate::{ - ctx::Ctx, domain::account::entity::Account, imap::model::ImapConnector, mbox::model::Mboxes, + domain::imap::ImapServiceInterface, + mbox::model::Mboxes, + output::service::{OutputService, OutputServiceInterface}, }; pub fn subcmds<'a>() -> Vec> { @@ -12,26 +14,25 @@ pub fn subcmds<'a>() -> Vec> { .about("Lists all mailboxes")] } -pub fn matches(ctx: &Ctx, account: &Account) -> Result { - if let Some(_) = ctx.arg_matches.subcommand_matches("mailboxes") { +pub fn matches( + arg_matches: &clap::ArgMatches, + output: &OutputService, + imap: &mut ImapService, +) -> Result { + if let Some(_) = arg_matches.subcommand_matches("mailboxes") { debug!("mailboxes command matched"); - - let mut imap_conn = ImapConnector::new(&account)?; - let names = imap_conn.list_mboxes()?; + let names = imap.list_mboxes()?; let mboxes = Mboxes::from(&names); - debug!("found {} mailboxes", mboxes.0.len()); - trace!("mailboxes: {:?}", mboxes); - ctx.output.print(mboxes); - - imap_conn.logout(); + debug!("mboxes len: {}", mboxes.0.len()); + trace!("{:#?}", mboxes); + output.print(mboxes)?; + imap.logout()?; return Ok(true); } - debug!("nothing matched"); Ok(false) } -// == Argument Functions == pub fn source_arg<'a>() -> clap::Arg<'a, 'a> { clap::Arg::with_name("mailbox") .short("m") diff --git a/src/msg/cli.rs b/src/msg/cli.rs index 81606b85..aa93092a 100644 --- a/src/msg/cli.rs +++ b/src/msg/cli.rs @@ -19,12 +19,11 @@ use super::{ model::{Msg, MsgSerialized, Msgs}, }; use crate::{ - ctx::Ctx, - domain::{account::entity::Account, smtp::*}, + domain::{account::entity::Account, imap::ImapServiceInterface, smtp::*}, flag::model::Flags, - imap::model::ImapConnector, input, mbox::cli::mbox_target_arg, + output::service::{OutputService, OutputServiceInterface}, }; pub fn subcmds<'a>() -> Vec> { @@ -127,27 +126,32 @@ pub fn subcmds<'a>() -> Vec> { ] } -pub fn matches( - ctx: &Ctx, +pub fn matches( + arg_matches: &clap::ArgMatches, + mbox: &str, account: &Account, - smtp: SmtpService, + output: &OutputService, + imap: &mut ImapService, + smtp: &mut SmtpService, ) -> Result { - match ctx.arg_matches.subcommand() { - ("attachments", Some(matches)) => msg_matches_attachments(&ctx, &account, &matches), - ("copy", Some(matches)) => msg_matches_copy(&ctx, &account, &matches), - ("delete", Some(matches)) => msg_matches_delete(&ctx, &account, &matches), - ("forward", Some(matches)) => msg_matches_forward(&ctx, &account, &matches, smtp), - ("move", Some(matches)) => msg_matches_move(&ctx, &account, &matches), - ("read", Some(matches)) => msg_matches_read(&ctx, &account, &matches), - ("reply", Some(matches)) => msg_matches_reply(&ctx, &account, &matches, smtp), - ("save", Some(matches)) => msg_matches_save(&ctx, &account, matches), - ("search", Some(matches)) => msg_matches_search(&ctx, &account, &matches), - ("send", Some(matches)) => msg_matches_send(&ctx, &account, &matches, smtp), - ("write", Some(matches)) => msg_matches_write(&ctx, &account, &matches, smtp), + match arg_matches.subcommand() { + ("attachments", Some(matches)) => { + msg_matches_attachments(&output, &account, &matches, imap) + } + ("copy", Some(matches)) => msg_matches_copy(&output, &matches, imap), + ("delete", Some(matches)) => msg_matches_delete(&output, &matches, imap), + ("forward", Some(matches)) => msg_matches_forward(&output, &account, &matches, imap, smtp), + ("move", Some(matches)) => msg_matches_move(&output, &matches, imap), + ("read", Some(matches)) => msg_matches_read(&output, &matches, imap), + ("reply", Some(matches)) => msg_matches_reply(&output, &account, &matches, imap, smtp), + ("save", Some(matches)) => msg_matches_save(&mbox, matches, imap), + ("search", Some(matches)) => msg_matches_search(&output, &account, &matches, imap), + ("send", Some(matches)) => msg_matches_send(&output, &matches, imap, smtp), + ("write", Some(matches)) => msg_matches_write(&output, &account, &matches, imap, smtp), - ("template", Some(matches)) => Ok(msg_matches_tpl(&ctx, &account, &matches)?), - ("list", opt_matches) => msg_matches_list(&ctx, &account, opt_matches), - (_other, opt_matches) => msg_matches_list(&ctx, &account, opt_matches), + ("template", Some(matches)) => Ok(msg_matches_tpl(&output, &account, &matches, imap)?), + ("list", opt_matches) => msg_matches_list(&output, &account, opt_matches, imap), + (_other, opt_matches) => msg_matches_list(&output, &account, opt_matches, imap), } } @@ -244,10 +248,11 @@ fn tpl_args<'a>() -> Vec> { ] } -fn msg_matches_list( - ctx: &Ctx, +fn msg_matches_list( + output: &OutputService, account: &Account, opt_matches: Option<&clap::ArgMatches>, + imap: &mut ImapService, ) -> Result { debug!("list command matched"); @@ -261,8 +266,7 @@ fn msg_matches_list( .unwrap_or_default(); debug!("page: {}", &page); - let mut imap_conn = ImapConnector::new(&account)?; - let msgs = imap_conn.list_msgs(&ctx.mbox, &page_size, &page)?; + let msgs = imap.list_msgs(&page_size, &page)?; let msgs = if let Some(ref fetches) = msgs { Msgs::try_from(fetches)? } else { @@ -271,13 +275,18 @@ fn msg_matches_list( trace!("messages: {:?}", msgs); - ctx.output.print(msgs); + output.print(msgs)?; - imap_conn.logout(); + imap.logout()?; Ok(true) } -fn msg_matches_search(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +fn msg_matches_search( + output: &OutputService, + account: &Account, + matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { debug!("search command matched"); let page_size: usize = matches @@ -319,21 +328,24 @@ fn msg_matches_search(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) .join(" "); debug!("query: {}", &page); - let mut imap_conn = ImapConnector::new(&account)?; - let msgs = imap_conn.search_msgs(&ctx.mbox, &query, &page_size, &page)?; + let msgs = imap.search_msgs(&query, &page_size, &page)?; let msgs = if let Some(ref fetches) = msgs { Msgs::try_from(fetches)? } else { Msgs::new() }; trace!("messages: {:?}", msgs); - ctx.output.print(msgs); + output.print(msgs)?; - imap_conn.logout(); + imap.logout()?; Ok(true) } -fn msg_matches_read(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +fn msg_matches_read( + output: &OutputService, + matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { debug!("read command matched"); let uid = matches.value_of("uid").unwrap(); @@ -343,22 +355,21 @@ fn msg_matches_read(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> let raw = matches.is_present("raw"); debug!("raw: {}", raw); - let mut imap_conn = ImapConnector::new(&account)?; - let msg = imap_conn.get_msg(&ctx.mbox, &uid)?; - + let msg = imap.get_msg(&uid)?; if raw { - ctx.output.print(msg.get_raw_as_string()?); + output.print(msg.get_raw_as_string()?)?; } else { - ctx.output.print(MsgSerialized::try_from(&msg)?); + output.print(MsgSerialized::try_from(&msg)?)?; } - imap_conn.logout(); + imap.logout()?; Ok(true) } -fn msg_matches_attachments( - ctx: &Ctx, +fn msg_matches_attachments( + output: &OutputService, account: &Account, matches: &clap::ArgMatches, + imap: &mut ImapService, ) -> Result { debug!("attachments command matched"); @@ -366,8 +377,7 @@ fn msg_matches_attachments( debug!("uid: {}", &uid); // get the msg and than it's attachments - let mut imap_conn = ImapConnector::new(&account)?; - let msg = imap_conn.get_msg(&ctx.mbox, &uid)?; + let msg = imap.get_msg(&uid)?; let attachments = msg.attachments.clone(); debug!( @@ -390,25 +400,24 @@ fn msg_matches_attachments( &attachments.len() ); - ctx.output.print(format!( + output.print(format!( "{} attachment(s) successfully downloaded", &attachments.len() - )); + ))?; - imap_conn.logout(); + imap.logout()?; Ok(true) } -fn msg_matches_write( - ctx: &Ctx, +fn msg_matches_write( + output: &OutputService, account: &Account, matches: &clap::ArgMatches, - smtp: SmtpService, + imap: &mut ImapService, + smtp: &mut SmtpService, ) -> Result { debug!("write command matched"); - let mut imap_conn = ImapConnector::new(&account)?; - // create the new msg // TODO: Make the header starting customizeable like from template let mut msg = Msg::new_with_headers( @@ -430,28 +439,26 @@ fn msg_matches_write( .iter() .for_each(|path| msg.add_attachment(path)); - msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?; + msg_interaction(output, &mut msg, imap, smtp)?; - // let's be nice to the server and say "bye" to the server - imap_conn.logout(); + imap.logout()?; Ok(true) } -fn msg_matches_reply( - ctx: &Ctx, +fn msg_matches_reply( + output: &OutputService, account: &Account, matches: &clap::ArgMatches, - smtp: SmtpService, + imap: &mut ImapService, + smtp: &mut SmtpService, ) -> Result { debug!("reply command matched"); // -- Preparations -- - let mut imap_conn = ImapConnector::new(&account)?; let uid = matches.value_of("uid").unwrap(); - let mut msg = imap_conn.get_msg(&ctx.mbox, &uid)?; - debug!("uid: {}", uid); + let mut msg = imap.get_msg(&uid)?; // Change the msg to a reply-msg. msg.change_to_reply(&account, matches.is_present("reply-all"))?; @@ -467,22 +474,21 @@ fn msg_matches_reply( debug!("found {} attachments", attachments.len()); trace!("attachments: {:?}", attachments); - msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?; + msg_interaction(output, &mut msg, imap, smtp)?; - imap_conn.logout(); + imap.logout()?; Ok(true) } -pub fn msg_matches_mailto( - ctx: &Ctx, +pub fn msg_matches_mailto( + output: &OutputService, account: &Account, url: &Url, - smtp: SmtpService, + imap: &mut ImapService, + smtp: &mut SmtpService, ) -> Result<()> { debug!("mailto command matched"); - let mut imap_conn = ImapConnector::new(&account)?; - let mut cc = Vec::new(); let mut bcc = Vec::new(); let mut subject = Cow::default(); @@ -519,27 +525,26 @@ pub fn msg_matches_mailto( let mut msg = Msg::new_with_headers(&account, headers); msg.body = Body::new_with_text(body); - msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?; + msg_interaction(output, &mut msg, imap, smtp)?; - imap_conn.logout(); + imap.logout()?; Ok(()) } -fn msg_matches_forward( - ctx: &Ctx, +fn msg_matches_forward( + output: &OutputService, account: &Account, matches: &clap::ArgMatches, - smtp: SmtpService, + imap: &mut ImapService, + smtp: &mut SmtpService, ) -> Result { debug!("forward command matched"); // fetch the msg - let mut imap_conn = ImapConnector::new(&account)?; let uid = matches.value_of("uid").unwrap(); - let mut msg = imap_conn.get_msg(&ctx.mbox, &uid)?; - debug!("uid: {}", uid); + let mut msg = imap.get_msg(&uid)?; // prepare to forward it msg.change_to_forwarding(&account); @@ -554,102 +559,104 @@ fn msg_matches_forward( trace!("attachments: {:?}", attachments); // apply changes - msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?; + msg_interaction(output, &mut msg, imap, smtp)?; - imap_conn.logout(); + imap.logout()?; Ok(true) } -fn msg_matches_copy(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +fn msg_matches_copy( + output: &OutputService, + matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { debug!("copy command matched"); // fetch the message to be copyied - let mut imap_conn = ImapConnector::new(&account)?; let uid = matches.value_of("uid").unwrap(); - let target = matches.value_of("target").unwrap(); - let mut msg = imap_conn.get_msg(&ctx.mbox, &uid)?; - debug!("uid: {}", &uid); + let target = matches.value_of("target").unwrap(); debug!("target: {}", &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_conn.append_msg(target, &mut msg)?; + imap.append_msg(target, &mut msg)?; debug!("message {} successfully copied to folder `{}`", uid, target); - ctx.output.print(format!( + output.print(format!( "Message {} successfully copied to folder `{}`", uid, target - )); + ))?; - imap_conn.logout(); + imap.logout()?; Ok(true) } -fn msg_matches_move(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +fn msg_matches_move( + output: &OutputService, + matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { debug!("move command matched"); // fetch the msg which should be moved - let mut imap_conn = ImapConnector::new(&account)?; let uid = matches.value_of("uid").unwrap(); - let target = matches.value_of("target").unwrap(); - let mut msg = imap_conn.get_msg(&ctx.mbox, &uid)?; - debug!("uid: {}", &uid); + let target = matches.value_of("target").unwrap(); debug!("target: {}", &target); + let mut msg = imap.get_msg(&uid)?; // create the msg in the target-msgbox msg.flags.insert(Flag::Seen); - imap_conn.append_msg(target, &mut msg)?; + imap.append_msg(target, &mut msg)?; debug!("message {} successfully moved to folder `{}`", uid, target); - ctx.output.print(format!( + output.print(format!( "Message {} successfully moved to folder `{}`", uid, target - )); + ))?; // delete the msg in the old mailbox let flags = vec![Flag::Seen, Flag::Deleted]; - imap_conn.add_flags(&ctx.mbox, uid, Flags::from(flags))?; - imap_conn.expunge(&ctx.mbox)?; - - imap_conn.logout(); + imap.add_flags(uid, Flags::from(flags))?; + imap.expunge()?; + imap.logout()?; Ok(true) } -fn msg_matches_delete(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +fn msg_matches_delete( + output: &OutputService, + matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { debug!("delete command matched"); - let mut imap_conn = ImapConnector::new(&account)?; - // remove the message according to its UID let uid = matches.value_of("uid").unwrap(); let flags = vec![Flag::Seen, Flag::Deleted]; - imap_conn.add_flags(&ctx.mbox, uid, Flags::from(flags))?; - imap_conn.expunge(&ctx.mbox)?; + imap.add_flags(uid, Flags::from(flags))?; + imap.expunge()?; debug!("message {} successfully deleted", uid); - ctx.output - .print(format!("Message {} successfully deleted", uid)); + output.print(format!("Message {} successfully deleted", uid))?; - imap_conn.logout(); + imap.logout()?; Ok(true) } -fn msg_matches_send( - ctx: &Ctx, - account: &Account, +fn msg_matches_send( + output: &OutputService, matches: &clap::ArgMatches, - mut smtp: SmtpService, + imap: &mut ImapService, + smtp: &mut SmtpService, ) -> Result { debug!("send command matched"); - let mut imap_conn = ImapConnector::new(&account)?; - - let msg = if atty::is(Stream::Stdin) || ctx.output.is_json() { + let msg = if atty::is(Stream::Stdin) || output.is_json() { matches .value_of("message") .unwrap_or_default() @@ -674,34 +681,37 @@ fn msg_matches_send( // add the message/msg to the Sent-Mailbox of the user msg.flags.insert(Flag::Seen); - imap_conn.append_msg("Sent", &mut msg)?; + imap.append_msg("Sent", &mut msg)?; - imap_conn.logout(); + imap.logout()?; Ok(true) } -fn msg_matches_save(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +fn msg_matches_save( + mbox: &str, + matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { debug!("save command matched"); - - let mut imap_conn = ImapConnector::new(&account)?; let msg: &str = matches.value_of("message").unwrap(); - let mut msg = Msg::try_from(msg)?; - msg.flags.insert(Flag::Seen); - imap_conn.append_msg(&ctx.mbox, &mut msg)?; - - imap_conn.logout(); - + imap.append_msg(&mbox, &mut msg)?; + imap.logout()?; Ok(true) } -pub fn msg_matches_tpl(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +pub fn msg_matches_tpl( + output: &OutputService, + account: &Account, + matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { match matches.subcommand() { - ("new", Some(matches)) => tpl_matches_new(&ctx, &account, matches), - ("reply", Some(matches)) => tpl_matches_reply(&ctx, &account, matches), - ("forward", Some(matches)) => tpl_matches_forward(&ctx, &account, matches), + ("new", Some(matches)) => tpl_matches_new(&output, &account, matches), + ("reply", Some(matches)) => tpl_matches_reply(&output, &account, matches, imap), + ("forward", Some(matches)) => tpl_matches_forward(&output, &account, matches, imap), // TODO: find a way to show the help message for template subcommand _ => Err(anyhow!("Subcommand not found")), @@ -798,62 +808,60 @@ fn override_msg_with_args(msg: &mut Msg, matches: &clap::ArgMatches) { msg.body = body; } -fn tpl_matches_new(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +fn tpl_matches_new( + output: &OutputService, + account: &Account, + matches: &clap::ArgMatches, +) -> Result { debug!("new command matched"); - let mut msg = Msg::new(&account); - override_msg_with_args(&mut msg, &matches); - trace!("Message: {:?}", msg); - ctx.output.print(MsgSerialized::try_from(&msg)?); - + output.print(MsgSerialized::try_from(&msg)?)?; Ok(true) } -fn tpl_matches_reply(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +fn tpl_matches_reply( + output: &OutputService, + account: &Account, + matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { debug!("reply command matched"); - let uid = matches.value_of("uid").unwrap(); debug!("uid: {}", uid); - - let mut imap_conn = ImapConnector::new(&account)?; - let mut msg = imap_conn.get_msg(&ctx.mbox, &uid)?; - + let mut msg = imap.get_msg(&uid)?; msg.change_to_reply(&account, matches.is_present("reply-all"))?; - override_msg_with_args(&mut msg, &matches); trace!("Message: {:?}", msg); - ctx.output.print(MsgSerialized::try_from(&msg)?); - + output.print(MsgSerialized::try_from(&msg)?)?; Ok(true) } -fn tpl_matches_forward(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result { +fn tpl_matches_forward( + output: &OutputService, + account: &Account, + matches: &clap::ArgMatches, + imap: &mut ImapService, +) -> Result { debug!("forward command matched"); - let uid = matches.value_of("uid").unwrap(); debug!("uid: {}", uid); - - let mut imap_conn = ImapConnector::new(&account)?; - let mut msg = imap_conn.get_msg(&ctx.mbox, &uid)?; + let mut msg = imap.get_msg(&uid)?; msg.change_to_forwarding(&account); - override_msg_with_args(&mut msg, &matches); - trace!("Message: {:?}", msg); - ctx.output.print(MsgSerialized::try_from(&msg)?); - + output.print(MsgSerialized::try_from(&msg)?)?; Ok(true) } /// This function opens the prompt to do some actions to the msg like sending, editing it again and /// so on. -fn msg_interaction( - ctx: &Ctx, +fn msg_interaction( + output: &OutputService, msg: &mut Msg, - imap_conn: &mut ImapConnector, - mut smtp: SmtpService, + imap: &mut ImapService, + smtp: &mut SmtpService, ) -> Result { // let the user change the body a little bit first, before opening the prompt msg.edit_body()?; @@ -884,11 +892,11 @@ fn msg_interaction( // let the server know, that the user sent a msg msg.flags.insert(Flag::Seen); - imap_conn.append_msg("Sent", msg)?; + imap.append_msg("Sent", msg)?; // remove the draft, since we sent it input::remove_draft()?; - ctx.output.print("Message successfully sent"); + output.print("Message successfully sent")?; break; } // edit the body of the msg @@ -909,13 +917,13 @@ fn msg_interaction( msg.flags.insert(Flag::Seen); - match imap_conn.append_msg("Drafts", msg) { + match imap.append_msg("Drafts", msg) { Ok(_) => { input::remove_draft()?; - ctx.output.print("Message successfully saved to Drafts"); + output.print("Message successfully saved to Drafts")?; } Err(err) => { - ctx.output.print("Cannot save draft to the server"); + output.print("Cannot save draft to the server")?; return Err(err.into()); } }; diff --git a/src/output/mod.rs b/src/output/mod.rs index d91021ca..b0118e0d 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -1,3 +1,5 @@ +//! Modules related to output formatting and printing. + pub mod cli; -pub mod model; +pub mod service; pub mod utils; diff --git a/src/output/model.rs b/src/output/service.rs similarity index 68% rename from src/output/model.rs rename to src/output/service.rs index 50cae8b7..786f8112 100644 --- a/src/output/model.rs +++ b/src/output/service.rs @@ -1,17 +1,16 @@ +use anyhow::Result; use serde::Serialize; use std::fmt; -// Output format - -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, PartialEq)] pub enum OutputFmt { Plain, Json, } impl From<&str> for OutputFmt { - fn from(s: &str) -> Self { - match s { + fn from(slice: &str) -> Self { + match slice { "json" => Self::Json, "plain" | _ => Self::Plain, } @@ -20,12 +19,11 @@ impl From<&str> for OutputFmt { impl fmt::Display for OutputFmt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let fmt = match *self { - OutputFmt::Json => "JSON", - OutputFmt::Plain => "PLAIN", + let slice = match self { + &OutputFmt::Json => "JSON", + &OutputFmt::Plain => "Plain", }; - - write!(f, "{}", fmt) + write!(f, "{}", slice) } } @@ -42,32 +40,21 @@ impl OutputJson { } } -// Output -/// A simple wrapper for a general formatting. -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct Output { +pub trait OutputServiceInterface { + fn print(&self, data: T) -> Result<()>; +} + +#[derive(Debug)] +pub struct OutputService { fmt: OutputFmt, } -impl Output { +impl OutputService { /// Create a new output-handler by setting the given formatting style. pub fn new(fmt: &str) -> Self { Self { fmt: fmt.into() } } - /// Print the provided item out according to the formatting setting when you created this - /// struct. - pub fn print(&self, item: T) { - match self.fmt { - OutputFmt::Plain => { - println!("{}", item) - } - OutputFmt::Json => { - print!("{}", serde_json::to_string(&OutputJson::new(item)).unwrap()) - } - } - } - /// Returns true, if the formatting should be plaintext. pub fn is_plain(&self) -> bool { self.fmt == OutputFmt::Plain @@ -79,7 +66,23 @@ impl Output { } } -impl Default for Output { +impl OutputServiceInterface for OutputService { + /// Print the provided item out according to the formatting setting when you created this + /// struct. + fn print(&self, data: T) -> Result<()> { + match self.fmt { + OutputFmt::Plain => { + println!("{}", data) + } + OutputFmt::Json => { + print!("{}", serde_json::to_string(&OutputJson::new(data))?) + } + }; + Ok(()) + } +} + +impl Default for OutputService { fn default() -> Self { Self { fmt: OutputFmt::Plain,