improve entities and services from and try_from

This commit is contained in:
Clément DOUIN 2021-09-16 15:01:33 +02:00
parent 0ed1147f3d
commit 248240f52d
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
8 changed files with 168 additions and 111 deletions

View file

@ -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<Self, Self::Error> {
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)
}
}

View file

@ -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<PathBuf> for Config {
impl TryFrom<Option<&str>> for Config {
type Error = Error;
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
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)
}
}

View file

@ -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<Option<ImapMsgs>>;
fn get_msg(&mut self, uid: &str) -> Result<Msg>;
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<ImapSession>,
}
impl<'a> ImapService<'a> {
pub fn new(account: &'a Account, mbox: &'a str) -> Result<Self> {
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<Option<ImapMsgs>> {
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<Msg> {
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<imap::types::Flag<'static>> = (*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<u32> =
@ -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,
}
}
}

View file

@ -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<S: AsRef<str>>(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<Option<&str>> for Mbox {
type Error = Error;
fn try_from(mbox: Option<&str>) -> Result<Self, Self::Error> {
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()

View file

@ -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<clap::App<'a, 'a>> {
pub fn matches<ImapService: ImapServiceInterface, SmtpService: SmtpServiceInterface>(
arg_matches: &clap::ArgMatches,
mbox: &str,
mbox: &Mbox,
account: &Account,
output: &OutputService,
imap: &mut ImapService,
@ -578,14 +580,13 @@ fn msg_matches_copy<ImapService: ImapServiceInterface>(
// 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<ImapService: ImapServiceInterface>(
// 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<ImapService: ImapServiceInterface, SmtpService: SmtpServiceI
// add the message/msg to the Sent-Mailbox of the user
msg.flags.insert(Flag::Seen);
imap.append_msg("Sent", &mut msg)?;
let mbox = Mbox::from("Sent");
imap.append_msg(&mbox, &mut msg)?;
imap.logout()?;
@ -691,7 +692,7 @@ fn msg_matches_send<ImapService: ImapServiceInterface, SmtpService: SmtpServiceI
}
fn msg_matches_save<ImapService: ImapServiceInterface>(
mbox: &str,
mbox: &Mbox,
matches: &clap::ArgMatches,
imap: &mut ImapService,
) -> Result<bool> {
@ -894,7 +895,8 @@ fn msg_interaction<ImapService: ImapServiceInterface, SmtpService: SmtpServiceIn
// let the server know, that the user sent a msg
msg.flags.insert(Flag::Seen);
imap.append_msg("Sent", msg)?;
let mbox = Mbox::from("Sent");
imap.append_msg(&mbox, msg)?;
// remove the draft, since we sent it
input::remove_draft()?;
@ -919,7 +921,8 @@ fn msg_interaction<ImapService: ImapServiceInterface, SmtpService: SmtpServiceIn
msg.flags.insert(Flag::Seen);
match imap.append_msg("Drafts", msg) {
let mbox = Mbox::from("Drafts");
match imap.append_msg(&mbox, msg) {
Ok(_) => {
input::remove_draft()?;
output.print("Message successfully saved to Drafts")?;

View file

@ -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<Self> {
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,
}
}
}

View file

@ -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<String> = 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(())
}

View file

@ -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<Self, Self::Err> {
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<Option<&str>> for OutputFmt {
type Error = Error;
fn try_from(fmt: Option<&str>) -> Result<Self, Self::Error> {
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<Self> {
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<Option<&str>> for OutputService {
type Error = Error;
fn try_from(fmt: Option<&str>) -> Result<Self, Self::Error> {
debug!("init output service");
debug!("output: `{:?}`", fmt);
let fmt = fmt.try_into()?;
Ok(Self { fmt })
}
}