lc/search/
config.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::PathBuf;
6
7use super::providers::{SearchProviderConfig, SearchProviderType};
8
9#[derive(Debug, Serialize, Deserialize, Clone)]
10pub struct SearchConfig {
11    pub providers: HashMap<String, SearchProviderConfig>,
12    pub default_provider: Option<String>,
13}
14
15impl SearchConfig {
16    pub fn new() -> Self {
17        Self {
18            providers: HashMap::new(),
19            default_provider: None,
20        }
21    }
22
23    pub fn load() -> Result<Self> {
24        let config_path = Self::config_file_path()?;
25
26        if config_path.exists() {
27            let content = fs::read_to_string(&config_path)?;
28            let config: SearchConfig = toml::from_str(&content)?;
29            Ok(config)
30        } else {
31            // Just return a new config without saving during load to prevent recursion
32            Ok(Self::new())
33        }
34    }
35
36    pub fn save(&self) -> Result<()> {
37        let config_path = Self::config_file_path()?;
38
39        // Ensure parent directory exists
40        if let Some(parent) = config_path.parent() {
41            // Only create directory if it doesn't exist to prevent potential recursion
42            if !parent.exists() {
43                fs::create_dir_all(parent)?;
44            }
45        }
46
47        let content = toml::to_string_pretty(self)?;
48        fs::write(&config_path, content)?;
49        Ok(())
50    }
51
52    pub fn add_provider(
53        &mut self,
54        name: String,
55        url: String,
56        provider_type: SearchProviderType,
57    ) -> Result<()> {
58        let provider_config = SearchProviderConfig {
59            url,
60            provider_type,
61            headers: HashMap::new(),
62        };
63
64        self.providers.insert(name.clone(), provider_config);
65
66        // Set as default if it's the first provider
67        if self.default_provider.is_none() {
68            self.default_provider = Some(name);
69        }
70
71        Ok(())
72    }
73
74    /// Add provider with auto-detected type from URL
75    pub fn add_provider_auto(&mut self, name: String, url: String) -> Result<()> {
76        let provider_type = SearchProviderType::detect_from_url(&url)?;
77        self.add_provider(name, url, provider_type)
78    }
79
80    pub fn delete_provider(&mut self, name: &str) -> Result<()> {
81        if self.providers.remove(name).is_none() {
82            anyhow::bail!("Search provider '{}' not found", name);
83        }
84
85        // Clear default if it was the deleted provider
86        if self.default_provider.as_ref() == Some(&name.to_string()) {
87            self.default_provider = None;
88        }
89
90        Ok(())
91    }
92
93    pub fn set_header(
94        &mut self,
95        provider: &str,
96        header_name: String,
97        header_value: String,
98    ) -> Result<()> {
99        if let Some(provider_config) = self.providers.get_mut(provider) {
100            provider_config.headers.insert(header_name, header_value);
101            Ok(())
102        } else {
103            anyhow::bail!("Search provider '{}' not found", provider);
104        }
105    }
106
107    pub fn get_provider(&self, name: &str) -> Result<&SearchProviderConfig> {
108        self.providers
109            .get(name)
110            .ok_or_else(|| anyhow::anyhow!("Search provider '{}' not found", name))
111    }
112
113    pub fn has_provider(&self, name: &str) -> bool {
114        self.providers.contains_key(name)
115    }
116
117    pub fn list_providers(&self) -> &HashMap<String, SearchProviderConfig> {
118        &self.providers
119    }
120
121    pub fn set_default_provider(&mut self, name: String) -> Result<()> {
122        if name.is_empty() {
123            self.default_provider = None;
124        } else if self.has_provider(&name) {
125            self.default_provider = Some(name);
126        } else {
127            anyhow::bail!("Search provider '{}' not found", name);
128        }
129        Ok(())
130    }
131
132    pub fn get_default_provider(&self) -> Option<&String> {
133        self.default_provider.as_ref()
134    }
135
136    fn config_file_path() -> Result<PathBuf> {
137        let config_dir = crate::config::Config::config_dir()?;
138        Ok(config_dir.join("search_config.toml"))
139    }
140}