use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::core::sensor::{Band, Sensor, SensorRegistry};
use crate::error::{GlintError, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SensorConfigFile {
pub version: String,
pub sensors: Vec<SensorConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SensorConfig {
pub id: String,
pub name: String,
pub description: Option<String>,
pub bit_depth: u8,
pub loader_type: String,
pub bands: Vec<BandConfig>,
pub loader_config: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BandConfig {
pub name: String,
pub default_threshold: f64,
pub wavelength: Option<f64>,
pub description: Option<String>,
}
impl From<BandConfig> for Band {
fn from(config: BandConfig) -> Self {
Band {
name: config.name,
default_threshold: config.default_threshold,
wavelength: config.wavelength,
description: config.description,
}
}
}
impl From<Band> for BandConfig {
fn from(band: Band) -> Self {
BandConfig {
name: band.name,
default_threshold: band.default_threshold,
wavelength: band.wavelength,
description: band.description,
}
}
}
impl From<SensorConfig> for Sensor {
fn from(config: SensorConfig) -> Self {
let bands: Vec<Band> = config.bands.into_iter().map(Band::from).collect();
let mut sensor = Sensor::new(
config.id,
config.name,
bands,
config.bit_depth,
config.loader_type,
);
if let Some(description) = config.description {
sensor = sensor.with_description(description);
}
if let Some(loader_config) = config.loader_config {
for (key, value) in loader_config {
sensor = sensor.with_loader_config(key, value);
}
}
sensor
}
}
impl From<Sensor> for SensorConfig {
fn from(sensor: Sensor) -> Self {
let bands: Vec<BandConfig> = sensor.bands.into_iter().map(BandConfig::from).collect();
let loader_config = if sensor.loader_config.is_empty() {
None
} else {
Some(sensor.loader_config)
};
SensorConfig {
id: sensor.id,
name: sensor.name,
description: sensor.description,
bit_depth: sensor.bit_depth,
loader_type: sensor.loader_type,
bands,
loader_config,
}
}
}
#[derive(Debug)]
pub struct ConfigManager {
config_dir: Option<PathBuf>,
}
impl ConfigManager {
pub fn new() -> Self {
Self { config_dir: None }
}
pub fn with_config_dir(config_dir: PathBuf) -> Self {
Self {
config_dir: Some(config_dir),
}
}
pub fn default_config_dir() -> Result<PathBuf> {
if let Some(config_dir) = dirs::config_dir() {
Ok(config_dir.join("glint-mask-tools"))
} else {
Ok(PathBuf::from("."))
}
}
pub fn get_config_dir(&self) -> Result<PathBuf> {
if let Some(ref dir) = self.config_dir {
Ok(dir.clone())
} else {
Self::default_config_dir()
}
}
pub fn load_from_file(&self, path: &Path) -> Result<Vec<Sensor>> {
let content = std::fs::read_to_string(path).map_err(GlintError::Io)?;
let config_file: SensorConfigFile =
toml::from_str(&content).map_err(GlintError::Deserialization)?;
if config_file.version != "1.0" {
return Err(GlintError::config(format!(
"Unsupported configuration file version: {}",
config_file.version
)));
}
let mut sensors = Vec::new();
for sensor_config in config_file.sensors {
let sensor = Sensor::from(sensor_config);
sensor.validate()?;
sensors.push(sensor);
}
Ok(sensors)
}
pub fn save_to_file(&self, sensors: &[Sensor], path: &Path) -> Result<()> {
let sensor_configs: Vec<SensorConfig> = sensors
.iter()
.map(|s| SensorConfig::from(s.clone()))
.collect();
let config_file = SensorConfigFile {
version: "1.0".to_string(),
sensors: sensor_configs,
};
let content = toml::to_string_pretty(&config_file).map_err(GlintError::Serialization)?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(GlintError::Io)?;
}
std::fs::write(path, content).map_err(GlintError::Io)?;
Ok(())
}
pub fn load_default_config(&self) -> Result<Vec<Sensor>> {
let config_dir = self.get_config_dir()?;
let config_path = config_dir.join("sensors.toml");
if config_path.exists() {
self.load_from_file(&config_path)
} else {
Ok(Vec::new())
}
}
pub fn save_default_config(&self, sensors: &[Sensor]) -> Result<()> {
let config_dir = self.get_config_dir()?;
let config_path = config_dir.join("sensors.toml");
self.save_to_file(sensors, &config_path)
}
pub fn create_sample_config(&self, path: &Path) -> Result<()> {
let registry = crate::core::sensor::SensorRegistry::with_defaults();
let default_sensors: Vec<_> = registry.sensors().into_iter().cloned().collect();
self.save_to_file(&default_sensors, path)
}
pub fn load_into_registry(&self, registry: &mut SensorRegistry) -> Result<()> {
match self.load_default_config() {
Ok(sensors) => {
for sensor in sensors {
registry.register_sensor(sensor)?;
}
}
Err(GlintError::Io(_)) => {
}
Err(e) => return Err(e),
}
Ok(())
}
}
impl Default for ConfigManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::sensor::bands;
use tempfile::tempdir;
#[test]
fn test_sensor_config_conversion() {
let sensor = Sensor::new(
"test",
"Test Sensor",
vec![bands::red(), bands::green(), bands::blue()],
8,
"single_file",
)
.with_description("Test description");
let config = SensorConfig::from(sensor.clone());
let converted_sensor = Sensor::from(config);
assert_eq!(sensor.id, converted_sensor.id);
assert_eq!(sensor.name, converted_sensor.name);
assert_eq!(sensor.description, converted_sensor.description);
assert_eq!(sensor.bit_depth, converted_sensor.bit_depth);
assert_eq!(sensor.loader_type, converted_sensor.loader_type);
assert_eq!(sensor.bands.len(), converted_sensor.bands.len());
}
#[test]
fn test_config_file_save_load() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("sensors.toml");
let registry = crate::core::sensor::SensorRegistry::with_defaults();
let sensors = vec![
registry.get_sensor("rgb").unwrap().clone(),
registry.get_sensor("p4ms").unwrap().clone(),
];
let manager = ConfigManager::new();
manager.save_to_file(&sensors, &config_path).unwrap();
assert!(config_path.exists());
let loaded_sensors = manager.load_from_file(&config_path).unwrap();
assert_eq!(loaded_sensors.len(), 2);
assert_eq!(loaded_sensors[0].id, "rgb");
assert_eq!(loaded_sensors[1].id, "p4ms");
}
#[test]
fn test_config_manager_with_directory() {
let temp_dir = tempdir().unwrap();
let manager = ConfigManager::with_config_dir(temp_dir.path().to_path_buf());
let registry = crate::core::sensor::SensorRegistry::with_defaults();
let sensors = vec![registry.get_sensor("rgb").unwrap().clone()];
manager.save_default_config(&sensors).unwrap();
let loaded_sensors = manager.load_default_config().unwrap();
assert_eq!(loaded_sensors.len(), 1);
assert_eq!(loaded_sensors[0].id, "rgb");
}
#[test]
fn test_sample_config_creation() {
let temp_dir = tempdir().unwrap();
let sample_path = temp_dir.path().join("sample_sensors.toml");
let manager = ConfigManager::new();
manager.create_sample_config(&sample_path).unwrap();
assert!(sample_path.exists());
let sensors = manager.load_from_file(&sample_path).unwrap();
assert_eq!(sensors.len(), 6); }
#[test]
fn test_registry_integration() {
let temp_dir = tempdir().unwrap();
let manager = ConfigManager::with_config_dir(temp_dir.path().to_path_buf());
let registry = crate::core::sensor::SensorRegistry::with_defaults();
let sensors = vec![
registry.get_sensor("rgb").unwrap().clone(),
registry.get_sensor("m3m").unwrap().clone(),
];
manager.save_default_config(&sensors).unwrap();
let mut registry = SensorRegistry::new();
manager.load_into_registry(&mut registry).unwrap();
assert!(registry.has_sensor("rgb"));
assert!(registry.has_sensor("m3m"));
assert!(!registry.has_sensor("p4ms")); }
}