1use super::Config;
2use crate::{Input, InputFile, InputFileType, Result, clean_xml};
3use quick_xml::{Reader, Writer};
4use std::{
5 borrow::Cow,
6 fs::{self, File},
7 io::{BufRead, BufReader, BufWriter, Write},
8 iter,
9 path::{Component, Path, PathBuf},
10};
11use walkdir::WalkDir;
12use zip::{ZipArchive, ZipWriter, write::SimpleFileOptions};
13
14impl Config {
15 pub fn run(&self) -> Result<()> {
17 let mut src_dests: Vec<(InputFile, Dest)> = Vec::new();
18 let mut zip_owner: Option<ZipArchive<File>> = None;
19 let mut is_found_any = false;
20
21 for (subdirs, src) in self.subdirs_src_iter() {
22 if !is_found_any {
23 println!("Cleaning input files...");
24 is_found_any = true;
25 }
26
27 println!("Cleaning '{}'...", src.path.display());
28 let dest = Dest::new(self, subdirs, &src);
29
30 if !self.force && dest.path.exists() {
31 eprintln!(
32 "Warning: output is already exists '{}'. Skipping",
33 dest.path.display()
34 );
35 continue;
36 }
37
38 match try_reader_writer(&mut zip_owner, &src, &dest)
39 .and_then(|(mut r, mut w)| clean_xml(&mut r, &mut w, &self.tags))
40 {
41 Err(e) if self.exit_on_err => return Err(e),
42 Err(e) => {
43 eprintln!("Error: {}. Skipping", e);
44 continue;
45 }
46 Ok(()) => println!("Success cleaned and saved to '{}'", dest.path.display()),
47 }
48
49 if self.force {
50 src_dests.push((src, dest));
51 }
52 }
53
54 if !src_dests.is_empty() {
55 force_overwrites(src_dests);
56 }
57
58 if !is_found_any {
59 if let Input::Dir(d) = &self.input {
60 eprintln!("Warning: not found any fb2 in dir '{}'", d.display());
61 }
62 }
63
64 Ok(())
65 }
66
67 fn subdirs_src_iter(&self) -> Box<dyn Iterator<Item = (Option<Vec<PathBuf>>, InputFile)> + '_> {
68 match &self.input {
69 Input::Dir(d) if self.recursive != 0 => {
70 let it = WalkDir::new(d)
71 .max_depth(self.recursive as usize)
72 .into_iter()
73 .filter_map(|e| e.ok())
74 .filter(|e| e.file_type().is_dir())
75 .filter(|e| !e.path().starts_with(&*self.output.dir))
76 .map(move |e| {
77 let subdirs: Vec<PathBuf> = e
78 .path()
79 .strip_prefix(d)
80 .unwrap_or(Path::new(""))
81 .components()
82 .filter_map(|x| match x {
83 Component::Normal(x) => Some(PathBuf::from(x)),
84 _ => None,
85 })
86 .collect();
87
88 (Some(subdirs), Input::Dir(e.into_path().into()))
89 })
90 .flat_map(|(subdirs, d)| iter::repeat(subdirs).zip(d.iter()));
91
92 Box::new(it)
93 }
94 _ => Box::new(iter::repeat(None).zip(self.input.iter())),
95 }
96 }
97}
98
99fn force_overwrites(mut src_dests: Vec<(InputFile, Dest)>) {
100 println!("\nOverwriting input files...");
101
102 for (src, dest) in &src_dests {
103 println!("Overwriting '{}'...", src.path.display());
104 match dest.force_overwrite(&src) {
105 Ok(()) => println!("Success overwrited from '{}'", dest.path.display()),
106 Err(e) => eprintln!("Error: Fail overwrite: {}", e),
107 }
108 }
109
110 src_dests.sort_by(|a, b| b.1.len_created_dirs.cmp(&a.1.len_created_dirs));
111
112 for (_, dest) in &src_dests {
113 dest.remove_created_dirs();
114 }
115}
116
117fn try_reader_writer<'a>(
118 zip_owner: &'a mut Option<ZipArchive<File>>,
119 src: &'a InputFile,
120 dest: &'a Dest,
121) -> Result<(Reader<Box<dyn BufRead + 'a>>, Writer<Box<dyn Write + 'a>>)> {
122 let src_file = File::open(&src.path)?;
123 let dest_file = File::create(&dest.path)?;
124
125 let reader = match src.ty {
126 InputFileType::Fb2 => {
127 Reader::from_reader(Box::new(BufReader::new(src_file)) as Box<dyn BufRead>)
128 }
129 InputFileType::Fb2Zip => {
130 *zip_owner = Some(ZipArchive::new(src_file)?);
131 let zip = zip_owner.as_mut().unwrap();
132 let fb2_index = (0..zip.len())
133 .find(|&i| {
134 zip.by_index(i)
135 .ok()
136 .map(|f| {
137 let bytes = f.name().as_bytes();
138 let len = bytes.len();
139 len > 3 && bytes[len - 4..len].eq_ignore_ascii_case(b".fb2")
140 })
141 .unwrap_or(false)
142 })
143 .ok_or_else(|| format!("fb2 not found in archive '{}'", src.path.display()))?;
144
145 let fb2_file = zip.by_index(fb2_index)?;
146 Reader::from_reader(Box::new(BufReader::new(fb2_file)) as Box<dyn BufRead>)
147 }
148 };
149
150 let writer = match dest.ty {
151 InputFileType::Fb2 => Writer::new(Box::new(BufWriter::new(dest_file)) as Box<dyn Write>),
152 InputFileType::Fb2Zip => {
153 let mut zip_writer = ZipWriter::new(BufWriter::new(dest_file));
154 zip_writer.start_file(dest.zip_start_file(), SimpleFileOptions::default())?;
155 Writer::new(Box::new(zip_writer) as Box<dyn Write>)
156 }
157 };
158
159 Ok((reader, writer))
160}
161
162struct Dest {
163 created_dirs: Option<Vec<PathBuf>>,
164 len_created_dirs: usize,
165 ty: InputFileType,
166 stem: PathBuf,
167 path: PathBuf,
168}
169
170impl Dest {
171 fn new(cfg: &Config, subdirs: Option<Vec<PathBuf>>, src: &InputFile) -> Dest {
172 let ty = if cfg.zip {
173 InputFileType::Fb2Zip
174 } else if cfg.unzip {
175 InputFileType::Fb2
176 } else {
177 src.ty
178 };
179
180 let stem = match src.path.file_stem() {
181 Some(os) => {
182 let mut p = PathBuf::from(os);
183 if src.ty.is_fb2_zip() {
184 p.set_extension("");
185 }
186 p
187 }
188 _ => PathBuf::from("cleaned"),
189 };
190
191 let mut path = cfg.output.dir.clone().into_path_buf();
192 let mut created_dirs: Option<Vec<PathBuf>> = None;
193
194 if let Some(xs) = &subdirs {
195 for x in xs {
196 path.push(x);
197 if let Ok(()) = fs::create_dir(&path) {
198 created_dirs
199 .get_or_insert_with(|| Vec::with_capacity(xs.len()))
200 .push(path.clone())
201 }
202 }
203 }
204 path.push(&stem);
205 path.add_extension(ty.as_extension());
206
207 Dest {
208 len_created_dirs: created_dirs.as_ref().map(|xs| xs.len()).unwrap_or(0),
209 created_dirs,
210 ty,
211 stem,
212 path,
213 }
214 }
215
216 fn force_overwrite(&self, src: &InputFile) -> Result<()> {
217 let force_path = self.force_path(src);
218
219 if let Err(_) = fs::rename(&self.path, &force_path) {
220 fs::copy(&self.path, &force_path)?;
221 if let Err(e) = fs::remove_file(&self.path) {
222 eprintln!(
223 "Error: fail remove temp file '{}': {}",
224 self.path.display(),
225 e
226 );
227 }
228 }
229
230 if force_path != &*src.path {
231 if let Err(e) = fs::remove_file(&src.path) {
232 eprintln!(
233 "Error: fail remove input file '{}': {}",
234 src.path.display(),
235 e
236 );
237 }
238 }
239 Ok(())
240 }
241
242 fn force_path<'a>(&self, src: &'a InputFile) -> Cow<'a, Path> {
243 if self.ty == src.ty {
244 return Cow::Borrowed(&*src.path);
245 }
246
247 let mut p = match src.path.parent() {
248 Some(p) => PathBuf::from(p),
249 None => PathBuf::from("."),
250 };
251 p.push(&self.stem);
252 p.add_extension(self.ty.as_extension());
253 Cow::Owned(p)
254 }
255
256 fn remove_created_dirs(&self) {
257 if let Some(xs) = &self.created_dirs {
258 for x in xs.iter().rev() {
259 if let Err(e) = fs::remove_dir(x) {
260 eprintln!("Error: Fail remove temp directory '{}': {}", x.display(), e);
261 }
262 }
263 }
264 }
265
266 fn zip_start_file(&self) -> String {
267 let mut s = self.stem.to_string_lossy().into_owned();
268 s.push_str(".fb2");
269 s
270 }
271}