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,
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
29// FIXME: can't use repo_manager.get_local_path because of borrowing rules
30fn 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
43// FIXME: can't use self.pkey_files because of borrowing rules
44fn 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        // TODO: Use File::lock. This only checks permission
87        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    // reads /pkg/packages/[package].pkgar_head
100    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        // TODO: A way to get chosen remote of a pkg so we can remove this trial loop
109        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    // reads /tmp/pkg_download/[package].pkgar
122    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            // the pubkey cache is failing to download?
132            Err(Error::RepoCacheNotFound(package.clone()))
133        }
134    }
135
136    // reads /tmp/pkg_download/[package].toml
137    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        // creates a head file
158        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        // we already check permissions before so the error can be ignored
251        let _ = fs::write(packages_path, self.packages.to_toml());
252    }
253}