1use std::io::{self, Read, Seek, Write};
2use std::path::Path;
3use serde::{Serialize, de::DeserializeOwned};
4
5use crate::zipdoc::{ZipDocumentReader, ZipDocumentWriter};
6use crate::zipdoc::snapshot::SnapshotShardMeta;
7
8use super::{json, cbor, msgpack};
9
10#[derive(Debug, Clone, Copy)]
11pub enum SnapshotFormat {
12 Json,
13 Cbor,
14 MsgPack,
15}
16
17impl SnapshotFormat {
18 pub fn write_shards<W, F, T>(
19 &self,
20 zw: &mut ZipDocumentWriter<W>,
21 meta: &SnapshotShardMeta,
22 get_shard_value: F,
23 zstd_level: i32,
24 ) -> io::Result<()>
25 where
26 W: Write + Seek,
27 F: FnMut(usize) -> io::Result<T>,
28 T: Serialize,
29 {
30 match self {
31 SnapshotFormat::Json => json::write_snapshot_shards_json(
32 zw,
33 meta,
34 get_shard_value,
35 zstd_level,
36 ),
37 SnapshotFormat::Cbor => cbor::write_snapshot_shards_cbor(
38 zw,
39 meta,
40 get_shard_value,
41 zstd_level,
42 ),
43 SnapshotFormat::MsgPack => msgpack::write_snapshot_shards_msgpack(
44 zw,
45 meta,
46 get_shard_value,
47 zstd_level,
48 ),
49 }
50 }
51
52 pub fn read_shards<R, T>(
53 &self,
54 zr: &mut ZipDocumentReader<R>,
55 ) -> io::Result<(SnapshotShardMeta, Vec<T>)>
56 where
57 R: Read + Seek,
58 T: DeserializeOwned,
59 {
60 match self {
61 SnapshotFormat::Json => {
62 json::read_and_decode_snapshot_shards_json(zr)
63 },
64 SnapshotFormat::Cbor => {
65 cbor::read_and_decode_snapshot_shards_cbor(zr)
66 },
67 SnapshotFormat::MsgPack => {
68 msgpack::read_and_decode_snapshot_shards_msgpack(zr)
69 },
70 }
71 }
72
73 pub fn for_each_shard<R, T, F>(
74 &self,
75 zr: &mut ZipDocumentReader<R>,
76 on_shard: F,
77 ) -> io::Result<SnapshotShardMeta>
78 where
79 R: Read + Seek,
80 T: DeserializeOwned,
81 F: FnMut(usize, T) -> io::Result<()>,
82 {
83 match self {
84 SnapshotFormat::Json => {
85 json::for_each_snapshot_shard_json(zr, on_shard)
86 },
87 SnapshotFormat::Cbor => {
88 cbor::for_each_snapshot_shard_cbor(zr, on_shard)
89 },
90 SnapshotFormat::MsgPack => {
91 msgpack::for_each_snapshot_shard_msgpack(zr, on_shard)
92 },
93 }
94 }
95
96 pub fn write_parent_map<W, T>(
97 &self,
98 zw: &mut ZipDocumentWriter<W>,
99 parent_map: &T,
100 zstd_level: i32,
101 ) -> io::Result<()>
102 where
103 W: Write + Seek,
104 T: Serialize,
105 {
106 match self {
107 SnapshotFormat::Json => {
108 json::write_parent_map_json(zw, parent_map, zstd_level)
109 },
110 SnapshotFormat::Cbor => {
111 cbor::write_parent_map_cbor(zw, parent_map, zstd_level)
112 },
113 SnapshotFormat::MsgPack => {
114 msgpack::write_parent_map_msgpack(zw, parent_map, zstd_level)
115 },
116 }
117 }
118
119 pub fn read_parent_map<R, T>(
120 &self,
121 zr: &mut ZipDocumentReader<R>,
122 ) -> io::Result<T>
123 where
124 R: Read + Seek,
125 T: DeserializeOwned,
126 {
127 match self {
128 SnapshotFormat::Json => json::read_parent_map_json(zr),
129 SnapshotFormat::Cbor => cbor::read_parent_map_cbor(zr),
130 SnapshotFormat::MsgPack => msgpack::read_parent_map_msgpack(zr),
131 }
132 }
133}
134
135impl SnapshotFormat {
136 pub fn as_str(&self) -> &'static str {
137 match self {
138 SnapshotFormat::Json => "json",
139 SnapshotFormat::Cbor => "cbor",
140 SnapshotFormat::MsgPack => "msgpack",
141 }
142 }
143 #[allow(clippy::should_implement_trait)]
144 pub fn from_str(s: &str) -> Option<Self> {
145 match s.to_ascii_lowercase().as_str() {
146 "json" => Some(SnapshotFormat::Json),
147 "cbor" => Some(SnapshotFormat::Cbor),
148 "msgpack" | "rmp" | "msg" => Some(SnapshotFormat::MsgPack),
149 _ => None,
150 }
151 }
152 pub fn from_extension<P: AsRef<Path>>(path: P) -> Option<Self> {
153 match path
154 .as_ref()
155 .extension()
156 .and_then(|e| e.to_str())
157 .map(|s| s.to_ascii_lowercase())
158 {
159 Some(ext) if ext == "json" => Some(SnapshotFormat::Json),
160 Some(ext) if ext == "cbor" || ext == "cbr" => {
161 Some(SnapshotFormat::Cbor)
162 },
163 Some(ext) if ext == "msgpack" || ext == "rmp" || ext == "msg" => {
164 Some(SnapshotFormat::MsgPack)
165 },
166 _ => None,
167 }
168 }
169}
170
171#[allow(clippy::too_many_arguments)]
173pub fn export_zip_with_format<P, F, T, PM>(
174 path: P,
175 meta_json: &serde_json::Value,
176 schema_xml: &[u8],
177 shard_meta: &SnapshotShardMeta,
178 get_shard_value: F,
179 parent_map: Option<&PM>,
180 plugin_states: Option<std::collections::HashMap<String, Vec<u8>>>,
181 zstd_level: i32,
182 format: SnapshotFormat,
183) -> io::Result<()>
184where
185 P: AsRef<Path>,
186 F: FnMut(usize) -> io::Result<T>,
187 T: Serialize,
188 PM: Serialize,
189{
190 let file = std::fs::File::create(path)?;
191 let mut zw = ZipDocumentWriter::new(file)?;
192 zw.add_json("meta.json", meta_json)?;
193 zw.add_deflated("schema.xml", schema_xml)?;
194 format.write_shards(&mut zw, shard_meta, get_shard_value, zstd_level)?;
195 if let Some(pm) = parent_map {
196 format.write_parent_map(&mut zw, pm, zstd_level)?;
197 }
198 if let Some(states) = plugin_states {
199 zw.add_plugin_states(states)?;
200 }
201 let _ = zw.finalize()?;
202 Ok(())
203}
204
205#[allow(clippy::type_complexity)]
207pub fn import_zip_with_format<P, T, PM>(
208 path: P,
209 format: SnapshotFormat,
210 read_parent_map: bool,
211 read_plugin_states: bool,
212) -> io::Result<(
213 serde_json::Value,
214 Vec<u8>,
215 SnapshotShardMeta,
216 Vec<T>,
217 Option<PM>,
218 Option<std::collections::HashMap<String, Vec<u8>>>,
219)>
220where
221 P: AsRef<Path>,
222 T: DeserializeOwned,
223 PM: DeserializeOwned,
224{
225 let file = std::fs::File::open(path)?;
226 let mut zr = ZipDocumentReader::new(file)?;
227 let meta_json = zr.read_all("meta.json")?;
228 let meta_val: serde_json::Value =
229 serde_json::from_slice(&meta_json).map_err(io::Error::other)?;
230 let schema_xml = zr.read_all("schema.xml")?;
231 let (shard_meta, decoded) = format.read_shards::<_, T>(&mut zr)?;
232 let parent_map = if read_parent_map {
233 Some(format.read_parent_map::<_, PM>(&mut zr)?)
234 } else {
235 None
236 };
237 let plugin_states = if read_plugin_states {
238 Some(zr.read_all_plugin_states()?)
239 } else {
240 None
241 };
242 Ok((meta_val, schema_xml, shard_meta, decoded, parent_map, plugin_states))
243}
244
245pub fn export_plugin_states_only<P>(
247 path: P,
248 plugin_states: std::collections::HashMap<String, Vec<u8>>,
249 meta_json: Option<&serde_json::Value>,
250) -> io::Result<()>
251where
252 P: AsRef<Path>,
253{
254 let file = std::fs::File::create(path)?;
255 let mut zw = ZipDocumentWriter::new(file)?;
256
257 if let Some(meta) = meta_json {
259 zw.add_json("meta.json", meta)?;
260 } else {
261 let timestamp = {
263 #[cfg(feature = "chrono")]
264 {
265 chrono::Utc::now().to_rfc3339()
266 }
267 #[cfg(not(feature = "chrono"))]
268 {
269 std::time::SystemTime::now()
270 .duration_since(std::time::UNIX_EPOCH)
271 .unwrap_or_default()
272 .as_secs()
273 .to_string()
274 }
275 };
276
277 let default_meta = serde_json::json!({
278 "type": "plugin_states_backup",
279 "version": "1.0",
280 "plugin_count": plugin_states.len(),
281 "timestamp": timestamp
282 });
283 zw.add_json("meta.json", &default_meta)?;
284 }
285
286 zw.add_plugin_states(plugin_states)?;
288 let _ = zw.finalize()?;
289 Ok(())
290}
291
292pub fn import_plugin_states_only<P>(
294 path: P
295) -> io::Result<(serde_json::Value, std::collections::HashMap<String, Vec<u8>>)>
296where
297 P: AsRef<Path>,
298{
299 let file = std::fs::File::open(path)?;
300 let mut zr = ZipDocumentReader::new(file)?;
301
302 let meta_json = zr.read_all("meta.json")?;
303 let meta_val: serde_json::Value =
304 serde_json::from_slice(&meta_json).map_err(io::Error::other)?;
305
306 let plugin_states = zr.read_all_plugin_states()?;
307 Ok((meta_val, plugin_states))
308}
309
310pub fn has_plugin_states<P>(path: P) -> io::Result<bool>
312where
313 P: AsRef<Path>,
314{
315 let file = std::fs::File::open(path)?;
316 let mut zr = ZipDocumentReader::new(file)?;
317 let plugins = zr.list_plugins()?;
318 Ok(!plugins.is_empty())
319}
320
321pub fn list_zip_plugins<P>(path: P) -> io::Result<Vec<String>>
323where
324 P: AsRef<Path>,
325{
326 let file = std::fs::File::open(path)?;
327 let mut zr = ZipDocumentReader::new(file)?;
328 zr.list_plugins()
329}