diff --git a/melib/src/maildir/backend.rs b/melib/src/maildir/backend.rs index 8efe29ce..d78fa56a 100644 --- a/melib/src/maildir/backend.rs +++ b/melib/src/maildir/backend.rs @@ -37,10 +37,12 @@ use std::{ use notify::{RecommendedWatcher, RecursiveMode, Watcher}; -use super::{watch, MaildirMailbox, MaildirOp, MaildirPathTrait}; use crate::{ backends::{prelude::*, RefreshEventKind::*}, error::{Error, ErrorKind, IntoError, Result}, + maildir::{ + mailbox::MaildirMailbox, operations::MaildirOp, watch, HashIndexes, MaildirPathTrait, + }, utils::shellexpand::ShellExpandTrait, }; @@ -103,8 +105,6 @@ impl DerefMut for HashIndex { } } -pub type HashIndexes = Arc>>; - /// The maildir backend instance type. #[derive(Debug)] pub struct MaildirType { diff --git a/melib/src/maildir/mailbox.rs b/melib/src/maildir/mailbox.rs new file mode 100644 index 00000000..ef9f4384 --- /dev/null +++ b/melib/src/maildir/mailbox.rs @@ -0,0 +1,227 @@ +// +// meli +// +// Copyright 2017 Emmanouil Pitsidianakis +// +// This file is part of meli. +// +// meli 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. +// +// meli 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 meli. If not, see . +// +// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later + +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + path::{Path, PathBuf}, + sync::{Arc, Mutex, RwLock}, +}; + +use crate::{ + backends::prelude::*, + error::{Error, Result}, + utils::shellexpand::ShellExpandTrait, +}; + +// let path = f.path(); +// if path.ends_with("cur") || path.ends_with("new") || path.ends_with("tmp") { +// continue 'entries; +// } +// if path.is_dir() { +// if let Ok(mut f) = MaildirMailbox::new( +// path.to_str().unwrap().to_string(), +// path.file_name().unwrap().to_str().unwrap().to_string(), +// None, +// Vec::new(), +// false, +// settings, +// ) { +// f.children = recurse_mailboxes(mailboxes, settings, &path)?; +// for c in &f.children { +// if let Some(f) = mailboxes.get_mut(c) { +// f.parent = Some(f.hash); +// } +// } +// children.push(f.hash); +// mailboxes.insert(f.hash, f); +// } else { +// /* If directory is invalid (i.e. has no {cur,new,tmp} +// * subfolders), accept it ONLY if +// * it contains subdirs of any depth that are +// * valid maildir paths +// */ +// let subdirs = recurse_mailboxes(mailboxes, settings, &path)?; +// if !subdirs.is_empty() { +// if let Ok(f) = MaildirMailbox::new( +// path.to_str().unwrap().to_string(), +// path.file_name().unwrap().to_str().unwrap().to_string(), +// None, +// subdirs, +// true, +// settings, +// ) { +// for c in &f.children { +// if let Some(f) = mailboxes.get_mut(c) { +// f.parent = Some(f.hash); +// } +// } +// children.push(f.hash); +// mailboxes.insert(f.hash, f); +// } +// } +// } +// } + +#[derive(Clone, Debug, Default)] +pub struct MaildirMailbox { + pub hash: MailboxHash, + pub name: String, + pub fs_path: PathBuf, + pub path: PathBuf, + pub parent: Option, + pub children: Vec, + pub usage: Arc>, + pub is_subscribed: bool, + pub permissions: MailboxPermissions, + pub total: Arc>, + pub unseen: Arc>, +} + +impl MaildirMailbox { + pub fn new( + path: String, + file_name: String, + parent: Option, + children: Vec, + accept_invalid: bool, + settings: &AccountSettings, + ) -> Result { + let pathbuf = PathBuf::from(&path).expand(); + let mut h = DefaultHasher::new(); + pathbuf.hash(&mut h); + + /* Check if mailbox path (Eg `INBOX/Lists/luddites`) is included in the + * subscribed mailboxes in user configuration */ + let fname = pathbuf + .strip_prefix( + PathBuf::from(&settings.root_mailbox) + .expand() + .parent() + .unwrap_or_else(|| Path::new("/")), + ) + .ok(); + + let read_only = if let Ok(metadata) = std::fs::metadata(&pathbuf) { + metadata.permissions().readonly() + } else { + true + }; + + let ret = Self { + hash: MailboxHash(h.finish()), + name: file_name, + path: fname.unwrap().to_path_buf(), + fs_path: pathbuf, + parent, + children, + usage: Arc::new(RwLock::new(SpecialUsageMailbox::Normal)), + is_subscribed: false, + permissions: MailboxPermissions { + create_messages: !read_only, + remove_messages: !read_only, + set_flags: !read_only, + create_child: !read_only, + rename_messages: !read_only, + delete_messages: !read_only, + delete_mailbox: !read_only, + change_permissions: false, + }, + unseen: Arc::new(Mutex::new(0)), + total: Arc::new(Mutex::new(0)), + }; + if !accept_invalid { + ret.is_valid()?; + } + Ok(ret) + } + + pub fn fs_path(&self) -> &Path { + self.fs_path.as_path() + } + + pub fn is_valid(&self) -> Result<()> { + let path = self.fs_path(); + let mut p = PathBuf::from(path); + for d in &["cur", "new", "tmp"] { + p.push(d); + if !p.is_dir() { + return Err(Error::new(format!( + "{} is not a valid maildir mailbox", + path.display() + ))); + } + p.pop(); + } + Ok(()) + } +} + +impl BackendMailbox for MaildirMailbox { + fn hash(&self) -> MailboxHash { + self.hash + } + + fn name(&self) -> &str { + &self.name + } + + fn path(&self) -> &str { + self.path.to_str().unwrap_or_else(|| self.name()) + } + + fn children(&self) -> &[MailboxHash] { + &self.children + } + + fn clone(&self) -> Mailbox { + Box::new(std::clone::Clone::clone(self)) + } + + fn special_usage(&self) -> SpecialUsageMailbox { + *self.usage.read().unwrap() + } + + fn parent(&self) -> Option { + self.parent + } + + fn permissions(&self) -> MailboxPermissions { + self.permissions + } + fn is_subscribed(&self) -> bool { + self.is_subscribed + } + fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> { + self.is_subscribed = new_val; + Ok(()) + } + + fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> { + *self.usage.write()? = new_val; + Ok(()) + } + + fn count(&self) -> Result<(usize, usize)> { + Ok((*self.unseen.lock()?, *self.total.lock()?)) + } +} diff --git a/melib/src/maildir/mod.rs b/melib/src/maildir/mod.rs index 47de22b1..7e9034e8 100644 --- a/melib/src/maildir/mod.rs +++ b/melib/src/maildir/mod.rs @@ -22,249 +22,24 @@ #[macro_use] mod backend; pub use self::backend::*; +pub mod mailbox; +pub mod operations; pub mod watch; mod stream; +pub use stream::*; + +pub type HashIndexes = Arc>>; + use std::{ collections::{hash_map::DefaultHasher, HashMap}, fs, hash::{Hash, Hasher}, - io::{BufReader, Read}, - path::{Path, PathBuf}, - sync::{Arc, Mutex, RwLock}, -}; - -pub use stream::*; - -use crate::{ - backends::prelude::*, - error::{Error, Result}, - utils::shellexpand::ShellExpandTrait, + path::Path, + sync::{Arc, Mutex}, }; -/// `BackendOp` implementor for Maildir -#[derive(Debug)] -pub struct MaildirOp { - hash_index: HashIndexes, - mailbox_hash: MailboxHash, - hash: EnvelopeHash, -} - -impl Clone for MaildirOp { - fn clone(&self) -> Self { - Self { - hash_index: self.hash_index.clone(), - mailbox_hash: self.mailbox_hash, - hash: self.hash, - } - } -} - -impl MaildirOp { - pub fn new(hash: EnvelopeHash, hash_index: HashIndexes, mailbox_hash: MailboxHash) -> Self { - Self { - hash_index, - mailbox_hash, - hash, - } - } - - pub fn path(&self) -> Option { - let map = self.hash_index.lock().unwrap(); - let map = map.get(&self.mailbox_hash)?; - log::trace!("looking for {} in {} map", self.hash, self.mailbox_hash); - let mut hash = self.hash; - loop { - let Some(p) = map.get(&hash) else { - log::trace!("doesn't contain it though len = {}\n{:#?}", map.len(), map); - for e in map.iter() { - log::debug!("{:#?}", e); - } - return None; - }; - if let Some(ref modif) = p.modified { - match modif { - PathMod::Path(ref path) => return Some(path.to_path_buf()), - PathMod::Hash(next_hash) => { - hash = *next_hash; - } - } - } else if p.removed { - return None; - } else { - return Some(p.buf.to_path_buf()); - } - } - } -} - -impl BackendOp for MaildirOp { - fn as_bytes(&self) -> ResultFuture> { - let _self = self.clone(); - Ok(Box::pin(async move { - smol::unblock(move || { - let Some(path) = _self.path() else { - return Err(Error::new("Not found") - .set_summary(format!("Message with hash {} was not found.", _self.hash)) - .set_kind(ErrorKind::NotFound)); - }; - let file = std::fs::OpenOptions::new() - .read(true) - .write(false) - .open(path)?; - let mut buf_reader = BufReader::new(file); - let mut contents = Vec::new(); - buf_reader.read_to_end(&mut contents)?; - Ok(contents) - }) - .await - })) - } -} - -#[derive(Clone, Debug, Default)] -pub struct MaildirMailbox { - hash: MailboxHash, - name: String, - fs_path: PathBuf, - path: PathBuf, - parent: Option, - children: Vec, - pub usage: Arc>, - pub is_subscribed: bool, - permissions: MailboxPermissions, - pub total: Arc>, - pub unseen: Arc>, -} - -impl MaildirMailbox { - pub fn new( - path: String, - file_name: String, - parent: Option, - children: Vec, - accept_invalid: bool, - settings: &AccountSettings, - ) -> Result { - let pathbuf = PathBuf::from(&path).expand(); - let mut h = DefaultHasher::new(); - pathbuf.hash(&mut h); - - /* Check if mailbox path (Eg `INBOX/Lists/luddites`) is included in the - * subscribed mailboxes in user configuration */ - let fname = pathbuf - .strip_prefix( - PathBuf::from(&settings.root_mailbox) - .expand() - .parent() - .unwrap_or_else(|| Path::new("/")), - ) - .ok(); - - let read_only = if let Ok(metadata) = std::fs::metadata(&pathbuf) { - metadata.permissions().readonly() - } else { - true - }; - - let ret = Self { - hash: MailboxHash(h.finish()), - name: file_name, - path: fname.unwrap().to_path_buf(), - fs_path: pathbuf, - parent, - children, - usage: Arc::new(RwLock::new(SpecialUsageMailbox::Normal)), - is_subscribed: false, - permissions: MailboxPermissions { - create_messages: !read_only, - remove_messages: !read_only, - set_flags: !read_only, - create_child: !read_only, - rename_messages: !read_only, - delete_messages: !read_only, - delete_mailbox: !read_only, - change_permissions: false, - }, - unseen: Arc::new(Mutex::new(0)), - total: Arc::new(Mutex::new(0)), - }; - if !accept_invalid { - ret.is_valid()?; - } - Ok(ret) - } - - pub fn fs_path(&self) -> &Path { - self.fs_path.as_path() - } - - fn is_valid(&self) -> Result<()> { - let path = self.fs_path(); - let mut p = PathBuf::from(path); - for d in &["cur", "new", "tmp"] { - p.push(d); - if !p.is_dir() { - return Err(Error::new(format!( - "{} is not a valid maildir mailbox", - path.display() - ))); - } - p.pop(); - } - Ok(()) - } -} - -impl BackendMailbox for MaildirMailbox { - fn hash(&self) -> MailboxHash { - self.hash - } - - fn name(&self) -> &str { - &self.name - } - - fn path(&self) -> &str { - self.path.to_str().unwrap_or_else(|| self.name()) - } - - fn children(&self) -> &[MailboxHash] { - &self.children - } - - fn clone(&self) -> Mailbox { - Box::new(std::clone::Clone::clone(self)) - } - - fn special_usage(&self) -> SpecialUsageMailbox { - *self.usage.read().unwrap() - } - - fn parent(&self) -> Option { - self.parent - } - - fn permissions(&self) -> MailboxPermissions { - self.permissions - } - fn is_subscribed(&self) -> bool { - self.is_subscribed - } - fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> { - self.is_subscribed = new_val; - Ok(()) - } - - fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> { - *self.usage.write()? = new_val; - Ok(()) - } - - fn count(&self) -> Result<(usize, usize)> { - Ok((*self.unseen.lock()?, *self.total.lock()?)) - } -} +use crate::backends::prelude::*; pub trait MaildirPathTrait { fn flags(&self) -> Flag; diff --git a/melib/src/maildir/operations.rs b/melib/src/maildir/operations.rs new file mode 100644 index 00000000..d768b5be --- /dev/null +++ b/melib/src/maildir/operations.rs @@ -0,0 +1,112 @@ +// +// meli +// +// Copyright 2017 Emmanouil Pitsidianakis +// +// This file is part of meli. +// +// meli 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. +// +// meli 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 meli. If not, see . +// +// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later + +use std::{ + io::{BufReader, Read}, + path::PathBuf, +}; + +use crate::{ + backends::prelude::*, + error::Error, + maildir::{HashIndexes, PathMod}, +}; + +/// `BackendOp` implementor for Maildir +#[derive(Debug)] +pub struct MaildirOp { + hash_index: HashIndexes, + mailbox_hash: MailboxHash, + hash: EnvelopeHash, +} + +impl Clone for MaildirOp { + fn clone(&self) -> Self { + Self { + hash_index: self.hash_index.clone(), + mailbox_hash: self.mailbox_hash, + hash: self.hash, + } + } +} + +impl MaildirOp { + pub fn new(hash: EnvelopeHash, hash_index: HashIndexes, mailbox_hash: MailboxHash) -> Self { + Self { + hash_index, + mailbox_hash, + hash, + } + } + + pub fn path(&self) -> Option { + let map = self.hash_index.lock().unwrap(); + let map = map.get(&self.mailbox_hash)?; + log::trace!("looking for {} in {} map", self.hash, self.mailbox_hash); + let mut hash = self.hash; + loop { + let Some(p) = map.get(&hash) else { + log::trace!("doesn't contain it though len = {}\n{:#?}", map.len(), map); + for e in map.iter() { + log::debug!("{:#?}", e); + } + return None; + }; + if let Some(ref modif) = p.modified { + match modif { + PathMod::Path(ref path) => return Some(path.to_path_buf()), + PathMod::Hash(next_hash) => { + hash = *next_hash; + } + } + } else if p.removed { + return None; + } else { + return Some(p.buf.to_path_buf()); + } + } + } +} + +impl BackendOp for MaildirOp { + fn as_bytes(&self) -> ResultFuture> { + let _self = self.clone(); + Ok(Box::pin(async move { + smol::unblock(move || { + let Some(path) = _self.path() else { + return Err(Error::new("Not found") + .set_summary(format!("Message with hash {} was not found.", _self.hash)) + .set_kind(ErrorKind::NotFound)); + }; + let file = std::fs::OpenOptions::new() + .read(true) + .write(false) + .open(path)?; + let mut buf_reader = BufReader::new(file); + let mut contents = Vec::new(); + buf_reader.read_to_end(&mut contents)?; + Ok(contents) + }) + .await + })) + } +}