pkg/
lib.rs

1//#![cfg_attr(target_os = "redox", feature(io_error_more))]
2
3use std::{cell::RefCell, cmp::Ordering, fs, path::Path, rc::Rc};
4
5use backend::pkgar_backend::PkgarBackend;
6use backend::Backend;
7use net_backend::{DefaultNetBackend, DownloadBackend};
8use package_list::PackageList;
9use repo_manager::RepoManager;
10
11pub use backend::Error;
12pub use callback::Callback;
13pub use package::{Package, PackageInfo, PackageName};
14
15pub mod backend;
16pub mod callback;
17pub mod net_backend;
18pub mod package;
19mod package_list;
20mod repo_manager;
21mod sorensen;
22
23pub struct Library {
24    package_list: PackageList,
25    repo_manager: RepoManager,
26    backend: Box<dyn Backend>,
27}
28
29const DOWNLOAD_PATH: &str = "/tmp/pkg_dowload/";
30
31// make them not relative
32const PACKAGES_PATH: &str = "etc/pkg/packages.toml";
33
34impl Library {
35    pub fn new<P: AsRef<Path>>(
36        install_path: P,
37        target: &str,
38        callback: Rc<RefCell<dyn Callback>>,
39    ) -> Result<Self, Error> {
40        let install_path = install_path.as_ref();
41
42        let mut remotes = Vec::new();
43        {
44            let repos_path = install_path.join("etc/pkg.d");
45            let mut repo_files = Vec::new();
46            for entry_res in fs::read_dir(&repos_path)? {
47                let entry = entry_res?;
48                let path = entry.path();
49                if path.is_file() {
50                    repo_files.push(path);
51                }
52            }
53            repo_files.sort();
54            for repo_file in repo_files {
55                let data = fs::read_to_string(repo_file)?;
56                for line in data.lines() {
57                    if !line.starts_with('#') {
58                        remotes.push(format!("{}/{target}", line.trim()));
59                    }
60                }
61            }
62        }
63
64        let download_backend = DefaultNetBackend::new()?;
65
66        let repo_manager = RepoManager {
67            remotes: remotes.clone(),
68            download_path: DOWNLOAD_PATH.into(),
69            download_backend: Box::new(download_backend.clone()),
70            callback: callback.clone(),
71        };
72
73        let backend = PkgarBackend::new(install_path, repo_manager, callback.clone())?;
74
75        // TODO: Dont init it multiple times
76        let repo_manager = RepoManager {
77            remotes: remotes.clone(),
78            download_path: DOWNLOAD_PATH.into(),
79            download_backend: Box::new(download_backend),
80            callback: callback.clone(),
81        };
82
83        Ok(Library {
84            repo_manager,
85            package_list: PackageList::default(),
86            backend: Box::new(backend),
87        })
88    }
89
90    pub fn get_installed_packages(&self) -> Result<Vec<PackageName>, Error> {
91        self.backend.get_installed_packages()
92    }
93
94    pub fn install(&mut self, packages: Vec<PackageName>) -> Result<(), Error> {
95        for package_name in packages {
96            self.package_list.install.push(package_name);
97        }
98
99        Ok(())
100    }
101
102    pub fn uninstall(&mut self, packages: Vec<PackageName>) -> Result<(), Error> {
103        for package_name in packages {
104            if self.get_installed_packages()?.contains(&package_name) {
105                self.package_list.uninstall.push(package_name);
106            }
107        }
108
109        Ok(())
110    }
111
112    /// if packages is empty then update all installed packages
113    pub fn update(&mut self, packages: Vec<PackageName>) -> Result<(), Error> {
114        if packages.is_empty() {
115            for package_name in &self.backend.get_installed_packages()? {
116                self.package_list.install.push(package_name.clone());
117            }
118        } else {
119            for package_name in packages {
120                if self.get_installed_packages()?.contains(&package_name) {
121                    self.package_list.uninstall.push(package_name);
122                }
123            }
124        }
125
126        Ok(())
127    }
128
129    pub fn get_all_package_names(&mut self) -> Result<Vec<PackageName>, Error> {
130        //TODO: use repo.toml
131        // get website html
132        let mut website = self.repo_manager.sync_website();
133
134        let mut names = vec![];
135        while let Some(end) = website.find(".toml</a>") {
136            let mut i = end;
137            loop {
138                let char = website.chars().nth(i).expect("this should work");
139                if char == '>' {
140                    break;
141                }
142                i -= 1;
143            }
144            let package_name = PackageName::new(&website[i + 1..end])?;
145            if !names.contains(&package_name) {
146                names.push(package_name);
147            }
148
149            website = website.replacen(".toml</a>", "", 1);
150        }
151
152        Ok(names)
153    }
154
155    pub fn search(&mut self, package: &str) -> Result<Vec<(PackageName, f64)>, Error> {
156        let names = self.get_all_package_names()?;
157
158        let mut result = vec![];
159
160        for name in names {
161            let mut rank = 0.0;
162
163            let dst = sorensen::distance(
164                package.to_lowercase().as_bytes(),
165                name.as_str().to_lowercase().as_bytes(),
166            );
167
168            if dst >= 0.2 {
169                rank += dst;
170            }
171
172            if name.as_str().contains(package) {
173                rank += 0.01;
174            }
175
176            if rank > 0.0 {
177                result.push((name, rank));
178            }
179        }
180
181        // this is hard to read
182        result.as_mut_slice().sort_by(|a, b| {
183            let check1 = b.1.partial_cmp(&a.1);
184            if check1 == Some(Ordering::Equal) {
185                a.0.cmp(&b.0)
186            } else {
187                check1.unwrap_or(Ordering::Equal)
188            }
189        });
190
191        Ok(result)
192    }
193
194    pub fn apply(&mut self) -> Result<(), Error> {
195        for package in self.package_list.uninstall.iter() {
196            self.backend.uninstall(package.clone())?;
197        }
198
199        let install = self.with_dependecies(&self.package_list.install.clone())?;
200
201        for package in install.into_iter() {
202            if self.backend.get_installed_packages()?.contains(&package) {
203                self.backend.upgrade(package)?;
204            } else {
205                self.backend.install(package)?;
206            }
207        }
208
209        self.package_list = Default::default();
210        Ok(())
211    }
212
213    // hard to read
214    fn get_package(&mut self, package_name: &PackageName) -> Result<Package, Error> {
215        let toml = self.repo_manager.sync_toml(package_name)?;
216
217        Ok(Package::from_toml(&toml)?)
218    }
219
220    pub fn with_dependecies(
221        &mut self,
222        packages: &Vec<PackageName>,
223    ) -> Result<Vec<PackageName>, Error> {
224        let mut list = vec![];
225        for package in packages {
226            self.get_dependecies_recursive(package, &mut list)?;
227        }
228
229        Ok(list)
230    }
231
232    fn get_dependecies_recursive(
233        &mut self,
234        package_name: &PackageName,
235        list: &mut Vec<PackageName>,
236    ) -> Result<(), Error> {
237        let package = self.get_package(package_name)?;
238        for dep in &package.depends {
239            let package = self.get_package(dep)?;
240
241            if list.contains(&package.name) {
242                continue;
243            }
244
245            list.push(package.name.clone());
246            self.get_dependecies_recursive(package_name, list)?;
247        }
248        list.push(package.name);
249        Ok(())
250    }
251
252    pub fn info(&mut self, package: PackageName) -> Result<PackageInfo, Error> {
253        let installed = self.backend.get_installed_packages()?.contains(&package);
254        let package = self.get_package(&package)?;
255
256        Ok(PackageInfo {
257            installed,
258            version: package.version,
259            target: package.target,
260            // this can be implemented
261            download_size: "not implemented".to_string(),
262            depends: package.depends,
263        })
264    }
265}