1use std::collections::HashMap;
3use std::fs;
4
5use serde::{Deserialize, Serialize};
6
7use crate::config::Config; pub type Artifact = serde_json::Value;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(untagged)]
14pub enum UrlField {
15 Simple(String),
16 WithSpec {
17 url: String,
18 #[serde(default)]
19 verified: Option<String>,
20 #[serde(flatten)]
21 other: HashMap<String, serde_json::Value>,
22 },
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum Sha256Field {
29 Hex(String),
30 #[serde(rename_all = "snake_case")]
31 NoCheck {
32 no_check: bool,
33 },
34 PerArch(HashMap<String, String>),
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Appcast {
40 pub url: String,
41 pub checkpoint: Option<String>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ConflictsWith {
47 #[serde(default)]
48 pub cask: Vec<String>,
49 #[serde(default)]
50 pub formula: Vec<String>,
51 #[serde(flatten)]
52 pub extra: HashMap<String, serde_json::Value>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(untagged)]
58pub enum ArchReq {
59 One(String), Many(Vec<String>), Specs(Vec<ArchSpec>), }
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(untagged)]
67pub enum MacOSReq {
68 Symbol(String), Symbols(Vec<String>), Comparison(String), Map(HashMap<String, Vec<String>>),
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76#[serde(untagged)]
77pub enum StringList {
78 One(String),
79 Many(Vec<String>),
80}
81
82impl From<StringList> for Vec<String> {
83 fn from(item: StringList) -> Self {
84 match item {
85 StringList::One(s) => vec![s],
86 StringList::Many(v) => v,
87 }
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct ArchSpec {
94 #[serde(rename = "type")] pub type_name: String, pub bits: u32, }
98
99#[derive(Debug, Clone, Serialize, Deserialize, Default)]
101pub struct DependsOn {
102 #[serde(default)]
103 pub cask: Vec<String>,
104 #[serde(default)]
105 pub formula: Vec<String>,
106 #[serde(default)]
107 pub arch: Option<ArchReq>,
108 #[serde(default)]
109 pub macos: Option<MacOSReq>,
110 #[serde(flatten)]
111 pub extra: HashMap<String, serde_json::Value>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, Default)]
116pub struct Cask {
117 pub token: String,
118
119 #[serde(default)]
120 pub name: Option<Vec<String>>,
121 pub version: Option<String>,
122 pub desc: Option<String>,
123 pub homepage: Option<String>,
124
125 #[serde(default)]
126 pub artifacts: Option<Vec<Artifact>>,
127
128 #[serde(default)]
129 pub url: Option<UrlField>,
130 #[serde(default)]
131 pub url_specs: Option<HashMap<String, serde_json::Value>>,
132
133 #[serde(default)]
134 pub sha256: Option<Sha256Field>,
135
136 pub appcast: Option<Appcast>,
137 pub auto_updates: Option<bool>,
138
139 #[serde(default)]
140 pub depends_on: Option<DependsOn>,
141
142 #[serde(default)]
143 pub conflicts_with: Option<ConflictsWith>,
144
145 pub caveats: Option<String>,
146 pub stage_only: Option<bool>,
147
148 #[serde(default)]
149 pub uninstall: Option<HashMap<String, serde_json::Value>>,
150 #[serde(default)]
151 pub zap: Option<HashMap<String, serde_json::Value>>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct CaskList {
156 pub casks: Vec<Cask>,
157}
158
159impl Cask {
160 pub fn is_installed(&self, config: &Config) -> bool {
163 let cask_dir = config.cask_dir(&self.token); if !cask_dir.exists() || !cask_dir.is_dir() {
165 return false;
166 }
167
168 match fs::read_dir(&cask_dir) {
170 Ok(entries) => {
171 for entry in entries.flatten() {
173 let version_path = entry.path();
175 if version_path.is_dir() {
177 let manifest_path = version_path.join("CASK_INSTALL_MANIFEST.json"); if manifest_path.is_file() {
180 return true;
183 }
184 }
185 }
186 false
188 }
189 Err(e) => {
190 tracing::warn!(
192 "Failed to read cask directory {} to check for installed versions: {}",
193 cask_dir.display(),
194 e
195 );
196 false
197 }
198 }
199 }
200
201 pub fn installed_version(&self, config: &Config) -> Option<String> {
205 let cask_dir = config.cask_dir(&self.token); if !cask_dir.exists() {
207 return None;
208 }
209 match fs::read_dir(&cask_dir) {
211 Ok(entries) => {
212 for entry in entries.flatten() {
214 let path = entry.path();
216 if path.is_dir() {
218 if let Some(version_str) = path.file_name().and_then(|name| name.to_str()) {
219 return Some(version_str.to_string());
221 }
222 }
223 }
224 None
226 }
227 Err(_) => None, }
229 }
230
231 pub fn display_name(&self) -> String {
233 self.name
234 .as_ref()
235 .and_then(|names| names.first().cloned())
236 .unwrap_or_else(|| self.token.clone())
237 }
238}