1#![allow(clippy::ref_option)]
2
3use std::{
4 fmt::Debug,
5 path::{Path, PathBuf}, sync::atomic::AtomicU16,
6};
7
8use flams_utils::settings::GitlabSettings;
9pub use flams_utils::settings::{SettingsSpec,BuildQueueSettings, ServerSettings};
10use lazy_static::lazy_static;
11
12static SETTINGS: std::sync::OnceLock<Settings> = std::sync::OnceLock::new();
13
14pub struct Settings {
15 pub mathhubs: Box<[Box<Path>]>,
16 pub mathhubs_is_default:bool,
17 pub debug: bool,
18 pub log_dir: Box<Path>,
19 pub port: AtomicU16,
20 pub ip: std::net::IpAddr,
21 pub admin_pwd: Option<Box<str>>,
22 pub database: Box<Path>,
23 external_url:Option<Box<str>>,
24 temp_dir: parking_lot::RwLock<Option<tempfile::TempDir>>,
25 pub num_threads: u8,
26 pub gitlab_url: Option<url::Url>,
27 pub gitlab_token: Option<Box<str>>,
28 pub gitlab_app_id:Option<Box<str>>,
29 pub gitlab_app_secret:Option<Box<str>>,
30 pub gitlab_redirect_url:Option<Box<str>>,
31 pub lsp:bool
32}
33impl Debug for Settings {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.write_str("Settings")
36 }
37}
38
39impl Settings {
40 pub fn port(&self) -> u16 { self.port.load(std::sync::atomic::Ordering::Relaxed) }
41 #[allow(clippy::missing_panics_doc)]
42 pub fn initialize(settings: SettingsSpec) {
43 SETTINGS
44 .set(settings.into())
45 .expect("Error initializing settings");
46 }
47
48 #[allow(clippy::missing_panics_doc)]
49 pub fn get() -> &'static Self {
50 SETTINGS.get().expect("Settings not initialized")
51 }
52
53 #[inline]
54 pub fn external_url(&self) -> Option<&str> {
55 self.external_url.as_deref()
56 }
57
58 pub fn temp_dir(&self) -> PathBuf {
60 self.temp_dir.read().as_ref().expect("This should never happen!").path().to_path_buf()
61 }
62
63 #[allow(clippy::significant_drop_in_scrutinee)]
64 pub fn close(&self) {
65 if let Some(td) = self.temp_dir.write().take() {
66 let _ = td.close();
67 }
68 }
69
70 #[must_use]
72 pub fn as_spec(&self) -> SettingsSpec {
73 let port = self.port();
74 let spec = SettingsSpec {
75 mathhubs: self.mathhubs.to_vec(),
76 debug: Some(self.debug),
77 log_dir: Some(self.log_dir.clone()),
78 temp_dir: Some(self.temp_dir.read().as_ref().expect("This should never happen!").path().to_path_buf().into_boxed_path()),
79 database: Some(self.database.clone()),
80 server: ServerSettings {
81 port,
82 ip: Some(self.ip),
83 external_url: self.external_url.as_ref().map(ToString::to_string).or_else(
84 || Some(format!("http://{}:{port}",self.ip)),
85 ),
86 admin_pwd: self.admin_pwd.as_ref().map(ToString::to_string),
87 },
88 buildqueue: BuildQueueSettings {
89 num_threads: Some(self.num_threads),
90 },
91 gitlab: GitlabSettings {
92 url: self.gitlab_url.clone(),
93 token: self.gitlab_token.clone(),
94 app_id: self.gitlab_app_id.clone(),
95 app_secret: self.gitlab_app_secret.clone(),
96 redirect_url: self.gitlab_redirect_url.clone(),
97 },
98 lsp: self.lsp
99 };
100 spec
101 }
102}
103impl From<SettingsSpec> for Settings {
104 #[allow(clippy::cast_possible_truncation)]
105 fn from(spec: SettingsSpec) -> Self {
106 let (mathhubs,mathhubs_is_default) = if spec.mathhubs.is_empty() {
107 (MATHHUB_PATHS.clone(),true)
108 } else {
109 let mhs = spec.mathhubs.into_boxed_slice();
110 let is_def = mhs == *MATHHUB_PATHS;
111 (mhs,is_def)
112 };
113 Self {
114 mathhubs,mathhubs_is_default,
115 debug: spec.debug.unwrap_or(cfg!(debug_assertions)),
116 log_dir: spec.log_dir.unwrap_or_else(|| {
117 CONFIG_DIR
118 .as_ref()
119 .expect("could not determine config directory")
120 .join("log")
121 .into_boxed_path()
122 }),
123 temp_dir: parking_lot::RwLock::new(Some(spec.temp_dir.map_or_else(
124 || tempfile::TempDir::new().expect("Could not create temp dir"),
125 |p| {
126 let _ = std::fs::create_dir_all(&p);
127 tempfile::Builder::new().tempdir_in(p).expect("Could not create temp dir")
128 },
129 ))),
130 external_url: spec.server.external_url.map(String::into_boxed_str),
131 port: AtomicU16::new(if spec.server.port == 0 {
132 8095
133 } else {
134 spec.server.port
135 }),
136 ip: spec
137 .server
138 .ip
139 .unwrap_or_else(|| "127.0.0.1".parse().unwrap_or_else(|_| unreachable!())),
140 admin_pwd: if spec.lsp {None} else {spec.server.admin_pwd.map(String::into_boxed_str)},
141 database: spec.database.unwrap_or_else(|| {
142 CONFIG_DIR
143 .as_ref()
144 .expect("could not determine config directory")
145 .join("users.sqlite")
146 .into_boxed_path()
147 }),
148 num_threads: spec.buildqueue.num_threads.unwrap_or_else(|| {
149 #[cfg(feature = "tokio")]
150 {
151 (tokio::runtime::Handle::current().metrics().num_workers() / 2) as u8
152 }
153 #[cfg(not(feature = "tokio"))]
154 {
155 1
156 }
157 }),
158 lsp: spec.lsp,
159 gitlab_token: spec.gitlab.token,
160 gitlab_url: spec.gitlab.url,
161 gitlab_app_id: spec.gitlab.app_id,
162 gitlab_app_secret: spec.gitlab.app_secret,
163 gitlab_redirect_url: spec.gitlab.redirect_url
164 }
165 }
166}
167
168lazy_static! {
169 static ref MATHHUB_PATHS: Box<[Box<Path>]> = mathhubs().into();
170 static ref CONFIG_DIR: Option<Box<Path>> =
171 simple_home_dir::home_dir().map(|d| d.join(".flams").into_boxed_path());
172 static ref EXE_DIR: Option<Box<Path>> = std::env::current_exe()
173 .ok()
174 .and_then(|p| p.parent().map(Into::into));
175}
176
177fn mathhubs() -> Vec<Box<Path>> {
178 if let Ok(f) = std::env::var("MATHHUB") {
179 return f
180 .split(',')
181 .map(|s| PathBuf::from(s.trim()).into_boxed_path())
182 .collect();
183 }
184 if let Some(d) = simple_home_dir::home_dir() {
185 let p = d.join(".mathhub").join("mathhub.path");
186 if let Ok(f) = std::fs::read_to_string(p) {
187 return f
188 .split('\n')
189 .map(|s| PathBuf::from(s.trim()).into_boxed_path())
190 .collect();
191 }
192 return vec![d.join("MathHub").into_boxed_path()];
193 }
194 panic!(
195 "No MathHub directory found and default ~/MathHub not accessible!\n\
196 Please set the MATHHUB environment variable or create a file ~/.mathhub/mathhub.path containing \
197 the path to the MathHub directory."
198 )
199}