mirror of
https://github.com/FliegendeWurst/inboxid.git
synced 2024-11-21 16:34:59 +00:00
browse: basic config system
This commit is contained in:
parent
7d2e6de827
commit
508934c50c
66
Cargo.lock
generated
66
Cargo.lock
generated
@ -275,6 +275,16 @@ dependencies = [
|
||||
"syn 0.11.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
@ -450,18 +460,25 @@ dependencies = [
|
||||
"chrono",
|
||||
"cursive",
|
||||
"cursive_tree_view",
|
||||
"directories-next",
|
||||
"imap",
|
||||
"itertools",
|
||||
"log",
|
||||
"maildir",
|
||||
"mailparse",
|
||||
"mailproc",
|
||||
"mime2ext",
|
||||
"moins",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"petgraph",
|
||||
"rusqlite",
|
||||
"rustls-connector",
|
||||
"rustyline",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"subprocess",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -474,6 +491,15 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.0"
|
||||
@ -526,13 +552,23 @@ checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.22.0"
|
||||
source = "git+https://github.com/rusqlite/rusqlite?branch=master#ddf69f749a67fdb673837ebb3c881a522fd7f638"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f6332d94daa84478d55a6aa9dbb3b305ed6500fb0cb9400cb9e1525d0e0e188"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
@ -733,6 +769,31 @@ dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.5.1"
|
||||
@ -852,7 +913,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.25.0"
|
||||
source = "git+https://github.com/rusqlite/rusqlite?branch=master#ddf69f749a67fdb673837ebb3c881a522fd7f638"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48381bf52627e7b0e02c4c0e4c0c88fc1cf2228a2eb7461d9499b1372399f1da"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
|
12
Cargo.toml
12
Cargo.toml
@ -15,8 +15,7 @@ mailparse = "0.13.2"
|
||||
rustls-connector = "0.13.1"
|
||||
ascii_table = { git = "https://gitlab.com/arnekeller/ascii-table.git", branch = "master" }
|
||||
chrono = "0.4.19"
|
||||
# remove when 0.24.3 is released
|
||||
rusqlite = { git = "https://github.com/rusqlite/rusqlite", branch = "master", features = ["bundled"] }
|
||||
rusqlite = { version = "0.25.0", features = ["bundled"] }
|
||||
rustyline = "8.0.0"
|
||||
moins = { git = "https://github.com/FliegendeWurst/moins", branch = "master" }
|
||||
anyhow = "1.0.40"
|
||||
@ -26,6 +25,13 @@ mime2ext = "0.1.2"
|
||||
petgraph = "0.5.1"
|
||||
cursive = { version = "0.16.3", default-features = false, features = ["termion-backend"] }
|
||||
cursive_tree_view = { git = "https://github.com/FliegendeWurst/cursive_tree_view.git", branch = "master" }
|
||||
directories-next = "2.0.0"
|
||||
serde_derive = "1.0.25"
|
||||
serde = "1.0.25"
|
||||
toml = "0.5.8"
|
||||
once_cell = "1.7.2"
|
||||
parking_lot = "0.11.1"
|
||||
log = "0.4.14"
|
||||
|
||||
[profile.release]
|
||||
overflow-checks = true
|
||||
overflow-checks = true # useful when debugging
|
||||
|
@ -3,17 +3,21 @@
|
||||
use std::{array::IntoIter, cell::RefCell, cmp, collections::{HashMap, HashSet}, env, fmt::Display, io, sync::{Arc, Mutex}};
|
||||
|
||||
use cursive::{Cursive, CursiveExt};
|
||||
use cursive::event::{Event, Key};
|
||||
use cursive::traits::Identifiable;
|
||||
use cursive::view::{Scrollable, SizeConstraint};
|
||||
use cursive::views::{LinearLayout, ResizedView, TextView};
|
||||
use cursive::view::{Scrollable, SizeConstraint, View};
|
||||
use cursive::views::{Button, Checkbox, LinearLayout, OnEventView, ResizedView, TextView};
|
||||
use cursive_tree_view::{Placement, TreeEntry, TreeView};
|
||||
use inboxid::*;
|
||||
use io::Write;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use mailparse::ParsedMail;
|
||||
use parking_lot::RwLock;
|
||||
use petgraph::{EdgeDirection, graph::{DiGraph, NodeIndex}, visit::{Dfs, IntoNodeReferences}};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
load_config();
|
||||
let sink = Arc::new(Mutex::new(Vec::new()));
|
||||
std::io::set_output_capture(Some(sink.clone()));
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
@ -238,6 +242,40 @@ fn show_listing(mailbox: &str) -> Result<()> {
|
||||
.child(mail_content_resized);
|
||||
siv.add_fullscreen_layer(ResizedView::with_full_screen(main));
|
||||
|
||||
let mut setup = LinearLayout::vertical();
|
||||
{
|
||||
let config = CONFIG.get().unwrap().read();
|
||||
let show_email_addresses = Checkbox::new()
|
||||
.with_checked(config.browse.show_email_addresses)
|
||||
.on_change(|_siv, checked| {
|
||||
CONFIG.get().unwrap().write().browse.show_email_addresses = checked;
|
||||
});
|
||||
setup.add_child(
|
||||
LinearLayout::horizontal()
|
||||
.child(show_email_addresses)
|
||||
.child(TextView::new(" Show email addresses"))
|
||||
);
|
||||
}
|
||||
// most horrible hack
|
||||
let setup: Arc<RwLock<Option<Box<dyn View>>>> = Arc::new(RwLock::new(Some(Box::new(ResizedView::new(SizeConstraint::Full, SizeConstraint::Full, setup)))));
|
||||
let setup2 = Arc::clone(&setup);
|
||||
let setup_view: ResizedView<LinearLayout> = *setup.write().take().unwrap().as_boxed_any().downcast().unwrap();
|
||||
let setup_view = OnEventView::new(setup_view)
|
||||
.on_event(Event::Key(Key::F10), move |s| {
|
||||
let setup = s.pop_layer().unwrap();
|
||||
*setup2.write() = Some(setup);
|
||||
if let Err(e) = CONFIG.get().unwrap().read().save() {
|
||||
error!("failed to save config {:?}", e);
|
||||
}
|
||||
});
|
||||
*setup.write() = Some(Box::new(setup_view));
|
||||
|
||||
let setup2 = Arc::clone(&setup);
|
||||
siv.add_global_callback(Event::Key(Key::F2), move |s| {
|
||||
let setup = setup2.write().take().unwrap();
|
||||
s.add_fullscreen_layer(setup);
|
||||
});
|
||||
|
||||
siv.add_global_callback('q', |s| s.quit());
|
||||
|
||||
siv.run();
|
||||
|
120
src/lib.rs
120
src/lib.rs
@ -1,15 +1,20 @@
|
||||
use std::{borrow::Cow, cmp, convert::{TryFrom, TryInto}, env, fmt::{Debug, Display}, fs, hash::Hash, io, net::TcpStream, ops::Deref};
|
||||
use std::{borrow::Cow, cmp, convert::{TryFrom, TryInto}, env, fmt::{Debug, Display}, fs, hash::Hash, io, net::TcpStream, ops::Deref, path::PathBuf};
|
||||
|
||||
use anyhow::Context;
|
||||
use chrono::{DateTime, Local, NaiveDateTime, TimeZone};
|
||||
use cursive::{theme::Style, utils::span::{IndexedCow, IndexedSpan, SpannedString}};
|
||||
use cursive_tree_view::TreeEntry;
|
||||
use directories_next::ProjectDirs;
|
||||
use imap::{Session, types::Flag};
|
||||
use log::info;
|
||||
use maildir::{MailEntry, Maildir};
|
||||
use mailparse::{MailHeaderMap, ParsedMail, SingleInfo, addrparse, dateparse};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::RwLock;
|
||||
use petgraph::{Graph, graph::NodeIndex};
|
||||
use rusqlite::{Connection, params};
|
||||
use rustls_connector::{RustlsConnector, rustls::{ClientSession, StreamOwned}};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
pub type ImapSession = Session<StreamOwned<ClientSession, TcpStream>>;
|
||||
@ -107,14 +112,14 @@ pub struct EasyMail<'a> {
|
||||
impl EasyMail<'_> {
|
||||
pub fn new_pseudo(subject: String) -> Self {
|
||||
Self {
|
||||
mail: None,
|
||||
id: MaildirID::new(0, 0),
|
||||
mail: None,
|
||||
id: MaildirID::new(0, 0),
|
||||
flags: "S".to_owned(),
|
||||
from: SingleInfo {
|
||||
display_name: None,
|
||||
addr: String::new()
|
||||
},
|
||||
subject,
|
||||
subject,
|
||||
date: Local.from_utc_datetime(&NaiveDateTime::from_timestamp(0, 0)),
|
||||
date_iso: "????-??-??".to_owned()
|
||||
}
|
||||
@ -125,7 +130,13 @@ impl EasyMail<'_> {
|
||||
}
|
||||
|
||||
pub fn from(&self) -> String {
|
||||
self.from.display_name.as_deref().unwrap_or_default().to_owned()
|
||||
let name = self.from.display_name.as_deref().unwrap_or_default();
|
||||
if let Some(config) = CONFIG.get() {
|
||||
if config.read().browse.show_email_addresses {
|
||||
return format!("{} <{}>", name, self.from.addr);
|
||||
}
|
||||
}
|
||||
name.to_owned()
|
||||
}
|
||||
|
||||
pub fn get_header(&self, header: &str) -> String {
|
||||
@ -138,32 +149,32 @@ impl EasyMail<'_> {
|
||||
}
|
||||
|
||||
impl Debug for EasyMail<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Mail[ID={},Subject={:?}]", self.id.uid, self.subject)
|
||||
}
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Mail[ID={},Subject={:?}]", self.id.uid, self.subject)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EasyMail<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.subject)
|
||||
}
|
||||
write!(f, "{}", self.subject)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for EasyMail<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id && self.from == other.from && self.subject == other.subject
|
||||
}
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id && self.from == other.from && self.subject == other.subject
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for EasyMail<'_> {}
|
||||
|
||||
impl Hash for EasyMail<'_> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
self.from.display_name.hash(state);
|
||||
self.from.addr.hash(state);
|
||||
self.subject.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for EasyMail<'a> {
|
||||
@ -387,3 +398,80 @@ pub fn get_imap_session() -> Result<ImapSession> {
|
||||
let port = 993;
|
||||
connect(&host, port, &user, &password)
|
||||
}
|
||||
|
||||
pub fn load_config() {
|
||||
CONFIG.get_or_init(|| {
|
||||
let config = Config::load_from_fs();
|
||||
let cfg = match config {
|
||||
Ok(config) => if let Some(config) = config {
|
||||
config.into()
|
||||
} else {
|
||||
Config::default().into()
|
||||
},
|
||||
Err(e) => panic!("failed to load configuration: {:?}", e)
|
||||
};
|
||||
info!("config {:?}", cfg);
|
||||
cfg
|
||||
});
|
||||
}
|
||||
|
||||
pub static CONFIG: OnceCell<RwLock<Config>> = OnceCell::new();
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
pub browse: Browse
|
||||
}
|
||||
|
||||
fn get_paths() -> Result<ProjectDirs> {
|
||||
Ok(directories_next::ProjectDirs::from("", "", "Inboxid").context("unable to determine configuration directory")?)
|
||||
}
|
||||
|
||||
fn get_config_path() -> Result<PathBuf> {
|
||||
let paths = get_paths()?;
|
||||
Ok(paths.config_dir().join("config.toml"))
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn load_from_fs() -> Result<Option<Self>> {
|
||||
let config = get_config_path()?;
|
||||
if config.exists() {
|
||||
let content = fs::read_to_string(&config)?;
|
||||
Ok(Some(toml::from_str(&content)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
let config = get_config_path()?;
|
||||
fs::create_dir_all(config.parent().unwrap())?;
|
||||
fs::write(config, toml::to_string(&self)?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
browse: Browse {
|
||||
show_email_addresses: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Browse {
|
||||
#[serde(default)]
|
||||
pub show_email_addresses: bool
|
||||
}
|
||||
|
||||
impl Default for Browse {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
show_email_addresses: Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user