melib/maildr: add rename_regex config option

Add optional rename_regex configuration option to allow stripping
patterns from pathnames when renaming them. This is useful when other
programs depend on specific substrings being unique like mbsync which
erroneously assumes UIDs are unique instead of UID+UIDVALIDITY+mailbox
name like the IMAP standard specifies.

Closes #463

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/482/head
Manos Pitsidianakis 2 months ago
parent 4c44c440f6
commit e9b87b2e40
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

1
Cargo.lock generated

@ -1398,6 +1398,7 @@ dependencies = [
"socket2",
"stderrlog",
"tempfile",
"toml",
"unicode-segmentation",
"url",
"uuid",

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

@ -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"] }

@ -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<String>,
#[serde(default = "false_val")]
pub read_only: bool,
#[serde(default)]
pub display_name: Option<String>,
#[serde(default)]
pub order: (SortField, SortOrder),

@ -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<M>(self, msg: M) -> Error
where

@ -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<Mutex<HashMap<MailboxHash, HashIndex>>>;
#[derive(Debug)]
pub struct Configuration {
pub rename_regex: Option<Regex>,
}
impl Configuration {
pub fn new(settings: &AccountSettings) -> Result<Self> {
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<Configuration>,
}
pub fn move_to_cur(p: &Path) -> Result<PathBuf> {
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<PathBuf> {
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<dyn Fn(&str) -> bool>,
event_consumer: BackendEventConsumer,
) -> Result<Box<dyn MailBackend>> {
let config = Arc::new(Configuration::new(settings)?);
let mut mailboxes: HashMap<MailboxHash, MaildirMailbox> = Default::default();
fn recurse_mailboxes<P: AsRef<Path>>(
mailboxes: &mut HashMap<MailboxHash, MaildirMailbox>,
@ -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<Vec<PathBuf>> {
pub fn list_mail_in_maildir_fs(
config: &Configuration,
mut path: PathBuf,
read_only: bool,
) -> Result<Vec<PathBuf>> {
let mut files: Vec<PathBuf> = 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());
}

@ -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<PathBuf>;
fn place_in_dir(&self, dest_dir: &Path, config: &Configuration) -> Result<PathBuf>;
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<PathBuf> {
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<PathBuf> {
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() {

@ -50,11 +50,12 @@ impl MaildirStream {
mut path: PathBuf,
map: HashIndexes,
mailbox_index: Arc<Mutex<HashMap<EnvelopeHash, MailboxHash>>>,
config: Arc<Configuration>,
) -> ResultStream<Vec<Envelope>> {
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");

@ -0,0 +1,231 @@
//
// melib
//
// Copyright 2024 Emmanouil Pitsidianakis <manos@pitsidianak.is>
//
// 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 <http://www.gnu.org/licenses/>.
//
// 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<PathBuf> {
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"
);
}

@ -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<Mutex<HashMap<EnvelopeHash, MailboxHash>>>,
pub root_mailbox_hash: MailboxHash,
pub config: Arc<Configuration>,
#[allow(clippy::type_complexity)]
pub mailbox_counts: HashMap<MailboxHash, (Arc<Mutex<usize>>, Arc<Mutex<usize>>)>,
}
@ -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!(

@ -0,0 +1,115 @@
//
// melib
//
// Copyright 2024 Emmanouil Pitsidianakis <manos@pitsidianak.is>
//
// 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 <http://www.gnu.org/licenses/>.
//
// 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();
}

@ -19,6 +19,7 @@
// along with meli. If not, see <http://www.gnu.org/licenses/>.
//
mod configs;
mod generating_email;
mod mbox_parse;

Loading…
Cancel
Save