Partial cleanup
This commit is contained in:
parent
f74963cf3c
commit
01a42cebf6
772
Cargo.lock
generated
772
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@ -7,15 +7,17 @@ edition = "2018"
|
||||
[dependencies]
|
||||
#maildir = { git = "https://github.com/lewisdiamond/maildir.git" }
|
||||
maildir = { path = "/home/ldiamond/dev/maildir/" }
|
||||
html2text = "0.1.13"
|
||||
#maildir = "0.4.2"
|
||||
html2text = "0.2.0"
|
||||
#html2text = { git = "https://github.com/lewisdiamond/rust-html2text.git"}
|
||||
mailparse = "0.13.0"
|
||||
rayon = "1.3.1"
|
||||
tantivy = "0.12.0"
|
||||
#mailparse = "0.13.0"
|
||||
mailparse = { path = "/home/ldiamond/dev/mailparse/" }
|
||||
rayon = "1.4.0"
|
||||
tantivy = "0.13.0"
|
||||
tempdir = "0.3.7"
|
||||
serde_json = "1.0.57"
|
||||
serde = { version = "1.0.114", features = ["derive"] }
|
||||
structopt = "0.3.15"
|
||||
serde = { version = "1.0.115", features = ["derive"] }
|
||||
structopt = "0.3.17"
|
||||
shellexpand = "2.0.0"
|
||||
log = { version = "0.4.11", features = ["max_level_debug", "release_max_level_warn"] }
|
||||
pretty_env_logger = "0.4.0"
|
||||
@ -24,13 +26,15 @@ num_cpus = "1.13.0"
|
||||
sys-info = "0.7.0"
|
||||
tui = "0.10.0"
|
||||
termion = "1.5.5"
|
||||
chrono = "0.4.13"
|
||||
chrono = "0.4.15"
|
||||
sha2 = "0.9.1"
|
||||
html5ever = "0.25.1"
|
||||
rocksdb = { path = "../rust-rocksdb/" }
|
||||
rocksdb = "0.15.0"
|
||||
jemallocator = "0.3.2"
|
||||
#maildir = "0.4.2"
|
||||
select = "0.5.0"
|
||||
futures = "0.3"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
async-trait = "0.1.40"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.3"
|
||||
|
@ -5,12 +5,12 @@ use structopt::clap::{App, Arg};
|
||||
|
||||
fn expand_path(input_str: &str) -> PathBuf {
|
||||
let expanded = shellexpand::full(input_str)
|
||||
.expect(format!("Unable to expand {}", input_str).as_str())
|
||||
.unwrap_or_else(|_| panic!("Unable to expand {}", input_str))
|
||||
.into_owned();
|
||||
return PathBuf::from(expanded);
|
||||
PathBuf::from(expanded)
|
||||
}
|
||||
|
||||
pub fn source() -> Box<BufRead> {
|
||||
pub fn source() -> Box<dyn BufRead> {
|
||||
let matches = App::new("Read Mail")
|
||||
.version("0.0.1")
|
||||
.author("Lewis Diamond <rms@lewisdiamond.com")
|
||||
|
@ -1,12 +1,13 @@
|
||||
use log::{error, info, trace};
|
||||
use rms::cmd::{opts, Command, OutputType};
|
||||
use rms::message::{Body, Mime};
|
||||
use rms::stores::{IMessageStore, MessageStoreBuilder, Searchers, Storages};
|
||||
use rms::terminal;
|
||||
use std::collections::HashSet;
|
||||
use std::io::{self, Write};
|
||||
use std::time::Instant;
|
||||
|
||||
fn main() {
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
pretty_env_logger::init();
|
||||
let opt = opts();
|
||||
trace!("Using config file at {:?}", opt.config); //, index.maildir_path);
|
||||
@ -24,7 +25,7 @@ fn main() {
|
||||
}
|
||||
let message_store = MessageStoreBuilder::new()
|
||||
.storage(Storages::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path))
|
||||
.debug(debug)
|
||||
.build();
|
||||
match message_store {
|
||||
@ -56,10 +57,9 @@ fn main() {
|
||||
//message_store.index_mails(full);
|
||||
}
|
||||
Command::Search { term, output, num } => {
|
||||
let now = Instant::now();
|
||||
let message_store = MessageStoreBuilder::new()
|
||||
.storage(Storages::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path))
|
||||
.read_only()
|
||||
.build();
|
||||
|
||||
@ -81,6 +81,20 @@ fn main() {
|
||||
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),
|
||||
@ -102,7 +116,7 @@ fn main() {
|
||||
Command::Get { id, output } => {
|
||||
let message_store = MessageStoreBuilder::new()
|
||||
.storage(Storages::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path))
|
||||
.read_only()
|
||||
.build();
|
||||
|
||||
@ -133,7 +147,20 @@ fn main() {
|
||||
.body
|
||||
.first()
|
||||
.map(|b| b.value.clone())
|
||||
.unwrap_or(String::from("No body"))
|
||||
.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())
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -150,7 +177,7 @@ fn main() {
|
||||
Command::Latest { num: _num } => {
|
||||
let message_store = MessageStoreBuilder::new()
|
||||
.storage(Storages::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path))
|
||||
.build();
|
||||
match message_store {
|
||||
Ok(store) => {
|
||||
@ -170,13 +197,14 @@ fn main() {
|
||||
Command::Tag { id, tags } => {
|
||||
let message_store = MessageStoreBuilder::new()
|
||||
.storage(Storages::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path.clone()))
|
||||
.searcher(Searchers::Tantivy(index_dir_path))
|
||||
.build();
|
||||
match message_store {
|
||||
Ok(mut store) => {
|
||||
match store.tag_message_id(id, tags.into_iter().collect::<HashSet<String>>()) {
|
||||
Err(e) => error!("{}", e),
|
||||
Ok(_) => {}
|
||||
if let Err(e) =
|
||||
store.tag_message_id(id, tags.into_iter().collect::<HashSet<String>>())
|
||||
{
|
||||
error!("{}", e)
|
||||
}
|
||||
}
|
||||
Err(e) => error!("{}", e),
|
||||
|
@ -9,9 +9,9 @@ pub fn expand_path(input: &OsStr) -> PathBuf {
|
||||
.to_str()
|
||||
.expect("Unable to expand the given path. Can't convert input to &str.");
|
||||
let expanded = shellexpand::full(input_str)
|
||||
.expect(format!("Unable to expand {}", input_str).as_str())
|
||||
.unwrap_or_else(|_| panic!("Unable to expand {}", input_str))
|
||||
.into_owned();
|
||||
return PathBuf::from(expanded);
|
||||
PathBuf::from(expanded)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -19,6 +19,7 @@ pub enum OutputType {
|
||||
Short,
|
||||
Full,
|
||||
Raw,
|
||||
Html,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -38,7 +39,7 @@ impl std::error::Error for OutputTypeError {
|
||||
"invalid first item to double"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
fn cause(&self) -> Option<&dyn error::Error> {
|
||||
// Generic error, underlying cause isn't tracked.
|
||||
None
|
||||
}
|
||||
@ -51,6 +52,7 @@ impl FromStr for OutputType {
|
||||
"short" => Ok(OutputType::Short),
|
||||
"full" => Ok(OutputType::Full),
|
||||
"raw" => Ok(OutputType::Raw),
|
||||
"html" => Ok(OutputType::Html),
|
||||
_ => Err(OutputTypeError::UnknownTypeError),
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
use crate::readmail;
|
||||
use crate::readmail::html2text;
|
||||
use chrono::prelude::*;
|
||||
use maildir::{MailEntry, ParsedMailEntry};
|
||||
use maildir::MailEntry;
|
||||
use mailparse::{dateparse, parse_mail, ParsedMail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha512};
|
||||
use std::collections::HashSet;
|
||||
use std::convert::AsRef;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fmt;
|
||||
use std::string::ToString;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@ -22,20 +22,35 @@ impl Default for Mime {
|
||||
Mime::PlainText
|
||||
}
|
||||
}
|
||||
impl Mime {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
&Mime::PlainText => "text/plain",
|
||||
&Mime::Html => "text/html",
|
||||
_ => "Unknown Mime",
|
||||
impl std::str::FromStr for Mime {
|
||||
type Err = std::convert::Infallible;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"text/plain" => Ok(Mime::PlainText),
|
||||
"text/html" => Ok(Mime::Html),
|
||||
"multipart/alternative" | "multipart/related" => Ok(Mime::Nested),
|
||||
_ => Ok(Mime::Unknown),
|
||||
}
|
||||
}
|
||||
pub fn from_str(s: &str) -> Mime {
|
||||
match s {
|
||||
"text/plain" => Mime::PlainText,
|
||||
"text/html" => Mime::Html,
|
||||
"multipart/alternative" | "multipart/related" => Mime::Nested,
|
||||
_ => Mime::Unknown,
|
||||
}
|
||||
impl fmt::Display for Mime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let msg = match self {
|
||||
Self::PlainText => "PlainText",
|
||||
Self::Html => "Html",
|
||||
Self::Unknown => "Unknown",
|
||||
Self::Nested => "Nested",
|
||||
};
|
||||
write!(f, "Mime:{}", msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mime {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match *self {
|
||||
Mime::PlainText => "text/plain",
|
||||
Mime::Html => "text/html",
|
||||
_ => "Unknown Mime",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,7 +91,7 @@ pub struct ShortMessage {
|
||||
pub from: String,
|
||||
pub date: u64,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct MessageBuilder {
|
||||
body: Option<String>,
|
||||
subject: Option<String>,
|
||||
@ -87,7 +102,7 @@ pub struct MessageBuilder {
|
||||
original: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub fn get_id(data: &Vec<u8>) -> String {
|
||||
pub fn get_id(data: &[u8]) -> String {
|
||||
format!("{:x}", Sha512::digest(data))
|
||||
}
|
||||
|
||||
@ -103,13 +118,8 @@ impl ToString for Message {
|
||||
fn to_string(&self) -> 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.len() > 0 {
|
||||
self.tags
|
||||
.iter()
|
||||
.map(|s| s.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
+ " ||"
|
||||
let tags = if self.tags.is_empty() {
|
||||
self.tags.iter().cloned().collect::<Vec<String>>().join(",") + " ||"
|
||||
} else {
|
||||
String::from("")
|
||||
};
|
||||
@ -122,13 +132,6 @@ impl ToString for Message {
|
||||
)
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for Message {
|
||||
fn as_ref(&self) -> &str {
|
||||
let dt = Local.timestamp(self.date as i64, 0);
|
||||
let dstr = dt.format("%a %b %e %T %Y").to_string();
|
||||
"aa" //self.to_string().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MessageError {
|
||||
pub message: String,
|
||||
@ -143,11 +146,8 @@ impl MessageError {
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn from_parsedmail(
|
||||
msg: &ParsedMail,
|
||||
id: String,
|
||||
original: Vec<u8>,
|
||||
) -> Result<Self, MessageError> {
|
||||
pub fn from_parsedmail(msg: &ParsedMail, id: String) -> Result<Self, MessageError> {
|
||||
let original = Vec::from(msg.data);
|
||||
let headers = &msg.headers;
|
||||
let mut subject: String = "".to_string();
|
||||
let mut from: String = "".to_string();
|
||||
@ -165,13 +165,16 @@ impl Message {
|
||||
"Received" | "Date" => {
|
||||
if date == default_date {
|
||||
let date_str = h.get_value();
|
||||
for ts in date_str.rsplit(';') {
|
||||
date = match dateparse(ts) {
|
||||
Ok(d) => d,
|
||||
Err(_) => default_date,
|
||||
};
|
||||
break;
|
||||
}
|
||||
let date_str = date_str
|
||||
.rsplit(';')
|
||||
.collect::<Vec<&str>>()
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or("");
|
||||
date = match dateparse(date_str) {
|
||||
Ok(d) => d,
|
||||
Err(_) => default_date,
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -194,25 +197,19 @@ impl Message {
|
||||
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, data.clone())
|
||||
Self::from_parsedmail(&parsed_mail, id)
|
||||
}
|
||||
pub fn from_mailentry(mailentry: MailEntry) -> Result<Self, MessageError> {
|
||||
let id = mailentry.id();
|
||||
mailentry.read_data().map_err(|e| MessageError {
|
||||
message: format!("Failed to parse email id {}", id),
|
||||
})?;
|
||||
let data = mailentry.data().ok_or(MessageError {
|
||||
message: format!("Mail {} could not read data", id),
|
||||
})?;
|
||||
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, String::from(id), data.clone()),
|
||||
Err(e) => Err(MessageError {
|
||||
Ok(parsed) => Self::from_parsedmail(&parsed, id),
|
||||
Err(_) => Err(MessageError {
|
||||
message: format!("Failed to parse email id {}", id),
|
||||
}),
|
||||
}
|
||||
}
|
||||
pub fn get_body(&self) -> &Body {
|
||||
self.body.iter().next().unwrap()
|
||||
self.body.get(0).unwrap()
|
||||
}
|
||||
pub fn to_long_string(&self) -> String {
|
||||
format!(
|
||||
@ -234,7 +231,7 @@ impl Message {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl MessageBuilder {
|
||||
fn body(mut self, body: String) -> Self {
|
||||
self.body = Some(body);
|
||||
@ -257,7 +254,7 @@ impl MessageBuilder {
|
||||
self
|
||||
}
|
||||
fn recipients(mut self, recipients: String) -> Self {
|
||||
self.recipients = Some(recipients.split(",").map(|s| String::from(s)).collect());
|
||||
self.recipients = Some(recipients.split(',').map(String::from).collect());
|
||||
self
|
||||
}
|
||||
fn original(mut self, original: Vec<u8>) -> Self {
|
||||
|
@ -1,5 +1,5 @@
|
||||
extern crate select;
|
||||
use crate::message::{get_id, Body, Message, Mime};
|
||||
use crate::message::{Body, Mime};
|
||||
use log::debug;
|
||||
use mailparse::*;
|
||||
use select::document::Document;
|
||||
@ -23,24 +23,22 @@ pub fn extract_body(msg: &ParsedMail, prefer_html: bool) -> Vec<Body> {
|
||||
} else {
|
||||
Mime::PlainText
|
||||
};
|
||||
let text = msg
|
||||
.get_body()
|
||||
.unwrap_or(msg.get_body_raw().map_or(String::from(""), |x| {
|
||||
String::from_utf8(x).unwrap_or(String::from(""))
|
||||
}));
|
||||
let mime = Mime::from_str(&msg.ctype.mimetype);
|
||||
let text = msg.get_body().unwrap_or_else(|_| {
|
||||
msg.get_body_raw().map_or(String::from(""), |x| {
|
||||
String::from_utf8(x).unwrap_or_else(|_| String::new())
|
||||
})
|
||||
});
|
||||
let mime = msg.ctype.mimetype.parse::<Mime>().unwrap();
|
||||
let raw_body = Some(Body::new(mime, text));
|
||||
|
||||
let mut bodies = msg
|
||||
.subparts
|
||||
.iter()
|
||||
.map(|mut s| {
|
||||
let mime = Mime::from_str(&s.ctype.mimetype);
|
||||
.map(|s| {
|
||||
let mime = s.ctype.mimetype.parse::<Mime>().unwrap();
|
||||
match mime {
|
||||
Mime::PlainText | Mime::Html => {
|
||||
s.get_body().ok().map(|b| Body::new(mime, String::from(b)))
|
||||
}
|
||||
Mime::Nested => extract_body(&mut s, prefer_html).into_iter().next(),
|
||||
Mime::PlainText | Mime::Html => s.get_body().ok().map(|b| Body::new(mime, b)),
|
||||
Mime::Nested => extract_body(&s, prefer_html).into_iter().next(),
|
||||
Mime::Unknown => {
|
||||
debug!("unknown mime {}", mime.as_str());
|
||||
None
|
||||
@ -53,7 +51,7 @@ pub fn extract_body(msg: &ParsedMail, prefer_html: bool) -> Vec<Body> {
|
||||
bodies.push(raw_body.expect("COULD NOT UNWRAP RAW_BODY"));
|
||||
}
|
||||
bodies.sort_unstable_by(|x, y| cmp_body(x, y, &prefered_mime));
|
||||
if bodies.len() == 0 {
|
||||
if bodies.is_empty() {
|
||||
println!(
|
||||
"No body for message: {}",
|
||||
msg.headers
|
||||
@ -70,7 +68,8 @@ pub fn html2text(text: &str) -> String {
|
||||
let document = Document::from(text);
|
||||
let text_nodes = document
|
||||
.find(Text)
|
||||
.map(|x| x.text())
|
||||
.map(|x| String::from(x.text().trim()))
|
||||
.filter(|x| x.len() > 1)
|
||||
.collect::<Vec<String>>();
|
||||
return text_nodes.join("\n\n");
|
||||
text_nodes.join("\n\n")
|
||||
}
|
||||
|
@ -1,28 +1,21 @@
|
||||
use crate::message::{Message, ShortMessage};
|
||||
use crate::message::Message;
|
||||
use crate::stores::{IMessageStorage, MessageStoreError};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rocksdb::{DBCompactionStyle, DBCompressionType};
|
||||
use rocksdb::{DBVector, Options, DB};
|
||||
use serde_json::Result as SResult;
|
||||
use std::collections::HashSet;
|
||||
use rocksdb::{Options, DB};
|
||||
use std::path::Path;
|
||||
use std::string::ToString;
|
||||
|
||||
type RocksDBMessage = Message;
|
||||
impl RocksDBMessage {
|
||||
fn from_rocksdb(msg: DBVector) -> Result<RocksDBMessage, MessageStoreError> {
|
||||
let msg_r = msg
|
||||
.to_utf8()
|
||||
.ok_or(Err(MessageStoreError::CouldNotGetMessage(
|
||||
"Message is malformed in some way".to_string(),
|
||||
)));
|
||||
fn from_rocksdb(msg: Vec<u8>) -> Result<RocksDBMessage, MessageStoreError> {
|
||||
let msg = String::from_utf8(msg).map_err(|_| {
|
||||
MessageStoreError::CouldNotGetMessage("Message is malformed in some way".to_string())
|
||||
})?;
|
||||
|
||||
match msg_r {
|
||||
Ok(msg) => serde_json::from_str(msg).map_err(|e| {
|
||||
MessageStoreError::CouldNotGetMessage("Unable to parse the value".to_string())
|
||||
}),
|
||||
Err(e) => e,
|
||||
}
|
||||
serde_json::from_str(&msg).map_err(|_| {
|
||||
MessageStoreError::CouldNotGetMessage("Unable to parse the value".to_string())
|
||||
})
|
||||
}
|
||||
fn to_rocksdb(&self) -> Result<(String, Vec<u8>), MessageStoreError> {
|
||||
let id = self.id.clone();
|
||||
@ -71,29 +64,30 @@ impl IMessageStorage for RocksDBStore {
|
||||
))),
|
||||
}
|
||||
}
|
||||
fn update_message(&mut self, msg: Message) -> Result<Message, MessageStoreError> {
|
||||
fn update_message(&mut self, _msg: Message) -> Result<Message, MessageStoreError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn delete_message(&mut self, msg: Message) -> Result<(), MessageStoreError> {
|
||||
fn delete_message(&mut self, _msg: Message) -> Result<(), MessageStoreError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_messages_page(
|
||||
&self,
|
||||
start: usize,
|
||||
_start: usize,
|
||||
num: usize,
|
||||
) -> Result<Vec<Message>, MessageStoreError> {
|
||||
Ok(self.latest(num))
|
||||
}
|
||||
fn get_by_date(
|
||||
&self,
|
||||
start: DateTime<Utc>,
|
||||
end: DateTime<Utc>,
|
||||
_start: DateTime<Utc>,
|
||||
_end: DateTime<Utc>,
|
||||
) -> Result<Vec<Message>, MessageStoreError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl RocksDBStore {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> Self {
|
||||
let mut opts = Options::default();
|
||||
opts.increase_parallelism(16);
|
||||
@ -156,8 +150,8 @@ mod test {
|
||||
fn drop(&mut self) {
|
||||
let opts = Options::default();
|
||||
let path = self.path.as_ref().unwrap();
|
||||
DB::destroy(&opts, path);
|
||||
std::fs::remove_dir_all(path);
|
||||
DB::destroy(&opts, path).unwrap();
|
||||
std::fs::remove_dir_all(path).unwrap();
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
@ -191,6 +185,6 @@ mod test {
|
||||
let store = &StoreInit::new().store;
|
||||
store.db.put(b"key", b"value2").unwrap();
|
||||
let get = store.db.get(b"key").ok().unwrap().unwrap();
|
||||
assert_eq!("value2", get.to_utf8().unwrap());
|
||||
assert_eq!("value2", String::from_utf8(get).unwrap());
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,26 @@
|
||||
use crate::stores::{IMessageSearcher, IMessageStorage, MessageStoreError};
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::message::{Message, MessageError};
|
||||
use crate::stores::{IMessageSearcher, MessageStoreError};
|
||||
use log::info;
|
||||
use std::cmp;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::panic;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::message::{Body, Message, Mime};
|
||||
use std::path::PathBuf;
|
||||
use std::string::ToString;
|
||||
use tantivy;
|
||||
use tantivy::collector::{Count, TopDocs};
|
||||
use tantivy::collector::TopDocs;
|
||||
use tantivy::directory::MmapDirectory;
|
||||
use tantivy::query::{AllQuery, BooleanQuery, FuzzyTermQuery, Occur, Query, RangeQuery, TermQuery};
|
||||
use tantivy::query::{AllQuery, BooleanQuery, FuzzyTermQuery, Occur, Query, TermQuery};
|
||||
use tantivy::schema::*;
|
||||
const BYTES_IN_MB: usize = 1024 * 1024;
|
||||
|
||||
pub type TantivyMessage = Message;
|
||||
|
||||
pub trait TantivyFrom<T> {
|
||||
fn from_tantivy(doc: Document, schema: &EmailSchema) -> T;
|
||||
fn from_tantivy(doc: Document, schema: &EmailSchema) -> Result<T, MessageError>;
|
||||
}
|
||||
|
||||
impl TantivyFrom<TantivyMessage> for TantivyMessage {
|
||||
fn from_tantivy(doc: Document, schema: &EmailSchema) -> TantivyMessage {
|
||||
fn from_tantivy(doc: Document, schema: &EmailSchema) -> Result<TantivyMessage, MessageError> {
|
||||
let original: Result<Vec<u8>, _> = match doc
|
||||
.get_first(schema.original)
|
||||
.expect("Unable to get original message")
|
||||
@ -33,55 +29,15 @@ impl TantivyFrom<TantivyMessage> for TantivyMessage {
|
||||
_ => Err("Missing original email from the index"),
|
||||
};
|
||||
|
||||
let tags: HashSet<String> = doc
|
||||
let _tags: HashSet<String> = doc
|
||||
.get_all(schema.tag)
|
||||
.into_iter()
|
||||
.filter_map(|s| s.text())
|
||||
.map(|s| String::from(s))
|
||||
.map(String::from)
|
||||
.collect();
|
||||
TantivyMessage {
|
||||
id: doc
|
||||
.get_first(schema.id)
|
||||
.expect("Message without an id")
|
||||
.text()
|
||||
.expect("Message ID is always a string")
|
||||
.to_string(),
|
||||
from: String::from(
|
||||
doc.get_first(schema.from)
|
||||
.expect("Message without from")
|
||||
.text()
|
||||
.expect("Message with non-text from"),
|
||||
),
|
||||
subject: String::from(
|
||||
doc.get_first(schema.subject)
|
||||
.expect("Message without subject")
|
||||
.text()
|
||||
.expect("Message with non-text subject"),
|
||||
),
|
||||
date: doc
|
||||
.get_first(schema.date)
|
||||
.map_or(0, |v: &tantivy::schema::Value| v.u64_value()),
|
||||
recipients: doc
|
||||
.get_first(schema.recipients)
|
||||
.unwrap_or(&tantivy::schema::Value::Str(String::from("a")))
|
||||
.text()
|
||||
.expect("Message with non-text recipients")
|
||||
.split(",")
|
||||
.map(|s| String::from(s))
|
||||
.collect(),
|
||||
body: vec![Body {
|
||||
mime: Mime::PlainText,
|
||||
value: String::from(
|
||||
doc.get_first(schema.body)
|
||||
.expect("Message without body")
|
||||
.text()
|
||||
.expect("Message with non-text body"),
|
||||
),
|
||||
}],
|
||||
|
||||
original: original.expect("Original was missing from the index"),
|
||||
tags,
|
||||
}
|
||||
TantivyMessage::from_data(
|
||||
original.map_err(|_| MessageError::from("Could not read original from index"))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,6 +47,7 @@ pub struct EmailSchema {
|
||||
body: Field,
|
||||
from: Field,
|
||||
recipients: Field,
|
||||
#[allow(dead_code)]
|
||||
thread: Field,
|
||||
id: Field,
|
||||
date: Field,
|
||||
@ -157,7 +114,7 @@ impl IMessageSearcher for TantivyStore {
|
||||
match writer {
|
||||
Some(writer) => match writer.commit() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(MessageStoreError::CouldNotAddMessage(
|
||||
Err(_) => Err(MessageStoreError::CouldNotAddMessage(
|
||||
"Failed to commit to index".to_string(),
|
||||
)),
|
||||
},
|
||||
@ -181,10 +138,10 @@ impl IMessageSearcher for TantivyStore {
|
||||
) -> Result<Vec<TantivyMessage>, MessageStoreError> {
|
||||
Ok(self.search(query.as_str(), num))
|
||||
}
|
||||
fn delete_message(&mut self, msg: &Message) -> Result<(), MessageStoreError> {
|
||||
fn delete_message(&mut self, _msg: &Message) -> Result<(), MessageStoreError> {
|
||||
Ok(())
|
||||
}
|
||||
fn update_message(&mut self, msg: Message) -> Result<Message, MessageStoreError> {
|
||||
fn update_message(&mut self, _msg: Message) -> Result<Message, MessageStoreError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
@ -207,7 +164,7 @@ impl TantivyStore {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
Self::_new(path, false)
|
||||
}
|
||||
fn _new(path: PathBuf, ro: bool) -> Self {
|
||||
fn _new(path: PathBuf, _ro: bool) -> Self {
|
||||
let email = EmailSchema::default();
|
||||
let index = TantivyStore::open_or_create_index(path, email.schema.clone());
|
||||
let reader = index
|
||||
@ -222,22 +179,21 @@ impl TantivyStore {
|
||||
mem_per_thread: None,
|
||||
}
|
||||
}
|
||||
pub fn new_ro(path: PathBuf) -> Self {
|
||||
pub fn _new_ro(path: PathBuf) -> Self {
|
||||
Self::_new(path, true)
|
||||
}
|
||||
|
||||
fn open_or_create(path: PathBuf, schema: Schema) -> tantivy::Index {
|
||||
tantivy::Index::open_or_create(MmapDirectory::open(path).unwrap(), schema.clone()).unwrap()
|
||||
tantivy::Index::open_or_create(MmapDirectory::open(path).unwrap(), schema).unwrap()
|
||||
}
|
||||
|
||||
fn open_or_create_index(path: PathBuf, schema: Schema) -> tantivy::Index {
|
||||
fs::create_dir_all(path.as_path()).expect(
|
||||
format!(
|
||||
fs::create_dir_all(path.as_path()).unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Unable to create or access the given index directory {}",
|
||||
path.to_str().unwrap()
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
});
|
||||
TantivyStore::open_or_create(path, schema)
|
||||
}
|
||||
|
||||
@ -262,7 +218,7 @@ impl TantivyStore {
|
||||
.iter()
|
||||
.for_each(|t| document.add_text(email.tag, t.as_str()));
|
||||
indexer.add_document(document);
|
||||
Ok(msg.id.clone())
|
||||
Ok(msg.id)
|
||||
}
|
||||
None => Err(MessageStoreError::CouldNotAddMessage(
|
||||
"No indexer was allocated".to_string(),
|
||||
@ -274,7 +230,7 @@ impl TantivyStore {
|
||||
match writer {
|
||||
Some(indexer) => {
|
||||
let term = Term::from_field_text(self.email.id, msg.id.as_ref());
|
||||
indexer.delete_term(term.clone());
|
||||
indexer.delete_term(term);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(MessageStoreError::CouldNotModifyMessage(
|
||||
@ -282,11 +238,15 @@ impl TantivyStore {
|
||||
)),
|
||||
}
|
||||
}
|
||||
pub fn _tag_doc(&self, doc: Document, tags: Vec<String>) -> Result<(), tantivy::TantivyError> {
|
||||
pub fn _tag_doc(&self, doc: Document, _tags: Vec<String>) -> Result<(), tantivy::TantivyError> {
|
||||
let mut writer = self.get_index_writer(1).ok().unwrap();
|
||||
let id = TantivyMessage::from_tantivy(doc, &self.email).id;
|
||||
let id = TantivyMessage::from_tantivy(doc, &self.email)
|
||||
.map_err(|_| {
|
||||
tantivy::TantivyError::SystemError(String::from("Can't read message from index"))
|
||||
})?
|
||||
.id;
|
||||
let term = Term::from_field_text(self.email.id, id.as_ref());
|
||||
writer.delete_term(term.clone());
|
||||
writer.delete_term(term);
|
||||
writer.commit()?;
|
||||
self.reader.reload()
|
||||
}
|
||||
@ -299,10 +259,7 @@ impl TantivyStore {
|
||||
Some(threads) => threads,
|
||||
None => cmp::min(
|
||||
(num_cpus::get() as f32 / 1.5).floor() as usize,
|
||||
cmp::max(
|
||||
1,
|
||||
(0.0818598 * (num_emails as f32).powf(0.31154938)) as usize,
|
||||
),
|
||||
cmp::max(1, (0.0818598 * (num_emails as f32).powf(0.311549)) as usize),
|
||||
),
|
||||
};
|
||||
let mem_per_thread = match self.mem_per_thread {
|
||||
@ -313,7 +270,7 @@ impl TantivyStore {
|
||||
cmp::min(
|
||||
mem_info.avail as usize * 1024 / (num_cpu + 1),
|
||||
cmp::max(
|
||||
(0.41268337 * (num_emails as f32).powf(0.67270258)) as usize
|
||||
(0.41268337 * (num_emails as f32).powf(0.672702)) as usize
|
||||
* BYTES_IN_MB,
|
||||
200 * BYTES_IN_MB,
|
||||
),
|
||||
@ -335,7 +292,7 @@ impl TantivyStore {
|
||||
.writer_with_num_threads(num_cpu, mem_per_thread * num_cpu)
|
||||
{
|
||||
Ok(index_writer) => Ok(index_writer),
|
||||
Err(e) => Err(MessageStoreError::CouldNotAddMessage(
|
||||
Err(_) => Err(MessageStoreError::CouldNotAddMessage(
|
||||
"Impossible to create the indexer".to_string(),
|
||||
)),
|
||||
}
|
||||
@ -353,14 +310,18 @@ impl TantivyStore {
|
||||
&AllQuery,
|
||||
&TopDocs::with_limit(num + skip).order_by_u64_field(self.email.date),
|
||||
)
|
||||
.map_err(|e| MessageStoreError::CouldNotGetMessages(vec![]))?;
|
||||
.map_err(|_| MessageStoreError::CouldNotGetMessages(vec![]))?;
|
||||
let mut ret = vec![];
|
||||
let page = docs
|
||||
.drain(skip..)
|
||||
.collect::<Vec<(u64, tantivy::DocAddress)>>();
|
||||
for doc in page {
|
||||
let retrieved_doc = searcher.doc(doc.1).unwrap();
|
||||
ret.push(TantivyMessage::from_tantivy(retrieved_doc, &self.email));
|
||||
ret.push(
|
||||
TantivyMessage::from_tantivy(retrieved_doc, &self.email).map_err(|_| {
|
||||
MessageStoreError::CouldNotGetMessage("Message is corrupt".to_string())
|
||||
})?,
|
||||
);
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
@ -369,7 +330,7 @@ impl TantivyStore {
|
||||
// Is this needed? self.reader.load_searchers()?;
|
||||
let searcher = self.reader.searcher();
|
||||
let termq = TermQuery::new(
|
||||
Term::from_field_text(self.email.id, id.as_ref()),
|
||||
Term::from_field_text(self.email.id, id),
|
||||
IndexRecordOption::Basic,
|
||||
);
|
||||
let addr = searcher.search(&termq, &TopDocs::with_limit(1));
|
||||
@ -387,8 +348,8 @@ impl TantivyStore {
|
||||
pub fn _get_message(&self, id: &str) -> Option<TantivyMessage> {
|
||||
let doc = self.get_doc(id);
|
||||
match doc {
|
||||
Ok(doc) => Some(TantivyMessage::from_tantivy(doc, &self.email)),
|
||||
Err(_) => None,
|
||||
Ok(doc) => TantivyMessage::from_tantivy(doc, &self.email).ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn search(&self, text: &str, num: usize) -> Vec<TantivyMessage> {
|
||||
@ -401,11 +362,14 @@ impl TantivyStore {
|
||||
let mut ret = vec![];
|
||||
for doc in top_docs {
|
||||
let retrieved_doc = searcher.doc(doc.1).unwrap();
|
||||
ret.push(TantivyMessage::from_tantivy(retrieved_doc, &self.email));
|
||||
if let Ok(d) = TantivyMessage::from_tantivy(retrieved_doc, &self.email) {
|
||||
ret.push(d);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fuzzy(&self, text: &str, num: usize) -> Vec<TantivyMessage> {
|
||||
let mut terms = text.split(' ').collect::<Vec<&str>>();
|
||||
terms.insert(0, text);
|
||||
@ -413,7 +377,7 @@ impl TantivyStore {
|
||||
let mut ret = self.search(text, num);
|
||||
for n in 1..2 {
|
||||
if ret.len() < num {
|
||||
let mut queries: Vec<(Occur, Box<Query>)> = vec![];
|
||||
let mut queries: Vec<(Occur, Box<dyn Query>)> = vec![];
|
||||
for (_, t) in terms.iter().enumerate() {
|
||||
let term = Term::from_field_text(self.email.subject, t);
|
||||
let term_body = Term::from_field_text(self.email.body, t);
|
||||
@ -427,7 +391,9 @@ impl TantivyStore {
|
||||
let top_docs = searcher.search(&bquery, &top_docs_by_date).unwrap();
|
||||
for doc in top_docs {
|
||||
let retrieved_doc = searcher.doc(doc.1).unwrap();
|
||||
ret.push(TantivyMessage::from_tantivy(retrieved_doc, &self.email));
|
||||
if let Ok(d) = TantivyMessage::from_tantivy(retrieved_doc, &self.email) {
|
||||
ret.push(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::message::{Message, ShortMessage};
|
||||
use crate::message::Message;
|
||||
use crate::stores::{IMessageSearcher, IMessageStorage, IMessageStore, MessageStoreError};
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::error;
|
||||
@ -9,7 +9,7 @@ use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct MessageStore {
|
||||
pub searcher: Box<dyn IMessageSearcher>,
|
||||
@ -37,21 +37,21 @@ impl IMessageStore for MessageStore {
|
||||
}
|
||||
fn tag_message_id(
|
||||
&mut self,
|
||||
id: String,
|
||||
tags: HashSet<String>,
|
||||
_id: String,
|
||||
_tags: HashSet<String>,
|
||||
) -> Result<usize, MessageStoreError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn tag_message(
|
||||
&mut self,
|
||||
msg: Message,
|
||||
tags: HashSet<String>,
|
||||
_msg: Message,
|
||||
_tags: HashSet<String>,
|
||||
) -> Result<usize, MessageStoreError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn update_message(&mut self, msg: Message) -> Result<Message, MessageStoreError> {
|
||||
fn update_message(&mut self, _msg: Message) -> Result<Message, MessageStoreError> {
|
||||
unimplemented!();
|
||||
}
|
||||
fn get_messages_page(
|
||||
@ -67,12 +67,12 @@ impl IMessageStore for MessageStore {
|
||||
}
|
||||
fn search_by_date(
|
||||
&self,
|
||||
start: DateTime<Utc>,
|
||||
end: DateTime<Utc>,
|
||||
_start: DateTime<Utc>,
|
||||
_end: DateTime<Utc>,
|
||||
) -> Result<Vec<Message>, MessageStoreError> {
|
||||
unimplemented!();
|
||||
}
|
||||
fn delete_message(&mut self, msg: Message) -> Result<(), MessageStoreError> {
|
||||
fn delete_message(&mut self, _msg: Message) -> Result<(), MessageStoreError> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ impl MessageStore {
|
||||
}
|
||||
|
||||
fn init_progress(&mut self, num: usize) -> thread::JoinHandle<()> {
|
||||
let mut mb = MultiBar::new();
|
||||
let mb = MultiBar::new();
|
||||
mb.println(&format!("Indexing {} emails", num));
|
||||
let mut index_bar = mb.create_bar(num as u64);
|
||||
if num < 10_000_000 {
|
||||
@ -190,7 +190,7 @@ impl MessageStore {
|
||||
self.do_index_mails(maildir, full)?;
|
||||
Ok(1)
|
||||
}
|
||||
Err(e) => Err(MessageStoreError::CouldNotOpenMaildir(
|
||||
Err(_) => Err(MessageStoreError::CouldNotOpenMaildir(
|
||||
"Failed to read maildir".to_string(),
|
||||
)),
|
||||
}
|
||||
|
@ -3,12 +3,10 @@ use chrono::{DateTime, Utc};
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
mod _impl;
|
||||
use _impl::rocksdb;
|
||||
use _impl::tantivy;
|
||||
use std::fmt;
|
||||
pub mod message_store;
|
||||
use message_store::MessageStore;
|
||||
use std::time::Instant;
|
||||
|
||||
pub enum Searchers {
|
||||
Tantivy(PathBuf),
|
||||
@ -40,7 +38,11 @@ impl fmt::Display for MessageStoreBuilderError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MessageStoreBuilder {
|
||||
fn default() -> Self {
|
||||
MessageStoreBuilder::new()
|
||||
}
|
||||
}
|
||||
impl MessageStoreBuilder {
|
||||
pub fn new() -> MessageStoreBuilder {
|
||||
MessageStoreBuilder {
|
||||
@ -109,7 +111,7 @@ impl MessageStoreBuilder {
|
||||
Searchers::Tantivy(path) => {
|
||||
let mut p = path.clone();
|
||||
p.push("searcher");
|
||||
Ok(tantivy::TantivyStore::new(std::path::PathBuf::from(p)))
|
||||
Ok(tantivy::TantivyStore::new(p))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -59,11 +59,11 @@ impl Runnable for ListRunner {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handler() -> Box<InputHandler> {
|
||||
Box::new(InputHandler {
|
||||
pub fn handler() -> InputHandler {
|
||||
InputHandler {
|
||||
name: String::from("List"),
|
||||
pre: true,
|
||||
f: Box::new(ListRunner {}),
|
||||
children: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ mod search;
|
||||
pub struct InputHandler {
|
||||
pub name: String,
|
||||
pre: bool,
|
||||
f: Box<Runnable>,
|
||||
children: Vec<Box<InputHandler>>,
|
||||
f: Box<dyn Runnable>,
|
||||
children: Vec<InputHandler>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -26,15 +26,11 @@ impl Runnable for CtrlCRunner {
|
||||
Event::Input(key) => match key {
|
||||
Key::Ctrl('c') => {
|
||||
store.exit = true;
|
||||
return true;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,15 +42,11 @@ impl Runnable for QExitRunner {
|
||||
Event::Input(key) => match key {
|
||||
Key::Char('q') => {
|
||||
store.exit = true;
|
||||
return true;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,7 +68,7 @@ pub trait Runnable {
|
||||
}
|
||||
|
||||
impl InputHandler {
|
||||
pub fn new(name: String, children: Vec<Box<InputHandler>>) -> InputHandler {
|
||||
pub fn new(name: String, children: Vec<InputHandler>) -> InputHandler {
|
||||
Self {
|
||||
name,
|
||||
f: Box::new(CtrlCRunner {}),
|
||||
@ -84,13 +76,13 @@ impl InputHandler {
|
||||
children,
|
||||
}
|
||||
}
|
||||
pub fn new_single(name: String, f: Box<Runnable>, pre: bool) -> Box<InputHandler> {
|
||||
Box::new(Self {
|
||||
pub fn new_single(name: String, f: Box<dyn Runnable>, pre: bool) -> InputHandler {
|
||||
Self {
|
||||
name,
|
||||
f,
|
||||
pre,
|
||||
children: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
fn input(&self, e: &Event<Key>, store: &mut Store) -> bool {
|
||||
if self.pre {
|
||||
|
@ -48,11 +48,11 @@ impl Runnable for ReaderRunner {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handler() -> Box<InputHandler> {
|
||||
Box::new(InputHandler {
|
||||
pub fn handler() -> InputHandler {
|
||||
InputHandler {
|
||||
name: String::from("Reader"),
|
||||
pre: true,
|
||||
f: Box::new(ReaderRunner {}),
|
||||
children: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -31,36 +31,30 @@ impl Runnable for SearchRunner {
|
||||
store.search_store.set_search("".to_string());
|
||||
true
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
match e {
|
||||
Event::Input(key) => match key {
|
||||
Key::Char('/') => {
|
||||
store.search_store.enable_search();
|
||||
return true;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => return false,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handler() -> Box<InputHandler> {
|
||||
Box::new(InputHandler {
|
||||
pub fn handler() -> InputHandler {
|
||||
InputHandler {
|
||||
name: String::from("Search"),
|
||||
pre: true,
|
||||
f: Box::new(SearchRunner {}),
|
||||
children: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,11 @@ pub struct ListStore<'a> {
|
||||
pub page_size: usize,
|
||||
pub curr_idx: usize,
|
||||
pub fetched_first: bool,
|
||||
pub message_store: &'a IMessageStore,
|
||||
pub message_store: &'a dyn IMessageStore,
|
||||
}
|
||||
|
||||
impl<'a> ListStore<'a> {
|
||||
pub fn new(msg_store: &'a IMessageStore) -> ListStore<'a> {
|
||||
pub fn new(msg_store: &'a dyn IMessageStore) -> ListStore<'a> {
|
||||
ListStore {
|
||||
messages: vec![],
|
||||
selected: 0,
|
||||
@ -40,7 +40,7 @@ impl<'a> ListStore<'a> {
|
||||
}
|
||||
|
||||
pub fn prev_page(&mut self) -> &Self {
|
||||
self.set_selected(-1 * self.page_size as i32);
|
||||
self.set_selected(-(self.page_size as i32));
|
||||
self
|
||||
}
|
||||
pub fn set_selected(&mut self, offset: i32) -> &Self {
|
||||
@ -66,7 +66,7 @@ impl<'a> ListStore<'a> {
|
||||
.get_messages_page(self.curr_idx, page_size);
|
||||
match messages {
|
||||
Ok(messages) => self.messages = messages,
|
||||
Err(e) => self.messages = vec![], // TODO Handle error
|
||||
Err(_) => self.messages = vec![], // TODO Handle error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ pub struct Store<'a> {
|
||||
pub tags_store: TagsStore<'a>,
|
||||
}
|
||||
impl<'a> Store<'a> {
|
||||
pub fn new(message_store: &'a IMessageStore) -> Store {
|
||||
pub fn new(message_store: &'a dyn IMessageStore) -> Store {
|
||||
Store {
|
||||
exit: false,
|
||||
search_store: SearchStore::new(message_store),
|
||||
|
@ -5,11 +5,11 @@ use std::cmp::max;
|
||||
pub struct ReaderStore<'a> {
|
||||
pub message: Option<Message>,
|
||||
pub scroll: u16,
|
||||
pub storage: &'a IMessageStore,
|
||||
pub storage: &'a dyn IMessageStore,
|
||||
}
|
||||
|
||||
impl<'a> ReaderStore<'a> {
|
||||
pub fn new(storage: &'a IMessageStore) -> ReaderStore<'a> {
|
||||
pub fn new(storage: &'a dyn IMessageStore) -> ReaderStore<'a> {
|
||||
ReaderStore {
|
||||
message: None,
|
||||
scroll: 0,
|
||||
|
@ -4,11 +4,11 @@ use crate::stores::IMessageStore;
|
||||
pub struct SearchStore<'a> {
|
||||
pub search_term: String,
|
||||
pub searching: bool,
|
||||
pub searcher: &'a IMessageStore,
|
||||
pub searcher: &'a dyn IMessageStore,
|
||||
pub results: Vec<Message>,
|
||||
}
|
||||
impl<'a> SearchStore<'a> {
|
||||
pub fn new(msg_store: &'a IMessageStore) -> SearchStore {
|
||||
pub fn new(msg_store: &'a dyn IMessageStore) -> SearchStore {
|
||||
SearchStore {
|
||||
search_term: String::from(""),
|
||||
searching: false,
|
||||
|
@ -2,11 +2,11 @@ use crate::message::Message;
|
||||
use crate::stores::IMessageStore;
|
||||
|
||||
pub struct TagsStore<'a> {
|
||||
pub message_store: &'a IMessageStore,
|
||||
pub message_store: &'a dyn IMessageStore,
|
||||
pub message: Option<Message>,
|
||||
}
|
||||
impl<'a> TagsStore<'a> {
|
||||
pub fn new(msg_store: &'a IMessageStore) -> TagsStore<'a> {
|
||||
pub fn new(msg_store: &'a dyn IMessageStore) -> TagsStore<'a> {
|
||||
TagsStore {
|
||||
message: None,
|
||||
message_store: msg_store,
|
||||
|
@ -1,13 +1,12 @@
|
||||
use crate::message::Message;
|
||||
use tui::backend::Backend;
|
||||
use tui::layout::Rect;
|
||||
use tui::style::{Modifier, Style};
|
||||
use tui::widgets::{Block, Borders, Paragraph, Text, Widget};
|
||||
use tui::text::Text;
|
||||
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 = [Text::raw(text)];
|
||||
let f_r = f.size();
|
||||
let rect = Rect {
|
||||
x: f_r.x + f_r.width / 2 - 40,
|
||||
@ -16,16 +15,14 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, message: &Message, scroll: u16) {
|
||||
height: f_r.height,
|
||||
};
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title_style(Style::default().modifier(Modifier::BOLD));
|
||||
Paragraph::new(text.iter())
|
||||
let block = Block::default().borders(Borders::ALL);
|
||||
let p = Paragraph::new(Text::from(text.as_str()))
|
||||
.block(block.clone())
|
||||
.wrap(true)
|
||||
.scroll(scroll)
|
||||
.render(f, rect);
|
||||
.wrap(Wrap { trim: true })
|
||||
.scroll((scroll, 0));
|
||||
f.render_widget(p, rect);
|
||||
|
||||
Paragraph::new([Text::raw(format!("scroll {}", scroll))].iter())
|
||||
.wrap(true)
|
||||
.render(f, Rect::new(0, 0, 100, 2));
|
||||
let p2_str = format!("scroll {}", scroll);
|
||||
let p2 = Paragraph::new(Text::from(p2_str.as_str())).wrap(Wrap { trim: true });
|
||||
f.render_widget(p2, Rect::new(0, 0, 100, 2));
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ use crate::terminal::store::Store;
|
||||
use std::io;
|
||||
use tui::backend::Backend;
|
||||
use tui::layout::{Constraint, Direction, Layout};
|
||||
use tui::widgets::{Block, Borders, Paragraph, Text, Widget};
|
||||
use tui::text::Text;
|
||||
use tui::widgets::{Block, Borders, Paragraph, Wrap};
|
||||
use tui::Terminal;
|
||||
pub mod email_read;
|
||||
pub mod search_results;
|
||||
@ -25,16 +26,14 @@ pub fn draw<B: Backend>(terminal: &mut Terminal<B>, store: &Store) -> Result<(),
|
||||
.constraints([Constraint::Length(40), Constraint::Min(100)].as_ref())
|
||||
.split(main[0]);
|
||||
if store.search_store.searching {
|
||||
Paragraph::new([Text::raw(store.search_store.search_term.as_str())].iter())
|
||||
let s = Paragraph::new(Text::from(store.search_store.search_term.as_str()))
|
||||
.block(Block::default().title("Search").borders(Borders::ALL))
|
||||
.wrap(true)
|
||||
.render(&mut f, main[1]);
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(s, main[1]);
|
||||
}
|
||||
|
||||
Block::default()
|
||||
.title("Tags")
|
||||
.borders(Borders::ALL)
|
||||
.render(&mut f, chunks[0]);
|
||||
let t = Block::default().title("Tags").borders(Borders::ALL);
|
||||
f.render_widget(t, chunks[0]);
|
||||
search_results::draw(&mut f, chunks[1], &store);
|
||||
}
|
||||
})
|
||||
|
@ -2,7 +2,8 @@ use crate::terminal::store::Store;
|
||||
use tui::backend::Backend;
|
||||
use tui::layout::Rect;
|
||||
use tui::style::{Color, Modifier, Style};
|
||||
use tui::widgets::{Block, Borders, SelectableList, Widget};
|
||||
use tui::text::Span;
|
||||
use tui::widgets::{Block, Borders, List, ListItem, ListState};
|
||||
use tui::Frame;
|
||||
|
||||
pub fn draw<B: Backend>(f: &mut Frame<B>, area: Rect, store: &Store) {
|
||||
@ -12,17 +13,19 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, area: Rect, store: &Store) {
|
||||
} else {
|
||||
&store.search_store.results
|
||||
};
|
||||
SelectableList::default()
|
||||
let mut state = ListState::default();
|
||||
let items: Vec<ListItem> = display
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let s = s.to_string();
|
||||
ListItem::new(Span::raw(s))
|
||||
})
|
||||
.collect::<Vec<ListItem>>();
|
||||
let list = List::new(items)
|
||||
.block(Block::default().borders(Borders::ALL).title("List"))
|
||||
.items(
|
||||
&display
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.select(Some(store.list_store.selected))
|
||||
.style(style)
|
||||
.highlight_style(style.fg(Color::LightGreen).modifier(Modifier::BOLD))
|
||||
.highlight_symbol(">")
|
||||
.render(f, area);
|
||||
.highlight_style(style.fg(Color::LightGreen).add_modifier(Modifier::BOLD))
|
||||
.highlight_symbol(">");
|
||||
state.select(Some(store.list_store.selected));
|
||||
f.render_stateful_widget(list, area, &mut state);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user