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::ffi::OsString;
14use std::fs::{self, File};
15use std::io::{self, ErrorKind, Write};
16use std::ops::Deref;
17use std::path::{Path, PathBuf};
18use std::sync::{Mutex, OnceLock};
19use tar::Archive;
20use url::Url;
21
22const LHAPDF_CONFIG: &str = "Verbosity: 1
23Interpolator: logcubic
24Extrapolator: continuation
25ForcePositive: 0
26AlphaS_Type: analytic
27MZ: 91.1876
28MUp: 0.002
29MDown: 0.005
30MStrange: 0.10
31MCharm: 1.29
32MBottom: 4.19
33MTop: 172.9
34Pythia6LambdaV5Compat: true
35";
36
37#[derive(Debug, Deserialize, Serialize)]
39#[serde(deny_unknown_fields)]
40pub struct Config {
41 lhapdf_data_path_read: Vec<PathBuf>,
42 lhapdf_data_path_write: PathBuf,
43 pdfsets_index_url: Url,
44 pdfset_urls: Vec<Url>,
45}
46
47impl Default for Config {
48 fn default() -> Self {
49 let mut config = Self {
50 lhapdf_data_path_read: vec![],
51 lhapdf_data_path_write: dirs::data_dir()
52 .unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| env::temp_dir()))
55 .join("managed-lhapdf"),
56 pdfsets_index_url: Url::parse("https://lhapdfsets.web.cern.ch/current/pdfsets.index")
58 .unwrap(),
59 pdfset_urls: vec![Url::parse("https://lhapdfsets.web.cern.ch/current/").unwrap()],
61 };
62
63 if let Some(os_str) = env::var_os("LHAPDF_DATA_PATH").or_else(|| env::var_os("LHAPATH")) {
65 let mut lhapdf_paths: Vec<_> =
66 os_str.to_str().unwrap().split(':').map(PathBuf::from).collect();
68
69 config.lhapdf_data_path_write = lhapdf_paths.remove(0);
71 config.lhapdf_data_path_read = lhapdf_paths;
73 }
74
75 config
76 }
77}
78
79fn get_url(url: &Url) -> Result<Box<dyn std::io::Read + Send + Sync + 'static>> {
80 ureq::request_url("GET", url)
81 .call()
82 .map_err(|err| match err {
83 ureq::Error::Status(404, _) => Error::Http404,
85 err @ _ => Error::Other(anyhow::Error::new(err)),
86 })
87 .map(ureq::Response::into_reader)
88}
89
90struct LhapdfData;
91
92impl Config {
93 pub fn get() -> &'static Self {
95 static SINGLETON: OnceLock<Result<Config>> = OnceLock::new();
96
97 let config = SINGLETON.get_or_init(|| {
98 let config_path = dirs::config_dir()
99 .ok_or_else(|| Error::General("no configuration directory found".to_owned()))?;
100
101 fs::create_dir_all(&config_path)?;
104
105 let config_path = config_path.join("managed-lhapdf.toml");
106
107 let config = match File::options()
112 .read(true)
113 .write(true)
114 .create_new(true)
115 .open(&config_path)
116 {
117 Ok(mut file) => {
119 let config = Config::default();
121 file.write_all(toml::to_string_pretty(&config)?.as_bytes())?;
122 config
123 }
124 Err(err) if err.kind() == ErrorKind::AlreadyExists => {
125 toml::from_str(&fs::read_to_string(&config_path)?)?
127 }
128 Err(err) => Err(err)?,
129 };
130
131 if let Some(lhapdf_data_path_write) = config.lhapdf_data_path_write() {
132 fs::create_dir_all(lhapdf_data_path_write)?;
134
135 if let Ok(mut file) = File::options()
137 .read(true)
138 .write(true)
139 .create_new(true)
140 .open(lhapdf_data_path_write.join("lhapdf.conf"))
141 {
142 file.write_all(LHAPDF_CONFIG.as_bytes())?;
144 }
145
146 let pdfsets_index = lhapdf_data_path_write.join("pdfsets.index");
147
148 if let Ok(mut file) = File::options()
150 .read(true)
151 .write(true)
152 .create_new(true)
153 .open(pdfsets_index)
154 {
155 let mut reader = get_url(config.pdfsets_index_url())?;
157 io::copy(&mut reader, &mut file)?;
158 }
159 }
160
161 let lhapdf_data_path = config
165 .lhapdf_data_path_write()
166 .into_iter()
167 .chain(config.lhapdf_data_path_read.iter().map(Deref::deref))
168 .map(|path| path.as_os_str())
169 .collect::<Vec<_>>()
170 .join(&OsString::from(":"));
171 unsafe { env::set_var("LHAPDF_DATA_PATH", lhapdf_data_path) };
175
176 Ok(config)
177 });
178
179 config.as_ref().unwrap()
182 }
183
184 pub fn lhapdf_data_path_write(&self) -> Option<&Path> {
186 if self.lhapdf_data_path_write.as_os_str().is_empty() {
187 None
188 } else {
189 Some(&self.lhapdf_data_path_write)
190 }
191 }
192
193 pub fn pdfsets_index_url(&self) -> &Url {
195 &self.pdfsets_index_url
196 }
197
198 pub fn pdfset_urls(&self) -> &[Url] {
201 &self.pdfset_urls
202 }
203}
204
205impl From<toml::ser::Error> for Error {
206 fn from(err: toml::ser::Error) -> Self {
207 Self::Other(anyhow::Error::new(err))
208 }
209}
210
211impl From<toml::de::Error> for Error {
212 fn from(err: toml::de::Error) -> Self {
213 Self::Other(anyhow::Error::new(err))
214 }
215}
216
217impl From<url::ParseError> for Error {
218 fn from(err: url::ParseError) -> Self {
219 Self::Other(anyhow::Error::new(err))
220 }
221}
222
223impl LhapdfData {
224 fn get() -> &'static Mutex<Self> {
225 static SINGLETON: Mutex<LhapdfData> = Mutex::new(LhapdfData);
226 &SINGLETON
227 }
228
229 fn download_set(&self, name: &str, config: &Config) -> Result<()> {
230 if let Some(lhapdf_data_path_write) = config.lhapdf_data_path_write() {
231 let lock_file = File::create(lhapdf_data_path_write.join(format!("{name}.lock")))?;
232 lock_file.lock_exclusive()?;
233
234 for url in config.pdfset_urls() {
235 let response = get_url(&url.join(&format!("{name}.tar.gz"))?);
236
237 if let Err(Error::Http404) = response {
239 continue;
240 }
241
242 Archive::new(GzDecoder::new(response?)).unpack(lhapdf_data_path_write)?;
243
244 break;
246 }
247
248 lock_file.unlock()?;
249 }
250
251 Ok(())
252 }
253
254 fn update_pdfsets_index(&self, config: &Config) -> Result<()> {
255 if let Some(lhapdf_data_path_write) = config.lhapdf_data_path_write() {
256 let lock_file = File::create(lhapdf_data_path_write.join("pdfsets.lock"))?;
257 lock_file.lock_exclusive()?;
258
259 ffi::empty_lhaindex();
262
263 let mut reader = get_url(config.pdfsets_index_url())?;
265 io::copy(
266 &mut reader,
267 &mut File::create(lhapdf_data_path_write.join("pdfsets.index"))?,
268 )?;
269
270 lock_file.unlock()?;
271 }
272
273 Ok(())
274 }
275
276 pub fn pdf_name_and_member_via_lhaid(&self, lhaid: i32) -> Option<(String, i32)> {
277 unmanaged::pdf_name_and_member_via_lhaid(lhaid)
278 }
279
280 fn pdf_with_setname_and_member(&self, setname: &str, member: i32) -> Result<UniquePtr<PDF>> {
281 unmanaged::pdf_with_setname_and_member(setname, member)
282 }
283
284 fn pdfset_new(&self, setname: &str) -> Result<UniquePtr<PDFSet>> {
285 unmanaged::pdfset_new(setname)
286 }
287
288 fn set_verbosity(&self, verbosity: i32) {
289 unmanaged::set_verbosity(verbosity);
290 }
291
292 fn verbosity(&self) -> i32 {
293 unmanaged::verbosity()
294 }
295}
296
297pub fn pdf_name_and_member_via_lhaid(lhaid: i32) -> Option<(String, i32)> {
298 let config = Config::get();
300
301 let lock = LhapdfData::get().lock().unwrap();
303
304 lock.pdf_name_and_member_via_lhaid(lhaid).or_else(|| {
305 lock.update_pdfsets_index(config).unwrap();
307 lock.pdf_name_and_member_via_lhaid(lhaid)
308 })
309}
310
311pub fn pdf_with_setname_and_member(setname: &str, member: i32) -> Result<UniquePtr<PDF>> {
312 let config = Config::get();
314
315 let lock = LhapdfData::get().lock().unwrap();
317
318 lock.pdf_with_setname_and_member(setname, member)
319 .or_else(|err: Error| {
320 if err.to_string() == format!("Info file not found for PDF set '{setname}'") {
322 lock.download_set(setname, config)
323 .and_then(|()| lock.pdf_with_setname_and_member(setname, member))
324 } else {
325 Err(err)
326 }
327 })
328}
329
330pub fn pdfset_new(setname: &str) -> Result<UniquePtr<PDFSet>> {
331 let config = Config::get();
333
334 let lock = LhapdfData::get().lock().unwrap();
336
337 lock.pdfset_new(setname).or_else(|err: Error| {
338 if err.to_string() == format!("Info file not found for PDF set '{setname}'") {
340 lock.download_set(setname, config)
341 .and_then(|()| lock.pdfset_new(setname))
342 } else {
343 Err(err)
344 }
345 })
346}
347
348pub fn set_verbosity(verbosity: i32) {
349 let _ = Config::get();
351
352 let lock = LhapdfData::get().lock().unwrap();
354
355 lock.set_verbosity(verbosity);
356}
357
358pub fn verbosity() -> i32 {
359 let _ = Config::get();
361
362 let lock = LhapdfData::get().lock().unwrap();
364
365 lock.verbosity()
366}