1use std::{
2 borrow::Cow,
3 error::Error,
4 fs::File,
5 io::{BufReader, BufWriter},
6 path::{Path, PathBuf},
7};
8
9use serde::{Deserialize, Serialize};
10use thedes_tui_core::audio::Volume;
11use thiserror::Error;
12use tokio::{io, task};
13use tracing::Level;
14
15#[derive(Debug, Error)]
16pub enum LoadErrorSource {
17 #[error("I/O error happened")]
18 Io(#[from] io::Error),
19 #[error("Failed to deserialize")]
20 Deserialize(#[from] serde_json::Error),
21}
22
23#[derive(Debug, Error)]
24#[error("Failed to load game from {path}")]
25pub struct LoadError {
26 pub path: PathBuf,
27 #[source]
28 pub source: LoadErrorSource,
29}
30
31#[derive(Debug, Error)]
32pub enum SaveErrorSource {
33 #[error("I/O error happened")]
34 Io(#[from] io::Error),
35 #[error("Failed to serialize")]
36 Serialize(#[from] serde_json::Error),
37}
38
39#[derive(Debug, Error)]
40#[error("Failed to save game to {path}")]
41pub struct SaveError {
42 pub path: PathBuf,
43 #[source]
44 pub source: SaveErrorSource,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
48pub enum AudioSinkType {
49 Music,
50 Fx,
51}
52
53impl AudioSinkType {
54 pub const fn name(self) -> &'static str {
55 match self {
56 Self::Music => "Music",
57 Self::Fx => "FX",
58 }
59 }
60}
61
62impl From<AudioSinkType> for &'static str {
63 fn from(value: AudioSinkType) -> Self {
64 value.name()
65 }
66}
67
68impl From<AudioSinkType> for Cow<'static, str> {
69 fn from(value: AudioSinkType) -> Self {
70 <&'static str>::from(value).into()
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct AudioSettings {
76 music: Volume,
77 fx: Volume,
78}
79
80impl Default for AudioSettings {
81 fn default() -> Self {
82 Self { music: 125, fx: 190 }
83 }
84}
85
86impl AudioSettings {
87 const VOLUME_STEP: u8 = 15;
88
89 pub fn volume(&self, controller_type: AudioSinkType) -> u8 {
90 match controller_type {
91 AudioSinkType::Music => self.music,
92 AudioSinkType::Fx => self.fx,
93 }
94 }
95
96 pub fn set_volume(&mut self, controller_type: AudioSinkType, value: u8) {
97 match controller_type {
98 AudioSinkType::Music => self.music = value,
99 AudioSinkType::Fx => self.fx = value,
100 }
101 }
102
103 pub fn increase_volume(&mut self, controller_type: AudioSinkType) {
104 self.set_volume(
105 controller_type,
106 self.volume(controller_type).saturating_add(Self::VOLUME_STEP),
107 );
108 }
109
110 pub fn decrease_volume(&mut self, controller_type: AudioSinkType) {
111 self.set_volume(
112 controller_type,
113 self.volume(controller_type).saturating_sub(Self::VOLUME_STEP),
114 );
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub struct Settings {
120 audio: AudioSettings,
121}
122
123impl Default for Settings {
124 fn default() -> Self {
125 Self { audio: AudioSettings::default() }
126 }
127}
128
129impl Settings {
130 pub async fn load(path: &Path) -> Result<Self, LoadError> {
131 if tracing::event_enabled!(Level::DEBUG) {
132 tracing::debug!(
133 path = path.display().to_string(),
134 "Loading settings"
135 );
136 }
137 task::block_in_place(|| {
138 let file =
139 File::open(&path).map_err(LoadErrorSource::from).map_err(
140 |source| LoadError { path: path.to_owned(), source },
141 )?;
142 let mut file = BufReader::new(file);
143 serde_json::from_reader(&mut file)
144 .map_err(LoadErrorSource::from)
145 .map_err(|source| LoadError { path: path.to_owned(), source })
146 })
147 }
148
149 pub async fn load_or_default(path: &Path) -> Self {
150 match Self::load(path).await {
151 Ok(this) => this,
152 Err(e) => {
153 let mut chain = String::new();
154 let mut next = Some(&e as &(dyn Error + 'static));
155 while let Some(current) = next {
156 chain.push_str(¤t.to_string());
157 chain.push_str("\n");
158 next = current.source();
159 }
160 let path = path.display().to_string();
161 tracing::error!(
162 %chain,
163 %path,
164 "Failed to load configuration",
165 );
166 Self::default()
167 },
168 }
169 }
170
171 pub async fn save(&self, path: &Path) -> Result<(), SaveError> {
172 if tracing::event_enabled!(Level::DEBUG) {
173 tracing::debug!(
174 path = path.display().to_string(),
175 "Saving settings"
176 );
177 }
178 task::block_in_place(|| {
179 let file =
180 File::create(&path).map_err(SaveErrorSource::from).map_err(
181 |source| SaveError { path: path.to_owned(), source },
182 )?;
183 let mut file = BufWriter::new(file);
184 serde_json::to_writer(&mut file, self)
185 .map_err(SaveErrorSource::from)
186 .map_err(|source| SaveError {
187 path: path.to_owned(),
188 source,
189 })?;
190 Ok(())
191 })
192 }
193
194 pub fn audio(&self) -> &AudioSettings {
195 &self.audio
196 }
197
198 pub fn audio_mut(&mut self) -> &mut AudioSettings {
199 &mut self.audio
200 }
201}