1use std::collections::HashMap;
3use std::fs;
4
5use serde::{Deserialize, Serialize};
6
7use crate::config::Config;
8
9pub 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)] pub 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)]
57pub struct ArchSpec {
58 #[serde(rename = "type")] pub type_name: String, pub bits: u32, }
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(untagged)]
66pub enum ArchReq {
67 One(String), Many(Vec<String>), Specs(Vec<ArchSpec>),
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(untagged)]
75pub enum MacOSReq {
76 Symbol(String), Symbols(Vec<String>), Comparison(String), Map(HashMap<String, Vec<String>>),
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(untagged)]
85pub enum StringList {
86 One(String),
87 Many(Vec<String>),
88}
89
90impl From<StringList> for Vec<String> {
91 fn from(item: StringList) -> Self {
92 match item {
93 StringList::One(s) => vec![s],
94 StringList::Many(v) => v,
95 }
96 }
97}
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
151 #[serde(default)] pub zap: Option<Vec<ZapStanza>>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct CaskList {
157 pub casks: Vec<Cask>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(untagged)]
165pub enum StringOrVec {
166 String(String),
167 Vec(Vec<String>),
168}
169impl StringOrVec {
170 pub fn into_vec(self) -> Vec<String> {
171 match self {
172 StringOrVec::String(s) => vec![s],
173 StringOrVec::Vec(v) => v,
174 }
175 }
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180#[serde(tag = "action", rename_all = "snake_case")]
181pub enum ZapActionDetail {
182 Trash(Vec<String>),
183 Delete(Vec<String>),
184 Rmdir(Vec<String>),
185 Pkgutil(StringOrVec),
186 Launchctl(StringOrVec),
187 Script {
188 executable: String,
189 args: Option<Vec<String>>,
190 },
191 Signal(Vec<String>),
192 }
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct ZapStanza(pub std::collections::HashMap<String, ZapActionDetail>);
198
199impl Cask {
202 pub fn is_installed(&self, config: &Config) -> bool {
205 let cask_dir = config.cask_room_token_path(&self.token); if !cask_dir.exists() || !cask_dir.is_dir() {
207 return false;
208 }
209
210 match fs::read_dir(&cask_dir) {
212 Ok(entries) => {
213 for entry in entries.flatten() {
215 let version_path = entry.path();
217 if version_path.is_dir() {
219 let manifest_path = version_path.join("CASK_INSTALL_MANIFEST.json"); if manifest_path.is_file() {
222 let mut include = true;
224 if let Ok(manifest_str) = std::fs::read_to_string(&manifest_path) {
225 if let Ok(manifest_json) =
226 serde_json::from_str::<serde_json::Value>(&manifest_str)
227 {
228 if let Some(is_installed) =
229 manifest_json.get("is_installed").and_then(|v| v.as_bool())
230 {
231 include = is_installed;
232 }
233 }
234 }
235 if include {
236 return true;
239 }
240 }
241 }
242 }
243 false
245 }
246 Err(e) => {
247 tracing::warn!(
249 "Failed to read cask directory {} to check for installed versions: {}",
250 cask_dir.display(),
251 e
252 );
253 false
254 }
255 }
256 }
257
258 pub fn installed_version(&self, config: &Config) -> Option<String> {
262 let cask_dir = config.cask_room_token_path(&self.token); if !cask_dir.exists() {
264 return None;
265 }
266 match fs::read_dir(&cask_dir) {
268 Ok(entries) => {
269 for entry in entries.flatten() {
271 let path = entry.path();
273 if path.is_dir() {
275 if let Some(version_str) = path.file_name().and_then(|name| name.to_str()) {
276 return Some(version_str.to_string());
278 }
279 }
280 }
281 None
283 }
284 Err(_) => None, }
286 }
287
288 pub fn display_name(&self) -> String {
290 self.name
291 .as_ref()
292 .and_then(|names| names.first().cloned())
293 .unwrap_or_else(|| self.token.clone())
294 }
295}