Working correctly to fetch 1 Nextcloud calendar
This commit is contained in:
parent
20a74ac7a4
commit
16d6fc375d
3 changed files with 406 additions and 24 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
327
src/main.rs
327
src/main.rs
|
|
@ -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,40 +130,319 @@ 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());
|
||||
|
||||
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 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);
|
||||
}
|
||||
if let Some(ref color) = calendar.color {
|
||||
println!(" Color: {}", color);
|
||||
|
||||
// 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");
|
||||
}
|
||||
if let Some(ref description) = calendar.description {
|
||||
println!(" Description: {}", description);
|
||||
} 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 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);
|
||||
}
|
||||
println!(" Supported Components: {}", calendar.supported_components.join(", "));
|
||||
println!();
|
||||
}
|
||||
if let Some(ref timezone) = calendar.timezone {
|
||||
println!(" Timezone: {}", timezone);
|
||||
}
|
||||
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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue