use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
use deno_semver::package::PackageNv;
use deno_unsync::future::LocalFutureExt;
use deno_unsync::future::SharedLocal;
use futures::FutureExt;
use crate::Executor;
use crate::ModuleSpecifier;
use crate::graph::JsrLoadError;
use crate::packages::JsrPackageInfo;
use crate::packages::JsrPackageVersionInfo;
use crate::rt::JoinHandle;
use crate::rt::spawn;
use crate::source::CacheSetting;
use crate::source::JsrUrlProvider;
use crate::source::LoadError;
use crate::source::LoadOptions;
use crate::source::LoadResponse;
use crate::source::Loader;
use crate::source::LoaderChecksum;
use crate::source::Locker;
#[derive(Debug, Clone)]
pub struct PendingJsrPackageVersionInfoLoadItem {
pub checksum_for_locker: Option<LoaderChecksum>,
pub info: Arc<JsrPackageVersionInfo>,
}
pub type PendingResult<T> = SharedLocal<JoinHandle<Result<T, JsrLoadError>>>;
#[derive(Clone, Copy)]
pub struct JsrMetadataStoreServices<'a> {
pub loader: &'a dyn Loader,
pub executor: &'a dyn Executor,
pub jsr_url_provider: &'a dyn JsrUrlProvider,
}
#[derive(Debug, Default)]
pub struct JsrMetadataStore {
pending_package_info_loads:
RefCell<HashMap<String, PendingResult<Arc<JsrPackageInfo>>>>,
pending_package_version_info_loads: RefCell<
HashMap<PackageNv, PendingResult<PendingJsrPackageVersionInfoLoadItem>>,
>,
}
impl JsrMetadataStore {
pub(crate) fn get_package_metadata(
&self,
package_name: &str,
) -> Option<PendingResult<Arc<JsrPackageInfo>>> {
self
.pending_package_info_loads
.borrow()
.get(package_name)
.cloned()
}
pub(crate) fn remove_package_metadata(&self, package_name: &str) {
self
.pending_package_info_loads
.borrow_mut()
.remove(package_name);
}
pub(crate) fn get_package_version_metadata(
&self,
nv: &PackageNv,
) -> Option<PendingResult<PendingJsrPackageVersionInfoLoadItem>> {
self
.pending_package_version_info_loads
.borrow()
.get(nv)
.cloned()
}
pub(crate) fn queue_load_package_info(
&self,
package_name: &str,
cache_setting: CacheSetting,
services: JsrMetadataStoreServices,
) {
let mut loads = self.pending_package_info_loads.borrow_mut();
if loads.contains_key(package_name) {
return; }
let specifier = services
.jsr_url_provider
.url()
.join(&format!("{}/meta.json", package_name))
.unwrap();
let fut = self.load_data(
specifier,
services,
cache_setting,
None,
|content| {
let package_info: JsrPackageInfo = serde_json::from_slice(content)?;
Ok(Arc::new(package_info))
},
{
let package_name = package_name.to_string();
|e| JsrLoadError::PackageManifestLoad(package_name, Arc::new(e))
},
{
let package_name = package_name.to_string();
|| JsrLoadError::PackageNotFound(package_name)
},
);
loads.insert(package_name.to_string(), fut);
}
pub(crate) fn queue_load_package_version_info(
&self,
package_nv: &PackageNv,
cache_setting: CacheSetting,
maybe_locker: Option<&dyn Locker>,
services: JsrMetadataStoreServices,
) {
let mut loads = self.pending_package_version_info_loads.borrow_mut();
if loads.contains_key(package_nv) {
return; }
let specifier = services
.jsr_url_provider
.url()
.join(&format!(
"{}/{}_meta.json",
package_nv.name, package_nv.version
))
.unwrap();
let maybe_expected_checksum = maybe_locker
.as_ref()
.and_then(|locker| locker.get_pkg_manifest_checksum(package_nv));
let should_compute_checksum =
maybe_expected_checksum.is_none() && maybe_locker.is_some();
let fut = self.load_data(
specifier,
services,
cache_setting,
maybe_expected_checksum,
move |content| {
let version_info: JsrPackageVersionInfo =
serde_json::from_slice(content)?;
let checksum_for_locker = should_compute_checksum.then(|| {
LoaderChecksum::new(
version_info
.lockfile_checksum
.clone()
.unwrap_or_else(|| LoaderChecksum::r#gen(content)),
)
});
Ok(PendingJsrPackageVersionInfoLoadItem {
checksum_for_locker,
info: Arc::new(version_info),
})
},
{
let package_nv = package_nv.clone();
|e| {
match e {
LoadError::ChecksumIntegrity(err) => {
JsrLoadError::PackageVersionManifestChecksumIntegrity(
Box::new(package_nv),
err,
)
}
LoadError::Other(err) => JsrLoadError::PackageVersionManifestLoad(
Box::new(package_nv),
err,
),
}
}
},
{
let package_nv = package_nv.clone();
|| JsrLoadError::PackageVersionNotFound(Box::new(package_nv))
},
);
loads.insert(package_nv.clone(), fut);
}
#[allow(clippy::too_many_arguments)]
fn load_data<T: Clone + 'static>(
&self,
specifier: ModuleSpecifier,
services: JsrMetadataStoreServices,
cache_setting: CacheSetting,
maybe_expected_checksum: Option<LoaderChecksum>,
handle_content: impl FnOnce(&[u8]) -> Result<T, serde_json::Error> + 'static,
create_failed_load_err: impl FnOnce(LoadError) -> JsrLoadError + 'static,
create_not_found_error: impl FnOnce() -> JsrLoadError + 'static,
) -> PendingResult<T> {
let fut = services.loader.load(
&specifier,
LoadOptions {
in_dynamic_branch: false,
was_dynamic_root: false,
cache_setting,
maybe_checksum: maybe_expected_checksum,
},
);
let fut = spawn(
services.executor,
async move {
let data = match fut.await {
Ok(data) => data,
Err(err) => return Err(create_failed_load_err(err)),
};
match data {
Some(LoadResponse::Module { content, .. }) => {
handle_content(&content).map_err(|e| {
create_failed_load_err(LoadError::Other(Arc::new(e)))
})
}
Some(LoadResponse::Redirect { specifier }) => {
Err(JsrLoadError::RedirectInPackage(specifier))
}
_ => Err(create_not_found_error()),
}
}
.boxed_local(),
);
fut.shared_local()
}
}