wormhole-common 0.2.0

Common code for the Wormhole mod manager project.
Documentation
use super::{InstanceJson, InstanceMod, KSPGame, KSP1_STEAM_API_SIZE, KSP2_STEAM_API_SIZE};

use crate::{
    finder::{find_ksp1_install_dir, find_ksp2_install_dir},
    util::{copy_dir_all, get_data_dir},
};

use serde::{Deserialize, Serialize};
use std::{
    fs::{remove_dir_all, File},
    io::{Read, Write},
    mem::replace,
    path::PathBuf,
};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Instance {
    pub id: i32,
    pub name: String,
    pub game: KSPGame,
    pub mods: Vec<InstanceMod>,
    pub install_path: PathBuf,
    pub description: Option<String>,
    pub time_played: Option<String>,
}

impl Instance {
    pub fn defaults() -> Vec<Self> {
        let v = vec![
            Instance {
                id: 0,
                name: "KSP2 Default Instance".to_string(),
                game: KSPGame::KSP2,
                install_path: find_ksp2_install_dir(),
                mods: Vec::new(),
                description: None,
                time_played: None,
            },
            Instance {
                id: 1,
                name: "KSP1 Default Instance".to_string(),
                game: KSPGame::KSP1,
                install_path: find_ksp1_install_dir(),
                mods: Vec::new(),
                description: None,
                time_played: None,
            },
        ];

        return Instance::validate_instances(v);
    }

    pub fn validate_instances(instances: Vec<Instance>) -> Vec<Instance> {
        let mut final_instances = Vec::new();

        for instance in instances {
            if instance.install_path.exists() {
                let api_dll = match instance.game {
                    KSPGame::KSP1 => instance
                        .install_path
                        .join("KSP_x64_Data/Plugins/x86_64/steam_api64.dll"),
                    KSPGame::KSP2 => instance
                        .install_path
                        .join("KSP2_x64_Data/Plugins/x86_64/steam_api64.dll`"),
                };

                let size = api_dll.metadata().unwrap().len();

                let needed_size = match instance.game {
                    KSPGame::KSP1 => KSP1_STEAM_API_SIZE,
                    KSPGame::KSP2 => KSP2_STEAM_API_SIZE,
                };

                if size == needed_size {
                    final_instances.push(instance);
                }
            }
        }

        return final_instances;
    }

    pub fn load() -> Vec<Self> {
        let instances;
        let instances_path = get_data_dir().join("instances.json");

        if instances_path.exists() {
            let mut file = File::open(instances_path).unwrap();
            let mut content = String::new();

            file.read_to_string(&mut content).unwrap();
            instances = serde_json::from_str(&content).unwrap();
        } else {
            instances = Instance::defaults();

            Instance::save_all(&instances);
        }

        return instances;
    }

    pub fn save_all(instances: &Vec<Self>) {
        let instances_path = get_data_dir().join("instances.json");
        let mut file = File::create(instances_path).unwrap();

        file.write_all(serde_json::to_string(&instances).unwrap().as_bytes())
            .unwrap();
    }

    pub fn save(&self) {
        let mut instances = Instance::load();

        for (index, instance) in instances.clone().iter().enumerate() {
            if instance.id == self.id {
                let _ = replace(&mut instances[index], self.clone());
            }
        }

        Instance::save_all(&instances);
    }

    pub fn from_id(id: i32) -> Option<Instance> {
        let instances = Instance::load();
        let instance = instances.iter().find(|i| i.id == id).cloned();

        return instance;
    }

    pub fn load_from_file(file_path: PathBuf) -> Option<Instance> {
        let mut file = File::open(file_path).unwrap();
        let mut content = String::new();

        file.read_to_string(&mut content).unwrap();

        let parsed = serde_json::from_str::<InstanceJson>(&content);

        if let Ok(instance_json) = parsed {
            return Instance::from_id(instance_json.id);
        }

        return None;
    }

    pub fn get_active_instance(game: KSPGame) -> Option<Instance> {
        let instance_info_file = (match game {
            KSPGame::KSP1 => find_ksp1_install_dir(),
            KSPGame::KSP2 => find_ksp2_install_dir(),
        })
        .join("instance.json");

        if instance_info_file.exists() {
            let instance = Instance::load_from_file(instance_info_file);

            return instance;
        }

        return None;
    }

    pub fn enable(&self) {
        let active = Instance::get_active_instance(self.game.clone());

        if let Some(instance) = active {
            instance.disable();
        }

        for instance_mod in self.mods.clone() {
            for path in instance_mod.paths {
                let local_path = self.install_path.join(path.clone());

                let saved_path = get_data_dir()
                    .join("instances")
                    .join(self.id.to_string())
                    .join(path);

                copy_dir_all(saved_path, local_path).unwrap();
            }
        }
    }

    pub fn disable(&self) {
        for instance_mod in self.mods.clone() {
            for path in instance_mod.paths {
                let local_path = self.install_path.join(path.clone());

                let saved_path = get_data_dir()
                    .join("instances")
                    .join(self.id.to_string())
                    .join(path);

                copy_dir_all(local_path.clone(), saved_path).unwrap();
                remove_dir_all(local_path).unwrap();
            }
        }
    }
}