Move versions to lazily initialized cache on downloader

feature/lookup-installed
trivernis 1 year ago
parent 1db8ffda5c
commit a081ea88de
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG Key ID: DFFFCC2C7A02DB45

@ -19,6 +19,7 @@ mod args;
mod config; mod config;
mod nenv; mod nenv;
mod version_detection; mod version_detection;
mod versioning;
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> { async fn main() -> Result<()> {

@ -41,7 +41,7 @@ impl Nenv {
pub async fn install(&mut self, version: NodeVersion) -> Result<()> { pub async fn install(&mut self, version: NodeVersion) -> Result<()> {
Self::clear_version_cache().await?; Self::clear_version_cache().await?;
if self.repo.is_installed(&version)? if self.repo.is_installed(&version).await?
&& !prompt( && !prompt(
false, false,
format!( format!(
@ -55,7 +55,7 @@ impl Nenv {
} else { } else {
self.repo.install_version(&version).await?; self.repo.install_version(&version).await?;
self.active_version = version.to_owned(); 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()); println!("Installed {}", version.to_string().bold());
Ok(()) Ok(())
@ -67,20 +67,20 @@ impl Nenv {
pub async fn set_system_default(&mut self, version: NodeVersion) -> Result<()> { pub async fn set_system_default(&mut self, version: NodeVersion) -> Result<()> {
self.active_version = version.to_owned(); self.active_version = version.to_owned();
if !self.repo.is_installed(&version)? { if !self.repo.is_installed(&version).await? {
if prompt( if prompt(
false, false,
format!("The version {version} is not installed. Do you want to install it?"), format!("The version {version} is not installed. Do you want to install it?"),
) { ) {
self.repo.install_version(&version).await?; self.repo.install_version(&version).await?;
self.config.get_mut().await.node.default_version = version.to_owned(); 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()); println!("Now using {}", version.to_string().bold());
} }
Ok(()) Ok(())
} else { } 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(); self.config.get_mut().await.node.default_version = version.to_owned();
println!("Now using {}", version.to_string().bold()); println!("Now using {}", version.to_string().bold());
@ -90,27 +90,27 @@ impl Nenv {
/// Executes a given node executable for the currently active version /// Executes a given node executable for the currently active version
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub async fn exec(&self, command: String, args: Vec<OsString>) -> Result<i32> { pub async fn exec(&mut self, command: String, args: Vec<OsString>) -> Result<i32> {
if !self.repo.is_installed(&self.active_version)? { if !self.repo.is_installed(&self.active_version).await? {
self.repo.install_version(&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)) Ok(exit_status.code().unwrap_or(0))
} }
/// Clears the version cache and remaps all executables /// Clears the version cache and remaps all executables
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub async fn refresh(&self) -> Result<()> { pub async fn refresh(&mut self) -> Result<()> {
Self::clear_version_cache().await?; Self::clear_version_cache().await?;
self.get_mapper()?.remap().await self.get_mapper().await?.remap().await
} }
/// Lists the currently installed versions /// Lists the currently installed versions
#[tracing::instrument(skip(self))] #[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 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(); let active_version = active_version.version.into();
println!("{}", "Installed versions:".bold()); println!("{}", "Installed versions:".bold());
@ -119,6 +119,7 @@ impl Nenv {
let info = self let info = self
.repo .repo
.all_versions() .all_versions()
.await?
.get(&version) .get(&version)
.ok_or_else(|| VersionError::unknown_version(version.to_string()))?; .ok_or_else(|| VersionError::unknown_version(version.to_string()))?;
let lts = info let lts = info
@ -138,7 +139,7 @@ impl Nenv {
} }
/// Initializes nenv and prompts for a default version. /// 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 items = vec!["latest", "lts", "custom"];
let selection = Select::with_theme(&ColorfulTheme::default()) let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Select a default node version") .with_prompt("Select a default node version")
@ -196,10 +197,11 @@ impl Nenv {
} }
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
fn get_mapper(&self) -> Result<Mapper> { async fn get_mapper(&mut self) -> Result<Mapper> {
let node_path = self let node_path = self
.repo .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_or_else(|| VersionError::not_installed(self.active_version.to_owned()))?;
Ok(Mapper::new(node_path)) Ok(Mapper::new(node_path))
} }

@ -10,9 +10,11 @@ use crate::{
consts::{CACHE_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR}, consts::{CACHE_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR},
error::ReqwestError, error::ReqwestError,
utils::progress_bar, utils::progress_bar,
versioning::SimpleVersion,
}; };
use super::versions::SimpleVersion; use self::versions::Versions;
use futures::StreamExt; use futures::StreamExt;
use miette::{miette, Context, IntoDiagnostic, Result}; use miette::{miette, Context, IntoDiagnostic, Result};
use tokio::{ use tokio::{
@ -21,29 +23,49 @@ use tokio::{
}; };
mod extract; mod extract;
mod version_info; mod version_info;
pub mod versions;
pub use version_info::VersionInfo; pub use version_info::VersionInfo;
#[derive(Clone)] #[derive(Clone)]
pub struct NodeDownloader { pub struct NodeDownloader {
config: ConfigAccess, config: ConfigAccess,
versions: Option<Versions>,
} }
impl NodeDownloader { impl NodeDownloader {
pub fn new(config: ConfigAccess) -> Self { pub fn new(config: ConfigAccess) -> Self {
Self { config } Self {
config,
versions: None,
}
} }
/// Returns the list of available node versions /// Returns the list of available node versions
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn get_versions(&self) -> Result<Vec<VersionInfo>> { pub async fn versions(&mut self) -> Result<&Versions> {
let versions = reqwest::get(format!("{}/index.json", self.base_url().await)) if self.versions.is_none() {
.await self.versions = Some(self.load_versions().await?);
.map_err(ReqwestError::from) }
.context("Fetching versions")?
.json() Ok(self.versions.as_ref().unwrap())
.await }
.map_err(ReqwestError::from)
.context("Parsing versions response")?; async fn load_versions(&self) -> Result<Versions> {
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) Ok(versions)
} }

@ -1,74 +1,29 @@
use std::{collections::HashMap, fmt::Display}; use std::collections::HashMap;
use semver::{Version, VersionReq}; use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use tokio::fs; 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 miette::{Context, IntoDiagnostic, Result};
use super::downloader::VersionInfo; use super::VersionInfo;
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Versions { pub struct Versions {
lts_versions: HashMap<String, u16>, lts_versions: HashMap<String, u16>,
versions: HashMap<SimpleVersion, SimpleVersionInfo>, versions: HashMap<SimpleVersion, VersionMetadata>,
// as this field is not serialized // as this field is not serialized
// it needs to be calculated after serialization // it needs to be calculated after serialization
#[serde(skip)] #[serde(skip)]
sorted_versions: Vec<SimpleVersion>, sorted_versions: Vec<SimpleVersion>,
} }
#[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<semver::Version> 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<SimpleVersion> 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<String>,
}
impl From<VersionInfo> for SimpleVersionInfo {
fn from(value: VersionInfo) -> Self {
Self {
version: value.version.into(),
lts: value.lts.lts(),
}
}
}
impl Versions { impl Versions {
/// Loads the versions from the cached versions.json file /// Loads the versions from the cached versions.json file
pub(crate) async fn load() -> Option<Self> { pub(crate) async fn load() -> Option<Self> {
@ -131,7 +86,7 @@ impl Versions {
/// Returns the latest known node version /// Returns the latest known node version
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn latest(&self) -> &SimpleVersionInfo { pub fn latest(&self) -> &VersionMetadata {
self.versions self.versions
.get(self.sorted_versions.last().expect("No known node versions")) .get(self.sorted_versions.last().expect("No known node versions"))
.unwrap() .unwrap()
@ -139,7 +94,7 @@ impl Versions {
/// Returns the latest node lts version /// Returns the latest node lts version
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn latest_lts(&self) -> &SimpleVersionInfo { pub fn latest_lts(&self) -> &VersionMetadata {
let mut versions = self let mut versions = self
.lts_versions .lts_versions
.values() .values()
@ -151,14 +106,14 @@ impl Versions {
/// Returns a lts version by name /// Returns a lts version by name
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub fn get_lts<S: AsRef<str> + Debug>(&self, lts_name: S) -> Option<&SimpleVersionInfo> { pub fn get_lts<S: AsRef<str> + Debug>(&self, lts_name: S) -> Option<&VersionMetadata> {
let lts_version = self.lts_versions.get(lts_name.as_ref())?; let lts_version = self.lts_versions.get(lts_name.as_ref())?;
self.get_latest_for_major(*lts_version) self.get_latest_for_major(*lts_version)
} }
/// Returns any version that fulfills the given requirement /// Returns any version that fulfills the given requirement
#[tracing::instrument(level = "debug", skip(self))] #[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 let fulfilling_versions = self
.sorted_versions .sorted_versions
.iter() .iter()
@ -172,13 +127,13 @@ impl Versions {
/// Returns the info for the given version /// Returns the info for the given version
#[tracing::instrument(level = "debug", skip(self))] #[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()) self.versions.get(&version.clone().into())
} }
/// Returns any version that fulfills the given requirement /// Returns any version that fulfills the given requirement
#[tracing::instrument(level = "debug", skip(self))] #[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 let fulfilling_versions = self
.sorted_versions .sorted_versions
.iter() .iter()

@ -10,19 +10,19 @@ use crate::{
config::ConfigAccess, config::ConfigAccess,
consts::{ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_VERSIONS_DIR, OS}, consts::{ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_VERSIONS_DIR, OS},
error::VersionError, error::VersionError,
versioning::{SimpleVersion, VersionMetadata},
}; };
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use self::{ use self::{
downloader::NodeDownloader, downloader::{versions::Versions, NodeDownloader},
node_path::NodePath, node_path::NodePath,
versions::{SimpleVersion, SimpleVersionInfo, Versions},
}; };
pub mod downloader; pub mod downloader;
mod local_versions;
pub(crate) mod node_path; pub(crate) mod node_path;
pub mod versions;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum NodeVersion { pub enum NodeVersion {
@ -90,7 +90,6 @@ impl fmt::Display for NodeVersion {
} }
pub struct Repository { pub struct Repository {
versions: Versions,
downloader: NodeDownloader, downloader: NodeDownloader,
} }
@ -100,12 +99,8 @@ impl Repository {
pub async fn init(config: ConfigAccess) -> Result<Self> { pub async fn init(config: ConfigAccess) -> Result<Self> {
Self::create_folders().await?; Self::create_folders().await?;
let downloader = NodeDownloader::new(config.clone()); let downloader = NodeDownloader::new(config.clone());
let versions = load_versions(&downloader).await?;
Ok(Self { Ok(Self { downloader })
downloader,
versions,
})
} }
#[tracing::instrument(level = "debug")] #[tracing::instrument(level = "debug")]
@ -137,8 +132,8 @@ impl Repository {
/// Returns the path for the given node version /// Returns the path for the given node version
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub fn get_version_path(&self, version: &NodeVersion) -> Result<Option<NodePath>> { pub async fn get_version_path(&mut self, version: &NodeVersion) -> Result<Option<NodePath>> {
let info = self.lookup_version(version)?; let info = self.lookup_version(version).await?;
let path = build_version_path(&info.version); let path = build_version_path(&info.version);
Ok(if path.exists() { Ok(if path.exists() {
@ -165,16 +160,16 @@ impl Repository {
/// Returns if the given version is installed /// Returns if the given version is installed
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub fn is_installed(&self, version: &NodeVersion) -> Result<bool> { pub async fn is_installed(&mut self, version: &NodeVersion) -> Result<bool> {
let info = self.lookup_version(version)?; let info = self.lookup_version(version).await?;
Ok(build_version_path(&info.version).exists()) Ok(build_version_path(&info.version).exists())
} }
/// Installs the given node version /// Installs the given node version
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn install_version(&self, version: &NodeVersion) -> Result<()> { pub async fn install_version(&mut self, version: &NodeVersion) -> Result<()> {
let info = self.lookup_version(version)?; let info = self.lookup_version(version).await?.to_owned();
self.downloader.download(&info.version).await?; self.downloader.download(&info.version).await?;
Ok(()) Ok(())
@ -182,19 +177,16 @@ impl Repository {
/// Performs a lookup for the given node version /// Performs a lookup for the given node version
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub fn lookup_version( pub async fn lookup_version(&mut self, version_req: &NodeVersion) -> Result<&VersionMetadata> {
&self, let versions = self.downloader.versions().await?;
version_req: &NodeVersion,
) -> Result<&SimpleVersionInfo, VersionError> {
let version = match version_req { let version = match version_req {
NodeVersion::Latest => self.versions.latest(), NodeVersion::Latest => versions.latest(),
NodeVersion::LatestLts => self.versions.latest_lts(), NodeVersion::LatestLts => versions.latest_lts(),
NodeVersion::Lts(lts) => self NodeVersion::Lts(lts) => versions
.versions
.get_lts(lts) .get_lts(lts)
.ok_or_else(|| VersionError::unknown_version(lts.to_owned()))?, .ok_or_else(|| VersionError::unknown_version(lts.to_owned()))?,
NodeVersion::Req(req) => self NodeVersion::Req(req) => versions
.versions
.get_fulfilling(req) .get_fulfilling(req)
.ok_or_else(|| VersionError::unfulfillable_version(req.to_owned()))?, .ok_or_else(|| VersionError::unfulfillable_version(req.to_owned()))?,
}; };
@ -204,25 +196,11 @@ impl Repository {
/// Returns the reference to all known versions /// Returns the reference to all known versions
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub fn all_versions(&self) -> &Versions { pub async fn all_versions(&mut self) -> Result<&Versions> {
&self.versions self.downloader.versions().await
} }
} }
#[inline]
#[tracing::instrument(level = "debug", skip_all)]
async fn load_versions(downloader: &NodeDownloader) -> Result<Versions> {
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 { fn build_version_path(version: &SimpleVersion) -> PathBuf {
NODE_VERSIONS_DIR NODE_VERSIONS_DIR
.join(version.to_string()) .join(version.to_string())

@ -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<String>,
}
impl From<semver::Version> 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<SimpleVersion> 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<VersionInfo> for VersionMetadata {
fn from(value: VersionInfo) -> Self {
Self {
version: value.version.into(),
lts: value.lts.lts(),
}
}
}
Loading…
Cancel
Save