use std::path::PathBuf;
use std::sync::Arc;
use arc_swap::ArcSwap;
use tracing::info;
use crate::nzb_core::config::AppConfig;
use crate::nzb_core::db::Database;
use crate::auth::{CredentialStore, TokenStore};
use crate::log_buffer::LogBuffer;
use crate::queue_manager::QueueManager;
use crate::state::AppState;
pub struct StartupConfig {
pub config_path: PathBuf,
pub listen_addr: Option<String>,
pub port: Option<u16>,
pub data_dir: Option<PathBuf>,
pub log_level: Option<String>,
}
pub struct StartupResult {
pub state: Arc<AppState>,
pub queue_manager: Arc<QueueManager>,
pub log_buffer: LogBuffer,
}
pub async fn initialize(
startup: StartupConfig,
log_buffer: Option<LogBuffer>,
) -> anyhow::Result<StartupResult> {
let config_path = startup.config_path;
let mut config = AppConfig::load(&config_path)?;
if let Some(addr) = startup.listen_addr {
config.general.listen_addr = addr;
}
if let Some(port) = startup.port {
config.general.port = port;
}
if let Some(data_dir) = startup.data_dir {
config.general.data_dir = data_dir;
}
if let Ok(val) = std::env::var("OTEL_ENABLED") {
config.otel.enabled = val == "true" || val == "1";
}
if let Ok(val) = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT") {
config.otel.endpoint = val;
}
if let Ok(val) = std::env::var("OTEL_SERVICE_NAME") {
config.otel.service_name = val;
}
std::fs::create_dir_all(&config.general.data_dir)?;
std::fs::create_dir_all(&config.general.incomplete_dir)?;
std::fs::create_dir_all(&config.general.complete_dir)?;
let db_path = config.general.data_dir.join("rustnzb.db");
let db = Database::open(&db_path)?;
info!(path = %db_path.display(), "Database opened");
let log_buffer = log_buffer.unwrap_or_default();
let queue_manager = QueueManager::new(
config.servers.clone(),
db,
config.general.incomplete_dir.clone(),
config.general.complete_dir.clone(),
log_buffer.clone(),
config.general.max_active_downloads,
config.categories.clone(),
config.general.min_free_space_bytes,
config.general.speed_limit_bps,
config.general.direct_unpack,
config.general.abort_hopeless,
config.general.early_failure_check,
config.general.required_completion_pct,
config.general.article_timeout_secs,
Some(crate::ServerProbePolicy::default()),
);
if let Some(retention) = config.general.history_retention {
queue_manager.set_history_retention(Some(retention));
}
if let Err(e) = queue_manager.restore_from_db() {
tracing::warn!("Failed to restore queue from database: {e}");
}
queue_manager.spawn_speed_tracker();
info!(servers = config.servers.len(), "Queue manager initialized");
if let Some(ref watch_dir) = config.general.watch_dir {
let watcher =
crate::dir_watcher::DirWatcher::new(watch_dir.clone(), Arc::clone(&queue_manager));
tokio::spawn(async move { watcher.run().await });
info!(dir = %watch_dir.display(), "Directory watcher started");
}
let credential_store = Arc::new(CredentialStore::new(config.general.data_dir.clone()));
let token_store = Arc::new(TokenStore::new());
if credential_store.has_credentials() {
info!("Authentication enabled (credentials configured)");
} else {
info!("Authentication not yet configured; first-boot setup required");
}
let shared_config = Arc::new(ArcSwap::new(Arc::new(config)));
let data_dir_for_rss = shared_config.load().general.data_dir.clone();
let monitor = crate::rss_monitor::RssMonitor::new(
Arc::clone(&shared_config),
Arc::clone(&queue_manager),
data_dir_for_rss,
);
tokio::spawn(async move { monitor.run().await });
let state = Arc::new(AppState::new(
shared_config,
config_path,
Arc::clone(&queue_manager),
log_buffer.clone(),
token_store,
credential_store,
));
Ok(StartupResult {
state,
queue_manager,
log_buffer,
})
}