git-next/crates/cli/src/repo/mod.rs
Paul Campbell 5d9915bdbd
All checks were successful
Rust / build (push) Successful in 14m16s
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
Release Please / Release-plz (push) Successful in 1m3s
feat(tui): (experimental) show repo state, messages and git log
2024-08-25 15:59:42 +01:00

226 lines
6.7 KiB
Rust

//
use actix::prelude::*;
use crate::{
alerts::messages::NotifyUser,
server::{
actor::messages::{RepoUpdate, ServerUpdate},
ServerActor,
},
};
use derive_more::Deref;
use kxio::network::Network;
use std::time::Duration;
use tracing::{info, instrument, warn, Instrument};
use git_next_core::{
git::{
self,
repository::{factory::RepositoryFactory, open::OpenRepositoryLike},
UserNotification,
},
server::ListenUrl,
WebhookAuth, WebhookId,
};
mod branch;
pub mod handlers;
mod load;
pub mod messages;
mod notifications;
#[cfg(test)]
pub mod tests;
#[derive(Clone, Debug, Default)]
pub struct ActorLog(std::sync::Arc<std::sync::RwLock<Vec<String>>>);
impl Deref for ActorLog {
type Target = std::sync::Arc<std::sync::RwLock<Vec<String>>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// An actor that represents a Git Repository.
///
/// When this actor is started it is sent the `CloneRepo` message.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, derive_more::Display, derive_with::With)]
#[display("{}:{}:{}", generation, repo_details.forge.forge_alias(), repo_details.repo_alias)]
pub struct RepoActor {
sleep_duration: std::time::Duration,
generation: git::Generation,
message_token: messages::MessageToken,
repo_details: git::RepoDetails,
listen_url: ListenUrl,
webhook_id: Option<WebhookId>, // INFO: if [None] then no webhook is configured
webhook_auth: Option<WebhookAuth>, // INFO: if [None] then no webhook is configured
last_main_commit: Option<git::Commit>,
last_next_commit: Option<git::Commit>,
last_dev_commit: Option<git::Commit>,
repository_factory: Box<dyn RepositoryFactory>,
open_repository: Option<Box<dyn OpenRepositoryLike>>,
net: Network,
forge: Box<dyn git::ForgeLike>,
log: Option<ActorLog>,
notify_user_recipient: Option<Recipient<NotifyUser>>,
server_addr: Option<Addr<ServerActor>>,
}
impl RepoActor {
#[allow(clippy::too_many_arguments)]
pub fn new(
repo_details: git::RepoDetails,
forge: Box<dyn git::ForgeLike>,
listen_url: ListenUrl,
generation: git::Generation,
net: Network,
repository_factory: Box<dyn RepositoryFactory>,
sleep_duration: std::time::Duration,
notify_user_recipient: Option<Recipient<NotifyUser>>,
server_addr: Option<Addr<ServerActor>>,
) -> Self {
let message_token = messages::MessageToken::default();
Self {
generation,
message_token,
repo_details,
listen_url,
webhook_id: None,
webhook_auth: None,
last_main_commit: None,
last_next_commit: None,
last_dev_commit: None,
repository_factory,
open_repository: None,
forge,
net,
sleep_duration,
log: None,
notify_user_recipient,
server_addr,
}
}
fn update_tui_branches(&self) {
#[cfg(feature = "tui")]
{
use crate::server::actor::messages::RepoUpdate;
let Some(repo_config) = &self.repo_details.repo_config else {
return;
};
let branches = repo_config.branches().clone();
self.update_tui(RepoUpdate::Branches { branches });
}
}
#[instrument]
fn update_tui_log(&self, log: git::graph::Log) {
info!("ready to send log");
#[cfg(feature = "tui")]
{
info!("sending...");
self.update_tui(RepoUpdate::Log { log });
info!("sent");
}
}
fn alert_tui(&self, alert: impl Into<String>) {
#[cfg(feature = "tui")]
{
self.update_tui(RepoUpdate::Alert {
alert: alert.into(),
});
}
}
fn update_tui(&self, repo_update: RepoUpdate) {
#[cfg(feature = "tui")]
{
let Some(server_addr) = &self.server_addr else {
return;
};
let update = ServerUpdate::RepoUpdate {
forge_alias: self.repo_details.forge.forge_alias().clone(),
repo_alias: self.repo_details.repo_alias.clone(),
repo_update,
};
server_addr.do_send(update);
}
}
}
impl Actor for RepoActor {
type Context = Context<Self>;
#[tracing::instrument(name = "RepoActor::stopping", skip_all, fields(repo = %self.repo_details))]
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
tracing::debug!("stopping");
info!("Checking webhook");
match self.webhook_id.take() {
Some(webhook_id) => {
tracing::warn!("stopping - unregistering webhook");
info!(%webhook_id, "Unregistring webhook");
let forge = self.forge.duplicate();
async move {
if let Err(err) = forge.unregister_webhook(&webhook_id).await {
warn!("unregistering webhook: {err}");
}
}
.in_current_span()
.into_actor(self)
.wait(ctx);
Running::Continue
}
None => Running::Stop,
}
}
}
pub fn delay_send<M>(addr: &Addr<RepoActor>, delay: Duration, msg: M, log: Option<&ActorLog>)
where
M: actix::Message + Send + 'static + std::fmt::Debug,
RepoActor: actix::Handler<M>,
<M as actix::Message>::Result: Send,
{
let log_message = format!("send-after-delay: {msg:?}");
tracing::debug!(log_message);
logger(log, log_message);
std::thread::sleep(delay);
do_send(addr, msg, log);
}
pub fn do_send<M>(addr: &Addr<RepoActor>, msg: M, log: Option<&ActorLog>)
where
M: actix::Message + Send + 'static + std::fmt::Debug,
RepoActor: actix::Handler<M>,
<M as actix::Message>::Result: Send,
{
let log_message = format!("send: {msg:?}");
tracing::debug!(log_message);
logger(log, log_message);
if cfg!(not(test)) {
// #[cfg(not(test))]
addr.do_send(msg);
}
}
pub fn logger(log: Option<&ActorLog>, message: impl Into<String>) {
if let Some(log) = log {
let message: String = message.into();
tracing::debug!(message);
let _ = log.write().map(|mut l| l.push(message));
}
}
pub fn notify_user(
recipient: Option<&Recipient<NotifyUser>>,
user_notification: UserNotification,
log: Option<&ActorLog>,
) {
let msg = NotifyUser::from(user_notification);
let log_message = format!("send: {msg:?}");
tracing::debug!(log_message);
logger(log, log_message);
if let Some(recipient) = &recipient {
recipient.do_send(msg);
}
}