1use std::{
2 fs::File,
3 io::{Read, Write},
4 path::PathBuf,
5};
6
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use crate::{common::open_file_with_vim, paths::get_share_path};
10
11#[derive(Clone, Debug, Serialize, Deserialize)]
12
13pub struct Config {
14 pub play_audio: bool,
15 pub show_images: bool,
16 pub download_media: bool,
17 #[serde(
18 serialize_with = "option_string_to_empty_string",
19 deserialize_with = "empty_string_to_option"
20 )]
21 pub git_remote: Option<String>,
22 #[serde(
23 serialize_with = "option_string_to_empty_string",
24 deserialize_with = "empty_string_to_option"
25 )]
26 pub gpt_key: Option<String>,
27}
28
29impl Config {
30 fn _config_path() -> PathBuf {
31 dirs::home_dir()
32 .unwrap()
33 .join(".config")
34 .join("speki")
35 .join("config.toml")
36 }
37
38 fn config_path() -> PathBuf {
39 get_share_path().join("config.toml")
40 }
41
42 pub fn edit_with_vim() {
43 open_file_with_vim(Self::config_path().as_path()).unwrap();
44 }
45
46 pub fn read_git_remote(&self) -> &Option<String> {
47 &self.git_remote
48 }
49
50 pub fn save(&self) -> std::io::Result<()> {
52 let toml = toml::to_string(&self).expect("Failed to serialize config");
53 let mut file = File::create(Self::config_path())?;
54 file.write_all(toml.as_bytes())?;
55 Ok(())
56 }
57
58 pub fn load() -> std::io::Result<Config> {
60 let mut file = match File::open(Self::config_path()) {
61 Ok(file) => file,
62 Err(_) => {
63 let _ =
64 std::fs::rename(Self::config_path(), get_share_path().join("invalid_config"));
65 Self::default().save()?;
66 File::open(Self::config_path())?
67 }
68 };
69
70 let mut contents = String::new();
71 file.read_to_string(&mut contents)?;
72 let config: Config = toml::from_str(&contents).expect("Failed to deserialize config");
73 Ok(config)
74 }
75}
76
77impl Default for Config {
78 fn default() -> Self {
79 Self {
80 play_audio: true,
81 show_images: true,
82 download_media: true,
83 git_remote: None,
84 gpt_key: None,
85 }
86 }
87}
88
89fn option_string_to_empty_string<S>(
90 value: &Option<String>,
91 serializer: S,
92) -> Result<S::Ok, S::Error>
93where
94 S: Serializer,
95{
96 match value {
97 Some(v) => serializer.serialize_str(v),
98 None => serializer.serialize_str(""),
99 }
100}
101
102fn empty_string_to_option<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
103where
104 D: Deserializer<'de>,
105{
106 let s: String = Deserialize::deserialize(deserializer)?;
107 Ok(if s.is_empty() { None } else { Some(s) })
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn foo() {
116 let x = Config::config_path();
117 dbg!(x);
118 }
119}