data_gov/
config.rs

1use crate::ui::StatusReporter;
2use data_gov_ckan::{ApiKey, Configuration as CkanConfiguration};
3use std::fmt;
4use std::path::PathBuf;
5use std::sync::Arc;
6
7/// Operating mode for the client
8#[derive(Debug, Clone, PartialEq)]
9pub enum OperatingMode {
10    /// Interactive REPL mode - downloads to system Downloads directory
11    Interactive,
12    /// Command-line mode - downloads to current directory
13    CommandLine,
14}
15
16/// Configuration for the Data.gov client
17#[derive(Clone)]
18pub struct DataGovConfig {
19    /// CKAN client configuration
20    pub ckan_config: Arc<CkanConfiguration>,
21    /// Operating mode (affects base download directory)
22    pub mode: OperatingMode,
23    /// Base download directory for files (before dataset subdirectory)
24    pub base_download_dir: PathBuf,
25    /// User agent for HTTP requests
26    pub user_agent: String,
27    /// Maximum concurrent downloads
28    pub max_concurrent_downloads: usize,
29    /// Timeout for downloads in seconds
30    pub download_timeout_secs: u64,
31    /// Optional status reporter for UI callbacks
32    pub status_reporter: Option<Arc<dyn StatusReporter + Send + Sync>>,
33}
34
35impl fmt::Debug for DataGovConfig {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.debug_struct("DataGovConfig")
38            .field("ckan_config", &self.ckan_config)
39            .field("mode", &self.mode)
40            .field("base_download_dir", &self.base_download_dir)
41            .field("user_agent", &self.user_agent)
42            .field("max_concurrent_downloads", &self.max_concurrent_downloads)
43            .field("download_timeout_secs", &self.download_timeout_secs)
44            .field(
45                "status_reporter",
46                &self
47                    .status_reporter
48                    .as_ref()
49                    .map(|_| "Some(StatusReporter)"),
50            )
51            .finish()
52    }
53}
54
55impl Default for DataGovConfig {
56    fn default() -> Self {
57        Self {
58            ckan_config: Arc::new(CkanConfiguration::default()),
59            mode: OperatingMode::Interactive, // Default to interactive mode
60            base_download_dir: Self::get_default_download_dir(),
61            user_agent: "data-gov-rs/1.0".to_string(),
62            max_concurrent_downloads: 3,
63            download_timeout_secs: 300, // 5 minutes
64            status_reporter: None,
65        }
66    }
67}
68
69impl DataGovConfig {
70    /// Get the default download directory (system Downloads folder)
71    fn get_default_download_dir() -> PathBuf {
72        // Try to get the user's Downloads directory
73        if let Some(download_dir) = dirs::download_dir() {
74            download_dir
75        } else {
76            // Fallback to home directory + Downloads
77            let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
78            home.join("Downloads")
79        }
80    }
81
82    /// Create a new configuration for data.gov
83    pub fn new() -> Self {
84        Self::default()
85    }
86
87    /// Create configuration with custom base download directory
88    pub fn with_download_dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
89        self.base_download_dir = dir.into();
90        self
91    }
92
93    /// Set the operating mode
94    pub fn with_mode(mut self, mode: OperatingMode) -> Self {
95        self.mode = mode;
96        self
97    }
98
99    /// Get the base download directory based on operating mode
100    pub fn get_base_download_dir(&self) -> PathBuf {
101        match self.mode {
102            OperatingMode::Interactive => {
103                // Use the configured base directory (usually system Downloads)
104                self.base_download_dir.clone()
105            }
106            OperatingMode::CommandLine => {
107                // Use current working directory for CLI mode
108                std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
109            }
110        }
111    }
112
113    /// Get the full download directory for a specific dataset
114    pub fn get_dataset_download_dir(&self, dataset_name: &str) -> PathBuf {
115        self.get_base_download_dir().join(dataset_name)
116    }
117
118    /// Add API key for higher rate limits
119    pub fn with_api_key<S: Into<String>>(mut self, api_key: S) -> Self {
120        let mut ckan_config = (*self.ckan_config).clone();
121        ckan_config.api_key = Some(ApiKey {
122            key: api_key.into(),
123            prefix: None,
124        });
125        self.ckan_config = Arc::new(ckan_config);
126        self
127    }
128
129    /// Set custom user agent
130    pub fn with_user_agent<S: Into<String>>(mut self, user_agent: S) -> Self {
131        self.user_agent = user_agent.into();
132        let mut ckan_config = (*self.ckan_config).clone();
133        ckan_config.user_agent = Some(self.user_agent.clone());
134        self.ckan_config = Arc::new(ckan_config);
135        self
136    }
137
138    /// Set maximum concurrent downloads
139    pub fn with_max_concurrent_downloads(mut self, max: usize) -> Self {
140        self.max_concurrent_downloads = max.max(1);
141        self
142    }
143
144    /// Set download timeout
145    pub fn with_download_timeout(mut self, timeout_secs: u64) -> Self {
146        self.download_timeout_secs = timeout_secs;
147        self
148    }
149
150    /// Attach a status reporter for UI callbacks
151    pub fn with_status_reporter<R>(mut self, reporter: Arc<R>) -> Self
152    where
153        R: StatusReporter + Send + Sync + 'static,
154    {
155        self.status_reporter = Some(reporter);
156        self
157    }
158
159    /// Remove any configured status reporter
160    pub fn without_status_reporter(mut self) -> Self {
161        self.status_reporter = None;
162        self
163    }
164
165    /// Borrow the configured status reporter
166    pub fn status_reporter(&self) -> Option<&Arc<dyn StatusReporter + Send + Sync>> {
167        self.status_reporter.as_ref()
168    }
169}