use crate::{Krate, Source};
use bytes::{BufMut, Bytes, BytesMut};
use failure::{bail, Error, ResultExt};
use log::debug;
use reqwest::Client;
use std::{convert::TryFrom, process::Command};
use tame_gcs::{objects::Object, BucketName, ObjectName};
pub fn from_crates_io(client: &Client, krate: &Krate) -> Result<Bytes, Error> {
match &krate.source {
Source::CratesIo(_) => {
let url = format!(
"https://static.crates.io/crates/{}/{}-{}.crate",
krate.name, krate.name, krate.version
);
let mut response = client.get(&url).send()?.error_for_status()?;
let res = crate::convert_response(&mut response)?;
Ok(res.into_body())
}
Source::Git { .. } => via_git(&krate),
}
}
pub fn from_gcs(
client: &Client,
krate: &Krate,
bucket: &BucketName<'_>,
prefix: &str,
) -> Result<Bytes, Error> {
let object_name = format!("{}{}", prefix, krate.gcs_id());
let object_name = ObjectName::try_from(object_name.as_ref())?;
let dl_req = Object::download(&(bucket, &object_name), None)?;
let (parts, _) = dl_req.into_parts();
let uri = parts.uri.to_string();
let builder = client.get(&uri);
let request = builder.headers(parts.headers).build()?;
let mut response = client.execute(request)?.error_for_status()?;
let res = crate::convert_response(&mut response)?;
Ok(res.into_body())
}
pub fn via_git(krate: &Krate) -> Result<Bytes, Error> {
match &krate.source {
Source::Git { url, ident } => {
let temp_dir = tempfile::tempdir()?;
debug!("cloning {}", krate);
let output = Command::new("git")
.arg("clone")
.arg("--bare")
.arg(url.as_str())
.arg(temp_dir.path())
.output()?;
if !output.status.success() {
let err_out = String::from_utf8(output.stderr)?;
bail!("failed to clone {}: {}", krate, err_out);
}
let rev = &ident[ident.len() - 7..];
let has_revision = Command::new("git")
.arg("cat-file")
.arg("-t")
.arg(rev)
.current_dir(temp_dir.path())
.output()?;
if !has_revision.status.success()
|| String::from_utf8(has_revision.stdout)
.ok()
.as_ref()
.map(|s| s.as_ref())
!= Some("commit\n")
{
bail!(
"git repo {} for {} does not contain revision {}",
url,
krate,
rev
);
}
tarball(temp_dir.path())
}
Source::CratesIo(_) => bail!("{} is not a git source", krate),
}
}
pub fn registry(url: &url::Url) -> Result<Bytes, Error> {
let temp_dir = tempfile::tempdir()?;
let output = Command::new("git")
.arg("init")
.arg("--template=''") .current_dir(&temp_dir)
.output()
.context("git-init")?;
if !output.status.success() {
bail!("failed to initialize registry index repo");
}
debug!("fetching crates.io index");
let output = Command::new("git")
.arg("fetch")
.arg(url.as_str())
.arg("refs/heads/master:refs/remotes/origin/master")
.current_dir(temp_dir.path())
.output()
.context("git-fetch")?;
if !output.status.success() {
bail!("failed to fetch registry index");
}
std::fs::File::create(temp_dir.path().join(".last-updated"))
.context("failed to create .last-updated")?;
tarball(temp_dir.path())
}
fn tarball(path: &std::path::Path) -> Result<Bytes, Error> {
let mut estimated_size = 0;
const TAR_HEADER_SIZE: u64 = 512;
for entry in walkdir::WalkDir::new(path)
.into_iter()
.filter_map(|e| e.ok())
{
estimated_size += TAR_HEADER_SIZE;
if let Ok(md) = entry.metadata() {
estimated_size += md.len();
}
}
let out_buffer = BytesMut::with_capacity(estimated_size as usize);
let buf_writer = out_buffer.writer();
let zstd_encoder = zstd::Encoder::new(buf_writer, 9)?;
let mut archiver = tar::Builder::new(zstd_encoder);
archiver.append_dir_all(".", path)?;
archiver.finish()?;
let zstd_encoder = archiver.into_inner()?;
let buf_writer = zstd_encoder.finish()?;
let out_buffer = buf_writer.into_inner();
Ok(out_buffer.freeze())
}