what the heck
This commit is contained in:
parent
dbe92e4962
commit
f5d3a89641
9 changed files with 119 additions and 25 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -247,6 +247,28 @@ 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 = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "format-bytes"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc35f5e45d6b31053cea13078ffc6fa52fa8617aa54b7ac2011720d9c009e04f"
|
||||||
|
dependencies = [
|
||||||
|
"format-bytes-macros",
|
||||||
|
"proc-macro-hack",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "format-bytes-macros"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05089e341a0460449e2210c3bf7b61597860b07f0deae58da38dbed0a4c6b6d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-hack",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "funty"
|
name = "funty"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -645,6 +667,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"format-bytes",
|
||||||
"futures",
|
"futures",
|
||||||
"inotify",
|
"inotify",
|
||||||
"lettre",
|
"lettre",
|
||||||
|
|
|
@ -19,6 +19,7 @@ async-trait = "0.1.42"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
crossterm = "0.19.0"
|
crossterm = "0.19.0"
|
||||||
|
format-bytes = "0.2.0"
|
||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
inotify = { version = "0.9.2", features = ["stream"] }
|
inotify = { version = "0.9.2", features = ["stream"] }
|
||||||
lettre = "0.9.5"
|
lettre = "0.9.5"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
panorama
|
panorama
|
||||||
========
|
========
|
||||||
|
|
||||||
|
[![](https://tokei.rs/b1/github/iptq/panorama?category=lines)](https://github.com/XAMPPRocky/tokei).
|
||||||
|
|
||||||
Panorama is a terminal Personal Information Manager (PIM).
|
Panorama is a terminal Personal Information Manager (PIM).
|
||||||
|
|
||||||
Goals:
|
Goals:
|
||||||
|
|
|
@ -90,6 +90,12 @@ async fn start_inotify_stream(
|
||||||
let config_home = config_home.as_ref().to_path_buf();
|
let config_home = config_home.as_ref().to_path_buf();
|
||||||
let config_path = config_home.join("panorama.toml");
|
let config_path = config_home.join("panorama.toml");
|
||||||
|
|
||||||
|
// first shot
|
||||||
|
{
|
||||||
|
let config = read_config(&config_path).await?;
|
||||||
|
config_tx.send(config)?;
|
||||||
|
}
|
||||||
|
|
||||||
while let Some(v) = event_stream.next().await {
|
while let Some(v) = event_stream.next().await {
|
||||||
let event = v.context("event")?;
|
let event = v.context("event")?;
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,15 @@
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
// TODO: get rid of this before any kind of public release
|
// TODO: get rid of this before any kind of public release
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports, unused_variables)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate anyhow;
|
extern crate anyhow;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate crossterm;
|
extern crate crossterm;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate format_bytes;
|
||||||
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
|
|
@ -5,8 +5,9 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::{
|
use futures::{
|
||||||
future::{Future, TryFuture},
|
future::{self, BoxFuture, Future, FutureExt, TryFuture},
|
||||||
stream::Stream,
|
sink::{Sink, SinkExt},
|
||||||
|
stream::{Stream, StreamExt},
|
||||||
};
|
};
|
||||||
use panorama_imap::builders::command::Command;
|
use panorama_imap::builders::command::Command;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -16,7 +17,7 @@ use tokio::{
|
||||||
sync::{oneshot, Notify},
|
sync::{oneshot, Notify},
|
||||||
};
|
};
|
||||||
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
|
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
|
||||||
use tokio_util::codec::{Framed, Decoder, LinesCodec};
|
use tokio_util::codec::{Decoder, Framed, FramedRead, FramedWrite, LinesCodec, LinesCodecError};
|
||||||
|
|
||||||
use crate::config::{ImapConfig, TlsMethod};
|
use crate::config::{ImapConfig, TlsMethod};
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ pub async fn open_imap_connection(config: ImapConfig) -> Result<()> {
|
||||||
|
|
||||||
let stream = TcpStream::connect((server, port)).await?;
|
let stream = TcpStream::connect((server, port)).await?;
|
||||||
|
|
||||||
|
debug!("hellosu");
|
||||||
match config.tls {
|
match config.tls {
|
||||||
TlsMethod::Off => begin_authentication(config, stream).await,
|
TlsMethod::Off => begin_authentication(config, stream).await,
|
||||||
TlsMethod::On => {
|
TlsMethod::On => {
|
||||||
|
@ -33,13 +35,23 @@ pub async fn open_imap_connection(config: ImapConfig) -> Result<()> {
|
||||||
begin_authentication(config, stream).await
|
begin_authentication(config, stream).await
|
||||||
}
|
}
|
||||||
TlsMethod::Starttls => {
|
TlsMethod::Starttls => {
|
||||||
let cmd_mgr = CommandManager::new(stream);
|
let (stream, cmd_mgr) = CommandManager::new(stream);
|
||||||
|
let flights = cmd_mgr.flights();
|
||||||
|
|
||||||
|
// listen(stream, flights).await?;
|
||||||
|
// async move {
|
||||||
|
// let mut cmd_mgr = cmd_mgr;
|
||||||
|
// cmd_mgr.capabilities().await;
|
||||||
|
// }
|
||||||
|
// .await;
|
||||||
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs TLS negotiation, using the webpki_roots and verifying the server name
|
/// Performs TLS negotiation, using the webpki_roots and verifying the server name
|
||||||
|
#[instrument(skip(server_name, stream))]
|
||||||
async fn perform_tls_negotiation(
|
async fn perform_tls_negotiation(
|
||||||
server_name: impl AsRef<str>,
|
server_name: impl AsRef<str>,
|
||||||
stream: impl AsyncRead + AsyncWrite + Unpin,
|
stream: impl AsyncRead + AsyncWrite + Unpin,
|
||||||
|
@ -61,10 +73,10 @@ async fn fetch_capabilities(stream: impl AsyncRead + AsyncWrite) -> Result<Vec<S
|
||||||
let codec = LinesCodec::new();
|
let codec = LinesCodec::new();
|
||||||
let framed = codec.framed(stream);
|
let framed = codec.framed(stream);
|
||||||
|
|
||||||
// framed.send("a0 CAPABILITY");
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(config, stream))]
|
||||||
async fn begin_authentication(
|
async fn begin_authentication(
|
||||||
config: ImapConfig,
|
config: ImapConfig,
|
||||||
stream: impl AsyncRead + AsyncWrite,
|
stream: impl AsyncRead + AsyncWrite,
|
||||||
|
@ -72,47 +84,94 @@ async fn begin_authentication(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ImapStream: AsyncRead + AsyncWrite + Unpin {}
|
pub async fn listen(
|
||||||
impl<T: AsyncRead + AsyncWrite + Unpin> ImapStream for T {}
|
mut stream: impl Stream<Item = Result<String, LinesCodecError>> + Unpin,
|
||||||
|
in_flight: InFlight,
|
||||||
|
) -> Result<()> {
|
||||||
|
debug!("listening for messages from server");
|
||||||
|
loop {
|
||||||
|
let line = match stream.next().await {
|
||||||
|
Some(v) => v?,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
debug!("line: {:?}", line);
|
||||||
|
|
||||||
|
let mut parts = line.split(' ');
|
||||||
|
let tag = parts.next().unwrap().parse()?; // TODO: handle empty
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut in_flight = in_flight.lock();
|
||||||
|
if let Some(sender) = in_flight.remove(&tag) {
|
||||||
|
sender.send(()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// trait ImapStream: AsyncRead + AsyncWrite + Send + Unpin {}
|
||||||
|
// impl<T: AsyncRead + AsyncWrite + Send + Unpin> ImapStream for T {}
|
||||||
|
|
||||||
|
trait ImapSink: Sink<String, Error = LinesCodecError> + Unpin {}
|
||||||
|
impl<T: Sink<String, Error = LinesCodecError> + Unpin> ImapSink for T {}
|
||||||
|
|
||||||
|
type InFlightMap = HashMap<usize, oneshot::Sender<()>>;
|
||||||
|
type InFlight = Arc<Mutex<InFlightMap>>;
|
||||||
|
|
||||||
struct CommandManager<'a> {
|
struct CommandManager<'a> {
|
||||||
id: usize,
|
id: usize,
|
||||||
in_flight: Arc<Mutex<HashMap<usize, oneshot::Sender<()>>>>,
|
in_flight: Arc<Mutex<HashMap<usize, oneshot::Sender<()>>>>,
|
||||||
stream: Framed<Box<dyn ImapStream + 'a>, LinesCodec>,
|
sink: Box<dyn ImapSink + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CommandManager<'a> {
|
impl<'a> CommandManager<'a> {
|
||||||
pub fn new(stream: impl ImapStream + 'a) -> Self {
|
pub fn new(
|
||||||
|
stream: impl AsyncRead + AsyncWrite + 'a,
|
||||||
|
) -> (impl Stream<Item = Result<String, LinesCodecError>>, Self) {
|
||||||
let codec = LinesCodec::new();
|
let codec = LinesCodec::new();
|
||||||
let framed = codec.framed(Box::new(stream) as Box<_>);
|
let framed = codec.framed(stream);
|
||||||
|
let (framed_sink, framed_stream) = framed.split();
|
||||||
|
|
||||||
CommandManager {
|
let cmd_mgr = CommandManager {
|
||||||
id: 0,
|
id: 0,
|
||||||
in_flight: Arc::new(Mutex::new(HashMap::new())),
|
in_flight: Arc::new(Mutex::new(HashMap::new())),
|
||||||
stream: framed,
|
sink: Box::new(framed_sink),
|
||||||
}
|
};
|
||||||
|
(framed_stream, cmd_mgr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decompose(self) -> impl ImapStream + 'a {
|
pub fn flights(&self) -> Arc<Mutex<HashMap<usize, oneshot::Sender<()>>>> {
|
||||||
let parts = self.stream.into_parts();
|
self.in_flight.clone()
|
||||||
parts.io
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn listen(&self) {
|
pub fn decompose(self) -> impl ImapSink + Unpin + 'a {
|
||||||
loop {
|
self.sink
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, command: Command) -> impl TryFuture {
|
pub async fn capabilities(&mut self) -> Result<Vec<String>> {
|
||||||
|
self.exec(Command {
|
||||||
|
args: b"CAPABILITY".to_vec(),
|
||||||
|
next_state: None,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn exec(&mut self, command: Command) -> Result<()> {
|
||||||
let id = self.id;
|
let id = self.id;
|
||||||
self.id += 1;
|
self.id += 1;
|
||||||
|
|
||||||
|
let cmd_str = String::from_utf8(command.args)?;
|
||||||
|
self.sink.send(cmd_str).await?;
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
{
|
{
|
||||||
let mut in_flight = self.in_flight.lock();
|
let mut in_flight = self.in_flight.lock();
|
||||||
in_flight.insert(id, tx);
|
in_flight.insert(id, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
async { rx.await }
|
rx.await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use tokio_stream::wrappers::WatchStream;
|
||||||
|
|
||||||
use crate::config::{Config, ConfigWatcher};
|
use crate::config::{Config, ConfigWatcher};
|
||||||
|
|
||||||
use self::imap::open_imap_connection;
|
use self::imap2::open_imap_connection;
|
||||||
|
|
||||||
/// Command sent to the mail thread by something else (i.e. UI)
|
/// Command sent to the mail thread by something else (i.e. UI)
|
||||||
pub enum MailCommand {
|
pub enum MailCommand {
|
||||||
|
@ -47,6 +47,7 @@ pub async fn run_mail(
|
||||||
|
|
||||||
let handle = tokio::spawn(async {
|
let handle = tokio::spawn(async {
|
||||||
for acct in config.mail_accounts.into_iter() {
|
for acct in config.mail_accounts.into_iter() {
|
||||||
|
debug!("opening imap connection for {:?}", acct);
|
||||||
open_imap_connection(acct.imap).await.unwrap();
|
open_imap_connection(acct.imap).await.unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ struct Opt {
|
||||||
#[tokio::main(flavor = "multi_thread")]
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// parse command line arguments into options struct
|
// parse command line arguments into options struct
|
||||||
let opt = Opt::from_args();
|
let _opt = Opt::from_args();
|
||||||
|
|
||||||
// print logs to file as directed by command line options
|
// print logs to file as directed by command line options
|
||||||
use tracing_subscriber::filter::LevelFilter;
|
use tracing_subscriber::filter::LevelFilter;
|
||||||
|
|
|
@ -27,7 +27,7 @@ const FRAME: Duration = Duration::from_millis(20);
|
||||||
pub struct Rect(u16, u16, u16, u16);
|
pub struct Rect(u16, u16, u16, u16);
|
||||||
|
|
||||||
/// UI entrypoint.
|
/// UI entrypoint.
|
||||||
#[instrument]
|
#[instrument(skip(w, exit))]
|
||||||
pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> {
|
pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> {
|
||||||
execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
|
execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
|
||||||
terminal::enable_raw_mode()?;
|
terminal::enable_raw_mode()?;
|
||||||
|
|
Loading…
Reference in a new issue