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 portable_cache: Option<String>,
209 pub install_patterns: Option<Vec<String>>,
210}
211
212impl FromRow for InstalledPackage {
213 fn from_row(row: &rusqlite::Row) -> rusqlite::Result<Self> {
214 let parse_provides = |idx: &str| -> rusqlite::Result<Option<Vec<PackageProvide>>> {
215 let value: Option<String> = row.get(idx)?;
216 Ok(value.and_then(|s| serde_json::from_str(&s).ok()))
217 };
218
219 let parse_install_patterns = |idx: &str| -> rusqlite::Result<Option<Vec<String>>> {
220 let value: Option<String> = row.get(idx)?;
221 Ok(value.and_then(|s| serde_json::from_str(&s).ok()))
222 };
223
224 let provides = parse_provides("provides")?;
225 let install_patterns = parse_install_patterns("install_patterns")?;
226
227 Ok(InstalledPackage {
228 id: row.get("id")?,
229 repo_name: row.get("repo_name")?,
230 pkg: row.get("pkg")?,
231 pkg_id: row.get("pkg_id")?,
232 pkg_name: row.get("pkg_name")?,
233 pkg_type: row.get("pkg_type")?,
234 version: row.get("version")?,
235 size: row.get("size")?,
236 checksum: row.get("checksum")?,
237 installed_path: row.get("installed_path")?,
238 installed_date: row.get("installed_date")?,
239 profile: row.get("profile")?,
240 pinned: row.get("pinned")?,
241 is_installed: row.get("is_installed")?,
242 with_pkg_id: row.get("with_pkg_id")?,
243 detached: row.get("detached")?,
244 unlinked: row.get("unlinked")?,
245 provides,
246 portable_path: row.get("portable_path")?,
247 portable_home: row.get("portable_home")?,
248 portable_config: row.get("portable_config")?,
249 portable_share: row.get("portable_share")?,
250 portable_cache: row.get("portable_cache")?,
251 install_patterns,
252 })
253 }
254}
255
256#[derive(Deserialize)]
257#[serde(untagged)]
258enum FlexiBool {
259 Bool(bool),
260 String(String),
261}
262
263fn empty_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
264where
265 D: Deserializer<'de>,
266{
267 let s: Option<String> = Option::deserialize(deserializer)?;
268 Ok(s.filter(|s| !s.is_empty()))
269}
270
271fn optional_number<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
272where
273 D: Deserializer<'de>,
274{
275 let s: Option<String> = Option::deserialize(deserializer)?;
276 Ok(s.filter(|s| !s.is_empty())
277 .and_then(|s| s.parse::<i64>().ok())
278 .filter(|&n| n >= 0)
279 .map(|n| n as u64))
280}
281
282fn flexible_bool<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
283where
284 D: Deserializer<'de>,
285{
286 match Option::<FlexiBool>::deserialize(deserializer)? {
287 Some(FlexiBool::Bool(b)) => Ok(Some(b)),
288 Some(FlexiBool::String(s)) => match s.to_lowercase().as_str() {
289 "true" | "yes" | "1" => Ok(Some(true)),
290 "false" | "no" | "0" => Ok(Some(false)),
291 "" => Ok(None),
292 _ => Err(de::Error::invalid_value(
293 de::Unexpected::Str(&s),
294 &"a valid boolean (true/false, yes/no, 1/0)",
295 )),
296 },
297 None => Ok(None),
298 }
299}
300
301#[derive(Debug, Default, Clone, Deserialize, Serialize)]
302pub struct RemotePackage {
303 #[serde(deserialize_with = "flexible_bool", alias = "_disabled")]
304 pub disabled: Option<bool>,
305
306 #[serde(alias = "_disabled_reason")]
307 pub disabled_reason: Option<serde_json::Value>,
308
309 #[serde(default, deserialize_with = "optional_number")]
310 pub rank: Option<u64>,
311
312 #[serde(default, deserialize_with = "empty_is_none")]
313 pub pkg: Option<String>,
314 pub pkg_id: String,
315 pub pkg_name: String,
316
317 #[serde(default, deserialize_with = "empty_is_none")]
318 pub pkg_family: Option<String>,
319
320 #[serde(default, deserialize_with = "empty_is_none")]
321 pub pkg_type: Option<String>,
322
323 #[serde(default, deserialize_with = "empty_is_none")]
324 pub pkg_webpage: Option<String>,
325
326 pub description: String,
327 pub version: String,
328
329 #[serde(default, deserialize_with = "empty_is_none")]
330 pub version_upstream: Option<String>,
331
332 pub download_url: String,
333
334 #[serde(default, deserialize_with = "optional_number")]
335 pub size_raw: Option<u64>,
336
337 #[serde(default, deserialize_with = "empty_is_none")]
338 pub ghcr_pkg: Option<String>,
339
340 #[serde(default, deserialize_with = "optional_number")]
341 pub ghcr_size_raw: Option<u64>,
342
343 pub ghcr_files: Option<Vec<String>>,
344
345 #[serde(default, deserialize_with = "empty_is_none")]
346 pub ghcr_blob: Option<String>,
347
348 #[serde(default, deserialize_with = "empty_is_none")]
349 pub ghcr_url: Option<String>,
350
351 #[serde(alias = "src_url")]
352 pub src_urls: Option<Vec<String>>,
353
354 #[serde(alias = "homepage")]
355 pub homepages: Option<Vec<String>>,
356
357 #[serde(alias = "license")]
358 pub licenses: Option<Vec<String>>,
359
360 #[serde(alias = "maintainer")]
361 pub maintainers: Option<Vec<String>>,
362
363 #[serde(alias = "note")]
364 pub notes: Option<Vec<String>>,
365
366 #[serde(alias = "tag")]
367 pub tags: Option<Vec<String>>,
368
369 #[serde(default, deserialize_with = "empty_is_none")]
370 pub bsum: Option<String>,
371
372 #[serde(default, deserialize_with = "empty_is_none")]
373 pub shasum: Option<String>,
374
375 #[serde(default, deserialize_with = "empty_is_none")]
376 pub build_id: Option<String>,
377
378 #[serde(default, deserialize_with = "empty_is_none")]
379 pub build_date: Option<String>,
380
381 #[serde(default, deserialize_with = "empty_is_none", alias = "build_gha")]
382 pub build_action: Option<String>,
383
384 #[serde(default, deserialize_with = "empty_is_none")]
385 pub build_script: Option<String>,
386
387 #[serde(default, deserialize_with = "empty_is_none")]
388 pub build_log: Option<String>,
389
390 #[serde(alias = "category")]
391 pub categories: Option<Vec<String>>,
392
393 pub provides: Option<Vec<String>>,
394
395 #[serde(default, deserialize_with = "empty_is_none")]
396 pub icon: Option<String>,
397
398 #[serde(default, deserialize_with = "empty_is_none")]
399 pub desktop: Option<String>,
400
401 #[serde(default, deserialize_with = "empty_is_none")]
402 pub appstream: Option<String>,
403
404 #[serde(default, deserialize_with = "empty_is_none")]
405 pub app_id: Option<String>,
406
407 #[serde(default, deserialize_with = "optional_number")]
408 pub download_count: Option<u64>,
409
410 #[serde(default, deserialize_with = "optional_number")]
411 pub download_count_month: Option<u64>,
412
413 #[serde(default, deserialize_with = "optional_number")]
414 pub download_count_week: Option<u64>,
415
416 #[serde(default, deserialize_with = "flexible_bool")]
417 pub bundle: Option<bool>,
418
419 #[serde(default, deserialize_with = "empty_is_none")]
420 pub bundle_type: Option<String>,
421
422 #[serde(default, deserialize_with = "flexible_bool")]
423 pub soar_syms: Option<bool>,
424
425 #[serde(default, deserialize_with = "flexible_bool")]
426 pub deprecated: Option<bool>,
427
428 #[serde(default, deserialize_with = "flexible_bool")]
429 pub desktop_integration: Option<bool>,
430
431 #[serde(default, deserialize_with = "flexible_bool")]
432 pub external: Option<bool>,
433
434 #[serde(default, deserialize_with = "flexible_bool")]
435 pub installable: Option<bool>,
436
437 #[serde(default, deserialize_with = "flexible_bool")]
438 pub portable: Option<bool>,
439
440 #[serde(default, deserialize_with = "flexible_bool")]
441 pub recurse_provides: Option<bool>,
442
443 #[serde(default, deserialize_with = "flexible_bool")]
444 pub trusted: Option<bool>,
445
446 #[serde(default, deserialize_with = "empty_is_none")]
447 pub version_latest: Option<String>,
448
449 #[serde(default, deserialize_with = "flexible_bool")]
450 pub version_outdated: Option<bool>,
451
452 pub repology: Option<Vec<String>>,
453 pub snapshots: Option<Vec<String>>,
454 pub replaces: Option<Vec<String>>,
455}
456
457impl PackageExt for Package {
458 fn pkg_name(&self) -> &str {
459 &self.pkg_name
460 }
461
462 fn pkg_id(&self) -> &str {
463 &self.pkg_id
464 }
465
466 fn version(&self) -> &str {
467 &self.version
468 }
469
470 fn repo_name(&self) -> &str {
471 &self.repo_name
472 }
473}
474
475impl PackageExt for InstalledPackage {
476 fn pkg_name(&self) -> &str {
477 &self.pkg_name
478 }
479
480 fn pkg_id(&self) -> &str {
481 &self.pkg_id
482 }
483
484 fn version(&self) -> &str {
485 &self.version
486 }
487
488 fn repo_name(&self) -> &str {
489 &self.repo_name
490 }
491}