z_mirror/fmm

git clone https://git.raiguard.me/z_mirror/fmm

raiguardAppease clippy0dfe100

rust-ice
4.0 KiB131 linesraw
1use anyhow::{anyhow, Result};
2use directories::BaseDirs;
3use serde::Deserialize;
4use serde_with::{serde_as, DisplayFromStr};
5use std::collections::HashMap;
6use std::fs;
7use std::path::PathBuf;
8use thiserror::Error;
9
10use crate::types::ModIdent;
11use crate::App;
12
13#[derive(Debug)]
14pub struct Config {
15    pub auto_download: bool,
16    pub game_dir: Option<PathBuf>,
17    pub mods_dir: PathBuf,
18    pub portal_auth: Option<PortalAuth>,
19    pub sets: ModSets,
20    pub sync_latest_versions: bool,
21}
22
23impl Config {
24    pub fn new(app: &App) -> Result<Self> {
25        // Input
26        let config_file = ConfigFile::new(&app.config)?.unwrap_or_default();
27
28        // Merge config options
29        let game_dir = app.game_dir.clone().or(config_file.game_dir);
30        Ok(Config {
31            auto_download: config_file.auto_download,
32            game_dir: game_dir.clone(),
33            mods_dir: match [
34                game_dir.clone(),
35                app.mods_dir.clone().or(config_file.mods_dir),
36            ] {
37                [_, Some(mods_dir)] if mods_dir.exists() => mods_dir,
38                [Some(game_dir), None] if game_dir.exists() => {
39                    let mut mods_dir = game_dir;
40                    mods_dir.push("mods");
41                    if !mods_dir.exists() {
42                        return Err(anyhow!("Could not find mods directory"));
43                    }
44                    mods_dir
45                }
46                _ => return Err(anyhow!("Invalid game or mods directories")),
47            },
48            portal_auth: config_file.portal.or_else(|| {
49                if let Some(game_dir) = &game_dir {
50                    let mut player_data_path = game_dir.clone();
51                    player_data_path.push("player-data.json");
52                    if player_data_path.exists() {
53                        let player_data_json = fs::read_to_string(&player_data_path).ok()?;
54                        if let PlayerDataJson {
55                            service_token: Some(token),
56                            service_username: Some(username),
57                        } = serde_json::from_str(&player_data_json).ok()?
58                        {
59                            return Some(PortalAuth { token, username });
60                        }
61                    }
62                }
63                None
64            }),
65            sets: config_file.sets,
66            sync_latest_versions: config_file.sync_latest_versions,
67        })
68    }
69}
70
71#[derive(Debug, Deserialize)]
72pub struct PortalAuth {
73    pub token: String,
74    pub username: String,
75}
76
77pub type ModSets = Option<HashMap<String, Vec<ModIdent>>>;
78
79#[serde_as]
80#[derive(Deserialize, Default)]
81struct ConfigFile {
82    #[serde(default)]
83    auto_download: bool,
84    game_dir: Option<PathBuf>,
85    mods_dir: Option<PathBuf>,
86    portal: Option<PortalAuth>,
87    #[serde_as(as = "Option<HashMap<_, Vec<DisplayFromStr>>>")]
88    sets: ModSets,
89    #[serde(default)]
90    sync_latest_versions: bool,
91}
92
93impl ConfigFile {
94    pub fn new(path: &Option<PathBuf>) -> Result<Option<Self>, ConfigFileErr> {
95        let config_path: Option<PathBuf> = path
96            .clone()
97            .or_else(|| {
98                BaseDirs::new().map(|base_dirs| {
99                    let mut config_path: PathBuf = base_dirs.config_dir().into();
100                    config_path.push("fmm");
101                    config_path.push("fmm.toml");
102                    config_path
103                })
104            })
105            .filter(|config_path| config_path.exists());
106
107        if config_path.is_none() {
108            return Ok(None);
109        }
110
111        let file = fs::read_to_string(config_path.unwrap()).map_err(|_| ConfigFileErr::Open)?;
112
113        let config: ConfigFile = toml::from_str(&file).map_err(|_| ConfigFileErr::ParseFile)?;
114        Ok(Some(config))
115    }
116}
117
118#[derive(Debug, Error)]
119pub enum ConfigFileErr {
120    #[error("Could not open config file.")]
121    Open,
122    #[error("Could not parse config file.")]
123    ParseFile,
124}
125
126#[derive(Deserialize)]
127#[serde(rename_all = "kebab-case")]
128struct PlayerDataJson {
129    service_username: Option<String>,
130    service_token: Option<String>,
131}