use tame_index::krate::IndexKrate;
use tame_index::utils::flock::FileLock;
use url::Url;
use super::errors::*;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum CertsSource {
#[default]
Webpki,
Native,
}
pub struct IndexCache {
certs_source: CertsSource,
index: std::collections::HashMap<Url, AnyIndexCache>,
}
impl IndexCache {
#[inline]
pub fn new(certs_source: CertsSource) -> Self {
Self {
certs_source,
index: Default::default(),
}
}
#[inline]
pub fn has_krate(&mut self, registry: &Url, name: &str) -> CargoResult<bool> {
self.index(registry)?.has_krate(name)
}
#[inline]
pub fn has_krate_version(
&mut self,
registry: &Url,
name: &str,
version: &str,
) -> CargoResult<Option<bool>> {
self.index(registry)?.has_krate_version(name, version)
}
#[inline]
pub fn update_krate(&mut self, registry: &Url, name: &str) -> CargoResult<()> {
self.index(registry)?.update_krate(name);
Ok(())
}
pub fn krate(&mut self, registry: &Url, name: &str) -> CargoResult<Option<IndexKrate>> {
self.index(registry)?.krate(name)
}
pub fn index<'s>(&'s mut self, registry: &Url) -> CargoResult<&'s mut AnyIndexCache> {
if !self.index.contains_key(registry) {
let index = AnyIndex::open(registry, self.certs_source)?;
let index = AnyIndexCache::new(index);
self.index.insert(registry.clone(), index);
}
Ok(self.index.get_mut(registry).unwrap())
}
}
pub struct AnyIndexCache {
index: AnyIndex,
cache: std::collections::HashMap<String, Option<IndexKrate>>,
}
impl AnyIndexCache {
#[inline]
pub fn new(index: AnyIndex) -> Self {
Self {
index,
cache: std::collections::HashMap::new(),
}
}
#[inline]
pub fn has_krate(&mut self, name: &str) -> CargoResult<bool> {
Ok(self.krate(name)?.map(|_| true).unwrap_or(false))
}
#[inline]
pub fn has_krate_version(&mut self, name: &str, version: &str) -> CargoResult<Option<bool>> {
let krate = self.krate(name)?;
Ok(krate.map(|ik| ik.versions.iter().any(|iv| iv.version == version)))
}
#[inline]
pub fn update_krate(&mut self, name: &str) {
self.cache.remove(name);
}
pub fn krate(&mut self, name: &str) -> CargoResult<Option<IndexKrate>> {
if let Some(entry) = self.cache.get(name) {
return Ok(entry.clone());
}
let entry = self.index.krate(name)?;
self.cache.insert(name.to_owned(), entry.clone());
Ok(entry)
}
}
pub enum AnyIndex {
Local(LocalIndex),
Remote(RemoteIndex),
}
impl AnyIndex {
pub fn open(url: &Url, certs_source: CertsSource) -> CargoResult<Self> {
if url.scheme() == "file" {
LocalIndex::open(url).map(Self::Local)
} else {
RemoteIndex::open(url, certs_source).map(Self::Remote)
}
}
pub(crate) fn krate(&mut self, name: &str) -> CargoResult<Option<IndexKrate>> {
match self {
Self::Local(index) => index.krate(name),
Self::Remote(index) => index.krate(name),
}
}
}
pub struct LocalIndex {
index: tame_index::index::LocalRegistry,
root: tame_index::PathBuf,
}
impl LocalIndex {
pub fn open(url: &Url) -> CargoResult<Self> {
let path = url
.to_file_path()
.map_err(|()| anyhow::format_err!("invalid local registry {url}"))?;
let path = tame_index::PathBuf::from_path_buf(path)
.map_err(|_err| anyhow::format_err!("invalid local registry {url:?}"))?;
let index = tame_index::index::LocalRegistry::open(path.clone(), false)?;
Ok(Self { index, root: path })
}
pub(crate) fn krate(&mut self, name: &str) -> CargoResult<Option<IndexKrate>> {
let name = tame_index::KrateName::cargo(name)?;
let entry_path = self.index.krate_path(name);
let rel_path = entry_path
.strip_prefix(&self.root)
.map_err(|_err| anyhow::format_err!("invalid index path {entry_path:?}"))?;
let rel_path = rel_path
.strip_prefix("index")
.map_err(|_err| anyhow::format_err!("invalid index path {entry_path:?}"))?;
let entry_path = self.root.join(rel_path);
let entry = std::fs::read(&entry_path)?;
let results = IndexKrate::from_slice(&entry)?;
Ok(Some(results))
}
}
pub struct RemoteIndex {
index: tame_index::SparseIndex,
client: tame_index::external::reqwest::blocking::Client,
lock: FileLock,
etags: Vec<(String, String)>,
}
impl RemoteIndex {
pub fn open(url: &Url, certs_source: CertsSource) -> CargoResult<Self> {
let url = url.to_string();
let url = tame_index::IndexUrl::NonCratesIo(std::borrow::Cow::Owned(url));
let index = tame_index::SparseIndex::new(tame_index::IndexLocation::new(url))?;
let client = {
let builder = tame_index::external::reqwest::blocking::ClientBuilder::new();
let builder = match certs_source {
CertsSource::Webpki => builder.tls_built_in_webpki_certs(true),
CertsSource::Native => builder.tls_built_in_native_certs(true),
};
builder.build()?
};
let lock = FileLock::unlocked();
Ok(Self {
index,
client,
lock,
etags: Vec::new(),
})
}
pub(crate) fn krate(&mut self, name: &str) -> CargoResult<Option<IndexKrate>> {
let etag = self
.etags
.iter()
.find_map(|(krate, etag)| (krate == name).then_some(etag.as_str()))
.unwrap_or("");
let krate_name = name.try_into()?;
let req = self
.index
.make_remote_request(krate_name, Some(etag), &self.lock)?;
let (
tame_index::external::http::request::Parts {
method,
uri,
version,
headers,
..
},
_,
) = req.into_parts();
let mut req = self.client.request(method, uri.to_string());
req = req.version(version);
req = req.headers(headers);
let res = self.client.execute(req.build()?)?;
if let Some(etag) = res
.headers()
.get(tame_index::external::reqwest::header::ETAG)
{
if let Ok(etag) = etag.to_str() {
if let Some(i) = self.etags.iter().position(|(krate, _)| krate == name) {
etag.clone_into(&mut self.etags[i].1);
} else {
self.etags.push((name.to_owned(), etag.to_owned()));
}
}
}
let mut builder = tame_index::external::http::Response::builder()
.status(res.status())
.version(res.version());
builder
.headers_mut()
.unwrap()
.extend(res.headers().iter().map(|(k, v)| (k.clone(), v.clone())));
let body = res.bytes()?;
let response = builder
.body(body.to_vec())
.map_err(|e| tame_index::Error::from(tame_index::error::HttpError::from(e)))?;
self.index
.parse_remote_response(krate_name, response, false, &self.lock)
.map_err(Into::into)
}
}