1use std::fmt::Display;
2use std::fs;
3use std::io::{Read, Write};
4
5use sha::sha256::Sha256;
6use sha::utils::DigestExt;
7
8use crate::defined_constants::{DEPENDENCY_CACHE_DIR, DEPENDENCY_INCLUDE_DIR};
9use crate::error::{ChurError, ChurResult};
10
11use archiver_rs::{Archive, Compressed};
12
13#[derive(Debug)]
14pub enum DependencyFormat {
15 Gzip,
16}
17
18#[derive(Debug)]
19pub struct Dependency {
20 pub(crate) url: String,
21 pub(crate) format: DependencyFormat,
22 pub(crate) subdir: Option<String>,
23}
24
25impl Dependency {
26 pub fn new(
27 url: impl Display,
28 format: DependencyFormat,
29 subdir: impl Into<Option<String>>,
30 ) -> Self {
31 Self {
32 url: url.to_string(),
33 format,
34 subdir: subdir.into(),
35 }
36 }
37
38 pub fn tarball(
39 url: impl Display,
40 format: DependencyFormat,
41 subdir: impl Into<Option<String>>,
42 ) -> Self {
43 Self {
44 url: url.to_string(),
45 format,
46 subdir: subdir.into(),
47 }
48 }
49
50 pub fn github(repo: impl Display, branch_or_hash: impl Into<Option<String>>) -> Self {
51 let branch_opt: Option<String> = branch_or_hash.into();
52 let branch_or_hash_unwrapped = branch_opt.unwrap_or("main".to_string());
53
54 let repo_string = repo.to_string();
55 let mut repo_split = repo_string.split("/");
56
57 let _user = repo_split.next().unwrap();
58 let repo = repo_split.next().unwrap();
59
60 Self::new(
65 format!("https://github.com/{repo_string}/archive/{branch_or_hash_unwrapped}.tar.gz"),
66 DependencyFormat::Gzip,
67 format!("{repo}-{branch_or_hash_unwrapped}"),
68 )
69 }
70
71 pub(crate) fn fetch(&self) -> ChurResult<String> {
72 match self.format {
73 DependencyFormat::Gzip => self.fetch_gzip_tar(),
74 }
75 }
76
77 pub(crate) fn fetch_gzip_tar(&self) -> ChurResult<String> {
78 let agent = ureq::Agent::new();
79
80 let response = agent.get(&self.url).call()?;
81 if response.status() != 200 {
82 ChurError::Dependency(format!(
83 "Dependency with URL \"{}\" returns a result of {}",
84 self.url,
85 response.status()
86 ));
87 }
88
89 let response_len = if let Some(header) = response.header("Content-Length") {
91 header.parse().unwrap_or(2_000_000)
92 } else {
93 2_000_000
94 };
95
96 let mut buf = Vec::with_capacity(response_len);
97 response
98 .into_reader()
99 .take(response_len as u64)
100 .read_to_end(&mut buf)?;
101
102 let mut hasher = Sha256::default();
103 hasher.write_all(&buf)?;
104
105 let hash = hasher.to_hex();
106
107 let mut archive = archiver_rs::Gzip::new(buf.as_slice())?;
108 fs::create_dir_all(DEPENDENCY_CACHE_DIR.as_path())?;
109 let tarball_file = DEPENDENCY_CACHE_DIR.join(&format!("tarball_{}", &hash));
110 archive.decompress(&tarball_file)?;
111 drop(archive);
112
113 let mut tarball = archiver_rs::Tar::open(&tarball_file)?;
114 let unpacked_dir = DEPENDENCY_INCLUDE_DIR.join(&hash);
115 fs::create_dir_all(&unpacked_dir)?;
116 tarball.extract(&unpacked_dir)?;
117
118 Ok(hash)
119 }
120}