49 lines
1.3 KiB
Rust
49 lines
1.3 KiB
Rust
|
|
use anyhow::{Context, Result};
|
||
|
|
use serde::Deserialize;
|
||
|
|
use std::path::{Path, PathBuf};
|
||
|
|
|
||
|
|
#[derive(Debug, Deserialize)]
|
||
|
|
struct Release {
|
||
|
|
assets: Vec<Asset>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Deserialize)]
|
||
|
|
struct Asset {
|
||
|
|
name: String,
|
||
|
|
browser_download_url: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Downloads the release asset named exactly `asset_name` for `repo`@`tag`
|
||
|
|
/// into `dest_dir`, returning the local path.
|
||
|
|
pub fn download_asset(
|
||
|
|
client: &reqwest::blocking::Client,
|
||
|
|
repo: &str,
|
||
|
|
tag: &str,
|
||
|
|
asset_name: &str,
|
||
|
|
dest_dir: &Path,
|
||
|
|
) -> Result<PathBuf> {
|
||
|
|
let api_url = format!("https://api.github.com/repos/{repo}/releases/tags/{tag}");
|
||
|
|
let release: Release = client
|
||
|
|
.get(&api_url)
|
||
|
|
.send()?
|
||
|
|
.error_for_status()
|
||
|
|
.with_context(|| format!("fetching release metadata from {api_url}"))?
|
||
|
|
.json()?;
|
||
|
|
|
||
|
|
let asset = release
|
||
|
|
.assets
|
||
|
|
.iter()
|
||
|
|
.find(|a| a.name == asset_name)
|
||
|
|
.with_context(|| format!("no asset named '{asset_name}' in {repo}@{tag}"))?;
|
||
|
|
|
||
|
|
std::fs::create_dir_all(dest_dir)?;
|
||
|
|
let dest_path = dest_dir.join(&asset.name);
|
||
|
|
let bytes = client
|
||
|
|
.get(&asset.browser_download_url)
|
||
|
|
.send()?
|
||
|
|
.error_for_status()?
|
||
|
|
.bytes()?;
|
||
|
|
std::fs::write(&dest_path, &bytes)?;
|
||
|
|
Ok(dest_path)
|
||
|
|
}
|