1use crate::exc::Result;
4use std::collections::HashMap;
5use std::io::Read;
6use std::path::Path;
7
8pub struct Package {
10 parts: HashMap<String, Vec<u8>>,
12}
13
14impl Package {
15 pub fn new() -> Self {
17 Package {
18 parts: HashMap::new(),
19 }
20 }
21
22 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
24 let path = path.as_ref();
25 let file = std::fs::File::open(path)?;
26 Self::open_reader(file)
27 }
28
29 pub fn open_reader<R: Read + std::io::Seek>(reader: R) -> Result<Self> {
31 let mut archive = zip::ZipArchive::new(reader)?;
32
33 let mut parts = HashMap::new();
34
35 for i in 0..archive.len() {
36 let mut file = archive.by_index(i)?;
37
38 if !file.is_dir() {
39 let mut content = Vec::new();
40 file.read_to_end(&mut content)?;
41 parts.insert(file.name().to_string(), content);
42 }
43 }
44
45 Ok(Package { parts })
46 }
47
48 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
50 let path = path.as_ref();
51 let file = std::fs::File::create(path)?;
52 self.save_writer(file)
53 }
54
55 pub fn save_writer<W: std::io::Write + std::io::Seek>(&self, writer: W) -> Result<()> {
57 let mut archive = zip::ZipWriter::new(writer);
58
59 for (path, content) in &self.parts {
60 let options = zip::write::FileOptions::default();
61 archive.start_file(path, options)?;
62 std::io::Write::write_all(&mut archive, content)?;
63 }
64
65 archive.finish()?;
66
67 Ok(())
68 }
69
70 pub fn get_part(&self, path: &str) -> Option<&[u8]> {
72 self.parts.get(path).map(|v| v.as_slice())
73 }
74
75 pub fn add_part(&mut self, path: String, content: Vec<u8>) {
77 self.parts.insert(path, content);
78 }
79
80 pub fn remove_part(&mut self, path: &str) -> Option<Vec<u8>> {
82 self.parts.remove(path)
83 }
84
85 pub fn has_part(&self, path: &str) -> bool {
87 self.parts.contains_key(path)
88 }
89
90 pub fn part_paths(&self) -> Vec<&str> {
92 self.parts.keys().map(|s| s.as_str()).collect()
93 }
94
95 pub fn part_count(&self) -> usize {
97 self.parts.len()
98 }
99
100 pub fn get_part_mut(&mut self, path: &str) -> Option<&mut Vec<u8>> {
102 self.parts.get_mut(path)
103 }
104
105 pub fn get_part_string(&self, path: &str) -> Option<String> {
107 self.parts
108 .get(path)
109 .map(|v| String::from_utf8_lossy(v).to_string())
110 }
111}
112
113impl Default for Package {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_package_creation() {
125 let package = Package::new();
126 assert_eq!(package.part_count(), 0);
127 }
128
129 #[test]
130 fn test_add_part() {
131 let mut package = Package::new();
132 package.add_part("test.txt".to_string(), b"content".to_vec());
133 assert_eq!(package.part_count(), 1);
134 assert_eq!(package.get_part("test.txt"), Some(b"content".as_slice()));
135 }
136
137 #[test]
138 fn test_part_paths() {
139 let mut package = Package::new();
140 package.add_part("file1.txt".to_string(), b"content1".to_vec());
141 package.add_part("file2.txt".to_string(), b"content2".to_vec());
142 let paths = package.part_paths();
143 assert_eq!(paths.len(), 2);
144 }
145}