melib/maildir: move Maildir{Mailbox,Op} to modules

Extract mailbox and MaildirOp code to their own modules.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
maildir-refactor
Manos Pitsidianakis 4 months ago
parent 9daf943758
commit e41a23880e
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -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<Mutex<HashMap<MailboxHash, HashIndex>>>;
/// The maildir backend instance type.
#[derive(Debug)]
pub struct MaildirType {

@ -0,0 +1,227 @@
//
// meli
//
// Copyright 2017 Emmanouil Pitsidianakis <manos@pitsidianak.is>
//
// 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 <http://www.gnu.org/licenses/>.
//
// 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<MailboxHash>,
pub children: Vec<MailboxHash>,
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
pub is_subscribed: bool,
pub permissions: MailboxPermissions,
pub total: Arc<Mutex<usize>>,
pub unseen: Arc<Mutex<usize>>,
}
impl MaildirMailbox {
pub fn new(
path: String,
file_name: String,
parent: Option<MailboxHash>,
children: Vec<MailboxHash>,
accept_invalid: bool,
settings: &AccountSettings,
) -> Result<Self> {
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<MailboxHash> {
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()?))
}
}

@ -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<Mutex<HashMap<MailboxHash, HashIndex>>>;
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<PathBuf> {
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<Vec<u8>> {
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<MailboxHash>,
children: Vec<MailboxHash>,
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
pub is_subscribed: bool,
permissions: MailboxPermissions,
pub total: Arc<Mutex<usize>>,
pub unseen: Arc<Mutex<usize>>,
}
impl MaildirMailbox {
pub fn new(
path: String,
file_name: String,
parent: Option<MailboxHash>,
children: Vec<MailboxHash>,
accept_invalid: bool,
settings: &AccountSettings,
) -> Result<Self> {
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<MailboxHash> {
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;

@ -0,0 +1,112 @@
//
// meli
//
// Copyright 2017 Emmanouil Pitsidianakis <manos@pitsidianak.is>
//
// 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 <http://www.gnu.org/licenses/>.
//
// 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<PathBuf> {
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<Vec<u8>> {
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
}))
}
}
Loading…
Cancel
Save