use std::{
path::{Path, PathBuf},
process::Command,
};
use clingwrap::runner::CommandRunner;
use serde::{Deserialize, Serialize};
use crate::{
action::{ActionError, Context},
action_impl::ActionImpl,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rsync {
artifacts: PathBuf,
rsync_target: Option<String>,
}
impl Rsync {
pub fn new<P: AsRef<Path>>(artifacts: P, rsync_target: Option<String>) -> Self {
Self {
artifacts: artifacts.as_ref().to_path_buf(),
rsync_target,
}
}
}
impl ActionImpl for Rsync {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
if let Some(target) = &self.rsync_target {
rsync_server(&context.artifacts_dir().join(&self.artifacts), target)?;
}
Ok(())
}
}
fn rsync_server(src: &Path, target: &str) -> Result<(), RsyncError> {
let src = src.join(".");
let target = format!("{target}/.");
let mut cmd = Command::new("rsync");
cmd.arg("-a").arg("--del").arg(&src).arg(&target);
let mut runner = CommandRunner::new(cmd);
runner.capture_stdout();
runner.capture_stderr();
let output = runner
.execute()
.map_err(|err| RsyncError::Execute("rsync", err))?;
if output.status.code() != Some(0) {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(RsyncError::Rsync(
src.clone(),
target.clone(),
stderr.into(),
));
}
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum RsyncError {
#[error("rsync failed to synchronize {0} to {1}:\n{2}")]
Rsync(PathBuf, String, String),
#[error("failed to run program {0}")]
Execute(&'static str, #[source] clingwrap::runner::CommandError),
}
impl From<RsyncError> for ActionError {
fn from(value: RsyncError) -> Self {
Self::Rsync(value)
}
}