1use std::io::{self, Write, Seek};
2use zip::{ZipWriter, write::SimpleFileOptions, CompressionMethod};
3
4pub struct ZipDocumentWriter<W: Write + Seek> {
6 pub(crate) zip: ZipWriter<W>,
7 pub(crate) manifest: serde_json::Value,
8}
9
10impl<W: Write + Seek> ZipDocumentWriter<W> {
11 pub fn new(w: W) -> io::Result<Self> {
13 let zip = ZipWriter::new(w);
14 let manifest = serde_json::json!({ "version": 1, "entries": [] });
15 Ok(Self { zip, manifest })
16 }
17 pub fn manifest(&self) -> &serde_json::Value {
19 &self.manifest
20 }
21 pub fn manifest_mut(&mut self) -> &mut serde_json::Value {
23 &mut self.manifest
24 }
25 pub fn set_manifest(
27 &mut self,
28 manifest: serde_json::Value,
29 ) -> &mut Self {
30 self.manifest = manifest;
31 self
32 }
33 pub fn add_json(
35 &mut self,
36 name: &str,
37 value: &serde_json::Value,
38 ) -> io::Result<()> {
39 let opts = SimpleFileOptions::default()
40 .compression_method(CompressionMethod::Deflated);
41 self.zip.start_file(name, opts)?;
42 let data = serde_json::to_vec(value).map_err(io::Error::other)?;
43 if let Some(entries) =
45 self.manifest.get_mut("entries").and_then(|v| v.as_array_mut())
46 {
47 entries.push(serde_json::json!({
48 "name": name,
49 "kind": "json",
50 "logical_len": data.len(),
51 "compression": "deflate"
52 }));
53 }
54 self.zip.write_all(&data)
55 }
56 pub fn add_stored(
58 &mut self,
59 name: &str,
60 bytes: &[u8],
61 ) -> io::Result<()> {
62 let opts = SimpleFileOptions::default()
63 .compression_method(CompressionMethod::Stored);
64 self.zip.start_file(name, opts)?;
65 if let Some(entries) =
66 self.manifest.get_mut("entries").and_then(|v| v.as_array_mut())
67 {
68 entries.push(serde_json::json!({
69 "name": name,
70 "kind": "binary",
71 "logical_len": bytes.len(),
72 "compression": "stored"
73 }));
74 }
75 self.zip.write_all(bytes)
76 }
77
78 pub fn add_plugin_state(
80 &mut self,
81 plugin_name: &str,
82 state_data: &[u8],
83 ) -> io::Result<()> {
84 let plugin_file_path = format!("plugins/{plugin_name}");
85 let opts = SimpleFileOptions::default()
86 .compression_method(CompressionMethod::Deflated);
87 self.zip.start_file(&plugin_file_path, opts)?;
88
89 if let Some(entries) =
90 self.manifest.get_mut("entries").and_then(|v| v.as_array_mut())
91 {
92 entries.push(serde_json::json!({
93 "name": plugin_file_path,
94 "kind": "plugin_state",
95 "plugin": plugin_name,
96 "logical_len": state_data.len(),
97 "compression": "deflate"
98 }));
99 }
100 self.zip.write_all(state_data)
101 }
102
103 pub fn add_plugin_states<I>(
105 &mut self,
106 plugin_states: I,
107 ) -> io::Result<()>
108 where
109 I: IntoIterator<Item = (String, Vec<u8>)>,
110 {
111 for (plugin_name, state_data) in plugin_states {
112 self.add_plugin_state(&plugin_name, &state_data)?;
113 }
114 Ok(())
115 }
116 pub fn add_deflated(
118 &mut self,
119 name: &str,
120 bytes: &[u8],
121 ) -> io::Result<()> {
122 let opts = SimpleFileOptions::default()
123 .compression_method(CompressionMethod::Deflated);
124 self.zip.start_file(name, opts)?;
125 if let Some(entries) =
126 self.manifest.get_mut("entries").and_then(|v| v.as_array_mut())
127 {
128 entries.push(serde_json::json!({
129 "name": name,
130 "kind": "binary",
131 "logical_len": bytes.len(),
132 "compression": "deflate"
133 }));
134 }
135 self.zip.write_all(bytes)
136 }
137 pub fn finalize(mut self) -> io::Result<W> {
139 let opts = SimpleFileOptions::default()
140 .compression_method(CompressionMethod::Deflated);
141 self.zip.start_file("manifest.json", opts)?;
142 let data =
143 serde_json::to_vec(&self.manifest).map_err(io::Error::other)?;
144 self.zip.write_all(&data)?;
145 self.zip.finish().map_err(io::Error::other)
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use std::io::Cursor;
153
154 #[test]
155 fn test_plugin_state_export() {
156 let buffer = Vec::new();
157 let cursor = Cursor::new(buffer);
158 let mut writer = ZipDocumentWriter::new(cursor).unwrap();
159
160 writer.add_plugin_state("test_plugin", b"test state data").unwrap();
162 writer
163 .add_plugin_state("another_plugin", b"another state data")
164 .unwrap();
165
166 let result = writer.finalize().unwrap();
167 let final_data = result.into_inner();
168
169 assert!(!final_data.is_empty());
171
172 let cursor = Cursor::new(&final_data);
174 let mut reader = crate::zipdoc::ZipDocumentReader::new(cursor).unwrap();
175
176 let plugins = reader.list_plugins().unwrap();
177 assert_eq!(plugins.len(), 2);
178 assert!(plugins.contains(&"test_plugin".to_string()));
179 assert!(plugins.contains(&"another_plugin".to_string()));
180
181 let test_state =
183 reader.read_plugin_state("test_plugin").unwrap().unwrap();
184 assert_eq!(test_state, b"test state data");
185 }
186
187 #[test]
188 fn test_batch_plugin_states() {
189 let buffer = Vec::new();
190 let cursor = Cursor::new(buffer);
191 let mut writer = ZipDocumentWriter::new(cursor).unwrap();
192
193 let plugin_states = vec![
194 ("plugin1".to_string(), b"state1".to_vec()),
195 ("plugin2".to_string(), b"state2".to_vec()),
196 ("plugin3".to_string(), b"state3".to_vec()),
197 ];
198
199 writer.add_plugin_states(plugin_states.clone()).unwrap();
200 let result = writer.finalize().unwrap();
201 let final_data = result.into_inner();
202
203 let cursor = Cursor::new(&final_data);
204 let mut reader = crate::zipdoc::ZipDocumentReader::new(cursor).unwrap();
205
206 let all_states = reader.read_all_plugin_states().unwrap();
207 assert_eq!(all_states.len(), 3);
208
209 for (name, expected_data) in plugin_states {
210 let actual_data = all_states.get(&name).unwrap();
211 assert_eq!(actual_data, &expected_data);
212 }
213 }
214}