use std::fs::File;
use std::io::{self, BufReader, Read, Seek, SeekFrom};
use std::path::Path;
use crate::error::Context;
use crate::pgz::ParallelGz;
use crate::{Result, UnpackOptions};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Decoder {
Plain,
GzipStream,
GzipParallel,
Zstd,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compression {
None,
Gzip,
Zstd,
}
impl Compression {
pub fn detect(head: &[u8]) -> Self {
if head.starts_with(&[0x1f, 0x8b]) {
Self::Gzip
} else if head.starts_with(&[0x28, 0xb5, 0x2f, 0xfd]) {
Self::Zstd
} else {
Self::None
}
}
}
pub fn decoder(blob: &[u8]) -> Result<Box<dyn Read + Send + '_>> {
Ok(match Compression::detect(blob) {
Compression::None => Box::new(blob),
Compression::Gzip => Box::new(flate2::read::MultiGzDecoder::new(blob)),
Compression::Zstd => {
Box::new(zstd::stream::read::Decoder::new(blob).ctx(|| "zstd header".into())?)
}
})
}
pub(crate) fn open_layer(
path: &Path,
opts: &UnpackOptions,
) -> Result<(Box<dyn Read + Send>, Decoder)> {
let what = || format!("opening layer {}", path.display());
let mut f = File::open(path).ctx(what)?;
let mut head = [0u8; 4];
let n = read_up_to(&mut f, &mut head).ctx(what)?;
f.seek(SeekFrom::Start(0)).ctx(what)?;
let len = f.metadata().ctx(what)?.len();
Ok(match Compression::detect(&head[..n]) {
Compression::None => (
Box::new(BufReader::with_capacity(1 << 20, f)),
Decoder::Plain,
),
Compression::Zstd => (
Box::new(zstd::stream::read::Decoder::new(f).ctx(what)?),
Decoder::Zstd,
),
Compression::Gzip
if opts.parallel_gzip
&& opts.threads() > 1
&& len >= lgz::PARALLEL_THRESHOLD as u64
&& has_full_flush(&mut f).ctx(what)? =>
{
(
Box::new(ParallelGz::new(f, opts.threads())),
Decoder::GzipParallel,
)
}
Compression::Gzip => (
Box::new(flate2::read::MultiGzDecoder::new(BufReader::with_capacity(
1 << 20,
f,
))),
Decoder::GzipStream,
),
})
}
const FLUSH_PROBE: usize = 4 << 20;
fn has_full_flush(f: &mut File) -> io::Result<bool> {
let mut head = vec![0u8; FLUSH_PROBE];
let n = read_up_to(f, &mut head)?;
f.seek(SeekFrom::Start(0))?;
head.truncate(n);
let Ok(start) = lgz::deflate_offset(&head) else {
return Ok(false);
};
Ok(lgz::deflate_scan::find_next_valid_flush(&head[start..], 0).is_some())
}
fn read_up_to(r: &mut impl Read, buf: &mut [u8]) -> io::Result<usize> {
let mut n = 0;
while n < buf.len() {
match r.read(&mut buf[n..])? {
0 => break,
k => n += k,
}
}
Ok(n)
}