1use serde::{Deserialize, Serialize};
2use std::collections::HashSet;
3use std::path::{Path, PathBuf};
4
5use crate::error::{Error, Result};
6use crate::finding::RiskLevel;
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10#[serde(default)]
11pub struct Config {
12 pub defaults: Defaults,
13 pub ignore: IgnoreConfig,
14 pub recognizers: RecognizerConfig,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(default)]
20pub struct Defaults {
21 pub risk_tolerance: String,
23 pub delete_mode: String,
25 pub min_size: u64,
27}
28
29#[derive(Debug, Clone, Default, Serialize, Deserialize)]
31#[serde(default)]
32pub struct IgnoreConfig {
33 pub paths: Vec<PathBuf>,
35}
36
37#[derive(Debug, Clone, Default, Serialize, Deserialize)]
39#[serde(default)]
40pub struct RecognizerConfig {
41 pub disabled: HashSet<String>,
43}
44
45impl Default for Defaults {
46 fn default() -> Self {
47 Self {
48 risk_tolerance: "moderate".to_string(),
49 delete_mode: "trash".to_string(),
50 min_size: 0,
51 }
52 }
53}
54
55impl Config {
56 pub fn path() -> Option<PathBuf> {
58 dirs::config_dir().map(|d| d.join("diskard").join("config.toml"))
59 }
60
61 pub fn load() -> Result<Self> {
63 match Self::path() {
64 Some(path) if path.exists() => Self::load_from(&path),
65 _ => Ok(Self::default()),
66 }
67 }
68
69 pub fn load_from(path: &Path) -> Result<Self> {
71 let content = std::fs::read_to_string(path).map_err(|e| Error::io(path, e))?;
72 let config: Config = toml::from_str(&content)?;
73 Ok(config)
74 }
75
76 pub fn init() -> Result<PathBuf> {
78 let path = Self::path()
79 .ok_or_else(|| Error::Config("Cannot determine config directory".into()))?;
80 if let Some(parent) = path.parent() {
81 std::fs::create_dir_all(parent).map_err(|e| Error::io(parent, e))?;
82 }
83 let content =
84 toml::to_string_pretty(&Config::default()).map_err(|e| Error::Config(e.to_string()))?;
85 std::fs::write(&path, content).map_err(|e| Error::io(&path, e))?;
86 Ok(path)
87 }
88
89 pub fn max_risk(&self) -> RiskLevel {
91 match self.defaults.risk_tolerance.to_lowercase().as_str() {
92 "safe" => RiskLevel::Safe,
93 "risky" => RiskLevel::Risky,
94 _ => RiskLevel::Moderate,
95 }
96 }
97
98 pub fn is_recognizer_enabled(&self, id: &str) -> bool {
100 !self.recognizers.disabled.contains(id)
101 }
102
103 pub fn is_path_ignored(&self, path: &Path) -> bool {
105 self.ignore
106 .paths
107 .iter()
108 .any(|ignored| path.starts_with(ignored))
109 }
110}