pkg/backend/pkgar_backend/
mod.rs1use std::{
2 collections::HashMap,
3 fs,
4 path::{Path, PathBuf},
5};
6
7use pkgar::{PackageFile, Transaction};
8use pkgar_keys::PublicKeyFile;
9
10use self::packages::Packages;
11use super::{Backend, Error};
12use crate::{
13 package::Repository,
14 repo_manager::{RemotePath, RepoManager},
15 Package, PackageName, DOWNLOAD_PATH, PACKAGES_PATH,
16};
17
18mod packages;
19
20pub struct PkgarBackend {
21 install_path: PathBuf,
22 packages: Packages,
23 repo_manager: RepoManager,
24 pkey_files: HashMap<String, PublicKeyFile>,
25}
26
27const PACKAGES_DIR: &str = "pkg/packages";
28
29fn get_package_path(repokey: &str, package: &PackageName) -> PathBuf {
31 let local_file = format!("{}_{}.pkgar", repokey, package.as_str());
32 PathBuf::from(DOWNLOAD_PATH).join(local_file)
33}
34fn get_package(
35 repokey: &str,
36 package: &PackageName,
37 pubkey: &PublicKeyFile,
38) -> Result<PackageFile, Error> {
39 let local_path = get_package_path(repokey, package);
40 Ok(PackageFile::new(local_path, &pubkey.pkey)?)
41}
42
43fn get_pkey_file<'a>(
45 key: &'a str,
46 pkey_files: &'a mut HashMap<String, PublicKeyFile>,
47 repo_manager: &'a RepoManager,
48) -> Result<Option<&'a PublicKeyFile>, Error> {
49 if pkey_files.get(key).is_none() {
50 for remote in repo_manager.remotes.iter() {
51 if remote.key == key {
52 pkey_files.insert(
53 remote.key.clone(),
54 PublicKeyFile::open(remote.pubkey.clone())?,
55 );
56 }
57 }
58 }
59
60 if let Some(value) = pkey_files.get(key) {
61 return Ok(Some(value));
62 }
63
64 Ok(None)
65}
66
67impl PkgarBackend {
68 pub fn new<P: AsRef<Path>>(install_path: P, repo_manager: RepoManager) -> Result<Self, Error> {
69 let install_path = install_path.as_ref();
70
71 let packages_path = install_path.join(PACKAGES_PATH);
72 let packages_dir = install_path.join(PACKAGES_DIR);
73 let file = fs::read_to_string(&packages_path);
74
75 let packages;
76 match file {
77 Ok(toml) => {
78 packages = Packages::from_toml(&toml)?;
79 }
80 Err(_) => {
81 packages = Packages::default();
82 fs::create_dir_all(Path::new(&packages_path).parent().unwrap())?;
83 }
84 }
85
86 fs::write(packages_path, packages.to_toml())?;
88 fs::create_dir_all(&packages_dir)?;
89 let pkey_files = HashMap::new();
90
91 Ok(PkgarBackend {
92 install_path: install_path.to_path_buf(),
93 packages,
94 repo_manager,
95 pkey_files,
96 })
97 }
98
99 fn get_package_head(&mut self, package: &PackageName) -> Result<PackageFile, Error> {
101 let path = self
102 .install_path
103 .join(PACKAGES_DIR)
104 .join(format!("{package}.pkgar_head"));
105
106 self.repo_manager.sync_keys()?;
107
108 for remote in self.repo_manager.remotes.iter() {
110 let pubkey = get_pkey_file(&remote.key, &mut self.pkey_files, &self.repo_manager)?;
111 if let Some(key) = pubkey {
112 let pkg = PackageFile::new(&path, &key.pkey);
113 if let Ok(p) = pkg {
114 return Ok(p);
115 }
116 }
117 }
118 Err(Error::RepoCacheNotFound(package.clone()))
119 }
120
121 fn get_package_pkgar(
123 &mut self,
124 package: &PackageName,
125 ) -> Result<(&RemotePath, &PublicKeyFile), Error> {
126 let r = self.repo_manager.sync_pkgar(package)?;
127 let pubkey = get_pkey_file(&r.key, &mut self.pkey_files, &self.repo_manager)?;
128 if let Some(pkey) = pubkey {
129 Ok((r, pkey))
130 } else {
131 Err(Error::RepoCacheNotFound(package.clone()))
133 }
134 }
135
136 fn get_package_toml(&self, package: &PackageName) -> Result<String, Error> {
138 self.repo_manager.sync_toml(package)
139 }
140
141 fn remove_package_head(&mut self, package: &PackageName) -> Result<(), Error> {
142 let path = self
143 .install_path
144 .join(PACKAGES_DIR)
145 .join(format!("{package}.pkgar_head"));
146
147 fs::remove_file(path)?;
148 Ok(())
149 }
150
151 fn create_head(
152 &mut self,
153 package: &PackageName,
154 repokey: &str,
155 pubkey_path: &str,
156 ) -> Result<(), Error> {
157 pkgar::split(
159 pubkey_path,
160 get_package_path(repokey, package),
161 self.install_path
162 .join(PACKAGES_DIR)
163 .join(format!("{package}.pkgar_head")),
164 Option::<&str>::None,
165 )?;
166
167 Ok(())
168 }
169}
170
171impl Backend for PkgarBackend {
172 fn install(&mut self, package: PackageName) -> Result<(), Error> {
173 let (repo, pubkey) = self.get_package_pkgar(&package)?;
174 let (repokey, pubkey_path) = (repo.key.clone(), repo.pubkey.clone());
175 let mut pkg = get_package(&repo.key, &package, pubkey)?;
176 let mut install = Transaction::install(&mut pkg, &self.install_path)?;
177 install.commit()?;
178
179 self.create_head(&package, &repokey, &pubkey_path)?;
180
181 Ok(())
182 }
183
184 fn uninstall(&mut self, package: PackageName) -> Result<(), Error> {
185 if self.packages.protected.contains(&package) {
186 return Err(Error::ProtectedPackage(package));
187 }
188
189 let mut pkg = self.get_package_head(&package)?;
190 let mut remove = Transaction::remove(&mut pkg, &self.install_path)?;
191 remove.commit()?;
192
193 self.remove_package_head(&package)?;
194
195 Ok(())
196 }
197
198 fn upgrade(&mut self, package: PackageName) -> Result<(), Error> {
199 let mut pkg = self.get_package_head(&package)?;
200 let (repo, pubkey) = self.get_package_pkgar(&package)?;
201 let (repokey, pubkey_path) = (repo.key.clone(), repo.pubkey.clone());
202 let mut pkg2 = get_package(&repo.key, &package, pubkey)?;
203 let mut update = Transaction::replace(&mut pkg, &mut pkg2, &self.install_path)?;
204 update.commit()?;
205
206 self.create_head(&package, &repokey, &pubkey_path)?;
207
208 Ok(())
209 }
210
211 fn get_package_detail(&self, package: &PackageName) -> Result<Package, Error> {
212 let toml = self.get_package_toml(package)?;
213
214 Ok(Package::from_toml(&toml)?)
215 }
216
217 fn get_repository_detail(&self) -> Result<Repository, Error> {
218 let repo_str = PackageName::new("repo".to_string())?;
219 let toml = self.get_package_toml(&repo_str)?;
220
221 Ok(Repository::from_toml(&toml)?)
222 }
223
224 fn get_installed_packages(&self) -> Result<Vec<PackageName>, Error> {
225 let entries = fs::read_dir(self.install_path.join(PACKAGES_DIR))?;
226
227 let mut packages = vec![];
228
229 for entry in entries {
230 let entry = entry?;
231 let file_name = entry.file_name();
232 let file_name_str = file_name.to_str().ok_or(Error::IO(std::io::Error::new(
233 std::io::ErrorKind::Other,
234 "file name isn't UTF-8",
235 )))?;
236
237 if file_name_str.ends_with(".pkgar_head") {
238 let package = file_name_str.replace(".pkgar_head", "");
239 packages.push(PackageName::new(package)?);
240 }
241 }
242
243 Ok(packages)
244 }
245}
246
247impl Drop for PkgarBackend {
248 fn drop(&mut self) {
249 let packages_path = self.install_path.join(PACKAGES_PATH);
250 let _ = fs::write(packages_path, self.packages.to_toml());
252 }
253}