use crate::application::services::SyncService;
use crate::dal::database::configuration_repository::ConfigurationRepository;
use crate::domain::configuration::ConfigFile;
use crate::{Database, MetisError, Result};
use diesel::{sqlite::SqliteConnection, Connection};
use std::path::Path;
pub struct ConfigurationRecoveryService;
impl ConfigurationRecoveryService {
pub fn recover_configuration<P: AsRef<Path>>(
workspace_dir: P,
db_path: P,
) -> Result<RecoveryReport> {
let workspace_dir = workspace_dir.as_ref();
let db_path = db_path.as_ref();
let config_file_path = workspace_dir.join("config.toml");
let mut report = RecoveryReport::new();
let config_file = if config_file_path.exists() {
tracing::info!("Loading configuration from {}", config_file_path.display());
ConfigFile::load(&config_file_path)
.map_err(|e| MetisError::ConfigurationError(e))?
} else {
tracing::warn!("config.toml not found, attempting migration from database");
report.config_file_created = true;
let config_file = Self::create_config_from_database(&config_file_path, db_path)?;
tracing::info!("Created config.toml from database at {}", config_file_path.display());
config_file
};
let mut config_repo = ConfigurationRepository::new(
SqliteConnection::establish(db_path.to_str().unwrap()).map_err(|e| {
MetisError::ConfigurationError(
crate::domain::configuration::ConfigurationError::InvalidValue(e.to_string()),
)
})?,
);
let db_prefix = config_repo.get_project_prefix()?;
if db_prefix.as_ref().map(|s| s.as_str()) != Some(config_file.prefix()) {
tracing::info!(
"Syncing prefix to database: {} -> {}",
db_prefix.as_deref().unwrap_or("none"),
config_file.prefix()
);
config_repo.set_project_prefix(config_file.prefix())?;
report.prefix_synced = true;
}
let db_flight_levels = config_repo.get_flight_level_config()?;
if &db_flight_levels != config_file.flight_levels() {
tracing::info!(
"Syncing flight levels to database: {} -> {}",
db_flight_levels.preset_name(),
config_file.flight_levels().preset_name()
);
config_repo.set_flight_level_config(config_file.flight_levels())?;
report.flight_levels_synced = true;
}
tracing::info!("Recovering short code counters from filesystem");
let counters = Self::recover_counters(workspace_dir, &mut config_repo)?;
report.counters_recovered = counters;
Ok(report)
}
pub fn sync_config_to_database<P: AsRef<Path>>(
workspace_dir: P,
db_path: P,
) -> Result<bool> {
let workspace_dir = workspace_dir.as_ref();
let db_path = db_path.as_ref();
let config_file_path = workspace_dir.join("config.toml");
if !config_file_path.exists() {
tracing::warn!("config.toml not found at {}, skipping config sync", config_file_path.display());
return Ok(false);
}
let config_file = ConfigFile::load(&config_file_path)
.map_err(|e| MetisError::ConfigurationError(e))?;
let mut config_repo = ConfigurationRepository::new(
SqliteConnection::establish(db_path.to_str().unwrap()).map_err(|e| {
MetisError::ConfigurationError(
crate::domain::configuration::ConfigurationError::InvalidValue(e.to_string()),
)
})?,
);
let mut synced = false;
let db_prefix = config_repo.get_project_prefix()?;
if db_prefix.as_ref().map(|s| s.as_str()) != Some(config_file.prefix()) {
config_repo.set_project_prefix(config_file.prefix())?;
synced = true;
}
let db_flight_levels = config_repo.get_flight_level_config()?;
if &db_flight_levels != config_file.flight_levels() {
config_repo.set_flight_level_config(config_file.flight_levels())?;
synced = true;
}
Ok(synced)
}
fn create_config_from_database(
config_file_path: &Path,
db_path: &Path,
) -> Result<ConfigFile> {
let mut config_repo = ConfigurationRepository::new(
SqliteConnection::establish(db_path.to_str().unwrap()).map_err(|e| {
MetisError::ConfigurationError(
crate::domain::configuration::ConfigurationError::InvalidValue(e.to_string()),
)
})?,
);
let prefix = config_repo.get_project_prefix()?.unwrap_or_else(|| "PROJ".to_string());
let flight_levels = config_repo.get_flight_level_config()?;
let config_file = ConfigFile::new(prefix, flight_levels)
.map_err(|e| MetisError::ConfigurationError(e))?;
config_file
.save(config_file_path)
.map_err(|e| MetisError::ConfigurationError(e))?;
Ok(config_file)
}
fn recover_counters(
workspace_dir: &Path,
config_repo: &mut ConfigurationRepository,
) -> Result<usize> {
use crate::application::services::database::DatabaseService;
let db = Database::new(workspace_dir.join("metis.db").to_str().unwrap())
.map_err(|e| MetisError::FileSystem(e.to_string()))?;
let mut db_service = DatabaseService::new(db.into_repository());
let sync_service = SyncService::new(&mut db_service);
let counters = sync_service.recover_counters_from_filesystem(workspace_dir)?;
let mut recovered_count = 0;
for (doc_type, max_counter) in counters {
if config_repo.set_counter_if_lower(&doc_type, max_counter)? {
recovered_count += 1;
}
}
tracing::info!("Recovered {} counter(s)", recovered_count);
Ok(recovered_count)
}
pub fn needs_recovery(workspace_dir: &Path) -> bool {
let db_path = workspace_dir.join("metis.db");
if !db_path.exists() {
tracing::warn!("Database not found at {}, recovery needed", db_path.display());
return true;
}
match Database::new(db_path.to_str().unwrap()) {
Ok(_) => false, Err(e) => {
tracing::error!("Database corrupt or unreadable: {}, recovery needed", e);
true
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RecoveryReport {
pub config_file_created: bool,
pub prefix_synced: bool,
pub flight_levels_synced: bool,
pub counters_recovered: usize,
}
impl RecoveryReport {
fn new() -> Self {
Self::default()
}
pub fn had_recovery_actions(&self) -> bool {
self.config_file_created
|| self.prefix_synced
|| self.flight_levels_synced
|| self.counters_recovered > 0
}
}