pkg/
library.rs

1use std::{
2    cell::RefCell,
3    cmp::Ordering,
4    fs,
5    path::{Path, PathBuf},
6    rc::Rc,
7};
8
9use crate::backend::pkgar_backend::PkgarBackend;
10use crate::backend::{Backend, Error};
11use crate::net_backend::{DefaultNetBackend, DownloadBackend};
12use crate::package_list::PackageList;
13use crate::repo_manager::RepoManager;
14
15use crate::callback::Callback;
16use crate::package::{PackageInfo, PackageName};
17
18use crate::{sorensen, DOWNLOAD_PATH};
19
20pub struct Library {
21    package_list: PackageList,
22    backend: Box<dyn Backend>,
23}
24
25impl Library {
26    pub fn new<P: AsRef<Path>>(
27        install_path: P,
28        target: &str,
29        callback: Rc<RefCell<dyn Callback>>,
30    ) -> Result<Self, Error> {
31        let install_path = install_path.as_ref();
32
33        let download_backend = DefaultNetBackend::new()?;
34        let prefer_cache = PathBuf::from(DOWNLOAD_PATH).join("prefer_cache").exists();
35
36        let mut repo_manager = RepoManager {
37            remotes: Vec::new(),
38            download_path: DOWNLOAD_PATH.into(),
39            download_backend: Box::new(download_backend.clone()),
40            callback: callback.clone(),
41            prefer_cache,
42        };
43
44        {
45            let repos_path = install_path.join("etc/pkg.d");
46            let mut repo_files = Vec::new();
47            for entry_res in fs::read_dir(&repos_path)? {
48                let entry = entry_res?;
49                let path = entry.path();
50                if path.is_file() {
51                    repo_files.push(path);
52                }
53            }
54            repo_files.sort();
55            for repo_file in repo_files {
56                let data = fs::read_to_string(repo_file)?;
57                for line in data.lines() {
58                    if !line.starts_with('#') {
59                        repo_manager.add_remote(line.trim(), target)?;
60                    }
61                }
62            }
63
64            // the local key installed by the installer
65            let local_pub_path = install_path.join("pkg");
66            let _ = repo_manager.add_local("installer_key", &local_pub_path);
67        }
68
69        let backend = PkgarBackend::new(install_path, repo_manager)?;
70
71        Ok(Library {
72            package_list: PackageList::default(),
73            backend: Box::new(backend),
74        })
75    }
76
77    pub fn get_installed_packages(&self) -> Result<Vec<PackageName>, Error> {
78        self.backend.get_installed_packages()
79    }
80
81    pub fn install(&mut self, packages: Vec<PackageName>) -> Result<(), Error> {
82        let installed_packages = self.get_installed_packages().unwrap_or(vec![]);
83        for package_name in packages {
84            if !installed_packages.contains(&package_name) {
85                self.package_list.install.push(package_name);
86            }
87        }
88
89        Ok(())
90    }
91
92    /// TODO: Cannot uninstall depedencies as manual mark is not implemented
93    pub fn uninstall(&mut self, packages: Vec<PackageName>) -> Result<(), Error> {
94        let installed_packages = self.get_installed_packages()?;
95        for package_name in packages {
96            if installed_packages.contains(&package_name) {
97                self.package_list.uninstall.push(package_name);
98            }
99        }
100
101        Ok(())
102    }
103
104    /// if packages is empty then update all installed packages
105    pub fn update(&mut self, packages: Vec<PackageName>) -> Result<(), Error> {
106        let installed_packages = self.get_installed_packages()?;
107        if packages.is_empty() {
108            for package_name in &installed_packages {
109                self.package_list.install.push(package_name.clone());
110            }
111        } else {
112            for package_name in packages {
113                if installed_packages.contains(&package_name) {
114                    self.package_list.install.push(package_name);
115                }
116            }
117        }
118
119        Ok(())
120    }
121
122    pub fn get_all_package_names(&mut self) -> Result<Vec<PackageName>, Error> {
123        let repository = self.backend.get_repository_detail()?;
124        let list = repository
125            .packages
126            .keys()
127            .cloned()
128            .fold(Vec::new(), |mut acc, x| {
129                match PackageName::new(x) {
130                    Ok(name) => {
131                        acc.push(name);
132                    }
133                    Err(_) => {}
134                };
135                acc
136            });
137        Ok(list)
138    }
139
140    pub fn search(&mut self, package: &str) -> Result<Vec<(PackageName, f64)>, Error> {
141        let names = self.get_all_package_names()?;
142
143        let mut result = vec![];
144
145        for name in names {
146            let mut rank = 0.0;
147
148            let dst = sorensen::distance(
149                package.to_lowercase().as_bytes(),
150                name.as_str().to_lowercase().as_bytes(),
151            );
152
153            if dst >= 0.2 {
154                rank += dst;
155            }
156
157            if name.as_str().contains(package) {
158                rank += 0.01;
159            }
160
161            if rank > 0.0 {
162                result.push((name, rank));
163            }
164        }
165
166        // this is hard to read
167        result.as_mut_slice().sort_by(|a, b| {
168            let check1 = b.1.partial_cmp(&a.1);
169            if check1 == Some(Ordering::Equal) {
170                a.0.cmp(&b.0)
171            } else {
172                check1.unwrap_or(Ordering::Equal)
173            }
174        });
175
176        Ok(result)
177    }
178
179    pub fn apply(&mut self) -> Result<(), Error> {
180        for package in self.package_list.uninstall.iter() {
181            // TODO: Allow self-trusting the package?
182            let r = self.backend.uninstall(package.clone());
183            if let Err(Error::RepoCacheNotFound(e)) = &r {
184                eprintln!("Repository source of {e} is not valid, please reinstall repository public keys to allow erasing, or reinstall the package.");
185            }
186            r?
187        }
188
189        let install = self.with_dependecies(&self.package_list.install.clone())?;
190
191        for package in install.into_iter() {
192            if self.backend.get_installed_packages()?.contains(&package) {
193                match self.backend.upgrade(package.clone()) {
194                    Err(Error::RepoCacheNotFound(e)) => {
195                        eprintln!("Repository source of {e} is not valid, reinstalling!");
196                        self.backend.install(package)?;
197                    }
198                    r => r?,
199                }
200            } else {
201                self.backend.install(package)?;
202            }
203        }
204
205        self.package_list = Default::default();
206        Ok(())
207    }
208
209    pub fn with_dependecies(
210        &mut self,
211        packages: &Vec<PackageName>,
212    ) -> Result<Vec<PackageName>, Error> {
213        let mut list = vec![];
214        for package in packages {
215            self.get_dependecies_recursive(package, &mut list)?;
216        }
217
218        Ok(list)
219    }
220
221    fn get_dependecies_recursive(
222        &mut self,
223        package_name: &PackageName,
224        list: &mut Vec<PackageName>,
225    ) -> Result<(), Error> {
226        let package = self.backend.get_package_detail(package_name)?;
227        if list.contains(&package.name) {
228            return Ok(());
229        }
230        for dep in &package.depends {
231            self.get_dependecies_recursive(dep, list)?;
232        }
233
234        // meta-packages is identified with empty version
235        // TODO: This is not the right time to check it,
236        // but the TOML data we needed will lost outside this fn
237        if package.version != "" {
238            list.push(package.name);
239        }
240        Ok(())
241    }
242
243    pub fn info(&mut self, package: PackageName) -> Result<PackageInfo, Error> {
244        let installed = self.backend.get_installed_packages()?.contains(&package);
245        let package = self.backend.get_package_detail(&package)?;
246
247        Ok(PackageInfo { installed, package })
248    }
249}