acorn_lib/powerpoint/
mod.rs1use crate::util::{get_all_files, get_command_folder, path_to_string, read_file, Label};
2use serde::{Deserialize, Serialize};
3use std::error::Error;
4use std::fs::create_dir_all;
5use std::fs::File;
6use std::io::{self, Read, Write};
7use std::path::PathBuf;
8use tracing::{debug, error, info};
9use zip::write::SimpleFileOptions;
10use zip::{ZipArchive, ZipWriter};
11
12#[derive(Clone, Debug, Deserialize, Serialize)]
13#[serde(rename_all = "PascalCase")]
14pub struct Relationships {
15 pub relationship: Vec<Relationship>,
16}
17#[derive(Clone, Debug, Deserialize, Serialize)]
18#[serde(rename_all = "PascalCase")]
19pub struct Relationship {
20 #[serde(rename = "@Id")]
21 pub id: String,
22 #[serde(rename = "@Type")]
23 pub relationship_type: String,
24 #[serde(rename = "@Target")]
25 pub target: String,
26 #[serde(rename = "@TargetMode")]
27 pub target_mode: Option<String>,
28}
29pub fn archive(path: PathBuf, destination: Option<PathBuf>) -> Result<PathBuf, Box<dyn Error>> {
30 let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
31 let zip_file_path = match destination {
32 | Some(value) => value,
33 | None => path.with_extension("zip"),
34 };
35 let mut zip = match File::create(&zip_file_path) {
36 | Ok(zip_file) => ZipWriter::new(zip_file),
37 | Err(why) => {
38 error!(file = path_to_string(path), "=> {} Create zip archive - {why}", Label::fail());
39 std::process::exit(exitcode::IOERR);
40 }
41 };
42 let files = get_all_files(path.clone(), None, None).into_iter().filter(|x| x.is_file());
43 for file_path in files {
44 if let Ok(file) = File::open(file_path.clone()) {
45 let name = match path.canonicalize() {
46 | Ok(relative) => file_path.strip_prefix(relative).unwrap_or_else(|_| &file_path),
47 | Err(_) => &file_path,
48 };
49 debug!(file = path_to_string(name.to_path_buf()), "=> {} Add file to archive", Label::using());
50 match zip.start_file_from_path(name, options) {
51 | Ok(_) => {
52 let mut buffer = Vec::new();
53 match io::copy(&mut file.take(u64::MAX), &mut buffer) {
54 | Ok(_) => match zip.write_all(&buffer) {
55 | Ok(_) => {}
56 | Err(why) => {
57 error!(file = path_to_string(file_path), "=> {} Write zip archive - {why}", Label::fail())
58 }
59 },
60 | Err(why) => {
61 error!("=> {} Copy buffer - {why}", Label::fail())
62 }
63 }
64 }
65 | Err(why) => {
66 error!(file = path_to_string(file_path), "=> {} Start zip archive - {why}", Label::fail());
67 }
68 }
69 }
70 }
71 match zip.finish() {
72 | Ok(_) => Ok(zip_file_path),
73 | Err(why) => {
74 error!(file = path_to_string(path), "=> {} Finish zip archive - {why}", Label::fail());
75 Err(why.into())
76 }
77 }
78}
79pub fn extract(path: PathBuf, destination: Option<PathBuf>) -> Result<PathBuf, Box<dyn Error>> {
80 let root = get_command_folder("extract", destination);
81 match File::open(path.clone()) {
82 | Ok(zip_file) => match ZipArchive::new(zip_file) {
83 | Ok(mut archive) => {
84 for index in 0..archive.len() {
85 if let Ok(mut file) = archive.by_index(index) {
86 let target = root.join(file.name());
87 if let Some(parent) = target.parent() {
88 match create_dir_all(parent) {
89 | Ok(_) => {}
90 | Err(why) => error!(path = path_to_string(parent.to_path_buf()), "=> {} Create - {}", Label::fail(), why),
91 }
92 }
93 if let Ok(mut output_file) = File::create(&target) {
94 io::copy(&mut file, &mut output_file).unwrap();
95 } else {
96 error!(path = path_to_string(target), "=> {} Create file", Label::fail());
97 std::process::exit(exitcode::UNAVAILABLE);
98 }
99 }
100 }
101 info!(path = root.to_str().unwrap(), "=> {} Extract zip archive", Label::pass());
102 Ok(root)
103 }
104 | Err(err) => {
105 error!(
106 error = err.to_string(),
107 path = path.to_str().unwrap(),
108 "=> {} Read zip archive",
109 Label::fail()
110 );
111 std::process::exit(exitcode::UNAVAILABLE);
112 }
113 },
114 | Err(err) => {
115 error!(error = err.to_string(), path = path.to_str().unwrap(), "=> {} Read file", Label::fail());
116 std::process::exit(exitcode::UNAVAILABLE);
117 }
118 }
119}
120pub fn read_xml_rel(path: PathBuf) -> Result<Relationships, quick_xml::DeError> {
121 match read_file(path) {
122 | Ok(content) => {
123 let parsed = quick_xml::de::from_str::<Relationships>(&content);
124 debug!("=> {} Relationships = {:#?}", Label::using(), parsed);
125 parsed
126 }
127 | Err(why) => {
128 error!("=> {} Cannot read xml.rels file - {why}", Label::fail());
129 std::process::exit(exitcode::IOERR);
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests;