use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use thiserror_no_std::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ArchiveError {
#[error("Archive not found: {0}")]
NotFound(String),
#[error("Invalid archive format")]
InvalidFormat,
#[error("Unsupported archive type: {0}")]
UnsupportedType(String),
#[error("Entry not found: {0}")]
EntryNotFound(String),
#[error("IO error: {0}")]
IoError(String),
#[error("Decompression error")]
DecompressError,
#[error("Archive is read-only")]
ReadOnly,
#[error("Checksum mismatch")]
ChecksumMismatch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArchiveType {
Zip,
Tar,
TarGz,
TarBz2,
TarXz,
SevenZip,
Rar,
}
impl ArchiveType {
pub fn extension(&self) -> &'static str {
match self {
ArchiveType::Zip => "zip",
ArchiveType::Tar => "tar",
ArchiveType::TarGz => "tar.gz",
ArchiveType::TarBz2 => "tar.bz2",
ArchiveType::TarXz => "tar.xz",
ArchiveType::SevenZip => "7z",
ArchiveType::Rar => "rar",
}
}
pub fn supports_write(&self) -> bool {
matches!(self, ArchiveType::Zip | ArchiveType::Tar)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionMethod {
Store,
Deflate,
Lzma,
Bzip2,
Unknown,
}
#[derive(Debug, Clone)]
pub struct ArchiveEntry {
pub name: String,
pub is_dir: bool,
pub size: u64,
pub compressed_size: u64,
pub mtime: u64,
pub mode: u32,
pub offset: u64,
pub compression: CompressionMethod,
pub crc32: u32,
}
#[derive(Debug, Clone)]
pub struct ArchiveMount {
pub archive_path: String,
pub archive_type: ArchiveType,
pub entries: BTreeMap<String, ArchiveEntry>,
pub writable: bool,
}
impl ArchiveMount {
pub fn open(path: &str) -> Result<Self, ArchiveError> {
let archive_type = super::detect_archive_type(path)
.ok_or_else(|| ArchiveError::UnsupportedType(path.to_string()))?;
let entries = match archive_type {
ArchiveType::Zip => super::parse_zip_directory(path)?,
ArchiveType::Tar | ArchiveType::TarGz | ArchiveType::TarBz2 | ArchiveType::TarXz => {
super::parse_tar_directory(path)?
}
_ => return Err(ArchiveError::UnsupportedType(path.to_string())),
};
Ok(Self {
archive_path: path.to_string(),
archive_type,
entries,
writable: archive_type.supports_write(),
})
}
pub fn create(path: &str, archive_type: ArchiveType) -> Result<Self, ArchiveError> {
if !archive_type.supports_write() {
return Err(ArchiveError::ReadOnly);
}
Ok(Self {
archive_path: path.to_string(),
archive_type,
entries: BTreeMap::new(),
writable: true,
})
}
pub fn list(&self, dir_path: &str) -> Vec<&ArchiveEntry> {
let prefix = if dir_path.is_empty() || dir_path == "/" {
""
} else {
dir_path.trim_start_matches('/')
};
self.entries
.values()
.filter(|e| {
if prefix.is_empty() {
!e.name.contains('/')
} else {
e.name.starts_with(prefix)
&& e.name[prefix.len()..].starts_with('/')
&& !e.name[prefix.len() + 1..].contains('/')
}
})
.collect()
}
pub fn read_file(&self, path: &str) -> Result<Vec<u8>, ArchiveError> {
let entry = self
.entries
.get(path.trim_start_matches('/'))
.ok_or_else(|| ArchiveError::EntryNotFound(path.to_string()))?;
if entry.is_dir {
return Err(ArchiveError::EntryNotFound(path.to_string()));
}
let _ = entry;
Ok(Vec::new())
}
}
#[derive(Debug, Clone, Default)]
pub struct ExtractResult {
pub files_extracted: u64,
pub directories_created: u64,
pub bytes_extracted: u64,
}