sqlx working?

This commit is contained in:
Michael Zhang 2021-03-25 04:30:31 -05:00
parent 8bbb0bcbc3
commit 19f42756c2
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
7 changed files with 104 additions and 21 deletions

2
.gitignore vendored
View file

@ -3,3 +3,5 @@
/output.log /output.log
/config.toml /config.toml
/public /public
/hellosu
/hellosu.db*

View file

@ -56,7 +56,8 @@ use tokio_rustls::{
use crate::command::{Command, FetchItems, SearchCriteria}; use crate::command::{Command, FetchItems, SearchCriteria};
use crate::response::{ use crate::response::{
AttributeValue, Envelope, MailboxData, Response, ResponseData, ResponseDone, AttributeValue, Envelope, MailboxData, MailboxFlag, Response, ResponseCode, ResponseData,
ResponseDone, Status,
}; };
pub use self::inner::{Client, ResponseStream}; pub use self::inner::{Client, ResponseStream};
@ -67,7 +68,7 @@ pub use self::inner::{Client, ResponseStream};
/// the connection to the server. /// the connection to the server.
/// ///
/// [1]: self::ClientConfigBuilder::build /// [1]: self::ClientConfigBuilder::build
/// [2]: self::ClientConfig::connect /// [2]: self::ClientConfig::open
pub type ClientBuilder = ClientConfigBuilder; pub type ClientBuilder = ClientConfigBuilder;
/// An IMAP client that hasn't been connected yet. /// An IMAP client that hasn't been connected yet.
@ -194,20 +195,36 @@ impl ClientAuthenticated {
} }
/// Runs the SELECT command /// Runs the SELECT command
pub async fn select(&mut self, mailbox: impl AsRef<str>) -> Result<()> { pub async fn select(&mut self, mailbox: impl AsRef<str>) -> Result<SelectResponse> {
let cmd = Command::Select { let cmd = Command::Select {
mailbox: mailbox.as_ref().to_owned(), mailbox: mailbox.as_ref().to_owned(),
}; };
let stream = self.execute(cmd).await?; let stream = self.execute(cmd).await?;
let (_, data) = stream.wait().await?; let (_, data) = stream.wait().await?;
let mut select = SelectResponse::default();
for resp in data { for resp in data {
debug!("execute called returned: {:?}", resp); debug!("execute called returned: {:?}", resp);
match resp {
Response::MailboxData(MailboxData::Flags(flags)) => select.flags = flags,
Response::MailboxData(MailboxData::Exists(exists)) => select.exists = Some(exists),
Response::MailboxData(MailboxData::Recent(recent)) => select.recent = Some(recent),
Response::Data(ResponseData {
status: Status::Ok,
code: Some(code),
..
}) => match code {
ResponseCode::Unseen(value) => select.unseen = Some(value),
ResponseCode::UidNext(value) => select.uid_next = Some(value),
ResponseCode::UidValidity(value) => select.uid_validity = Some(value),
_ => {}
},
_ => {}
}
} }
// nuke the capabilities cache Ok(select)
// self.nuke_capabilities();
Ok(())
} }
/// Runs the SEARCH command /// Runs the SEARCH command
@ -274,6 +291,16 @@ impl ClientAuthenticated {
} }
} }
#[derive(Debug, Default)]
pub struct SelectResponse {
flags: Vec<MailboxFlag>,
exists: Option<u32>,
recent: Option<u32>,
uid_next: Option<u32>,
uid_validity: Option<u32>,
unseen: Option<u32>,
}
/// A token that represents an idling connection. /// A token that represents an idling connection.
/// ///
/// Dropping this token indicates that the idling should be completed, and the DONE command will be /// Dropping this token indicates that the idling should be completed, and the DONE command will be
@ -289,7 +316,8 @@ pub struct IdleToken {
#[cfg_attr(docsrs, doc(cfg(feature = "rfc2177-idle")))] #[cfg_attr(docsrs, doc(cfg(feature = "rfc2177-idle")))]
impl Drop for IdleToken { impl Drop for IdleToken {
fn drop(&mut self) { fn drop(&mut self) {
self.sender.send(format!("DONE\r\n")); // TODO: should ignore this?
self.sender.send(format!("DONE\r\n")).unwrap();
} }
} }

15
migrations/1_initial.sql Normal file
View file

@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS "accounts" (
"id" INTEGER PRIMARY KEY,
-- hash of the account details, used to check if accounts have changed
"checksum" TEXT,
"name" TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "mail" (
"id" INTEGER PRIMARY KEY,
"account_id" INTEGER,
"folder" TEXT,
"uid" INTEGER,
FOREIGN KEY ("account_id") REFERENCES "accounts" ("id")
);

View file

@ -19,13 +19,14 @@ use tokio::{
use crate::config::{Config, ConfigWatcher, ImapAuth, MailAccountConfig, TlsMethod}; use crate::config::{Config, ConfigWatcher, ImapAuth, MailAccountConfig, TlsMethod};
use super::{MailCommand, MailEvent}; use super::{MailCommand, MailEvent, MailStore};
/// The main sequence of steps for the IMAP thread to follow /// The main function for the IMAP syncing thread
pub async fn imap_main( pub async fn sync_main(
acct_name: impl AsRef<str>, acct_name: impl AsRef<str>,
acct: MailAccountConfig, acct: MailAccountConfig,
mail2ui_tx: UnboundedSender<MailEvent>, mail2ui_tx: UnboundedSender<MailEvent>,
mail_store: MailStore,
) -> Result<()> { ) -> Result<()> {
let acct_name = acct_name.as_ref().to_owned(); let acct_name = acct_name.as_ref().to_owned();
@ -66,7 +67,8 @@ pub async fn imap_main(
// let's just select INBOX for now, maybe have a config for default mailbox later? // let's just select INBOX for now, maybe have a config for default mailbox later?
debug!("selecting the INBOX mailbox"); debug!("selecting the INBOX mailbox");
authed.select("INBOX").await?; let select = authed.select("INBOX").await?;
debug!("select result: {:?}", select);
loop { loop {
let folder_list = authed.list().await?; let folder_list = authed.list().await?;

View file

@ -29,6 +29,7 @@ use crate::config::{Config, ConfigWatcher, ImapAuth, MailAccountConfig, TlsMetho
pub use self::event::MailEvent; pub use self::event::MailEvent;
pub use self::metadata::EmailMetadata; pub use self::metadata::EmailMetadata;
pub use self::store::MailStore;
/// Command sent to the mail thread by something else (i.e. UI) /// Command sent to the mail thread by something else (i.e. UI)
#[derive(Debug)] #[derive(Debug)]
@ -66,14 +67,23 @@ pub async fn run_mail(
conn.abort(); conn.abort();
} }
let mail_store = MailStore::new().await?;
for (acct_name, acct) in config.mail_accounts.into_iter() { for (acct_name, acct) in config.mail_accounts.into_iter() {
let mail2ui_tx = mail2ui_tx.clone(); let mail2ui_tx = mail2ui_tx.clone();
let mail_store = mail_store.clone();
let handle = tokio::spawn(async move { let handle = tokio::spawn(async move {
// debug!("opening imap connection for {:?}", acct); // debug!("opening imap connection for {:?}", acct);
// this loop is to make sure accounts are restarted on error // this loop is to make sure accounts are restarted on error
loop { loop {
match client::imap_main(&acct_name, acct.clone(), mail2ui_tx.clone()).await { match client::sync_main(
&acct_name,
acct.clone(),
mail2ui_tx.clone(),
mail_store.clone(),
)
.await
{
Ok(_) => {} Ok(_) => {}
Err(err) => { Err(err) => {
error!("IMAP Error: {}", err); error!("IMAP Error: {}", err);

View file

@ -1,23 +1,49 @@
//! Package for managing the offline storage of emails //! Package for managing the offline storage of emails
use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use sqlx::sqlite::SqlitePool; use sqlx::{
migrate::{MigrateDatabase, Migrator},
sqlite::{Sqlite, SqlitePool},
};
use tokio::fs;
static MIGRATOR: Migrator = sqlx::migrate!();
/// SQLite email manager /// SQLite email manager
///
/// This struct is clone-safe: cloning it will just return a reference to the same data structure
#[derive(Clone)] #[derive(Clone)]
pub struct MailStore { pub struct MailStore {
mail_dir: PathBuf,
pool: SqlitePool, pool: SqlitePool,
} }
impl MailStore { impl MailStore {
/// Creates a new MailStore
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
let pool = SqlitePool::connect("hellosu.db").await?; let db_path = "sqlite:hellosu.db";
let run = tokio::spawn(listen_loop(pool.clone())); // create the database file if it doesn't already exist -_ -
if !Sqlite::database_exists(db_path).await? {
Ok(MailStore { pool }) Sqlite::create_database(db_path).await?;
}
} }
async fn listen_loop(pool: SqlitePool) { let pool = SqlitePool::connect(db_path).await?;
MIGRATOR.run(&pool).await?;
debug!("run migrations : {:?}", MIGRATOR);
let mail_dir = PathBuf::from("hellosu/");
if !mail_dir.exists() {
fs::create_dir_all(&mail_dir).await?;
}
Ok(MailStore { mail_dir, pool })
}
/// Gets the list of all the UIDs in the given folder that need to be updated
pub fn get_new_uids(&self, exists: u32) {
}
} }

View file

@ -127,7 +127,7 @@ fn setup_logger(log_file: Option<impl AsRef<Path>>) -> Result<()> {
.format(move |out, message, record| { .format(move |out, message, record| {
out.finish(format_args!( out.finish(format_args!(
"{}[{}][{}] {}", "{}[{}][{}] {}",
now, chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(), record.target(),
colors.color(record.level()), colors.color(record.level()),
message message