1use crate::calendar::Calendar;
2use crate::calendar::CalendarLanguagePair;
3use crate::db_sqlite;
4use crate::language::Language;
5use crate::prelude::Error as AppError;
6use crate::prelude::Result as AppResult;
7use arc_swap::ArcSwap;
8use once_cell::sync::OnceCell;
9use serde::{Deserialize, Serialize};
10use std::path::Path;
11use std::sync::Arc;
12use std::{fs, path::PathBuf};
13
14pub static CONFIG: OnceCell<ArcSwap<Config>> = OnceCell::new();
17
18pub fn set_config(new_cfg: Config) {
19 if CONFIG.get().is_none() {
20 CONFIG.set(ArcSwap::from_pointee(new_cfg.clone())).unwrap();
21 } else {
22 CONFIG.get().unwrap().store(Arc::new(new_cfg.clone()));
23 }
24}
25
26pub fn reload_config_file() {
27 println!("reloading config file...");
28 let config_path = get_config_path();
29 if let Ok(config) = load_from_filepath(config_path) {
30 set_config(config);
31 println!("success.");
32 } else {
33 println!("failed.");
34 }
35}
36
37pub fn read_config_file_or_save_default_config_file() -> Result<Config, AppError> {
38 let config_path = get_config_path();
39
40 if let Ok(config) = load_from_filepath(config_path) {
41 println!("config file available and ok");
42 println!("config: {config:?}");
43 Ok(config)
44 } else {
45 println!("no config file or syntax error!");
48 let default_config = Config::default();
49 println!("saving default config: {default_config:?}");
50 save_config(default_config.clone())?;
51 println!("save successful.");
53 Ok(default_config)
54 }
55}
56
57pub fn check_database_file_is_valid_or_create_database_file(db_path: &str) -> Result<(), AppError> {
58 if db_sqlite::is_correct_db(db_path) {
59 Ok(())
60 } else {
61 db_sqlite::create_db(db_path)
62 }
63}
64
65pub fn get_config() -> Config {
66 let gaurd = CONFIG.get_or_init(|| {
67 println!("Init CONFIG (global OnceCell first run init)");
68 let new_cfg = read_config_file_or_save_default_config_file().unwrap();
70 if let Some(parent) = Path::new(&new_cfg.database).parent() {
72 fs::create_dir_all(parent)
73 .map_err(|_| AppError::DatabaseFileCreateError)
74 .unwrap();
75 }
76
77 ArcSwap::from_pointee(new_cfg)
82 });
83 let gaurd = gaurd.load();
84 gaurd.get_copy()
85}
86
87pub fn move_database<P: AsRef<str>>(filepath: P) -> Result<(), AppError> {
88 let filepath = filepath.as_ref();
89 let mut config = get_config();
90 let current_db_path = config.database;
91 let current_db_valid = db_sqlite::is_correct_db(¤t_db_path);
92 let exists = Path::new(filepath).exists();
93 if !exists {
94 if !current_db_valid {
95 println!("Attempting to creating new database file: {}", filepath);
97 db_sqlite::create_db(filepath)
98 } else {
99 if let Some(parent) = Path::new(filepath).parent() {
102 fs::create_dir_all(parent).map_err(|_| AppError::DatabaseFileCopyError)?;
103 }
104 std::fs::copy(¤t_db_path, filepath)
106 .map_err(|_| AppError::DatabaseFileCopyError)?;
107 config.database = filepath.to_string();
109 set_config(config.clone());
110 save_config(config)?;
111 std::fs::remove_file(¤t_db_path)
113 .map(|_| ())
114 .map_err(|_| AppError::DatabaseFileRemoveError)
115 }
116 } else {
117 Err(AppError::DatabaseFileNotEmptyError)
118 }
119}
120
121pub fn open_database<P: AsRef<str>>(filepath: P) -> Result<(), AppError> {
122 let filepath = filepath.as_ref();
123 let mut config = get_config();
124 let exists = Path::new(filepath).exists();
125 if !exists {
126 Err(AppError::DatabaseFileDontExistsError)
127 } else {
128 let db_valid = db_sqlite::is_correct_db(filepath);
129 if !db_valid {
130 Err(AppError::DatabaseFileInvalidError)
131 } else {
132 config.database = filepath.to_string();
135 set_config(config.clone());
136 save_config(config)
137 }
138 }
139}
140
141pub fn set_database_file(filepath: String) -> Result<(), AppError> {
145 let mut config = get_config();
146 let current_db_path = config.database;
147 let current_db_valid = db_sqlite::is_correct_db(¤t_db_path);
148 let exists = Path::new(&filepath).exists();
149 let valid = db_sqlite::is_correct_db(&filepath);
150 if !exists {
151 if !current_db_valid {
152 println!("Attempting to creating new database file: {}", filepath);
154 db_sqlite::create_db(&filepath)
155 } else {
156 if let Some(parent) = Path::new(&filepath).parent() {
159 fs::create_dir_all(parent).map_err(|_| AppError::DatabaseFileCopyError)?;
160 }
161 std::fs::copy(¤t_db_path, &filepath)
163 .map_err(|_| AppError::DatabaseFileCopyError)?;
164 config.database = filepath;
166 set_config(config.clone());
167 save_config(config)?;
168 std::fs::remove_file(¤t_db_path)
170 .map(|_| ())
171 .map_err(|_| AppError::DatabaseFileRemoveError)
172 }
173 } else if exists && valid {
174 config.database = filepath;
177 set_config(config.clone());
178 save_config(config)
179 } else {
180 Err(AppError::DatabaseFileInvalidError)
181 }
182}
183
184pub fn set_main_cal_config(
185 main_calendar_type: String,
186 main_calendar_language: String,
187 main_calendar_start_weekday: String,
188 weekdates_display_direction: String,
189) -> Result<(), AppError> {
190 let mut config = get_config();
191 config.main_calendar_type = main_calendar_type;
192 config.main_calendar_language = main_calendar_language;
193 config.main_calendar_start_weekday = main_calendar_start_weekday;
194 config.weekdates_display_direction = weekdates_display_direction;
195 set_config(config.clone());
196 save_config(config)
197}
198
199pub fn set_secondary_cal_config(
200 secondary_calendar_type: Option<String>,
201 secondary_calendar_language: Option<String>,
202) -> Result<(), AppError> {
203 let mut config = get_config();
204 config.secondary_calendar_type = secondary_calendar_type;
205 config.secondary_calendar_language = secondary_calendar_language;
206 set_config(config.clone());
207 save_config(config)
208}
209
210pub fn set_items_display_direction_config(items_direction: String) -> Result<(), AppError> {
211 let mut config = get_config();
212 config.items_display_direction = items_direction;
213 set_config(config.clone());
214 save_config(config)
215}
216
217pub fn save_config(config: Config) -> Result<(), AppError> {
218 let toml_str = toml::to_string(&config).map_err(|e| {
219 println!("Failed to serialize config to TOML: {}", e);
220 AppError::ConfigTomlGenerateError
221 })?;
222
223 let config_path = get_config_path();
224 println!(
225 "Attempting to save config to: {}",
226 config_path.to_string_lossy()
227 );
228
229 if let Some(parent) = config_path.parent() {
231 println!("Creating directory if needed: {}", parent.to_string_lossy());
232 fs::create_dir_all(parent).map_err(|e| {
233 println!("Failed to create directory: {}", e);
234 AppError::ConfigFileSaveError
235 })?;
236 }
237
238 fs::write(&config_path, toml_str).map_err(|e| {
240 println!("Failed to write config file: {}", e);
241 println!("Path: {}", config_path.to_string_lossy());
242 AppError::ConfigFileSaveError
243 })
244}
245
246pub fn get_main_cal_lang_pair() -> CalendarLanguagePair {
247 let calendar: Calendar = get_config().main_calendar_type.into();
248 let language: Language = get_config().main_calendar_language.into();
249 CalendarLanguagePair { calendar, language }
250}
251
252pub fn get_second_cal_lang_pair() -> Option<CalendarLanguagePair> {
253 get_config().secondary_calendar_type.map(|cal| {
254 let language: Language = get_config()
255 .secondary_calendar_language
256 .unwrap_or_default()
257 .into();
258 let calendar: Calendar = cal.into();
259 CalendarLanguagePair { calendar, language }
260 })
261}
262
263fn default_config_data_path() -> AppResult<(PathBuf, PathBuf)> {
264 if let Some(proj_dirs) = directories::ProjectDirs::from("", "", "ThisWeek") {
266 let config_path = proj_dirs.config_dir().join("config.toml");
268 let data_path = proj_dirs.data_dir().join("thisweek.db");
272 Ok((config_path, data_path))
274 } else {
275 eprintln!("Could not determine project directories!");
276 Err(AppError::DefaultAppPathError)
277 }
278}
279
280fn load_from_filepath(path: PathBuf) -> AppResult<Config> {
281 println!("reading config file {}...", path.to_string_lossy());
282 if let Ok(config) = fs::read_to_string(path) {
283 let config = toml::from_str(&config).map_err(AppError::ConfigSyntaxError)?;
284 Ok(config)
285 } else {
286 Err(AppError::ConfigNotFoundError)
287 }
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct Config {
292 pub database: String,
293 pub main_calendar_type: String,
294 pub main_calendar_language: String,
295 pub main_calendar_start_weekday: String,
296 pub secondary_calendar_type: Option<String>,
297 pub secondary_calendar_language: Option<String>,
298 pub weekdates_display_direction: String,
299 pub items_display_direction: String,
300}
301
302impl Config {
303 pub fn get_copy(&self) -> Config {
304 Config {
305 database: self.database.clone(),
306 main_calendar_type: self.main_calendar_type.clone(),
307 main_calendar_language: self.main_calendar_language.clone(),
308 main_calendar_start_weekday: self.main_calendar_start_weekday.clone(),
309 secondary_calendar_type: self.secondary_calendar_type.clone(),
310 secondary_calendar_language: self.secondary_calendar_language.clone(),
311 weekdates_display_direction: self.weekdates_display_direction.clone(),
312 items_display_direction: self.items_display_direction.clone(),
313 }
314 }
315}
316
317impl Default for Config {
318 fn default() -> Self {
319 let data_path = default_config_data_path()
320 .unwrap()
321 .1
322 .to_string_lossy()
323 .into_owned();
324 Self {
325 database: data_path,
326 main_calendar_type: "Gregorian".into(),
327 main_calendar_language: "en".into(),
328 main_calendar_start_weekday: "MON".into(),
329 secondary_calendar_type: None,
330 secondary_calendar_language: None,
331 weekdates_display_direction: "ltr".into(),
332 items_display_direction: "auto".into(),
333 }
334 }
335}
336
337pub fn get_config_path() -> PathBuf {
338 default_config_data_path().unwrap().0
339}