Partially working migration to async/await

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

View File

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

View File

@ -1,5 +1,6 @@
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::stores::{IMessageStore, MessageStoreBuilder, Searchers, Storages};
use rms::terminal;
@ -25,27 +26,27 @@ pub async fn main() {
}
let message_store = MessageStoreBuilder::new()
.storage(Storages::Tantivy(index_dir_path.clone()))
.searcher(Searchers::Tantivy(index_dir_path))
.searcher(Searchers::Tantivy(index_dir_path.clone()))
.debug(debug)
.build();
match message_store {
Ok(mut store) => {
maildir_path.into_iter().for_each(|m| {
for m in maildir_path {
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!(
"Failed to add mails from {}, detauls: {}",
m.to_str().unwrap(),
e
),
Ok(_) => println!("Successfully added {}", m.to_str().unwrap()),
}
});
};
}
}
Err(e) => {
error!("{}", e);
}
}
};
//maildir_path[0].clone(), index_dir_path);
//if let Some(threads) = threads {
// indexer_builder.threads(threads);
@ -56,7 +57,7 @@ pub async fn main() {
//let mut indexer = indexer_builder.build();
//message_store.index_mails(full);
}
Command::Search { term, output, num } => {
Command::Search { term, output, num, advanced } => {
let message_store = MessageStoreBuilder::new()
.storage(Storages::Tantivy(index_dir_path.clone()))
.searcher(Searchers::Tantivy(index_dir_path))
@ -66,36 +67,39 @@ pub async fn main() {
match message_store {
Ok(store) => {
let results = store.search_fuzzy(term, num).ok().unwrap();
match output {
OutputType::Short => {
for r in results {
println!("{:?} | {}", r.id, r.subject);
}
for r in results {
print!("{}", r.display(&output));
}
OutputType::Full => {
println!("{:?}", results);
}
OutputType::Raw => {
let mut out = io::stdout();
for result in results {
out.write_all(result.original.as_ref()).unwrap();
}
}
OutputType::Html => {
for m in results {
println!(
"{}",
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())
);
}
}
}
//match output {
// OutputType::Short => {
// for r in results {
// println!("{:?} | {}", r.id, r.subject);
// }
// }
// OutputType::Full => {
// println!("{:?}", results);
// }
// OutputType::Raw => {
// let mut out = io::stdout();
// for result in results {
// out.write_all(result.original.as_ref()).unwrap();
// }
// }
// OutputType::Html => {
// for m in results {
// println!(
// "{}",
// 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),
}
@ -124,46 +128,7 @@ pub async fn main() {
Ok(store) => {
let result = store.get_message(id);
match result {
Ok(Some(good_msg)) => match 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(Some(good_msg)) => println!("{}", good_msg.display(&output)),
Ok(None) => error!("Message not found"),
Err(e) => error!("ERROR {}", e),
}
@ -174,18 +139,18 @@ pub async fn main() {
Command::Interactive {} => {
terminal::start(index_dir_path).unwrap();
}
Command::Latest { num: _num } => {
Command::Latest { num: _num, skip, output } => {
let message_store = MessageStoreBuilder::new()
.storage(Storages::Tantivy(index_dir_path.clone()))
.searcher(Searchers::Tantivy(index_dir_path))
.build();
match message_store {
Ok(store) => {
let page = store.get_messages_page(0, _num);
let page = store.get_messages_page(skip, _num);
match page {
Ok(msgs) => {
for m in msgs {
println!("{}", m.id);
println!("{}", m.display(&output));
}
}
Err(e) => println!("Could not read messages, {}", e),

View File

@ -1,8 +1,7 @@
use std::ffi::OsStr;
use std::path::PathBuf;
use std::str::FromStr;
use std::{error, fmt};
use structopt::StructOpt;
use crate::readmail::display::OutputType;
pub fn expand_path(input: &OsStr) -> PathBuf {
let input_str = input
@ -14,49 +13,8 @@ pub fn expand_path(input: &OsStr) -> PathBuf {
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)]
#[structopt(
@ -115,6 +73,9 @@ pub enum Command {
#[structopt(short, long, default_value = "100")]
num: usize,
#[structopt(short, long)]
advanced: bool,
},
#[structopt(name = "date", rename_all = "kebab-case")]
Date { term: i64 },
@ -131,6 +92,10 @@ pub enum Command {
Latest {
#[structopt(short, long)]
num: usize,
#[structopt(short, long, default_value = "0")]
skip: usize,
#[structopt(short, long, default_value = "short")]
output: OutputType,
},
#[structopt(name = "tag")]

View File

@ -8,7 +8,8 @@ use sha2::{Digest, Sha512};
use std::collections::HashSet;
use std::convert::AsRef;
use std::fmt;
use std::string::ToString;
use crate::readmail::display::{DisplayAs, OutputType};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Mime {
@ -106,16 +107,21 @@ pub fn get_id(data: &[u8]) -> String {
format!("{:x}", Sha512::digest(data))
}
impl ToString for ShortMessage {
fn to_string(&self) -> String {
impl fmt::Display for ShortMessage {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let dt = Local.timestamp(self.date as i64, 0);
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 {
fn to_string(&self) -> String {
impl fmt::Display for Message {
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 dstr = dt.format("%a %b %e %T %Y").to_string();
let tags = if self.tags.is_empty() {
@ -123,13 +129,28 @@ impl ToString for Message {
} else {
String::from("")
};
format!(
"{} {}: [{}] {}",
tags,
dstr,
match t {
OutputType::Short => format!("{} | {} | {}", self.short_id(), dstr, self.subject.as_str()),
OutputType::Full => format!(
r#"
From: {}
to/cc/bcc: {}
Date: {}
Subject: {}
{}
# {}
"#,
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 {
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 headers = &msg.headers;
let mut subject: String = "".to_string();
@ -193,42 +215,26 @@ impl Message {
})
}
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 {
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> {
let id = mailentry.id().to_owned();
match mailentry.parsed() {
Ok(parsed) => Self::from_parsedmail(&parsed, id),
Ok(parsed) => Self::from_parsedmail(&parsed),
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 {
self.body.get(0).unwrap()
pub fn get_body(&self, mime: Option<Mime>) -> &Body {
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: {}
{}
"#,
self.from,
self.recipients.join(","),
self.subject,
self.body
.iter()
.map(|b| b.value.replace('\r', ""))
.collect::<Vec<String>>()
.join("-----")
)
pub fn short_id(&self) -> &str{
&self.id[..24]
}
}
#[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 mailparse::*;
use select::document::Document;
use select::predicate::Text;
use select::predicate::{Text, Name, Predicate};
use std::cmp::Ordering;
pub mod display;
fn cmp_body(x: &Body, y: &Body, prefer: &Mime) -> Ordering {
if x.mime == y.mime {
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 {
let document = Document::from(text);
let text_nodes = document
.find(Text)
let body = document.find(Name("body")).nth(0).unwrap();
let text_nodes = body.find(Text)
.map(|x| String::from(x.text().trim()))
.filter(|x| x.len() > 1)
.collect::<Vec<String>>();

View File

@ -1,15 +1,15 @@
use crate::message::{Message, MessageError};
use crate::stores::{IMessageSearcher, MessageStoreError};
use log::info;
use log::{error, info};
use std::cmp;
use std::collections::HashSet;
use std::fs;
use std::panic;
use std::path::PathBuf;
use std::string::ToString;
use tantivy::collector::TopDocs;
use tantivy::collector::{TopDocs, Count};
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::*;
const BYTES_IN_MB: usize = 1024 * 1024;
@ -218,6 +218,7 @@ impl TantivyStore {
.iter()
.for_each(|t| document.add_text(email.tag, t.as_str()));
indexer.add_document(document);
indexer.commit();
Ok(msg.id)
}
None => Err(MessageStoreError::CouldNotAddMessage(
@ -305,17 +306,14 @@ impl TantivyStore {
) -> Result<Vec<TantivyMessage>, MessageStoreError> {
let searcher = self.reader.searcher();
let skip = _skip.unwrap_or(0);
let mut docs = searcher
let docs = searcher
.search(
&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![]))?;
let mut ret = vec![];
let page = docs
.drain(skip..)
.collect::<Vec<(u64, tantivy::DocAddress)>>();
for doc in page {
for doc in docs {
let retrieved_doc = searcher.doc(doc.1).unwrap();
ret.push(
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> {
// Is this needed? self.reader.load_searchers()?;
let searcher = self.reader.searcher();
let termq = TermQuery::new(
Term::from_field_text(self.email.id, id),
IndexRecordOption::Basic,
);
let addr = searcher.search(&termq, &TopDocs::with_limit(1));
match addr {
Ok(doc) => match doc.first() {
let termq = RegexQuery::from_pattern(format!("{}.*",id).as_str(), self.email.id)?;
let res = searcher.search(&termq, &(TopDocs::with_limit(1), Count));
match res {
Ok((doc, count)) => match doc.first() {
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(),
)),
))
},
},
Err(e) => Err(e),

View File

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

View File

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

View File

@ -6,7 +6,6 @@ pub struct ListStore<'a> {
pub selected: usize,
pub page_size: usize,
pub curr_idx: usize,
pub fetched_first: bool,
pub message_store: &'a dyn IMessageStore,
}
@ -15,7 +14,6 @@ impl<'a> ListStore<'a> {
ListStore {
messages: vec![],
selected: 0,
fetched_first: false,
page_size: 10,
curr_idx: 0,
message_store: msg_store,
@ -49,6 +47,8 @@ impl<'a> ListStore<'a> {
if r < 0 {
r = 0
} 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
};
self.selected = r as usize;
@ -56,14 +56,9 @@ impl<'a> ListStore<'a> {
}
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
.message_store
.get_messages_page(self.curr_idx, page_size);
.get_messages_page(self.curr_idx, self.page_size);
match messages {
Ok(messages) => self.messages = messages,
Err(_) => self.messages = vec![], // TODO Handle error

View File

@ -1,4 +1,5 @@
use crate::message::Message;
use crate::readmail::display::{OutputType, DisplayAs};
use tui::backend::Backend;
use tui::layout::Rect;
use tui::text::Text;
@ -6,7 +7,7 @@ use tui::widgets::{Block, Borders, Paragraph, Wrap};
use tui::Frame;
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 rect = Rect {
x: f_r.x + f_r.width / 2 - 40,

View File

@ -1,4 +1,5 @@
use crate::terminal::store::Store;
use crate::readmail::display::{OutputType, DisplayAs};
use tui::backend::Backend;
use tui::layout::Rect;
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
.iter()
.map(|s| {
let s = s.to_string();
ListItem::new(Span::raw(s))
ListItem::new(Span::raw(s.display(&OutputType::Summary)))
})
.collect::<Vec<ListItem>>();
let list = List::new(items)