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