|
@@ -16,20 +16,25 @@ use tokio_rustls::client::TlsStream;
|
|
use tokio_rustls::rustls::{ClientConfig, RootCertStore};
|
|
use tokio_rustls::rustls::{ClientConfig, RootCertStore};
|
|
use tokio_rustls::rustls::pki_types::ServerName;
|
|
use tokio_rustls::rustls::pki_types::ServerName;
|
|
use crate::config::Config;
|
|
use crate::config::Config;
|
|
-use crate::{add_email};
|
|
|
|
|
|
+use crate::{add_email, delete_email};
|
|
use tokio::task;
|
|
use tokio::task;
|
|
use tokio::time::sleep;
|
|
use tokio::time::sleep;
|
|
|
|
+use imap_proto::Response;
|
|
|
|
+use crate::indexes::{Indexes, SerializableMessage};
|
|
|
|
|
|
#[cfg(not(target_os = "wasi"))]
|
|
#[cfg(not(target_os = "wasi"))]
|
|
use async_imap::{Client, Session};
|
|
use async_imap::{Client, Session};
|
|
#[cfg(not(target_os = "wasi"))]
|
|
#[cfg(not(target_os = "wasi"))]
|
|
use async_imap::extensions::idle::IdleResponse::NewData;
|
|
use async_imap::extensions::idle::IdleResponse::NewData;
|
|
-
|
|
|
|
|
|
+#[cfg(not(target_os = "wasi"))]
|
|
|
|
+use async_imap::types::{Uid, UnsolicitedResponse};
|
|
|
|
+use tokio::task::JoinHandle;
|
|
#[cfg(target_os = "wasi")]
|
|
#[cfg(target_os = "wasi")]
|
|
use async_imap_wasi::{Client, Session};
|
|
use async_imap_wasi::{Client, Session};
|
|
#[cfg(target_os = "wasi")]
|
|
#[cfg(target_os = "wasi")]
|
|
use async_imap_wasi::extensions::idle::IdleResponse::NewData;
|
|
use async_imap_wasi::extensions::idle::IdleResponse::NewData;
|
|
-use crate::indexes::Indexes;
|
|
|
|
|
|
+#[cfg(target_os = "wasi")]
|
|
|
|
+use async_imap_wasi::types::Uid;
|
|
|
|
|
|
/// create TLS connect with the IMAP server
|
|
/// create TLS connect with the IMAP server
|
|
pub async fn connect_to_imap() -> anyhow::Result<Client<TlsStream<TcpStream>>>{
|
|
pub async fn connect_to_imap() -> anyhow::Result<Client<TlsStream<TcpStream>>>{
|
|
@@ -149,7 +154,6 @@ pub async fn fetch_and_store_emails(session: &mut Session<TlsStream<TcpStream>>,
|
|
let mail_file = store(Config::global().maildir.clone().join(list.clone()), uid.clone().to_string(), "new".to_string(), body, "");
|
|
let mail_file = store(Config::global().maildir.clone().join(list.clone()), uid.clone().to_string(), "new".to_string(), body, "");
|
|
match mail_file {
|
|
match mail_file {
|
|
Ok(file) => {
|
|
Ok(file) => {
|
|
- // TODO convert and persist html
|
|
|
|
// persist to the maildir
|
|
// persist to the maildir
|
|
stored_paths.push((uid.to_string().parse().unwrap(), file.clone()));
|
|
stored_paths.push((uid.to_string().parse().unwrap(), file.clone()));
|
|
|
|
|
|
@@ -159,7 +163,6 @@ pub async fn fetch_and_store_emails(session: &mut Session<TlsStream<TcpStream>>,
|
|
Err(_) => {println!("Error adding email from {:?}", file.clone())}
|
|
Err(_) => {println!("Error adding email from {:?}", file.clone())}
|
|
};
|
|
};
|
|
|
|
|
|
- // TODO adjust indexes
|
|
|
|
Indexes::persist_threads().expect("Unable to persist threads");
|
|
Indexes::persist_threads().expect("Unable to persist threads");
|
|
Indexes::persist_indexes(vec![list.clone()]).expect("Unable to persist indexes");
|
|
Indexes::persist_indexes(vec![list.clone()]).expect("Unable to persist indexes");
|
|
},
|
|
},
|
|
@@ -184,7 +187,7 @@ pub async fn fetch_and_store_emails(session: &mut Session<TlsStream<TcpStream>>,
|
|
}
|
|
}
|
|
|
|
|
|
/// read uids that have been already downloaded
|
|
/// read uids that have been already downloaded
|
|
-fn read_local_uids(uids_path: PathBuf) -> anyhow::Result<HashMap<String, HashSet<String>>> {
|
|
|
|
|
|
+pub fn read_local_uids(uids_path: PathBuf) -> anyhow::Result<HashMap<String, HashSet<String>>> {
|
|
let file = match File::open(&uids_path) {
|
|
let file = match File::open(&uids_path) {
|
|
Ok(file) => file,
|
|
Ok(file) => file,
|
|
Err(_) => return Ok(HashMap::new()), // Propagate other errors
|
|
Err(_) => return Ok(HashMap::new()), // Propagate other errors
|
|
@@ -299,8 +302,53 @@ pub async fn delete_folder(name: String) -> anyhow::Result<()>{
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/// deletes all emails locally that were deleted remotely
|
|
|
|
+pub async fn remove_deleted_emails(mailbox: String) -> anyhow::Result<u32> {
|
|
|
|
+ // TODO move session creation to the function
|
|
|
|
+ let mut client = match connect_to_imap().await {
|
|
|
|
+ Ok(client) => client,
|
|
|
|
+ Err(e) => {
|
|
|
|
+ return Err(anyhow!("Failed to connect to IMAP"));
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ let session_result = client
|
|
|
|
+ .login(Config::global().username.clone(), Config::global().password.clone())
|
|
|
|
+ .await;
|
|
|
|
+
|
|
|
|
+ let mut session = match session_result {
|
|
|
|
+ Ok(session) => session,
|
|
|
|
+ Err(_) => return Err(anyhow!("Unable to login to IMAP server")),
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ session.select(mailbox.clone()).await?;
|
|
|
|
+
|
|
|
|
+ let uids_path = Config::global().maildir.clone().join(".uids.json");
|
|
|
|
+ let uids_local = match read_local_uids(uids_path.clone()).unwrap().get(&mailbox){
|
|
|
|
+ None => HashSet::new(),
|
|
|
|
+ Some(hash_set) => (*hash_set).clone()
|
|
|
|
+ };
|
|
|
|
+ let uids_server = session.uid_search("ALL").await.unwrap();
|
|
|
|
+
|
|
|
|
+ let deleted_uids: Vec<u32> = uids_local
|
|
|
|
+ .into_iter()
|
|
|
|
+ .filter_map(|s| s.parse::<Uid>().ok())
|
|
|
|
+ .filter(|uid| !uids_server.contains(uid))
|
|
|
|
+ .collect();
|
|
|
|
+
|
|
|
|
+ // delete email from indexes
|
|
|
|
+ for deleted_uid in &deleted_uids {
|
|
|
|
+ match Indexes::find_by_uid(&Indexes::get_messages()?, *deleted_uid) {
|
|
|
|
+ None => {}
|
|
|
|
+ Some(message) => delete_email(message.id).await.unwrap()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Ok(deleted_uids.len() as u32)
|
|
|
|
+}
|
|
|
|
+
|
|
/// run a async task to monitor any changes in the mailbox
|
|
/// run a async task to monitor any changes in the mailbox
|
|
-pub async fn check_for_updates(mailbox: String) -> anyhow::Result<()> {
|
|
|
|
|
|
+pub fn check_for_updates(mailbox: String) -> JoinHandle<Result<(), anyhow::Error>> {
|
|
task::spawn(async move {
|
|
task::spawn(async move {
|
|
let mut client = match connect_to_imap().await {
|
|
let mut client = match connect_to_imap().await {
|
|
Ok(client) => client,
|
|
Ok(client) => client,
|
|
@@ -321,28 +369,42 @@ pub async fn check_for_updates(mailbox: String) -> anyhow::Result<()> {
|
|
session.select(mailbox.clone()).await?;
|
|
session.select(mailbox.clone()).await?;
|
|
|
|
|
|
loop {
|
|
loop {
|
|
|
|
+ println!("Start IDLE loop");
|
|
let mut idle = session.idle();
|
|
let mut idle = session.idle();
|
|
idle.init().await.unwrap();
|
|
idle.init().await.unwrap();
|
|
let (idle_wait, interrupt) = idle.wait();
|
|
let (idle_wait, interrupt) = idle.wait();
|
|
let idle_handle = task::spawn(async move {
|
|
let idle_handle = task::spawn(async move {
|
|
- println!("IDLE: waiting for 30s"); // TODO remove debug prints
|
|
|
|
- sleep(Duration::from_secs(30)).await;
|
|
|
|
- println!("IDLE: waited 30 secs, now interrupting idle");
|
|
|
|
|
|
+ sleep(Duration::from_mins(25)).await;
|
|
drop(interrupt);
|
|
drop(interrupt);
|
|
});
|
|
});
|
|
|
|
|
|
match idle_wait.await.unwrap() {
|
|
match idle_wait.await.unwrap() {
|
|
- // TODO add more cases like delete, move...
|
|
|
|
NewData(data) => {
|
|
NewData(data) => {
|
|
- // TODO do not update all emails (IMAP returns * {number} RECENT) and do it only for one mailbox
|
|
|
|
- // TODO CHANGE IT!!!
|
|
|
|
- // let new_paths = download_email_from_imap().await.expect("Cannot download new emails");
|
|
|
|
- // for (uid, path) in new_paths.clone() {
|
|
|
|
- // match add_email(path.clone(), uid.clone()){
|
|
|
|
- // Ok(_) => {}
|
|
|
|
- // Err(_) => {println!("Error adding email from {:?}", path.clone())}
|
|
|
|
- // };
|
|
|
|
- // }
|
|
|
|
|
|
+ println!("Parsed response from IDLE: {:?}", data.parsed());
|
|
|
|
+ match data.parsed() {
|
|
|
|
+ Response::Capabilities(_) => {}
|
|
|
|
+ Response::Continue { .. } => {}
|
|
|
|
+ Response::Done { .. } => {}
|
|
|
|
+ Response::Data { .. } => {}
|
|
|
|
+ Response::Expunge(_) => {
|
|
|
|
+ let _ = remove_deleted_emails(mailbox.clone()).await;
|
|
|
|
+ // check other folders if the email was moved
|
|
|
|
+ // download_email_from_imap().await.expect("Cannot download new emails");
|
|
|
|
+ }
|
|
|
|
+ Response::Vanished { .. } => {}
|
|
|
|
+ Response::Fetch(_, _) => {}
|
|
|
|
+ Response::MailboxData(_) => {
|
|
|
|
+ //TODO check only RECENT
|
|
|
|
+ download_email_from_imap().await.expect("Cannot download new emails");
|
|
|
|
+ }
|
|
|
|
+ Response::Quota(_) => {}
|
|
|
|
+ Response::QuotaRoot(_) => {}
|
|
|
|
+ Response::Id(_) => {}
|
|
|
|
+ Response::Acl(_) => {}
|
|
|
|
+ Response::ListRights(_) => {}
|
|
|
|
+ Response::MyRights(_) => {}
|
|
|
|
+ _ => {}
|
|
|
|
+ }
|
|
}
|
|
}
|
|
reason => {
|
|
reason => {
|
|
println!("IDLE failed {:?}", reason);
|
|
println!("IDLE failed {:?}", reason);
|
|
@@ -350,14 +412,12 @@ pub async fn check_for_updates(mailbox: String) -> anyhow::Result<()> {
|
|
}
|
|
}
|
|
|
|
|
|
// Ensure the idle handle is dropped before the next loop iteration
|
|
// Ensure the idle handle is dropped before the next loop iteration
|
|
- idle_handle.await.unwrap();
|
|
|
|
|
|
+ // idle_handle.await.unwrap();
|
|
|
|
|
|
// Reassign session to prevent ownership issues in the next loop iteration
|
|
// Reassign session to prevent ownership issues in the next loop iteration
|
|
session = idle.done().await.unwrap();
|
|
session = idle.done().await.unwrap();
|
|
}
|
|
}
|
|
- });
|
|
|
|
-
|
|
|
|
- Ok(())
|
|
|
|
|
|
+ })
|
|
}
|
|
}
|
|
|
|
|
|
/// saves a raw email properly
|
|
/// saves a raw email properly
|