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