forked from kemitix/git-next
feat(server): restart server when config file is updated
Closes kemitix/git-next#59
This commit is contained in:
parent
7e79f4877a
commit
6d147efe32
7 changed files with 148 additions and 21 deletions
|
@ -58,6 +58,9 @@ warp = "0.3"
|
|||
derive_more = { version = "1.0.0-beta.6", features = ["from", "display"] }
|
||||
terrors = "0.3"
|
||||
|
||||
# file watcher
|
||||
inotify = "0.10"
|
||||
|
||||
# Actors
|
||||
actix = "0.13"
|
||||
actix-rt = "2.9"
|
||||
|
|
84
src/server/actors/file_watcher.rs
Normal file
84
src/server/actors/file_watcher.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use actix::prelude::*;
|
||||
|
||||
use actix::Recipient;
|
||||
use inotify::{EventMask, Inotify, WatchMask};
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
use tracing::{debug, info};
|
||||
|
||||
const CHECK_INTERVAL: Duration = Duration::from_secs(1);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WatchFile;
|
||||
impl Message for WatchFile {
|
||||
type Result = ();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileUpdated;
|
||||
impl Message for FileUpdated {
|
||||
type Result = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, derive_more::From, derive_more::Display)]
|
||||
pub enum Error {
|
||||
Io(std::io::Error),
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub struct FileWatcher {
|
||||
path: PathBuf,
|
||||
inotify: Inotify,
|
||||
recipient: Recipient<FileUpdated>,
|
||||
run_interval: Option<SpawnHandle>,
|
||||
}
|
||||
impl FileWatcher {
|
||||
pub fn new(path: PathBuf, recipient: Recipient<FileUpdated>) -> Result<Self, Error> {
|
||||
let inotify = Inotify::init()?;
|
||||
inotify.watches().add(
|
||||
path.clone(),
|
||||
WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE,
|
||||
)?;
|
||||
Ok(Self {
|
||||
path,
|
||||
inotify,
|
||||
recipient,
|
||||
run_interval: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Actor for FileWatcher {
|
||||
type Context = Context<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
info!("Starting file watcher actor");
|
||||
self.run_interval
|
||||
.replace(ctx.run_interval(CHECK_INTERVAL, |_act, ctx| {
|
||||
ctx.notify(WatchFile);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<WatchFile> for FileWatcher {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, _msg: WatchFile, _ctx: &mut Self::Context) -> Self::Result {
|
||||
debug!("Watching {} for activity...", self.path.display());
|
||||
let mut buffer = [0u8; 4096];
|
||||
if let Ok(mut events) = self.inotify.read_events(&mut buffer) {
|
||||
if events.any(|event| event.mask.contains(EventMask::MODIFY)) {
|
||||
info!("File modified");
|
||||
self.recipient.do_send(FileUpdated);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// impl Handler<Stop> for FileWatcher {
|
||||
// type Result = anyhow::Result<()>;
|
||||
//
|
||||
// fn handle(&mut self, _msg: Stop, ctx: &mut Self::Context) -> Self::Result {
|
||||
// warn!("Stopping file watcher actor");
|
||||
// self.run_interval.take();
|
||||
// ctx.stop();
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
|
@ -1,3 +1,4 @@
|
|||
pub mod file_watcher;
|
||||
pub mod repo;
|
||||
pub mod server;
|
||||
pub mod webhook;
|
||||
|
|
|
@ -7,8 +7,9 @@ use tracing::{error, info};
|
|||
|
||||
use crate::server::{
|
||||
actors::{
|
||||
file_watcher::FileUpdated,
|
||||
repo::{CloneRepo, RepoActor},
|
||||
webhook::{AddWebhookRecipient, WebhookActor, WebhookRouter},
|
||||
webhook::{AddWebhookRecipient, ShutdownWebhook, WebhookActor, WebhookRouter},
|
||||
},
|
||||
config::{
|
||||
ForgeConfig, ForgeName, GitDir, RepoAlias, RepoDetails, ServerConfig, ServerRepoConfig,
|
||||
|
@ -35,17 +36,36 @@ type Result<T> = core::result::Result<T, Error>;
|
|||
|
||||
pub struct Server {
|
||||
generation: ServerGeneration,
|
||||
webhook: Option<Addr<WebhookActor>>,
|
||||
fs: FileSystem,
|
||||
net: Network,
|
||||
}
|
||||
impl Actor for Server {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
impl Handler<FileUpdated> for Server {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, _msg: FileUpdated, ctx: &mut Self::Context) -> Self::Result {
|
||||
let server_config = match ServerConfig::load(&self.fs) {
|
||||
Ok(server_config) => server_config,
|
||||
Err(err) => {
|
||||
error!("Failed to load config file. Error: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
ctx.notify(server_config);
|
||||
}
|
||||
}
|
||||
impl Handler<ServerConfig> for Server {
|
||||
type Result = ();
|
||||
|
||||
#[allow(clippy::cognitive_complexity)] // TODO: (#75) reduce complexity
|
||||
fn handle(&mut self, msg: ServerConfig, _ctx: &mut Self::Context) -> Self::Result {
|
||||
if let Some(webhook) = self.webhook.take() {
|
||||
webhook.do_send(ShutdownWebhook);
|
||||
}
|
||||
self.generation.inc();
|
||||
let server_config = msg;
|
||||
// Server Storage
|
||||
let dir = server_config.storage().path();
|
||||
|
@ -83,13 +103,16 @@ impl Handler<ServerConfig> for Server {
|
|||
.for_each(|msg| webhook_router.do_send(msg));
|
||||
}
|
||||
|
||||
WebhookActor::new(webhook_router.recipient()).start();
|
||||
let webhook = WebhookActor::new(webhook_router.recipient()).start();
|
||||
self.webhook.replace(webhook);
|
||||
}
|
||||
}
|
||||
impl Server {
|
||||
pub const fn new(generation: ServerGeneration, fs: FileSystem, net: Network) -> Self {
|
||||
pub fn new(fs: FileSystem, net: Network) -> Self {
|
||||
let generation = ServerGeneration::new();
|
||||
Self {
|
||||
generation,
|
||||
webhook: None,
|
||||
fs,
|
||||
net,
|
||||
}
|
||||
|
|
|
@ -37,3 +37,17 @@ impl Actor for WebhookActor {
|
|||
self.spawn_handle.replace(spawn_handle);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShutdownWebhook;
|
||||
impl Message for ShutdownWebhook {
|
||||
type Result = ();
|
||||
}
|
||||
impl Handler<ShutdownWebhook> for WebhookActor {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, _msg: ShutdownWebhook, ctx: &mut Self::Context) -> Self::Result {
|
||||
self.spawn_handle.take();
|
||||
ctx.stop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,10 @@ use tracing::{error, info, level_filters::LevelFilter};
|
|||
|
||||
use crate::{
|
||||
fs::FileSystem,
|
||||
server::{actors::server::Server, types::ServerGeneration},
|
||||
server::actors::{
|
||||
file_watcher::{self, FileUpdated},
|
||||
server::Server,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn init(fs: FileSystem) {
|
||||
|
@ -40,22 +43,21 @@ pub fn init(fs: FileSystem) {
|
|||
|
||||
pub async fn start(fs: FileSystem, net: Network) {
|
||||
init_logging();
|
||||
let generation = ServerGeneration::new();
|
||||
{
|
||||
let span = tracing::info_span!("Server", %generation);
|
||||
let _guard = span.enter();
|
||||
info!("Starting Server...");
|
||||
let server_config = match config::ServerConfig::load(&fs) {
|
||||
Ok(server_config) => server_config,
|
||||
Err(err) => {
|
||||
error!("Failed to load config file. Error: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let server = Server::new(generation, fs.clone(), net.clone()).start();
|
||||
server.do_send(server_config);
|
||||
}
|
||||
info!("Starting Server...");
|
||||
let server = Server::new(fs.clone(), net.clone()).start();
|
||||
server.do_send(FileUpdated);
|
||||
|
||||
info!("Starting File Watcher...");
|
||||
let fw = match file_watcher::FileWatcher::new("git-next-server.toml".into(), server.recipient())
|
||||
{
|
||||
Ok(fw) => fw,
|
||||
Err(err) => {
|
||||
error!(?err, "Failed to start file watcher");
|
||||
return;
|
||||
}
|
||||
};
|
||||
fw.start();
|
||||
|
||||
info!("Server running - Press Ctrl-C to stop...");
|
||||
let _ = actix_rt::signal::ctrl_c().await;
|
||||
|
|
|
@ -42,8 +42,8 @@ impl ServerGeneration {
|
|||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub const fn next(&self) -> Self {
|
||||
Self(self.0 + 1)
|
||||
pub fn inc(&mut self) {
|
||||
self.0 += 1
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for ServerGeneration {
|
||||
|
|
Loading…
Reference in a new issue