-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
337 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub mod node_manager; | ||
pub mod node_model; | ||
pub mod node_schedule; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
use dialoguer::Confirm; | ||
use snm_core::traits::atom::AtomTrait; | ||
use snm_download_builder::{DownloadBuilder, WriteStrategy}; | ||
use snm_utils::snm_error::SnmError; | ||
use std::{collections::HashMap, fs, ops::Not as _, path::PathBuf, time::Duration}; | ||
|
||
use super::{node_model::NodeModel, node_schedule::NodeSchedule}; | ||
|
||
pub struct NodeManager<'a, T: AtomTrait> { | ||
node_atom: &'a T, | ||
} | ||
|
||
impl<'a, T> NodeManager<'a, T> | ||
where | ||
T: AtomTrait, | ||
{ | ||
async fn internal_download(&self, version: &str) -> Result<(), SnmError> { | ||
let download_url = self.node_atom.get_download_url(version); | ||
let downloaded_file_path_buf = self.node_atom.get_downloaded_file_path_buf(version)?; | ||
|
||
DownloadBuilder::new() | ||
.retries(3) | ||
.write_strategy(WriteStrategy::Nothing) | ||
.download(&download_url, &downloaded_file_path_buf) | ||
.await?; | ||
|
||
let runtime = self.node_atom.get_runtime_dir_path_buf(version)?; | ||
|
||
if runtime.exists() { | ||
fs::remove_dir_all(&runtime)?; | ||
} | ||
|
||
self.node_atom | ||
.decompress_download_file(&downloaded_file_path_buf, &runtime)?; | ||
|
||
fs::remove_file(&downloaded_file_path_buf)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn internal_set_default(&self, version: &str) -> Result<PathBuf, SnmError> { | ||
let default_dir = self.node_atom.get_runtime_dir_for_default_path_buf()?; | ||
if default_dir.exists() { | ||
fs::remove_dir_all(&default_dir)?; | ||
} | ||
|
||
let from_dir = self.node_atom.get_runtime_dir_path_buf(version)?; | ||
|
||
#[cfg(unix)] | ||
{ | ||
std::os::unix::fs::symlink(&from_dir, &default_dir)?; | ||
} | ||
#[cfg(windows)] | ||
{ | ||
std::os::windows::fs::symlink_dir(&version_dir, &default_dir)?; | ||
} | ||
Ok(default_dir) | ||
} | ||
|
||
async fn get_node_list_remote(&self) -> Result<Vec<NodeModel>, SnmError> { | ||
let host = self.node_atom.get_snm_config().get_node_dist_url(); | ||
let node_list_url = format!("{}/index.json", host); | ||
|
||
let client = reqwest::Client::new(); | ||
|
||
let node_vec: Vec<NodeModel> = client | ||
.get(&node_list_url) | ||
.timeout(Duration::from_secs(10)) | ||
.send() | ||
.await? | ||
.json::<Vec<NodeModel>>() | ||
.await?; | ||
Ok(node_vec) | ||
} | ||
|
||
async fn get_node_schedule(&self) -> Result<Vec<NodeSchedule>, SnmError> { | ||
let host = self | ||
.node_atom | ||
.get_snm_config() | ||
.get_node_github_resource_host(); | ||
|
||
let node_schedule_url = format!("{}/nodejs/Release/main/schedule.json", host); | ||
|
||
let client = reqwest::Client::new(); | ||
|
||
let node_schedule_vec: Vec<NodeSchedule> = client | ||
.get(&node_schedule_url) | ||
.timeout(Duration::from_secs(10)) | ||
.send() | ||
.await? | ||
.json::<std::collections::HashMap<String, NodeSchedule>>() | ||
.await? | ||
.into_iter() | ||
.map(|(v, mut schedule)| { | ||
schedule.version = Some(v[1..].to_string()); | ||
schedule | ||
}) | ||
.collect(); | ||
|
||
Ok(node_schedule_vec) | ||
} | ||
|
||
async fn get_node_sha256_hashmap( | ||
&self, | ||
node_version: &str, | ||
) -> Result<HashMap<String, String>, SnmError> { | ||
let host = self.node_atom.get_snm_config().get_node_dist_url(); | ||
let url = format!("{}/v{}/SHASUMS256.txt", host, node_version); | ||
|
||
let sha256_str = reqwest::get(&url).await?.text().await?; | ||
|
||
let sha256_map: std::collections::HashMap<String, String> = sha256_str | ||
.lines() | ||
.map(|line| { | ||
let mut iter = line.split_whitespace(); | ||
let sha256 = iter.next().unwrap(); | ||
let file = iter.next().unwrap(); | ||
(file.to_string(), sha256.to_string()) | ||
}) | ||
.collect(); | ||
|
||
Ok(sha256_map) | ||
} | ||
} | ||
|
||
impl<'a, T> NodeManager<'a, T> | ||
where | ||
T: AtomTrait, | ||
{ | ||
pub fn new(node_atom: &'a T) -> Self { | ||
Self { node_atom } | ||
} | ||
|
||
pub async fn set_default(&self, version: &str) -> Result<(), SnmError> { | ||
if self | ||
.node_atom | ||
.get_anchor_file_path_buf(version)? | ||
.exists() | ||
.not() | ||
{ | ||
let msg = format!( | ||
"🤔 v{} is not installed, do you want to install it ?", | ||
version | ||
); | ||
if Confirm::new().with_prompt(msg).interact()? { | ||
self.install(version).await?; | ||
} | ||
} | ||
|
||
self.internal_set_default(version)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
pub async fn install(&self, version: &str) -> Result<(), SnmError> { | ||
let anchor_file = self.node_atom.get_anchor_file_path_buf(&version)?; | ||
let version_dir = self.node_atom.get_runtime_dir_path_buf(&version)?; | ||
|
||
if anchor_file.exists() { | ||
let confirm = Confirm::new() | ||
.with_prompt(format!( | ||
"🤔 v{} is already installed, do you want to reinstall it ?", | ||
&version | ||
)) | ||
.interact()?; | ||
|
||
if confirm { | ||
fs::remove_dir_all(&version_dir)?; | ||
self.internal_download(version).await?; | ||
} | ||
} else { | ||
self.internal_download(version).await?; | ||
} | ||
|
||
self.internal_set_default(version)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
pub async fn un_install(&self, version: &str) -> Result<(), SnmError> { | ||
let default_dir = self.node_atom.get_runtime_dir_for_default_path_buf()?; | ||
let version_dir = self.node_atom.get_runtime_dir_path_buf(&version)?; | ||
if fs::read_link(&default_dir)?.eq(&version_dir) { | ||
let msg = format!( | ||
"🤔 {} is default instance, do you want to uninstall it ?", | ||
version | ||
); | ||
if Confirm::new().with_prompt(msg).interact()? { | ||
fs::remove_file(&default_dir)?; | ||
fs::remove_dir_all(version_dir)?; | ||
} | ||
} else { | ||
fs::remove_dir_all(version_dir)?; | ||
} | ||
Ok(()) | ||
} | ||
|
||
pub async fn list(&self) -> Result<(), SnmError> { | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
use serde::{Deserialize, Deserializer, Serialize}; | ||
use std::{borrow::Cow, fmt}; | ||
|
||
#[derive(Deserialize, Debug, Serialize)] | ||
pub struct NodeModel { | ||
pub version: String, | ||
pub date: String, | ||
pub files: Vec<String>, | ||
pub npm: Option<String>, | ||
pub v8: String, | ||
pub uv: Option<String>, | ||
pub zlib: Option<String>, | ||
pub openssl: Option<String>, | ||
pub modules: Option<String>, | ||
pub lts: Lts, | ||
pub security: bool, | ||
pub end: Option<String>, | ||
pub current: Option<String>, | ||
pub deprecated: Option<bool>, | ||
} | ||
|
||
impl fmt::Display for NodeModel { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!( | ||
f, | ||
"version: {:<10}, lts: {:<10}, date: {:<12}, end: {:<12}, npm: {:<14}, v8: {:<14}, uv: {:<12}, zlib: {:<18}, openssl: {:<14}, modules: {:<12}, deprecated: {}", | ||
self.version, | ||
match &self.lts { | ||
Lts::Str(s) => Cow::Borrowed(s), | ||
Lts::Bool(b) => Cow::Owned(b.to_string()), | ||
}, | ||
self.date, | ||
self.end.as_deref().unwrap_or("None"), | ||
self.npm.as_deref().unwrap_or("None"), | ||
self.v8, | ||
self.uv.as_deref().unwrap_or("None"), | ||
self.zlib.as_deref().unwrap_or("None"), | ||
self.openssl.as_deref().unwrap_or("None"), | ||
self.modules.as_deref().unwrap_or("None"), | ||
self.deprecated.map_or("None".to_string(), |v| v.to_string()) | ||
) | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub enum Lts { | ||
Str(String), | ||
Bool(bool), | ||
} | ||
|
||
impl Serialize for Lts { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: serde::ser::Serializer, | ||
{ | ||
match self { | ||
Lts::Str(s) => serializer.serialize_str(s), | ||
Lts::Bool(b) => serializer.serialize_bool(*b), | ||
} | ||
} | ||
} | ||
|
||
// 自定义反序列化 | ||
impl<'de> Deserialize<'de> for Lts { | ||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
let value = serde_json::Value::deserialize(deserializer)?; | ||
match value { | ||
serde_json::Value::String(s) => Ok(Lts::Str(s)), | ||
serde_json::Value::Bool(b) => Ok(Lts::Bool(b)), | ||
_ => Err(serde::de::Error::custom("expected a string or a bool")), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
use colored::*; | ||
use core::fmt; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct NodeSchedule { | ||
pub start: String, | ||
pub end: String, | ||
pub maintenance: Option<String>, | ||
pub lts: Option<String>, | ||
pub codename: Option<String>, | ||
pub version: Option<String>, | ||
} | ||
|
||
impl fmt::Display for NodeSchedule { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let lts = self | ||
.lts | ||
.as_deref() | ||
.map_or_else(|| format!(""), |lts| format!("Lts by {:<12}", lts)); | ||
|
||
let codename = self.codename.as_deref().map_or_else( | ||
|| format!("{:<20}", ""), | ||
|codename| format!("{:<10} {:<10}", codename.bright_black(), lts.bright_black()), | ||
); | ||
|
||
write!( | ||
f, | ||
"Create by: {:<12}, Death by: {:<12}, Maintenance By {:<12}, Version {:<5} {}", | ||
self.start.bright_green(), | ||
self.end.bright_magenta(), | ||
self.maintenance.as_deref().unwrap_or("none").bright_green(), | ||
self.version.as_deref().unwrap_or("none").bright_green(), | ||
codename, | ||
) | ||
} | ||
} |
Oops, something went wrong.