soar_core/database/
repository.rs1use regex::Regex;
2use rusqlite::{params, Result, Transaction};
3
4use super::{models::RemotePackage, packages::PackageProvide, statements::DbStatements};
5
6pub struct PackageRepository<'a> {
7 tx: &'a Transaction<'a>,
8 statements: DbStatements<'a>,
9 repo_name: &'a str,
10}
11
12impl<'a> PackageRepository<'a> {
13 pub fn new(tx: &'a Transaction<'a>, statements: DbStatements<'a>, repo_name: &'a str) -> Self {
14 Self {
15 tx,
16 statements,
17 repo_name,
18 }
19 }
20
21 pub fn import_packages(&mut self, metadata: &[RemotePackage]) -> Result<()> {
22 self.statements
23 .repo_insert
24 .execute(params![self.repo_name, ""])?;
27
28 for package in metadata {
29 self.insert_package(package)?;
30 }
31 Ok(())
32 }
33
34 fn get_or_create_maintainer(&mut self, name: &str, contact: &str) -> Result<i64> {
35 self.statements
36 .maintainer_check
37 .query_row(params![contact], |row| row.get(0))
38 .or_else(|_| {
39 self.statements
40 .maintainer_insert
41 .execute(params![name, contact])?;
42 Ok(self.tx.last_insert_rowid())
43 })
44 }
45
46 fn extract_name_and_contact(&self, input: &str) -> Option<(String, String)> {
47 let re = Regex::new(r"^([^()]+) \(([^)]+)\)$").unwrap();
48
49 if let Some(captures) = re.captures(input) {
50 let name = captures.get(1).map_or("", |m| m.as_str()).to_string();
51 let contact = captures.get(2).map_or("", |m| m.as_str()).to_string();
52 Some((name, contact))
53 } else {
54 None
55 }
56 }
57
58 fn insert_package(&mut self, package: &RemotePackage) -> Result<()> {
59 let disabled_reason = serde_json::to_string(&package.disabled_reason).unwrap();
60 let licenses = serde_json::to_string(&package.licenses).unwrap();
61 let ghcr_files = serde_json::to_string(&package.ghcr_files).unwrap();
62 let homepages = serde_json::to_string(&package.homepages).unwrap();
63 let notes = serde_json::to_string(&package.notes).unwrap();
64 let source_urls = serde_json::to_string(&package.src_urls).unwrap();
65 let tags = serde_json::to_string(&package.tags).unwrap();
66 let categories = serde_json::to_string(&package.categories).unwrap();
67 let snapshots = serde_json::to_string(&package.snapshots).unwrap();
68 let repology = serde_json::to_string(&package.repology).unwrap();
69 let replaces = serde_json::to_string(&package.replaces).unwrap();
70
71 const PROVIDES_DELIMITERS: &[&str] = &["==", "=>", ":"];
72
73 let provides = package.provides.as_ref().map(|vec| {
74 vec.iter()
75 .filter_map(|p| {
76 let include = *p == package.pkg_name
77 || matches!(package.recurse_provides, Some(true))
78 || p.strip_prefix(&package.pkg_name).is_some_and(|rest| {
79 PROVIDES_DELIMITERS.iter().any(|d| rest.starts_with(d))
80 });
81
82 include.then(|| PackageProvide::from_string(p))
83 })
84 .collect::<Vec<_>>()
85 });
86
87 let provides = serde_json::to_string(&provides).unwrap();
88 let inserted = self.statements.package_insert.execute(params![
89 package.disabled,
90 disabled_reason,
91 package.rank,
92 package.pkg,
93 package.pkg_id,
94 package.pkg_name,
95 package.pkg_family,
96 package.pkg_type,
97 package.pkg_webpage,
98 package.app_id,
99 package.description,
100 package.version,
101 package.version_upstream,
102 licenses,
103 package.download_url,
104 package.size_raw,
105 package.ghcr_pkg,
106 package.ghcr_size_raw,
107 ghcr_files,
108 package.ghcr_blob,
109 package.ghcr_url,
110 package.bsum,
111 package.shasum,
112 package.icon,
113 package.desktop,
114 package.appstream,
115 homepages,
116 notes,
117 source_urls,
118 tags,
119 categories,
120 package.build_id,
121 package.build_date,
122 package.build_action,
123 package.build_script,
124 package.build_log,
125 provides,
126 snapshots,
127 repology,
128 replaces,
129 package.download_count,
130 package.download_count_week,
131 package.download_count_month,
132 package.bundle.unwrap_or(false),
133 package.bundle_type,
134 package.soar_syms.unwrap_or(false),
135 package.deprecated.unwrap_or(false),
136 package.desktop_integration,
137 package.external,
138 package.installable,
139 package.portable,
140 package.recurse_provides,
141 package.trusted,
142 package.version_latest,
143 package.version_outdated
144 ])?;
145 if inserted == 0 {
146 return Ok(());
147 }
148 let package_id = self.tx.last_insert_rowid();
149 for maintainer in &package.maintainers.clone().unwrap_or_default() {
150 let typed = self.extract_name_and_contact(maintainer);
151 if let Some((name, contact)) = typed {
152 let maintainer_id = self.get_or_create_maintainer(&name, &contact)?;
153 self.statements
154 .pkg_maintainer_insert
155 .execute(params![maintainer_id, package_id])?;
156 }
157 }
158
159 Ok(())
160 }
161}