mirror of
https://github.com/chatmail/core.git
synced 2026-05-20 07:16:31 +03:00
imap: batch message deletion
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
### Changes
|
### Changes
|
||||||
- refactorings #3026
|
- refactorings #3026
|
||||||
- move messages in batches #3058
|
- move messages in batches #3058
|
||||||
|
- delete messages in batches #3060
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- avoid archived, fresh chats #3053
|
- avoid archived, fresh chats #3053
|
||||||
|
|||||||
@@ -2500,8 +2500,7 @@ class TestOnlineAccount:
|
|||||||
lp.sec("ac2: deleting all messages except third")
|
lp.sec("ac2: deleting all messages except third")
|
||||||
assert len(to_delete) == len(texts) - 1
|
assert len(to_delete) == len(texts) - 1
|
||||||
ac2.delete_messages(to_delete)
|
ac2.delete_messages(to_delete)
|
||||||
for msg in to_delete:
|
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||||
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
|
||||||
|
|
||||||
ac2._evtracker.get_info_contains("close/expunge succeeded")
|
ac2._evtracker.get_info_contains("close/expunge succeeded")
|
||||||
|
|
||||||
|
|||||||
143
src/imap.rs
143
src/imap.rs
@@ -458,12 +458,9 @@ impl Imap {
|
|||||||
.await
|
.await
|
||||||
.context("fetch_new_messages")?;
|
.context("fetch_new_messages")?;
|
||||||
|
|
||||||
self.move_messages(context, watch_folder)
|
self.move_delete_messages(context, watch_folder)
|
||||||
.await
|
.await
|
||||||
.context("move_messages")?;
|
.context("move_delete_messages")?;
|
||||||
self.delete_messages(context, watch_folder)
|
|
||||||
.await
|
|
||||||
.context("delete_messages")?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -827,6 +824,37 @@ impl Imap {
|
|||||||
Ok(read_cnt > 0)
|
Ok(read_cnt > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes batch of messages identified by their UID from the currently
|
||||||
|
/// selected folder.
|
||||||
|
async fn delete_message_batch(
|
||||||
|
&mut self,
|
||||||
|
context: &Context,
|
||||||
|
uid_set: &str,
|
||||||
|
row_ids: Vec<i64>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// mark the message for deletion
|
||||||
|
self.add_flag_finalized_with_set(uid_set, "\\Deleted")
|
||||||
|
.await?;
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
format!(
|
||||||
|
"DELETE FROM imap WHERE id IN ({})",
|
||||||
|
row_ids.iter().map(|_| "?").collect::<Vec<&str>>().join(",")
|
||||||
|
),
|
||||||
|
rusqlite::params_from_iter(row_ids),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("cannot remove deleted messages from imap table")?;
|
||||||
|
|
||||||
|
context.emit_event(EventType::ImapMessageDeleted(format!(
|
||||||
|
"IMAP messages {} marked as deleted",
|
||||||
|
uid_set
|
||||||
|
)));
|
||||||
|
self.config.selected_folder_needs_expunge = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Moves batch of messages identified by their UID from the currently
|
/// Moves batch of messages identified by their UID from the currently
|
||||||
/// selected folder to the target folder.
|
/// selected folder to the target folder.
|
||||||
async fn move_message_batch(
|
async fn move_message_batch(
|
||||||
@@ -907,17 +935,16 @@ impl Imap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves messages.
|
/// Moves and deletes messages as planned in the `imap` table.
|
||||||
///
|
///
|
||||||
/// This is the only place where messages are moved on the IMAP server.
|
/// This is the only place where messages are moved or deleted on the IMAP server.
|
||||||
async fn move_messages(&mut self, context: &Context, folder: &str) -> Result<()> {
|
async fn move_delete_messages(&mut self, context: &Context, folder: &str) -> Result<()> {
|
||||||
let mut rows = context
|
let mut rows = context
|
||||||
.sql
|
.sql
|
||||||
.query_map(
|
.query_map(
|
||||||
"SELECT id, uid, target FROM imap
|
"SELECT id, uid, target FROM imap
|
||||||
WHERE folder = ?
|
WHERE folder = ?
|
||||||
AND target != folder
|
AND target != folder
|
||||||
AND target != '' -- Not planned for deletion.
|
|
||||||
ORDER BY target, uid",
|
ORDER BY target, uid",
|
||||||
paramsv![folder],
|
paramsv![folder],
|
||||||
|row| {
|
|row| {
|
||||||
@@ -970,57 +997,20 @@ impl Imap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute request.
|
// Empty target folder name means messages should be deleted.
|
||||||
self.move_message_batch(context, &uid_set, rowid_set, &target)
|
if target.is_empty() {
|
||||||
.await
|
self.delete_message_batch(context, &uid_set, rowid_set)
|
||||||
.with_context(|| {
|
.await
|
||||||
format!(
|
.with_context(|| format!("cannot delete batch of messages {:?}", &uid_set))?;
|
||||||
"cannot move batch of messages {:?} to folder {:?}",
|
} else {
|
||||||
&uid_set, target
|
self.move_message_batch(context, &uid_set, rowid_set, &target)
|
||||||
)
|
.await
|
||||||
})?;
|
.with_context(|| {
|
||||||
}
|
format!(
|
||||||
|
"cannot move batch of messages {:?} to folder {:?}",
|
||||||
Ok(())
|
&uid_set, target
|
||||||
}
|
)
|
||||||
|
})?;
|
||||||
/// Deletes messages that are marked as planned for deletion in `imap` table.
|
|
||||||
///
|
|
||||||
/// This is the only place where messages are deleted from the IMAP server.
|
|
||||||
async fn delete_messages(&mut self, context: &Context, folder: &str) -> Result<()> {
|
|
||||||
let rows = context
|
|
||||||
.sql
|
|
||||||
.query_map(
|
|
||||||
"SELECT id, uid FROM imap
|
|
||||||
WHERE folder=? AND target=''
|
|
||||||
ORDER BY uid ASC
|
|
||||||
LIMIT 50", // Do not try to delete too many messages at once.
|
|
||||||
paramsv![folder],
|
|
||||||
|row| {
|
|
||||||
let rowid: i64 = row.get(0)?;
|
|
||||||
let uid: u32 = row.get(1)?;
|
|
||||||
Ok((rowid, uid))
|
|
||||||
},
|
|
||||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if rows.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rowid, uid) in rows {
|
|
||||||
match self.delete_msg(context, folder, uid).await {
|
|
||||||
ImapActionResult::Failed | ImapActionResult::RetryLater => {
|
|
||||||
warn!(context, "Deletion of message {}/{} failed", folder, uid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ImapActionResult::Success => {
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.execute("DELETE FROM imap WHERE id=?", paramsv![rowid])
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1449,39 +1439,6 @@ impl Imap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_msg(
|
|
||||||
&mut self,
|
|
||||||
context: &Context,
|
|
||||||
folder: &str,
|
|
||||||
uid: u32,
|
|
||||||
) -> ImapActionResult {
|
|
||||||
if let Some(imapresult) = self
|
|
||||||
.prepare_imap_operation_on_msg(context, folder, uid)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
return imapresult;
|
|
||||||
}
|
|
||||||
// we are connected, and the folder is selected
|
|
||||||
|
|
||||||
let display_imap_id = format!("{}/{}", folder, uid);
|
|
||||||
|
|
||||||
// mark the message for deletion
|
|
||||||
if let Err(err) = self.add_flag_finalized(uid, "\\Deleted").await {
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"Cannot mark message {} as \"Deleted\": {}.", display_imap_id, err
|
|
||||||
);
|
|
||||||
ImapActionResult::RetryLater
|
|
||||||
} else {
|
|
||||||
context.emit_event(EventType::ImapMessageDeleted(format!(
|
|
||||||
"IMAP Message {} marked as deleted",
|
|
||||||
display_imap_id
|
|
||||||
)));
|
|
||||||
self.config.selected_folder_needs_expunge = true;
|
|
||||||
ImapActionResult::Success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn ensure_configured_folders(
|
pub async fn ensure_configured_folders(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
|
|||||||
Reference in New Issue
Block a user