1use std::path::Path;
2
3use anyhow::Context;
4use diesel::{prelude::*, sqlite::Sqlite};
5
6use crate::db::{
7 connection,
8 schema::{dependencies, downloaded_bottles, linked_files},
9};
10
11#[derive(Clone, Debug, Queryable, Selectable)]
12#[diesel(table_name = downloaded_bottles)]
13#[diesel(check_for_backend(Sqlite))]
14pub struct DownloadedBottle {
15 id: i32,
16 name: String,
17 version: String,
18 path: String,
19}
20
21#[derive(Debug, Insertable)]
22#[diesel(table_name = downloaded_bottles)]
23#[diesel(check_for_backend(Sqlite))]
24struct NewDownloadedBottle<'a> {
25 name: &'a str,
26 version: &'a str,
27 path: &'a str,
28}
29
30#[derive(Debug, Queryable, Selectable)]
31#[diesel(table_name = linked_files)]
32#[diesel(check_for_backend(Sqlite))]
33pub struct LinkedFile {
34 id: i32,
35 path: String,
36}
37
38#[derive(Debug, Insertable)]
39#[diesel(table_name = linked_files)]
40#[diesel(check_for_backend(Sqlite))]
41struct NewLinkedFile<'a> {
42 path: &'a str,
43 bottle_id: i32,
44}
45
46#[derive(Debug, Queryable, Selectable, Insertable)]
47#[diesel(table_name = dependencies)]
48#[diesel(check_for_backend(Sqlite))]
49pub struct Dependency {
50 dependent_id: Option<i32>,
51 dependency_id: i32,
52}
53
54impl DownloadedBottle {
55 pub fn create(name: &str, version: &str, path: &Path) -> anyhow::Result<DownloadedBottle> {
56 let mut db = connection()?;
57
58 let result = diesel::insert_into(downloaded_bottles::table)
59 .values(NewDownloadedBottle {
60 name,
61 version,
62 path: path.to_str().context("Installed bottle path is non-utf8")?,
63 })
64 .returning(DownloadedBottle::as_returning())
65 .get_result(&mut *db)?;
66
67 Ok(result)
68 }
69
70 pub fn get(name: &str, version: &str) -> anyhow::Result<Option<DownloadedBottle>> {
71 use downloaded_bottles::dsl;
72
73 let mut db = connection()?;
74 let name_value = name;
75 let version_value = version;
76
77 let mut results = dsl::downloaded_bottles
78 .filter(dsl::name.eq(name_value))
79 .filter(dsl::version.eq(version_value))
80 .select(DownloadedBottle::as_select())
81 .load_iter(&mut *db)?;
82
83 if let Some(bottle) = results.next() {
84 Ok(Some(bottle?))
85 } else {
86 Ok(None)
87 }
88 }
89
90 fn get_by_id(id: i32) -> anyhow::Result<DownloadedBottle> {
91 use downloaded_bottles::dsl;
92
93 let mut db = connection()?;
94
95 let result = dsl::downloaded_bottles
96 .filter(dsl::id.eq(id))
97 .select(Self::as_select())
98 .first(&mut *db)?;
99
100 Ok(result)
101 }
102
103 pub fn get_all() -> anyhow::Result<Vec<DownloadedBottle>> {
104 use downloaded_bottles::dsl;
105
106 let mut db = connection()?;
107
108 let results = dsl::downloaded_bottles
109 .order((dsl::name, dsl::version))
110 .select(DownloadedBottle::as_select())
111 .load(&mut *db)?;
112
113 Ok(results)
114 }
115
116 pub fn delete(&self) -> anyhow::Result<()> {
117 use downloaded_bottles::dsl;
118
119 let mut db = connection()?;
120
121 diesel::delete(downloaded_bottles::table)
122 .filter(dsl::id.eq(self.id))
123 .execute(&mut *db)?;
124
125 Ok(())
126 }
127
128 pub fn id(&self) -> i32 {
129 self.id
130 }
131
132 pub fn name(&self) -> &str {
133 &self.name
134 }
135
136 pub fn version(&self) -> &str {
137 &self.version
138 }
139
140 pub fn path(&self) -> &Path {
141 self.path.as_ref()
142 }
143
144 pub fn linked_files(&self) -> anyhow::Result<Vec<LinkedFile>> {
145 use linked_files::dsl;
146
147 let mut db = connection()?;
148
149 let results = dsl::linked_files
150 .filter(dsl::bottle_id.eq(self.id))
151 .select(LinkedFile::as_select())
152 .load(&mut *db)?;
153
154 Ok(results)
155 }
156}
157
158impl LinkedFile {
159 pub fn create(path: &Path, bottle: &DownloadedBottle) -> anyhow::Result<()> {
160 let mut db = connection()?;
161
162 diesel::insert_into(linked_files::table)
163 .values(NewLinkedFile {
164 path: path.to_str().context("Linked file path is non-utf8")?,
165 bottle_id: bottle.id,
166 })
167 .on_conflict(linked_files::dsl::path)
168 .do_nothing()
169 .execute(&mut *db)?;
170
171 Ok(())
172 }
173
174 pub fn delete(&self) -> anyhow::Result<()> {
175 use linked_files::dsl;
176
177 let mut db = connection()?;
178
179 diesel::delete(linked_files::table)
180 .filter(dsl::id.eq(self.id))
181 .execute(&mut *db)?;
182
183 Ok(())
184 }
185
186 pub fn path(&self) -> &Path {
187 self.path.as_ref()
188 }
189}
190
191impl Dependency {
192 pub fn get_all() -> anyhow::Result<Vec<Dependency>> {
193 use dependencies::dsl;
194
195 let mut db = connection()?;
196
197 let result = dsl::dependencies
198 .select(Dependency::as_select())
199 .load(&mut *db)?;
200
201 Ok(result)
202 }
203
204 pub fn replace_all<'a>(
205 dependencies: impl Iterator<Item = (Option<&'a DownloadedBottle>, &'a DownloadedBottle)>,
206 ) -> anyhow::Result<()> {
207 use dependencies::dsl;
208
209 let mut db = connection()?;
210
211 db.transaction::<(), anyhow::Error, _>(|db| {
212 diesel::delete(dsl::dependencies).execute(db)?;
213
214 let new_dependencies = dependencies
215 .map(|(dependent, dependency)| Dependency {
216 dependent_id: dependent.map(|b| b.id),
217 dependency_id: dependency.id,
218 })
219 .collect::<Vec<_>>();
220 diesel::insert_into(dsl::dependencies)
221 .values(&new_dependencies)
222 .execute(db)?;
223
224 Ok(())
225 })?;
226
227 Ok(())
228 }
229
230 pub fn dependent(&self) -> anyhow::Result<Option<DownloadedBottle>> {
232 if let Some(id) = self.dependent_id {
233 Ok(Some(DownloadedBottle::get_by_id(id)?))
234 } else {
235 Ok(None)
236 }
237 }
238
239 pub fn dependent_id(&self) -> Option<i32> {
240 self.dependent_id
241 }
242
243 pub fn dependency(&self) -> anyhow::Result<DownloadedBottle> {
244 DownloadedBottle::get_by_id(self.dependency_id)
245 }
246
247 pub fn dependency_id(&self) -> i32 {
248 self.dependency_id
249 }
250}