1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::fmt::Debug;
4use std::fs::File;
5use std::path::Path;
6use std::rc::Rc;
7use std::{fs, path::PathBuf};
8
9use crate::callback::Callback;
10#[cfg(feature = "library")]
11use crate::net_backend::DownloadError;
12use crate::net_backend::{DownloadBackend, DownloadBackendWriter};
13use crate::package::RemoteName;
14use crate::{backend::Error, package::PackageError, PackageName};
15use crate::{DOWNLOAD_DIR, PACKAGES_REMOTE_DIR};
16use serde_derive::{Deserialize, Serialize};
17pub struct RepoManager {
19 pub remotes: Vec<RemoteName>,
21 pub locals: Vec<RemoteName>,
23 pub remote_map: BTreeMap<RemoteName, RemotePath>,
25 pub download_path: PathBuf,
26 pub download_backend: Rc<Box<dyn DownloadBackend>>,
27
28 pub callback: Rc<RefCell<dyn Callback>>,
29}
30
31impl Debug for RepoManager {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct("RepoManager")
34 .field("remotes", &self.remotes)
35 .field("locals", &self.locals)
36 .field("remote_map", &self.remote_map)
37 .field("download_path", &self.download_path)
38 .finish()
39 }
40}
41
42impl Clone for RepoManager {
43 fn clone(&self) -> Self {
44 Self {
45 remotes: self.remotes.clone(),
46 locals: self.locals.clone(),
47 remote_map: self.remote_map.clone(),
48 download_path: self.download_path.clone(),
49 download_backend: self.download_backend.clone(),
50 callback: self.callback.clone(),
51 }
52 }
53}
54
55pub type RepoPublicKey = [u8; 32];
57
58#[derive(Clone, Debug, Deserialize, Serialize)]
59
60pub struct RepoPublicKeyFile {
62 #[serde(
63 serialize_with = "hex::serialize",
64 deserialize_with = "hex::deserialize"
65 )]
66 pub pkey: RepoPublicKey,
67}
68
69impl RepoPublicKeyFile {
70 pub fn new(pubkey: RepoPublicKey) -> Self {
71 Self { pkey: pubkey }
72 }
73
74 pub fn open(file: impl AsRef<Path>) -> Result<RepoPublicKeyFile, Error> {
75 let content = fs::read_to_string(file.as_ref()).map_err(Error::IO)?;
76 toml::from_str(&content).map_err(|_| {
77 Error::ContentIsNotValidUnicode(file.as_ref().to_string_lossy().to_string())
78 })
79 }
80
81 pub fn save(&self, file: impl AsRef<Path>) -> Result<(), Error> {
82 fs::write(file, toml::to_string(&self).unwrap()).map_err(Error::IO)
83 }
84}
85
86#[derive(Clone, Debug)]
87pub struct RemotePath {
88 pub path: String,
90 pub pubpath: String,
92 pub name: RemoteName,
94 pub pubkey: Option<RepoPublicKey>,
96}
97
98impl RemotePath {
99 pub fn is_local(&self) -> bool {
100 self.pubpath.is_empty()
101 }
102}
103
104const PUB_TOML: &str = "id_ed25519.pub.toml";
105
106impl RepoManager {
107 pub fn new(
108 callback: Rc<RefCell<dyn Callback>>,
109 download_backend: Box<dyn DownloadBackend>,
110 ) -> Self {
111 Self {
112 remotes: Vec::new(),
113 locals: Vec::new(),
114 download_path: DOWNLOAD_DIR.into(),
115 download_backend: Rc::new(download_backend),
116 callback: callback,
117 remote_map: BTreeMap::new(),
118 }
119 }
120
121 pub fn set_download_path(&mut self, path: PathBuf) {
123 self.download_path = path;
124 }
125
126 pub fn set_callback(&mut self, callback: Rc<RefCell<dyn Callback>>) {
128 self.callback = callback;
129 }
130
131 pub fn update_remotes(&mut self, target: &str, install_path: &Path) -> Result<(), Error> {
133 self.remotes = Vec::new();
134 self.locals = Vec::new();
135 self.remote_map = BTreeMap::new();
136
137 let repos_path = install_path.join(PACKAGES_REMOTE_DIR);
138 let mut repo_files = Vec::new();
139 for entry_res in fs::read_dir(&repos_path)? {
140 let entry = entry_res?;
141 let path = entry.path();
142 if path.is_file() {
143 repo_files.push(path);
144 }
145 }
146 repo_files.sort();
147 for repo_file in repo_files {
148 let data = fs::read_to_string(repo_file)?;
149 for line in data.lines() {
150 if !line.starts_with('#') {
151 self.add_remote(line.trim(), target)?;
152 }
153 }
154 }
155 let local_pub_path = install_path.join("pkg");
157 let _ = self.add_local("installer_key", "", target, &local_pub_path);
158 Ok(())
159 }
160
161 fn extract_host(path: &str) -> Option<&str> {
162 path.split("://")
163 .nth(1)?
164 .split('/')
165 .next()?
166 .split(':')
167 .next()
168 }
169
170 pub fn add_remote(&mut self, url: &str, target: &str) -> Result<(), Error> {
172 let host = Self::extract_host(url)
173 .ok_or_else(|| Error::RepoPathInvalid(url.into()))?
174 .to_string();
175
176 if self
177 .remote_map
178 .insert(
179 host.clone(),
180 RemotePath {
181 path: format!("{}/{}", url, target),
182 pubpath: format!("{}/{}", url, PUB_TOML),
183 name: host.clone(),
184 pubkey: None,
185 },
186 )
187 .is_none()
188 {
189 self.remotes.push(host);
190 };
191
192 Ok(())
193 }
194
195 pub fn add_local(
197 &mut self,
198 host: &str,
199 path: &str,
200 target: &str,
201 pubkey_dir: &Path,
202 ) -> Result<(), Error> {
203 let pubkey_path = pubkey_dir.join(PUB_TOML);
204 if !pubkey_path.is_file() {
205 return Err(Error::RepoPathInvalid(
206 pubkey_path.to_string_lossy().to_string(),
207 ));
208 }
209 let pubkey = RepoPublicKeyFile::open(pubkey_path).map_err(Error::from)?;
211 if self
212 .remote_map
213 .insert(
214 host.into(),
215 RemotePath {
216 path: if path.is_empty() {
217 path.into()
218 } else {
219 format!("{}/{}", path, target)
220 },
221 pubpath: "".into(),
223 name: host.into(),
224 pubkey: Some(pubkey.pkey),
225 },
226 )
227 .is_none()
228 {
229 self.locals.push(host.into());
230 };
231 Ok(())
232 }
233
234 fn sync_toml(&self, package_name: &PackageName) -> Result<(String, RemoteName), Error> {
236 let file = format!("{package_name}.toml");
237 if let Some((r, path)) = self.local_search(&file)? {
238 let toml = fs::read_to_string(path)?;
239 return Ok((toml, r));
240 }
241 let mut writer = DownloadBackendWriter::ToBuf(Vec::new());
242 match self.download(&file, None, &mut writer) {
243 Ok(r) => {
244 let text = writer.to_inner_buf();
245 let toml = String::from_utf8(text)
246 .map_err(|_| Error::ContentIsNotValidUnicode(file.into()))?;
247 Ok((toml, r))
248 }
249 Err(Error::ValidRepoNotFound) => {
250 Err(PackageError::PackageNotFound(package_name.to_owned()).into())
251 }
252 Err(e) => Err(e),
253 }
254 }
255
256 fn sync_pkgar(
258 &self,
259 package_name: &PackageName,
260 len_hint: u64,
261 dst_path: PathBuf,
262 ) -> Result<(PathBuf, RemoteName), Error> {
263 let file = format!("{package_name}.pkgar");
264 if let Some((r, path)) = self.local_search(&file)? {
265 return Ok((path, r));
266 }
267 let mut writer = DownloadBackendWriter::ToFile(File::create(&dst_path)?);
268 match self.download(&file, Some(len_hint), &mut writer) {
269 Ok(r) => Ok((dst_path, r)),
270 Err(Error::ValidRepoNotFound) => {
271 Err(PackageError::PackageNotFound(package_name.to_owned()).into())
272 }
273 Err(e) => Err(e),
274 }
275 }
276
277 pub fn get_local_path(&self, remote: &RemoteName, file: &str, ext: &str) -> PathBuf {
278 self.download_path.join(format!("{}_{file}.{ext}", remote))
279 }
280
281 pub fn sync_keys(&mut self) -> Result<(), Error> {
283 let download_dir = &self.download_path;
284 if !download_dir.is_dir() {
285 fs::create_dir_all(download_dir)?;
286 }
287 for (_, remote) in self.remote_map.iter_mut() {
288 if remote.pubkey.is_some() {
289 continue;
290 }
291 if remote.pubkey.is_none() {
293 let local_keypath = download_dir.join(format!("pub_key_{}.toml", remote.name));
294 if !local_keypath.exists() {
295 self.download_backend.download_to_file(
296 &remote.pubpath,
297 None,
298 &local_keypath,
299 self.callback.clone(),
300 )?;
301 }
302 let pubkey = RepoPublicKeyFile::open(local_keypath)?;
303 remote.pubkey = Some(pubkey.pkey);
304 }
305 }
306
307 Ok(())
308 }
309
310 pub fn download(
312 &self,
313 file: &str,
314 len: Option<u64>,
315 mut dest: &mut DownloadBackendWriter,
316 ) -> Result<RemoteName, Error> {
317 if !self.download_path.exists() {
318 fs::create_dir_all(self.download_path.clone())?;
319 }
320
321 for rname in self.remotes.iter() {
322 let Some(remote) = self.remote_map.get(rname) else {
323 continue;
324 };
325 if remote.path == "" {
326 continue;
328 }
329
330 let remote_path = format!("{}/{}", remote.path, file);
331 let res =
332 self.download_backend
333 .download(&remote_path, len, &mut dest, self.callback.clone());
334 match res {
335 Ok(_) => return Ok(rname.into()),
336 #[cfg(feature = "library")]
337 Err(DownloadError::HttpStatus(_)) => continue,
338 Err(e) => {
339 return Err(Error::Download(e));
340 }
341 };
342 }
343
344 Err(Error::ValidRepoNotFound)
345 }
346
347 pub fn local_search(&self, file: &str) -> Result<Option<(RemoteName, PathBuf)>, Error> {
349 if !self.download_path.exists() {
350 fs::create_dir_all(self.download_path.clone())?;
351 }
352
353 for rname in self.locals.iter() {
354 let Some(remote) = self.remote_map.get(rname) else {
355 continue;
356 };
357 if remote.path == "" {
358 continue;
360 }
361
362 let remote_path = Path::new(&remote.path).join(file);
363 match remote_path.metadata() {
364 Ok(e) => {
365 if e.is_file() {
366 return Ok(Some((rname.into(), remote_path)));
367 } else {
368 continue;
369 }
370 }
371 Err(err) => {
372 if err.kind() == std::io::ErrorKind::NotFound {
373 continue;
374 } else {
375 return Err(Error::IO(err));
376 }
377 }
378 }
379 }
380
381 Ok(None)
382 }
383
384 pub fn get_package_pkgar(
386 &self,
387 package: &PackageName,
388 len_hint: u64,
389 ) -> Result<(PathBuf, &RemotePath), Error> {
390 let local_path = self.get_local_path(&"".to_string(), package.as_str(), "pkgar");
391 let (local_path, remote) = self.sync_pkgar(&package, len_hint, local_path)?;
392 if let Some(r) = self.remote_map.get(&remote) {
393 if r.is_local() {
394 return Ok((local_path, r));
395 }
396 let new_local_path = self.get_local_path(&r.name, package.as_str(), "pkgar");
397 if new_local_path != local_path {
398 fs::rename(&local_path, &new_local_path)?;
399 }
400 Ok((new_local_path, r))
401 } else {
402 Err(Error::RepoCacheNotFound(package.clone()))
404 }
405 }
406
407 pub fn get_package_toml(&self, package: &PackageName) -> Result<(String, RemoteName), Error> {
409 self.callback.borrow_mut().fetch_package_name(&package);
410 self.sync_toml(package)
411 }
412}