From a1e9576d07ce453c0831940f44a4f6f302190cb9 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Fri, 26 Mar 2021 13:23:02 -0500 Subject: [PATCH] lots of updates, gonna probably ditch tui-rs --- .github/workflows/ci.yml | 5 +- Cargo.lock | 273 +-------------------------------------- Cargo.toml | 3 +- imap/src/parser/mod.rs | 1 - src/lib.rs | 2 + src/mail/mod.rs | 2 +- src/mail/store.rs | 140 ++++++++++++++++---- src/main.rs | 10 +- src/ui/mail_view.rs | 158 ++++++++++------------ src/ui/mod.rs | 12 +- src/ui/windows.rs | 18 +-- tui/Cargo.toml | 7 + tui/src/lib.rs | 0 13 files changed, 224 insertions(+), 407 deletions(-) create mode 100644 tui/Cargo.toml create mode 100644 tui/src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f0398f..9ab4521 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,7 @@ -on: [push] +on: + push: + branches: + - master name: workflow diff --git a/Cargo.lock b/Cargo.lock index f44e117..799115f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,15 +203,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "bitpacking" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3744aff20a3437a99ebc0bb7733e9e60c7bf590478c9b897e95b38d57e5acb68" -dependencies = [ - "crunchy", -] - [[package]] name = "bitvec" version = "0.19.5" @@ -385,12 +376,6 @@ dependencies = [ "jobserver", ] -[[package]] -name = "census" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5927edd8345aef08578bcbb4aea7314f340d80c7f4931f99fbeb40b99d8f5060" - [[package]] name = "cfg-if" version = "0.1.10" @@ -486,15 +471,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "combine" -version = "4.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4369b5e4c0cddf64ad8981c0111e7df4f7078f4d6ba98fb31f2e17c4c57b7e" -dependencies = [ - "memchr", -] - [[package]] name = "commoncrypto" version = "0.2.0" @@ -583,20 +559,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crossbeam" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils 0.8.3", -] - [[package]] name = "crossbeam-channel" version = "0.5.0" @@ -607,30 +569,6 @@ dependencies = [ "crossbeam-utils 0.8.3", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-epoch", - "crossbeam-utils 0.8.3", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils 0.8.3", - "lazy_static", - "memoffset", - "scopeguard", -] - [[package]] name = "crossbeam-queue" version = "0.3.1" @@ -951,17 +889,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "fail" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3c61c59fdc91f5dbc3ea31ee8623122ce80057058be560654c5d410d181a6" -dependencies = [ - "lazy_static", - "log", - "rand 0.7.3", -] - [[package]] name = "fastrand" version = "1.4.0" @@ -1071,16 +998,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e04cda45add94e71c2990de778ae13059897d77b773130a9bc225e2970c413e" -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "funty" version = "1.1.0" @@ -1133,7 +1050,6 @@ dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] @@ -1534,12 +1450,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "htmlescape" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" - [[package]] name = "humantime" version = "2.1.0" @@ -1694,7 +1604,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.6.23", + "regex-syntax", "string_cache", "term", "tiny-keccak", @@ -1722,12 +1632,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "levenshtein_automata" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44db4199cdb049b494a92d105acbfa43c25b3925e33803923ba9580b7bc9e1a" - [[package]] name = "lexical-core" version = "0.7.5" @@ -1826,15 +1730,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "lru" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f374d42cdfc1d7dbf3d3dec28afab2eb97ffbf43a3234d795b5986dbf4b90ba" -dependencies = [ - "hashbrown 0.9.1", -] - [[package]] name = "mac-notification-sys" version = "0.3.0" @@ -1885,25 +1780,6 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" -[[package]] -name = "memmap" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "memoffset" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" -dependencies = [ - "autocfg", -] - [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1937,15 +1813,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "murmurhash32" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d736ff882f0e85fe9689fb23db229616c4c00aee2b3ac282f666d8f20eb25d4a" -dependencies = [ - "byteorder", -] - [[package]] name = "nb-connect" version = "1.0.3" @@ -2167,7 +2034,6 @@ dependencies = [ "shellexpand", "sqlx", "structopt", - "tantivy", "tokio 1.3.0", "tokio-rustls", "tokio-stream", @@ -2204,6 +2070,10 @@ dependencies = [ name = "panorama-smtp" version = "0.1.0" +[[package]] +name = "panorama-tui" +version = "0.1.0" + [[package]] name = "parking" version = "2.0.0" @@ -2552,31 +2422,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rayon" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils 0.8.3", - "lazy_static", - "num_cpus", -] - [[package]] name = "redox_syscall" version = "0.1.57" @@ -2621,15 +2466,9 @@ checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.6.23", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" - [[package]] name = "regex-syntax" version = "0.6.23" @@ -2681,16 +2520,6 @@ dependencies = [ "crossbeam-utils 0.8.3", ] -[[package]] -name = "rust-stemmers" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" -dependencies = [ - "serde", - "serde_derive", -] - [[package]] name = "rustc-hash" version = "1.1.0" @@ -2931,12 +2760,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" -[[package]] -name = "snap" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc725476a1398f0480d56cd0ad381f6f32acf2642704456f8f59a35df464b59a" - [[package]] name = "socket2" version = "0.3.19" @@ -3050,12 +2873,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "0.3.4" @@ -3188,68 +3005,6 @@ dependencies = [ "unicode-xid 0.0.4", ] -[[package]] -name = "tantivy" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca90bddda472f39fdc74a031d61d52b08b1de97f2a704afae726a8004abb0d" -dependencies = [ - "base64 0.13.0", - "bitpacking", - "byteorder", - "census", - "chrono", - "crc32fast", - "crossbeam", - "downcast-rs", - "fail", - "fnv", - "fs2", - "futures 0.3.13", - "htmlescape", - "levenshtein_automata", - "log", - "lru", - "memmap", - "murmurhash32", - "num_cpus", - "once_cell", - "rayon", - "regex", - "rust-stemmers", - "serde", - "serde_json", - "smallvec", - "snap", - "stable_deref_trait", - "tantivy-fst", - "tantivy-query-grammar", - "tempfile", - "thiserror", - "uuid", - "winapi", -] - -[[package]] -name = "tantivy-fst" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb20cdc0d83e9184560bdde9cd60142dbb4af2e0f770e88fce45770495224205" -dependencies = [ - "byteorder", - "regex-syntax 0.4.2", - "utf8-ranges", -] - -[[package]] -name = "tantivy-query-grammar" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70864085b31ecd5af8f53a76506440ece1c426d187f3d72f4b722e238d2ce19a" -dependencies = [ - "combine", -] - [[package]] name = "tap" version = "1.0.1" @@ -3562,28 +3317,12 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf8-ranges" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" - [[package]] name = "utf8parse" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom 0.2.2", - "serde", -] - [[package]] name = "vcpkg" version = "0.2.11" diff --git a/Cargo.toml b/Cargo.toml index e1a8e2f..514d657 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ license = "GPL-3.0-or-later" members = [ "imap", "smtp", + "tui", ] [dependencies] @@ -46,7 +47,7 @@ sha2 = "0.9.3" hex = "0.4.3" shellexpand = "2.1.0" mailparse = "0.13.2" -tantivy = "0.14.0" +# tantivy = "0.14.0" [dependencies.panorama-imap] path = "imap" diff --git a/imap/src/parser/mod.rs b/imap/src/parser/mod.rs index d7afb82..118afc5 100644 --- a/imap/src/parser/mod.rs +++ b/imap/src/parser/mod.rs @@ -36,7 +36,6 @@ pub fn parse_capability(s: impl AsRef) -> ParseResult { pub fn parse_streamed_response(s: impl AsRef) -> ParseResult<(Response, usize)> { let s = s.as_ref(); - let len = s.len(); let mut pairs = match Rfc3501::parse(Rule::streamed_response, s) { Ok(v) => v, Err(e) => { diff --git a/src/lib.rs b/src/lib.rs index 4d97a5f..9efbe9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ #[macro_use] extern crate anyhow; #[macro_use] +extern crate async_trait; +#[macro_use] extern crate crossterm; #[macro_use] extern crate format_bytes; diff --git a/src/mail/mod.rs b/src/mail/mod.rs index ec4b1a9..edce37b 100644 --- a/src/mail/mod.rs +++ b/src/mail/mod.rs @@ -44,6 +44,7 @@ pub enum MailCommand { /// Main entrypoint for the mail listener. pub async fn run_mail( + mail_store: MailStore, mut config_watcher: ConfigWatcher, ui2mail_rx: UnboundedReceiver, mail2ui_tx: UnboundedSender, @@ -67,7 +68,6 @@ pub async fn run_mail( conn.abort(); } - let mail_store = MailStore::new(config.clone()).await?; for (acct_name, acct) in config.mail_accounts.clone().into_iter() { let mail2ui_tx = mail2ui_tx.clone(); let mail_store = mail_store.clone(); diff --git a/src/mail/store.rs b/src/mail/store.rs index e1d572e..d9c0471 100644 --- a/src/mail/store.rs +++ b/src/mail/store.rs @@ -1,5 +1,7 @@ -//! Package for managing the offline storage of emails +//! Module for managing the offline storage of emails +use std::collections::HashMap; +use std::mem; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -11,21 +13,34 @@ use sqlx::{ sqlite::{Sqlite, SqlitePool}, Error as SqlxError, Row, }; -use tokio::{fs, sync::broadcast}; +use tokio::{ + fs, + sync::{broadcast, RwLock}, + task::JoinHandle, +}; -use crate::config::Config; +use crate::config::{Config, ConfigWatcher}; + +use super::MailEvent; static MIGRATOR: Migrator = sqlx::migrate!(); /// Manages email storage on disk, for both database and caches /// /// This struct is clone-safe: cloning it will just return a reference to the same data structure -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MailStore { - config: Config, - mail_dir: PathBuf, + config: Arc>>, + inner: Arc>>, + handle: Arc>, +} + +#[derive(Debug)] +/// This is associated with a particular config. When the config is updated, this gets replaced +struct MailStoreInner { pool: SqlitePool, - // email_events: broadcast::Sender, + mail_dir: PathBuf, + accounts: HashMap>, } #[derive(Clone, Debug)] @@ -33,7 +48,33 @@ pub struct EmailUpdateInfo {} impl MailStore { /// Creates a new MailStore - pub async fn new(config: Config) -> Result { + pub fn new(mut config_watcher: ConfigWatcher) -> Self { + let config = Arc::new(RwLock::new(None)); + let config2 = config.clone(); + + let listener = async move { + while let Ok(()) = config_watcher.changed().await { + let new_config = config_watcher.borrow().clone(); + let mut write = config2.write().await; + + // drop old config + if let Some(old_config) = write.take() { + mem::drop(old_config); + } + + *write = Some(new_config); + } + }; + let handle = tokio::spawn(listener); + + MailStore { + config, + inner: Arc::new(RwLock::new(None)), + handle: Arc::new(handle), + } + } + + async fn init_with_config(&self, config: Config) -> Result<()> { let data_dir = config.data_dir.to_string_lossy(); let data_dir = PathBuf::from(shellexpand::tilde(data_dir.as_ref()).as_ref()); @@ -63,21 +104,27 @@ impl MailStore { MIGRATOR.run(&pool).await?; debug!("run migrations : {:?}", MIGRATOR); + let accounts = config + .mail_accounts + .keys() + .map(|acct| { + let folders = RwLock::new(Vec::new()); + (acct.to_owned(), Arc::new(AccountRef { folders })) + }) + .collect(); + // let (new_email_tx, new_email_rx) = broadcast::channel(100); - - Ok(MailStore { - config, - mail_dir, - pool, - // email_events: new_email_tx, - }) + { + let mut write = self.inner.write().await; + *write = Some(MailStoreInner { + mail_dir, + pool, + accounts, + }); + } + Ok(()) } - // /// Subscribes to the email updates - // pub fn subscribe(&self) -> broadcast::Receiver { - // self.email_events.subscribe() - // } - /// Given a UID and optional message-id try to identify a particular message pub async fn try_identify_email( &self, @@ -87,6 +134,11 @@ impl MailStore { uidvalidity: u32, message_id: Option<&str>, ) -> Result> { + let read = self.inner.read().await; + let inner = match &*read { + Some(v) => v, + None => return Ok(None), + }; let existing: Option<(u32,)> = into_opt( sqlx::query_as( r#" @@ -99,9 +151,10 @@ impl MailStore { .bind(folder.as_ref()) .bind(uid) .bind(uidvalidity) - .fetch_one(&self.pool) + .fetch_one(&inner.pool) .await, )?; + mem::drop(inner); if let Some(existing) = existing { let rowid = existing.0; @@ -149,7 +202,12 @@ impl MailStore { hasher.update(body.as_bytes()); let hash = hasher.finalize(); let filename = format!("{}.mail", hex::encode(hash)); - let path = self.mail_dir.join(&filename); + let path = { + match &*self.inner.read().await { + Some(inner) => inner.mail_dir.join(&filename), + None => return Ok(()), + } + }; fs::write(path, &body) .await .context("error writing email to file")?; @@ -171,6 +229,11 @@ impl MailStore { debug!("message-id: {:?}", message_id); + let read = self.inner.read().await; + let inner = match &*read { + Some(v) => v, + None => return Ok(()), + }; let existing = into_opt( sqlx::query( r#" @@ -183,7 +246,7 @@ impl MailStore { .bind(folder.as_ref()) .bind(uid) .bind(uidvalidity) - .fetch_one(&self.pool) + .fetch_one(&inner.pool) .await, )?; @@ -204,11 +267,12 @@ impl MailStore { .bind(uidvalidity) .bind(filename) .bind(internaldate.to_rfc3339()) - .execute(&self.pool) + .execute(&inner.pool) .await .context("error inserting email into db")? .last_insert_rowid(); } + mem::drop(inner); // self.email_events // .send(EmailUpdateInfo {}) @@ -216,6 +280,34 @@ impl MailStore { Ok(()) } + + /// Event handerl + pub fn handle_mail_event(&self, evt: MailEvent) { + debug!("TODO: handle {:?}", evt); + } + + /// Return a map of the accounts that are currently being tracked as well as a reference to the + /// account handles themselves + pub async fn list_accounts(&self) -> HashMap> { + let read = self.inner.read().await; + let inner = match &*read { + Some(v) => v, + None => return HashMap::new(), + }; + + inner.accounts.clone() + } +} + +#[derive(Debug)] +pub struct AccountRef { + folders: RwLock>, +} + +impl AccountRef { + pub async fn folders(&self) -> Vec { + self.folders.read().await.clone() + } } fn into_opt(res: Result) -> Result> { diff --git a/src/main.rs b/src/main.rs index a1257b6..adbe28f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use fern::colors::{Color, ColoredLevelConfig}; use futures::future::TryFutureExt; use panorama::{ config::{spawn_config_watcher_system, ConfigWatcher}, - mail::{self, MailEvent}, + mail::{self, MailEvent, MailStore}, report_err, ui::{self, UiParams}, }; @@ -50,6 +50,7 @@ fn main() -> Result<()> { async fn run(opt: Opt) -> Result<()> { let _xdg = BaseDirectories::new()?; let (_config_thread, config_update) = spawn_config_watcher_system()?; + let mail_store = MailStore::new(config_update.clone()); // used to notify the runtime that the process should exit let (exit_tx, mut exit_rx) = mpsc::channel::<()>(1); @@ -64,15 +65,16 @@ async fn run(opt: Opt) -> Result<()> { let (ui2vm_tx, _ui2vm_rx) = mpsc::unbounded_channel(); let config_update2 = config_update.clone(); + let mail_store2 = mail_store.clone(); tokio::spawn(async move { - mail::run_mail(config_update2, ui2mail_rx, mail2ui_tx) + mail::run_mail(mail_store2, config_update2, ui2mail_rx, mail2ui_tx) .unwrap_or_else(report_err) .await; }); if !opt.headless { let config_update2 = config_update.clone(); - run_ui(config_update2, exit_tx, mail2ui_rx, ui2vm_tx); + run_ui(config_update2, mail_store.clone(), exit_tx, mail2ui_rx, ui2vm_tx); } exit_rx.recv().await; @@ -87,6 +89,7 @@ async fn run(opt: Opt) -> Result<()> { // Spawns the entire UI in a different thread, since it must be thread-local fn run_ui( config_update: ConfigWatcher, + mail_store: MailStore, exit_tx: mpsc::Sender<()>, mail2ui_rx: mpsc::UnboundedReceiver, _ui2vm_tx: mpsc::UnboundedSender<()>, @@ -102,6 +105,7 @@ fn run_ui( let localset = LocalSet::new(); let params = UiParams { config_update, + mail_store, stdout, exit_tx, mail2ui_rx, diff --git a/src/ui/mail_view.rs b/src/ui/mail_view.rs index 00ba7b2..dd573ca 100644 --- a/src/ui/mail_view.rs +++ b/src/ui/mail_view.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::rc::Rc; use std::sync::{ atomic::{AtomicI8, AtomicU32, Ordering}, Arc, @@ -47,110 +48,83 @@ impl HandlesInput for MailView { } } +#[async_trait(?Send)] impl Window for MailView { fn name(&self) -> String { String::from("email") } - fn draw(&self, f: FrameType, area: Rect, ui: &UI) { - let chunks = Layout::default() - .direction(Direction::Horizontal) - .margin(0) - .constraints([Constraint::Length(20), Constraint::Max(5000)]) - .split(area); + async fn draw(&self) { + // let chunks = Layout::default() + // .direction(Direction::Horizontal) + // .margin(0) + // .constraints([Constraint::Length(20), Constraint::Max(5000)]) + // .split(area); - let accts = self.mail_store.iter_accts(); + // let accts = self.mail_store.list_accounts().await; - // folder list - let mut items = vec![]; - for acct in accts.iter() { - let result = self.mail_store.folders_of(acct); - if let Some(folders) = result { - items.push(ListItem::new(acct.to_owned())); - for folder in folders { - items.push(ListItem::new(format!(" {}", folder))); - } - } - } + // // folder list + // let mut items = vec![]; + // for (acct_name, acct_ref) in accts.iter() { + // let folders = acct_ref.folders().await; - let dirlist = List::new(items) - .block(Block::default().borders(Borders::NONE).title(Span::styled( - "hellosu", - Style::default().add_modifier(Modifier::BOLD), - ))) - .style(Style::default().fg(Color::White)) - .highlight_style(Style::default().add_modifier(Modifier::ITALIC)) - .highlight_symbol(">>"); + // items.push(ListItem::new(acct_name.to_owned())); + // for folder in folders { + // items.push(ListItem::new(format!(" {}", folder))); + // } + // } - // message list table - // let mut metas = self - // .message_uids - // .iter() - // .filter_map(|id| self.message_map.get(id)) - // .collect::>(); - // metas.sort_by_key(|m| m.date); - // let rows = metas - // .iter() - // .rev() - // .map(|meta| { - // let mut row = Row::new(vec![ - // String::from(if meta.unread { "\u{2b24}" } else { "" }), - // meta.uid.map(|u| u.to_string()).unwrap_or_default(), - // meta.date.map(|d| humanize_timestamp(d)).unwrap_or_default(), - // meta.from.clone(), - // meta.subject.clone(), - // ]); - // if meta.unread { - // row = row.style( - // Style::default() - // .fg(Color::LightCyan) - // .add_modifier(Modifier::BOLD), - // ); + // let dirlist = List::new(items) + // .block(Block::default().borders(Borders::NONE).title(Span::styled( + // "hellosu", + // Style::default().add_modifier(Modifier::BOLD), + // ))) + // .style(Style::default().fg(Color::White)) + // .highlight_style(Style::default().add_modifier(Modifier::ITALIC)) + // .highlight_symbol(">>"); + + // let mut rows = vec![]; + // for acct in accts.iter() { + // // TODO: messages + // let result: Option> = None; // self.mail_store.messages_of(acct); + // if let Some(messages) = result { + // for meta in messages { + // let mut row = Row::new(vec![ + // String::from(if meta.unread { "\u{2b24}" } else { "" }), + // meta.uid.map(|u| u.to_string()).unwrap_or_default(), + // meta.date.map(|d| humanize_timestamp(d)).unwrap_or_default(), + // meta.from.clone(), + // meta.subject.clone(), + // ]); + // if meta.unread { + // row = row.style( + // Style::default() + // .fg(Color::LightCyan) + // .add_modifier(Modifier::BOLD), + // ); + // } + // rows.push(row); // } - // row - // }) - // .collect::>(); + // } + // } - let mut rows = vec![]; - for acct in accts.iter() { - let result = self.mail_store.messages_of(acct); - if let Some(messages) = result { - for meta in messages { - let mut row = Row::new(vec![ - String::from(if meta.unread { "\u{2b24}" } else { "" }), - meta.uid.map(|u| u.to_string()).unwrap_or_default(), - meta.date.map(|d| humanize_timestamp(d)).unwrap_or_default(), - meta.from.clone(), - meta.subject.clone(), - ]); - if meta.unread { - row = row.style( - Style::default() - .fg(Color::LightCyan) - .add_modifier(Modifier::BOLD), - ); - } - rows.push(row); - } - } - } - let table = Table::new(rows) - .style(Style::default().fg(Color::White)) - .widths(&[ - Constraint::Length(1), - Constraint::Max(3), - Constraint::Min(20), - Constraint::Min(35), - Constraint::Max(5000), - ]) - .header( - Row::new(vec!["", "UID", "Date", "From", "Subject"]) - .style(Style::default().add_modifier(Modifier::BOLD)), - ) - .highlight_style(Style::default().bg(Color::DarkGray)); + // let table = Table::new(rows) + // .style(Style::default().fg(Color::White)) + // .widths(&[ + // Constraint::Length(1), + // Constraint::Max(3), + // Constraint::Min(20), + // Constraint::Min(35), + // Constraint::Max(5000), + // ]) + // .header( + // Row::new(vec!["", "UID", "Date", "From", "Subject"]) + // .style(Style::default().add_modifier(Modifier::BOLD)), + // ) + // .highlight_style(Style::default().bg(Color::DarkGray)); - f.render_widget(dirlist, chunks[0]); - f.render_widget(table, chunks[1]); + // f.render_widget(dirlist, chunks[0]); + // f.render_widget(table, chunks[1]); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f753b06..8e38bf4 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -3,7 +3,6 @@ mod colon_prompt; mod input; mod keybinds; -mod mail_store; mod mail_view; mod messages; mod windows; @@ -39,11 +38,10 @@ use tui::{ }; use crate::config::ConfigWatcher; -use crate::mail::{EmailMetadata, MailEvent}; +use crate::mail::{EmailMetadata, MailEvent, MailStore}; use self::colon_prompt::ColonPrompt; use self::input::{BaseInputHandler, HandlesInput, InputResult}; -use self::mail_store::MailStore; use self::mail_view::MailView; pub(crate) use self::messages::*; use self::windows::*; @@ -56,6 +54,9 @@ pub struct UiParams { /// Config updates pub config_update: ConfigWatcher, + /// Mail store + pub mail_store: MailStore, + /// Handle to the screen pub stdout: Stdout, @@ -81,8 +82,7 @@ pub async fn run_ui2(params: UiParams) -> Result<()> { let should_exit = Arc::new(AtomicBool::new(false)); - let mail_store = MailStore::default(); - + let mail_store = params.mail_store; let mut ui = UI { should_exit: should_exit.clone(), window_layout: WindowLayout::default(), @@ -172,7 +172,7 @@ impl UI { let visible = self.window_layout.visible_windows(chunks[0]); for (layout_id, area) in visible.into_iter() { if let Some(window) = self.windows.get(&layout_id) { - window.draw(f, area, self); + window.draw(); } } } diff --git a/src/ui/windows.rs b/src/ui/windows.rs index ca7ff69..5625095 100644 --- a/src/ui/windows.rs +++ b/src/ui/windows.rs @@ -1,23 +1,19 @@ use std::collections::{HashMap, HashSet, VecDeque}; +use std::rc::Rc; +use futures::future::Future; use tui::layout::Rect; use super::{FrameType, HandlesInput, UI}; +#[async_trait(?Send)] pub trait Window: HandlesInput { - // Return some kind of name + /// Return some kind of name fn name(&self) -> String; - // Main draw function - fn draw(&self, f: FrameType, area: Rect, ui: &UI); - - /// Draw function, except the window is not the actively focused one - /// - /// By default, this just calls the regular draw function but the window may choose to perform - /// a less intensive draw if it's known to not be active - fn draw_inactive(&mut self, f: FrameType, area: Rect, ui: &UI) { - self.draw(f, area, ui); - } + /// Main draw function + async fn draw(&self); + // async fn draw(&self, f: FrameType, area: Rect, ui: Rc); /// Update function fn update(&mut self) {} diff --git a/tui/Cargo.toml b/tui/Cargo.toml new file mode 100644 index 0000000..7d965be --- /dev/null +++ b/tui/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "panorama-tui" +version = "0.1.0" +authors = ["Michael Zhang "] +edition = "2018" + +[dependencies] diff --git a/tui/src/lib.rs b/tui/src/lib.rs new file mode 100644 index 0000000..e69de29