Manos Pitsidianakis 4 years ago
parent
commit
5a28320004
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 115
      melib/src/async.rs
  2. 159
      melib/src/mailbox/backends/maildir/backend.rs
  3. 11
      melib/src/mailbox/backends/maildir/mod.rs
  4. 5
      melib/src/mailbox/backends/mod.rs
  5. 89
      melib/src/mailbox/collection.rs
  6. 2
      melib/src/mailbox/email/attachments.rs
  7. 30
      melib/src/mailbox/email/mod.rs
  8. 22
      melib/src/mailbox/mod.rs
  9. 365
      melib/src/mailbox/thread.rs
  10. 12
      src/bin.rs
  11. 187
      ui/src/components/indexer/index.rs
  12. 128
      ui/src/components/indexer/mod.rs
  13. 29
      ui/src/components/mail/listing/compact.rs
  14. 2
      ui/src/components/mail/listing/mod.rs
  15. 272
      ui/src/components/mail/listing/thread.rs
  16. 3
      ui/src/components/mail/mod.rs
  17. 3
      ui/src/components/mod.rs
  18. 3
      ui/src/lib.rs
  19. 34
      ui/src/state.rs
  20. 75
      ui/src/types/accounts.rs
  21. 6
      ui/src/types/cells.rs
  22. 298
      ui/src/workers/mod.rs

115
melib/src/async.rs

