mf_file/zipdoc/
writer.rs

1use std::io::{self, Write, Seek};
2use zip::{ZipWriter, write::SimpleFileOptions, CompressionMethod};
3
4// 基于 ZIP 的文档写入器(docx 风格容器)
5pub 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    // 创建写入器
12    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    // 读取当前 manifest 的不可变引用
18    pub fn manifest(&self) -> &serde_json::Value {
19        &self.manifest
20    }
21    // 读取当前 manifest 的可变引用(可自由添加自定义字段)
22    pub fn manifest_mut(&mut self) -> &mut serde_json::Value {
23        &mut self.manifest
24    }
25    // 替换 manifest(链式调用)
26    pub fn set_manifest(
27        &mut self,
28        manifest: serde_json::Value,
29    ) -> &mut Self {
30        self.manifest = manifest;
31        self
32    }
33    // 写入 JSON 文件(deflate 压缩)
34    #[cfg_attr(feature = "dev-tracing", tracing::instrument(skip(self, value), fields(
35        crate_name = "file",
36        file_name = %name
37    )))]
38    pub fn add_json(
39        &mut self,
40        name: &str,
41        value: &serde_json::Value,
42    ) -> io::Result<()> {
43        let opts = SimpleFileOptions::default()
44            .compression_method(CompressionMethod::Deflated);
45        self.zip.start_file(name, opts)?;
46        let data = serde_json::to_vec(value).map_err(io::Error::other)?;
47        // 记录到 manifest.entries(若存在且为数组)
48        if let Some(entries) =
49            self.manifest.get_mut("entries").and_then(|v| v.as_array_mut())
50        {
51            entries.push(serde_json::json!({
52                "name": name,
53                "kind": "json",
54                "logical_len": data.len(),
55                "compression": "deflate"
56            }));
57        }
58        self.zip.write_all(&data)
59    }
60    // 写入原样存储的条目(不压缩)
61    pub fn add_stored(
62        &mut self,
63        name: &str,
64        bytes: &[u8],
65    ) -> io::Result<()> {
66        let opts = SimpleFileOptions::default()
67            .compression_method(CompressionMethod::Stored);
68        self.zip.start_file(name, opts)?;
69        if let Some(entries) =
70            self.manifest.get_mut("entries").and_then(|v| v.as_array_mut())
71        {
72            entries.push(serde_json::json!({
73                "name": name,
74                "kind": "binary",
75                "logical_len": bytes.len(),
76                "compression": "stored"
77            }));
78        }
79        self.zip.write_all(bytes)
80    }
81
82    // 添加插件状态目录和文件(二进制存储)
83    pub fn add_plugin_state(
84        &mut self,
85        plugin_name: &str,
86        state_data: &[u8],
87    ) -> io::Result<()> {
88        let plugin_file_path = format!("plugins/{plugin_name}");
89        let opts = SimpleFileOptions::default()
90            .compression_method(CompressionMethod::Deflated);
91        self.zip.start_file(&plugin_file_path, opts)?;
92
93        if let Some(entries) =
94            self.manifest.get_mut("entries").and_then(|v| v.as_array_mut())
95        {
96            entries.push(serde_json::json!({
97                "name": plugin_file_path,
98                "kind": "plugin_state",
99                "plugin": plugin_name,
100                "logical_len": state_data.len(),
101                "compression": "deflate"
102            }));
103        }
104        self.zip.write_all(state_data)
105    }
106
107    // 批量添加插件状态
108    pub fn add_plugin_states<I>(
109        &mut self,
110        plugin_states: I,
111    ) -> io::Result<()>
112    where
113        I: IntoIterator<Item = (String, Vec<u8>)>,
114    {
115        for (plugin_name, state_data) in plugin_states {
116            self.add_plugin_state(&plugin_name, &state_data)?;
117        }
118        Ok(())
119    }
120    // 写入 deflate 压缩条目
121    pub fn add_deflated(
122        &mut self,
123        name: &str,
124        bytes: &[u8],
125    ) -> io::Result<()> {
126        let opts = SimpleFileOptions::default()
127            .compression_method(CompressionMethod::Deflated);
128        self.zip.start_file(name, opts)?;
129        if let Some(entries) =
130            self.manifest.get_mut("entries").and_then(|v| v.as_array_mut())
131        {
132            entries.push(serde_json::json!({
133                "name": name,
134                "kind": "binary",
135                "logical_len": bytes.len(),
136                "compression": "deflate"
137            }));
138        }
139        self.zip.write_all(bytes)
140    }
141    // 完成写入,附带 manifest.json
142    pub fn finalize(mut self) -> io::Result<W> {
143        let opts = SimpleFileOptions::default()
144            .compression_method(CompressionMethod::Deflated);
145        self.zip.start_file("manifest.json", opts)?;
146        let data =
147            serde_json::to_vec(&self.manifest).map_err(io::Error::other)?;
148        self.zip.write_all(&data)?;
149        self.zip.finish().map_err(io::Error::other)
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use std::io::Cursor;
157
158    #[test]
159    fn test_plugin_state_export() {
160        let buffer = Vec::new();
161        let cursor = Cursor::new(buffer);
162        let mut writer = ZipDocumentWriter::new(cursor).unwrap();
163
164        // 添加插件状态
165        writer.add_plugin_state("test_plugin", b"test state data").unwrap();
166        writer
167            .add_plugin_state("another_plugin", b"another state data")
168            .unwrap();
169
170        let result = writer.finalize().unwrap();
171        let final_data = result.into_inner();
172
173        // 验证数据已写入
174        assert!(!final_data.is_empty());
175
176        // 验证 manifest 包含插件条目
177        let cursor = Cursor::new(&final_data);
178        let mut reader = crate::zipdoc::ZipDocumentReader::new(cursor).unwrap();
179
180        let plugins = reader.list_plugins().unwrap();
181        assert_eq!(plugins.len(), 2);
182        assert!(plugins.contains(&"test_plugin".to_string()));
183        assert!(plugins.contains(&"another_plugin".to_string()));
184
185        // 验证插件状态可以读取
186        let test_state =
187            reader.read_plugin_state("test_plugin").unwrap().unwrap();
188        assert_eq!(test_state, b"test state data");
189    }
190
191    #[test]
192    fn test_batch_plugin_states() {
193        let buffer = Vec::new();
194        let cursor = Cursor::new(buffer);
195        let mut writer = ZipDocumentWriter::new(cursor).unwrap();
196
197        let plugin_states = vec![
198            ("plugin1".to_string(), b"state1".to_vec()),
199            ("plugin2".to_string(), b"state2".to_vec()),
200            ("plugin3".to_string(), b"state3".to_vec()),
201        ];
202
203        writer.add_plugin_states(plugin_states.clone()).unwrap();
204        let result = writer.finalize().unwrap();
205        let final_data = result.into_inner();
206
207        let cursor = Cursor::new(&final_data);
208        let mut reader = crate::zipdoc::ZipDocumentReader::new(cursor).unwrap();
209
210        let all_states = reader.read_all_plugin_states().unwrap();
211        assert_eq!(all_states.len(), 3);
212
213        for (name, expected_data) in plugin_states {
214            let actual_data = all_states.get(&name).unwrap();
215            assert_eq!(actual_data, &expected_data);
216        }
217    }
218}