From 526f344c7c00b856d670d9cd8d209b447ad75f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Mon, 28 Feb 2022 21:20:36 +0100 Subject: [PATCH] improve maildir backend logs and comments --- src/backends/maildir/maildir_backend.rs | 403 +++++++++++++++++++----- 1 file changed, 327 insertions(+), 76 deletions(-) diff --git a/src/backends/maildir/maildir_backend.rs b/src/backends/maildir/maildir_backend.rs index 28ac626..7198ce5 100644 --- a/src/backends/maildir/maildir_backend.rs +++ b/src/backends/maildir/maildir_backend.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Context, Result}; +use log::{debug, info, trace}; use std::{convert::TryInto, fs, path::PathBuf}; use crate::{ @@ -8,9 +9,10 @@ use crate::{ msg::{Envelopes, Msg}, }; +/// Represents the maildir backend. pub struct MaildirBackend<'a> { - mdir: maildir::Maildir, account_config: &'a AccountConfig, + mdir: maildir::Maildir, } impl<'a> MaildirBackend<'a> { @@ -28,14 +30,12 @@ impl<'a> MaildirBackend<'a> { if mdir_path.is_dir() { Ok(mdir_path) } else { - Err(anyhow!( - "cannot read maildir from directory {:?}", - mdir_path - )) + Err(anyhow!("cannot read maildir directory {:?}", mdir_path)) } } - fn get_mdir_from_name(&self, mdir: &str) -> Result { + /// Creates a maildir instance from a string slice. + fn get_mdir_from_dir(&self, dir: &str) -> Result { let inbox_folder = self .account_config .mailboxes @@ -43,13 +43,20 @@ impl<'a> MaildirBackend<'a> { .map(|s| s.as_str()) .unwrap_or(DEFAULT_INBOX_FOLDER); - if mdir == inbox_folder { + // If the dir points to the inbox folder, creates a maildir + // instance from the root folder. + if dir == inbox_folder { self.validate_mdir_path(self.mdir.path().to_owned()) .map(maildir::Maildir::from) } else { - self.validate_mdir_path(mdir.into()) + // If the dir is a valid maildir path, creates a maildir instance from it. + self.validate_mdir_path(dir.into()) .or_else(|_| { - let path = self.mdir.path().join(format!(".{}", mdir)); + // Otherwise creates a maildir instance from a + // maildir subdirectory by adding a "." in front + // of the name as described in the spec: + // https://cr.yp.to/proto/maildir.html + let path = self.mdir.path().join(format!(".{}", dir)); self.validate_mdir_path(path) }) .map(maildir::Maildir::from) @@ -58,28 +65,66 @@ impl<'a> MaildirBackend<'a> { } impl<'a> Backend<'a> for MaildirBackend<'a> { - fn add_mbox(&mut self, mdir: &str) -> Result<()> { - fs::create_dir(self.mdir.path().join(format!(".{}", mdir))) - .context(format!("cannot create maildir subfolder {:?}", mdir)) + fn add_mbox(&mut self, subdir: &str) -> Result<()> { + info!(">> add maildir subdir"); + debug!("subdir: {:?}", subdir); + + let path = self.mdir.path().join(format!(".{}", subdir)); + trace!("subdir path: {:?}", path); + + fs::create_dir(&path).context(format!( + "cannot create maildir subdir {:?} at {:?}", + subdir, path + ))?; + + info!("<< add maildir subdir"); + Ok(()) } fn get_mboxes(&mut self) -> Result> { - let mboxes: MaildirMboxes = self.mdir.list_subdirs().try_into()?; - Ok(Box::new(mboxes)) + info!(">> get maildir subdirs"); + + let subdirs: MaildirMboxes = self.mdir.list_subdirs().try_into().context(format!( + "cannot parse maildir subdirs from {:?}", + self.mdir.path() + ))?; + trace!("subdirs: {:?}", subdirs); + + info!("<< get maildir subdirs"); + Ok(Box::new(subdirs)) } - fn del_mbox(&mut self, mdir: &str) -> Result<()> { - fs::remove_dir_all(self.mdir.path().join(format!(".{}", mdir))) - .context(format!("cannot delete maildir subfolder {:?}", mdir)) + fn del_mbox(&mut self, subdir: &str) -> Result<()> { + info!(">> delete maildir subdir"); + debug!("subdir: {:?}", subdir); + + let path = self.mdir.path().join(format!(".{}", subdir)); + trace!("subdir path: {:?}", path); + + fs::remove_dir_all(&path).context(format!( + "cannot delete maildir subdir {:?} from {:?}", + subdir, path + ))?; + + info!("<< delete maildir subdir"); + Ok(()) } fn get_envelopes( &mut self, - mdir: &str, + subdir: &str, page_size: usize, page: usize, ) -> Result> { - let mdir = self.get_mdir_from_name(mdir)?; + info!(">> get maildir envelopes"); + debug!("maildir subdir: {:?}", subdir); + debug!("page size: {:?}", page_size); + debug!("page: {:?}", page); + + let mdir = self.get_mdir_from_dir(subdir).context(format!( + "cannot get maildir instance from subdir {:?}", + subdir + ))?; // Reads envelopes from the "cur" folder of the selected // maildir. @@ -87,16 +132,20 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { "cannot parse maildir envelopes from {:?}", self.mdir.path() ))?; + debug!("envelopes len: {:?}", envelopes.len()); + trace!("envelopes: {:?}", envelopes); // Calculates pagination boundaries. let page_begin = page * page_size; + debug!("page begin: {:?}", page_begin); if page_begin > envelopes.len() { return Err(anyhow!(format!( - "cannot list maildir envelopes at page {:?} (out of bounds)", + "cannot get maildir envelopes at page {:?} (out of bounds)", page_begin + 1, ))); } let page_end = envelopes.len().min(page_begin + page_size); + debug!("page end: {:?}", page_end); // Sorts envelopes by most recent date. envelopes.sort_by(|a, b| b.date.partial_cmp(&a.date).unwrap()); @@ -104,9 +153,10 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { // Applies pagination boundaries. envelopes.0 = envelopes[page_begin..page_end].to_owned(); - // Writes envelope ids and their hashes to a cache file. The - // cache file name is based on the name of the given maildir: - // this way there is one cache per maildir. + // Appends envelopes hash to the id mapper cache file and + // calculates the new short hash length. The short hash length + // represents the minimum hash length possible to avoid + // conflicts. let short_hash_len = { let mut mapper = IdMapper::new(mdir.path())?; let entries = envelopes @@ -115,70 +165,132 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { .collect(); mapper.append(entries)? }; + debug!("short hash length: {:?}", short_hash_len); // Shorten envelopes hash. envelopes .iter_mut() .for_each(|env| env.hash = env.hash[0..short_hash_len].to_owned()); + info!("<< get maildir envelopes"); Ok(Box::new(envelopes)) } fn search_envelopes( &mut self, - _mdir: &str, + _subdir: &str, _query: &str, _sort: &str, _page_size: usize, _page: usize, ) -> Result> { + info!(">> search maildir envelopes"); + info!("<< search maildir envelopes"); Err(anyhow!( "cannot find maildir envelopes: feature not implemented" )) } - fn add_msg(&mut self, mdir: &str, msg: &[u8], flags: &str) -> Result> { - let mdir = self.get_mdir_from_name(mdir)?; - let flags: MaildirFlags = flags.try_into()?; + fn add_msg(&mut self, subdir: &str, msg: &[u8], flags: &str) -> Result> { + info!(">> add maildir message"); + debug!("subdir: {:?}", subdir); + debug!("flags: {:?}", flags); + + let mdir = self.get_mdir_from_dir(subdir).context(format!( + "cannot get maildir instance from subdir {:?}", + subdir + ))?; + let flags: MaildirFlags = flags + .try_into() + .context(format!("cannot parse flags {:?}", flags))?; let id = mdir .store_cur_with_flags(msg, &flags.to_string()) .context(format!("cannot add maildir message to {:?}", mdir.path()))?; + debug!("id: {:?}", id); let hash = format!("{:x}", md5::compute(&id)); + debug!("hash: {:?}", hash); - // Appends hash line to the maildir cache file. - let mut mapper = IdMapper::new(mdir.path())?; - mapper.append(vec![(hash.clone(), id)])?; + // Appends hash entry to the id mapper cache file. + let mut mapper = IdMapper::new(mdir.path()).context(format!( + "cannot create id mapper instance for {:?}", + mdir.path() + ))?; + mapper + .append(vec![(hash.clone(), id.clone())]) + .context(format!( + "cannot append hash {:?} with id {:?} to id mapper", + hash, id + ))?; + info!("<< add maildir message"); Ok(Box::new(hash)) } - fn get_msg(&mut self, mdir: &str, short_hash: &str) -> Result { - let mdir = self.get_mdir_from_name(mdir)?; + fn get_msg(&mut self, subdir: &str, short_hash: &str) -> Result { + info!(">> get maildir message"); + debug!("subdir: {:?}", subdir); + debug!("short hash: {:?}", short_hash); + + let mdir = self.get_mdir_from_dir(subdir).context(format!( + "cannot get maildir instance from subdir {:?}", + subdir + ))?; let id = IdMapper::new(mdir.path())? .find(short_hash) .context(format!( - "cannot get maildir message from short hash {:?}", - short_hash + "cannot find maildir message by short hash {:?} at {:?}", + short_hash, + mdir.path() ))?; - let mut mail_entry = mdir - .find(&id) - .ok_or_else(|| anyhow!("cannot find maildir message {:?} in {:?}", id, mdir.path()))?; + debug!("id: {:?}", id); + let mut mail_entry = mdir.find(&id).ok_or_else(|| { + anyhow!( + "cannot find maildir message by id {:?} at {:?}", + id, + mdir.path() + ) + })?; let parsed_mail = mail_entry.parsed().context(format!( - "cannot parse maildir message {:?} in {:?}", + "cannot parse maildir message {:?} at {:?}", id, mdir.path() ))?; - Msg::from_parsed_mail(parsed_mail, self.account_config).context(format!( - "cannot parse maildir message {:?} from {:?}", + let msg = Msg::from_parsed_mail(parsed_mail, self.account_config).context(format!( + "cannot parse maildir message {:?} at {:?}", id, mdir.path() - )) + ))?; + trace!("message: {:?}", msg); + + info!("<< get maildir message"); + Ok(msg) } - fn copy_msg(&mut self, mdir_src: &str, mdir_dst: &str, short_hash: &str) -> Result<()> { - let mdir_src = self.get_mdir_from_name(mdir_src)?; - let mdir_dst = self.get_mdir_from_name(mdir_dst)?; - let id = IdMapper::new(mdir_src.path())?.find(short_hash)?; + fn copy_msg(&mut self, subdir_src: &str, subdir_dst: &str, short_hash: &str) -> Result<()> { + info!(">> copy maildir message"); + debug!("source subdir: {:?}", subdir_src); + debug!("destination subdir: {:?}", subdir_dst); + + let mdir_src = self.get_mdir_from_dir(subdir_src).context(format!( + "cannot get source maildir instance from subdir {:?}", + subdir_src + ))?; + let mdir_dst = self.get_mdir_from_dir(subdir_dst).context(format!( + "cannot get destination maildir instance from subdir {:?}", + subdir_dst + ))?; + let id = IdMapper::new(mdir_src.path()) + .context(format!( + "cannot create id mapper instance for {:?}", + mdir_src.path() + ))? + .find(short_hash) + .context(format!( + "cannot find maildir message by short hash {:?} at {:?}", + short_hash, + mdir_src.path() + ))?; + debug!("id: {:?}", id); mdir_src.copy_to(&id, &mdir_dst).context(format!( "cannot copy message {:?} from maildir {:?} to maildir {:?}", @@ -187,16 +299,48 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { mdir_dst.path() ))?; - // Appends hash line to the destination maildir cache file. - IdMapper::new(mdir_dst.path())?.append(vec![(format!("{:x}", md5::compute(&id)), id)])?; + // Appends hash entry to the id mapper cache file. + let mut mapper = IdMapper::new(mdir_dst.path()).context(format!( + "cannot create id mapper instance for {:?}", + mdir_dst.path() + ))?; + let hash = format!("{:x}", md5::compute(&id)); + mapper + .append(vec![(hash.clone(), id.clone())]) + .context(format!( + "cannot append hash {:?} with id {:?} to id mapper", + hash, id + ))?; + info!("<< copy maildir message"); Ok(()) } - fn move_msg(&mut self, mdir_src: &str, mdir_dst: &str, short_hash: &str) -> Result<()> { - let mdir_src = self.get_mdir_from_name(mdir_src)?; - let mdir_dst = self.get_mdir_from_name(mdir_dst)?; - let id = IdMapper::new(mdir_src.path())?.find(short_hash)?; + fn move_msg(&mut self, subdir_src: &str, subdir_dst: &str, short_hash: &str) -> Result<()> { + info!(">> move maildir message"); + debug!("source subdir: {:?}", subdir_src); + debug!("destination subdir: {:?}", subdir_dst); + + let mdir_src = self.get_mdir_from_dir(subdir_src).context(format!( + "cannot get source maildir instance from subdir {:?}", + subdir_src + ))?; + let mdir_dst = self.get_mdir_from_dir(subdir_dst).context(format!( + "cannot get destination maildir instance from subdir {:?}", + subdir_dst + ))?; + let id = IdMapper::new(mdir_src.path()) + .context(format!( + "cannot create id mapper instance for {:?}", + mdir_src.path() + ))? + .find(short_hash) + .context(format!( + "cannot find maildir message by short hash {:?} at {:?}", + short_hash, + mdir_src.path() + ))?; + debug!("id: {:?}", id); mdir_src.move_to(&id, &mdir_dst).context(format!( "cannot move message {:?} from maildir {:?} to maildir {:?}", @@ -205,49 +349,156 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { mdir_dst.path() ))?; - // Appends hash line to the destination maildir cache file. - IdMapper::new(mdir_dst.path())?.append(vec![(format!("{:x}", md5::compute(&id)), id)])?; + // Appends hash entry to the id mapper cache file. + let mut mapper = IdMapper::new(mdir_dst.path()).context(format!( + "cannot create id mapper instance for {:?}", + mdir_dst.path() + ))?; + let hash = format!("{:x}", md5::compute(&id)); + mapper + .append(vec![(hash.clone(), id.clone())]) + .context(format!( + "cannot append hash {:?} with id {:?} to id mapper", + hash, id + ))?; + info!("<< move maildir message"); Ok(()) } - fn del_msg(&mut self, mdir: &str, short_hash: &str) -> Result<()> { - let mdir = self.get_mdir_from_name(mdir)?; - let id = IdMapper::new(mdir.path())?.find(short_hash)?; + fn del_msg(&mut self, subdir: &str, short_hash: &str) -> Result<()> { + info!(">> delete maildir message"); + debug!("subdir: {:?}", subdir); + debug!("short hash: {:?}", short_hash); + + let mdir = self.get_mdir_from_dir(subdir).context(format!( + "cannot get maildir instance from subdir {:?}", + subdir + ))?; + let id = IdMapper::new(mdir.path()) + .context(format!( + "cannot create id mapper instance for {:?}", + mdir.path() + ))? + .find(short_hash) + .context(format!( + "cannot find maildir message by short hash {:?} at {:?}", + short_hash, + mdir.path() + ))?; + debug!("id: {:?}", id); mdir.delete(&id).context(format!( "cannot delete message {:?} from maildir {:?}", id, mdir.path() - )) + ))?; + + info!("<< delete maildir message"); + Ok(()) } - fn add_flags(&mut self, mdir: &str, short_hash: &str, flags_str: &str) -> Result<()> { - let mdir = self.get_mdir_from_name(mdir)?; - let id = IdMapper::new(mdir.path())?.find(short_hash)?; - let flags: MaildirFlags = flags_str.try_into()?; + fn add_flags(&mut self, subdir: &str, short_hash: &str, flags: &str) -> Result<()> { + info!(">> add maildir message flags"); + debug!("subdir: {:?}", subdir); + debug!("short hash: {:?}", short_hash); + debug!("flags: {:?}", flags); + + let mdir = self.get_mdir_from_dir(subdir).context(format!( + "cannot get maildir instance from subdir {:?}", + subdir + ))?; + let flags: MaildirFlags = flags + .try_into() + .context(format!("cannot parse maildir flags {:?}", flags))?; + debug!("flags: {:?}", flags); + let id = IdMapper::new(mdir.path()) + .context(format!( + "cannot create id mapper instance for {:?}", + mdir.path() + ))? + .find(short_hash) + .context(format!( + "cannot find maildir message by short hash {:?} at {:?}", + short_hash, + mdir.path() + ))?; + debug!("id: {:?}", id); mdir.add_flags(&id, &flags.to_string()).context(format!( "cannot add flags {:?} to maildir message {:?}", - flags_str, id - )) + flags, id + ))?; + + info!("<< add maildir message flags"); + Ok(()) } - fn set_flags(&mut self, mdir: &str, short_hash: &str, flags_str: &str) -> Result<()> { - let mdir = self.get_mdir_from_name(mdir)?; - let id = IdMapper::new(mdir.path())?.find(short_hash)?; - let flags: MaildirFlags = flags_str.try_into()?; + fn set_flags(&mut self, subdir: &str, short_hash: &str, flags: &str) -> Result<()> { + info!(">> set maildir message flags"); + debug!("subdir: {:?}", subdir); + debug!("short hash: {:?}", short_hash); + debug!("flags: {:?}", flags); + + let mdir = self.get_mdir_from_dir(subdir).context(format!( + "cannot get maildir instance from subdir {:?}", + subdir + ))?; + let flags: MaildirFlags = flags + .try_into() + .context(format!("cannot parse maildir flags {:?}", flags))?; + debug!("flags: {:?}", flags); + let id = IdMapper::new(mdir.path()) + .context(format!( + "cannot create id mapper instance for {:?}", + mdir.path() + ))? + .find(short_hash) + .context(format!( + "cannot find maildir message by short hash {:?} at {:?}", + short_hash, + mdir.path() + ))?; + debug!("id: {:?}", id); mdir.set_flags(&id, &flags.to_string()).context(format!( "cannot set flags {:?} to maildir message {:?}", - flags_str, id - )) + flags, id + ))?; + + info!("<< set maildir message flags"); + Ok(()) } - fn del_flags(&mut self, mdir: &str, short_hash: &str, flags_str: &str) -> Result<()> { - let mdir = self.get_mdir_from_name(mdir)?; - let id = IdMapper::new(mdir.path())?.find(short_hash)?; - let flags: MaildirFlags = flags_str.try_into()?; + fn del_flags(&mut self, subdir: &str, short_hash: &str, flags: &str) -> Result<()> { + info!(">> delete maildir message flags"); + debug!("subdir: {:?}", subdir); + debug!("short hash: {:?}", short_hash); + debug!("flags: {:?}", flags); + + let mdir = self.get_mdir_from_dir(subdir).context(format!( + "cannot get maildir instance from subdir {:?}", + subdir + ))?; + let flags: MaildirFlags = flags + .try_into() + .context(format!("cannot parse maildir flags {:?}", flags))?; + debug!("flags: {:?}", flags); + let id = IdMapper::new(mdir.path()) + .context(format!( + "cannot create id mapper instance for {:?}", + mdir.path() + ))? + .find(short_hash) + .context(format!( + "cannot find maildir message by short hash {:?} at {:?}", + short_hash, + mdir.path() + ))?; + debug!("id: {:?}", id); mdir.remove_flags(&id, &flags.to_string()).context(format!( - "cannot remove flags {:?} from maildir message {:?}", - flags_str, id - )) + "cannot delete flags {:?} to maildir message {:?}", + flags, id + ))?; + + info!("<< delete maildir message flags"); + Ok(()) } }