mirror of
https://github.com/FliegendeWurst/inboxid.git
synced 2024-11-21 16:34:59 +00:00
Refactor maildir reading into lib
This commit is contained in:
parent
19347d3249
commit
a349925f78
41
Cargo.lock
generated
41
Cargo.lock
generated
@ -17,6 +17,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
@ -265,6 +271,7 @@ dependencies = [
|
||||
name = "inboxid"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ascii_table",
|
||||
"chrono",
|
||||
"imap",
|
||||
@ -288,9 +295,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.49"
|
||||
version = "0.3.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821"
|
||||
checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@ -316,9 +323,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.91"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
|
||||
checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
@ -367,7 +374,7 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
[[package]]
|
||||
name = "moins"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/FliegendeWurst/moins?branch=master#fd6d6ccebbe33effe49d13fd26ff2214f1655d41"
|
||||
source = "git+https://github.com/FliegendeWurst/moins?branch=master#46df794709b7fdc23e83e61d97c75b11e8248f0f"
|
||||
dependencies = [
|
||||
"termion",
|
||||
]
|
||||
@ -772,9 +779,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.72"
|
||||
version = "0.2.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe"
|
||||
checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
@ -782,9 +789,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.72"
|
||||
version = "0.2.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3"
|
||||
checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
@ -797,9 +804,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.72"
|
||||
version = "0.2.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b"
|
||||
checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@ -807,9 +814,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.72"
|
||||
version = "0.2.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d"
|
||||
checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -820,15 +827,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.72"
|
||||
version = "0.2.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa"
|
||||
checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.49"
|
||||
version = "0.3.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310"
|
||||
checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
@ -19,3 +19,4 @@ chrono = "0.4.19"
|
||||
rusqlite = { git = "https://github.com/rusqlite/rusqlite", branch = "master", features = ["bundled"] }
|
||||
rustyline = "8.0.0"
|
||||
moins = { git = "https://github.com/FliegendeWurst/moins", branch = "master" }
|
||||
anyhow = "1.0.40"
|
||||
|
@ -1,10 +1,8 @@
|
||||
use std::{collections::HashMap, env};
|
||||
use std::{array::IntoIter, env};
|
||||
|
||||
use ascii_table::{Align, AsciiTable, Column};
|
||||
use chrono::{Local, NaiveDateTime, TimeZone};
|
||||
use inboxid::*;
|
||||
use itertools::Itertools;
|
||||
use mailparse::{MailHeaderMap, dateparse};
|
||||
use rustyline::{Editor, error::ReadlineError};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@ -19,57 +17,29 @@ fn main() -> Result<()> {
|
||||
fn show_listing(mailbox: &str) -> Result<()> {
|
||||
let maildir = get_maildir(mailbox)?;
|
||||
|
||||
let mut rows = Vec::new();
|
||||
let mut mail_list = Vec::new();
|
||||
let mut i = 0;
|
||||
// TODO(refactor) merge with new
|
||||
let mut list = maildir.list_cur_sorted(Box::new(|name| {
|
||||
// sort by UID
|
||||
name.splitn(2, '_').nth(1).map(|x| x.parse().unwrap_or(0)).unwrap_or(0)
|
||||
})).collect_vec();
|
||||
let list = list.iter_mut().map(
|
||||
|x| x.as_mut().map(|x| (x.flags().to_owned(), x.id().to_owned(), x.parsed()))).collect_vec();
|
||||
for maile in &list {
|
||||
match maile {
|
||||
Ok((flags, id, Ok(mail))) => {
|
||||
let headers = mail.get_headers();
|
||||
let from = headers.get_all_values("From").join(" ");
|
||||
let subj = headers.get_all_values("Subject").join(" ");
|
||||
let date = headers.get_all_values("Date").join(" ");
|
||||
let date = dateparse(&date).map(|x| {
|
||||
let dt = Local.from_utc_datetime(&NaiveDateTime::from_timestamp(x, 0));
|
||||
dt.format("%Y-%m-%d %H:%M").to_string()
|
||||
}).unwrap_or(date);
|
||||
let mut flags_display = String::new();
|
||||
if flags.contains('F') {
|
||||
flags_display.push('+');
|
||||
}
|
||||
if flags.contains('R') {
|
||||
flags_display.push('R');
|
||||
}
|
||||
if flags.contains('S') {
|
||||
flags_display.push(' ');
|
||||
} else {
|
||||
flags_display.push('*');
|
||||
}
|
||||
rows.push(vec![i.to_string(), flags_display, from, subj, date]);
|
||||
i += 1;
|
||||
mail_list.push((flags, id, mail));
|
||||
}
|
||||
Ok((_, _, Err(e))) => {
|
||||
println!("error parsing mail: {:?}", e);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error: {:?}", e);
|
||||
}
|
||||
}
|
||||
let mut mails = Vec::new();
|
||||
for x in maildir.list_cur() {
|
||||
mails.push(x?);
|
||||
}
|
||||
rows.sort_unstable_by(|x, y| x[4].cmp(&y[4]));
|
||||
let count = rows.len();
|
||||
let mut mails = HashMap::new();
|
||||
for (i, row) in rows.iter_mut().enumerate() {
|
||||
mails.insert(count - i, &mail_list[row[0].parse::<usize>().unwrap()]);
|
||||
row[0] = (count - i).to_string();
|
||||
let mut mails = maildir.get_mails(&mut mails)?;
|
||||
mails.sort_by_key(|x| x.date);
|
||||
|
||||
let mut rows = Vec::new();
|
||||
for (i, mail) in mails.iter().enumerate() {
|
||||
let flags = &mail.flags;
|
||||
let mut flags_display = String::new();
|
||||
if flags.contains('F') {
|
||||
flags_display.push('+');
|
||||
}
|
||||
if flags.contains('R') {
|
||||
flags_display.push('R');
|
||||
}
|
||||
if flags.contains('S') {
|
||||
flags_display.push(' ');
|
||||
} else {
|
||||
flags_display.push('*');
|
||||
}
|
||||
rows.push(IntoIter::new([(mails.len() - i).to_string(), flags_display, mail.from.clone(), mail.subject.clone(), mail.date_iso.clone()]));
|
||||
}
|
||||
|
||||
let mut ascii_table = AsciiTable::default();
|
||||
@ -98,9 +68,10 @@ fn show_listing(mailbox: &str) -> Result<()> {
|
||||
let readline = rl.readline(">> ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
let mail = &mails[&line.trim().parse::<usize>().unwrap()];
|
||||
println!("{}", std::str::from_utf8(&mail.2.get_body_raw().unwrap()).unwrap());
|
||||
for x in &mail.2.subparts {
|
||||
let idx = mails.len() - line.trim().parse::<usize>().unwrap();
|
||||
let mail = &mails[idx];
|
||||
println!("{}", std::str::from_utf8(&mail.get_body_raw().unwrap()).unwrap());
|
||||
for x in &mail.subparts {
|
||||
if x.ctype.mimetype == "text/html" {
|
||||
continue; // TODO
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
use std::env;
|
||||
use std::{array::IntoIter, env};
|
||||
|
||||
use ascii_table::{Align, AsciiTable, Column};
|
||||
use chrono::{Local, NaiveDateTime, TimeZone};
|
||||
use itertools::Itertools;
|
||||
use mailparse::{MailHeaderMap, dateparse};
|
||||
|
||||
use inboxid::*;
|
||||
|
||||
@ -19,32 +17,16 @@ fn main() -> Result<()> {
|
||||
fn show_listing(mailbox: &str) -> Result<()> {
|
||||
let maildir = get_maildir(mailbox)?;
|
||||
|
||||
let mut mails = Vec::new();
|
||||
for x in maildir.list_new() {
|
||||
mails.push(x?);
|
||||
}
|
||||
let mut mails = maildir.get_mails(&mut mails)?;
|
||||
mails.sort_by_key(|x| x.id);
|
||||
|
||||
let mut rows = Vec::new();
|
||||
let mut seen = Vec::new();
|
||||
for mut maile in maildir.list_new_sorted(Box::new(|name| {
|
||||
// sort by UID
|
||||
u32::MAX - name.splitn(2, '_').nth(1).map(|x| x.parse().unwrap_or(0)).unwrap_or(0)
|
||||
})) {
|
||||
match maile.as_mut().map(|x| x.parsed()) {
|
||||
Ok(Ok(mail)) => {
|
||||
let headers = mail.get_headers();
|
||||
let from = headers.get_all_values("From").join(" ");
|
||||
let subj = headers.get_all_values("Subject").join(" ");
|
||||
let date = headers.get_all_values("Date").join(" ");
|
||||
let date = dateparse(&date).map(|x| {
|
||||
let dt = Local.from_utc_datetime(&NaiveDateTime::from_timestamp(x, 0));
|
||||
dt.format("%Y-%m-%d %H:%M").to_string()
|
||||
}).unwrap_or(date);
|
||||
rows.push(vec![from, subj, date]);
|
||||
seen.push(maile.as_ref().unwrap().id().to_owned());
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
println!("error parsing mail: {:?}", e);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error: {:?}", e);
|
||||
}
|
||||
}
|
||||
for mail in &mails {
|
||||
rows.push(IntoIter::new([mail.from.clone(), mail.subject.clone(), mail.date_iso.clone()]));
|
||||
}
|
||||
|
||||
let mut ascii_table = AsciiTable::default();
|
||||
@ -64,8 +46,8 @@ fn show_listing(mailbox: &str) -> Result<()> {
|
||||
ascii_table.print(rows); // prints a 0 if empty :)
|
||||
|
||||
// only after the user saw the new mail, move it out of 'new'
|
||||
for seen in seen {
|
||||
maildir.move_new_to_cur(&seen)?;
|
||||
for seen in mails {
|
||||
maildir.move_new_to_cur(&seen.id.to_string())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
94
src/lib.rs
94
src/lib.rs
@ -1,7 +1,10 @@
|
||||
use std::{borrow::Cow, env, fs, io, net::TcpStream};
|
||||
use std::{borrow::Cow, convert::{TryFrom, TryInto}, env, fs, io, net::TcpStream, ops::Deref};
|
||||
|
||||
use anyhow::Context;
|
||||
use chrono::{DateTime, Local, NaiveDateTime, TimeZone};
|
||||
use imap::{Session, types::Flag};
|
||||
use maildir::Maildir;
|
||||
use maildir::{MailEntry, Maildir};
|
||||
use mailparse::{MailHeaderMap, ParsedMail, dateparse};
|
||||
use rusqlite::{Connection, params};
|
||||
use rustls_connector::{RustlsConnector, rustls::{ClientSession, StreamOwned}};
|
||||
|
||||
@ -48,9 +51,54 @@ pub fn gen_id(uid_validity: u32, uid: u32) -> String {
|
||||
format!("{}_{}", uid_validity, uid)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct MaildirID {
|
||||
uid_validity: u32,
|
||||
uid: u32,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for MaildirID {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
|
||||
fn try_from(id: &str) -> Result<Self> {
|
||||
let mut parts = id.splitn(2, '_');
|
||||
let uid_validity = parts.next().context("invalid ID")?.parse()?;
|
||||
let uid = parts.next().context("invalid ID")?.parse()?;
|
||||
Ok(MaildirID {
|
||||
uid_validity,
|
||||
uid,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for MaildirID {
|
||||
fn to_string(&self) -> String {
|
||||
format!("{}_{}", self.uid_validity, self.uid)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EasyMail<'a> {
|
||||
pub mail: ParsedMail<'a>,
|
||||
pub id: MaildirID,
|
||||
pub flags: String,
|
||||
pub from: String,
|
||||
pub subject: String,
|
||||
pub date: DateTime<Local>,
|
||||
pub date_iso: String,
|
||||
}
|
||||
|
||||
impl<'a> Deref for EasyMail<'a> {
|
||||
type Target = ParsedMail<'a>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.mail
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MaildirExtension {
|
||||
fn get_file(&self, name: &str) -> std::result::Result<String, io::Error>;
|
||||
fn save_file(&self, name: &str, content: &str) -> std::result::Result<(), io::Error>;
|
||||
fn get_mails<'a>(&self, entries: &'a mut [MailEntry]) -> Result<Vec<EasyMail<'a>>>;
|
||||
}
|
||||
|
||||
impl MaildirExtension for Maildir {
|
||||
@ -61,6 +109,32 @@ impl MaildirExtension for Maildir {
|
||||
fn save_file(&self, name: &str, content: &str) -> std::result::Result<(), io::Error> {
|
||||
fs::write(self.path().join(name), content)
|
||||
}
|
||||
|
||||
fn get_mails<'a>(&self, entries: &'a mut [MailEntry]) -> Result<Vec<EasyMail<'a>>> {
|
||||
let mut mails = Vec::new();
|
||||
for maile in entries {
|
||||
let id = maile.id().try_into()?;
|
||||
let flags = maile.flags().to_owned();
|
||||
let mail = maile.parsed()?;
|
||||
let headers = mail.get_headers();
|
||||
let from = headers.get_all_values("From").join(" ");
|
||||
let subject = headers.get_all_values("Subject").join(" ");
|
||||
let date = headers.get_all_values("Date").join(" ");
|
||||
let date = dateparse(&date).map(|x|
|
||||
Local.from_utc_datetime(&NaiveDateTime::from_timestamp(x, 0))
|
||||
)?;
|
||||
mails.push(EasyMail {
|
||||
mail,
|
||||
flags,
|
||||
id,
|
||||
from,
|
||||
subject,
|
||||
date_iso: date.format("%Y-%m-%d %H:%M").to_string(),
|
||||
date,
|
||||
});
|
||||
}
|
||||
Ok(mails)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_i64(x: u64) -> i64 {
|
||||
@ -73,13 +147,13 @@ pub fn load_i64(x: i64) -> u64 {
|
||||
|
||||
pub fn remove_cow<'a>(x: &Flag<'a>) -> Flag<'static> {
|
||||
match x {
|
||||
Flag::Custom(x) => Flag::Custom(Cow::Owned(x.to_string())),
|
||||
Flag::Seen => Flag::Seen,
|
||||
Flag::Answered => Flag::Answered,
|
||||
Flag::Flagged => Flag::Flagged,
|
||||
Flag::Deleted => Flag::Deleted,
|
||||
Flag::Draft => Flag::Draft,
|
||||
Flag::Recent => Flag::Recent,
|
||||
Flag::MayCreate => Flag::MayCreate,
|
||||
Flag::Custom(x) => Flag::Custom(Cow::Owned(x.to_string())),
|
||||
Flag::Seen => Flag::Seen,
|
||||
Flag::Answered => Flag::Answered,
|
||||
Flag::Flagged => Flag::Flagged,
|
||||
Flag::Deleted => Flag::Deleted,
|
||||
Flag::Draft => Flag::Draft,
|
||||
Flag::Recent => Flag::Recent,
|
||||
Flag::MayCreate => Flag::MayCreate,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user