use crate::{game::ModCfg, Error::*, Result};
use serde::{Deserialize, Serialize};
use std::{
collections::HashSet,
ffi::OsStr,
fs::{self, File},
io::{BufRead, BufReader, BufWriter, Write},
path::Path,
};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Preset {
name: String,
mods: Vec<String>,
enabled: bool,
}
impl Preset {
pub fn list(presets_dir: &Path) -> Result<impl Iterator<Item = String>> {
Ok(fs::read_dir(presets_dir)?
.filter_map(|f| f.ok().map(|f| f.path())) .filter(|f| f.is_file() && f.extension().unwrap_or(OsStr::new("")) == "json") .filter_map(|f| {
f.with_extension("")
.file_name()
.and_then(OsStr::to_str)
.map(|f| f.to_string())
}))
}
pub fn new(name: String, mods: Vec<String>) -> Self {
Preset {
name,
mods,
enabled: false,
}
}
pub fn save<W: Write>(&self, mut writer: W) -> Result<()> {
serde_json::to_writer_pretty(&mut writer, self)?;
writer.flush()?;
Ok(())
}
pub fn save_to_path(&self, presets_dir: &Path) -> Result<()> {
let file = File::create(presets_dir.join(&self.name).with_extension("json"))?;
let writer = BufWriter::new(file);
self.save(writer)
}
pub fn load<R: BufRead>(reader: R) -> Result<Self> {
Ok(serde_json::from_reader(reader)?)
}
pub fn load_from_path(name: &str, presets_dir: &Path) -> Result<Self> {
let preset_path = presets_dir.join(name).with_extension("json");
if preset_path.try_exists()? {
let file = File::open(preset_path)?;
let reader = BufReader::new(file);
Self::load(reader)
} else {
Err(MissingPreset {
dir: presets_dir.into(),
preset: name.into(),
})
}
}
pub fn delete(name: &str, presets_dir: &Path) -> Result<()> {
fs::remove_file(presets_dir.join(name).with_extension("json"))?;
Ok(())
}
pub fn add_mod(&mut self, mod_name: &str) {
self.mods.push(String::from(mod_name))
}
pub fn add_mods(&mut self, mods: &[String]) {
self.mods.extend(mods.iter().cloned())
}
pub fn remove_mod(&mut self, mod_name: &str) {
self.mods.retain(|m| m != mod_name)
}
pub fn remove_mods(&mut self, mods: &[String]) {
let values_to_remove: HashSet<&String> = mods.iter().collect();
self.mods.retain(|m| !values_to_remove.contains(m))
}
pub fn enable(&mut self) {
self.enabled = true
}
pub fn disable(&mut self, mod_config: &mut ModCfg) -> Result<()> {
mod_config.set_mods_active(&self.mods, false)?;
self.enabled = false;
Ok(())
}
pub fn force_disable(&mut self, mod_config: &mut ModCfg) {
self.enabled = false;
for mod_name in &self.mods {
let _ = mod_config.set_mod_active(mod_name, false);
}
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn get_mods(&self) -> &Vec<String> {
&self.mods
}
#[cfg_attr(coverage_nightly, coverage(off))]
pub fn exists(name: &str, presets_dir: &Path) -> bool {
presets_dir.join(name).with_extension("json").exists()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::MockData;
#[test]
fn listing_presets() {
let mock = MockData::new();
let presets = Preset::list(&mock.presets_dir).unwrap().collect::<Vec<_>>();
assert_eq!(presets, vec!["preset1", "preset2"]);
}
#[test]
fn creating_preset() {
let mods = vec!["mod1".into(), "mod2".into()];
let preset = Preset::new("preset3".into(), mods.clone());
assert_eq!(preset.get_mods(), &mods);
}
#[test]
fn saving_and_loading_preset() {
let mock = MockData::new();
let mods = vec!["mod1".into(), "mod2".into()];
let preset = Preset::new("preset3".into(), mods);
preset.save_to_path(&mock.presets_dir).unwrap();
assert!(mock.presets_dir.join("preset3.json").exists());
let loaded_preset = Preset::load_from_path("preset3", &mock.presets_dir).unwrap();
assert_eq!(loaded_preset, preset);
}
#[test]
fn load_missing_preset() {
let mock = MockData::new();
let result = Preset::load_from_path("missing_preset", &mock.presets_dir);
assert!(matches!(result, Err(MissingPreset { .. })));
}
#[test]
fn deleting_preset() {
let mock = MockData::new();
Preset::delete("preset1", &mock.presets_dir).unwrap();
let presets = Preset::list(&mock.presets_dir).unwrap().collect::<Vec<_>>();
assert_eq!(presets, vec!["preset2"]);
}
#[test]
fn adding_mods() {
let mock = MockData::new();
let mut preset = mock.preset1;
preset.add_mod("mod2");
preset.add_mods(&["mod3".into(), "mod4".into()]);
assert_eq!(preset.get_mods(), &["mod1", "mod2", "mod3", "mod4"]);
}
#[test]
fn removing_mods() {
let mut preset = Preset::new(
"preset5".into(),
vec!["mod1".into(), "mod2".into(), "mod3".into()],
);
preset.remove_mod("mod2");
preset.remove_mods(&["mod1".into(), "mod4".into()]);
assert_eq!(preset.get_mods(), &["mod3"]);
}
#[test]
fn enabling_preset() {
let mock = MockData::new();
let mut preset = mock.preset2;
preset.enable();
preset.save_to_path(&mock.presets_dir).unwrap();
let loaded_preset = Preset::load_from_path("preset2", &mock.presets_dir).unwrap();
assert!(loaded_preset.is_enabled());
}
#[test]
fn disabling_preset() {
let mock = MockData::new();
let mut mod_cfg = mock.modcfg;
let mut preset = mock.preset1;
preset.disable(&mut mod_cfg).unwrap();
assert!(!preset.is_enabled());
assert!(!mod_cfg.is_mod_active("mod1").unwrap());
}
#[test]
fn force_disabling_preset() {
let mock = MockData::new();
let mut mod_cfg = mock.modcfg;
let mut preset = mock.preset1;
preset.add_mod("FakeMod");
preset.force_disable(&mut mod_cfg);
assert!(!preset.is_enabled());
assert!(!mod_cfg.is_mod_active("mod1").unwrap());
}
}