lots of updates, gonna probably ditch tui-rs

This commit is contained in:
Michael Zhang 2021-03-26 13:23:02 -05:00
parent 6b82137124
commit a1e9576d07
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
13 changed files with 224 additions and 407 deletions

View file

@ -1,4 +1,7 @@
on: [push] on:
push:
branches:
- master
name: workflow name: workflow

273
Cargo.lock generated
View file

@ -203,15 +203,6 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "bitpacking"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3744aff20a3437a99ebc0bb7733e9e60c7bf590478c9b897e95b38d57e5acb68"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "bitvec" name = "bitvec"
version = "0.19.5" version = "0.19.5"
@ -385,12 +376,6 @@ dependencies = [
"jobserver", "jobserver",
] ]
[[package]]
name = "census"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5927edd8345aef08578bcbb4aea7314f340d80c7f4931f99fbeb40b99d8f5060"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.10"
@ -486,15 +471,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "combine"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4369b5e4c0cddf64ad8981c0111e7df4f7078f4d6ba98fb31f2e17c4c57b7e"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "commoncrypto" name = "commoncrypto"
version = "0.2.0" version = "0.2.0"
@ -583,20 +559,6 @@ dependencies = [
"cfg-if 1.0.0", "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]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.0" version = "0.5.0"
@ -607,30 +569,6 @@ dependencies = [
"crossbeam-utils 0.8.3", "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]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.1" version = "0.3.1"
@ -951,17 +889,6 @@ dependencies = [
"termcolor", "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]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.4.0" version = "1.4.0"
@ -1071,16 +998,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e04cda45add94e71c2990de778ae13059897d77b773130a9bc225e2970c413e" 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]] [[package]]
name = "funty" name = "funty"
version = "1.1.0" version = "1.1.0"
@ -1133,7 +1050,6 @@ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
"futures-util", "futures-util",
"num_cpus",
] ]
[[package]] [[package]]
@ -1534,12 +1450,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "htmlescape"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
@ -1694,7 +1604,7 @@ dependencies = [
"petgraph", "petgraph",
"pico-args", "pico-args",
"regex", "regex",
"regex-syntax 0.6.23", "regex-syntax",
"string_cache", "string_cache",
"term", "term",
"tiny-keccak", "tiny-keccak",
@ -1722,12 +1632,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "levenshtein_automata"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f44db4199cdb049b494a92d105acbfa43c25b3925e33803923ba9580b7bc9e1a"
[[package]] [[package]]
name = "lexical-core" name = "lexical-core"
version = "0.7.5" version = "0.7.5"
@ -1826,15 +1730,6 @@ dependencies = [
"cfg-if 1.0.0", "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]] [[package]]
name = "mac-notification-sys" name = "mac-notification-sys"
version = "0.3.0" version = "0.3.0"
@ -1885,25 +1780,6 @@ version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 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]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.4.4" version = "0.4.4"
@ -1937,15 +1813,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "murmurhash32"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d736ff882f0e85fe9689fb23db229616c4c00aee2b3ac282f666d8f20eb25d4a"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "nb-connect" name = "nb-connect"
version = "1.0.3" version = "1.0.3"
@ -2167,7 +2034,6 @@ dependencies = [
"shellexpand", "shellexpand",
"sqlx", "sqlx",
"structopt", "structopt",
"tantivy",
"tokio 1.3.0", "tokio 1.3.0",
"tokio-rustls", "tokio-rustls",
"tokio-stream", "tokio-stream",
@ -2204,6 +2070,10 @@ dependencies = [
name = "panorama-smtp" name = "panorama-smtp"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "panorama-tui"
version = "0.1.0"
[[package]] [[package]]
name = "parking" name = "parking"
version = "2.0.0" version = "2.0.0"
@ -2552,31 +2422,6 @@ dependencies = [
"rand_core 0.5.1", "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]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.1.57" version = "0.1.57"
@ -2621,15 +2466,9 @@ checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "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]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.23" version = "0.6.23"
@ -2681,16 +2520,6 @@ dependencies = [
"crossbeam-utils 0.8.3", "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]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"
@ -2931,12 +2760,6 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "snap"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc725476a1398f0480d56cd0ad381f6f32acf2642704456f8f59a35df464b59a"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.3.19" version = "0.3.19"
@ -3050,12 +2873,6 @@ dependencies = [
"tokio-rustls", "tokio-rustls",
] ]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "0.3.4" version = "0.3.4"
@ -3188,68 +3005,6 @@ dependencies = [
"unicode-xid 0.0.4", "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]] [[package]]
name = "tap" name = "tap"
version = "1.0.1" version = "1.0.1"
@ -3562,28 +3317,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf8-ranges"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.1.1" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" 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]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.11" version = "0.2.11"

View file

@ -12,6 +12,7 @@ license = "GPL-3.0-or-later"
members = [ members = [
"imap", "imap",
"smtp", "smtp",
"tui",
] ]
[dependencies] [dependencies]
@ -46,7 +47,7 @@ sha2 = "0.9.3"
hex = "0.4.3" hex = "0.4.3"
shellexpand = "2.1.0" shellexpand = "2.1.0"
mailparse = "0.13.2" mailparse = "0.13.2"
tantivy = "0.14.0" # tantivy = "0.14.0"
[dependencies.panorama-imap] [dependencies.panorama-imap]
path = "imap" path = "imap"

View file

@ -36,7 +36,6 @@ pub fn parse_capability(s: impl AsRef<str>) -> ParseResult<Capability> {
pub fn parse_streamed_response(s: impl AsRef<str>) -> ParseResult<(Response, usize)> { pub fn parse_streamed_response(s: impl AsRef<str>) -> ParseResult<(Response, usize)> {
let s = s.as_ref(); let s = s.as_ref();
let len = s.len();
let mut pairs = match Rfc3501::parse(Rule::streamed_response, s) { let mut pairs = match Rfc3501::parse(Rule::streamed_response, s) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {

View file

@ -9,6 +9,8 @@
#[macro_use] #[macro_use]
extern crate anyhow; extern crate anyhow;
#[macro_use] #[macro_use]
extern crate async_trait;
#[macro_use]
extern crate crossterm; extern crate crossterm;
#[macro_use] #[macro_use]
extern crate format_bytes; extern crate format_bytes;

View file

@ -44,6 +44,7 @@ pub enum MailCommand {
/// Main entrypoint for the mail listener. /// Main entrypoint for the mail listener.
pub async fn run_mail( pub async fn run_mail(
mail_store: MailStore,
mut config_watcher: ConfigWatcher, mut config_watcher: ConfigWatcher,
ui2mail_rx: UnboundedReceiver<MailCommand>, ui2mail_rx: UnboundedReceiver<MailCommand>,
mail2ui_tx: UnboundedSender<MailEvent>, mail2ui_tx: UnboundedSender<MailEvent>,
@ -67,7 +68,6 @@ pub async fn run_mail(
conn.abort(); conn.abort();
} }
let mail_store = MailStore::new(config.clone()).await?;
for (acct_name, acct) in config.mail_accounts.clone().into_iter() { for (acct_name, acct) in config.mail_accounts.clone().into_iter() {
let mail2ui_tx = mail2ui_tx.clone(); let mail2ui_tx = mail2ui_tx.clone();
let mail_store = mail_store.clone(); let mail_store = mail_store.clone();

View file

@ -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::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
@ -11,21 +13,34 @@ use sqlx::{
sqlite::{Sqlite, SqlitePool}, sqlite::{Sqlite, SqlitePool},
Error as SqlxError, Row, 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!(); static MIGRATOR: Migrator = sqlx::migrate!();
/// Manages email storage on disk, for both database and caches /// 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 /// 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 { pub struct MailStore {
config: Config, config: Arc<RwLock<Option<Config>>>,
mail_dir: PathBuf, inner: Arc<RwLock<Option<MailStoreInner>>>,
handle: Arc<JoinHandle<()>>,
}
#[derive(Debug)]
/// This is associated with a particular config. When the config is updated, this gets replaced
struct MailStoreInner {
pool: SqlitePool, pool: SqlitePool,
// email_events: broadcast::Sender<EmailUpdateInfo>, mail_dir: PathBuf,
accounts: HashMap<String, Arc<AccountRef>>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -33,7 +48,33 @@ pub struct EmailUpdateInfo {}
impl MailStore { impl MailStore {
/// Creates a new MailStore /// Creates a new MailStore
pub async fn new(config: Config) -> Result<Self> { 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 = config.data_dir.to_string_lossy();
let data_dir = PathBuf::from(shellexpand::tilde(data_dir.as_ref()).as_ref()); let data_dir = PathBuf::from(shellexpand::tilde(data_dir.as_ref()).as_ref());
@ -63,20 +104,26 @@ impl MailStore {
MIGRATOR.run(&pool).await?; MIGRATOR.run(&pool).await?;
debug!("run migrations : {:?}", MIGRATOR); debug!("run migrations : {:?}", MIGRATOR);
// let (new_email_tx, new_email_rx) = broadcast::channel(100); let accounts = config
.mail_accounts
.keys()
.map(|acct| {
let folders = RwLock::new(Vec::new());
(acct.to_owned(), Arc::new(AccountRef { folders }))
})
.collect();
Ok(MailStore { // let (new_email_tx, new_email_rx) = broadcast::channel(100);
config, {
let mut write = self.inner.write().await;
*write = Some(MailStoreInner {
mail_dir, mail_dir,
pool, pool,
// email_events: new_email_tx, accounts,
}) });
}
Ok(())
} }
// /// Subscribes to the email updates
// pub fn subscribe(&self) -> broadcast::Receiver<EmailUpdateInfo> {
// self.email_events.subscribe()
// }
/// Given a UID and optional message-id try to identify a particular message /// Given a UID and optional message-id try to identify a particular message
pub async fn try_identify_email( pub async fn try_identify_email(
@ -87,6 +134,11 @@ impl MailStore {
uidvalidity: u32, uidvalidity: u32,
message_id: Option<&str>, message_id: Option<&str>,
) -> Result<Option<u32>> { ) -> Result<Option<u32>> {
let read = self.inner.read().await;
let inner = match &*read {
Some(v) => v,
None => return Ok(None),
};
let existing: Option<(u32,)> = into_opt( let existing: Option<(u32,)> = into_opt(
sqlx::query_as( sqlx::query_as(
r#" r#"
@ -99,9 +151,10 @@ impl MailStore {
.bind(folder.as_ref()) .bind(folder.as_ref())
.bind(uid) .bind(uid)
.bind(uidvalidity) .bind(uidvalidity)
.fetch_one(&self.pool) .fetch_one(&inner.pool)
.await, .await,
)?; )?;
mem::drop(inner);
if let Some(existing) = existing { if let Some(existing) = existing {
let rowid = existing.0; let rowid = existing.0;
@ -149,7 +202,12 @@ impl MailStore {
hasher.update(body.as_bytes()); hasher.update(body.as_bytes());
let hash = hasher.finalize(); let hash = hasher.finalize();
let filename = format!("{}.mail", hex::encode(hash)); 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) fs::write(path, &body)
.await .await
.context("error writing email to file")?; .context("error writing email to file")?;
@ -171,6 +229,11 @@ impl MailStore {
debug!("message-id: {:?}", message_id); 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( let existing = into_opt(
sqlx::query( sqlx::query(
r#" r#"
@ -183,7 +246,7 @@ impl MailStore {
.bind(folder.as_ref()) .bind(folder.as_ref())
.bind(uid) .bind(uid)
.bind(uidvalidity) .bind(uidvalidity)
.fetch_one(&self.pool) .fetch_one(&inner.pool)
.await, .await,
)?; )?;
@ -204,11 +267,12 @@ impl MailStore {
.bind(uidvalidity) .bind(uidvalidity)
.bind(filename) .bind(filename)
.bind(internaldate.to_rfc3339()) .bind(internaldate.to_rfc3339())
.execute(&self.pool) .execute(&inner.pool)
.await .await
.context("error inserting email into db")? .context("error inserting email into db")?
.last_insert_rowid(); .last_insert_rowid();
} }
mem::drop(inner);
// self.email_events // self.email_events
// .send(EmailUpdateInfo {}) // .send(EmailUpdateInfo {})
@ -216,6 +280,34 @@ impl MailStore {
Ok(()) 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<String, Arc<AccountRef>> {
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<Vec<String>>,
}
impl AccountRef {
pub async fn folders(&self) -> Vec<String> {
self.folders.read().await.clone()
}
} }
fn into_opt<T>(res: Result<T, SqlxError>) -> Result<Option<T>> { fn into_opt<T>(res: Result<T, SqlxError>) -> Result<Option<T>> {

View file

@ -6,7 +6,7 @@ use fern::colors::{Color, ColoredLevelConfig};
use futures::future::TryFutureExt; use futures::future::TryFutureExt;
use panorama::{ use panorama::{
config::{spawn_config_watcher_system, ConfigWatcher}, config::{spawn_config_watcher_system, ConfigWatcher},
mail::{self, MailEvent}, mail::{self, MailEvent, MailStore},
report_err, report_err,
ui::{self, UiParams}, ui::{self, UiParams},
}; };
@ -50,6 +50,7 @@ fn main() -> Result<()> {
async fn run(opt: Opt) -> Result<()> { async fn run(opt: Opt) -> Result<()> {
let _xdg = BaseDirectories::new()?; let _xdg = BaseDirectories::new()?;
let (_config_thread, config_update) = spawn_config_watcher_system()?; 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 // used to notify the runtime that the process should exit
let (exit_tx, mut exit_rx) = mpsc::channel::<()>(1); 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 (ui2vm_tx, _ui2vm_rx) = mpsc::unbounded_channel();
let config_update2 = config_update.clone(); let config_update2 = config_update.clone();
let mail_store2 = mail_store.clone();
tokio::spawn(async move { 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) .unwrap_or_else(report_err)
.await; .await;
}); });
if !opt.headless { if !opt.headless {
let config_update2 = config_update.clone(); 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; 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 // Spawns the entire UI in a different thread, since it must be thread-local
fn run_ui( fn run_ui(
config_update: ConfigWatcher, config_update: ConfigWatcher,
mail_store: MailStore,
exit_tx: mpsc::Sender<()>, exit_tx: mpsc::Sender<()>,
mail2ui_rx: mpsc::UnboundedReceiver<MailEvent>, mail2ui_rx: mpsc::UnboundedReceiver<MailEvent>,
_ui2vm_tx: mpsc::UnboundedSender<()>, _ui2vm_tx: mpsc::UnboundedSender<()>,
@ -102,6 +105,7 @@ fn run_ui(
let localset = LocalSet::new(); let localset = LocalSet::new();
let params = UiParams { let params = UiParams {
config_update, config_update,
mail_store,
stdout, stdout,
exit_tx, exit_tx,
mail2ui_rx, mail2ui_rx,

View file

@ -1,4 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc;
use std::sync::{ use std::sync::{
atomic::{AtomicI8, AtomicU32, Ordering}, atomic::{AtomicI8, AtomicU32, Ordering},
Arc, Arc,
@ -47,52 +48,47 @@ impl HandlesInput for MailView {
} }
} }
#[async_trait(?Send)]
impl Window for MailView { impl Window for MailView {
fn name(&self) -> String { fn name(&self) -> String {
String::from("email") String::from("email")
} }
fn draw(&self, f: FrameType, area: Rect, ui: &UI) { async fn draw(&self) {
let chunks = Layout::default() // let chunks = Layout::default()
.direction(Direction::Horizontal) // .direction(Direction::Horizontal)
.margin(0) // .margin(0)
.constraints([Constraint::Length(20), Constraint::Max(5000)]) // .constraints([Constraint::Length(20), Constraint::Max(5000)])
.split(area); // .split(area);
let accts = self.mail_store.iter_accts(); // let accts = self.mail_store.list_accounts().await;
// folder list // // folder list
let mut items = vec![]; // let mut items = vec![];
for acct in accts.iter() { // for (acct_name, acct_ref) in accts.iter() {
let result = self.mail_store.folders_of(acct); // let folders = acct_ref.folders().await;
if let Some(folders) = result {
items.push(ListItem::new(acct.to_owned()));
for folder in folders {
items.push(ListItem::new(format!(" {}", folder)));
}
}
}
let dirlist = List::new(items) // items.push(ListItem::new(acct_name.to_owned()));
.block(Block::default().borders(Borders::NONE).title(Span::styled( // for folder in folders {
"hellosu", // items.push(ListItem::new(format!(" {}", folder)));
Style::default().add_modifier(Modifier::BOLD), // }
))) // }
.style(Style::default().fg(Color::White))
.highlight_style(Style::default().add_modifier(Modifier::ITALIC))
.highlight_symbol(">>");
// message list table // let dirlist = List::new(items)
// let mut metas = self // .block(Block::default().borders(Borders::NONE).title(Span::styled(
// .message_uids // "hellosu",
// .iter() // Style::default().add_modifier(Modifier::BOLD),
// .filter_map(|id| self.message_map.get(id)) // )))
// .collect::<Vec<_>>(); // .style(Style::default().fg(Color::White))
// metas.sort_by_key(|m| m.date); // .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
// let rows = metas // .highlight_symbol(">>");
// .iter()
// .rev() // let mut rows = vec![];
// .map(|meta| { // for acct in accts.iter() {
// // TODO: messages
// let result: Option<Vec<EmailMetadata>> = None; // self.mail_store.messages_of(acct);
// if let Some(messages) = result {
// for meta in messages {
// let mut row = Row::new(vec![ // let mut row = Row::new(vec![
// String::from(if meta.unread { "\u{2b24}" } else { "" }), // String::from(if meta.unread { "\u{2b24}" } else { "" }),
// meta.uid.map(|u| u.to_string()).unwrap_or_default(), // meta.uid.map(|u| u.to_string()).unwrap_or_default(),
@ -107,50 +103,28 @@ impl Window for MailView {
// .add_modifier(Modifier::BOLD), // .add_modifier(Modifier::BOLD),
// ); // );
// } // }
// row // rows.push(row);
// }) // }
// .collect::<Vec<_>>(); // }
// }
let mut rows = vec![]; // let table = Table::new(rows)
for acct in accts.iter() { // .style(Style::default().fg(Color::White))
let result = self.mail_store.messages_of(acct); // .widths(&[
if let Some(messages) = result { // Constraint::Length(1),
for meta in messages { // Constraint::Max(3),
let mut row = Row::new(vec![ // Constraint::Min(20),
String::from(if meta.unread { "\u{2b24}" } else { "" }), // Constraint::Min(35),
meta.uid.map(|u| u.to_string()).unwrap_or_default(), // Constraint::Max(5000),
meta.date.map(|d| humanize_timestamp(d)).unwrap_or_default(), // ])
meta.from.clone(), // .header(
meta.subject.clone(), // Row::new(vec!["", "UID", "Date", "From", "Subject"])
]); // .style(Style::default().add_modifier(Modifier::BOLD)),
if meta.unread { // )
row = row.style( // .highlight_style(Style::default().bg(Color::DarkGray));
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));
f.render_widget(dirlist, chunks[0]); // f.render_widget(dirlist, chunks[0]);
f.render_widget(table, chunks[1]); // f.render_widget(table, chunks[1]);
} }
} }

View file

@ -3,7 +3,6 @@
mod colon_prompt; mod colon_prompt;
mod input; mod input;
mod keybinds; mod keybinds;
mod mail_store;
mod mail_view; mod mail_view;
mod messages; mod messages;
mod windows; mod windows;
@ -39,11 +38,10 @@ use tui::{
}; };
use crate::config::ConfigWatcher; use crate::config::ConfigWatcher;
use crate::mail::{EmailMetadata, MailEvent}; use crate::mail::{EmailMetadata, MailEvent, MailStore};
use self::colon_prompt::ColonPrompt; use self::colon_prompt::ColonPrompt;
use self::input::{BaseInputHandler, HandlesInput, InputResult}; use self::input::{BaseInputHandler, HandlesInput, InputResult};
use self::mail_store::MailStore;
use self::mail_view::MailView; use self::mail_view::MailView;
pub(crate) use self::messages::*; pub(crate) use self::messages::*;
use self::windows::*; use self::windows::*;
@ -56,6 +54,9 @@ pub struct UiParams {
/// Config updates /// Config updates
pub config_update: ConfigWatcher, pub config_update: ConfigWatcher,
/// Mail store
pub mail_store: MailStore,
/// Handle to the screen /// Handle to the screen
pub stdout: Stdout, pub stdout: Stdout,
@ -81,8 +82,7 @@ pub async fn run_ui2(params: UiParams) -> Result<()> {
let should_exit = Arc::new(AtomicBool::new(false)); let should_exit = Arc::new(AtomicBool::new(false));
let mail_store = MailStore::default(); let mail_store = params.mail_store;
let mut ui = UI { let mut ui = UI {
should_exit: should_exit.clone(), should_exit: should_exit.clone(),
window_layout: WindowLayout::default(), window_layout: WindowLayout::default(),
@ -172,7 +172,7 @@ impl UI {
let visible = self.window_layout.visible_windows(chunks[0]); let visible = self.window_layout.visible_windows(chunks[0]);
for (layout_id, area) in visible.into_iter() { for (layout_id, area) in visible.into_iter() {
if let Some(window) = self.windows.get(&layout_id) { if let Some(window) = self.windows.get(&layout_id) {
window.draw(f, area, self); window.draw();
} }
} }
} }

View file

@ -1,23 +1,19 @@
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
use std::rc::Rc;
use futures::future::Future;
use tui::layout::Rect; use tui::layout::Rect;
use super::{FrameType, HandlesInput, UI}; use super::{FrameType, HandlesInput, UI};
#[async_trait(?Send)]
pub trait Window: HandlesInput { pub trait Window: HandlesInput {
// Return some kind of name /// Return some kind of name
fn name(&self) -> String; fn name(&self) -> String;
// Main draw function /// Main draw function
fn draw(&self, f: FrameType, area: Rect, ui: &UI); async fn draw(&self);
// async fn draw(&self, f: FrameType, area: Rect, ui: Rc<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);
}
/// Update function /// Update function
fn update(&mut self) {} fn update(&mut self) {}

7
tui/Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "panorama-tui"
version = "0.1.0"
authors = ["Michael Zhang <mail@mzhang.io>"]
edition = "2018"
[dependencies]

0
tui/src/lib.rs Normal file
View file