1use crate::catalog;
2use crate::error::FilesystemError;
3use crate::repository::CompressionFormat;
4use glob::MatchOptions;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fmt::{self, Display, Formatter};
8use std::fs::create_dir_all;
9use std::path::{Path, PathBuf};
10
11mod addons;
12mod wow;
13
14use crate::fs::PersistentData;
15
16pub use crate::config::addons::Addons;
17pub use crate::config::wow::{Flavor, Wow};
18
19#[derive(Deserialize, Serialize, Debug, PartialEq, Default, Clone)]
21pub struct Config {
22 #[serde(default)]
23 pub wow: Wow,
24
25 #[serde(default)]
26 pub addons: Addons,
27
28 pub theme: Option<String>,
29
30 #[serde(default)]
31 pub column_config: ColumnConfig,
32
33 pub window_size: Option<(u32, u32)>,
34
35 pub scale: Option<f64>,
36
37 pub backup_directory: Option<PathBuf>,
38
39 #[serde(default)]
40 pub backup_addons: bool,
41
42 #[serde(default)]
43 pub backup_wtf: bool,
44
45 #[serde(default)]
46 pub hide_ignored_addons: bool,
47
48 #[serde(default)]
49 pub self_update_channel: SelfUpdateChannel,
50
51 #[serde(default)]
52 pub weak_auras_account: HashMap<Flavor, String>,
53
54 #[serde(default = "default_true")]
55 pub alternating_row_colors: bool,
56
57 #[serde(default)]
58 pub language: Language,
59
60 #[serde(default)]
61 pub catalog_source: Option<catalog::Source>,
62
63 #[serde(default)]
64 pub auto_update: bool,
65
66 #[serde(default)]
67 pub compression_format: CompressionFormat,
68}
69
70impl Config {
71 pub fn get_flavor_directory_for_flavor(&self, flavor: &Flavor, path: &Path) -> PathBuf {
73 path.join(&flavor.folder_name())
74 }
75
76 pub fn get_root_directory_for_flavor(&self, flavor: &Flavor) -> Option<PathBuf> {
78 if let Some(flavor_dir) = self.wow.directories.get(flavor) {
79 Some(flavor_dir.parent().unwrap().to_path_buf())
80 } else {
81 None
82 }
83 }
84
85 pub fn get_addon_directory_for_flavor(&self, flavor: &Flavor) -> Option<PathBuf> {
88 let dir = self.wow.directories.get(flavor);
89 match dir {
90 Some(dir) => {
91 let mut addon_dir = dir.join("Interface/AddOns");
93
94 if !addon_dir.exists() {
97 let options = MatchOptions {
98 case_sensitive: false,
99 ..Default::default()
100 };
101
102 let pattern = format!("{}/?nterface/?ddons", dir.display());
105
106 for entry in glob::glob_with(&pattern, options).unwrap() {
107 if let Ok(path) = entry {
108 addon_dir = path;
109 }
110 }
111 }
112
113 if dir.exists() && !addon_dir.exists() {
117 let _ = create_dir_all(&addon_dir);
118 }
119
120 Some(addon_dir)
121 }
122 None => None,
123 }
124 }
125
126 pub fn get_download_directory_for_flavor(&self, flavor: Flavor) -> Option<PathBuf> {
130 self.wow.directories.get(&flavor).cloned()
131 }
132
133 pub fn get_wtf_directory_for_flavor(&self, flavor: &Flavor) -> Option<PathBuf> {
136 let dir = self.wow.directories.get(flavor);
137 match dir {
138 Some(dir) => {
139 let mut addon_dir = dir.join("WTF");
141
142 if !addon_dir.exists() {
145 let options = MatchOptions {
146 case_sensitive: false,
147 ..Default::default()
148 };
149
150 let pattern = format!("{}/?tf", dir.display());
153
154 for entry in glob::glob_with(&pattern, options).unwrap() {
155 if let Ok(path) = entry {
156 addon_dir = path;
157 }
158 }
159 }
160
161 Some(addon_dir)
162 }
163 None => None,
164 }
165 }
166}
167
168impl PersistentData for Config {
169 fn relative_path() -> PathBuf {
170 PathBuf::from("ajour.yml")
171 }
172}
173
174#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
175pub enum ColumnConfig {
176 V1 {
177 local_version_width: u16,
178 remote_version_width: u16,
179 status_width: u16,
180 },
181 V2 {
182 columns: Vec<ColumnConfigV2>,
183 },
184 V3 {
185 my_addons_columns: Vec<ColumnConfigV2>,
186 catalog_columns: Vec<ColumnConfigV2>,
187 #[serde(default)]
188 aura_columns: Vec<ColumnConfigV2>,
189 },
190}
191
192#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
193pub struct ColumnConfigV2 {
194 pub key: String,
195 pub width: Option<u16>,
196 pub hidden: bool,
197}
198
199impl Default for ColumnConfig {
200 fn default() -> Self {
201 ColumnConfig::V1 {
202 local_version_width: 150,
203 remote_version_width: 150,
204 status_width: 85,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
210pub enum SelfUpdateChannel {
211 Stable,
212 Beta,
213}
214
215impl SelfUpdateChannel {
216 pub const fn all() -> [Self; 2] {
217 [SelfUpdateChannel::Stable, SelfUpdateChannel::Beta]
218 }
219}
220
221impl Default for SelfUpdateChannel {
222 fn default() -> Self {
223 SelfUpdateChannel::Stable
224 }
225}
226
227impl Display for SelfUpdateChannel {
228 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
229 let s = match self {
230 SelfUpdateChannel::Stable => "Stable",
231 SelfUpdateChannel::Beta => "Beta",
232 };
233
234 write!(f, "{}", s)
235 }
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Hash, PartialOrd, Ord)]
239pub enum Language {
240 Czech,
241 Norwegian,
242 English,
243 Danish,
244 German,
245 French,
246 Hungarian,
247 Portuguese,
248 Russian,
249 Slovak,
250 Swedish,
251 Spanish,
252 Turkish,
253 Ukrainian,
254}
255
256impl std::fmt::Display for Language {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 write!(
259 f,
260 "{}",
261 match self {
262 Language::Czech => "Čeština",
263 Language::Danish => "Dansk",
264 Language::English => "English",
265 Language::French => "Français",
266 Language::German => "Deutsch",
267 Language::Hungarian => "Magyar",
268 Language::Norwegian => "Norsk Bokmål",
269 Language::Portuguese => "Português",
270 Language::Russian => "Pусский",
271 Language::Slovak => "Slovenčina",
272 Language::Spanish => "Español",
273 Language::Swedish => "Svenska",
274 Language::Turkish => "Türkçe",
275 Language::Ukrainian => "Yкраїнська",
276 }
277 )
278 }
279}
280
281impl Language {
282 pub const ALL: [Language; 14] = [
284 Language::Czech,
285 Language::Danish,
286 Language::German,
287 Language::English,
288 Language::Spanish,
289 Language::French,
290 Language::Hungarian,
291 Language::Norwegian,
292 Language::Portuguese,
293 Language::Russian,
294 Language::Slovak,
295 Language::Swedish,
296 Language::Turkish,
297 Language::Ukrainian,
298 ];
299
300 pub const fn language_code(self) -> &'static str {
301 match self {
302 Language::Czech => "cs_CZ",
303 Language::English => "en_US",
304 Language::Danish => "da_DK",
305 Language::German => "de_DE",
306 Language::French => "fr_FR",
307 Language::Russian => "ru_RU",
308 Language::Swedish => "se_SE",
309 Language::Spanish => "es_ES",
310 Language::Hungarian => "hu_HU",
311 Language::Norwegian => "nb_NO",
312 Language::Slovak => "sk_SK",
313 Language::Turkish => "tr_TR",
314 Language::Portuguese => "pt_PT",
315 Language::Ukrainian => "uk_UA",
316 }
317 }
318}
319
320impl Default for Language {
321 fn default() -> Language {
322 Language::English
323 }
324}
325
326pub async fn load_config() -> Result<Config, FilesystemError> {
330 log::debug!("loading config");
331
332 Ok(Config::load_or_default()?)
333}
334
335const fn default_true() -> bool {
336 true
337}