mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-30 23:55:59 +00:00
fix: diff UIDs against ProcessedMail before fetching message bodies
This commit is contained in:
@@ -680,9 +680,38 @@ class MailAccountHandler(LoggingMixin):
|
||||
f"Rule {rule}: Searching folder with criteria {criterias}",
|
||||
)
|
||||
|
||||
try:
|
||||
all_uids = set(
|
||||
M.uids(criteria=criterias, charset=rule.account.character_set),
|
||||
)
|
||||
except Exception as err:
|
||||
raise MailError(
|
||||
f"Rule {rule}: Error while searching folder {rule.folder}",
|
||||
) from err
|
||||
|
||||
processed_uids_qs = ProcessedMail.objects.filter(
|
||||
rule=rule,
|
||||
folder=rule.folder,
|
||||
uid__in=all_uids,
|
||||
)
|
||||
if self._current_uid_validity is not None:
|
||||
processed_uids_qs = processed_uids_qs.filter(
|
||||
Q(uid_validity=self._current_uid_validity)
|
||||
| Q(uid_validity__isnull=True),
|
||||
)
|
||||
processed_uids = set(processed_uids_qs.values_list("uid", flat=True))
|
||||
|
||||
new_uids = all_uids - processed_uids
|
||||
|
||||
if not new_uids:
|
||||
self.log.debug(
|
||||
f"Rule {rule}: No new mail matching criteria {criterias}",
|
||||
)
|
||||
return 0
|
||||
|
||||
try:
|
||||
messages = M.fetch(
|
||||
criteria=criterias,
|
||||
criteria=AND(uid=list(new_uids)),
|
||||
mark_seen=False,
|
||||
charset=rule.account.character_set,
|
||||
bulk=True,
|
||||
|
||||
@@ -964,6 +964,62 @@ class TestMail(
|
||||
]
|
||||
self.assertEqual(queued_rule.id, first_rule.id)
|
||||
|
||||
def test_handle_mail_account_skips_body_fetch_for_already_processed_mail(
|
||||
self,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- An attachment-less mail under an attachments-only mark-read rule,
|
||||
already recorded as PROCESSED_WO_CONSUMPTION
|
||||
WHEN:
|
||||
- The mail account is processed again and the mail still matches the
|
||||
search criteria (it was never marked read, since no mail action is
|
||||
applied for the no-consumption case)
|
||||
THEN:
|
||||
- No IMAP body fetch happens for that mail; only the cheap UID search runs.
|
||||
"""
|
||||
account = MailAccount.objects.create(
|
||||
name="test",
|
||||
imap_server="",
|
||||
username="admin",
|
||||
password="secret",
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.MailAction.MARK_READ,
|
||||
consumption_scope=MailRule.ConsumptionScope.ATTACHMENTS_ONLY,
|
||||
)
|
||||
|
||||
message = self.mailMocker.messageBuilder.create_message(
|
||||
subject="No attachment",
|
||||
attachments=[],
|
||||
)
|
||||
self.mailMocker.bogus_mailbox.messages = [message]
|
||||
self.mailMocker.bogus_mailbox.updateClient()
|
||||
|
||||
# First run: records ProcessedMail without consuming anything.
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
self.assertTrue(
|
||||
ProcessedMail.objects.filter(
|
||||
rule=rule,
|
||||
uid=message.uid,
|
||||
folder=rule.folder,
|
||||
).exists(),
|
||||
)
|
||||
self.mailMocker._queue_consumption_tasks_mock.assert_not_called()
|
||||
|
||||
# Second run: message still matches UNSEEN (mark-read action never ran),
|
||||
# but its body must not be downloaded again.
|
||||
with mock.patch.object(
|
||||
self.mailMocker.bogus_mailbox,
|
||||
"fetch",
|
||||
wraps=self.mailMocker.bogus_mailbox.fetch,
|
||||
) as fetch_spy:
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
fetch_spy.assert_not_called()
|
||||
|
||||
def test_handle_mail_account_skip_duplicate_uids_from_fetch(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
|
||||
Reference in New Issue
Block a user