managed_lhapdf/
manager.rs1use super::ffi::{self, PDFSet, PDF};
6use super::unmanaged;
7use super::{Error, Result};
8use cxx::UniquePtr;
9use flate2::read::GzDecoder;
10use fs2::FileExt;
11use serde::{Deserialize, Serialize};
12use std::env;
13use std::fs::{self, File};
14use std::io;
15use std::io::{ErrorKind, Write};
16use std::path::Path;
17use std::sync::{Mutex, OnceLock};
18use tar::Archive;
19
20const LHAPDF_CONFIG: &str = "Verbosity: 1
21Interpolator: logcubic
22Extrapolator: continuation
23ForcePositive: 0
24AlphaS_Type: analytic
25MZ: 91.1876
26MUp: 0.002
27MDown: 0.005
28MStrange: 0.10
29MCharm: 1.29
30MBottom: 4.19
31MTop: 172.9
32Pythia6LambdaV5Compat: true
33";
34
35#[derive(Debug, Deserialize, Serialize)]
37#[serde(deny_unknown_fields)]
38pub struct Config {
39 lhapdf_data_path_read: Vec<String>,
40 lhapdf_data_path_write: String,
41 pdfsets_index_url: String,
42 pdfset_urls: Vec<String>,
43}
44
45struct LhapdfData;
46
47impl Config {
48 pub fn get() -> &'static Self {
50 static SINGLETON: OnceLock<Result<Config>> = OnceLock::new();
51
52 let config = SINGLETON.get_or_init(|| {
53 let config_path = dirs::config_dir()
54 .ok_or_else(|| Error::General("no configuration directory found".to_owned()))?;
55
56 fs::create_dir_all(&config_path)?;
59
60 let config_path = config_path.join("managed-lhapdf.toml");
61
62 let config = match File::options()
67 .read(true)
68 .write(true)
69 .create_new(true)
70 .open(&config_path)
71 {
72 Ok(mut file) => {
74 let mut config = Self {
76 lhapdf_data_path_read: vec![],
77 lhapdf_data_path_write: dirs::data_dir()
78 .ok_or_else(|| Error::General("no data directory found".to_owned()))?
79 .join("managed-lhapdf")
80 .to_str()
81 .unwrap()
83 .to_owned(),
84 pdfsets_index_url: "https://lhapdfsets.web.cern.ch/current/pdfsets.index"
85 .to_owned(),
86 pdfset_urls: vec!["https://lhapdfsets.web.cern.ch/current/".to_owned()],
87 };
88
89 if let Some(os_str) =
91 env::var_os("LHAPDF_DATA_PATH").or_else(|| env::var_os("LHAPATH"))
92 {
93 config.lhapdf_data_path_read =
94 os_str.to_str().unwrap().split(':').map(ToOwned::to_owned).collect();
96 }
97
98 file.write_all(toml::to_string_pretty(&config)?.as_bytes())?;
99
100 config
101 }
102 Err(err) if err.kind() == ErrorKind::AlreadyExists => {
103 toml::from_str(&fs::read_to_string(&config_path)?)?
105 }
106 Err(err) => Err(err)?,
107 };
108
109 if let Some(lhapdf_data_path_write) = config.lhapdf_data_path_write() {
110 fs::create_dir_all(lhapdf_data_path_write)?;
112
113 if let Ok(mut file) = File::options()
115 .read(true)
116 .write(true)
117 .create_new(true)
118 .open(Path::new(lhapdf_data_path_write).join("lhapdf.conf"))
119 {
120 file.write_all(LHAPDF_CONFIG.as_bytes())?;
122 }
123
124 let pdfsets_index = Path::new(lhapdf_data_path_write).join("pdfsets.index");
125
126 if let Ok(mut file) = File::options()
128 .read(true)
129 .write(true)
130 .create_new(true)
131 .open(pdfsets_index)
132 {
133 let mut reader = ureq::get(config.pdfsets_index_url()).call()?.into_reader();
135 io::copy(&mut reader, &mut file)?;
136 }
137 }
138
139 let mut lhapdf_data_path = config
143 .lhapdf_data_path_write()
144 .map_or_else(Vec::new, |path| vec![path.to_owned()]);
145 lhapdf_data_path.extend(config.lhapdf_data_path_read.iter().cloned());
146 env::set_var("LHAPDF_DATA_PATH", lhapdf_data_path.join(":"));
150
151 Ok(config)
152 });
153
154 config.as_ref().unwrap()
157 }
158
159 pub fn lhapdf_data_path_write(&self) -> Option<&str> {
161 if self.lhapdf_data_path_write.is_empty() {
162 None
163 } else {
164 Some(&self.lhapdf_data_path_write)
165 }
166 }
167
168 pub fn pdfsets_index_url(&self) -> &str {
170 &self.pdfsets_index_url
171 }
172
173 pub fn pdfset_urls(&self) -> &[String] {
176 &self.pdfset_urls
177 }
178}
179
180impl From<toml::ser::Error> for Error {
181 fn from(err: toml::ser::Error) -> Self {
182 Self::Other(anyhow::Error::new(err))
183 }
184}
185
186impl From<toml::de::Error> for Error {
187 fn from(err: toml::de::Error) -> Self {
188 Self::Other(anyhow::Error::new(err))
189 }
190}
191
192impl From<ureq::Error> for Error {
193 fn from(err: ureq::Error) -> Self {
194 Self::Other(anyhow::Error::new(err))
195 }
196}
197
198impl LhapdfData {
199 fn get() -> &'static Mutex<Self> {
200 static SINGLETON: Mutex<LhapdfData> = Mutex::new(LhapdfData);
201 &SINGLETON
202 }
203
204 fn download_set(&self, name: &str, config: &Config) -> Result<()> {
205 if let Some(lhapdf_data_path_write) = config.lhapdf_data_path_write() {
206 let lock_file =
207 File::create(Path::new(lhapdf_data_path_write).join(format!("{name}.lock")))?;
208 lock_file.lock_exclusive()?;
209
210 for url in config.pdfset_urls() {
211 let response = ureq::get(&format!("{url}/{name}.tar.gz")).call();
212
213 if let Err(ureq::Error::Status(404, _)) = response {
214 continue;
215 }
216
217 let reader = response?.into_reader();
218
219 Archive::new(GzDecoder::new(reader)).unpack(lhapdf_data_path_write)?;
221
222 break;
224 }
225
226 lock_file.unlock()?;
227 }
228
229 Ok(())
230 }
231
232 fn update_pdfsets_index(&self, config: &Config) -> Result<()> {
233 if let Some(lhapdf_data_path_write) = config.lhapdf_data_path_write() {
234 let lock_file = File::create(Path::new(lhapdf_data_path_write).join("pdfsets.lock"))?;
235 lock_file.lock_exclusive()?;
236
237 ffi::empty_lhaindex();
240
241 let content = ureq::get(config.pdfsets_index_url())
243 .call()?
244 .into_string()?;
245
246 let pdfsets_index = Path::new(lhapdf_data_path_write).join("pdfsets.index");
247
248 File::create(pdfsets_index)?.write_all(content.as_bytes())?;
250
251 lock_file.unlock()?;
252 }
253
254 Ok(())
255 }
256
257 pub fn pdf_name_and_member_via_lhaid(&self, lhaid: i32) -> Option<(String, i32)> {
258 unmanaged::pdf_name_and_member_via_lhaid(lhaid)
259 }
260
261 fn pdf_with_setname_and_member(&self, setname: &str, member: i32) -> Result<UniquePtr<PDF>> {
262 unmanaged::pdf_with_setname_and_member(setname, member)
263 }
264
265 fn pdfset_new(&self, setname: &str) -> Result<UniquePtr<PDFSet>> {
266 unmanaged::pdfset_new(setname)
267 }
268
269 fn set_verbosity(&self, verbosity: i32) {
270 unmanaged::set_verbosity(verbosity);
271 }
272
273 fn verbosity(&self) -> i32 {
274 unmanaged::verbosity()
275 }
276}
277
278pub fn pdf_name_and_member_via_lhaid(lhaid: i32) -> Option<(String, i32)> {
279 let config = Config::get();
281
282 let lock = LhapdfData::get().lock().unwrap();
284
285 lock.pdf_name_and_member_via_lhaid(lhaid).or_else(|| {
286 lock.update_pdfsets_index(config).unwrap();
288 lock.pdf_name_and_member_via_lhaid(lhaid)
289 })
290}
291
292pub fn pdf_with_setname_and_member(setname: &str, member: i32) -> Result<UniquePtr<PDF>> {
293 let config = Config::get();
295
296 let lock = LhapdfData::get().lock().unwrap();
298
299 lock.pdf_with_setname_and_member(setname, member)
300 .or_else(|err: Error| {
301 if err.to_string() == format!("Info file not found for PDF set '{setname}'") {
303 lock.download_set(setname, config)
304 .and_then(|()| lock.pdf_with_setname_and_member(setname, member))
305 } else {
306 Err(err)
307 }
308 })
309}
310
311pub fn pdfset_new(setname: &str) -> Result<UniquePtr<PDFSet>> {
312 let config = Config::get();
314
315 let lock = LhapdfData::get().lock().unwrap();
317
318 lock.pdfset_new(setname).or_else(|err: Error| {
319 if err.to_string() == format!("Info file not found for PDF set '{setname}'") {
321 lock.download_set(setname, config)
322 .and_then(|()| lock.pdfset_new(setname))
323 } else {
324 Err(err)
325 }
326 })
327}
328
329pub fn set_verbosity(verbosity: i32) {
330 let _ = Config::get();
332
333 let lock = LhapdfData::get().lock().unwrap();
335
336 lock.set_verbosity(verbosity);
337}
338
339pub fn verbosity() -> i32 {
340 let _ = Config::get();
342
343 let lock = LhapdfData::get().lock().unwrap();
345
346 lock.verbosity()
347}