rget/src/install_package.rs
2023-09-18 03:30:20 -05:00

195 lines
5.5 KiB
Rust

use std::{path::PathBuf, sync::Arc};
use anyhow::{ensure, Context, Result};
use async_compression::tokio::bufread::GzipDecoder;
use async_tar::Archive;
use futures::TryStreamExt;
use reqwest::Client;
use semver::{Version, VersionReq};
use sha1::{Digest, Sha1};
use tempfile::NamedTempFile;
use tokio::{
fs::File,
io::{AsyncReadExt, AsyncWriteExt, BufReader},
};
use tokio_util::{compat::TokioAsyncReadCompatExt, io::StreamReader};
use crate::{
parse_package::{parse_package_json, Package, PackageMetadata},
queue_utils::add_reqs_to_queue,
TaskQueue,
};
#[derive(Debug)]
pub enum PackageDef {
// Look up from registry
Name(String, VersionReq),
// Local file
Local(PathBuf),
}
pub async fn install_package(
queue: Arc<TaskQueue>,
package_def: PackageDef,
client: Client,
rget_path: PathBuf,
) -> Result<()> {
match package_def {
PackageDef::Name(name, _version_req) => {
let uri = format!("https://registry.npmjs.org/{}", name);
let res = client.get(uri).send().await?;
let mut body = res.text().await?;
let res: PackageMetadata =
simd_json::from_slice(unsafe { body.as_bytes_mut() })?;
// println!("body {res:?}");
let matching_versions = res
.versions
.iter()
.map(|(version, object)| {
(Version::parse(version).expect("wtf"), object)
})
// .filter(|(version, _)| version_req.matches(&version))
.collect::<Vec<_>>();
// println!("Matching versions: {matching_versions:?}");
// TODO: Take the highest version lmao
let (used_version, version_object) = matching_versions.first().unwrap();
let version = used_version.to_string();
let base_path = rget_path.join(format!("{name}@{version}"));
let tarball_fut = download_tarball(
&client,
base_path,
&version_object.dist.tarball,
&version_object.dist.shasum,
);
let deps_fut = add_deps(&queue, &client, &name, used_version);
let (tarball_res, deps_res) = futures::join!(tarball_fut, deps_fut,);
tarball_res?;
deps_res?;
Ok::<_, anyhow::Error>(())
}
PackageDef::Local(path) => {
let package = parse_package_json(path)?;
println!("parsed :3");
add_reqs_to_queue(&queue, &package).await?;
println!("added :3");
Ok(())
}
}
}
async fn add_deps(
queue: &TaskQueue,
client: &Client,
name: &str,
used_version: &Version,
) -> Result<()> {
let uri = format!(
"https://registry.npmjs.org/{}/{}",
name,
used_version.to_string()
);
let res = client.get(uri).send().await?;
let mut body = res.text().await?;
let package: Package = simd_json::from_slice(unsafe { body.as_bytes_mut() })?;
add_reqs_to_queue(&queue, &package).await?;
Ok(())
}
async fn download_tarball(
client: &Client,
base_path: PathBuf,
url: &str,
sum: &str,
) -> Result<()> {
let dest = NamedTempFile::new()?;
let (file, path) = dest.keep()?;
let mut dest_file = File::from_std(file);
let res = client.get(url).send().await?;
let stream = res.bytes_stream();
let mut stream_reader = StreamReader::new(stream.map_err(convert_error));
// Hashing copy
let mut buf = [0u8; 4096];
let mut hasher = Sha1::new();
loop {
let bytes = stream_reader.read(&mut buf).await?;
if bytes == 0 {
break;
}
dest_file.write(&buf[..bytes]).await?;
hasher.update(&buf[..bytes]);
}
// tokio::io::copy(&mut stream_reader, &mut dest_file).await?;
let actual_hash = hasher.finalize();
let actual_hash_str = format!("{:x}", actual_hash);
ensure!(sum == actual_hash_str, "SHIT the hash is wrong.");
std::mem::drop(dest_file);
tokio::fs::remove_dir_all(&base_path).await;
tokio::fs::create_dir_all(&base_path).await?;
fn convert_error(err: reqwest::Error) -> std::io::Error {
eprintln!("WTF? {err}");
todo!()
}
// let stream = res.bytes_stream();
// let stream_reader = StreamReader::new(stream.map_err(convert_error));
let file = File::open(path).await?;
let file_reader = BufReader::new(file);
let decoder = GzipDecoder::new(file_reader);
let decoder = TokioAsyncReadCompatExt::compat(decoder);
let archive = Archive::new(decoder);
let temp_dir = tempfile::tempdir()?;
let temp_dir = temp_dir.into_path();
archive.unpack(&temp_dir).await?;
let package_dir = temp_dir.join("package");
tokio::fs::rename(package_dir, &base_path)
.await
.context("could not rename")?;
base_path.read_dir()?.try_for_each(|x| {
let y = x?;
// println!("wtf? {}", y.path().display());
Ok::<_, anyhow::Error>(())
})?;
// println!("Unpacked {}", base_path.display());
// let files = archive.entries()?;
// let files = files.try_collect::<Vec<_>>().await?;
// for mut file in files {
// let tar_path = file.path()?;
// let without_prefix = match tar_path.strip_prefix("package/") {
// Ok(v) => v,
// Err(_) => continue,
// };
// let mut new_path = base_path.clone();
// new_path.extend(without_prefix);
// let parent = new_path.parent().unwrap();
// tokio::fs::create_dir_all(parent).await?;
// file.unpack(&new_path).await?;
// println!("- Unpacked {}", new_path.display());
// // println!("new path: {new_path:?}");
// }
// println!("Installed to {}", base_path.display());
// // println!("files: {:?}", files);
// // let mut content = Cursor::new(res.bytes().await?);
// // println!("dest: {dest:?}");
// // io::copy(&mut content, &mut dest).await?;
// // dest.seek(SeekFrom::Start(0)).await?;
Ok(())
}