fb2_clean/config/
run.rs

1use super::Config;
2use crate::{InputFile, InputFileType, Result, clean_xml};
3use quick_xml::{Reader, Writer};
4use std::{
5    ffi::OsString,
6    fs::File,
7    io::{BufRead, BufReader, BufWriter, Write},
8    path::PathBuf,
9};
10use zip::{ZipArchive, ZipWriter, write::SimpleFileOptions};
11
12impl Config {
13    /// Run for current [`Config`].
14    pub fn run(&self) -> Result<()> {
15        let mut zip_owner: Option<ZipArchive<File>> = None;
16
17        for src in self.input.iter() {
18            println!("Cleaning '{}'...", src.path.display());
19            let dest = Dest::new(self, &src);
20
21            if !self.force && dest.path.exists() {
22                eprintln!(
23                    "Warning: output is already exists '{}'. Skipping",
24                    dest.path.display()
25                );
26                continue;
27            }
28
29            match try_reader_writer(&mut zip_owner, &src, &dest)
30                .and_then(|(mut r, mut w)| clean_xml(&mut r, &mut w, &self.tags))
31            {
32                Err(e) if self.exit_on_err => return Err(e),
33                Err(e) => eprintln!("Error: {}. Skipping", e),
34                Ok(()) => println!("Success cleaned and saved to '{}'", dest.path.display()),
35            }
36        }
37        Ok(())
38    }
39}
40
41fn try_reader_writer<'a>(
42    zip_owner: &'a mut Option<ZipArchive<File>>,
43    src: &'a InputFile,
44    dest: &'a Dest,
45) -> Result<(Reader<Box<dyn BufRead + 'a>>, Writer<Box<dyn Write + 'a>>)> {
46    let src_file = File::open(&src.path)?;
47    let dest_file = File::create(&dest.path)?;
48
49    let reader = match src.ty {
50        InputFileType::Fb2 => {
51            Reader::from_reader(Box::new(BufReader::new(src_file)) as Box<dyn BufRead>)
52        }
53        InputFileType::Fb2Zip => {
54            *zip_owner = Some(ZipArchive::new(src_file)?);
55            let zip = zip_owner.as_mut().unwrap();
56            let fb2_index = (0..zip.len())
57                .find(|&i| {
58                    zip.by_index(i)
59                        .ok()
60                        .map(|f| {
61                            let bytes = f.name().as_bytes();
62                            let len = bytes.len();
63                            len > 3 && bytes[len - 4..len].eq_ignore_ascii_case(b".fb2")
64                        })
65                        .unwrap_or(false)
66                })
67                .ok_or_else(|| format!("fb2 not found in archive '{}'", src.path.display()))?;
68
69            let fb2_file = zip.by_index(fb2_index)?;
70            Reader::from_reader(Box::new(BufReader::new(fb2_file)) as Box<dyn BufRead>)
71        }
72    };
73
74    let writer = match dest.ty {
75        InputFileType::Fb2 => Writer::new(Box::new(BufWriter::new(dest_file)) as Box<dyn Write>),
76        InputFileType::Fb2Zip => {
77            let mut zip_writer = ZipWriter::new(BufWriter::new(dest_file));
78            zip_writer.start_file(dest.zip_start_file(), SimpleFileOptions::default())?;
79            Writer::new(Box::new(zip_writer) as Box<dyn Write>)
80        }
81    };
82
83    Ok((reader, writer))
84}
85
86struct Dest {
87    ty: InputFileType,
88    name: OsString,
89    path: PathBuf,
90}
91
92impl Dest {
93    fn new(cfg: &Config, src: &InputFile) -> Dest {
94        let ty = if cfg.zip {
95            InputFileType::Fb2Zip
96        } else if cfg.unzip {
97            InputFileType::Fb2
98        } else {
99            src.ty
100        };
101
102        let name = match src.path.file_stem() {
103            Some(os) => {
104                let mut os = os.to_owned();
105                if src.ty.is_fb2() {
106                    os.push(if ty.is_fb2() { ".fb2" } else { ".fb2.zip" });
107                } else if ty.is_fb2_zip() {
108                    os.push(".zip")
109                }
110                os
111            }
112            None if ty.is_fb2() => OsString::from("cleaned.fb2"),
113            None => OsString::from("cleaned.fb2.zip"),
114        };
115
116        Dest {
117            path: cfg.output.dir.join(&name),
118            ty,
119            name,
120        }
121    }
122
123    fn zip_start_file(&self) -> String {
124        let mut s = self.name.to_string_lossy().into_owned();
125        if self.ty.is_fb2_zip() {
126            s.truncate(s.len() - 4);
127        }
128        s
129    }
130}