- 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
204 lines
5.5 KiB
Rust
204 lines
5.5 KiB
Rust
//! 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());
|
|
}
|
|
}
|