feat: CalDAV sync adapter with calendar discovery and delta sync

Add CalDAV protocol implementation: principal discovery, calendar-home-set
lookup, calendar listing, and sync-collection delta sync. CalDAVSync parses
iCalendar events from CalDAV resources and populates the sync cache.
CalDAVSyncProvider implements SyncProvider for account-level orchestration.
This commit is contained in:
2026-07-23 20:01:28 +01:00
parent 2dab5b97b6
commit 1a69950f93
7 changed files with 689 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <string_view>
namespace nocal::sync {
// CalDAV account configuration.
struct CalDAVAccountConfig {
std::string server_url;
std::string username;
std::string password;
std::string account_id;
std::string display_name;
};
// Derive a deterministic account ID from server URL and username.
[[nodiscard]] std::string caldav_account_id(
std::string_view server_url, std::string_view username);
} // namespace nocal::sync

View File

@@ -0,0 +1,52 @@
#pragma once
#include "nocal/sync/caldav_account.hpp"
#include "nocal/sync/http.hpp"
#include "nocal/storage/sync_cache.hpp"
#include <string>
#include <vector>
namespace nocal::sync {
// CalDAV calendar listing entry.
struct CalDAVCalendar {
std::string href;
std::string name;
std::string description;
bool read_only{false};
};
class CalDAVSync {
public:
CalDAVSync(HttpTransport& transport, storage::SyncCache& cache,
const CalDAVAccountConfig& config);
// Discover principal URL and calendar-home-set.
void discover();
// List calendars under the calendar-home-set.
std::vector<CalDAVCalendar> list_calendars();
// Sync one calendar using sync-collection (delta) or full PROPFIND.
// `sync_token` is empty for initial sync. Returns new sync token.
std::string sync_calendar(const CalDAVCalendar& calendar,
std::string_view sync_token);
private:
HttpTransport& transport_;
storage::SyncCache& cache_;
CalDAVAccountConfig config_;
std::string principal_url_;
std::string calendar_home_set_;
std::string send_request(int method, const std::string& url,
const std::string& body, const std::vector<HttpHeader>& extra_headers);
std::string calendar_resource_url(const std::string& calendar_href) const;
std::string stable_calendar_id(const std::string& remote_id) const;
std::string stable_event_id(const std::string& calendar_id, const std::string& remote_uid) const;
std::string encode_basic_auth() const;
};
} // namespace nocal::sync

View File

@@ -0,0 +1,31 @@
#pragma once
#include "nocal/sync/caldav_account.hpp"
#include "nocal/sync/http.hpp"
#include "nocal/sync/provider.hpp"
namespace nocal::storage {
class SyncCache;
}
#include <string>
namespace nocal::sync {
class CalDAVSyncProvider final : public SyncProvider {
public:
CalDAVSyncProvider(HttpTransport& transport, storage::SyncCache& cache,
CalDAVAccountConfig config);
std::string_view provider_id() const noexcept override { return "caldav"; }
ConnectedAccount connect_account() override;
void sync_account(std::string_view account_id, SyncObserver& observer) override;
void disconnect_account(std::string_view account_id) override;
private:
HttpTransport& transport_;
storage::SyncCache& cache_;
CalDAVAccountConfig config_;
};
} // namespace nocal::sync