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
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Config {
16 pub database: String,
17 pub main_calendar_type: String,
18 pub main_calendar_language: String,
19 pub main_calendar_start_weekday: String,
20 pub secondary_calendar_type: Option<String>,
21 pub secondary_calendar_language: Option<String>,
22 pub weekdates_display_direction: String,
23 pub items_display_direction: String,
24 pub weekend_holidays: Vec<String>,
25}
26
27impl Config {
28 pub fn get_copy(&self) -> Config {
29 Config {
30 database: self.database.clone(),
31 main_calendar_type: self.main_calendar_type.clone(),
32 main_calendar_language: self.main_calendar_language.clone(),
33 main_calendar_start_weekday: self.main_calendar_start_weekday.clone(),
34 secondary_calendar_type: self.secondary_calendar_type.clone(),
35 secondary_calendar_language: self.secondary_calendar_language.clone(),
36 weekdates_display_direction: self.weekdates_display_direction.clone(),
37 items_display_direction: self.items_display_direction.clone(),
38 weekend_holidays: self.weekend_holidays.clone(),
39 }
40 }
41}
42
43impl Default for Config {
44 fn default() -> Self {
45 let data_path = default_config_data_path()
46 .unwrap()
47 .1
48 .to_string_lossy()
49 .into_owned();
50 Self {
51 database: data_path,
52 main_calendar_type: "Gregorian".into(),
53 main_calendar_language: "en".into(),
54 main_calendar_start_weekday: "MON".into(),
55 secondary_calendar_type: None,
56 secondary_calendar_language: None,
57 weekdates_display_direction: "auto".into(),
58 items_display_direction: "auto".into(),
59 weekend_holidays: vec![],
60 }
61 }
62}
63
64pub static CONFIG: OnceCell<ArcSwap<Config>> = OnceCell::new();
67
68pub fn set_config(new_cfg: Config) {
69 if CONFIG.get().is_none() {
70 CONFIG.set(ArcSwap::from_pointee(new_cfg.clone())).unwrap();
71 } else {
72 CONFIG.get().unwrap().store(Arc::new(new_cfg.clone()));
73 }
74}
75
76pub fn reload_config_file() {
77 println!("reloading config file...");
78 let config_path = get_config_path();
79 if let Ok(config) = load_from_filepath(config_path) {
80 set_config(config);
81 println!("success.");
82 } else {
83 println!("failed.");
84 }
85}
86
87pub fn read_config_file_or_save_default_config_file() -> Result<Config, AppError> {
88 let config_path = get_config_path();
89
90 if let Ok(config) = load_from_filepath(config_path) {
91 println!("config file available and ok");
92 println!("config: {config:?}");
93 Ok(config)
94 } else {
95 println!("no config file or syntax error!");
98 let default_config = Config::default();
99 println!("saving default config: {default_config:?}");
100 save_config(default_config.clone())?;
101 println!("save successful.");
103 Ok(default_config)
104 }
105}
106
107pub fn check_database_file_is_valid_or_create_database_file(db_path: &str) -> Result<(), AppError> {
108 if db_sqlite::is_correct_db(db_path) {
109 Ok(())
110 } else {
111 db_sqlite::create_db(db_path)
112 }
113}
114
115pub fn get_config() -> Config {
116 let gaurd = CONFIG.get_or_init(|| {
117 println!("Init CONFIG (global OnceCell first run init)");
118 let new_cfg = read_config_file_or_save_default_config_file().unwrap();
120 if let Some(parent) = Path::new(&new_cfg.database).parent() {
122 fs::create_dir_all(parent)
123 .map_err(|_| AppError::DatabaseFileCreateError)
124 .unwrap();
125 }
126
127 ArcSwap::from_pointee(new_cfg)
132 });
133 let gaurd = gaurd.load();
134 gaurd.get_copy()
135}
136
137pub fn set_and_save_config(new_config: Config) -> Result<(), AppError> {
138 set_config(new_config.clone());
139 save_config(new_config)
140}
141
142pub fn move_database<P: AsRef<str>>(filepath: P) -> Result<(), AppError> {
143 let filepath = filepath.as_ref();
144 let mut config = get_config();
145 let current_db_path = config.database;
146 let current_db_valid = db_sqlite::is_correct_db(¤t_db_path);
147 let exists = Path::new(filepath).exists();
148 if !exists {
149 if !current_db_valid {
150 println!("Attempting to creating new database file: {}", filepath);
152 db_sqlite::create_db(filepath)
153 } else {
154 if let Some(parent) = Path::new(filepath).parent() {
157 fs::create_dir_all(parent).map_err(|_| AppError::DatabaseFileCopyError)?;
158 }
159 std::fs::copy(¤t_db_path, filepath)
161 .map_err(|_| AppError::DatabaseFileCopyError)?;
162 config.database = filepath.to_string();
164 set_and_save_config(config)?;
165 std::fs::remove_file(¤t_db_path)
167 .map(|_| ())
168 .map_err(|_| AppError::DatabaseFileRemoveError)
169 }
170 } else {
171 Err(AppError::DatabaseFileNotEmptyError)
172 }
173}
174
175pub fn open_database<P: AsRef<str>>(filepath: P) -> Result<(), AppError> {
176 let filepath = filepath.as_ref();
177 let mut config = get_config();
178 let exists = Path::new(filepath).exists();
179 if !exists {
180 Err(AppError::DatabaseFileDontExistsError)
181 } else {
182 let db_valid = db_sqlite::is_correct_db(filepath);
183 if !db_valid {
184 Err(AppError::DatabaseFileInvalidError)
185 } else {
186 config.database = filepath.to_string();
189 set_and_save_config(config)
190 }
191 }
192}
193
194pub fn save_config(config: Config) -> Result<(), AppError> {
195 let toml_str = toml::to_string(&config).map_err(|e| {
196 println!("Failed to serialize config to TOML: {}", e);
197 AppError::ConfigTomlGenerateError
198 })?;
199
200 let config_path = get_config_path();
201 println!(
202 "Attempting to save config to: {}",
203 config_path.to_string_lossy()
204 );
205
206 if let Some(parent) = config_path.parent() {
208 println!("Creating directory if needed: {}", parent.to_string_lossy());
209 fs::create_dir_all(parent).map_err(|e| {
210 println!("Failed to create directory: {}", e);
211 AppError::ConfigFileSaveError
212 })?;
213 }
214
215 fs::write(&config_path, toml_str).map_err(|e| {
217 println!("Failed to write config file: {}", e);
218 println!("Path: {}", config_path.to_string_lossy());
219 AppError::ConfigFileSaveError
220 })
221}
222
223pub fn get_main_cal_lang_pair() -> CalendarLanguagePair {
224 let calendar: Calendar = get_config().main_calendar_type.into();
225 let language: Language = get_config().main_calendar_language.into();
226 CalendarLanguagePair { calendar, language }
227}
228
229pub fn get_second_cal_lang_pair() -> Option<CalendarLanguagePair> {
230 get_config().secondary_calendar_type.map(|cal| {
231 let language: Language = get_config()
232 .secondary_calendar_language
233 .unwrap_or_default()
234 .into();
235 let calendar: Calendar = cal.into();
236 CalendarLanguagePair { calendar, language }
237 })
238}
239
240fn default_config_data_path() -> AppResult<(PathBuf, PathBuf)> {
241 if let Some(proj_dirs) = directories::ProjectDirs::from("", "", "ThisWeek") {
243 let config_path = proj_dirs.config_dir().join("config.toml");
245 let data_path = proj_dirs.data_dir().join("thisweek.db");
249 Ok((config_path, data_path))
251 } else {
252 eprintln!("Could not determine project directories!");
253 Err(AppError::DefaultAppPathError)
254 }
255}
256
257fn load_from_filepath(path: PathBuf) -> AppResult<Config> {
258 println!("reading config file {}...", path.to_string_lossy());
259 if let Ok(config) = fs::read_to_string(path) {
260 let config = toml::from_str(&config).map_err(AppError::ConfigSyntaxError)?;
261 Ok(config)
262 } else {
263 Err(AppError::ConfigNotFoundError)
264 }
265}
266pub fn get_config_path() -> PathBuf {
267 default_config_data_path().unwrap().0
268}
269
270