use std::{
path::{Path, PathBuf},
process::Command,
};
use clingwrap::runner::CommandRunner;
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
use crate::{
action::{ActionError, Context},
action_impl::ActionImpl,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Dput {
artifacts: PathBuf,
dput_target: Option<String>,
}
impl Dput {
pub fn new<P: AsRef<Path>>(artifacts: P, dput_target: Option<String>) -> Self {
Self {
artifacts: artifacts.as_ref().to_path_buf(),
dput_target,
}
}
}
impl ActionImpl for Dput {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
if let Some(target) = &self.dput_target {
let changes = changes_file(context.artifacts_dir())?;
dput(target, &changes)?;
}
Ok(())
}
}
fn changes_file(dir: &Path) -> Result<PathBuf, DputError> {
let mut result = None;
for entry in WalkDir::new(dir).into_iter() {
let path = entry.map_err(DputError::WalkDir)?.path().to_path_buf();
if path.display().to_string().ends_with(".changes") {
if result.is_some() {
return Err(DputError::ManyChanges);
}
result = Some(path);
}
}
if let Some(path) = result {
Ok(path)
} else {
Err(DputError::NoChanges)
}
}
fn dput(dest: &str, changes: &Path) -> Result<(), DputError> {
let mut cmd = Command::new("dput");
cmd.arg(dest).arg(changes);
let mut runner = CommandRunner::new(cmd);
runner.capture_stdout();
runner.capture_stderr();
let output = runner
.execute()
.map_err(|err| DputError::Execute("dput", err))?;
if output.status.code() != Some(0) {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(DputError::Dput(
dest.into(),
changes.to_path_buf(),
stderr.into(),
));
}
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum DputError {
#[error("dput failed to upload {1} to {0}:\n{2}")]
Dput(String, PathBuf, String),
#[error("failed to run program {0}")]
Execute(&'static str, #[source] clingwrap::runner::CommandError),
#[error("failed to list contents of upload directory")]
WalkDir(#[source] walkdir::Error),
#[error("no *.changes file built for deb project")]
NoChanges,
#[error("more than one *.changes file built for deb project")]
ManyChanges,
}
impl From<DputError> for ActionError {
fn from(value: DputError) -> Self {
Self::Dput(value)
}
}