feat(sync): reconcile secondary Graph calendars

Read documented Graph v1 calendarView pages for secondary calendars and publish each finite window only after the complete bounded listing validates.

Add schema-v2 per-window event membership so moved-out occurrences are evicted without being mislabeled as remote tombstones, while overlapping windows retain shared instances.

Verified: 18/18 fresh normal, 18/18 -Werror, and 18/18 ASan/UBSan tests.
This commit is contained in:
2026-07-18 13:05:21 +01:00
parent 0582444c61
commit 98e07e287e
11 changed files with 1520 additions and 60 deletions

View File

@@ -76,6 +76,16 @@ struct PullPage {
bool operator==(const PullPage&) const = default;
};
// A provider's complete view of one finite calendar window. Membership is
// replaced only after the provider listing has completed successfully.
struct CompleteWindow {
std::vector<CachedEvent> events;
std::vector<CachedEventInstance> instances;
SyncCheckpoint checkpoint;
bool operator==(const CompleteWindow&) const = default;
};
struct CacheSnapshot {
std::vector<CachedAccount> accounts;
std::vector<CachedCalendar> calendars;
@@ -88,7 +98,7 @@ struct CacheSnapshot {
class SyncCache {
public:
static constexpr int current_schema_version = 1;
static constexpr int current_schema_version = 2;
explicit SyncCache(std::filesystem::path path);
~SyncCache();
@@ -112,6 +122,12 @@ public:
// events retain their last raw payload and have no instances.
void apply_pull_page(const PullPage& page);
// Atomically replaces membership for one complete, finite provider window.
// Returned events become live and have their instances replaced. Events
// omitted from the window lose cached instances only when no other complete
// window references them; omission is not treated as a remote tombstone.
void replace_complete_window(const CompleteWindow& window);
[[nodiscard]] CacheSnapshot snapshot() const;
private:

View File

@@ -29,9 +29,9 @@ public:
};
// Read-only Microsoft Graph v1.0 synchronization. Calendar discovery covers
// every calendar returned by /me/calendars. Stable v1.0 event delta is exposed
// only for the signed-in user's primary calendar; this class deliberately does
// not call beta or undocumented per-calendar delta routes.
// every calendar returned by /me/calendars. Stable v1.0 event delta is used for
// the primary calendar and complete calendarView reconciliation for secondary
// calendars; this class does not call beta or undocumented delta routes.
class MicrosoftGraphSync {
public:
MicrosoftGraphSync(HttpTransport& transport, storage::SyncCache& cache);
@@ -51,6 +51,12 @@ public:
void sync_primary_calendar_window(const MicrosoftGraphCredentials& credentials,
const MicrosoftGraphWindow& window);
// Reads every page of the documented Graph v1.0 calendarView for one
// active, non-primary calendar. The cache window is replaced only after the
// listing completes, so a failed or malformed page leaves it unchanged.
void sync_secondary_calendar_window(const MicrosoftGraphCredentials& credentials,
std::string calendar_id, const MicrosoftGraphWindow& window);
private:
HttpTransport& transport_;
storage::SyncCache& cache_;