use std::collections::HashMap;
use std::fmt::Debug;
use std::io::Cursor;
use std::ops::Range;
use vfs::FileSystem;
use vfs::VfsError;
use vfs::VfsMetadata;
use vfs::error::VfsErrorKind;
use crate::Chunk;
use crate::FileEntryMeta;
use crate::PakFile;
use crate::RcFileEntry;
pub trait Prime {
fn prime_file(&self, file_range: Range<usize>) -> Result<impl AsRef<[u8]>, VfsError>;
}
pub trait ReadAt {
fn read_at(&self, file_range: std::ops::Range<usize>) -> Result<impl AsRef<[u8]>, VfsError>;
}
#[derive(Debug, Clone)]
pub struct PakVfs<T> {
pub(crate) source: T,
entry_cache: HashMap<String, RcFileEntry>,
}
impl<T> PakVfs<T>
where
T: std::ops::Deref,
T::Target: AsRef<PakFile>,
{
pub fn new(source: T) -> Self {
let mut entry_cache = HashMap::new();
let pak: &PakFile = source.as_ref();
let file_chunk = pak.file_chunk().unwrap();
let Chunk::File { fs } = file_chunk else { panic!("file chunk is not a file?") };
let mut queue = vec![("".to_string(), RcFileEntry::clone(fs))];
while let Some((path, current)) = queue.pop() {
let this_path = if path == "/" {
format!("{}{}", path, current.name())
} else {
format!("{}/{}", path, current.name())
};
let key = this_path.clone();
entry_cache.insert(key, RcFileEntry::clone(¤t));
match current.meta() {
FileEntryMeta::Folder { children } => {
for child in children {
queue.push((this_path.clone(), RcFileEntry::clone(child)));
}
}
FileEntryMeta::File { .. } => {
}
}
}
Self { source, entry_cache }
}
}
impl<T> PakVfs<T>
where
T: std::ops::Deref,
T::Target: AsRef<PakFile>,
{
pub fn entry_at(&self, path: &str) -> vfs::VfsResult<&RcFileEntry> {
let lookup_key = if path.is_empty() { "/" } else { path };
self.entry_cache.get(lookup_key).ok_or_else(|| VfsError::from(VfsErrorKind::FileNotFound))
}
}
#[cfg(feature = "arc")]
impl<T> FileSystem for PakVfs<T>
where
T: std::ops::Deref + Sync + Send + Debug + 'static,
T::Target: AsRef<PakFile> + Prime,
{
fn read_dir(&self, path: &str) -> vfs::VfsResult<Box<dyn Iterator<Item = String> + Send>> {
let entry = self.entry_at(path)?;
match entry.meta() {
FileEntryMeta::Folder { children } => Ok(Box::new(
children
.iter()
.map(|child| child.name().to_string())
.collect::<Vec<_>>()
.into_iter(),
)),
FileEntryMeta::File { .. } => Err(VfsError::from(VfsErrorKind::NotSupported)),
}
}
fn create_dir(&self, _path: &str) -> vfs::VfsResult<()> {
todo!()
}
fn open_file(&self, path: &str) -> vfs::VfsResult<Box<dyn vfs::SeekAndRead + Send>> {
let entry = self.entry_at(path)?;
let FileEntryMeta::File { offset, compressed_len, decompressed_len, compressed, .. } =
entry.meta()
else {
return Err(VfsError::from(VfsErrorKind::NotSupported));
};
let mut data = Vec::with_capacity(*decompressed_len as usize);
let data_start = *offset as usize;
let data_end = data_start + *compressed_len as usize;
let primed_file = self.source.prime_file(data_start..data_end)?;
let source_slice: &[u8] = primed_file.as_ref();
let mut source_range = source_slice;
if *compressed != 0 {
let mut decoder = flate2::read::ZlibDecoder::new(source_range);
std::io::copy(&mut decoder, &mut data).map_err(|err| {
println!("error occurred during decompression: {err:#?}");
println!("offset: {:#X?}", *offset);
VfsError::from(VfsErrorKind::IoError(err))
})?;
Ok(Box::new(Cursor::new(data)))
} else {
let _ = std::io::copy(&mut source_range, &mut data);
Ok(Box::new(Cursor::new(data)))
}
}
fn create_file(&self, _path: &str) -> vfs::VfsResult<Box<dyn vfs::SeekAndWrite + Send>> {
todo!()
}
fn append_file(&self, _path: &str) -> vfs::VfsResult<Box<dyn vfs::SeekAndWrite + Send>> {
todo!()
}
fn metadata(&self, path: &str) -> vfs::VfsResult<vfs::VfsMetadata> {
let entry = self.entry_at(path)?;
let pak_meta = entry.meta();
let meta = match pak_meta {
FileEntryMeta::Folder { children: _ } => VfsMetadata {
file_type: vfs::VfsFileType::Directory,
len: 0,
created: None,
modified: None,
accessed: None,
},
FileEntryMeta::File { decompressed_len, .. } => {
VfsMetadata {
file_type: vfs::VfsFileType::File,
len: *decompressed_len as u64,
created: None,
modified: None,
accessed: None,
}
}
};
Ok(meta)
}
fn exists(&self, path: &str) -> vfs::VfsResult<bool> {
if self.entry_at(path).is_ok() {
return Ok(true);
}
Ok(false)
}
fn remove_file(&self, _path: &str) -> vfs::VfsResult<()> {
todo!()
}
fn remove_dir(&self, _path: &str) -> vfs::VfsResult<()> {
todo!()
}
fn set_creation_time(&self, _path: &str, _time: std::time::SystemTime) -> vfs::VfsResult<()> {
Err(vfs::VfsError::from(vfs::error::VfsErrorKind::NotSupported))
}
fn set_modification_time(
&self,
_path: &str,
_time: std::time::SystemTime,
) -> vfs::VfsResult<()> {
Err(vfs::VfsError::from(vfs::error::VfsErrorKind::NotSupported))
}
fn set_access_time(&self, _path: &str, _time: std::time::SystemTime) -> vfs::VfsResult<()> {
Err(vfs::VfsError::from(vfs::error::VfsErrorKind::NotSupported))
}
fn copy_file(&self, _src: &str, _dest: &str) -> vfs::VfsResult<()> {
Err(vfs::error::VfsErrorKind::NotSupported.into())
}
fn move_file(&self, _src: &str, _dest: &str) -> vfs::VfsResult<()> {
Err(vfs::error::VfsErrorKind::NotSupported.into())
}
fn move_dir(&self, _src: &str, _dest: &str) -> vfs::VfsResult<()> {
Err(vfs::error::VfsErrorKind::NotSupported.into())
}
}