use crate::exc::Result;
use std::collections::HashMap;
use std::io::Read;
use std::path::Path;
pub struct Package {
parts: HashMap<String, Vec<u8>>,
}
impl Package {
pub fn new() -> Self {
Package {
parts: HashMap::new(),
}
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let file = std::fs::File::open(path)?;
Self::open_reader(file)
}
pub fn open_reader<R: Read + std::io::Seek>(reader: R) -> Result<Self> {
let mut archive = zip::ZipArchive::new(reader)?;
let mut parts = HashMap::new();
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
if !file.is_dir() {
let mut content = Vec::new();
file.read_to_end(&mut content)?;
parts.insert(file.name().to_string(), content);
}
}
Ok(Package { parts })
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let path = path.as_ref();
let file = std::fs::File::create(path)?;
self.save_writer(file)
}
pub fn save_writer<W: std::io::Write + std::io::Seek>(&self, writer: W) -> Result<()> {
let mut archive = zip::ZipWriter::new(writer);
for (path, content) in &self.parts {
let options = zip::write::FileOptions::default();
archive.start_file(path, options)?;
std::io::Write::write_all(&mut archive, content)?;
}
archive.finish()?;
Ok(())
}
pub fn get_part(&self, path: &str) -> Option<&[u8]> {
self.parts.get(path).map(|v| v.as_slice())
}
pub fn add_part(&mut self, path: String, content: Vec<u8>) {
self.parts.insert(path, content);
}
pub fn remove_part(&mut self, path: &str) -> Option<Vec<u8>> {
self.parts.remove(path)
}
pub fn has_part(&self, path: &str) -> bool {
self.parts.contains_key(path)
}
pub fn part_paths(&self) -> Vec<&str> {
self.parts.keys().map(|s| s.as_str()).collect()
}
pub fn part_count(&self) -> usize {
self.parts.len()
}
pub fn get_part_mut(&mut self, path: &str) -> Option<&mut Vec<u8>> {
self.parts.get_mut(path)
}
pub fn get_part_string(&self, path: &str) -> Option<String> {
self.parts
.get(path)
.map(|v| String::from_utf8_lossy(v).to_string())
}
}
impl Default for Package {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_package_creation() {
let package = Package::new();
assert_eq!(package.part_count(), 0);
}
#[test]
fn test_add_part() {
let mut package = Package::new();
package.add_part("test.txt".to_string(), b"content".to_vec());
assert_eq!(package.part_count(), 1);
assert_eq!(package.get_part("test.txt"), Some(b"content".as_slice()));
}
#[test]
fn test_part_paths() {
let mut package = Package::new();
package.add_part("file1.txt".to_string(), b"content1".to_vec());
package.add_part("file2.txt".to_string(), b"content2".to_vec());
let paths = package.part_paths();
assert_eq!(paths.len(), 2);
}
}