Readur/src/config.rs

839 lines
38 KiB
Rust

use anyhow::Result;
use std::env;
use crate::models::S3SourceConfig;
#[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 user_watch_base_dir: String,
pub enable_per_user_watch: bool,
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,
// OIDC Configuration
pub oidc_enabled: bool,
pub oidc_client_id: Option<String>,
pub oidc_client_secret: Option<String>,
pub oidc_issuer_url: Option<String>,
pub oidc_redirect_uri: Option<String>,
pub oidc_auto_register: Option<bool>,
// Authentication Configuration
pub allow_local_auth: Option<bool>,
// S3 Configuration
pub s3_enabled: bool,
pub s3_config: Option<S3SourceConfig>,
}
impl Config {
pub fn from_env() -> Result<Self> {
// Load .env file if present
match dotenvy::dotenv() {
Ok(path) => println!("🔧 Loaded environment variables from: {}", path.display()),
Err(_) => println!("🔧 No .env file found, using system environment variables"),
}
// Log all environment variable loading with detailed information
println!("\n📋 CONFIGURATION LOADING:");
println!("{}", "=".repeat(50));
// Database Configuration
let database_url = match env::var("DATABASE_URL") {
Ok(val) => {
println!("✅ DATABASE_URL: configured (loaded from env)");
val
}
Err(_) => {
// Try to construct from individual env vars
let db_host = env::var("POSTGRES_HOST").unwrap_or_else(|_| "localhost".to_string());
let db_port = env::var("POSTGRES_PORT").unwrap_or_else(|_| "5432".to_string());
let db_name = env::var("POSTGRES_DB").unwrap_or_else(|_| "readur".to_string());
let db_user = env::var("POSTGRES_USER").unwrap_or_else(|_| "readur".to_string());
let db_password = env::var("POSTGRES_PASSWORD").unwrap_or_else(|_| "readur".to_string());
// Check if any individual env vars were set
let using_individual_vars = env::var("POSTGRES_HOST").is_ok()
|| env::var("POSTGRES_PORT").is_ok()
|| env::var("POSTGRES_DB").is_ok()
|| env::var("POSTGRES_USER").is_ok()
|| env::var("POSTGRES_PASSWORD").is_ok();
if using_individual_vars {
println!("📊 Constructing DATABASE_URL from individual environment variables:");
if env::var("POSTGRES_HOST").is_ok() {
println!(" ✅ POSTGRES_HOST: {}", db_host);
} else {
println!(" ⚠️ POSTGRES_HOST: {} (using default)", db_host);
}
if env::var("POSTGRES_PORT").is_ok() {
println!(" ✅ POSTGRES_PORT: {}", db_port);
} else {
println!(" ⚠️ POSTGRES_PORT: {} (using default)", db_port);
}
if env::var("POSTGRES_DB").is_ok() {
println!(" ✅ POSTGRES_DB: {}", db_name);
} else {
println!(" ⚠️ POSTGRES_DB: {} (using default)", db_name);
}
if env::var("POSTGRES_USER").is_ok() {
println!(" ✅ POSTGRES_USER: {}", db_user);
} else {
println!(" ⚠️ POSTGRES_USER: {} (using default)", db_user);
}
if env::var("POSTGRES_PASSWORD").is_ok() {
println!(" ✅ POSTGRES_PASSWORD: ***hidden*** ({} chars)", db_password.len());
} else {
println!(" ⚠️ POSTGRES_PASSWORD: using default");
}
}
let constructed_url = format!(
"postgresql://{}:{}@{}:{}/{}",
db_user, db_password, db_host, db_port, db_name
);
if using_individual_vars {
println!("🔗 Constructed DATABASE_URL: postgresql://{}:***@{}:{}/{}",
db_user, db_host, db_port, db_name);
} else {
println!("⚠️ DATABASE_URL: postgresql://readur:***@localhost:5432/readur (using all defaults - no env vars set)");
}
constructed_url
}
};
let config = Config {
database_url,
server_address: {
// Support both SERVER_ADDRESS (full address) and SERVER_PORT (just port)
match env::var("SERVER_ADDRESS") {
Ok(addr) => {
println!("✅ SERVER_ADDRESS: {} (loaded from env)", addr);
addr
}
Err(_) => {
let host = match env::var("SERVER_HOST") {
Ok(h) => {
println!("✅ SERVER_HOST: {} (loaded from env)", h);
h
}
Err(_) => {
let default_host = "0.0.0.0".to_string();
println!("⚠️ SERVER_HOST: {} (using default - env var not set)", default_host);
default_host
}
};
let port = match env::var("SERVER_PORT") {
Ok(p) => {
println!("✅ SERVER_PORT: {} (loaded from env)", p);
p
}
Err(_) => {
let default_port = "8000".to_string();
println!("⚠️ SERVER_PORT: {} (using default - env var not set)", default_port);
default_port
}
};
let combined_address = format!("{}:{}", host, port);
println!("🔗 Combined server_address: {}", combined_address);
combined_address
}
}
},
jwt_secret: match env::var("JWT_SECRET") {
Ok(secret) => {
if secret == "your-secret-key" {
println!("⚠️ JWT_SECRET: Using default value (SECURITY RISK in production!)");
} else {
println!("✅ JWT_SECRET: ***hidden*** (loaded from env, {} chars)", secret.len());
}
secret
}
Err(_) => {
let default_secret = "your-secret-key".to_string();
println!("⚠️ JWT_SECRET: Using default value (SECURITY RISK - env var not set!)");
default_secret
}
},
upload_path: match env::var("UPLOAD_PATH") {
Ok(path) => {
println!("✅ UPLOAD_PATH: {} (loaded from env)", path);
path
}
Err(_) => {
let default_path = "./uploads".to_string();
println!("⚠️ UPLOAD_PATH: {} (using default - env var not set)", default_path);
default_path
}
},
watch_folder: match env::var("WATCH_FOLDER") {
Ok(folder) => {
println!("✅ WATCH_FOLDER: {} (loaded from env)", folder);
folder
}
Err(_) => {
let default_folder = "./watch".to_string();
println!("⚠️ WATCH_FOLDER: {} (using default - env var not set)", default_folder);
default_folder
}
},
user_watch_base_dir: match env::var("USER_WATCH_BASE_DIR") {
Ok(dir) => {
println!("✅ USER_WATCH_BASE_DIR: {} (loaded from env)", dir);
dir
}
Err(_) => {
let default_dir = "./user_watch".to_string();
println!("⚠️ USER_WATCH_BASE_DIR: {} (using default - env var not set)", default_dir);
default_dir
}
},
enable_per_user_watch: match env::var("ENABLE_PER_USER_WATCH") {
Ok(val) => match val.to_lowercase().as_str() {
"true" | "1" | "yes" | "on" => {
println!("✅ ENABLE_PER_USER_WATCH: true (loaded from env)");
true
}
_ => {
println!("✅ ENABLE_PER_USER_WATCH: false (loaded from env)");
false
}
},
Err(_) => {
println!("⚠️ ENABLE_PER_USER_WATCH: false (using default - env var not set)");
false
}
},
allowed_file_types: {
let file_types_str = match env::var("ALLOWED_FILE_TYPES") {
Ok(types) => {
println!("✅ ALLOWED_FILE_TYPES: {} (loaded from env)", types);
types
}
Err(_) => {
let default_types = "pdf,txt,doc,docx,png,jpg,jpeg".to_string();
println!("⚠️ ALLOWED_FILE_TYPES: {} (using default - env var not set)", default_types);
default_types
}
};
let types_vec: Vec<String> = file_types_str
.split(',')
.map(|s| s.trim().to_lowercase())
.collect();
println!("📄 Parsed file types: {:?}", types_vec);
types_vec
},
// Watcher Configuration
watch_interval_seconds: {
match env::var("WATCH_INTERVAL_SECONDS") {
Ok(val) => match val.parse::<u64>() {
Ok(parsed) => {
println!("✅ WATCH_INTERVAL_SECONDS: {} (loaded from env)", parsed);
Some(parsed)
}
Err(e) => {
println!("❌ WATCH_INTERVAL_SECONDS: Invalid value '{}' - {}, using default", val, e);
None
}
},
Err(_) => {
println!("⚠️ WATCH_INTERVAL_SECONDS: Not set, using default behavior");
None
}
}
},
file_stability_check_ms: {
match env::var("FILE_STABILITY_CHECK_MS") {
Ok(val) => match val.parse::<u64>() {
Ok(parsed) => {
println!("✅ FILE_STABILITY_CHECK_MS: {} (loaded from env)", parsed);
Some(parsed)
}
Err(e) => {
println!("❌ FILE_STABILITY_CHECK_MS: Invalid value '{}' - {}, using default", val, e);
None
}
},
Err(_) => {
println!("⚠️ FILE_STABILITY_CHECK_MS: Not set, using default behavior");
None
}
}
},
max_file_age_hours: {
match env::var("MAX_FILE_AGE_HOURS") {
Ok(val) => match val.parse::<u64>() {
Ok(parsed) => {
println!("✅ MAX_FILE_AGE_HOURS: {} (loaded from env)", parsed);
Some(parsed)
}
Err(e) => {
println!("❌ MAX_FILE_AGE_HOURS: Invalid value '{}' - {}, using unlimited", val, e);
None
}
},
Err(_) => {
println!("⚠️ MAX_FILE_AGE_HOURS: Not set, files will not expire");
None
}
}
},
// OCR Configuration
ocr_language: match env::var("OCR_LANGUAGE") {
Ok(lang) => {
println!("✅ OCR_LANGUAGE: {} (loaded from env)", lang);
lang
}
Err(_) => {
let default_lang = "eng".to_string();
println!("⚠️ OCR_LANGUAGE: {} (using default - env var not set)", default_lang);
default_lang
}
},
concurrent_ocr_jobs: {
match env::var("CONCURRENT_OCR_JOBS") {
Ok(val) => match val.parse::<usize>() {
Ok(parsed) => {
println!("✅ CONCURRENT_OCR_JOBS: {} (loaded from env)", parsed);
parsed
}
Err(e) => {
let default_jobs = 4;
println!("❌ CONCURRENT_OCR_JOBS: Invalid value '{}' - {}, using default {}", val, e, default_jobs);
default_jobs
}
},
Err(_) => {
let default_jobs = 4;
println!("⚠️ CONCURRENT_OCR_JOBS: {} (using default - env var not set)", default_jobs);
default_jobs
}
}
},
ocr_timeout_seconds: {
match env::var("OCR_TIMEOUT_SECONDS") {
Ok(val) => match val.parse::<u64>() {
Ok(parsed) => {
println!("✅ OCR_TIMEOUT_SECONDS: {} (loaded from env)", parsed);
parsed
}
Err(e) => {
let default_timeout = 300;
println!("❌ OCR_TIMEOUT_SECONDS: Invalid value '{}' - {}, using default {}", val, e, default_timeout);
default_timeout
}
},
Err(_) => {
let default_timeout = 300;
println!("⚠️ OCR_TIMEOUT_SECONDS: {} (using default - env var not set)", default_timeout);
default_timeout
}
}
},
max_file_size_mb: {
match env::var("MAX_FILE_SIZE_MB") {
Ok(val) => match val.parse::<u64>() {
Ok(parsed) => {
println!("✅ MAX_FILE_SIZE_MB: {} (loaded from env)", parsed);
parsed
}
Err(e) => {
let default_size = 50;
println!("❌ MAX_FILE_SIZE_MB: Invalid value '{}' - {}, using default {}", val, e, default_size);
default_size
}
},
Err(_) => {
let default_size = 50;
println!("⚠️ MAX_FILE_SIZE_MB: {} (using default - env var not set)", default_size);
default_size
}
}
},
// Performance Configuration
memory_limit_mb: {
match env::var("MEMORY_LIMIT_MB") {
Ok(val) => match val.parse::<usize>() {
Ok(parsed) => {
println!("✅ MEMORY_LIMIT_MB: {} (loaded from env)", parsed);
parsed
}
Err(e) => {
let default_memory = 512;
println!("❌ MEMORY_LIMIT_MB: Invalid value '{}' - {}, using default {}", val, e, default_memory);
default_memory
}
},
Err(_) => {
let default_memory = 512;
println!("⚠️ MEMORY_LIMIT_MB: {} (using default - env var not set)", default_memory);
default_memory
}
}
},
cpu_priority: match env::var("CPU_PRIORITY") {
Ok(priority) => {
println!("✅ CPU_PRIORITY: {} (loaded from env)", priority);
priority
}
Err(_) => {
let default_priority = "normal".to_string();
println!("⚠️ CPU_PRIORITY: {} (using default - env var not set)", default_priority);
default_priority
}
},
// OIDC Configuration
oidc_enabled: match env::var("OIDC_ENABLED") {
Ok(val) => match val.to_lowercase().as_str() {
"true" | "1" | "yes" | "on" => {
println!("✅ OIDC_ENABLED: true (loaded from env)");
true
}
_ => {
println!("✅ OIDC_ENABLED: false (loaded from env)");
false
}
},
Err(_) => {
println!("⚠️ OIDC_ENABLED: false (using default - env var not set)");
false
}
},
oidc_client_id: match env::var("OIDC_CLIENT_ID") {
Ok(client_id) => {
println!("✅ OIDC_CLIENT_ID: {} (loaded from env)", client_id);
Some(client_id)
}
Err(_) => {
println!("⚠️ OIDC_CLIENT_ID: Not set");
None
}
},
oidc_client_secret: match env::var("OIDC_CLIENT_SECRET") {
Ok(secret) => {
println!("✅ OIDC_CLIENT_SECRET: ***hidden*** (loaded from env, {} chars)", secret.len());
Some(secret)
}
Err(_) => {
println!("⚠️ OIDC_CLIENT_SECRET: Not set");
None
}
},
oidc_issuer_url: match env::var("OIDC_ISSUER_URL") {
Ok(url) => {
println!("✅ OIDC_ISSUER_URL: {} (loaded from env)", url);
Some(url)
}
Err(_) => {
println!("⚠️ OIDC_ISSUER_URL: Not set");
None
}
},
oidc_redirect_uri: match env::var("OIDC_REDIRECT_URI") {
Ok(uri) => {
println!("✅ OIDC_REDIRECT_URI: {} (loaded from env)", uri);
Some(uri)
}
Err(_) => {
println!("⚠️ OIDC_REDIRECT_URI: Not set");
None
}
},
oidc_auto_register: match env::var("OIDC_AUTO_REGISTER") {
Ok(val) => match val.to_lowercase().as_str() {
"true" | "1" | "yes" | "on" => {
println!("✅ OIDC_AUTO_REGISTER: true (loaded from env)");
Some(true)
}
"false" | "0" | "no" | "off" => {
println!("✅ OIDC_AUTO_REGISTER: false (loaded from env)");
Some(false)
}
_ => {
println!("⚠️ OIDC_AUTO_REGISTER: Invalid value '{}', using default (false)", val);
None
}
},
Err(_) => {
println!("⚠️ OIDC_AUTO_REGISTER: Not set, will use default (false)");
None
}
},
// Authentication Configuration
allow_local_auth: match env::var("ALLOW_LOCAL_AUTH") {
Ok(val) => match val.to_lowercase().as_str() {
"true" | "1" | "yes" | "on" => {
println!("✅ ALLOW_LOCAL_AUTH: true (loaded from env)");
Some(true)
}
"false" | "0" | "no" | "off" => {
println!("✅ ALLOW_LOCAL_AUTH: false (loaded from env)");
Some(false)
}
_ => {
println!("⚠️ ALLOW_LOCAL_AUTH: Invalid value '{}', using default (true)", val);
None
}
},
Err(_) => {
println!("⚠️ ALLOW_LOCAL_AUTH: Not set, will use default (true)");
None
}
},
// S3 Configuration
s3_enabled: match env::var("S3_ENABLED") {
Ok(val) => {
let enabled = val.to_lowercase() == "true";
println!("✅ S3_ENABLED: {} (loaded from env)", enabled);
enabled
}
Err(_) => {
println!("⚠️ S3_ENABLED: false (using default - env var not set)");
false
}
},
s3_config: if env::var("S3_ENABLED").unwrap_or_default().to_lowercase() == "true" {
// Only load S3 config if S3 is enabled
let bucket_name = env::var("S3_BUCKET_NAME").unwrap_or_default();
let region = env::var("S3_REGION").unwrap_or_else(|_| "us-east-1".to_string());
let access_key_id = env::var("S3_ACCESS_KEY_ID").unwrap_or_default();
let secret_access_key = env::var("S3_SECRET_ACCESS_KEY").unwrap_or_default();
let endpoint_url = env::var("S3_ENDPOINT_URL").ok();
let prefix = env::var("S3_PREFIX").ok();
if !bucket_name.is_empty() && !access_key_id.is_empty() && !secret_access_key.is_empty() {
println!("✅ S3_BUCKET_NAME: {} (loaded from env)", bucket_name);
println!("✅ S3_REGION: {} (loaded from env)", region);
println!("✅ S3_ACCESS_KEY_ID: configured (loaded from env)");
println!("✅ S3_SECRET_ACCESS_KEY: configured (loaded from env)");
if let Some(ref endpoint) = endpoint_url {
println!("✅ S3_ENDPOINT_URL: {} (loaded from env)", endpoint);
}
if let Some(ref pref) = prefix {
println!("✅ S3_PREFIX: {} (loaded from env)", pref);
}
Some(S3SourceConfig {
bucket_name,
region,
access_key_id,
secret_access_key,
endpoint_url,
prefix,
watch_folders: vec![], // Will be configured separately for sources
file_extensions: vec![], // Will be configured separately for sources
auto_sync: false, // Not used for general storage
sync_interval_minutes: 0, // Not used for general storage
})
} else {
println!("❌ S3 enabled but missing required configuration (bucket_name, access_key_id, or secret_access_key)");
None
}
} else {
None
},
};
println!("\n🔍 CONFIGURATION VALIDATION:");
println!("{}", "=".repeat(50));
// Validate server address format
if !config.server_address.contains(':') {
println!("❌ SERVER_ADDRESS: Invalid format '{}' - missing port", config.server_address);
return Err(anyhow::anyhow!(
"Invalid server address format: '{}'. Expected format: 'host:port' (e.g., '0.0.0.0:8000')",
config.server_address
));
}
// Validate database URL format
if !config.database_url.starts_with("postgresql://") && !config.database_url.starts_with("postgres://") {
println!("❌ DATABASE_URL: Invalid format - must start with 'postgresql://' or 'postgres://'");
return Err(anyhow::anyhow!(
"Invalid database URL format. Must start with 'postgresql://' or 'postgres://'"
));
}
// Validate configuration to prevent recursion issues
println!("🔍 Validating directory paths for conflicts...");
config.validate_paths()?;
println!("\n📊 CONFIGURATION SUMMARY:");
println!("{}", "=".repeat(50));
println!("🌐 Server will bind to: {}", config.server_address);
println!("📁 Upload directory: {}", config.upload_path);
println!("👁️ Watch directory: {}", config.watch_folder);
println!("👥 Per-user watch enabled: {}", config.enable_per_user_watch);
if config.enable_per_user_watch {
println!("📂 User watch base directory: {}", config.user_watch_base_dir);
}
println!("📄 Allowed file types: {:?}", config.allowed_file_types);
println!("🧠 OCR language: {}", config.ocr_language);
println!("⚙️ Concurrent OCR jobs: {}", config.concurrent_ocr_jobs);
println!("⏱️ OCR timeout: {}s", config.ocr_timeout_seconds);
println!("📏 Max file size: {}MB", config.max_file_size_mb);
println!("💾 Memory limit: {}MB", config.memory_limit_mb);
// Warning checks
println!("\n⚠️ CONFIGURATION WARNINGS:");
println!("{}", "=".repeat(50));
if config.jwt_secret == "your-secret-key" {
println!("🚨 SECURITY WARNING: Using default JWT secret! Set JWT_SECRET environment variable in production!");
}
if config.server_address.starts_with("0.0.0.0") {
println!("🌍 INFO: Server will listen on all interfaces (0.0.0.0)");
}
if config.max_file_size_mb > 100 {
println!("📏 INFO: Large file size limit ({}MB) may impact performance", config.max_file_size_mb);
}
if config.concurrent_ocr_jobs > 8 {
println!("⚙️ INFO: High OCR concurrency ({}) may use significant CPU/memory", config.concurrent_ocr_jobs);
}
// OIDC validation
if config.oidc_enabled {
println!("🔐 OIDC is enabled");
println!("🔓 OIDC auto-registration: {}", config.oidc_auto_register.unwrap_or(false));
if config.oidc_client_id.is_none() {
println!("❌ OIDC_CLIENT_ID is required when OIDC is enabled");
}
if config.oidc_client_secret.is_none() {
println!("❌ OIDC_CLIENT_SECRET is required when OIDC is enabled");
}
if config.oidc_issuer_url.is_none() {
println!("❌ OIDC_ISSUER_URL is required when OIDC is enabled");
}
if config.oidc_redirect_uri.is_none() {
println!("❌ OIDC_REDIRECT_URI is required when OIDC is enabled");
}
} else {
println!("🔐 OIDC is disabled");
}
// Authentication method validation
let allow_local_auth = config.allow_local_auth.unwrap_or(true);
println!("🔑 Local authentication (username/password): {}",
if allow_local_auth { "enabled" } else { "disabled" });
if !config.oidc_enabled && !allow_local_auth {
println!("❌ WARNING: Both OIDC and local authentication are disabled!");
println!(" You will not be able to log in. Enable at least one authentication method.");
return Err(anyhow::anyhow!(
"Invalid authentication configuration: Both OIDC and local auth are disabled. \
Enable at least one authentication method (OIDC_ENABLED=true or ALLOW_LOCAL_AUTH=true)"
));
}
println!("✅ Configuration validation completed successfully!\n");
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);
let user_watch_path = Path::new(&self.user_watch_base_dir);
println!("📁 Checking upload directory: {}", self.upload_path);
println!("👁️ Checking watch directory: {}", self.watch_folder);
if self.enable_per_user_watch {
println!("👥 Checking user watch base directory: {}", self.user_watch_base_dir);
}
// Check if paths exist and are accessible
if !upload_path.exists() {
println!("⚠️ Upload directory does not exist yet: {}", self.upload_path);
} else if !upload_path.is_dir() {
println!("❌ Upload path exists but is not a directory: {}", self.upload_path);
return Err(anyhow::anyhow!(
"Upload path '{}' exists but is not a directory", self.upload_path
));
} else {
println!("✅ Upload directory exists and is accessible");
}
if !watch_path.exists() {
println!("⚠️ Watch directory does not exist yet: {}", self.watch_folder);
} else if !watch_path.is_dir() {
println!("❌ Watch path exists but is not a directory: {}", self.watch_folder);
return Err(anyhow::anyhow!(
"Watch folder '{}' exists but is not a directory", self.watch_folder
));
} else {
println!("✅ Watch directory exists and is accessible");
}
if self.enable_per_user_watch {
if !user_watch_path.exists() {
println!("⚠️ User watch base directory does not exist yet: {}", self.user_watch_base_dir);
} else if !user_watch_path.is_dir() {
println!("❌ User watch base path exists but is not a directory: {}", self.user_watch_base_dir);
return Err(anyhow::anyhow!(
"User watch base directory '{}' exists but is not a directory", self.user_watch_base_dir
));
} else {
println!("✅ User watch base directory exists and is accessible");
}
}
// Normalize paths to handle relative paths and symlinks
let upload_canonical = upload_path.canonicalize()
.unwrap_or_else(|_| {
println!("⚠️ Could not canonicalize upload path, using as-is");
upload_path.to_path_buf()
});
let watch_canonical = watch_path.canonicalize()
.unwrap_or_else(|_| {
println!("⚠️ Could not canonicalize watch path, using as-is");
watch_path.to_path_buf()
});
let user_watch_canonical = if self.enable_per_user_watch {
Some(user_watch_path.canonicalize()
.unwrap_or_else(|_| {
println!("⚠️ Could not canonicalize user watch path, using as-is");
user_watch_path.to_path_buf()
}))
} else {
None
};
println!("📍 Canonical upload path: {}", upload_canonical.display());
println!("📍 Canonical watch path: {}", watch_canonical.display());
if let Some(ref user_watch) = user_watch_canonical {
println!("📍 Canonical user watch path: {}", user_watch.display());
}
// Check if paths are the same
if upload_canonical == watch_canonical {
println!("❌ CRITICAL ERROR: Upload and watch directories are the same!");
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) {
println!("❌ CRITICAL ERROR: Watch folder is inside upload directory!");
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) {
println!("❌ CRITICAL ERROR: Upload directory is inside watch folder!");
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
));
}
// Additional validation for user watch directory if enabled
if let Some(ref user_watch) = user_watch_canonical {
// Check if user watch is same as upload or watch
if user_watch == &upload_canonical {
println!("❌ CRITICAL ERROR: User watch base directory is same as upload directory!");
return Err(anyhow::anyhow!(
"❌ Configuration Error: USER_WATCH_BASE_DIR cannot be the same as UPLOAD_PATH.\n\
Current config:\n\
- UPLOAD_PATH: {}\n\
- USER_WATCH_BASE_DIR: {}\n\
Please set them to different directories.",
self.upload_path, self.user_watch_base_dir
));
}
if user_watch == &watch_canonical {
println!("❌ CRITICAL ERROR: User watch base directory is same as global watch directory!");
return Err(anyhow::anyhow!(
"❌ Configuration Error: USER_WATCH_BASE_DIR cannot be the same as WATCH_FOLDER.\n\
Current config:\n\
- WATCH_FOLDER: {}\n\
- USER_WATCH_BASE_DIR: {}\n\
Please set them to different directories.",
self.watch_folder, self.user_watch_base_dir
));
}
// Check if user watch is inside upload or vice versa
if user_watch.starts_with(&upload_canonical) {
println!("❌ CRITICAL ERROR: User watch base directory is inside upload directory!");
return Err(anyhow::anyhow!(
"❌ Configuration Error: USER_WATCH_BASE_DIR cannot be inside UPLOAD_PATH.\n\
This would cause recursion issues.\n\
Current config:\n\
- UPLOAD_PATH: {}\n\
- USER_WATCH_BASE_DIR: {}\n\
Please move the user watch directory outside the upload directory.",
self.upload_path, self.user_watch_base_dir
));
}
if upload_canonical.starts_with(user_watch) {
println!("❌ CRITICAL ERROR: Upload directory is inside user watch base directory!");
return Err(anyhow::anyhow!(
"❌ Configuration Error: UPLOAD_PATH cannot be inside USER_WATCH_BASE_DIR.\n\
This would cause recursion issues.\n\
Current config:\n\
- UPLOAD_PATH: {}\n\
- USER_WATCH_BASE_DIR: {}\n\
Please move the upload directory outside the user watch directory.",
self.upload_path, self.user_watch_base_dir
));
}
}
println!("✅ Directory path validation passed - no conflicts detected");
Ok(())
}
}