Handle non-tuple IMAP fetch responses
The prompts used to arrive at this change since the previous commit Ik krijg de volgende foutmelding: AttributeError: 'int' object has no attribute 'decode' while running verplaats_bestaand.py --account backup --uitvoeren. Any special observations that may be relevant for version management for this version. Be brief. The actual backup run modified local verplaats_log.json with processed INBOX UID entries; that run-state was intentionally not committed. Rerunning should preserve it so already logged moves are skipped.
This commit is contained in:
@@ -80,6 +80,7 @@ Scripts using shared IMAP folder handling:
|
|||||||
- `--audit` is available only in dry-run mode. It suppresses per-message dry-run lines and prints planned move counts grouped by source folder and destination folder for step 2 and step 3.
|
- `--audit` is available only in dry-run mode. It suppresses per-message dry-run lines and prints planned move counts grouped by source folder and destination folder for step 2 and step 3.
|
||||||
- Long scans show progress counters: source folders are printed as `[current/total]`, and audit mode prints `Gescand: processed/total` every 250 messages and at folder completion.
|
- Long scans show progress counters: source folders are printed as `[current/total]`, and audit mode prints `Gescand: processed/total` every 250 messages and at folder completion.
|
||||||
- IMAP `LIST`, `SELECT`, `UID SEARCH`, and `UID FETCH` aborts are logged and retried once after reconnecting. If reconnect also fails, the script records an `ABORT`/`ERROR` in `verplaats_log.json` instead of printing a Python traceback.
|
- IMAP `LIST`, `SELECT`, `UID SEARCH`, and `UID FETCH` aborts are logged and retried once after reconnecting. If reconnect also fails, the script records an `ABORT`/`ERROR` in `verplaats_log.json` instead of printing a Python traceback.
|
||||||
|
- IMAP `FETCH` responses are parsed by selecting the first tuple bytes payload. This avoids crashes when `imaplib` returns extra response items before/after the actual header payload.
|
||||||
- `MAP_RENAMES` is intentionally empty.
|
- `MAP_RENAMES` is intentionally empty.
|
||||||
- Source folders are selected by shared `list_folders()` plus `mail_routes.is_source_folder()`.
|
- Source folders are selected by shared `list_folders()` plus `mail_routes.is_source_folder()`.
|
||||||
- Step 2 routes all eligible source folders, not just `INBOX`.
|
- Step 2 routes all eligible source folders, not just `INBOX`.
|
||||||
@@ -188,6 +189,7 @@ Latest live mailbox verification:
|
|||||||
|
|
||||||
- Direct read-only IMAP count audit of both `hans` and `backup` accounts after the mirror.
|
- Direct read-only IMAP count audit of both `hans` and `backup` accounts after the mirror.
|
||||||
- The user ran the previous dry-run sorter version against `backup@australius.nl`. It reached `INBOX.Archief.2022.verzonden` after completing `INBOX.Archief.2022.inkomend`, then the IMAP server closed the connection with `Server shutting down.`. The current code has reconnect/retry handling for that failure mode.
|
- The user ran the previous dry-run sorter version against `backup@australius.nl`. It reached `INBOX.Archief.2022.verzonden` after completing `INBOX.Archief.2022.inkomend`, then the IMAP server closed the connection with `Server shutting down.`. The current code has reconnect/retry handling for that failure mode.
|
||||||
|
- The user started an actual sorter run on `backup@australius.nl`; it began moving messages from `INBOX` and then crashed on an unexpected IMAP `FETCH` response shape: `AttributeError: 'int' object has no attribute 'decode'`. The local `verplaats_log.json` contains processed `INBOX:<uid>` entries from that run and should be preserved if the user resumes sorting. The current code fixes this parser crash.
|
||||||
- No live mailbox script was run by Codex while adding the latest reconnect changes.
|
- No live mailbox script was run by Codex while adding the latest reconnect changes.
|
||||||
|
|
||||||
## What To Do Next
|
## What To Do Next
|
||||||
|
|||||||
+16
-4
@@ -132,6 +132,16 @@ def print_progress(folder: str, processed: int, total: int):
|
|||||||
print(f" Gescand: {processed}/{total} | {folder}")
|
print(f" Gescand: {processed}/{total} | {folder}")
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_body(data) -> bytes | None:
|
||||||
|
"""Return the first bytes payload from an IMAP FETCH response."""
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
for item in data:
|
||||||
|
if isinstance(item, tuple) and len(item) >= 2 and isinstance(item[1], bytes):
|
||||||
|
return item[1]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def safe_select(session: ImapSession, log: dict, stap: str, folder: str, readonly: bool):
|
def safe_select(session: ImapSession, log: dict, stap: str, folder: str, readonly: bool):
|
||||||
for attempt in range(2):
|
for attempt in range(2):
|
||||||
try:
|
try:
|
||||||
@@ -357,12 +367,13 @@ def stap2_bronmappen_routing(session: ImapSession, log: dict, dry: bool):
|
|||||||
readonly=dry,
|
readonly=dry,
|
||||||
)
|
)
|
||||||
mail = session.mail
|
mail = session.mail
|
||||||
if status != "OK" or not data or not data[0]:
|
raw_header = fetch_body(data)
|
||||||
|
if status != "OK" or raw_header is None:
|
||||||
mislukt += 1
|
mislukt += 1
|
||||||
log_failure(log, "stap2", bron, uid, None, "fetch_header", status, data)
|
log_failure(log, "stap2", bron, uid, None, "fetch_header", status, data)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
msg = email.message_from_bytes(data[0][1])
|
msg = email.message_from_bytes(raw_header)
|
||||||
from_raw = decode_hdr(msg.get("From", ""))
|
from_raw = decode_hdr(msg.get("From", ""))
|
||||||
subject = decode_hdr(msg.get("Subject", ""))
|
subject = decode_hdr(msg.get("Subject", ""))
|
||||||
date_str = msg.get("Date", "")
|
date_str = msg.get("Date", "")
|
||||||
@@ -486,12 +497,13 @@ def stap3_facturen(session: ImapSession, log: dict, dry: bool):
|
|||||||
readonly=dry,
|
readonly=dry,
|
||||||
)
|
)
|
||||||
mail = session.mail
|
mail = session.mail
|
||||||
if status != "OK" or not data or not data[0]:
|
raw_header = fetch_body(data)
|
||||||
|
if status != "OK" or raw_header is None:
|
||||||
mislukt += 1
|
mislukt += 1
|
||||||
log_failure(log, "stap3", bron, uid, None, "fetch_header", status, data)
|
log_failure(log, "stap3", bron, uid, None, "fetch_header", status, data)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
msg = email.message_from_bytes(data[0][1])
|
msg = email.message_from_bytes(raw_header)
|
||||||
date_str = msg.get("Date", "")
|
date_str = msg.get("Date", "")
|
||||||
doel = PREFIX + quarter_folder(date_str)
|
doel = PREFIX + quarter_folder(date_str)
|
||||||
gepland += 1
|
gepland += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user