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 nenv;
mod version_detection;
mod versioning;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {

@ -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<OsString>) -> Result<i32> {
if !self.repo.is_installed(&self.active_version)? {
pub async fn exec(&mut self, command: String, args: Vec<OsString>) -> Result<i32> {
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<Mapper> {
async fn get_mapper(&mut self) -> Result<Mapper> {
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))
}

@ -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<Versions>,
}
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<Vec<VersionInfo>> {
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<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)
}

@ -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<String, u16>,
versions: HashMap<SimpleVersion, SimpleVersionInfo>,
versions: HashMap<SimpleVersion, VersionMetadata>,
// as this field is not serialized
// it needs to be calculated after serialization
#[serde(skip)]
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 {
/// Loads the versions from the cached versions.json file
pub(crate) async fn load() -> Option<Self> {
@ -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<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())?;
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()

@ -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> {
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<Option<NodePath>> {
let info = self.lookup_version(version)?;
pub async fn get_version_path(&mut self, version: &NodeVersion) -> Result<Option<NodePath>> {
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<bool> {
let info = self.lookup_version(version)?;
pub async fn is_installed(&mut self, version: &NodeVersion) -> Result<bool> {
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<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 {
NODE_VERSIONS_DIR
.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