Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions src/commands/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod progress_reader;
use super::{Command, Config};
use crate::curl;
use crate::decorized::Decorized;
use crate::release::{self, Hash};
use crate::releases::{self, hash::Hash, Installable};
use crate::version::{self, Version};
use colored::Colorize;
use flate2::read::GzDecoder;
Expand Down Expand Up @@ -51,13 +51,13 @@ pub enum Error {
UnsupportedPHP3,

#[error(transparent)]
FailedFetchRelease(#[from] release::FetchError),
FailedFetchRelease(#[from] releases::FetchError),

#[error(transparent)]
FailedDownload(#[from] curl::Error),

#[error(transparent)]
InvalidChecksum(#[from] release::ChecksumError),
InvalidChecksum(#[from] releases::ChecksumError),

#[error(transparent)]
FailedMake(#[from] make::Error),
Expand All @@ -78,8 +78,8 @@ impl Command for Install {
return Err(Error::UnsupportedPHP3);
}

let release = release::fetch_latest(request_version)?;
let install_version = release.version.unwrap();
let installable = Installable::fetch(request_version)?;
let install_version = installable.version();

if version::latest_installed_by(&request_version, config) == Some(install_version) {
println!(
Expand All @@ -100,7 +100,7 @@ impl Command for Install {
.prefix(".downloads-")
.tempdir_in(&config.base_dir())?;

let (url, checksum) = release.source_url();
let (url, checksum) = installable.source_url();

let tar_gz = download(&url, &download_dir)?;
verify(&tar_gz, checksum)?;
Expand Down Expand Up @@ -140,7 +140,8 @@ impl Install {

fn download(url: &str, dir: impl AsRef<Path>) -> Result<PathBuf, Error> {
let curl::Header { content_length } = curl::get_header(url)?;
let progress_bar = ProgressBar::new(content_length.unwrap() as u64)
// Sometimes we don't get a content length header
let progress_bar = ProgressBar::new(content_length.unwrap_or(0) as u64)
.with_style(PROGRESS_STYLE.clone())
.with_prefix("Downloading")
.with_message(url.to_owned());
Expand Down
38 changes: 28 additions & 10 deletions src/commands/list_remote.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use super::{Command, Config};
use crate::release;
use crate::releases;
use crate::version;
use crate::version::Local;
use crate::version::Version;
use colored::Colorize;
use itertools::Itertools;
use std::collections::BTreeSet;
use thiserror::Error;

#[derive(clap::Parser, Debug)]
Expand All @@ -21,7 +22,7 @@ pub struct ListRemote {
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
FailedFetchRelease(#[from] release::FetchError),
FailedFetchRelease(#[from] releases::FetchError),
}

impl Command for ListRemote {
Expand Down Expand Up @@ -52,18 +53,35 @@ impl Command for ListRemote {

let installed_versions = version::installed(config).collect_vec();
let current_version = Local::current(config);
let pre_releases = releases::pre_release::fetch_all()?;

for query_version in query_versions {
let releases = release::fetch_all(query_version)?;
let remote_versions = releases.keys();

let remote_versions = if self.only_latest_patch {
filter_latest_patch(remote_versions).collect_vec()
let remote_versions = if query_version.pre_type().is_some() {
match pre_releases.get(&query_version) {
Some(v) => Ok(vec![v.version]),
None => Err(releases::FetchError::NotFoundRelease(query_version)),
}
} else {
remote_versions.collect_vec()
};
let releases = releases::release::fetch_all(query_version);
let pre_release_keys = pre_releases
.get_versions_included_by(&query_version)
.sorted();
match releases {
Ok(r) => {
let mut keys: BTreeSet<Version> = r.keys().copied().collect();
keys.extend(pre_release_keys);
if self.only_latest_patch {
Ok(filter_latest_patch(keys.iter()).copied().collect_vec())
} else {
Ok(keys.iter().copied().collect_vec())
}
}
Err(_) if pre_release_keys.len() > 0 => Ok(pre_release_keys.copied().collect()),
Err(e) => Err(e),
}
}?;

for &remote_version in remote_versions {
for remote_version in remote_versions {
let installed = installed_versions.contains(&remote_version);
let remote_version = Local::Installed(remote_version);
let used = Some(&remote_version) == current_version.as_ref();
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod commands;
pub mod config;
pub mod curl;
pub mod decorized;
pub mod release;
pub mod releases;
pub mod shell;
pub mod symlink;
pub mod version;
63 changes: 63 additions & 0 deletions src/releases.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use self::{hash::Hash, pre_release::PreRelease, release::Release};
use crate::curl;
use crate::version::Version;
use thiserror::Error;

pub mod hash;
pub mod pre_release;
pub mod release;

pub enum Installable {
Stable(Release),
Pre(PreRelease),
}

impl Installable {
pub fn fetch(version: Version) -> Result<Self, FetchError> {
match version.pre_type() {
Some(_) => {
let pre_release = pre_release::fetch(version)?;
Ok(Self::Pre(pre_release))
}
None => {
let release = release::fetch_latest(version)?;
Ok(Self::Stable(release))
}
}
}

pub fn version(&self) -> Version {
match self {
Self::Stable(release) => release.version.unwrap(),
Self::Pre(pre_release) => pre_release.version,
}
}

pub fn source_url(&self) -> (String, Option<&Hash>) {
match self {
Self::Stable(release) => release.source_url(),
Self::Pre(pre_release) => pre_release.source_url(),
}
}
}

#[derive(Error, Debug)]
pub enum FetchError {
#[error("Can't find releases that matches {0}")]
NotFoundRelease(Version),

#[error(transparent)]
CurlError(#[from] curl::Error),

#[error("Receive error message from release site: {0}")]
Other(String),
}

#[derive(Error, Debug)]
pub enum ChecksumError {
#[error("Invalid checksum\nexptected: {expected}\ngot: {got}")]
InvalidChecksum { expected: String, got: String },

#[error(transparent)]
Io(#[from] std::io::Error),
}
43 changes: 43 additions & 0 deletions src/releases/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all(deserialize = "lowercase", serialize = "lowercase"))]
pub enum Hash {
SHA256(String),
MD5(String),
}

use crate::releases::ChecksumError;
use serde::{Deserialize, Serialize};
use sha2::Digest;

impl Hash {
pub fn hash_type(&self) -> &'static str {
match self {
Hash::SHA256(_) => "SHA-256",
Hash::MD5(_) => "MD5",
}
}
pub fn verify(&self, mut data: impl std::io::Read) -> Result<(), ChecksumError> {
let (checksum, hash) = match self {
Hash::SHA256(checksum) => {
let mut sha256 = sha2::Sha256::new();
std::io::copy(&mut data, &mut sha256)?;
let hash = sha256.finalize();
(checksum, format!("{:x}", hash))
}
Hash::MD5(checksum) => {
let mut md5 = md5::Context::new();
std::io::copy(&mut data, &mut md5)?;
let hash = md5.compute();
(checksum, format!("{:x}", hash))
}
};
if checksum == &hash {
Ok(())
} else {
Err(ChecksumError::InvalidChecksum {
expected: checksum.clone(),
got: hash,
})
}
}
}
Loading