diff --git a/src/constants.rs b/src/constants.rs index 062632fdf..c6c6ec17b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -122,6 +122,15 @@ pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9; pub const DC_CREATE_MVBOX: usize = 1; +#[repr(i32)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] +pub enum Delay { + DoNotTryAgain = 0, + AtOnce = -1, + Standard = 3, + IncreationPoll = 2, +} + // Flags for configuring IMAP and SMTP servers. // These flags are optional // and may be set together with the username, password etc. diff --git a/src/imap.rs b/src/imap.rs index a9d814787..e3c335fd4 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -12,14 +12,19 @@ use crate::dc_loginparam::*; use crate::dc_tools::CStringExt; use crate::oauth2::dc_get_oauth2_access_token; use crate::types::*; +use deltachat_derive::*; const DC_IMAP_SEEN: usize = 0x0001; const DC_REGENERATE: usize = 0x01; -const DC_SUCCESS: usize = 3; -const DC_ALREADY_DONE: usize = 2; -const DC_RETRY_LATER: usize = 1; -const DC_FAILED: usize = 0; +#[repr(usize)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] +pub enum ImapResult { + Success = 3, + AlreadyDone = 2, + RetryLater = 1, + Failed = 0, +} const PREFETCH_FLAGS: &str = "(UID ENVELOPE)"; const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])"; @@ -1164,12 +1169,12 @@ impl Imap { uid: u32, dest_folder: S2, dest_uid: &mut u32, - ) -> usize { - let mut res = DC_RETRY_LATER; + ) -> ImapResult { + let mut res = ImapResult::RetryLater; let set = format!("{}", uid); if uid == 0 { - res = DC_FAILED; + res = ImapResult::Failed; } else if folder.as_ref() == dest_folder.as_ref() { info!( context, @@ -1180,7 +1185,7 @@ impl Imap { dest_folder.as_ref() ); - res = DC_ALREADY_DONE; + res = ImapResult::AlreadyDone; } else { info!( context, @@ -1202,7 +1207,7 @@ impl Imap { let moved = if let Some(ref mut session) = &mut *self.session.lock().unwrap() { match session.uid_mv(&set, &dest_folder) { Ok(_) => { - res = DC_SUCCESS; + res = ImapResult::Success; true } Err(err) => { @@ -1239,41 +1244,38 @@ impl Imap { }; if copied { - if self.add_flag(context, uid, "\\Deleted") == 0 { + if !self.add_flag(context, uid, "\\Deleted") { warn!(context, 0, "Cannot mark message as \"Deleted\".",); } self.config.write().unwrap().selected_folder_needs_expunge = true; - res = DC_SUCCESS; + res = ImapResult::Success; } } } } - if res == DC_SUCCESS { + if res == ImapResult::Success { // TODO: is this correct? *dest_uid = uid; } - if res == DC_RETRY_LATER { - if self.should_reconnect() { - DC_RETRY_LATER - } else { - DC_FAILED - } - } else { - res + if res == ImapResult::RetryLater && !self.should_reconnect() { + res = ImapResult::Failed; } + res } - fn add_flag>(&self, context: &Context, server_uid: u32, flag: S) -> usize { + fn add_flag>(&self, context: &Context, server_uid: u32, flag: S) -> bool { if server_uid == 0 { - return 0; + return false; } if let Some(ref mut session) = &mut *self.session.lock().unwrap() { let set = format!("{}", server_uid); let query = format!("+FLAGS ({})", flag.as_ref()); match session.uid_store(&set, &query) { - Ok(_) => {} + Ok(_) => { + return true; + } Err(err) => { warn!( context, @@ -1282,22 +1284,20 @@ impl Imap { } } } - // All non-connection states are treated as success - the mail may // already be deleted or moved away on the server. - if self.should_reconnect() { - 0 + if !self.should_reconnect() { + true } else { - 1 + false } } - pub fn set_seen>(&self, context: &Context, folder: S, uid: u32) -> usize { - let mut res = DC_RETRY_LATER; - + pub fn set_seen>(&self, context: &Context, folder: S, uid: u32) -> ImapResult { if uid == 0 { - res = DC_FAILED - } else if self.is_connected() { + return ImapResult::Failed; + } + if self.is_connected() { info!( context, 0, @@ -1313,32 +1313,29 @@ impl Imap { "Cannot select folder {} for setting SEEN flag.", folder.as_ref(), ); - } else if self.add_flag(context, uid, "\\Seen") == 0 { + } else if !self.add_flag(context, uid, "\\Seen") { warn!(context, 0, "Cannot mark message as seen.",); } else { - res = DC_SUCCESS + return ImapResult::Success; } } - if res == DC_RETRY_LATER { - if self.should_reconnect() { - DC_RETRY_LATER - } else { - DC_FAILED - } + if self.should_reconnect() { + ImapResult::RetryLater } else { - res + ImapResult::Failed } } - pub fn set_mdnsent>(&self, context: &Context, folder: S, uid: u32) -> usize { - // returns 0=job should be retried later, 1=job done, 2=job done and flag just set - let mut res = DC_RETRY_LATER; + pub fn set_mdnsent>(&self, context: &Context, folder: S, uid: u32) -> ImapResult { let set = format!("{}", uid); if uid == 0 { - res = DC_FAILED; - } else if self.is_connected() { + return ImapResult::Failed; + } + + let mut res = ImapResult::RetryLater; + if self.is_connected() { info!( context, 0, @@ -1409,21 +1406,22 @@ impl Imap { .unwrap_or_else(|| false); res = if flag_set { - DC_ALREADY_DONE - } else if self.add_flag(context, uid, "$MDNSent") != 0 { - DC_SUCCESS + ImapResult::AlreadyDone + } else if self.add_flag(context, uid, "$MDNSent") { + ImapResult::Success } else { + assert!(res == ImapResult::RetryLater); res }; - if res == DC_SUCCESS { + if res == ImapResult::Success { info!(context, 0, "$MDNSent just set and MDN will be sent."); } else { info!(context, 0, "$MDNSent already set and MDN already sent."); } } } else { - res = DC_SUCCESS; + res = ImapResult::Success; info!( context, 0, "Cannot store $MDNSent flags, risk sending duplicate MDN.", @@ -1432,15 +1430,10 @@ impl Imap { } } - if res == DC_RETRY_LATER { - if self.should_reconnect() { - DC_RETRY_LATER - } else { - DC_FAILED - } - } else { - res + if res == ImapResult::RetryLater && !self.should_reconnect() { + res = ImapResult::Failed } + res } // only returns 0 on connection problems; we should try later again in this case * @@ -1450,11 +1443,11 @@ impl Imap { message_id: S1, folder: S2, server_uid: &mut u32, - ) -> usize { - let mut success = false; + ) -> ImapResult { if *server_uid == 0 { - success = true - } else { + return ImapResult::Success; + } + { info!( context, 0, @@ -1513,20 +1506,21 @@ impl Imap { } // mark the message for deletion - if self.add_flag(context, *server_uid, "\\Deleted") == 0 { + if !self.add_flag(context, *server_uid, "\\Deleted") { warn!(context, 0, "Cannot mark message as \"Deleted\"."); } else { self.config.write().unwrap().selected_folder_needs_expunge = true; - success = true + return ImapResult::Success; } } } - - if success { - 1 + // deletion was not successful or message was deleted already + return if self.is_connected() { + ImapResult::Failed } else { - self.is_connected() as usize - } + // we are not connected so the caller may retry + ImapResult::RetryLater + }; } pub fn configure_folders(&self, context: &Context, flags: libc::c_int) { diff --git a/src/job.rs b/src/job.rs index 76634aed7..a549c3496 100644 --- a/src/job.rs +++ b/src/job.rs @@ -1,5 +1,4 @@ use std::ffi::CStr; -use std::ptr; use std::time::Duration; use deltachat_derive::{FromSql, ToSql}; @@ -82,7 +81,7 @@ pub struct Job { pub added_timestamp: i64, pub tries: i32, pub param: Params, - pub try_again: i32, + pub try_again: Delay, pub pending_error: Option, } @@ -111,121 +110,91 @@ impl Job { #[allow(non_snake_case)] fn do_DC_JOB_SEND(&mut self, context: &Context) { - let ok_to_continue; - let mut filename = ptr::null_mut(); - let mut buf = ptr::null_mut(); - let mut buf_bytes = 0; - /* connect to SMTP server, if not yet done */ if !context.smtp.lock().unwrap().is_connected() { let loginparam = dc_loginparam_read(context, &context.sql, "configured_"); let connected = context.smtp.lock().unwrap().connect(context, &loginparam); if !connected { - self.try_again_later(3i32, None); - ok_to_continue = false; - } else { - ok_to_continue = true; + self.try_again_later(Delay::Standard, None); + return; } - } else { - ok_to_continue = true; } - if ok_to_continue { - let filename_s = self.param.get(Param::File).unwrap_or_default(); - filename = unsafe { filename_s.strdup() }; - if unsafe { strlen(filename) } == 0 { - warn!(context, 0, "Missing file name for job {}", self.job_id,); - } else if 0 != unsafe { dc_read_file(context, filename, &mut buf, &mut buf_bytes) } { - let recipients = self.param.get(Param::Recipients); - if recipients.is_none() { - warn!(context, 0, "Missing recipients for job {}", self.job_id,); - } else { - let recipients_list = recipients - .unwrap() - .split("\x1e") - .filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) { - Ok(addr) => Some(addr), - Err(err) => { - eprintln!("WARNING: invalid recipient: {} {:?}", addr, err); - None - } - }) - .collect::>(); - /* if there is a msg-id and it does not exist in the db, cancel sending. - this happends if dc_delete_msgs() was called - before the generated mime was sent out */ - let ok_to_continue1; - if 0 != self.foreign_id { - if 0 == unsafe { dc_msg_exists(context, self.foreign_id) } { - warn!( - context, - 0, - "Message {} for job {} does not exist", - self.foreign_id, - self.job_id, - ); - ok_to_continue1 = false; - } else { - ok_to_continue1 = true; - } - } else { - ok_to_continue1 = true; - } - if ok_to_continue1 { - /* send message */ - let body = unsafe { - std::slice::from_raw_parts(buf as *const u8, buf_bytes).to_vec() - }; + let filename = self.param.get(Param::File).unwrap_or_default(); + let body = match dc_read_file_safe(context, filename) { + Some(bytes) => bytes, + None => { + warn!(context, 0, "job {} error", self.job_id); + return; + } + }; - // hold the smtp lock during sending of a job and - // its ok/error response processing. Note that if a message - // was sent we need to mark it in the database as we - // otherwise might send it twice. - let mut sock = context.smtp.lock().unwrap(); - if 0 == sock.send(context, recipients_list, body) { - sock.disconnect(); - self.try_again_later(-1i32, Some(as_str(sock.error))); - } else { - dc_delete_file(context, filename_s); - if 0 != self.foreign_id { - dc_update_msg_state( - context, - self.foreign_id, - MessageState::OutDelivered, - ); - let chat_id: i32 = context - .sql - .query_row_col( - context, - "SELECT chat_id FROM msgs WHERE id=?", - params![self.foreign_id as i32], - 0, - ) - .unwrap_or_default(); - context.call_cb( - Event::MSG_DELIVERED, - chat_id as uintptr_t, - self.foreign_id as uintptr_t, - ); - } - } - } + let recipients = self.param.get(Param::Recipients); + if recipients.is_none() { + error!(context, 0, "Missing recipients for job {}", self.job_id,); + return; + } + let recipients_list = recipients + .unwrap() + .split("\x1e") + .filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) { + Ok(addr) => Some(addr), + Err(err) => { + eprintln!("WARNING: invalid recipient: {} {:?}", addr, err); + None } + }) + .collect::>(); + /* if there is a msg-id and it does not exist in the db, cancel sending. + this happends if dc_delete_msgs() was called + before the generated mime was sent out */ + if 0 != self.foreign_id { + if 0 == unsafe { dc_msg_exists(context, self.foreign_id) } { + warn!( + context, + 0, "Message {} for job {} does not exist", self.foreign_id, self.job_id, + ); + return; } } - unsafe { free(buf) }; - unsafe { free(filename.cast()) }; + /* send message while holding the smtp lock long enough + to also mark success in the database, to reduce chances + of a message getting sent twice. + */ + let mut sock = context.smtp.lock().unwrap(); + if 0 == sock.send(context, recipients_list, body) { + sock.disconnect(); + self.try_again_later(Delay::AtOnce, Some(as_str(sock.error))); + return; + } + dc_delete_file(context, filename); + if 0 != self.foreign_id { + dc_update_msg_state(context, self.foreign_id, MessageState::OutDelivered); + let chat_id: i32 = context + .sql + .query_row_col( + context, + "SELECT chat_id FROM msgs WHERE id=?", + params![self.foreign_id as i32], + 0, + ) + .unwrap_or_default(); + context.call_cb( + Event::MSG_DELIVERED, + chat_id as uintptr_t, + self.foreign_id as uintptr_t, + ); + } } // this value does not increase the number of tries - fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<&str>) { + fn try_again_later(&mut self, try_again: Delay, pending_error: Option<&str>) { self.try_again = try_again; self.pending_error = pending_error.map(|s| s.to_string()); } #[allow(non_snake_case)] fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) { - let ok_to_continue; let mut dest_uid = 0; let inbox = context.inbox.read().unwrap(); @@ -233,45 +202,38 @@ impl Job { if !inbox.is_connected() { connect_to_inbox(context, &inbox); if !inbox.is_connected() { - self.try_again_later(3, None); - ok_to_continue = false; - } else { - ok_to_continue = true; + self.try_again_later(Delay::Standard, None); + return; } - } else { - ok_to_continue = true; } - if ok_to_continue { - if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) { - if context - .sql - .get_config_int(context, "folders_configured") - .unwrap_or_default() - < 3 - { - inbox.configure_folders(context, 0x1i32); - } - let dest_folder = context.sql.get_config(context, "configured_mvbox_folder"); + if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) { + if context + .sql + .get_config_int(context, "folders_configured") + .unwrap_or_default() + < 3 + { + inbox.configure_folders(context, 0x1i32); + } + let dest_folder = context.sql.get_config(context, "configured_mvbox_folder"); - if let Some(dest_folder) = dest_folder { - let server_folder = msg.server_folder.as_ref().unwrap(); + if let Some(dest_folder) = dest_folder { + let server_folder = msg.server_folder.as_ref().unwrap(); - match inbox.mv( - context, - server_folder, - msg.server_uid, - &dest_folder, - &mut dest_uid, - ) as libc::c_uint - { - 1 => { - self.try_again_later(3i32, None); - } - 3 => { - dc_update_server_uid(context, msg.rfc724_mid, &dest_folder, dest_uid); - } - 0 | 2 | _ => {} + match inbox.mv( + context, + server_folder, + msg.server_uid, + &dest_folder, + &mut dest_uid, + ) { + ImapResult::RetryLater => { + self.try_again_later(Delay::Standard, None); } + ImapResult::Success => { + dc_update_server_uid(context, msg.rfc724_mid, &dest_folder, dest_uid); + } + _ => {} } } } @@ -279,53 +241,37 @@ impl Job { #[allow(non_snake_case)] fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) { - let mut delete_from_server = 1; let inbox = context.inbox.read().unwrap(); if let Ok(mut msg) = dc_msg_load_from_db(context, self.foreign_id) { if !(msg.rfc724_mid.is_null() || unsafe { *msg.rfc724_mid.offset(0isize) as libc::c_int == 0 }) { - let ok_to_continue1; /* eg. device messages have no Message-ID */ if dc_rfc724_mid_cnt(context, msg.rfc724_mid) != 1 { info!( context, 0, "The message is deleted from the server when all parts are deleted.", ); - delete_from_server = 0i32 + return; } /* if this is the last existing part of the message, we delete the message from the server */ - if 0 != delete_from_server { - let ok_to_continue; + if !inbox.is_connected() { + connect_to_inbox(context, &inbox); if !inbox.is_connected() { - connect_to_inbox(context, &inbox); - if !inbox.is_connected() { - self.try_again_later(3i32, None); - ok_to_continue = false; - } else { - ok_to_continue = true; - } - } else { - ok_to_continue = true; + self.try_again_later(Delay::Standard, None); + return; } - if ok_to_continue { - let mid = unsafe { CStr::from_ptr(msg.rfc724_mid).to_str().unwrap() }; - let server_folder = msg.server_folder.as_ref().unwrap(); - if 0 == inbox.delete_msg(context, mid, server_folder, &mut msg.server_uid) { - self.try_again_later(-1i32, None); - ok_to_continue1 = false; - } else { - ok_to_continue1 = true; - } - } else { - ok_to_continue1 = false; - } - } else { - ok_to_continue1 = true; } - if ok_to_continue1 { - dc_delete_msg_from_db(context, msg.id); + let mid = unsafe { CStr::from_ptr(msg.rfc724_mid).to_str().unwrap() }; + let server_folder = msg.server_folder.as_ref().unwrap(); + match inbox.delete_msg(context, mid, server_folder, &mut msg.server_uid) { + ImapResult::RetryLater => { + self.try_again_later(Delay::AtOnce, None); + } + _ => { + dc_delete_msg_from_db(context, msg.id); + } } } } @@ -333,57 +279,51 @@ impl Job { #[allow(non_snake_case)] fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) { - let ok_to_continue; let inbox = context.inbox.read().unwrap(); if !inbox.is_connected() { connect_to_inbox(context, &inbox); if !inbox.is_connected() { - self.try_again_later(3i32, None); - ok_to_continue = false; - } else { - ok_to_continue = true; + self.try_again_later(Delay::Standard, None); + return; } - } else { - ok_to_continue = true; } - if ok_to_continue { - if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) { - let server_folder = msg.server_folder.as_ref().unwrap(); - match inbox.set_seen(context, server_folder, msg.server_uid) as libc::c_uint { - 0 => {} - 1 => { - self.try_again_later(3i32, None); - } - _ => { - if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default() - && 0 != context - .sql - .get_config_int(context, "mdns_enabled") - .unwrap_or_else(|| 1) - { - let folder = msg.server_folder.as_ref().unwrap(); - - match inbox.set_mdnsent(context, folder, msg.server_uid) as libc::c_uint - { - 1 => { - self.try_again_later(3i32, None); - } - 3 => { - send_mdn(context, msg.id); - } - 0 | 2 | _ => {} - } - } - } + if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) { + let server_folder = msg.server_folder.as_ref().unwrap(); + match inbox.set_seen(context, server_folder, msg.server_uid) { + ImapResult::Failed => { + return; } + ImapResult::RetryLater => { + self.try_again_later(Delay::Standard, None); + return; + } + _ => {} + }; + if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default() + && 0 != context + .sql + .get_config_int(context, "mdns_enabled") + .unwrap_or_else(|| 1) + { + let folder = msg.server_folder.as_ref().unwrap(); + + match inbox.set_mdnsent(context, folder, msg.server_uid) { + ImapResult::RetryLater => { + self.try_again_later(Delay::Standard, None); + } + ImapResult::Success => { + send_mdn(context, msg.id); + } + ImapResult::AlreadyDone => {} + ImapResult::Failed => {} + }; } } } #[allow(non_snake_case)] fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) { - let ok_to_continue; let folder = self .param .get(Param::ServerFolder) @@ -396,34 +336,37 @@ impl Job { if !inbox.is_connected() { connect_to_inbox(context, &inbox); if !inbox.is_connected() { - self.try_again_later(3, None); - ok_to_continue = false; - } else { - ok_to_continue = true; + self.try_again_later(Delay::Standard, None); + return; } - } else { - ok_to_continue = true; } - if ok_to_continue { - if inbox.set_seen(context, &folder, uid) == 0 { - self.try_again_later(3i32, None); + + match inbox.set_seen(context, &folder, uid) { + ImapResult::RetryLater => { + self.try_again_later(Delay::Standard, None); + return; } - if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() { - if context - .sql - .get_config_int(context, "folders_configured") - .unwrap_or_default() - < 3 - { - inbox.configure_folders(context, 0x1i32); - } - let dest_folder = context.sql.get_config(context, "configured_mvbox_folder"); - if let Some(dest_folder) = dest_folder { - if 1 == inbox.mv(context, folder, uid, dest_folder, &mut dest_uid) - as libc::c_uint - { - self.try_again_later(3, None); + ImapResult::Failed => { + return; + } + _ => {} + }; + if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() { + if context + .sql + .get_config_int(context, "folders_configured") + .unwrap_or_default() + < 3 + { + inbox.configure_folders(context, 0x1i32); + } + let dest_folder = context.sql.get_config(context, "configured_mvbox_folder"); + if let Some(dest_folder) = dest_folder { + match inbox.mv(context, folder, uid, dest_folder, &mut dest_uid) { + ImapResult::RetryLater => { + self.try_again_later(Delay::Standard, None); } + _ => {} } } } @@ -811,7 +754,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { added_timestamp: row.get(4)?, tries: row.get(6)?, param: row.get::<_, String>(3)?.parse().unwrap_or_default(), - try_again: 0, + try_again: Delay::DoNotTryAgain, pending_error: None, }; @@ -843,7 +786,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { // some configuration jobs are "exclusive": // - they are always executed in the imap-thread and the smtp-thread is suspended during execution // - they may change the database handle change the database handle; we do not keep old pointers therefore - // - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution + // - they can be re-executed one time AtOnce, but they are not save in the database for later execution if Action::ConfigureImap == job.action || Action::ImexImap == job.action { job_kill_action(context, job.action); &context @@ -864,7 +807,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { let mut tries = 0; while tries <= 1 { // this can be modified by a job using dc_job_try_again_later() - job.try_again = 0; + job.try_again = Delay::DoNotTryAgain; match job.action { Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context), @@ -885,7 +828,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { Action::SendMdnOld => {} Action::SendMsgToSmtpOld => {} } - if job.try_again != -1 { + if job.try_again != Delay::AtOnce { break; } tries += 1 @@ -905,7 +848,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { .unsuspend(context); suspend_smtp_thread(context, false); break; - } else if job.try_again == 2 { + } else if job.try_again == Delay::IncreationPoll { // just try over next loop unconditionally, the ui typically interrupts idle when the file (video) is ready info!( context, @@ -918,7 +861,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { }, job.job_id ); - } else if job.try_again == -1 || job.try_again == 3 { + } else if job.try_again == Delay::AtOnce || job.try_again == Delay::Standard { let tries = job.tries + 1; if tries < 17 { job.tries = tries;