feat: zip file reading

This commit is contained in:
DecDuck
2025-07-02 11:55:04 +10:00
parent c1aaf8adcd
commit 48e5b97a4e
8 changed files with 163 additions and 42 deletions

View File

@@ -1,11 +1,18 @@
use core::arch;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::{
fs::File,
io::{self, Read},
path::PathBuf,
pin::Pin,
rc::Rc,
sync::Arc,
};
use rawzip::{
FileReader, ReaderAt, ZipArchive, ZipArchiveEntryWayfinder, ZipEntry, RECOMMENDED_BUFFER_SIZE,
};
use zip::{read::ZipFile, ZipArchive};
use crate::version::{
types::{MinimumFileObject, Skippable, VersionBackend, VersionFile},
@@ -57,51 +64,96 @@ impl VersionBackend for PathVersionBackend {
}
pub struct ZipVersionBackend {
archive: ZipArchive<File>,
archive: Arc<ZipArchive<FileReader>>,
}
impl ZipVersionBackend {
pub fn new(archive: PathBuf) -> Self {
let handle = File::open(archive).unwrap();
pub fn new(archive: File) -> Self {
let archive = ZipArchive::from_file(archive, &mut [0u8; RECOMMENDED_BUFFER_SIZE]).unwrap();
Self {
archive: ZipArchive::new(handle).unwrap(),
archive: Arc::new(archive),
}
}
pub fn new_entry(&self, entry: ZipEntry<'_, FileReader>) -> ZipFileWrapper {
ZipFileWrapper {
archive: self.archive.clone(),
wayfinder: entry.entry,
offset: entry.body_offset,
end_offset: entry.body_end_offset,
}
}
}
struct ZipFileWrapper<'a> {
inner: ZipFile<'a, File>,
impl Drop for ZipVersionBackend {
fn drop(&mut self) {
println!("dropping archive");
}
}
impl Read for ZipFileWrapper<'_> {
struct ZipFileWrapper {
pub archive: Arc<ZipArchive<FileReader>>,
wayfinder: ZipArchiveEntryWayfinder,
offset: u64,
end_offset: u64,
}
impl Read for ZipFileWrapper {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inner.read(buf)
let read_size = buf.len().min((self.end_offset - self.offset) as usize);
let read = self
.archive
.reader
.read_at(&mut buf[..read_size], self.offset)?;
self.offset += read as u64;
Ok(read)
}
}
impl Skippable for ZipFileWrapper<'_> {
impl Skippable for ZipFileWrapper {
fn skip(&mut self, amount: u64) {
io::copy(&mut self.inner.by_ref().take(amount), &mut io::sink()).unwrap();
/*io::copy(
&mut self.inner.reader().by_ref().take(amount),
&mut io::sink(),
)
.unwrap();
*/
}
}
impl MinimumFileObject for ZipFileWrapper<'_> {}
impl MinimumFileObject for ZipFileWrapper {}
impl VersionBackend for ZipVersionBackend {
fn list_files(&mut self) -> Vec<VersionFile> {
let mut results = Vec::new();
for i in 0..self.archive.len() {
let entry = self.archive.by_index(i).unwrap();
let read_buffer = &mut [0u8; RECOMMENDED_BUFFER_SIZE];
let mut budget_iterator = self.archive.entries(read_buffer);
while let Some(entry) = budget_iterator.next_entry().unwrap() {
if entry.is_dir() {
continue;
}
results.push(VersionFile {
relative_filename: entry.name().to_owned(),
permission: entry.unix_mode().or(Some(0)).unwrap(),
relative_filename: entry.file_safe_path().unwrap().to_string(),
permission: 744, // apparently ZIPs with permissions are not supported by this library, so we let the owner do anything
});
}
results
}
fn reader(&mut self, file: &VersionFile) -> Option<Box<(dyn MinimumFileObject)>> {
let file = self.archive.by_name(&file.relative_filename).ok()?;
let zip_file_wrapper = ZipFileWrapper { inner: file };
let read_buffer = &mut [0u8; RECOMMENDED_BUFFER_SIZE];
let mut entries = self.archive.entries(read_buffer);
let entry = loop {
if let Some(v) = entries.next_entry().unwrap() {
if v.file_safe_path().unwrap().to_string() == file.relative_filename {
break Some(v);
}
} else {
break None;
}
}?;
//Some(Box::new(zip_file_wrapper))
None
let wayfinder = entry.wayfinder();
let local_entry = self.archive.get_entry(wayfinder).unwrap();
let wrapper = self.new_entry(local_entry);
Some(Box::new(wrapper))
}
}

View File

@@ -1,4 +1,6 @@
use std::io::{Read, Seek, SeekFrom};
use std::{
fmt::Debug, io::{Read, Seek, SeekFrom}
};
use tokio::io::{self, AsyncRead};
@@ -26,6 +28,7 @@ impl<T: Read + Send + Seek> MinimumFileObject for T {}
// Intentionally not a generic, because of types in read_file
pub struct ReadToAsyncRead {
pub inner: Box<(dyn Read + Send)>,
pub backend: Box<(dyn VersionBackend + Send)>,
}
impl AsyncRead for ReadToAsyncRead {
@@ -35,7 +38,8 @@ impl AsyncRead for ReadToAsyncRead {
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<io::Result<()>> {
let mut read_buf = [0u8; 8192];
let amount = self.inner.read(&mut read_buf).unwrap();
let var_name = self.inner.read(&mut read_buf).unwrap();
let amount = var_name;
buf.put_slice(&read_buf[0..amount]);
std::task::Poll::Ready(Ok(()))
}

View File

@@ -6,11 +6,10 @@ use std::{
use napi::{bindgen_prelude::*, tokio_stream::StreamExt};
use tokio_util::codec::{BytesCodec, FramedRead};
use zip::ZipArchive;
use crate::version::{
backends::{PathVersionBackend, ZipVersionBackend},
types::{MinimumFileObject, ReadToAsyncRead, VersionBackend, VersionFile},
types::{ReadToAsyncRead, VersionBackend, VersionFile},
};
pub fn _list_files(vec: &mut Vec<PathBuf>, path: &Path) {
@@ -27,7 +26,7 @@ pub fn _list_files(vec: &mut Vec<PathBuf>, path: &Path) {
}
}
pub fn create_backend_for_path(path: &Path) -> Option<Box<(dyn VersionBackend + Send)>> {
pub fn create_backend_for_path<'a>(path: &Path) -> Option<Box<(dyn VersionBackend + Send + 'a)>> {
let is_directory = path.is_dir();
if is_directory {
return Some(Box::new(PathVersionBackend {
@@ -35,12 +34,9 @@ pub fn create_backend_for_path(path: &Path) -> Option<Box<(dyn VersionBackend +
}));
};
/*
Insert checks for whatever backend you like
*/
if path.ends_with(".zip") {
return Some(Box::new(ZipVersionBackend::new(path.to_path_buf())));
if path.to_string_lossy().ends_with(".zip") {
let f = File::open(path.to_path_buf()).unwrap();
return Some(Box::new(ZipVersionBackend::new(f)));
}
None
@@ -56,11 +52,12 @@ pub fn has_backend_for_path(path: String) -> bool {
}
#[napi]
pub fn list_files(path: String) -> Vec<String> {
pub fn list_files(path: String) -> Result<Vec<String>> {
let path = Path::new(&path);
let mut backend = create_backend_for_path(path).unwrap();
let mut backend =
create_backend_for_path(path).ok_or(napi::Error::from_reason("No backend for path"))?;
let files = backend.list_files();
files.into_iter().map(|e| e.relative_filename).collect()
Ok(files.into_iter().map(|e| e.relative_filename).collect())
}
#[napi]
@@ -70,7 +67,7 @@ pub fn read_file(
env: &Env,
start: Option<u32>,
end: Option<u32>,
) -> Option<ReadableStream<'static, BufferSlice<'static>>> {
) -> Option<ReadableStream<'_, BufferSlice<'_>>> {
let path = Path::new(&path);
let mut backend = create_backend_for_path(path).unwrap();
let version_file = VersionFile {
@@ -90,9 +87,10 @@ pub fn read_file(
let amount = limit - start.or(Some(0)).unwrap();
ReadToAsyncRead {
inner: Box::new(reader.take(amount.into())),
backend
}
} else {
ReadToAsyncRead { inner: reader }
ReadToAsyncRead { inner: reader, backend }
};
// Create a FramedRead stream with BytesCodec for chunking