fm/modes/menu/
compress.rs1use 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#[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#[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 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 Self::make_tar(files, tar::Builder::new(&mut encoder))?;
100
101 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 Self::make_tar(files, tar::Builder::new(&mut encoder))?;
112
113 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 Self::make_tar(files, tar::Builder::new(&mut encoder))?;
124
125 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 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);