melib: add per-folder hash indexes in maildir backend

embed
Manos Pitsidianakis 2018-09-23 19:55:29 +03:00
parent 2f3c168aeb
commit 9b58908f6f
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
15 changed files with 334 additions and 233 deletions

View File

@ -29,7 +29,7 @@ use async::*;
use conf::AccountSettings; use conf::AccountSettings;
use error::Result; use error::Result;
use mailbox::backends::{ use mailbox::backends::{
BackendFolder, BackendOp, Folder, MailBackend, RefreshEvent, RefreshEventConsumer, BackendFolder, BackendOp, Folder, FolderHash, MailBackend, RefreshEvent, RefreshEventConsumer,
RefreshEventKind::*, RefreshEventKind::*,
}; };
use mailbox::email::{Envelope, EnvelopeHash}; use mailbox::email::{Envelope, EnvelopeHash};
@ -49,19 +49,39 @@ use std::ffi::OsStr;
use std::fs; use std::fs;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::io; use std::io;
use std::io::Read; use std::ops::{Deref, DerefMut};
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
use std::result; use std::result;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
type HashIndex = Arc<Mutex<FnvHashMap<EnvelopeHash, (usize, PathBuf)>>>; #[derive(Debug, Default)]
pub struct HashIndex {
index: FnvHashMap<EnvelopeHash, (usize, PathBuf)>,
hash: FolderHash,
}
impl Deref for HashIndex {
type Target = FnvHashMap<EnvelopeHash, (usize, PathBuf)>;
fn deref(&self) -> &FnvHashMap<EnvelopeHash, (usize, PathBuf)> {
&self.index
}
}
impl DerefMut for HashIndex {
fn deref_mut(&mut self) -> &mut FnvHashMap<EnvelopeHash, (usize, PathBuf)> {
&mut self.index
}
}
pub type HashIndexes = Arc<Mutex<FnvHashMap<FolderHash, HashIndex>>>;
/// Maildir backend https://cr.yp.to/proto/maildir.html /// Maildir backend https://cr.yp.to/proto/maildir.html
#[derive(Debug)] #[derive(Debug)]
pub struct MaildirType { pub struct MaildirType {
name: String, name: String,
folders: Vec<MaildirFolder>, folders: Vec<MaildirFolder>,
hash_index: HashIndex, //folder_index: FnvHashMap<FolderHash, usize>,
hash_indexes: HashIndexes,
path: PathBuf, path: PathBuf,
} }
@ -80,18 +100,18 @@ macro_rules! path_is_new {
} }
macro_rules! get_path_hash { macro_rules! get_path_hash {
($path:expr) => {{ ($path:expr) => {{
if $path.is_dir() { let mut path = $path.clone();
if $path.ends_with("cur") | $path.ends_with("new") { if path.is_dir() {
$path.pop(); if path.ends_with("cur") | path.ends_with("new") {
path.pop();
} }
} else { } else {
$path.pop(); path.pop();
$path.pop(); path.pop();
}; };
eprintln!(" got event in {}", $path.display());
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
$path.hash(&mut hasher); path.hash(&mut hasher);
hasher.finish() hasher.finish()
}}; }};
} }
@ -111,7 +131,7 @@ fn get_file_hash(file: &Path) -> EnvelopeHash {
hasher.finish() hasher.finish()
} }
fn move_to_cur(p: PathBuf) { fn move_to_cur(p: PathBuf) -> PathBuf {
let mut new = p.clone(); let mut new = p.clone();
{ {
let file_name = p.file_name().unwrap(); let file_name = p.file_name().unwrap();
@ -121,7 +141,8 @@ fn move_to_cur(p: PathBuf) {
new.push("cur"); new.push("cur");
new.push(file_name); new.push(file_name);
} }
fs::rename(p, new).unwrap(); fs::rename(p, &new).unwrap();
new
} }
impl MailBackend for MaildirType { impl MailBackend for MaildirType {
@ -148,7 +169,7 @@ impl MailBackend for MaildirType {
p.push("new"); p.push("new");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap(); watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
} }
let hash_index = self.hash_index.clone(); let hash_indexes = self.hash_indexes.clone();
thread::Builder::new() thread::Builder::new()
.name("folder watch".to_string()) .name("folder watch".to_string())
.spawn(move || { .spawn(move || {
@ -169,25 +190,26 @@ impl MailBackend for MaildirType {
Ok(event) => match event { Ok(event) => match event {
/* Create */ /* Create */
DebouncedEvent::Create(mut pathbuf) => { DebouncedEvent::Create(mut pathbuf) => {
if path_is_new!(pathbuf) { let folder_hash = get_path_hash!(pathbuf);
move_to_cur(pathbuf);
continue;
}
let file_name = pathbuf let file_name = pathbuf
.as_path() .as_path()
.strip_prefix(&root_path) .strip_prefix(&root_path)
.unwrap() .unwrap()
.to_path_buf(); .to_path_buf();
if let Some(env) = add_path_to_index( if let Some(env) = add_path_to_index(
&hash_index, &hash_indexes,
folder_hash,
pathbuf.as_path(), pathbuf.as_path(),
&cache_dir, &cache_dir,
file_name, file_name,
) { ) {
sender.send(RefreshEvent { sender.send(RefreshEvent {
hash: get_path_hash!(pathbuf), hash: folder_hash,
kind: Create(Box::new(env)), kind: Create(Box::new(env)),
}); });
if path_is_new!(pathbuf) {
move_to_cur(pathbuf);
}
} else { } else {
continue; continue;
} }
@ -195,6 +217,9 @@ impl MailBackend for MaildirType {
/* Update */ /* Update */
DebouncedEvent::NoticeWrite(mut pathbuf) DebouncedEvent::NoticeWrite(mut pathbuf)
| DebouncedEvent::Write(mut pathbuf) => { | DebouncedEvent::Write(mut pathbuf) => {
let folder_hash = get_path_hash!(pathbuf);
let mut hash_indexes_lock = hash_indexes.lock().unwrap();
let index_lock = &mut hash_indexes_lock.entry(folder_hash).or_default();
let file_name = pathbuf let file_name = pathbuf
.as_path() .as_path()
.strip_prefix(&root_path) .strip_prefix(&root_path)
@ -202,7 +227,6 @@ impl MailBackend for MaildirType {
.to_path_buf(); .to_path_buf();
/* Linear search in hash_index to find old hash */ /* Linear search in hash_index to find old hash */
let old_hash: EnvelopeHash = { let old_hash: EnvelopeHash = {
let mut index_lock = hash_index.lock().unwrap();
if let Some((k, v)) = if let Some((k, v)) =
index_lock.iter_mut().find(|(_, v)| v.1 == pathbuf) index_lock.iter_mut().find(|(_, v)| v.1 == pathbuf)
{ {
@ -212,13 +236,14 @@ impl MailBackend for MaildirType {
/* Did we just miss a Create event? In any case, create /* Did we just miss a Create event? In any case, create
* envelope. */ * envelope. */
if let Some(env) = add_path_to_index( if let Some(env) = add_path_to_index(
&hash_index, &hash_indexes,
folder_hash,
pathbuf.as_path(), pathbuf.as_path(),
&cache_dir, &cache_dir,
file_name, file_name,
) { ) {
sender.send(RefreshEvent { sender.send(RefreshEvent {
hash: get_path_hash!(pathbuf), hash: folder_hash,
kind: Create(Box::new(env)), kind: Create(Box::new(env)),
}); });
} }
@ -226,25 +251,28 @@ impl MailBackend for MaildirType {
} }
}; };
let new_hash: EnvelopeHash = get_file_hash(pathbuf.as_path()); let new_hash: EnvelopeHash = get_file_hash(pathbuf.as_path());
let mut index_lock = hash_index.lock().unwrap();
if index_lock.get_mut(&new_hash).is_none() { if index_lock.get_mut(&new_hash).is_none() {
let op = Box::new(MaildirOp::new(new_hash, hash_index.clone())); let op = Box::new(MaildirOp::new(new_hash, hash_indexes.clone(), folder_hash));
if let Some(env) = Envelope::from_token(op, new_hash) { if let Some(env) = Envelope::from_token(op, new_hash) {
index_lock.insert(new_hash, (0, pathbuf.clone())); index_lock.insert(new_hash, (0, pathbuf.clone()));
/* Send Write notice */ /* Send Write notice */
sender.send(RefreshEvent { sender.send(RefreshEvent {
hash: get_path_hash!(pathbuf), hash: folder_hash,
kind: Update(old_hash, Box::new(env)), kind: Update(old_hash, Box::new(env)),
}); });
} else {
eprintln!("DEBUG: hash {}, path: {} couldn't be parsed in `add_path_to_index`", new_hash, pathbuf.as_path().display());
} }
} }
} }
/* Remove */ /* Remove */
DebouncedEvent::NoticeRemove(mut pathbuf) DebouncedEvent::NoticeRemove(mut pathbuf)
| DebouncedEvent::Remove(mut pathbuf) => { | DebouncedEvent::Remove(mut pathbuf) => {
let index_lock = hash_index.lock().unwrap(); let folder_hash = get_path_hash!(pathbuf);
let hash_indexes_lock = hash_indexes.lock().unwrap();
let index_lock = &hash_indexes_lock[&folder_hash];
let hash: EnvelopeHash = if let Some((k, _)) = let hash: EnvelopeHash = if let Some((k, _)) =
index_lock.iter().find(|(_, v)| v.1 == pathbuf) index_lock.iter().find(|(_, v)| v.1 == pathbuf)
{ {
@ -254,14 +282,16 @@ impl MailBackend for MaildirType {
}; };
sender.send(RefreshEvent { sender.send(RefreshEvent {
hash: get_path_hash!(pathbuf), hash: folder_hash,
kind: Remove(hash), kind: Remove(hash),
}); });
} }
/* Envelope hasn't changed, so handle this here */ /* Envelope hasn't changed, so handle this here */
DebouncedEvent::Rename(src, mut dest) => { DebouncedEvent::Rename(mut src, mut dest) => {
let folder_hash = get_path_hash!(src);
let old_hash: EnvelopeHash = get_file_hash(src.as_path()); let old_hash: EnvelopeHash = get_file_hash(src.as_path());
let mut index_lock = hash_index.lock().unwrap(); let mut hash_indexes_lock = hash_indexes.lock().unwrap();
let mut index_lock = hash_indexes_lock.entry(folder_hash).or_default();
if let Some(v) = index_lock.get_mut(&old_hash) { if let Some(v) = index_lock.get_mut(&old_hash) {
v.1 = dest; v.1 = dest;
} else { } else {
@ -285,8 +315,8 @@ impl MailBackend for MaildirType {
})?; })?;
Ok(()) Ok(())
} }
fn operation(&self, hash: EnvelopeHash) -> Box<BackendOp> { fn operation(&self, hash: EnvelopeHash, folder_hash: FolderHash) -> Box<BackendOp> {
Box::new(MaildirOp::new(hash, self.hash_index.clone())) Box::new(MaildirOp::new(hash, self.hash_indexes.clone(), folder_hash))
} }
} }
@ -329,13 +359,26 @@ impl MaildirType {
} }
} }
folders[0].children = recurse_folders(&mut folders, &path); folders[0].children = recurse_folders(&mut folders, &path);
let hash_indexes = Arc::new(Mutex::new(FnvHashMap::with_capacity_and_hasher(
folders.len(),
Default::default(),
)));
{
let mut hash_indexes = hash_indexes.lock().unwrap();
for f in &folders {
hash_indexes.insert(
f.hash(),
HashIndex {
index: FnvHashMap::with_capacity_and_hasher(0, Default::default()),
hash: f.hash(),
},
);
}
}
MaildirType { MaildirType {
name: f.name().to_string(), name: f.name().to_string(),
folders, folders,
hash_index: Arc::new(Mutex::new(FnvHashMap::with_capacity_and_hasher( hash_indexes,
0,
Default::default(),
))),
path: PathBuf::from(f.root_folder()), path: PathBuf::from(f.root_folder()),
} }
} }
@ -357,14 +400,20 @@ impl MaildirType {
let mut w = AsyncBuilder::new(); let mut w = AsyncBuilder::new();
let root_path = self.path.to_path_buf(); let root_path = self.path.to_path_buf();
let cache_dir = xdg::BaseDirectories::with_profile("meli", &self.name).unwrap(); let cache_dir = xdg::BaseDirectories::with_profile("meli", &self.name).unwrap();
{
let mut hash_index = self.hash_indexes.lock().unwrap();
let index_lock = hash_index.entry(folder.hash()).or_default();
index_lock.clear();
}
let handle = { let handle = {
let tx = w.tx(); let tx = w.tx();
// TODO: Avoid clone // TODO: Avoid clone
let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)]; let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)];
let folder_hash = folder.hash();
let mut path: PathBuf = folder.path().into(); let mut path: PathBuf = folder.path().into();
let name = format!("parsing {:?}", folder.name()); let name = format!("parsing {:?}", folder.name());
let map = self.hash_index.clone(); let map = self.hash_indexes.clone();
let map2 = self.hash_index.clone(); let map2 = self.hash_indexes.clone();
thread::Builder::new() thread::Builder::new()
.name(name.clone()) .name(name.clone())
@ -412,10 +461,10 @@ impl MaildirType {
Envelope, Envelope,
> = Vec::with_capacity(chunk.len()); > = Vec::with_capacity(chunk.len());
for c in chunk.chunks(size) { for c in chunk.chunks(size) {
//thread::yield_now();
let map = map.clone(); let map = map.clone();
let len = c.len(); let len = c.len();
for file in c { for file in c {
//thread::yield_now();
/* Check if we have a cache file with this email's /* Check if we have a cache file with this email's
* filename */ * filename */
@ -423,7 +472,7 @@ impl MaildirType {
.strip_prefix(&root_path) .strip_prefix(&root_path)
.unwrap() .unwrap()
.to_path_buf(); .to_path_buf();
let hash = if let Some(cached) = if let Some(cached) =
cache_dir.find_cache_file(&file_name) cache_dir.find_cache_file(&file_name)
{ {
/* Cached struct exists, try to load it */ /* Cached struct exists, try to load it */
@ -433,43 +482,25 @@ impl MaildirType {
let result: result::Result<Envelope, _> = bincode::deserialize_from(reader); let result: result::Result<Envelope, _> = bincode::deserialize_from(reader);
if let Ok(env) = result { if let Ok(env) = result {
let mut map = map.lock().unwrap(); let mut map = map.lock().unwrap();
let mut map = map.entry(folder_hash).or_default();;
let hash = env.hash(); let hash = env.hash();
if (*map).contains_key(&hash) { map.insert(hash, (0, file.clone()));
continue;
}
(*map).insert(hash, (0, file.clone()));
local_r.push(env); local_r.push(env);
continue; continue;
} }
let mut reader = io::BufReader::new(
fs::File::open(cached).unwrap(),
);
let mut buf = Vec::with_capacity(2048);
reader.read_to_end(&mut buf).unwrap_or_else(|_| {
panic!("Can't read {}", file.display())
});
let mut hasher = FnvHasher::default();
hasher.write(&buf);
hasher.finish()
} else {
get_file_hash(file)
}; };
let hash = get_file_hash(file);
{ {
{ let mut map = map.lock().unwrap();
let mut map = map.lock().unwrap(); let mut map = map.entry(folder_hash).or_default();
if (*map).contains_key(&hash) { (*map).insert(hash, (0, PathBuf::from(file)));
continue; }
} let op =
(*map).insert(hash, (0, PathBuf::from(file))); Box::new(MaildirOp::new(hash, map.clone(), folder_hash));
} if let Some(mut e) = Envelope::from_token(op, hash)
let op = {
Box::new(MaildirOp::new(hash, map.clone())); if let Ok(cached) =
if let Some(mut e) = Envelope::from_token(op, hash) cache_dir.place_cache_file(file_name)
{
if let Ok(cached) =
cache_dir.place_cache_file(file_name)
{ {
/* place result in cache directory */ /* place result in cache directory */
let f = match fs::File::create(cached) { let f = match fs::File::create(cached) {
@ -482,10 +513,10 @@ impl MaildirType {
bincode::serialize_into(writer, &e) bincode::serialize_into(writer, &e)
.unwrap(); .unwrap();
} }
local_r.push(e); local_r.push(e);
} else { } else {
continue; eprintln!("DEBUG: hash {}, path: {} couldn't be parsed in `add_path_to_index`", hash, file.as_path().display());
} continue;
} }
} }
tx.send(AsyncStatus::ProgressReport(len)); tx.send(AsyncStatus::ProgressReport(len));
@ -501,6 +532,7 @@ impl MaildirType {
r.append(&mut result); r.append(&mut result);
} }
let mut map = map2.lock().unwrap(); let mut map = map2.lock().unwrap();
let map = map.entry(folder_hash).or_default();
for (idx, e) in r.iter().enumerate() { for (idx, e) in r.iter().enumerate() {
let mut y = (*map)[&e.hash()].clone(); let mut y = (*map)[&e.hash()].clone();
y.0 = idx; y.0 = idx;
@ -517,7 +549,8 @@ impl MaildirType {
} }
fn add_path_to_index( fn add_path_to_index(
hash_index: &HashIndex, hash_index: &HashIndexes,
folder_hash: FolderHash,
path: &Path, path: &Path,
cache_dir: &xdg::BaseDirectories, cache_dir: &xdg::BaseDirectories,
file_name: PathBuf, file_name: PathBuf,
@ -525,13 +558,14 @@ fn add_path_to_index(
let env: Envelope; let env: Envelope;
let hash = get_file_hash(path); let hash = get_file_hash(path);
{ {
let mut index_lock = hash_index.lock().unwrap(); let mut hash_index = hash_index.lock().unwrap();
if (*index_lock).contains_key(&hash) { let index_lock = hash_index.entry(folder_hash).or_default();
if index_lock.contains_key(&hash) {
return None; return None;
} }
(*index_lock).insert(hash, (0, path.to_path_buf())); index_lock.insert(hash, (0, path.to_path_buf()));
} }
let op = Box::new(MaildirOp::new(hash, hash_index.clone())); let op = Box::new(MaildirOp::new(hash, hash_index.clone(), folder_hash));
if let Some(e) = Envelope::from_token(op, hash) { if let Some(e) = Envelope::from_token(op, hash) {
if let Ok(cached) = cache_dir.place_cache_file(file_name) { if let Ok(cached) = cache_dir.place_cache_file(file_name) {
/* place result in cache directory */ /* place result in cache directory */
@ -546,6 +580,11 @@ fn add_path_to_index(
} }
env = e; env = e;
} else { } else {
eprintln!(
"DEBUG: hash {}, path: {} couldn't be parsed in `add_path_to_index`",
hash,
path.display()
);
return None; return None;
} }
Some(env) Some(env)

View File

@ -20,7 +20,6 @@
*/ */
extern crate fnv; extern crate fnv;
use self::fnv::FnvHashMap;
mod backend; mod backend;
pub use self::backend::*; pub use self::backend::*;
@ -35,12 +34,12 @@ use std::collections::hash_map::DefaultHasher;
use std::fs; use std::fs;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
/// `BackendOp` implementor for Maildir /// `BackendOp` implementor for Maildir
#[derive(Debug)] #[derive(Debug)]
pub struct MaildirOp { pub struct MaildirOp {
hash_index: Arc<Mutex<FnvHashMap<EnvelopeHash, (usize, PathBuf)>>>, hash_index: HashIndexes,
folder_hash: FolderHash,
hash: EnvelopeHash, hash: EnvelopeHash,
slice: Option<Mmap>, slice: Option<Mmap>,
} }
@ -49,6 +48,7 @@ impl Clone for MaildirOp {
fn clone(&self) -> Self { fn clone(&self) -> Self {
MaildirOp { MaildirOp {
hash_index: self.hash_index.clone(), hash_index: self.hash_index.clone(),
folder_hash: self.folder_hash,
hash: self.hash, hash: self.hash,
slice: None, slice: None,
} }
@ -56,19 +56,17 @@ impl Clone for MaildirOp {
} }
impl MaildirOp { impl MaildirOp {
pub fn new( pub fn new(hash: EnvelopeHash, hash_index: HashIndexes, folder_hash: FolderHash) -> Self {
hash: EnvelopeHash,
hash_index: Arc<Mutex<FnvHashMap<EnvelopeHash, (usize, PathBuf)>>>,
) -> Self {
MaildirOp { MaildirOp {
hash_index, hash_index,
folder_hash,
hash, hash,
slice: None, slice: None,
} }
} }
fn path(&self) -> PathBuf { fn path(&self) -> PathBuf {
let hash_index = self.hash_index.clone(); let map = self.hash_index.lock().unwrap();
let map = hash_index.lock().unwrap(); let map = &map[&self.folder_hash];
map.get(&self.hash).unwrap().1.clone() map.get(&self.hash).unwrap().1.clone()
} }
} }
@ -154,6 +152,7 @@ impl<'a> BackendOp for MaildirOp {
let hash = envelope.hash(); let hash = envelope.hash();
let hash_index = self.hash_index.clone(); let hash_index = self.hash_index.clone();
let mut map = hash_index.lock().unwrap(); let mut map = hash_index.lock().unwrap();
let map = map.entry(self.folder_hash).or_default();
map.get_mut(&hash).unwrap().1 = PathBuf::from(new_name); map.get_mut(&hash).unwrap().1 = PathBuf::from(new_name);
Ok(()) Ok(())
} }

View File

@ -31,6 +31,7 @@ use mailbox::backends::maildir::MaildirType;
use mailbox::email::{Envelope, EnvelopeHash, Flag}; use mailbox::email::{Envelope, EnvelopeHash, Flag};
use std::fmt; use std::fmt;
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
extern crate fnv; extern crate fnv;
@ -147,7 +148,7 @@ pub trait MailBackend: ::std::fmt::Debug {
fn get(&mut self, folder: &Folder, notify_fn: Arc<NotifyFn>) -> Async<Result<Vec<Envelope>>>; fn get(&mut self, folder: &Folder, notify_fn: Arc<NotifyFn>) -> Async<Result<Vec<Envelope>>>;
fn watch(&self, sender: RefreshEventConsumer) -> Result<()>; fn watch(&self, sender: RefreshEventConsumer) -> Result<()>;
fn folders(&self) -> Vec<Folder>; fn folders(&self) -> Vec<Folder>;
fn operation(&self, hash: EnvelopeHash) -> Box<BackendOp>; fn operation(&self, hash: EnvelopeHash, folder_hash: FolderHash) -> Box<BackendOp>;
//login function //login function
} }
@ -265,4 +266,16 @@ pub fn folder_default() -> Folder {
} }
pub type FolderHash = u64; pub type FolderHash = u64;
pub type Folder = Box<BackendFolder + Send>; pub type Folder = Box<dyn BackendFolder + Send>;
impl Clone for Folder {
fn clone(&self) -> Self {
BackendFolder::clone(self.deref())
}
}
impl Default for Folder {
fn default() -> Self {
folder_default()
}
}

View File

@ -14,12 +14,32 @@ use self::fnv::FnvHashMap;
/// `Mailbox` represents a folder of mail. /// `Mailbox` represents a folder of mail.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Collection { pub struct Collection {
folder: Folder,
pub envelopes: FnvHashMap<EnvelopeHash, Envelope>, pub envelopes: FnvHashMap<EnvelopeHash, Envelope>,
date_index: BTreeMap<UnixTimestamp, EnvelopeHash>, date_index: BTreeMap<UnixTimestamp, EnvelopeHash>,
subject_index: Option<BTreeMap<String, EnvelopeHash>>, subject_index: Option<BTreeMap<String, EnvelopeHash>>,
pub threads: Threads, pub threads: Threads,
} }
impl Drop for Collection {
fn drop(&mut self) {
let cache_dir =
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", self.folder.hash()))
.unwrap();
if let Ok(cached) = cache_dir.place_cache_file("threads") {
/* place result in cache directory */
let f = match fs::File::create(cached) {
Ok(f) => f,
Err(e) => {
panic!("{}", e);
}
};
let writer = io::BufWriter::new(f);
bincode::serialize_into(writer, &self.threads).unwrap();
}
}
}
impl Collection { impl Collection {
pub fn new(vec: Vec<Envelope>, folder: &Folder) -> Collection { pub fn new(vec: Vec<Envelope>, folder: &Folder) -> Collection {
let mut envelopes: FnvHashMap<EnvelopeHash, Envelope> = let mut envelopes: FnvHashMap<EnvelopeHash, Envelope> =
@ -36,26 +56,26 @@ impl Collection {
let threads = if let Some(cached) = cache_dir.find_cache_file("threads") { let threads = if let Some(cached) = cache_dir.find_cache_file("threads") {
let reader = io::BufReader::new(fs::File::open(cached).unwrap()); let reader = io::BufReader::new(fs::File::open(cached).unwrap());
let result: result::Result<Threads, _> = bincode::deserialize_from(reader); let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
if let Ok(mut cached_t) = result { let ret = if let Ok(mut cached_t) = result {
cached_t.update(&mut envelopes); cached_t.update(&mut envelopes);
cached_t cached_t
} else { } else {
let ret = Threads::new(&mut envelopes); // sent_folder); Threads::new(&mut envelopes)
if let Ok(cached) = cache_dir.place_cache_file("threads") { };
/* place result in cache directory */ if let Ok(cached) = cache_dir.place_cache_file("threads") {
let f = match fs::File::create(cached) { /* place result in cache directory */
Ok(f) => f, let f = match fs::File::create(cached) {
Err(e) => { Ok(f) => f,
panic!("{}", e); Err(e) => {
} panic!("{}", e);
}; }
let writer = io::BufWriter::new(f); };
bincode::serialize_into(writer, &ret).unwrap(); let writer = io::BufWriter::new(f);
} bincode::serialize_into(writer, &ret).unwrap();
ret
} }
ret
} else { } else {
let ret = Threads::new(&mut envelopes); // sent_folder); let ret = Threads::new(&mut envelopes);
if let Ok(cached) = cache_dir.place_cache_file("threads") { if let Ok(cached) = cache_dir.place_cache_file("threads") {
/* place result in cache directory */ /* place result in cache directory */
let f = match fs::File::create(cached) { let f = match fs::File::create(cached) {
@ -70,6 +90,7 @@ impl Collection {
ret ret
}; };
Collection { Collection {
folder: folder.clone(),
envelopes, envelopes,
date_index, date_index,
subject_index, subject_index,
@ -94,7 +115,8 @@ impl Collection {
} }
} }
pub(crate) fn insert_reply(&mut self, envelope: Envelope) { pub(crate) fn insert_reply(&mut self, envelope: Envelope) {
self.threads.insert_reply(envelope, &mut self.envelopes); self.insert(envelope);
//self.threads.insert_reply(envelope, &mut self.envelopes);
} }
} }

View File

@ -58,7 +58,7 @@ impl<'a> From<&'a [u8]> for Charset {
b"utf-16" | b"UTF-16" => Charset::UTF16, b"utf-16" | b"UTF-16" => Charset::UTF16,
b"iso-8859-1" | b"ISO-8859-1" => Charset::ISO8859_1, b"iso-8859-1" | b"ISO-8859-1" => Charset::ISO8859_1,
b"iso-8859-2" | b"ISO-8859-2" => Charset::ISO8859_2, b"iso-8859-2" | b"ISO-8859-2" => Charset::ISO8859_2,
b"iso-8859-7" | b"ISO-8859-7" => Charset::ISO8859_7, b"iso-8859-7" | b"ISO-8859-7" | b"iso8859-7" => Charset::ISO8859_7,
b"iso-8859-15" | b"ISO-8859-15" => Charset::ISO8859_15, b"iso-8859-15" | b"ISO-8859-15" => Charset::ISO8859_15,
b"windows-1251" | b"Windows-1251" => Charset::Windows1251, b"windows-1251" | b"Windows-1251" => Charset::Windows1251,
b"windows-1252" | b"Windows-1252" => Charset::Windows1252, b"windows-1252" | b"Windows-1252" => Charset::Windows1252,

View File

@ -209,12 +209,18 @@ impl fmt::Debug for MessageID {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
struct References { struct References {
raw: Vec<u8>, raw: Vec<u8>,
refs: Vec<MessageID>, refs: Vec<MessageID>,
} }
impl fmt::Debug for References {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:#?}", self.refs)
}
}
bitflags! { bitflags! {
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
pub struct Flag: u8 { pub struct Flag: u8 {

View File

@ -30,7 +30,7 @@ pub use self::email::*;
/* Mail backends. Currently only maildir is supported */ /* Mail backends. Currently only maildir is supported */
pub mod backends; pub mod backends;
use error::Result; use error::Result;
use mailbox::backends::{folder_default, Folder}; use mailbox::backends::Folder;
pub mod thread; pub mod thread;
pub use mailbox::thread::{SortField, SortOrder, ThreadNode, Threads}; pub use mailbox::thread::{SortField, SortOrder, ThreadNode, Threads};
@ -40,7 +40,7 @@ pub use self::collection::*;
use std::option::Option; use std::option::Option;
/// `Mailbox` represents a folder of mail. /// `Mailbox` represents a folder of mail.
#[derive(Debug)] #[derive(Debug, Clone, Default)]
pub struct Mailbox { pub struct Mailbox {
pub folder: Folder, pub folder: Folder,
name: String, name: String,
@ -48,27 +48,6 @@ pub struct Mailbox {
has_sent: bool, has_sent: bool,
} }
impl Clone for Mailbox {
fn clone(&self) -> Self {
Mailbox {
folder: self.folder.clone(),
collection: self.collection.clone(),
has_sent: self.has_sent,
name: self.name.clone(),
}
}
}
impl Default for Mailbox {
fn default() -> Self {
Mailbox {
folder: folder_default(),
collection: Collection::default(),
has_sent: false,
name: String::new(),
}
}
}
impl Mailbox { impl Mailbox {
pub fn new(folder: Folder, envelopes: Result<Vec<Envelope>>) -> Result<Mailbox> { pub fn new(folder: Folder, envelopes: Result<Vec<Envelope>>) -> Result<Mailbox> {
let mut envelopes: Vec<Envelope> = envelopes?; let mut envelopes: Vec<Envelope> = envelopes?;

View File

@ -719,6 +719,21 @@ impl Threads {
} }
} }
pub fn update_envelope(&mut self, old_hash: EnvelopeHash, envelope: &Envelope) {
/* must update:
* - hash_set
* - message fields in thread_nodes
*/
self.hash_set.remove(&old_hash);
self.hash_set.insert(envelope.hash());
let node = self
.thread_nodes
.iter_mut()
.find(|n| n.message.map(|n| n == old_hash).unwrap_or(false))
.unwrap();
node.message = Some(envelope.hash());
}
pub fn update(&mut self, collection: &mut FnvHashMap<EnvelopeHash, Envelope>) { pub fn update(&mut self, collection: &mut FnvHashMap<EnvelopeHash, Envelope>) {
let new_hash_set = FnvHashSet::from_iter(collection.keys().cloned()); let new_hash_set = FnvHashSet::from_iter(collection.keys().cloned());
@ -732,6 +747,18 @@ impl Threads {
self.insert(&mut (*env), collection); self.insert(&mut (*env), collection);
} }
} }
let difference: Vec<EnvelopeHash> =
self.hash_set.difference(&new_hash_set).cloned().collect();
for h in difference {
self.hash_set.remove(&h);
let node = self
.thread_nodes
.iter_mut()
.find(|n| n.message.map(|n| n == h).unwrap_or(false))
.unwrap();
node.message = None;
}
} }
pub fn insert( pub fn insert(
@ -787,29 +814,34 @@ impl Threads {
stack.push(node_idx); stack.push(node_idx);
} }
/* Trace path from root ThreadTree to the envelope's parent */ {
let mut tree = self.tree.get_mut(); /* Trace path from root ThreadTree to the envelope's parent */
for &s in stack.iter().rev() { let mut tree = self.tree.get_mut();
/* Borrow checker is being a tad silly here, so the following for &s in stack.iter().rev() {
* is basically this: /* Borrow checker is being a tad silly here, so the following
* * is basically this:
* let tree = &mut tree[s].children; *
*/ * let tree = &mut tree[s].children;
let temp_tree = tree; */
if let Some(pos) = temp_tree.iter().position(|v| v.id == s) { let temp_tree = tree;
tree = &mut temp_tree[pos].children; if let Some(pos) = temp_tree.iter().position(|v| v.id == s) {
} else { tree = &mut temp_tree[pos].children;
let tree_node = ThreadTree::new(s); } else {
temp_tree.push(tree_node); let tree_node = ThreadTree::new(s);
let new_id = temp_tree.len() - 1; temp_tree.push(tree_node);
tree = &mut temp_tree[new_id].children; let new_id = temp_tree.len() - 1;
tree = &mut temp_tree[new_id].children;
}
} }
/* Add new child */
let tree_node = ThreadTree::new(id);
tree.push(tree_node);
let new_id = tree.len() - 1;
node_build(&mut tree[new_id], id, &mut self.thread_nodes, 1, collection);
} }
/* Add new child */ // FIXME: use insertion according to self.sort etc instead of sorting everytime
let tree_node = ThreadTree::new(id); self.inner_sort_by(*self.sort.borrow(), collection);
tree.push(tree_node); self.inner_subsort_by(*self.subsort.borrow(), collection);
let new_id = tree.len() - 1;
node_build(&mut tree[new_id], id, &mut self.thread_nodes, 1, collection);
} }
/* /*
@ -1073,26 +1105,6 @@ impl Index<usize> for Threads {
.expect("thread index out of bounds") .expect("thread index out of bounds")
} }
} }
/*
*
*
/* Update thread's date */
let mut parent_iter = parent_id;
'date: loop {
let p: &mut ThreadNode = &mut self.thread_nodes[parent_iter];
if p.date < envelope.date() {
p.date = envelope.date();
}
if let Some(p) = p.parent {
parent_iter = p;
} else {
break 'date;
}
}
ref_ptr = parent_id;
*
*
* */
fn node_build( fn node_build(
tree: &mut ThreadTree, tree: &mut ThreadTree,
@ -1104,6 +1116,10 @@ fn node_build(
if let Some(hash) = thread_nodes[idx].message { if let Some(hash) = thread_nodes[idx].message {
if let Some(parent_id) = thread_nodes[idx].parent { if let Some(parent_id) = thread_nodes[idx].parent {
if let Some(parent_hash) = thread_nodes[parent_id].message { if let Some(parent_hash) = thread_nodes[parent_id].message {
/* decide if the subject should be shown in the UI.
* If parent subject is Foobar and reply is `Re: Foobar`
* then showing the reply's subject can be reduntant
*/
let mut subject = collection[&hash].subject(); let mut subject = collection[&hash].subject();
let mut subject = subject.to_mut().as_bytes(); let mut subject = subject.to_mut().as_bytes();
let subject = subject.strip_prefixes(); let subject = subject.strip_prefixes();

View File

@ -114,7 +114,7 @@ impl Composer {
let parent_message = &mailbox.collection[&p.message().unwrap()]; let parent_message = &mailbox.collection[&p.message().unwrap()];
let mut op = context.accounts[coordinates.0] let mut op = context.accounts[coordinates.0]
.backend .backend
.operation(parent_message.hash()); .operation(parent_message.hash(), mailbox.folder.hash());
let parent_bytes = op.as_bytes(); let parent_bytes = op.as_bytes();
ret.draft = Draft::new_reply(parent_message, parent_bytes.unwrap()); ret.draft = Draft::new_reply(parent_message, parent_bytes.unwrap());
@ -456,12 +456,11 @@ impl Component for Composer {
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty || self.pager.is_dirty() self.dirty || self.pager.is_dirty() || self
|| self .reply_context
.reply_context .as_ref()
.as_ref() .map(|(_, p)| p.is_dirty())
.map(|(_, p)| p.is_dirty()) .unwrap_or(false)
.unwrap_or(false)
} }
fn set_dirty(&mut self) { fn set_dirty(&mut self) {

View File

@ -113,7 +113,6 @@ impl CompactListing {
}); });
// Get mailbox as a reference. // Get mailbox as a reference.
// //
// TODO: Show progress visually
match context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) { match context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) {
Ok(_) => {} Ok(_) => {}
Err(_) => { Err(_) => {
@ -160,6 +159,17 @@ impl CompactListing {
} }
threads.thread_nodes()[iter_ptr].message().unwrap() threads.thread_nodes()[iter_ptr].message().unwrap()
}; };
if !mailbox.collection.contains_key(&i) {
eprintln!("key = {}", i);
eprintln!(
"name = {} {}",
mailbox.name(),
context.accounts[self.cursor_pos.0].name()
);
eprintln!("{:#?}", context.accounts);
panic!();
}
let root_envelope: &Envelope = &mailbox.collection[&i]; let root_envelope: &Envelope = &mailbox.collection[&i];
let fg_color = if thread_node.has_unseen() { let fg_color = if thread_node.has_unseen() {
Color::Byte(0) Color::Byte(0)
@ -292,6 +302,10 @@ impl CompactListing {
} else if self.cursor_pos != self.new_cursor_pos { } else if self.cursor_pos != self.new_cursor_pos {
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
} }
if self.new_cursor_pos.2 >= self.length {
self.new_cursor_pos.2 = self.length - 1;
self.cursor_pos.2 = self.new_cursor_pos.2;
}
/* Page_no has changed, so draw new page */ /* Page_no has changed, so draw new page */
copy_area( copy_area(
@ -441,12 +455,13 @@ impl Component for CompactListing {
return true; return true;
} }
UIEventType::RefreshMailbox(_) => { UIEventType::RefreshMailbox(_) => {
self.dirty = true;
self.view = None; self.view = None;
self.dirty = true;
} }
UIEventType::MailboxUpdate((ref idxa, ref idxf)) => { UIEventType::MailboxUpdate((ref idxa, ref idxf)) => {
if *idxa == self.new_cursor_pos.0 && *idxf == self.new_cursor_pos.1 { if *idxa == self.new_cursor_pos.0 && *idxf == self.new_cursor_pos.1 {
self.refresh_mailbox(context); self.refresh_mailbox(context);
self.set_dirty();
} }
} }
UIEventType::ChangeMode(UIMode::Normal) => { UIEventType::ChangeMode(UIMode::Normal) => {

View File

@ -342,9 +342,13 @@ impl Component for PlainListing {
(envelope.hash(), envelope.is_seen()) (envelope.hash(), envelope.is_seen())
}; };
if !is_seen { if !is_seen {
let folder_hash = {
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
mailbox.folder.hash()
};
let op = { let op = {
let backend = &account.backend; let backend = &account.backend;
backend.operation(hash) backend.operation(hash, folder_hash)
}; };
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap(); let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
let envelope: &mut Envelope = &mut mailbox let envelope: &mut Envelope = &mut mailbox

View File

@ -415,9 +415,13 @@ impl Component for ThreadListing {
(envelope.hash(), envelope.is_seen()) (envelope.hash(), envelope.is_seen())
}; };
if !is_seen { if !is_seen {
let folder_hash = {
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
mailbox.folder.hash()
};
let op = { let op = {
let backend = &account.backend; let backend = &account.backend;
backend.operation(hash) backend.operation(hash, folder_hash)
}; };
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap(); let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
let envelope: &mut Envelope = mailbox.thread_to_mail_mut(idx); let envelope: &mut Envelope = mailbox.thread_to_mail_mut(idx);

View File

@ -305,7 +305,7 @@ impl Component for MailView {
let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2]; let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2];
let op = context.accounts[mailbox_idx.0] let op = context.accounts[mailbox_idx.0]
.backend .backend
.operation(envelope.hash()); .operation(envelope.hash(), mailbox.folder.hash());
let body = envelope.body(op); let body = envelope.body(op);
match self.mode { match self.mode {
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => { ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
@ -422,7 +422,7 @@ impl Component for MailView {
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2]; let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
let op = context.accounts[self.coordinates.0] let op = context.accounts[self.coordinates.0]
.backend .backend
.operation(envelope.hash()); .operation(envelope.hash(), mailbox.folder.hash());
if let Some(u) = envelope.body(op).attachments().get(lidx) { if let Some(u) = envelope.body(op).attachments().get(lidx) {
match u.content_type() { match u.content_type() {
ContentType::MessageRfc822 => { ContentType::MessageRfc822 => {
@ -518,7 +518,7 @@ impl Component for MailView {
let finder = LinkFinder::new(); let finder = LinkFinder::new();
let op = context.accounts[self.coordinates.0] let op = context.accounts[self.coordinates.0]
.backend .backend
.operation(envelope.hash()); .operation(envelope.hash(), mailbox.folder.hash());
let mut t = envelope.body(op).text().to_string(); let mut t = envelope.body(op).text().to_string();
let links: Vec<Link> = finder.links(&t).collect(); let links: Vec<Link> = finder.links(&t).collect();
if let Some(u) = links.get(lidx) { if let Some(u) = links.get(lidx) {

View File

@ -64,8 +64,7 @@ impl InputHandler {
}, },
&rx, &rx,
) )
}) }).unwrap();
.unwrap();
} }
fn kill(&self) { fn kill(&self) {
self.tx.send(false); self.tx.send(false);
@ -187,8 +186,7 @@ impl State {
sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck)) sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck))
})), })),
) )
}) }).collect();
.collect();
accounts.sort_by(|a, b| a.name().cmp(&b.name())); accounts.sort_by(|a, b| a.name().cmp(&b.name()));
let mut s = State { let mut s = State {
cols, cols,
@ -258,6 +256,10 @@ impl State {
id: 0, id: 0,
event_type: notification, event_type: notification,
}); });
self.context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::MailboxUpdate((idxa, idxm)),
});
} }
} else { } else {
eprintln!( eprintln!(

View File

@ -30,6 +30,7 @@ use mailbox::backends::{
}; };
use mailbox::*; use mailbox::*;
use melib::error::Result; use melib::error::Result;
use std::mem;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::result; use std::result;
use std::sync::Arc; use std::sync::Arc;
@ -102,45 +103,49 @@ impl Account {
tx.send(AsyncStatus::Finished); tx.send(AsyncStatus::Finished);
notify_fn.notify(); notify_fn.notify();
ret ret
}) }).unwrap(),
.unwrap(),
), ),
) )
} }
pub fn reload(&mut self, event: RefreshEvent, idx: usize) -> Option<UIEventType> { pub fn reload(&mut self, event: RefreshEvent, idx: usize) -> Option<UIEventType> {
let kind = event.kind(); let kind = event.kind();
let mailbox: &mut Mailbox = self.folders[idx].as_mut().unwrap().as_mut().unwrap(); {
match kind { let mailbox: &mut Mailbox = self.folders[idx].as_mut().unwrap().as_mut().unwrap();
RefreshEventKind::Update(old_hash, envelope) => { match kind {
mailbox.update(old_hash, *envelope); RefreshEventKind::Update(old_hash, envelope) => {
} mailbox.update(old_hash, *envelope);
RefreshEventKind::Create(envelope) => { }
let env: &Envelope = mailbox.insert(*envelope); RefreshEventKind::Create(envelope) => {
let ref_folders: Vec<Folder> = self.backend.folders(); let env: &Envelope = mailbox.insert(*envelope);
return Some(Notification( let ref_folders: Vec<Folder> = self.backend.folders();
Some("new mail".into()), return Some(Notification(
format!( Some("new mail".into()),
"{:.15}:\nSubject: {:.15}\nFrom: {:.15}", format!(
ref_folders[idx].name(), "{:.15}:\nSubject: {:.15}\nFrom: {:.15}",
env.subject(), ref_folders[idx].name(),
env.field_from_to_string() env.subject(),
), env.field_from_to_string()
)); ),
} ));
RefreshEventKind::Remove(envelope_hash) => { }
mailbox.remove(envelope_hash); RefreshEventKind::Remove(envelope_hash) => {
} mailbox.remove(envelope_hash);
RefreshEventKind::Rescan => { }
let ref_folders: Vec<Folder> = self.backend.folders(); RefreshEventKind::Rescan => {
let handle = Account::new_worker( let ref_folders: Vec<Folder> = self.backend.folders();
&self.name, let handle = Account::new_worker(
ref_folders[idx].clone(), &self.name,
&mut self.backend, ref_folders[idx].clone(),
self.notify_fn.clone(), &mut self.backend,
); self.notify_fn.clone(),
self.workers[idx] = handle; );
self.workers[idx] = handle;
}
} }
} }
if self.workers[idx].is_some() {
self.folders[idx] = None;
}
None None
} }
pub fn watch(&self, r: RefreshEventConsumer) -> () { pub fn watch(&self, r: RefreshEventConsumer) -> () {
@ -204,17 +209,15 @@ impl Account {
use std::slice::from_raw_parts_mut; use std::slice::from_raw_parts_mut;
( (
from_raw_parts_mut(ptr.offset(*sent_index as isize), *sent_index + 1) from_raw_parts_mut(ptr.offset(*sent_index as isize), *sent_index + 1)
[0] [0].as_mut()
.as_mut() .unwrap()
.unwrap() .as_mut()
.as_mut() .unwrap(),
.unwrap(),
from_raw_parts_mut(ptr.offset(folder_index as isize), folder_index + 1) from_raw_parts_mut(ptr.offset(folder_index as isize), folder_index + 1)
[0] [0].as_mut()
.as_mut() .unwrap()
.unwrap() .as_mut()
.as_mut() .unwrap(),
.unwrap(),
) )
} }
}; };