use std::io::{Read, Seek};
use chrono::{Datelike, Timelike};
use delharc::header::LhaHeader;
use delharc::LhaDecodeReader;
use crate::date_time::DosDateTime;
use crate::error::{ArchiveError, Result};
#[derive(Debug, Clone)]
pub struct LhaFileHeader {
pub name: String,
pub compressed_size: u64,
pub original_size: u64,
pub compression_method: String,
pub date_time: Option<DosDateTime>,
pub crc16: u16,
pub is_supported: bool,
pub is_directory: bool,
}
impl LhaFileHeader {
pub fn from_lha_header(header: &LhaHeader) -> Self {
let path = header.parse_pathname();
let name = path.to_string_lossy().to_string();
let modified = header.parse_last_modified();
let date_time = modified.to_naive_utc().map(|dt| {
let year = dt.year() as u32;
let month = dt.month();
let day = dt.day();
let hour = dt.hour();
let minute = dt.minute();
let second = dt.second();
let dos_date = ((year.saturating_sub(1980) & 0x7F) << 9) | ((month & 0xF) << 5) | (day & 0x1F);
let dos_time = ((hour & 0x1F) << 11) | ((minute & 0x3F) << 5) | ((second / 2) & 0x1F);
DosDateTime::new((dos_date << 16) | dos_time)
});
LhaFileHeader {
name,
compressed_size: header.compressed_size,
original_size: header.original_size,
compression_method: match header.compression_method() {
Ok(m) => format!("{:?}", m),
Err(e) => e.to_string(),
},
date_time,
crc16: header.file_crc,
is_supported: true, is_directory: header.is_directory(),
}
}
}
pub struct LhaArchive<T: Read> {
reader: Option<LhaDecodeReader<T>>,
current_header: Option<LhaFileHeader>,
finished: bool,
}
impl<T: Read> LhaArchive<T> {
pub fn new(reader: T) -> Result<Self> {
let lha_reader = LhaDecodeReader::new(reader)?;
let header = LhaFileHeader::from_lha_header(lha_reader.header());
let is_supported = lha_reader.is_decoder_supported();
Ok(Self {
reader: Some(lha_reader),
current_header: Some(LhaFileHeader { is_supported, ..header }),
finished: false,
})
}
pub fn get_next_entry(&mut self) -> Result<Option<LhaFileHeader>> {
if self.finished {
return Ok(None);
}
if let Some(header) = self.current_header.take() {
return Ok(Some(header));
}
if let Some(ref mut reader) = self.reader {
if reader.next_file()? {
let header = LhaFileHeader::from_lha_header(reader.header());
let is_supported = reader.is_decoder_supported();
return Ok(Some(LhaFileHeader { is_supported, ..header }));
}
}
self.finished = true;
Ok(None)
}
pub fn skip(&mut self, _header: &LhaFileHeader) -> Result<()> {
if let Some(ref mut reader) = self.reader {
if !reader.next_file()? {
self.finished = true;
} else {
let header = LhaFileHeader::from_lha_header(reader.header());
let is_supported = reader.is_decoder_supported();
self.current_header = Some(LhaFileHeader { is_supported, ..header });
}
}
Ok(())
}
pub fn read(&mut self, header: &LhaFileHeader) -> Result<Vec<u8>> {
if header.is_directory {
return Ok(Vec::new());
}
if !header.is_supported {
return Err(ArchiveError::unsupported_method("LHA", &header.compression_method));
}
if let Some(ref mut reader) = self.reader {
let mut data = Vec::with_capacity(header.original_size as usize);
reader.read_to_end(&mut data)?;
reader.crc_check()?;
if !reader.next_file()? {
self.finished = true;
} else {
let next_header = LhaFileHeader::from_lha_header(reader.header());
let is_supported = reader.is_decoder_supported();
self.current_header = Some(LhaFileHeader { is_supported, ..next_header });
}
Ok(data)
} else {
Err(ArchiveError::decompression_failed(&header.name, "Archive reader not available"))
}
}
}
pub struct LhaArchiveSeekable<T: Read + Seek> {
inner: LhaArchive<T>,
}
impl<T: Read + Seek> LhaArchiveSeekable<T> {
pub fn new(reader: T) -> Result<Self> {
Ok(Self {
inner: LhaArchive::new(reader)?,
})
}
pub fn get_next_entry(&mut self) -> Result<Option<LhaFileHeader>> {
self.inner.get_next_entry()
}
pub fn skip(&mut self, header: &LhaFileHeader) -> Result<()> {
self.inner.skip(header)
}
pub fn read(&mut self, header: &LhaFileHeader) -> Result<Vec<u8>> {
self.inner.read(header)
}
}