Partially working migration to async/await

This commit is contained in:
2021-03-08 17:30:07 -05:00
parent 01a42cebf6
commit 3a15ec26b2
14 changed files with 1219 additions and 1077 deletions

1763
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,33 +8,33 @@ edition = "2018"
#maildir = { git = "https://github.com/lewisdiamond/maildir.git" } #maildir = { git = "https://github.com/lewisdiamond/maildir.git" }
maildir = { path = "/home/ldiamond/dev/maildir/" } maildir = { path = "/home/ldiamond/dev/maildir/" }
#maildir = "0.4.2" #maildir = "0.4.2"
html2text = "0.2.0" html2text = "0.2.1"
#html2text = { git = "https://github.com/lewisdiamond/rust-html2text.git"} #html2text = { git = "https://github.com/lewisdiamond/rust-html2text.git"}
#mailparse = "0.13.0" #mailparse = "0.13.0"
mailparse = { path = "/home/ldiamond/dev/mailparse/" } mailparse = { path = "/home/ldiamond/dev/mailparse/" }
rayon = "1.4.0" rayon = "1.5.0"
tantivy = "0.13.0" tantivy = "0.13.2"
tempdir = "0.3.7" tempdir = "0.3.7"
serde_json = "1.0.57" serde_json = "1.0.61"
serde = { version = "1.0.115", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
structopt = "0.3.17" structopt = "0.3.21"
shellexpand = "2.0.0" shellexpand = "2.1.0"
log = { version = "0.4.11", features = ["max_level_debug", "release_max_level_warn"] } log = { version = "0.4.11", features = ["max_level_debug", "release_max_level_warn"] }
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
pbr = "1.0.3" pbr = "1.0.3"
num_cpus = "1.13.0" num_cpus = "1.13.0"
sys-info = "0.7.0" sys-info = "0.7.0"
tui = "0.10.0" tui = "0.13.0"
termion = "1.5.5" termion = "1.5.5"
chrono = "0.4.15" chrono = "0.4.19"
sha2 = "0.9.1" sha2 = "0.9.2"
html5ever = "0.25.1" html5ever = "0.25.1"
rocksdb = "0.15.0" rocksdb = "0.15.0"
jemallocator = "0.3.2" jemallocator = "0.3.2"
select = "0.5.0" select = "0.5.0"
futures = "0.3" futures = "0.3.8"
tokio = { version = "0.2", features = ["full"] } tokio = { version = "1.0.1", features = ["full"] }
async-trait = "0.1.40" async-trait = "0.1.42"
[dev-dependencies] [dev-dependencies]
rand = "0.7.3" rand = "0.8.0"

View File

@@ -18,13 +18,11 @@ fn main() {
println!("From: {}", message.from); println!("From: {}", message.from);
println!("To: {}", message.recipients.join(", ")); println!("To: {}", message.recipients.join(", "));
println!("Subject: {}", message.subject); println!("Subject: {}", message.subject);
for b in message.body { let body = message.get_body(None);
println!("Body Mime: {}", b.mime.as_str()); match body.mime {
match b.mime { Mime::PlainText => println!("\n\n{}", body.value),
Mime::PlainText => println!("\n\n{}", b.value), Mime::Html => println!("\n\n{}", html2text(&body.value)),
Mime::Html => println!("\n\n{}", html2text(&b.value)), _ => println!("Unknown mime type"),
_ => println!("Unknown mime type"),
}
} }
} }
Err(_e) => error!("Failed to make sense of the message"), Err(_e) => error!("Failed to make sense of the message"),

View File

