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
|
/output.log
|
||||||
/config.toml
|
/config.toml
|
||||||
/public
|
/public
|
||||||
|
/hellosu
|
||||||
|
/hellosu.db*
|
||||||
|
|
|
@ -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
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 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?;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue