# Nextcloud CalDAV Import Implementation Plan ## Current State Analysis ### Current Code Overview The caldavpuller project is a Rust-based CalDAV synchronization tool that currently: - **Reads events from Zoho calendars** using multiple approaches (zoho-export, zoho-events-list, zoho-events-direct) - **Supports basic CalDAV operations** like listing calendars and events - **Has a solid event model** in `src/event.rs` with support for datetime, timezone, title, and other properties - **Implements CalDAV client functionality** in `src/caldav_client.rs` and related files - **Can already generate iCalendar format** using the `to_ical()` method ### Current Capabilities - ✅ **Event listing**: Can read and display events from external sources - ✅ **iCalendar generation**: Has basic iCalendar export functionality - ✅ **CalDAV client**: Basic WebDAV operations implemented - ✅ **Configuration**: Flexible configuration system for different CalDAV servers ### Missing Functionality for Nextcloud Import - ❌ **PUT/POST operations**: No ability to write events to CalDAV servers - ❌ **Calendar creation**: Cannot create new calendars on Nextcloud - ❌ **Nextcloud-specific optimizations**: No handling for Nextcloud's CalDAV implementation specifics - ❌ **Import workflow**: No dedicated import command or process ## Nextcloud CalDAV Architecture Based on research of Nextcloud's CalDAV implementation (built on SabreDAV): ### Key Requirements 1. **Standard CalDAV Compliance**: Nextcloud follows RFC 4791 CalDAV specification 2. **iCalendar Format**: Requires RFC 5545 compliant iCalendar data 3. **Authentication**: Basic auth or app password authentication 4. **URL Structure**: Typically `/remote.php/dav/calendars/{user}/{calendar-name}/` ### Nextcloud-Specific Features - **SabreDAV Backend**: Nextcloud uses SabreDAV as its CalDAV server - **WebDAV Extensions**: Supports standard WebDAV sync operations - **Calendar Discovery**: Can auto-discover user calendars via PROPFIND - **ETag Support**: Proper ETag handling for synchronization - **Multi-Get Operations**: Supports calendar-multiget for efficiency ## Implementation Plan ### Phase 1: Core CalDAV Write Operations #### 1.1 Extend CalDAV Client for Write Operations **File**: `src/caldav_client.rs` **Required Methods**: ```rust // Create or update an event pub async fn put_event(&self, calendar_url: &str, event_path: &str, ical_data: &str) -> CalDavResult<()> // Create a new calendar pub async fn create_calendar(&self, calendar_name: &str, display_name: Option<&str>) -> CalDavResult // Upload multiple events efficiently pub async fn import_events_batch(&self, calendar_url: &str, events: &[Event]) -> CalDavResult>> ``` **Implementation Details**: - Use HTTP PUT method for individual events - Handle ETag conflicts with If-Match headers - Use proper content-type: `text/calendar; charset=utf-8` - Support both creating new events and updating existing ones #### 1.2 Enhanced Event to iCalendar Conversion **File**: `src/event.rs` **Current Issues**: - Timezone handling is incomplete - Missing proper DTSTAMP and LAST-MODIFIED - Limited property support **Required Enhancements**: ```rust impl Event { pub fn to_ical_for_nextcloud(&self) -> CalDavResult { // Enhanced iCalendar generation with: // - Proper timezone handling // - Nextcloud-specific properties // - Better datetime formatting // - Required properties for Nextcloud compatibility } pub fn generate_unique_path(&self) -> String { // Generate filename/path for CalDAV storage format!("{}.ics", self.uid) } } ``` ### Phase 2: Nextcloud Integration #### 2.1 Nextcloud Client Extension **New File**: `src/nextcloud_client.rs` ```rust pub struct NextcloudClient { client: CalDavClient, base_url: String, username: String, } impl NextcloudClient { pub fn new(config: NextcloudConfig) -> CalDavResult // Auto-discover calendars pub async fn discover_calendars(&self) -> CalDavResult> // Create calendar if it doesn't exist pub async fn ensure_calendar_exists(&self, name: &str, display_name: Option<&str>) -> CalDavResult // Import events with conflict resolution pub async fn import_events(&self, calendar_name: &str, events: Vec) -> CalDavResult // Check if event already exists pub async fn event_exists(&self, calendar_name: &str, event_uid: &str) -> CalDavResult // Get existing event ETag pub async fn get_event_etag(&self, calendar_name: &str, event_uid: &str) -> CalDavResult> } ``` #### 2.2 Nextcloud Configuration **File**: `src/config.rs` Add Nextcloud-specific configuration: ```toml [nextcloud] # Nextcloud server URL (e.g., https://cloud.example.com) server_url = "https://cloud.example.com" # Username username = "your_username" # App password (recommended) or regular password password = "your_app_password" # Default calendar for imports default_calendar = "imported-events" # Import behavior import_behavior = "skip_duplicates" # or "overwrite" or "merge" # Conflict resolution conflict_resolution = "keep_existing" # or "overwrite_remote" or "merge" ``` ### Phase 3: Import Workflow Implementation #### 3.1 Import Command Line Interface **File**: `src/main.rs` Add new CLI options: ```rust /// Import events into Nextcloud calendar #[arg(long)] import_nextcloud: bool, /// Target calendar name for Nextcloud import #[arg(long)] nextcloud_calendar: Option, /// Import behavior (skip_duplicates, overwrite, merge) #[arg(long, default_value = "skip_duplicates")] import_behavior: String, /// Dry run - show what would be imported without actually doing it #[arg(long)] dry_run: bool, ``` #### 3.2 Import Engine **New File**: `src/nextcloud_import.rs` ```rust pub struct ImportEngine { nextcloud_client: NextcloudClient, config: ImportConfig, } pub struct ImportResult { pub total_events: usize, pub imported: usize, pub skipped: usize, pub errors: Vec, pub conflicts: Vec, } impl ImportEngine { pub async fn import_events(&self, events: Vec) -> CalDavResult { // 1. Validate events // 2. Check for existing events // 3. Resolve conflicts based on configuration // 4. Batch upload events // 5. Report results } fn validate_event(&self, event: &Event) -> CalDavResult<()> { // Ensure required fields are present // Validate datetime and timezone // Check for Nextcloud compatibility } async fn check_existing_event(&self, event: &Event) -> CalDavResult> { // Return ETag if event exists, None otherwise } async fn resolve_conflict(&self, existing_event: &str, new_event: &Event) -> CalDavResult { // Based on configuration: skip, overwrite, or merge } } ``` ### Phase 4: Error Handling and Validation #### 4.1 Enhanced Error Types **File**: `src/error.rs` ```rust #[derive(Debug, thiserror::Error)] pub enum ImportError { #[error("Event validation failed: {message}")] ValidationFailed { message: String }, #[error("Event already exists: {uid}")] EventExists { uid: String }, #[error("Calendar creation failed: {message}")] CalendarCreationFailed { message: String }, #[error("Import conflict: {event_uid} - {message}")] ImportConflict { event_uid: String, message: String }, #[error("Nextcloud API error: {status} - {message}")] NextcloudError { status: u16, message: String }, } ``` #### 4.2 Event Validation ```rust impl Event { pub fn validate_for_nextcloud(&self) -> CalDavResult<()> { // Check required fields if self.summary.trim().is_empty() { return Err(CalDavError::EventProcessing("Event summary cannot be empty".to_string())); } // Validate timezone if let Some(ref tz) = self.timezone { if !is_valid_timezone(tz) { return Err(CalDavError::EventProcessing(format!("Invalid timezone: {}", tz))); } } // Check date ranges if self.start > self.end { return Err(CalDavError::EventProcessing("Event start must be before end".to_string())); } Ok(()) } } ``` ### Phase 5: Testing and Integration #### 5.1 Unit Tests **File**: `tests/nextcloud_import_tests.rs` ```rust #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_event_validation() { // Test valid and invalid events } #[tokio::test] async fn test_ical_generation() { // Test iCalendar output format } #[tokio::test] async fn test_conflict_resolution() { // Test different conflict strategies } #[tokio::test] async fn test_calendar_creation() { // Test Nextcloud calendar creation } } ``` #### 5.2 Integration Tests **File**: `tests/nextcloud_integration_tests.rs` ```rust // These tests require a real Nextcloud instance // Use environment variables for test credentials #[tokio::test] #[ignore] // Run manually with real instance async fn test_full_import_workflow() { // Test complete import process } #[tokio::test] #[ignore] async fn test_duplicate_handling() { // Test duplicate event handling } ``` ## Implementation Priorities ### Priority 1: Core Import Functionality 1. **Enhanced CalDAV client with PUT support** - Essential for writing events 2. **Basic Nextcloud client** - Discovery and calendar operations 3. **Import command** - CLI interface for importing events 4. **Event validation** - Ensure data quality ### Priority 2: Advanced Features 1. **Conflict resolution** - Handle existing events gracefully 2. **Batch operations** - Improve performance for many events 3. **Error handling** - Comprehensive error management 4. **Testing suite** - Ensure reliability ### Priority 3: Optimization and Polish 1. **Progress reporting** - User feedback during import 2. **Dry run mode** - Preview imports before execution 3. **Configuration validation** - Better error messages 4. **Documentation** - User guides and API docs ## Technical Considerations ### Nextcloud URL Structure ``` Base URL: https://cloud.example.com Principal: /remote.php/dav/principals/users/{username}/ Calendar Home: /remote.php/dav/calendars/{username}/ Calendar URL: /remote.php/dav/calendars/{username}/{calendar-name}/ Event URL: /remote.php/dav/calendars/{username}/{calendar-name}/{event-uid}.ics ``` ### Authentication - **App Passwords**: Recommended over regular passwords - **Basic Auth**: Standard HTTP Basic authentication - **Two-Factor**: Must use app passwords if 2FA enabled ### iCalendar Compliance - **RFC 5545**: Strict compliance required - **Required Properties**: UID, DTSTAMP, SUMMARY, DTSTART, DTEND - **Timezone Support**: Proper TZID usage - **Line Folding**: Handle long lines properly ### Performance Considerations - **Batch Operations**: Use calendar-multiget where possible - **Concurrency**: Import multiple events in parallel - **Memory Management**: Process large event lists in chunks - **Network Efficiency**: Minimize HTTP requests ## Success Criteria ### Minimum Viable Product 1. ✅ Can import events with title, datetime, and timezone into Nextcloud 2. ✅ Handles duplicate events gracefully 3. ✅ Provides clear error messages and progress feedback 4. ✅ Works with common Nextcloud configurations ### Complete Implementation 1. ✅ Full conflict resolution strategies 2. ✅ Batch import with performance optimization 3. ✅ Comprehensive error handling and recovery 4. ✅ Test suite with >90% coverage 5. ✅ Documentation and examples ## Next Steps 1. **Week 1**: Implement CalDAV PUT operations and basic Nextcloud client 2. **Week 2**: Add import command and basic workflow 3. **Week 3**: Implement validation and error handling 4. **Week 4**: Add conflict resolution and batch operations 5. **Week 5**: Testing, optimization, and documentation This plan provides a structured approach to implementing robust Nextcloud CalDAV import functionality while maintaining compatibility with the existing codebase architecture.