Skip to main content

cc_switch/config/
config_storage.rs

1use anyhow::{Context, Result};
2use std::fs;
3
4use crate::config::config::get_config_storage_path;
5use crate::config::types::{ConfigStorage, Configuration};
6
7impl ConfigStorage {
8    /// Load configurations from disk
9    ///
10    /// Reads the JSON file from `~/.claude/cc_auto_switch_setting.json`
11    /// Auto-migrates from old location `~/.cc-switch/configurations.json` if it exists
12    /// Returns default empty storage if file doesn't exist
13    ///
14    /// # Errors
15    /// Returns error if file exists but cannot be read or parsed
16    pub fn load() -> Result<Self> {
17        let new_path = get_config_storage_path()?;
18
19        // Check if the new file already exists
20        if new_path.exists() {
21            let content = fs::read_to_string(&new_path).with_context(|| {
22                format!(
23                    "Failed to read configuration storage from {}",
24                    new_path.display()
25                )
26            })?;
27
28            let storage: ConfigStorage = serde_json::from_str(&content)
29                .with_context(|| "Failed to parse configuration storage JSON")?;
30
31            return Ok(storage);
32        }
33
34        // No configuration file exists at new path, return default empty storage
35        Ok(ConfigStorage::default())
36    }
37
38    /// Save configurations to disk
39    ///
40    /// Writes the current state to `~/.claude/cc_auto_switch_setting.json`
41    /// Creates the directory structure if it doesn't exist
42    ///
43    /// # Errors
44    /// Returns error if directory cannot be created or file cannot be written
45    pub fn save(&self) -> Result<()> {
46        let path = get_config_storage_path()?;
47
48        // Create directory if it doesn't exist
49        if let Some(parent) = path.parent() {
50            fs::create_dir_all(parent)
51                .with_context(|| format!("Failed to create directory {}", parent.display()))?;
52        }
53
54        let json = serde_json::to_string_pretty(self)
55            .with_context(|| "Failed to serialize configuration storage")?;
56
57        fs::write(&path, json).with_context(|| format!("Failed to write to {}", path.display()))?;
58
59        Ok(())
60    }
61
62    /// Migrate configurations from old path to new path
63    ///
64    /// Old path: `~/.cc_auto_switch/configurations.json`
65    /// New path: `~/.claude/cc_auto_switch_setting.json`
66    ///
67    /// Safe to run multiple times. If old path does not exist, returns Ok(()) and prints a note.
68    pub fn migrate_from_old_path() -> Result<()> {
69        let new_path = get_config_storage_path()?;
70        let old_path = dirs::home_dir()
71            .map(|home| home.join(".cc_auto_switch").join("configurations.json"))
72            .ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
73
74        if !old_path.exists() {
75            println!("â„šī¸ No old configuration found at {}", old_path.display());
76            return Ok(());
77        }
78
79        println!("🔄 Migrating configuration from old location...");
80
81        let content = fs::read_to_string(&old_path).with_context(|| {
82            format!(
83                "Failed to read old configuration from {}",
84                old_path.display()
85            )
86        })?;
87
88        let storage: ConfigStorage = serde_json::from_str(&content)
89            .with_context(|| "Failed to parse old configuration storage JSON")?;
90
91        // Save to new location
92        storage
93            .save()
94            .with_context(|| "Failed to save migrated configuration to new location")?;
95
96        // Remove old directory
97        if let Some(parent) = old_path.parent() {
98            fs::remove_dir_all(parent).with_context(|| {
99                format!(
100                    "Failed to remove old configuration directory {}",
101                    parent.display()
102                )
103            })?;
104        }
105
106        println!(
107            "✅ Configuration migrated successfully to {}",
108            new_path.display()
109        );
110
111        Ok(())
112    }
113
114    /// Add a new configuration to storage
115    ///
116    /// # Arguments
117    /// * `config` - Configuration object to add
118    ///
119    /// Overwrites existing configuration with same alias
120    pub fn add_configuration(&mut self, config: Configuration) {
121        self.configurations
122            .insert(config.alias_name.clone(), config);
123    }
124
125    /// Remove a configuration by alias name
126    ///
127    /// # Arguments
128    /// * `alias_name` - Name of configuration to remove
129    ///
130    /// # Returns
131    /// `true` if configuration was found and removed, `false` if not found
132    pub fn remove_configuration(&mut self, alias_name: &str) -> bool {
133        self.configurations.remove(alias_name).is_some()
134    }
135
136    /// Get a configuration by alias name
137    ///
138    /// # Arguments
139    /// * `alias_name` - Name of configuration to retrieve
140    ///
141    /// # Returns
142    /// `Some(&Configuration)` if found, `None` if not found
143    pub fn get_configuration(&self, alias_name: &str) -> Option<&Configuration> {
144        self.configurations.get(alias_name)
145    }
146
147    /// Set the default directory for Claude settings
148    ///
149    /// # Arguments
150    /// * `directory` - Directory path for Claude settings
151    #[allow(dead_code)]
152    pub fn set_claude_settings_dir(&mut self, directory: String) {
153        self.claude_settings_dir = Some(directory);
154    }
155
156    /// Get the current Claude settings directory
157    ///
158    /// # Returns
159    /// `Some(&String)` if custom directory is set, `None` if using default
160    #[allow(dead_code)]
161    pub fn get_claude_settings_dir(&self) -> Option<&String> {
162        self.claude_settings_dir.as_ref()
163    }
164
165    /// Update an existing configuration
166    ///
167    /// This method handles updating a configuration, including potential alias renaming.
168    /// If the new configuration has a different alias name than the old one, it removes
169    /// the old entry and creates a new one.
170    ///
171    /// # Arguments
172    /// * `old_alias` - Current alias name of the configuration to update
173    /// * `new_config` - Updated configuration object
174    ///
175    /// # Returns
176    /// `Ok(())` if update succeeds, `Err` if the old configuration doesn't exist
177    ///
178    /// # Errors
179    /// Returns error if the configuration with `old_alias` doesn't exist
180    pub fn update_configuration(
181        &mut self,
182        old_alias: &str,
183        new_config: Configuration,
184    ) -> Result<()> {
185        // Check if the old configuration exists
186        if !self.configurations.contains_key(old_alias) {
187            return Err(anyhow::anyhow!("Configuration '{}' not found", old_alias));
188        }
189
190        // If alias changed, remove the old entry
191        if old_alias != new_config.alias_name {
192            self.configurations.remove(old_alias);
193        }
194
195        // Insert the updated configuration (this will overwrite if alias hasn't changed)
196        self.configurations
197            .insert(new_config.alias_name.clone(), new_config);
198
199        Ok(())
200    }
201}