EWS fixes - part 3

This commit is contained in:
Maurus Decimus
2026-06-10 22:19:28 +02:00
parent e88e213987
commit f263f57c09
6 changed files with 600 additions and 92 deletions
+32 -3
View File
@@ -9,7 +9,7 @@ use serde_json::Value;
use crate::db::exchange_ews_ids;
use crate::error::Error;
use crate::exchange_ews::calendar_map::to_jscalendar;
use crate::exchange_ews::calendar_map::to_jscalendar_with_exceptions;
use crate::exchange_ews::parse::{CalendarItemRaw, parse_calendar_item};
use crate::exchange_ews::types::{CalendarItemType, ItemId};
use crate::exchange_ews::xml::ItemShape;
@@ -19,7 +19,8 @@ use crate::sync::TypeCounts;
use super::attachments::{fetch_attachments, intern_attachment};
use super::folders::FolderPlan;
use super::items::{
EnumerationMode, ItemRunCtx, delete_vanished, enumerate_folder, for_each_fetched_item, plan_for,
EnumerationMode, ItemRunCtx, delete_vanished, enumerate_folder, for_each_fetched_item,
get_items, plan_for,
};
pub fn reconcile_all(
@@ -176,7 +177,8 @@ fn apply_event(
existing_local_id: Option<i64>,
counts: &mut TypeCounts,
) -> Result<(), Error> {
let mut built = to_jscalendar(raw);
let modified_full = fetch_modified_occurrences(ctx, raw);
let mut built = to_jscalendar_with_exceptions(raw, &modified_full);
attach_calendar_links(conn, ctx, raw, &mut built.data)?;
let calendar_ids = serde_json::json!([local_folder_id]).to_string();
let data = built.data.to_string();
@@ -229,6 +231,33 @@ fn apply_event(
Ok(())
}
fn fetch_modified_occurrences(ctx: &ItemRunCtx<'_>, raw: &CalendarItemRaw) -> Vec<CalendarItemRaw> {
let ids: Vec<ItemId> = raw
.modified_occurrences
.iter()
.filter(|o| !o.item_id.id.is_empty())
.map(|o| o.item_id.clone())
.collect();
if ids.is_empty() {
return Vec::new();
}
match get_items(ctx, ItemShape::CalendarItem, &ids) {
Ok(outcome) => outcome
.messages
.iter()
.filter(|m| m.success)
.filter_map(|m| parse_calendar_item(&m.inner_xml).ok())
.filter(|occ| !occ.id.id.is_empty())
.collect(),
Err(e) => {
ctx.logger.warn(&format!(
"GetItem (calendar exceptions) failed: {e}; overrides keep time-only changes"
));
Vec::new()
}
}
}
fn attach_calendar_links(
conn: &mut Connection,
ctx: &ItemRunCtx<'_>,
+36
View File
@@ -269,3 +269,39 @@ fn keyword_array(item: &MessageItem) -> String {
let value: Value = Value::Array(kws.into_iter().map(Value::String).collect());
value.to_string()
}
#[cfg(test)]
mod tests {
use super::keyword_array;
use crate::exchange_ews::parse::MessageItem;
#[test]
fn read_draft_flagged_and_categories_map_to_jmap_keywords() {
let item = MessageItem {
is_read: Some(true),
is_draft: Some(true),
flag_status: Some("Flagged".to_owned()),
categories: vec!["Red Category".to_owned(), "VIP".to_owned()],
..MessageItem::default()
};
let kws = keyword_array(&item);
for expected in ["$seen", "$draft", "$flagged", "red category", "vip"] {
assert!(kws.contains(expected), "missing {expected} in {kws}");
}
}
#[test]
fn unread_unflagged_message_has_no_seen_or_flagged() {
let item = MessageItem {
is_read: Some(false),
flag_status: Some("NotFlagged".to_owned()),
..MessageItem::default()
};
let kws = keyword_array(&item);
assert!(!kws.contains("$seen"), "unread must not be $seen: {kws}");
assert!(
!kws.contains("$flagged"),
"only FlagStatus=Flagged maps to $flagged: {kws}"
);
}
}