pkg/backend/pkgar_backend/
mod.rs

1use 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, repo_manager::RepoManager, Package, PackageName, DOWNLOAD_PATH,
14    PACKAGES_PATH,
15};
16
17mod packages;
18
19pub struct PkgarBackend {
20    install_path: PathBuf,
21    packages: Packages,
22    repo_manager: RepoManager,
23    pkey_files: HashMap<String, PublicKeyFile>,
24}
25
26const PACKAGES_DIR: &str = "pkg/packages";
27
28impl PkgarBackend {
29    pub fn new<P: AsRef<Path>>(install_path: P, repo_manager: RepoManager) -> Result<Self, Error> {
30        let install_path = install_path.as_ref();
31
32        let packages_path = install_path.join(PACKAGES_PATH);
33        let file = fs::read_to_string(&packages_path);
34
35        let packages;
36        match file {
37            Ok(toml) => {
38                packages = Packages::from_toml(&toml)?;
39            }
40
41            Err(_) => {
42                packages = Packages::default();
43                fs::create_dir_all(Path::new(&packages_path).parent().unwrap())?;
44                let write_result = fs::write(packages_path, packages.to_toml());
45                PkgarBackend::check_write_result(write_result)?;
46            }
47        }
48
49        let packages_dir = install_path.join(PACKAGES_DIR);
50        fs::create_dir_all(&packages_dir)?;
51
52        let mut pkey_files = HashMap::new();
53        for remote in repo_manager.remotes.iter() {
54            pkey_files.insert(
55                remote.key.clone(),
56                PublicKeyFile::open(remote.pubkey.clone())?,
57            );
58        }
59
60        Ok(PkgarBackend {
61            install_path: install_path.to_path_buf(),
62            packages,
63            repo_manager,
64            pkey_files,
65        })
66    }
67
68    fn get_package_head(&mut self, package: &PackageName) -> Result<PackageFile, Error> {
69        let path = self
70            .install_path
71            .join(PACKAGES_DIR)
72            .join(format!("{package}.pkgar_head"));
73
74        // TODO: A way to get chosen remote of a pkg so we can remove this trial loop
75        for remote in self.repo_manager.remotes.iter() {
76            let pubkey = self.pkey_files.get(&remote.key);
77            if let Some(key) = pubkey {
78                let pkg = PackageFile::new(&path, &key.pkey);
79                if let Ok(p) = pkg {
80                    return Ok(p);
81                }
82            }
83        }
84        Err(Error::ValidRepoNotFound)
85    }
86
87    fn get_package(&self, package: &PackageName, repokey: &str) -> Result<PackageFile, Error> {
88        Ok(PackageFile::new(
89            format!("{}/{package}.pkgar", DOWNLOAD_PATH),
90            &self
91                .pkey_files
92                .get(repokey)
93                .ok_or(Error::ValidRepoNotFound)?
94                .pkey,
95        )?)
96    }
97
98    fn get_package_toml(&self, package: &PackageName) -> Result<String, Error> {
99        self.repo_manager.sync_toml(package)
100    }
101
102    fn remove_package_head(&mut self, package: &PackageName) -> Result<(), Error> {
103        let path = self
104            .install_path
105            .join(PACKAGES_DIR)
106            .join(format!("{package}.pkgar_head"));
107
108        fs::remove_file(path)?;
109        Ok(())
110    }
111
112    fn create_head(&mut self, package: &PackageName, pubkey_path: &str) -> Result<(), Error> {
113        // creates a head file
114        pkgar::split(
115            pubkey_path,
116            format!("{}/{package}.pkgar", DOWNLOAD_PATH),
117            self.install_path
118                .join(PACKAGES_DIR)
119                .join(format!("{package}.pkgar_head")),
120            Option::<&str>::None,
121        )?;
122
123        Ok(())
124    }
125
126    fn check_write_result(write_result: Result<(), std::io::Error>) -> Result<(), Error> {
127        if let Err(error) = write_result {
128            if error.kind() == std::io::ErrorKind::PermissionDenied {
129                return Err(Error::MissingPermissions);
130            } else {
131                return Err(Error::IO(error));
132            }
133        }
134        Ok(())
135    }
136}
137
138impl Backend for PkgarBackend {
139    fn install(&mut self, package: PackageName) -> Result<(), Error> {
140        let repo = self.repo_manager.sync_pkgar(&package)?;
141        let mut pkg = self.get_package(&package, &repo.key)?;
142        let pubkey_path = repo.pubkey.clone();
143
144        let mut install = Transaction::install(&mut pkg, &self.install_path)?;
145        install.commit()?;
146
147        self.create_head(&package, &pubkey_path)?;
148
149        Ok(())
150    }
151
152    fn uninstall(&mut self, package: PackageName) -> Result<(), Error> {
153        if self.packages.protected.contains(&package) {
154            return Err(Error::ProtectedPackage(package));
155        }
156
157        let mut pkg = self.get_package_head(&package)?;
158        let mut remove = Transaction::remove(&mut pkg, &self.install_path)?;
159        remove.commit()?;
160
161        self.remove_package_head(&package)?;
162
163        Ok(())
164    }
165
166    fn upgrade(&mut self, package: PackageName) -> Result<(), Error> {
167        let mut pkg = self.get_package_head(&package)?;
168
169        let repo = self.repo_manager.sync_pkgar(&package)?;
170        let pubkey_path = repo.pubkey.clone();
171
172        let mut pkg2 = self.get_package(&package, &repo.key)?;
173
174        let mut update = Transaction::replace(&mut pkg, &mut pkg2, &self.install_path)?;
175        update.commit()?;
176
177        self.create_head(&package, &pubkey_path)?;
178
179        Ok(())
180    }
181
182    fn get_package_detail(&self, package: &PackageName) -> Result<Package, Error> {
183        let toml = self.get_package_toml(package)?;
184
185        Ok(Package::from_toml(&toml)?)
186    }
187
188    fn get_repository_detail(&self) -> Result<Repository, Error> {
189        let repo_str = PackageName::new("repo".to_string())?;
190        let toml = self.get_package_toml(&repo_str)?;
191
192        Ok(Repository::from_toml(&toml)?)
193    }
194
195    fn get_installed_packages(&self) -> Result<Vec<PackageName>, Error> {
196        let entries = fs::read_dir(self.install_path.join(PACKAGES_DIR))?;
197
198        let mut packages = vec![];
199
200        for entry in entries {
201            let entry = entry?;
202            let file_name = entry.file_name();
203            let file_name_str = file_name.to_str().ok_or(Error::IO(std::io::Error::new(
204                std::io::ErrorKind::Other,
205                "file name isn't UTF-8",
206            )))?;
207
208            if file_name_str.ends_with(".pkgar_head") {
209                let package = file_name_str.replace(".pkgar_head", "");
210                packages.push(PackageName::new(package)?);
211            }
212        }
213
214        Ok(packages)
215    }
216}
217
218impl Drop for PkgarBackend {
219    fn drop(&mut self) {
220        let packages_path = self.install_path.join(PACKAGES_PATH);
221        let write_result = fs::write(packages_path, self.packages.to_toml());
222        PkgarBackend::check_write_result(write_result).unwrap();
223    }
224}