helixlauncher_core/
config.rs

1//! Initial config support for libhelix
2//!
3//! TODO:
4//! - Allow for users to provide their own path
5//! - make sure get_base_path doesn't panic
6//! - add fields the rest of the fields into Config
7
8use serde::{Deserialize, Serialize};
9use std::env;
10use std::path::PathBuf;
11use std::{
12    fs::{self, File},
13    io,
14};
15
16pub const CONFIG_NAME: &str = "libhelix_config.json";
17
18#[derive(Debug, Deserialize, Serialize)]
19pub struct Config {
20    // the path doesn't need to be saved in the file,
21    // but it is useful to keep around imo
22    #[serde(skip)]
23    path: PathBuf, // profiles/instances
24                   // settings... feel free to add fields as required
25}
26
27impl Config {
28    pub fn new(name: &str) -> Result<Self, Error> {
29        // TODO allow the user to provide their own path
30        let mut path = get_base_path();
31        path.push(name);
32
33        let config = if !path.exists() {
34            // Could return an error if the dir already exists,
35            // but we don't care about that
36            if let Err(e) = fs::create_dir_all(&path) {
37                if e.kind() == io::ErrorKind::PermissionDenied {
38                    return Err(Error::PermissionDenied(e));
39                }
40            }
41
42            let conf = Self::default_config(path);
43            conf.save_config()?;
44            conf
45        } else {
46            Self::read_config(path)?
47        };
48
49        Ok(config)
50    }
51
52    pub fn save_config(&self) -> Result<(), Error> {
53        let filepath = self.path.join(CONFIG_NAME);
54
55        let mut file = File::options().write(true).create(true).open(filepath)?;
56        serde_json::to_writer_pretty(&mut file, self)?;
57
58        Ok(())
59    }
60
61    pub fn read_config<P: Into<PathBuf>>(path: P) -> io::Result<Self> {
62        let path: PathBuf = path.into();
63
64        let file = File::open(path.join(CONFIG_NAME))?;
65        let mut read: Self = serde_json::from_reader(file)?;
66        read.path = path;
67
68        Ok(read)
69    }
70
71    fn default_config(path: PathBuf) -> Self {
72        Self { path }
73    }
74}
75
76#[derive(Debug, thiserror::Error)]
77pub enum Error {
78    #[error("Could not find config file")]
79    ConfigNotFound,
80    #[error("Permission denied: {0}")]
81    PermissionDenied(io::Error),
82    #[error("An IO error occurred {0}")]
83    IoError(io::Error),
84    #[error("Serialization failed: {0}")]
85    SerializeFailed(serde_json::Error),
86    #[error("Deserialization failed: {0}")]
87    DeserializeFailed(serde_json::Error),
88}
89
90impl From<io::Error> for Error {
91    fn from(e: io::Error) -> Self {
92        use io::ErrorKind;
93
94        match e.kind() {
95            ErrorKind::NotFound => Self::ConfigNotFound,
96            ErrorKind::PermissionDenied => Self::PermissionDenied(e),
97            _ => Self::IoError(e),
98        }
99    }
100}
101
102impl From<serde_json::Error> for Error {
103    fn from(e: serde_json::Error) -> Self {
104        use serde_json::error::Category;
105
106        match e.classify() {
107            Category::Syntax | Category::Data | Category::Eof => Self::DeserializeFailed(e),
108            Category::Io => Self::IoError(e.into()),
109        }
110    }
111}
112
113fn get_base_path() -> PathBuf {
114    // This logic isn't perfect and could crash in its current state
115    if cfg!(windows) {
116        env::var("APPDATA").unwrap().into()
117    } else {
118        let home = env::var("HOME");
119
120        match home {
121            Ok(ok) => {
122                let mut path: PathBuf = ok.into();
123                path.push(".local");
124                path.push("share");
125                path
126            }
127            Err(_) => env::current_dir().unwrap(),
128        }
129    }
130}