lots of updates, gonna probably ditch tui-rs
This commit is contained in:
parent
6b82137124
commit
a1e9576d07
13 changed files with 224 additions and 407 deletions
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
@ -1,4 +1,7 @@
|
|||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
name: workflow
|
||||
|
||||
|
|
273
Cargo.lock
generated
273
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)> {
|
||||
let s = s.as_ref();
|
||||
let len = s.len();
|
||||
let mut pairs = match Rfc3501::parse(Rule::streamed_response, s) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<MailCommand>,
|
||||
mail2ui_tx: UnboundedSender<MailEvent>,
|
||||
|
@ -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();
|
||||
|
|
|
@ -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<RwLock<Option<Config>>>,
|
||||
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,
|
||||
// email_events: broadcast::Sender<EmailUpdateInfo>,
|
||||
mail_dir: PathBuf,
|
||||
accounts: HashMap<String, Arc<AccountRef>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -33,7 +48,33 @@ pub struct EmailUpdateInfo {}
|
|||
|
||||
impl 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 = PathBuf::from(shellexpand::tilde(data_dir.as_ref()).as_ref());
|
||||
|
||||
|
@ -63,20 +104,26 @@ impl MailStore {
|
|||
MIGRATOR.run(&pool).await?;
|
||||
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 {
|
||||
config,
|
||||
// let (new_email_tx, new_email_rx) = broadcast::channel(100);
|
||||
{
|
||||
let mut write = self.inner.write().await;
|
||||
*write = Some(MailStoreInner {
|
||||
mail_dir,
|
||||
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
|
||||
pub async fn try_identify_email(
|
||||
|
@ -87,6 +134,11 @@ impl MailStore {
|
|||
uidvalidity: u32,
|
||||
message_id: Option<&str>,
|
||||
) -> 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(
|
||||
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<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>> {
|
||||
|
|
10
src/main.rs
10
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<MailEvent>,
|
||||
_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,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{
|
||||
atomic::{AtomicI8, AtomicU32, Ordering},
|
||||
Arc,
|
||||
|
@ -47,52 +48,47 @@ 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::<Vec<_>>();
|
||||
// metas.sort_by_key(|m| m.date);
|
||||
// let rows = metas
|
||||
// .iter()
|
||||
// .rev()
|
||||
// .map(|meta| {
|
||||
// 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<Vec<EmailMetadata>> = 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(),
|
||||
|
@ -107,50 +103,28 @@ impl Window for MailView {
|
|||
// .add_modifier(Modifier::BOLD),
|
||||
// );
|
||||
// }
|
||||
// row
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
// rows.push(row);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<UI>);
|
||||
|
||||
/// Update function
|
||||
fn update(&mut self) {}
|
||||
|
|
7
tui/Cargo.toml
Normal file
7
tui/Cargo.toml
Normal 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
0
tui/src/lib.rs
Normal file
Loading…
Reference in a new issue