From ad1f97faed38ef7119897722e8217ea70120b10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Sun, 27 Feb 2022 23:40:19 +0100 Subject: [PATCH] move id mapper to its dedicated module --- src/backends/id_mapper.rs | 130 +++++++++++++++++ src/backends/maildir/maildir_backend.rs | 178 +++--------------------- src/lib.rs | 3 + 3 files changed, 153 insertions(+), 158 deletions(-) create mode 100644 src/backends/id_mapper.rs diff --git a/src/backends/id_mapper.rs b/src/backends/id_mapper.rs new file mode 100644 index 0000000..7ca77a1 --- /dev/null +++ b/src/backends/id_mapper.rs @@ -0,0 +1,130 @@ +use anyhow::{anyhow, Context, Result}; +use std::{ + collections::HashMap, + fs::OpenOptions, + io::{BufRead, BufReader, Write}, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, +}; + +#[derive(Debug, Default)] +pub struct IdMapper { + path: PathBuf, + map: HashMap, + short_hash_len: usize, +} + +impl IdMapper { + pub fn new(dir: &Path) -> Result { + let mut mapper = Self::default(); + mapper.path = dir.join(".himalaya-id-map"); + + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&mapper.path) + .context("cannot open id hash map file")?; + let reader = BufReader::new(file); + + for line in reader.lines() { + let line = + line.context("cannot read line from maildir envelopes id mapper cache file")?; + if mapper.short_hash_len == 0 { + mapper.short_hash_len = 2.max(line.parse().unwrap_or(2)); + } else { + let (hash, id) = line.split_once(' ').ok_or_else(|| { + anyhow!( + "cannot parse line {:?} from maildir envelopes id mapper cache file", + line + ) + })?; + mapper.insert(hash.to_owned(), id.to_owned()); + } + } + + Ok(mapper) + } + + pub fn find(&self, short_hash: &str) -> Result { + let matching_hashes: Vec<_> = self + .keys() + .filter(|hash| hash.starts_with(short_hash)) + .collect(); + if matching_hashes.len() == 0 { + Err(anyhow!( + "cannot find maildir message id from short hash {:?}", + short_hash, + )) + } else if matching_hashes.len() > 1 { + Err(anyhow!( + "the short hash {:?} matches more than one hash: {}", + short_hash, + matching_hashes + .iter() + .map(|s| s.to_string()) + .collect::>() + .join(", ") + ) + .context(format!( + "cannot find maildir message id from short hash {:?}", + short_hash + ))) + } else { + Ok(self.get(matching_hashes[0]).unwrap().to_owned()) + } + } + + pub fn append(&mut self, lines: Vec<(String, String)>) -> Result { + let mut entries = String::new(); + + self.extend(lines.clone()); + + for (hash, id) in self.iter() { + entries.push_str(&format!("{} {}\n", hash, id)); + } + + for (hash, id) in lines { + loop { + let short_hash = &hash[0..self.short_hash_len]; + let conflict_found = self + .map + .keys() + .find(|cached_hash| { + cached_hash.starts_with(short_hash) && *cached_hash != &hash + }) + .is_some(); + if self.short_hash_len > 32 || !conflict_found { + break; + } + self.short_hash_len += 1; + } + entries.push_str(&format!("{} {}\n", hash, id)); + } + + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&self.path) + .context("cannot open maildir id hash map cache")? + .write(format!("{}\n{}", self.short_hash_len, entries).as_bytes()) + .context("cannot write maildir id hash map cache")?; + + Ok(self.short_hash_len) + } +} + +impl Deref for IdMapper { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.map + } +} + +impl DerefMut for IdMapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map + } +} diff --git a/src/backends/maildir/maildir_backend.rs b/src/backends/maildir/maildir_backend.rs index 94b0f26..28ac626 100644 --- a/src/backends/maildir/maildir_backend.rs +++ b/src/backends/maildir/maildir_backend.rs @@ -1,149 +1,13 @@ use anyhow::{anyhow, Context, Result}; -use std::{ - collections::HashMap, - convert::TryInto, - env::temp_dir, - fs::{self, OpenOptions}, - io::{BufRead, BufReader, Write}, - ops::{Deref, DerefMut}, - path::PathBuf, -}; +use std::{convert::TryInto, fs, path::PathBuf}; use crate::{ - backends::{Backend, MaildirEnvelopes, MaildirFlags, MaildirMboxes}, + backends::{Backend, IdMapper, MaildirEnvelopes, MaildirFlags, MaildirMboxes}, config::{AccountConfig, MaildirBackendConfig, DEFAULT_INBOX_FOLDER}, mbox::Mboxes, msg::{Envelopes, Msg}, }; -#[derive(Debug, Default)] -pub struct MaildirEnvelopesIdHashMapper { - path: PathBuf, - map: HashMap, - short_hash_len: usize, -} - -impl MaildirEnvelopesIdHashMapper { - fn get_cache_path(mdir: &maildir::Maildir) -> PathBuf { - let path_digest = md5::compute(format!("{:?}", mdir.path())); - let file_name = format!("himalaya-hash-map-{:x}", path_digest); - temp_dir().join(file_name) - } - - pub fn new(mdir: &maildir::Maildir) -> Result { - let mut mapper = Self::default(); - mapper.path = Self::get_cache_path(mdir); - - let file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(&mapper.path) - .context("cannot open id hash map file")?; - let reader = BufReader::new(file); - - for line in reader.lines() { - let line = - line.context("cannot read line from maildir envelopes id mapper cache file")?; - if mapper.short_hash_len == 0 { - mapper.short_hash_len = 2.max(line.parse().unwrap_or(2)); - } else { - let (hash, id) = line.split_once(' ').ok_or_else(|| { - anyhow!( - "cannot parse line {:?} from maildir envelopes id mapper cache file", - line - ) - })?; - mapper.insert(hash.to_owned(), id.to_owned()); - } - } - - Ok(mapper) - } - - pub fn find(&self, short_hash: &str) -> Result { - let matching_hashes: Vec<_> = self - .keys() - .filter(|hash| hash.starts_with(short_hash)) - .collect(); - if matching_hashes.len() == 0 { - Err(anyhow!( - "cannot find maildir message id from short hash {:?}", - short_hash, - )) - } else if matching_hashes.len() > 1 { - Err(anyhow!( - "the short hash {:?} matches more than one hash: {}", - short_hash, - matching_hashes - .iter() - .map(|s| s.to_string()) - .collect::>() - .join(", ") - ) - .context(format!( - "cannot find maildir message id from short hash {:?}", - short_hash - ))) - } else { - Ok(self.get(matching_hashes[0]).unwrap().to_owned()) - } - } - - fn append(&mut self, lines: Vec<(String, String)>) -> Result { - let mut entries = String::new(); - - self.extend(lines.clone()); - - for (hash, id) in self.iter() { - entries.push_str(&format!("{} {}\n", hash, id)); - } - - for (hash, id) in lines { - loop { - let short_hash = &hash[0..self.short_hash_len]; - let conflict_found = self - .map - .keys() - .find(|cached_hash| { - cached_hash.starts_with(short_hash) && *cached_hash != &hash - }) - .is_some(); - if self.short_hash_len > 32 || !conflict_found { - break; - } - self.short_hash_len += 1; - } - entries.push_str(&format!("{} {}\n", hash, id)); - } - - OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(&self.path) - .context("cannot open maildir id hash map cache")? - .write(format!("{}\n{}", self.short_hash_len, entries).as_bytes()) - .context("cannot write maildir id hash map cache")?; - - Ok(self.short_hash_len) - } -} - -impl Deref for MaildirEnvelopesIdHashMapper { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - &self.map - } -} - -impl DerefMut for MaildirEnvelopesIdHashMapper { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.map - } -} - pub struct MaildirBackend<'a> { mdir: maildir::Maildir, account_config: &'a AccountConfig, @@ -211,18 +75,18 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { fn get_envelopes( &mut self, - mdir_str: &str, + mdir: &str, page_size: usize, page: usize, ) -> Result> { - let mdir = self.get_mdir_from_name(mdir_str)?; + let mdir = self.get_mdir_from_name(mdir)?; // Reads envelopes from the "cur" folder of the selected // maildir. - let mut envelopes: MaildirEnvelopes = mdir - .list_cur() - .try_into() - .context("cannot parse maildir envelopes from {:?}")?; + let mut envelopes: MaildirEnvelopes = mdir.list_cur().try_into().context(format!( + "cannot parse maildir envelopes from {:?}", + self.mdir.path() + ))?; // Calculates pagination boundaries. let page_begin = page * page_size; @@ -244,7 +108,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { // cache file name is based on the name of the given maildir: // this way there is one cache per maildir. let short_hash_len = { - let mut mapper = MaildirEnvelopesIdHashMapper::new(&mdir)?; + let mut mapper = IdMapper::new(mdir.path())?; let entries = envelopes .iter() .map(|env| (env.hash.to_owned(), env.id.to_owned())) @@ -278,11 +142,11 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { let flags: MaildirFlags = flags.try_into()?; let id = mdir .store_cur_with_flags(msg, &flags.to_string()) - .context(format!("cannot add maildir message at {:?}", mdir.path()))?; + .context(format!("cannot add maildir message to {:?}", mdir.path()))?; let hash = format!("{:x}", md5::compute(&id)); // Appends hash line to the maildir cache file. - let mut mapper = MaildirEnvelopesIdHashMapper::new(&mdir)?; + let mut mapper = IdMapper::new(mdir.path())?; mapper.append(vec![(hash.clone(), id)])?; Ok(Box::new(hash)) @@ -290,7 +154,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { fn get_msg(&mut self, mdir: &str, short_hash: &str) -> Result { let mdir = self.get_mdir_from_name(mdir)?; - let id = MaildirEnvelopesIdHashMapper::new(&mdir)? + let id = IdMapper::new(mdir.path())? .find(short_hash) .context(format!( "cannot get maildir message from short hash {:?}", @@ -314,7 +178,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { 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 = MaildirEnvelopesIdHashMapper::new(&mdir_src)?.find(short_hash)?; + let id = IdMapper::new(mdir_src.path())?.find(short_hash)?; mdir_src.copy_to(&id, &mdir_dst).context(format!( "cannot copy message {:?} from maildir {:?} to maildir {:?}", @@ -324,8 +188,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { ))?; // Appends hash line to the destination maildir cache file. - MaildirEnvelopesIdHashMapper::new(&mdir_dst)? - .append(vec![(format!("{:x}", md5::compute(&id)), id)])?; + IdMapper::new(mdir_dst.path())?.append(vec![(format!("{:x}", md5::compute(&id)), id)])?; Ok(()) } @@ -333,7 +196,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { 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 = MaildirEnvelopesIdHashMapper::new(&mdir_src)?.find(short_hash)?; + let id = IdMapper::new(mdir_src.path())?.find(short_hash)?; mdir_src.move_to(&id, &mdir_dst).context(format!( "cannot move message {:?} from maildir {:?} to maildir {:?}", @@ -343,15 +206,14 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { ))?; // Appends hash line to the destination maildir cache file. - MaildirEnvelopesIdHashMapper::new(&mdir_dst)? - .append(vec![(format!("{:x}", md5::compute(&id)), id)])?; + IdMapper::new(mdir_dst.path())?.append(vec![(format!("{:x}", md5::compute(&id)), id)])?; Ok(()) } fn del_msg(&mut self, mdir: &str, short_hash: &str) -> Result<()> { let mdir = self.get_mdir_from_name(mdir)?; - let id = MaildirEnvelopesIdHashMapper::new(&mdir)?.find(short_hash)?; + let id = IdMapper::new(mdir.path())?.find(short_hash)?; mdir.delete(&id).context(format!( "cannot delete message {:?} from maildir {:?}", id, @@ -361,7 +223,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { fn add_flags(&mut self, mdir: &str, short_hash: &str, flags_str: &str) -> Result<()> { let mdir = self.get_mdir_from_name(mdir)?; - let id = MaildirEnvelopesIdHashMapper::new(&mdir)?.find(short_hash)?; + let id = IdMapper::new(mdir.path())?.find(short_hash)?; let flags: MaildirFlags = flags_str.try_into()?; mdir.add_flags(&id, &flags.to_string()).context(format!( "cannot add flags {:?} to maildir message {:?}", @@ -371,7 +233,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { fn set_flags(&mut self, mdir: &str, short_hash: &str, flags_str: &str) -> Result<()> { let mdir = self.get_mdir_from_name(mdir)?; - let id = MaildirEnvelopesIdHashMapper::new(&mdir)?.find(short_hash)?; + let id = IdMapper::new(mdir.path())?.find(short_hash)?; let flags: MaildirFlags = flags_str.try_into()?; mdir.set_flags(&id, &flags.to_string()).context(format!( "cannot set flags {:?} to maildir message {:?}", @@ -381,7 +243,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> { fn del_flags(&mut self, mdir: &str, short_hash: &str, flags_str: &str) -> Result<()> { let mdir = self.get_mdir_from_name(mdir)?; - let id = MaildirEnvelopesIdHashMapper::new(&mdir)?.find(short_hash)?; + let id = IdMapper::new(mdir.path())?.find(short_hash)?; let flags: MaildirFlags = flags_str.try_into()?; mdir.remove_flags(&id, &flags.to_string()).context(format!( "cannot remove flags {:?} from maildir message {:?}", diff --git a/src/lib.rs b/src/lib.rs index 48b162d..2665914 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,9 @@ pub mod backends { pub use backend::*; pub mod backend; + pub use id_mapper::*; + pub mod id_mapper; + pub use self::imap::*; pub mod imap { pub mod imap_arg;