From 8911d8f6bc2689f36a407e597eab2c477294565c Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Date: Tue, 9 Apr 2024 14:26:37 +0200 Subject: [PATCH] core: fixing the nurse run command Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> --- coffee_cmd/src/coffee_term/command_show.rs | 51 +++++++++++++++++++++- coffee_cmd/src/main.rs | 4 +- coffee_core/src/coffee.rs | 21 ++++----- coffee_core/src/config.rs | 35 ++++++++------- coffee_core/src/nurse/chain.rs | 2 +- coffee_core/src/nurse/strategy.rs | 5 +++ coffee_github/src/repository.rs | 4 ++ coffee_lib/src/repository.rs | 5 +++ coffee_lib/src/types/mod.rs | 35 ++------------- coffee_lib/src/url.rs | 4 ++ coffee_lib/src/utils.rs | 35 ++++++++++++++- rust-toolchain | 2 +- 12 files changed, 136 insertions(+), 67 deletions(-) diff --git a/coffee_cmd/src/coffee_term/command_show.rs b/coffee_cmd/src/coffee_term/command_show.rs index 0977600..28497d8 100644 --- a/coffee_cmd/src/coffee_term/command_show.rs +++ b/coffee_cmd/src/coffee_term/command_show.rs @@ -7,7 +7,10 @@ use term::Element; use coffee_lib::error; use coffee_lib::errors::CoffeeError; -use coffee_lib::types::response::{CoffeeList, CoffeeNurse, CoffeeRemote, CoffeeTip, NurseStatus}; +use coffee_lib::types::response::{ + ChainOfResponsibilityStatus, CoffeeList, CoffeeNurse, CoffeeRemote, CoffeeTip, Defect, + NurseStatus, +}; pub fn show_list(coffee_list: Result<CoffeeList, CoffeeError>) -> Result<(), CoffeeError> { let remotes = coffee_list?; @@ -86,6 +89,52 @@ pub fn show_remote_list(remote_list: Result<CoffeeRemote, CoffeeError>) -> Resul Ok(()) } +pub fn show_nurse_verify(nurse_verify: &ChainOfResponsibilityStatus) -> Result<(), CoffeeError> { + if nurse_verify.defects.is_empty() { + term::success!("Coffee configuration is not corrupt! No need to run coffee nurse"); + } else { + let mut table = radicle_term::Table::new(TableOptions::bordered()); + table.push([ + term::format::dim(String::from("●")), + term::format::bold(String::from("Defects")), + term::format::bold(String::from("Affected repositories")), + ]); + table.divider(); + + for defect in nurse_verify.defects.iter() { + match defect { + Defect::RepositoryLocallyAbsent(repos) => { + let defect = "Repository missing locally"; + for repo in repos { + table.push([ + term::format::positive("●").into(), + term::format::bold(defect.to_owned()), + term::format::highlight(repo.clone()), + ]); + } + } + Defect::CoffeeGlobalrepoCleanup(networks) => { + let defect = format!( + "Global repository migration completed for the networks: {}", + networks + .iter() + .map(|(network, _)| format!("{network} ")) + .collect::<String>() + .trim_end() + ); + table.push([ + term::format::positive("●").into(), + term::format::bold(defect.to_owned()), + term::format::highlight("_".to_owned()), + ]); + } + } + } + table.print(); + } + Ok(()) +} + pub fn show_nurse_result( nurse_result: Result<CoffeeNurse, CoffeeError>, ) -> Result<(), CoffeeError> { diff --git a/coffee_cmd/src/main.rs b/coffee_cmd/src/main.rs index c64106e..5cd7a4f 100644 --- a/coffee_cmd/src/main.rs +++ b/coffee_cmd/src/main.rs @@ -154,9 +154,9 @@ async fn run(args: CoffeeArgs, mut coffee: CoffeeManager) -> Result<(), CoffeeEr CoffeeCommand::Nurse { verify } => { if verify { let result = coffee.nurse_verify().await?; - term::info!("{}", result); + coffee_term::show_nurse_verify(&result)?; if !result.is_sane() { - term::info!("Coffee local directory is damaged, please run `coffee nurse` to try to fix it"); + term::warning(term::style("Coffee local directory is damaged, please run `coffee nurse` to try to fix it").bold()); } } else { let nurse_result = coffee.nurse().await; diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index 79a3a50..0ed4adf 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -21,7 +21,7 @@ use coffee_lib::plugin_manager::PluginManager; use coffee_lib::repository::Repository; use coffee_lib::types::response::*; use coffee_lib::url::URL; -use coffee_lib::utils::{copy_dir_if_exist, rm_dir_if_exist}; +use coffee_lib::utils::{check_dir_or_make_if_missing, copy_dir_if_exist, rm_dir_if_exist}; use coffee_lib::{commit_id, error, get_repo_info, sh}; use coffee_storage::model::repository::{Kind, Repository as RepositoryInfo}; use coffee_storage::nosql_db::NoSQlStorage; @@ -80,8 +80,8 @@ pub struct CoffeeManager { } impl CoffeeManager { - pub async fn new(conf: &dyn CoffeeArgs) -> Result<Self, CoffeeError> { - let conf = CoffeeConf::new(conf).await?; + pub async fn new(conf_args: &dyn CoffeeArgs) -> Result<Self, CoffeeError> { + let conf = CoffeeConf::new(conf_args).await?; let mut coffee = CoffeeManager { config: conf.clone(), coffee_cln_config: CLNConf::new(conf.config_path, true), @@ -98,6 +98,7 @@ impl CoffeeManager { /// when coffee is configured, run an inventory to collect all the necessary information /// about the coffee ecosystem. async fn inventory(&mut self) -> Result<(), CoffeeError> { + let skip_verify = self.config.skip_verify; let _ = self .storage .load::<CoffeeStorageInfo>(&self.config.network) @@ -123,7 +124,7 @@ impl CoffeeManager { if let Err(err) = self.coffee_cln_config.parse() { log::error!("{}", err.cause); } - if !self.config.skip_verify { + if !skip_verify { // Check for the chain of responsibility let status = self.recovery_strategies.scan(self).await?; log::debug!("Chain of responsibility status: {:?}", status); @@ -427,15 +428,10 @@ impl PluginManager for CoffeeManager { } async fn add_remote(&mut self, name: &str, url: &str) -> Result<(), CoffeeError> { - // FIXME: we should allow some error here like - // for the add remote command the no found error for the `repository` - // directory is fine. - if self.repos.contains_key(name) { return Err(error!("repository with name: {name} already exists")); } - let local_path = format!("{}/{}", self.config.root_path, self.config.network); - let url = URL::new(&local_path, url, name); + let url = URL::new(&self.config.path(), url, name); log::debug!("remote adding: {} {}", name, &url.url_string); let mut repo = Github::new(name, &url); repo.init().await?; @@ -551,6 +547,8 @@ impl PluginManager for CoffeeManager { Defect::CoffeeGlobalrepoCleanup(networks) => { let global_repo = format!("{}/repositories", self.config.root_path); for (network, path) in networks { + log::info!("{network} - {path}"); + check_dir_or_make_if_missing(path.to_owned()).await?; if !Path::exists(Path::new(&path)) { copy_dir_if_exist(&global_repo, path).await?; } @@ -565,6 +563,8 @@ impl PluginManager for CoffeeManager { status: nurse_actions, }; nurse.organize(); + // Refesh the status + self.flush().await?; Ok(nurse) } @@ -587,6 +587,7 @@ impl PluginManager for CoffeeManager { .get_mut(repo_name) .ok_or_else(|| error!("repository with name: {repo_name} not found"))?; + repo.change_root_path(&self.config.path()); match repo.recover().await { Ok(_) => { log::info!("repository {} recovered", repo_name.clone()); diff --git a/coffee_core/src/config.rs b/coffee_core/src/config.rs index 5f9e287..1f5328d 100644 --- a/coffee_core/src/config.rs +++ b/coffee_core/src/config.rs @@ -1,13 +1,14 @@ //! Coffee configuration utils. -use log::info; -use serde::{Deserialize, Serialize}; use std::env; -use crate::CoffeeOperation; -use coffee_lib::utils::{check_dir_or_make_if_missing, copy_dir_if_exist}; +use serde::{Deserialize, Serialize}; + +use coffee_lib::utils::check_dir_or_make_if_missing; use coffee_lib::{errors::CoffeeError, plugin::Plugin}; use crate::CoffeeArgs; +use crate::CoffeeOperation; + /// Custom coffee configuration, given by a command line list of arguments /// or a coffee configuration file. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -15,23 +16,25 @@ pub struct CoffeeConf { /// Network configuration related /// to core lightning network pub network: String, + /// root path plugin manager + pub root_path: String, /// path of core lightning configuration file /// managed by coffee pub config_path: String, /// path of the core lightning configuration file /// not managed by core lightning - /// (this file included the file managed by coffee) + /// + /// This file included the file managed by coffee pub cln_config_path: Option<String>, /// root cln directory path pub cln_root: Option<String>, - /// root path plugin manager - pub root_path: String, /// all plugins that are installed /// with the plugin manager. pub plugins: Vec<Plugin>, /// A flag that indicates if the /// user wants to skip the verification /// of nurse. + #[serde(skip)] pub skip_verify: bool, } @@ -47,7 +50,7 @@ impl CoffeeConf { def_path = def_path.strip_suffix('/').unwrap_or(&def_path).to_string(); def_path += "/.coffee"; check_dir_or_make_if_missing(def_path.to_string()).await?; - info!("creating coffee home at {def_path}"); + log::info!("creating coffee home at {def_path}"); let mut coffee = CoffeeConf { network: "bitcoin".to_owned(), @@ -62,14 +65,8 @@ impl CoffeeConf { // check the command line arguments and bind them // inside the coffee conf coffee.bind_cmd_line_params(conf)?; - check_dir_or_make_if_missing(format!("{def_path}/{}", coffee.network)).await?; check_dir_or_make_if_missing(format!("{def_path}/{}/plugins", coffee.network)).await?; - let repo_dir = format!("{def_path}/{}/repositories", coffee.network); - // older version of coffee has a repository inside the directory - copy_dir_if_exist(&format!("{def_path}/repositories"), &repo_dir).await?; - // FIXME: nurse should clean up the `{def_path}/repositories`. - check_dir_or_make_if_missing(repo_dir).await?; // after we know all the information regarding // the configuration we try to see if there is // something stored already to the disk. @@ -105,10 +102,12 @@ impl CoffeeConf { } } } - - // FIXME: be able to put the directory also in another place! - // for now it is fixed in the Home/.coffee but another good place - // will be, the .lightning dir Ok(()) } + + /// Return the root path of the coffee manager instance + /// this include also the network path. + pub fn path(&self) -> String { + format!("{}/{}", self.root_path, self.network) + } } diff --git a/coffee_core/src/nurse/chain.rs b/coffee_core/src/nurse/chain.rs index 2443772..a189271 100644 --- a/coffee_core/src/nurse/chain.rs +++ b/coffee_core/src/nurse/chain.rs @@ -53,8 +53,8 @@ impl RecoveryChainOfResponsibility { pub async fn new() -> Result<Self, CoffeeError> { Ok(Self { handlers: vec![ - Arc::new(GitRepositoryLocallyAbsentStrategy), Arc::new(CoffeeRepositoryDirCleanUp), + Arc::new(GitRepositoryLocallyAbsentStrategy), ], }) } diff --git a/coffee_core/src/nurse/strategy.rs b/coffee_core/src/nurse/strategy.rs index fca65fe..0ecfc55 100644 --- a/coffee_core/src/nurse/strategy.rs +++ b/coffee_core/src/nurse/strategy.rs @@ -94,6 +94,11 @@ impl Handler for CoffeeRepositoryDirCleanUp { // Check whether there exists a network-specific repositories folder for each network. let mut directory_moving = vec![]; for network in networks { + let network_dir = format!("{}/{network}", coffee.config.root_path); + if !Path::exists(Path::new(&network_dir)) { + log::debug!("network dir `{network_dir}` not found"); + continue; + } let subpath_repo = format!("{}/{network}/repositories", coffee.config.root_path); if !Path::exists(Path::new(&subpath_repo)) { directory_moving.push((network.to_string(), subpath_repo)); diff --git a/coffee_github/src/repository.rs b/coffee_github/src/repository.rs index be237e6..fa6f7e2 100644 --- a/coffee_github/src/repository.rs +++ b/coffee_github/src/repository.rs @@ -233,6 +233,10 @@ impl Repository for Github { } } + fn change_root_path(&mut self, path: &str) { + self.url.set_coffee_path(path); + } + async fn upgrade( &mut self, plugins: &Vec<Plugin>, diff --git a/coffee_lib/src/repository.rs b/coffee_lib/src/repository.rs index dbb9bec..586a51d 100644 --- a/coffee_lib/src/repository.rs +++ b/coffee_lib/src/repository.rs @@ -34,6 +34,11 @@ pub trait Repository: Any { /// recover the repository from the commit id. async fn recover(&mut self) -> Result<(), CoffeeError>; + /// While migrating there is a possibility that we should + /// move an old repository into a new path. So this + /// is semplyfing this process. + fn change_root_path(&mut self, path: &str); + /// return the name of the repository. fn name(&self) -> String; diff --git a/coffee_lib/src/types/mod.rs b/coffee_lib/src/types/mod.rs index 0fb1e21..ba33b74 100644 --- a/coffee_lib/src/types/mod.rs +++ b/coffee_lib/src/types/mod.rs @@ -166,37 +166,6 @@ pub mod response { } } - impl fmt::Display for ChainOfResponsibilityStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.defects.is_empty() { - write!(f, "Coffee is sane") - } else { - writeln!(f, "Coffee has the following defects:")?; - for (i, defect) in self.defects.iter().enumerate() { - match defect { - Defect::RepositoryLocallyAbsent(repos) => { - write!(f, "{}. Repository missing locally: ", i + 1)?; - for repo in repos { - write!(f, " {}", repo)?; - } - } - Defect::CoffeeGlobalrepoCleanup(networks) => { - writeln!( - f, - "Global repository migration completed for the networks: {}", - networks - .iter() - .map(|(network, _)| network.to_owned()) - .collect::<String>() - )?; - } - } - } - Ok(()) - } - } - } - /// This struct is used to represent the status of nurse, /// either sane or not. /// If not sane, return the action that nurse has taken. @@ -231,7 +200,9 @@ pub mod response { NurseStatus::RepositoryLocallyRestored(repos) => { repositories_locally_restored.append(&mut repos.clone()) } - NurseStatus::MovingGlobalRepostoryTo(_) => {} + NurseStatus::MovingGlobalRepostoryTo(status) => { + new_status.push(NurseStatus::MovingGlobalRepostoryTo(status.to_owned())); + } } } if !repositories_locally_removed.is_empty() { diff --git a/coffee_lib/src/url.rs b/coffee_lib/src/url.rs index c7dc4b8..9b4dcd7 100644 --- a/coffee_lib/src/url.rs +++ b/coffee_lib/src/url.rs @@ -57,6 +57,10 @@ impl URL { repo_name: get_repo_name_from_url(url), } } + + pub fn set_coffee_path(&mut self, path: &str) { + self.path_string = format!("{path}/{}", self.repo_name); + } } impl fmt::Display for URL { diff --git a/coffee_lib/src/utils.rs b/coffee_lib/src/utils.rs index f951aae..ca7717c 100644 --- a/coffee_lib/src/utils.rs +++ b/coffee_lib/src/utils.rs @@ -1,5 +1,5 @@ use super::macros::error; -use std::path::Path; +use std::path::{Path, PathBuf}; use tokio::fs; @@ -21,6 +21,7 @@ pub fn get_plugin_info_from_path(path: &Path) -> Result<(String, String), Coffee } pub async fn check_dir_or_make_if_missing(path: String) -> Result<(), CoffeeError> { + log::trace!("check_dir_or_make_if_missing: `{path}`"); if !Path::exists(Path::new(&path.to_owned())) { fs::create_dir(path.clone()).await?; log::debug!("created dir {path}"); @@ -29,13 +30,43 @@ pub async fn check_dir_or_make_if_missing(path: String) -> Result<(), CoffeeErro } pub async fn copy_dir_if_exist(origin: &str, destination: &str) -> Result<(), CoffeeError> { + log::trace!("copy_dir_if_exist: from: `{origin}` to `{destination}`"); if Path::exists(Path::new(&origin)) { - fs::copy(origin, destination).await?; + copy_dir_recursive(origin.to_owned(), destination.to_owned()).await?; log::debug!("copying dir from {origin} to {destination}"); } Ok(()) } +async fn copy_dir_recursive(source: String, destination: String) -> Result<(), CoffeeError> { + async fn inner_copy_dir_recursive( + source: PathBuf, + destination: PathBuf, + ) -> Result<(), CoffeeError> { + check_dir_or_make_if_missing(destination.to_string_lossy().to_string()).await?; + + let mut entries = fs::read_dir(source).await?; + while let Some(entry) = entries.next_entry().await? { + let file_type = entry.file_type().await?; + let dest_path = destination.join(entry.file_name()); + log::debug!("copy entry {:?} in {:?}", entry, dest_path); + if file_type.is_dir() { + // Here we use Box::pin to allow recursion + let fut = inner_copy_dir_recursive(entry.path(), dest_path); + Box::pin(fut).await?; + } else if file_type.is_file() { + fs::copy(entry.path(), &dest_path).await?; + } + } + + Ok(()) + } + let source = Path::new(&source); + let destination = Path::new(&destination); + log::info!("{:?} - {:?}", source, destination); + inner_copy_dir_recursive(source.to_path_buf(), destination.to_path_buf()).await +} + pub async fn rm_dir_if_exist(origin: &str) -> Result<(), CoffeeError> { if Path::exists(Path::new(&origin)) { fs::remove_dir_all(origin).await?; diff --git a/rust-toolchain b/rust-toolchain index 07cde98..3245dca 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.75 +1.77