use super::ffi::{self, PDF, PDFSet};
use super::unmanaged;
use super::{Error, Result};
use cxx::UniquePtr;
use flate2::read::GzDecoder;
use serde::{Deserialize, Serialize};
use std::env;
use std::ffi::OsString;
use std::fs::{self, File};
use std::io::{self, ErrorKind, Write};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
use tar::Archive;
use url::Url;
const LHAPDF_CONFIG: &str = "Verbosity: 1
Interpolator: logcubic
Extrapolator: continuation
ForcePositive: 0
AlphaS_Type: analytic
MZ: 91.1876
MUp: 0.002
MDown: 0.005
MStrange: 0.10
MCharm: 1.29
MBottom: 4.19
MTop: 172.9
Pythia6LambdaV5Compat: true
";
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
lhapdf_data_path_read: Vec<PathBuf>,
lhapdf_data_path_write: PathBuf,
pdfsets_index_url: Url,
pdfset_urls: Vec<Url>,
}
impl Default for Config {
fn default() -> Self {
let mut config = Self {
lhapdf_data_path_read: vec![],
lhapdf_data_path_write: dirs::data_dir()
.unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| env::temp_dir()))
.join("managed-lhapdf"),
pdfsets_index_url: Url::parse("https://lhapdfsets.web.cern.ch/current/pdfsets.index")
.unwrap(),
pdfset_urls: vec![Url::parse("https://lhapdfsets.web.cern.ch/current/").unwrap()],
};
if let Some(os_str) = env::var_os("LHAPDF_DATA_PATH").or_else(|| env::var_os("LHAPATH")) {
let mut lhapdf_paths: Vec<_> =
os_str.to_str().unwrap().split(':').map(PathBuf::from).collect();
config.lhapdf_data_path_write = lhapdf_paths.remove(0);
config.lhapdf_data_path_read = lhapdf_paths;
}
config
}
}
fn get_url(url: &Url) -> Result<Box<dyn std::io::Read + Send + Sync + 'static>> {
ureq::request_url("GET", url)
.call()
.map_err(|err| match err {
ureq::Error::Status(404, _) => Error::Http404,
err @ _ => Error::Other(anyhow::Error::new(err)),
})
.map(ureq::Response::into_reader)
}
struct LhapdfData;
impl Config {
pub fn get() -> &'static Self {
static SINGLETON: OnceLock<Result<Config>> = OnceLock::new();
let config = SINGLETON.get_or_init(|| {
let config_path = dirs::config_dir()
.ok_or_else(|| Error::General("no configuration directory found".to_owned()))?;
fs::create_dir_all(&config_path)?;
let config_path = config_path.join("managed-lhapdf.toml");
let config = match File::options()
.read(true)
.write(true)
.create_new(true)
.open(&config_path)
{
Ok(mut file) => {
let config = Config::default();
file.write_all(toml::to_string_pretty(&config)?.as_bytes())?;
config
}
Err(err) if err.kind() == ErrorKind::AlreadyExists => {
toml::from_str(&fs::read_to_string(&config_path)?)?
}
Err(err) => Err(err)?,
};
if let Some(lhapdf_data_path_write) = config.lhapdf_data_path_write() {
fs::create_dir_all(lhapdf_data_path_write)?;
if let Ok(mut file) = File::options()
.read(true)
.write(true)
.create_new(true)
.open(lhapdf_data_path_write.join("lhapdf.conf"))
{
file.write_all(LHAPDF_CONFIG.as_bytes())?;
}
let pdfsets_index = lhapdf_data_path_write.join("pdfsets.index");
if let Ok(mut file) = File::options()
.read(true)
.write(true)
.create_new(true)
.open(pdfsets_index)
{
let mut reader = get_url(config.pdfsets_index_url())?;
io::copy(&mut reader, &mut file)?;
}
}
let lhapdf_data_path = config
.lhapdf_data_path_write()
.into_iter()
.chain(config.lhapdf_data_path_read.iter().map(Deref::deref))
.map(|path| path.as_os_str())
.collect::<Vec<_>>()
.join(&OsString::from(":"));
unsafe { env::set_var("LHAPDF_DATA_PATH", lhapdf_data_path) };
Ok(config)
});
config.as_ref().unwrap()
}
pub fn lhapdf_data_path_write(&self) -> Option<&Path> {
if self.lhapdf_data_path_write.as_os_str().is_empty() {
None
} else {
Some(&self.lhapdf_data_path_write)
}
}
pub fn pdfsets_index_url(&self) -> &Url {
&self.pdfsets_index_url
}
pub fn pdfset_urls(&self) -> &[Url] {
&self.pdfset_urls
}
}
impl From<toml::ser::Error> for Error {
fn from(err: toml::ser::Error) -> Self {
Self::Other(anyhow::Error::new(err))
}
}
impl From<toml::de::Error> for Error {
fn from(err: toml::de::Error) -> Self {
Self::Other(anyhow::Error::new(err))
}
}
impl From<url::ParseError> for Error {
fn from(err: url::ParseError) -> Self {
Self::Other(anyhow::Error::new(err))
}
}
impl LhapdfData {
fn get() -> &'static Mutex<Self> {
static SINGLETON: Mutex<LhapdfData> = Mutex::new(LhapdfData);
&SINGLETON
}
fn download_set(&self, name: &str, config: &Config) -> Result<()> {
if let Some(lhapdf_data_path_write) = config.lhapdf_data_path_write() {
let lock_file = File::create(lhapdf_data_path_write.join(format!("{name}.lock")))?;
lock_file.lock()?;
for url in config.pdfset_urls() {
let response = get_url(&url.join(&format!("{name}.tar.gz"))?);
if let Err(Error::Http404) = response {
continue;
}
Archive::new(GzDecoder::new(response?)).unpack(lhapdf_data_path_write)?;
break;
}
lock_file.unlock()?;
}
Ok(())
}
fn update_pdfsets_index(&self, config: &Config) -> Result<()> {
if let Some(lhapdf_data_path_write) = config.lhapdf_data_path_write() {
let lock_file = File::create(lhapdf_data_path_write.join("pdfsets.lock"))?;
lock_file.lock()?;
ffi::empty_lhaindex();
let mut reader = get_url(config.pdfsets_index_url())?;
io::copy(
&mut reader,
&mut File::create(lhapdf_data_path_write.join("pdfsets.index"))?,
)?;
lock_file.unlock()?;
}
Ok(())
}
pub fn pdf_name_and_member_via_lhaid(&self, lhaid: i32) -> Option<(String, i32)> {
unmanaged::pdf_name_and_member_via_lhaid(lhaid)
}
fn pdf_with_setname_and_member(&self, setname: &str, member: i32) -> Result<UniquePtr<PDF>> {
unmanaged::pdf_with_setname_and_member(setname, member)
}
fn pdfset_new(&self, setname: &str) -> Result<UniquePtr<PDFSet>> {
unmanaged::pdfset_new(setname)
}
fn set_verbosity(&self, verbosity: i32) {
unmanaged::set_verbosity(verbosity);
}
fn verbosity(&self) -> i32 {
unmanaged::verbosity()
}
}
pub fn pdf_name_and_member_via_lhaid(lhaid: i32) -> Option<(String, i32)> {
let config = Config::get();
let lock = LhapdfData::get().lock().unwrap();
lock.pdf_name_and_member_via_lhaid(lhaid).or_else(|| {
lock.update_pdfsets_index(config).unwrap();
lock.pdf_name_and_member_via_lhaid(lhaid)
})
}
pub fn pdf_with_setname_and_member(setname: &str, member: i32) -> Result<UniquePtr<PDF>> {
let config = Config::get();
let lock = LhapdfData::get().lock().unwrap();
lock.pdf_with_setname_and_member(setname, member)
.or_else(|err: Error| {
if err.to_string() == format!("Info file not found for PDF set '{setname}'") {
lock.download_set(setname, config)
.and_then(|()| lock.pdf_with_setname_and_member(setname, member))
} else {
Err(err)
}
})
}
pub fn pdfset_new(setname: &str) -> Result<UniquePtr<PDFSet>> {
let config = Config::get();
let lock = LhapdfData::get().lock().unwrap();
lock.pdfset_new(setname).or_else(|err: Error| {
if err.to_string() == format!("Info file not found for PDF set '{setname}'") {
lock.download_set(setname, config)
.and_then(|()| lock.pdfset_new(setname))
} else {
Err(err)
}
})
}
pub fn set_verbosity(verbosity: i32) {
let _ = Config::get();
let lock = LhapdfData::get().lock().unwrap();
lock.set_verbosity(verbosity);
}
pub fn verbosity() -> i32 {
let _ = Config::get();
let lock = LhapdfData::get().lock().unwrap();
lock.verbosity()
}