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