use std::{
collections::HashMap, fs, io::{Error, ErrorKind, Read, Seek, SeekFrom}, path::{Path, PathBuf}
};
use crate::byte;
pub struct Gob {
pub files: GobMap,
}
impl Gob {
fn get_files_from_directory(
files: &mut GobMap,
directory: &mut fs::ReadDir,
root: Option<&Path>,
) -> std::io::Result<()> {
for item in directory {
let item = item?;
let path = item.path();
let root = match root {
Some(root) => root,
None => match path.parent() {
Some(root) => root,
None => {
return Err(Error::new(ErrorKind::Other, "Unable to get parent directory from path."));
}
}
};
if path.is_file() {
let mut file = fs::File::open(&path)?;
let mut data: Vec<u8> = Vec::new();
file.read_to_end(&mut data)?;
let filepath: PathBuf = path
.strip_prefix(root)
.expect("Should be able to get relative path")
.into();
files.insert(filepath, data);
} else if path.is_dir() {
let mut directory = path.read_dir()?;
Self::get_files_from_directory(files, &mut directory, Some(root))?;
} else {
return Err(Error::new(ErrorKind::InvalidInput, "Path is neither file nor directory."));
}
}
Ok(())
}
pub fn from_directory(path: &Path) -> std::io::Result<Self> {
if !path.is_dir() {
return Err(Error::new(ErrorKind::InvalidInput, "Path is not a directory."));
}
let mut directory = fs::read_dir(path)?;
let mut files = GobMap::new();
Self::get_files_from_directory(&mut files, &mut directory, None)?;
Ok(Self { files })
}
const SIGNATURE: &'static [u8; 4] = b"GOB ";
const VERSION: u32 = 0x14;
pub fn from_file(path: &Path) -> std::io::Result<Self> {
if !path.is_file() {
return Err(Error::new(ErrorKind::InvalidInput, "Path is not a file."));
}
let mut file = fs::File::open(path)?;
file.seek(SeekFrom::Start(0))?;
let signature = &byte::slice!(file, 4);
if signature != Self::SIGNATURE {
return Err(Error::new(ErrorKind::InvalidData, "Bad signature in header of GOB file."));
}
let version = u32::from_le_bytes(byte::slice!(file, 4));
if version != Self::VERSION {
return Err(Error::new(ErrorKind::InvalidData, "Bad version in header of GOB file."));
}
let body_offset = u32::from_le_bytes(byte::slice!(file, 4)) as u64;
file.seek(SeekFrom::Start(body_offset))?;
let file_count = u32::from_le_bytes(byte::slice!(file, 4));
let mut file_definitions: Vec<FileDefinition> = Vec::new();
for _ in 0..file_count {
let offset = u32::from_le_bytes(byte::slice!(file, 4)) as usize;
let size = u32::from_le_bytes(byte::slice!(file, 4)) as usize;
let filepath_bytes = byte::slice!(file, 128);
let filepath_end = filepath_bytes.iter().position(|&n| n == 0).unwrap_or(128);
let filepath = match byte::string_from_bytes(&filepath_bytes[..filepath_end]) {
Ok(filepath) => filepath,
Err(_) => {
return Err(Error::new(ErrorKind::InvalidData, format!("Cannot convert following bytes to string: {filepath_bytes:?}")));
}
};
let filepath = PathBuf::from(filepath);
file_definitions.push(FileDefinition {
offset,
size,
filepath,
});
}
let mut files = GobMap::new();
for file_definition in file_definitions {
file.seek(SeekFrom::Start(file_definition.offset as u64))?;
let mut data: Vec<u8> = vec![0; file_definition.size];
file.read_exact(&mut data)?;
files.insert(file_definition.filepath, data);
}
Ok(Self { files })
}
pub fn as_bytes(self) -> Result<Vec<u8>, String> {
let mut bytes: Vec<u8> = Vec::new();
bytes.extend(Self::SIGNATURE);
bytes.extend(&Self::VERSION.to_le_bytes());
let body_offset: u32 = 12;
bytes.extend(&body_offset.to_le_bytes());
let file_count = self.files.len() as u32;
bytes.extend(&file_count.to_le_bytes());
let mut file_data_offset: u32 = 16 + 136 * file_count;
for (filepath, file_data) in &self.files {
bytes.extend(&file_data_offset.to_le_bytes());
let size = file_data.len() as u32;
file_data_offset += size;
bytes.extend(&size.to_le_bytes());
let filepath_bytes = filepath.as_os_str().as_encoded_bytes();
if filepath_bytes.len() > 128 {
return Err(format!("Filepath is longer than 128 bytes: {}", filepath.display()))
}
bytes.extend(filepath_bytes);
bytes.extend(vec![0; 128 - filepath_bytes.len()]);
}
for (_, file_data) in &self.files {
bytes.extend(file_data);
}
Ok(bytes)
}
pub fn new() -> Self {
let files = GobMap::new();
Self {
files,
}
}
}
impl From<GobMap> for Gob {
fn from(files: GobMap) -> Self {
Self {
files
}
}
}
struct FileDefinition {
offset: usize,
size: usize,
filepath: PathBuf,
}
pub type GobMap = HashMap<PathBuf, Vec<u8>>;