clean process and account modules (#340)

This commit is contained in:
Clément DOUIN 2022-06-27 01:13:55 +02:00
parent a5c4fdaac6
commit c0e002ea1b
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
27 changed files with 251 additions and 216 deletions

View file

@ -3,7 +3,7 @@
//! This module gathers all account actions triggered by the CLI. //! This module gathers all account actions triggered by the CLI.
use anyhow::Result; use anyhow::Result;
use himalaya_lib::account::{AccountConfig, DeserializedConfig}; use himalaya_lib::account::{Account, DeserializedConfig};
use log::{info, trace}; use log::{info, trace};
use crate::{ use crate::{
@ -15,7 +15,7 @@ use crate::{
pub fn list<'a, P: PrinterService>( pub fn list<'a, P: PrinterService>(
max_width: Option<usize>, max_width: Option<usize>,
config: &DeserializedConfig, config: &DeserializedConfig,
account_config: &AccountConfig, account_config: &Account,
printer: &mut P, printer: &mut P,
) -> Result<()> { ) -> Result<()> {
info!(">> account list handler"); info!(">> account list handler");
@ -38,7 +38,7 @@ pub fn list<'a, P: PrinterService>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use himalaya_lib::account::{ use himalaya_lib::account::{
AccountConfig, DeserializedAccountConfig, DeserializedConfig, DeserializedImapAccountConfig, Account, DeserializedAccountConfig, DeserializedConfig, DeserializedImapAccountConfig,
}; };
use std::{collections::HashMap, fmt::Debug, io, iter::FromIterator}; use std::{collections::HashMap, fmt::Debug, io, iter::FromIterator};
use termcolor::ColorSpec; use termcolor::ColorSpec;
@ -122,7 +122,7 @@ mod tests {
..DeserializedConfig::default() ..DeserializedConfig::default()
}; };
let account_config = AccountConfig::default(); let account_config = Account::default();
let mut printer = PrinterServiceTest::default(); let mut printer = PrinterServiceTest::default();
assert!(list(None, &config, &account_config, &mut printer).is_ok()); assert!(list(None, &config, &account_config, &mut printer).is_ok());

View file

@ -1,6 +1,6 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use himalaya_lib::{ use himalaya_lib::{
account::{AccountConfig, BackendConfig, DeserializedConfig, DEFAULT_INBOX_FOLDER}, account::{Account, BackendConfig, DeserializedConfig, DEFAULT_INBOX_FOLDER},
backend::Backend, backend::Backend,
}; };
use std::{convert::TryFrom, env}; use std::{convert::TryFrom, env};
@ -58,7 +58,7 @@ fn main() -> Result<()> {
if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") { if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") {
let config = DeserializedConfig::from_opt_path(None)?; let config = DeserializedConfig::from_opt_path(None)?;
let (account_config, backend_config) = let (account_config, backend_config) =
AccountConfig::from_config_and_opt_account_name(&config, None)?; Account::from_config_and_opt_account_name(&config, None)?;
let mut printer = StdoutPrinter::from(OutputFmt::Plain); let mut printer = StdoutPrinter::from(OutputFmt::Plain);
let url = Url::parse(&raw_args[1])?; let url = Url::parse(&raw_args[1])?;
let mut smtp = LettreService::from(&account_config); let mut smtp = LettreService::from(&account_config);
@ -114,7 +114,7 @@ fn main() -> Result<()> {
// Init entities and services. // Init entities and services.
let config = DeserializedConfig::from_opt_path(m.value_of("config"))?; let config = DeserializedConfig::from_opt_path(m.value_of("config"))?;
let (account_config, backend_config) = let (account_config, backend_config) =
AccountConfig::from_config_and_opt_account_name(&config, m.value_of("account"))?; Account::from_config_and_opt_account_name(&config, m.value_of("account"))?;
let mbox = m let mbox = m
.value_of("mbox-source") .value_of("mbox-source")
.or_else(|| account_config.mailboxes.get("inbox").map(|s| s.as_str())) .or_else(|| account_config.mailboxes.get("inbox").map(|s| s.as_str()))

View file

@ -3,7 +3,7 @@
//! This module gathers all mailbox actions triggered by the CLI. //! This module gathers all mailbox actions triggered by the CLI.
use anyhow::Result; use anyhow::Result;
use himalaya_lib::{account::AccountConfig, backend::Backend}; use himalaya_lib::{account::Account, backend::Backend};
use log::{info, trace}; use log::{info, trace};
use crate::output::{PrintTableOpts, PrinterService}; use crate::output::{PrintTableOpts, PrinterService};
@ -11,7 +11,7 @@ use crate::output::{PrintTableOpts, PrinterService};
/// Lists all mailboxes. /// Lists all mailboxes.
pub fn list<'a, P: PrinterService, B: Backend<'a> + ?Sized>( pub fn list<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
max_width: Option<usize>, max_width: Option<usize>,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
) -> Result<()> { ) -> Result<()> {
@ -170,7 +170,7 @@ mod tests {
} }
} }
let config = AccountConfig::default(); let config = Account::default();
let mut printer = PrinterServiceTest::default(); let mut printer = PrinterServiceTest::default();
let mut backend = TestBackend {}; let mut backend = TestBackend {};
let backend = Box::new(&mut backend); let backend = Box::new(&mut backend);

View file

@ -5,7 +5,7 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use atty::Stream; use atty::Stream;
use himalaya_lib::{ use himalaya_lib::{
account::{AccountConfig, DEFAULT_SENT_FOLDER}, account::{Account, DEFAULT_SENT_FOLDER},
backend::Backend, backend::Backend,
msg::{Msg, Part, Parts, TextPlainPart, TplOverride}, msg::{Msg, Part, Parts, TextPlainPart, TplOverride},
}; };
@ -28,7 +28,7 @@ use crate::{
pub fn attachments<'a, P: PrinterService, B: Backend<'a> + ?Sized>( pub fn attachments<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
seq: &str, seq: &str,
mbox: &str, mbox: &str,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
) -> Result<()> { ) -> Result<()> {
@ -92,7 +92,7 @@ pub fn forward<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>(
attachments_paths: Vec<&str>, attachments_paths: Vec<&str>,
encrypt: bool, encrypt: bool,
mbox: &str, mbox: &str,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
smtp: &mut S, smtp: &mut S,
@ -112,7 +112,7 @@ pub fn list<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
page_size: Option<usize>, page_size: Option<usize>,
page: usize, page: usize,
mbox: &str, mbox: &str,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
imap: Box<&'a mut B>, imap: Box<&'a mut B>,
) -> Result<()> { ) -> Result<()> {
@ -134,7 +134,7 @@ pub fn list<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
/// [mailto]: https://en.wikipedia.org/wiki/Mailto /// [mailto]: https://en.wikipedia.org/wiki/Mailto
pub fn mailto<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>( pub fn mailto<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>(
url: &Url, url: &Url,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
smtp: &mut S, smtp: &mut S,
@ -212,7 +212,7 @@ pub fn read<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
raw: bool, raw: bool,
headers: Vec<&str>, headers: Vec<&str>,
mbox: &str, mbox: &str,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
) -> Result<()> { ) -> Result<()> {
@ -233,7 +233,7 @@ pub fn reply<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>(
attachments_paths: Vec<&str>, attachments_paths: Vec<&str>,
encrypt: bool, encrypt: bool,
mbox: &str, mbox: &str,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
smtp: &mut S, smtp: &mut S,
@ -285,7 +285,7 @@ pub fn search<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
page_size: Option<usize>, page_size: Option<usize>,
page: usize, page: usize,
mbox: &str, mbox: &str,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
) -> Result<()> { ) -> Result<()> {
@ -310,7 +310,7 @@ pub fn sort<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
page_size: Option<usize>, page_size: Option<usize>,
page: usize, page: usize,
mbox: &str, mbox: &str,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
) -> Result<()> { ) -> Result<()> {
@ -330,7 +330,7 @@ pub fn sort<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
/// Send a raw message. /// Send a raw message.
pub fn send<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>( pub fn send<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>(
raw_msg: &str, raw_msg: &str,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&mut B>, backend: Box<&mut B>,
smtp: &mut S, smtp: &mut S,
@ -371,7 +371,7 @@ pub fn write<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>(
tpl: TplOverride, tpl: TplOverride,
attachments_paths: Vec<&str>, attachments_paths: Vec<&str>,
encrypt: bool, encrypt: bool,
config: &AccountConfig, config: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
smtp: &mut S, smtp: &mut S,

View file

@ -5,7 +5,7 @@
use anyhow::Result; use anyhow::Result;
use atty::Stream; use atty::Stream;
use himalaya_lib::{ use himalaya_lib::{
account::AccountConfig, account::Account,
backend::Backend, backend::Backend,
msg::{Msg, TplOverride}, msg::{Msg, TplOverride},
}; };
@ -16,7 +16,7 @@ use crate::{output::PrinterService, smtp::SmtpService};
/// Generate a new message template. /// Generate a new message template.
pub fn new<'a, P: PrinterService>( pub fn new<'a, P: PrinterService>(
opts: TplOverride<'a>, opts: TplOverride<'a>,
account: &'a AccountConfig, account: &'a Account,
printer: &'a mut P, printer: &'a mut P,
) -> Result<()> { ) -> Result<()> {
let tpl = Msg::default().to_tpl(opts, account)?; let tpl = Msg::default().to_tpl(opts, account)?;
@ -29,7 +29,7 @@ pub fn reply<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
all: bool, all: bool,
opts: TplOverride<'a>, opts: TplOverride<'a>,
mbox: &str, mbox: &str,
config: &'a AccountConfig, config: &'a Account,
printer: &'a mut P, printer: &'a mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
) -> Result<()> { ) -> Result<()> {
@ -45,7 +45,7 @@ pub fn forward<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
seq: &str, seq: &str,
opts: TplOverride<'a>, opts: TplOverride<'a>,
mbox: &str, mbox: &str,
config: &'a AccountConfig, config: &'a Account,
printer: &'a mut P, printer: &'a mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
) -> Result<()> { ) -> Result<()> {
@ -59,7 +59,7 @@ pub fn forward<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
/// Saves a message based on a template. /// Saves a message based on a template.
pub fn save<'a, P: PrinterService, B: Backend<'a> + ?Sized>( pub fn save<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
mbox: &str, mbox: &str,
config: &AccountConfig, config: &Account,
attachments_paths: Vec<&str>, attachments_paths: Vec<&str>,
tpl: &str, tpl: &str,
printer: &mut P, printer: &mut P,
@ -84,7 +84,7 @@ pub fn save<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
/// Sends a message based on a template. /// Sends a message based on a template.
pub fn send<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>( pub fn send<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>(
mbox: &str, mbox: &str,
account: &AccountConfig, account: &Account,
attachments_paths: Vec<&str>, attachments_paths: Vec<&str>,
tpl: &str, tpl: &str,
printer: &mut P, printer: &mut P,

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use himalaya_lib::account::Format; use himalaya_lib::account::TextPlainFormat;
use std::io; use std::io;
use termcolor::{self, StandardStream}; use termcolor::{self, StandardStream};
@ -12,6 +12,6 @@ pub trait PrintTable {
} }
pub struct PrintTableOpts<'a> { pub struct PrintTableOpts<'a> {
pub format: &'a Format, pub format: &'a TextPlainFormat,
pub max_width: Option<usize>, pub max_width: Option<usize>,
} }

View file

@ -1,5 +1,5 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use himalaya_lib::{account::AccountConfig, msg::Msg}; use himalaya_lib::{account::Account, msg::Msg};
use lettre::{ use lettre::{
self, self,
transport::smtp::{ transport::smtp::{
@ -13,11 +13,11 @@ use std::convert::TryInto;
use crate::output::pipe_cmd; use crate::output::pipe_cmd;
pub trait SmtpService { pub trait SmtpService {
fn send(&mut self, account: &AccountConfig, msg: &Msg) -> Result<Vec<u8>>; fn send(&mut self, account: &Account, msg: &Msg) -> Result<Vec<u8>>;
} }
pub struct LettreService<'a> { pub struct LettreService<'a> {
account: &'a AccountConfig, account: &'a Account,
transport: Option<SmtpTransport>, transport: Option<SmtpTransport>,
} }
@ -56,7 +56,7 @@ impl LettreService<'_> {
} }
impl SmtpService for LettreService<'_> { impl SmtpService for LettreService<'_> {
fn send(&mut self, account: &AccountConfig, msg: &Msg) -> Result<Vec<u8>> { fn send(&mut self, account: &Account, msg: &Msg) -> Result<Vec<u8>> {
let mut raw_msg = msg.into_sendable_msg(account)?.formatted(); let mut raw_msg = msg.into_sendable_msg(account)?.formatted();
let envelope: lettre::address::Envelope = let envelope: lettre::address::Envelope =
@ -76,8 +76,8 @@ impl SmtpService for LettreService<'_> {
} }
} }
impl<'a> From<&'a AccountConfig> for LettreService<'a> { impl<'a> From<&'a Account> for LettreService<'a> {
fn from(account: &'a AccountConfig) -> Self { fn from(account: &'a Account) -> Self {
Self { Self {
account, account,
transport: None, transport: None,

View file

@ -1,6 +1,6 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use himalaya_lib::{ use himalaya_lib::{
account::{AccountConfig, DEFAULT_DRAFT_FOLDER, DEFAULT_SENT_FOLDER}, account::{Account, DEFAULT_DRAFT_FOLDER, DEFAULT_SENT_FOLDER},
backend::Backend, backend::Backend,
msg::{local_draft_path, remove_local_draft, Msg, TplOverride}, msg::{local_draft_path, remove_local_draft, Msg, TplOverride},
}; };
@ -39,7 +39,7 @@ pub fn open_with_draft() -> Result<String> {
open_with_tpl(tpl) open_with_tpl(tpl)
} }
fn _edit_msg_with_editor(msg: &Msg, tpl: TplOverride, account: &AccountConfig) -> Result<Msg> { fn _edit_msg_with_editor(msg: &Msg, tpl: TplOverride, account: &Account) -> Result<Msg> {
let tpl = msg.to_tpl(tpl, account)?; let tpl = msg.to_tpl(tpl, account)?;
let tpl = open_with_tpl(tpl)?; let tpl = open_with_tpl(tpl)?;
Msg::from_tpl(&tpl).context("cannot parse message from template") Msg::from_tpl(&tpl).context("cannot parse message from template")
@ -48,7 +48,7 @@ fn _edit_msg_with_editor(msg: &Msg, tpl: TplOverride, account: &AccountConfig) -
pub fn edit_msg_with_editor<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>( pub fn edit_msg_with_editor<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>(
mut msg: Msg, mut msg: Msg,
tpl: TplOverride, tpl: TplOverride,
account: &AccountConfig, account: &Account,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
smtp: &mut S, smtp: &mut S,

View file

@ -5,7 +5,7 @@
//! [builder design pattern]: https://refactoring.guru/design-patterns/builder //! [builder design pattern]: https://refactoring.guru/design-patterns/builder
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use himalaya_lib::account::Format; use himalaya_lib::account::TextPlainFormat;
use log::trace; use log::trace;
use termcolor::{Color, ColorSpec}; use termcolor::{Color, ColorSpec};
use terminal_size; use terminal_size;
@ -169,11 +169,11 @@ where
/// Writes the table to the writer. /// Writes the table to the writer.
fn print(writer: &mut dyn WriteColor, items: &[Self], opts: PrintTableOpts) -> Result<()> { fn print(writer: &mut dyn WriteColor, items: &[Self], opts: PrintTableOpts) -> Result<()> {
let is_format_flowed = matches!(opts.format, Format::Flowed); let is_format_flowed = matches!(opts.format, TextPlainFormat::Flowed);
let max_width = match opts.format { let max_width = match opts.format {
Format::Fixed(width) => opts.max_width.unwrap_or(*width), TextPlainFormat::Fixed(width) => opts.max_width.unwrap_or(*width),
Format::Flowed => 0, TextPlainFormat::Flowed => 0,
Format::Auto => opts TextPlainFormat::Auto => opts
.max_width .max_width
.or_else(|| terminal_size::terminal_size().map(|(w, _)| w.0 as usize)) .or_else(|| terminal_size::terminal_size().map(|(w, _)| w.0 as usize))
.unwrap_or(DEFAULT_TERM_WIDTH), .unwrap_or(DEFAULT_TERM_WIDTH),

View file

@ -1,52 +1,78 @@
//! Account config module.
//!
//! This module contains the representation of the user account.
use lettre::transport::smtp::authentication::Credentials as SmtpCredentials; use lettre::transport::smtp::authentication::Credentials as SmtpCredentials;
use log::{debug, info, trace}; use log::{debug, info, trace};
use mailparse::MailAddr; use mailparse::MailAddr;
use serde::Deserialize;
use shellexpand; use shellexpand;
use std::{collections::HashMap, env, ffi::OsStr, fs, path::PathBuf, result}; use std::{collections::HashMap, env, ffi::OsStr, fs, path::PathBuf};
use thiserror::Error; use thiserror::Error;
use crate::process; use crate::process::{self, ProcessError};
use super::*; use super::*;
#[derive(Error, Debug)] pub const DEFAULT_PAGE_SIZE: usize = 10;
pub enum Error { pub const DEFAULT_SIG_DELIM: &str = "-- \n";
#[error("cannot run encrypt file command")]
RunEncryptFileCmdError(#[source] process::Error), pub const DEFAULT_INBOX_FOLDER: &str = "INBOX";
#[error("cannot find pgp encrypt file command from config")] pub const DEFAULT_SENT_FOLDER: &str = "Sent";
FindPgpEncryptFileCmdError, pub const DEFAULT_DRAFT_FOLDER: &str = "Drafts";
#[error("cannot find pgp decrypt file command from config")]
FindPgpDecryptFileCmdError, #[derive(Debug, Error)]
pub enum AccountError {
#[error("cannot encrypt file using pgp")]
EncryptFileError(#[source] ProcessError),
#[error("cannot find encrypt file command from config file")]
EncryptFileMissingCmdError,
#[error("cannot decrypt file using pgp")]
DecryptFileError(#[source] ProcessError),
#[error("cannot find decrypt file command from config file")]
DecryptFileMissingCmdError,
#[error("cannot get smtp password")]
GetSmtpPasswdError(#[source] ProcessError),
#[error("cannot get smtp password: password is empty")]
GetSmtpPasswdEmptyError,
#[cfg(feature = "imap-backend")]
#[error("cannot get imap password")]
GetImapPasswdError(#[source] ProcessError),
#[cfg(feature = "imap-backend")]
#[error("cannot get imap password: password is empty")]
GetImapPasswdEmptyError,
#[error("cannot find default account")] #[error("cannot find default account")]
FindDefaultAccountError, FindDefaultAccountError,
#[error("cannot find account \"{0}\"")] #[error("cannot find account {0}")]
FindAccountError(String), FindAccountError(String),
#[error("cannot shell expand")] #[error("cannot parse account address {0}")]
ShellExpandError(#[from] shellexpand::LookupError<env::VarError>), ParseAccountAddrError(#[source] mailparse::MailParseError, String),
#[error("cannot parse account address")] #[error("cannot find account address in {0}")]
ParseAccountAddressError(#[from] mailparse::MailParseError), ParseAccountAddrNotFoundError(String),
#[error("cannot find account address from \"{0}\"")]
FindAccountAddressError(String),
#[error("cannot parse download file name from \"{0}\"")]
ParseDownloadFileNameError(PathBuf),
#[error("cannot find password")]
FindPasswordError,
#[error("cannot get smtp password")]
GetSmtpPasswdError(#[source] process::Error),
#[error("cannot get imap password")]
GetImapPasswdError(#[source] process::Error),
#[error("cannot decrypt pgp file")]
DecryptPgpFileError(#[source] process::Error),
#[error("cannot run notify command")]
RunNotifyCmdError(#[source] process::Error),
}
pub type Result<T> = result::Result<T, Error>; #[cfg(feature = "maildir-backend")]
#[error("cannot expand maildir path")]
ExpandMaildirPathError(#[source] shellexpand::LookupError<env::VarError>),
#[cfg(feature = "notmuch-backend")]
#[error("cannot expand notmuch path")]
ExpandNotmuchDatabasePathError(#[source] shellexpand::LookupError<env::VarError>),
#[error("cannot expand mailbox alias {1}")]
ExpandMboxAliasError(#[source] shellexpand::LookupError<env::VarError>, String),
#[error("cannot parse download file name from {0}")]
ParseDownloadFileNameError(PathBuf),
#[error("cannot start the notify mode")]
StartNotifyModeError(#[source] ProcessError),
}
/// Represents the user account. /// Represents the user account.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct AccountConfig { pub struct Account {
/// Represents the name of the user account. /// Represents the name of the user account.
pub name: String, pub name: String,
/// Makes this account the default one. /// Makes this account the default one.
@ -69,7 +95,7 @@ pub struct AccountConfig {
pub watch_cmds: Vec<String>, pub watch_cmds: Vec<String>,
/// Represents the text/plain format as defined in the /// Represents the text/plain format as defined in the
/// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt) /// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt)
pub format: Format, pub format: TextPlainFormat,
/// Overrides the default headers displayed at the top of /// Overrides the default headers displayed at the top of
/// the read message. /// the read message.
pub read_headers: Vec<String>, pub read_headers: Vec<String>,
@ -99,13 +125,13 @@ pub struct AccountConfig {
pub pgp_decrypt_cmd: Option<String>, pub pgp_decrypt_cmd: Option<String>,
} }
impl<'a> AccountConfig { impl<'a> Account {
/// Tries to create an account from a config and an optional /// Tries to create an account from a config and an optional
/// account name. /// account name.
pub fn from_config_and_opt_account_name( pub fn from_config_and_opt_account_name(
config: &'a DeserializedConfig, config: &'a DeserializedConfig,
account_name: Option<&str>, account_name: Option<&str>,
) -> Result<(AccountConfig, BackendConfig)> { ) -> Result<(Account, BackendConfig), AccountError> {
info!("begin: parsing account and backend configs from config and account name"); info!("begin: parsing account and backend configs from config and account name");
debug!("account name: {:?}", account_name.unwrap_or("default")); debug!("account name: {:?}", account_name.unwrap_or("default"));
@ -126,12 +152,12 @@ impl<'a> AccountConfig {
} }
}) })
.map(|(name, account)| (name.to_owned(), account)) .map(|(name, account)| (name.to_owned(), account))
.ok_or_else(|| Error::FindDefaultAccountError), .ok_or_else(|| AccountError::FindDefaultAccountError),
Some(name) => config Some(name) => config
.accounts .accounts
.get(name) .get(name)
.map(|account| (name.to_owned(), account)) .map(|account| (name.to_owned(), account))
.ok_or_else(|| Error::FindAccountError(name.to_owned())), .ok_or_else(|| AccountError::FindAccountError(name.to_owned())),
}?; }?;
let base_account = account.to_base(); let base_account = account.to_base();
@ -175,7 +201,7 @@ impl<'a> AccountConfig {
.or_else(|| sig.map(|sig| sig.to_owned())) .or_else(|| sig.map(|sig| sig.to_owned()))
.map(|sig| format!("{}{}", sig_delim, sig.trim_end())); .map(|sig| format!("{}{}", sig_delim, sig.trim_end()));
let account_config = AccountConfig { let account_config = Account {
name, name,
display_name: base_account display_name: base_account
.name .name
@ -234,13 +260,17 @@ impl<'a> AccountConfig {
#[cfg(feature = "maildir-backend")] #[cfg(feature = "maildir-backend")]
DeserializedAccountConfig::Maildir(config) => { DeserializedAccountConfig::Maildir(config) => {
BackendConfig::Maildir(MaildirBackendConfig { BackendConfig::Maildir(MaildirBackendConfig {
maildir_dir: shellexpand::full(&config.maildir_dir)?.to_string().into(), maildir_dir: shellexpand::full(&config.maildir_dir)
.map_err(AccountError::ExpandMaildirPathError)?
.to_string()
.into(),
}) })
} }
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
DeserializedAccountConfig::Notmuch(config) => { DeserializedAccountConfig::Notmuch(config) => {
BackendConfig::Notmuch(NotmuchBackendConfig { BackendConfig::Notmuch(NotmuchBackendConfig {
notmuch_database_dir: shellexpand::full(&config.notmuch_database_dir)? notmuch_database_dir: shellexpand::full(&config.notmuch_database_dir)
.map_err(AccountError::ExpandNotmuchDatabasePathError)?
.to_string() .to_string()
.into(), .into(),
}) })
@ -253,7 +283,7 @@ impl<'a> AccountConfig {
} }
/// Builds the full RFC822 compliant address of the user account. /// Builds the full RFC822 compliant address of the user account.
pub fn address(&self) -> Result<MailAddr> { pub fn address(&self) -> Result<MailAddr, AccountError> {
let has_special_chars = "()<>[]:;@.,".contains(|c| self.display_name.contains(c)); let has_special_chars = "()<>[]:;@.,".contains(|c| self.display_name.contains(c));
let addr = if self.display_name.is_empty() { let addr = if self.display_name.is_empty() {
self.email.clone() self.email.clone()
@ -264,19 +294,21 @@ impl<'a> AccountConfig {
format!("{} <{}>", self.display_name, self.email) format!("{} <{}>", self.display_name, self.email)
}; };
Ok(mailparse::addrparse(&addr)? Ok(mailparse::addrparse(&addr)
.map_err(|err| AccountError::ParseAccountAddrError(err, addr.to_owned()))?
.first() .first()
.ok_or_else(|| Error::FindAccountAddressError(addr.into()))? .ok_or_else(|| AccountError::ParseAccountAddrNotFoundError(addr.to_owned()))?
.clone()) .clone())
} }
/// Builds the user account SMTP credentials. /// Builds the user account SMTP credentials.
pub fn smtp_creds(&self) -> Result<SmtpCredentials> { pub fn smtp_creds(&self) -> Result<SmtpCredentials, AccountError> {
let passwd = process::run_cmd(&self.smtp_passwd_cmd).map_err(Error::GetSmtpPasswdError)?; let passwd =
process::run(&self.smtp_passwd_cmd).map_err(AccountError::GetSmtpPasswdError)?;
let passwd = passwd let passwd = passwd
.lines() .lines()
.next() .next()
.ok_or_else(|| Error::FindPasswordError)?; .ok_or_else(|| AccountError::GetSmtpPasswdEmptyError)?;
Ok(SmtpCredentials::new( Ok(SmtpCredentials::new(
self.smtp_login.to_owned(), self.smtp_login.to_owned(),
@ -285,27 +317,30 @@ impl<'a> AccountConfig {
} }
/// Encrypts a file. /// Encrypts a file.
pub fn pgp_encrypt_file(&self, addr: &str, path: PathBuf) -> Result<String> { pub fn pgp_encrypt_file(&self, addr: &str, path: PathBuf) -> Result<String, AccountError> {
if let Some(cmd) = self.pgp_encrypt_cmd.as_ref() { if let Some(cmd) = self.pgp_encrypt_cmd.as_ref() {
let encrypt_file_cmd = format!("{} {} {:?}", cmd, addr, path); let encrypt_file_cmd = format!("{} {} {:?}", cmd, addr, path);
Ok(process::run_cmd(&encrypt_file_cmd).map_err(Error::RunEncryptFileCmdError)?) Ok(process::run(&encrypt_file_cmd).map_err(AccountError::EncryptFileError)?)
} else { } else {
Err(Error::FindPgpEncryptFileCmdError) Err(AccountError::EncryptFileMissingCmdError)
} }
} }
/// Decrypts a file. /// Decrypts a file.
pub fn pgp_decrypt_file(&self, path: PathBuf) -> Result<String> { pub fn pgp_decrypt_file(&self, path: PathBuf) -> Result<String, AccountError> {
if let Some(cmd) = self.pgp_decrypt_cmd.as_ref() { if let Some(cmd) = self.pgp_decrypt_cmd.as_ref() {
let decrypt_file_cmd = format!("{} {:?}", cmd, path); let decrypt_file_cmd = format!("{} {:?}", cmd, path);
Ok(process::run_cmd(&decrypt_file_cmd).map_err(Error::DecryptPgpFileError)?) Ok(process::run(&decrypt_file_cmd).map_err(AccountError::DecryptFileError)?)
} else { } else {
Err(Error::FindPgpDecryptFileCmdError) Err(AccountError::DecryptFileMissingCmdError)
} }
} }
/// Gets the download path from a file name. /// Gets the download path from a file name.
pub fn get_download_file_path<S: AsRef<str>>(&self, file_name: S) -> Result<PathBuf> { pub fn get_download_file_path<S: AsRef<str>>(
&self,
file_name: S,
) -> Result<PathBuf, AccountError> {
let file_path = self.downloads_dir.join(file_name.as_ref()); let file_path = self.downloads_dir.join(file_name.as_ref());
self.get_unique_download_file_path(&file_path, |path, _count| path.is_file()) self.get_unique_download_file_path(&file_path, |path, _count| path.is_file())
} }
@ -316,7 +351,7 @@ impl<'a> AccountConfig {
&self, &self,
original_file_path: &PathBuf, original_file_path: &PathBuf,
is_file: impl Fn(&PathBuf, u8) -> bool, is_file: impl Fn(&PathBuf, u8) -> bool,
) -> Result<PathBuf> { ) -> Result<PathBuf, AccountError> {
let mut count = 0; let mut count = 0;
let file_ext = original_file_path let file_ext = original_file_path
.extension() .extension()
@ -332,7 +367,9 @@ impl<'a> AccountConfig {
.file_stem() .file_stem()
.and_then(OsStr::to_str) .and_then(OsStr::to_str)
.map(|fstem| format!("{}_{}{}", fstem, count, file_ext)) .map(|fstem| format!("{}_{}{}", fstem, count, file_ext))
.ok_or_else(|| Error::ParseDownloadFileNameError(file_path.to_owned()))?, .ok_or_else(|| {
AccountError::ParseDownloadFileNameError(file_path.to_owned())
})?,
)); ));
} }
@ -340,7 +377,7 @@ impl<'a> AccountConfig {
} }
/// Runs the notify command. /// Runs the notify command.
pub fn run_notify_cmd<S: AsRef<str>>(&self, subject: S, sender: S) -> Result<()> { pub fn run_notify_cmd<S: AsRef<str>>(&self, subject: S, sender: S) -> Result<(), AccountError> {
let subject = subject.as_ref(); let subject = subject.as_ref();
let sender = sender.as_ref(); let sender = sender.as_ref();
@ -351,19 +388,21 @@ impl<'a> AccountConfig {
.map(|cmd| format!(r#"{} {:?} {:?}"#, cmd, subject, sender)) .map(|cmd| format!(r#"{} {:?} {:?}"#, cmd, subject, sender))
.unwrap_or(default_cmd); .unwrap_or(default_cmd);
process::run_cmd(&cmd).map_err(Error::RunNotifyCmdError)?; process::run(&cmd).map_err(AccountError::StartNotifyModeError)?;
Ok(()) Ok(())
} }
/// Gets the mailbox alias if exists, otherwise returns the /// Gets the mailbox alias if exists, otherwise returns the
/// mailbox. Also tries to expand shell variables. /// mailbox. Also tries to expand shell variables.
pub fn get_mbox_alias(&self, mbox: &str) -> Result<String> { pub fn get_mbox_alias(&self, mbox: &str) -> Result<String, AccountError> {
let mbox = self let mbox = self
.mailboxes .mailboxes
.get(&mbox.trim().to_lowercase()) .get(&mbox.trim().to_lowercase())
.map(|s| s.as_str()) .map(|s| s.as_str())
.unwrap_or(mbox); .unwrap_or(mbox);
let mbox = shellexpand::full(mbox).map(String::from)?; let mbox = shellexpand::full(mbox)
.map(String::from)
.map_err(|err| AccountError::ExpandMboxAliasError(err, mbox.to_owned()))?;
Ok(mbox) Ok(mbox)
} }
} }
@ -400,12 +439,13 @@ pub struct ImapBackendConfig {
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
impl ImapBackendConfig { impl ImapBackendConfig {
/// Gets the IMAP password of the user account. /// Gets the IMAP password of the user account.
pub fn imap_passwd(&self) -> Result<String> { pub fn imap_passwd(&self) -> Result<String, AccountError> {
let passwd = process::run_cmd(&self.imap_passwd_cmd).map_err(Error::GetImapPasswdError)?; let passwd =
process::run(&self.imap_passwd_cmd).map_err(AccountError::GetImapPasswdError)?;
let passwd = passwd let passwd = passwd
.lines() .lines()
.next() .next()
.ok_or_else(|| Error::FindPasswordError)?; .ok_or_else(|| AccountError::GetImapPasswdEmptyError)?;
Ok(passwd.to_string()) Ok(passwd.to_string())
} }
} }
@ -426,13 +466,39 @@ pub struct NotmuchBackendConfig {
pub notmuch_database_dir: PathBuf, pub notmuch_database_dir: PathBuf,
} }
/// Represents the text/plain format as defined in the [RFC2646].
///
/// [RFC2646]: https://www.ietf.org/rfc/rfc2646.txt
#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
#[serde(tag = "type", content = "width", rename_all = "lowercase")]
pub enum TextPlainFormat {
// Forces the content width with a fixed amount of pixels.
Fixed(usize),
// Makes the content fit the terminal.
Auto,
// Does not restrict the content.
Flowed,
}
impl Default for TextPlainFormat {
fn default() -> Self {
Self::Auto
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Hooks {
pub pre_send: Option<String>,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn it_should_get_unique_download_file_path() { fn it_should_get_unique_download_file_path() {
let account = AccountConfig::default(); let account = Account::default();
let path = PathBuf::from("downloads/file.ext"); let path = PathBuf::from("downloads/file.ext");
// When file path is unique // When file path is unique

View file

@ -1,7 +1,12 @@
//! Deserialized account config module.
//!
//! This module contains the raw deserialized representation of an
//! account in the accounts section of the user configuration file.
use serde::Deserialize; use serde::Deserialize;
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf};
use crate::account::{Format, Hooks}; use super::*;
pub trait ToDeserializedBaseAccountConfig { pub trait ToDeserializedBaseAccountConfig {
fn to_base(&self) -> DeserializedBaseAccountConfig; fn to_base(&self) -> DeserializedBaseAccountConfig;
@ -53,9 +58,8 @@ macro_rules! make_account_config {
pub notify_query: Option<String>, pub notify_query: Option<String>,
/// Overrides the watch commands for this account. /// Overrides the watch commands for this account.
pub watch_cmds: Option<Vec<String>>, pub watch_cmds: Option<Vec<String>>,
/// Represents the text/plain format as defined in the /// Represents the text/plain format.
/// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt) pub format: Option<TextPlainFormat>,
pub format: Option<Format>,
/// Represents the default headers displayed at the top of /// Represents the default headers displayed at the top of
/// the read message. /// the read message.
#[serde(default)] #[serde(default)]

View file

@ -1,17 +1,15 @@
//! Deserialized config module.
//!
//! This module contains the raw deserialized representation of the
//! user configuration file.
use log::{debug, trace}; use log::{debug, trace};
use serde::Deserialize; use serde::Deserialize;
use std::{collections::HashMap, env, fs, io, path::PathBuf, result}; use std::{collections::HashMap, env, fs, io, path::PathBuf, result};
use thiserror::Error; use thiserror::Error;
use toml; use toml;
use crate::account::DeserializedAccountConfig; use super::*;
pub const DEFAULT_PAGE_SIZE: usize = 10;
pub const DEFAULT_SIG_DELIM: &str = "-- \n";
pub const DEFAULT_INBOX_FOLDER: &str = "INBOX";
pub const DEFAULT_SENT_FOLDER: &str = "Sent";
pub const DEFAULT_DRAFT_FOLDER: &str = "Drafts";
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum DeserializeConfigError { pub enum DeserializeConfigError {

View file

@ -1,23 +0,0 @@
use serde::Deserialize;
/// Represents the text/plain format as defined in the [RFC2646]. The
/// format is then used by the table system to adjust the way it is
/// rendered.
///
/// [RFC2646]: https://www.ietf.org/rfc/rfc2646.txt
#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
#[serde(tag = "type", content = "width", rename_all = "lowercase")]
pub enum Format {
// Forces the content width with a fixed amount of pixels.
Fixed(usize),
// Makes the content fit the terminal.
Auto,
// Does not restrict the content.
Flowed,
}
impl Default for Format {
fn default() -> Self {
Self::Auto
}
}

View file

@ -1,7 +0,0 @@
use serde::Deserialize;
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Hooks {
pub pre_send: Option<String>,
}

View file

@ -1,16 +1,12 @@
pub mod deserialized_config; //! Account module.
pub use deserialized_config::*; //!
//! This module contains everything related to the user configuration.
pub mod deserialized_account_config; mod account_config;
pub use deserialized_account_config::*;
// pub mod account_handlers;
pub mod account_config;
pub use account_config::*; pub use account_config::*;
pub mod format; mod deserialized_config;
pub use format::*; pub use deserialized_config::*;
pub mod hooks; mod deserialized_account_config;
pub use hooks::*; pub use deserialized_account_config::*;

View file

@ -27,7 +27,7 @@ pub enum Error {
ImapError(#[from] super::imap::Error), ImapError(#[from] super::imap::Error),
#[error(transparent)] #[error(transparent)]
AccountError(#[from] account::Error), AccountError(#[from] account::AccountError),
#[error(transparent)] #[error(transparent)]
MsgError(#[from] msg::Error), MsgError(#[from] msg::Error),

View file

@ -78,7 +78,7 @@ pub enum Error {
LogoutError(#[source] imap::Error), LogoutError(#[source] imap::Error),
#[error(transparent)] #[error(transparent)]
AccountError(#[from] account::Error), AccountError(#[from] account::AccountError),
#[error(transparent)] #[error(transparent)]
MsgError(#[from] msg::Error), MsgError(#[from] msg::Error),
} }

View file

@ -8,26 +8,26 @@ use native_tls::{TlsConnector, TlsStream};
use std::{collections::HashSet, convert::TryInto, net::TcpStream, thread}; use std::{collections::HashSet, convert::TryInto, net::TcpStream, thread};
use crate::{ use crate::{
account::{AccountConfig, ImapBackendConfig}, account::{Account, ImapBackendConfig},
backend::{ backend::{
backend::Result, from_imap_fetch, from_imap_fetches, backend::Result, from_imap_fetch, from_imap_fetches,
imap::msg_sort_criterion::SortCriteria, imap::Error, into_imap_flags, Backend, imap::msg_sort_criterion::SortCriteria, imap::Error, into_imap_flags, Backend,
}, },
mbox::{Mbox, Mboxes}, mbox::{Mbox, Mboxes},
msg::{Envelopes, Flags, Msg}, msg::{Envelopes, Flags, Msg},
process::run_cmd, process,
}; };
type ImapSess = imap::Session<TlsStream<TcpStream>>; type ImapSess = imap::Session<TlsStream<TcpStream>>;
pub struct ImapBackend<'a> { pub struct ImapBackend<'a> {
account_config: &'a AccountConfig, account_config: &'a Account,
imap_config: &'a ImapBackendConfig, imap_config: &'a ImapBackendConfig,
sess: Option<ImapSess>, sess: Option<ImapSess>,
} }
impl<'a> ImapBackend<'a> { impl<'a> ImapBackend<'a> {
pub fn new(account_config: &'a AccountConfig, imap_config: &'a ImapBackendConfig) -> Self { pub fn new(account_config: &'a Account, imap_config: &'a ImapBackendConfig) -> Self {
Self { Self {
account_config, account_config,
imap_config, imap_config,
@ -187,7 +187,7 @@ impl<'a> ImapBackend<'a> {
debug!("batch execution of {} cmd(s)", cmds.len()); debug!("batch execution of {} cmd(s)", cmds.len());
cmds.iter().for_each(|cmd| { cmds.iter().for_each(|cmd| {
debug!("running command {:?}…", cmd); debug!("running command {:?}…", cmd);
let res = run_cmd(cmd); let res = process::run(cmd);
debug!("{:?}", res); debug!("{:?}", res);
}) })
}); });

View file

@ -7,7 +7,7 @@ use log::{debug, info, trace};
use std::{env, ffi::OsStr, fs, path::PathBuf}; use std::{env, ffi::OsStr, fs, path::PathBuf};
use crate::{ use crate::{
account::{AccountConfig, MaildirBackendConfig}, account::{Account, MaildirBackendConfig},
backend::{backend::Result, maildir_envelopes, maildir_flags, Backend, IdMapper}, backend::{backend::Result, maildir_envelopes, maildir_flags, Backend, IdMapper},
mbox::{Mbox, Mboxes}, mbox::{Mbox, Mboxes},
msg::{Envelopes, Flags, Msg}, msg::{Envelopes, Flags, Msg},
@ -17,15 +17,12 @@ use super::MaildirError;
/// Represents the maildir backend. /// Represents the maildir backend.
pub struct MaildirBackend<'a> { pub struct MaildirBackend<'a> {
account_config: &'a AccountConfig, account_config: &'a Account,
mdir: maildir::Maildir, mdir: maildir::Maildir,
} }
impl<'a> MaildirBackend<'a> { impl<'a> MaildirBackend<'a> {
pub fn new( pub fn new(account_config: &'a Account, maildir_config: &'a MaildirBackendConfig) -> Self {
account_config: &'a AccountConfig,
maildir_config: &'a MaildirBackendConfig,
) -> Self {
Self { Self {
account_config, account_config,
mdir: maildir_config.maildir_dir.clone().into(), mdir: maildir_config.maildir_dir.clone().into(),

View file

@ -2,7 +2,7 @@ use log::{debug, info, trace};
use std::fs; use std::fs;
use crate::{ use crate::{
account::{AccountConfig, NotmuchBackendConfig}, account::{Account, NotmuchBackendConfig},
backend::{ backend::{
backend::Result, notmuch_envelopes, Backend, IdMapper, MaildirBackend, NotmuchError, backend::Result, notmuch_envelopes, Backend, IdMapper, MaildirBackend, NotmuchError,
}, },
@ -12,7 +12,7 @@ use crate::{
/// Represents the Notmuch backend. /// Represents the Notmuch backend.
pub struct NotmuchBackend<'a> { pub struct NotmuchBackend<'a> {
account_config: &'a AccountConfig, account_config: &'a Account,
notmuch_config: &'a NotmuchBackendConfig, notmuch_config: &'a NotmuchBackendConfig,
pub mdir: &'a mut MaildirBackend<'a>, pub mdir: &'a mut MaildirBackend<'a>,
db: notmuch::Database, db: notmuch::Database,
@ -20,7 +20,7 @@ pub struct NotmuchBackend<'a> {
impl<'a> NotmuchBackend<'a> { impl<'a> NotmuchBackend<'a> {
pub fn new( pub fn new(
account_config: &'a AccountConfig, account_config: &'a Account,
notmuch_config: &'a NotmuchBackendConfig, notmuch_config: &'a NotmuchBackendConfig,
mdir: &'a mut MaildirBackend<'a>, mdir: &'a mut MaildirBackend<'a>,
) -> Result<NotmuchBackend<'a>> { ) -> Result<NotmuchBackend<'a>> {

View file

@ -34,7 +34,7 @@ pub enum Error {
ParseAddressError(#[from] lettre::address::AddressError), ParseAddressError(#[from] lettre::address::AddressError),
#[error(transparent)] #[error(transparent)]
AccountError(#[from] account::Error), AccountError(#[from] account::AccountError),
#[error("cannot get content type of multipart")] #[error("cannot get content type of multipart")]
GetMultipartContentTypeError, GetMultipartContentTypeError,
@ -47,7 +47,7 @@ pub enum Error {
#[error("cannot write encrypted part to temporary file")] #[error("cannot write encrypted part to temporary file")]
WriteEncryptedPartBodyError(#[source] io::Error), WriteEncryptedPartBodyError(#[source] io::Error),
#[error("cannot write encrypted part to temporary file")] #[error("cannot write encrypted part to temporary file")]
DecryptPartError(#[source] account::Error), DecryptPartError(#[source] account::AccountError),
#[error("cannot delete local draft: {1}")] #[error("cannot delete local draft: {1}")]
DeleteLocalDraftError(#[source] io::Error, path::PathBuf), DeleteLocalDraftError(#[source] io::Error, path::PathBuf),

View file

@ -17,7 +17,7 @@ use tree_magic;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
account::{AccountConfig, DEFAULT_SIG_DELIM}, account::{Account, DEFAULT_SIG_DELIM},
msg::{ msg::{
from_addrs_to_sendable_addrs, from_addrs_to_sendable_mbox, from_slice_to_addrs, Addr, from_addrs_to_sendable_addrs, from_addrs_to_sendable_mbox, from_slice_to_addrs, Addr,
Addrs, BinaryPart, Error, Part, Parts, Result, TextPlainPart, TplOverride, Addrs, BinaryPart, Error, Part, Parts, Result, TextPlainPart, TplOverride,
@ -166,7 +166,7 @@ impl Msg {
} }
} }
pub fn into_reply(mut self, all: bool, account: &AccountConfig) -> Result<Self> { pub fn into_reply(mut self, all: bool, account: &Account) -> Result<Self> {
let account_addr = account.address()?; let account_addr = account.address()?;
// In-Reply-To // In-Reply-To
@ -264,7 +264,7 @@ impl Msg {
Ok(self) Ok(self)
} }
pub fn into_forward(mut self, account: &AccountConfig) -> Result<Self> { pub fn into_forward(mut self, account: &Account) -> Result<Self> {
let account_addr = account.address()?; let account_addr = account.address()?;
let prev_subject = self.subject.to_owned(); let prev_subject = self.subject.to_owned();
@ -380,7 +380,7 @@ impl Msg {
} }
} }
pub fn to_tpl(&self, opts: TplOverride, account: &AccountConfig) -> Result<String> { pub fn to_tpl(&self, opts: TplOverride, account: &Account) -> Result<String> {
let account_addr: Addrs = vec![account.address()?].into(); let account_addr: Addrs = vec![account.address()?].into();
let mut tpl = String::default(); let mut tpl = String::default();
@ -463,10 +463,10 @@ impl Msg {
let parsed_mail = mailparse::parse_mail(tpl.as_bytes()).map_err(Error::ParseTplError)?; let parsed_mail = mailparse::parse_mail(tpl.as_bytes()).map_err(Error::ParseTplError)?;
info!("end: building message from template"); info!("end: building message from template");
Self::from_parsed_mail(parsed_mail, &AccountConfig::default()) Self::from_parsed_mail(parsed_mail, &Account::default())
} }
pub fn into_sendable_msg(&self, account: &AccountConfig) -> Result<lettre::Message> { pub fn into_sendable_msg(&self, account: &Account) -> Result<lettre::Message> {
let mut msg_builder = lettre::Message::builder() let mut msg_builder = lettre::Message::builder()
.message_id(self.message_id.to_owned()) .message_id(self.message_id.to_owned())
.subject(self.subject.to_owned()); .subject(self.subject.to_owned());
@ -551,7 +551,7 @@ impl Msg {
pub fn from_parsed_mail( pub fn from_parsed_mail(
parsed_mail: mailparse::ParsedMail<'_>, parsed_mail: mailparse::ParsedMail<'_>,
config: &AccountConfig, config: &Account,
) -> Result<Self> { ) -> Result<Self> {
trace!(">> build message from parsed mail"); trace!(">> build message from parsed mail");
trace!("parsed mail: {:?}", parsed_mail); trace!("parsed mail: {:?}", parsed_mail);
@ -623,7 +623,7 @@ impl Msg {
&self, &self,
text_mime: &str, text_mime: &str,
headers: Vec<&str>, headers: Vec<&str>,
config: &AccountConfig, config: &Account,
) -> Result<String> { ) -> Result<String> {
let mut all_headers = vec![]; let mut all_headers = vec![];
for h in config.read_headers.iter() { for h in config.read_headers.iter() {
@ -750,10 +750,10 @@ mod tests {
#[test] #[test]
fn test_into_reply() { fn test_into_reply() {
let config = AccountConfig { let config = Account {
display_name: "Test".into(), display_name: "Test".into(),
email: "test-account@local".into(), email: "test-account@local".into(),
..AccountConfig::default() ..Account::default()
}; };
// Checks that: // Checks that:
@ -889,7 +889,7 @@ mod tests {
#[test] #[test]
fn test_to_readable() { fn test_to_readable() {
let config = AccountConfig::default(); let config = Account::default();
let msg = Msg { let msg = Msg {
parts: Parts(vec![Part::TextPlain(TextPlainPart { parts: Parts(vec![Part::TextPlain(TextPlainPart {
content: String::from("hello, world!"), content: String::from("hello, world!"),
@ -952,14 +952,14 @@ mod tests {
.unwrap() .unwrap()
); );
let config = AccountConfig { let config = Account {
read_headers: vec![ read_headers: vec![
"CusTOM-heaDER".into(), "CusTOM-heaDER".into(),
"Subject".into(), "Subject".into(),
"from".into(), "from".into(),
"cc".into(), "cc".into(),
], ],
..AccountConfig::default() ..Account::default()
}; };
// header present but empty in msg headers, empty config // header present but empty in msg headers, empty config
assert_eq!( assert_eq!(

View file

@ -6,7 +6,7 @@ use std::{
}; };
use uuid::Uuid; use uuid::Uuid;
use crate::{account::AccountConfig, msg}; use crate::{account::Account, msg};
#[derive(Debug, Clone, Default, Serialize)] #[derive(Debug, Clone, Default, Serialize)]
pub struct TextPlainPart { pub struct TextPlainPart {
@ -50,7 +50,7 @@ impl Parts {
} }
pub fn from_parsed_mail<'a>( pub fn from_parsed_mail<'a>(
account: &'a AccountConfig, account: &'a Account,
part: &'a mailparse::ParsedMail<'a>, part: &'a mailparse::ParsedMail<'a>,
) -> msg::Result<Self> { ) -> msg::Result<Self> {
let mut parts = vec![]; let mut parts = vec![];
@ -80,7 +80,7 @@ impl DerefMut for Parts {
} }
fn build_parts_map_rec( fn build_parts_map_rec(
account: &AccountConfig, account: &Account,
parsed_mail: &mailparse::ParsedMail, parsed_mail: &mailparse::ParsedMail,
parts: &mut Vec<Part>, parts: &mut Vec<Part>,
) -> msg::Result<()> { ) -> msg::Result<()> {
@ -137,7 +137,7 @@ fn build_parts_map_rec(
Ok(()) Ok(())
} }
fn decrypt_part(account: &AccountConfig, msg: &mailparse::ParsedMail) -> msg::Result<String> { fn decrypt_part(account: &Account, msg: &mailparse::ParsedMail) -> msg::Result<String> {
let msg_path = env::temp_dir().join(Uuid::new_v4().to_string()); let msg_path = env::temp_dir().join(Uuid::new_v4().to_string());
let msg_body = msg let msg_body = msg
.get_body() .get_body()

View file

@ -1,30 +1,34 @@
//! Process module.
//!
//! This module contains cross platform helpers around the
//! `std::process` crate.
use log::{debug, trace}; use log::{debug, trace};
use std::{io, process, result, string}; use std::{io, process::Command, string};
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Debug, Error)]
pub enum Error { pub enum ProcessError {
#[error("cannot run command: {1}")] #[error("cannot run command {1:?}")]
RunCmdError(#[source] io::Error, String), RunCmdError(#[source] io::Error, String),
#[error("cannot parse command output")] #[error("cannot parse command output")]
ParseCmdOutputError(#[source] string::FromUtf8Error), ParseCmdOutputError(#[source] string::FromUtf8Error),
} }
pub type Result<T> = result::Result<T, Error>; pub fn run(cmd: &str) -> Result<String, ProcessError> {
debug!(">> run command");
pub fn run_cmd(cmd: &str) -> Result<String> {
trace!(">> run command");
debug!("command: {}", cmd); debug!("command: {}", cmd);
let output = if cfg!(target_os = "windows") { let output = if cfg!(target_os = "windows") {
process::Command::new("cmd").args(&["/C", cmd]).output() Command::new("cmd").args(&["/C", cmd]).output()
} else { } else {
process::Command::new("sh").arg("-c").arg(cmd).output() Command::new("sh").arg("-c").arg(cmd).output()
}; };
let output = output.map_err(|err| Error::RunCmdError(err, cmd.to_string()))?; let output = output.map_err(|err| ProcessError::RunCmdError(err, cmd.to_string()))?;
let output = String::from_utf8(output.stdout).map_err(Error::ParseCmdOutputError)?; let output = String::from_utf8(output.stdout).map_err(ProcessError::ParseCmdOutputError)?;
debug!("command output: {}", output); trace!("command output: {}", output);
trace!("<< run command"); debug!("<< run command");
Ok(output) Ok(output)
} }

View file

@ -1,6 +1,6 @@
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
use himalaya_lib::{ use himalaya_lib::{
account::{AccountConfig, ImapBackendConfig}, account::{Account, ImapBackendConfig},
backend::{Backend, ImapBackend}, backend::{Backend, ImapBackend},
}; };
@ -8,14 +8,14 @@ use himalaya_lib::{
#[test] #[test]
fn test_imap_backend() { fn test_imap_backend() {
// configure accounts // configure accounts
let account_config = AccountConfig { let account_config = Account {
smtp_host: "localhost".into(), smtp_host: "localhost".into(),
smtp_port: 3465, smtp_port: 3465,
smtp_starttls: false, smtp_starttls: false,
smtp_insecure: true, smtp_insecure: true,
smtp_login: "inbox@localhost".into(), smtp_login: "inbox@localhost".into(),
smtp_passwd_cmd: "echo 'password'".into(), smtp_passwd_cmd: "echo 'password'".into(),
..AccountConfig::default() ..Account::default()
}; };
let imap_config = ImapBackendConfig { let imap_config = ImapBackendConfig {
imap_host: "localhost".into(), imap_host: "localhost".into(),

View file

@ -2,7 +2,7 @@ use maildir::Maildir;
use std::{collections::HashMap, env, fs, iter::FromIterator}; use std::{collections::HashMap, env, fs, iter::FromIterator};
use himalaya_lib::{ use himalaya_lib::{
account::{AccountConfig, MaildirBackendConfig}, account::{Account, MaildirBackendConfig},
backend::{Backend, MaildirBackend}, backend::{Backend, MaildirBackend},
msg::Flag, msg::Flag,
}; };
@ -19,9 +19,9 @@ fn test_maildir_backend() {
mdir_sub.create_dirs().unwrap(); mdir_sub.create_dirs().unwrap();
// configure accounts // configure accounts
let account_config = AccountConfig { let account_config = Account {
mailboxes: HashMap::from_iter([("subdir".into(), "Subdir".into())]), mailboxes: HashMap::from_iter([("subdir".into(), "Subdir".into())]),
..AccountConfig::default() ..Account::default()
}; };
let mdir_config = MaildirBackendConfig { let mdir_config = MaildirBackendConfig {
maildir_dir: mdir.path().to_owned(), maildir_dir: mdir.path().to_owned(),

View file

@ -3,7 +3,7 @@ use std::{collections::HashMap, env, fs, iter::FromIterator};
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
use himalaya_lib::{ use himalaya_lib::{
account::{AccountConfig, MaildirBackendConfig, NotmuchBackendConfig}, account::{Account, MaildirBackendConfig, NotmuchBackendConfig},
backend::{Backend, MaildirBackend, NotmuchBackend}, backend::{Backend, MaildirBackend, NotmuchBackend},
}; };