Skip to content

Commit

Permalink
feat(core): implement handler and patcher for GitRepositoryLocallyAbs…
Browse files Browse the repository at this point in the history
…entStrategy

Signed-off-by: Tarek <[email protected]>
  • Loading branch information
tareknaser committed Sep 26, 2023
1 parent 93eb17d commit bf617d8
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 59 deletions.
18 changes: 4 additions & 14 deletions coffee_cmd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use radicle_term as term;
use coffee_core::coffee::CoffeeManager;
use coffee_lib::errors::CoffeeError;
use coffee_lib::plugin_manager::PluginManager;
use coffee_lib::types::response::{NurseStatus, UpgradeStatus};
use coffee_lib::types::response::UpgradeStatus;

use crate::cmd::CoffeeArgs;
use crate::cmd::CoffeeCommand;
Expand Down Expand Up @@ -125,19 +125,9 @@ async fn main() -> Result<(), CoffeeError> {
},
CoffeeCommand::Nurse {} => match coffee.nurse().await {
Ok(val) => {
match val.status {
NurseStatus::Sane => {
term::success!("coffee configuration is not corrupt!")
}
NurseStatus::RepositoryLocallyAbsent => {
term::success!("A repository was locally absent");
}
NurseStatus::RepositoryLocallyCorrupt => {
term::success!("A repository was locally corrupt");
}
NurseStatus::RepositoryMissingInConfiguration => {
term::success!("A repository was missing in the configuration");
}
// For every status in CoffeeNurse, print it
for status in val.status {
term::success!("{}", status.to_string());
}
Ok(())
}
Expand Down
72 changes: 70 additions & 2 deletions coffee_core/src/coffee.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Coffee mod implementation
use coffee_storage::nosql_db::NoSQlStorage;
use std::collections::HashMap;
use std::fmt::Debug;
use std::vec::Vec;
Expand All @@ -23,6 +23,7 @@ use coffee_lib::types::response::*;
use coffee_lib::url::URL;
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;
use coffee_storage::storage::StorageManager;

use super::config;
Expand Down Expand Up @@ -78,6 +79,11 @@ pub struct CoffeeManager {
}

impl CoffeeManager {
/// return the repos of the plugin manager
pub fn repos(&self) -> &HashMap<String, Box<dyn Repository + Send + Sync>> {
&self.repos
}

pub async fn new(conf: &dyn CoffeeArgs) -> Result<Self, CoffeeError> {
let conf = CoffeeConf::new(conf).await?;
let mut coffee = CoffeeManager {
Expand Down Expand Up @@ -443,7 +449,69 @@ impl PluginManager for CoffeeManager {
}

async fn nurse(&mut self) -> Result<CoffeeNurse, CoffeeError> {
self.recovery_strategies.scan().await
let status = self.recovery_strategies.scan(self).await?;
let mut nurse_actions: Vec<NurseStatus> = vec![];
for defect in status.defects.iter() {
log::debug!("defect: {:?}", defect);
match defect {
Defect::RepositoryLocallyAbsent(repos) => {
let mut actions = self.patch_repository_locally_absent(repos.to_vec()).await?;
nurse_actions.append(&mut actions);
}
}
}

// If there was no actions taken by nurse, we return a sane status.
if nurse_actions.is_empty() {
nurse_actions.push(NurseStatus::Sane);
}
Ok(CoffeeNurse {
status: nurse_actions,
})
}

async fn patch_repository_locally_absent(
&mut self,
repos: Vec<String>,
) -> Result<Vec<NurseStatus>, CoffeeError> {
// initialize the nurse actions
let mut nurse_actions: Vec<NurseStatus> = vec![];
// for every repository that is absent locally
// we try to recover it.
// There are 2 cases:
// 1. the repository can be recovered from the remote
// 2. the repository can't be recovered from the remote. In this case
// we remove the repository from the coffee configuration.
for repo_name in repos.iter() {
// Get the repository from the name
let repo = self
.repos
.get_mut(repo_name)
.ok_or_else(|| error!("repository with name: {repo_name} not found"))?;

match repo.recover().await {
Ok(_) => {
log::info!("repository {} recovered", repo_name.clone());
nurse_actions.push(NurseStatus::RepositoryLocallyRestored(vec![
repo_name.clone()
]));
}
Err(err) => {
log::debug!(
"error while recovering repository {}: {err}",
repo_name.clone()
);
log::info!("removing repository {}", repo_name.clone());
self.repos.remove(repo_name);
log::debug!("remote removed: {}", repo_name);
self.flush().await?;
nurse_actions.push(NurseStatus::RepositoryLocallyRemoved(vec![
repo_name.clone()
]));
}
}
}
Ok(nurse_actions)
}
}

Expand Down
25 changes: 16 additions & 9 deletions coffee_core/src/nurse/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,44 @@ use std::sync::Arc;
use async_trait::async_trait;

use coffee_lib::errors::CoffeeError;
use coffee_lib::types::response::{CoffeeNurse, NurseStatus};
use coffee_lib::types::response::{ChainOfResponsibilityStatus, Defect};

use super::strategy::GitRepositoryLocallyAbsentStrategy;
use super::strategy::RecoveryStrategy;
use crate::coffee::CoffeeManager;

#[async_trait]
pub trait Handler: Send + Sync {
async fn can_be_applied(
self: Arc<Self>,
) -> Result<Option<Arc<dyn RecoveryStrategy>>, CoffeeError>;
coffee: &CoffeeManager,
) -> Result<Option<Defect>, CoffeeError>;
}

pub struct RecoveryChainOfResponsibility {
pub handlers: Vec<Arc<dyn Handler>>,
}

impl RecoveryChainOfResponsibility {
/// Create a new instance of the chain of responsibility
pub async fn new() -> Result<Self, CoffeeError> {
Ok(Self {
handlers: vec![Arc::new(GitRepositoryLocallyAbsentStrategy)],
})
}

pub async fn scan(&self) -> Result<CoffeeNurse, CoffeeError> {
/// Scan the chain of responsibility to see what can be applied
/// and return the status of the chain of responsibility
/// with the list of defects
pub async fn scan(
&self,
coffee: &CoffeeManager,
) -> Result<ChainOfResponsibilityStatus, CoffeeError> {
let mut defects: Vec<Defect> = vec![];
for handler in self.handlers.iter() {
if let Some(strategy) = handler.clone().can_be_applied().await? {
return strategy.patch().await;
if let Some(defect) = handler.clone().can_be_applied(coffee).await? {
defects.push(defect);
}
}
Ok(CoffeeNurse {
status: NurseStatus::Sane,
})
Ok(ChainOfResponsibilityStatus { defects })
}
}
54 changes: 25 additions & 29 deletions coffee_core/src/nurse/strategy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,17 @@
//! be able to choose the algorithm at runtime.
//!
//! Author: Vincenzo Palazzo <[email protected]>
use std::path::Path;
use std::sync::Arc;

use async_trait::async_trait;

use coffee_lib::errors::CoffeeError;
use coffee_lib::types::response::{CoffeeNurse, NurseStatus};
use coffee_lib::types::response::Defect;

use crate::coffee::CoffeeManager;
use crate::nurse::chain::Handler;

#[async_trait]
pub trait RecoveryStrategy: Send + Sync {
async fn patch(&self) -> Result<CoffeeNurse, CoffeeError>;
}

/// Strategy for handling the situation when a Git repository exists in coffee configuration
/// but is absent from the local storage.
///
Expand All @@ -44,37 +41,36 @@ pub trait RecoveryStrategy: Send + Sync {
/// a change in the storage location.
pub struct GitRepositoryLocallyAbsentStrategy;

#[async_trait]
impl RecoveryStrategy for GitRepositoryLocallyAbsentStrategy {
/// Attempts to address the absence of a Git repository from local storage.
///
/// This method is responsible for managing the scenario where a Git repository is listed
/// in the coffee configuration but is not present in the `.coffee/repositories` folder.
///
/// It takes the following actions:
///
/// 1. Attempts to clone the repository using the Git HEAD reference stored in the configuration.
/// This is done in an effort to retrieve the missing repository from its source.
///
/// 2. If the cloning process fails, it will remove the repository entry from the coffee configuration.
async fn patch(&self) -> Result<CoffeeNurse, CoffeeError> {
Ok(CoffeeNurse {
status: NurseStatus::RepositoryLocallyAbsent,
})
}
}

#[async_trait]
impl Handler for GitRepositoryLocallyAbsentStrategy {
/// Determines if [`GitRepositoryLocallyAbsentStrategy`] can be applied.
/// Determines if a repository is missing from local storage.
///
/// This function iterates over the Git repositories listed in the coffee configuration and
/// checks if each one exists in the `.coffee/repositories` folder. If any repository is found
/// to be missing from local storage, it indicates that the strategy to handle
/// this situation should be applied.
async fn can_be_applied(
self: Arc<Self>,
) -> Result<Option<std::sync::Arc<dyn RecoveryStrategy>>, CoffeeError> {
Ok(Some(self))
coffee: &CoffeeManager,
) -> Result<Option<Defect>, CoffeeError> {
let mut repos: Vec<String> = Vec::new();
let coffee_repos = coffee.repos();
for repo in coffee_repos.values() {
log::debug!("Checking if repository {} exists locally", repo.name());
let repo_path = repo.url().path_string;
let repo_path = Path::new(&repo_path);
if !repo_path.exists() {
log::debug!("Repository {} is missing locally", repo.name());
repos.push(repo.name().to_string());
}
}

if repos.is_empty() {
log::debug!("No repositories missing locally");
return Ok(None);
} else {
log::debug!("Found {} repositories missing locally", repos.len());
return Ok(Some(Defect::RepositoryLocallyAbsent(repos)));
}
}
}
40 changes: 40 additions & 0 deletions coffee_github/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,46 @@ impl Repository for Github {
})
}

async fn recover(&mut self) -> Result<(), CoffeeError> {
let commit = self.git_head.clone();

debug!(
"recovering repository: {} {} > {}",
self.name, &self.url.url_string, &self.url.path_string,
);
// recursively clone the repository
let res = git2::Repository::clone(&self.url.url_string, &self.url.path_string);
match res {
Ok(repo) => {
// get the commit id
let oid = git2::Oid::from_str(&commit.unwrap())
.map_err(|err| error!("{}", err.message()))?;
// Retrieve the commit associated with the OID
let target_commit = match repo.find_commit(oid) {
Ok(commit) => commit,
Err(err) => return Err(error!("{}", err.message())),
};

// Update HEAD to point to the target commit
repo.set_head_detached(target_commit.id())
.map_err(|err| error!("{}", err.message()))?;

// retrieve the submodules
let submodules = repo.submodules().unwrap_or_default();
for (_, sub) in submodules.iter().enumerate() {
let path =
format!("{}/{}", &self.url.path_string, sub.path().to_str().unwrap());
if let Err(err) = git2::Repository::clone(sub.url().unwrap(), &path) {
return Err(error!("{}", err.message()));
}
}

Ok(())
}
Err(err) => Err(error!("{}", err.message())),
}
}

/// list of the plugin installed inside the repository.
async fn list(&self) -> Result<Vec<Plugin>, CoffeeError> {
Ok(self.plugins.clone())
Expand Down
7 changes: 7 additions & 0 deletions coffee_lib/src/plugin_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,11 @@ pub trait PluginManager {

/// clean up storage information about the remote repositories of the plugin manager.
async fn nurse(&mut self) -> Result<CoffeeNurse, CoffeeError>;

/// patch coffee configuration in the case that a repository is present in the coffee
/// configuration but is absent from the local storage.
async fn patch_repository_locally_absent(
&mut self,
repos: Vec<String>,
) -> Result<Vec<NurseStatus>, CoffeeError>;
}
3 changes: 3 additions & 0 deletions coffee_lib/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ pub trait Repository: Any {
/// upgrade the repository
async fn upgrade(&mut self, plugins: &Vec<Plugin>) -> Result<CoffeeUpgrade, CoffeeError>;

/// recover the repository from the commit id.
async fn recover(&mut self) -> Result<(), CoffeeError>;

/// return the name of the repository.
fn name(&self) -> String;

Expand Down
Loading

0 comments on commit bf617d8

Please sign in to comment.