acorn_lib/powerpoint/
mod.rs

1use 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;