@ -32,61 +32,94 @@
*/
use chan;
use std::thread;
use std::fmt;
use std::sync::Arc;
#[derive(Clone)]
pub struct Work(pub Arc<Box<dyn Fn() -> ()>>);
impl Work {
pub fn compute(&self) {
(self.0)();
}
}
impl fmt::Debug for Work {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Work object")
}
}
unsafe impl Send for Work {}
unsafe impl Sync for Work {}
/// Messages to pass between `Async<T>` owner and its worker thread.
#[derive(Debug)]
pub enum AsyncStatus {
#[derive(Clone)]
pub enum AsyncStatus<T> {
NoUpdate,
Payload(T),
Finished,
///The number may hold whatever meaning the user chooses.
ProgressReport(usize),
}
impl<T> fmt::Debug for AsyncStatus<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AsyncStatus::NoUpdate => write!(f, "AsyncStatus<T>::NoUpdate"),
AsyncStatus::Payload(_) => write!(f, "AsyncStatus<T>::Payload(_)"),
AsyncStatus::Finished => write!(f, "AsyncStatus<T>::Finished"),
AsyncStatus::ProgressReport(u) => write!(f, "AsyncStatus<T>::ProgressReport({})", u),
}
}
}
/// A builder object for `Async<T>`
#[derive(Debug)]
pub struct AsyncBuilder {
tx: chan::Sender<AsyncStatus>,
rx: chan::Receiver<AsyncStatus>,
#[derive(Debug, Clone)]
pub struct AsyncBuilder<T> {
tx: chan::Sender<AsyncStatus<T>>,
rx: chan::Receiver<AsyncStatus<T>>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Async<T> {
value: Option<T>,
worker: Option<thread::JoinHandle<T>>,
tx: chan::Sender<AsyncStatus>,
rx: chan::Receiver<AsyncStatus>,
work: Work,
active: bool,
tx: chan::Sender<AsyncStatus<T>>,
rx: chan::Receiver<AsyncStatus<T>>,
}
impl Default for AsyncBuilder {
impl<T> Default for AsyncBuilder<T> {
fn default() -> Self {
AsyncBuilder::new()
AsyncBuilder::<T>::new()
}
}
impl AsyncBuilder {
impl<T> AsyncBuilder<T> {
pub fn new() -> Self {
let (sender, receiver) = chan::sync(::std::mem::size_of::<AsyncStatus>());
let (sender, receiver) = chan::sync(8 * ::std::mem::size_of::<AsyncStatus<T>>());
AsyncBuilder {
tx: sender,
rx: receiver,
}
}
/// Returns the sender object of the promise's channel.
pub fn tx(&mut self) -> chan::Sender<AsyncStatus> {
pub fn tx(&mut self) -> chan::Sender<AsyncStatus<T>> {
self.tx.clone()
}
/// Returns the receiver object of the promise's channel.
pub fn rx(&mut self) -> chan::Receiver<AsyncStatus> {
pub fn rx(&mut self) -> chan::Receiver<AsyncStatus<T>> {
self.rx.clone()
}
/// Returns an `Async<T>` object that contains a `Thread` join handle that returns a `T`
pub fn build<T: Clone>(self, worker: thread::JoinHandle<T>) -> Async<T> {
pub fn build(self, work: Box<dyn Fn() -> ()>) -> Async<T> {
Async {
worker: Some(worker),
work: Work(Arc::new(work)),
value: None,
tx: self.tx,
rx: self.rx,
active: false,
}
}
}
@ -96,20 +129,34 @@ impl<T> Async<T> {
pub fn extract(self) -> T {
self.value.unwrap()
}
pub fn work(&mut self) -> Option<Work> {
if !self.active {
self.active = true;
Some(self.work.clone())
} else {
None
}
}
/// Returns the sender object of the promise's channel.
pub fn tx(&mut self) -> chan::Sender<AsyncStatus<T>> {
self.tx.clone()
}
/// Polls worker thread and returns result.
pub fn poll(&mut self) -> Result<AsyncStatus, ()> {
pub fn poll(&mut self) -> Result<AsyncStatus<T>, ()> {
if self.value.is_some() {
return Ok(AsyncStatus::Finished);
}
//self.tx.send(true);
let rx = &self.rx;
let result: T;
chan_select! {
default => {
return Ok(AsyncStatus::NoUpdate);
},
rx.recv() -> r => {
match r {
Some(AsyncStatus::Finished) => {
Some(AsyncStatus::Payload(payload)) => {
result = payload;
},
Some(a) => {
return Ok(a);
@ -118,15 +165,29 @@ impl<T> Async<T> {
return Err(());
},
}
},
}
let v = self.worker.take().unwrap().join().unwrap();
self.value = Some(v);
};
self.value = Some(result);
Ok(AsyncStatus::Finished)
}
/// Blocks until thread joins.
pub fn join(mut self) -> T {
self.worker.take().unwrap().join().unwrap()
pub fn join(&mut self) {
let result: T;
let rx = &self.rx;
loop {
chan_select! {
rx.recv() -> r => {
match r {
Some(AsyncStatus::Payload(payload)) => {
result = payload;
break;
},
_ => continue,
}
}
}
}
self.value = Some(result);
}
}

159
melib/src/mailbox/backends/maildir/backend.rs

@ -24,7 +24,7 @@ extern crate fnv;
extern crate notify;
extern crate xdg;
use super::{MaildirFolder, MaildirOp, NotifyFn};
use super::{MaildirFolder, MaildirOp};
use async::*;
use conf::AccountSettings;
use error::Result;
@ -56,19 +56,19 @@ use std::sync::{Arc, Mutex};
#[derive(Debug, Default)]
pub struct HashIndex {
index: FnvHashMap<EnvelopeHash, (usize, PathBuf)>,
index: FnvHashMap<EnvelopeHash, PathBuf>,
hash: FolderHash,
}
impl Deref for HashIndex {
type Target = FnvHashMap<EnvelopeHash, (usize, PathBuf)>;
fn deref(&self) -> &FnvHashMap<EnvelopeHash, (usize, PathBuf)> {
type Target = FnvHashMap<EnvelopeHash, PathBuf>;
fn deref(&self) -> &FnvHashMap<EnvelopeHash, PathBuf> {
&self.index
}
}
impl DerefMut for HashIndex {
fn deref_mut(&mut self) -> &mut FnvHashMap<EnvelopeHash, (usize, PathBuf)> {
fn deref_mut(&mut self) -> &mut FnvHashMap<EnvelopeHash, PathBuf> {
&mut self.index
}
}
@ -82,7 +82,6 @@ pub struct MaildirType {
folders: Vec<MaildirFolder>,
//folder_index: FnvHashMap<FolderHash, usize>,
hash_indexes: HashIndexes,
path: PathBuf,
}
@ -93,11 +92,11 @@ macro_rules! path_is_new {
} else {
let mut iter = $path.components().rev();
iter.next();
iter.next();
iter.next() == Some(Component::Normal(OsStr::new("new")))
}
};
}
macro_rules! get_path_hash {
($path:expr) => {{
let mut path = $path.clone();
@ -132,6 +131,7 @@ fn get_file_hash(file: &Path) -> EnvelopeHash {
}
fn move_to_cur(p: PathBuf) -> PathBuf {
eprintln!("moved to cur");
let mut new = p.clone();
{
let file_name = p.file_name().unwrap();
@ -149,8 +149,8 @@ impl MailBackend for MaildirType {
fn folders(&self) -> Vec<Folder> {
self.folders.iter().map(|f| f.clone()).collect()
}
fn get(&mut self, folder: &Folder, notify_fn: Arc<NotifyFn>) -> Async<Result<Vec<Envelope>>> {
self.multicore(4, folder, notify_fn)
fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
self.multicore(4, folder)
}
fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
let (tx, rx) = channel();
@ -190,6 +190,9 @@ impl MailBackend for MaildirType {
Ok(event) => match event {
/* Create */
DebouncedEvent::Create(mut pathbuf) => {
if path_is_new!(pathbuf) {
pathbuf = move_to_cur(pathbuf);
}
let folder_hash = get_path_hash!(pathbuf);
let file_name = pathbuf
.as_path()
@ -203,13 +206,11 @@ impl MailBackend for MaildirType {
&cache_dir,
file_name,
) {
eprintln!("Create event {} {} {}", env.hash(), env.subject(), pathbuf.display());
sender.send(RefreshEvent {
hash: folder_hash,
kind: Create(Box::new(env)),
});
if path_is_new!(pathbuf) {
move_to_cur(pathbuf);
}
} else {
continue;
}
@ -228,9 +229,9 @@ impl MailBackend for MaildirType {
/* Linear search in hash_index to find old hash */
let old_hash: EnvelopeHash = {
if let Some((k, v)) =
index_lock.iter_mut().find(|(_, v)| v.1 == pathbuf)
index_lock.iter_mut().find(|(_, v)| **v == pathbuf)
{
v.1 = pathbuf.clone();
*v = pathbuf.clone();
*k
} else {
/* Did we just miss a Create event? In any case, create
@ -254,7 +255,8 @@ impl MailBackend for MaildirType {
if index_lock.get_mut(&new_hash).is_none() {
let op = Box::new(MaildirOp::new(new_hash, hash_indexes.clone(), folder_hash));
if let Some(env) = Envelope::from_token(op, new_hash) {
index_lock.insert(new_hash, (0, pathbuf.clone()));
eprintln!("{}\t{}", new_hash, pathbuf.display());
index_lock.insert(new_hash, pathbuf);
/* Send Write notice */
@ -271,15 +273,16 @@ impl MailBackend for MaildirType {
DebouncedEvent::NoticeRemove(mut pathbuf)
| DebouncedEvent::Remove(mut pathbuf) => {
let folder_hash = get_path_hash!(pathbuf);
let hash_indexes_lock = hash_indexes.lock().unwrap();
let index_lock = &hash_indexes_lock[&folder_hash];
let mut hash_indexes_lock = hash_indexes.lock().unwrap();
let index_lock = hash_indexes_lock.entry(folder_hash).or_default();
let hash: EnvelopeHash = if let Some((k, _)) =
index_lock.iter().find(|(_, v)| v.1 == pathbuf)
index_lock.iter().find(|(_, v)| **v == pathbuf)
{
*k
} else {
continue;
};
index_lock.remove(&hash);
sender.send(RefreshEvent {
hash: folder_hash,
@ -290,10 +293,18 @@ impl MailBackend for MaildirType {
DebouncedEvent::Rename(mut src, mut dest) => {
let folder_hash = get_path_hash!(src);
let old_hash: EnvelopeHash = get_file_hash(src.as_path());
let new_hash: EnvelopeHash = get_file_hash(dest.as_path());
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) {
v.1 = dest;
let index_lock = hash_indexes_lock.entry(folder_hash).or_default();
if index_lock.contains_key(&old_hash) {
sender.send(RefreshEvent {
hash: get_path_hash!(dest),
kind: Rename(old_hash, new_hash),
});
index_lock.remove(&old_hash);
index_lock.insert(new_hash, dest);
} else {
/* Maybe a re-read should be triggered here just to be safe. */
sender.send(RefreshEvent {
@ -391,33 +402,31 @@ impl MaildirType {
.0
}
pub fn multicore(
&mut self,
cores: usize,
folder: &Folder,
notify_fn: Arc<NotifyFn>,
) -> Async<Result<Vec<Envelope>>> {
pub fn multicore(&mut self, cores: usize, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
let mut w = AsyncBuilder::new();
let root_path = self.path.to_path_buf();
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 tx = w.tx();
// TODO: Avoid clone
let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)];
let folder_hash = folder.hash();
let mut path: PathBuf = folder.path().into();
let tx_final = w.tx();
let path: PathBuf = folder.path().into();
let name = format!("parsing {:?}", folder.name());
let root_path = self.path.to_path_buf();
let map = self.hash_indexes.clone();
let map2 = self.hash_indexes.clone();
thread::Builder::new()
.name(name.clone())
.spawn(move || {
let closure = move || {
let name = name.clone();
let root_path = root_path.clone();
let map = map.clone();
let map2 = map.clone();
let tx = tx.clone();
let cache_dir = cache_dir.clone();
let path = path.clone();
let thunk = move || {
let mut path = path.clone();
let cache_dir = cache_dir.clone();
{
path.push("new");
@ -428,11 +437,12 @@ impl MaildirType {
}
path.pop();
}
path.push("cur");
let iter = path.read_dir()?;
let count = path.read_dir()?.count();
let mut files: Vec<PathBuf> = Vec::with_capacity(count);
let mut r = Vec::with_capacity(count);
let mut ret = Vec::with_capacity(count);
for e in iter {
let e = e.and_then(|x| {
let path = x.path();
@ -459,7 +469,7 @@ impl MaildirType {
let size = if len <= 100 { 100 } else { (len / 100) * 100 };
let mut local_r: Vec<
Envelope,
> = Vec::with_capacity(chunk.len());
> = Vec::with_capacity(chunk.len());
for c in chunk.chunks(size) {
//thread::yield_now();
let map = map.clone();
@ -474,26 +484,26 @@ impl MaildirType {
.to_path_buf();
if let Some(cached) =
cache_dir.find_cache_file(&file_name)
{
/* Cached struct exists, try to load it */
let reader = io::BufReader::new(
fs::File::open(&cached).unwrap(),
);
let result: result::Result<Envelope, _> = bincode::deserialize_from(reader);
if let Ok(env) = result {
let mut map = map.lock().unwrap();
let mut map = map.entry(folder_hash).or_default();;
let hash = env.hash();
map.insert(hash, (0, file.clone()));
local_r.push(env);
continue;
}
};
{
/* Cached struct exists, try to load it */
let reader = io::BufReader::new(
fs::File::open(&cached).unwrap(),
);
let result: result::Result<Envelope, _> = bincode::deserialize_from(reader);
if let Ok(env) = result {
let mut map = map.lock().unwrap();
let mut map = map.entry(folder_hash).or_default();;
let hash = env.hash();
map.insert(hash, file.clone());
local_r.push(env);
continue;
}
};
let hash = get_file_hash(file);
{
let mut map = map.lock().unwrap();
let mut map = map.entry(folder_hash).or_default();
(*map).insert(hash, (0, PathBuf::from(file)));
(*map).insert(hash, PathBuf::from(file));
}
let op =
Box::new(MaildirOp::new(hash, map.clone(), folder_hash));
@ -515,7 +525,7 @@ impl MaildirType {
}
local_r.push(e);
} else {
eprintln!("DEBUG: hash {}, path: {} couldn't be parsed in `add_path_to_index`", hash, file.as_path().display());
eprintln!("DEBUG: hash {}, path: {} couldn't be parsed in `add_path_to_index`", hash, file.as_path().display());
continue;
}
}
@ -529,20 +539,16 @@ impl MaildirType {
}
for t in threads {
let mut result = t.join();
r.append(&mut result);
ret.append(&mut result);
}
let mut map = map2.lock().unwrap();
let map = map.entry(folder_hash).or_default();
for (idx, e) in r.iter().enumerate() {
let mut y = (*map)[&e.hash()].clone();
y.0 = idx;
(*map).insert(e.hash(), y);
}
tx.send(AsyncStatus::Finished);
notify_fn.notify();
Ok(r)
})
.unwrap()
Ok(ret)
};
let result = thunk();
tx_final.send(AsyncStatus::Payload(result));
};
Box::new(closure)
};
w.build(handle)
}
@ -558,15 +564,22 @@ fn add_path_to_index(
let env: Envelope;
let hash = get_file_hash(path);
{
let mut hash_index = hash_index.lock().unwrap();
let index_lock = hash_index.entry(folder_hash).or_default();
if index_lock.contains_key(&hash) {
return None;
let mut map = hash_index.lock().unwrap();
let map = map.entry(folder_hash).or_default();;
map.insert(hash, path.to_path_buf());
eprintln!(
"inserted {} in {} map, len={}",
hash,
folder_hash,
map.len()
);
for e in map.iter() {
eprintln!("{:#?}", e);
}
index_lock.insert(hash, (0, path.to_path_buf()));
}
let op = Box::new(MaildirOp::new(hash, hash_index.clone(), folder_hash));
if let Some(e) = Envelope::from_token(op, hash) {
eprintln!("add_path_to_index gen {}\t{}", hash, file_name.display());
if let Ok(cached) = cache_dir.place_cache_file(file_name) {
/* place result in cache directory */
let f = match fs::File::create(cached) {

11
melib/src/mailbox/backends/maildir/mod.rs

@ -67,7 +67,14 @@ impl MaildirOp {
fn path(&self) -> PathBuf {
let map = self.hash_index.lock().unwrap();
let map = &map[&self.folder_hash];
map.get(&self.hash).unwrap().1.clone()
eprintln!("looking for {} in {} map", self.hash, self.folder_hash);
if !map.contains_key(&self.hash) {
eprintln!("doesn't contain it though len = {}\n{:#?}", map.len(), map);
for e in map.iter() {
eprintln!("{:#?}", e);
}
}
map.get(&self.hash).unwrap().clone()
}
}
@ -153,7 +160,7 @@ impl<'a> BackendOp for MaildirOp {
let hash_index = self.hash_index.clone();
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() = PathBuf::from(new_name);
Ok(())
}
}

5
melib/src/mailbox/backends/mod.rs

@ -32,7 +32,6 @@ use mailbox::email::{Envelope, EnvelopeHash, Flag};
use std::fmt;
use std::fmt::Debug;
use std::ops::Deref;
use std::sync::Arc;
extern crate fnv;
use self::fnv::FnvHashMap;
@ -84,6 +83,8 @@ impl Backends {
#[derive(Debug)]
pub enum RefreshEventKind {
Update(EnvelopeHash, Box<Envelope>),
/// Rename(old_hash, new_hash)
Rename(EnvelopeHash, EnvelopeHash),
Create(Box<Envelope>),
Remove(FolderHash),
Rescan,
@ -145,7 +146,7 @@ impl NotifyFn {
}
}
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) -> Async<Result<Vec<Envelope>>>;
fn watch(&self, sender: RefreshEventConsumer) -> Result<()>;
fn folders(&self) -> Vec<Folder>;
fn operation(&self, hash: EnvelopeHash, folder_hash: FolderHash) -> Box<BackendOp>;

89
melib/src/mailbox/collection.rs

@ -10,6 +10,7 @@ use std::result;
extern crate fnv;
use self::fnv::FnvHashMap;
use self::fnv::FnvHashSet;
/// `Mailbox` represents a folder of mail.
#[derive(Debug, Clone, Default)]
@ -53,42 +54,29 @@ impl Collection {
let cache_dir =
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash()))
.unwrap();
let threads = if let Some(cached) = cache_dir.find_cache_file("threads") {
/* Scrap caching for now. When a cached threads file is loaded, we must remove/rehash the
* thread nodes that shouldn't exist anymore (e.g. because their file moved from /new to
* /cur, or it was deleted).
*/
let threads = Threads::new(&mut envelopes);
/*if let Some(cached) = cache_dir.find_cache_file("threads") {
let reader = io::BufReader::new(fs::File::open(cached).unwrap());
let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
let ret = if let Ok(mut cached_t) = result {
cached_t.update(&mut envelopes);
use std::iter::FromIterator;
eprintln!("loaded cache, our hash set is {:?}\n and the cached one is {:?}", FnvHashSet::from_iter(envelopes.keys().cloned()), cached_t.hash_set);
cached_t.amend(&mut envelopes);
cached_t
} else {
Threads::new(&mut envelopes)
};
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, &ret).unwrap();
}
ret
} else {
let ret = Threads::new(&mut envelopes);
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, &ret).unwrap();
}
ret
Threads::new(&mut envelopes)
};
*/
Collection {
folder: folder.clone(),
envelopes,
@ -106,17 +94,60 @@ impl Collection {
self.envelopes.is_empty()
}
pub fn remove(&mut self, envelope_hash: EnvelopeHash) {
eprintln!("DEBUG: Removing {}", envelope_hash);
self.envelopes.remove(&envelope_hash);
self.threads.remove(envelope_hash, &mut self.envelopes);
}
pub fn rename(&mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash) {
let mut env = self.envelopes.remove(&old_hash).unwrap();
env.set_hash(new_hash);
self.envelopes.insert(new_hash, env);
{
if self.threads.update_envelope(old_hash, new_hash).is_ok() {
return;
}
}
/* envelope is not in threads, so insert it */
let env = self.envelopes.entry(new_hash).or_default() as *mut Envelope;
unsafe {
self.threads.insert(&mut (*env), &self.envelopes);
}
}
pub fn update_envelope(&mut self, old_hash: EnvelopeHash, envelope: Envelope) {
self.envelopes.remove(&old_hash);
let new_hash = envelope.hash();
self.envelopes.insert(new_hash, envelope);
{
if self.threads.update_envelope(old_hash, new_hash).is_ok() {
return;
}
}
/* envelope is not in threads, so insert it */
let env = self.envelopes.entry(new_hash).or_default() as *mut Envelope;
unsafe {
self.threads.insert(&mut (*env), &self.envelopes);
}
}
pub fn insert(&mut self, envelope: Envelope) {
let hash = envelope.hash();
eprintln!("DEBUG: Inserting hash {} in {}", hash, self.folder.name());
self.envelopes.insert(hash, envelope);
let env = self.envelopes.entry(hash).or_default() as *mut Envelope;
unsafe {
self.threads.insert(&mut (*env), &self.envelopes);
}
}
pub(crate) fn insert_reply(&mut self, envelope: Envelope) {
self.insert(envelope);
//self.threads.insert_reply(envelope, &mut self.envelopes);
pub(crate) fn insert_reply(&mut self, _envelope: &Envelope) {
return;
/*
//self.insert(envelope);
eprintln!("insert_reply in collections");
self.threads.insert_reply(envelope, &mut self.envelopes);
*/
}
}

2
melib/src/mailbox/email/attachments.rs

@ -160,6 +160,7 @@ impl AttachmentBuilder {
};
self
}
/*
fn decode(&self) -> Vec<u8> {
// TODO merge this and standalone decode() function
let charset = match self.content_type {
@ -186,6 +187,7 @@ impl AttachmentBuilder {
self.raw.to_vec()
}
}
*/
pub fn build(self) -> Attachment {
Attachment {
content_type: self.content_type,

30
melib/src/mailbox/email/mod.rs

@ -336,6 +336,11 @@ impl Envelope {
flags: Flag::default(),
}
}
pub fn set_hash(&mut self, new_hash: EnvelopeHash) {
self.hash = new_hash;
}
pub fn from_bytes(bytes: &[u8]) -> Result<Envelope> {
let mut h = DefaultHasher::new();
h.write(bytes);
@ -569,22 +574,21 @@ impl Envelope {
_ => Cow::from(String::new()),
}
}
pub fn in_reply_to_bytes<'a>(&'a self) -> &'a [u8] {
match self.in_reply_to {
Some(ref s) => s.raw(),
_ => &[],
}
pub fn in_reply_to(&self) -> Option<&MessageID> {
self.in_reply_to.as_ref()
}
pub fn in_reply_to(&self) -> Cow<str> {
match self.in_reply_to {
Some(ref s) => String::from_utf8_lossy(s.val()),
_ => Cow::from(String::new()),
pub fn in_reply_to_display(&self) -> Option<Cow<str>> {
if let Some(ref m) = self.in_reply_to {
Some(String::from_utf8_lossy(m.val()))
} else {
None
}
}
pub fn in_reply_to_raw(&self) -> Cow<str> {
match self.in_reply_to {
Some(ref s) => String::from_utf8_lossy(s.raw()),
_ => Cow::from(String::new()),
pub fn in_reply_to_raw(&self) -> Option<Cow<str>> {
if let Some(ref m) = self.in_reply_to {
Some(String::from_utf8_lossy(m.raw()))
} else {
None
}
}
pub fn message_id(&self) -> &MessageID {

22
melib/src/mailbox/mod.rs

@ -57,7 +57,7 @@ impl Mailbox {
Ok(Mailbox {
folder,
collection,
name: name,
name,
..Default::default()
})
}
@ -96,18 +96,21 @@ impl Mailbox {
&self.collection.threads.thread_nodes()[i]
}
pub fn insert_sent_folder(&mut self, sent: &Mailbox) {
if !self.has_sent {
for envelope in sent.collection.envelopes.values().cloned() {
pub fn insert_sent_folder(&mut self, _sent: &Mailbox) {
/*if !self.has_sent {
for envelope in sent.collection.envelopes.values() {
self.insert_reply(envelope);
}
self.has_sent = true;
}
}*/
}
pub fn rename(&mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash) {
self.collection.rename(old_hash, new_hash);
}
pub fn update(&mut self, old_hash: EnvelopeHash, envelope: Envelope) {
self.collection.remove(&old_hash);
self.collection.insert(envelope);
self.collection.update_envelope(old_hash, envelope);
}
pub fn insert(&mut self, envelope: Envelope) -> &Envelope {
@ -116,12 +119,13 @@ impl Mailbox {
&self.collection[&hash]
}
fn insert_reply(&mut self, envelope: Envelope) {
pub fn insert_reply(&mut self, envelope: &Envelope) {
eprintln!("mailbox insert reply {}", self.name);
self.collection.insert_reply(envelope);
}
pub fn remove(&mut self, envelope_hash: EnvelopeHash) {
self.collection.remove(&envelope_hash);
self.collection.remove(envelope_hash);
// eprintln!("envelope_hash: {}\ncollection:\n{:?}", envelope_hash, self.collection);
}
}

365
melib/src/mailbox/thread.rs

@ -47,6 +47,8 @@ use std::ops::Index;
use std::result::Result as StdResult;
use std::str::FromStr;
type Envelopes = FnvHashMap<EnvelopeHash, Envelope>;
/* Helper macros to avoid repeating ourselves */
fn rec_change_root_parent(b: &mut Vec<ThreadNode>, idx: usize, new_root: usize) {
@ -221,7 +223,7 @@ impl ThreadTree {
}
}
/* `ThreadIterator` returns messages according to the sorted order. For example, for the following
/* `ThreadsIterator` returns messages according to the sorted order. For example, for the following
* threads:
*
* ```
@ -236,6 +238,53 @@ impl ThreadTree {
* the iterator returns them as `A, B, C, D, E, F`
*/
pub struct ThreadsIterator<'a> {
pos: usize,
stack: Vec<usize>,
tree: Ref<'a, Vec<ThreadTree>>,
}
impl<'a> Iterator for ThreadsIterator<'a> {
type Item = (usize, usize);
fn next(&mut self) -> Option<(usize, usize)> {
{
let mut tree = &(*self.tree);
for i in &self.stack {
tree = &tree[*i].children;
}
if self.pos == tree.len() {
if let Some(p) = self.stack.pop() {
self.pos = p + 1;
} else {
return None;
}
} else {
debug_assert!(self.pos < tree.len());
let ret = (self.stack.len(), tree[self.pos].id);
if !tree[self.pos].children.is_empty() {
self.stack.push(self.pos);
self.pos = 0;
return Some(ret);
}
self.pos += 1;
return Some(ret);
}
}
self.next()
}
}
/* `ThreadIterator` returns messages of a specific thread according to the sorted order. For example, for the following
* thread:
*
* ```
* A_
* |_ B
* |_C
* |_D
* ```
*
* the iterator returns them as `A, B, C, D`
*/
pub struct ThreadIterator<'a> {
init_pos: usize,
pos: usize,
@ -355,7 +404,7 @@ pub struct Threads {
tree: RefCell<Vec<ThreadTree>>,
message_ids: FnvHashMap<Vec<u8>, usize>,
hash_set: FnvHashSet<EnvelopeHash>,
pub hash_set: FnvHashSet<EnvelopeHash>,
sort: RefCell<(SortField, SortOrder)>,
subsort: RefCell<(SortField, SortOrder)>,
}
@ -504,8 +553,7 @@ impl Threads {
}
}
// FIXME: Split this function
pub fn new(collection: &mut FnvHashMap<EnvelopeHash, Envelope>) -> Threads {
pub fn new(collection: &mut Envelopes) -> Threads {
/* To reconstruct thread information from the mails we need: */
/* a vector to hold thread members */
@ -529,20 +577,46 @@ impl Threads {
* References / In-Reply-To headers */
t.link_threads(collection);
t.create_root_set(collection);
t.build_collection(collection);
for (i, _t) in t.thread_nodes.iter().enumerate() {
eprintln!("Thread #{}, children {}", i, _t.children.len());
if !_t.children.is_empty() {
eprintln!("{:?}", _t.children);
}
if let Some(m) = _t.message {
eprintln!("\tmessage: {}", collection[&m].subject());
} else {
eprintln!("\tNo message");
}
}
eprintln!("\n");
for (i, _t) in t.tree.borrow().iter().enumerate() {
eprintln!("Tree #{} id {}, children {}", i, _t.id, _t.children.len());
if let Some(m) = t.thread_nodes[_t.id].message {
eprintln!("\tmessage: {}", collection[&m].subject());
} else {
eprintln!("\tNo message");
}
}
t
}
fn create_root_set(&mut self, collection: &Envelopes) {
/* Walk over the elements of message_ids, and gather a list of the ThreadNode objects that
* have no parents. These are the root messages of each thread */
let mut root_set: Vec<usize> = Vec::with_capacity(collection.len());
/* Find the root set */
'root_set: for v in t.message_ids.values() {
if t.thread_nodes[*v].parent.is_none() {
'root_set: for v in self.message_ids.values() {
if self.thread_nodes[*v].parent.is_none() {
root_set.push(*v);
}
}
let mut roots_to_remove: Vec<usize> = Vec::with_capacity(root_set.len());
/* Prune empty thread nodes */
t.prune_empty_nodes(&mut root_set);
self.prune_empty_nodes(&mut root_set);
/* "Group root set by subject."
*
@ -553,19 +627,19 @@ impl Threads {
let mut subject_table: FnvHashMap<Vec<u8>, (bool, usize)> =
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
for r in &root_set {
for (i, &r) in root_set.iter().enumerate() {
/* "Find the subject of that sub-tree": */
let (mut subject, mut is_re): (_, bool) = if t.thread_nodes[*r].message.is_some() {
let (mut subject, mut is_re): (_, bool) = if self.thread_nodes[r].message.is_some() {
/* "If there is a message in the Container, the subject is the subject of that
* message. " */
let msg_idx = t.thread_nodes[*r].message.unwrap();
let msg_idx = self.thread_nodes[r].message.unwrap();
let envelope = &collection[&msg_idx];
(envelope.subject(), !envelope.references().is_empty())
} else {
/* "If there is no message in the Container, then the Container will have at least
* one child Container, and that Container will have a message. Use the subject of
* that message instead." */
let msg_idx = t.thread_nodes[t.thread_nodes[*r].children[0]]
let mut msg_idx = self.thread_nodes[self.thread_nodes[r].children[0]]
.message
.unwrap();
let envelope = &collection[&msg_idx];
@ -591,17 +665,17 @@ impl Threads {
* "The container in the table has a ``Re:'' version of this subject, and this
* container has a non-``Re:'' version of this subject. The non-re version is the
* more interesting of the two." */
if (!t.thread_nodes[id].has_message() && t.thread_nodes[*r].has_message())
if (!self.thread_nodes[id].has_message() && self.thread_nodes[r].has_message())
|| (other_is_re && !is_re)
{
mem::replace(
subject_table.entry(stripped_subj.to_vec()).or_default(),
(is_re, *r),
(is_re, r),
);
}
} else {
/* "There is no container in the table with this subject" */
subject_table.insert(stripped_subj.to_vec(), (is_re, *r));
subject_table.insert(stripped_subj.to_vec(), (is_re, r));
}
}
@ -609,13 +683,14 @@ impl Threads {
* root set. Now iterate over the root set, and gather together the difference." */
for i in 0..root_set.len() {
let r = root_set[i];
/* "Find the subject of this Container (as above.)" */
let (mut subject, mut is_re): (_, bool) = if t.thread_nodes[r].message.is_some() {
let msg_idx = t.thread_nodes[r].message.unwrap();
let (mut subject, mut is_re): (_, bool) = if self.thread_nodes[r].message.is_some() {
let msg_idx = self.thread_nodes[r].message.unwrap();
let envelope = &collection[&msg_idx];
(envelope.subject(), !envelope.references().is_empty())
} else {
let msg_idx = t.thread_nodes[t.thread_nodes[r].children[0]]
let msg_idx = self.thread_nodes[self.thread_nodes[r].children[0]]
.message
.unwrap();
let envelope = &collection[&msg_idx];
@ -630,7 +705,7 @@ impl Threads {
let (other_is_re, other_idx) = subject_table[subject];
/* "If it is null, or if it is this container, continue." */
if !t.thread_nodes[other_idx].has_message() || other_idx == r {
if !self.thread_nodes[other_idx].has_message() || other_idx == r {
continue;
}
@ -641,10 +716,10 @@ impl Threads {
* "If both are dummies, append one's children to the other, and remove the now-empty
* container."
*/
if !t.thread_nodes[r].has_message() && !t.thread_nodes[other_idx].has_message() {
let children = t.thread_nodes[r].children.clone();
if !self.thread_nodes[r].has_message() && !self.thread_nodes[other_idx].has_message() {
let children = self.thread_nodes[r].children.clone();
for c in children {
make!((other_idx) parent of (c), &mut t.thread_nodes);
make!((other_idx) parent of (c), &mut self.thread_nodes);
}
roots_to_remove.push(i);
@ -652,14 +727,18 @@ impl Threads {
* of the empty, and a sibling of the other ``real'' messages with the same subject
* (the empty's children.)"
*/
} else if t.thread_nodes[r].has_message() && !t.thread_nodes[other_idx].has_message() {
make!((other_idx) parent of (r), &mut t.thread_nodes);
} else if self.thread_nodes[r].has_message()
&& !self.thread_nodes[other_idx].has_message()
{
make!((other_idx) parent of (r), &mut self.thread_nodes);
if !root_set.contains(&other_idx) {
root_set.push(other_idx);
}
roots_to_remove.push(i);
} else if !t.thread_nodes[r].has_message() && t.thread_nodes[other_idx].has_message() {
make!((r) parent of (other_idx), &mut t.thread_nodes);
} else if !self.thread_nodes[r].has_message()
&& self.thread_nodes[other_idx].has_message()
{
make!((r) parent of (other_idx), &mut self.thread_nodes);
if let Some(pos) = root_set.iter().position(|&i| i == other_idx) {
roots_to_remove.push(pos);
}
@ -667,8 +746,8 @@ impl Threads {
* "If that container is a non-empty, and that message's subject does not begin with ``Re:'', but this
* message's subject does, then make this be a child of the other."
*/
} else if t.thread_nodes[other_idx].has_message() && !other_is_re && is_re {
make!((other_idx) parent of (r), &mut t.thread_nodes);
} else if self.thread_nodes[other_idx].has_message() && !other_is_re && is_re {
make!((other_idx) parent of (r), &mut self.thread_nodes);
roots_to_remove.push(i);
/* "If that container is a non-empty, and that message's subject begins with ``Re:'', but this
@ -677,8 +756,8 @@ impl Threads {
* without will be in the hash table, regardless of the order in which they were
* seen.)"
*/
} else if t.thread_nodes[other_idx].has_message() && other_is_re && !is_re {
make!((r) parent of (other_idx), &mut t.thread_nodes);
} else if self.thread_nodes[other_idx].has_message() && other_is_re && !is_re {
make!((r) parent of (other_idx), &mut self.thread_nodes);
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
roots_to_remove.push(pos);
}
@ -688,11 +767,11 @@ impl Threads {
* hierarchical relationship which might not be true."
*/
} else {
t.thread_nodes.push(Default::default());
let new_id = t.thread_nodes.len() - 1;
t.thread_nodes[new_id].thread_group = new_id;
make!((new_id) parent of (r), &mut t.thread_nodes);
make!((new_id) parent of (other_idx), &mut t.thread_nodes);
self.thread_nodes.push(Default::default());
let new_id = self.thread_nodes.len() - 1;
self.thread_nodes[new_id].thread_group = new_id;
make!((new_id) parent of (r), &mut self.thread_nodes);
make!((new_id) parent of (other_idx), &mut self.thread_nodes);
root_set[i] = new_id;
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
roots_to_remove.push(pos);
@ -705,41 +784,118 @@ impl Threads {
root_set.remove(r);
}
t.root_set = RefCell::new(root_set);
t.build_collection(&collection);
t
self.root_set = RefCell::new(root_set);
}
pub fn threads_iter(&self) -> ThreadsIterator {
ThreadsIterator {
pos: 0,
stack: Vec::with_capacity(4),
tree: self.tree.borrow(),
}
}
pub fn thread_iter(&self, index: usize) -> ThreadIterator {
ThreadIterator {
init_pos: index,
pos: index,
stack: Vec::new(),
stack: Vec::with_capacity(4),
tree: self.tree.borrow(),
}
}
pub fn update_envelope(&mut self, old_hash: EnvelopeHash, envelope: &Envelope) {
pub fn update_envelope(
&mut self,
old_hash: EnvelopeHash,
new_hash: EnvelopeHash,
) -> Result<(), ()> {
/* must update:
* - hash_set
* - message fields in thread_nodes
*/
self.hash_set.remove(&old_hash);
self.hash_set.insert(envelope.hash());
let node = self
if let Some(node) = self
.thread_nodes
.iter_mut()
.find(|n| n.message.map(|n| n == old_hash).unwrap_or(false))
.unwrap();
node.message = Some(envelope.hash());
{
node.message = Some(new_hash);
} else {
return Err(());
}
self.hash_set.insert(new_hash);
Ok(())
}
pub fn update(&mut self, collection: &mut FnvHashMap<EnvelopeHash, Envelope>) {
#[inline]
pub fn remove(&mut self, envelope_hash: EnvelopeHash, collection: &mut Envelopes) {
self.hash_set.remove(&envelope_hash);
//{
// let pos = self
// .thread_nodes
// .iter()
// .position(|n| n.message.map(|n| n == envelope_hash).unwrap_or(false))
// .unwrap();
// eprintln!("DEBUG: {} in threads is idx= {}", envelope_hash, pos);
//}
let t_id: usize;
{
if let Some(pos) = self
.thread_nodes
.iter()
.position(|n| n.message.map(|n| n == envelope_hash).unwrap_or(false))
{
t_id = pos;