Skip to main content

pkg/
library.rs

1use std::collections::{btree_map, BTreeMap};
2use std::{cell::RefCell, cmp::Ordering, path::Path, rc::Rc};
3
4use crate::backend::pkgar_backend::PkgarBackend;
5use crate::backend::{Backend, Error};
6use crate::net_backend::{DefaultNetBackend, DownloadBackend};
7use crate::repo_manager::RepoManager;
8
9use crate::callback::Callback;
10use crate::package::{PackageInfo, PackageName, RemotePackage};
11
12use crate::{sorensen, PackageState};
13
14pub struct Library {
15    /// the computed package state before commit
16    package_state: PackageState,
17    cached_info: BTreeMap<PackageName, RemotePackage>,
18    backend: Box<dyn Backend>,
19    callback: Rc<RefCell<dyn Callback>>,
20}
21
22impl Library {
23    /// Create standard network-based package library from existing configuration on install_path
24    pub fn new<P: AsRef<Path>>(
25        install_path: P,
26        target: &str,
27        callback: Rc<RefCell<dyn Callback>>,
28    ) -> Result<Self, Error> {
29        let install_path = install_path.as_ref();
30
31        let download_backend = DefaultNetBackend::new()?;
32
33        let mut repo_manager = RepoManager::new(callback.clone(), Box::new(download_backend));
34        repo_manager.update_remotes(target, install_path)?;
35
36        let backend = PkgarBackend::new(install_path, repo_manager)?;
37
38        Ok(Library {
39            package_state: backend.get_package_state(),
40            backend: Box::new(backend),
41            cached_info: BTreeMap::new(),
42            callback: callback,
43        })
44    }
45
46    /// Create local-based package library from provided local on install_path
47    pub fn new_local<P: AsRef<Path>>(
48        source_dir: P,
49        pubkey_dir: P,
50        install_path: P,
51        target: &str,
52        callback: Rc<RefCell<dyn Callback>>,
53    ) -> Result<Self, Error> {
54        let install_path = install_path.as_ref();
55
56        let download_backend = DefaultNetBackend::new()?;
57
58        let mut repo_manager = RepoManager::new(callback.clone(), Box::new(download_backend));
59
60        repo_manager.add_local(
61            "local",
62            &source_dir.as_ref().to_string_lossy(),
63            target,
64            pubkey_dir.as_ref(),
65        )?;
66
67        let backend = PkgarBackend::new(install_path, repo_manager)?;
68
69        Ok(Library {
70            package_state: backend.get_package_state(),
71            backend: Box::new(backend),
72            cached_info: BTreeMap::new(),
73            callback: callback,
74        })
75    }
76
77    /// Create remote-based package library from provided list of remote_urls
78    pub fn new_remote<P: AsRef<Path>>(
79        remote_urls: &Vec<&str>,
80        install_path: P,
81        target: &str,
82        callback: Rc<RefCell<dyn Callback>>,
83    ) -> Result<Self, Error> {
84        let install_path = install_path.as_ref();
85
86        let download_backend = DefaultNetBackend::new()?;
87
88        let mut repo_manager = RepoManager::new(callback.clone(), Box::new(download_backend));
89
90        for remote_url in remote_urls {
91            repo_manager.add_remote(remote_url.trim(), target)?;
92        }
93
94        let backend = PkgarBackend::new(install_path, repo_manager)?;
95
96        Ok(Library {
97            package_state: backend.get_package_state(),
98            backend: Box::new(backend),
99            cached_info: BTreeMap::new(),
100            callback: callback,
101        })
102    }
103
104    pub fn get_installed_packages(&self) -> Result<Vec<PackageName>, Error> {
105        Ok(self.package_state.get_installed_list())
106    }
107
108    pub fn install(&mut self, packages: Vec<PackageName>) -> Result<(), Error> {
109        self.callback.borrow_mut().fetch_start(packages.len());
110        self.install_inner(packages.clone(), 100)?;
111        self.package_state.mark_as_manual(true, &packages);
112        self.callback.borrow_mut().fetch_end();
113        Ok(())
114    }
115
116    fn install_inner(&mut self, packages: Vec<PackageName>, iter: u32) -> Result<(), Error> {
117        if iter == 0 {
118            return Err(Error::RepoRecursion(packages));
119        }
120        let mut pinfos = Vec::new();
121        for p in &packages {
122            let premote = match self.cached_info.entry(p.clone()) {
123                btree_map::Entry::Occupied(occupied_entry) => occupied_entry.get().clone(),
124                btree_map::Entry::Vacant(vacant_entry) => {
125                    let p = self.backend.get_package_detail(p)?;
126                    vacant_entry.insert(p).clone()
127                }
128            };
129            self.callback.borrow_mut().fetch_package_increment(1, 0);
130            pinfos.push(premote);
131        }
132        let remainder = self.package_state.install(&pinfos);
133        if remainder.len() > 0 {
134            self.callback
135                .borrow_mut()
136                .fetch_package_increment(0, remainder.len());
137            self.install_inner(remainder, iter - 1)?;
138        }
139        Ok(())
140    }
141
142    pub fn uninstall(&mut self, packages: Vec<PackageName>) -> Result<(), Error> {
143        self.uninstall_inner(packages, 100)
144    }
145
146    fn uninstall_inner(&mut self, packages: Vec<PackageName>, iter: u32) -> Result<(), Error> {
147        if iter == 0 {
148            return Err(Error::RepoRecursion(packages));
149        }
150        let remainder = self.package_state.uninstall(&packages);
151        if remainder.len() > 0 {
152            self.uninstall_inner(remainder, iter - 1)?;
153        }
154        Ok(())
155    }
156
157    /// if packages is empty then update all installed packages
158    pub fn update(&mut self, mut packages: Vec<PackageName>) -> Result<(), Error> {
159        let repo_list = self.backend.get_repository_detail()?;
160        let local_list = self.backend.get_package_state();
161        if packages.len() == 0 {
162            packages = local_list.get_installed_list();
163        }
164
165        let mut new_packages = Vec::new();
166        for package in packages {
167            if let Some(source_hash) = repo_list.packages.get(package.as_str()) {
168                if let Some(local_hash) = local_list.installed.get(package.as_str()) {
169                    if local_hash.blake3 != *source_hash {
170                        new_packages.push(package);
171                    }
172                }
173            }
174        }
175
176        self.install(new_packages)
177    }
178
179    pub fn get_all_package_names(&mut self) -> Result<Vec<PackageName>, Error> {
180        let repository = self.backend.get_repository_detail()?;
181        let list = repository
182            .packages
183            .keys()
184            .cloned()
185            .fold(Vec::new(), |mut acc, x| {
186                match PackageName::new(x) {
187                    Ok(name) => {
188                        acc.push(name);
189                    }
190                    Err(_) => {}
191                };
192                acc
193            });
194        Ok(list)
195    }
196
197    pub fn search(&mut self, package: &str) -> Result<Vec<(PackageName, f64)>, Error> {
198        let names = self.get_all_package_names()?;
199
200        let mut result = vec![];
201
202        for name in names {
203            let mut rank = 0.0;
204
205            let dst = sorensen::distance(
206                package.to_lowercase().as_bytes(),
207                name.as_str().to_lowercase().as_bytes(),
208            );
209
210            if dst >= 0.2 {
211                rank += dst;
212            }
213
214            if name.as_str().contains(package) {
215                rank += 0.01;
216            }
217
218            if rank > 0.0 {
219                result.push((name, rank));
220            }
221        }
222
223        // this is hard to read
224        result.as_mut_slice().sort_by(|a, b| {
225            let check1 = b.1.partial_cmp(&a.1);
226            if check1 == Some(Ordering::Equal) {
227                a.0.cmp(&b.0)
228            } else {
229                check1.unwrap_or(Ordering::Equal)
230            }
231        });
232
233        Ok(result)
234    }
235
236    pub fn abort(&mut self) -> Result<usize, Error> {
237        self.backend.abort_state()
238    }
239
240    pub fn apply(&mut self) -> Result<usize, Error> {
241        self.apply_inner()
242    }
243
244    fn apply_inner(&mut self) -> Result<usize, Error> {
245        let diff = self.backend.get_package_state().diff(&self.package_state);
246        if diff.is_empty() {
247            return Ok(0);
248        }
249
250        self.callback.borrow_mut().install_prompt(&diff)?;
251
252        for package in &diff.uninstall {
253            // TODO: Allow self-trusting the package?
254            let r = self.backend.uninstall(package.clone());
255            if let Err(Error::RepoCacheNotFound(e)) = &r {
256                eprintln!("Repository source of {e} is not valid, please reinstall repository public keys to allow erasing, or reinstall the package.");
257            }
258            r?
259        }
260
261        for package in &diff.update {
262            if let Some(cache) = self.cached_info.remove(package) {
263                let r = self.backend.upgrade(&cache);
264                if let Err(Error::RepoCacheNotFound(e)) = &r {
265                    eprintln!("Repository source of {e} is not valid, reinstalling!");
266                    self.backend.install(cache)?;
267                }
268                r?
269            }
270        }
271
272        for package in &diff.install {
273            if let Some(cache) = self.cached_info.remove(package) {
274                self.backend.install(cache)?;
275            }
276        }
277
278        self.callback
279            .borrow_mut()
280            .install_check_conflict(self.backend.commit_check_conflict()?)?;
281
282        self.backend.commit_state(self.package_state.clone())
283    }
284
285    pub fn info(&mut self, package: PackageName) -> Result<PackageInfo, Error> {
286        let installed = self.package_state.get_installed_list().contains(&package);
287        let package = self.backend.get_package_detail(&package)?;
288
289        Ok(PackageInfo { installed, package })
290    }
291}