Skip to content

Commit b4c85c5

Browse files
Alexander Krotovlink2xt
Alexander Krotov
authored and
link2xt
committed
Add a job to resync folder UIDs
1 parent 763334d commit b4c85c5

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

src/imap/mod.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ const PREFETCH_FLAGS: &str = "(UID BODY.PEEK[HEADER.FIELDS (\
108108
AUTOCRYPT-SETUP-MESSAGE\
109109
)])";
110110
const DELETE_CHECK_FLAGS: &str = "(UID BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)])";
111+
const RFC724MID_UID: &str = "(UID BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)])";
111112
const JUST_UID: &str = "(UID)";
112113
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
113114
const SELECT_ALL: &str = "1:*";
@@ -517,6 +518,84 @@ impl Imap {
517518
}
518519
}
519520

521+
/// Synchronizes UIDs in the database with UIDs on the server.
522+
///
523+
/// It is assumed that no operations are taking place on the same
524+
/// folder at the moment. Make sure to run it in the same
525+
/// thread/task as other network operations on this folder to
526+
/// avoid race conditions.
527+
pub(crate) async fn resync_folder_uids(
528+
&mut self,
529+
context: &Context,
530+
folder: String,
531+
) -> Result<()> {
532+
// Collect pairs of UID and Message-ID.
533+
let mut msg_ids = BTreeMap::new();
534+
535+
self.select_folder(context, Some(&folder)).await?;
536+
537+
let session = if let Some(ref mut session) = &mut self.session {
538+
session
539+
} else {
540+
return Err(Error::NoConnection);
541+
};
542+
543+
match session.uid_fetch("1:*", RFC724MID_UID).await {
544+
Ok(mut list) => {
545+
while let Some(fetch) = list.next().await {
546+
let msg = fetch.map_err(|err| Error::Other(err.to_string()))?;
547+
548+
// Get Message-ID
549+
let message_id = get_fetch_headers(&msg)
550+
.and_then(|headers| prefetch_get_message_id(&headers))
551+
.ok();
552+
553+
if let (Some(uid), Some(rfc724_mid)) = (msg.uid, message_id) {
554+
msg_ids.insert(uid, rfc724_mid);
555+
}
556+
}
557+
}
558+
Err(err) => {
559+
return Err(Error::Other(format!(
560+
"Can't resync folder {}: {}",
561+
folder, err
562+
)))
563+
}
564+
}
565+
566+
info!(
567+
context,
568+
"Resync: collected {} message IDs in folder {}",
569+
msg_ids.len(),
570+
&folder
571+
);
572+
573+
// Write collected UIDs to SQLite database.
574+
context
575+
.sql
576+
.with_conn(move |mut conn| {
577+
let conn2 = &mut conn;
578+
let tx = conn2.transaction()?;
579+
tx.execute(
580+
"UPDATE msgs SET server_uid=0 WHERE server_folder=?",
581+
params![folder],
582+
)?;
583+
for (uid, rfc724_mid) in &msg_ids {
584+
// This may detect previously undetected moved
585+
// messages, so we update server_folder too.
586+
tx.execute(
587+
"UPDATE msgs \
588+
SET server_folder=?,server_uid=? WHERE rfc724_mid=?",
589+
params![folder, uid, rfc724_mid],
590+
)?;
591+
}
592+
tx.commit()?;
593+
Ok(())
594+
})
595+
.await?;
596+
Ok(())
597+
}
598+
520599
/// return Result with (uid_validity, last_seen_uid) tuple.
521600
pub(crate) async fn select_with_uidvalidity(
522601
&mut self,

src/job.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ pub enum Action {
102102
MoveMsg = 200,
103103
DeleteMsgOnImap = 210,
104104

105+
// UID synchronization is high-priority to make sure correct UIDs
106+
// are used by message moving/deletion.
107+
ResyncFolders = 300,
108+
105109
// Jobs in the SMTP-thread, range from DC_SMTP_THREAD..DC_SMTP_THREAD+999
106110
MaybeSendLocations = 5005, // low priority ...
107111
MaybeSendLocationsEnded = 5007,
@@ -124,6 +128,7 @@ impl From<Action> for Thread {
124128

125129
Housekeeping => Thread::Imap,
126130
DeleteMsgOnImap => Thread::Imap,
131+
ResyncFolders => Thread::Imap,
127132
EmptyServer => Thread::Imap,
128133
MarkseenMsgOnImap => Thread::Imap,
129134
MoveMsg => Thread::Imap,
@@ -619,6 +624,44 @@ impl Job {
619624
}
620625
}
621626

627+
/// Synchronizes UIDs for sentbox, inbox and mvbox, in this order.
628+
///
629+
/// If a copy of the message is present in multiple folders, mvbox
630+
/// is preferred to inbox, which is in turn preferred to
631+
/// sentbox. This is because in the database it is impossible to
632+
/// store multiple UIDs for one message, so we prefer to
633+
/// automatically delete messages in the folders managed by Delta
634+
/// Chat in contrast to the Sent folder, which is normally managed
635+
/// by the user via webmail or another email client.
636+
async fn resync_folders(&mut self, context: &Context, imap: &mut Imap) -> Status {
637+
if let Err(err) = imap.connect_configured(context).await {
638+
warn!(context, "could not connect: {:?}", err);
639+
return Status::RetryLater;
640+
}
641+
642+
if let Some(sentbox_folder) = &context.get_config(Config::ConfiguredSentboxFolder).await {
643+
job_try!(
644+
imap.resync_folder_uids(context, sentbox_folder.to_string())
645+
.await
646+
);
647+
}
648+
649+
if let Some(inbox_folder) = &context.get_config(Config::ConfiguredInboxFolder).await {
650+
job_try!(
651+
imap.resync_folder_uids(context, inbox_folder.to_string())
652+
.await
653+
);
654+
}
655+
656+
if let Some(mvbox_folder) = &context.get_config(Config::ConfiguredMvboxFolder).await {
657+
job_try!(
658+
imap.resync_folder_uids(context, mvbox_folder.to_string())
659+
.await
660+
);
661+
}
662+
Status::Finished(Ok(()))
663+
}
664+
622665
async fn empty_server(&mut self, context: &Context, imap: &mut Imap) -> Status {
623666
if let Err(err) = imap.connect_configured(context).await {
624667
warn!(context, "could not connect: {:?}", err);
@@ -986,6 +1029,7 @@ async fn perform_job_action(
9861029
}
9871030
Action::EmptyServer => job.empty_server(context, connection.inbox()).await,
9881031
Action::DeleteMsgOnImap => job.delete_msg_on_imap(context, connection.inbox()).await,
1032+
Action::ResyncFolders => job.resync_folders(context, connection.inbox()).await,
9891033
Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context, connection.inbox()).await,
9901034
Action::MoveMsg => job.move_msg(context, connection.inbox()).await,
9911035
Action::Housekeeping => {
@@ -1043,6 +1087,7 @@ pub async fn add(context: &Context, job: Job) {
10431087
Action::Housekeeping
10441088
| Action::EmptyServer
10451089
| Action::DeleteMsgOnImap
1090+
| Action::ResyncFolders
10461091
| Action::MarkseenMsgOnImap
10471092
| Action::MoveMsg => {
10481093
info!(context, "interrupt: imap");

0 commit comments

Comments
 (0)