1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use std::fs;
use std::path::PathBuf;
use anyhow::{anyhow, Result};
use log::{info, trace, warn};
use crate::data::{SaveFile, SaveFileEntryType};

pub struct CleanSettings {
    pub input: PathBuf,
    pub output: PathBuf,
    pub root: Option<String>,
    pub follow_symlinks: bool,
}

pub fn run(
    clean_settings: CleanSettings,
) -> Result<()> {
    let mut input_file_options = fs::File::options();
    input_file_options.read(true);
    input_file_options.write(false);

    let mut output_file_options = fs::File::options();
    output_file_options.create(true);
    output_file_options.write(true);

    let input_file = match input_file_options.open(clean_settings.input) {
        Ok(file) => file,
        Err(err) => {
            return Err(anyhow!("Failed to open input file: {}", err));
        }
    };

    let output_file = match output_file_options.open(clean_settings.output) {
        Ok(file) => file,
        Err(err) => {
            return Err(anyhow!("Failed to open output file: {}", err));
        }
    };

    let mut input_buf_reader = std::io::BufReader::new(&input_file);
    let mut output_buf_writer = std::io::BufWriter::new(&output_file);

    let mut save_file = SaveFile::new(&mut output_buf_writer, &mut input_buf_reader, false, true, true);
    save_file.load_header()?;

    // remove duplicates, remove deleted files
    save_file.load_all_entries(|entry| {
        match entry.path.resolve_file() {
            Ok(path) => {
                if !path.exists() {
                    return false;
                }
                
                let metadata = match clean_settings.follow_symlinks { 
                    true => fs::metadata(path),
                    false => fs::symlink_metadata(path)
                };
                let metadata = match metadata {
                    Ok(data) => Some(data),
                    Err(err) => {
                        warn!("Unable to read metadata of {:?}: {}", entry.path, err);
                        None
                    }
                };
                
                if let Some(metadata) = metadata {
                    return if metadata.is_symlink() {
                        entry.file_type == SaveFileEntryType::Symlink
                    } else if metadata.is_dir() {
                        entry.file_type == SaveFileEntryType::Directory
                    } else if metadata.is_file() {
                        entry.file_type == SaveFileEntryType::File
                    } else {
                        entry.file_type == SaveFileEntryType::Other
                    }
                }
                
                true
            },
            Err(err) => {
                warn!("File {:?} resolving failed: {}", entry.path, err);
                true
            }
        }
    })?;
    
    // todo filter files deleted from inside archives

    // save results

    info!("Saving results to output file. Dont interrupt this process. It may corrupt the file.");
    save_file.save_header()?;
    for entry in save_file.all_entries.iter() {
        save_file.write_entry(entry)?;
    }
    
    save_file.flush()?;

    trace!("Truncating output file.");
    fs::File::set_len(&output_file, save_file.get_written_bytes() as u64)?;

    Ok(())
}