diff --git a/README.md b/README.md index 1ff60f6..6ef4ef5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ 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). diff --git a/imap/.ignore b/imap/.ignore index 72e8ffc..54d47a5 100644 --- a/imap/.ignore +++ b/imap/.ignore @@ -1 +1,2 @@ -* +src/builders +src/parser diff --git a/imap/examples/parse_response.rs b/imap/examples/parse_response.rs deleted file mode 100644 index 5751892..0000000 --- a/imap/examples/parse_response.rs +++ /dev/null @@ -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(()) -} diff --git a/imap/src/client/inner.rs b/imap/src/client/inner.rs index 6be5161..c712123 100644 --- a/imap/src/client/inner.rs +++ b/imap/src/client/inner.rs @@ -6,7 +6,7 @@ use std::task::{Context, Poll}; use anyhow::Result; use futures::future::{Future, FutureExt}; use panorama_strings::{StringEntry, StringStore}; -use parking_lot::RwLock; +use parking_lot::{Mutex, RwLock}; use tokio::{ io::{ self, AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, @@ -16,6 +16,7 @@ use tokio::{ }; use crate::command::Command; +use crate::response::Response; pub type BoxedFunc = Box; @@ -25,7 +26,7 @@ pub struct Client { symbols: StringStore, id: usize, - handlers: Arc>>, + results: ResultMap, /// Cached capabilities that shouldn't change between caps: Vec, @@ -39,40 +40,49 @@ where /// Creates a new client that wraps a connection pub fn new(conn: C) -> Self { 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 { conn: write_half, symbols: StringStore::new(256), id: 0, - handlers: Arc::new(RwLock::new(HashMap::new())), + results, caps: Vec::new(), handle: listen_fut, } } /// 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 { + debug!("executing command {:?}", cmd); let id = self.id; self.id += 1; - { - let mut handlers = self.handlers.write(); - handlers.insert(id, false); + let mut handlers = self.results.write(); + 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?; + debug!("[{}] written.", id); 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 - pub async fn supports(&mut self) { + pub async fn supports(&mut self) -> Result<()> { let cmd = Command::Capability; - let result = self.execute(cmd).await; - debug!("poggers {:?}", result); + debug!("sending: {:?} {:?}", cmd, cmd.to_string()); + let result = self.execute(cmd).await?; + debug!("result from supports: {:?}", result); + Ok(()) } } @@ -80,28 +90,48 @@ pub struct ExecHandle<'a, C>(&'a Client, usize); impl<'a, C> Future for ExecHandle<'a, C> { type Output = (); - fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll { - let state = { - let handlers = self.0.handlers.read(); - handlers.get(&self.1).cloned() - }; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let mut handlers = self.0.results.write(); + let mut state = handlers.get_mut(&self.1); // TODO: handle the None case here - let state = state.unwrap(); + debug!("f[{}] {:?}", self.1, state); + let (result, waker) = state.unwrap(); - match state { - true => Poll::Ready(()), - false => Poll::Pending, + match result { + Some(_) => Poll::Ready(()), + None => { + *waker = Some(cx.waker().clone()); + Poll::Pending + } } } } -async fn listen(conn: impl AsyncRead + Unpin) -> Result<()> { +use std::task::Waker; +pub type ResultMap = Arc, Option)>>>; + +async fn listen(conn: impl AsyncRead + Unpin, results: ResultMap) -> Result<()> { debug!("amogus"); let mut reader = BufReader::new(conn); loop { let mut next_line = String::new(); reader.read_line(&mut next_line).await?; - debug!("line: {:?}", next_line); + + // debug!("line: {:?}", next_line); + let parts = next_line.split(" ").collect::>(); + let tag = parts[0]; + if tag == "*" { + debug!("UNTAGGED {:?}", next_line); + } else if tag.starts_with("pano") { + let id = tag.trim_start_matches("pano").parse::()?; + 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(); + } + } } } diff --git a/imap/src/client/mod.rs b/imap/src/client/mod.rs index 0012887..dfced4d 100644 --- a/imap/src/client/mod.rs +++ b/imap/src/client/mod.rs @@ -4,6 +4,11 @@ //! 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 //! 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; @@ -18,6 +23,13 @@ use tokio_rustls::{client::TlsStream, rustls::ClientConfig, webpki::DNSNameRef, 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; /// An IMAP client that hasn't been connected yet. @@ -52,37 +64,45 @@ impl ClientNotConnected { let inner = Client::new(conn); return Ok(ClientUnauthenticated::Encrypted( - ClientEncryptedUnauthenticated { inner }, + ClientUnauthenticatedEncrypted { inner }, )); } let inner = Client::new(conn); return Ok(ClientUnauthenticated::Unencrypted( - ClientUnencryptedUnauthenticated { inner }, + ClientUnauthenticatedUnencrypted { inner }, )); } } pub enum ClientUnauthenticated { - Encrypted(ClientEncryptedUnauthenticated), - Unencrypted(ClientUnencryptedUnauthenticated), + Encrypted(ClientUnauthenticatedEncrypted), + 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 inner: Client, } -impl ClientUnencryptedUnauthenticated { +impl ClientUnauthenticatedUnencrypted { pub async fn upgrade(&self) {} } /// An IMAP client that isn't authenticated. -pub struct ClientEncryptedUnauthenticated { +pub struct ClientUnauthenticatedEncrypted { /// Connection to the remote server inner: Client>, } -impl ClientEncryptedUnauthenticated {} +impl ClientUnauthenticatedEncrypted {} diff --git a/imap/src/command/mod.rs b/imap/src/command/mod.rs index 44f5c2a..1018026 100644 --- a/imap/src/command/mod.rs +++ b/imap/src/command/mod.rs @@ -1,6 +1,7 @@ use std::fmt; /// Commands, without the tag part. +#[derive(Clone, Debug)] pub enum Command { Capability, } diff --git a/imap/src/response/mod.rs b/imap/src/response/mod.rs index 12a31b8..2a6bc74 100644 --- a/imap/src/response/mod.rs +++ b/imap/src/response/mod.rs @@ -1 +1,2 @@ -pub struct Response {} +#[derive(Clone, Debug)] +pub struct Response(pub String); diff --git a/src/mail/mod.rs b/src/mail/mod.rs index 907d924..d523429 100644 --- a/src/mail/mod.rs +++ b/src/mail/mod.rs @@ -48,7 +48,7 @@ pub async fn run_mail( let handle = tokio::spawn(async { for acct in config.mail_accounts.into_iter() { debug!("opening imap connection for {:?}", acct); - osu(acct).await; + osu(acct).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))?; 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(()) }