use flate2::read::GzDecoder;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::path::{Path, PathBuf};
use crate::model::asset_name::mk_exe_name;
pub struct Archive<'a> {
archive_path: &'a PathBuf,
tmp_dir: &'a Path,
exe_name: &'a str,
archive_type: ArchiveType<'a>,
}
enum ArchiveType<'a> {
Exe(&'a str),
Zip(&'a str),
TarGz(&'a str),
}
pub enum UnpackError {
IOError(std::io::Error),
ZipError(zip::result::ZipError),
ExeNotFound(String),
}
impl Display for UnpackError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
UnpackError::IOError(e) => write!(f, "{}", e),
UnpackError::ZipError(e) => write!(f, "{}", e),
UnpackError::ExeNotFound(archive_name) => {
write!(f, "Can't find executable in archive: {}", archive_name)
}
}
}
}
impl<'a> Archive<'a> {
pub fn from(
archive_path: &'a PathBuf,
tmp_dir: &'a Path,
exe_name: &'a str,
asset_name: &'a str,
) -> Option<Archive<'a>> {
let tar_gz_dir = asset_name.strip_suffix(".tar.gz");
match tar_gz_dir {
Some(tar_gz_dir) => Some(Archive {
archive_path,
tmp_dir,
exe_name,
archive_type: ArchiveType::TarGz(tar_gz_dir),
}),
None => {
let zip_dir = asset_name.strip_suffix(".zip");
match zip_dir {
Some(zip_dir) => Some(Archive {
archive_path,
tmp_dir,
exe_name,
archive_type: ArchiveType::Zip(zip_dir),
}),
None => {
let exe_file = asset_name.strip_suffix(".exe");
exe_file.map(|_| Archive {
archive_path,
tmp_dir,
exe_name,
archive_type: ArchiveType::Exe(asset_name),
})
}
}
}
}
}
pub fn unpack(&self) -> Result<PathBuf, UnpackError> {
match self.archive_type {
ArchiveType::Exe(exe_file) => Ok(PathBuf::from(exe_file)),
ArchiveType::TarGz(asset_name) => {
unpack_tar(self.archive_path, self.tmp_dir).map_err(UnpackError::IOError)?;
find_path_to_exe(self.archive_path, self.tmp_dir, self.exe_name, asset_name)
}
ArchiveType::Zip(asset_name) => {
unpack_zip(self.archive_path, self.tmp_dir)?;
find_path_to_exe(self.archive_path, self.tmp_dir, self.exe_name, asset_name)
}
}
}
}
fn unpack_tar(tar_path: &PathBuf, tmp_dir: &Path) -> Result<(), std::io::Error> {
let tar_file = File::open(tar_path)?;
let tar_decoder = GzDecoder::new(tar_file);
let mut archive = tar::Archive::new(tar_decoder);
archive.unpack(tmp_dir)
}
fn unpack_zip(zip_path: &PathBuf, tmp_dir: &Path) -> Result<(), UnpackError> {
let zip_archive_file = File::open(&zip_path).map_err(UnpackError::IOError)?;
let mut archive = zip::ZipArchive::new(zip_archive_file).map_err(UnpackError::ZipError)?;
archive.extract(tmp_dir).map_err(UnpackError::ZipError)
}
fn find_path_to_exe(
archive_path: &Path,
tmp_dir: &Path,
exe_name: &str,
asset_name: &str,
) -> Result<PathBuf, UnpackError> {
let path_candidates = exe_paths(exe_name, asset_name);
for path in path_candidates {
let mut tool_path = PathBuf::new();
tool_path.push(tmp_dir);
tool_path.push(path);
if tool_path.is_file() {
return Ok(tool_path);
}
}
Err(UnpackError::ExeNotFound(format!(
"{}",
archive_path.display()
)))
}
fn exe_paths(exe_name: &str, asset_name: &str) -> Vec<PathBuf> {
let exe_name = mk_exe_name(exe_name);
vec![
[asset_name, &exe_name].iter().collect(),
[&exe_name].iter().collect(),
["bin", &exe_name].iter().collect(),
[asset_name, "bin", &exe_name].iter().collect(),
]
}