1use std::{collections::HashMap, fmt::Debug, path::PathBuf};
2
3use miette::{Diagnostic, IntoDiagnostic, Result};
4use rusqlite::{Connection, Error as RusqliteError};
5use thiserror::Error;
6
7use crate::input::PkgDeclaration;
8
9pub type EntryPoint = PathBuf;
10
11#[derive(Debug)]
12pub enum PkgType {
13 SingleExecutable,
14 Directory(EntryPoint),
15}
16
17#[derive(Debug)]
18pub struct Version {
19 pub first_cell: String,
20 pub second_cell: String,
21 pub third_cell: String,
22}
23
24pub type Verstion = Version;
25
26#[derive(Debug)]
27pub struct Pkg {
28 pub name: String,
29 pub version: Version,
30 pub path: PathBuf,
31 pub pkg_type: PkgType,
32}
33
34#[derive(Debug)]
35pub struct Db {
36 pub conn: Connection,
37 pub path: PathBuf,
38}
39
40#[derive(Error, Debug, Diagnostic)]
41pub enum DbError {
42 #[error(transparent)]
43 #[diagnostic(code(db::sqlite_error))]
44 SqliteError(#[from] RusqliteError),
45
46 #[error(transparent)]
47 #[diagnostic(code(db::io_error))]
48 IoError(#[from] std::io::Error),
49
50 #[error("Invalid UTF-8 in package path")]
51 #[diagnostic(code(db::invalid_utf8))]
52 InvalidPath,
53}
54
55mod sql {
56 pub const CREATE_PKGS_TABLE: &str = r#"
57 CREATE TABLE IF NOT EXISTS packages (
58 name TEXT NOT NULL,
59 version TEXT NOT NULL,
60 path TEXT NOT NULL,
61 pkg_type TEXT NOT NULL,
62 entry_point TEXT NOT NULL,
63 bridge TEXT NOT NULL,
64 PRIMARY KEY (name)
65 );
66 "#; pub const GET_PKGS: &str = r#"
68 SELECT name, version, path, pkg_type, entry_point FROM packages;
69 "#;
70
71 pub const GET_PKGS_BY_NAME: &str = r#"
72 SELECT name, version, path, pkg_type FROM packages WHERE name = ?;
73 "#;
74
75 pub const GET_PKGS_BY_NAMES: &str = r#"
76 SELECT name, version, path, pkg_type, entry_point FROM packages WHERE name IN ({});
77 "#;
78 pub const INSERT_PKGS: &str = r#"
79 INSERT INTO packages (name, version, path, pkg_type, entry_point, bridge)
80 VALUES (?, ?, ?, ?, ?, ?);
81 "#;
82 pub const DELETE_PKGS: &str = r#"
83 DELETE FROM packages WHERE name = ?;
84 "#;
85 pub const GET_PKG_BRIDGE_BY_NAME: &str = r#"
86 SELECT bridge FROM packages WHERE name = ?;
87 "#;
88 pub const GET_PKGS_BY_BRIDGE: &str = r#"
89 SELECT name, version, path, pkg_type, entry_point FROM packages WHERE bridge = ?;
90 "#;
91 pub const GET_BRIDGES: &str = r#"
92 SELECT bridge FROM packages GROUP BY bridge;
93 "#;
94}
95
96impl Pkg {
97 pub fn to_pkg_declaration_with_empty_attributes(&self) -> PkgDeclaration {
100 PkgDeclaration {
101 name: self.name.clone(),
102 input: self.path.to_str().unwrap().to_string(),
103 attributes: HashMap::new(),
104 }
105 }
106}
107
108impl Db {
109 pub fn new(path: &PathBuf) -> Result<Self> {
110 let parent = path.parent().ok_or(DbError::InvalidPath)?;
111 std::fs::create_dir_all(parent).into_diagnostic()?;
112
113 let conn = Connection::open(path).into_diagnostic()?;
114
115 conn.execute(sql::CREATE_PKGS_TABLE, []).into_diagnostic()?;
116
117 Ok(Self {
118 conn,
119 path: path.clone(),
120 })
121 }
122
123 pub fn get_bridges(&self) -> Result<Vec<String>> {
124 let mut stmt = self.conn.prepare(sql::GET_BRIDGES).into_diagnostic()?;
125 let rows = stmt
126 .query_map([], |row| {
127 let bridge: String = row.get(0)?;
128 Ok(bridge)
129 })
130 .into_diagnostic()?;
131
132 let mut bridges = Vec::new();
133
134 for bridge in rows {
135 bridges.push(bridge.into_diagnostic()?);
136 }
137 Ok(bridges)
138 }
139
140 pub fn get_pkg_bridge_by_name(&self, pkg_name: &str) -> Result<String> {
141 let mut stmt = self
142 .conn
143 .prepare(sql::GET_PKG_BRIDGE_BY_NAME)
144 .into_diagnostic()?;
145
146 let bridge = stmt
147 .query_row([&pkg_name], |row| row.get(0))
148 .into_diagnostic()?;
149
150 Ok(bridge)
151 }
152
153 pub fn which_pkgs_are_installed<'a>(&'a self, pkgs: &'a [String]) -> Result<Vec<&'a String>> {
156 let mut installed_pkgs = Vec::new();
157 let mut stmt = self.conn.prepare(sql::GET_PKGS_BY_NAME).into_diagnostic()?;
158
159 for pkg in pkgs {
160 let exists = stmt.exists([&pkg]).into_diagnostic()?;
162 if exists {
163 installed_pkgs.push(pkg);
164 }
165 }
166
167 Ok(installed_pkgs)
168 }
169
170 pub fn install_bridge_pkgs(&self, pkgs: &[&Pkg], bridge: &String) -> Result<()> {
171 let mut stmt = self.conn.prepare(sql::INSERT_PKGS).into_diagnostic()?;
172
173 for pkg in pkgs {
174 let pkg_version = format!(
175 "{}.{}.{}",
176 pkg.version.first_cell, pkg.version.second_cell, pkg.version.third_cell
177 );
178
179 let pkg_type = match &pkg.pkg_type {
180 PkgType::SingleExecutable => "SingleExecutable".to_string(),
181 PkgType::Directory(_) => "Directory".to_string(),
182 };
183
184 let pkg_path = pkg.path.to_str().ok_or(DbError::InvalidPath)?.to_string();
185
186 let entry_point = match &pkg.pkg_type {
187 PkgType::SingleExecutable => pkg_path.to_string(), PkgType::Directory(ep) => ep.to_string_lossy().into_owned(), };
190
191 stmt.execute([
192 &pkg.name,
193 &pkg_version,
194 &pkg_path,
195 &pkg_type,
196 &entry_point,
197 bridge,
198 ])
199 .into_diagnostic()?;
200 }
201
202 Ok(())
203 }
204
205 pub fn remove_pkgs(&self, pkgs_names: &[String]) -> Result<()> {
206 let mut stmt = self.conn.prepare(sql::DELETE_PKGS).into_diagnostic()?;
207
208 for pkg_name in pkgs_names {
209 stmt.execute([&pkg_name]).into_diagnostic()?;
210 }
211
212 Ok(())
213 }
214
215 pub fn get_pkgs(&self) -> Result<Vec<Pkg>> {
216 let mut stmt = self.conn.prepare(sql::GET_PKGS).into_diagnostic()?;
217 let rows = stmt
218 .query_map([], |row| {
219 let name: String = row.get(0)?;
220 let version: String = row.get(1)?;
221 let path: String = row.get(2)?;
222 let pkg_type: String = row.get(3)?;
223 let entry_point: String = row.get(4)?;
224
225 let version_parts: Vec<&str> = version.split('.').collect();
227 if version_parts.len() != 3 {
228 return Err(RusqliteError::InvalidQuery);
229 }
230
231 let pkg_type = match pkg_type.as_str() {
233 "SingleExecutable" => PkgType::SingleExecutable,
234 "Directory" => PkgType::Directory(PathBuf::from(&entry_point)),
235 _ => return Err(RusqliteError::InvalidQuery),
236 };
237
238 Ok(Pkg {
239 name,
240 version: Version {
241 first_cell: version_parts[0].to_string(),
242 second_cell: version_parts[1].to_string(),
243 third_cell: version_parts[2].to_string(),
244 },
245 path: PathBuf::from(path),
246 pkg_type,
247 })
248 })
249 .into_diagnostic()?;
250
251 let mut pkgs = Vec::new();
252 for pkg in rows {
253 pkgs.push(pkg.into_diagnostic()?);
254 }
255
256 Ok(pkgs)
257 }
258
259 pub fn get_pkgs_by_name(&self, pkg_names: &[String]) -> Result<Vec<Pkg>> {
260 if pkg_names.is_empty() {
261 return Ok(Vec::new());
262 }
263
264 let placeholders = pkg_names.iter().map(|_| "?").collect::<Vec<_>>().join(",");
265 let sql = sql::GET_PKGS_BY_NAMES.replace("{}", &placeholders);
266
267 let mut stmt = self.conn.prepare(&sql).into_diagnostic()?;
268
269 let params: Vec<&str> = pkg_names.iter().map(|s| s.as_str()).collect();
270
271 let rows = stmt
272 .query_map(rusqlite::params_from_iter(params.iter()), |row| {
273 let name: String = row.get(0)?;
274 let version: String = row.get(1)?;
275 let path: String = row.get(2)?;
276 let pkg_type: String = row.get(3)?;
277 let entry_point: String = row.get(4)?;
278
279 let version_parts: Vec<&str> = version.split('.').collect();
281 if version_parts.len() != 3 {
282 return Err(RusqliteError::InvalidQuery);
283 }
284
285 let pkg_type = match pkg_type.as_str() {
287 "SingleExecutable" => PkgType::SingleExecutable,
288 "Directory" => PkgType::Directory(PathBuf::from(&entry_point)),
289 _ => return Err(RusqliteError::InvalidQuery),
290 };
291
292 Ok(Pkg {
293 name,
294 version: Version {
295 first_cell: version_parts[0].to_string(),
296 second_cell: version_parts[1].to_string(),
297 third_cell: version_parts[2].to_string(),
298 },
299 path: PathBuf::from(path),
300 pkg_type,
301 })
302 })
303 .into_diagnostic()?;
304
305 let mut pkgs = Vec::new();
306 for pkg in rows {
307 pkgs.push(pkg.into_diagnostic()?);
308 }
309
310 Ok(pkgs)
311 }
312
313 pub fn get_pkgs_by_bridge(&self, bridge_name: &String) -> Result<Vec<Pkg>> {
314 let mut stmt = self
315 .conn
316 .prepare(sql::GET_PKGS_BY_BRIDGE)
317 .into_diagnostic()?;
318
319 let rows = stmt
320 .query_map([&bridge_name], |row| {
321 let name: String = row.get(0)?;
322 let version: String = row.get(1)?;
323 let path: String = row.get(2)?;
324 let pkg_type: String = row.get(3)?;
325 let entry_point: String = row.get(4)?;
326
327 let version_parts: Vec<&str> = version.split('.').collect();
329 if version_parts.len() != 3 {
330 return Err(RusqliteError::InvalidQuery);
331 }
332
333 let pkg_type = match pkg_type.as_str() {
335 "SingleExecutable" => PkgType::SingleExecutable,
336 "Directory" => PkgType::Directory(PathBuf::from(&entry_point)),
337 _ => return Err(RusqliteError::InvalidQuery),
338 };
339
340 Ok(Pkg {
341 name,
342 version: Version {
343 first_cell: version_parts[0].to_string(),
344 second_cell: version_parts[1].to_string(),
345 third_cell: version_parts[2].to_string(),
346 },
347 path: PathBuf::from(path),
348 pkg_type,
349 })
350 })
351 .into_diagnostic()?;
352
353 let mut pkgs = Vec::new();
354 for pkg in rows {
355 pkgs.push(pkg.into_diagnostic()?);
356 }
357
358 Ok(pkgs)
359 }
360
361 pub fn which_pkgs_are_not_installed<'a>(
362 &'a self,
363 pkgs: &'a [String],
364 ) -> Result<Vec<&'a String>> {
365 let installed_pkgs = self.get_pkgs()?;
366 let mut not_installed_pkgs = Vec::new();
367
368 for pkg in pkgs {
369 if !installed_pkgs
370 .iter()
371 .any(|installed| installed.name == pkg.clone())
372 {
373 not_installed_pkgs.push(pkg);
374 }
375 }
376
377 Ok(not_installed_pkgs)
378 }
379}