From a081ea88debc9c0458230ccaf8670bd0abe18acd Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 24 Jan 2023 20:11:07 +0100 Subject: [PATCH] Move versions to lazily initialized cache on downloader --- src/main.rs | 1 + src/nenv.rs | 32 ++++----- src/repository/downloader/mod.rs | 44 +++++++++---- src/repository/{ => downloader}/versions.rs | 73 ++++----------------- src/repository/local_versions.rs | 0 src/repository/mod.rs | 60 ++++++----------- src/versioning.rs | 56 ++++++++++++++++ 7 files changed, 140 insertions(+), 126 deletions(-) rename src/repository/{ => downloader}/versions.rs (72%) create mode 100644 src/repository/local_versions.rs create mode 100644 src/versioning.rs diff --git a/src/main.rs b/src/main.rs index 634cd33..18ce2f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ mod args; mod config; mod nenv; mod version_detection; +mod versioning; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { diff --git a/src/nenv.rs b/src/nenv.rs index 88092e8..22912a8 100644 --- a/src/nenv.rs +++ b/src/nenv.rs @@ -41,7 +41,7 @@ impl Nenv { pub async fn install(&mut self, version: NodeVersion) -> Result<()> { Self::clear_version_cache().await?; - if self.repo.is_installed(&version)? + if self.repo.is_installed(&version).await? && !prompt( false, format!( @@ -55,7 +55,7 @@ impl Nenv { } else { self.repo.install_version(&version).await?; self.active_version = version.to_owned(); - self.get_mapper()?.remap_additive().await?; + self.get_mapper().await?.remap_additive().await?; println!("Installed {}", version.to_string().bold()); Ok(()) @@ -67,20 +67,20 @@ impl Nenv { pub async fn set_system_default(&mut self, version: NodeVersion) -> Result<()> { self.active_version = version.to_owned(); - if !self.repo.is_installed(&version)? { + if !self.repo.is_installed(&version).await? { if prompt( false, format!("The version {version} is not installed. Do you want to install it?"), ) { self.repo.install_version(&version).await?; self.config.get_mut().await.node.default_version = version.to_owned(); - self.get_mapper()?.remap_additive().await?; + self.get_mapper().await?.remap_additive().await?; println!("Now using {}", version.to_string().bold()); } Ok(()) } else { - self.get_mapper()?.remap_additive().await?; + self.get_mapper().await?.remap_additive().await?; self.config.get_mut().await.node.default_version = version.to_owned(); println!("Now using {}", version.to_string().bold()); @@ -90,27 +90,27 @@ impl Nenv { /// Executes a given node executable for the currently active version #[tracing::instrument(skip(self))] - pub async fn exec(&self, command: String, args: Vec) -> Result { - if !self.repo.is_installed(&self.active_version)? { + pub async fn exec(&mut self, command: String, args: Vec) -> Result { + if !self.repo.is_installed(&self.active_version).await? { self.repo.install_version(&self.active_version).await?; } - let exit_status = self.get_mapper()?.exec(command, args).await?; + let exit_status = self.get_mapper().await?.exec(command, args).await?; Ok(exit_status.code().unwrap_or(0)) } /// Clears the version cache and remaps all executables #[tracing::instrument(skip(self))] - pub async fn refresh(&self) -> Result<()> { + pub async fn refresh(&mut self) -> Result<()> { Self::clear_version_cache().await?; - self.get_mapper()?.remap().await + self.get_mapper().await?.remap().await } /// Lists the currently installed versions #[tracing::instrument(skip(self))] - pub async fn list_versions(&self) -> Result<()> { + pub async fn list_versions(&mut self) -> Result<()> { let versions = self.repo.installed_versions().await?; - let active_version = self.repo.lookup_version(&self.active_version)?; + let active_version = self.repo.lookup_version(&self.active_version).await?; let active_version = active_version.version.into(); println!("{}", "Installed versions:".bold()); @@ -119,6 +119,7 @@ impl Nenv { let info = self .repo .all_versions() + .await? .get(&version) .ok_or_else(|| VersionError::unknown_version(version.to_string()))?; let lts = info @@ -138,7 +139,7 @@ impl Nenv { } /// Initializes nenv and prompts for a default version. - pub async fn init_nenv(&self) -> Result<()> { + pub async fn init_nenv(&mut self) -> Result<()> { let items = vec!["latest", "lts", "custom"]; let selection = Select::with_theme(&ColorfulTheme::default()) .with_prompt("Select a default node version") @@ -196,10 +197,11 @@ impl Nenv { } #[tracing::instrument(level = "debug", skip(self))] - fn get_mapper(&self) -> Result { + async fn get_mapper(&mut self) -> Result { let node_path = self .repo - .get_version_path(&self.active_version)? + .get_version_path(&self.active_version) + .await? .ok_or_else(|| VersionError::not_installed(self.active_version.to_owned()))?; Ok(Mapper::new(node_path)) } diff --git a/src/repository/downloader/mod.rs b/src/repository/downloader/mod.rs index bda1a20..78aaeab 100644 --- a/src/repository/downloader/mod.rs +++ b/src/repository/downloader/mod.rs @@ -10,9 +10,11 @@ use crate::{ consts::{CACHE_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR}, error::ReqwestError, utils::progress_bar, + versioning::SimpleVersion, }; -use super::versions::SimpleVersion; +use self::versions::Versions; + use futures::StreamExt; use miette::{miette, Context, IntoDiagnostic, Result}; use tokio::{ @@ -21,29 +23,49 @@ use tokio::{ }; mod extract; mod version_info; +pub mod versions; pub use version_info::VersionInfo; #[derive(Clone)] pub struct NodeDownloader { config: ConfigAccess, + versions: Option, } impl NodeDownloader { pub fn new(config: ConfigAccess) -> Self { - Self { config } + Self { + config, + versions: None, + } } /// 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")?; + pub async fn versions(&mut self) -> Result<&Versions> { + if self.versions.is_none() { + self.versions = Some(self.load_versions().await?); + } + + Ok(self.versions.as_ref().unwrap()) + } + + async fn load_versions(&self) -> Result { + let versions = if let Some(v) = Versions::load().await { + v + } else { + 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")?; + let v = Versions::new(versions); + v.save().await?; + v + }; Ok(versions) } diff --git a/src/repository/versions.rs b/src/repository/downloader/versions.rs similarity index 72% rename from src/repository/versions.rs rename to src/repository/downloader/versions.rs index 78f617a..c98f35c 100644 --- a/src/repository/versions.rs +++ b/src/repository/downloader/versions.rs @@ -1,74 +1,29 @@ -use std::{collections::HashMap, fmt::Display}; +use std::collections::HashMap; use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use tokio::fs; -use crate::{consts::VERSION_FILE_PATH, error::SerializeBincodeError}; +use crate::{ + consts::VERSION_FILE_PATH, + error::SerializeBincodeError, + versioning::{SimpleVersion, VersionMetadata}, +}; use miette::{Context, IntoDiagnostic, Result}; -use super::downloader::VersionInfo; +use super::VersionInfo; #[derive(Clone, Serialize, Deserialize)] pub struct Versions { lts_versions: HashMap, - versions: HashMap, + versions: HashMap, // as this field is not serialized // it needs to be calculated after serialization #[serde(skip)] sorted_versions: Vec, } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize, Hash)] -pub struct SimpleVersion { - pub major: u16, - pub minor: u16, - pub patch: u32, -} - -impl From for SimpleVersion { - fn from(value: semver::Version) -> Self { - Self { - major: value.major as u16, - minor: value.minor as u16, - patch: value.patch as u32, - } - } -} - -impl From for semver::Version { - fn from(value: SimpleVersion) -> Self { - Self::new(value.major as u64, value.minor as u64, value.patch as u64) - } -} - -impl Display for SimpleVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { - major, - minor, - patch, - } = self; - write!(f, "{major}.{minor}.{patch}") - } -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct SimpleVersionInfo { - pub version: SimpleVersion, - pub lts: Option, -} - -impl From for SimpleVersionInfo { - fn from(value: VersionInfo) -> Self { - Self { - version: value.version.into(), - lts: value.lts.lts(), - } - } -} - impl Versions { /// Loads the versions from the cached versions.json file pub(crate) async fn load() -> Option { @@ -131,7 +86,7 @@ impl Versions { /// Returns the latest known node version #[tracing::instrument(level = "debug", skip_all)] - pub fn latest(&self) -> &SimpleVersionInfo { + pub fn latest(&self) -> &VersionMetadata { self.versions .get(self.sorted_versions.last().expect("No known node versions")) .unwrap() @@ -139,7 +94,7 @@ impl Versions { /// Returns the latest node lts version #[tracing::instrument(level = "debug", skip_all)] - pub fn latest_lts(&self) -> &SimpleVersionInfo { + pub fn latest_lts(&self) -> &VersionMetadata { let mut versions = self .lts_versions .values() @@ -151,14 +106,14 @@ impl Versions { /// Returns a lts version by name #[tracing::instrument(level = "debug", skip(self))] - pub fn get_lts + Debug>(&self, lts_name: S) -> Option<&SimpleVersionInfo> { + pub fn get_lts + Debug>(&self, lts_name: S) -> Option<&VersionMetadata> { let lts_version = self.lts_versions.get(lts_name.as_ref())?; self.get_latest_for_major(*lts_version) } /// Returns any version that fulfills the given requirement #[tracing::instrument(level = "debug", skip(self))] - pub fn get_fulfilling(&self, req: &VersionReq) -> Option<&SimpleVersionInfo> { + pub fn get_fulfilling(&self, req: &VersionReq) -> Option<&VersionMetadata> { let fulfilling_versions = self .sorted_versions .iter() @@ -172,13 +127,13 @@ impl Versions { /// Returns the info for the given version #[tracing::instrument(level = "debug", skip(self))] - pub fn get(&self, version: &Version) -> Option<&SimpleVersionInfo> { + pub fn get(&self, version: &Version) -> Option<&VersionMetadata> { self.versions.get(&version.clone().into()) } /// Returns any version that fulfills the given requirement #[tracing::instrument(level = "debug", skip(self))] - fn get_latest_for_major(&self, major: u16) -> Option<&SimpleVersionInfo> { + fn get_latest_for_major(&self, major: u16) -> Option<&VersionMetadata> { let fulfilling_versions = self .sorted_versions .iter() diff --git a/src/repository/local_versions.rs b/src/repository/local_versions.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/repository/mod.rs b/src/repository/mod.rs index d52e34a..d5f5f6e 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -10,19 +10,19 @@ use crate::{ config::ConfigAccess, consts::{ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_VERSIONS_DIR, OS}, error::VersionError, + versioning::{SimpleVersion, VersionMetadata}, }; use miette::{IntoDiagnostic, Result}; use self::{ - downloader::NodeDownloader, + downloader::{versions::Versions, NodeDownloader}, node_path::NodePath, - versions::{SimpleVersion, SimpleVersionInfo, Versions}, }; pub mod downloader; +mod local_versions; pub(crate) mod node_path; -pub mod versions; #[derive(Clone, Debug)] pub enum NodeVersion { @@ -90,7 +90,6 @@ impl fmt::Display for NodeVersion { } pub struct Repository { - versions: Versions, downloader: NodeDownloader, } @@ -100,12 +99,8 @@ impl Repository { pub async fn init(config: ConfigAccess) -> Result { Self::create_folders().await?; let downloader = NodeDownloader::new(config.clone()); - let versions = load_versions(&downloader).await?; - Ok(Self { - downloader, - versions, - }) + Ok(Self { downloader }) } #[tracing::instrument(level = "debug")] @@ -137,8 +132,8 @@ impl Repository { /// Returns the path for the given node version #[tracing::instrument(level = "debug", skip(self))] - pub fn get_version_path(&self, version: &NodeVersion) -> Result> { - let info = self.lookup_version(version)?; + pub async fn get_version_path(&mut self, version: &NodeVersion) -> Result> { + let info = self.lookup_version(version).await?; let path = build_version_path(&info.version); Ok(if path.exists() { @@ -165,16 +160,16 @@ impl Repository { /// Returns if the given version is installed #[tracing::instrument(level = "debug", skip(self))] - pub fn is_installed(&self, version: &NodeVersion) -> Result { - let info = self.lookup_version(version)?; + pub async fn is_installed(&mut self, version: &NodeVersion) -> Result { + let info = self.lookup_version(version).await?; 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)?; + pub async fn install_version(&mut self, version: &NodeVersion) -> Result<()> { + let info = self.lookup_version(version).await?.to_owned(); self.downloader.download(&info.version).await?; Ok(()) @@ -182,19 +177,16 @@ impl Repository { /// Performs a lookup for the given node version #[tracing::instrument(level = "debug", skip(self))] - pub fn lookup_version( - &self, - version_req: &NodeVersion, - ) -> Result<&SimpleVersionInfo, VersionError> { + pub async fn lookup_version(&mut self, version_req: &NodeVersion) -> Result<&VersionMetadata> { + let versions = self.downloader.versions().await?; + let version = match version_req { - NodeVersion::Latest => self.versions.latest(), - NodeVersion::LatestLts => self.versions.latest_lts(), - NodeVersion::Lts(lts) => self - .versions + NodeVersion::Latest => versions.latest(), + NodeVersion::LatestLts => versions.latest_lts(), + NodeVersion::Lts(lts) => versions .get_lts(lts) .ok_or_else(|| VersionError::unknown_version(lts.to_owned()))?, - NodeVersion::Req(req) => self - .versions + NodeVersion::Req(req) => versions .get_fulfilling(req) .ok_or_else(|| VersionError::unfulfillable_version(req.to_owned()))?, }; @@ -204,25 +196,11 @@ impl Repository { /// Returns the reference to all known versions #[tracing::instrument(level = "debug", skip(self))] - pub fn all_versions(&self) -> &Versions { - &self.versions + pub async fn all_versions(&mut self) -> Result<&Versions> { + self.downloader.versions().await } } -#[inline] -#[tracing::instrument(level = "debug", skip_all)] -async fn load_versions(downloader: &NodeDownloader) -> Result { - let versions = if let Some(v) = Versions::load().await { - v - } else { - let all_versions = downloader.get_versions().await?; - let v = Versions::new(all_versions); - v.save().await?; - v - }; - Ok(versions) -} - fn build_version_path(version: &SimpleVersion) -> PathBuf { NODE_VERSIONS_DIR .join(version.to_string()) diff --git a/src/versioning.rs b/src/versioning.rs new file mode 100644 index 0000000..bf7b860 --- /dev/null +++ b/src/versioning.rs @@ -0,0 +1,56 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; + +use crate::repository::downloader::VersionInfo; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize, Hash)] +pub struct SimpleVersion { + pub major: u16, + pub minor: u16, + pub patch: u32, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct VersionMetadata { + /// The semver version + pub version: SimpleVersion, + /// The lts name of this version if it is an lts version + pub lts: Option, +} + +impl From for SimpleVersion { + fn from(value: semver::Version) -> Self { + Self { + major: value.major as u16, + minor: value.minor as u16, + patch: value.patch as u32, + } + } +} + +impl From for semver::Version { + fn from(value: SimpleVersion) -> Self { + Self::new(value.major as u64, value.minor as u64, value.patch as u64) + } +} + +impl Display for SimpleVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + major, + minor, + patch, + } = self; + write!(f, "{major}.{minor}.{patch}") + } +} + +impl From for VersionMetadata { + fn from(value: VersionInfo) -> Self { + Self { + version: value.version.into(), + lts: value.lts.lts(), + } + } +}