use crate::error::BobError;
use cli_stream::InstallEvent;
use std::io::{BufRead, BufReader, Write};
use std::os::unix::fs::PermissionsExt;
use std::process::{Command, Stdio};
use std::thread;
use tempfile::NamedTempFile;
const INSTALL_SCRIPT: &str = include_str!("../scripts/install-bob.sh");
pub fn install_bob<F>(mut callback: F) -> Result<(), BobError>
where
F: FnMut(InstallEvent) + Send + Sync + 'static + Clone,
{
let mut tmp = NamedTempFile::new().map_err(|source| BobError::Io {
context: "create install tempfile",
source,
})?;
tmp.write_all(INSTALL_SCRIPT.as_bytes())
.map_err(|source| BobError::Io {
context: "write install tempfile",
source,
})?;
let metadata = tmp.as_file().metadata().map_err(|source| BobError::Io {
context: "stat install tempfile",
source,
})?;
let mut perms = metadata.permissions();
perms.set_mode(0o755);
tmp.as_file()
.set_permissions(perms)
.map_err(|source| BobError::Io {
context: "chmod install tempfile",
source,
})?;
let mut child = Command::new("bash")
.arg("-l")
.arg(tmp.path())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|source| BobError::Io {
context: "spawn install script",
source,
})?;
let stdout = child
.stdout
.take()
.ok_or(BobError::PipeNotCaptured { stream: "stdout" })?;
let stderr = child
.stderr
.take()
.ok_or(BobError::PipeNotCaptured { stream: "stderr" })?;
let stdout_cb = callback.clone();
let stdout_handle = thread::spawn(move || {
let mut cb = stdout_cb;
let reader = BufReader::new(stdout);
for line in reader.lines().map_while(Result::ok) {
let event = if let Some(text) = line.strip_prefix("[BOB-INSTALL] ") {
InstallEvent::Step { text: text.to_owned() }
} else {
InstallEvent::Stdout { text: line }
};
cb(event);
}
});
let stderr_cb = callback.clone();
let stderr_handle = thread::spawn(move || {
let mut cb = stderr_cb;
let reader = BufReader::new(stderr);
for line in reader.lines().map_while(Result::ok) {
cb(InstallEvent::Stderr { text: line });
}
});
let status = child.wait().map_err(|source| BobError::Io {
context: "wait on install script",
source,
})?;
let _ = stdout_handle.join();
let _ = stderr_handle.join();
callback(InstallEvent::Done {
exit_code: status.code(),
ok: status.success(),
});
drop(tmp);
Ok(())
}