refactor config and account system

This commit is contained in:
Clément DOUIN 2021-09-14 00:34:34 +02:00
parent 5a9481910f
commit 979c6ef1c9
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
18 changed files with 1130 additions and 1051 deletions

View file

@ -1,2 +1 @@
pub mod cli;
pub mod model;

View file

@ -1,16 +1,12 @@
use clap;
use crate::{
config::model::{Account, Config},
output::model::Output,
};
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 account: Account,
pub output: Output,
pub mbox: String,
pub arg_matches: clap::ArgMatches<'a>,
@ -19,7 +15,6 @@ pub struct Ctx<'a> {
impl<'a> Ctx<'a> {
pub fn new<S: ToString>(
config: Config,
account: Account,
output: Output,
mbox: S,
arg_matches: clap::ArgMatches<'a>,
@ -28,7 +23,6 @@ impl<'a> Ctx<'a> {
Self {
config,
account,
output,
mbox,
arg_matches,

View file

@ -0,0 +1,296 @@
use anyhow::{anyhow, Context, Error, Result};
use lettre::transport::smtp::authentication::Credentials as SmtpCredentials;
use log::debug;
use std::{convert::TryFrom, env, fs, path::PathBuf};
use crate::{domain::config::entity::Config, output::utils::run_cmd};
const DEFAULT_PAGE_SIZE: usize = 10;
const DEFAULT_SIG_DELIM: &str = "-- \n";
/// Representation of a user account.
#[derive(Debug, Default)]
pub struct Account {
pub name: String,
pub from: String,
pub downloads_dir: PathBuf,
pub signature: String,
pub default_page_size: usize,
pub watch_cmds: Vec<String>,
pub default: bool,
pub email: String,
pub imap_host: String,
pub imap_port: u16,
pub imap_starttls: bool,
pub imap_insecure: bool,
pub imap_login: String,
pub imap_passwd_cmd: String,
pub smtp_host: String,
pub smtp_port: u16,
pub smtp_starttls: bool,
pub smtp_insecure: bool,
pub smtp_login: String,
pub smtp_passwd_cmd: String,
}
impl Account {
/// This is a little helper-function like which uses the the name and email
/// of the account to create a valid address for the header of the headers
/// of a msg.
///
/// # Hint
/// If the name includes some special characters like a whitespace, comma or semicolon, then
/// the name will be automatically wrapped between two `"`.
///
/// # Exapmle
/// ```
/// use himalaya::config::model::{Account, Config};
///
/// fn main() {
/// let config = Config::default();
///
/// let normal_account = Account::new(Some("Acc1"), "acc1@mail.com");
/// // notice the semicolon in the name!
/// let special_account = Account::new(Some("TL;DR"), "acc2@mail.com");
///
/// // -- Expeced outputs --
/// let expected_normal = Account {
/// name: Some("Acc1".to_string()),
/// email: "acc1@mail.com".to_string(),
/// .. Account::default()
/// };
///
/// let expected_special = Account {
/// name: Some("\"TL;DR\"".to_string()),
/// email: "acc2@mail.com".to_string(),
/// .. Account::default()
/// };
///
/// assert_eq!(config.address(&normal_account), "Acc1 <acc1@mail.com>");
/// assert_eq!(config.address(&special_account), "\"TL;DR\" <acc2@mail.com>");
/// }
/// ```
pub fn address(&self) -> String {
let name = &self.from;
let has_special_chars = "()<>[]:;@.,".contains(|special_char| name.contains(special_char));
if name.is_empty() {
format!("{}", self.email)
} else if has_special_chars {
// so the name has special characters => Wrap it with '"'
format!("\"{}\" <{}>", name, self.email)
} else {
format!("{} <{}>", name, self.email)
}
}
/// Returns the imap-host address + the port usage of the account
///
/// # Example
/// ```rust
/// use himalaya::config::model::Account;
/// fn main () {
/// let account = Account {
/// imap_host: String::from("hostExample"),
/// imap_port: 42,
/// .. Account::default()
/// };
///
/// let expected_output = ("hostExample", 42);
///
/// assert_eq!(account.imap_addr(), expected_output);
/// }
/// ```
pub fn imap_addr(&self) -> (&str, u16) {
debug!("host: {}", self.imap_host);
debug!("port: {}", self.imap_port);
(&self.imap_host, self.imap_port)
}
/// Runs the given command in your password string and returns it.
pub fn imap_passwd(&self) -> Result<String> {
let passwd = run_cmd(&self.imap_passwd_cmd).context("cannot run IMAP passwd cmd")?;
let passwd = passwd
.trim_end_matches(|c| c == '\r' || c == '\n')
.to_owned();
Ok(passwd)
}
pub fn smtp_creds(&self) -> Result<SmtpCredentials> {
let passwd = run_cmd(&self.smtp_passwd_cmd).context("cannot run SMTP passwd cmd")?;
let passwd = passwd
.trim_end_matches(|c| c == '\r' || c == '\n')
.to_owned();
Ok(SmtpCredentials::new(self.smtp_login.to_owned(), passwd))
}
/// Creates a new account with the given values and returns it. All other attributes of the
/// account are gonna be empty/None.
///
/// # Example
/// ```rust
/// use himalaya::config::model::Account;
///
/// fn main() {
/// let account1 = Account::new(Some("Name1"), "email@address.com");
/// let account2 = Account::new(None, "email@address.com");
///
/// let expected1 = Account {
/// name: Some("Name1".to_string()),
/// email: "email@address.com".to_string(),
/// .. Account::default()
/// };
///
/// let expected2 = Account {
/// email: "email@address.com".to_string(),
/// .. Account::default()
/// };
///
/// assert_eq!(account1, expected1);
/// assert_eq!(account2, expected2);
/// }
/// ```
pub fn new<S: ToString + Default>(name: Option<S>, email_addr: S) -> Self {
Self {
name: name.unwrap_or_default().to_string(),
email: email_addr.to_string(),
..Self::default()
}
}
/// Creates a new account with a custom signature. Passing `None` to `signature` sets the
/// signature to `Account Signature`.
///
/// # Examples
/// ```rust
/// use himalaya::config::model::Account;
///
/// fn main() {
///
/// // the testing accounts
/// let account_with_custom_signature = Account::new_with_signature(
/// Some("Email name"), "some@mail.com", Some("Custom signature! :)"));
/// let account_with_default_signature = Account::new_with_signature(
/// Some("Email name"), "some@mail.com", None);
///
/// // How they should look like
/// let account_cmp1 = Account {
/// name: Some("Email name".to_string()),
/// email: "some@mail.com".to_string(),
/// signature: Some("Custom signature! :)".to_string()),
/// .. Account::default()
/// };
///
/// let account_cmp2 = Account {
/// name: Some("Email name".to_string()),
/// email: "some@mail.com".to_string(),
/// .. Account::default()
/// };
///
/// assert_eq!(account_with_custom_signature, account_cmp1);
/// assert_eq!(account_with_default_signature, account_cmp2);
/// }
/// ```
pub fn new_with_signature<S: AsRef<str> + ToString + Default>(
name: Option<S>,
email_addr: S,
signature: Option<S>,
) -> Self {
let mut account = Account::new(name, email_addr);
account.signature = signature.unwrap_or_default().to_string();
account
}
}
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> {
let (name, account) = match account_name {
Some("") | None => config
.accounts
.iter()
.find(|(_, account)| account.default.unwrap_or(false))
.map(|(name, account)| (name.to_owned(), account))
.ok_or_else(|| anyhow!("cannot find default account")),
Some(name) => config
.accounts
.get(name)
.map(|account| (name.to_owned(), account))
.ok_or_else(|| anyhow!(format!("cannot find account `{}`", name))),
}?;
let downloads_dir = account
.downloads_dir
.as_ref()
.and_then(|dir| dir.to_str())
.and_then(|dir| shellexpand::full(dir).ok())
.map(|dir| PathBuf::from(dir.to_string()))
.or_else(|| {
config
.downloads_dir
.as_ref()
.and_then(|dir| dir.to_str())
.and_then(|dir| shellexpand::full(dir).ok())
.map(|dir| PathBuf::from(dir.to_string()))
})
.unwrap_or_else(|| env::temp_dir());
let default_page_size = account
.default_page_size
.as_ref()
.or_else(|| config.default_page_size.as_ref())
.unwrap_or(&DEFAULT_PAGE_SIZE)
.to_owned();
let default_sig_delim = DEFAULT_SIG_DELIM.to_string();
let signature_delim = account
.signature_delimiter
.as_ref()
.or_else(|| config.signature_delimiter.as_ref())
.unwrap_or(&default_sig_delim);
let signature = account
.signature
.as_ref()
.or_else(|| config.signature.as_ref());
let signature = signature
.and_then(|sig| shellexpand::full(sig).ok())
.map(|sig| sig.to_string())
.and_then(|sig| fs::read_to_string(sig).ok())
.or_else(|| signature.map(|sig| sig.to_owned()))
.map(|sig| format!("\n{}{}", signature_delim, sig))
.unwrap_or_default();
Ok(Account {
name,
from: account.name.as_ref().unwrap_or(&config.name).to_owned(),
downloads_dir,
signature,
default_page_size,
watch_cmds: account
.watch_cmds
.as_ref()
.or_else(|| config.watch_cmds.as_ref())
.unwrap_or(&vec![])
.to_owned(),
default: account.default.unwrap_or(false),
email: account.email.to_owned(),
imap_host: account.imap_host.to_owned(),
imap_port: account.imap_port,
imap_starttls: account.imap_starttls.unwrap_or_default(),
imap_insecure: account.imap_insecure.unwrap_or_default(),
imap_login: account.imap_login.to_owned(),
imap_passwd_cmd: account.imap_passwd_cmd.to_owned(),
smtp_host: account.smtp_host.to_owned(),
smtp_port: account.smtp_port,
smtp_starttls: account.smtp_starttls.unwrap_or_default(),
smtp_insecure: account.smtp_insecure.unwrap_or_default(),
smtp_login: account.smtp_login.to_owned(),
smtp_passwd_cmd: account.smtp_passwd_cmd.to_owned(),
})
}
}

View file

@ -0,0 +1,3 @@
//! Modules related to the user's accounts.
pub mod entity;

View file

@ -1,48 +1,32 @@
use anyhow::{anyhow, Context, Result};
use lettre::transport::smtp::authentication::Credentials as SmtpCredentials;
use anyhow::{anyhow, Context, Error, Result};
use log::debug;
use serde::Deserialize;
use shellexpand;
use std::{
collections::HashMap,
env,
fs::{self, File},
io::Read,
path::PathBuf,
thread,
};
use std::{collections::HashMap, convert::TryFrom, env, fs, path::PathBuf, thread};
use toml;
use crate::output::utils::run_cmd;
const DEFAULT_PAGE_SIZE: usize = 10;
// --- Account ---
/// Represents an account section in your config file.
///
/// [account section]: https://github.com/soywod/himalaya/wiki/Configuration:config-file#account-specific-settings
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Account {
// Override
// TODO: rename with `from`
pub name: Option<String>,
pub downloads_dir: Option<PathBuf>,
pub signature_delimiter: Option<String>,
pub signature: Option<String>,
pub default_page_size: Option<usize>,
pub watch_cmds: Option<Vec<String>>,
// Specific
pub default: Option<bool>,
pub email: String,
pub imap_host: String,
pub imap_port: u16,
pub imap_starttls: Option<bool>,
pub imap_insecure: Option<bool>,
pub imap_login: String,
pub imap_passwd_cmd: String,
pub smtp_host: String,
pub smtp_port: u16,
pub smtp_starttls: Option<bool>,
@ -51,193 +35,13 @@ pub struct Account {
pub smtp_passwd_cmd: String,
}
impl Account {
/// Returns the imap-host address + the port usage of the account
///
/// # Example
/// ```rust
/// use himalaya::config::model::Account;
/// fn main () {
/// let account = Account {
/// imap_host: String::from("hostExample"),
/// imap_port: 42,
/// .. Account::default()
/// };
///
/// let expected_output = ("hostExample", 42);
///
/// assert_eq!(account.imap_addr(), expected_output);
/// }
/// ```
pub fn imap_addr(&self) -> (&str, u16) {
debug!("host: {}", self.imap_host);
debug!("port: {}", self.imap_port);
(&self.imap_host, self.imap_port)
}
pub type AccountsMap = HashMap<String, Account>;
/// Runs the given command in your password string and returns it.
pub fn imap_passwd(&self) -> Result<String> {
let passwd = run_cmd(&self.imap_passwd_cmd).context("cannot run IMAP passwd cmd")?;
let passwd = passwd
.trim_end_matches(|c| c == '\r' || c == '\n')
.to_owned();
Ok(passwd)
}
pub fn imap_starttls(&self) -> bool {
let starttls = match self.imap_starttls {
Some(true) => true,
_ => false,
};
debug!("STARTTLS: {}", starttls);
starttls
}
pub fn imap_insecure(&self) -> bool {
let insecure = match self.imap_insecure {
Some(true) => true,
_ => false,
};
debug!("insecure: {}", insecure);
insecure
}
pub fn smtp_creds(&self) -> Result<SmtpCredentials> {
let passwd = run_cmd(&self.smtp_passwd_cmd).context("cannot run SMTP passwd cmd")?;
let passwd = passwd
.trim_end_matches(|c| c == '\r' || c == '\n')
.to_owned();
Ok(SmtpCredentials::new(self.smtp_login.to_owned(), passwd))
}
pub fn smtp_starttls(&self) -> bool {
match self.smtp_starttls {
Some(true) => true,
_ => false,
}
}
pub fn smtp_insecure(&self) -> bool {
match self.smtp_insecure {
Some(true) => true,
_ => false,
}
}
/// Creates a new account with the given values and returns it. All other attributes of the
/// account are gonna be empty/None.
///
/// # Example
/// ```rust
/// use himalaya::config::model::Account;
///
/// fn main() {
/// let account1 = Account::new(Some("Name1"), "email@address.com");
/// let account2 = Account::new(None, "email@address.com");
///
/// let expected1 = Account {
/// name: Some("Name1".to_string()),
/// email: "email@address.com".to_string(),
/// .. Account::default()
/// };
///
/// let expected2 = Account {
/// email: "email@address.com".to_string(),
/// .. Account::default()
/// };
///
/// assert_eq!(account1, expected1);
/// assert_eq!(account2, expected2);
/// }
/// ```
pub fn new<S: ToString>(name: Option<S>, email_addr: S) -> Self {
Self {
name: name.and_then(|name| Some(name.to_string())),
email: email_addr.to_string(),
..Self::default()
}
}
/// Creates a new account with a custom signature. Passing `None` to `signature` sets the
/// signature to `Account Signature`.
///
/// # Examples
/// ```rust
/// use himalaya::config::model::Account;
///
/// fn main() {
///
/// // the testing accounts
/// let account_with_custom_signature = Account::new_with_signature(
/// Some("Email name"), "some@mail.com", Some("Custom signature! :)"));
/// let account_with_default_signature = Account::new_with_signature(
/// Some("Email name"), "some@mail.com", None);
///
/// // How they should look like
/// let account_cmp1 = Account {
/// name: Some("Email name".to_string()),
/// email: "some@mail.com".to_string(),
/// signature: Some("Custom signature! :)".to_string()),
/// .. Account::default()
/// };
///
/// let account_cmp2 = Account {
/// name: Some("Email name".to_string()),
/// email: "some@mail.com".to_string(),
/// .. Account::default()
/// };
///
/// assert_eq!(account_with_custom_signature, account_cmp1);
/// assert_eq!(account_with_default_signature, account_cmp2);
/// }
/// ```
pub fn new_with_signature<S: AsRef<str> + ToString>(
name: Option<S>,
email_addr: S,
signature: Option<S>,
) -> Self {
let mut account = Account::new(name, email_addr);
account.signature = signature.and_then(|signature| Some(signature.to_string()));
account
}
}
impl Default for Account {
fn default() -> Self {
Self {
name: None,
downloads_dir: None,
signature_delimiter: None,
signature: None,
default_page_size: None,
default: None,
email: String::new(),
watch_cmds: None,
imap_host: String::new(),
imap_port: 0,
imap_starttls: None,
imap_insecure: None,
imap_login: String::new(),
imap_passwd_cmd: String::new(),
smtp_host: String::new(),
smtp_port: 0,
smtp_starttls: None,
smtp_insecure: None,
smtp_login: String::new(),
smtp_passwd_cmd: String::new(),
}
}
}
// --- Config ---
/// Represents the whole config file.
#[derive(Debug, Default, Deserialize, Clone)]
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
// TODO: rename with `from`
pub name: String,
pub downloads_dir: Option<PathBuf>,
pub notify_cmd: Option<String>,
@ -290,22 +94,13 @@ impl Config {
Ok(path)
}
/// Parses the config file by the given path and stores the values into the struct.
pub fn new(path: Option<PathBuf>) -> Result<Self> {
let path = match path {
Some(path) => path,
None => Self::path_from_xdg()
.or_else(|_| Self::path_from_xdg_alt())
.or_else(|_| Self::path_from_home())
.context("cannot find config path")?,
};
pub fn path() -> Result<PathBuf> {
let path = Self::path_from_xdg()
.or_else(|_| Self::path_from_xdg_alt())
.or_else(|_| Self::path_from_home())
.context("cannot find config path")?;
let mut file = File::open(path).context("cannot open config file")?;
let mut content = vec![];
file.read_to_end(&mut content)
.context("cannot read config file")?;
Ok(toml::from_slice(&content).context("cannot parse config file")?)
Ok(path)
}
/// Returns the account by the given name.
@ -386,9 +181,7 @@ impl Config {
/// ```
pub fn address(&self, account: &Account) -> String {
let name = account.name.as_ref().unwrap_or(&self.name);
let has_special_chars: bool =
"()<>[]:;@.,".contains(|special_char| name.contains(special_char));
let has_special_chars = "()<>[]:;@.,".contains(|special_char| name.contains(special_char));
if name.is_empty() {
format!("{}", account.email)
@ -491,57 +284,62 @@ impl Config {
}
}
#[cfg(test)]
mod tests {
impl TryFrom<PathBuf> for Config {
type Error = Error;
#[cfg(test)]
mod config_test {
use crate::config::model::{Account, Config};
// a quick way to get a config instance for testing
fn get_config() -> Config {
Config {
name: String::from("Config Name"),
..Config::default()
}
}
#[test]
fn test_find_account_by_name() {
let mut config = get_config();
let account1 = Account::new(None, "one@mail.com");
let account2 = Account::new(Some("Two"), "two@mail.com");
// add some accounts
config.accounts.insert("One".to_string(), account1.clone());
config.accounts.insert("Two".to_string(), account2.clone());
let ret1 = config.find_account_by_name(Some("One")).unwrap();
let ret2 = config.find_account_by_name(Some("Two")).unwrap();
assert_eq!(*ret1, account1);
assert_eq!(*ret2, account2);
}
#[test]
fn test_address() {
let config = get_config();
let account1 = Account::new(None, "one@mail.com");
let account2 = Account::new(Some("Two"), "two@mail.com");
let account3 = Account::new(Some("TL;DR"), "three@mail.com");
let account4 = Account::new(Some("TL,DR"), "lol@mail.com");
let account5 = Account::new(Some("TL:DR"), "rofl@mail.com");
let account6 = Account::new(Some("TL.DR"), "rust@mail.com");
assert_eq!(&config.address(&account1), "Config Name <one@mail.com>");
assert_eq!(&config.address(&account2), "Two <two@mail.com>");
assert_eq!(&config.address(&account3), "\"TL;DR\" <three@mail.com>");
assert_eq!(&config.address(&account4), "\"TL,DR\" <lol@mail.com>");
assert_eq!(&config.address(&account5), "\"TL:DR\" <rofl@mail.com>");
assert_eq!(&config.address(&account6), "\"TL.DR\" <rust@mail.com>");
}
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")?)
}
}
// FIXME: tests
// #[cfg(test)]
// mod tests {
// use crate::domain::{account::entity::Account, config::entity::Config};
// // a quick way to get a config instance for testing
// fn get_config() -> Config {
// Config {
// name: String::from("Config Name"),
// ..Config::default()
// }
// }
// #[test]
// fn test_find_account_by_name() {
// let mut config = get_config();
// let account1 = Account::new(None, "one@mail.com");
// let account2 = Account::new(Some("Two"), "two@mail.com");
// // add some accounts
// config.accounts.insert("One".to_string(), account1.clone());
// config.accounts.insert("Two".to_string(), account2.clone());
// let ret1 = config.find_account_by_name(Some("One")).unwrap();
// let ret2 = config.find_account_by_name(Some("Two")).unwrap();
// assert_eq!(*ret1, account1);
// assert_eq!(*ret2, account2);
// }
// #[test]
// fn test_address() {
// let config = get_config();
// let account1 = Account::new(None, "one@mail.com");
// let account2 = Account::new(Some("Two"), "two@mail.com");
// let account3 = Account::new(Some("TL;DR"), "three@mail.com");
// let account4 = Account::new(Some("TL,DR"), "lol@mail.com");
// let account5 = Account::new(Some("TL:DR"), "rofl@mail.com");
// let account6 = Account::new(Some("TL.DR"), "rust@mail.com");
// assert_eq!(&config.address(&account1), "Config Name <one@mail.com>");
// assert_eq!(&config.address(&account2), "Two <two@mail.com>");
// assert_eq!(&config.address(&account3), "\"TL;DR\" <three@mail.com>");
// assert_eq!(&config.address(&account4), "\"TL,DR\" <lol@mail.com>");
// assert_eq!(&config.address(&account5), "\"TL:DR\" <rofl@mail.com>");
// assert_eq!(&config.address(&account6), "\"TL.DR\" <rust@mail.com>");
// }
// }

3
src/domain/config/mod.rs Normal file
View file

@ -0,0 +1,3 @@
//! Modules related to the user's configuration.
pub mod entity;

View file

@ -1,3 +1,5 @@
//! Domain-specific modules.
pub mod account;
pub mod config;
pub mod smtp;

View file

@ -5,9 +5,9 @@ use lettre::{
Transport,
};
use crate::config::model::Account;
use crate::domain::account::entity::Account;
pub trait SMTPServiceInterface<'a> {
pub trait SMTPServiceInterface {
fn send(&self, msg: &lettre::Message) -> Result<()>;
}
@ -16,24 +16,24 @@ pub struct SMTPService<'a> {
}
impl<'a> SMTPService<'a> {
pub fn init(account: &'a Account) -> Self {
Self { account }
pub fn new(account: &'a Account) -> Result<Self> {
Ok(Self { account })
}
}
impl<'a> SMTPServiceInterface<'a> for SMTPService<'a> {
impl<'a> SMTPServiceInterface for SMTPService<'a> {
fn send(&self, msg: &lettre::Message) -> Result<()> {
let smtp_relay = if self.account.smtp_starttls() {
let smtp_relay = if self.account.smtp_starttls {
SmtpTransport::starttls_relay
} else {
SmtpTransport::relay
};
let tls = TlsParameters::builder(self.account.smtp_host.to_string())
.dangerous_accept_invalid_hostnames(self.account.smtp_insecure())
.dangerous_accept_invalid_certs(self.account.smtp_insecure())
.dangerous_accept_invalid_hostnames(self.account.smtp_insecure)
.dangerous_accept_invalid_certs(self.account.smtp_insecure)
.build()?;
let tls = if self.account.smtp_starttls() {
let tls = if self.account.smtp_starttls {
Tls::Required(tls)
} else {
Tls::Wrapper(tls)

View file

@ -2,7 +2,10 @@ use anyhow::Result;
use clap;
use log::debug;
use crate::{ctx::Ctx, flag::model::Flags, imap::model::ImapConnector, msg::cli::uid_arg};
use crate::{
ctx::Ctx, domain::account::entity::Account, flag::model::Flags, imap::model::ImapConnector,
msg::cli::uid_arg,
};
fn flags_arg<'a>() -> clap::Arg<'a, 'a> {
clap::Arg::with_name("flags")
@ -36,7 +39,7 @@ pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
)]
}
pub fn matches(ctx: &Ctx) -> Result<bool> {
pub fn matches(ctx: &Ctx, account: &Account) -> Result<bool> {
if let Some(matches) = ctx.arg_matches.subcommand_matches("set") {
debug!("set command matched");
@ -47,7 +50,7 @@ pub fn matches(ctx: &Ctx) -> Result<bool> {
debug!("flags: {}", flags);
let flags = Flags::from(flags);
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.set_flags(&ctx.mbox, uid, flags)?;
imap_conn.logout();
@ -64,7 +67,7 @@ pub fn matches(ctx: &Ctx) -> Result<bool> {
debug!("flags: {}", flags);
let flags = Flags::from(flags);
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.add_flags(&ctx.mbox, uid, flags)?;
imap_conn.logout();
@ -81,7 +84,7 @@ pub fn matches(ctx: &Ctx) -> Result<bool> {
debug!("flags: {}", flags);
let flags = Flags::from(flags);
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.remove_flags(&ctx.mbox, uid, flags)?;
imap_conn.logout();

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use clap;
use log::debug;
use crate::{ctx::Ctx, imap::model::ImapConnector};
use crate::{ctx::Ctx, domain::account::entity::Account, imap::model::ImapConnector};
pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
vec![
@ -30,14 +30,14 @@ pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
]
}
pub fn matches(ctx: &Ctx) -> Result<bool> {
pub fn matches(ctx: &Ctx, account: &Account) -> Result<bool> {
if let Some(matches) = ctx.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(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.notify(&ctx, keepalive)?;
imap_conn.logout();
@ -50,7 +50,7 @@ pub fn matches(ctx: &Ctx) -> Result<bool> {
let keepalive = clap::value_t_or_exit!(matches.value_of("keepalive"), u64);
debug!("keepalive: {}", &keepalive);
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.watch(&ctx, keepalive)?;
imap_conn.logout();

View file

@ -4,7 +4,7 @@ use log::{debug, trace};
use native_tls::{self, TlsConnector, TlsStream};
use std::{collections::HashSet, convert::TryFrom, iter::FromIterator, net::TcpStream};
use crate::{config::model::Account, ctx::Ctx, flag::model::Flags, msg::model::Msg};
use crate::{ctx::Ctx, 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 {
@ -41,16 +41,15 @@ impl<'a> ImapConnector<'a> {
/// to the server ;)
pub fn new(account: &'a Account) -> Result<Self> {
debug!("create TLS builder");
let insecure = account.imap_insecure();
let ssl_conn = TlsConnector::builder()
.danger_accept_invalid_certs(insecure)
.danger_accept_invalid_hostnames(insecure)
.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() {
if account.imap_starttls {
debug!("enable STARTTLS");
client_builder.starttls();
}
@ -260,7 +259,8 @@ impl<'a> ImapConnector<'a> {
})
})
.context("cannot start the idle mode")?;
ctx.config.exec_watch_cmds(&ctx.account)?;
// FIXME
// ctx.config.exec_watch_cmds(&ctx.account)?;
debug!("end loop");
}
}

View file

@ -42,8 +42,5 @@ pub mod msg;
/// Handles the output. For example the JSON and HTML output.
pub mod output;
/// This module takes care for sending your mails!
pub mod smtp;
pub mod domain;
pub mod ui;

View file

@ -1,16 +1,15 @@
use anyhow::Result;
use clap::{self, ArgMatches};
use clap;
use env_logger;
use log::{debug, trace};
use std::{env, path::PathBuf};
use url::{self, Url};
use std::{convert::TryFrom, env, path::PathBuf};
use himalaya::{
comp,
config::{cli::config_args, model::Config},
config::cli::config_args,
ctx::Ctx,
domain, flag, imap, mbox,
msg::{self, cli::msg_matches_mailto},
domain::{account::entity::Account, config::entity::Config, smtp::service::SMTPService},
flag, imap, mbox, msg,
output::{cli::output_args, model::Output},
};
@ -35,20 +34,20 @@ fn main() -> Result<()> {
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "off"),
);
let raw_args: Vec<String> = env::args().collect();
// 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();
let output = Output::new("plain");
let mbox = "INBOX";
let arg_matches = ArgMatches::default();
let app = Ctx::new(config, account, output, mbox, arg_matches);
let url = Url::parse(&raw_args[1])?;
let smtp = domain::smtp::service::SMTPService::init(&app.account);
return Ok(msg_matches_mailto(&app, &url, smtp)?);
}
// // 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();
// let output = Output::new("plain");
// let mbox = "INBOX";
// let arg_matches = ArgMatches::default();
// let app = Ctx::new(config, output, mbox, arg_matches);
// let url = Url::parse(&raw_args[1])?;
// let smtp = domain::smtp::service::SMTPService::new(&app.account);
// return Ok(msg_matches_mailto(&app, &url, smtp)?);
// }
let args = parse_args();
let arg_matches = args.get_matches();
@ -63,27 +62,32 @@ fn main() -> Result<()> {
debug!("init config");
let custom_config: Option<PathBuf> = arg_matches.value_of("config").map(|s| s.into());
debug!("custom config path: {:?}", custom_config);
let config = Config::new(custom_config)?;
let config_path: PathBuf = arg_matches
.value_of("config")
.map(|s| s.into())
.unwrap_or(Config::path()?);
debug!("config path: {:?}", config_path);
let config = Config::try_from(config_path.clone())?;
trace!("config: {:?}", config);
let account_name = arg_matches.value_of("account");
debug!("init account: {}", account_name.unwrap_or("default"));
let account = config.find_account_by_name(account_name)?.clone();
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);
let mbox = arg_matches.value_of("mailbox").unwrap().to_string();
debug!("mailbox: {}", mbox);
debug!("begin matching");
let ctx = Ctx::new(config, output, mbox, arg_matches);
trace!("context: {:?}", ctx);
let app = Ctx::new(config, account, output, mbox, arg_matches);
let smtp = domain::smtp::service::SMTPService::init(&app.account);
let _matched = mbox::cli::matches(&app)?
|| flag::cli::matches(&app)?
|| imap::cli::matches(&app)?
|| msg::cli::matches(&app, smtp)?;
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)?;
Ok(())
}

View file

@ -2,7 +2,9 @@ use anyhow::Result;
use clap;
use log::{debug, trace};
use crate::{ctx::Ctx, imap::model::ImapConnector, mbox::model::Mboxes};
use crate::{
ctx::Ctx, domain::account::entity::Account, imap::model::ImapConnector, mbox::model::Mboxes,
};
pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
vec![clap::SubCommand::with_name("mailboxes")
@ -10,11 +12,11 @@ pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
.about("Lists all mailboxes")]
}
pub fn matches(ctx: &Ctx) -> Result<bool> {
pub fn matches(ctx: &Ctx, account: &Account) -> Result<bool> {
if let Some(_) = ctx.arg_matches.subcommand_matches("mailboxes") {
debug!("mailboxes command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let names = imap_conn.list_mboxes()?;
let mboxes = Mboxes::from(&names);
debug!("found {} mailboxes", mboxes.0.len());

View file

@ -19,7 +19,11 @@ use super::{
model::{Msg, MsgSerialized, Msgs},
};
use crate::{
ctx::Ctx, domain::smtp, flag::model::Flags, imap::model::ImapConnector, input,
ctx::Ctx,
domain::{account::entity::Account, smtp},
flag::model::Flags,
imap::model::ImapConnector,
input,
mbox::cli::mbox_target_arg,
};
@ -123,26 +127,27 @@ pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
]
}
pub fn matches<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
pub fn matches<SMTP: smtp::service::SMTPServiceInterface>(
ctx: &Ctx,
account: &Account,
smtp: SMTP,
) -> Result<bool> {
match ctx.arg_matches.subcommand() {
("attachments", Some(matches)) => msg_matches_attachments(ctx, matches),
("copy", Some(matches)) => msg_matches_copy(ctx, matches),
("delete", Some(matches)) => msg_matches_delete(ctx, matches),
("forward", Some(matches)) => msg_matches_forward(ctx, matches, smtp),
("move", Some(matches)) => msg_matches_move(ctx, matches),
("read", Some(matches)) => msg_matches_read(ctx, matches),
("reply", Some(matches)) => msg_matches_reply(ctx, matches, smtp),
("save", Some(matches)) => msg_matches_save(ctx, matches),
("search", Some(matches)) => msg_matches_search(ctx, matches),
("send", Some(matches)) => msg_matches_send(ctx, matches, smtp),
("write", Some(matches)) => msg_matches_write(ctx, matches, smtp),
("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),
("template", Some(matches)) => Ok(msg_matches_tpl(ctx, matches)?),
("list", opt_matches) => msg_matches_list(ctx, opt_matches),
(_other, opt_matches) => msg_matches_list(ctx, opt_matches),
("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),
}
}
@ -239,12 +244,16 @@ fn tpl_args<'a>() -> Vec<clap::Arg<'a, 'a>> {
]
}
fn msg_matches_list(ctx: &Ctx, opt_matches: Option<&clap::ArgMatches>) -> Result<bool> {
fn msg_matches_list(
ctx: &Ctx,
account: &Account,
opt_matches: Option<&clap::ArgMatches>,
) -> Result<bool> {
debug!("list command matched");
let page_size: usize = opt_matches
.and_then(|matches| matches.value_of("page-size").and_then(|s| s.parse().ok()))
.unwrap_or_else(|| ctx.config.default_page_size(&ctx.account));
.unwrap_or(account.default_page_size);
debug!("page size: {:?}", page_size);
let page: usize = opt_matches
.and_then(|matches| matches.value_of("page").unwrap().parse().ok())
@ -252,7 +261,7 @@ fn msg_matches_list(ctx: &Ctx, opt_matches: Option<&clap::ArgMatches>) -> Result
.unwrap_or_default();
debug!("page: {}", &page);
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let msgs = imap_conn.list_msgs(&ctx.mbox, &page_size, &page)?;
let msgs = if let Some(ref fetches) = msgs {
Msgs::try_from(fetches)?
@ -268,13 +277,13 @@ fn msg_matches_list(ctx: &Ctx, opt_matches: Option<&clap::ArgMatches>) -> Result
Ok(true)
}
fn msg_matches_search(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn msg_matches_search(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
debug!("search command matched");
let page_size: usize = matches
.value_of("page-size")
.and_then(|s| s.parse().ok())
.unwrap_or(ctx.config.default_page_size(&ctx.account));
.unwrap_or(account.default_page_size);
debug!("page size: {}", &page_size);
let page: usize = matches
.value_of("page")
@ -310,7 +319,7 @@ fn msg_matches_search(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
.join(" ");
debug!("query: {}", &page);
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let msgs = imap_conn.search_msgs(&ctx.mbox, &query, &page_size, &page)?;
let msgs = if let Some(ref fetches) = msgs {
Msgs::try_from(fetches)?
@ -324,7 +333,7 @@ fn msg_matches_search(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true)
}
fn msg_matches_read(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn msg_matches_read(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
debug!("read command matched");
let uid = matches.value_of("uid").unwrap();
@ -334,7 +343,7 @@ fn msg_matches_read(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
let raw = matches.is_present("raw");
debug!("raw: {}", raw);
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let msg = imap_conn.get_msg(&ctx.mbox, &uid)?;
if raw {
@ -346,14 +355,18 @@ fn msg_matches_read(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true)
}
fn msg_matches_attachments(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn msg_matches_attachments(
ctx: &Ctx,
account: &Account,
matches: &clap::ArgMatches,
) -> Result<bool> {
debug!("attachments command matched");
let uid = matches.value_of("uid").unwrap();
debug!("uid: {}", &uid);
// get the msg and than it's attachments
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let msg = imap_conn.get_msg(&ctx.mbox, &uid)?;
let attachments = msg.attachments.clone();
@ -366,12 +379,8 @@ fn msg_matches_attachments(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool
// Iterate through all attachments and download them to the download
// directory of the account.
for attachment in &attachments {
let filepath = ctx
.config
.downloads_filepath(&ctx.account, &attachment.filename);
let filepath = account.downloads_dir.join(&attachment.filename);
debug!("downloading {}…", &attachment.filename);
fs::write(&filepath, &attachment.body_raw)
.with_context(|| format!("cannot save attachment {:?}", filepath))?;
}
@ -390,19 +399,20 @@ fn msg_matches_attachments(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool
Ok(true)
}
fn msg_matches_write<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
fn msg_matches_write<'a, SMTP: smtp::service::SMTPServiceInterface>(
ctx: &Ctx,
account: &Account,
matches: &clap::ArgMatches,
smtp: SMTP,
) -> Result<bool> {
debug!("write command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?;
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(
&ctx,
&account,
Headers {
subject: Some(String::new()),
to: Vec::new(),
@ -428,22 +438,23 @@ fn msg_matches_write<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
Ok(true)
}
fn msg_matches_reply<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
fn msg_matches_reply<'a, SMTP: smtp::service::SMTPServiceInterface>(
ctx: &Ctx,
account: &Account,
matches: &clap::ArgMatches,
smtp: SMTP,
) -> Result<bool> {
debug!("reply command matched");
// -- Preparations --
let mut imap_conn = ImapConnector::new(&ctx.account)?;
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);
// Change the msg to a reply-msg.
msg.change_to_reply(&ctx, matches.is_present("reply-all"))?;
msg.change_to_reply(&account, matches.is_present("reply-all"))?;
// Apply the given attachments to the reply-msg.
let attachments: Vec<&str> = matches
@ -462,14 +473,15 @@ fn msg_matches_reply<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
Ok(true)
}
pub fn msg_matches_mailto<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
pub fn msg_matches_mailto<'a, SMTP: smtp::service::SMTPServiceInterface>(
ctx: &Ctx,
account: &Account,
url: &Url,
smtp: SMTP,
) -> Result<()> {
debug!("mailto command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let mut cc = Vec::new();
let mut bcc = Vec::new();
@ -495,17 +507,17 @@ pub fn msg_matches_mailto<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
}
let headers = Headers {
from: vec![ctx.config.address(&ctx.account)],
from: vec![account.address()],
to: vec![url.path().to_string()],
encoding: ContentTransferEncoding::Base64,
bcc: Some(bcc),
cc: Some(cc),
signature: ctx.config.signature(&ctx.account),
signature: Some(account.signature.to_owned()),
subject: Some(subject.into()),
..Headers::default()
};
let mut msg = Msg::new_with_headers(&ctx, headers);
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)?;
@ -513,22 +525,23 @@ pub fn msg_matches_mailto<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
Ok(())
}
fn msg_matches_forward<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
fn msg_matches_forward<'a, SMTP: smtp::service::SMTPServiceInterface>(
ctx: &Ctx,
account: &Account,
matches: &clap::ArgMatches,
smtp: SMTP,
) -> Result<bool> {
debug!("forward command matched");
// fetch the msg
let mut imap_conn = ImapConnector::new(&ctx.account)?;
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);
// prepare to forward it
msg.change_to_forwarding(&ctx);
msg.change_to_forwarding(&account);
let attachments: Vec<&str> = matches
.values_of("attachments")
@ -548,11 +561,11 @@ fn msg_matches_forward<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
Ok(true)
}
fn msg_matches_copy(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn msg_matches_copy(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
debug!("copy command matched");
// fetch the message to be copyied
let mut imap_conn = ImapConnector::new(&ctx.account)?;
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)?;
@ -576,11 +589,11 @@ fn msg_matches_copy(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true)
}
fn msg_matches_move(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn msg_matches_move(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
debug!("move command matched");
// fetch the msg which should be moved
let mut imap_conn = ImapConnector::new(&ctx.account)?;
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)?;
@ -607,10 +620,10 @@ fn msg_matches_move(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true)
}
fn msg_matches_delete(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn msg_matches_delete(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
debug!("delete command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
// remove the message according to its UID
let uid = matches.value_of("uid").unwrap();
@ -626,14 +639,15 @@ fn msg_matches_delete(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true)
}
fn msg_matches_send<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
fn msg_matches_send<'a, SMTP: smtp::service::SMTPServiceInterface>(
ctx: &Ctx,
account: &Account,
matches: &clap::ArgMatches,
smtp: SMTP,
) -> Result<bool> {
debug!("send command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let msg = if atty::is(Stream::Stdin) || ctx.output.is_json() {
matches
@ -667,10 +681,10 @@ fn msg_matches_send<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
Ok(true)
}
fn msg_matches_save(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn msg_matches_save(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
debug!("save command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let msg: &str = matches.value_of("message").unwrap();
let mut msg = Msg::try_from(msg)?;
@ -683,11 +697,11 @@ fn msg_matches_save(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true)
}
pub fn msg_matches_tpl(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
pub fn msg_matches_tpl(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
match matches.subcommand() {
("new", Some(matches)) => tpl_matches_new(ctx, matches),
("reply", Some(matches)) => tpl_matches_reply(ctx, matches),
("forward", Some(matches)) => tpl_matches_forward(ctx, matches),
("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),
// TODO: find a way to show the help message for template subcommand
_ => Err(anyhow!("Subcommand not found")),
@ -784,10 +798,10 @@ fn override_msg_with_args(msg: &mut Msg, matches: &clap::ArgMatches) {
msg.body = body;
}
fn tpl_matches_new(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn tpl_matches_new(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
debug!("new command matched");
let mut msg = Msg::new(&ctx);
let mut msg = Msg::new(&account);
override_msg_with_args(&mut msg, &matches);
@ -797,16 +811,16 @@ fn tpl_matches_new(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true)
}
fn tpl_matches_reply(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn tpl_matches_reply(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
debug!("reply command matched");
let uid = matches.value_of("uid").unwrap();
debug!("uid: {}", uid);
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let mut msg = imap_conn.get_msg(&ctx.mbox, &uid)?;
msg.change_to_reply(&ctx, matches.is_present("reply-all"))?;
msg.change_to_reply(&account, matches.is_present("reply-all"))?;
override_msg_with_args(&mut msg, &matches);
trace!("Message: {:?}", msg);
@ -815,15 +829,15 @@ fn tpl_matches_reply(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true)
}
fn tpl_matches_forward(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
fn tpl_matches_forward(ctx: &Ctx, account: &Account, matches: &clap::ArgMatches) -> Result<bool> {
debug!("forward command matched");
let uid = matches.value_of("uid").unwrap();
debug!("uid: {}", uid);
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let mut imap_conn = ImapConnector::new(&account)?;
let mut msg = imap_conn.get_msg(&ctx.mbox, &uid)?;
msg.change_to_forwarding(&ctx);
msg.change_to_forwarding(&account);
override_msg_with_args(&mut msg, &matches);
@ -835,7 +849,7 @@ fn tpl_matches_forward(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
/// This function opens the prompt to do some actions to the msg like sending, editing it again and
/// so on.
fn msg_interaction<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
fn msg_interaction<SMTP: smtp::service::SMTPServiceInterface>(
ctx: &Ctx,
msg: &mut Msg,
imap_conn: &mut ImapConnector,

File diff suppressed because it is too large Load diff

View file

@ -1,35 +0,0 @@
use anyhow::Result;
use lettre::{
self,
transport::{smtp::client::Tls, smtp::client::TlsParameters, smtp::SmtpTransport},
Transport,
};
use crate::config::model::Account;
pub fn send(account: &Account, msg: &lettre::Message) -> Result<()> {
let smtp_relay = if account.smtp_starttls() {
SmtpTransport::starttls_relay
} else {
SmtpTransport::relay
};
let tls = TlsParameters::builder(account.smtp_host.to_string())
.dangerous_accept_invalid_hostnames(account.smtp_insecure())
.dangerous_accept_invalid_certs(account.smtp_insecure())
.build()?;
let tls = if account.smtp_starttls() {
Tls::Required(tls)
} else {
Tls::Wrapper(tls)
};
smtp_relay(&account.smtp_host)?
.port(account.smtp_port)
.tls(tls)
.credentials(account.smtp_creds()?)
.build()
.send(msg)?;
Ok(())
}

View file

@ -1,149 +1,150 @@
use std::convert::TryFrom;
// FIXME: fix tests
// use std::convert::TryFrom;
use himalaya::{
config::model::Account, flag::model::Flags, imap::model::ImapConnector, mbox::model::Mboxes,
msg::model::Msgs, smtp,
};
// use himalaya::{
// domain::account::entity::Account, flag::model::Flags, imap::model::ImapConnector,
// mbox::model::Mboxes, msg::model::Msgs,
// };
use imap::types::Flag;
// use imap::types::Flag;
use lettre::message::SinglePart;
use lettre::Message;
// use lettre::message::SinglePart;
// use lettre::Message;
fn get_account(addr: &str) -> Account {
Account {
name: None,
downloads_dir: None,
signature_delimiter: None,
signature: None,
default_page_size: None,
default: Some(true),
email: addr.into(),
watch_cmds: None,
imap_host: String::from("localhost"),
imap_port: 3993,
imap_starttls: Some(false),
imap_insecure: Some(true),
imap_login: addr.into(),
imap_passwd_cmd: String::from("echo 'password'"),
smtp_host: String::from("localhost"),
smtp_port: 3465,
smtp_starttls: Some(false),
smtp_insecure: Some(true),
smtp_login: addr.into(),
smtp_passwd_cmd: String::from("echo 'password'"),
}
}
// fn get_account(addr: &str) -> Account {
// Account {
// name: None,
// downloads_dir: None,
// signature_delimiter: None,
// signature: None,
// default_page_size: None,
// default: Some(true),
// email: addr.into(),
// watch_cmds: None,
// imap_host: String::from("localhost"),
// imap_port: 3993,
// imap_starttls: Some(false),
// imap_insecure: Some(true),
// imap_login: addr.into(),
// imap_passwd_cmd: String::from("echo 'password'"),
// smtp_host: String::from("localhost"),
// smtp_port: 3465,
// smtp_starttls: Some(false),
// smtp_insecure: Some(true),
// smtp_login: addr.into(),
// smtp_passwd_cmd: String::from("echo 'password'"),
// }
// }
#[test]
fn mbox() {
let account = get_account("inbox@localhost");
let mut imap_conn = ImapConnector::new(&account).unwrap();
let names = imap_conn.list_mboxes().unwrap();
let mboxes: Vec<String> = Mboxes::from(&names)
.0
.into_iter()
.map(|mbox| mbox.name)
.collect();
assert_eq!(mboxes, vec![String::from("INBOX")]);
imap_conn.logout();
}
// #[test]
// fn mbox() {
// let account = get_account("inbox@localhost");
// let mut imap_conn = ImapConnector::new(&account).unwrap();
// let names = imap_conn.list_mboxes().unwrap();
// let mboxes: Vec<String> = Mboxes::from(&names)
// .0
// .into_iter()
// .map(|mbox| mbox.name)
// .collect();
// assert_eq!(mboxes, vec![String::from("INBOX")]);
// imap_conn.logout();
// }
#[test]
fn msg() {
// Preparations
// #[test]
// fn msg() {
// // Preparations
// Get the test-account and clean up the server.
let account = get_account("inbox@localhost");
// // Get the test-account and clean up the server.
// let account = get_account("inbox@localhost");
// Login
let mut imap_conn = ImapConnector::new(&account).unwrap();
// // Login
// let mut imap_conn = ImapConnector::new(&account).unwrap();
// remove all previous mails first
let fetches = imap_conn.list_msgs("INBOX", &10, &0).unwrap();
let msgs = if let Some(ref fetches) = fetches {
Msgs::try_from(fetches).unwrap()
} else {
Msgs::new()
};
// // remove all previous mails first
// let fetches = imap_conn.list_msgs("INBOX", &10, &0).unwrap();
// let msgs = if let Some(ref fetches) = fetches {
// Msgs::try_from(fetches).unwrap()
// } else {
// Msgs::new()
// };
// mark all mails as deleted
for msg in msgs.0.iter() {
imap_conn
.add_flags(
"INBOX",
&msg.get_uid().unwrap().to_string(),
Flags::from(vec![Flag::Deleted]),
)
.unwrap();
}
imap_conn.expunge("INBOX").unwrap();
// // mark all mails as deleted
// for msg in msgs.0.iter() {
// imap_conn
// .add_flags(
// "INBOX",
// &msg.get_uid().unwrap().to_string(),
// Flags::from(vec![Flag::Deleted]),
// )
// .unwrap();
// }
// imap_conn.expunge("INBOX").unwrap();
// make sure, that they are *really* deleted
assert!(imap_conn.list_msgs("INBOX", &10, &0).unwrap().is_none());
// // make sure, that they are *really* deleted
// assert!(imap_conn.list_msgs("INBOX", &10, &0).unwrap().is_none());
// == Testing ==
// Add messages
let message_a = Message::builder()
.from("sender-a@localhost".parse().unwrap())
.to("inbox@localhost".parse().unwrap())
.subject("Subject A")
.singlepart(SinglePart::builder().body("Body A".as_bytes().to_vec()))
.unwrap();
// // == Testing ==
// // Add messages
// let message_a = Message::builder()
// .from("sender-a@localhost".parse().unwrap())
// .to("inbox@localhost".parse().unwrap())
// .subject("Subject A")
// .singlepart(SinglePart::builder().body("Body A".as_bytes().to_vec()))
// .unwrap();
let message_b = Message::builder()
.from("Sender B <sender-b@localhost>".parse().unwrap())
.to("inbox@localhost".parse().unwrap())
.subject("Subject B")
.singlepart(SinglePart::builder().body("Body B".as_bytes().to_vec()))
.unwrap();
// let message_b = Message::builder()
// .from("Sender B <sender-b@localhost>".parse().unwrap())
// .to("inbox@localhost".parse().unwrap())
// .subject("Subject B")
// .singlepart(SinglePart::builder().body("Body B".as_bytes().to_vec()))
// .unwrap();
smtp::send(&account, &message_a).unwrap();
smtp::send(&account, &message_b).unwrap();
// smtp::send(&account, &message_a).unwrap();
// smtp::send(&account, &message_b).unwrap();
// -- Get the messages --
// TODO: check non-existance of \Seen flag
let msgs = imap_conn.list_msgs("INBOX", &10, &0).unwrap();
let msgs = if let Some(ref fetches) = msgs {
Msgs::try_from(fetches).unwrap()
} else {
Msgs::new()
};
// // -- Get the messages --
// // TODO: check non-existance of \Seen flag
// let msgs = imap_conn.list_msgs("INBOX", &10, &0).unwrap();
// let msgs = if let Some(ref fetches) = msgs {
// Msgs::try_from(fetches).unwrap()
// } else {
// Msgs::new()
// };
// make sure that there are both mails which we sended
assert_eq!(msgs.0.len(), 2);
// // make sure that there are both mails which we sended
// assert_eq!(msgs.0.len(), 2);
let msg_a = msgs
.0
.iter()
.find(|msg| msg.headers.subject.clone().unwrap() == "Subject A")
.unwrap();
// let msg_a = msgs
// .0
// .iter()
// .find(|msg| msg.headers.subject.clone().unwrap() == "Subject A")
// .unwrap();
let msg_b = msgs
.0
.iter()
.find(|msg| msg.headers.subject.clone().unwrap() == "Subject B")
.unwrap();
// let msg_b = msgs
// .0
// .iter()
// .find(|msg| msg.headers.subject.clone().unwrap() == "Subject B")
// .unwrap();
// -- Checkup --
// look, if we received the correct credentials of the msgs.
assert_eq!(
msg_a.headers.subject.clone().unwrap_or_default(),
"Subject A"
);
assert_eq!(&msg_a.headers.from[0], "sender-a@localhost");
// // -- Checkup --
// // look, if we received the correct credentials of the msgs.
// assert_eq!(
// msg_a.headers.subject.clone().unwrap_or_default(),
// "Subject A"
// );
// assert_eq!(&msg_a.headers.from[0], "sender-a@localhost");
assert_eq!(
msg_b.headers.subject.clone().unwrap_or_default(),
"Subject B"
);
assert_eq!(&msg_b.headers.from[0], "Sender B <sender-b@localhost>");
// assert_eq!(
// msg_b.headers.subject.clone().unwrap_or_default(),
// "Subject B"
// );
// assert_eq!(&msg_b.headers.from[0], "Sender B <sender-b@localhost>");
// TODO: search messages
// TODO: read message (+ \Seen flag)
// TODO: list message attachments
// TODO: add/set/remove flags
// // TODO: search messages
// // TODO: read message (+ \Seen flag)
// // TODO: list message attachments
// // TODO: add/set/remove flags
// Logout
imap_conn.logout();
}
// // Logout
// imap_conn.logout();
// }