use anyhow::{Context, Result};
use std::io::{BufRead, BufReader};
use std::process::{Command, ExitStatus, Stdio};
use std::sync::mpsc;
use std::thread;
use super::parser::{parse_dry_run_output, PackageChange};
#[derive(Debug, Clone)]
pub enum AptOperation {
Update,
Upgrade { full: bool },
Install(Vec<String>),
Remove(Vec<String>),
Purge(Vec<String>),
}
impl AptOperation {
fn apt_args(&self) -> Vec<String> {
match self {
AptOperation::Update => vec!["update".into()],
AptOperation::Upgrade { full } => {
if *full {
vec!["full-upgrade".into(), "-y".into()]
} else {
vec!["upgrade".into(), "-y".into()]
}
}
AptOperation::Install(pkgs) => {
let mut args = vec!["install".into(), "-y".into()];
args.extend(pkgs.iter().cloned());
args
}
AptOperation::Remove(pkgs) => {
let mut args = vec!["remove".into(), "-y".into()];
args.extend(pkgs.iter().cloned());
args
}
AptOperation::Purge(pkgs) => {
let mut args = vec!["purge".into(), "-y".into()];
args.extend(pkgs.iter().cloned());
args
}
}
}
fn dry_run_args(&self) -> Option<Vec<String>> {
match self {
AptOperation::Update => None,
AptOperation::Upgrade { full } => {
let sub = if *full { "full-upgrade" } else { "upgrade" };
Some(vec![sub.into(), "--simulate".into()])
}
AptOperation::Install(pkgs) => {
let mut args = vec!["install".into(), "--simulate".into()];
args.extend(pkgs.iter().cloned());
Some(args)
}
AptOperation::Remove(pkgs) => {
let mut args = vec!["remove".into(), "--simulate".into()];
args.extend(pkgs.iter().cloned());
Some(args)
}
AptOperation::Purge(pkgs) => {
let mut args = vec!["purge".into(), "--simulate".into()];
args.extend(pkgs.iter().cloned());
Some(args)
}
}
}
}
pub fn dry_run(op: &AptOperation) -> Result<Vec<PackageChange>> {
let args = match op.dry_run_args() {
Some(a) => a,
None => return Ok(vec![]),
};
let output = Command::new("apt-get")
.args(&args)
.output()
.context("failed to run apt-get --simulate")?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if !output.status.success() {
let msg = if stderr.is_empty() {
stdout.to_string()
} else {
stderr.to_string()
};
anyhow::bail!("{}", msg.trim());
}
Ok(parse_dry_run_output(&stdout))
}
pub fn execute(op: &AptOperation, tx: Option<mpsc::Sender<String>>) -> Result<ExitStatus> {
let args = op.apt_args();
match tx {
None => {
let status = Command::new("apt-get")
.args(&args)
.status()
.context("failed to run apt-get")?;
Ok(status)
}
Some(sender) => {
let mut child = Command::new("apt-get")
.args(&args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.context("failed to spawn apt-get")?;
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();
let tx1 = sender.clone();
let tx2 = sender;
let t1 = thread::spawn(move || {
let reader = BufReader::new(stdout);
for line in reader.lines().map_while(Result::ok) {
let _ = tx1.send(line);
}
});
let t2 = thread::spawn(move || {
let reader = BufReader::new(stderr);
for line in reader.lines().map_while(Result::ok) {
let _ = tx2.send(line);
}
});
let status = child.wait().context("apt-get process error")?;
let _ = t1.join();
let _ = t2.join();
Ok(status)
}
}
}