The cross-platform version manager toolset
crates.io/crates/hyper-jump
1use std::fs;
2
3use anyhow::anyhow;
4use anyhow::Result;
5use liblzma::read::XzDecoder;
6use zip::ZipArchive as ZipReader;
7
8use crate::domain::package::Package;
9use crate::domain::version::LocalVersion;
10use crate::ports::Archive;
11
12pub struct LocalArchive;
13
14impl Archive for LocalArchive {
15 async fn extract(&self, package: Package, file: LocalVersion) -> anyhow::Result<()> {
16 unarchive(package, file).await
17 }
18}
19
20async fn unarchive(package: Package, file: LocalVersion) -> Result<()> {
21 let path = format!("{}/{}.{}", file.path, file.file_name, file.file_format);
22 tokio::task::spawn_blocking(move || expand(package, file))
23 .await?
24 .map_err(|e| anyhow!(e))?;
25
26 tokio::fs::remove_file(path).await?;
27
28 Ok(())
29}
30
31fn expand(package: Package, tmp: LocalVersion) -> Result<()> {
32 use std::fs::File;
33 use std::os::unix::fs::PermissionsExt;
34
35 use anyhow::Context;
36 use flate2::read::GzDecoder;
37 use tar::Archive;
38
39 if fs::metadata(&tmp.file_name).is_ok() {
40 fs::remove_dir_all(&tmp.file_name)?;
41 }
42
43 let file_path = format!("{}/{}.{}", tmp.path, tmp.file_name, tmp.file_format);
44 let file = File::open(&file_path).map_err(|error| {
45 anyhow!(
46 "Failed to open file {}.{}, file doesn't exist. additional info: {error}",
47 tmp.file_name,
48 tmp.file_format,
49 )
50 })?;
51
52 let output = format!("{}/{}", tmp.path, tmp.file_name);
53
54 let context_msg = format!(
55 "Failed to decompress or extract file {}.{}",
56 tmp.file_name, tmp.file_format
57 );
58
59 match tmp.file_format.as_str() {
60 "tar.gz" => {
61 let decompress_stream = GzDecoder::new(file);
62 let mut archive = Archive::new(decompress_stream);
63 archive.unpack(&output).with_context(|| context_msg)?;
64 }
65 "tar.xz" => {
66 let decompress_stream = XzDecoder::new(file);
67 let mut archive = Archive::new(decompress_stream);
68 archive.unpack(&output).with_context(|| context_msg)?;
69 }
70 "zip" => {
71 let mut archive = ZipReader::new(file)
72 .map_err(|error| anyhow!("{context_msg}. additional info: {error}"))?;
73 archive.extract(&output).with_context(|| context_msg)?;
74 }
75 _ => return Err(anyhow!("Unsupported file format")),
76 }
77
78 let binary = &format!(
79 "{}/{}/{}",
80 tmp.file_name,
81 package.binary_path(),
82 package.binary_name()
83 );
84
85 let mut perms = fs::metadata(binary)?.permissions();
86 perms.set_mode(0o551);
87 fs::set_permissions(binary, perms)?;
88
89 Ok(())
90}