ajour_core/fs/
addon.rs

1use super::Result;
2use crate::{
3    addon::{Addon, AddonFolder},
4    parse::parse_toc_path,
5};
6use std::collections::HashSet;
7use std::fs::{remove_dir_all, remove_file};
8use std::path::Path;
9use walkdir::WalkDir;
10
11/// Deletes an Addon and all dependencies from disk.
12pub fn delete_addons(addon_folders: &[AddonFolder]) -> Result<()> {
13    for folder in addon_folders {
14        let path = &folder.path;
15        if path.exists() {
16            remove_dir_all(path)?;
17        }
18    }
19
20    Ok(())
21}
22
23/// Deletes all saved varaible files correlating to `[AddonFolder]`.
24pub fn delete_saved_variables(addon_folders: &[AddonFolder], wtf_path: &Path) -> Result<()> {
25    for entry in WalkDir::new(&wtf_path)
26        .into_iter()
27        .filter_map(std::result::Result::ok)
28    {
29        let path = entry.path();
30        let parent_name = path
31            .parent()
32            .and_then(|a| a.file_name())
33            .and_then(|a| a.to_str());
34
35        if parent_name == Some("SavedVariables") {
36            let file_name = path
37                .file_stem()
38                .and_then(|a| a.to_str())
39                .map(|a| a.trim_end_matches(".bak"));
40
41            // NOTE: Will reject "Foobar_<invalid utf8>".
42            if let Some(file_name_str) = file_name {
43                for folder in addon_folders {
44                    if file_name_str == folder.id {
45                        remove_file(path)?;
46                    }
47                }
48            }
49        }
50    }
51
52    Ok(())
53}
54
55/// Unzips an `Addon` archive, and once that is done, it moves the content
56/// to the `to_directory`.
57/// At the end it will cleanup and remove the archive.
58pub async fn install_addon(
59    addon: &Addon,
60    from_directory: &Path,
61    to_directory: &Path,
62) -> Result<Vec<AddonFolder>> {
63    let zip_path = from_directory.join(&addon.primary_folder_id);
64    let mut zip_file = std::fs::File::open(&zip_path)?;
65    let mut archive = zip::ZipArchive::new(&mut zip_file)?;
66
67    // Remove all existing top level addon folders.
68    for folder in addon.folders.iter() {
69        let path = &folder.path;
70        if path.exists() {
71            remove_dir_all(path)?;
72        }
73    }
74
75    // Get all new top level folders
76    let new_top_level_folders = archive
77        .file_names()
78        .filter_map(|name| name.split('/').next())
79        .collect::<HashSet<_>>();
80
81    // Remove all new top level addon folders.
82    for folder in new_top_level_folders {
83        let path = to_directory.join(&folder);
84
85        if path.exists() {
86            let _ = std::fs::remove_dir_all(path);
87        }
88    }
89
90    let mut toc_files = vec![];
91
92    for i in 0..archive.len() {
93        let mut file = archive.by_index(i)?;
94        #[allow(deprecated)]
95        let path = to_directory.join(file.sanitized_name());
96
97        if let Some(ext) = path.extension() {
98            if let Ok(remainder) = path.strip_prefix(to_directory) {
99                if ext == "toc" && remainder.components().count() == 2 {
100                    toc_files.push(path.clone());
101                }
102            }
103        }
104
105        if file.is_dir() {
106            std::fs::create_dir_all(&path)?;
107        } else {
108            if let Some(p) = path.parent() {
109                if !p.exists() {
110                    std::fs::create_dir_all(&p)?;
111                }
112            }
113            let mut outfile = std::fs::File::create(&path)?;
114            std::io::copy(&mut file, &mut outfile)?;
115        }
116    }
117
118    // Cleanup
119    std::fs::remove_file(&zip_path)?;
120
121    let mut addon_folders: Vec<_> = toc_files
122        .iter()
123        .filter_map(|p| parse_toc_path(&p))
124        .collect();
125    addon_folders.sort();
126
127    Ok(addon_folders)
128}