Fix recurring event series modification via drag and drop operations
This commit resolves the "Failed to fetch" errors when updating recurring event series through drag operations by implementing proper request sequencing and fixing time parameter handling. Key fixes: - Eliminate HTTP request cancellation by sequencing operations properly - Add global mutex to prevent CalDAV HTTP race conditions - Implement complete RFC 5545-compliant series splitting for "this_and_future" - Fix frontend to pass dragged times instead of original times - Add comprehensive error handling and request timing logs - Backend now handles both UPDATE (add UNTIL) and CREATE (new series) in single request Technical changes: - Frontend: Remove concurrent CREATE request, pass dragged times to backend - Backend: Implement full this_and_future logic with sequential operations - CalDAV: Add mutex serialization and detailed error tracking - Series: Create new series with occurrence date + dragged times 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use calendar_models::{VEvent, EventStatus, EventClass, CalendarUser, VAlarm};
|
||||
|
||||
// Global mutex to serialize CalDAV HTTP requests to prevent race conditions
|
||||
lazy_static::lazy_static! {
|
||||
static ref CALDAV_HTTP_MUTEX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
|
||||
}
|
||||
|
||||
/// Type alias for shared VEvent (for backward compatibility during migration)
|
||||
pub type CalendarEvent = VEvent;
|
||||
|
||||
@@ -105,9 +113,15 @@ pub struct CalDAVClient {
|
||||
impl CalDAVClient {
|
||||
/// Create a new CalDAV client with the given configuration
|
||||
pub fn new(config: crate::config::CalDAVConfig) -> Self {
|
||||
// Create HTTP client with global timeout to prevent hanging requests
|
||||
let http_client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(60)) // 60 second global timeout
|
||||
.build()
|
||||
.expect("Failed to create HTTP client");
|
||||
|
||||
Self {
|
||||
config,
|
||||
http_client: reqwest::Client::new(),
|
||||
http_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,6 +787,10 @@ impl CalDAVClient {
|
||||
println!("Creating event at: {}", full_url);
|
||||
println!("iCal data: {}", ical_data);
|
||||
|
||||
println!("📡 Acquiring CalDAV HTTP lock for CREATE request...");
|
||||
let _lock = CALDAV_HTTP_MUTEX.lock().await;
|
||||
println!("📡 Lock acquired, sending CREATE request to CalDAV server...");
|
||||
|
||||
let response = self.http_client
|
||||
.put(&full_url)
|
||||
.header("Authorization", format!("Basic {}", self.config.get_basic_auth()))
|
||||
@@ -823,15 +841,49 @@ impl CalDAVClient {
|
||||
println!("📝 Updated iCal data: {}", ical_data);
|
||||
println!("📝 Event has {} exception dates", event.exdate.len());
|
||||
|
||||
let response = self.http_client
|
||||
println!("📡 Acquiring CalDAV HTTP lock for PUT request...");
|
||||
let _lock = CALDAV_HTTP_MUTEX.lock().await;
|
||||
println!("📡 Lock acquired, sending PUT request to CalDAV server...");
|
||||
println!("🔗 PUT URL: {}", full_url);
|
||||
println!("🔍 Request headers: Authorization: Basic [HIDDEN], Content-Type: text/calendar; charset=utf-8");
|
||||
|
||||
let request_builder = self.http_client
|
||||
.put(&full_url)
|
||||
.header("Authorization", format!("Basic {}", self.config.get_basic_auth()))
|
||||
.header("Content-Type", "text/calendar; charset=utf-8")
|
||||
.header("User-Agent", "calendar-app/0.1.0")
|
||||
.body(ical_data)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| CalDAVError::ParseError(e.to_string()))?;
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.body(ical_data);
|
||||
|
||||
println!("📡 About to execute PUT request at {}", chrono::Utc::now().format("%H:%M:%S%.3f"));
|
||||
let start_time = std::time::Instant::now();
|
||||
let response_result = request_builder.send().await;
|
||||
let elapsed = start_time.elapsed();
|
||||
|
||||
println!("📡 PUT request completed after {}ms at {}", elapsed.as_millis(), chrono::Utc::now().format("%H:%M:%S%.3f"));
|
||||
let response = response_result.map_err(|e| {
|
||||
println!("❌ HTTP PUT request failed after {}ms: {}", elapsed.as_millis(), e);
|
||||
println!("❌ Error source: {:?}", e.source());
|
||||
println!("❌ Error string: {}", e.to_string());
|
||||
if e.is_timeout() {
|
||||
println!("❌ Error was a timeout");
|
||||
} else if e.is_connect() {
|
||||
println!("❌ Error was a connection error");
|
||||
} else if e.is_request() {
|
||||
println!("❌ Error was a request error");
|
||||
} else if e.to_string().contains("operation was canceled") || e.to_string().contains("cancelled") {
|
||||
println!("❌ Error indicates operation was cancelled");
|
||||
} else {
|
||||
println!("❌ Error was of unknown type");
|
||||
}
|
||||
|
||||
// Check if this might be a concurrent request issue
|
||||
if e.to_string().contains("cancel") {
|
||||
println!("⚠️ Potential race condition detected - request was cancelled, possibly by another concurrent operation");
|
||||
}
|
||||
|
||||
CalDAVError::ParseError(e.to_string())
|
||||
})?;
|
||||
|
||||
println!("Event update response status: {}", response.status());
|
||||
|
||||
@@ -1020,6 +1072,10 @@ impl CalDAVClient {
|
||||
|
||||
println!("Deleting event at: {}", full_url);
|
||||
|
||||
println!("📡 Acquiring CalDAV HTTP lock for DELETE request...");
|
||||
let _lock = CALDAV_HTTP_MUTEX.lock().await;
|
||||
println!("📡 Lock acquired, sending DELETE request to CalDAV server...");
|
||||
|
||||
let response = self.http_client
|
||||
.delete(&full_url)
|
||||
.header("Authorization", format!("Basic {}", self.config.get_basic_auth()))
|
||||
|
||||
Reference in New Issue
Block a user