use anyhow::Result; use std::env; #[derive(Clone, Debug)] pub struct Config { pub database_url: String, pub server_address: String, pub jwt_secret: String, pub upload_path: String, pub watch_folder: String, pub allowed_file_types: Vec, pub watch_interval_seconds: Option, pub file_stability_check_ms: Option, pub max_file_age_hours: Option, // OCR Configuration pub ocr_language: String, pub concurrent_ocr_jobs: usize, pub ocr_timeout_seconds: u64, pub max_file_size_mb: u64, // Performance pub memory_limit_mb: usize, pub cpu_priority: String, } impl Config { pub fn from_env() -> Result { dotenvy::dotenv().ok(); let config = Config { database_url: env::var("DATABASE_URL") .unwrap_or_else(|_| "postgresql://readur:readur@localhost/readur".to_string()), server_address: { // Support both SERVER_ADDRESS (full address) and SERVER_PORT (just port) if let Ok(addr) = env::var("SERVER_ADDRESS") { addr } else { let host = env::var("SERVER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string()); let port = env::var("SERVER_PORT").unwrap_or_else(|_| "8000".to_string()); format!("{}:{}", host, port) } }, jwt_secret: env::var("JWT_SECRET") .unwrap_or_else(|_| "your-secret-key".to_string()), upload_path: env::var("UPLOAD_PATH") .unwrap_or_else(|_| "./uploads".to_string()), watch_folder: env::var("WATCH_FOLDER") .unwrap_or_else(|_| "./watch".to_string()), allowed_file_types: env::var("ALLOWED_FILE_TYPES") .unwrap_or_else(|_| "pdf,txt,doc,docx,png,jpg,jpeg".to_string()) .split(',') .map(|s| s.trim().to_lowercase()) .collect(), watch_interval_seconds: env::var("WATCH_INTERVAL_SECONDS") .ok() .and_then(|s| s.parse().ok()), file_stability_check_ms: env::var("FILE_STABILITY_CHECK_MS") .ok() .and_then(|s| s.parse().ok()), max_file_age_hours: env::var("MAX_FILE_AGE_HOURS") .ok() .and_then(|s| s.parse().ok()), // OCR Configuration ocr_language: env::var("OCR_LANGUAGE") .unwrap_or_else(|_| "eng".to_string()), concurrent_ocr_jobs: env::var("CONCURRENT_OCR_JOBS") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(4), ocr_timeout_seconds: env::var("OCR_TIMEOUT_SECONDS") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(300), max_file_size_mb: env::var("MAX_FILE_SIZE_MB") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(50), // Performance memory_limit_mb: env::var("MEMORY_LIMIT_MB") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(512), cpu_priority: env::var("CPU_PRIORITY") .unwrap_or_else(|_| "normal".to_string()), }; // Validate configuration to prevent recursion issues config.validate_paths()?; Ok(config) } fn validate_paths(&self) -> Result<()> { use std::path::Path; let upload_path = Path::new(&self.upload_path); let watch_path = Path::new(&self.watch_folder); // Normalize paths to handle relative paths and symlinks let upload_canonical = upload_path.canonicalize() .unwrap_or_else(|_| upload_path.to_path_buf()); let watch_canonical = watch_path.canonicalize() .unwrap_or_else(|_| watch_path.to_path_buf()); // Check if paths are the same if upload_canonical == watch_canonical { return Err(anyhow::anyhow!( "Configuration Error: UPLOAD_PATH and WATCH_FOLDER cannot be the same directory.\n\ This would cause infinite recursion where WebDAV files are downloaded to the upload \n\ directory and then immediately reprocessed by the watcher.\n\ Current config:\n\ - UPLOAD_PATH: {}\n\ - WATCH_FOLDER: {}\n\ Please set them to different directories.", self.upload_path, self.watch_folder )); } // Check if watch folder is inside upload folder if watch_canonical.starts_with(&upload_canonical) { return Err(anyhow::anyhow!( "Configuration Error: WATCH_FOLDER cannot be inside UPLOAD_PATH.\n\ This would cause recursion where WebDAV files downloaded to uploads are \n\ detected by the watcher as new files.\n\ Current config:\n\ - UPLOAD_PATH: {}\n\ - WATCH_FOLDER: {}\n\ Please move the watch folder outside the upload directory.", self.upload_path, self.watch_folder )); } // Check if upload folder is inside watch folder if upload_canonical.starts_with(&watch_canonical) { return Err(anyhow::anyhow!( "Configuration Error: UPLOAD_PATH cannot be inside WATCH_FOLDER.\n\ This would cause recursion where files from the watch folder are \n\ copied to uploads (inside the watch folder) and reprocessed.\n\ Current config:\n\ - UPLOAD_PATH: {}\n\ - WATCH_FOLDER: {}\n\ Please move the upload directory outside the watch folder.", self.upload_path, self.watch_folder )); } Ok(()) } }