mirror of
https://github.com/spartanz51/tutabridge.git
synced 2026-06-24 10:54:32 +02:00
Recover .eml from disk even when the metadata row says has_details=0
After a SQLite schema migration the `mails` table is dropped and recreated empty; the encrypted `.eml.enc` files on disk survive that migration (they are keyed by element id, not by row id). Phase 0 of the syncer was gating the body recovery on `meta.has_details`, so the post- migration boot would deliver a `mail_to_rfc2822(mail, None)` headers-only body to IMAP even though the full body sat right there on disk — a real mail's content quietly showed up as a placeholder in Thunderbird until something forced a refetch. Try `read_eml` unconditionally. When it returns the body, also flip `has_details = 1` on the row so subsequent prefetch sweeps skip the mail and the heal is permanent. Mismatch → still falls back to headers-only as before. The extra read at boot is a `Path::exists()` plus an AES decrypt per mail, negligible vs the network costs we already pay. While here, demote the misleading IMAP-fetch log: when `details` is None but `rfc2822` is populated we *do* serve the real body, so log "placeholder" only when `rfc2822` is also missing. 166 bridge lib tests pass.
This commit is contained in:
@@ -394,12 +394,16 @@ impl ImapSession {
|
||||
self.mails[idx].details.as_ref(),
|
||||
);
|
||||
self.mails[idx].rfc2822 = Some(rfc);
|
||||
} else if self.mails[idx].details.is_none() {
|
||||
} else if self.mails[idx].rfc2822.is_none() {
|
||||
log::warn!(
|
||||
"No details for uid={}, body will be placeholder",
|
||||
"No body for uid={}, will serve headers-only placeholder",
|
||||
self.mails[idx].uid,
|
||||
);
|
||||
}
|
||||
// If `rfc2822` is already populated (Phase 0 read it from
|
||||
// disk), there is nothing else to do — the body will be
|
||||
// served from it; `details` being None is normal and not a
|
||||
// placeholder situation.
|
||||
}
|
||||
|
||||
let cached = &self.mails[idx];
|
||||
|
||||
+23
-10
@@ -434,17 +434,30 @@ async fn load_cached_folder(
|
||||
let mail: Mail = serde_json::from_str(&meta.mail_json)
|
||||
.map_err(|e| format!("Bad cached mail {}: {e}", meta.element_id))?;
|
||||
|
||||
let rfc2822 = if meta.has_details {
|
||||
match local_store.read_eml(&meta.element_id) {
|
||||
Ok(Some(eml)) => Some(eml),
|
||||
Ok(None) => Some(mail_to_rfc2822(&mail, None)),
|
||||
Err(e) => {
|
||||
warn!("Failed to read cached eml {}: {e}", meta.element_id);
|
||||
Some(mail_to_rfc2822(&mail, None))
|
||||
// Always try to recover the full body from disk first — the `.eml`
|
||||
// file is the source of truth and may exist even when the metadata
|
||||
// row says otherwise (a schema migration drops the `mails` table but
|
||||
// keeps the encrypted `.eml.enc` files, so `has_details` is reset to
|
||||
// 0 on first boot after the migration). Self-heal the row when we
|
||||
// find an orphaned body, so the prefetch loop knows to skip it on
|
||||
// the next sweep.
|
||||
let rfc2822 = match local_store.read_eml(&meta.element_id) {
|
||||
Ok(Some(eml)) => {
|
||||
if !meta.has_details {
|
||||
if let Err(e) = local_store.mark_has_details(&meta.element_id) {
|
||||
warn!(
|
||||
"Failed to heal has_details for {}: {e}",
|
||||
meta.element_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(mail_to_rfc2822(&mail, None))
|
||||
Some(eml)
|
||||
},
|
||||
Ok(None) => Some(mail_to_rfc2822(&mail, None)),
|
||||
Err(e) => {
|
||||
warn!("Failed to read cached eml {}: {e}", meta.element_id);
|
||||
Some(mail_to_rfc2822(&mail, None))
|
||||
},
|
||||
};
|
||||
|
||||
stored_mails.push(StoredMail {
|
||||
|
||||
Reference in New Issue
Block a user