fm/modes/menu/
compress.rs

1use std::borrow::Cow;
2use std::io::{prelude::*, Write};
3use std::path::{Path, PathBuf};
4
5use anyhow::{Context, Result};
6use flate2::write::{DeflateEncoder, GzEncoder, ZlibEncoder};
7use flate2::Compression;
8use zip::write::SimpleFileOptions;
9
10use crate::common::{is_in_path, SEVENZ};
11use crate::io::execute_without_output;
12use crate::{impl_content, impl_draw_menu_with_char, impl_selectable, log_info, log_line};
13
14/// Different kind of compression methods
15#[derive(Debug)]
16pub enum CompressionMethod {
17    Zip,
18    Defl,
19    Gz,
20    Zlib,
21    Sevenz,
22}
23
24impl CompressionMethod {
25    fn to_str(&self) -> &'static str {
26        match self {
27            Self::Zip => "ZIP:     archive.zip",
28            Self::Defl => "DEFLATE: archive.tar.gz",
29            Self::Gz => "GZ:      archive.tar.gz",
30            Self::Zlib => "ZLIB:    archive.tar.xz",
31            Self::Sevenz => "7Z:      archive.7z",
32        }
33    }
34}
35
36/// Holds a vector of CompressionMethod and a few methods to compress some files.
37#[derive(Debug, Default)]
38pub struct Compresser {
39    content: Vec<CompressionMethod>,
40    pub index: usize,
41}
42
43impl Compresser {
44    pub fn setup(&mut self) {
45        self.content = vec![
46            CompressionMethod::Zip,
47            CompressionMethod::Zlib,
48            CompressionMethod::Gz,
49            CompressionMethod::Defl,
50            CompressionMethod::Sevenz,
51        ];
52    }
53    /// Archive the files with tar and compress them with the selected method.
54    /// The compression method is chosen by the user.
55    /// Archive is created `here` which should be the path of the selected tab.
56    pub fn compress(&self, files: Vec<PathBuf>, here: &Path) -> Result<()> {
57        let Some(selected) = self.selected() else {
58            return Ok(());
59        };
60        match selected {
61            #[rustfmt::skip]
62            CompressionMethod::Zip  => Self::zip (Self::archive(here, "archive.zip")?, files)?,
63            CompressionMethod::Zlib => Self::zlib(Self::archive(here, "archive.tar.xz")?, files)?,
64            CompressionMethod::Gz => Self::gzip(Self::archive(here, "archive.tar.gz")?, files)?,
65            CompressionMethod::Defl => Self::defl(Self::archive(here, "archive.tar.gz")?, files)?,
66            CompressionMethod::Sevenz => Self::sevenz(here, "archive.7z", files)?,
67        }
68        log_line!("Compressed with {selected}", selected = selected.to_str());
69        Ok(())
70    }
71
72    fn make_tar<W>(files: Vec<PathBuf>, mut archive: tar::Builder<W>) -> Result<()>
73    where
74        W: Write,
75    {
76        for path in files.iter() {
77            if path.starts_with("..") {
78                continue;
79            }
80            if path.is_dir() {
81                archive.append_dir_all(path, path)?;
82            } else {
83                archive.append_path(path)?;
84            }
85        }
86        Ok(())
87    }
88
89    fn archive(here: &Path, archive_name: &str) -> Result<std::fs::File> {
90        let mut full_path = here.to_path_buf();
91        full_path.push(archive_name);
92        Ok(std::fs::File::create(full_path)?)
93    }
94
95    fn gzip(archive: std::fs::File, files: Vec<PathBuf>) -> Result<()> {
96        let mut encoder = GzEncoder::new(archive, Compression::default());
97
98        // Create tar archive and compress files
99        Self::make_tar(files, tar::Builder::new(&mut encoder))?;
100
101        // Finish Gzip file
102        encoder.finish()?;
103
104        Ok(())
105    }
106
107    fn defl(archive: std::fs::File, files: Vec<PathBuf>) -> Result<()> {
108        let mut encoder = DeflateEncoder::new(archive, Compression::default());
109
110        // Create tar archive and compress files
111        Self::make_tar(files, tar::Builder::new(&mut encoder))?;
112
113        // Finish deflate file
114        encoder.finish()?;
115
116        Ok(())
117    }
118
119    fn zlib(archive: std::fs::File, files: Vec<PathBuf>) -> Result<()> {
120        let mut encoder = ZlibEncoder::new(archive, Compression::default());
121
122        // Create tar archive and compress files
123        Self::make_tar(files, tar::Builder::new(&mut encoder))?;
124
125        // Finish zlib file
126        encoder.finish()?;
127
128        Ok(())
129    }
130
131    fn zip(archive: std::fs::File, files: Vec<PathBuf>) -> Result<()> {
132        let mut zip = zip::ZipWriter::new(archive);
133        let options = SimpleFileOptions::default()
134            .compression_method(zip::CompressionMethod::Stored)
135            .unix_permissions(0o755)
136            .compression_method(zip::CompressionMethod::Bzip2);
137        for file in files.iter() {
138            zip.start_file(file.to_string_lossy().as_ref(), options)?;
139            let mut buffer = Vec::new();
140            let mut content = std::fs::File::open(file)?;
141            content.read_to_end(&mut buffer)?;
142            zip.write_all(&buffer)?;
143        }
144
145        // Finish zip file
146        zip.finish()?;
147        Ok(())
148    }
149
150    fn sevenz(dest: &Path, filename: &str, files: Vec<PathBuf>) -> Result<()> {
151        if !is_in_path(SEVENZ) {
152            log_info!("Can't compress with 7z without {SEVENZ} executable");
153            log_line!("Can't compress with 7z without {SEVENZ} executable");
154            return Ok(());
155        }
156        let dest = dest.join(filename);
157        let dest = dest.to_str().context("")?;
158        let mut args = vec!["a", &dest];
159        args.extend(files.iter().filter_map(|file| file.to_str()));
160        args.extend(&["-y", "-bd"]);
161        let _ = execute_without_output(SEVENZ, &args);
162        Ok(())
163    }
164}
165
166impl_content!(Compresser, CompressionMethod);
167
168impl CowStr for CompressionMethod {
169    fn cow_str(&self) -> Cow<'_, str> {
170        self.to_str().into()
171    }
172}
173
174impl_draw_menu_with_char!(Compresser, CompressionMethod);