// use actix::prelude::*; use crate::{ alerts::messages::NotifyUser, server::{actor::messages::RepoUpdate, 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>>); impl Deref for ActorLog { type Target = std::sync::Arc>>; 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, // INFO: if [None] then no webhook is configured webhook_auth: Option, // INFO: if [None] then no webhook is configured last_main_commit: Option, last_next_commit: Option, last_dev_commit: Option, repository_factory: Box, open_repository: Option>, net: Network, forge: Box, log: Option, notify_user_recipient: Option>, server_addr: Option>, } impl RepoActor { #[allow(clippy::too_many_arguments)] pub fn new( repo_details: git::RepoDetails, forge: Box, listen_url: ListenUrl, generation: git::Generation, net: Network, repository_factory: Box, sleep_duration: std::time::Duration, notify_user_recipient: Option>, server_addr: Option>, ) -> 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 }); } } #[allow(unused_variables)] fn update_tui_log(&self, log: git::graph::Log) { #[cfg(feature = "tui")] { self.update_tui(RepoUpdate::Log { log }); } } #[allow(unused_variables)] fn alert_tui(&self, alert: impl Into) { #[cfg(feature = "tui")] { self.update_tui(RepoUpdate::Alert { alert: alert.into(), }); } } #[allow(unused_variables)] fn update_tui(&self, repo_update: RepoUpdate) { #[cfg(feature = "tui")] { let Some(server_addr) = &self.server_addr else { return; }; let update = crate::server::actor::messages::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; #[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(addr: &Addr, delay: Duration, msg: M, log: Option<&ActorLog>) where M: actix::Message + Send + 'static + std::fmt::Debug, RepoActor: actix::Handler, ::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(addr: &Addr, msg: M, log: Option<&ActorLog>) where M: actix::Message + Send + 'static + std::fmt::Debug, RepoActor: actix::Handler, ::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) { 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>, 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); } }