195 lines
5.5 KiB
Rust
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(())
|
|
}
|