From 3ad9790bc7e7cbd0bf236a94875670490b766eca Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 24 Jan 2023 18:34:10 +0100 Subject: [PATCH] Move downloading part to downloader subdir in repository --- src/error.rs | 13 -- src/main.rs | 1 - src/repository/{ => downloader}/extract.rs | 0 src/repository/downloader/mod.rs | 128 ++++++++++++++++++ .../downloader/version_info.rs} | 0 src/repository/mod.rs | 74 +++------- src/repository/versions.rs | 4 +- src/web_api/mod.rs | 96 ------------- src/web_api/test.rs | 19 --- 9 files changed, 152 insertions(+), 183 deletions(-) rename src/repository/{ => downloader}/extract.rs (100%) create mode 100644 src/repository/downloader/mod.rs rename src/{web_api/model.rs => repository/downloader/version_info.rs} (100%) delete mode 100644 src/web_api/mod.rs delete mode 100644 src/web_api/test.rs diff --git a/src/error.rs b/src/error.rs index dec3728..51b6449 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,19 +4,6 @@ use miette::{Diagnostic, NamedSource, SourceSpan}; use thiserror::Error; -use crate::repository::extract::ExtractError; - -#[derive(Debug, Error, Diagnostic)] -pub enum Error { - #[diagnostic(code(nenv::extract))] - #[error("The node archive could not be extracted")] - Extract(#[from] ExtractError), - - #[diagnostic(code(nenv::version))] - #[error("The passed version is invalid")] - Version(#[from] VersionError), -} - #[derive(Debug, Error, Diagnostic)] #[error("{detail}")] #[diagnostic(code(nenv::version), help("Make sure there's no typo in the version."))] diff --git a/src/main.rs b/src/main.rs index 3569127..2042d4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,6 @@ pub mod error; pub mod mapper; pub mod repository; mod utils; -mod web_api; use miette::Result; use tracing::metadata::LevelFilter; use tracing_subscriber::fmt::format::FmtSpan; diff --git a/src/repository/extract.rs b/src/repository/downloader/extract.rs similarity index 100% rename from src/repository/extract.rs rename to src/repository/downloader/extract.rs diff --git a/src/repository/downloader/mod.rs b/src/repository/downloader/mod.rs new file mode 100644 index 0000000..bda1a20 --- /dev/null +++ b/src/repository/downloader/mod.rs @@ -0,0 +1,128 @@ +use std::{ + cmp::min, + fmt::Debug, + fmt::Display, + path::{Path, PathBuf}, +}; + +use crate::{ + config::ConfigAccess, + consts::{CACHE_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR}, + error::ReqwestError, + utils::progress_bar, +}; + +use super::versions::SimpleVersion; +use futures::StreamExt; +use miette::{miette, Context, IntoDiagnostic, Result}; +use tokio::{ + fs::File, + io::{AsyncWrite, AsyncWriteExt, BufWriter}, +}; +mod extract; +mod version_info; +pub use version_info::VersionInfo; + +#[derive(Clone)] +pub struct NodeDownloader { + config: ConfigAccess, +} + +impl NodeDownloader { + pub fn new(config: ConfigAccess) -> Self { + Self { config } + } + + /// Returns the list of available node versions + #[tracing::instrument(level = "debug", skip(self))] + pub async fn get_versions(&self) -> Result> { + let versions = reqwest::get(format!("{}/index.json", self.base_url().await)) + .await + .map_err(ReqwestError::from) + .context("Fetching versions")? + .json() + .await + .map_err(ReqwestError::from) + .context("Parsing versions response")?; + + Ok(versions) + } + + /// Downloads a specified node version to the repository + #[tracing::instrument(level = "debug", skip(self))] + pub async fn download(&self, version: &SimpleVersion) -> Result<()> { + let archive_path = self.download_archive_to_cache(version).await?; + self.extract_archive(version, &archive_path)?; + + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self))] + fn extract_archive(&self, version: &SimpleVersion, archive_path: &Path) -> Result<()> { + let dst_path = NODE_VERSIONS_DIR.join(version.to_string()); + extract::extract_file(archive_path, &dst_path)?; + + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self))] + async fn download_archive_to_cache(&self, version: &SimpleVersion) -> Result { + let download_path = CACHE_DIR.join(format!("node-v{}{}", version, *NODE_ARCHIVE_SUFFIX)); + + if download_path.exists() { + return Ok(download_path); + } + let mut download_writer = + BufWriter::new(File::create(&download_path).await.into_diagnostic()?); + self.download_archive(version.to_string(), &mut download_writer) + .await?; + + Ok(download_path) + } + /// Downloads a specific node version + /// and writes it to the given writer + #[tracing::instrument(level = "debug", skip(self, writer))] + pub async fn download_archive( + &self, + version: S, + writer: &mut W, + ) -> Result { + let res = reqwest::get(format!( + "{}/v{version}/node-v{version}{}", + self.base_url().await, + *NODE_ARCHIVE_SUFFIX + )) + .await + .map_err(ReqwestError::from) + .context("Downloading nodejs")?; + + let total_size = res + .content_length() + .ok_or_else(|| miette!("Missing content_length header"))?; + + let pb = progress_bar(total_size); + pb.set_message(format!("Downloading node v{version}")); + let mut stream = res.bytes_stream(); + let mut total_downloaded = 0; + + while let Some(item) = stream.next().await { + let chunk = item.map_err(ReqwestError::from)?; + writer + .write_all(&chunk) + .await + .into_diagnostic() + .context("Writing download chunk to file")?; + total_downloaded = min(chunk.len() as u64 + total_downloaded, total_size); + pb.set_position(total_downloaded); + } + + writer.flush().await.into_diagnostic()?; + pb.finish_with_message(format!("Downloaded node v{version}.")); + + Ok(total_downloaded) + } + + async fn base_url(&self) -> String { + self.config.get().await.download.dist_base_url.to_owned() + } +} diff --git a/src/web_api/model.rs b/src/repository/downloader/version_info.rs similarity index 100% rename from src/web_api/model.rs rename to src/repository/downloader/version_info.rs diff --git a/src/repository/mod.rs b/src/repository/mod.rs index a220f48..9707913 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -1,34 +1,26 @@ use core::fmt; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; +use std::{path::PathBuf, str::FromStr}; use futures::future; use semver::{Version, VersionReq}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use tokio::{ - fs::{self, File}, - io::BufWriter, -}; +use tokio::fs; use crate::{ config::ConfigAccess, - consts::{ - ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR, OS, - }, + consts::{ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_VERSIONS_DIR, OS}, error::VersionError, - web_api::WebApi, }; use miette::{IntoDiagnostic, Result}; use self::{ + downloader::NodeDownloader, node_path::NodePath, versions::{SimpleVersion, SimpleVersionInfo, Versions}, }; -pub(crate) mod extract; +pub mod downloader; pub(crate) mod node_path; pub mod versions; @@ -89,7 +81,7 @@ impl fmt::Display for NodeVersion { pub struct Repository { versions: Versions, - web_api: WebApi, + downloader: NodeDownloader, } impl Repository { @@ -97,10 +89,13 @@ impl Repository { #[tracing::instrument(level = "debug", skip_all)] pub async fn init(config: ConfigAccess) -> Result { Self::create_folders().await?; - let web_api = WebApi::new(&config.get().await.download.dist_base_url); - let versions = load_versions(&web_api).await?; + let downloader = NodeDownloader::new(config.clone()); + let versions = load_versions(&downloader).await?; - Ok(Self { web_api, versions }) + Ok(Self { + downloader, + versions, + }) } #[tracing::instrument(level = "debug")] @@ -165,6 +160,13 @@ impl Repository { Ok(build_version_path(&info.version).exists()) } + /// Installs the given node version + #[tracing::instrument(level = "debug", skip(self))] + pub async fn install_version(&self, version: &NodeVersion) -> Result<()> { + let info = self.lookup_version(version)?; + self.downloader.download(&info.version).await + } + /// Performs a lookup for the given node version #[tracing::instrument(level = "debug", skip(self))] pub fn lookup_version( @@ -192,49 +194,15 @@ impl Repository { pub fn all_versions(&self) -> &Versions { &self.versions } - - /// Installs a specified node version - #[tracing::instrument(level = "debug", skip(self))] - pub async fn install_version(&self, version_req: &NodeVersion) -> Result<()> { - let info = self.lookup_version(version_req)?; - let archive_path = self.download_version(&info.version).await?; - self.extract_archive(&info.version, &archive_path)?; - - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self))] - async fn download_version(&self, version: &SimpleVersion) -> Result { - let download_path = CACHE_DIR.join(format!("node-v{}{}", version, *NODE_ARCHIVE_SUFFIX)); - - if download_path.exists() { - return Ok(download_path); - } - let mut download_writer = - BufWriter::new(File::create(&download_path).await.into_diagnostic()?); - self.web_api - .download_version(version.to_string(), &mut download_writer) - .await?; - - Ok(download_path) - } - - #[tracing::instrument(level = "debug", skip(self))] - fn extract_archive(&self, version: &SimpleVersion, archive_path: &Path) -> Result<()> { - let dst_path = NODE_VERSIONS_DIR.join(version.to_string()); - extract::extract_file(archive_path, &dst_path)?; - - Ok(()) - } } #[inline] #[tracing::instrument(level = "debug", skip_all)] -async fn load_versions(web_api: &WebApi) -> Result { +async fn load_versions(downloader: &NodeDownloader) -> Result { let versions = if let Some(v) = Versions::load().await { v } else { - let all_versions = web_api.get_versions().await?; + let all_versions = downloader.get_versions().await?; let v = Versions::new(all_versions); v.save().await?; v diff --git a/src/repository/versions.rs b/src/repository/versions.rs index d4b5abf..8778c00 100644 --- a/src/repository/versions.rs +++ b/src/repository/versions.rs @@ -5,9 +5,11 @@ use serde::{Deserialize, Serialize}; use std::fmt::Debug; use tokio::fs; -use crate::{consts::VERSION_FILE_PATH, error::SerializeBincodeError, web_api::VersionInfo}; +use crate::{consts::VERSION_FILE_PATH, error::SerializeBincodeError}; use miette::{Context, IntoDiagnostic, Result}; +use super::downloader::VersionInfo; + #[derive(Clone, Serialize, Deserialize)] pub struct Versions { lts_versions: HashMap, diff --git a/src/web_api/mod.rs b/src/web_api/mod.rs deleted file mode 100644 index 51e9e15..0000000 --- a/src/web_api/mod.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::{ - cmp::min, - fmt::{Debug, Display}, -}; - -use crate::{ - consts::{NODE_ARCHIVE_SUFFIX, NODE_DIST_URL}, - error::ReqwestError, - utils::progress_bar, -}; - -mod model; -use futures_util::StreamExt; -use miette::{miette, Context, IntoDiagnostic, Result}; -pub use model::*; -use tokio::io::{AsyncWrite, AsyncWriteExt}; - -#[cfg(test)] -mod test; - -#[derive(Clone, Debug)] -pub struct WebApi { - base_url: String, -} - -impl Default for WebApi { - fn default() -> Self { - Self::new(NODE_DIST_URL) - } -} - -impl WebApi { - /// Creates a new instance to access the nodejs website - pub fn new(base_url: S) -> Self { - Self { - base_url: base_url.to_string(), - } - } - - /// Returns the list of available node versions - #[tracing::instrument(level = "debug")] - pub async fn get_versions(&self) -> Result> { - let versions = reqwest::get(format!("{}/index.json", self.base_url)) - .await - .map_err(ReqwestError::from) - .context("Fetching versions")? - .json() - .await - .map_err(ReqwestError::from) - .context("Parsing versions response")?; - - Ok(versions) - } - - /// Downloads a specific node version - /// and writes it to the given writer - #[tracing::instrument(level = "debug", skip(writer))] - pub async fn download_version( - &self, - version: S, - writer: &mut W, - ) -> Result { - let res = reqwest::get(format!( - "{}/v{version}/node-v{version}{}", - self.base_url, *NODE_ARCHIVE_SUFFIX - )) - .await - .map_err(ReqwestError::from) - .context("Downloading nodejs")?; - - let total_size = res - .content_length() - .ok_or_else(|| miette!("Missing content_length header"))?; - - let pb = progress_bar(total_size); - pb.set_message(format!("Downloading node v{version}")); - let mut stream = res.bytes_stream(); - let mut total_downloaded = 0; - - while let Some(item) = stream.next().await { - let chunk = item.map_err(ReqwestError::from)?; - writer - .write_all(&chunk) - .await - .into_diagnostic() - .context("Writing download chunk to file")?; - total_downloaded = min(chunk.len() as u64 + total_downloaded, total_size); - pb.set_position(total_downloaded); - } - - writer.flush().await.into_diagnostic()?; - pb.finish_with_message(format!("Downloaded node v{version}.")); - - Ok(total_downloaded) - } -} diff --git a/src/web_api/test.rs b/src/web_api/test.rs deleted file mode 100644 index bcce32e..0000000 --- a/src/web_api/test.rs +++ /dev/null @@ -1,19 +0,0 @@ -use tokio::io::sink; - -use super::WebApi; - -#[tokio::test] -async fn it_fetches_all_versions() { - let versions = WebApi::default().get_versions().await.unwrap(); - assert!(!versions.is_empty()); -} - -#[tokio::test] -async fn it_downloads_a_specific_version() { - let mut writer = sink(); - let bytes_written = WebApi::default() - .download_version("15.0.0", &mut writer) - .await - .unwrap(); - assert!(bytes_written > 0); -}