Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
rfuzzo committed Jan 15, 2024
1 parent 736e3db commit 67b654e
Show file tree
Hide file tree
Showing 3 changed files with 408 additions and 350 deletions.
58 changes: 58 additions & 0 deletions src/archive/archiveinmemory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/////////////////////////////////////////////////////////////////////////////////////////
// IN-MEMORY ARCHIVE
//
//https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchivemode?view=net-8.0
//
// When you set the mode to Update, the underlying file or stream must support reading, writing, and seeking.
// The content of the entire archive is held in memory,
// and no data is written to the underlying file or stream until the archive is disposed.
//
// We don't implement a wrapped stream here. Archive needs to be written manually for now.
//
/////////////////////////////////////////////////////////////////////////////////////////

use std::{collections::HashMap, io::Result, path::Path};

use super::*;

#[derive(Debug)]
pub struct ZipArchiveMemory {
/// The files inside an archive
pub entries: HashMap<u64, ZipEntry>,
pub dependencies: Vec<Dependency>,
}

/////////////////////////////////////////////////////////////////////////////////////////
// IMPL

impl ZipArchiveMemory {
fn write(&mut self) {
todo!()
}

/// Compresses and adds a file to the archive.
///
/// # Errors
///
/// This function will return an error if compression or io fails, or if the mode is Read.
pub fn create_entry<P: AsRef<Path>>(
&mut self,
_file_path: P,
_compression_level: CompressionLevel,
) -> Result<ZipEntry> {
// can only add entries in update mode

// write?

todo!()
}

/// Deletes an entry from the archive
pub fn delete_entry(&mut self, hash: &u64) -> Option<ZipEntry> {
// can only delete entries in update mode

// Set dirty

self.entries.remove(hash)
}
}
310 changes: 310 additions & 0 deletions src/archive/archivereadonly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
/////////////////////////////////////////////////////////////////////////////////////////
// READ ONLY ARCHIVE
//
//https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchivemode?view=net-8.0
//
// When you set the mode to Read, the underlying file or stream must support reading, but does not have to support seeking.
// If the underlying file or stream supports seeking, the files are read from the archive as they are requested.
// If the underlying file or stream does not support seeking, the entire archive is held in memory.
//
// We only implement Read + Seek and never hold anything in memory here.
//
/////////////////////////////////////////////////////////////////////////////////////////

use std::{
borrow::BorrowMut,
collections::HashMap,
fs::{create_dir_all, File},
io::{self, BufWriter, Read, Result, Seek, SeekFrom, Write},
path::Path,
};

use byteorder::{LittleEndian, ReadBytesExt};

use crate::*;
use crate::{fnv1a64_hash_string, io::FromReader};

use super::*;

#[derive(Debug)]
pub struct ZipArchiveReadonly<R>
where
R: Read + Seek,
{
/// wraps a read-only stream
stream: R,
/// The files inside an archive
pub entries: HashMap<u64, ZipEntry>,
pub dependencies: Vec<Dependency>,
}

/////////////////////////////////////////////////////////////////////////////////////////
// IMPL

impl<R> ZipArchiveReadonly<R>
where
R: Read + Seek,
{
/// Get an entry in the archive by resource path.
pub fn get_entry(&self, name: &str) -> Option<&ZipEntry> {
self.entries.get(&fnv1a64_hash_string(&name.to_owned()))
}

/// Get an entry in the archive by hash (FNV1a64 of resource path).
pub fn get_entry_by_hash(&self, hash: &u64) -> Option<&ZipEntry> {
self.entries.get(hash)
}

/// Extracts a single entry to a directory path.
///
/// # Errors
///
/// This function will return an error if the entry cannot be found or any io fails.
pub fn extract_entry<P: AsRef<Path>>(
&mut self,
entry: ZipEntry,
destination_directory_name: &P,
overwrite_files: bool,
hash_map: &HashMap<u64, String>,
) -> Result<()> {
let Some(info) = entry.get_resolved_name(&hash_map) else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Could not get entry info from archive.",
));
};

// name or hash is a relative path
let outfile = destination_directory_name.as_ref().join(info);
create_dir_all(outfile.parent().expect("Could not create an out_dir"))?;

// extract to stream
let mut fs = if overwrite_files {
File::create(outfile)?
} else {
File::options()
.read(true)
.write(true)
.create_new(true)
.open(outfile)?
};

let writer = BufWriter::new(&mut fs);
self.extract_segments(&entry, writer)?;

Ok(())
}

/// Extracts a single entry by hash to a directory path.
///
/// # Errors
///
/// This function will return an error if the entry cannot be found or any io fails.
pub fn extract_entry_by_hash<P: AsRef<Path>>(
&mut self,
hash: u64,
destination_directory_name: &P,
overwrite_files: bool,
hash_map: &HashMap<u64, String>,
) -> Result<()> {
if let Some(entry) = self.get_entry_by_hash(&hash) {
self.extract_entry(
entry.clone(),
destination_directory_name,
overwrite_files,
hash_map,
)
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Could not find entry.",
));
}
}

