1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
use crate::error::Error;
use flate2::read::GzDecoder;
use std::fs::{self, File};
use std::path::Path;
use tempfile::tempdir_in;
use zip_extensions::read::zip_extract;
use zstd::Decoder as ZstdDecoder;

/// Supported archive types.
pub(crate) enum ArchiveFormat {
    TarGz,
    TarZstd,
    Zip,
}

impl ArchiveFormat {
    /// Parse archive type from resource extension.
    pub(crate) fn parse_from_extension(resource: &str) -> Result<Self, Error> {
        if resource.ends_with(".tar.gz") || resource.ends_with(".tgz") {
            Ok(Self::TarGz)
        } else if resource.ends_with(".tar.zst") {
            Ok(Self::TarZstd)
        } else if resource.ends_with(".zip") {
            Ok(Self::Zip)
        } else {
            Err(Error::ExtractionError("unsupported archive format".into()))
        }
    }
}

pub(crate) fn extract_archive<P: AsRef<Path>>(
    path: P,
    target: P,
    format: &ArchiveFormat,
) -> Result<(), Error> {
    // We'll first extract to a temp directory in the same parent as the target directory.
    let target_parent_dir = target.as_ref().parent().unwrap();
    let temp_target = tempdir_in(target_parent_dir)?;

    match format {
        ArchiveFormat::TarGz => {
            let tar_gz = File::open(path)?;
            let tar = GzDecoder::new(tar_gz);
            let mut archive = tar::Archive::new(tar);
            archive.unpack(&temp_target)?;
        }
        ArchiveFormat::TarZstd => {
            let tar_zst = File::open(path)?;
            let tar = ZstdDecoder::new(tar_zst)?;
            let mut archive = tar::Archive::new(tar);
            archive.unpack(&temp_target)?;
        }
        ArchiveFormat::Zip => {
            zip_extract(
                &path.as_ref().to_path_buf(),
                &temp_target.path().to_path_buf(),
            )
            .map_err(|e| Error::ExtractionError(format!("{:?}", e)))?;
        }
    };

    // Now rename the temp directory to the final target directory.
    fs::rename(temp_target, target)?;

    Ok(())
}