Skip to content

Commit

Permalink
Merge pull request #52 from thinkgos/feature-dl
Browse files Browse the repository at this point in the history
feat: add downloads manager command
  • Loading branch information
thinkgos authored Feb 4, 2024
2 parents c0f35a0 + 7d8be1f commit b265484
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 41 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ $ goup rm
✔ Select multiple version · 1.21.5
```

### Manage download archive files

```bash
$ goup dl show --contain-sha256
go1.21.6.linux-amd64.tar.gz
go1.21.6.linux-amd64.tar.gz.sha256

$ goup dl clean
✔ Do you want to clean archive file? · yes
```

### Modify the goup installation

```bash
Expand Down Expand Up @@ -191,6 +202,7 @@ $ goup env
- `goup ls/list/show` list all installed Go version located at `$HOME/.goup`.
- `goup remove/rm [VERSION]...` remove the specified Go version list.
- `goup search [FILTER]` lists all available Go versions.
- `goup downloads [COMMAND]` Manage download archive files.
- `goup upgrade` upgrades `goup`, deprecated in future version, use `goup self update` instead
- `goup self <COMMAND>` Modify the goup installation.
- `goup init` write all necessary environment variables and values to `$HOME/.goup/env`.
Expand Down
96 changes: 67 additions & 29 deletions goup-downloader/src/downloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,25 +112,37 @@ impl Downloader {
);
return Ok(());
}
// 压缩包url
let archive_url = consts::go_version_archive_url(version);
// 压缩包长度
let archive_content_length =
Self::get_upstream_archive_content_length(version, &archive_url)?;
// download directory
let dl_dest_dir = Dir::new(&home).dl();
// 压缩包文件名称
let archive_file_name = Path::new(&archive_url)
.file_name()
.ok_or_else(|| anyhow!("Getting archive filename failure."))?
.to_string_lossy();
// 版本路径
if !version_dest_dir.exists() {
fs::create_dir_all(&version_dest_dir)?
let archive_filename = consts::go_version_archive(version);
// 压缩包sha256文件名称
let archive_sha256_filename = consts::archive_sha256(&archive_filename);
// 压缩包url
let (archive_url, archive_sha256_url) = consts::archive_url(&archive_filename);
if !dl_dest_dir.exists() {
log::debug!("Create download directory");
fs::create_dir_all(&dl_dest_dir)?
}

// 压缩包文件
let archive_file = version_dest_dir.join(archive_file_name.as_ref());
if !archive_file.exists() || archive_file.metadata()?.len() != archive_content_length {
// 下载
let archive_file = dl_dest_dir.join(archive_filename);
let archive_sha256_file = dl_dest_dir.join(archive_sha256_filename);
if !archive_file.exists()
|| !archive_sha256_file.exists()
|| Self::verify_archive_file_sha256(&archive_file, &archive_sha256_file).is_err()
{
log::debug!(
"Download archive file from {} to {}",
archive_url,
archive_file.display(),
);
// 下载压缩包
Self::download_archive(&archive_file, &archive_url)?;
log::debug!("Check archive file content length");
// 压缩包长度
let archive_content_length =
Self::get_upstream_archive_content_length(version, &archive_url)?;
// 检查大小
let got_archive_content_length = archive_file.metadata()?.len();
if got_archive_content_length != archive_content_length {
Expand All @@ -141,11 +153,27 @@ impl Downloader {
archive_content_length,
));
}
// 下载压缩包sha256
log::debug!(
"Download archive sha256 file from {} to {}",
archive_sha256_url,
archive_sha256_file.display()
);
Self::download_archive_sha256(&archive_sha256_file, &archive_sha256_url)?;
// 校验压缩包sha256
Self::verify_archive_file_sha256(&archive_file, &archive_sha256_file)?;
}
// 校验压缩包sha256
Self::verify_archive_file_sha256(&archive_file, &archive_url)?;

// 解压
log::info!("Unpacking {} ...", archive_file.display());
log::info!(
"Unpacking {} to {} ...",
archive_file.display(),
version_dest_dir.display()
);
if !version_dest_dir.exists() {
log::debug!("Create version directory: {}", version_dest_dir.display());
fs::create_dir_all(&version_dest_dir)?
}
archive_file
.to_string_lossy()
.parse::<Unpack>()?
Expand Down Expand Up @@ -199,11 +227,20 @@ impl Downloader {
response.copy_to(&mut file)?;
Ok(())
}

/// get_upstream_archive_file_sha256 获取上游压缩包的sha256值
fn get_upstream_archive_file_sha256(archive_url: &str) -> Result<String, anyhow::Error> {
Ok(blocking::get(format!("{}.sha256", archive_url))?.text()?)
/// download_archive_sha256 下载压缩包sha256
fn download_archive_sha256<P: AsRef<Path>>(
dest: P,
archive_sha256_url: &str,
) -> Result<(), anyhow::Error> {
let mut response = blocking::get(archive_sha256_url)?;
if !response.status().is_success() {
return Err(anyhow!("Downloading archive failure"));
}
let mut file = File::create(dest)?;
response.copy_to(&mut file)?;
Ok(())
}

/// compute_file_sha256 计算文件的sha256
fn compute_file_sha256<P: AsRef<Path>>(path: P) -> Result<String, anyhow::Error> {
let mut context = Sha256::new();
Expand All @@ -218,18 +255,19 @@ impl Downloader {
}
Ok(format!("{:x}", context.finalize()))
}
/// verify_archive_file_sha256 校验本地文件和上游文件的sha256
fn verify_archive_file_sha256<P: AsRef<Path>>(
path: P,
archive_url: &str,

/// verify_archive_file_sha256 校验文件压缩包的sha256
fn verify_archive_file_sha256<P1: AsRef<Path>, P2: AsRef<Path>>(
archive_file: P1,
archive_sha256_file: P2,
) -> Result<(), anyhow::Error> {
let expect_sha256 = Self::get_upstream_archive_file_sha256(archive_url)?;
let expect_sha256 = fs::read_to_string(archive_sha256_file)?;
let expect_sha256 = expect_sha256.trim();
let got_sha256 = Self::compute_file_sha256(&path)?;
let got_sha256 = Self::compute_file_sha256(&archive_file)?;
if expect_sha256 != got_sha256 {
return Err(anyhow!(
"{} corrupt? does not have expected SHA-256 of {}",
path.as_ref().display(),
archive_file.as_ref().display(),
expect_sha256,
));
}
Expand Down
34 changes: 25 additions & 9 deletions goup-version/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ pub fn go_source_upstream_git_url() -> String {
})
}

// go_version_archive_url returns the zip or tar.gz URL of the given Go version.
pub fn go_version_archive_url(version: &str) -> String {
// go_version_archive returns the zip or tar.gz of the given Go version.
// (go1.21.5.linux-amd64.tar.gz, go1.21.5.linux-amd64.tar.gz.sha256)
pub fn go_version_archive(version: &str) -> String {
let os = env::consts::OS;
let arch = match (os, env::consts::ARCH) {
(_, "x86") => "386",
Expand All @@ -39,16 +40,31 @@ pub fn go_version_archive_url(version: &str) -> String {
_ => env::consts::ARCH,
};
let ext = if os == "windows" { "zip" } else { "tar.gz" };
format!(
"{}/{}.{}-{}.{}",
go_download_base_url(),
version,
os,
arch,
ext
format!("{}.{}-{}.{}", version, os, arch, ext)
}

// archive_sha256 returns `{archive}.sha256`
// go1.21.5.linux-amd64.tar.gz.sha256
#[inline]
pub fn archive_sha256(archive_filename: &str) -> String {
format!("{}.sha256", archive_filename)
}

// archive_url returns returns the zip or tar.gz URL of the given Go version.
#[inline]
pub fn archive_url(archive_filename: &str) -> (String, String) {
let host = go_download_base_url();
(
format!("{}/{}", host, archive_filename),
format!("{}/{}.sha256", host, archive_filename),
)
}

// go_version_archive_url returns the zip or tar.gz URL of the given Go version.
pub fn go_version_archive_url(version: &str) -> String {
format!("{}/{}", go_download_base_url(), go_version_archive(version))
}

#[inline]
fn get_var_or_else(key: &str, op: impl FnOnce() -> String) -> String {
env::var(key)
Expand Down
5 changes: 3 additions & 2 deletions goup-version/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ impl Version {
Ok(current)
}

pub fn list_dl(contain_sha256: bool) -> Result<Vec<String>, anyhow::Error> {
pub fn list_dl(contain_sha256: Option<bool>) -> Result<Vec<String>, anyhow::Error> {
let home = Dir::home_dir()?;
// may be .goup or .goup/dl not exist
if !Dir::new(&home).exists() || !Dir::new(&home).dl().exists() {
Expand All @@ -201,7 +201,8 @@ impl Version {
}
let filename = v.file_name();
let filename = filename.to_string_lossy();
(contain_sha256 || !filename.ends_with(".sha256")).then(|| filename.to_string())
(contain_sha256.unwrap_or_default() || !filename.ends_with(".sha256"))
.then(|| filename.to_string())
})
.collect();
archive_files.sort();
Expand Down
53 changes: 53 additions & 0 deletions goup/src/command/downloads.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use clap::Args;
use clap::Subcommand;
use dialoguer::theme::ColorfulTheme;
use dialoguer::Confirm;
use goup_version::Version;

use super::Run;

#[derive(Args, Debug, PartialEq)]
pub struct Downloads {
// outputs the completion content for given shell
#[command(subcommand)]
command: Command,
}

#[derive(Subcommand, Clone, Debug, PartialEq)]
enum Command {
/// Show download archive file
Show(Show),
/// Clean download archive file
Clean,
}

#[derive(Args, Clone, Debug, PartialEq)]
struct Show {
#[arg(short, long, default_value_t = false)]
contain_sha256: bool,
}

impl Run for Downloads {
fn run(&self) -> Result<(), anyhow::Error> {
match self.command {
Command::Show(ref show) => {
Version::list_dl(Some(show.contain_sha256))?
.iter()
.for_each(|v| {
println!("{}", v);
});
}
Command::Clean => {
let confirmation = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Do you want to clean archive file?")
.interact()?;
if confirmation {
Version::remove_dl()?;
} else {
log::info!("Cancelled");
}
}
}
Ok(())
}
}
6 changes: 6 additions & 0 deletions goup/src/command/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod completion;
mod downloads;
mod env;
#[cfg(unix)]
mod init;
Expand All @@ -17,6 +18,7 @@ use shadow_rs::shadow;
use std::env::consts::{ARCH, OS};

use self::completion::Completion;
use self::downloads::Downloads;
use self::env::Env;
#[cfg(unix)]
use self::init::Init;
Expand Down Expand Up @@ -117,6 +119,9 @@ enum Command {
/// Modify the goup installation.
#[command(name = "self")]
Oneself(Oneself),
/// Manage download archive files.
#[command(visible_alias = "dl")]
Downloads(Downloads),
}

impl Run for Cli {
Expand All @@ -136,6 +141,7 @@ impl Run for Cli {
#[cfg(unix)]
Command::Init(cmd) => cmd.run(),
Command::Env(cmd) => cmd.run(),
Command::Downloads(cmd) => cmd.run(),
Command::Completion(c) => completion::print_completions(c.shell, &mut Self::command()),
}
}
Expand Down
2 changes: 1 addition & 1 deletion goup/src/command/oneself.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use super::Run;

#[derive(Args, Debug, PartialEq)]
pub struct Oneself {
// outputs the completion content for given shell
// the goup installation command.
#[command(subcommand)]
command: Command,
}
Expand Down

0 comments on commit b265484

Please sign in to comment.