WIP
parent
21a918e4c0
commit
5a28320004
|
@ -32,61 +32,94 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use chan;
|
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.
|
/// Messages to pass between `Async<T>` owner and its worker thread.
|
||||||
#[derive(Debug)]
|
#[derive(Clone)]
|
||||||
pub enum AsyncStatus {
|
pub enum AsyncStatus<T> {
|
||||||
NoUpdate,
|
NoUpdate,
|
||||||
|
Payload(T),
|
||||||
Finished,
|
Finished,
|
||||||
///The number may hold whatever meaning the user chooses.
|
///The number may hold whatever meaning the user chooses.
|
||||||
ProgressReport(usize),
|
ProgressReport(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder object for `Async<T>`
|
impl<T> fmt::Debug for AsyncStatus<T> {
|
||||||
#[derive(Debug)]
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
pub struct AsyncBuilder {
|
match self {
|
||||||
tx: chan::Sender<AsyncStatus>,
|
AsyncStatus::NoUpdate => write!(f, "AsyncStatus<T>::NoUpdate"),
|
||||||
rx: chan::Receiver<AsyncStatus>,
|
AsyncStatus::Payload(_) => write!(f, "AsyncStatus<T>::Payload(_)"),
|
||||||
|
AsyncStatus::Finished => write!(f, "AsyncStatus<T>::Finished"),
|
||||||
|
AsyncStatus::ProgressReport(u) => write!(f, "AsyncStatus<T>::ProgressReport({})", u),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// A builder object for `Async<T>`
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AsyncBuilder<T> {
|
||||||
|
tx: chan::Sender<AsyncStatus<T>>,
|
||||||
|
rx: chan::Receiver<AsyncStatus<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Async<T> {
|
pub struct Async<T> {
|
||||||
value: Option<T>,
|
value: Option<T>,
|
||||||
worker: Option<thread::JoinHandle<T>>,
|
work: Work,
|
||||||
tx: chan::Sender<AsyncStatus>,
|
active: bool,
|
||||||
rx: chan::Receiver<AsyncStatus>,
|
tx: chan::Sender<AsyncStatus<T>>,
|
||||||
|
rx: chan::Receiver<AsyncStatus<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AsyncBuilder {
|
impl<T> Default for AsyncBuilder<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
AsyncBuilder::new()
|
AsyncBuilder::<T>::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncBuilder {
|
impl<T> AsyncBuilder<T> {
|
||||||
pub fn new() -> Self {
|
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 {
|
AsyncBuilder {
|
||||||
tx: sender,
|
tx: sender,
|
||||||
rx: receiver,
|
rx: receiver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns the sender object of the promise's channel.
|
/// 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()
|
self.tx.clone()
|
||||||
}
|
}
|
||||||
/// Returns the receiver object of the promise's channel.
|
/// 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()
|
self.rx.clone()
|
||||||
}
|
}
|
||||||
/// Returns an `Async<T>` object that contains a `Thread` join handle that returns a `T`
|
/// 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 {
|
Async {
|
||||||
worker: Some(worker),
|
work: Work(Arc::new(work)),
|
||||||
value: None,
|
value: None,
|
||||||
tx: self.tx,
|
tx: self.tx,
|
||||||
rx: self.rx,
|
rx: self.rx,
|
||||||
|
active: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,20 +129,34 @@ impl<T> Async<T> {
|
||||||
pub fn extract(self) -> T {
|
pub fn extract(self) -> T {
|
||||||
self.value.unwrap()
|
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.
|
/// 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() {
|
if self.value.is_some() {
|
||||||
return Ok(AsyncStatus::Finished);
|
return Ok(AsyncStatus::Finished);
|
||||||
}
|
}
|
||||||
//self.tx.send(true);
|
//self.tx.send(true);
|
||||||
let rx = &self.rx;
|
let rx = &self.rx;
|
||||||
|
let result: T;
|
||||||
chan_select! {
|
chan_select! {
|
||||||
default => {
|
default => {
|
||||||
return Ok(AsyncStatus::NoUpdate);
|
return Ok(AsyncStatus::NoUpdate);
|
||||||
},
|
},
|
||||||
rx.recv() -> r => {
|
rx.recv() -> r => {
|
||||||
match r {
|
match r {
|
||||||
Some(AsyncStatus::Finished) => {
|
Some(AsyncStatus::Payload(payload)) => {
|
||||||
|
result = payload;
|
||||||
},
|
},
|
||||||
Some(a) => {
|
Some(a) => {
|
||||||
return Ok(a);
|
return Ok(a);
|
||||||
|
@ -118,15 +165,29 @@ impl<T> Async<T> {
|
||||||
return Err(());
|
return Err(());
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
let v = self.worker.take().unwrap().join().unwrap();
|
self.value = Some(result);
|
||||||
self.value = Some(v);
|
|
||||||
Ok(AsyncStatus::Finished)
|
Ok(AsyncStatus::Finished)
|
||||||
}
|
}
|
||||||
/// Blocks until thread joins.
|
/// Blocks until thread joins.
|
||||||
pub fn join(mut self) -> T {
|
pub fn join(&mut self) {
|
||||||
self.worker.take().unwrap().join().unwrap()
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ extern crate fnv;
|
||||||
extern crate notify;
|
extern crate notify;
|
||||||
extern crate xdg;
|
extern crate xdg;
|
||||||
|
|
||||||
use super::{MaildirFolder, MaildirOp, NotifyFn};
|
use super::{MaildirFolder, MaildirOp};
|
||||||
use async::*;
|
use async::*;
|
||||||
use conf::AccountSettings;
|
use conf::AccountSettings;
|
||||||
use error::Result;
|
use error::Result;
|
||||||
|
@ -56,19 +56,19 @@ use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct HashIndex {
|
pub struct HashIndex {
|
||||||
index: FnvHashMap<EnvelopeHash, (usize, PathBuf)>,
|
index: FnvHashMap<EnvelopeHash, PathBuf>,
|
||||||
hash: FolderHash,
|
hash: FolderHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for HashIndex {
|
impl Deref for HashIndex {
|
||||||
type Target = FnvHashMap<EnvelopeHash, (usize, PathBuf)>;
|
type Target = FnvHashMap<EnvelopeHash, PathBuf>;
|
||||||
fn deref(&self) -> &FnvHashMap<EnvelopeHash, (usize, PathBuf)> {
|
fn deref(&self) -> &FnvHashMap<EnvelopeHash, PathBuf> {
|
||||||
&self.index
|
&self.index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for HashIndex {
|
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
|
&mut self.index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,6 @@ pub struct MaildirType {
|
||||||
folders: Vec<MaildirFolder>,
|
folders: Vec<MaildirFolder>,
|
||||||
//folder_index: FnvHashMap<FolderHash, usize>,
|
//folder_index: FnvHashMap<FolderHash, usize>,
|
||||||
hash_indexes: HashIndexes,
|
hash_indexes: HashIndexes,
|
||||||
|
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,11 +92,11 @@ macro_rules! path_is_new {
|
||||||
} else {
|
} else {
|
||||||
let mut iter = $path.components().rev();
|
let mut iter = $path.components().rev();
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
|
||||||
iter.next() == Some(Component::Normal(OsStr::new("new")))
|
iter.next() == Some(Component::Normal(OsStr::new("new")))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! get_path_hash {
|
macro_rules! get_path_hash {
|
||||||
($path:expr) => {{
|
($path:expr) => {{
|
||||||
let mut path = $path.clone();
|
let mut path = $path.clone();
|
||||||
|
@ -132,6 +131,7 @@ fn get_file_hash(file: &Path) -> EnvelopeHash {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_to_cur(p: PathBuf) -> PathBuf {
|
fn move_to_cur(p: PathBuf) -> PathBuf {
|
||||||
|
eprintln!("moved to cur");
|
||||||
let mut new = p.clone();
|
let mut new = p.clone();
|
||||||
{
|
{
|
||||||
let file_name = p.file_name().unwrap();
|
let file_name = p.file_name().unwrap();
|
||||||
|
@ -149,8 +149,8 @@ impl MailBackend for MaildirType {
|
||||||
fn folders(&self) -> Vec<Folder> {
|
fn folders(&self) -> Vec<Folder> {
|
||||||
self.folders.iter().map(|f| f.clone()).collect()
|
self.folders.iter().map(|f| f.clone()).collect()
|
||||||
}
|
}
|
||||||
fn get(&mut self, folder: &Folder, notify_fn: Arc<NotifyFn>) -> Async<Result<Vec<Envelope>>> {
|
fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
|
||||||
self.multicore(4, folder, notify_fn)
|
self.multicore(4, folder)
|
||||||
}
|
}
|
||||||
fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
|
fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
|
@ -190,6 +190,9 @@ impl MailBackend for MaildirType {
|
||||||
Ok(event) => match event {
|
Ok(event) => match event {
|
||||||
/* Create */
|
/* Create */
|
||||||
DebouncedEvent::Create(mut pathbuf) => {
|
DebouncedEvent::Create(mut pathbuf) => {
|
||||||
|
if path_is_new!(pathbuf) {
|
||||||
|
pathbuf = move_to_cur(pathbuf);
|
||||||
|
}
|
||||||
let folder_hash = get_path_hash!(pathbuf);
|
let folder_hash = get_path_hash!(pathbuf);
|
||||||
let file_name = pathbuf
|
let file_name = pathbuf
|
||||||
.as_path()
|
.as_path()
|
||||||
|
@ -203,13 +206,11 @@ impl MailBackend for MaildirType {
|
||||||
&cache_dir,
|
&cache_dir,
|
||||||
file_name,
|
file_name,
|
||||||
) {
|
) {
|
||||||
|
eprintln!("Create event {} {} {}", env.hash(), env.subject(), pathbuf.display());
|
||||||
sender.send(RefreshEvent {
|
sender.send(RefreshEvent {
|
||||||
hash: folder_hash,
|
hash: folder_hash,
|
||||||
kind: Create(Box::new(env)),
|
kind: Create(Box::new(env)),
|
||||||
});
|
});
|
||||||
if path_is_new!(pathbuf) {
|
|
||||||
move_to_cur(pathbuf);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -228,9 +229,9 @@ impl MailBackend for MaildirType {
|
||||||
/* Linear search in hash_index to find old hash */
|
/* Linear search in hash_index to find old hash */
|
||||||
let old_hash: EnvelopeHash = {
|
let old_hash: EnvelopeHash = {
|
||||||
if let Some((k, v)) =
|
if let Some((k, v)) =
|
||||||
index_lock.iter_mut().find(|(_, v)| v.1 == pathbuf)
|
index_lock.iter_mut().find(|(_, v)| **v == pathbuf)
|
||||||
{
|
{
|
||||||
v.1 = pathbuf.clone();
|
*v = pathbuf.clone();
|
||||||
*k
|
*k
|
||||||
} else {
|
} else {
|
||||||
/* Did we just miss a Create event? In any case, create
|
/* 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() {
|
if index_lock.get_mut(&new_hash).is_none() {
|
||||||
let op = Box::new(MaildirOp::new(new_hash, hash_indexes.clone(), folder_hash));
|
let op = Box::new(MaildirOp::new(new_hash, hash_indexes.clone(), folder_hash));
|
||||||
if let Some(env) = Envelope::from_token(op, new_hash) {
|
if let Some(env) = Envelope::from_token(op, new_hash) {
|
||||||
index_lock.insert(new_hash, (0, pathbuf.clone()));
|
eprintln!("{}\t{}", new_hash, pathbuf.display());
|
||||||
|
index_lock.insert(new_hash, pathbuf);
|
||||||
|
|
||||||
/* Send Write notice */
|
/* Send Write notice */
|
||||||
|
|
||||||
|
@ -271,15 +273,16 @@ impl MailBackend for MaildirType {
|
||||||
DebouncedEvent::NoticeRemove(mut pathbuf)
|
DebouncedEvent::NoticeRemove(mut pathbuf)
|
||||||
| DebouncedEvent::Remove(mut pathbuf) => {
|
| DebouncedEvent::Remove(mut pathbuf) => {
|
||||||
let folder_hash = get_path_hash!(pathbuf);
|
let folder_hash = get_path_hash!(pathbuf);
|
||||||
let hash_indexes_lock = hash_indexes.lock().unwrap();
|
let mut hash_indexes_lock = hash_indexes.lock().unwrap();
|
||||||
let index_lock = &hash_indexes_lock[&folder_hash];
|
let index_lock = hash_indexes_lock.entry(folder_hash).or_default();
|
||||||
let hash: EnvelopeHash = if let Some((k, _)) =
|
let hash: EnvelopeHash = if let Some((k, _)) =
|
||||||
index_lock.iter().find(|(_, v)| v.1 == pathbuf)
|
index_lock.iter().find(|(_, v)| **v == pathbuf)
|
||||||
{
|
{
|
||||||
*k
|
*k
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
index_lock.remove(&hash);
|
||||||
|
|
||||||
sender.send(RefreshEvent {
|
sender.send(RefreshEvent {
|
||||||
hash: folder_hash,
|
hash: folder_hash,
|
||||||
|
@ -290,10 +293,18 @@ impl MailBackend for MaildirType {
|
||||||
DebouncedEvent::Rename(mut src, mut dest) => {
|
DebouncedEvent::Rename(mut src, mut dest) => {
|
||||||
let folder_hash = get_path_hash!(src);
|
let folder_hash = get_path_hash!(src);
|
||||||
let old_hash: EnvelopeHash = get_file_hash(src.as_path());
|
let old_hash: EnvelopeHash = get_file_hash(src.as_path());
|
||||||
|
let new_hash: EnvelopeHash = get_file_hash(dest.as_path());
|
||||||
|
|
||||||
let mut hash_indexes_lock = hash_indexes.lock().unwrap();
|
let mut hash_indexes_lock = hash_indexes.lock().unwrap();
|
||||||
let mut index_lock = hash_indexes_lock.entry(folder_hash).or_default();
|
let index_lock = hash_indexes_lock.entry(folder_hash).or_default();
|
||||||
if let Some(v) = index_lock.get_mut(&old_hash) {
|
|
||||||
v.1 = dest;
|
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 {
|
} else {
|
||||||
/* Maybe a re-read should be triggered here just to be safe. */
|
/* Maybe a re-read should be triggered here just to be safe. */
|
||||||
sender.send(RefreshEvent {
|
sender.send(RefreshEvent {
|
||||||
|
@ -391,33 +402,31 @@ impl MaildirType {
|
||||||
.0
|
.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn multicore(
|
pub fn multicore(&mut self, cores: usize, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
|
||||||
&mut self,
|
|
||||||
cores: usize,
|
|
||||||
folder: &Folder,
|
|
||||||
notify_fn: Arc<NotifyFn>,
|
|
||||||
) -> Async<Result<Vec<Envelope>>> {
|
|
||||||
let mut w = AsyncBuilder::new();
|
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 cache_dir = xdg::BaseDirectories::with_profile("meli", &self.name).unwrap();
|
||||||
{
|
|
||||||
let mut hash_index = self.hash_indexes.lock().unwrap();
|
|
||||||
let index_lock = hash_index.entry(folder.hash()).or_default();
|
|
||||||
index_lock.clear();
|
|
||||||
}
|
|
||||||
let handle = {
|
let handle = {
|
||||||
let tx = w.tx();
|
let tx = w.tx();
|
||||||
// TODO: Avoid clone
|
// TODO: Avoid clone
|
||||||
let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)];
|
let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)];
|
||||||
let folder_hash = folder.hash();
|
let 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 name = format!("parsing {:?}", folder.name());
|
||||||
|
let root_path = self.path.to_path_buf();
|
||||||
let map = self.hash_indexes.clone();
|
let map = self.hash_indexes.clone();
|
||||||
let map2 = self.hash_indexes.clone();
|
|
||||||
|
|
||||||
thread::Builder::new()
|
let closure = move || {
|
||||||
.name(name.clone())
|
let name = name.clone();
|
||||||
.spawn(move || {
|
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();
|
let cache_dir = cache_dir.clone();
|
||||||
{
|
{
|
||||||
path.push("new");
|
path.push("new");
|
||||||
|
@ -428,11 +437,12 @@ impl MaildirType {
|
||||||
}
|
}
|
||||||
path.pop();
|
path.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
path.push("cur");
|
path.push("cur");
|
||||||
let iter = path.read_dir()?;
|
let iter = path.read_dir()?;
|
||||||
let count = path.read_dir()?.count();
|
let count = path.read_dir()?.count();
|
||||||
let mut files: Vec<PathBuf> = Vec::with_capacity(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 {
|
for e in iter {
|
||||||
let e = e.and_then(|x| {
|
let e = e.and_then(|x| {
|
||||||
let path = x.path();
|
let path = x.path();
|
||||||
|
@ -484,7 +494,7 @@ impl MaildirType {
|
||||||
let mut map = map.lock().unwrap();
|
let mut map = map.lock().unwrap();
|
||||||
let mut map = map.entry(folder_hash).or_default();;
|
let mut map = map.entry(folder_hash).or_default();;
|
||||||
let hash = env.hash();
|
let hash = env.hash();
|
||||||
map.insert(hash, (0, file.clone()));
|
map.insert(hash, file.clone());
|
||||||
local_r.push(env);
|
local_r.push(env);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -493,7 +503,7 @@ impl MaildirType {
|
||||||
{
|
{
|
||||||
let mut map = map.lock().unwrap();
|
let mut map = map.lock().unwrap();
|
||||||
let mut map = map.entry(folder_hash).or_default();
|
let mut map = map.entry(folder_hash).or_default();
|
||||||
(*map).insert(hash, (0, PathBuf::from(file)));
|
(*map).insert(hash, PathBuf::from(file));
|
||||||
}
|
}
|
||||||
let op =
|
let op =
|
||||||
Box::new(MaildirOp::new(hash, map.clone(), folder_hash));
|
Box::new(MaildirOp::new(hash, map.clone(), folder_hash));
|
||||||
|
@ -529,20 +539,16 @@ impl MaildirType {
|
||||||
}
|
}
|
||||||
for t in threads {
|
for t in threads {
|
||||||
let mut result = t.join();
|
let mut result = t.join();
|
||||||
r.append(&mut result);
|
ret.append(&mut result);
|
||||||
}
|
}
|
||||||
let mut map = map2.lock().unwrap();
|
let mut map = map2.lock().unwrap();
|
||||||
let map = map.entry(folder_hash).or_default();
|
let map = map.entry(folder_hash).or_default();
|
||||||
for (idx, e) in r.iter().enumerate() {
|
Ok(ret)
|
||||||
let mut y = (*map)[&e.hash()].clone();
|
};
|
||||||
y.0 = idx;
|
let result = thunk();
|
||||||
(*map).insert(e.hash(), y);
|
tx_final.send(AsyncStatus::Payload(result));
|
||||||
}
|
};
|
||||||
tx.send(AsyncStatus::Finished);
|
Box::new(closure)
|
||||||
notify_fn.notify();
|
|
||||||
Ok(r)
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
};
|
};
|
||||||
w.build(handle)
|
w.build(handle)
|
||||||
}
|
}
|
||||||
|
@ -558,15 +564,22 @@ fn add_path_to_index(
|
||||||
let env: Envelope;
|
let env: Envelope;
|
||||||
let hash = get_file_hash(path);
|
let hash = get_file_hash(path);
|
||||||
{
|
{
|
||||||
let mut hash_index = hash_index.lock().unwrap();
|
let mut map = hash_index.lock().unwrap();
|
||||||
let index_lock = hash_index.entry(folder_hash).or_default();
|
let map = map.entry(folder_hash).or_default();;
|
||||||
if index_lock.contains_key(&hash) {
|
map.insert(hash, path.to_path_buf());
|
||||||
return None;
|
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));
|
let op = Box::new(MaildirOp::new(hash, hash_index.clone(), folder_hash));
|
||||||
if let Some(e) = Envelope::from_token(op, hash) {
|
if let Some(e) = Envelope::from_token(op, hash) {
|
||||||
|
eprintln!("add_path_to_index gen {}\t{}", hash, file_name.display());
|
||||||
if let Ok(cached) = cache_dir.place_cache_file(file_name) {
|
if let Ok(cached) = cache_dir.place_cache_file(file_name) {
|
||||||
/* place result in cache directory */
|
/* place result in cache directory */
|
||||||
let f = match fs::File::create(cached) {
|
let f = match fs::File::create(cached) {
|
||||||
|
|
|
@ -67,7 +67,14 @@ impl MaildirOp {
|
||||||
fn path(&self) -> PathBuf {
|
fn path(&self) -> PathBuf {
|
||||||
let map = self.hash_index.lock().unwrap();
|
let map = self.hash_index.lock().unwrap();
|
||||||
let map = &map[&self.folder_hash];
|
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 hash_index = self.hash_index.clone();
|
||||||
let mut map = hash_index.lock().unwrap();
|
let mut map = hash_index.lock().unwrap();
|
||||||
let map = map.entry(self.folder_hash).or_default();
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ use mailbox::email::{Envelope, EnvelopeHash, Flag};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
use self::fnv::FnvHashMap;
|
use self::fnv::FnvHashMap;
|
||||||
|
@ -84,6 +83,8 @@ impl Backends {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RefreshEventKind {
|
pub enum RefreshEventKind {
|
||||||
Update(EnvelopeHash, Box<Envelope>),
|
Update(EnvelopeHash, Box<Envelope>),
|
||||||
|
/// Rename(old_hash, new_hash)
|
||||||
|
Rename(EnvelopeHash, EnvelopeHash),
|
||||||
Create(Box<Envelope>),
|
Create(Box<Envelope>),
|
||||||
Remove(FolderHash),
|
Remove(FolderHash),
|
||||||
Rescan,
|
Rescan,
|
||||||
|
@ -145,7 +146,7 @@ impl NotifyFn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub trait MailBackend: ::std::fmt::Debug {
|
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 watch(&self, sender: RefreshEventConsumer) -> Result<()>;
|
||||||
fn folders(&self) -> Vec<Folder>;
|
fn folders(&self) -> Vec<Folder>;
|
||||||
fn operation(&self, hash: EnvelopeHash, folder_hash: FolderHash) -> Box<BackendOp>;
|
fn operation(&self, hash: EnvelopeHash, folder_hash: FolderHash) -> Box<BackendOp>;
|
||||||
|
|
|
@ -10,6 +10,7 @@ use std::result;
|
||||||
|
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
use self::fnv::FnvHashMap;
|
use self::fnv::FnvHashMap;
|
||||||
|
use self::fnv::FnvHashSet;
|
||||||
|
|
||||||
/// `Mailbox` represents a folder of mail.
|
/// `Mailbox` represents a folder of mail.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
@ -53,42 +54,29 @@ impl Collection {
|
||||||
let cache_dir =
|
let cache_dir =
|
||||||
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash()))
|
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash()))
|
||||||
.unwrap();
|
.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 reader = io::BufReader::new(fs::File::open(cached).unwrap());
|
||||||
let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
|
let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
|
||||||
let ret = if let Ok(mut cached_t) = result {
|
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
|
cached_t
|
||||||
} else {
|
} else {
|
||||||
Threads::new(&mut envelopes)
|
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
|
ret
|
||||||
} else {
|
} else {
|
||||||
let ret = Threads::new(&mut envelopes);
|
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
|
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
Collection {
|
Collection {
|
||||||
folder: folder.clone(),
|
folder: folder.clone(),
|
||||||
envelopes,
|
envelopes,
|
||||||
|
@ -106,17 +94,60 @@ impl Collection {
|
||||||
self.envelopes.is_empty()
|
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) {
|
pub fn insert(&mut self, envelope: Envelope) {
|
||||||
let hash = envelope.hash();
|
let hash = envelope.hash();
|
||||||
|
eprintln!("DEBUG: Inserting hash {} in {}", hash, self.folder.name());
|
||||||
self.envelopes.insert(hash, envelope);
|
self.envelopes.insert(hash, envelope);
|
||||||
let env = self.envelopes.entry(hash).or_default() as *mut Envelope;
|
let env = self.envelopes.entry(hash).or_default() as *mut Envelope;
|
||||||
unsafe {
|
unsafe {
|
||||||
self.threads.insert(&mut (*env), &self.envelopes);
|
self.threads.insert(&mut (*env), &self.envelopes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn insert_reply(&mut self, envelope: Envelope) {
|
pub(crate) fn insert_reply(&mut self, _envelope: &Envelope) {
|
||||||
self.insert(envelope);
|
return;
|
||||||
//self.threads.insert_reply(envelope, &mut self.envelopes);
|
/*
|
||||||
|
//self.insert(envelope);
|
||||||
|
eprintln!("insert_reply in collections");
|
||||||
|
self.threads.insert_reply(envelope, &mut self.envelopes);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,7 @@ impl AttachmentBuilder {
|
||||||
};
|
};
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
fn decode(&self) -> Vec<u8> {
|
fn decode(&self) -> Vec<u8> {
|
||||||
// TODO merge this and standalone decode() function
|
// TODO merge this and standalone decode() function
|
||||||
let charset = match self.content_type {
|
let charset = match self.content_type {
|
||||||
|
@ -186,6 +187,7 @@ impl AttachmentBuilder {
|
||||||
self.raw.to_vec()
|
self.raw.to_vec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
pub fn build(self) -> Attachment {
|
pub fn build(self) -> Attachment {
|
||||||
Attachment {
|
Attachment {
|
||||||
content_type: self.content_type,
|
content_type: self.content_type,
|
||||||
|
|
|
@ -336,6 +336,11 @@ impl Envelope {
|
||||||
flags: Flag::default(),
|
flags: Flag::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_hash(&mut self, new_hash: EnvelopeHash) {
|
||||||
|
self.hash = new_hash;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<Envelope> {
|
pub fn from_bytes(bytes: &[u8]) -> Result<Envelope> {
|
||||||
let mut h = DefaultHasher::new();
|
let mut h = DefaultHasher::new();
|
||||||
h.write(bytes);
|
h.write(bytes);
|
||||||
|
@ -569,22 +574,21 @@ impl Envelope {
|
||||||
_ => Cow::from(String::new()),
|
_ => Cow::from(String::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn in_reply_to_bytes<'a>(&'a self) -> &'a [u8] {
|
pub fn in_reply_to(&self) -> Option<&MessageID> {
|
||||||
match self.in_reply_to {
|
self.in_reply_to.as_ref()
|
||||||
Some(ref s) => s.raw(),
|
}
|
||||||
_ => &[],
|
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(&self) -> Cow<str> {
|
pub fn in_reply_to_raw(&self) -> Option<Cow<str>> {
|
||||||
match self.in_reply_to {
|
if let Some(ref m) = self.in_reply_to {
|
||||||
Some(ref s) => String::from_utf8_lossy(s.val()),
|
Some(String::from_utf8_lossy(m.raw()))
|
||||||
_ => Cow::from(String::new()),
|
} 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 message_id(&self) -> &MessageID {
|
pub fn message_id(&self) -> &MessageID {
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl Mailbox {
|
||||||
Ok(Mailbox {
|
Ok(Mailbox {
|
||||||
folder,
|
folder,
|
||||||
collection,
|
collection,
|
||||||
name: name,
|
name,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -96,18 +96,21 @@ impl Mailbox {
|
||||||
&self.collection.threads.thread_nodes()[i]
|
&self.collection.threads.thread_nodes()[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_sent_folder(&mut self, sent: &Mailbox) {
|
pub fn insert_sent_folder(&mut self, _sent: &Mailbox) {
|
||||||
if !self.has_sent {
|
/*if !self.has_sent {
|
||||||
for envelope in sent.collection.envelopes.values().cloned() {
|
for envelope in sent.collection.envelopes.values() {
|
||||||
self.insert_reply(envelope);
|
self.insert_reply(envelope);
|
||||||
}
|
}
|
||||||
self.has_sent = true;
|
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) {
|
pub fn update(&mut self, old_hash: EnvelopeHash, envelope: Envelope) {
|
||||||
self.collection.remove(&old_hash);
|
self.collection.update_envelope(old_hash, envelope);
|
||||||
self.collection.insert(envelope);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, envelope: Envelope) -> &Envelope {
|
pub fn insert(&mut self, envelope: Envelope) -> &Envelope {
|
||||||
|
@ -116,12 +119,13 @@ impl Mailbox {
|
||||||
&self.collection[&hash]
|
&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);
|
self.collection.insert_reply(envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, envelope_hash: EnvelopeHash) {
|
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);
|
// eprintln!("envelope_hash: {}\ncollection:\n{:?}", envelope_hash, self.collection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ use std::ops::Index;
|
||||||
use std::result::Result as StdResult;
|
use std::result::Result as StdResult;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
type Envelopes = FnvHashMap<EnvelopeHash, Envelope>;
|
||||||
|
|
||||||
/* Helper macros to avoid repeating ourselves */
|
/* Helper macros to avoid repeating ourselves */
|
||||||
|
|
||||||
fn rec_change_root_parent(b: &mut Vec<ThreadNode>, idx: usize, new_root: usize) {
|
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:
|
* threads:
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
|
@ -236,6 +238,53 @@ impl ThreadTree {
|
||||||
* the iterator returns them as `A, B, C, D, E, F`
|
* 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> {
|
pub struct ThreadIterator<'a> {
|
||||||
init_pos: usize,
|
init_pos: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
|
@ -355,7 +404,7 @@ pub struct Threads {
|
||||||
tree: RefCell<Vec<ThreadTree>>,
|
tree: RefCell<Vec<ThreadTree>>,
|
||||||
|
|
||||||
message_ids: FnvHashMap<Vec<u8>, usize>,
|
message_ids: FnvHashMap<Vec<u8>, usize>,
|
||||||
hash_set: FnvHashSet<EnvelopeHash>,
|
pub hash_set: FnvHashSet<EnvelopeHash>,
|
||||||
sort: RefCell<(SortField, SortOrder)>,
|
sort: RefCell<(SortField, SortOrder)>,
|
||||||
subsort: RefCell<(SortField, SortOrder)>,
|
subsort: RefCell<(SortField, SortOrder)>,
|
||||||
}
|
}
|
||||||
|
@ -504,8 +553,7 @@ impl Threads {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Split this function
|
pub fn new(collection: &mut Envelopes) -> Threads {
|
||||||
pub fn new(collection: &mut FnvHashMap<EnvelopeHash, Envelope>) -> Threads {
|
|
||||||
/* To reconstruct thread information from the mails we need: */
|
/* To reconstruct thread information from the mails we need: */
|
||||||
|
|
||||||
/* a vector to hold thread members */
|
/* a vector to hold thread members */
|
||||||
|
@ -529,20 +577,46 @@ impl Threads {
|
||||||
* References / In-Reply-To headers */
|
* References / In-Reply-To headers */
|
||||||
t.link_threads(collection);
|
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
|
/* 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 */
|
* have no parents. These are the root messages of each thread */
|
||||||
let mut root_set: Vec<usize> = Vec::with_capacity(collection.len());
|
let mut root_set: Vec<usize> = Vec::with_capacity(collection.len());
|
||||||
|
|
||||||
/* Find the root set */
|
/* Find the root set */
|
||||||
'root_set: for v in t.message_ids.values() {
|
'root_set: for v in self.message_ids.values() {
|
||||||
if t.thread_nodes[*v].parent.is_none() {
|
if self.thread_nodes[*v].parent.is_none() {
|
||||||
root_set.push(*v);
|
root_set.push(*v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut roots_to_remove: Vec<usize> = Vec::with_capacity(root_set.len());
|
let mut roots_to_remove: Vec<usize> = Vec::with_capacity(root_set.len());
|
||||||
/* Prune empty thread nodes */
|
/* Prune empty thread nodes */
|
||||||
t.prune_empty_nodes(&mut root_set);
|
self.prune_empty_nodes(&mut root_set);
|
||||||
|
|
||||||
/* "Group root set by subject."
|
/* "Group root set by subject."
|
||||||
*
|
*
|
||||||
|
@ -553,19 +627,19 @@ impl Threads {
|
||||||
let mut subject_table: FnvHashMap<Vec<u8>, (bool, usize)> =
|
let mut subject_table: FnvHashMap<Vec<u8>, (bool, usize)> =
|
||||||
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
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": */
|
/* "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
|
/* "If there is a message in the Container, the subject is the subject of that
|
||||||
* message. " */
|
* message. " */
|
||||||
let msg_idx = t.thread_nodes[*r].message.unwrap();
|
let msg_idx = self.thread_nodes[r].message.unwrap();
|
||||||
let envelope = &collection[&msg_idx];
|
let envelope = &collection[&msg_idx];
|
||||||
(envelope.subject(), !envelope.references().is_empty())
|
(envelope.subject(), !envelope.references().is_empty())
|
||||||
} else {
|
} else {
|
||||||
/* "If there is no message in the Container, then the Container will have at least
|
/* "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
|
* one child Container, and that Container will have a message. Use the subject of
|
||||||
* that message instead." */
|
* 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
|
.message
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let envelope = &collection[&msg_idx];
|
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
|
* "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
|
* container has a non-``Re:'' version of this subject. The non-re version is the
|
||||||
* more interesting of the two." */
|
* 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)
|
|| (other_is_re && !is_re)
|
||||||
{
|
{
|
||||||
mem::replace(
|
mem::replace(
|
||||||
subject_table.entry(stripped_subj.to_vec()).or_default(),
|
subject_table.entry(stripped_subj.to_vec()).or_default(),
|
||||||
(is_re, *r),
|
(is_re, r),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* "There is no container in the table with this subject" */
|
/* "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." */
|
* root set. Now iterate over the root set, and gather together the difference." */
|
||||||
for i in 0..root_set.len() {
|
for i in 0..root_set.len() {
|
||||||
let r = root_set[i];
|
let r = root_set[i];
|
||||||
|
|
||||||
/* "Find the subject of this Container (as above.)" */
|
/* "Find the subject of this Container (as above.)" */
|
||||||
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() {
|
||||||
let msg_idx = t.thread_nodes[r].message.unwrap();
|
let msg_idx = self.thread_nodes[r].message.unwrap();
|
||||||
let envelope = &collection[&msg_idx];
|
let envelope = &collection[&msg_idx];
|
||||||
(envelope.subject(), !envelope.references().is_empty())
|
(envelope.subject(), !envelope.references().is_empty())
|
||||||
} else {
|
} 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
|
.message
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let envelope = &collection[&msg_idx];
|
let envelope = &collection[&msg_idx];
|
||||||
|
@ -630,7 +705,7 @@ impl Threads {
|
||||||
|
|
||||||
let (other_is_re, other_idx) = subject_table[subject];
|
let (other_is_re, other_idx) = subject_table[subject];
|
||||||
/* "If it is null, or if it is this container, continue." */
|
/* "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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,10 +716,10 @@ impl Threads {
|
||||||
* "If both are dummies, append one's children to the other, and remove the now-empty
|
* "If both are dummies, append one's children to the other, and remove the now-empty
|
||||||
* container."
|
* container."
|
||||||
*/
|
*/
|
||||||
if !t.thread_nodes[r].has_message() && !t.thread_nodes[other_idx].has_message() {
|
if !self.thread_nodes[r].has_message() && !self.thread_nodes[other_idx].has_message() {
|
||||||
let children = t.thread_nodes[r].children.clone();
|
let children = self.thread_nodes[r].children.clone();
|
||||||
for c in children {
|
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);
|
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
|
* of the empty, and a sibling of the other ``real'' messages with the same subject
|
||||||
* (the empty's children.)"
|
* (the empty's children.)"
|
||||||
*/
|
*/
|
||||||
} else if t.thread_nodes[r].has_message() && !t.thread_nodes[other_idx].has_message() {
|
} else if self.thread_nodes[r].has_message()
|
||||||
make!((other_idx) parent of (r), &mut t.thread_nodes);
|
&& !self.thread_nodes[other_idx].has_message()
|
||||||
|
{
|
||||||
|
make!((other_idx) parent of (r), &mut self.thread_nodes);
|
||||||
if !root_set.contains(&other_idx) {
|
if !root_set.contains(&other_idx) {
|
||||||
root_set.push(other_idx);
|
root_set.push(other_idx);
|
||||||
}
|
}
|
||||||
roots_to_remove.push(i);
|
roots_to_remove.push(i);
|
||||||
} else if !t.thread_nodes[r].has_message() && t.thread_nodes[other_idx].has_message() {
|
} else if !self.thread_nodes[r].has_message()
|
||||||
make!((r) parent of (other_idx), &mut t.thread_nodes);
|
&& 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) {
|
if let Some(pos) = root_set.iter().position(|&i| i == other_idx) {
|
||||||
roots_to_remove.push(pos);
|
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
|
* "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."
|
* 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 {
|
} else if self.thread_nodes[other_idx].has_message() && !other_is_re && is_re {
|
||||||
make!((other_idx) parent of (r), &mut t.thread_nodes);
|
make!((other_idx) parent of (r), &mut self.thread_nodes);
|
||||||
roots_to_remove.push(i);
|
roots_to_remove.push(i);
|
||||||
|
|
||||||
/* "If that container is a non-empty, and that message's subject begins with ``Re:'', but this
|
/* "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
|
* without will be in the hash table, regardless of the order in which they were
|
||||||
* seen.)"
|
* seen.)"
|
||||||
*/
|
*/
|
||||||
} else if t.thread_nodes[other_idx].has_message() && other_is_re && !is_re {
|
} else if self.thread_nodes[other_idx].has_message() && other_is_re && !is_re {
|
||||||
make!((r) parent of (other_idx), &mut t.thread_nodes);
|
make!((r) parent of (other_idx), &mut self.thread_nodes);
|
||||||
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
|
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
|
||||||
roots_to_remove.push(pos);
|
roots_to_remove.push(pos);
|
||||||
}
|
}
|
||||||
|
@ -688,11 +767,11 @@ impl Threads {
|
||||||
* hierarchical relationship which might not be true."
|
* hierarchical relationship which might not be true."
|
||||||
*/
|
*/
|
||||||
} else {
|
} else {
|
||||||
t.thread_nodes.push(Default::default());
|
self.thread_nodes.push(Default::default());
|
||||||
let new_id = t.thread_nodes.len() - 1;
|
let new_id = self.thread_nodes.len() - 1;
|
||||||
t.thread_nodes[new_id].thread_group = new_id;
|
self.thread_nodes[new_id].thread_group = new_id;
|
||||||
make!((new_id) parent of (r), &mut t.thread_nodes);
|
make!((new_id) parent of (r), &mut self.thread_nodes);
|
||||||
make!((new_id) parent of (other_idx), &mut t.thread_nodes);
|
make!((new_id) parent of (other_idx), &mut self.thread_nodes);
|
||||||
root_set[i] = new_id;
|
root_set[i] = new_id;
|
||||||
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
|
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
|
||||||
roots_to_remove.push(pos);
|
roots_to_remove.push(pos);
|
||||||
|
@ -705,41 +784,118 @@ impl Threads {
|
||||||
root_set.remove(r);
|
root_set.remove(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
t.root_set = RefCell::new(root_set);
|
self.root_set = RefCell::new(root_set);
|
||||||
t.build_collection(&collection);
|
}
|
||||||
t
|
|
||||||
|
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 {
|
pub fn thread_iter(&self, index: usize) -> ThreadIterator {
|
||||||
ThreadIterator {
|
ThreadIterator {
|
||||||
init_pos: index,
|
init_pos: index,
|
||||||
pos: index,
|
pos: index,
|
||||||
stack: Vec::new(),
|
stack: Vec::with_capacity(4),
|
||||||
tree: self.tree.borrow(),
|
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:
|
/* must update:
|
||||||
* - hash_set
|
* - hash_set
|
||||||
* - message fields in thread_nodes
|
* - message fields in thread_nodes
|
||||||
*/
|
*/
|
||||||
self.hash_set.remove(&old_hash);
|
self.hash_set.remove(&old_hash);
|
||||||
self.hash_set.insert(envelope.hash());
|
if let Some(node) = self
|
||||||
let node = self
|
|
||||||
.thread_nodes
|
.thread_nodes
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find(|n| n.message.map(|n| n == old_hash).unwrap_or(false))
|
.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;
|
||||||
|
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<usize> = 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 new_hash_set = FnvHashSet::from_iter(collection.keys().cloned());
|
||||||
|
|
||||||
|
let difference: Vec<EnvelopeHash> =
|
||||||
|
self.hash_set.difference(&new_hash_set).cloned().collect();
|
||||||
|
for h in difference {
|
||||||
|
self.remove(h, collection);
|
||||||
|
}
|
||||||
|
|
||||||
let difference: Vec<EnvelopeHash> =
|
let difference: Vec<EnvelopeHash> =
|
||||||
new_hash_set.difference(&self.hash_set).cloned().collect();
|
new_hash_set.difference(&self.hash_set).cloned().collect();
|
||||||
for h in difference {
|
for h in difference {
|
||||||
|
eprintln!("inserting {}", collection[&h].subject());
|
||||||
let env = collection.entry(h).or_default() as *mut Envelope;
|
let env = collection.entry(h).or_default() as *mut Envelope;
|
||||||
unsafe {
|
unsafe {
|
||||||
// `collection` is borrowed immutably and `insert` only changes the envelope's
|
// `collection` is borrowed immutably and `insert` only changes the envelope's
|
||||||
|
@ -747,25 +903,15 @@ impl Threads {
|
||||||
self.insert(&mut (*env), collection);
|
self.insert(&mut (*env), collection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.create_root_set(collection);
|
||||||
|
|
||||||
let difference: Vec<EnvelopeHash> =
|
let mut root_set: Vec<usize> = self.tree.borrow().iter().map(|t| t.id).collect();
|
||||||
self.hash_set.difference(&new_hash_set).cloned().collect();
|
self.prune_empty_nodes(&mut root_set);
|
||||||
for h in difference {
|
let tree = self.tree.get_mut();
|
||||||
self.hash_set.remove(&h);
|
tree.retain(|t| root_set.contains(&t.id));
|
||||||
let node = self
|
|
||||||
.thread_nodes
|
|
||||||
.iter_mut()
|
|
||||||
.find(|n| n.message.map(|n| n == h).unwrap_or(false))
|
|
||||||
.unwrap();
|
|
||||||
node.message = None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(
|
pub fn insert(&mut self, envelope: &mut Envelope, collection: &Envelopes) {
|
||||||
&mut self,
|
|
||||||
envelope: &mut Envelope,
|
|
||||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
|
||||||
) {
|
|
||||||
self.link_envelope(envelope);
|
self.link_envelope(envelope);
|
||||||
{
|
{
|
||||||
let id = self.message_ids[envelope.message_id().raw()];
|
let id = self.message_ids[envelope.message_id().raw()];
|
||||||
|
@ -773,30 +919,29 @@ impl Threads {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_reply(
|
pub fn insert_reply(&mut self, envelope: &Envelope, collection: &mut Envelopes) -> bool {
|
||||||
&mut self,
|
|
||||||
envelope: Envelope,
|
|
||||||
collection: &mut FnvHashMap<EnvelopeHash, Envelope>,
|
|
||||||
) -> bool {
|
|
||||||
//return false;
|
//return false;
|
||||||
{
|
{
|
||||||
let in_reply_to = envelope.in_reply_to_bytes();
|
if let Some(in_reply_to) = envelope.in_reply_to() {
|
||||||
if !self.message_ids.contains_key(in_reply_to) {
|
if !self.message_ids.contains_key(in_reply_to.raw()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let hash: EnvelopeHash = envelope.hash();
|
let hash: EnvelopeHash = envelope.hash();
|
||||||
collection.insert(hash, envelope);
|
collection.insert(hash, envelope.clone());
|
||||||
{
|
{
|
||||||
let envelope: &mut Envelope = collection.entry(hash).or_default();
|
let envelope = collection.entry(hash).or_default() as *mut Envelope;
|
||||||
|
unsafe {
|
||||||
/* FIXME: This does not update show_subject and len which is done in node_build upon
|
/* Safe because insert only changes envelope's fields and nothing more */
|
||||||
* creation */
|
self.insert(&mut (*envelope), &collection);
|
||||||
self.link_envelope(envelope);
|
}
|
||||||
}
|
}
|
||||||
let envelope: &Envelope = &collection[&hash];
|
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];
|
let parent_id = self.message_ids[in_reply_to];
|
||||||
self.rebuild_thread(parent_id, collection);
|
self.rebuild_thread(parent_id, collection);
|
||||||
}
|
}
|
||||||
|
@ -804,7 +949,7 @@ impl Threads {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update thread tree information on envelope insertion */
|
/* Update thread tree information on envelope insertion */
|
||||||
fn rebuild_thread(&mut self, id: usize, collection: &FnvHashMap<EnvelopeHash, Envelope>) {
|
fn rebuild_thread(&mut self, id: usize, collection: &Envelopes) {
|
||||||
let mut node_idx = id;
|
let mut node_idx = id;
|
||||||
let mut stack = Vec::with_capacity(32);
|
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. */
|
* Finalize instance by building the thread tree, set show subject and thread lengths etc. */
|
||||||
fn build_collection(&mut self, collection: &FnvHashMap<EnvelopeHash, Envelope>) {
|
fn build_collection(&mut self, collection: &Envelopes) {
|
||||||
{
|
{
|
||||||
let tree = self.tree.get_mut();
|
let tree = self.tree.get_mut();
|
||||||
|
tree.clear();
|
||||||
for i in self.root_set.borrow().iter() {
|
for i in self.root_set.borrow().iter() {
|
||||||
let mut tree_node = ThreadTree::new(*i);
|
let mut tree_node = ThreadTree::new(*i);
|
||||||
node_build(
|
node_build(
|
||||||
|
@ -865,11 +1011,7 @@ impl Threads {
|
||||||
self.inner_subsort_by(*self.subsort.borrow(), collection);
|
self.inner_subsort_by(*self.subsort.borrow(), collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner_subsort_by(
|
fn inner_subsort_by(&self, subsort: (SortField, SortOrder), collection: &Envelopes) {
|
||||||
&self,
|
|
||||||
subsort: (SortField, SortOrder),
|
|
||||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
|
||||||
) {
|
|
||||||
let tree = &mut self.tree.borrow_mut();
|
let tree = &mut self.tree.borrow_mut();
|
||||||
for mut t in tree.iter_mut() {
|
for mut t in tree.iter_mut() {
|
||||||
t.children.sort_by(|a, b| match subsort {
|
t.children.sort_by(|a, b| match subsort {
|
||||||
|
@ -909,11 +1051,7 @@ impl Threads {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner_sort_by(
|
fn inner_sort_by(&self, sort: (SortField, SortOrder), collection: &Envelopes) {
|
||||||
&self,
|
|
||||||
sort: (SortField, SortOrder),
|
|
||||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
|
||||||
) {
|
|
||||||
let tree = &mut self.tree.borrow_mut();
|
let tree = &mut self.tree.borrow_mut();
|
||||||
tree.sort_by(|a, b| match sort {
|
tree.sort_by(|a, b| match sort {
|
||||||
(SortField::Date, SortOrder::Desc) => {
|
(SortField::Date, SortOrder::Desc) => {
|
||||||
|
@ -955,7 +1093,7 @@ impl Threads {
|
||||||
&self,
|
&self,
|
||||||
sort: (SortField, SortOrder),
|
sort: (SortField, SortOrder),
|
||||||
subsort: (SortField, SortOrder),
|
subsort: (SortField, SortOrder),
|
||||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
collection: &Envelopes,
|
||||||
) {
|
) {
|
||||||
if *self.sort.borrow() != sort {
|
if *self.sort.borrow() != sort {
|
||||||
self.inner_sort_by(sort, collection);
|
self.inner_sort_by(sort, collection);
|
||||||
|
@ -968,7 +1106,7 @@ impl Threads {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thread_to_mail(&self, i: usize) -> EnvelopeHash {
|
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()
|
thread.message().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -976,6 +1114,10 @@ impl Threads {
|
||||||
&self.thread_nodes
|
&self.thread_nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.thread_nodes.len()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn root_len(&self) -> usize {
|
pub fn root_len(&self) -> usize {
|
||||||
self.tree.borrow().len()
|
self.tree.borrow().len()
|
||||||
}
|
}
|
||||||
|
@ -1089,7 +1231,7 @@ impl Threads {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn link_threads(&mut self, collection: &mut FnvHashMap<EnvelopeHash, Envelope>) {
|
fn link_threads(&mut self, collection: &mut Envelopes) {
|
||||||
for v in collection.values_mut() {
|
for v in collection.values_mut() {
|
||||||
self.link_envelope(v);
|
self.link_envelope(v);
|
||||||
}
|
}
|
||||||
|
@ -1101,7 +1243,7 @@ impl Index<usize> for Threads {
|
||||||
|
|
||||||
fn index(&self, index: usize) -> &ThreadNode {
|
fn index(&self, index: usize) -> &ThreadNode {
|
||||||
self.thread_nodes
|
self.thread_nodes
|
||||||
.get(self.tree.borrow()[index].id)
|
.get(index)
|
||||||
.expect("thread index out of bounds")
|
.expect("thread index out of bounds")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1111,11 +1253,18 @@ fn node_build(
|
||||||
idx: usize,
|
idx: usize,
|
||||||
thread_nodes: &mut Vec<ThreadNode>,
|
thread_nodes: &mut Vec<ThreadNode>,
|
||||||
indentation: usize,
|
indentation: usize,
|
||||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
collection: &Envelopes,
|
||||||
) {
|
) {
|
||||||
if let Some(hash) = thread_nodes[idx].message {
|
if let Some(hash) = thread_nodes[idx].message {
|
||||||
if let Some(parent_id) = thread_nodes[idx].parent {
|
if !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 {
|
if let Some(parent_hash) = thread_nodes[parent_id].message {
|
||||||
|
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.
|
/* decide if the subject should be shown in the UI.
|
||||||
* If parent subject is Foobar and reply is `Re: Foobar`
|
* If parent subject is Foobar and reply is `Re: Foobar`
|
||||||
* then showing the reply's subject can be reduntant
|
* then showing the reply's subject can be reduntant
|
||||||
|
@ -1132,6 +1281,7 @@ fn node_build(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let indentation = if thread_nodes[idx].has_message() {
|
let indentation = if thread_nodes[idx].has_message() {
|
||||||
thread_nodes[idx].indentation = indentation;
|
thread_nodes[idx].indentation = indentation;
|
||||||
|
@ -1146,7 +1296,12 @@ fn node_build(
|
||||||
let mut child_vec: Vec<ThreadTree> = Vec::new();
|
let mut child_vec: Vec<ThreadTree> = Vec::new();
|
||||||
|
|
||||||
thread_nodes[idx].len = thread_nodes[idx].children.len();
|
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<usize>;
|
||||||
|
for &c in unsafe { &(*children) } {
|
||||||
let mut new_tree = ThreadTree::new(c);
|
let mut new_tree = ThreadTree::new(c);
|
||||||
node_build(&mut new_tree, c, thread_nodes, indentation, collection);
|
node_build(&mut new_tree, c, thread_nodes, indentation, collection);
|
||||||
thread_nodes[idx].len += thread_nodes[c].len;
|
thread_nodes[idx].len += thread_nodes[c].len;
|
||||||
|
|
12
src/bin.rs
12
src/bin.rs
|
@ -60,6 +60,8 @@ fn main() {
|
||||||
|
|
||||||
let receiver = state.receiver();
|
let receiver = state.receiver();
|
||||||
|
|
||||||
|
let worker_receiver = state.worker_receiver();
|
||||||
|
|
||||||
/* Register some reasonably useful interfaces */
|
/* Register some reasonably useful interfaces */
|
||||||
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
|
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
|
||||||
let listing = listing::Listing::default();
|
let listing = listing::Listing::default();
|
||||||
|
@ -84,6 +86,7 @@ fn main() {
|
||||||
for e in events {
|
for e in events {
|
||||||
state.rcv_event(e);
|
state.rcv_event(e);
|
||||||
}
|
}
|
||||||
|
state.redraw();
|
||||||
|
|
||||||
/* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */
|
/* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */
|
||||||
chan_select! {
|
chan_select! {
|
||||||
|
@ -151,11 +154,12 @@ fn main() {
|
||||||
for idx_a in 0..state.context.accounts.len() {
|
for idx_a in 0..state.context.accounts.len() {
|
||||||
let len = state.context.accounts[idx_a].len();
|
let len = state.context.accounts[idx_a].len();
|
||||||
for idx_m in 0..len {
|
for idx_m in 0..len {
|
||||||
|
|
||||||
match state.context.account_status(idx_a, idx_m) {
|
match state.context.account_status(idx_a, idx_m) {
|
||||||
Ok(true) => {
|
Ok(_) => {
|
||||||
render_flag = true;
|
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
|
} // end of 'inner
|
||||||
|
|
||||||
|
|
|
@ -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<usize>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<IndexContent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! Entities that handle Mail specific functions.
|
||||||
|
*/
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub mod index;
|
||||||
|
pub use self::index::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MenuEntry {
|
||||||
|
name: String,
|
||||||
|
subentries: Vec<MenuEntry>,
|
||||||
|
index: Index,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Indexer {
|
||||||
|
entries: Vec<MenuEntry>,
|
||||||
|
dirty: bool,
|
||||||
|
cursor: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,7 +63,7 @@ impl CompactListing {
|
||||||
/// Helper function to format entry strings for CompactListing */
|
/// Helper function to format entry strings for CompactListing */
|
||||||
/* TODO: Make this configurable */
|
/* TODO: Make this configurable */
|
||||||
fn make_entry_string(e: &Envelope, len: usize, idx: usize) -> String {
|
fn make_entry_string(e: &Envelope, len: usize, idx: usize) -> String {
|
||||||
if len > 1 {
|
if len > 0 {
|
||||||
format!(
|
format!(
|
||||||
"{} {} {:.85} ({})",
|
"{} {} {:.85} ({})",
|
||||||
idx,
|
idx,
|
||||||
|
@ -101,8 +101,13 @@ impl CompactListing {
|
||||||
/// chosen.
|
/// chosen.
|
||||||
fn refresh_mailbox(&mut self, context: &mut Context) {
|
fn refresh_mailbox(&mut self, context: &mut Context) {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
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.cursor_pos.2 = 0;
|
||||||
self.new_cursor_pos.2 = 0;
|
self.new_cursor_pos.2 = 0;
|
||||||
|
}
|
||||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||||
|
|
||||||
|
@ -124,7 +129,7 @@ impl CompactListing {
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
((0, 0), (MAX_COLS - 1, 0)),
|
((0, 0), (MAX_COLS - 1, 0)),
|
||||||
true,
|
false,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -142,7 +147,7 @@ impl CompactListing {
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
((0, 0), (MAX_COLS - 1, 0)),
|
((0, 0), (MAX_COLS - 1, 0)),
|
||||||
true,
|
false,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -204,6 +209,9 @@ impl CompactListing {
|
||||||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
if mailbox.len() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let threads = &mailbox.collection.threads;
|
let threads = &mailbox.collection.threads;
|
||||||
let thread_node = threads.root_set(idx);
|
let thread_node = threads.root_set(idx);
|
||||||
let thread_node = &threads.thread_nodes()[thread_node];
|
let thread_node = &threads.thread_nodes()[thread_node];
|
||||||
|
@ -216,6 +224,7 @@ impl CompactListing {
|
||||||
}
|
}
|
||||||
threads.thread_nodes()[iter_ptr].message().unwrap()
|
threads.thread_nodes()[iter_ptr].message().unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let root_envelope: &Envelope = &mailbox.collection[&i];
|
let root_envelope: &Envelope = &mailbox.collection[&i];
|
||||||
let fg_color = if !root_envelope.is_seen() {
|
let fg_color = if !root_envelope.is_seen() {
|
||||||
Color::Byte(0)
|
Color::Byte(0)
|
||||||
|
@ -253,7 +262,12 @@ impl CompactListing {
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
if self.length == 0 {
|
if self.length == 0 {
|
||||||
clear_area(grid, area);
|
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);
|
context.dirty_areas.push_back(area);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -346,7 +360,6 @@ impl Component for CompactListing {
|
||||||
if !self.is_dirty() {
|
if !self.is_dirty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.dirty = false;
|
|
||||||
/* Draw the entire list */
|
/* Draw the entire list */
|
||||||
self.draw_list(grid, area, context);
|
self.draw_list(grid, area, context);
|
||||||
} else {
|
} else {
|
||||||
|
@ -365,8 +378,8 @@ impl Component for CompactListing {
|
||||||
}
|
}
|
||||||
self.view = Some(ThreadView::new(self.cursor_pos, None, context));
|
self.view = Some(ThreadView::new(self.cursor_pos, None, context));
|
||||||
self.view.as_mut().unwrap().draw(grid, area, 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 {
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
|
||||||
if let Some(ref mut v) = self.view {
|
if let Some(ref mut v) = self.view {
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl fmt::Display for Listing {
|
||||||
|
|
||||||
impl Default for Listing {
|
impl Default for Listing {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Listing::Compact(Default::default())
|
Listing::Threaded(Default::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,10 +35,13 @@ pub struct ThreadListing {
|
||||||
subsort: (SortField, SortOrder),
|
subsort: (SortField, SortOrder),
|
||||||
/// Cache current view.
|
/// Cache current view.
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
|
|
||||||
|
locations: Vec<(usize, usize)>,
|
||||||
/// If we must redraw on next redraw event
|
/// If we must redraw on next redraw event
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
/// If `self.view` exists or not.
|
/// If `self.view` exists or not.
|
||||||
unfocused: bool,
|
unfocused: bool,
|
||||||
|
initialised: bool,
|
||||||
view: Option<MailView>,
|
view: Option<MailView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,17 +67,24 @@ impl ThreadListing {
|
||||||
sort: (Default::default(), Default::default()),
|
sort: (Default::default(), Default::default()),
|
||||||
subsort: (Default::default(), Default::default()),
|
subsort: (Default::default(), Default::default()),
|
||||||
content,
|
content,
|
||||||
|
locations: Vec::new(),
|
||||||
dirty: true,
|
dirty: true,
|
||||||
unfocused: false,
|
unfocused: false,
|
||||||
view: None,
|
view: None,
|
||||||
|
initialised: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
|
/// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
|
||||||
/// chosen.
|
/// chosen.
|
||||||
fn refresh_mailbox(&mut self, context: &mut Context) {
|
fn refresh_mailbox(&mut self, context: &mut Context) {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
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.cursor_pos.2 = 0;
|
||||||
self.new_cursor_pos.2 = 0;
|
self.new_cursor_pos.2 = 0;
|
||||||
|
}
|
||||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||||
|
|
||||||
|
@ -85,20 +95,27 @@ impl ThreadListing {
|
||||||
});
|
});
|
||||||
// Get mailbox as a reference.
|
// Get mailbox as a reference.
|
||||||
//
|
//
|
||||||
loop {
|
match context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) {
|
||||||
// TODO: Show progress visually
|
Ok(_) => {}
|
||||||
if context.accounts[self.cursor_pos.0]
|
Err(_) => {
|
||||||
.status(self.cursor_pos.1)
|
self.content = CellBuffer::new(MAX_COLS, 1, Cell::with_char(' '));
|
||||||
.is_ok()
|
self.length = 0;
|
||||||
{
|
write_string_to_grid(
|
||||||
break;
|
"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]
|
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.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(' '));
|
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||||
if self.length == 0 {
|
if self.length == 0 {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
|
@ -118,25 +135,19 @@ impl ThreadListing {
|
||||||
let threads = &mailbox.collection.threads;
|
let threads = &mailbox.collection.threads;
|
||||||
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
|
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
|
||||||
let thread_nodes: &Vec<ThreadNode> = &threads.thread_nodes();
|
let thread_nodes: &Vec<ThreadNode> = &threads.thread_nodes();
|
||||||
let mut iter = threads.root_iter().peekable();
|
self.locations = threads.threads_iter().collect();
|
||||||
let len = threads.root_len().to_string().chars().count();
|
let mut iter = self.locations.iter().peekable();
|
||||||
/* This is just a desugared for loop so that we can use .peek() */
|
/* This is just a desugared for loop so that we can use .peek() */
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
while let Some(i) = iter.next() {
|
while let Some((indentation, i)) = iter.next() {
|
||||||
let thread_node = &thread_nodes[i];
|
let thread_node = &thread_nodes[*i];
|
||||||
|
|
||||||
if !thread_node.has_message() {
|
if *indentation == 0 {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let indentation = thread_node.indentation();
|
|
||||||
|
|
||||||
if indentation == 0 {
|
|
||||||
thread_idx += 1;
|
thread_idx += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&x) if thread_nodes[x].indentation() == indentation => {
|
Some((x, _)) if *x == *indentation => {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
indentations.push(true);
|
indentations.push(true);
|
||||||
}
|
}
|
||||||
|
@ -145,10 +156,11 @@ impl ThreadListing {
|
||||||
indentations.push(false);
|
indentations.push(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if threads.has_sibling(i) {
|
if threads.has_sibling(*i) {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
indentations.push(true);
|
indentations.push(true);
|
||||||
}
|
}
|
||||||
|
if thread_node.has_message() {
|
||||||
let envelope: &Envelope = &mailbox.collection[&thread_node.message().unwrap()];
|
let envelope: &Envelope = &mailbox.collection[&thread_node.message().unwrap()];
|
||||||
let fg_color = if !envelope.is_seen() {
|
let fg_color = if !envelope.is_seen() {
|
||||||
Color::Byte(0)
|
Color::Byte(0)
|
||||||
|
@ -166,11 +178,11 @@ impl ThreadListing {
|
||||||
&ThreadListing::make_thread_entry(
|
&ThreadListing::make_thread_entry(
|
||||||
envelope,
|
envelope,
|
||||||
idx,
|
idx,
|
||||||
indentation,
|
*indentation,
|
||||||
i,
|
*i,
|
||||||
threads,
|
threads,
|
||||||
&indentations,
|
&indentations,
|
||||||
len,
|
self.length,
|
||||||
// context.accounts[self.cursor_pos.0].backend.operation(envelope.hash())
|
// context.accounts[self.cursor_pos.0].backend.operation(envelope.hash())
|
||||||
),
|
),
|
||||||
&mut self.content,
|
&mut self.content,
|
||||||
|
@ -183,13 +195,19 @@ impl ThreadListing {
|
||||||
self.content[(x, idx)].set_ch(' ');
|
self.content[(x, idx)].set_ch(' ');
|
||||||
self.content[(x, idx)].set_bg(bg_color);
|
self.content[(x, idx)].set_bg(bg_color);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for x in 0..MAX_COLS {
|
||||||
|
self.content[(x, idx)].set_ch(' ');
|
||||||
|
self.content[(x, idx)].set_bg(Color::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&x) if thread_nodes[x].indentation() > indentation => {
|
Some((x, _)) if *x > *indentation => {
|
||||||
indentations.push(false);
|
indentations.push(false);
|
||||||
}
|
}
|
||||||
Some(&x) if thread_nodes[x].indentation() < indentation => {
|
Some((x, _)) if *x < *indentation => {
|
||||||
for _ in 0..(indentation - thread_nodes[x].indentation()) {
|
for _ in 0..(*indentation - *x) {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +221,13 @@ impl ThreadListing {
|
||||||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.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() {
|
let fg_color = if !envelope.is_seen() {
|
||||||
Color::Byte(0)
|
Color::Byte(0)
|
||||||
|
@ -224,12 +248,20 @@ impl ThreadListing {
|
||||||
bg_color,
|
bg_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
|
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]
|
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let envelope: &Envelope = mailbox.thread_to_mail(idx);
|
if mailbox.len() == 0 || mailbox.len() <= idx {
|
||||||
|
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() {
|
let fg_color = if !envelope.is_seen() {
|
||||||
Color::Byte(0)
|
Color::Byte(0)
|
||||||
|
@ -247,6 +279,7 @@ impl ThreadListing {
|
||||||
};
|
};
|
||||||
change_colors(grid, area, fg_color, bg_color);
|
change_colors(grid, area, fg_color, bg_color);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw the list of `Envelope`s.
|
/// Draw the list of `Envelope`s.
|
||||||
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
@ -266,7 +299,25 @@ impl ThreadListing {
|
||||||
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
|
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
|
||||||
|
|
||||||
let top_idx = page_no * 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
|
/* If cursor position has changed, remove the highlight from the previous position and
|
||||||
* apply it in the new one. */
|
* apply it in the new one. */
|
||||||
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
|
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);
|
context.dirty_areas.push_back(area);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* Mark message as read */
|
|
||||||
let idx = self.cursor_pos.2;
|
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 = {
|
let must_highlight = {
|
||||||
if self.length == 0 {
|
if self.length == 0 {
|
||||||
false
|
false
|
||||||
|
@ -411,7 +480,8 @@ impl Component for ThreadListing {
|
||||||
let account = &mut context.accounts[self.cursor_pos.0];
|
let account = &mut context.accounts[self.cursor_pos.0];
|
||||||
let (hash, is_seen) = {
|
let (hash, is_seen) = {
|
||||||
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
||||||
let envelope: &mut Envelope = mailbox.thread_to_mail_mut(idx);
|
let envelope: &mut Envelope =
|
||||||
|
mailbox.thread_to_mail_mut(self.locations[self.new_cursor_pos.2].1);
|
||||||
(envelope.hash(), envelope.is_seen())
|
(envelope.hash(), envelope.is_seen())
|
||||||
};
|
};
|
||||||
if !is_seen {
|
if !is_seen {
|
||||||
|
@ -424,7 +494,8 @@ impl Component for ThreadListing {
|
||||||
backend.operation(hash, folder_hash)
|
backend.operation(hash, folder_hash)
|
||||||
};
|
};
|
||||||
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
||||||
let envelope: &mut Envelope = mailbox.thread_to_mail_mut(idx);
|
let envelope: &mut Envelope =
|
||||||
|
mailbox.thread_to_mail_mut(self.locations[self.new_cursor_pos.2].1);
|
||||||
envelope.set_seen(op).unwrap();
|
envelope.set_seen(op).unwrap();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -476,7 +547,7 @@ impl Component for ThreadListing {
|
||||||
let coordinates = (
|
let coordinates = (
|
||||||
self.cursor_pos.0,
|
self.cursor_pos.0,
|
||||||
self.cursor_pos.1,
|
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));
|
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 {
|
if *idxa == self.new_cursor_pos.0 && *idxf == self.new_cursor_pos.1 {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
self.refresh_mailbox(context);
|
self.refresh_mailbox(context);
|
||||||
|
eprintln!("mailboxupdate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UIEventType::ChangeMode(UIMode::Normal) => {
|
UIEventType::ChangeMode(UIMode::Normal) => {
|
||||||
|
@ -596,8 +668,8 @@ impl Component for ThreadListing {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
UIEventType::Action(ref action) => match action {
|
UIEventType::Action(ref action) => match action {
|
||||||
Action::ViewMailbox(idx) => {
|
Action::ViewMailbox(idx_m) => {
|
||||||
self.new_cursor_pos.1 = *idx;
|
self.new_cursor_pos.1 = *idx_m;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
self.refresh_mailbox(context);
|
self.refresh_mailbox(context);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -70,8 +70,7 @@ impl AccountMenu {
|
||||||
}
|
}
|
||||||
entries
|
entries
|
||||||
},
|
},
|
||||||
})
|
}).collect();
|
||||||
.collect();
|
|
||||||
AccountMenu {
|
AccountMenu {
|
||||||
accounts,
|
accounts,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
|
|
|
@ -32,6 +32,9 @@ pub use mail::*;
|
||||||
|
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
|
|
||||||
|
pub mod indexer;
|
||||||
|
pub use self::indexer::*;
|
||||||
|
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
pub use self::utilities::*;
|
pub use self::utilities::*;
|
||||||
|
|
||||||
|
|
|
@ -59,3 +59,6 @@ pub use components::*;
|
||||||
|
|
||||||
pub mod conf;
|
pub mod conf;
|
||||||
pub use conf::*;
|
pub use conf::*;
|
||||||
|
|
||||||
|
pub mod workers;
|
||||||
|
pub use workers::*;
|
||||||
|
|
|
@ -100,14 +100,14 @@ impl Context {
|
||||||
pub fn restore_input(&self) {
|
pub fn restore_input(&self) {
|
||||||
self.input.restore(self.sender.clone());
|
self.input.restore(self.sender.clone());
|
||||||
}
|
}
|
||||||
pub fn account_status(&mut self, idx_a: usize, idx_m: usize) -> result::Result<bool, usize> {
|
pub fn account_status(&mut self, idx_a: usize, idx_m: usize) -> result::Result<(), usize> {
|
||||||
match self.accounts[idx_a].status(idx_m) {
|
match self.accounts[idx_a].status(idx_m) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
self.replies.push_back(UIEvent {
|
self.replies.push_back(UIEvent {
|
||||||
id: 0,
|
id: 0,
|
||||||
event_type: UIEventType::MailboxUpdate((idx_a, idx_m)),
|
event_type: UIEventType::MailboxUpdate((idx_a, idx_m)),
|
||||||
});
|
});
|
||||||
Ok(true)
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(n) => Err(n),
|
Err(n) => Err(n),
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,7 @@ pub struct State {
|
||||||
entities: Vec<Entity>,
|
entities: Vec<Entity>,
|
||||||
pub context: Context,
|
pub context: Context,
|
||||||
threads: FnvHashMap<thread::ThreadId, (chan::Sender<bool>, thread::JoinHandle<()>)>,
|
threads: FnvHashMap<thread::ThreadId, (chan::Sender<bool>, thread::JoinHandle<()>)>,
|
||||||
|
work_controller: WorkController,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for State {
|
impl Drop for State {
|
||||||
|
@ -155,7 +156,7 @@ impl State {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
/* Create a channel to communicate with other threads. The main process is the sole receiver.
|
/* Create a channel to communicate with other threads. The main process is the sole receiver.
|
||||||
* */
|
* */
|
||||||
let (sender, receiver) = chan::sync(::std::mem::size_of::<ThreadEvent>());
|
let (sender, receiver) = chan::sync(32 * ::std::mem::size_of::<ThreadEvent>());
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create async channel to block the input-thread if we need to fork and stop it from reading
|
* 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()),
|
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!(
|
write!(
|
||||||
s.stdout(),
|
s.stdout(),
|
||||||
"{}{}{}{}",
|
"{}{}{}{}",
|
||||||
|
@ -239,6 +251,11 @@ impl State {
|
||||||
s.restore_input();
|
s.restore_input();
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn worker_receiver(&mut self) -> chan::Receiver<bool> {
|
||||||
|
self.work_controller.results_rx()
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When we receive a folder hash from a watcher thread,
|
* When we receive a folder hash from a watcher thread,
|
||||||
* we match the hash to the index of the mailbox, request a reload
|
* we match the hash to the index of the mailbox, request a reload
|
||||||
|
@ -252,15 +269,18 @@ impl State {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(notification) = self.context.accounts[idxa].reload(event, idxm) {
|
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 {
|
self.context.replies.push_back(UIEvent {
|
||||||
id: 0,
|
id: 0,
|
||||||
event_type: notification,
|
event_type: notification,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
self.context.replies.push_back(UIEvent {
|
self.context.replies.push_back(UIEvent {
|
||||||
id: 0,
|
id: 0,
|
||||||
event_type: UIEventType::MailboxUpdate((idxa, idxm)),
|
event_type: UIEventType::MailboxUpdate((idxa, idxm)),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"BUG: mailbox with hash {} not found in mailbox_hashes.",
|
"BUG: mailbox with hash {} not found in mailbox_hashes.",
|
||||||
|
|
|
@ -34,11 +34,16 @@ use std::mem;
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
|
||||||
use types::UIEventType::{self, Notification};
|
use types::UIEventType::{self, Notification};
|
||||||
|
|
||||||
pub type Worker = Option<Async<Result<Mailbox>>>;
|
pub type Worker = Option<Async<Result<Mailbox>>>;
|
||||||
|
|
||||||
|
macro_rules! mailbox {
|
||||||
|
($idx:expr, $folders:expr) => {
|
||||||
|
$folders[$idx].as_mut().unwrap().as_mut().unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -66,13 +71,9 @@ impl Account {
|
||||||
let notify_fn = Arc::new(notify_fn);
|
let notify_fn = Arc::new(notify_fn);
|
||||||
for f in ref_folders {
|
for f in ref_folders {
|
||||||
folders.push(None);
|
folders.push(None);
|
||||||
workers.push(Account::new_worker(
|
workers.push(Account::new_worker(f, &mut backend, notify_fn.clone()));
|
||||||
&name,
|
|
||||||
f,
|
|
||||||
&mut backend,
|
|
||||||
notify_fn.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
eprintln!("sent_folder for {} is {:?}", name, sent_folder);
|
||||||
Account {
|
Account {
|
||||||
name,
|
name,
|
||||||
folders,
|
folders,
|
||||||
|
@ -85,56 +86,56 @@ impl Account {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn new_worker(
|
fn new_worker(
|
||||||
name: &str,
|
|
||||||
folder: Folder,
|
folder: Folder,
|
||||||
backend: &mut Box<MailBackend>,
|
backend: &mut Box<MailBackend>,
|
||||||
notify_fn: Arc<NotifyFn>,
|
notify_fn: Arc<NotifyFn>,
|
||||||
) -> Worker {
|
) -> Worker {
|
||||||
let mailbox_handle = backend.get(&folder, notify_fn.clone());
|
let mailbox_handle = backend.get(&folder);
|
||||||
let mut builder = AsyncBuilder::new();
|
let mut builder = AsyncBuilder::new();
|
||||||
let tx = builder.tx();
|
let tx = builder.tx();
|
||||||
Some(
|
Some(builder.build(Box::new(move || {
|
||||||
builder.build(
|
let mut handle = mailbox_handle.clone();
|
||||||
thread::Builder::new()
|
let folder = folder.clone();
|
||||||
.name(format!("Loading {}", name))
|
let work = handle.work().unwrap();
|
||||||
.spawn(move || {
|
work.compute();
|
||||||
let envelopes = mailbox_handle.join();
|
handle.join();
|
||||||
|
let envelopes = handle.extract();
|
||||||
let ret = Mailbox::new(folder, envelopes);
|
let ret = Mailbox::new(folder, envelopes);
|
||||||
tx.send(AsyncStatus::Finished);
|
tx.send(AsyncStatus::Payload(ret));
|
||||||
notify_fn.notify();
|
notify_fn.notify();
|
||||||
ret
|
})))
|
||||||
}).unwrap(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
pub fn reload(&mut self, event: RefreshEvent, idx: usize) -> Option<UIEventType> {
|
pub fn reload(&mut self, event: RefreshEvent, idx: usize) -> Option<UIEventType> {
|
||||||
let kind = event.kind();
|
let kind = event.kind();
|
||||||
{
|
{
|
||||||
let mailbox: &mut Mailbox = self.folders[idx].as_mut().unwrap().as_mut().unwrap();
|
//let mailbox: &mut Mailbox = self.folders[idx].as_mut().unwrap().as_mut().unwrap();
|
||||||
match kind {
|
match kind {
|
||||||
RefreshEventKind::Update(old_hash, envelope) => {
|
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) => {
|
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<Folder> = self.backend.folders();
|
let ref_folders: Vec<Folder> = self.backend.folders();
|
||||||
return Some(Notification(
|
return Some(Notification(
|
||||||
Some("new mail".into()),
|
Some("new mail".into()),
|
||||||
format!(
|
format!(
|
||||||
"{:.15}:\nSubject: {:.15}\nFrom: {:.15}",
|
"{:<15}:\nSubject: {:<15}\nFrom: {:<15}",
|
||||||
ref_folders[idx].name(),
|
ref_folders[idx].name(),
|
||||||
env.subject(),
|
env.subject(),
|
||||||
env.field_from_to_string()
|
env.field_from_to_string(),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
RefreshEventKind::Remove(envelope_hash) => {
|
RefreshEventKind::Remove(envelope_hash) => {
|
||||||
mailbox.remove(envelope_hash);
|
mailbox!(idx, self.folders).remove(envelope_hash);
|
||||||
}
|
}
|
||||||
RefreshEventKind::Rescan => {
|
RefreshEventKind::Rescan => {
|
||||||
let ref_folders: Vec<Folder> = self.backend.folders();
|
let ref_folders: Vec<Folder> = self.backend.folders();
|
||||||
let handle = Account::new_worker(
|
let handle = Account::new_worker(
|
||||||
&self.name,
|
|
||||||
ref_folders[idx].clone(),
|
ref_folders[idx].clone(),
|
||||||
&mut self.backend,
|
&mut self.backend,
|
||||||
self.notify_fn.clone(),
|
self.notify_fn.clone(),
|
||||||
|
@ -143,9 +144,6 @@ impl Account {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.workers[idx].is_some() {
|
|
||||||
self.folders[idx] = None;
|
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pub fn watch(&self, r: RefreshEventConsumer) -> () {
|
pub fn watch(&self, r: RefreshEventConsumer) -> () {
|
||||||
|
@ -180,6 +178,8 @@ impl Account {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_mailbox(&mut self, index: usize, mailbox: Result<Mailbox>) {
|
fn load_mailbox(&mut self, index: usize, mailbox: Result<Mailbox>) {
|
||||||
|
self.folders[index] = Some(mailbox);
|
||||||
|
/*
|
||||||
if self.sent_folder.is_some() && self.sent_folder.unwrap() == index {
|
if self.sent_folder.is_some() && self.sent_folder.unwrap() == index {
|
||||||
self.folders[index] = Some(mailbox);
|
self.folders[index] = Some(mailbox);
|
||||||
/* Add our replies to other folders */
|
/* Add our replies to other folders */
|
||||||
|
@ -190,6 +190,7 @@ impl Account {
|
||||||
self.folders[index] = Some(mailbox);
|
self.folders[index] = Some(mailbox);
|
||||||
self.add_replies_to_folder(index);
|
self.add_replies_to_folder(index);
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_replies_to_folder(&mut self, folder_index: usize) {
|
fn add_replies_to_folder(&mut self, folder_index: usize) {
|
||||||
|
@ -231,7 +232,7 @@ impl Account {
|
||||||
None => {
|
None => {
|
||||||
return Ok(());
|
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) => {
|
Ok(AsyncStatus::NoUpdate) => {
|
||||||
return Err(0);
|
return Err(0);
|
||||||
}
|
}
|
||||||
|
@ -239,13 +240,15 @@ impl Account {
|
||||||
Ok(AsyncStatus::ProgressReport(n)) => {
|
Ok(AsyncStatus::ProgressReport(n)) => {
|
||||||
return Err(n);
|
return Err(n);
|
||||||
}
|
}
|
||||||
a => {
|
_ => {
|
||||||
eprintln!("Error: {:?}", a);
|
|
||||||
return Err(0);
|
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.workers[index] = None;
|
||||||
self.load_mailbox(index, m);
|
self.load_mailbox(index, m);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -627,15 +627,15 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area,
|
||||||
src_x += 1;
|
src_x += 1;
|
||||||
}
|
}
|
||||||
src_x = get_x(upper_left!(src));
|
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(
|
clear_area(
|
||||||
grid_dest,
|
grid_dest,
|
||||||
((get_x(upper_left!(dest)), y), bottom_right!(dest)),
|
((get_x(upper_left!(dest)), y + 1), bottom_right!(dest)),
|
||||||
);
|
);
|
||||||
ret.1 = y;
|
ret.1 = y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
src_y += 1;
|
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Work>,
|
||||||
|
thread_end_tx: chan::Sender<bool>,
|
||||||
|
results: Option<chan::Receiver<bool>>,
|
||||||
|
threads: Vec<std::thread::JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorkController {
|
||||||
|
pub fn results_rx(&mut self) -> chan::Receiver<bool> {
|
||||||
|
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<T: Send> {
|
||||||
|
inner: Arc<Mutex<VecDeque<T>>>,
|
||||||
|
new_jobs_tx: chan::Sender<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Send> WorkQueue<T> {
|
||||||
|
// 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<bool>) -> 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<T> {
|
||||||
|
// 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<MutexGuard<VecDeque<T>>>.
|
||||||
|
|
||||||
|
// 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<VecDeque>, 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<VecDeque<T>> 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<Work> = 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::<bool>());
|
||||||
|
|
||||||
|
// 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.");}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
Loading…
Reference in New Issue