use std::fs::{self, File};
use std::io;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use super::marketplace;
#[derive(Debug)]
pub struct Downloaded {
pub path: PathBuf,
pub label: String,
pub version: String,
}
fn default_dir() -> PathBuf {
dirs::download_dir()
.or_else(|| dirs::home_dir().map(|home| home.join("Downloads")))
.unwrap_or_else(|| PathBuf::from("."))
}
pub fn resolve_and_download(input: &str) -> Result<Downloaded> {
resolve_and_download_to(input, None)
}
pub fn resolve_and_download_to(input: &str, dir: Option<&Path>) -> Result<Downloaded> {
let ext = marketplace::parse_extension_input(input).ok_or_else(|| {
anyhow!("无法解析输入。请使用 \"publisher.extension\" 或完整的 Marketplace 链接")
})?;
let resolved = marketplace::resolve_extension(&ext)?;
let url = marketplace::build_download_url(&ext, &resolved.version);
let filename = marketplace::build_vsix_filename(&ext, &resolved.version);
let dest = match dir {
Some(dir) => dir.join(&filename),
None => default_dir().join(&filename),
};
download_to_file(&url, &dest)?;
Ok(Downloaded {
path: dest,
label: resolved.display_name,
version: resolved.version,
})
}
fn download_to_file(url: &str, dest: &PathBuf) -> Result<()> {
let response = match ureq::get(url).call() {
Ok(response) => response,
Err(ureq::Error::StatusCode(code)) => return Err(anyhow!("下载失败(HTTP {code})")),
Err(err) => return Err(anyhow!("下载失败:{err}")),
};
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)?;
}
let mut reader = response.into_body().into_reader();
let mut file = File::create(dest)?;
if let Err(err) = io::copy(&mut reader, &mut file) {
let _ = fs::remove_file(dest);
return Err(anyhow!("写入文件失败:{err}"));
}
Ok(())
}
pub fn human_size(path: &PathBuf) -> String {
let bytes = fs::metadata(path).map(|m| m.len()).unwrap_or(0);
let kb = bytes as f64 / 1024.0;
if kb < 1024.0 {
format!("{kb:.0} KB")
} else {
format!("{:.1} MB", kb / 1024.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalid_input_errors_before_network() {
let err = resolve_and_download("not-an-extension").unwrap_err();
assert!(err.to_string().contains("无法解析输入"));
}
}