Readur/src/config.rs

152 lines
6.0 KiB
Rust

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<String>,
pub watch_interval_seconds: Option<u64>,
pub file_stability_check_ms: Option<u64>,
pub max_file_age_hours: Option<u64>,
// 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<Self> {
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(())
}
}