diff --git a/Cargo.lock b/Cargo.lock index 288416cf..1f78aa5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1398,6 +1398,7 @@ dependencies = [ "socket2", "stderrlog", "tempfile", + "toml", "unicode-segmentation", "url", "uuid", diff --git a/meli/src/conf.rs b/meli/src/conf.rs index e2240d05..34a733a6 100644 --- a/meli/src/conf.rs +++ b/meli/src/conf.rs @@ -674,8 +674,13 @@ mod deserializers { D: Deserializer<'de>, { let v: Value = Deserialize::deserialize(deserializer)?; + if let Some(s) = v.as_str() { + return Ok(s.to_string()); + } let mut ret = v.to_string(); - if ret.starts_with('"') && ret.ends_with('"') { + if (ret.starts_with('"') && ret.ends_with('"')) + || (ret.starts_with('\"') && ret.ends_with('\'')) + { ret.drain(0..1).count(); ret.drain(ret.len() - 1..).count(); } diff --git a/melib/Cargo.toml b/melib/Cargo.toml index f726a0ae..5d996e1b 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -84,3 +84,4 @@ mailin-embedded = { version = "0.8", features = ["rtls"] } sealed_test = { version = "1.1.0" } stderrlog = { version = "^0.5" } tempfile = { version = "3.3" } +toml = { version = "0.8", default-features = false, features = ["display","preserve_order","parse"] } diff --git a/melib/src/conf.rs b/melib/src/conf.rs index 1a9cc7e6..93eaaf9f 100644 --- a/melib/src/conf.rs +++ b/melib/src/conf.rs @@ -34,7 +34,7 @@ use crate::{ }; pub use crate::{SortField, SortOrder}; -#[derive(Clone, Debug, Default, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct AccountSettings { pub name: String, /// Name of mailbox that is the root of the mailbox hierarchy. @@ -44,8 +44,11 @@ pub struct AccountSettings { pub root_mailbox: String, pub format: String, pub identity: String, + #[serde(default)] pub extra_identities: Vec, + #[serde(default = "false_val")] pub read_only: bool, + #[serde(default)] pub display_name: Option, #[serde(default)] pub order: (SortField, SortOrder), diff --git a/melib/src/error.rs b/melib/src/error.rs index 29814645..202ac2d9 100644 --- a/melib/src/error.rs +++ b/melib/src/error.rs @@ -147,6 +147,13 @@ pub struct Error { pub kind: ErrorKind, } +#[cfg(test)] +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + self.to_string().eq(&other.to_string()) + } +} + pub trait IntoError { fn set_err_summary(self, msg: M) -> Error where diff --git a/melib/src/maildir/backend.rs b/melib/src/maildir/backend.rs index ceb7880c..b2e2d786 100644 --- a/melib/src/maildir/backend.rs +++ b/melib/src/maildir/backend.rs @@ -36,6 +36,7 @@ use std::{ }; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; +use regex::Regex; use super::{watch, MaildirMailbox, MaildirOp, MaildirPathTrait}; use crate::{ @@ -105,6 +106,34 @@ impl DerefMut for HashIndex { pub type HashIndexes = Arc>>; +#[derive(Debug)] +pub struct Configuration { + pub rename_regex: Option, +} + +impl Configuration { + pub fn new(settings: &AccountSettings) -> Result { + let rename_regex = if let Some(v) = settings.extra.get("rename_regex").map(|v| { + Regex::new(v).map_err(|e| { + Error::new(format!( + "Configuration error ({}): Invalid value for field `{}`: {}", + settings.name.as_str(), + "rename_regex", + v, + )) + .set_source(Some(crate::src_err_arc_wrap!(e))) + .set_kind(ErrorKind::ValueError) + }) + }) { + Some(v?) + } else { + None + }; + + Ok(Self { rename_regex }) + } +} + /// The maildir backend instance type. #[derive(Debug)] pub struct MaildirType { @@ -115,23 +144,22 @@ pub struct MaildirType { pub event_consumer: BackendEventConsumer, pub collection: Collection, pub path: PathBuf, + pub config: Arc, } -pub fn move_to_cur(p: &Path) -> Result { - let mut new = p.to_path_buf(); - let file_name = p.to_string_lossy(); - let slash_pos = file_name.bytes().rposition(|c| c == b'/').unwrap() + 1; - new.pop(); - new.pop(); - - new.push("cur"); - new.push(&file_name[slash_pos..]); - if !file_name.ends_with(":2,") { - new.set_extension(":2,"); - } - log::trace!("moved to cur: {}", new.display()); - fs::rename(p, &new)?; - Ok(new) +pub fn move_to_cur(config: &Configuration, p: &Path) -> Result { + let cur = { + let mut cur = p.to_path_buf(); + cur.pop(); + cur.pop(); + cur.push("cur"); + cur + }; + let dest_path = p.place_in_dir(&cur, config)?; + log::trace!("moved to cur: {}", dest_path.display()); + #[cfg(not(test))] + fs::rename(p, &dest_path)?; + Ok(dest_path) } impl MailBackend for MaildirType { @@ -169,7 +197,15 @@ impl MailBackend for MaildirType { let path: PathBuf = mailbox.fs_path().into(); let map = self.hash_indexes.clone(); let mailbox_index = self.mailbox_index.clone(); - super::stream::MaildirStream::new(mailbox_hash, unseen, total, path, map, mailbox_index) + super::stream::MaildirStream::new( + mailbox_hash, + unseen, + total, + path, + map, + mailbox_index, + self.config.clone(), + ) } fn refresh(&mut self, mailbox_hash: MailboxHash) -> ResultFuture<()> { @@ -180,12 +216,13 @@ impl MailBackend for MaildirType { let path: PathBuf = mailbox.fs_path().into(); let map = self.hash_indexes.clone(); let mailbox_index = self.mailbox_index.clone(); + let config = self.config.clone(); Ok(Box::pin(async move { let thunk = move |sender: &BackendEventConsumer| { log::trace!("refreshing"); let mut buf = Vec::with_capacity(4096); - let files = Self::list_mail_in_maildir_fs(path.clone(), false)?; + let files = Self::list_mail_in_maildir_fs(&config, path.clone(), false)?; let mut current_hashes = { let mut map = map.lock().unwrap(); let map = map.entry(mailbox_hash).or_default(); @@ -287,6 +324,7 @@ impl MailBackend for MaildirType { mailbox_index: self.mailbox_index.clone(), root_mailbox_hash, mailbox_counts, + config: self.config.clone(), }; Ok(Box::pin(async move { watch_state.watch().await })) } @@ -315,19 +353,20 @@ impl MailBackend for MaildirType { &mut self, env_hashes: EnvelopeHashBatch, mailbox_hash: MailboxHash, - flags: SmallVec<[FlagOp; 8]>, + flag_ops: SmallVec<[FlagOp; 8]>, ) -> ResultFuture<()> { - let hash_index = self.hash_indexes.clone(); - if flags.iter().any(|op| op.is_tag()) { + if flag_ops.iter().any(|op| op.is_tag()) { return Err(Error::new("Maildir doesn't support tags.")); } + let hash_index = self.hash_indexes.clone(); + let config = self.config.clone(); Ok(Box::pin(async move { let mut hash_indexes_lck = hash_index.lock().unwrap(); let hash_index = hash_indexes_lck.entry(mailbox_hash).or_default(); for env_hash in env_hashes.iter() { - let _path = { + let path = { if !hash_index.contains_key(&env_hash) { continue; } @@ -340,38 +379,14 @@ impl MailBackend for MaildirType { hash_index[&env_hash].to_path_buf() } }; - let mut env_flags = _path.flags(); - let path = _path.to_str().unwrap(); // Assume UTF-8 validity - let idx: usize = path - .rfind(":2,") - .ok_or_else(|| Error::new(format!("Invalid email filename: {:?}", path)))? - + 3; - let mut new_name: String = path[..idx].to_string(); - for op in flags.iter() { + let mut new_flags = path.flags(); + for op in flag_ops.iter() { if let FlagOp::Set(f) | FlagOp::UnSet(f) = op { - env_flags.set(*f, op.as_bool()); + new_flags.set(*f, op.as_bool()); } } - if !(env_flags & Flag::DRAFT).is_empty() { - new_name.push('D'); - } - if !(env_flags & Flag::FLAGGED).is_empty() { - new_name.push('F'); - } - if !(env_flags & Flag::PASSED).is_empty() { - new_name.push('P'); - } - if !(env_flags & Flag::REPLIED).is_empty() { - new_name.push('R'); - } - if !(env_flags & Flag::SEEN).is_empty() { - new_name.push('S'); - } - if !(env_flags & Flag::TRASHED).is_empty() { - new_name.push('T'); - } - let new_name: PathBuf = new_name.into(); + let new_name: PathBuf = path.set_flags(new_flags, &config)?; hash_index.entry(env_hash).or_default().modified = Some(PathMod::Path(new_name.clone())); @@ -394,7 +409,7 @@ impl MailBackend for MaildirType { let hash_index = hash_indexes_lck.entry(mailbox_hash).or_default(); for env_hash in env_hashes.iter() { - let _path = { + let path = { if !hash_index.contains_key(&env_hash) { continue; } @@ -408,7 +423,7 @@ impl MailBackend for MaildirType { } }; - fs::remove_file(&_path)?; + fs::remove_file(&path)?; } Ok(()) })) @@ -421,14 +436,15 @@ impl MailBackend for MaildirType { destination_mailbox_hash: MailboxHash, move_: bool, ) -> ResultFuture<()> { - let hash_index = self.hash_indexes.clone(); if !self.mailboxes.contains_key(&source_mailbox_hash) { return Err(Error::new("Invalid source mailbox hash").set_kind(ErrorKind::Bug)); } else if !self.mailboxes.contains_key(&destination_mailbox_hash) { return Err(Error::new("Invalid destination mailbox hash").set_kind(ErrorKind::Bug)); } - let mut dest_path: PathBuf = self.mailboxes[&destination_mailbox_hash].fs_path().into(); - dest_path.push("cur"); + let hash_index = self.hash_indexes.clone(); + let config = self.config.clone(); + let mut dest_dir: PathBuf = self.mailboxes[&destination_mailbox_hash].fs_path().into(); + dest_dir.push("cur"); Ok(Box::pin(async move { let mut hash_indexes_lck = hash_index.lock().unwrap(); let hash_index = hash_indexes_lck.entry(source_mailbox_hash).or_default(); @@ -447,10 +463,7 @@ impl MailBackend for MaildirType { hash_index[&env_hash].to_path_buf() } }; - let filename = path_src.file_name().ok_or_else(|| { - format!("Could not get filename of `{}`", path_src.display(),) - })?; - dest_path.push(filename); + let dest_path = path_src.place_in_dir(&dest_dir, &config)?; hash_index.entry(env_hash).or_default().modified = Some(PathMod::Path(dest_path.clone())); if move_ { @@ -462,7 +475,6 @@ impl MailBackend for MaildirType { fs::copy(&path_src, &dest_path)?; log::trace!("success in copy"); } - dest_path.pop(); } Ok(()) })) @@ -588,6 +600,8 @@ impl MaildirType { is_subscribed: Box bool>, event_consumer: BackendEventConsumer, ) -> Result> { + let config = Arc::new(Configuration::new(settings)?); + let mut mailboxes: HashMap = Default::default(); fn recurse_mailboxes>( mailboxes: &mut HashMap, @@ -737,6 +751,7 @@ impl MaildirType { event_consumer, collection: Default::default(), path: root_mailbox, + config, })) } @@ -823,16 +838,22 @@ impl MaildirType { s.root_mailbox.as_str() ))); } + _ = Configuration::new(s)?; + _ = s.extra.swap_remove("rename_regex"); Ok(()) } - pub fn list_mail_in_maildir_fs(mut path: PathBuf, read_only: bool) -> Result> { + pub fn list_mail_in_maildir_fs( + config: &Configuration, + mut path: PathBuf, + read_only: bool, + ) -> Result> { let mut files: Vec = vec![]; path.push("new"); for p in path.read_dir()?.flatten() { if !read_only { - move_to_cur(&p.path()).ok().take(); + move_to_cur(config, &p.path()).ok().take(); } else { files.push(p.path()); } diff --git a/melib/src/maildir/mod.rs b/melib/src/maildir/mod.rs index 47de22b1..038e890c 100644 --- a/melib/src/maildir/mod.rs +++ b/melib/src/maildir/mod.rs @@ -22,9 +22,12 @@ #[macro_use] mod backend; pub use self::backend::*; +mod stream; pub mod watch; -mod stream; +#[cfg(test)] +mod tests; + use std::{ collections::{hash_map::DefaultHasher, HashMap}, fs, @@ -268,6 +271,8 @@ impl BackendMailbox for MaildirMailbox { pub trait MaildirPathTrait { fn flags(&self) -> Flag; + fn set_flags(&self, flags: Flag, config: &Configuration) -> Result; + fn place_in_dir(&self, dest_dir: &Path, config: &Configuration) -> Result; fn to_mailbox_hash(&self) -> MailboxHash; fn to_envelope_hash(&self) -> EnvelopeHash; fn is_in_new(&self) -> bool; @@ -303,6 +308,70 @@ impl MaildirPathTrait for Path { flag } + fn set_flags(&self, flags: Flag, config: &Configuration) -> Result { + let filename = self + .file_name() + .ok_or_else(|| format!("Could not get filename of `{}`", self.display(),))? + .to_string_lossy() + .to_string(); + let (idx, append_2): (usize, bool) = if let Some(idx) = filename.rfind(":2,") { + (idx + 3, false) + } else { + log::trace!( + "Invalid maildir filename: {:?}\nBacktrace:\n{}", + self, + std::backtrace::Backtrace::capture() + ); + (filename.len(), true) + }; + let mut new_name: String = if let Some(ref rename_regex) = config.rename_regex { + rename_regex.replace_all(&filename[..idx], "").to_string() + } else { + filename[..idx].to_string() + }; + if append_2 { + new_name.push_str(":2,"); + } + if !(flags & Flag::DRAFT).is_empty() { + new_name.push('D'); + } + if !(flags & Flag::FLAGGED).is_empty() { + new_name.push('F'); + } + if !(flags & Flag::PASSED).is_empty() { + new_name.push('P'); + } + if !(flags & Flag::REPLIED).is_empty() { + new_name.push('R'); + } + if !(flags & Flag::SEEN).is_empty() { + new_name.push('S'); + } + if !(flags & Flag::TRASHED).is_empty() { + new_name.push('T'); + } + let mut new_path: PathBuf = self.into(); + new_path.set_file_name(new_name); + Ok(new_path) + } + + fn place_in_dir(&self, dest_dir: &Path, config: &Configuration) -> Result { + let mut filename = self + .file_name() + .ok_or_else(|| format!("Could not get filename of `{}`", self.display()))? + .to_string_lossy(); + if !filename.contains(":2,") { + filename = Cow::Owned(format!("{}:2,", filename)); + } + let mut new_path = dest_dir.to_path_buf(); + if let Some(ref rename_regex) = config.rename_regex { + new_path.push(rename_regex.replace_all(&filename, "").as_ref()); + } else { + new_path.push(filename.as_ref()); + }; + Ok(new_path) + } + fn to_mailbox_hash(&self) -> MailboxHash { let mut path = self.to_path_buf(); if path.is_file() { diff --git a/melib/src/maildir/stream.rs b/melib/src/maildir/stream.rs index 0e6517cb..de789c06 100644 --- a/melib/src/maildir/stream.rs +++ b/melib/src/maildir/stream.rs @@ -50,11 +50,12 @@ impl MaildirStream { mut path: PathBuf, map: HashIndexes, mailbox_index: Arc>>, + config: Arc, ) -> ResultStream> { let chunk_size = 2048; path.push("new"); for p in path.read_dir()?.flatten() { - move_to_cur(&p.path()).ok().take(); + move_to_cur(&config, &p.path()).ok().take(); } path.pop(); path.push("cur"); diff --git a/melib/src/maildir/tests.rs b/melib/src/maildir/tests.rs new file mode 100644 index 00000000..2e0f2f29 --- /dev/null +++ b/melib/src/maildir/tests.rs @@ -0,0 +1,231 @@ +// +// melib +// +// Copyright 2024 Emmanouil Pitsidianakis +// +// This file is part of melib. +// +// melib is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// melib is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with melib. If not, see . +// +// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later + +use std::path::{Path, PathBuf}; + +use regex::Regex; + +use crate::{ + backends::FlagOp, + email::Flag, + error::Result, + maildir::{move_to_cur, Configuration, MaildirPathTrait}, +}; + +fn set_flags(config: &Configuration, path: &Path, flag_ops: &[FlagOp]) -> Result { + let mut new_flags = path.flags(); + for op in flag_ops.iter() { + if let FlagOp::Set(f) | FlagOp::UnSet(f) = op { + new_flags.set(*f, op.as_bool()); + } + } + + path.set_flags(new_flags, config) +} + +#[test] +fn test_maildir_move_to_cur_rename() { + let config = Configuration { rename_regex: None }; + assert_eq!( + move_to_cur(&config, Path::new("/path/to/new/1423819205.29514_1:2,FRS")).unwrap(), + Path::new("/path/to/cur/1423819205.29514_1:2,FRS") + ); + assert_eq!( + move_to_cur(&config, Path::new("/path/to/new/1423819205.29514_1:2,")).unwrap(), + Path::new("/path/to/cur/1423819205.29514_1:2,") + ); + assert_eq!( + move_to_cur(&config, Path::new("/path/to/new/1423819205.29514_1:1,")).unwrap(), + Path::new("/path/to/cur/1423819205.29514_1:1,:2,") + ); + assert_eq!( + move_to_cur(&config, Path::new("/path/to/new/1423819205.29514_1")).unwrap(), + Path::new("/path/to/cur/1423819205.29514_1:2,") + ); +} + +#[test] +fn test_maildir_move_to_cur_rename_regexp() { + let config = Configuration { + rename_regex: Some(Regex::new(r",U=\d\d*").unwrap()), + }; + assert_eq!( + move_to_cur( + &config, + Path::new("/path/to/new/1423819205.29514_1.foo,U=123:2,S") + ) + .unwrap(), + Path::new("/path/to/cur/1423819205.29514_1.foo:2,S") + ); + assert_eq!( + move_to_cur( + &config, + Path::new("/path/to/new/1423819205.29514_1.foo,U=1:2,S") + ) + .unwrap(), + Path::new("/path/to/cur/1423819205.29514_1.foo:2,S") + ); + assert_eq!( + move_to_cur( + &config, + Path::new("/path/to/new/1423819205.29514_1.foo,U=:2,S") + ) + .unwrap(), + Path::new("/path/to/cur/1423819205.29514_1.foo,U=:2,S") + ); + assert_eq!( + move_to_cur( + &config, + Path::new("/path/to/new/1423819205.29514_1.foo:2,S") + ) + .unwrap(), + Path::new("/path/to/cur/1423819205.29514_1.foo:2,S") + ); +} + +#[test] +fn test_maildir_set_flags() { + let config = Configuration { rename_regex: None }; + + assert_eq!( + set_flags( + &config, + Path::new("/path/to/new/1423819205.29514_1:2,FRS"), + &[FlagOp::Set(Flag::FLAGGED | Flag::SEEN | Flag::REPLIED)] + ), + Ok(Path::new("/path/to/new/1423819205.29514_1:2,FRS").to_path_buf()), + "Setting the same flags should not change the path" + ); + assert_eq!( + set_flags( + &config, + Path::new("/path/to/new/1423819205.29514_1:2,FRS"), + &[FlagOp::UnSet(Flag::FLAGGED | Flag::SEEN | Flag::REPLIED)] + ), + Ok(Path::new("/path/to/new/1423819205.29514_1:2,").to_path_buf()), + "UnSetting all the set flags should change the path" + ); + assert_eq!( + set_flags( + &config, + Path::new("/path/to/new/1423819205.29514_1:2,FRS"), + &[FlagOp::Set(Flag::FLAGGED | Flag::TRASHED)] + ), + Ok(Path::new("/path/to/new/1423819205.29514_1:2,FRST").to_path_buf()), + "Setting new flags should change the path to include them" + ); +} + +#[test] +fn test_maildir_set_flags_regexp() { + let config = Configuration { + rename_regex: Some(Regex::new(r",U=\d\d*").unwrap()), + }; + + assert_eq!( + set_flags( + &config, + Path::new("/path/to/new/1423819205.29514_1.foo,U=123:2,S"), + &[FlagOp::Set(Flag::FLAGGED | Flag::SEEN | Flag::REPLIED)] + ), + Ok(Path::new("/path/to/new/1423819205.29514_1.foo:2,FRS").to_path_buf()), + "Setting the same flags should not change the path" + ); + assert_eq!( + set_flags( + &config, + Path::new("/path/to/new/1423819205.29514_1.foo,U=123:2,FRS"), + &[FlagOp::UnSet(Flag::FLAGGED | Flag::SEEN | Flag::REPLIED)] + ), + Ok(Path::new("/path/to/new/1423819205.29514_1.foo:2,").to_path_buf()), + "UnSetting all the set flags should change the path" + ); + assert_eq!( + set_flags( + &config, + Path::new("/path/to/new/1423819205.29514_1.foo,U=123:2,FRS"), + &[FlagOp::Set(Flag::FLAGGED | Flag::TRASHED)] + ), + Ok(Path::new("/path/to/new/1423819205.29514_1.foo:2,FRST").to_path_buf()), + "Setting new flags should change the path to include them" + ); +} + +#[test] +fn test_maildir_place_in_dir() { + let config = Configuration { rename_regex: None }; + + assert_eq!( + Path::new("/path/to/new/1423819205.29514_1:2,") + .place_in_dir(Path::new("/path/to/new/"), &config), + Ok(Path::new("/path/to/new/1423819205.29514_1:2,").to_path_buf()), + "place_in_dir() where dest_dir is the same should not change the parent dir", + ); + assert_eq!( + Path::new("/path/to/new/1423819205.29514_1:2,") + .place_in_dir(Path::new("/path/to2/new/"), &config), + Ok(Path::new("/path/to2/new/1423819205.29514_1:2,").to_path_buf()), + ); + assert_eq!( + Path::new("/path/to/new/1423819205.29514_1:2,FRS") + .place_in_dir(Path::new("/path/to2/new/"), &config), + Ok(Path::new("/path/to2/new/1423819205.29514_1:2,FRS").to_path_buf()), + "place_in_dir() where dest_dir is the same should not change flags", + ); + assert_eq!( + Path::new("/path/to/new/1423819205.29514_1") + .place_in_dir(Path::new("/path/to2/new/"), &config), + Ok(Path::new("/path/to2/new/1423819205.29514_1:2,").to_path_buf()), + "place_in_dir() should add missing `:2,` substring" + ); +} + +#[test] +fn test_maildir_place_in_dir_regexp() { + let config = Configuration { + rename_regex: Some(Regex::new(r",U=\d\d*").unwrap()), + }; + + assert_eq!( + Path::new("/path/to/new/1423819205.29514_1.foo,U=123:2,") + .place_in_dir(Path::new("/path/to/new/"), &config), + Ok(Path::new("/path/to/new/1423819205.29514_1.foo:2,").to_path_buf()), + "place_in_dir() where dest_dir is the same should not change the parent dir", + ); + assert_eq!( + Path::new("/path/to/new/1423819205.29514_1.foo,U=123:2,") + .place_in_dir(Path::new("/path/to2/new/"), &config), + Ok(Path::new("/path/to2/new/1423819205.29514_1.foo:2,").to_path_buf()), + ); + assert_eq!( + Path::new("/path/to/new/1423819205.29514_1.foo,U=123:2,FRS") + .place_in_dir(Path::new("/path/to2/new/"), &config), + Ok(Path::new("/path/to2/new/1423819205.29514_1.foo:2,FRS").to_path_buf()), + "place_in_dir() where dest_dir is the same should not change flags", + ); + assert_eq!( + Path::new("/path/to/new/1423819205.29514_1.foo,U=123") + .place_in_dir(Path::new("/path/to2/new/"), &config), + Ok(Path::new("/path/to2/new/1423819205.29514_1.foo:2,").to_path_buf()), + "place_in_dir() should add missing `:2,` substring" + ); +} diff --git a/melib/src/maildir/watch.rs b/melib/src/maildir/watch.rs index 0d604530..d04d29e7 100644 --- a/melib/src/maildir/watch.rs +++ b/melib/src/maildir/watch.rs @@ -33,7 +33,7 @@ use notify::{self, event::EventKind as NotifyEvent}; use crate::{ backends::{prelude::*, RefreshEventKind::*}, error, - maildir::{move_to_cur, HashIndexes, MaildirPathTrait, PathMod}, + maildir::{move_to_cur, Configuration, HashIndexes, MaildirPathTrait, PathMod}, }; pub struct MaildirWatch { @@ -45,6 +45,7 @@ pub struct MaildirWatch { pub hash_indexes: HashIndexes, pub mailbox_index: Arc>>, pub root_mailbox_hash: MailboxHash, + pub config: Arc, #[allow(clippy::type_complexity)] pub mailbox_counts: HashMap>, Arc>)>, } @@ -61,6 +62,7 @@ impl MaildirWatch { mailbox_index, root_mailbox_hash, mailbox_counts, + config, } = self; let mut buf = Vec::with_capacity(4096); @@ -72,7 +74,7 @@ impl MaildirWatch { for mut pathbuf in event.paths { if pathbuf.is_in_new() { // This creates a Rename event that we will receive later - pathbuf = match move_to_cur(&pathbuf) { + pathbuf = match move_to_cur(&config, &pathbuf) { Ok(p) => p, Err(err) => { log::error!( diff --git a/melib/tests/integration/configs.rs b/melib/tests/integration/configs.rs new file mode 100644 index 00000000..24bf6ce2 --- /dev/null +++ b/melib/tests/integration/configs.rs @@ -0,0 +1,115 @@ +// +// melib +// +// Copyright 2024 Emmanouil Pitsidianakis +// +// This file is part of melib. +// +// melib is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// melib is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with melib. If not, see . +// +// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later + +#[test] +fn test_maildir_config() { + use melib::maildir::Configuration; + use regex::Regex; + use tempfile::TempDir; + + let tmp_dir = TempDir::new().unwrap(); + + let config = Configuration { + rename_regex: Some(Regex::new(r",U=\d\d*").unwrap()), + }; + + let mut s: melib::AccountSettings = toml::from_str(&format!( + r#" +name = "foo" +root_mailbox = "{}" +format = "maildir" +identity = "foo@example.com" +subscribed_mailboxes = [] + "#, + tmp_dir.path().display() + )) + .unwrap(); + + melib::maildir::MaildirType::validate_config(&mut s).unwrap(); + let mut s: melib::AccountSettings = toml::from_str(&format!( + r#" +name = "foo" +root_mailbox = "{}" +format = "maildir" +identity = "foo@example.com" +subscribed_mailboxes = [] +rename_regex = ',U=\d\d*' + "#, + tmp_dir.path().display() + )) + .unwrap(); + assert_eq!( + melib::maildir::Configuration::new(&s) + .unwrap() + .rename_regex + .unwrap() + .as_str(), + config.rename_regex.as_ref().unwrap().as_str() + ); + + melib::maildir::MaildirType::validate_config(&mut s).unwrap(); + let mut s: melib::AccountSettings = toml::from_str(&format!( + r#" +name = "foo" +root_mailbox = "{}" +format = "maildir" +identity = "foo@example.com" +subscribed_mailboxes = [] +rename_regex = ",U=\\d\\d*" + "#, + tmp_dir.path().display() + )) + .unwrap(); + assert_eq!( + melib::maildir::Configuration::new(&s) + .unwrap() + .rename_regex + .unwrap() + .as_str(), + config.rename_regex.as_ref().unwrap().as_str() + ); + + melib::maildir::MaildirType::validate_config(&mut s).unwrap(); + let mut s: melib::AccountSettings = toml::from_str(&format!( + r#" +name = "foo" +root_mailbox = "{}" +format = "maildir" +identity = "foo@example.com" +subscribed_mailboxes = [] +rename_regex = ',U=\d\d*' + "#, + tmp_dir.path().display() + )) + .unwrap(); + + assert_eq!( + melib::maildir::Configuration::new(&s) + .unwrap() + .rename_regex + .unwrap() + .as_str(), + config.rename_regex.as_ref().unwrap().as_str() + ); + melib::maildir::MaildirType::validate_config(&mut s).unwrap(); + _ = tmp_dir.close(); +} diff --git a/melib/tests/integration/main.rs b/melib/tests/integration/main.rs index 8dbc538d..9c12b9db 100644 --- a/melib/tests/integration/main.rs +++ b/melib/tests/integration/main.rs @@ -19,6 +19,7 @@ // along with meli. If not, see . // +mod configs; mod generating_email; mod mbox_parse;