proton_launch/command/
backup.rs

1use std::{
2    collections::HashSet,
3    fs::File,
4    path::{Path, PathBuf},
5};
6
7use crate::{paths::Paths, steam::SteamData};
8
9use super::{Runnable, RunnableResult};
10
11#[derive(Debug, Clone)]
12#[cfg_attr(feature = "commandline", derive(clap::Args))]
13pub struct Backup {
14    /// Path to the game exe
15    exe: PathBuf,
16
17    /// Optional save name to use
18    /// If not specified, the game exe without the extension will be used
19    #[cfg_attr(feature = "commandline", clap(short, long))]
20    save_name: Option<String>,
21}
22
23impl Runnable for Backup {
24    fn run(&self, paths: &Paths, _steam_data: &SteamData) -> RunnableResult<()> {
25        let save_name = self
26            .save_name
27            .as_deref()
28            .unwrap_or_else(|| self.exe.file_stem().unwrap().to_str().unwrap());
29        let global_compat_dir = paths.compat_dir(save_name);
30        let r = find_new_files(&global_compat_dir).unwrap();
31        let f = File::create(format!("{}.backup", save_name)).unwrap();
32        let w = zstd::Encoder::new(f, 3).unwrap().auto_finish();
33        let mut t = tar::Builder::new(w);
34        for f in r {
35            let path = f.strip_prefix(&global_compat_dir).unwrap();
36            t.append_path_with_name(&f, path).unwrap();
37        }
38        t.finish().unwrap();
39        Ok(())
40    }
41}
42
43fn find_new_files(compat_dir: &Path) -> std::io::Result<Vec<PathBuf>> {
44    let mut v = Vec::new();
45    let tracked_files = compat_dir.join("tracked_files");
46    let tracked_files = std::fs::read_to_string(tracked_files)?;
47    let tracked_files: HashSet<&str> = tracked_files.lines().collect();
48    let prefix = compat_dir.join("pfx");
49
50    for entry in walkdir::WalkDir::new(&prefix) {
51        let entry = entry?;
52        let path = entry.path();
53        if entry.file_type().is_file() {
54            let path = path.strip_prefix(&prefix).unwrap();
55            if !tracked_files.contains(path.to_str().unwrap())
56                && path.to_string_lossy().contains("users")
57            {
58                v.push(entry.path().to_path_buf());
59            }
60        }
61    }
62
63    Ok(v)
64}