pkgutils/
lib.rs

1use libflate::gzip::Encoder;
2use sha3::{Digest, Sha3_512};
3use std::fs::{self, File};
4use std::io::{self, stderr, BufWriter, Read, Write};
5use std::path::Path;
6use std::str;
7
8pub use crate::database::{Database, PackageDepends};
9pub use crate::download::{download, download_client};
10pub use crate::package::Package;
11pub use crate::packagemeta::{PackageMeta, PackageMetaList};
12
13mod database;
14mod download;
15mod package;
16mod packagemeta;
17
18#[derive(Debug)]
19pub struct Repo {
20    local: String,
21    remotes: Vec<String>,
22    client: reqwest::blocking::Client,
23    target: String,
24}
25
26impl Repo {
27    pub fn new(target: &str) -> Repo {
28        let mut remotes = vec![];
29
30        //TODO: Cleanup
31        // This will add every line in every file in /etc/pkg.d to the remotes,
32        // provided it does not start with #
33        {
34            let mut entries = vec![];
35            if let Ok(read_dir) = fs::read_dir("/etc/pkg.d") {
36                for entry_res in read_dir {
37                    if let Ok(entry) = entry_res {
38                        let path = entry.path();
39                        if path.is_file() {
40                            entries.push(path);
41                        }
42                    }
43                }
44            }
45
46            entries.sort();
47
48            for entry in entries {
49                if let Ok(mut file) = File::open(entry) {
50                    let mut data = String::new();
51                    if let Ok(_) = file.read_to_string(&mut data) {
52                        for line in data.lines() {
53                            if !line.starts_with('#') {
54                                remotes.push(line.to_string());
55                            }
56                        }
57                    }
58                }
59            }
60        }
61
62        Repo {
63            local: format!("/tmp/pkg"),
64            remotes,
65            client: download_client(),
66            target: target.to_string(),
67        }
68    }
69
70    pub fn sync(&self, file: &str) -> io::Result<String> {
71        let local_path = format!("{}/{}", self.local, file);
72
73        if let Some(parent) = Path::new(&local_path).parent() {
74            fs::create_dir_all(parent)?;
75        }
76
77        let mut res = Err(io::Error::new(
78            io::ErrorKind::NotFound,
79            format!("no remote paths"),
80        ));
81        for remote in self.remotes.iter() {
82            let remote_path = format!("{}/{}/{}", remote, self.target, file);
83            res = download(&self.client, &remote_path, &local_path).map(|_| local_path.clone());
84            if res.is_ok() {
85                break;
86            }
87        }
88        res
89    }
90
91    pub fn signature(&self, file: &str) -> io::Result<String> {
92        let mut data = vec![];
93        File::open(&file)?.read_to_end(&mut data)?;
94
95        let mut hash = Sha3_512::default();
96        hash.update(&data);
97        let output = hash.finalize();
98
99        let mut encoded = String::new();
100        for b in output.iter() {
101            //TODO: {:>02x}
102            encoded.push_str(&format!("{:X}", b));
103        }
104
105        Ok(encoded)
106    }
107
108    pub fn clean(&self, package: &str) -> io::Result<String> {
109        let tardir = format!("{}/{}", self.local, package);
110        fs::remove_dir_all(&tardir)?;
111        Ok(tardir)
112    }
113
114    pub fn create(&self, package: &str) -> io::Result<String> {
115        if !Path::new(package).is_dir() {
116            return Err(io::Error::new(
117                io::ErrorKind::NotFound,
118                format!("{} not found", package),
119            ));
120        }
121
122        let sigfile = format!("{}.sig", package);
123        let tarfile = format!("{}.tar.gz", package);
124
125        {
126            let file = File::create(&tarfile)?;
127            let encoder = Encoder::new(BufWriter::new(file))?;
128
129            let mut tar = tar::Builder::new(encoder);
130            tar.follow_symlinks(false);
131            tar.append_dir_all("", package)?;
132
133            let encoder = tar.into_inner()?;
134            let mut file = encoder.finish().into_result()?;
135            file.flush()?;
136        }
137
138        let mut signature = self.signature(&tarfile)?;
139        signature.push('\n');
140
141        File::create(&sigfile)?.write_all(&signature.as_bytes())?;
142
143        Ok(tarfile)
144    }
145
146    pub fn fetch_meta(&self, package: &str) -> io::Result<PackageMeta> {
147        let tomlfile = self.sync(&format!("{}.toml", package))?;
148
149        let mut toml = String::new();
150        File::open(tomlfile)?.read_to_string(&mut toml)?;
151
152        PackageMeta::from_toml(&toml).map_err(|err| {
153            io::Error::new(io::ErrorKind::InvalidData, format!("TOML error: {}", err))
154        })
155    }
156
157    pub fn fetch(&self, package: &str) -> io::Result<Package> {
158        let sigfile = self.sync(&format!("{}.sig", package))?;
159
160        let mut expected = String::new();
161        File::open(sigfile)?.read_to_string(&mut expected)?;
162        let expected = expected.trim();
163
164        {
165            let tarfile = format!("{}/{}.tar.gz", self.local, package);
166            if let Ok(signature) = self.signature(&tarfile) {
167                if signature == expected {
168                    write!(stderr(), "* Already downloaded {}\n", package)?;
169                    return Package::from_path(tarfile);
170                }
171            }
172        }
173
174        let tarfile = self.sync(&format!("{}.tar.gz", package))?;
175
176        if self.signature(&tarfile)? != expected {
177            return Err(io::Error::new(
178                io::ErrorKind::InvalidData,
179                format!("{} not valid", package),
180            ));
181        }
182
183        Package::from_path(tarfile)
184    }
185
186    pub fn extract(&self, package: &str) -> io::Result<String> {
187        let tardir = format!("{}/{}", self.local, package);
188        fs::create_dir_all(&tardir)?;
189        self.fetch(package)?.install(&tardir)?;
190        Ok(tardir)
191    }
192
193    pub fn add_remote(&mut self, remote: &str) {
194        self.remotes.push(remote.to_string());
195    }
196}