1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::PathBuf;
5use std::str::FromStr;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub enum CompressionAlgorithm {
9 Gzip,
10 Zstd,
11 Lz4,
12}
13
14impl FromStr for CompressionAlgorithm {
15 type Err = anyhow::Error;
16
17 fn from_str(s: &str) -> Result<Self> {
18 Self::from_str(s)
19 }
20}
21
22impl FromStr for DeltaAlgorithm {
23 type Err = anyhow::Error;
24
25 fn from_str(s: &str) -> Result<Self> {
26 Self::from_str(s)
27 }
28}
29
30impl Default for CompressionAlgorithm {
31 fn default() -> Self {
32 CompressionAlgorithm::Gzip
33 }
34}
35
36impl CompressionAlgorithm {
37 pub fn from_str(s: &str) -> Result<Self> {
38 match s.to_lowercase().as_str() {
39 "gzip" => Ok(CompressionAlgorithm::Gzip),
40 "zstd" => Ok(CompressionAlgorithm::Zstd),
41 "lz4" => Ok(CompressionAlgorithm::Lz4),
42 _ => Err(anyhow::anyhow!("Invalid compression algorithm. Valid values: gzip, zstd, lz4")),
43 }
44 }
45
46 pub fn to_string(&self) -> String {
47 match self {
48 CompressionAlgorithm::Gzip => "gzip".to_string(),
49 CompressionAlgorithm::Zstd => "zstd".to_string(),
50 CompressionAlgorithm::Lz4 => "lz4".to_string(),
51 }
52 }
53
54 pub fn file_extension(&self) -> &'static str {
55 match self {
56 CompressionAlgorithm::Gzip => "gz",
57 CompressionAlgorithm::Zstd => "zst",
58 CompressionAlgorithm::Lz4 => "lz4",
59 }
60 }
61
62 pub fn validate_level(&self, level: u32) -> Result<u32> {
63 match self {
64 CompressionAlgorithm::Gzip => {
65 if level > 9 {
66 Err(anyhow::anyhow!("Gzip compression level must be between 0-9"))
67 } else {
68 Ok(level)
69 }
70 }
71 CompressionAlgorithm::Zstd => {
72 if level < 1 || level > 22 {
73 Err(anyhow::anyhow!("Zstd compression level must be between 1-22"))
74 } else {
75 Ok(level)
76 }
77 }
78 CompressionAlgorithm::Lz4 => {
79 Ok(0)
81 }
82 }
83 }
84
85 pub fn default_level(&self) -> u32 {
86 match self {
87 CompressionAlgorithm::Gzip => 6,
88 CompressionAlgorithm::Zstd => 3,
89 CompressionAlgorithm::Lz4 => 0,
90 }
91 }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
95pub enum DeltaAlgorithm {
96 Simple, XDelta, BsDiff, }
100
101impl Default for DeltaAlgorithm {
102 fn default() -> Self {
103 DeltaAlgorithm::Simple
104 }
105}
106
107impl DeltaAlgorithm {
108 pub fn from_str(s: &str) -> Result<Self> {
109 match s.to_lowercase().as_str() {
110 "simple" => Ok(DeltaAlgorithm::Simple),
111 "xdelta" => Ok(DeltaAlgorithm::XDelta),
112 "bsdiff" => Ok(DeltaAlgorithm::BsDiff),
113 _ => Err(anyhow::anyhow!("Invalid delta algorithm. Valid values: simple, xdelta, bsdiff")),
114 }
115 }
116
117 pub fn to_string(&self) -> String {
118 match self {
119 DeltaAlgorithm::Simple => "simple".to_string(),
120 DeltaAlgorithm::XDelta => "xdelta".to_string(),
121 DeltaAlgorithm::BsDiff => "bsdiff".to_string(),
122 }
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct Config {
128 pub storage_path: PathBuf,
129 pub index_mode: IndexMode,
130 #[serde(default = "default_multithread")]
131 pub multithread: usize,
132 #[serde(default = "default_compression_algorithm")]
133 pub compression_algorithm: CompressionAlgorithm,
134 #[serde(default = "default_compression_level")]
135 pub compression_level: u32,
136 #[serde(default = "default_enable_deduplication")]
137 pub enable_deduplication: bool,
138 #[serde(default = "default_enable_delta_compression")]
139 pub enable_delta_compression: bool,
140 #[serde(default = "default_similarity_threshold")]
141 pub similarity_threshold: f32,
142 #[serde(default = "default_delta_algorithm")]
143 pub delta_algorithm: DeltaAlgorithm,
144}
145
146fn default_multithread() -> usize {
147 1
148}
149
150fn default_compression_algorithm() -> CompressionAlgorithm {
151 CompressionAlgorithm::Gzip
152}
153
154fn default_compression_level() -> u32 {
155 6 }
157
158fn default_enable_deduplication() -> bool {
159 true
160}
161
162fn default_enable_delta_compression() -> bool {
163 false
164}
165
166fn default_similarity_threshold() -> f32 {
167 0.7 }
169
170fn default_delta_algorithm() -> DeltaAlgorithm {
171 DeltaAlgorithm::Simple
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub enum IndexMode {
176 Auto,
177 Json,
178 Sqlite,
179}
180
181impl Default for Config {
182 fn default() -> Self {
183 Self {
184 storage_path: PathBuf::from(".stowr").join("storage"),
185 index_mode: IndexMode::Auto,
186 multithread: 1,
187 compression_algorithm: CompressionAlgorithm::Gzip,
188 compression_level: 6,
189 enable_deduplication: true,
190 enable_delta_compression: false,
191 similarity_threshold: 0.7,
192 delta_algorithm: DeltaAlgorithm::Simple,
193 }
194 }
195}
196
197impl Config {
198 pub fn load() -> Result<Self> {
199 let config_path = Self::config_path()?;
200
201 if config_path.exists() {
202 let content = fs::read_to_string(&config_path)
203 .context("Failed to read config file")?;
204 let config: Config = serde_json::from_str(&content)
205 .context("Failed to parse config file")?;
206 Ok(config)
207 } else {
208 let config = Config::default();
209 config.save()?;
210 Ok(config)
211 }
212 }
213
214 pub fn save(&self) -> Result<()> {
215 let config_path = Self::config_path()?;
216
217 if let Some(parent) = config_path.parent() {
219 fs::create_dir_all(parent)
220 .context("Failed to create config directory")?;
221 }
222
223 fs::create_dir_all(&self.storage_path)
225 .context("Failed to create storage directory")?;
226
227 let content = serde_json::to_string_pretty(self)
228 .context("Failed to serialize config")?;
229
230 fs::write(&config_path, content)
231 .context("Failed to write config file")?;
232
233 Ok(())
234 }
235
236 pub fn config_path() -> Result<PathBuf> {
237 Ok(PathBuf::from(".stowr").join("config.json"))
238 }
239
240 pub fn set(&mut self, key: &str, value: &str) -> Result<()> {
241 match key {
242 "storage.path" => {
243 self.storage_path = PathBuf::from(value);
244 }
245 "index.mode" => {
246 self.index_mode = match value.to_lowercase().as_str() {
247 "auto" => IndexMode::Auto,
248 "json" => IndexMode::Json,
249 "sqlite" => IndexMode::Sqlite,
250 _ => return Err(anyhow::anyhow!("Invalid index mode. Valid values: auto, json, sqlite")),
251 };
252 }
253 "multithread" => {
254 self.multithread = value.parse::<usize>()
255 .map_err(|_| anyhow::anyhow!("Invalid multithread value. Must be a positive number"))?;
256 if self.multithread == 0 {
257 return Err(anyhow::anyhow!("Multithread value must be greater than 0"));
258 }
259 }
260 "compression.algorithm" => {
261 self.compression_algorithm = CompressionAlgorithm::from_str(value)?;
262 self.compression_level = self.compression_algorithm.default_level();
264 }
265 "compression.level" => {
266 let level = value.parse::<u32>()
267 .map_err(|_| anyhow::anyhow!("Invalid compression level. Must be a number"))?;
268
269 if self.compression_algorithm == CompressionAlgorithm::Lz4 {
271 println!("Note: LZ4 does not use compression levels. Level set to 0.");
272 self.compression_level = 0;
273 } else {
274 self.compression_level = self.compression_algorithm.validate_level(level)?;
275 }
276 }
277 "dedup.enable" => {
278 self.enable_deduplication = value.parse::<bool>()
279 .map_err(|_| anyhow::anyhow!("Invalid boolean value. Must be true or false"))?;
280 }
281 "delta.enable" => {
282 self.enable_delta_compression = value.parse::<bool>()
283 .map_err(|_| anyhow::anyhow!("Invalid boolean value. Must be true or false"))?;
284 }
285 "delta.similarity_threshold" => {
286 let threshold = value.parse::<f32>()
287 .map_err(|_| anyhow::anyhow!("Invalid similarity threshold. Must be a number between 0.0 and 1.0"))?;
288 if threshold < 0.0 || threshold > 1.0 {
289 return Err(anyhow::anyhow!("Similarity threshold must be between 0.0 and 1.0"));
290 }
291 self.similarity_threshold = threshold;
292 }
293 "delta.algorithm" => {
294 self.delta_algorithm = DeltaAlgorithm::from_str(value)?;
295 }
296 _ => return Err(anyhow::anyhow!("Unknown config key: {}", key)),
297 }
298 Ok(())
299 }
300
301 pub fn list(&self) -> Vec<(String, String)> {
302 vec![
303 ("storage.path".to_string(), self.storage_path.display().to_string()),
304 ("index.mode".to_string(), format!("{:?}", self.index_mode).to_lowercase()),
305 ("multithread".to_string(), self.multithread.to_string()),
306 ("compression.algorithm".to_string(), self.compression_algorithm.to_string()),
307 ("compression.level".to_string(), self.compression_level.to_string()),
308 ("dedup.enable".to_string(), self.enable_deduplication.to_string()),
309 ("delta.enable".to_string(), self.enable_delta_compression.to_string()),
310 ("delta.similarity_threshold".to_string(), self.similarity_threshold.to_string()),
311 ("delta.algorithm".to_string(), self.delta_algorithm.to_string()),
312 ]
313 }
314}