mineflake/utils/
net.rs

1use anyhow::Result;
2use std::{
3	env::var,
4	fs::{create_dir_all, File},
5	io,
6	path::PathBuf,
7};
8
9/// Download a file from a URL to a local path.
10pub fn download_file(url: &url::Url, path: &PathBuf) -> Result<()> {
11	let url = url.clone();
12	debug!("Downloading {} to {:?}", url, path);
13	// Check if path is not a directory
14	if path.is_dir() {
15		return Err(anyhow!("Path is a directory"));
16	}
17	// Create the parent directory if it doesn't exist
18	let parent = path.parent().unwrap();
19	create_dir_all(parent)?;
20	// Download the file
21	let mut resp = reqwest::blocking::get(url)?;
22	let mut out = File::create(path)?;
23	io::copy(&mut resp, &mut out)?;
24	Ok(())
25}
26
27/// Get path to cache directory
28///
29/// This function will return the path to the $HOME/.cache directory, or to /tmp/mineflake if $HOME is not set.
30/// On Windows, this function will return the path to the %LOCALAPPDATA% directory, or to %TEMP% if %LOCALAPPDATA% is not set.
31pub fn get_cache_dir() -> PathBuf {
32	#[cfg(target_os = "windows")]
33	{
34		let path = var("MINEFLAKE_CACHE").unwrap_or_else(|_| {
35			var("TEMP").unwrap_or_else(|_| {
36				var("LOCALAPPDATA").unwrap_or_else(|_| {
37					var("TEMP").unwrap_or_else(|_| "C:\\Windows\\Temp".to_string())
38				})
39			})
40		});
41		let mut path = PathBuf::from(path);
42		path.push("mineflake");
43		path
44	}
45	#[cfg(not(target_os = "windows"))]
46	{
47		let path = var("MINEFLAKE_CACHE")
48			.unwrap_or_else(|_| var("HOME").unwrap_or_else(|_| "/tmp".to_string()));
49		let mut path = PathBuf::from(path);
50		path.push(".cache");
51		path.push("mineflake");
52		path
53	}
54}
55
56/// Split a hash into 4 parts of 2 characters each and remaining characters
57pub fn split_hash(hash: &str) -> PathBuf {
58	let mut path = PathBuf::new();
59	let mut chars = hash.chars();
60	for _ in 0..4 {
61		path.push(chars.by_ref().take(2).collect::<String>());
62	}
63	path.push(chars.collect::<String>());
64	path
65}
66
67/// Get sha256 hash of URL and download it to cache directory $CACHE_DIR/$HA/SH
68///
69/// Download only if file doesn't exist in cache directory.
70///
71/// # Returns
72/// sha256 hash of URL
73pub fn download_file_to_cache(url: &url::Url, extension: &str) -> Result<String> {
74	let hash = sha256::digest(url.to_string().as_bytes());
75	let hash_path = split_hash(&hash);
76	let mut path = get_cache_dir();
77	path.push(hash_path);
78	path.set_extension(extension);
79	if !path.exists() {
80		download_file(url, &path)?;
81	} else {
82		debug!("File already exists in cache");
83	}
84	Ok(hash)
85}
86
87/// Get sha256 hash of URL and download it to cache directory $CACHE_DIR/$HA/SH.$EXTENSION
88/// and return the full path to the file.
89pub fn download_file_to_cache_full_path(url: &url::Url, extension: &str) -> Result<PathBuf> {
90	let hash = download_file_to_cache(url, extension)?;
91	let hash_path = split_hash(&hash);
92	let mut path = get_cache_dir();
93	path.push(hash_path);
94	path.set_extension(extension);
95	Ok(path)
96}
97
98/// Unzip a $CACHE_DIR/$HA/SH.zip file to $CACHE_DIR/$HA/SH
99pub fn unzip_file_from_cache(hash: &str) -> Result<PathBuf> {
100	let cache = get_cache_dir();
101	let hash_path = split_hash(hash);
102	let mut zip_path = cache.clone();
103	zip_path.push(&hash_path);
104	zip_path.set_extension("zip");
105	let mut dir_path = cache;
106	dir_path.push(&hash_path);
107	if dir_path.exists() {
108		return Ok(dir_path);
109	}
110	if !zip_path.exists() {
111		return Err(anyhow!("File doesn't exist in cache"));
112	}
113	if !dir_path.exists() {
114		zip::read::ZipArchive::new(File::open(zip_path)?)?.extract(&dir_path)?;
115	} else {
116		debug!("File already unzipped in cache");
117	}
118	Ok(dir_path)
119}
120
121/// Download a file from a URL to a local path, and unzip it.
122pub fn download_and_unzip_file(url: &url::Url) -> Result<PathBuf> {
123	let hash = download_file_to_cache(url, "zip")?;
124	unzip_file_from_cache(&hash)
125}
126
127#[cfg(test)]
128mod tests {
129	#[test]
130	pub fn test_split_hash() {
131		assert_eq!(
132			super::split_hash("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
133			std::path::PathBuf::from(
134				"01/23/45/67/89abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
135			)
136		);
137	}
138}