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 {
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 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}