caldavpuller/NEXTCLOUD_IMPORT_PLAN.md
Alvaro Soliverez f84ce62f73 feat: Add comprehensive Nextcloud import functionality and fix compilation issues
Major additions:
- New NextcloudImportEngine with import behaviors (SkipDuplicates, Overwrite, Merge)
- Complete import workflow with result tracking and conflict resolution
- Support for dry-run mode and detailed progress reporting
- Import command integration in CLI with --import-events flag

Configuration improvements:
- Added ImportConfig struct for structured import settings
- Backward compatibility with legacy ImportTargetConfig
- Enhanced get_import_config() method supporting both formats

CalDAV client enhancements:
- Improved XML parsing for multiple calendar display name formats
- Better fallback handling for calendar discovery
- Enhanced error handling and debugging capabilities

Bug fixes:
- Fixed test compilation errors in error.rs (reqwest::Error type conversion)
- Resolved unused variable warning in main.rs
- All tests now pass (16/16)

Documentation:
- Added comprehensive NEXTCLOUD_IMPORT_PLAN.md with implementation roadmap
- Updated library exports to include new modules

Files changed:
- src/nextcloud_import.rs: New import engine implementation
- src/config.rs: Enhanced configuration with import support
- src/main.rs: Added import command and CLI integration
- src/minicaldav_client.rs: Improved calendar discovery and XML parsing
- src/error.rs: Fixed test compilation issues
- src/lib.rs: Updated module exports
- Deleted: src/real_caldav_client.rs (removed unused file)
2025-10-29 13:39:48 -03:00

12 KiB

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:

// 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<String>

// Upload multiple events efficiently
pub async fn import_events_batch(&self, calendar_url: &str, events: &[Event]) -> CalDavResult<Vec<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:

impl Event {
    pub fn to_ical_for_nextcloud(&self) -> CalDavResult<String> {
        // 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

pub struct NextcloudClient {
    client: CalDavClient,
    base_url: String,
    username: String,
}

impl NextcloudClient {
    pub fn new(config: NextcloudConfig) -> CalDavResult<Self>
    
    // Auto-discover calendars
    pub async fn discover_calendars(&self) -> CalDavResult<Vec<CalendarInfo>>
    
    // Create calendar if it doesn't exist
    pub async fn ensure_calendar_exists(&self, name: &str, display_name: Option<&str>) -> CalDavResult<String>
    
    // Import events with conflict resolution
    pub async fn import_events(&self, calendar_name: &str, events: Vec<Event>) -> CalDavResult<ImportResult>
    
    // Check if event already exists
    pub async fn event_exists(&self, calendar_name: &str, event_uid: &str) -> CalDavResult<bool>
    
    // Get existing event ETag
    pub async fn get_event_etag(&self, calendar_name: &str, event_uid: &str) -> CalDavResult<Option<String>>
}

2.2 Nextcloud Configuration

File: src/config.rs

Add Nextcloud-specific configuration:

[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:

/// Import events into Nextcloud calendar
#[arg(long)]
import_nextcloud: bool,

/// Target calendar name for Nextcloud import
#[arg(long)]
nextcloud_calendar: Option<String>,

/// 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

pub struct ImportEngine {
    nextcloud_client: NextcloudClient,
    config: ImportConfig,
}

pub struct ImportResult {
    pub total_events: usize,
    pub imported: usize,
    pub skipped: usize,
    pub errors: Vec<ImportError>,
    pub conflicts: Vec<ConflictInfo>,
}

impl ImportEngine {
    pub async fn import_events(&self, events: Vec<Event>) -> CalDavResult<ImportResult> {
        // 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<Option<String>> {
        // Return ETag if event exists, None otherwise
    }
    
    async fn resolve_conflict(&self, existing_event: &str, new_event: &Event) -> CalDavResult<ConflictResolution> {
        // Based on configuration: skip, overwrite, or merge
    }
}

Phase 4: Error Handling and Validation

4.1 Enhanced Error Types

File: src/error.rs

#[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

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

#[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

// 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.