/// Extracts a single entry by resource path to a directory path.
///
/// # Errors
///
/// This function will return an error if the entry cannot be found or any io fails.
pub fn extract_entry_by_name<P: AsRef<Path>>(
&mut self,
name: String,
destination_directory_name: &P,
overwrite_files: bool,
hash_map: &HashMap<u64, String>,
) -> Result<()> {
if let Some(entry) = self.get_entry(&name) {
self.extract_entry(
entry.clone(),
destination_directory_name,
overwrite_files,
hash_map,
)
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Could not find entry.",
));
}
}

/// Returns an open read stream to an entry of this [`ZipArchive<R>`].
pub fn open_entry<W: Write>(&mut self, entry: ZipEntry, writer: W) -> Result<()> {
self.extract_segments(&entry, writer)?;

Ok(())
}

/// Extracts all entries to the given directory.
///
/// # Errors
///
/// This function will return an error if io fails.
pub fn extract_to_directory<P: AsRef<Path>>(
&mut self,
destination_directory_name: &P,
overwrite_files: bool,
hash_map: Option<HashMap<u64, String>>,
) -> Result<()> {
let hash_map = if let Some(hash_map) = hash_map {
hash_map
} else {
get_red4_hashes()
};

// collect info
let mut entries: Vec<ZipEntry> = vec![];
for (_hash, entry) in &self.entries {
entries.push(entry.clone());
}

for entry in entries {
self.extract_entry(
entry,
destination_directory_name,
overwrite_files,
&hash_map,
)?;
}

Ok(())
}

// getters

fn reader_mut(&mut self) -> &mut R {
self.stream.borrow_mut()
}

// methods

/// Extracts segments to a writer, expects correct offset info.
///
/// # Errors
///
/// This function will return an error if io fails
fn extract_segments<W: Write>(&mut self, entry: &ZipEntry, mut writer: W) -> Result<()> {
let segment = entry.segment;
let buffers = entry.buffers.clone();

if segment.size() == segment.z_size() {
// just copy
self.reader_mut().seek(SeekFrom::Start(segment.offset()))?;
let mut buffer = vec![0; segment.z_size() as usize];
self.reader_mut().read_exact(&mut buffer[..])?;
writer.write_all(&buffer)?;
} else {
decompress_segment(self.reader_mut(), &segment, &mut writer)?;
}
for segment in buffers {
self.reader_mut().seek(SeekFrom::Start(segment.offset()))?;
let mut buffer = vec![0; segment.z_size() as usize];
self.reader_mut().read_exact(&mut buffer[..])?;
writer.write_all(&buffer)?;
}

Ok(())
}

/// Opens an archive, needs to be read-only
pub(crate) fn from_reader_consume(mut reader: R) -> Result<ZipArchiveReadonly<R>> {
// read header
let header = Header::from_reader(&mut reader)?;

// read custom data
let mut file_names: HashMap<u64, String> = HashMap::default();
if let Ok(custom_data_length) = reader.read_u32::<LittleEndian>() {
if custom_data_length > 0 {
reader.seek(io::SeekFrom::Start(Header::HEADER_EXTENDED_SIZE))?;
if let Ok(footer) = LxrsFooter::from_reader(&mut reader) {
// add files to hashmap
for f in footer.files() {
let hash = fnv1a64_hash_string(f);
file_names.insert(hash, f.to_owned());
}
}
}
}

// read index
// move to offset Header.IndexPosition
reader.seek(io::SeekFrom::Start(header.index_position()))?;
let index = Index::from_reader(&mut reader)?;

// read tables
let mut file_entries: HashMap<u64, FileEntry> = HashMap::default();
for _i in 0..index.file_entry_count() {
let entry = FileEntry::from_reader(&mut reader)?;
file_entries.insert(entry.name_hash_64(), entry);
}

let mut file_segments = Vec::default();
for _i in 0..index.file_segment_count() {
file_segments.push(FileSegment::from_reader(&mut reader)?);
}

// dependencies can't be connected to individual files anymore
let mut dependencies = Vec::default();
for _i in 0..index.resource_dependency_count() {
dependencies.push(Dependency::from_reader(&mut reader)?);
}

// construct wrapper
let mut entries = HashMap::default();
for (hash, entry) in file_entries.iter() {
let resolved = if let Some(name) = file_names.get(hash) {
Some(name.to_owned())
} else {
None
};

let start_index = entry.segments_start();
let next_index = entry.segments_end();
if let Some(segment) = file_segments.get(start_index as usize) {
let mut buffers: Vec<FileSegment> = vec![];
for i in start_index + 1..next_index {
if let Some(buffer) = file_segments.get(i as usize) {
buffers.push(*buffer);
}
}

let zip_entry = ZipEntry {
hash: *hash,
name: resolved,
entry: *entry,
segment: *segment,
buffers,
};
entries.insert(*hash, zip_entry);
}
}

let archive = ZipArchiveReadonly::<R> {
stream: reader,
entries,
dependencies,
};
Ok(archive)
}
}
Loading

0 comments on commit 67b654e

Please sign in to comment.