Add lookup table for locally installed versions

feature/lookup-installed
trivernis 2 years ago
parent 88fc9d4742
commit 97a6ee5a60
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -15,6 +15,7 @@ lazy_static! {
.join(PathBuf::from("nenv")); .join(PathBuf::from("nenv"));
pub static ref CFG_FILE_PATH: PathBuf = CFG_DIR.join("config.toml"); pub static ref CFG_FILE_PATH: PathBuf = CFG_DIR.join("config.toml");
pub static ref VERSION_FILE_PATH: PathBuf = CACHE_DIR.join("versions.cache"); pub static ref VERSION_FILE_PATH: PathBuf = CACHE_DIR.join("versions.cache");
pub static ref INSTALLED_VERSION_FILE: PathBuf = DATA_DIR.join("installed_versions");
pub static ref BIN_DIR: PathBuf = DATA_DIR.join("bin"); pub static ref BIN_DIR: PathBuf = DATA_DIR.join("bin");
pub static ref NODE_VERSIONS_DIR: PathBuf = DATA_DIR.join("versions"); pub static ref NODE_VERSIONS_DIR: PathBuf = DATA_DIR.join("versions");
pub static ref NODE_ARCHIVE_SUFFIX: String = format!("-{OS}-{ARCH}.{ARCHIVE_TYPE}"); pub static ref NODE_ARCHIVE_SUFFIX: String = format!("-{OS}-{ARCH}.{ARCHIVE_TYPE}");

@ -126,8 +126,11 @@ impl Nenv {
/// Lists the currently installed versions /// Lists the currently installed versions
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub async fn list_versions(&mut self) -> Result<()> { pub async fn list_versions(&mut self) -> Result<()> {
let versions = self.repo.installed_versions().await?; let versions = self.repo.installed_versions();
let active_version = self.repo.lookup_version(&self.active_version).await?; let active_version = self
.repo
.lookup_remote_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());
@ -234,8 +237,7 @@ impl Nenv {
async fn get_mapper(&mut 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))
} }

@ -0,0 +1,106 @@
use std::{fs::File, io::Write};
use semver::VersionReq;
use serde::{Deserialize, Serialize};
use crate::{
consts::INSTALLED_VERSION_FILE,
versioning::{SimpleVersion, VersionMetadata},
};
use miette::{Context, IntoDiagnostic, Result};
#[derive(Serialize, Deserialize, Default)]
pub struct InstalledVersions {
ordered_versions: Vec<(SimpleVersion, VersionMetadata)>,
}
impl InstalledVersions {
pub fn new(mut versions: Vec<(SimpleVersion, VersionMetadata)>) -> Self {
versions.sort_by_key(|e| e.0);
Self {
ordered_versions: versions,
}
}
/// Loads the local versions
pub fn load() -> Result<Self> {
let reader = File::open(&*INSTALLED_VERSION_FILE)
.into_diagnostic()
.context("Opening local versions file")?;
let versions = bincode::deserialize_from(reader)
.into_diagnostic()
.context("Deserializing local versions")?;
Ok(versions)
}
/// Saves the local versions
pub fn save(&self) -> Result<()> {
let mut file = File::create(&*INSTALLED_VERSION_FILE)
.into_diagnostic()
.context("Opening local versions file")?;
bincode::serialize_into(&mut file, &self)
.into_diagnostic()
.context("Serializing local versions")?;
file.flush()
.into_diagnostic()
.context("Flushing local versions to file")?;
Ok(())
}
/// Inserts a new version. This requires reordering the list
pub fn insert(&mut self, version: (SimpleVersion, VersionMetadata)) {
self.ordered_versions.push(version);
self.ordered_versions.sort_by_key(|e| e.0);
}
/// Removes a version. This keeps the order intact
pub fn remove(&mut self, version: &SimpleVersion) {
self.ordered_versions.retain(|(v, _)| v != version)
}
pub fn all(&self) -> Vec<&SimpleVersion> {
self.ordered_versions.iter().map(|(v, _)| v).collect()
}
pub fn latest(&self) -> Option<&VersionMetadata> {
self.ordered_versions.last().map(|(_, m)| m)
}
pub fn latest_lts(&self) -> Option<&VersionMetadata> {
self.ordered_versions
.iter()
.filter(|(_, m)| m.lts.is_some())
.last()
.map(|(_, m)| m)
}
pub fn lts<S: AsRef<str>>(&self, lts: S) -> Option<&VersionMetadata> {
self.ordered_versions
.iter()
.filter_map(|(v, m)| Some((v, m.lts.clone()?, m)))
.filter(|(_, n, _)| n == lts.as_ref())
.last()
.map(|(_, _, m)| m)
}
pub fn fulfilling(&self, req: &VersionReq) -> Option<&VersionMetadata> {
self.ordered_versions
.iter()
.filter(|(v, _)| req.matches(&v.to_owned().into()))
.last()
.map(|(_, m)| m)
}
}
impl From<Vec<VersionMetadata>> for InstalledVersions {
fn from(versions: Vec<VersionMetadata>) -> Self {
let versions = versions
.into_iter()
.map(|v| (v.version.to_owned(), v))
.collect::<Vec<_>>();
Self::new(versions)
}
}

@ -17,6 +17,7 @@ use miette::{Context, IntoDiagnostic, Result};
use self::{ use self::{
downloader::{versions::Versions, NodeDownloader}, downloader::{versions::Versions, NodeDownloader},
local_versions::InstalledVersions,
node_path::NodePath, node_path::NodePath,
}; };
@ -91,6 +92,7 @@ impl fmt::Display for NodeVersion {
pub struct Repository { pub struct Repository {
downloader: NodeDownloader, downloader: NodeDownloader,
installed_versions: InstalledVersions,
} }
impl Repository { impl Repository {
@ -98,9 +100,19 @@ impl Repository {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
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 mut downloader = NodeDownloader::new(config.clone());
Ok(Self { downloader }) let installed_versions = match InstalledVersions::load() {
Ok(v) => v,
Err(_) => load_installed_versions_info(downloader.versions().await?)
.await?
.into(),
};
Ok(Self {
downloader,
installed_versions,
})
} }
#[tracing::instrument(level = "debug")] #[tracing::instrument(level = "debug")]
@ -132,8 +144,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 async fn get_version_path(&mut self, version: &NodeVersion) -> Result<Option<NodePath>> { pub fn get_version_path(&mut self, version: &NodeVersion) -> Result<Option<NodePath>> {
let info = self.lookup_version(version).await?; let info = self.lookup_local_version(version)?;
let path = build_version_path(&info.version); let path = build_version_path(&info.version);
Ok(if path.exists() { Ok(if path.exists() {
@ -144,24 +156,22 @@ impl Repository {
} }
/// Returns a list of installed versions /// Returns a list of installed versions
#[tracing::instrument(level = "debug", skip(self))] pub fn installed_versions(&self) -> Vec<Version> {
pub async fn installed_versions(&self) -> Result<Vec<Version>> { self.installed_versions
let mut versions = Vec::new(); .all()
let mut iter = fs::read_dir(&*NODE_VERSIONS_DIR).await.into_diagnostic()?; .into_iter()
.map(|v| v.clone().into())
while let Some(entry) = iter.next_entry().await.into_diagnostic()? { .collect()
if let Ok(version) = Version::parse(entry.file_name().to_string_lossy().as_ref()) {
versions.push(version);
};
}
Ok(versions)
} }
/// 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 async fn is_installed(&mut self, version: &NodeVersion) -> Result<bool> { pub async fn is_installed(&mut self, version: &NodeVersion) -> Result<bool> {
let info = self.lookup_version(version).await?; let info = if let Ok(v) = self.lookup_local_version(version) {
v
} else {
self.lookup_remote_version(version).await?
};
Ok(build_version_path(&info.version).exists()) Ok(build_version_path(&info.version).exists())
} }
@ -169,8 +179,10 @@ impl Repository {
/// 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(&mut self, version: &NodeVersion) -> Result<()> { pub async fn install_version(&mut self, version: &NodeVersion) -> Result<()> {
let info = self.lookup_version(version).await?.to_owned(); let info = self.lookup_remote_version(version).await?.to_owned();
self.downloader.download(&info.version).await?; self.downloader.download(&info.version).await?;
self.installed_versions.insert((info.version, info));
self.installed_versions.save()?;
Ok(()) Ok(())
} }
@ -178,7 +190,7 @@ impl Repository {
/// Uninstalls the given node version by deleting the versions directory /// Uninstalls the given node version by deleting the versions directory
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn uninstall(&mut self, version: &NodeVersion) -> Result<()> { pub async fn uninstall(&mut self, version: &NodeVersion) -> Result<()> {
let info = self.lookup_version(version).await?; let info = self.lookup_local_version(version)?.clone();
let version_dir = NODE_VERSIONS_DIR.join(info.version.to_string()); let version_dir = NODE_VERSIONS_DIR.join(info.version.to_string());
if !version_dir.exists() { if !version_dir.exists() {
@ -189,13 +201,18 @@ impl Repository {
.await .await
.into_diagnostic() .into_diagnostic()
.context("Deleting node version")?; .context("Deleting node version")?;
self.installed_versions.remove(&info.version);
self.installed_versions.save()?;
Ok(()) Ok(())
} }
/// 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 async fn lookup_version(&mut self, version_req: &NodeVersion) -> Result<&VersionMetadata> { pub async fn lookup_remote_version(
&mut self,
version_req: &NodeVersion,
) -> Result<&VersionMetadata> {
let versions = self.downloader.versions().await?; let versions = self.downloader.versions().await?;
let version = match version_req { let version = match version_req {
@ -212,6 +229,28 @@ impl Repository {
Ok(version) Ok(version)
} }
/// Performs a lookup for the given node version
#[tracing::instrument(level = "debug", skip(self))]
pub fn lookup_local_version(&self, version_req: &NodeVersion) -> Result<&VersionMetadata> {
let versions = &self.installed_versions;
let version = match version_req {
NodeVersion::Latest => versions
.latest()
.ok_or_else(|| VersionError::not_installed("latest"))?,
NodeVersion::LatestLts => versions
.latest_lts()
.ok_or_else(|| VersionError::not_installed("lts"))?,
NodeVersion::Lts(lts) => versions
.lts(lts)
.ok_or_else(|| VersionError::unknown_version(lts.to_owned()))?,
NodeVersion::Req(req) => versions
.fulfilling(req)
.ok_or_else(|| VersionError::unfulfillable_version(req.to_owned()))?,
};
Ok(version)
}
/// 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 async fn all_versions(&mut self) -> Result<&Versions> { pub async fn all_versions(&mut self) -> Result<&Versions> {
@ -224,3 +263,21 @@ fn build_version_path(version: &SimpleVersion) -> PathBuf {
.join(version.to_string()) .join(version.to_string())
.join(format!("node-v{}-{}-{}", version, OS, ARCH)) .join(format!("node-v{}-{}-{}", version, OS, ARCH))
} }
async fn load_installed_versions_info(versions: &Versions) -> Result<Vec<VersionMetadata>> {
let mut installed_versions = Vec::new();
let mut iter = fs::read_dir(&*NODE_VERSIONS_DIR).await.into_diagnostic()?;
while let Some(entry) = iter.next_entry().await.into_diagnostic()? {
if let Ok(version) = Version::parse(entry.file_name().to_string_lossy().as_ref()) {
installed_versions.push(version);
};
}
let versions = installed_versions
.into_iter()
.filter_map(|v| versions.get(&v))
.cloned()
.collect();
Ok(versions)
}

Loading…
Cancel
Save