pkg/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
//#![cfg_attr(target_os = "redox", feature(io_error_more))]

use std::{cell::RefCell, cmp::Ordering, fs, rc::Rc};

use backend::pkgar_backend::PkgarBackend;
use backend::{Backend, Error};
use net_backend::{Callback, DefaultNetBackend, DownloadBackend};
use package::{Package, PackageInfo};
use package_list::PackageList;
use repo_manager::RepoManager;

pub mod backend;
pub mod net_backend;
mod package;
mod package_list;
mod repo_manager;
mod sorensen;

pub struct Library {
    package_list: PackageList,
    repo_manager: RepoManager,
    backend: Box<dyn Backend>,
}

const DOWNLOAD_PATH: &str = "/tmp/pkg_dowload/";
#[cfg(target_os = "linux")]
const INSTALL_PATH: &str = "/tmp/pkg_install";
#[cfg(target_os = "redox")]
const INSTALL_PATH: &str = "/";

// make them not relative
// make repos a folder
const REPOS_PATH: &str = "etc/pkg/repos";
const PACKAGES_PATH: &str = "etc/pkg/packages.toml";

impl Library {
    pub fn new(callback: Rc<RefCell<dyn Callback>>) -> Result<Self, Error> {
        let mut remotes = vec![];
        // only add this is none were added
        remotes.push("https://static.redox-os.org/pkg/x86_64-unknown-redox".to_string());

        let repos_path = format!("{}/{}", INSTALL_PATH, REPOS_PATH);
        let file = fs::read_to_string(repos_path);

        if let Ok(data) = file {
            for line in data.lines() {
                if !line.starts_with('#') {
                    remotes.push(line.to_string());
                }
            }
        }

        let download_backend = DefaultNetBackend::new()?;

        let repo_manager = RepoManager {
            remotes: remotes.clone(),
            download_path: DOWNLOAD_PATH.into(),
            download_backend: Box::new(download_backend.clone()),
            callback: callback.clone(),
        };

        //let backend = TarBackend::new(repo_manager)?;
        let backend = PkgarBackend::new(repo_manager, callback.clone())?;

        // TODO: Dont init it multiple times
        let repo_manager = RepoManager {
            remotes: remotes.clone(),
            download_path: DOWNLOAD_PATH.into(),
            download_backend: Box::new(download_backend),
            callback: callback.clone(),
        };

        Ok(Library {
            repo_manager,
            package_list: PackageList::default(),
            backend: Box::new(backend),
        })
    }

    pub fn get_installed_packages(&self) -> Result<Vec<String>, Error> {
        self.backend.get_installed_packages()
    }

    pub fn install(&mut self, packages: Vec<String>) -> Result<(), Error> {
        for package_name in packages {
            self.package_list.install.push(package_name);
        }

        Ok(())
    }

    pub fn uninstall(&mut self, packages: Vec<String>) -> Result<(), Error> {
        for package_name in packages {
            if self.get_installed_packages()?.contains(&package_name) {
                self.package_list.uninstall.push(package_name);
            }
        }

        Ok(())
    }

    /// if packages is empty then update all installed packages
    pub fn update(&mut self, packages: Vec<String>) -> Result<(), Error> {
        if packages.is_empty() {
            for package_name in &self.backend.get_installed_packages()? {
                self.package_list.install.push(package_name.to_string());
            }
        } else {
            for package_name in packages {
                if self.get_installed_packages()?.contains(&package_name) {
                    self.package_list.uninstall.push(package_name);
                }
            }
        }

        Ok(())
    }

    pub fn get_all_package_names(&mut self) -> Result<Vec<String>, Error> {
        // get website html
        let mut website = self.repo_manager.sync_website();

        let mut names = vec![];
        while let Some(end) = website.find(".toml</a>") {
            let mut i = end;
            loop {
                let char = website.chars().nth(i).expect("this should work");
                if char == '>' {
                    break;
                }
                i -= 1;
            }
            let package_name = &website[i + 1..end];
            if !names.contains(&package_name.to_string()) {
                names.push(package_name.to_string());
            }

            website = website.replacen(".toml</a>", "", 1);
        }

        Ok(names)
    }

    pub fn search(&mut self, package: &str) -> Result<Vec<(String, f64)>, Error> {
        let names = self.get_all_package_names()?;

        let mut result = vec![];

        for name in names {
            let mut rank = 0.0;

            let dst = sorensen::distance(
                package.to_lowercase().as_bytes(),
                name.to_lowercase().as_bytes(),
            );

            if dst >= 0.2 {
                rank += dst;
            }

            if name.contains(package) {
                rank += 0.01;
            }

            if rank > 0.0 {
                result.push((name, rank));
            }
        }

        // this is hard to read
        result.as_mut_slice().sort_by(|a, b| {
            let check1 = b.1.partial_cmp(&a.1);
            if check1 == Some(Ordering::Equal) {
                a.0.cmp(&b.0)
            } else {
                check1.unwrap_or(Ordering::Equal)
            }
        });

        Ok(result)
    }

    pub fn apply(&mut self) -> Result<(), Error> {
        for package in self.package_list.uninstall.iter() {
            self.backend.uninstall(package.to_string())?;
        }

        let install = self.with_dependecies(&self.package_list.install.clone())?;

        for package in install.into_iter() {
            if self.backend.get_installed_packages()?.contains(&package) {
                self.backend.upgrade(package)?;
            } else {
                self.backend.install(package)?;
            }
        }

        self.package_list = Default::default();
        Ok(())
    }

    // hard to read
    fn get_package(&mut self, package_name: &str) -> Result<Package, Error> {
        let toml = self.repo_manager.sync_toml(package_name);

        Ok(Package::from_toml(&toml)?)
    }

    pub fn with_dependecies(&mut self, packages: &Vec<String>) -> Result<Vec<String>, Error> {
        let mut list = vec![];
        for package in packages {
            self.get_dependecies_recursive(package, &mut list)?;
        }

        Ok(list)
    }

    fn get_dependecies_recursive(
        &mut self,
        package_name: &str,
        list: &mut Vec<String>,
    ) -> Result<(), Error> {
        let package = self.get_package(package_name)?;
        for dep in &package.depends {
            let package = self.get_package(dep)?;

            if list.contains(&package.name) {
                continue;
            }

            list.push(package.name.clone());
            self.get_dependecies_recursive(package_name, list)?;
        }
        list.push(package.name);
        Ok(())
    }

    pub fn info(&mut self, package: String) -> Result<PackageInfo, Error> {
        let installed = self.backend.get_installed_packages()?.contains(&package);
        let package = self.get_package(&package)?;

        Ok(PackageInfo {
            installed,
            version: package.version,
            target: package.target,
            // this can be implemented
            download_size: "not implemented".to_string(),
            depends: package.depends,
        })
    }
}