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)
This commit is contained in:
Alvaro Soliverez 2025-10-29 13:39:48 -03:00
parent 16d6fc375d
commit f84ce62f73
10 changed files with 1461 additions and 342 deletions

390
NEXTCLOUD_IMPORT_PLAN.md Normal file
View file

@ -0,0 +1,390 @@
# 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<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**:
```rust
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`
```rust
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:
```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<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`
```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<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`
```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.