1use std::fmt::Display;
2
3use rusqlite::types::Value;
4use serde::{de, Deserialize, Deserializer, Serialize};
5
6use super::packages::PackageProvide;
7
8#[derive(Debug, Clone, Deserialize, Serialize)]
9pub struct Maintainer {
10 pub name: String,
11 pub contact: String,
12}
13
14impl Display for Maintainer {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 write!(f, "{} ({})", self.name, self.contact)
17 }
18}
19
20pub trait PackageExt {
21 fn pkg_name(&self) -> &str;
22 fn pkg_id(&self) -> &str;
23 fn version(&self) -> &str;
24 fn repo_name(&self) -> &str;
25}
26
27pub trait FromRow: Sized {
28 fn from_row(row: &rusqlite::Row) -> rusqlite::Result<Self>;
29}
30
31#[derive(Debug, Clone, Default)]
32pub struct Package {
33 pub id: u64,
34 pub repo_name: String,
35 pub disabled: Option<bool>,
36 pub disabled_reason: Option<Value>,
37 pub rank: Option<u64>,
38 pub pkg: Option<String>,
39 pub pkg_id: String,
40 pub pkg_name: String,
41 pub pkg_family: Option<String>,
42 pub pkg_type: Option<String>,
43 pub pkg_webpage: Option<String>,
44 pub app_id: Option<String>,
45 pub description: String,
46 pub version: String,
47 pub version_upstream: Option<String>,
48 pub licenses: Option<Vec<String>>,
49 pub download_url: String,
50 pub size: Option<u64>,
51 pub ghcr_pkg: Option<String>,
52 pub ghcr_size: Option<u64>,
53 pub ghcr_files: Option<Vec<String>>,
54 pub ghcr_blob: Option<String>,
55 pub ghcr_url: Option<String>,
56 pub bsum: Option<String>,
57 pub shasum: Option<String>,
58 pub homepages: Option<Vec<String>>,
59 pub notes: Option<Vec<String>>,
60 pub source_urls: Option<Vec<String>>,
61 pub tags: Option<Vec<String>>,
62 pub categories: Option<Vec<String>>,
63 pub icon: Option<String>,
64 pub desktop: Option<String>,
65 pub appstream: Option<String>,
66 pub build_id: Option<String>,
67 pub build_date: Option<String>,
68 pub build_action: Option<String>,
69 pub build_script: Option<String>,
70 pub build_log: Option<String>,
71 pub provides: Option<Vec<PackageProvide>>,
72 pub snapshots: Option<Vec<String>>,
73 pub repology: Option<Vec<String>>,
74 pub download_count: Option<u64>,
75 pub download_count_month: Option<u64>,
76 pub download_count_week: Option<u64>,
77 pub maintainers: Option<Vec<Maintainer>>,
78 pub replaces: Option<Vec<String>>,
79 pub bundle: bool,
80 pub bundle_type: Option<String>,
81 pub soar_syms: bool,
82 pub deprecated: bool,
83 pub desktop_integration: Option<bool>,
84 pub external: Option<bool>,
85 pub installable: Option<bool>,
86 pub portable: Option<bool>,
87 pub trusted: Option<bool>,
88 pub version_latest: Option<String>,
89 pub version_outdated: Option<bool>,
90}
91
92impl FromRow for Package {
93 fn from_row(row: &rusqlite::Row) -> rusqlite::Result<Self> {
94 let parse_json_vec = |idx: &str| -> rusqlite::Result<Option<Vec<String>>> {
95 Ok(row
96 .get::<_, Option<String>>(idx)?
97 .and_then(|json| serde_json::from_str(&json).ok()))
98 };
99
100 let parse_provides = |idx: &str| -> rusqlite::Result<Option<Vec<PackageProvide>>> {
101 Ok(row
102 .get::<_, Option<String>>(idx)?
103 .and_then(|json| serde_json::from_str(&json).ok()))
104 };
105
106 let maintainers: Option<Vec<Maintainer>> = row
107 .get::<_, Option<String>>("maintainers")?
108 .and_then(|json| serde_json::from_str(&json).ok());
109
110 let licenses = parse_json_vec("licenses")?;
111 let ghcr_files = parse_json_vec("ghcr_files")?;
112 let homepages = parse_json_vec("homepages")?;
113 let notes = parse_json_vec("notes")?;
114 let source_urls = parse_json_vec("source_urls")?;
115 let tags = parse_json_vec("tags")?;
116 let categories = parse_json_vec("categories")?;
117 let provides = parse_provides("provides")?;
118 let snapshots = parse_json_vec("snapshots")?;
119 let repology = parse_json_vec("repology")?;
120 let replaces = parse_json_vec("replaces")?;
121
122 Ok(Package {
123 id: row.get("id")?,
124 disabled: row.get("disabled")?,
125 disabled_reason: row.get("disabled_reason")?,
126 rank: row.get("rank")?,
127 pkg: row.get("pkg")?,
128 pkg_id: row.get("pkg_id")?,
129 pkg_name: row.get("pkg_name")?,
130 pkg_family: row.get("pkg_family")?,
131 pkg_type: row.get("pkg_type")?,
132 pkg_webpage: row.get("pkg_webpage")?,
133 app_id: row.get("app_id")?,
134 description: row.get("description")?,
135 version: row.get("version")?,
136 version_upstream: row.get("version_upstream")?,
137 licenses,
138 download_url: row.get("download_url")?,
139 size: row.get("size")?,
140 ghcr_pkg: row.get("ghcr_pkg")?,
141 ghcr_size: row.get("ghcr_size")?,
142 ghcr_files,
143 ghcr_blob: row.get("ghcr_blob")?,
144 ghcr_url: row.get("ghcr_url")?,
145 bsum: row.get("bsum")?,
146 shasum: row.get("shasum")?,
147 icon: row.get("icon")?,
148 desktop: row.get("desktop")?,
149 appstream: row.get("appstream")?,
150 homepages,
151 notes,
152 source_urls,
153 tags,
154 categories,
155 build_id: row.get("build_id")?,
156 build_date: row.get("build_date")?,
157 build_action: row.get("build_action")?,
158 build_script: row.get("build_script")?,
159 build_log: row.get("build_log")?,
160 provides,
161 snapshots,
162 repology,
163 download_count: row.get("download_count")?,
164 download_count_week: row.get("download_count_week")?,
165 download_count_month: row.get("download_count_month")?,
166 repo_name: row.get("repo_name")?,
167 maintainers,
168 replaces,
169 bundle: row.get("bundle")?,
170 bundle_type: row.get("bundle_type")?,
171 soar_syms: row.get("soar_syms")?,
172 deprecated: row.get("deprecated")?,
173 desktop_integration: row.get("desktop_integration")?,
174 external: row.get("external")?,
175 installable: row.get("installable")?,
176 portable: row.get("portable")?,
177 trusted: row.get("trusted")?,
178 version_latest: row.get("version_latest")?,
179 version_outdated: row.get("version_outdated")?,
180 })
181 }
182}
183
184#[derive(Debug, Clone)]
185pub struct InstalledPackage {
186 pub id: u64,
187 pub repo_name: String,
188 pub pkg: Option<String>,
189 pub pkg_id: String,
190 pub pkg_name: String,
191 pub pkg_type: Option<String>,
192 pub version: String,
193 pub size: u64,
194 pub checksum: Option<String>,
195 pub installed_path: String,
196 pub installed_date: String,
197 pub profile: String,
198 pub pinned: bool,
199 pub is_installed: bool,
200 pub with_pkg_id: bool,
201 pub detached: bool,
202 pub unlinked: bool,
203 pub provides: Option<Vec<PackageProvide>>,
204 pub portable_path: Option<String>,
205 pub portable_home: Option<String>,
206 pub portable_config: Option<String>,
207 pub portable_share: Option<String>,
208 pub install_patterns: Option<Vec<String>>,
209}
210
211impl FromRow for InstalledPackage {
212 fn from_row(row: &rusqlite::Row) -> rusqlite::Result<Self> {
213 let parse_provides = |idx: &str| -> rusqlite::Result<Option<Vec<PackageProvide>>> {
214 let value: Option<String> = row.get(idx)?;
215 Ok(value.and_then(|s| serde_json::from_str(&s).ok()))
216 };
217
218 let parse_install_patterns = |idx: &str| -> rusqlite::Result<Option<Vec<String>>> {
219 let value: Option<String> = row.get(idx)?;
220 Ok(value.and_then(|s| serde_json::from_str(&s).ok()))
221 };
222
223 let provides = parse_provides("provides")?;
224 let install_patterns = parse_install_patterns("install_patterns")?;
225
226 Ok(InstalledPackage {
227 id: row.get("id")?,
228 repo_name: row.get("repo_name")?,
229 pkg: row.get("pkg")?,
230 pkg_id: row.get("pkg_id")?,
231 pkg_name: row.get("pkg_name")?,
232 pkg_type: row.get("pkg_type")?,
233 version: row.get("version")?,
234 size: row.get("size")?,
235 checksum: row.get("checksum")?,
236 installed_path: row.get("installed_path")?,
237 installed_date: row.get("installed_date")?,
238 profile: row.get("profile")?,
239 pinned: row.get("pinned")?,
240 is_installed: row.get("is_installed")?,
241 with_pkg_id: row.get("with_pkg_id")?,
242 detached: row.get("detached")?,
243 unlinked: row.get("unlinked")?,
244 provides,
245 portable_path: row.get("portable_path")?,
246 portable_home: row.get("portable_home")?,
247 portable_config: row.get("portable_config")?,
248 portable_share: row.get("portable_share")?,
249 install_patterns,
250 })
251 }
252}
253
254#[derive(Deserialize)]
255#[serde(untagged)]
256enum FlexiBool {
257 Bool(bool),
258 String(String),
259}
260
261fn empty_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
262where
263 D: Deserializer<'de>,
264{
265 let s: Option<String> = Option::deserialize(deserializer)?;
266 Ok(s.filter(|s| !s.is_empty()))
267}
268
269fn optional_number<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
270where
271 D: Deserializer<'de>,
272{
273 let s: Option<String> = Option::deserialize(deserializer)?;
274 Ok(s.filter(|s| !s.is_empty())
275 .and_then(|s| s.parse::<i64>().ok())
276 .filter(|&n| n >= 0)
277 .map(|n| n as u64))
278}
279
280fn flexible_bool<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
281where
282 D: Deserializer<'de>,
283{
284 match Option::<FlexiBool>::deserialize(deserializer)? {
285 Some(FlexiBool::Bool(b)) => Ok(Some(b)),
286 Some(FlexiBool::String(s)) => match s.to_lowercase().as_str() {
287 "true" | "yes" | "1" => Ok(Some(true)),
288 "false" | "no" | "0" => Ok(Some(false)),
289 "" => Ok(None),
290 _ => Err(de::Error::invalid_value(
291 de::Unexpected::Str(&s),
292 &"a valid boolean (true/false, yes/no, 1/0)",
293 )),
294 },
295 None => Ok(None),
296 }
297}
298
299#[derive(Debug, Default, Clone, Deserialize, Serialize)]
300pub struct RemotePackage {
301 #[serde(deserialize_with = "flexible_bool", alias = "_disabled")]
302 pub disabled: Option<bool>,
303
304 #[serde(alias = "_disabled_reason")]
305 pub disabled_reason: Option<serde_json::Value>,
306
307 #[serde(default, deserialize_with = "optional_number")]
308 pub rank: Option<u64>,
309
310 #[serde(default, deserialize_with = "empty_is_none")]
311 pub pkg: Option<String>,
312 pub pkg_id: String,
313 pub pkg_name: String,
314
315 #[serde(default, deserialize_with = "empty_is_none")]
316 pub pkg_family: Option<String>,
317
318 #[serde(default, deserialize_with = "empty_is_none")]
319 pub pkg_type: Option<String>,
320
321 #[serde(default, deserialize_with = "empty_is_none")]
322 pub pkg_webpage: Option<String>,
323
324 pub description: String,
325 pub version: String,
326
327 #[serde(default, deserialize_with = "empty_is_none")]
328 pub version_upstream: Option<String>,
329
330 pub download_url: String,
331
332 #[serde(default, deserialize_with = "optional_number")]
333 pub size_raw: Option<u64>,
334
335 #[serde(default, deserialize_with = "empty_is_none")]
336 pub ghcr_pkg: Option<String>,
337
338 #[serde(default, deserialize_with = "optional_number")]
339 pub ghcr_size_raw: Option<u64>,
340
341 pub ghcr_files: Option<Vec<String>>,
342
343 #[serde(default, deserialize_with = "empty_is_none")]
344 pub ghcr_blob: Option<String>,
345
346 #[serde(default, deserialize_with = "empty_is_none")]
347 pub ghcr_url: Option<String>,
348
349 #[serde(alias = "src_url")]
350 pub src_urls: Option<Vec<String>>,
351
352 #[serde(alias = "homepage")]
353 pub homepages: Option<Vec<String>>,
354
355 #[serde(alias = "license")]
356 pub licenses: Option<Vec<String>>,
357
358 #[serde(alias = "maintainer")]
359 pub maintainers: Option<Vec<String>>,
360
361 #[serde(alias = "note")]
362 pub notes: Option<Vec<String>>,
363
364 #[serde(alias = "tag")]
365 pub tags: Option<Vec<String>>,
366
367 #[serde(default, deserialize_with = "empty_is_none")]
368 pub bsum: Option<String>,
369
370 #[serde(default, deserialize_with = "empty_is_none")]
371 pub shasum: Option<String>,
372
373 #[serde(default, deserialize_with = "empty_is_none")]
374 pub build_id: Option<String>,
375
376 #[serde(default, deserialize_with = "empty_is_none")]
377 pub build_date: Option<String>,
378
379 #[serde(default, deserialize_with = "empty_is_none", alias = "build_gha")]
380 pub build_action: Option<String>,
381
382 #[serde(default, deserialize_with = "empty_is_none")]
383 pub build_script: Option<String>,
384
385 #[serde(default, deserialize_with = "empty_is_none")]
386 pub build_log: Option<String>,
387
388 #[serde(alias = "category")]
389 pub categories: Option<Vec<String>>,
390
391 pub provides: Option<Vec<String>>,
392
393 #[serde(default, deserialize_with = "empty_is_none")]
394 pub icon: Option<String>,
395
396 #[serde(default, deserialize_with = "empty_is_none")]
397 pub desktop: Option<String>,
398
399 #[serde(default, deserialize_with = "empty_is_none")]
400 pub appstream: Option<String>,
401
402 #[serde(default, deserialize_with = "empty_is_none")]
403 pub app_id: Option<String>,
404
405 #[serde(default, deserialize_with = "optional_number")]
406 pub download_count: Option<u64>,
407
408 #[serde(default, deserialize_with = "optional_number")]
409 pub download_count_month: Option<u64>,
410
411 #[serde(default, deserialize_with = "optional_number")]
412 pub download_count_week: Option<u64>,
413
414 #[serde(default, deserialize_with = "flexible_bool")]
415 pub bundle: Option<bool>,
416
417 #[serde(default, deserialize_with = "empty_is_none")]
418 pub bundle_type: Option<String>,
419
420 #[serde(default, deserialize_with = "flexible_bool")]
421 pub soar_syms: Option<bool>,
422
423 #[serde(default, deserialize_with = "flexible_bool")]
424 pub deprecated: Option<bool>,
425
426 #[serde(default, deserialize_with = "flexible_bool")]
427 pub desktop_integration: Option<bool>,
428
429 #[serde(default, deserialize_with = "flexible_bool")]
430 pub external: Option<bool>,
431
432 #[serde(default, deserialize_with = "flexible_bool")]
433 pub installable: Option<bool>,
434
435 #[serde(default, deserialize_with = "flexible_bool")]
436 pub portable: Option<bool>,
437
438 #[serde(default, deserialize_with = "flexible_bool")]
439 pub recurse_provides: Option<bool>,
440
441 #[serde(default, deserialize_with = "flexible_bool")]
442 pub trusted: Option<bool>,
443
444 #[serde(default, deserialize_with = "empty_is_none")]
445 pub version_latest: Option<String>,
446
447 #[serde(default, deserialize_with = "flexible_bool")]
448 pub version_outdated: Option<bool>,
449
450 pub repology: Option<Vec<String>>,
451 pub snapshots: Option<Vec<String>>,
452 pub replaces: Option<Vec<String>>,
453}
454
455impl PackageExt for Package {
456 fn pkg_name(&self) -> &str {
457 &self.pkg_name
458 }
459
460 fn pkg_id(&self) -> &str {
461 &self.pkg_id
462 }
463
464 fn version(&self) -> &str {
465 &self.version
466 }
467
468 fn repo_name(&self) -> &str {
469 &self.repo_name
470 }
471}
472
473impl PackageExt for InstalledPackage {
474 fn pkg_name(&self) -> &str {
475 &self.pkg_name
476 }
477
478 fn pkg_id(&self) -> &str {
479 &self.pkg_id
480 }
481
482 fn version(&self) -> &str {
483 &self.version
484 }
485
486 fn repo_name(&self) -> &str {
487 &self.repo_name
488 }
489}