diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index acddf4d80..0b4d993b6 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1513,6 +1513,16 @@ char* dc_get_mime_headers (dc_context_t* context, uint32_t ms */ void dc_delete_msgs (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt); +/** + * Empty IMAP server folder: delete all messages. + * + * @memberof dc_context_t + * @param context The context object as created by dc_context_new() + * @param flags uint32_t with DC_EMPTY_* flags + * @return None. + */ +void dc_empty_server (dc_context_t* context, const uint32_t flags); + /** * Forward messages to another chat. @@ -3929,6 +3939,30 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * @{ */ +/** + * @defgroup DC_EMPTY + * + * These constants configure emptying imap folders. + * + * @addtogroup DC_EMPTY + * @{ + */ + +/** + * Clear all mvbox messages. + */ +#define DC_EMPTY_MVBOX 0x01 + +/** + * Clear all INBOX messages. + */ +#define DC_EMPTY_INBOX 0x02 + +/** + * @} + */ + + /** * The library-user may write an informational string to the log. @@ -3995,6 +4029,16 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); */ #define DC_EVENT_IMAP_MESSAGE_MOVED 105 +/** + * Emitted when an IMAP folder was emptied. + * + * @param data1 0 + * @param data2 (const char*) folder name. + * Must not be unref'd or modified and is valid only until the callback returns. + * @return 0 + */ +#define DC_EVENT_IMAP_FOLDER_EMPTIED 106 + /** * Emitted when a new blob file was successfully written * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index d572e466b..6f0c33b75 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -130,6 +130,7 @@ impl ContextWrapper { | Event::SmtpMessageSent(msg) | Event::ImapMessageDeleted(msg) | Event::ImapMessageMoved(msg) + | Event::ImapFolderEmptied(msg) | Event::NewBlobFile(msg) | Event::DeletedBlobFile(msg) | Event::Warning(msg) @@ -1286,6 +1287,18 @@ pub unsafe extern "C" fn dc_delete_msgs( .unwrap_or(()) } +#[no_mangle] +pub unsafe extern "C" fn dc_empty_server(context: *mut dc_context_t, flags: u32) { + if context.is_null() || flags == 0 { + eprintln!("ignoring careless call to dc_empty_server()"); + return; + } + let ffi_context = &*context; + ffi_context + .with_inner(|ctx| message::dc_empty_server(ctx, flags)) + .unwrap_or(()) +} + #[no_mangle] pub unsafe extern "C" fn dc_forward_msgs( context: *mut dc_context_t, diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 527c1ab24..fd5bc1a21 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -404,6 +404,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E checkqr \n\ event \n\ fileinfo \n\ + emptyserver (1=MVBOX 2=INBOX)\n\ clear -- clear screen\n\ exit or quit\n\ =============================================" @@ -976,6 +977,11 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E bail!("Command failed."); } } + "emptyserver" => { + ensure!(!arg1.is_empty(), "Argument missing"); + + message::dc_empty_server(context, arg1.parse()?); + } "" => (), _ => bail!("Unknown command: \"{}\" type ? for help.", arg0), } diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index b34eeaa8e..9af639891 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -135,6 +135,17 @@ class Account(object): if not self.is_configured(): raise ValueError("need to configure first") + def empty_server_folders(self, inbox=False, mvbox=False): + """ empty server folders. """ + flags = 0 + if inbox: + flags |= const.DC_EMPTY_INBOX + if mvbox: + flags |= const.DC_EMPTY_MVBOX + if not flags: + raise ValueError("no flags set") + lib.dc_empty_server(self._dc_context, flags) + def get_infostring(self): """ return info of the configured account. """ self.check_is_configured() diff --git a/python/src/deltachat/const.py b/python/src/deltachat/const.py index 09827ff03..ecfd5a86b 100644 --- a/python/src/deltachat/const.py +++ b/python/src/deltachat/const.py @@ -69,12 +69,15 @@ DC_CERTCK_AUTO = 0 DC_CERTCK_STRICT = 1 DC_CERTCK_ACCEPT_INVALID_HOSTNAMES = 2 DC_CERTCK_ACCEPT_INVALID_CERTIFICATES = 3 +DC_EMPTY_MVBOX = 0x01 +DC_EMPTY_INBOX = 0x02 DC_EVENT_INFO = 100 DC_EVENT_SMTP_CONNECTED = 101 DC_EVENT_IMAP_CONNECTED = 102 DC_EVENT_SMTP_MESSAGE_SENT = 103 DC_EVENT_IMAP_MESSAGE_DELETED = 104 DC_EVENT_IMAP_MESSAGE_MOVED = 105 +DC_EVENT_IMAP_FOLDER_EMPTIED = 106 DC_EVENT_NEW_BLOB_FILE = 150 DC_EVENT_DELETED_BLOB_FILE = 151 DC_EVENT_WARNING = 300 @@ -151,7 +154,7 @@ DC_STR_COUNT = 67 def read_event_defines(f): - rex = re.compile(r'#define\s+((?:DC_EVENT|DC_QR|DC_MSG|DC_LP|DC_CERTCK|DC_STATE|DC_STR|' + rex = re.compile(r'#define\s+((?:DC_EVENT|DC_QR|DC_MSG|DC_LP|DC_EMPTY|DC_CERTCK|DC_STATE|DC_STR|' r'DC_CONTACT_ID|DC_GCL|DC_CHAT|DC_PROVIDER)_\S+)\s+([x\d]+).*') for line in f: m = rex.match(line) diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 1a14833cd..f0fec7c7c 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -180,6 +180,12 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig): ac.start_threads(mvbox=mvbox, sentbox=sentbox) return ac + def get_one_online_account(self): + ac1 = self.get_online_configuring_account() + wait_successful_IMAP_SMTP_connection(ac1) + wait_configuration_progress(ac1, 1000) + return ac1 + def get_two_online_accounts(self): ac1 = self.get_online_configuring_account() ac2 = self.get_online_configuring_account() diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 383f7f37e..545ba04ab 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -493,6 +493,18 @@ class TestOnlineAccount: assert msg_in.text == "message2" assert msg_in.is_forwarded() + def test_send_self_message_and_empty_folder(self, acfactory, lp): + ac1 = acfactory.get_one_online_account() + lp.sec("ac1: create self chat") + chat = ac1.create_chat_by_contact(ac1.get_self_contact()) + chat.send_text("hello") + ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT") + ac1.empty_server_folders(inbox=True, mvbox=True) + ev = ac1._evlogger.get_matching("DC_EVENT_IMAP_FOLDER_EMPTIED") + assert ev[2] == "DeltaChat" + ev = ac1._evlogger.get_matching("DC_EVENT_IMAP_FOLDER_EMPTIED") + assert ev[2] == "INBOX" + def test_send_and_receive_message_markseen(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() diff --git a/src/events.rs b/src/events.rs index f74b886ef..e447101f3 100644 --- a/src/events.rs +++ b/src/events.rs @@ -54,6 +54,12 @@ pub enum Event { #[strum(props(id = "105"))] ImapMessageMoved(String), + /// Emitted when an IMAP folder was emptied + /// + /// @return 0 + #[strum(props(id = "106"))] + ImapFolderEmptied(String), + /// Emitted when an new file in the $BLOBDIR was created /// /// @return 0 diff --git a/src/imap.rs b/src/imap.rs index de7880a53..9eb572616 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -651,7 +651,8 @@ impl Imap { } // deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then) - if self.config.read().unwrap().selected_folder_needs_expunge { + let needs_expunge = { self.config.read().unwrap().selected_folder_needs_expunge }; + if needs_expunge { if let Some(ref folder) = self.config.read().unwrap().selected_folder { info!(context, "Expunge messages in \"{}\".", folder); @@ -659,16 +660,19 @@ impl Imap { // https://tools.ietf.org/html/rfc3501#section-6.4.2 if let Some(ref mut session) = &mut *self.session.lock().unwrap() { match session.close() { - Ok(_) => {} + Ok(_) => { + info!(context, "close/expunge succeeded"); + } Err(err) => { warn!(context, "failed to close session: {:?}", err); + return 0; } } } else { return 0; } - self.config.write().unwrap().selected_folder_needs_expunge = true; } + self.config.write().unwrap().selected_folder_needs_expunge = false; } // select new folder @@ -1510,10 +1514,10 @@ impl Imap { if self.select_folder::(context, None) == 0 { warn!( context, - "could not perform expunge on empty folder {}", folder + "could not perform expunge on empty-marked folder {}", folder ); } else { - info!(context, "Emptying folder '{}' done.", folder); + emit_event!(context, Event::ImapFolderEmptied(folder.to_string())); } } } diff --git a/src/message.rs b/src/message.rs index 3e2ea253d..d7d6d8756 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1276,6 +1276,12 @@ pub fn update_server_uid( } } +#[allow(dead_code)] +pub fn dc_empty_server(context: &Context, flags: u32) { + job_kill_action(context, Action::EmptyServer); + job_add(context, Action::EmptyServer, flags as i32, Params::new(), 0); +} + #[cfg(test)] mod tests { use super::*;