mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-02-06 14:17:02 +00:00
194 lines
6.2 KiB
Rust
194 lines
6.2 KiB
Rust
use either::{self, Either};
|
|
|
|
use std::{fs, io, path};
|
|
|
|
/// The supported archive formats.
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub enum ArchiveFormat {
|
|
/// Tar archive.
|
|
Tar(Option<Compression>),
|
|
/// Plain archive.
|
|
Plain(Option<Compression>),
|
|
/// Zip archive.
|
|
Zip,
|
|
}
|
|
|
|
/// The supported compression types.
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub enum Compression {
|
|
/// Gz compression (e.g. `.tar.gz` archives)
|
|
Gz,
|
|
}
|
|
|
|
/// The extract manager.
|
|
#[derive(Debug)]
|
|
pub struct Extract<'a> {
|
|
source: &'a path::Path,
|
|
archive_format: Option<ArchiveFormat>,
|
|
}
|
|
|
|
fn detect_archive_type(path: &path::Path) -> ArchiveFormat {
|
|
match path.extension() {
|
|
Some(extension) if extension == std::ffi::OsStr::new("zip") => ArchiveFormat::Zip,
|
|
Some(extension) if extension == std::ffi::OsStr::new("tar") => ArchiveFormat::Tar(None),
|
|
Some(extension) if extension == std::ffi::OsStr::new("gz") => match path
|
|
.file_stem()
|
|
.map(|e| path::Path::new(e))
|
|
.and_then(|f| f.extension())
|
|
{
|
|
Some(extension) if extension == std::ffi::OsStr::new("tar") => {
|
|
ArchiveFormat::Tar(Some(Compression::Gz))
|
|
}
|
|
_ => ArchiveFormat::Plain(Some(Compression::Gz)),
|
|
},
|
|
_ => ArchiveFormat::Plain(None),
|
|
}
|
|
}
|
|
|
|
impl<'a> Extract<'a> {
|
|
/// Create an `Extractor from a source path
|
|
pub fn from_source(source: &'a path::Path) -> Extract<'a> {
|
|
Self {
|
|
source,
|
|
archive_format: None,
|
|
}
|
|
}
|
|
|
|
/// Specify an archive format of the source being extracted. If not specified, the
|
|
/// archive format will determined from the file extension.
|
|
pub fn archive_format(&mut self, format: ArchiveFormat) -> &mut Self {
|
|
self.archive_format = Some(format);
|
|
self
|
|
}
|
|
|
|
fn get_archive_reader(
|
|
source: fs::File,
|
|
compression: Option<Compression>,
|
|
) -> Either<fs::File, flate2::read::GzDecoder<fs::File>> {
|
|
match compression {
|
|
Some(Compression::Gz) => Either::Right(flate2::read::GzDecoder::new(source)),
|
|
None => Either::Left(source),
|
|
}
|
|
}
|
|
|
|
/// Extract an entire source archive into a specified path. If the source is a single compressed
|
|
/// file and not an archive, it will be extracted into a file with the same name inside of
|
|
/// `into_dir`.
|
|
pub fn extract_into(&self, into_dir: &path::Path) -> crate::Result<()> {
|
|
let source = fs::File::open(self.source)?;
|
|
let archive = self
|
|
.archive_format
|
|
.unwrap_or_else(|| detect_archive_type(&self.source));
|
|
|
|
match archive {
|
|
ArchiveFormat::Plain(compression) | ArchiveFormat::Tar(compression) => {
|
|
let mut reader = Self::get_archive_reader(source, compression);
|
|
|
|
match archive {
|
|
ArchiveFormat::Plain(_) => {
|
|
match fs::create_dir_all(into_dir) {
|
|
Ok(_) => (),
|
|
Err(e) => {
|
|
if e.kind() != io::ErrorKind::AlreadyExists {
|
|
return Err(e.into());
|
|
}
|
|
}
|
|
}
|
|
let file_name = self
|
|
.source
|
|
.file_name()
|
|
.ok_or_else(|| crate::Error::Extract("Extractor source has no file-name".into()))?;
|
|
let mut out_path = into_dir.join(file_name);
|
|
out_path.set_extension("");
|
|
let mut out_file = fs::File::create(&out_path)?;
|
|
io::copy(&mut reader, &mut out_file)?;
|
|
}
|
|
ArchiveFormat::Tar(_) => {
|
|
let mut archive = tar::Archive::new(reader);
|
|
archive.unpack(into_dir)?;
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
}
|
|
ArchiveFormat::Zip => {
|
|
let mut archive = zip::ZipArchive::new(source)?;
|
|
for i in 0..archive.len() {
|
|
let mut file = archive.by_index(i)?;
|
|
let path = into_dir.join(file.name());
|
|
let mut output = fs::File::create(path)?;
|
|
io::copy(&mut file, &mut output)?;
|
|
}
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
/// Extract a single file from a source and save to a file of the same name in `into_dir`.
|
|
/// If the source is a single compressed file, it will be saved with the name `file_to_extract`
|
|
/// in the specified `into_dir`.
|
|
pub fn extract_file<T: AsRef<path::Path>>(
|
|
&self,
|
|
into_dir: &path::Path,
|
|
file_to_extract: T,
|
|
) -> crate::Result<()> {
|
|
let file_to_extract = file_to_extract.as_ref();
|
|
let source = fs::File::open(self.source)?;
|
|
let archive = self
|
|
.archive_format
|
|
.unwrap_or_else(|| detect_archive_type(&self.source));
|
|
|
|
match archive {
|
|
ArchiveFormat::Plain(compression) | ArchiveFormat::Tar(compression) => {
|
|
let mut reader = Self::get_archive_reader(source, compression);
|
|
|
|
match archive {
|
|
ArchiveFormat::Plain(_) => {
|
|
match fs::create_dir_all(into_dir) {
|
|
Ok(_) => (),
|
|
Err(e) => {
|
|
if e.kind() != io::ErrorKind::AlreadyExists {
|
|
return Err(e.into());
|
|
}
|
|
}
|
|
}
|
|
let file_name = file_to_extract
|
|
.file_name()
|
|
.ok_or_else(|| crate::Error::Extract("Extractor source has no file-name".into()))?;
|
|
let out_path = into_dir.join(file_name);
|
|
let mut out_file = fs::File::create(&out_path)?;
|
|
io::copy(&mut reader, &mut out_file)?;
|
|
}
|
|
ArchiveFormat::Tar(_) => {
|
|
let mut archive = tar::Archive::new(reader);
|
|
let mut entry = archive
|
|
.entries()?
|
|
.filter_map(|e| e.ok())
|
|
.find(|e| e.path().ok().filter(|p| p == file_to_extract).is_some())
|
|
.ok_or_else(|| {
|
|
crate::Error::Extract(format!(
|
|
"Could not find the required path in the archive: {:?}",
|
|
file_to_extract
|
|
))
|
|
})?;
|
|
entry.unpack_in(into_dir)?;
|
|
}
|
|
_ => {
|
|
panic!("Unreasonable code");
|
|
}
|
|
};
|
|
}
|
|
ArchiveFormat::Zip => {
|
|
let mut archive = zip::ZipArchive::new(source)?;
|
|
let mut file = archive.by_name(
|
|
file_to_extract
|
|
.to_str()
|
|
.expect("Could not convert file to str"),
|
|
)?;
|
|
let mut output = fs::File::create(into_dir.join(file.name()))?;
|
|
io::copy(&mut file, &mut output)?;
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
}
|