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