fuck everything

This commit is contained in:
Michael Zhang 2021-02-20 01:30:58 -06:00
parent a53f6d7e28
commit 8b7888d985
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
8 changed files with 94 additions and 69 deletions

View file

@ -1,7 +1,7 @@
panorama panorama
======== ========
[![](https://tokei.rs/b1/github/iptq/panorama?category=lines)](https://github.com/XAMPPRocky/tokei). [![](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).

View file

@ -1 +1,2 @@
* src/builders
src/parser

View file

@ -1,31 +0,0 @@
use panorama_imap::Response;
use std::io::Write;
fn main() -> std::io::Result<()> {
loop {
let line = {
print!("Enter IMAP4REV1 response: ");
std::io::stdout().flush().unwrap();
let mut line = String::new();
std::io::stdin().read_line(&mut line)?;
line
};
match Response::from_bytes(line.replace("\n", "\r\n").as_bytes()) {
Ok((remaining, command)) => {
println!("{:#?}", command);
if !remaining.is_empty() {
println!("Remaining data in buffer: {:?}", remaining);
}
}
Err(_) => {
println!("Error parsing the response. Is it correct? Exiting.");
break;
}
}
}
Ok(())
}

View file

@ -6,7 +6,7 @@ use std::task::{Context, Poll};
use anyhow::Result; use anyhow::Result;
use futures::future::{Future, FutureExt}; use futures::future::{Future, FutureExt};
use panorama_strings::{StringEntry, StringStore}; use panorama_strings::{StringEntry, StringStore};
use parking_lot::RwLock; use parking_lot::{Mutex, RwLock};
use tokio::{ use tokio::{
io::{ io::{
self, AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, self, AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader,
@ -16,6 +16,7 @@ use tokio::{
}; };
use crate::command::Command; use crate::command::Command;
use crate::response::Response;
pub type BoxedFunc = Box<dyn Fn()>; pub type BoxedFunc = Box<dyn Fn()>;
@ -25,7 +26,7 @@ pub struct Client<C> {
symbols: StringStore, symbols: StringStore,
id: usize, id: usize,
handlers: Arc<RwLock<HashMap<usize, bool>>>, results: ResultMap,
/// Cached capabilities that shouldn't change between /// Cached capabilities that shouldn't change between
caps: Vec<StringEntry>, caps: Vec<StringEntry>,
@ -39,40 +40,49 @@ where
/// Creates a new client that wraps a connection /// Creates a new client that wraps a connection
pub fn new(conn: C) -> Self { pub fn new(conn: C) -> Self {
let (read_half, write_half) = io::split(conn); let (read_half, write_half) = io::split(conn);
let listen_fut = tokio::spawn(listen(read_half)); let results = Arc::new(RwLock::new(HashMap::new()));
let listen_fut = tokio::spawn(listen(read_half, results.clone()));
Client { Client {
conn: write_half, conn: write_half,
symbols: StringStore::new(256), symbols: StringStore::new(256),
id: 0, id: 0,
handlers: Arc::new(RwLock::new(HashMap::new())), results,
caps: Vec::new(), caps: Vec::new(),
handle: listen_fut, handle: listen_fut,
} }
} }
/// Sends a command to the server and returns a handle to retrieve the result /// Sends a command to the server and returns a handle to retrieve the result
pub async fn execute(&mut self, cmd: Command) -> Result<()> { pub async fn execute(&mut self, cmd: Command) -> Result<Response> {
debug!("executing command {:?}", cmd);
let id = self.id; let id = self.id;
self.id += 1; self.id += 1;
{ {
let mut handlers = self.handlers.write(); let mut handlers = self.results.write();
handlers.insert(id, false); handlers.insert(id, (None, None));
} }
let cmd_str = cmd.to_string(); let cmd_str = format!("pano{} {}\n", id, cmd);
debug!("[{}] writing to socket: {:?}", id, cmd_str);
self.conn.write_all(cmd_str.as_bytes()).await?; self.conn.write_all(cmd_str.as_bytes()).await?;
debug!("[{}] written.", id);
ExecHandle(self, id).await; ExecHandle(self, id).await;
Ok(()) let resp = {
let mut handlers = self.results.write();
handlers.remove(&id).unwrap().0.unwrap()
};
Ok(Response(resp))
} }
/// Executes the CAPABILITY command /// Executes the CAPABILITY command
pub async fn supports(&mut self) { pub async fn supports(&mut self) -> Result<()> {
let cmd = Command::Capability; let cmd = Command::Capability;
let result = self.execute(cmd).await; debug!("sending: {:?} {:?}", cmd, cmd.to_string());
debug!("poggers {:?}", result); let result = self.execute(cmd).await?;
debug!("result from supports: {:?}", result);
Ok(())
} }
} }
@ -80,28 +90,48 @@ pub struct ExecHandle<'a, C>(&'a Client<C>, usize);
impl<'a, C> Future for ExecHandle<'a, C> { impl<'a, C> Future for ExecHandle<'a, C> {
type Output = (); type Output = ();
fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let state = { let mut handlers = self.0.results.write();
let handlers = self.0.handlers.read(); let mut state = handlers.get_mut(&self.1);
handlers.get(&self.1).cloned()
};
// TODO: handle the None case here // TODO: handle the None case here
let state = state.unwrap(); debug!("f[{}] {:?}", self.1, state);
let (result, waker) = state.unwrap();
match state { match result {
true => Poll::Ready(()), Some(_) => Poll::Ready(()),
false => Poll::Pending, None => {
*waker = Some(cx.waker().clone());
Poll::Pending
}
} }
} }
} }
async fn listen(conn: impl AsyncRead + Unpin) -> Result<()> { use std::task::Waker;
pub type ResultMap = Arc<RwLock<HashMap<usize, (Option<String>, Option<Waker>)>>>;
async fn listen(conn: impl AsyncRead + Unpin, results: ResultMap) -> Result<()> {
debug!("amogus"); debug!("amogus");
let mut reader = BufReader::new(conn); let mut reader = BufReader::new(conn);
loop { loop {
let mut next_line = String::new(); let mut next_line = String::new();
reader.read_line(&mut next_line).await?; reader.read_line(&mut next_line).await?;
debug!("line: {:?}", next_line);
// debug!("line: {:?}", next_line);
let parts = next_line.split(" ").collect::<Vec<_>>();
let tag = parts[0];
if tag == "*" {
debug!("UNTAGGED {:?}", next_line);
} else if tag.starts_with("pano") {
let id = tag.trim_start_matches("pano").parse::<usize>()?;
debug!("set {} to {:?}", id, next_line);
let mut results = results.write();
if let Some((c, w)) = results.get_mut(&id) {
*c = Some(next_line);
let w = w.take().unwrap();
w.wake();
}
}
} }
} }

View file

@ -4,6 +4,11 @@
//! The IMAP client in this module is implemented as a state machine in the type system: methods //! The IMAP client in this module is implemented as a state machine in the type system: methods
//! that are not supported in a particular state (ex. fetch in an unauthenticated state) cannot be //! that are not supported in a particular state (ex. fetch in an unauthenticated state) cannot be
//! expressed in the type system entirely. //! expressed in the type system entirely.
//!
//! Because there's many client types for the different types of clients, you'll want to start
//! here:
//!
//! - [ClientBuilder][self::ClientBuilder] : Constructs the config for the IMAP client
mod inner; mod inner;
@ -18,6 +23,13 @@ use tokio_rustls::{client::TlsStream, rustls::ClientConfig, webpki::DNSNameRef,
use self::inner::Client; use self::inner::Client;
/// Struct used to start building the config for a client.
///
/// Call [`.build`][1] to _build_ the config, then run [`.connect`][2] to actually start opening
/// the connection to the server.
///
/// [1]: self::ClientNotConnectedBuilder::build
/// [2]: self::ClientNotConnected::connect
pub type ClientBuilder = ClientNotConnectedBuilder; pub type ClientBuilder = ClientNotConnectedBuilder;
/// An IMAP client that hasn't been connected yet. /// An IMAP client that hasn't been connected yet.
@ -52,37 +64,45 @@ impl ClientNotConnected {
let inner = Client::new(conn); let inner = Client::new(conn);
return Ok(ClientUnauthenticated::Encrypted( return Ok(ClientUnauthenticated::Encrypted(
ClientEncryptedUnauthenticated { inner }, ClientUnauthenticatedEncrypted { inner },
)); ));
} }
let inner = Client::new(conn); let inner = Client::new(conn);
return Ok(ClientUnauthenticated::Unencrypted( return Ok(ClientUnauthenticated::Unencrypted(
ClientUnencryptedUnauthenticated { inner }, ClientUnauthenticatedUnencrypted { inner },
)); ));
} }
} }
pub enum ClientUnauthenticated { pub enum ClientUnauthenticated {
Encrypted(ClientEncryptedUnauthenticated), Encrypted(ClientUnauthenticatedEncrypted),
Unencrypted(ClientUnencryptedUnauthenticated), Unencrypted(ClientUnauthenticatedUnencrypted),
} }
impl ClientUnauthenticated {} impl ClientUnauthenticated {
pub async fn supports(&mut self) -> Result<()> {
match self {
ClientUnauthenticated::Encrypted(e) => e.inner.supports().await?,
ClientUnauthenticated::Unencrypted(e) => e.inner.supports().await?,
}
Ok(())
}
}
pub struct ClientUnencryptedUnauthenticated { pub struct ClientUnauthenticatedUnencrypted {
/// Connection to the remote server /// Connection to the remote server
inner: Client<TcpStream>, inner: Client<TcpStream>,
} }
impl ClientUnencryptedUnauthenticated { impl ClientUnauthenticatedUnencrypted {
pub async fn upgrade(&self) {} pub async fn upgrade(&self) {}
} }
/// An IMAP client that isn't authenticated. /// An IMAP client that isn't authenticated.
pub struct ClientEncryptedUnauthenticated { pub struct ClientUnauthenticatedEncrypted {
/// Connection to the remote server /// Connection to the remote server
inner: Client<TlsStream<TcpStream>>, inner: Client<TlsStream<TcpStream>>,
} }
impl ClientEncryptedUnauthenticated {} impl ClientUnauthenticatedEncrypted {}

View file

@ -1,6 +1,7 @@
use std::fmt; use std::fmt;
/// Commands, without the tag part. /// Commands, without the tag part.
#[derive(Clone, Debug)]
pub enum Command { pub enum Command {
Capability, Capability,
} }

View file

@ -1 +1,2 @@
pub struct Response {} #[derive(Clone, Debug)]
pub struct Response(pub String);

View file

@ -48,7 +48,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); debug!("opening imap connection for {:?}", acct);
osu(acct).await; osu(acct).await.unwrap();
// open_imap_connection(acct.imap).await.unwrap(); // open_imap_connection(acct.imap).await.unwrap();
} }
}); });
@ -68,7 +68,10 @@ async fn osu(acct: MailAccountConfig) -> Result<()> {
.map_err(|err| anyhow!("err: {}", err))?; .map_err(|err| anyhow!("err: {}", err))?;
debug!("connecting to {}:{}", &acct.imap.server, acct.imap.port); debug!("connecting to {}:{}", &acct.imap.server, acct.imap.port);
let unauth = builder.connect().await; let mut unauth = builder.connect().await?;
debug!("sending CAPABILITY");
unauth.supports().await?;
Ok(()) Ok(())
} }