use super::{commands, responses};
use super::{Command, DialogParams, DialogType, ErrorKind, Response, Result};
use std::process::Stdio;
use tokio::process;
use tokio::sync::mpsc;
use tokio::task;
pub async fn prompt(params: DialogParams) -> Result<Option<Vec<u8>>> {
let mut dialog = Dialog::start().await?;
dialog.ack().await?;
dialog.show(params).await?;
let outcome = dialog.outcome().await;
dialog.exit().await?;
outcome
}
pub struct Dialog {
program: process::Child,
commands: mpsc::Sender<Command>,
responses: mpsc::Receiver<Response>,
}
impl Dialog {
async fn start() -> Result<Self> {
let mut program = process::Command::new("/usr/bin/pinentry")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.kill_on_drop(true)
.spawn()?;
let stdin = program.stdin.take().unwrap();
let (cmdtx, cmdrx) = mpsc::channel::<Command>(1);
task::spawn(commands::transmit(stdin, cmdrx));
let stdout = program.stdout.take().unwrap();
let (restx, resrx) = mpsc::channel::<Response>(1);
task::spawn(responses::listen(stdout, restx));
Ok(Self {
program,
commands: cmdtx,
responses: resrx,
})
}
async fn show(&mut self, params: DialogParams) -> Result<()> {
self.send(Command::Title(params.title)).await?;
self.ack().await?;
self.send(Command::Description(params.description)).await?;
self.ack().await?;
self.send(Command::Prompt(params.prompt)).await?;
self.ack().await?;
self.send(Command::OKButton(params.label_yes)).await?;
self.ack().await?;
self.send(Command::CancelButton(params.label_cancel))
.await?;
self.ack().await?;
if let Some(label) = params.label_no {
self.send(Command::NotOKButton(label)).await?;
self.ack().await?;
}
match params.dialog_type {
DialogType::Password => self.send(Command::Password),
DialogType::Confirmation => self.send(Command::Confirm),
DialogType::Notification => self.send(Command::Notify),
}
.await?;
Ok(())
}
async fn send(&mut self, cmd: Command) -> Result<()> {
Ok(self.commands.send(cmd).await?)
}
async fn get(&mut self) -> Result<Response> {
Ok(self
.responses
.recv()
.await
.ok_or(ErrorKind::EmptyResponse)?)
}
async fn ack(&mut self) -> Result<()> {
match self.get().await? {
Response::Success(..) => Ok(()),
Response::Failure(..) => Err(ErrorKind::CommandFail),
_ => Err(ErrorKind::UnexpectedResponse(format!(
"Expected acknowledgement"
))),
}
}
async fn outcome(&mut self) -> Result<Option<Vec<u8>>> {
let out;
loop {
out = match self.get().await? {
Response::Success(..) => Ok(None),
Response::Failure(83886179, ..) => Err(ErrorKind::UserCancel),
Response::Failure(83886194, ..) => Err(ErrorKind::UserNo),
Response::DataOK(data, ..) => Ok(Some(data)),
Response::DataFail(_, 83886179, ..) => Err(ErrorKind::UserCancel),
Response::DataFail(_, 83886194, ..) => Err(ErrorKind::UserNo),
_ => continue,
};
break out;
}
}
async fn exit(mut self) -> Result<()> {
self.send(Command::Exit).await?;
self.ack().await?;
self.program.start_kill()?;
drop(self.commands);
drop(self.responses);
Ok(())
}
}