Working correctly to fetch 1 Nextcloud calendar

This commit is contained in:
Alvaro Soliverez 2025-10-26 13:10:16 -03:00
parent 20a74ac7a4
commit 16d6fc375d
3 changed files with 406 additions and 24 deletions

View file

@ -39,6 +39,27 @@ delete_missing = false
# Date range configuration
date_range = { days_ahead = 30, days_back = 30, sync_all_events = false }
[import]
# Target server configuration (e.g., Nextcloud)
[import.target_server]
# Nextcloud CalDAV URL
url = "https://cloud.soliverez.com.ar/remote.php/dav/calendars/alvaro/"
# Username for Nextcloud authentication
username = "alvaro"
# Password for Nextcloud authentication (use app-specific password)
password = "D7F2o-fFoqp-j2ttJ-t4etE-yz3oS"
# Whether to use HTTPS (recommended)
use_https = true
# Request timeout in seconds
timeout = 30
# Target calendar configuration
[import.target_calendar]
# Target calendar name
name = "trabajo-alvaro"
enabled = true
# Optional filtering configuration
[filters]
# Keywords to filter events by (events containing any of these will be included)

View file

@ -7,10 +7,12 @@ use anyhow::Result;
/// Main configuration structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
/// Server configuration
/// Source server configuration (e.g., Zoho)
pub server: ServerConfig,
/// Calendar configuration
/// Source calendar configuration
pub calendar: CalendarConfig,
/// Import configuration (e.g., Nextcloud as target)
pub import: Option<ImportConfig>,
/// Filter configuration
pub filters: Option<FilterConfig>,
/// Sync configuration
@ -49,6 +51,47 @@ pub struct CalendarConfig {
pub enabled: bool,
}
/// Import configuration for unidirectional sync to target server
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportConfig {
/// Target server configuration
pub target_server: ImportTargetServerConfig,
/// Target calendar configuration
pub target_calendar: ImportTargetCalendarConfig,
}
/// Target server configuration for Nextcloud or other CalDAV servers
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportTargetServerConfig {
/// Target CalDAV server URL
pub url: String,
/// Username for authentication
pub username: String,
/// Password for authentication
pub password: String,
/// Whether to use HTTPS
pub use_https: bool,
/// Timeout in seconds
pub timeout: u64,
/// Custom headers to send with requests
pub headers: Option<std::collections::HashMap<String, String>>,
}
/// Target calendar configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportTargetCalendarConfig {
/// Target calendar name
pub name: String,
/// Target calendar display name
pub display_name: Option<String>,
/// Target calendar color
pub color: Option<String>,
/// Target calendar timezone
pub timezone: Option<String>,
/// Whether this calendar is enabled for import
pub enabled: bool,
}
/// Filter configuration for events
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilterConfig {
@ -97,6 +140,7 @@ impl Default for Config {
Self {
server: ServerConfig::default(),
calendar: CalendarConfig::default(),
import: None,
filters: None,
sync: SyncConfig::default(),
}
@ -128,6 +172,40 @@ impl Default for CalendarConfig {
}
}
impl Default for ImportConfig {
fn default() -> Self {
Self {
target_server: ImportTargetServerConfig::default(),
target_calendar: ImportTargetCalendarConfig::default(),
}
}
}
impl Default for ImportTargetServerConfig {
fn default() -> Self {
Self {
url: "https://nextcloud.example.com/remote.php/dav/calendars/user".to_string(),
username: String::new(),
password: String::new(),
use_https: true,
timeout: 30,
headers: None,
}
}
}
impl Default for ImportTargetCalendarConfig {
fn default() -> Self {
Self {
name: "Imported-Events".to_string(),
display_name: None,
color: None,
timezone: None,
enabled: true,
}
}
}
impl Default for SyncConfig {
fn default() -> Self {
Self {

View file

@ -58,6 +58,10 @@ struct Cli {
/// Use specific calendar URL instead of discovering from config
#[arg(long)]
calendar_url: Option<String>,
/// Show detailed import-relevant information for calendars
#[arg(long)]
import_info: bool,
}
#[tokio::main]
@ -126,24 +130,299 @@ async fn main() -> Result<()> {
}
async fn run_sync(config: Config, cli: &Cli) -> CalDavResult<()> {
// Create sync engine
let mut sync_engine = SyncEngine::new(config.clone()).await?;
if cli.list_calendars {
// List calendars and exit
info!("Listing available calendars from server");
// Get calendars directly from the client
let calendars = sync_engine.client.discover_calendars().await?;
println!("Found {} calendars:", calendars.len());
if cli.import_info {
println!("🔍 Import Analysis Report");
println!("========================\n");
// Show source calendars (current configuration)
println!("📤 SOURCE CALENDARS (Zoho/Current Server)");
println!("==========================================");
// Get calendars from the source server - handle errors gracefully
let source_calendars = match SyncEngine::new(config.clone()).await {
Ok(sync_engine) => {
match sync_engine.client.discover_calendars().await {
Ok(calendars) => {
Some(calendars)
}
Err(e) => {
println!("⚠️ Failed to discover source calendars: {}", e);
println!("Source server may be unavailable or credentials may be incorrect.\n");
None
}
}
}
Err(e) => {
println!("⚠️ Failed to connect to source server: {}", e);
println!("Source server configuration may need checking.\n");
None
}
};
let target_calendar_name = &config.calendar.name;
if let Some(ref calendars) = source_calendars {
println!("Found {} source calendars:", calendars.len());
println!("Current source calendar: {}\n", target_calendar_name);
for (i, calendar) in calendars.iter().enumerate() {
let is_target = calendar.name == *target_calendar_name
|| calendar.display_name.as_ref().map_or(false, |dn| dn == target_calendar_name);
// Calendar header with target indicator
if is_target {
println!(" {}. {} 🎯 [CURRENT SOURCE]", i + 1, calendar.display_name.as_ref().unwrap_or(&calendar.name));
} else {
println!(" {}. {}", i + 1, calendar.display_name.as_ref().unwrap_or(&calendar.name));
}
// Basic information
println!(" Name: {}", calendar.name);
println!(" URL: {}", calendar.url);
if let Some(ref display_name) = calendar.display_name {
println!(" Display Name: {}", display_name);
}
// Import-relevant information
if let Some(ref color) = calendar.color {
println!(" Color: {}", color);
}
if let Some(ref description) = calendar.description {
println!(" Description: {}", description);
}
if let Some(ref timezone) = calendar.timezone {
println!(" Timezone: {}", timezone);
}
// Supported components - crucial for export compatibility
let components = &calendar.supported_components;
println!(" Supported Components: {}", components.join(", "));
// Export suitability analysis
let supports_events = components.contains(&"VEVENT".to_string());
let supports_todos = components.contains(&"VTODO".to_string());
let supports_journals = components.contains(&"VJOURNAL".to_string());
println!(" 📤 Export Analysis:");
println!(" Event Support: {}", if supports_events { "✅ Yes" } else { "❌ No" });
println!(" Task Support: {}", if supports_todos { "✅ Yes" } else { "❌ No" });
println!(" Journal Support: {}", if supports_journals { "✅ Yes" } else { "❌ No" });
// Server type detection
if calendar.url.contains("/zoho/") || calendar.url.contains("zoho.com") {
println!(" Server Type: 🔵 Zoho");
println!(" CalDAV Standard: ⚠️ Partially Compliant");
println!(" Special Features: Zoho-specific APIs available");
} else {
println!(" Server Type: 🔧 Generic CalDAV");
println!(" CalDAV Standard: ✅ Likely Compliant");
}
println!();
}
} else {
println!("⚠️ Could not retrieve source calendars");
println!("Please check your source server configuration:\n");
println!(" URL: {}", config.server.url);
println!(" Username: {}", config.server.username);
println!(" Calendar: {}\n", config.calendar.name);
}
// Show target import calendars if configured
if let Some(ref import_config) = config.import {
println!("📥 TARGET IMPORT CALENDARS (Nextcloud/Destination)");
println!("=================================================");
println!("Configured target server: {}", import_config.target_server.url);
println!("Configured target calendar: {}\n", import_config.target_calendar.name);
// Create a temporary config for the target server
let mut target_config = config.clone();
target_config.server.url = import_config.target_server.url.clone();
target_config.server.username = import_config.target_server.username.clone();
target_config.server.password = import_config.target_server.password.clone();
target_config.server.timeout = import_config.target_server.timeout;
target_config.server.use_https = import_config.target_server.use_https;
target_config.server.headers = import_config.target_server.headers.clone();
println!("Attempting to connect to target server...");
// Try to connect to target server and list calendars
match SyncEngine::new(target_config).await {
Ok(target_sync_engine) => {
println!("✅ Successfully connected to target server!");
match target_sync_engine.client.discover_calendars().await {
Ok(target_calendars) => {
println!("Found {} target calendars:", target_calendars.len());
for (i, calendar) in target_calendars.iter().enumerate() {
let is_target = calendar.name == import_config.target_calendar.name
|| calendar.display_name.as_ref().map_or(false, |dn| *dn == import_config.target_calendar.name);
// Calendar header with target indicator
if is_target {
println!(" {}. {} 🎯 [IMPORT TARGET]", i + 1, calendar.display_name.as_ref().unwrap_or(&calendar.name));
} else {
println!(" {}. {}", i + 1, calendar.display_name.as_ref().unwrap_or(&calendar.name));
}
// Basic information
println!(" Name: {}", calendar.name);
println!(" URL: {}", calendar.url);
if let Some(ref display_name) = calendar.display_name {
println!(" Display Name: {}", display_name);
}
// Import-relevant information
if let Some(ref color) = calendar.color {
println!(" Color: {}", color);
}
if let Some(ref description) = calendar.description {
println!(" Description: {}", description);
}
if let Some(ref timezone) = calendar.timezone {
println!(" Timezone: {}", timezone);
}
// Supported components - crucial for import compatibility
let components = &calendar.supported_components;
println!(" Supported Components: {}", components.join(", "));
// Import suitability analysis
let supports_events = components.contains(&"VEVENT".to_string());
let supports_todos = components.contains(&"VTODO".to_string());
let supports_journals = components.contains(&"VJOURNAL".to_string());
println!(" 📥 Import Analysis:");
println!(" Event Support: {}", if supports_events { "✅ Yes" } else { "❌ No" });
println!(" Task Support: {}", if supports_todos { "✅ Yes" } else { "❌ No" });
println!(" Journal Support: {}", if supports_journals { "✅ Yes" } else { "❌ No" });
// Server type detection
if calendar.url.contains("/remote.php/dav/calendars/") {
println!(" Server Type: ☁️ Nextcloud");
println!(" CalDAV Standard: ✅ RFC 4791 Compliant");
println!(" Recommended: ✅ High compatibility");
println!(" Special Features: Full SabreDAV support");
} else {
println!(" Server Type: 🔧 Generic CalDAV");
println!(" CalDAV Standard: ✅ Likely Compliant");
}
// Additional Nextcloud-specific checks
if calendar.url.contains("/remote.php/dav/calendars/") && supports_events {
println!(" ✅ Ready for Nextcloud event import");
} else if !supports_events {
println!(" ⚠️ This calendar doesn't support events - not suitable for import");
}
println!();
}
// Import compatibility summary
let target_calendar = target_calendars.iter()
.find(|c| c.name == import_config.target_calendar.name
|| c.display_name.as_ref().map_or(false, |dn| *dn == import_config.target_calendar.name));
if let Some(target_cal) = target_calendar {
let supports_events = target_cal.supported_components.contains(&"VEVENT".to_string());
let is_nextcloud = target_cal.url.contains("/remote.php/dav/calendars/");
println!("📋 IMPORT READINESS SUMMARY");
println!("============================");
println!("Target Calendar: {}", target_cal.display_name.as_ref().unwrap_or(&target_cal.name));
println!("Supports Events: {}", if supports_events { "✅ Yes" } else { "❌ No" });
println!("Server Type: {}", if is_nextcloud { "☁️ Nextcloud" } else { "🔧 Generic CalDAV" });
if supports_events {
if is_nextcloud {
println!("Overall Status: ✅ Excellent - Nextcloud with full event support");
} else {
println!("Overall Status: ✅ Good - Generic CalDAV with event support");
}
} else {
println!("Overall Status: ❌ Not suitable - No event support");
}
} else {
println!("⚠️ Target calendar '{}' not found on server", import_config.target_calendar.name);
println!("Available calendars:");
for calendar in &target_calendars {
println!(" - {}", calendar.display_name.as_ref().unwrap_or(&calendar.name));
}
}
}
Err(e) => {
println!("❌ Failed to discover calendars on target server: {}", e);
println!("The server connection was successful, but calendar discovery failed.");
println!("Please check your import configuration:");
println!(" URL: {}", import_config.target_server.url);
println!(" Username: {}", import_config.target_server.username);
println!(" Target Calendar: {}", import_config.target_calendar.name);
}
}
}
Err(e) => {
println!("❌ Failed to connect to target server: {}", e);
println!("Please check your import configuration:");
println!(" URL: {}", import_config.target_server.url);
println!(" Username: {}", import_config.target_server.username);
println!(" Target Calendar: {}", import_config.target_calendar.name);
// Provide guidance based on the error
if e.to_string().contains("401") || e.to_string().contains("Unauthorized") {
println!("");
println!("💡 Troubleshooting tips:");
println!(" - Check username and password");
println!(" - For Nextcloud with 2FA, use app-specific passwords");
println!(" - Verify the URL format: https://your-nextcloud.com/remote.php/dav/calendars/username/");
} else if e.to_string().contains("404") || e.to_string().contains("Not Found") {
println!("");
println!("💡 Troubleshooting tips:");
println!(" - Verify the Nextcloud URL is correct");
println!(" - Check if CalDAV is enabled in Nextcloud settings");
println!(" - Ensure the username is correct (case-sensitive)");
} else if e.to_string().contains("timeout") || e.to_string().contains("connection") {
println!("");
println!("💡 Troubleshooting tips:");
println!(" - Check network connectivity");
println!(" - Verify the Nextcloud server is accessible");
println!(" - Try increasing timeout value in configuration");
}
}
}
} else {
println!("📥 No import target configured");
println!("To configure import target, add [import] section to config.toml:");
println!("");
println!("[import]");
println!("[import.target_server]");
println!("url = \"https://your-nextcloud.com/remote.php/dav/calendars/user\"");
println!("username = \"your-username\"");
println!("password = \"your-password\"");
println!("[import.target_calendar]");
println!("name = \"Imported-Zoho-Events\"");
println!("enabled = true");
}
} else {
// Regular calendar listing (original behavior) - only if not import_info
let sync_engine = SyncEngine::new(config.clone()).await?;
let calendars = sync_engine.client.discover_calendars().await?;
println!("Found {} calendars:", calendars.len());
for (i, calendar) in calendars.iter().enumerate() {
println!(" {}. {}", i + 1, calendar.display_name.as_ref().unwrap_or(&calendar.name));
println!(" Name: {}", calendar.name);
println!(" URL: {}", calendar.url);
if let Some(ref display_name) = calendar.display_name {
println!(" Display Name: {}", display_name);
}
if let Some(ref color) = calendar.color {
println!(" Color: {}", color);
}
@ -156,10 +435,14 @@ async fn run_sync(config: Config, cli: &Cli) -> CalDavResult<()> {
println!(" Supported Components: {}", calendar.supported_components.join(", "));
println!();
}
}
return Ok(());
}
// Create sync engine for other operations
let mut sync_engine = SyncEngine::new(config.clone()).await?;
if cli.list_events {
// List events and exit
info!("Listing events from calendar: {}", config.calendar.name);