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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//#![cfg_attr(target_os = "redox", feature(io_error_more))]

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

use backend::pkgar_backend::PkgarBackend;
use backend::Backend;
use net_backend::{DefaultNetBackend, DownloadBackend};
use package_list::PackageList;
use repo_manager::RepoManager;

pub use backend::Error;
pub use callback::Callback;
pub use package::{Package, PackageInfo, PackageName};

pub mod backend;
pub mod callback;
pub mod net_backend;
pub 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/";

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

impl Library {
    pub fn new<P: AsRef<Path>>(
        install_path: P,
        target: &str,
        callback: Rc<RefCell<dyn Callback>>,
    ) -> Result<Self, Error> {
        let install_path = install_path.as_ref();

        let mut remotes = Vec::new();
        {
            let repos_path = install_path.join("etc/pkg.d");
            let mut repo_files = Vec::new();
            for entry_res in fs::read_dir(&repos_path)? {
                let entry = entry_res?;
                let path = entry.path();
                if path.is_file() {
                    repo_files.push(path);
                }
            }
            repo_files.sort();
            for repo_file in repo_files {
                let data = fs::read_to_string(repo_file)?;
                for line in data.lines() {
                    if !line.starts_with('#') {
                        remotes.push(format!("{}/{target}", line.trim()));
                    }
                }
            }
        }

        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 = PkgarBackend::new(install_path, 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<PackageName>, Error> {
        self.backend.get_installed_packages()
    }

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

        Ok(())
    }

    pub fn uninstall(&mut self, packages: Vec<PackageName>) -> 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<PackageName>) -> Result<(), Error> {
        if packages.is_empty() {
            for package_name in &self.backend.get_installed_packages()? {
                self.package_list.install.push(package_name.clone());
            }
        } 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<PackageName>, Error> {
        //TODO: use repo.toml
        // 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 = PackageName::new(&website[i + 1..end])?;
            if !names.contains(&package_name) {
                names.push(package_name);
            }

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

        Ok(names)
    }

    pub fn search(&mut self, package: &str) -> Result<Vec<(PackageName, 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.as_str().to_lowercase().as_bytes(),
            );

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

            if name.as_str().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.clone())?;
        }

        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: &PackageName) -> 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<PackageName>,
    ) -> Result<Vec<PackageName>, 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: &PackageName,
        list: &mut Vec<PackageName>,
    ) -> 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: PackageName) -> 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,
        })
    }
}