can_viewer/
config.rs

1//! Session configuration persistence.
2//!
3//! Saves and loads user session settings like the last used DBC file.
4
5use serde::{Deserialize, Serialize};
6use std::fs;
7use std::path::PathBuf;
8
9/// Embedded sample files for first run
10const SAMPLE_MF4: &[u8] = include_bytes!("../examples/sample.mf4");
11const SAMPLE_DBC: &[u8] = include_bytes!("../examples/sample.dbc");
12
13/// Session configuration that persists across app restarts.
14#[derive(Debug, Clone, Default, Serialize, Deserialize)]
15pub struct SessionConfig {
16    /// Path to the last loaded DBC file.
17    pub dbc_path: Option<String>,
18    /// Path to the last loaded MDF4 file.
19    pub mdf4_path: Option<String>,
20    /// Whether first-run setup has been completed.
21    #[serde(default)]
22    pub setup_complete: bool,
23}
24
25impl SessionConfig {
26    /// Get the config directory for the application.
27    pub fn config_dir() -> Option<PathBuf> {
28        dirs::config_dir().map(|p| p.join("can-viewer"))
29    }
30
31    /// Get the config file path for the application.
32    pub fn config_path() -> Option<PathBuf> {
33        Self::config_dir().map(|p| p.join("session.json"))
34    }
35
36    /// Load session config from disk, running first-time setup if needed.
37    pub fn load() -> Self {
38        // Load existing config or create default
39        let mut config: Self = Self::config_path()
40            .and_then(|path| fs::read_to_string(&path).ok())
41            .and_then(|content| serde_json::from_str(&content).ok())
42            .unwrap_or_default();
43
44        // First run: extract samples and set as defaults
45        if !config.setup_complete {
46            if let Some(config_dir) = Self::config_dir() {
47                if let Err(e) = fs::create_dir_all(&config_dir) {
48                    log::warn!("Failed to create config directory: {}", e);
49                }
50
51                let mf4_path = config_dir.join("sample.mf4");
52                let dbc_path = config_dir.join("sample.dbc");
53
54                // Extract sample files
55                if let Err(e) = fs::write(&mf4_path, SAMPLE_MF4) {
56                    log::warn!("Failed to extract sample MF4: {}", e);
57                }
58                if let Err(e) = fs::write(&dbc_path, SAMPLE_DBC) {
59                    log::warn!("Failed to extract sample DBC: {}", e);
60                }
61
62                // Set as defaults
63                config.mdf4_path = Some(mf4_path.to_string_lossy().into_owned());
64                config.dbc_path = Some(dbc_path.to_string_lossy().into_owned());
65                config.setup_complete = true;
66                if let Err(e) = config.save() {
67                    log::warn!("Failed to save initial config: {}", e);
68                }
69            }
70        }
71
72        config
73    }
74
75    /// Save session config to disk.
76    pub fn save(&self) -> Result<(), String> {
77        let path = Self::config_path().ok_or("Could not determine config directory")?;
78
79        // Create config directory if it doesn't exist
80        if let Some(parent) = path.parent() {
81            fs::create_dir_all(parent)
82                .map_err(|e| format!("Failed to create config directory: {}", e))?;
83        }
84
85        let content = serde_json::to_string_pretty(self)
86            .map_err(|e| format!("Failed to serialize: {}", e))?;
87
88        fs::write(&path, content).map_err(|e| format!("Failed to write config: {}", e))?;
89
90        Ok(())
91    }
92
93    /// Update DBC path and save.
94    pub fn set_dbc_path(&mut self, path: Option<String>) -> Result<(), String> {
95        self.dbc_path = path;
96        self.save()
97    }
98
99    /// Update MDF4 path and save.
100    pub fn set_mdf4_path(&mut self, path: Option<String>) -> Result<(), String> {
101        self.mdf4_path = path;
102        self.save()
103    }
104}