@@ -1,5 +1,6 @@
use log::{error, info, trace}; use log::{error, info, trace};
use rms::cmd::{opts, Command, OutputType}; use rms::cmd::{opts, Command};
use rms::readmail::display::{OutputType, DisplayAs};
use rms::message::{Body, Mime}; use rms::message::{Body, Mime};
use rms::stores::{IMessageStore, MessageStoreBuilder, Searchers, Storages}; use rms::stores::{IMessageStore, MessageStoreBuilder, Searchers, Storages};
use rms::terminal; use rms::terminal;
@@ -25,27 +26,27 @@ pub async fn main() {
} }
let message_store = MessageStoreBuilder::new() let message_store = MessageStoreBuilder::new()
.storage(Storages::Tantivy(index_dir_path.clone())) .storage(Storages::Tantivy(index_dir_path.clone()))
.searcher(Searchers::Tantivy(index_dir_path)) .searcher(Searchers::Tantivy(index_dir_path.clone()))
.debug(debug) .debug(debug)
.build(); .build();
match message_store { match message_store {
Ok(mut store) => { Ok(mut store) => {
maildir_path.into_iter().for_each(|m| { for m in maildir_path {
println!("Adding maildir at {}", m.to_str().unwrap()); println!("Adding maildir at {}", m.to_str().unwrap());
match store.add_maildir(m.clone(), full) { match store.add_maildir(m.clone(), full).await {
Err(e) => error!( Err(e) => error!(
"Failed to add mails from {}, detauls: {}", "Failed to add mails from {}, detauls: {}",
m.to_str().unwrap(), m.to_str().unwrap(),
e e
), ),
Ok(_) => println!("Successfully added {}", m.to_str().unwrap()), Ok(_) => println!("Successfully added {}", m.to_str().unwrap()),
} };
}); }
} }
Err(e) => { Err(e) => {
error!("{}", e); error!("{}", e);
} }
} };
//maildir_path[0].clone(), index_dir_path); //maildir_path[0].clone(), index_dir_path);
//if let Some(threads) = threads { //if let Some(threads) = threads {
// indexer_builder.threads(threads); // indexer_builder.threads(threads);
@@ -56,7 +57,7 @@ pub async fn main() {
//let mut indexer = indexer_builder.build(); //let mut indexer = indexer_builder.build();
//message_store.index_mails(full); //message_store.index_mails(full);
} }
Command::Search { term, output, num } => { Command::Search { term, output, num, advanced } => {
let message_store = MessageStoreBuilder::new() let message_store = MessageStoreBuilder::new()
.storage(Storages::Tantivy(index_dir_path.clone())) .storage(Storages::Tantivy(index_dir_path.clone()))
.searcher(Searchers::Tantivy(index_dir_path)) .searcher(Searchers::Tantivy(index_dir_path))
@@ -66,36 +67,39 @@ pub async fn main() {
match message_store { match message_store {
Ok(store) => { Ok(store) => {
let results = store.search_fuzzy(term, num).ok().unwrap(); let results = store.search_fuzzy(term, num).ok().unwrap();
match output { for r in results {
OutputType::Short => { print!("{}", r.display(&output));
for r in results {
println!("{:?} | {}", r.id, r.subject);
}
} }
OutputType::Full => { //match output {
println!("{:?}", results); // OutputType::Short => {
} // for r in results {
OutputType::Raw => { // println!("{:?} | {}", r.id, r.subject);
let mut out = io::stdout(); // }
for result in results { // }
out.write_all(result.original.as_ref()).unwrap(); // OutputType::Full => {
} // println!("{:?}", results);
} // }
OutputType::Html => { // OutputType::Raw => {
for m in results { // let mut out = io::stdout();
println!( // for result in results {
"{}", // out.write_all(result.original.as_ref()).unwrap();
m.body // }
.iter() // }
.filter(|x| x.mime == Mime::Html) // OutputType::Html => {
.collect::<Vec<&Body>>() // for m in results {
.first() // println!(
.map(|b| b.value.clone()) // "{}",
.unwrap_or_else(|| "No body".to_string()) // m.body
); // .iter()
} // .filter(|x| x.mime == Mime::Html)
} // .collect::<Vec<&Body>>()
} // .first()
// .map(|b| b.value.clone())
// .unwrap_or_else(|| "No body".to_string())
// );
// }
// }
// }
} }
Err(e) => error!("{}", e), Err(e) => error!("{}", e),
} }
@@ -124,46 +128,7 @@ pub async fn main() {
Ok(store) => { Ok(store) => {
let result = store.get_message(id); let result = store.get_message(id);
match result { match result {
Ok(Some(good_msg)) => match output { Ok(Some(good_msg)) => println!("{}", good_msg.display(&output)),
OutputType::Short => {
println!("{} | {}", good_msg.id, good_msg.subject);
}
OutputType::Raw => {
io::stdout().write_all(good_msg.original.as_ref()).unwrap();
}
OutputType::Full => {
println!("From: {}", good_msg.from);
println!(
"To: {}",
good_msg
.recipients
.get(0)
.unwrap_or(&String::from("Unknown"))
);
println!("Subject: {}", good_msg.subject);
println!(
"{}",
good_msg
.body
.first()
.map(|b| b.value.clone())
.unwrap_or_else(|| "No body".to_string())
);
}
OutputType::Html => {
println!(
"{}",
good_msg
.body
.iter()
.filter(|x| x.mime == Mime::Html)
.collect::<Vec<&Body>>()
.first()
.map(|b| b.value.clone())
.unwrap_or_else(|| "No body".to_string())
);
}
},
Ok(None) => error!("Message not found"), Ok(None) => error!("Message not found"),
Err(e) => error!("ERROR {}", e), Err(e) => error!("ERROR {}", e),
} }
@@ -174,18 +139,18 @@ pub async fn main() {
Command::Interactive {} => { Command::Interactive {} => {
terminal::start(index_dir_path).unwrap(); terminal::start(index_dir_path).unwrap();
} }
Command::Latest { num: _num } => { Command::Latest { num: _num, skip, output } => {
let message_store = MessageStoreBuilder::new() let message_store = MessageStoreBuilder::new()
.storage(Storages::Tantivy(index_dir_path.clone())) .storage(Storages::Tantivy(index_dir_path.clone()))
.searcher(Searchers::Tantivy(index_dir_path)) .searcher(Searchers::Tantivy(index_dir_path))
.build(); .build();
match message_store { match message_store {
Ok(store) => { Ok(store) => {
let page = store.get_messages_page(0, _num); let page = store.get_messages_page(skip, _num);
match page { match page {
Ok(msgs) => { Ok(msgs) => {
for m in msgs { for m in msgs {
println!("{}", m.id); println!("{}", m.display(&output));
} }
} }
Err(e) => println!("Could not read messages, {}", e), Err(e) => println!("Could not read messages, {}", e),

View File

@@ -1,8 +1,7 @@
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use std::{error, fmt};
use structopt::StructOpt; use structopt::StructOpt;
use crate::readmail::display::OutputType;
pub fn expand_path(input: &OsStr) -> PathBuf { pub fn expand_path(input: &OsStr) -> PathBuf {
let input_str = input let input_str = input
@@ -14,49 +13,8 @@ pub fn expand_path(input: &OsStr) -> PathBuf {
PathBuf::from(expanded) PathBuf::from(expanded)
} }
#[derive(Debug)]
pub enum OutputType {
Short,
Full,
Raw,
Html,
}
#[derive(Debug)]
pub enum OutputTypeError {
UnknownTypeError,
}
impl fmt::Display for OutputTypeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Could not figure out output format")
}
}
// This is important for other errors to wrap this one.
impl std::error::Error for OutputTypeError {
fn description(&self) -> &str {
"invalid first item to double"
}
fn cause(&self) -> Option<&dyn error::Error> {
// Generic error, underlying cause isn't tracked.
None
}
}
impl FromStr for OutputType {
type Err = OutputTypeError;
fn from_str(input: &str) -> Result<OutputType, Self::Err> {
match input.to_lowercase().as_str() {
"short" => Ok(OutputType::Short),
"full" => Ok(OutputType::Full),
"raw" => Ok(OutputType::Raw),
"html" => Ok(OutputType::Html),
_ => Err(OutputTypeError::UnknownTypeError),
}
}
}
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt( #[structopt(
@@ -115,6 +73,9 @@ pub enum Command {
#[structopt(short, long, default_value = "100")] #[structopt(short, long, default_value = "100")]
num: usize, num: usize,
#[structopt(short, long)]
advanced: bool,
}, },
#[structopt(name = "date", rename_all = "kebab-case")] #[structopt(name = "date", rename_all = "kebab-case")]
Date { term: i64 }, Date { term: i64 },
@@ -131,6 +92,10 @@ pub enum Command {
Latest { Latest {
#[structopt(short, long)] #[structopt(short, long)]
num: usize, num: usize,
#[structopt(short, long, default_value = "0")]
skip: usize,
#[structopt(short, long, default_value = "short")]
output: OutputType,
}, },
#[structopt(name = "tag")] #[structopt(name = "tag")]

View File

@@ -8,7 +8,8 @@ use sha2::{Digest, Sha512};
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::AsRef; use std::convert::AsRef;
use std::fmt; use std::fmt;
use std::string::ToString; use crate::readmail::display::{DisplayAs, OutputType};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Mime { pub enum Mime {
@@ -106,16 +107,21 @@ pub fn get_id(data: &[u8]) -> String {
format!("{:x}", Sha512::digest(data)) format!("{:x}", Sha512::digest(data))
} }
impl ToString for ShortMessage { impl fmt::Display for ShortMessage {
fn to_string(&self) -> String { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let dt = Local.timestamp(self.date as i64, 0); let dt = Local.timestamp(self.date as i64, 0);
let dstr = dt.format("%a %b %e %T %Y").to_string(); let dstr = dt.format("%a %b %e %T %Y").to_string();
format!("{}: [{}] {}", dstr, self.from, self.subject.as_str()) write!(f, "{}: [{}] {}", dstr, self.from, self.subject.as_str())
} }
} }
impl ToString for Message { impl fmt::Display for Message {
fn to_string(&self) -> String { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.display(&OutputType::Short))
}
}
impl DisplayAs for Message {
fn display(&self, t: &OutputType) -> String {
let dt = Local.timestamp(self.date as i64, 0); let dt = Local.timestamp(self.date as i64, 0);
let dstr = dt.format("%a %b %e %T %Y").to_string(); let dstr = dt.format("%a %b %e %T %Y").to_string();
let tags = if self.tags.is_empty() { let tags = if self.tags.is_empty() {
@@ -123,13 +129,28 @@ impl ToString for Message {
} else { } else {
String::from("") String::from("")
}; };
format!( match t {
"{} {}: [{}] {}", OutputType::Short => format!("{} | {} | {}", self.short_id(), dstr, self.subject.as_str()),
tags, OutputType::Full => format!(
dstr, r#"
From: {}
to/cc/bcc: {}
Date: {}
Subject: {}
{}
# {}
"#,
self.from, self.from,
self.subject.as_str() self.recipients.join(","),
) dstr,
self.subject,
self.get_body(None).as_text(), self.id
),
OutputType::Html => format!("{}", self.get_body(Some(Mime::Html)).as_text()),
OutputType::Summary => format!("{} | {} [{}]", dstr, self.subject.as_str(), self.from),
OutputType::Raw => String::from_utf8(self.original.clone()).unwrap_or(String::from("BAD FILE, please open an issue")),
}
} }
} }
@@ -146,7 +167,8 @@ impl MessageError {
} }
impl Message { impl Message {
pub fn from_parsedmail(msg: &ParsedMail, id: String) -> Result<Self, MessageError> { pub fn from_parsedmail(msg: &ParsedMail) -> Result<Self, MessageError> {
let id = get_id(msg.data);
let original = Vec::from(msg.data); let original = Vec::from(msg.data);
let headers = &msg.headers; let headers = &msg.headers;
let mut subject: String = "".to_string(); let mut subject: String = "".to_string();
@@ -193,42 +215,26 @@ impl Message {
}) })
} }
pub fn from_data(data: Vec<u8>) -> Result<Self, MessageError> { pub fn from_data(data: Vec<u8>) -> Result<Self, MessageError> {
let id = get_id(data.as_ref());
let parsed_mail = parse_mail(data.as_slice()).map_err(|_| MessageError { let parsed_mail = parse_mail(data.as_slice()).map_err(|_| MessageError {
message: String::from("Unable to parse email data"), message: String::from("Unable to parse email data"),
})?; })?;
Self::from_parsedmail(&parsed_mail, id) Self::from_parsedmail(&parsed_mail)
} }
pub fn from_mailentry(mut mailentry: MailEntry) -> Result<Self, MessageError> { pub fn from_mailentry(mut mailentry: MailEntry) -> Result<Self, MessageError> {
let id = mailentry.id().to_owned();
match mailentry.parsed() { match mailentry.parsed() {
Ok(parsed) => Self::from_parsedmail(&parsed, id), Ok(parsed) => Self::from_parsedmail(&parsed),
Err(_) => Err(MessageError { Err(_) => Err(MessageError {
message: format!("Failed to parse email id {}", id), message: format!("Failed to parse email id {}", mailentry.id()),
}), }),
} }
} }
pub fn get_body(&self) -> &Body { pub fn get_body(&self, mime: Option<Mime>) -> &Body {
self.body.get(0).unwrap() let m = mime.unwrap_or(Mime::PlainText);
self.body.iter().find(|b| b.mime == m ).unwrap_or(self.body.get(0).unwrap())
} }
pub fn to_long_string(&self) -> String {
format!(
r#"
From: {}
to/cc/bcc: {}
Subject: {}
{} pub fn short_id(&self) -> &str{
"#, &self.id[..24]
self.from,
self.recipients.join(","),
self.subject,
self.body
.iter()
.map(|b| b.value.replace('\r', ""))
.collect::<Vec<String>>()
.join("-----")
)
} }
} }
#[allow(dead_code)] #[allow(dead_code)]

64
src/readmail/display.rs Normal file
View File

@@ -0,0 +1,64 @@
use std::fmt;
impl fmt::Display for OutputTypeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Could not figure out output format")
}
}
// This is important for other errors to wrap this one.
impl std::error::Error for OutputTypeError {
fn description(&self) -> &str {
"invalid first item to double"
}
fn cause(&self) -> Option<&dyn std::error::Error> {
// Generic error, underlying cause isn't tracked.
None
}
}
impl std::str::FromStr for OutputType {
type Err = OutputTypeError;
fn from_str(input: &str) -> Result<OutputType, Self::Err> {
match input.to_lowercase().as_str() {
"short" => Ok(OutputType::Short),
"full" => Ok(OutputType::Full),
"raw" => Ok(OutputType::Raw),
"html" => Ok(OutputType::Html),
"summary" => Ok(OutputType::Summary),
_ => Err(OutputTypeError::UnknownTypeError),
}
}
}
impl fmt::Display for OutputType{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = match self {
OutputType::Summary => "Summary",
OutputType::Full => "Full",
OutputType::Html => "Html",
OutputType::Raw => "Raw",
OutputType::Short => "Short",
};
write!(f,"{}", msg)
}
}
#[derive(Debug)]
pub enum OutputTypeError {
UnknownTypeError,
}
#[derive(Debug)]
pub enum OutputType {
Summary,
Short,
Full,
Raw,
Html,
}
pub trait DisplayAs {
fn display(&self, t: &OutputType) -> String;
}

View File

@@ -3,9 +3,11 @@ use crate::message::{Body, Mime};
use log::debug; use log::debug;
use mailparse::*; use mailparse::*;
use select::document::Document; use select::document::Document;
use select::predicate::Text; use select::predicate::{Text, Name, Predicate};
use std::cmp::Ordering; use std::cmp::Ordering;
pub mod display;
fn cmp_body(x: &Body, y: &Body, prefer: &Mime) -> Ordering { fn cmp_body(x: &Body, y: &Body, prefer: &Mime) -> Ordering {
if x.mime == y.mime { if x.mime == y.mime {
return x.value.len().cmp(&y.value.len()); return x.value.len().cmp(&y.value.len());
@@ -66,8 +68,8 @@ pub fn extract_body(msg: &ParsedMail, prefer_html: bool) -> Vec<Body> {
pub fn html2text(text: &str) -> String { pub fn html2text(text: &str) -> String {
let document = Document::from(text); let document = Document::from(text);
let text_nodes = document let body = document.find(Name("body")).nth(0).unwrap();
.find(Text) let text_nodes = body.find(Text)
.map(|x| String::from(x.text().trim())) .map(|x| String::from(x.text().trim()))
.filter(|x| x.len() > 1) .filter(|x| x.len() > 1)
.collect::<Vec<String>>(); .collect::<Vec<String>>();

View File

@@ -1,15 +1,15 @@
use crate::message::{Message, MessageError}; use crate::message::{Message, MessageError};
use crate::stores::{IMessageSearcher, MessageStoreError}; use crate::stores::{IMessageSearcher, MessageStoreError};
use log::info; use log::{error, info};
use std::cmp; use std::cmp;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs; use std::fs;
use std::panic; use std::panic;
use std::path::PathBuf; use std::path::PathBuf;
use std::string::ToString; use std::string::ToString;
use tantivy::collector::TopDocs; use tantivy::collector::{TopDocs, Count};
use tantivy::directory::MmapDirectory; use tantivy::directory::MmapDirectory;
use tantivy::query::{AllQuery, BooleanQuery, FuzzyTermQuery, Occur, Query, TermQuery}; use tantivy::query::{AllQuery, BooleanQuery, FuzzyTermQuery, Occur, Query, TermQuery, RegexQuery};
use tantivy::schema::*; use tantivy::schema::*;
const BYTES_IN_MB: usize = 1024 * 1024; const BYTES_IN_MB: usize = 1024 * 1024;
@@ -218,6 +218,7 @@ impl TantivyStore {
.iter() .iter()
.for_each(|t| document.add_text(email.tag, t.as_str())); .for_each(|t| document.add_text(email.tag, t.as_str()));
indexer.add_document(document); indexer.add_document(document);
indexer.commit();
Ok(msg.id) Ok(msg.id)
} }
None => Err(MessageStoreError::CouldNotAddMessage( None => Err(MessageStoreError::CouldNotAddMessage(
@@ -305,17 +306,14 @@ impl TantivyStore {
) -> Result<Vec<TantivyMessage>, MessageStoreError> { ) -> Result<Vec<TantivyMessage>, MessageStoreError> {
let searcher = self.reader.searcher(); let searcher = self.reader.searcher();
let skip = _skip.unwrap_or(0); let skip = _skip.unwrap_or(0);
let mut docs = searcher let docs = searcher
.search( .search(
&AllQuery, &AllQuery,
&TopDocs::with_limit(num + skip).order_by_u64_field(self.email.date), &TopDocs::with_limit(num).and_offset(skip).order_by_u64_field(self.email.date),
) )
.map_err(|_| MessageStoreError::CouldNotGetMessages(vec![]))?; .map_err(|_| MessageStoreError::CouldNotGetMessages(vec![]))?;
let mut ret = vec![]; let mut ret = vec![];
let page = docs for doc in docs {
.drain(skip..)
.collect::<Vec<(u64, tantivy::DocAddress)>>();
for doc in page {
let retrieved_doc = searcher.doc(doc.1).unwrap(); let retrieved_doc = searcher.doc(doc.1).unwrap();
ret.push( ret.push(
TantivyMessage::from_tantivy(retrieved_doc, &self.email).map_err(|_| { TantivyMessage::from_tantivy(retrieved_doc, &self.email).map_err(|_| {
@@ -329,17 +327,17 @@ impl TantivyStore {
pub fn get_doc(&self, id: &str) -> Result<Document, tantivy::TantivyError> { pub fn get_doc(&self, id: &str) -> Result<Document, tantivy::TantivyError> {
// Is this needed? self.reader.load_searchers()?; // Is this needed? self.reader.load_searchers()?;
let searcher = self.reader.searcher(); let searcher = self.reader.searcher();
let termq = TermQuery::new( let termq = RegexQuery::from_pattern(format!("{}.*",id).as_str(), self.email.id)?;
Term::from_field_text(self.email.id, id), let res = searcher.search(&termq, &(TopDocs::with_limit(1), Count));
IndexRecordOption::Basic, match res {
); Ok((doc, count)) => match doc.first() {
let addr = searcher.search(&termq, &TopDocs::with_limit(1));
match addr {
Ok(doc) => match doc.first() {
Some((_score, doc_address)) => searcher.doc(*doc_address), Some((_score, doc_address)) => searcher.doc(*doc_address),
None => Err(tantivy::TantivyError::InvalidArgument( None => {
error!("Got count {:}", count);
Err(tantivy::TantivyError::InvalidArgument(
"Document not found".to_string(), "Document not found".to_string(),
)), ))
},
}, },
Err(e) => Err(e), Err(e) => Err(e),

View File

@@ -1,22 +1,24 @@
use crate::message::Message; use crate::message::Message;
use crate::stores::{IMessageSearcher, IMessageStorage, IMessageStore, MessageStoreError}; use crate::stores::{IMessageSearcher, IMessageStorage, IMessageStore, MessageStoreError};
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use futures::future::join_all;
use log::error; use log::error;
use maildir::{MailEntry, Maildir}; use maildir::{MailEntry, Maildir};
use pbr::{MultiBar, ProgressBar}; use pbr::{MultiBar, ProgressBar};
use rayon::prelude::*; use std::{collections::HashSet, fmt};
use std::collections::HashSet;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use tokio::sync::mpsc;
pub struct MessageStore { pub struct MessageStore {
pub searcher: Box<dyn IMessageSearcher>, pub searcher: Box<dyn IMessageSearcher + Send + Sync>,
pub storage: Option<Box<dyn IMessageStorage>>, pub storage: Option<Box<dyn IMessageStorage + Send + Sync>>,
progress: Option<ProgressBar<pbr::Pipe>>, progress: Option<ProgressBar<pbr::Pipe>>,
display_progress: bool, display_progress: bool,
} }
#[async_trait]
impl IMessageStore for MessageStore { impl IMessageStore for MessageStore {
fn get_message(&self, id: String) -> Result<Option<Message>, MessageStoreError> { fn get_message(&self, id: String) -> Result<Option<Message>, MessageStoreError> {
self.searcher.get_message(id) self.searcher.get_message(id)
@@ -32,8 +34,8 @@ impl IMessageStore for MessageStore {
Ok(id) Ok(id)
} }
fn add_maildir(&mut self, path: PathBuf, all: bool) -> Result<usize, MessageStoreError> { async fn add_maildir(&mut self, path: PathBuf, all: bool) -> Result<usize, MessageStoreError> {
self.index_mails(path, all) self.index_mails(path, all).await
} }
fn tag_message_id( fn tag_message_id(
&mut self, &mut self,
@@ -76,10 +78,21 @@ impl IMessageStore for MessageStore {
unimplemented!(); unimplemented!();
} }
} }
struct Entry {
e: MailEntry,
}
impl fmt::Debug for Entry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Entry").field("e", &String::from("ARGH")).finish()
}
}
impl MessageStore { impl MessageStore {
pub fn new( pub fn new(
searcher: Box<dyn IMessageSearcher>, searcher: Box<dyn IMessageSearcher + Send + Sync>,
storage: Option<Box<dyn IMessageStorage>>, storage: Option<Box<dyn IMessageStorage + Send + Sync>>,
display_progress: bool, display_progress: bool,
) -> Self { ) -> Self {
MessageStore { MessageStore {
@@ -135,7 +148,26 @@ impl MessageStore {
} }
} }
fn do_index_mails(&mut self, maildir: Maildir, full: bool) -> Result<usize, MessageStoreError> { fn parse_message(mail: MailEntry) -> Result<(Message, String), MessageStoreError> {
let message = Message::from_mailentry(mail);
match message {
Ok(msg) => {
let parsed_body = msg.get_body(None).as_text();
Ok((msg, parsed_body))
}
Err(err) => {
error!("A message could not be parsed: {}", err.message);
Err(MessageStoreError::CouldNotAddMessage(
"Failed to parse email".to_string(),
))
}
}
}
async fn do_index_mails(
&mut self,
maildir: Maildir,
full: bool,
) -> Result<usize, MessageStoreError> {
let mails: Vec<Result<MailEntry, _>> = Self::mail_iterator(&maildir, full).collect(); let mails: Vec<Result<MailEntry, _>> = Self::mail_iterator(&maildir, full).collect();
let count = mails.len(); let count = mails.len();
self.start_indexing_process(count)?; self.start_indexing_process(count)?;
@@ -143,37 +175,32 @@ impl MessageStore {
if self.display_progress { if self.display_progress {
progress_handle = Some(self.init_progress(count)); progress_handle = Some(self.init_progress(count));
} }
let (tx, rx) = mpsc::channel(); let (tx, mut rx) = mpsc::channel(100);
let handles = mails
let t = thread::spawn(move || { .into_iter()
mails .map(|m| {
.into_par_iter() let tx = tx.clone();
.for_each_with(tx, |tx, msg| match msg { tokio::spawn(async move {
Ok(unparsed_msg) => { if let Ok(entry) = m {
let message = Message::from_mailentry(unparsed_msg); let id = match entry.is_seen() {
match message { true => None,
Ok(msg) => { false => Some(String::from(entry.id()))
let parsed_body = msg.get_body().as_text(); };
tx.send((msg, parsed_body)) if let Ok((msg, body)) = MessageStore::parse_message(entry) {
.expect("Could not send to channel?") tx.send((msg, body, id)).await.unwrap();
} };
Err(err) => {
error!("A message could not be parsed: {}", err.message);
}
}
} }
Err(e) => { })
error!("Failed to get message {}", e); })
} .collect::<Vec<tokio::task::JoinHandle<_>>>();
}); drop(tx);
}); while let Some((msg, parsed_body, id)) = rx.recv().await {
while let Ok((msg, parsed_body)) = rx.recv() {
self.add_message(msg, parsed_body)?; self.add_message(msg, parsed_body)?;
self.inc_progress(); self.inc_progress();
id.map( |id| maildir.move_new_to_cur(&id).map_err(|_| MessageStoreError::CouldNotModifyMessage(format!("Message couldn't be moved {}", id))));
} }
join_all(handles).await;
self.finish_indexing_process()?; self.finish_indexing_process()?;
t.join().expect("Unable to join threads for some reason");
self.finish_progress(); self.finish_progress();
if let Some(handle) = progress_handle { if let Some(handle) = progress_handle {
handle handle
@@ -183,11 +210,15 @@ impl MessageStore {
Ok(count) Ok(count)
} }
pub fn index_mails(&mut self, path: PathBuf, full: bool) -> Result<usize, MessageStoreError> { pub async fn index_mails(
&mut self,
path: PathBuf,
full: bool,
) -> Result<usize, MessageStoreError> {
let maildir = self.maildir(path); let maildir = self.maildir(path);
match maildir { match maildir {
Ok(maildir) => { Ok(maildir) => {
self.do_index_mails(maildir, full)?; self.do_index_mails(maildir, full).await?;
Ok(1) Ok(1)
} }
Err(_) => Err(MessageStoreError::CouldNotOpenMaildir( Err(_) => Err(MessageStoreError::CouldNotOpenMaildir(

View File

@@ -1,4 +1,5 @@
use crate::message::Message; use crate::message::Message;
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::collections::HashSet; use std::collections::HashSet;
use std::path::PathBuf; use std::path::PathBuf;
@@ -192,6 +193,7 @@ pub trait IMessageStorage {
) -> Result<Vec<Message>, MessageStoreError>; ) -> Result<Vec<Message>, MessageStoreError>;
} }
#[async_trait]
pub trait IMessageStore { pub trait IMessageStore {
fn get_message(&self, id: String) -> Result<Option<Message>, MessageStoreError>; fn get_message(&self, id: String) -> Result<Option<Message>, MessageStoreError>;
fn add_message( fn add_message(
@@ -199,7 +201,9 @@ pub trait IMessageStore {
msg: Message, msg: Message,
parsed_body: String, parsed_body: String,
) -> Result<String, MessageStoreError>; ) -> Result<String, MessageStoreError>;
fn add_maildir(&mut self, path: PathBuf, all: bool) -> Result<usize, MessageStoreError>;
async fn add_maildir(&mut self, path: PathBuf, all: bool) -> Result<usize, MessageStoreError>;
fn tag_message_id( fn tag_message_id(
&mut self, &mut self,
id: String, id: String,

View File

@@ -6,7 +6,6 @@ pub struct ListStore<'a> {
pub selected: usize, pub selected: usize,
pub page_size: usize, pub page_size: usize,
pub curr_idx: usize, pub curr_idx: usize,
pub fetched_first: bool,
pub message_store: &'a dyn IMessageStore, pub message_store: &'a dyn IMessageStore,
} }
@@ -15,7 +14,6 @@ impl<'a> ListStore<'a> {
ListStore { ListStore {
messages: vec![], messages: vec![],
selected: 0, selected: 0,
fetched_first: false,
page_size: 10, page_size: 10,
curr_idx: 0, curr_idx: 0,
message_store: msg_store, message_store: msg_store,
@@ -49,6 +47,8 @@ impl<'a> ListStore<'a> {
if r < 0 { if r < 0 {
r = 0 r = 0
} else if r > l - 1 { } else if r > l - 1 {
let mut messages = self.message_store.get_messages_page(r as usize, self.page_size);
self.messages.append(messages.as_mut().ok().unwrap());
r = l r = l
}; };
self.selected = r as usize; self.selected = r as usize;
@@ -56,14 +56,9 @@ impl<'a> ListStore<'a> {
} }
pub fn latest(&mut self) { pub fn latest(&mut self) {
let mut page_size = self.page_size;
if !self.fetched_first {
page_size = 1000;
self.fetched_first = true;
}
let messages = self let messages = self
.message_store .message_store
.get_messages_page(self.curr_idx, page_size); .get_messages_page(self.curr_idx, self.page_size);
match messages { match messages {
Ok(messages) => self.messages = messages, Ok(messages) => self.messages = messages,
Err(_) => self.messages = vec![], // TODO Handle error Err(_) => self.messages = vec![], // TODO Handle error

View File

@@ -1,4 +1,5 @@
use crate::message::Message; use crate::message::Message;
use crate::readmail::display::{OutputType, DisplayAs};
use tui::backend::Backend; use tui::backend::Backend;
use tui::layout::Rect; use tui::layout::Rect;
use tui::text::Text; use tui::text::Text;
@@ -6,7 +7,7 @@ use tui::widgets::{Block, Borders, Paragraph, Wrap};
use tui::Frame; use tui::Frame;
pub fn draw<B: Backend>(f: &mut Frame<B>, message: &Message, scroll: u16) { pub fn draw<B: Backend>(f: &mut Frame<B>, message: &Message, scroll: u16) {
let text = message.to_long_string(); let text = message.display(&OutputType::Full);
let f_r = f.size(); let f_r = f.size();
let rect = Rect { let rect = Rect {
x: f_r.x + f_r.width / 2 - 40, x: f_r.x + f_r.width / 2 - 40,

View File

@@ -1,4 +1,5 @@
use crate::terminal::store::Store; use crate::terminal::store::Store;
use crate::readmail::display::{OutputType, DisplayAs};
use tui::backend::Backend; use tui::backend::Backend;
use tui::layout::Rect; use tui::layout::Rect;
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
@@ -17,8 +18,7 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, area: Rect, store: &Store) {
let items: Vec<ListItem> = display let items: Vec<ListItem> = display
.iter() .iter()
.map(|s| { .map(|s| {
let s = s.to_string(); ListItem::new(Span::raw(s.display(&OutputType::Summary)))
ListItem::new(Span::raw(s))
}) })
.collect::<Vec<ListItem>>(); .collect::<Vec<ListItem>>();
let list = List::new(items) let list = List::new(items)