sqlx working?
This commit is contained in:
parent
8bbb0bcbc3
commit
19f42756c2
7 changed files with 104 additions and 21 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,3 +3,5 @@
|
|||
/output.log
|
||||
/config.toml
|
||||
/public
|
||||
/hellosu
|
||||
/hellosu.db*
|
||||
|
|
|
@ -56,7 +56,8 @@ use tokio_rustls::{
|
|||
|
||||
use crate::command::{Command, FetchItems, SearchCriteria};
|
||||
use crate::response::{
|
||||
AttributeValue, Envelope, MailboxData, Response, ResponseData, ResponseDone,
|
||||
AttributeValue, Envelope, MailboxData, MailboxFlag, Response, ResponseCode, ResponseData,
|
||||
ResponseDone, Status,
|
||||
};
|
||||
|
||||
pub use self::inner::{Client, ResponseStream};
|
||||
|
@ -67,7 +68,7 @@ pub use self::inner::{Client, ResponseStream};
|
|||
/// the connection to the server.
|
||||
///
|
||||
/// [1]: self::ClientConfigBuilder::build
|
||||
/// [2]: self::ClientConfig::connect
|
||||
/// [2]: self::ClientConfig::open
|
||||
pub type ClientBuilder = ClientConfigBuilder;
|
||||
|
||||
/// An IMAP client that hasn't been connected yet.
|
||||
|
@ -194,20 +195,36 @@ impl ClientAuthenticated {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
mailbox: mailbox.as_ref().to_owned(),
|
||||
};
|
||||
|
||||
let stream = self.execute(cmd).await?;
|
||||
let (_, data) = stream.wait().await?;
|
||||
|
||||
let mut select = SelectResponse::default();
|
||||
for resp in data {
|
||||
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
|
||||
// self.nuke_capabilities();
|
||||
|
||||
Ok(())
|
||||
Ok(select)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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")))]
|
||||
impl Drop for IdleToken {
|
||||
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
15
migrations/1_initial.sql
Normal 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")
|
||||
);
|
|
@ -19,13 +19,14 @@ use tokio::{
|
|||
|
||||
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
|
||||
pub async fn imap_main(
|
||||
/// The main function for the IMAP syncing thread
|
||||
pub async fn sync_main(
|
||||
acct_name: impl AsRef<str>,
|
||||
acct: MailAccountConfig,
|
||||
mail2ui_tx: UnboundedSender<MailEvent>,
|
||||
mail_store: MailStore,
|
||||
) -> Result<()> {
|
||||
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?
|
||||
debug!("selecting the INBOX mailbox");
|
||||
authed.select("INBOX").await?;
|
||||
let select = authed.select("INBOX").await?;
|
||||
debug!("select result: {:?}", select);
|
||||
|
||||
loop {
|
||||
let folder_list = authed.list().await?;
|
||||
|
|
|
@ -29,6 +29,7 @@ use crate::config::{Config, ConfigWatcher, ImapAuth, MailAccountConfig, TlsMetho
|
|||
|
||||
pub use self::event::MailEvent;
|
||||
pub use self::metadata::EmailMetadata;
|
||||
pub use self::store::MailStore;
|
||||
|
||||
/// Command sent to the mail thread by something else (i.e. UI)
|
||||
#[derive(Debug)]
|
||||
|
@ -66,14 +67,23 @@ pub async fn run_mail(
|
|||
conn.abort();
|
||||
}
|
||||
|
||||
let mail_store = MailStore::new().await?;
|
||||
for (acct_name, acct) in config.mail_accounts.into_iter() {
|
||||
let mail2ui_tx = mail2ui_tx.clone();
|
||||
let mail_store = mail_store.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
// debug!("opening imap connection for {:?}", acct);
|
||||
|
||||
// this loop is to make sure accounts are restarted on error
|
||||
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(_) => {}
|
||||
Err(err) => {
|
||||
error!("IMAP Error: {}", err);
|
||||
|
|
|
@ -1,23 +1,49 @@
|
|||
//! Package for managing the offline storage of emails
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
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
|
||||
///
|
||||
/// This struct is clone-safe: cloning it will just return a reference to the same data structure
|
||||
#[derive(Clone)]
|
||||
pub struct MailStore {
|
||||
mail_dir: PathBuf,
|
||||
pool: SqlitePool,
|
||||
}
|
||||
|
||||
impl MailStore {
|
||||
/// Creates a new MailStore
|
||||
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? {
|
||||
Sqlite::create_database(db_path).await?;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
Ok(MailStore { pool })
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen_loop(pool: SqlitePool) {
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ fn setup_logger(log_file: Option<impl AsRef<Path>>) -> Result<()> {
|
|||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}[{}][{}] {}",
|
||||
now,
|
||||
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
||||
record.target(),
|
||||
colors.color(record.level()),
|
||||
message
|
||||
|
|
Loading…
Reference in a new issue