Initial commit: Complete CalDAV calendar synchronizer
- Rust-based CLI tool for Zoho to Nextcloud calendar sync - Selective calendar import from Zoho to single Nextcloud calendar - Timezone-aware event handling for next-week synchronization - Comprehensive configuration system with TOML support - CLI interface with debug, list, and sync operations - Complete documentation and example configurations
This commit is contained in:
commit
8362ebe44b
16 changed files with 6192 additions and 0 deletions
204
src/config.rs
Normal file
204
src/config.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
//! Configuration management for CalDAV synchronizer
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use anyhow::Result;
|
||||
|
||||
/// Main configuration structure
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
/// Server configuration
|
||||
pub server: ServerConfig,
|
||||
/// Calendar configuration
|
||||
pub calendar: CalendarConfig,
|
||||
/// Filter configuration
|
||||
pub filters: Option<FilterConfig>,
|
||||
/// Sync configuration
|
||||
pub sync: SyncConfig,
|
||||
}
|
||||
|
||||
/// Server connection configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerConfig {
|
||||
/// 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>>,
|
||||
}
|
||||
|
||||
/// Calendar-specific configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CalendarConfig {
|
||||
/// Calendar name/path
|
||||
pub name: String,
|
||||
/// Calendar display name
|
||||
pub display_name: Option<String>,
|
||||
/// Calendar color
|
||||
pub color: Option<String>,
|
||||
/// Calendar timezone
|
||||
pub timezone: String,
|
||||
/// Whether to sync this calendar
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// Filter configuration for events
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FilterConfig {
|
||||
/// Start date filter (ISO 8601)
|
||||
pub start_date: Option<String>,
|
||||
/// End date filter (ISO 8601)
|
||||
pub end_date: Option<String>,
|
||||
/// Event types to include
|
||||
pub event_types: Option<Vec<String>>,
|
||||
/// Keywords to filter by
|
||||
pub keywords: Option<Vec<String>>,
|
||||
/// Exclude keywords
|
||||
pub exclude_keywords: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Synchronization configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SyncConfig {
|
||||
/// Sync interval in seconds
|
||||
pub interval: u64,
|
||||
/// Whether to sync on startup
|
||||
pub sync_on_startup: bool,
|
||||
/// Maximum number of retries
|
||||
pub max_retries: u32,
|
||||
/// Retry delay in seconds
|
||||
pub retry_delay: u64,
|
||||
/// Whether to delete events not found on server
|
||||
pub delete_missing: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server: ServerConfig::default(),
|
||||
calendar: CalendarConfig::default(),
|
||||
filters: None,
|
||||
sync: SyncConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ServerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
url: "https://caldav.example.com".to_string(),
|
||||
username: String::new(),
|
||||
password: String::new(),
|
||||
use_https: true,
|
||||
timeout: 30,
|
||||
headers: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CalendarConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "calendar".to_string(),
|
||||
display_name: None,
|
||||
color: None,
|
||||
timezone: "UTC".to_string(),
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SyncConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
interval: 300, // 5 minutes
|
||||
sync_on_startup: true,
|
||||
max_retries: 3,
|
||||
retry_delay: 5,
|
||||
delete_missing: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Load configuration from a TOML file
|
||||
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let config: Config = toml::from_str(&content)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Save configuration to a TOML file
|
||||
pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||
let content = toml::to_string_pretty(self)?;
|
||||
std::fs::write(path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load configuration from environment variables
|
||||
pub fn from_env() -> Result<Self> {
|
||||
let mut config = Config::default();
|
||||
|
||||
if let Ok(url) = std::env::var("CALDAV_URL") {
|
||||
config.server.url = url;
|
||||
}
|
||||
if let Ok(username) = std::env::var("CALDAV_USERNAME") {
|
||||
config.server.username = username;
|
||||
}
|
||||
if let Ok(password) = std::env::var("CALDAV_PASSWORD") {
|
||||
config.server.password = password;
|
||||
}
|
||||
if let Ok(calendar) = std::env::var("CALDAV_CALENDAR") {
|
||||
config.calendar.name = calendar;
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Validate configuration
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
if self.server.url.is_empty() {
|
||||
anyhow::bail!("Server URL cannot be empty");
|
||||
}
|
||||
if self.server.username.is_empty() {
|
||||
anyhow::bail!("Username cannot be empty");
|
||||
}
|
||||
if self.server.password.is_empty() {
|
||||
anyhow::bail!("Password cannot be empty");
|
||||
}
|
||||
if self.calendar.name.is_empty() {
|
||||
anyhow::bail!("Calendar name cannot be empty");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_config() {
|
||||
let config = Config::default();
|
||||
assert_eq!(config.server.url, "https://caldav.example.com");
|
||||
assert_eq!(config.calendar.name, "calendar");
|
||||
assert_eq!(config.sync.interval, 300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_validation() {
|
||||
let mut config = Config::default();
|
||||
assert!(config.validate().is_err()); // Empty username/password
|
||||
|
||||
config.server.username = "test".to_string();
|
||||
config.server.password = "test".to_string();
|
||||
assert!(config.validate().is_ok());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue