open_timeline_gui/
config.rs1use crate::app_colours::{AppColours, ColourTheme};
8use directories_next::ProjectDirs;
9use log::info;
10use open_timeline_crud::{CrudError, setup_database_at_path};
11use serde::{Deserialize, Serialize};
12use sqlx::SqlitePool;
13use std::fs::{self, File};
14use std::path::PathBuf;
15use std::sync::Arc;
16use tokio::sync::RwLock;
17
18const PROJECT_QUALIFIER: &str = "org";
19const ORG_NAME: &str = "OpenTimeline";
20const APPLICATION_NAME: &str = "OpenTimeline";
21const CONFIG_FILE_NAME: &str = "config.json";
22const DEFAULT_DATABASE_FILE_NAME: &str = "timeline.sqlite";
23
24pub type SharedConfig = Arc<RwLock<RuntimeConfig>>;
25
26#[derive(Debug, Clone)]
28pub struct RuntimeConfig {
29 pub db_pool: SqlitePool,
30 pub config: Config,
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct Config {
36 database_path: PathBuf,
38
39 pub colour_theme: ColourTheme,
41
42 pub custom_theme: AppColours,
44}
45
46impl Config {
47 pub fn load() -> Result<Self, CrudError> {
51 info!("Loading config");
52 let config_file_path = config_file_path()?;
53 let data = fs::read_to_string(config_file_path)?;
54 info!("JSON config loaded = {data}");
55 let config: Config = serde_json::from_str(&data)?;
56 info!("Config loaded = {config:?}");
57 Ok(config)
58 }
59
60 pub fn set_to_default(&mut self) {
61 let default = default_config();
62 self.colour_theme = default.colour_theme();
63 self.database_path = default.database_path();
64 }
65
66 pub fn colour_theme(&self) -> ColourTheme {
67 self.colour_theme
68 }
69
70 pub fn set_colour_theme(&mut self, colour_theme: ColourTheme) {
71 self.colour_theme = colour_theme.to_owned();
72 }
73
74 pub fn database_path(&self) -> PathBuf {
75 self.database_path.clone()
76 }
77
78 pub fn set_database_path(&mut self, path: &PathBuf) {
79 self.database_path = path.to_owned();
80 }
81
82 pub async fn ensure_setup() -> Result<(), CrudError> {
83 info!("Ensuring config exists");
84 let config_file_path = config_file_path()?;
85 if !config_file_path.exists() {
86 info!("No config file found");
87 let new_config = default_config();
88 new_config.save().await?;
89 info!("Config created = {new_config:?}");
90 setup_database_at_path(&new_config.database_path).await?;
91 info!("Database setup at {}", &new_config.database_path.display());
92 };
93 info!("Config is setup");
94 Ok(())
95 }
96
97 pub async fn save(&self) -> Result<(), CrudError> {
98 let path = self.database_path.to_owned();
100 setup_database_at_path(&path).await?;
101
102 let config_path = config_file_path()?;
104 ensure_config_file_exists(&config_path)?;
105 info!("Saving config to {config_path:?}");
106 let json = serde_json::to_string_pretty(self)?;
107 fs::write(config_path, json)?;
108
109 info!("Config saved");
111 Ok(())
112 }
113}
114
115fn default_config() -> Config {
117 info!("Creating default config");
118 let database_path = default_db_file_path();
119 Config {
120 colour_theme: ColourTheme::System,
121 database_path,
122 custom_theme: AppColours::default(),
123 }
124}
125
126#[cfg(debug_assertions)]
128fn project_dirs() -> Result<ProjectDirs, CrudError> {
129 info!("Getting project directories (dev build)");
130 ProjectDirs::from(
131 PROJECT_QUALIFIER,
132 ORG_NAME,
133 &format!("{APPLICATION_NAME} Dev"),
134 )
135 .ok_or(CrudError::Config)
136}
137
138#[cfg(not(debug_assertions))]
140fn project_dirs() -> Result<ProjectDirs, CrudError> {
141 info!("Getting project directories");
142 ProjectDirs::from(PROJECT_QUALIFIER, ORG_NAME, APPLICATION_NAME).ok_or(CrudError::Config)
143}
144
145fn config_file_path() -> Result<PathBuf, CrudError> {
147 info!("Getting config file path");
148 let config_file = project_dirs()?
149 .config_dir()
150 .to_path_buf()
151 .join(CONFIG_FILE_NAME);
152 info!("Config file path = {config_file:?}");
153 Ok(config_file)
154}
155
156fn ensure_config_file_exists(path: &PathBuf) -> Result<(), CrudError> {
158 info!("Ensuring config path exists: {path:?}");
159 if let Some(parent) = path.parent() {
160 fs::create_dir_all(parent)?;
161 }
162 File::create(path)?;
163 Ok(())
164}
165
166fn default_db_file_path() -> PathBuf {
168 project_dirs()
169 .unwrap()
170 .data_dir()
171 .to_path_buf()
172 .join(DEFAULT_DATABASE_FILE_NAME)
173}