replace xxx-folder config props by mailboxes

This commit is contained in:
Clément DOUIN 2022-02-25 21:56:48 +01:00
parent 34ab0f4fa5
commit b855c44508
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
8 changed files with 56 additions and 57 deletions

View file

@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- No tilde expansion in `maildir-dir` [#305] - No tilde expansion in `maildir-dir` [#305]
- Unknown command SORT [#308] - Unknown command SORT [#308]
### Changed
- [**BREAKING**] Replace `inbox-folder`, `sent-folder` and `draft-folder` by a generic hashmap `mailboxes`
## [0.5.6] - 2022-02-22 ## [0.5.6] - 2022-02-22
### Added ### Added

View file

@ -3,7 +3,7 @@ use std::{convert::TryInto, fs, path::PathBuf};
use crate::{ use crate::{
backends::{Backend, MaildirEnvelopes, MaildirFlags, MaildirMboxes}, backends::{Backend, MaildirEnvelopes, MaildirFlags, MaildirMboxes},
config::{AccountConfig, MaildirBackendConfig}, config::{AccountConfig, MaildirBackendConfig, DEFAULT_INBOX_FOLDER},
mbox::Mboxes, mbox::Mboxes,
msg::{Envelopes, Msg}, msg::{Envelopes, Msg},
}; };
@ -36,7 +36,14 @@ impl<'a> MaildirBackend<'a> {
} }
fn get_mdir_from_name(&self, mdir: &str) -> Result<maildir::Maildir> { fn get_mdir_from_name(&self, mdir: &str) -> Result<maildir::Maildir> {
if mdir == self.account_config.inbox_folder { let inbox_folder = self
.account_config
.mailboxes
.get("inbox")
.map(|s| s.as_str())
.unwrap_or(DEFAULT_INBOX_FOLDER);
if mdir == inbox_folder {
self.validate_mdir_path(self.mdir.path().to_owned()) self.validate_mdir_path(self.mdir.path().to_owned())
.map(maildir::Maildir::from) .map(maildir::Maildir::from)
} else { } else {

View file

@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result};
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 std::{env, ffi::OsStr, fs, path::PathBuf}; use std::{collections::HashMap, env, ffi::OsStr, fs, path::PathBuf};
use crate::{config::*, output::run_cmd}; use crate::{config::*, output::run_cmd};
@ -23,12 +23,6 @@ pub struct AccountConfig {
pub sig: Option<String>, pub sig: Option<String>,
/// Represents the default page size for listings. /// Represents the default page size for listings.
pub default_page_size: usize, pub default_page_size: usize,
/// Represents the inbox folder name for this account.
pub inbox_folder: String,
/// Represents the sent folder name for this account.
pub sent_folder: String,
/// Represents the draft folder name for this account.
pub draft_folder: String,
/// Represents the notify command. /// Represents the notify command.
pub notify_cmd: Option<String>, pub notify_cmd: Option<String>,
/// Overrides the default IMAP query "NEW" used to fetch new messages /// Overrides the default IMAP query "NEW" used to fetch new messages
@ -36,6 +30,9 @@ pub struct AccountConfig {
/// Represents the watch commands. /// Represents the watch commands.
pub watch_cmds: Vec<String>, pub watch_cmds: Vec<String>,
/// Represents mailbox aliases.
pub mailboxes: HashMap<String, String>,
/// Represents the SMTP host. /// Represents the SMTP host.
pub smtp_host: String, pub smtp_host: String,
/// Represents the SMTP port. /// Represents the SMTP port.
@ -137,24 +134,6 @@ impl<'a> AccountConfig {
downloads_dir, downloads_dir,
sig, sig,
default_page_size, default_page_size,
inbox_folder: base_account
.inbox_folder
.as_deref()
.or_else(|| config.inbox_folder.as_deref())
.unwrap_or(DEFAULT_INBOX_FOLDER)
.to_string(),
sent_folder: base_account
.sent_folder
.as_deref()
.or_else(|| config.sent_folder.as_deref())
.unwrap_or(DEFAULT_SENT_FOLDER)
.to_string(),
draft_folder: base_account
.draft_folder
.as_deref()
.or_else(|| config.draft_folder.as_deref())
.unwrap_or(DEFAULT_DRAFT_FOLDER)
.to_string(),
notify_cmd: base_account.notify_cmd.clone(), notify_cmd: base_account.notify_cmd.clone(),
notify_query: base_account notify_query: base_account
.notify_query .notify_query
@ -168,6 +147,7 @@ impl<'a> AccountConfig {
.or_else(|| config.watch_cmds.as_ref()) .or_else(|| config.watch_cmds.as_ref())
.unwrap_or(&vec![]) .unwrap_or(&vec![])
.to_owned(), .to_owned(),
mailboxes: base_account.mailboxes.clone(),
default: base_account.default.unwrap_or_default(), default: base_account.default.unwrap_or_default(),
email: base_account.email.to_owned(), email: base_account.email.to_owned(),

View file

@ -1,5 +1,5 @@
use serde::Deserialize; use serde::Deserialize;
use std::path::PathBuf; use std::{collections::HashMap, path::PathBuf};
pub trait ToDeserializedBaseAccountConfig { pub trait ToDeserializedBaseAccountConfig {
fn to_base(&self) -> DeserializedBaseAccountConfig; fn to_base(&self) -> DeserializedBaseAccountConfig;
@ -39,12 +39,6 @@ macro_rules! make_account_config {
pub signature_delimiter: Option<String>, pub signature_delimiter: Option<String>,
/// Overrides the default page size for this account. /// Overrides the default page size for this account.
pub default_page_size: Option<usize>, pub default_page_size: Option<usize>,
/// Overrides the inbox folder name for this account.
pub inbox_folder: Option<String>,
/// Overrides the sent folder name for this account.
pub sent_folder: Option<String>,
/// Overrides the draft folder name for this account.
pub draft_folder: Option<String>,
/// Overrides the notify command for this account. /// Overrides the notify command for this account.
pub notify_cmd: Option<String>, pub notify_cmd: Option<String>,
/// Overrides the IMAP query used to fetch new messages for this account. /// Overrides the IMAP query used to fetch new messages for this account.
@ -75,6 +69,10 @@ macro_rules! make_account_config {
/// Represents the command used to decrypt a message. /// Represents the command used to decrypt a message.
pub pgp_decrypt_cmd: Option<String>, pub pgp_decrypt_cmd: Option<String>,
/// Represents mailbox aliases.
#[serde(default)]
pub mailboxes: HashMap<String, String>,
$(pub $element: $ty),* $(pub $element: $ty),*
} }
@ -86,9 +84,6 @@ macro_rules! make_account_config {
signature: self.signature.clone(), signature: self.signature.clone(),
signature_delimiter: self.signature_delimiter.clone(), signature_delimiter: self.signature_delimiter.clone(),
default_page_size: self.default_page_size.clone(), default_page_size: self.default_page_size.clone(),
inbox_folder: self.inbox_folder.clone(),
sent_folder: self.sent_folder.clone(),
draft_folder: self.draft_folder.clone(),
notify_cmd: self.notify_cmd.clone(), notify_cmd: self.notify_cmd.clone(),
notify_query: self.notify_query.clone(), notify_query: self.notify_query.clone(),
watch_cmds: self.watch_cmds.clone(), watch_cmds: self.watch_cmds.clone(),
@ -105,6 +100,8 @@ macro_rules! make_account_config {
pgp_encrypt_cmd: self.pgp_encrypt_cmd.clone(), pgp_encrypt_cmd: self.pgp_encrypt_cmd.clone(),
pgp_decrypt_cmd: self.pgp_decrypt_cmd.clone(), pgp_decrypt_cmd: self.pgp_decrypt_cmd.clone(),
mailboxes: self.mailboxes.clone(),
} }
} }
} }

View file

@ -27,12 +27,6 @@ pub struct DeserializedConfig {
pub signature_delimiter: Option<String>, pub signature_delimiter: Option<String>,
/// Represents the default page size for listings. /// Represents the default page size for listings.
pub default_page_size: Option<usize>, pub default_page_size: Option<usize>,
/// Overrides the default inbox folder name "INBOX".
pub inbox_folder: Option<String>,
/// Overrides the default sent folder name "Sent".
pub sent_folder: Option<String>,
/// Overrides the default draft folder name "Drafts".
pub draft_folder: Option<String>,
/// Represents the notify command. /// Represents the notify command.
pub notify_cmd: Option<String>, pub notify_cmd: Option<String>,
/// Overrides the default IMAP query "NEW" used to fetch new messages /// Overrides the default IMAP query "NEW" used to fetch new messages
@ -48,12 +42,12 @@ pub struct DeserializedConfig {
impl DeserializedConfig { impl DeserializedConfig {
/// Tries to create a config from an optional path. /// Tries to create a config from an optional path.
pub fn from_opt_path(path: Option<&str>) -> Result<Self> { pub fn from_opt_path(path: Option<&str>) -> Result<Self> {
info!("begin: trying to parse config from path"); info!("begin: try to parse config from path");
debug!("path: {:?}", path); debug!("path: {:?}", path);
let path = path.map(|s| s.into()).unwrap_or(Self::path()?); let path = path.map(|s| s.into()).unwrap_or(Self::path()?);
let content = fs::read_to_string(path).context("cannot read config file")?; let content = fs::read_to_string(path).context("cannot read config file")?;
let config = toml::from_str(&content).context("cannot parse config file")?; let config = toml::from_str(&content).context("cannot parse config file")?;
info!("end: trying to parse config from path"); info!("end: try to parse config from path");
trace!("config: {:?}", config); trace!("config: {:?}", config);
Ok(config) Ok(config)
} }

View file

@ -5,7 +5,10 @@ use url::Url;
use himalaya::{ use himalaya::{
backends::{imap_arg, imap_handler, Backend, ImapBackend, MaildirBackend, NotmuchBackend}, backends::{imap_arg, imap_handler, Backend, ImapBackend, MaildirBackend, NotmuchBackend},
compl::{compl_arg, compl_handler}, compl::{compl_arg, compl_handler},
config::{account_args, config_args, AccountConfig, BackendConfig, DeserializedConfig}, config::{
account_args, config_args, AccountConfig, BackendConfig, DeserializedConfig,
DEFAULT_INBOX_FOLDER,
},
mbox::{mbox_arg, mbox_handler}, mbox::{mbox_arg, mbox_handler},
msg::{flag_arg, flag_handler, msg_arg, msg_handler, tpl_arg, tpl_handler}, msg::{flag_arg, flag_handler, msg_arg, msg_handler, tpl_arg, tpl_handler},
output::{output_arg, OutputFmt, StdoutPrinter}, output::{output_arg, OutputFmt, StdoutPrinter},
@ -82,7 +85,8 @@ fn main() -> Result<()> {
AccountConfig::from_config_and_opt_account_name(&config, m.value_of("account"))?; AccountConfig::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")
.unwrap_or(&account_config.inbox_folder); .or_else(|| account_config.mailboxes.get("inbox").map(|s| s.as_str()))
.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut printer = StdoutPrinter::try_from(m.value_of("output"))?; let mut printer = StdoutPrinter::try_from(m.value_of("output"))?;
let mut imap; let mut imap;
let mut maildir; let mut maildir;

View file

@ -10,7 +10,7 @@ use uuid::Uuid;
use crate::{ use crate::{
backends::Backend, backends::Backend,
config::{AccountConfig, DEFAULT_SIG_DELIM}, config::{AccountConfig, DEFAULT_DRAFT_FOLDER, DEFAULT_SENT_FOLDER, DEFAULT_SIG_DELIM},
msg::{ msg::{
from_addrs_to_sendable_addrs, from_addrs_to_sendable_mbox, from_slice_to_addrs, msg_utils, from_addrs_to_sendable_addrs, from_addrs_to_sendable_mbox, from_slice_to_addrs, msg_utils,
Addrs, BinaryPart, Part, Parts, TextPlainPart, TplOverride, Addrs, BinaryPart, Part, Parts, TextPlainPart, TplOverride,
@ -340,7 +340,12 @@ impl Msg {
match choice::post_edit() { match choice::post_edit() {
Ok(PostEditChoice::Send) => { Ok(PostEditChoice::Send) => {
let sent_msg = smtp.send_msg(account, &self)?; let sent_msg = smtp.send_msg(account, &self)?;
backend.add_msg(&account.sent_folder, &sent_msg.formatted(), "seen")?; let sent_folder = account
.mailboxes
.get("sent")
.map(|s| s.as_str())
.unwrap_or(DEFAULT_SENT_FOLDER);
backend.add_msg(&sent_folder, &sent_msg.formatted(), "seen")?;
msg_utils::remove_local_draft()?; msg_utils::remove_local_draft()?;
printer.print("Message successfully sent")?; printer.print("Message successfully sent")?;
break; break;
@ -355,12 +360,14 @@ impl Msg {
} }
Ok(PostEditChoice::RemoteDraft) => { Ok(PostEditChoice::RemoteDraft) => {
let tpl = self.to_tpl(TplOverride::default(), account)?; let tpl = self.to_tpl(TplOverride::default(), account)?;
backend.add_msg(&account.draft_folder, tpl.as_bytes(), "seen draft")?; let draft_folder = account
.mailboxes
.get("draft")
.map(|s| s.as_str())
.unwrap_or(DEFAULT_DRAFT_FOLDER);
backend.add_msg(&draft_folder, tpl.as_bytes(), "seen draft")?;
msg_utils::remove_local_draft()?; msg_utils::remove_local_draft()?;
printer.print(format!( printer.print(format!("Message successfully saved to {}", draft_folder))?;
"Message successfully saved to {}",
account.draft_folder
))?;
break; break;
} }
Ok(PostEditChoice::Discard) => { Ok(PostEditChoice::Discard) => {

View file

@ -16,7 +16,7 @@ use url::Url;
use crate::{ use crate::{
backends::Backend, backends::Backend,
config::AccountConfig, config::{AccountConfig, DEFAULT_SENT_FOLDER},
msg::{Msg, Part, Parts, TextPlainPart}, msg::{Msg, Part, Parts, TextPlainPart},
output::{PrintTableOpts, PrinterService}, output::{PrintTableOpts, PrinterService},
smtp::SmtpService, smtp::SmtpService,
@ -312,6 +312,13 @@ pub fn send<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>(
let is_json = printer.is_json(); let is_json = printer.is_json();
debug!("is json: {}", is_json); debug!("is json: {}", is_json);
let sent_folder = config
.mailboxes
.get("sent")
.map(|s| s.as_str())
.unwrap_or(DEFAULT_SENT_FOLDER);
debug!("sent folder: {:?}", sent_folder);
let raw_msg = if is_tty || is_json { let raw_msg = if is_tty || is_json {
raw_msg.replace("\r", "").replace("\n", "\r\n") raw_msg.replace("\r", "").replace("\n", "\r\n")
} else { } else {
@ -325,9 +332,8 @@ pub fn send<'a, P: PrinterService, B: Backend<'a> + ?Sized, S: SmtpService>(
trace!("raw message: {:?}", raw_msg); trace!("raw message: {:?}", raw_msg);
let envelope: lettre::address::Envelope = Msg::from_tpl(&raw_msg)?.try_into()?; let envelope: lettre::address::Envelope = Msg::from_tpl(&raw_msg)?.try_into()?;
trace!("envelope: {:?}", envelope); trace!("envelope: {:?}", envelope);
smtp.send_raw_msg(&envelope, raw_msg.as_bytes())?; smtp.send_raw_msg(&envelope, raw_msg.as_bytes())?;
backend.add_msg(&config.sent_folder, raw_msg.as_bytes(), "seen")?; backend.add_msg(&sent_folder, raw_msg.as_bytes(), "seen")?;
Ok(()) Ok(())
} }