diff --git a/crates/snm_core/src/model/dispatch_manage.rs b/crates/snm_core/src/model/dispatch_manage.rs index a9ff74dc..d23c6d66 100644 --- a/crates/snm_core/src/model/dispatch_manage.rs +++ b/crates/snm_core/src/model/dispatch_manage.rs @@ -23,20 +23,17 @@ impl DispatchManage { // 分配 impl DispatchManage { pub async fn list(&self) -> Result<(), SnmError> { - let dir_tuple = self.read_runtime_dir_name_vec()?; - self.manager.show_list(&dir_tuple).await; + self.manager.show_list().await?; Ok(()) } pub async fn list_offline(&self) -> Result<(), SnmError> { - let dir_tuple = self.read_runtime_dir_name_vec()?; - self.manager.show_list_offline(&dir_tuple).await; + self.manager.show_list_offline().await?; Ok(()) } pub async fn list_remote(&self, all: bool) -> Result<(), SnmError> { - let dir_tuple = self.read_runtime_dir_name_vec()?; - self.manager.show_list_remote(&dir_tuple, all).await?; + self.manager.show_list_remote(all).await?; Ok(()) } @@ -112,55 +109,6 @@ impl DispatchManage { Ok(()) } - fn read_runtime_dir_name_vec(&self) -> Result<(Vec, Option), SnmError> { - let runtime_dir_path_buf = self.manager.get_runtime_base_dir_path_buf()?; - - let mut default_dir = None; - - if runtime_dir_path_buf.exists().not() { - // TODO here create not suitable , should be find a better way - fs::create_dir_all(&runtime_dir_path_buf).expect( - format!( - "read_runtime_dir_name_vec create_dir_all error {:?}", - &runtime_dir_path_buf.display() - ) - .as_str(), - ); - } - - let dir_name_vec = runtime_dir_path_buf - .read_dir() - .expect( - format!( - "read_runtime_dir_name_vec read_dir error {:?}", - &runtime_dir_path_buf.display() - ) - .as_str(), - ) - .filter_map(|dir_entry| dir_entry.ok()) - .filter(|dir_entry| dir_entry.path().is_dir()) - .filter_map(|dir_entry| { - let file_name = dir_entry.file_name().into_string().ok()?; - - if file_name.eq("default") { - if let Some(o) = fs::read_link(dir_entry.path()).ok() { - if let Some(last) = - o.components().last().and_then(|x| x.as_os_str().to_str()) - { - default_dir = Some(String::from(last)); - } - } - - return None; - } - - return Some(file_name); - }) - .collect::>(); - - Ok((dir_name_vec, default_dir)) - } - async fn download(&self, v: &str) -> Result<(), SnmError> { let download_url = self.manager.get_download_url(v); let downloaded_file_path_buf = self.manager.get_downloaded_file_path_buf(v)?; diff --git a/crates/snm_core/src/traits/atom.rs b/crates/snm_core/src/traits/atom.rs index e4122ef5..df9fbe7d 100644 --- a/crates/snm_core/src/traits/atom.rs +++ b/crates/snm_core/src/traits/atom.rs @@ -24,19 +24,14 @@ pub trait AtomTrait { downloaded_file_path_buf: &'a PathBuf, ) -> Pin> + Send + 'a>>; - fn show_list<'a>( - &'a self, - dir_tuple: &'a (Vec, Option), - ) -> Pin + Send + 'a>>; + fn show_list<'a>(&'a self) -> Pin> + Send + 'a>>; fn show_list_offline<'a>( &'a self, - dir_tuple: &'a (Vec, Option), - ) -> Pin + Send + 'a>>; + ) -> Pin> + Send + 'a>>; fn show_list_remote<'a>( &'a self, - dir_tuple: &'a (Vec, Option), all: bool, ) -> Pin> + Send + 'a>>; @@ -77,8 +72,15 @@ pub trait AtomTrait { .filter_map(|dir_entry| { let file_name = dir_entry.file_name().into_string().ok()?; - if file_name.ends_with("-default") { - default_dir = Some(file_name.trim_end_matches("-default").to_string()); + if file_name.eq("default") { + if let Some(o) = fs::read_link(dir_entry.path()).ok() { + if let Some(last) = + o.components().last().and_then(|x| x.as_os_str().to_str()) + { + default_dir = Some(String::from(last)); + } + } + return None; } diff --git a/crates/snm_node/Cargo.toml b/crates/snm_node/Cargo.toml index 1efe821d..e70f8e0f 100644 --- a/crates/snm_node/Cargo.toml +++ b/crates/snm_node/Cargo.toml @@ -25,6 +25,7 @@ snm_utils = { path = "../snm_utils" } snm_config = { path = "../snm_config" } snm_node_version = { path = "../snm_node_version" } snm_tarball = { path = "../snm_tarball" } +snm_download_builder = { path = "../snm_download_builder" } xz2 = {version = "0.1.7" , features = ["static"]} tar = "0.4" num-format = "0.4.0" diff --git a/crates/snm_node/src/snm_node.rs b/crates/snm_node/src/snm_node.rs index 3814e952..d0874153 100644 --- a/crates/snm_node/src/snm_node.rs +++ b/crates/snm_node/src/snm_node.rs @@ -16,49 +16,169 @@ use sha2::Sha256; use snm_config::InstallStrategy; use snm_config::SnmConfig; use snm_core::traits::atom::AtomTrait; +use snm_download_builder::{DownloadBuilder, WriteStrategy}; use snm_tarball::decompress; use snm_utils::snm_error::SnmError; use snm_utils::to_ok::ToOk; use std::collections::HashMap; +use std::fs; +use std::ops::Not; use std::pin::Pin; +use std::time::Duration; use std::{ fs::File, io::{BufReader, Read}, path::PathBuf, }; +use tokio::try_join; pub struct SnmNode { snm_config: SnmConfig, } impl SnmNode { + async fn download(&self, version: &str) -> Result<(), SnmError> { + let download_url = self.get_download_url(version); + let downloaded_file_path_buf = self.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.get_runtime_dir_path_buf(version)?; + + if runtime.exists() { + fs::remove_dir_all(&runtime)?; + } + + self.decompress_download_file(&downloaded_file_path_buf, &runtime)?; + + fs::remove_file(&downloaded_file_path_buf)?; + + Ok(()) + } + + pub async fn set_default(&self, version: &str) -> Result<(), SnmError> { + if self.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?; + } + } else { + let default_dir = self.get_runtime_dir_for_default_path_buf()?; + if default_dir.exists() { + fs::remove_dir_all(&default_dir)?; + } + + let from_dir = self.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(()) + } + + pub async fn un_install(&self, version: &str) -> Result<(), SnmError> { + let default_dir = self.get_runtime_dir_for_default_path_buf()?; + let version_dir = self.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 install(&self, version: &str) -> Result<(), SnmError> { + let anchor_file = self.get_anchor_file_path_buf(&version)?; + let version_dir = self.get_runtime_dir_path_buf(&version)?; + + if anchor_file.exists().not() { + self.download(version).await?; + } else { + let confirm = Confirm::new() + .with_prompt(format!( + "🤔 v{} is already installed, do you want to reinstall it ?", + &version + )) + .interact() + .expect("install Confirm error"); + + if confirm { + fs::remove_dir_all(&version_dir)?; + self.download(version).await?; + } + + let default_dir = self.get_runtime_dir_for_default_path_buf()?; + + if default_dir.exists().not() { + #[cfg(unix)] + { + std::os::unix::fs::symlink(&version_dir, &default_dir)?; + } + #[cfg(windows)] + { + std::os::windows::fs::symlink_dir(&version_dir, &default_dir)?; + } + } + } + + Ok(()) + } + pub fn new(snm_config: SnmConfig) -> Self { Self { snm_config } } - async fn get_node_list_remote(&self) -> Vec { + async fn get_node_list_remote(&self) -> Result, SnmError> { let host = self.snm_config.get_node_dist_url(); let node_list_url = format!("{}/index.json", host); - let node_vec: Vec = reqwest::get(&node_list_url) - .await - .expect(format!("fetch {} failed", &node_list_url).as_str()) + + let client = reqwest::Client::new(); + + let node_vec: Vec = client + .get(&node_list_url) + .timeout(Duration::from_secs(10)) + .send() + .await? .json::>() - .await - .expect(format!("parse {} response to json failed", &node_list_url).as_str()); - node_vec + .await?; + Ok(node_vec) } - async fn get_node_schedule(&self) -> Vec { + async fn get_node_schedule(&self) -> Result, SnmError> { let host = self.snm_config.get_node_github_resource_host(); let node_schedule_url = format!("{}/nodejs/Release/main/schedule.json", host); - let node_schedule_vec: Vec = reqwest::get(&node_schedule_url) - .await - .expect(format!("fetch {} failed", node_schedule_url).as_str()) + let client = reqwest::Client::new(); + + let node_schedule_vec: Vec = client + .get(&node_schedule_url) + .timeout(Duration::from_secs(10)) + .send() + .await? .json::>() - .await - .expect(format!("parse {} response to json failed", node_schedule_url).as_str()) + .await? .into_iter() .map(|(v, mut schedule)| { schedule.version = Some(v[1..].to_string()); @@ -66,7 +186,7 @@ impl SnmNode { }) .collect(); - node_schedule_vec + Ok(node_schedule_vec) } async fn get_node_sha256_hashmap(&self, node_version: &str) -> HashMap { @@ -285,12 +405,9 @@ impl AtomTrait for SnmNode { }) } - fn show_list<'a>( - &'a self, - dir_tuple: &'a (Vec, Option), - ) -> Pin + Send + 'a>> { + fn show_list<'a>(&'a self) -> Pin> + Send + 'a>> { Box::pin(async move { - let (dir_vec, default_v) = dir_tuple; + let (dir_vec, default_v) = self.read_runtime_dir_name_vec()?; if dir_vec.is_empty() { let msg = format!( "Node list is empty, please use {} to get the latest version.", @@ -301,7 +418,7 @@ impl AtomTrait for SnmNode { let now = Utc::now().date_naive(); let (remote_node_vec, node_schedule_vec) = - join!(self.get_node_list_remote(), self.get_node_schedule()); + try_join!(self.get_node_list_remote(), self.get_node_schedule())?; let version_req_vec = node_schedule_vec .into_iter() @@ -362,7 +479,7 @@ impl AtomTrait for SnmNode { if let Some(v) = default_v { self.show_node_list(node_vec, |node_v| { - if node_v == v { + if *node_v == v { return "⛳️"; } else { return ""; @@ -373,15 +490,15 @@ impl AtomTrait for SnmNode { return ""; }); } + Ok(()) }) } fn show_list_offline<'a>( &'a self, - dir_tuple: &'a (Vec, Option), - ) -> Pin + Send + 'a>> { + ) -> Pin> + Send + 'a>> { Box::pin(async move { - let (dir_vec, default_v) = dir_tuple; + let (dir_vec, default_v) = self.read_runtime_dir_name_vec()?; if dir_vec.is_empty() { let msg = format!( "Node list is empty, please use {} to get the latest version.", @@ -397,20 +514,20 @@ impl AtomTrait for SnmNode { " " }; println!("{:<2} {}", prefix, item); - }) + }); + Ok(()) }) } fn show_list_remote<'a>( &'a self, - dir_tuple: &'a (Vec, Option), all: bool, ) -> Pin> + Send + 'a>> { Box::pin(async move { - let (dir_vec, _default_v) = dir_tuple; + let (dir_vec, _default_v) = self.read_runtime_dir_name_vec()?; let (mut node_vec, node_schedule_vec) = - join!(self.get_node_list_remote(), self.get_node_schedule()); + try_join!(self.get_node_list_remote(), self.get_node_schedule())?; let now = Utc::now().date_naive(); diff --git a/crates/snm_package_manager/src/snm_package_manager.rs b/crates/snm_package_manager/src/snm_package_manager.rs index 9ef7cd1d..f0361950 100644 --- a/crates/snm_package_manager/src/snm_package_manager.rs +++ b/crates/snm_package_manager/src/snm_package_manager.rs @@ -217,34 +217,30 @@ impl AtomTrait for SnmPackageManager { }) } - fn show_list<'a>( - &'a self, - dir_tuple: &'a (Vec, Option), - ) -> Pin + Send + 'a>> { + fn show_list<'a>(&'a self) -> Pin> + Send + 'a>> { Box::pin(async move { - let (dir_vec, default_v) = &dir_tuple; - + let (dir_vec, default_v) = self.read_runtime_dir_name_vec()?; dir_vec.into_iter().for_each(|dir| { - let prefix = if Some(dir) == default_v.as_ref() { + let prefix = if Some(dir.clone()) == default_v { "⛳️" } else { " " }; println!("{:<2} {:<10}", prefix, dir.bright_green()); }); + + Ok(()) }) } fn show_list_offline<'a>( &'a self, - _dir_tuple: &'a (Vec, Option), - ) -> Pin + Send + 'a>> { + ) -> Pin> + Send + 'a>> { todo!("show_list_remote") } fn show_list_remote<'a>( &'a self, - _dir_tuple: &'a (Vec, Option), _all: bool, ) -> Pin> + Send + 'a>> { Box::pin(async move {