/* * meli - mailbox module. * * Copyright 2017 Manos 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 . */ #[macro_use] mod backend; pub use self::backend::*; mod stream; pub use stream::*; use crate::backends::*; use crate::email::Flag; use crate::error::{MeliError, Result}; use crate::shellexpand::ShellExpandTrait; pub use futures::stream::Stream; use std::collections::hash_map::DefaultHasher; use std::fs; use std::hash::{Hash, Hasher}; use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; /// `BackendOp` implementor for Maildir #[derive(Debug)] pub struct MaildirOp { hash_index: HashIndexes, mailbox_hash: MailboxHash, hash: EnvelopeHash, slice: Option>, } impl Clone for MaildirOp { fn clone(&self) -> Self { MaildirOp { hash_index: self.hash_index.clone(), mailbox_hash: self.mailbox_hash, hash: self.hash, slice: None, } } } impl MaildirOp { pub fn new(hash: EnvelopeHash, hash_index: HashIndexes, mailbox_hash: MailboxHash) -> Self { MaildirOp { hash_index, mailbox_hash, hash, slice: None, } } fn path(&self) -> PathBuf { let map = self.hash_index.lock().unwrap(); let map = &map[&self.mailbox_hash]; debug!("looking for {} in {} map", self.hash, self.mailbox_hash); if !map.contains_key(&self.hash) { debug!("doesn't contain it though len = {}\n{:#?}", map.len(), map); for e in map.iter() { debug!("{:#?}", e); } } if let Some(modif) = &map[&self.hash].modified { match modif { PathMod::Path(ref path) => path.clone(), PathMod::Hash(hash) => map[&hash].to_path_buf(), } } else { map.get(&self.hash).unwrap().to_path_buf() } } } impl<'a> BackendOp for MaildirOp { fn as_bytes(&mut self) -> ResultFuture> { if self.slice.is_none() { let file = std::fs::OpenOptions::new() .read(true) .write(false) .open(&self.path())?; let mut buf_reader = BufReader::new(file); let mut contents = Vec::new(); buf_reader.read_to_end(&mut contents)?; self.slice = Some(contents); } let ret = Ok(self.slice.as_ref().unwrap().as_slice().to_vec()); Ok(Box::pin(async move { ret })) } fn fetch_flags(&self) -> ResultFuture { let path = self.path(); let ret = Ok(path.flags()); Ok(Box::pin(async move { ret })) } } #[derive(Debug, Default, Clone)] 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); 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 = MaildirMailbox { hash: 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(MeliError::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(self.name()) } fn change_name(&mut self, s: &str) { self.name = s.to_string(); } 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()?)) } } pub trait MaildirPathTrait { fn flags(&self) -> Flag; } impl MaildirPathTrait for Path { fn flags(&self) -> Flag { let mut flag = Flag::default(); let path = self.to_string_lossy(); if !path.contains(":2,") { return flag; } for f in path.chars().rev() { match f { ',' => break, 'D' => flag |= Flag::DRAFT, 'F' => flag |= Flag::FLAGGED, 'P' => flag |= Flag::PASSED, 'R' => flag |= Flag::REPLIED, 'S' => flag |= Flag::SEEN, 'T' => flag |= Flag::TRASHED, _ => { debug!("DEBUG: in MaildirPathTrait::flags(), encountered unknown flag marker {:?}, path is {}", f, path); } } } flag } }