diff --git a/melib/src/async.rs b/melib/src/async.rs index 0235d93f1..41c2c613f 100644 --- a/melib/src/async.rs +++ b/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 ()>>); + +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` owner and its worker thread. -#[derive(Debug)] -pub enum AsyncStatus { +#[derive(Clone)] +pub enum AsyncStatus { NoUpdate, + Payload(T), Finished, ///The number may hold whatever meaning the user chooses. ProgressReport(usize), } -/// A builder object for `Async` -#[derive(Debug)] -pub struct AsyncBuilder { - tx: chan::Sender, - rx: chan::Receiver, -} - -#[derive(Debug)] -pub struct Async { - value: Option, - worker: Option>, - tx: chan::Sender, - rx: chan::Receiver, -} - -impl Default for AsyncBuilder { - fn default() -> Self { - AsyncBuilder::new() +impl fmt::Debug for AsyncStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AsyncStatus::NoUpdate => write!(f, "AsyncStatus::NoUpdate"), + AsyncStatus::Payload(_) => write!(f, "AsyncStatus::Payload(_)"), + AsyncStatus::Finished => write!(f, "AsyncStatus::Finished"), + AsyncStatus::ProgressReport(u) => write!(f, "AsyncStatus::ProgressReport({})", u), + } } } -impl AsyncBuilder { +/// A builder object for `Async` +#[derive(Debug, Clone)] +pub struct AsyncBuilder { + tx: chan::Sender>, + rx: chan::Receiver>, +} + +#[derive(Debug, Clone)] +pub struct Async { + value: Option, + work: Work, + active: bool, + tx: chan::Sender>, + rx: chan::Receiver>, +} + +impl Default for AsyncBuilder { + fn default() -> Self { + AsyncBuilder::::new() + } +} + +impl AsyncBuilder { pub fn new() -> Self { - let (sender, receiver) = chan::sync(::std::mem::size_of::()); + let (sender, receiver) = chan::sync(8 * ::std::mem::size_of::>()); AsyncBuilder { tx: sender, rx: receiver, } } /// Returns the sender object of the promise's channel. - pub fn tx(&mut self) -> chan::Sender { + pub fn tx(&mut self) -> chan::Sender> { self.tx.clone() } /// Returns the receiver object of the promise's channel. - pub fn rx(&mut self) -> chan::Receiver { + pub fn rx(&mut self) -> chan::Receiver> { self.rx.clone() } /// Returns an `Async` object that contains a `Thread` join handle that returns a `T` - pub fn build(self, worker: thread::JoinHandle) -> Async { + pub fn build(self, work: Box ()>) -> Async { Async { - worker: Some(worker), + work: Work(Arc::new(work)), value: None, tx: self.tx, rx: self.rx, + active: false, } } } @@ -96,20 +129,34 @@ impl Async { pub fn extract(self) -> T { self.value.unwrap() } + pub fn work(&mut self) -> Option { + 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> { + self.tx.clone() + } /// Polls worker thread and returns result. - pub fn poll(&mut self) -> Result { + pub fn poll(&mut self) -> Result, ()> { 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 Async { 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); } } diff --git a/melib/src/mailbox/backends/maildir/backend.rs b/melib/src/mailbox/backends/maildir/backend.rs index 3b57c6974..8934a2fdf 100644 --- a/melib/src/mailbox/backends/maildir/backend.rs +++ b/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, + index: FnvHashMap, hash: FolderHash, } impl Deref for HashIndex { - type Target = FnvHashMap; - fn deref(&self) -> &FnvHashMap { + type Target = FnvHashMap; + fn deref(&self) -> &FnvHashMap { &self.index } } impl DerefMut for HashIndex { - fn deref_mut(&mut self) -> &mut FnvHashMap { + fn deref_mut(&mut self) -> &mut FnvHashMap { &mut self.index } } @@ -82,7 +82,6 @@ pub struct MaildirType { folders: Vec, //folder_index: FnvHashMap, 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 { self.folders.iter().map(|f| f.clone()).collect() } - fn get(&mut self, folder: &Folder, notify_fn: Arc) -> Async>> { - self.multicore(4, folder, notify_fn) + fn get(&mut self, folder: &Folder) -> Async>> { + 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, - ) -> Async>> { + pub fn multicore(&mut self, cores: usize, folder: &Folder) -> Async>> { 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 = 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 = 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 = 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) { diff --git a/melib/src/mailbox/backends/maildir/mod.rs b/melib/src/mailbox/backends/maildir/mod.rs index 723d3dbc5..f240b4d8a 100644 --- a/melib/src/mailbox/backends/maildir/mod.rs +++ b/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(()) } } diff --git a/melib/src/mailbox/backends/mod.rs b/melib/src/mailbox/backends/mod.rs index d254af723..66f6fb522 100644 --- a/melib/src/mailbox/backends/mod.rs +++ b/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), + /// Rename(old_hash, new_hash) + Rename(EnvelopeHash, EnvelopeHash), Create(Box), Remove(FolderHash), Rescan, @@ -145,7 +146,7 @@ impl NotifyFn { } } pub trait MailBackend: ::std::fmt::Debug { - fn get(&mut self, folder: &Folder, notify_fn: Arc) -> Async>>; + fn get(&mut self, folder: &Folder) -> Async>>; fn watch(&self, sender: RefreshEventConsumer) -> Result<()>; fn folders(&self) -> Vec; fn operation(&self, hash: EnvelopeHash, folder_hash: FolderHash) -> Box; diff --git a/melib/src/mailbox/collection.rs b/melib/src/mailbox/collection.rs index 4174867f5..a78278804 100644 --- a/melib/src/mailbox/collection.rs +++ b/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 = 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); + */ } } diff --git a/melib/src/mailbox/email/attachments.rs b/melib/src/mailbox/email/attachments.rs index 61523b9e6..b49f60c4c 100644 --- a/melib/src/mailbox/email/attachments.rs +++ b/melib/src/mailbox/email/attachments.rs @@ -160,6 +160,7 @@ impl AttachmentBuilder { }; self } + /* fn decode(&self) -> Vec { // 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, diff --git a/melib/src/mailbox/email/mod.rs b/melib/src/mailbox/email/mod.rs index 0acefffec..b0de68114 100644 --- a/melib/src/mailbox/email/mod.rs +++ b/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 { 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_display(&self) -> Option> { + if let Some(ref m) = self.in_reply_to { + Some(String::from_utf8_lossy(m.val())) + } else { + None } } - pub fn in_reply_to(&self) -> Cow { - match self.in_reply_to { - Some(ref s) => String::from_utf8_lossy(s.val()), - _ => Cow::from(String::new()), - } - } - pub fn in_reply_to_raw(&self) -> Cow { - 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> { + if let Some(ref m) = self.in_reply_to { + Some(String::from_utf8_lossy(m.raw())) + } else { + None } } pub fn message_id(&self) -> &MessageID { diff --git a/melib/src/mailbox/mod.rs b/melib/src/mailbox/mod.rs index 2c26ff2f2..29c423acf 100644 --- a/melib/src/mailbox/mod.rs +++ b/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); } } diff --git a/melib/src/mailbox/thread.rs b/melib/src/mailbox/thread.rs index 13791d9a8..682bdb986 100644 --- a/melib/src/mailbox/thread.rs +++ b/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; + /* Helper macros to avoid repeating ourselves */ fn rec_change_root_parent(b: &mut Vec, 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, + tree: Ref<'a, Vec>, +} +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>, message_ids: FnvHashMap, usize>, - hash_set: FnvHashSet, + pub hash_set: FnvHashSet, sort: RefCell<(SortField, SortOrder)>, subsort: RefCell<(SortField, SortOrder)>, } @@ -504,8 +553,7 @@ impl Threads { } } - // FIXME: Split this function - pub fn new(collection: &mut FnvHashMap) -> 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 = 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 = 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, (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) { + #[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; + self.thread_nodes[pos].message = None; + } else { + /* else it was deleted during a thread_rebuild or others */ + return; + } + } + + let mut node_idx = t_id; + + /* Trace path back to root ThreadNode */ + while let Some(p) = &self.thread_nodes[node_idx].parent { + node_idx = *p; + } + { + let tree = self.tree.get_mut(); + if let Some(pos) = tree.iter().position(|t| t.id == node_idx) { + tree[pos].children.clear(); + if node_idx == t_id { + tree.remove(pos); + } else { + node_build( + &mut tree[pos], + node_idx, + &mut self.thread_nodes, + 1, + collection, + ); + } + } + } + + let mut root_set: Vec = self.tree.borrow().iter().map(|t| t.id).collect(); + self.prune_empty_nodes(&mut root_set); + self.tree.borrow_mut().retain(|t| root_set.contains(&t.id)); + } + + pub fn amend(&mut self, collection: &mut Envelopes) { let new_hash_set = FnvHashSet::from_iter(collection.keys().cloned()); + let difference: Vec = + self.hash_set.difference(&new_hash_set).cloned().collect(); + for h in difference { + self.remove(h, collection); + } + let difference: Vec = new_hash_set.difference(&self.hash_set).cloned().collect(); for h in difference { + eprintln!("inserting {}", collection[&h].subject()); let env = collection.entry(h).or_default() as *mut Envelope; unsafe { // `collection` is borrowed immutably and `insert` only changes the envelope's @@ -747,25 +903,15 @@ impl Threads { self.insert(&mut (*env), collection); } } + self.create_root_set(collection); - let difference: Vec = - 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; - } + let mut root_set: Vec = self.tree.borrow().iter().map(|t| t.id).collect(); + self.prune_empty_nodes(&mut root_set); + let tree = self.tree.get_mut(); + tree.retain(|t| root_set.contains(&t.id)); } - pub fn insert( - &mut self, - envelope: &mut Envelope, - collection: &FnvHashMap, - ) { + pub fn insert(&mut self, envelope: &mut Envelope, collection: &Envelopes) { self.link_envelope(envelope); { let id = self.message_ids[envelope.message_id().raw()]; @@ -773,30 +919,29 @@ impl Threads { } } - pub fn insert_reply( - &mut self, - envelope: Envelope, - collection: &mut FnvHashMap, - ) -> bool { + pub fn insert_reply(&mut self, envelope: &Envelope, collection: &mut Envelopes) -> bool { //return false; { - let in_reply_to = envelope.in_reply_to_bytes(); - if !self.message_ids.contains_key(in_reply_to) { + if let Some(in_reply_to) = envelope.in_reply_to() { + if !self.message_ids.contains_key(in_reply_to.raw()) { + return false; + } + } else { return false; } } let hash: EnvelopeHash = envelope.hash(); - collection.insert(hash, envelope); + collection.insert(hash, envelope.clone()); { - let envelope: &mut Envelope = collection.entry(hash).or_default(); - - /* FIXME: This does not update show_subject and len which is done in node_build upon - * creation */ - self.link_envelope(envelope); + let envelope = collection.entry(hash).or_default() as *mut Envelope; + unsafe { + /* Safe because insert only changes envelope's fields and nothing more */ + self.insert(&mut (*envelope), &collection); + } } let envelope: &Envelope = &collection[&hash]; { - let in_reply_to = envelope.in_reply_to_bytes(); + let in_reply_to = envelope.in_reply_to().unwrap().raw(); let parent_id = self.message_ids[in_reply_to]; self.rebuild_thread(parent_id, collection); } @@ -804,7 +949,7 @@ impl Threads { } /* Update thread tree information on envelope insertion */ - fn rebuild_thread(&mut self, id: usize, collection: &FnvHashMap) { + fn rebuild_thread(&mut self, id: usize, collection: &Envelopes) { let mut node_idx = id; let mut stack = Vec::with_capacity(32); @@ -846,9 +991,10 @@ impl Threads { /* * Finalize instance by building the thread tree, set show subject and thread lengths etc. */ - fn build_collection(&mut self, collection: &FnvHashMap) { + fn build_collection(&mut self, collection: &Envelopes) { { let tree = self.tree.get_mut(); + tree.clear(); for i in self.root_set.borrow().iter() { let mut tree_node = ThreadTree::new(*i); node_build( @@ -865,11 +1011,7 @@ impl Threads { self.inner_subsort_by(*self.subsort.borrow(), collection); } - fn inner_subsort_by( - &self, - subsort: (SortField, SortOrder), - collection: &FnvHashMap, - ) { + fn inner_subsort_by(&self, subsort: (SortField, SortOrder), collection: &Envelopes) { let tree = &mut self.tree.borrow_mut(); for mut t in tree.iter_mut() { t.children.sort_by(|a, b| match subsort { @@ -909,11 +1051,7 @@ impl Threads { } } - fn inner_sort_by( - &self, - sort: (SortField, SortOrder), - collection: &FnvHashMap, - ) { + fn inner_sort_by(&self, sort: (SortField, SortOrder), collection: &Envelopes) { let tree = &mut self.tree.borrow_mut(); tree.sort_by(|a, b| match sort { (SortField::Date, SortOrder::Desc) => { @@ -955,7 +1093,7 @@ impl Threads { &self, sort: (SortField, SortOrder), subsort: (SortField, SortOrder), - collection: &FnvHashMap, + collection: &Envelopes, ) { if *self.sort.borrow() != sort { self.inner_sort_by(sort, collection); @@ -968,7 +1106,7 @@ impl Threads { } pub fn thread_to_mail(&self, i: usize) -> EnvelopeHash { - let thread = &self.thread_nodes[self.root_set.borrow()[i]]; + let thread = &self.thread_nodes[i]; thread.message().unwrap() } @@ -976,6 +1114,10 @@ impl Threads { &self.thread_nodes } + pub fn len(&self) -> usize { + self.thread_nodes.len() + } + pub fn root_len(&self) -> usize { self.tree.borrow().len() } @@ -1089,7 +1231,7 @@ impl Threads { } } - fn link_threads(&mut self, collection: &mut FnvHashMap) { + fn link_threads(&mut self, collection: &mut Envelopes) { for v in collection.values_mut() { self.link_envelope(v); } @@ -1101,7 +1243,7 @@ impl Index for Threads { fn index(&self, index: usize) -> &ThreadNode { self.thread_nodes - .get(self.tree.borrow()[index].id) + .get(index) .expect("thread index out of bounds") } } @@ -1111,23 +1253,31 @@ fn node_build( idx: usize, thread_nodes: &mut Vec, indentation: usize, - collection: &FnvHashMap, + collection: &Envelopes, ) { if let Some(hash) = thread_nodes[idx].message { - if let Some(parent_id) = thread_nodes[idx].parent { + if !collection.contains_key(&hash) { + /* invalidate node */ + // thread_nodes[idx].message = None; + } else if let Some(parent_id) = thread_nodes[idx].parent { 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 = subject.to_mut().as_bytes(); - let subject = subject.strip_prefixes(); - let mut parent_subject = collection[&parent_hash].subject(); - let mut parent_subject = parent_subject.to_mut().as_bytes(); - let parent_subject = parent_subject.strip_prefixes(); - if subject == parent_subject { - thread_nodes[idx].show_subject = false; + if !collection.contains_key(&parent_hash) { + /* invalidate node */ + // thread_nodes[parent_id].message = None; + } else { + /* 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 = subject.to_mut().as_bytes(); + let subject = subject.strip_prefixes(); + let mut parent_subject = collection[&parent_hash].subject(); + let mut parent_subject = parent_subject.to_mut().as_bytes(); + let parent_subject = parent_subject.strip_prefixes(); + if subject == parent_subject { + thread_nodes[idx].show_subject = false; + } } } } @@ -1146,7 +1296,12 @@ fn node_build( let mut child_vec: Vec = Vec::new(); thread_nodes[idx].len = thread_nodes[idx].children.len(); - for c in thread_nodes[idx].children.clone() { + + /* No child/parent relationship is mutated at any point and no nodes are added or removed. Only + * each node's fields change, so the following is safe. + */ + let children = &thread_nodes[idx].children as *const Vec; + for &c in unsafe { &(*children) } { let mut new_tree = ThreadTree::new(c); node_build(&mut new_tree, c, thread_nodes, indentation, collection); thread_nodes[idx].len += thread_nodes[c].len; diff --git a/src/bin.rs b/src/bin.rs index 7894b6461..5b314f944 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -60,6 +60,8 @@ fn main() { let receiver = state.receiver(); + let worker_receiver = state.worker_receiver(); + /* Register some reasonably useful interfaces */ let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts))); let listing = listing::Listing::default(); @@ -84,6 +86,7 @@ fn main() { for e in events { state.rcv_event(e); } + state.redraw(); /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */ chan_select! { @@ -151,11 +154,12 @@ fn main() { for idx_a in 0..state.context.accounts.len() { let len = state.context.accounts[idx_a].len(); for idx_m in 0..len { + match state.context.account_status(idx_a, idx_m) { - Ok(true) => { + Ok(_) => { render_flag = true; }, - Ok(false) | Err(_) => {} + Err(_) => {} } } } @@ -181,6 +185,10 @@ fn main() { } } }, + worker_receiver.recv() -> _ => { + /* Some worker thread finished their job, acknowledge + * it and move on*/ + }, } } // end of 'inner diff --git a/ui/src/components/indexer/index.rs b/ui/src/components/indexer/index.rs new file mode 100644 index 000000000..3faed023c --- /dev/null +++ b/ui/src/components/indexer/index.rs @@ -0,0 +1,187 @@ +use super::*; +use components::utilities::PageMovement; + +pub trait IndexContent: Component { + /* Handles the drawing of one entry */ + fn make_entry(&mut self, idx: usize) -> (); + + /* Handles what happens when the user selects an entry in the index listing */ + fn enter_entry(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) -> (); + + /* Refreshes content */ + fn refresh(&mut self, context: &mut Context) -> (); + + fn search(&self, term: &str) -> Option; +} + +#[derive(Debug, PartialEq)] +enum IndexState { + Uninitialized, + Listing, + Unfocused, + Search, +} + +#[derive(Debug)] +pub struct Index { + cursor_pos: usize, + new_cursor_pos: usize, + length: usize, + + /// Cache current view. + canvas: CellBuffer, + /// If we must redraw on next redraw event + dirty: bool, + state: IndexState, + + content: Box, +} + +impl Index { + fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize) { + let fg_color = Color::Default; + let bg_color = if self.cursor_pos == idx { + Color::Byte(246) + /* } else if idx % 2 == 0 { + Color::Byte(236)*/ + } else { + Color::Default + }; + change_colors(grid, area, fg_color, bg_color); + } +} + +impl Component for Index { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !self.dirty { + return; + } + + match self.state { + IndexState::Uninitialized => { + self.content.refresh(context); + + /* copy area */ + self.state = IndexState::Listing; + self.draw(grid, area, context); + return; + } + IndexState::Listing => { + /* rehighlight entries, redraw pages */ + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + let rows = get_y(bottom_right) - get_y(upper_left) + 1; + let prev_page_no = (self.cursor_pos).wrapping_div(rows); + let page_no = (self.new_cursor_pos).wrapping_div(rows); + + let top_idx = page_no * rows; + if self.new_cursor_pos >= self.length { + self.new_cursor_pos = self.length - 1; + } + /* If cursor position has changed, remove the highlight from the previous position and + * apply it in the new one. */ + if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no { + let old_cursor_pos = self.cursor_pos; + self.cursor_pos = self.new_cursor_pos; + for idx in &[old_cursor_pos, self.new_cursor_pos] { + if *idx >= self.length { + continue; //bounds check + } + let new_area = ( + set_y(upper_left, get_y(upper_left) + (*idx % rows)), + set_y(bottom_right, get_y(upper_left) + (*idx % rows)), + ); + self.highlight_line(grid, new_area, *idx); + context.dirty_areas.push_back(new_area); + } + return; + } else if self.cursor_pos != self.new_cursor_pos { + self.cursor_pos = self.new_cursor_pos; + } + + /* Page_no has changed, so draw new page */ + copy_area( + grid, + &self.canvas, + area, + ((0, top_idx), (500 - 1, self.length)), + ); + self.highlight_line( + grid, + ( + ( + get_x(upper_left), + get_y(upper_left) + (self.cursor_pos % rows), + ), + ( + get_x(bottom_right), + get_y(upper_left) + (self.cursor_pos % rows), + ), + ), + self.cursor_pos, + ); + context.dirty_areas.push_back(area); + } + IndexState::Unfocused => { + self.content.draw(grid, area, context); + } + IndexState::Search => unreachable!(), + } + + self.dirty = false; + return; + } + + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if self.content.process_event(event, context) { + return true; + } + match event.event_type { + UIEventType::Input(Key::Up) => { + if self.cursor_pos > 0 { + self.new_cursor_pos = self.new_cursor_pos.saturating_sub(1); + self.set_dirty(); + } + return true; + } + UIEventType::Input(Key::Down) => { + if self.length > 0 && self.new_cursor_pos < self.length - 1 { + self.new_cursor_pos += 1; + self.set_dirty(); + } + return true; + } + UIEventType::Input(Key::Char('\n')) if self.state == IndexState::Listing => { + self.state = IndexState::Unfocused; + self.set_dirty(); + return true; + } + UIEventType::Input(Key::Char('i')) if self.state == IndexState::Unfocused => { + self.state = IndexState::Listing; + self.set_dirty(); + return true; + } + UIEventType::ChangeMode(UIMode::Normal) => { + self.set_dirty(); + } + UIEventType::Resize => { + self.set_dirty(); + } + _ => {} + } + + false + } + fn is_dirty(&self) -> bool { + self.dirty || self.content.is_dirty() + } + fn set_dirty(&mut self) { + self.dirty = true; + } +} + +impl fmt::Display for Index { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(&self.content, f) + } +} diff --git a/ui/src/components/indexer/mod.rs b/ui/src/components/indexer/mod.rs new file mode 100644 index 000000000..078685bd6 --- /dev/null +++ b/ui/src/components/indexer/mod.rs @@ -0,0 +1,128 @@ +/* + * meli - ui crate. + * + * Copyright 2017-2018 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 . + */ + +/*! Entities that handle Mail specific functions. + */ +use super::*; + +pub mod index; +pub use self::index::*; + +#[derive(Debug)] +struct MenuEntry { + name: String, + subentries: Vec, + index: Index, +} + +#[derive(Debug)] +pub struct Indexer { + entries: Vec, + dirty: bool, + cursor: Vec, +} + +impl fmt::Display for Indexer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO display subject/info + write!(f, "index") + } +} + +impl Default for Indexer { + fn default() -> Self { + Indexer { + entries: Vec::with_capacity(8), + dirty: true, + cursor: Vec::with_capacity(8), + } + } +} + +impl Indexer { + fn draw_menu(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {} +} + +impl Component for Indexer { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !self.is_dirty() { + return; + } + + clear_area(grid, area); + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + + let total_cols = get_x(bottom_right) - get_x(upper_left); + let index_entity_width = (30 * total_cols) / 100; + let mid = get_x(bottom_right) - index_entity_width; + for i in get_y(upper_left)..=get_y(bottom_right) { + set_and_join_box(grid, (mid, i), VERT_BOUNDARY); + } + + let left_menu_area = (upper_left, (set_x(bottom_right, mid - 1))); + let right_index_area = (set_x(upper_left, mid + 1), bottom_right); + + self.draw_menu(grid, left_menu_area, context); + self.entries[self.cursor[0]] + .index + .draw(grid, right_index_area, context); + + self.dirty = false; + context.dirty_areas.push_back(area); + } + + fn process_event(&mut self, event: &UIEvent, _context: &mut Context) -> bool { + if !self.entries[self.cursor[0]] + .index + .process_event(event, _context) + { + for i in 0..self.entries.len() { + if i == self.cursor[0] { + continue; + } + self.entries[i].index.process_event(event, _context); + } + } + + match event.event_type { + UIEventType::RefreshMailbox(c) => { + self.dirty = true; + } + UIEventType::ChangeMode(UIMode::Normal) => { + self.dirty = true; + } + UIEventType::Resize => { + self.dirty = true; + } + _ => {} + } + false + } + + fn is_dirty(&self) -> bool { + self.dirty + } + + fn set_dirty(&mut self) { + self.dirty = true; + } +} diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index 3d339737e..8814d30eb 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -63,7 +63,7 @@ impl CompactListing { /// Helper function to format entry strings for CompactListing */ /* TODO: Make this configurable */ fn make_entry_string(e: &Envelope, len: usize, idx: usize) -> String { - if len > 1 { + if len > 0 { format!( "{} {} {:.85} ({})", idx, @@ -101,8 +101,13 @@ impl CompactListing { /// chosen. fn refresh_mailbox(&mut self, context: &mut Context) { self.dirty = true; - self.cursor_pos.2 = 0; - self.new_cursor_pos.2 = 0; + if !(self.cursor_pos.0 == self.new_cursor_pos.0 + && self.cursor_pos.1 == self.new_cursor_pos.1) + { + //TODO: store cursor_pos in each folder + self.cursor_pos.2 = 0; + self.new_cursor_pos.2 = 0; + } self.cursor_pos.1 = self.new_cursor_pos.1; self.cursor_pos.0 = self.new_cursor_pos.0; @@ -124,7 +129,7 @@ impl CompactListing { Color::Default, Color::Default, ((0, 0), (MAX_COLS - 1, 0)), - true, + false, ); return; } @@ -142,7 +147,7 @@ impl CompactListing { Color::Default, Color::Default, ((0, 0), (MAX_COLS - 1, 0)), - true, + false, ); return; } @@ -204,6 +209,9 @@ impl CompactListing { let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] .as_ref() .unwrap(); + if mailbox.len() == 0 { + return; + } let threads = &mailbox.collection.threads; let thread_node = threads.root_set(idx); let thread_node = &threads.thread_nodes()[thread_node]; @@ -216,6 +224,7 @@ impl CompactListing { } threads.thread_nodes()[iter_ptr].message().unwrap() }; + let root_envelope: &Envelope = &mailbox.collection[&i]; let fg_color = if !root_envelope.is_seen() { Color::Byte(0) @@ -253,7 +262,12 @@ impl CompactListing { let bottom_right = bottom_right!(area); if self.length == 0 { clear_area(grid, area); - copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0))); + copy_area( + grid, + &self.content, + area, + ((0, 0), (MAX_COLS - 1, self.length)), + ); context.dirty_areas.push_back(area); return; } @@ -346,7 +360,6 @@ impl Component for CompactListing { if !self.is_dirty() { return; } - self.dirty = false; /* Draw the entire list */ self.draw_list(grid, area, context); } else { @@ -365,8 +378,8 @@ impl Component for CompactListing { } self.view = Some(ThreadView::new(self.cursor_pos, None, context)); self.view.as_mut().unwrap().draw(grid, area, context); - self.dirty = false; } + self.dirty = false; } fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { if let Some(ref mut v) = self.view { diff --git a/ui/src/components/mail/listing/mod.rs b/ui/src/components/mail/listing/mod.rs index d600d33af..4bd6898d3 100644 --- a/ui/src/components/mail/listing/mod.rs +++ b/ui/src/components/mail/listing/mod.rs @@ -49,7 +49,7 @@ impl fmt::Display for Listing { impl Default for Listing { fn default() -> Self { - Listing::Compact(Default::default()) + Listing::Threaded(Default::default()) } } diff --git a/ui/src/components/mail/listing/thread.rs b/ui/src/components/mail/listing/thread.rs index efc3a2913..9df89568c 100644 --- a/ui/src/components/mail/listing/thread.rs +++ b/ui/src/components/mail/listing/thread.rs @@ -35,10 +35,13 @@ pub struct ThreadListing { subsort: (SortField, SortOrder), /// Cache current view. content: CellBuffer, + + locations: Vec<(usize, usize)>, /// If we must redraw on next redraw event dirty: bool, /// If `self.view` exists or not. unfocused: bool, + initialised: bool, view: Option, } @@ -64,17 +67,24 @@ impl ThreadListing { sort: (Default::default(), Default::default()), subsort: (Default::default(), Default::default()), content, + locations: Vec::new(), dirty: true, unfocused: false, view: None, + initialised: false, } } /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has /// chosen. fn refresh_mailbox(&mut self, context: &mut Context) { self.dirty = true; - self.cursor_pos.2 = 0; - self.new_cursor_pos.2 = 0; + if !(self.cursor_pos.0 == self.new_cursor_pos.0 + && self.cursor_pos.1 == self.new_cursor_pos.1) + { + //TODO: store cursor_pos in each folder + self.cursor_pos.2 = 0; + self.new_cursor_pos.2 = 0; + } self.cursor_pos.1 = self.new_cursor_pos.1; self.cursor_pos.0 = self.new_cursor_pos.0; @@ -85,20 +95,27 @@ impl ThreadListing { }); // Get mailbox as a reference. // - loop { - // TODO: Show progress visually - if context.accounts[self.cursor_pos.0] - .status(self.cursor_pos.1) - .is_ok() - { - break; + match context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) { + Ok(_) => {} + Err(_) => { + self.content = CellBuffer::new(MAX_COLS, 1, Cell::with_char(' ')); + self.length = 0; + write_string_to_grid( + "Loading.", + &mut self.content, + Color::Default, + Color::Default, + ((0, 0), (MAX_COLS - 1, 0)), + false, + ); + return; } } let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] .as_ref() .unwrap(); - self.length = mailbox.collection.threads.root_len(); + self.length = mailbox.collection.threads.len(); self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' ')); if self.length == 0 { write_string_to_grid( @@ -118,25 +135,19 @@ impl ThreadListing { let threads = &mailbox.collection.threads; threads.sort_by(self.sort, self.subsort, &mailbox.collection); let thread_nodes: &Vec = &threads.thread_nodes(); - let mut iter = threads.root_iter().peekable(); - let len = threads.root_len().to_string().chars().count(); + self.locations = threads.threads_iter().collect(); + let mut iter = self.locations.iter().peekable(); /* This is just a desugared for loop so that we can use .peek() */ let mut idx = 0; - while let Some(i) = iter.next() { - let thread_node = &thread_nodes[i]; + while let Some((indentation, i)) = iter.next() { + let thread_node = &thread_nodes[*i]; - if !thread_node.has_message() { - continue; - } - - let indentation = thread_node.indentation(); - - if indentation == 0 { + if *indentation == 0 { thread_idx += 1; } match iter.peek() { - Some(&x) if thread_nodes[x].indentation() == indentation => { + Some((x, _)) if *x == *indentation => { indentations.pop(); indentations.push(true); } @@ -145,51 +156,58 @@ impl ThreadListing { indentations.push(false); } } - if threads.has_sibling(i) { + if threads.has_sibling(*i) { indentations.pop(); indentations.push(true); } - let envelope: &Envelope = &mailbox.collection[&thread_node.message().unwrap()]; - let fg_color = if !envelope.is_seen() { - Color::Byte(0) + if thread_node.has_message() { + let envelope: &Envelope = &mailbox.collection[&thread_node.message().unwrap()]; + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if !envelope.is_seen() { + Color::Byte(251) + } else if thread_idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + }; + let (x, _) = write_string_to_grid( + &ThreadListing::make_thread_entry( + envelope, + idx, + *indentation, + *i, + threads, + &indentations, + self.length, + // context.accounts[self.cursor_pos.0].backend.operation(envelope.hash()) + ), + &mut self.content, + fg_color, + bg_color, + ((0, idx), (MAX_COLS - 1, idx)), + false, + ); + for x in x..MAX_COLS { + self.content[(x, idx)].set_ch(' '); + self.content[(x, idx)].set_bg(bg_color); + } } else { - Color::Default - }; - let bg_color = if !envelope.is_seen() { - Color::Byte(251) - } else if thread_idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - }; - let (x, _) = write_string_to_grid( - &ThreadListing::make_thread_entry( - envelope, - idx, - indentation, - i, - threads, - &indentations, - len, - // context.accounts[self.cursor_pos.0].backend.operation(envelope.hash()) - ), - &mut self.content, - fg_color, - bg_color, - ((0, idx), (MAX_COLS - 1, idx)), - false, - ); - for x in x..MAX_COLS { - self.content[(x, idx)].set_ch(' '); - self.content[(x, idx)].set_bg(bg_color); + for x in 0..MAX_COLS { + self.content[(x, idx)].set_ch(' '); + self.content[(x, idx)].set_bg(Color::Default); + } } match iter.peek() { - Some(&x) if thread_nodes[x].indentation() > indentation => { + Some((x, _)) if *x > *indentation => { indentations.push(false); } - Some(&x) if thread_nodes[x].indentation() < indentation => { - for _ in 0..(indentation - thread_nodes[x].indentation()) { + Some((x, _)) if *x < *indentation => { + for _ in 0..(*indentation - *x) { indentations.pop(); } } @@ -203,49 +221,64 @@ impl ThreadListing { let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] .as_ref() .unwrap(); - let envelope: &Envelope = mailbox.thread_to_mail(idx); + if mailbox.len() == 0 { + return; + } + if let Some(hash) = + mailbox.collection.threads.thread_nodes()[self.locations[idx].1].message() + { + let envelope: &Envelope = &mailbox.collection[&hash]; - let fg_color = if !envelope.is_seen() { - Color::Byte(0) - } else { - Color::Default - }; - let bg_color = if !envelope.is_seen() { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - }; - change_colors( - &mut self.content, - ((0, idx), (MAX_COLS - 1, idx)), - fg_color, - bg_color, - ); + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if !envelope.is_seen() { + Color::Byte(251) + } else if idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + }; + change_colors( + &mut self.content, + ((0, idx), (MAX_COLS - 1, idx)), + fg_color, + bg_color, + ); + } } fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] .as_ref() .unwrap(); - let envelope: &Envelope = mailbox.thread_to_mail(idx); + if mailbox.len() == 0 || mailbox.len() <= idx { + return; + } - let fg_color = if !envelope.is_seen() { - Color::Byte(0) - } else { - Color::Default - }; - let bg_color = if self.cursor_pos.2 == idx { - Color::Byte(246) - } else if !envelope.is_seen() { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - }; - change_colors(grid, area, fg_color, bg_color); + if let Some(hash) = + mailbox.collection.threads.thread_nodes()[self.locations[idx].1].message() + { + let envelope: &Envelope = &mailbox.collection[&hash]; + + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if self.cursor_pos.2 == idx { + Color::Byte(246) + } else if !envelope.is_seen() { + Color::Byte(251) + } else if idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + }; + change_colors(grid, area, fg_color, bg_color); + } } /// Draw the list of `Envelope`s. @@ -266,7 +299,25 @@ impl ThreadListing { let page_no = (self.new_cursor_pos.2).wrapping_div(rows); let top_idx = page_no * rows; - + if !self.initialised { + self.initialised = false; + copy_area( + grid, + &self.content, + area, + ((0, top_idx), (MAX_COLS - 1, self.length)), + ); + self.highlight_line( + grid, + ( + set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)), + set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)), + ), + self.cursor_pos.2, + context, + ); + context.dirty_areas.push_back(area); + } /* If cursor position has changed, remove the highlight from the previous position and * apply it in the new one. */ if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { @@ -402,8 +453,26 @@ impl Component for ThreadListing { context.dirty_areas.push_back(area); return; } - /* Mark message as read */ + let idx = self.cursor_pos.2; + + { + let has_message: bool = { + let account = &context.accounts[self.cursor_pos.0]; + let mailbox = &account[self.cursor_pos.1].as_ref().unwrap(); + mailbox.collection.threads.thread_nodes() + [self.locations[self.new_cursor_pos.2].1] + .message() + .is_some() + }; + if !has_message { + self.dirty = false; + /* Draw the entire list */ + return self.draw_list(grid, area, context); + } + } + + /* Mark message as read */ let must_highlight = { if self.length == 0 { false @@ -411,7 +480,8 @@ impl Component for ThreadListing { let account = &mut context.accounts[self.cursor_pos.0]; let (hash, is_seen) = { 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(self.locations[self.new_cursor_pos.2].1); (envelope.hash(), envelope.is_seen()) }; if !is_seen { @@ -424,7 +494,8 @@ impl Component for ThreadListing { backend.operation(hash, folder_hash) }; 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(self.locations[self.new_cursor_pos.2].1); envelope.set_seen(op).unwrap(); true } else { @@ -476,7 +547,7 @@ impl Component for ThreadListing { let coordinates = ( self.cursor_pos.0, self.cursor_pos.1, - mailbox.threaded_mail(self.cursor_pos.2), + mailbox.threaded_mail(self.locations[self.cursor_pos.2].1), ); self.view = Some(MailView::new(coordinates, None, None)); } @@ -587,6 +658,7 @@ impl Component for ThreadListing { if *idxa == self.new_cursor_pos.0 && *idxf == self.new_cursor_pos.1 { self.dirty = true; self.refresh_mailbox(context); + eprintln!("mailboxupdate"); } } UIEventType::ChangeMode(UIMode::Normal) => { @@ -596,8 +668,8 @@ impl Component for ThreadListing { self.dirty = true; } UIEventType::Action(ref action) => match action { - Action::ViewMailbox(idx) => { - self.new_cursor_pos.1 = *idx; + Action::ViewMailbox(idx_m) => { + self.new_cursor_pos.1 = *idx_m; self.dirty = true; self.refresh_mailbox(context); return true; diff --git a/ui/src/components/mail/mod.rs b/ui/src/components/mail/mod.rs index 62f9243ce..f3dbc19a1 100644 --- a/ui/src/components/mail/mod.rs +++ b/ui/src/components/mail/mod.rs @@ -70,8 +70,7 @@ impl AccountMenu { } entries }, - }) - .collect(); + }).collect(); AccountMenu { accounts, dirty: true, diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs index 4eba15791..a3ffa21c0 100644 --- a/ui/src/components/mod.rs +++ b/ui/src/components/mod.rs @@ -32,6 +32,9 @@ pub use mail::*; pub mod notifications; +pub mod indexer; +pub use self::indexer::*; + pub mod utilities; pub use self::utilities::*; diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 2482eec33..7a8ba50ac 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -59,3 +59,6 @@ pub use components::*; pub mod conf; pub use conf::*; + +pub mod workers; +pub use workers::*; diff --git a/ui/src/state.rs b/ui/src/state.rs index 23879f19e..b9a6ef6f4 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -100,14 +100,14 @@ impl Context { pub fn restore_input(&self) { self.input.restore(self.sender.clone()); } - pub fn account_status(&mut self, idx_a: usize, idx_m: usize) -> result::Result { + pub fn account_status(&mut self, idx_a: usize, idx_m: usize) -> result::Result<(), usize> { match self.accounts[idx_a].status(idx_m) { Ok(()) => { self.replies.push_back(UIEvent { id: 0, event_type: UIEventType::MailboxUpdate((idx_a, idx_m)), }); - Ok(true) + Ok(()) } Err(n) => Err(n), } @@ -127,6 +127,7 @@ pub struct State { entities: Vec, pub context: Context, threads: FnvHashMap, thread::JoinHandle<()>)>, + work_controller: WorkController, } impl Drop for State { @@ -155,7 +156,7 @@ impl State { pub fn new() -> Self { /* Create a channel to communicate with other threads. The main process is the sole receiver. * */ - let (sender, receiver) = chan::sync(::std::mem::size_of::()); + let (sender, receiver) = chan::sync(32 * ::std::mem::size_of::()); /* * Create async channel to block the input-thread if we need to fork and stop it from reading @@ -215,7 +216,18 @@ impl State { }, }, threads: FnvHashMap::with_capacity_and_hasher(1, Default::default()), + work_controller: WorkController::new(), }; + for a in s.context.accounts.iter_mut() { + for worker in a.workers.iter_mut() { + if let Some(worker) = worker.as_mut() { + if let Some(w) = worker.work() { + s.work_controller.queue.add_work(w); + } + } + } + } + write!( s.stdout(), "{}{}{}{}", @@ -239,6 +251,11 @@ impl State { s.restore_input(); s } + + pub fn worker_receiver(&mut self) -> chan::Receiver { + self.work_controller.results_rx() + } + /* * When we receive a folder hash from a watcher thread, * we match the hash to the index of the mailbox, request a reload @@ -252,15 +269,18 @@ impl State { return; } if let Some(notification) = self.context.accounts[idxa].reload(event, idxm) { + self.context + .sender + .send(ThreadEvent::UIEvent(UIEventType::StartupCheck)); self.context.replies.push_back(UIEvent { id: 0, event_type: notification, }); - self.context.replies.push_back(UIEvent { - id: 0, - event_type: UIEventType::MailboxUpdate((idxa, idxm)), - }); } + self.context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::MailboxUpdate((idxa, idxm)), + }); } else { eprintln!( "BUG: mailbox with hash {} not found in mailbox_hashes.", diff --git a/ui/src/types/accounts.rs b/ui/src/types/accounts.rs index a8e034ea5..3ab572d20 100644 --- a/ui/src/types/accounts.rs +++ b/ui/src/types/accounts.rs @@ -34,11 +34,16 @@ use std::mem; use std::ops::{Index, IndexMut}; use std::result; use std::sync::Arc; -use std::thread; use types::UIEventType::{self, Notification}; pub type Worker = Option>>; +macro_rules! mailbox { + ($idx:expr, $folders:expr) => { + $folders[$idx].as_mut().unwrap().as_mut().unwrap() + }; +} + #[derive(Debug)] pub struct Account { name: String, @@ -66,13 +71,9 @@ impl Account { let notify_fn = Arc::new(notify_fn); for f in ref_folders { folders.push(None); - workers.push(Account::new_worker( - &name, - f, - &mut backend, - notify_fn.clone(), - )); + workers.push(Account::new_worker(f, &mut backend, notify_fn.clone())); } + eprintln!("sent_folder for {} is {:?}", name, sent_folder); Account { name, folders, @@ -85,56 +86,56 @@ impl Account { } } fn new_worker( - name: &str, folder: Folder, backend: &mut Box, notify_fn: Arc, ) -> Worker { - let mailbox_handle = backend.get(&folder, notify_fn.clone()); + let mailbox_handle = backend.get(&folder); let mut builder = AsyncBuilder::new(); let tx = builder.tx(); - Some( - builder.build( - thread::Builder::new() - .name(format!("Loading {}", name)) - .spawn(move || { - let envelopes = mailbox_handle.join(); - let ret = Mailbox::new(folder, envelopes); - tx.send(AsyncStatus::Finished); - notify_fn.notify(); - ret - }).unwrap(), - ), - ) + Some(builder.build(Box::new(move || { + let mut handle = mailbox_handle.clone(); + let folder = folder.clone(); + let work = handle.work().unwrap(); + work.compute(); + handle.join(); + let envelopes = handle.extract(); + let ret = Mailbox::new(folder, envelopes); + tx.send(AsyncStatus::Payload(ret)); + notify_fn.notify(); + }))) } pub fn reload(&mut self, event: RefreshEvent, idx: usize) -> Option { let kind = event.kind(); { - let mailbox: &mut Mailbox = self.folders[idx].as_mut().unwrap().as_mut().unwrap(); + //let mailbox: &mut Mailbox = self.folders[idx].as_mut().unwrap().as_mut().unwrap(); match kind { RefreshEventKind::Update(old_hash, envelope) => { - mailbox.update(old_hash, *envelope); + mailbox!(idx, self.folders).update(old_hash, *envelope); + } + RefreshEventKind::Rename(old_hash, new_hash) => { + mailbox!(idx, self.folders).rename(old_hash, new_hash); } RefreshEventKind::Create(envelope) => { - let env: &Envelope = mailbox.insert(*envelope); + eprintln!("create {}", envelope.hash()); + let env: &Envelope = mailbox!(idx, self.folders).insert(*envelope); let ref_folders: Vec = self.backend.folders(); return Some(Notification( Some("new mail".into()), format!( - "{:.15}:\nSubject: {:.15}\nFrom: {:.15}", + "{:<15}:\nSubject: {:<15}\nFrom: {:<15}", ref_folders[idx].name(), env.subject(), - env.field_from_to_string() + env.field_from_to_string(), ), )); } RefreshEventKind::Remove(envelope_hash) => { - mailbox.remove(envelope_hash); + mailbox!(idx, self.folders).remove(envelope_hash); } RefreshEventKind::Rescan => { let ref_folders: Vec = self.backend.folders(); let handle = Account::new_worker( - &self.name, ref_folders[idx].clone(), &mut self.backend, self.notify_fn.clone(), @@ -143,9 +144,6 @@ impl Account { } } } - if self.workers[idx].is_some() { - self.folders[idx] = None; - } None } pub fn watch(&self, r: RefreshEventConsumer) -> () { @@ -180,6 +178,8 @@ impl Account { } fn load_mailbox(&mut self, index: usize, mailbox: Result) { + self.folders[index] = Some(mailbox); + /* if self.sent_folder.is_some() && self.sent_folder.unwrap() == index { self.folders[index] = Some(mailbox); /* Add our replies to other folders */ @@ -190,6 +190,7 @@ impl Account { self.folders[index] = Some(mailbox); self.add_replies_to_folder(index); }; + */ } fn add_replies_to_folder(&mut self, folder_index: usize) { @@ -231,7 +232,7 @@ impl Account { None => { return Ok(()); } - Some(ref mut w) => match w.poll() { + Some(ref mut w) if self.folders[index].is_none() => match w.poll() { Ok(AsyncStatus::NoUpdate) => { return Err(0); } @@ -239,13 +240,15 @@ impl Account { Ok(AsyncStatus::ProgressReport(n)) => { return Err(n); } - a => { - eprintln!("Error: {:?}", a); + _ => { return Err(0); } }, + Some(_) => return Ok(()), }; - let m = self.workers[index].take().unwrap().extract(); + let m = mem::replace(&mut self.workers[index], None) + .unwrap() + .extract(); self.workers[index] = None; self.load_mailbox(index, m); Ok(()) diff --git a/ui/src/types/cells.rs b/ui/src/types/cells.rs index d4dd07041..7c196ef74 100644 --- a/ui/src/types/cells.rs +++ b/ui/src/types/cells.rs @@ -627,15 +627,15 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src_x += 1; } src_x = get_x(upper_left!(src)); - if src_y >= get_y(bottom_right!(src)) { + src_y += 1; + if src_y > get_y(bottom_right!(src)) { clear_area( grid_dest, - ((get_x(upper_left!(dest)), y), bottom_right!(dest)), + ((get_x(upper_left!(dest)), y + 1), bottom_right!(dest)), ); ret.1 = y; break; } - src_y += 1; } ret } diff --git a/ui/src/workers/mod.rs b/ui/src/workers/mod.rs new file mode 100644 index 000000000..09f31d909 --- /dev/null +++ b/ui/src/workers/mod.rs @@ -0,0 +1,298 @@ +use chan; +use melib::async::Work; +use std; +use std::mem; +use std::thread; + +const MAX_WORKER: usize = 4; + +pub struct WorkController { + pub queue: WorkQueue, + thread_end_tx: chan::Sender, + results: Option>, + threads: Vec>, +} + +impl WorkController { + pub fn results_rx(&mut self) -> chan::Receiver { + self.results.take().unwrap() + } +} + +impl Drop for WorkController { + fn drop(&mut self) { + for _ in 0..self.threads.len() { + self.thread_end_tx.send(true); + } + let threads = mem::replace(&mut self.threads, Vec::new()); + for handle in threads { + handle.join().unwrap(); + } + } +} + +// We need a way to keep track of what work needs to be done. +// This is a multi-source, multi-consumer queue which we call a +// WorkQueue. + +// To create this type, we wrap a mutex (std::sync::mutex) around a +// queue (technically a double-ended queue, std::collections::VecDeque). +// +// Mutex stands for MUTually EXclusive. It essentially ensures that only +// one thread has access to a given resource at one time. +use std::sync::Mutex; + +// A VecDeque is a double-ended queue, but we will only be using it in forward +// mode; that is, we will push onto the back and pull from the front. +use std::collections::VecDeque; + +// Finally we wrap the whole thing in Arc (Atomic Reference Counting) so that +// we can safely share it with other threads. Arc (std::sync::arc) is a lot +// like Rc (std::rc::Rc), in that it allows multiple references to some memory +// which is freed when no references remain, except that it is atomic, making +// it comparitively slow but able to be shared across the thread boundary. +use std::sync::Arc; + +// All three of these types are wrapped around a generic type T. +// T is required to be Send (a marker trait automatically implemented when +// it is safe to do so) because it denotes types that are safe to move between +// threads, which is the whole point of the WorkQueue. +// For this implementation, T is required to be Copy as well, for simplicity. + +/// A generic work queue for work elements which can be trivially copied. +/// Any producer of work can add elements and any worker can consume them. +/// WorkQueue derives Clone so that it can be distributed among threads. +#[derive(Clone)] +pub struct WorkQueue { + inner: Arc>>, + new_jobs_tx: chan::Sender, +} + +impl WorkQueue { + // Creating one of these by hand would be kind of a pain, + // so let's provide a convenience function. + + /// Creates a new WorkQueue, ready to be used. + fn new(new_jobs_tx: chan::Sender) -> Self { + Self { + inner: Arc::new(Mutex::new(VecDeque::new())), + new_jobs_tx, + } + } + + // This is the function workers will use to acquire work from the queue. + // They will call it in a loop, checking to see if there is any work available. + + /// Blocks the current thread until work is available, then + /// gets the data required to perform that work. + /// + /// # Errors + /// Returns None if there is no more work in the queue. + /// + /// # Panics + /// Panics if the underlying mutex became poisoned. This is exceedingly + /// unlikely. + fn get_work(&self) -> Option { + // Try to get a lock on the Mutex. If this fails, there is a + // problem with the mutex - it's poisoned, meaning that a thread that + // held the mutex lock panicked before releasing it. There is no way + // to guarantee that all its invariants are upheld, so we need to not + // use it in that case. + let maybe_queue = self.inner.lock(); + // A lot is going on here. self.inner is an Arc of Mutex. Arc can deref + // into its internal type, so we can call the methods of that inner + // type (Mutex) without dereferencing, so this is like + // *(self.inner).lock() + // but doesn't look awful. Mutex::lock() returns a + // Result>>. + + // Unwrapping with if let, we get a MutexGuard, which is an RAII guard + // that unlocks the Mutex when it goes out of scope. + if let Ok(mut queue) = maybe_queue { + // queue is a MutexGuard, so this is like + // (*queue).pop_front() + // Returns Some(item) or None if there are no more items. + queue.pop_front() + + // The function has returned, so queue goes out of scope and the + // mutex unlocks. + } else { + // There's a problem with the mutex. + panic!("WorkQueue::get_work() tried to lock a poisoned mutex"); + } + } + + // Both the controller (main thread) and possibly workers can use this + // function to add work to the queue. + + /// Blocks the current thread until work can be added, then + /// adds that work to the end of the queue. + /// Returns the amount of work now in the queue. + /// + /// # Panics + /// Panics if the underlying mutex became poisoned. This is exceedingly + /// unlikely. + pub fn add_work(&self, work: T) -> usize { + // As above, try to get a lock on the mutex. + if let Ok(mut queue) = self.inner.lock() { + // As above, we can use the MutexGuard> to access + // the internal VecDeque. + queue.push_back(work); + + self.new_jobs_tx.send(true); + // Now return the length of the queue. + queue.len() + } else { + panic!("WorkQueue::add_work() tried to lock a poisoned mutex"); + } + } +} + +impl WorkController { + pub fn new() -> WorkController { + let (new_jobs_tx, new_jobs_rx) = chan::async(); + // Create a new work queue to keep track of what work needs to be done. + // Note that the queue is internally mutable (or, rather, the Mutex is), + // but this binding doesn't need to be mutable. This isn't unsound because + // the Mutex ensures at runtime that no two references can be used; + // therefore no mutation can occur at the same time as aliasing. + let queue: WorkQueue = WorkQueue::new(new_jobs_tx); + + // Create a MPSC (Multiple Producer, Single Consumer) channel. Every worker + // is a producer, the main thread is a consumer; the producers put their + // work into the channel when it's done. + let (results_tx, results_rx) = chan::async(); + + // Create a SyncFlag to share whether or not there are more jobs to be done. + let (thread_end_tx, thread_end_rx) = chan::sync(::std::mem::size_of::()); + + // This Vec will hold thread join handles to allow us to not exit while work + // is still being done. These handles provide a .join() method which blocks + // the current thread until the thread referred to by the handle exits. + let mut threads = Vec::new(); + + for thread_num in 0..MAX_WORKER { + // Get a reference to the queue for the thread to use + // .clone() here doesn't clone the actual queue data, but rather the + // internal Arc produces a new reference for use in the new queue + // instance. + let thread_queue = queue.clone(); + + // Similarly, create a new transmitter for the thread to use + let thread_results_tx = results_tx.clone(); + + let thread_end_rx = thread_end_rx.clone(); + let new_jobs_rx = new_jobs_rx.clone(); + + // thread::spawn takes a closure (an anonymous function that "closes" + // over its environment). The move keyword means it takes ownership of + // those variables, meaning they can't be used again in the main thread. + let handle = thread::spawn(move || { + // A varaible to keep track of how much work was done. + let mut work_done = 0; + + 'work_loop: loop { + // Loop while there's expected to be work, looking for work. + chan_select! { + thread_end_rx.recv() -> _ => { + break 'work_loop; + }, + new_jobs_rx.recv() -> _ => { + // If work is available, do that work. + while let Some(work) = thread_queue.get_work() { + // Do some work. + work.compute(); + + // Record that some work was done. + work_done += 1; + + // Send the work and the result of that work. + // + // Sending could fail. If so, there's no use in + // doing any more work, so abort. + thread_results_tx.send(true); + + // Signal to the operating system that now is a good time + // to give another thread a chance to run. + // + // This isn't strictly necessary - the OS can preemptively + // switch between threads, without asking - but it helps make + // sure that other threads do get a chance to get some work. + std::thread::yield_now(); + } + continue 'work_loop; + }, + } + } + + // Report the amount of work done. + eprintln!("Thread {} did {} jobs.", thread_num, work_done); + }); + + // Add the handle for the newly spawned thread to the list of handles + threads.push(handle); + } + + WorkController { + queue, + thread_end_tx, + results: Some(results_rx), + threads, + } + } +} +/* +pub fn add_jobkk + + println!("Adding jobs to the queue."); + // Variables to keep track of the number of jobs we expect to do. + let mut jobs_remaining = 0; + let mut jobs_total = 0; + + // Just add some numbers to the queue. + // These numbers will be passed into fib(), so they need to stay pretty + // small. + for work in 0..90 { + // Add each one several times. + for _ in 0..100 { + jobs_remaining = queue.add_work(work); + jobs_total += 1; + } + } + + + // Report that some jobs were inserted, and how many are left to be done. + // This is interesting because the workers have been taking jobs out of the queue + // the whole time the control thread has been putting them in! + // + // Try removing the use of std::thread::yield_now() in the thread closure. + // You'll probably (depending on your system) notice that the number remaining + // after insertion goes way up. That's because the operating system is usually + // (not always, but usually) fairly conservative about interrupting a thread + // that is actually doing work. + // + // Similarly, if you add a call to yield_now() in the loop above, you'll see the + // number remaining probably drop to 1 or 2. This can also change depending on + // how optimized the output code is - try `cargo run --release` vs `cargo run`. + // + // This inconsistency should drive home to you that you as the programmer can't + // make any assumptions at all about when and in what order things will happen + // in parallel code unless you use thread control primatives as demonstrated + // in this program. + println!("Total of {} jobs inserted into the queue ({} remaining at this time).", + jobs_total, + jobs_remaining); + + + // Get completed work from the channel while there's work to be done. + while jobs_total > 0 { + match results_rx.recv() { + // If the control thread successfully receives, a job was completed. + Ok(_) => { jobs_total -= 1 }, + // If the control thread is the one left standing, that's pretty + // problematic. + Err(_) => {panic!("All workers died unexpectedly.");} + } + } + +*/