use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use crate::command;
use crate::error::{AgiError, Result};
use crate::response::AgiResponse;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ChannelState {
Ready,
InFlight,
Poisoned,
}
#[derive(Debug)]
pub struct AgiChannel {
reader: BufReader<OwnedReadHalf>,
writer: OwnedWriteHalf,
hung_up: bool,
state: ChannelState,
}
impl AgiChannel {
pub fn new(reader: BufReader<OwnedReadHalf>, writer: OwnedWriteHalf) -> Self {
Self {
reader,
writer,
hung_up: false,
state: ChannelState::Ready,
}
}
pub async fn send_command(&mut self, command: &str) -> Result<AgiResponse> {
if self.hung_up {
return Err(AgiError::ChannelHungUp);
}
match self.state {
ChannelState::InFlight => return Err(AgiError::CommandInFlight),
ChannelState::Poisoned => return Err(AgiError::ChannelPoisoned),
ChannelState::Ready => {}
}
self.state = ChannelState::InFlight;
if let Err(e) = self.writer.write_all(command.as_bytes()).await {
self.state = ChannelState::Poisoned;
return Err(AgiError::Io(e));
}
if let Err(e) = self.writer.flush().await {
self.state = ChannelState::Poisoned;
return Err(AgiError::Io(e));
}
let mut line = String::new();
let bytes_read = match self.reader.read_line(&mut line).await {
Ok(n) => n,
Err(e) => {
self.state = ChannelState::Poisoned;
return Err(AgiError::Io(e));
}
};
if bytes_read == 0 {
self.hung_up = true;
self.state = ChannelState::Poisoned;
return Err(AgiError::ChannelHungUp);
}
if let Some(stripped) = line.strip_prefix("520-") {
let first = stripped.trim().to_owned();
let mut usage = first;
loop {
let mut next = String::new();
let n = match self.reader.read_line(&mut next).await {
Ok(n) => n,
Err(e) => {
self.state = ChannelState::Poisoned;
return Err(AgiError::Io(e));
}
};
if n == 0 {
self.hung_up = true;
self.state = ChannelState::Poisoned;
return Err(AgiError::ChannelHungUp);
}
let trimmed = next.trim();
if trimmed == "520 End of proper usage." {
break;
}
if !usage.is_empty() {
usage.push('\n');
}
usage.push_str(trimmed);
}
self.state = ChannelState::Ready;
return Err(AgiError::CommandFailed {
code: 520,
message: usage,
});
}
let response = match AgiResponse::parse(&line) {
Ok(r) => r,
Err(e) => {
self.state = ChannelState::Poisoned;
return Err(e);
}
};
if response.code == 511 {
self.hung_up = true;
self.state = ChannelState::Poisoned;
return Err(AgiError::ChannelHungUp);
}
self.state = ChannelState::Ready;
Ok(response)
}
pub async fn answer(&mut self) -> Result<AgiResponse> {
let cmd = command::format_command(command::ANSWER, &[])?;
self.send_command(&cmd).await
}
pub async fn hangup(&mut self, channel: Option<&str>) -> Result<AgiResponse> {
let cmd = match channel {
Some(ch) => command::format_command(command::HANGUP, &[ch])?,
None => command::format_command(command::HANGUP, &[])?,
};
self.send_command(&cmd).await
}
pub async fn stream_file(
&mut self,
filename: &str,
escape_digits: &str,
) -> Result<AgiResponse> {
let cmd = command::format_command(command::STREAM_FILE, &[filename, escape_digits])?;
self.send_command(&cmd).await
}
pub async fn get_data(
&mut self,
filename: &str,
timeout_ms: u64,
max_digits: u32,
) -> Result<AgiResponse> {
let timeout = timeout_ms.to_string();
let digits = max_digits.to_string();
let cmd = command::format_command(command::GET_DATA, &[filename, &timeout, &digits])?;
self.send_command(&cmd).await
}
pub async fn say_digits(&mut self, digits: &str, escape_digits: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SAY_DIGITS, &[digits, escape_digits])?;
self.send_command(&cmd).await
}
pub async fn say_number(&mut self, number: i64, escape_digits: &str) -> Result<AgiResponse> {
let num = number.to_string();
let cmd = command::format_command(command::SAY_NUMBER, &[&num, escape_digits])?;
self.send_command(&cmd).await
}
pub async fn set_variable(&mut self, name: &str, value: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SET_VARIABLE, &[name, value])?;
self.send_command(&cmd).await
}
pub async fn get_variable(&mut self, name: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::GET_VARIABLE, &[name])?;
self.send_command(&cmd).await
}
pub async fn exec(&mut self, application: &str, args: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::EXEC, &[application, args])?;
self.send_command(&cmd).await
}
pub async fn wait_for_digit(&mut self, timeout_ms: i64) -> Result<AgiResponse> {
let timeout = timeout_ms.to_string();
let cmd = command::format_command(command::WAIT_FOR_DIGIT, &[&timeout])?;
self.send_command(&cmd).await
}
pub async fn channel_status(&mut self, channel: Option<&str>) -> Result<AgiResponse> {
let cmd = match channel {
Some(ch) => command::format_command(command::CHANNEL_STATUS, &[ch])?,
None => command::format_command(command::CHANNEL_STATUS, &[])?,
};
self.send_command(&cmd).await
}
pub async fn verbose(&mut self, message: &str, level: u8) -> Result<AgiResponse> {
let lvl = level.to_string();
let cmd = command::format_command(command::VERBOSE, &[message, &lvl])?;
self.send_command(&cmd).await
}
pub async fn set_callerid(&mut self, callerid: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SET_CALLERID, &[callerid])?;
self.send_command(&cmd).await
}
pub async fn database_get(&mut self, family: &str, key: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::DATABASE_GET, &[family, key])?;
self.send_command(&cmd).await
}
pub async fn database_put(
&mut self,
family: &str,
key: &str,
value: &str,
) -> Result<AgiResponse> {
let cmd = command::format_command(command::DATABASE_PUT, &[family, key, value])?;
self.send_command(&cmd).await
}
pub async fn database_del(&mut self, family: &str, key: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::DATABASE_DEL, &[family, key])?;
self.send_command(&cmd).await
}
pub async fn database_deltree(
&mut self,
family: &str,
key: Option<&str>,
) -> Result<AgiResponse> {
let cmd = match key {
Some(k) => command::format_command(command::DATABASE_DELTREE, &[family, k])?,
None => command::format_command(command::DATABASE_DELTREE, &[family])?,
};
self.send_command(&cmd).await
}
pub async fn control_stream_file(
&mut self,
filename: &str,
escape_digits: &str,
skipms: Option<i64>,
ff_char: Option<&str>,
rew_char: Option<&str>,
pause_char: Option<&str>,
) -> Result<AgiResponse> {
let skip = skipms.unwrap_or(3000).to_string();
let ff = ff_char.unwrap_or("");
let rew = rew_char.unwrap_or("");
let pause = pause_char.unwrap_or("");
let cmd = command::format_command(
command::CONTROL_STREAM_FILE,
&[filename, escape_digits, &skip, ff, rew, pause],
)?;
self.send_command(&cmd).await
}
pub async fn get_full_variable(
&mut self,
expression: &str,
channel: Option<&str>,
) -> Result<AgiResponse> {
let cmd = match channel {
Some(ch) => command::format_command(command::GET_FULL_VARIABLE, &[expression, ch])?,
None => command::format_command(command::GET_FULL_VARIABLE, &[expression])?,
};
self.send_command(&cmd).await
}
pub async fn get_option(
&mut self,
filename: &str,
escape_digits: &str,
timeout_ms: Option<i64>,
) -> Result<AgiResponse> {
let cmd = match timeout_ms {
Some(t) => {
let ts = t.to_string();
command::format_command(command::GET_OPTION, &[filename, escape_digits, &ts])?
}
None => command::format_command(command::GET_OPTION, &[filename, escape_digits])?,
};
self.send_command(&cmd).await
}
pub async fn gosub(
&mut self,
context: &str,
extension: &str,
priority: &str,
args: Option<&str>,
) -> Result<AgiResponse> {
let cmd = match args {
Some(a) => command::format_command(command::GOSUB, &[context, extension, priority, a])?,
None => command::format_command(command::GOSUB, &[context, extension, priority])?,
};
self.send_command(&cmd).await
}
pub async fn noop(&mut self) -> Result<AgiResponse> {
let cmd = command::format_command(command::NOOP, &[])?;
self.send_command(&cmd).await
}
pub async fn receive_char(&mut self, timeout_ms: i64) -> Result<AgiResponse> {
let timeout = timeout_ms.to_string();
let cmd = command::format_command(command::RECEIVE_CHAR, &[&timeout])?;
self.send_command(&cmd).await
}
pub async fn receive_text(&mut self, timeout_ms: i64) -> Result<AgiResponse> {
let timeout = timeout_ms.to_string();
let cmd = command::format_command(command::RECEIVE_TEXT, &[&timeout])?;
self.send_command(&cmd).await
}
pub async fn record_file(
&mut self,
filename: &str,
format: &str,
escape_digits: &str,
timeout_ms: i64,
beep: bool,
silence: Option<u32>,
) -> Result<AgiResponse> {
let timeout = timeout_ms.to_string();
let mut args = vec![filename, format, escape_digits, &timeout];
let beep_str;
if beep {
beep_str = "beep".to_string();
args.push(&beep_str);
}
let silence_str;
if let Some(s) = silence {
silence_str = format!("s={s}");
args.push(&silence_str);
}
let cmd = command::format_command(command::RECORD_FILE, &args)?;
self.send_command(&cmd).await
}
pub async fn say_alpha(&mut self, text: &str, escape_digits: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SAY_ALPHA, &[text, escape_digits])?;
self.send_command(&cmd).await
}
pub async fn say_date(&mut self, date: i64, escape_digits: &str) -> Result<AgiResponse> {
let d = date.to_string();
let cmd = command::format_command(command::SAY_DATE, &[&d, escape_digits])?;
self.send_command(&cmd).await
}
pub async fn say_datetime(
&mut self,
datetime: i64,
escape_digits: &str,
format: Option<&str>,
timezone: Option<&str>,
) -> Result<AgiResponse> {
let dt = datetime.to_string();
let mut args = vec![dt.as_str(), escape_digits];
let fmt;
if let Some(f) = format {
fmt = f.to_string();
args.push(&fmt);
if let Some(tz) = timezone {
args.push(tz);
}
}
let cmd = command::format_command(command::SAY_DATETIME, &args)?;
self.send_command(&cmd).await
}
pub async fn say_phonetic(&mut self, text: &str, escape_digits: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SAY_PHONETIC, &[text, escape_digits])?;
self.send_command(&cmd).await
}
pub async fn say_time(&mut self, time: i64, escape_digits: &str) -> Result<AgiResponse> {
let t = time.to_string();
let cmd = command::format_command(command::SAY_TIME, &[&t, escape_digits])?;
self.send_command(&cmd).await
}
pub async fn send_image(&mut self, image: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SEND_IMAGE, &[image])?;
self.send_command(&cmd).await
}
pub async fn send_text(&mut self, text: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SEND_TEXT, &[text])?;
self.send_command(&cmd).await
}
pub async fn set_autohangup(&mut self, seconds: u32) -> Result<AgiResponse> {
let s = seconds.to_string();
let cmd = command::format_command(command::SET_AUTOHANGUP, &[&s])?;
self.send_command(&cmd).await
}
pub async fn set_context(&mut self, context: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SET_CONTEXT, &[context])?;
self.send_command(&cmd).await
}
pub async fn set_extension(&mut self, extension: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SET_EXTENSION, &[extension])?;
self.send_command(&cmd).await
}
pub async fn set_music(&mut self, enabled: bool, class: Option<&str>) -> Result<AgiResponse> {
let on_off = if enabled { "on" } else { "off" };
let cmd = match class {
Some(c) => command::format_command(command::SET_MUSIC, &[on_off, c])?,
None => command::format_command(command::SET_MUSIC, &[on_off])?,
};
self.send_command(&cmd).await
}
pub async fn set_priority(&mut self, priority: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SET_PRIORITY, &[priority])?;
self.send_command(&cmd).await
}
pub async fn speech_create(&mut self, engine: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SPEECH_CREATE, &[engine])?;
self.send_command(&cmd).await
}
pub async fn speech_destroy(&mut self) -> Result<AgiResponse> {
let cmd = command::format_command(command::SPEECH_DESTROY, &[])?;
self.send_command(&cmd).await
}
pub async fn speech_activate_grammar(&mut self, grammar_name: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SPEECH_ACTIVATE_GRAMMAR, &[grammar_name])?;
self.send_command(&cmd).await
}
pub async fn speech_deactivate_grammar(&mut self, grammar_name: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SPEECH_DEACTIVATE_GRAMMAR, &[grammar_name])?;
self.send_command(&cmd).await
}
pub async fn speech_load_grammar(
&mut self,
grammar_name: &str,
path: &str,
) -> Result<AgiResponse> {
let cmd = command::format_command(command::SPEECH_LOAD_GRAMMAR, &[grammar_name, path])?;
self.send_command(&cmd).await
}
pub async fn speech_unload_grammar(&mut self, grammar_name: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SPEECH_UNLOAD_GRAMMAR, &[grammar_name])?;
self.send_command(&cmd).await
}
pub async fn speech_recognize(
&mut self,
prompt: &str,
timeout_ms: i64,
offset: Option<i64>,
) -> Result<AgiResponse> {
let t = timeout_ms.to_string();
let cmd = match offset {
Some(o) => {
let os = o.to_string();
command::format_command(command::SPEECH_RECOGNIZE, &[prompt, &t, &os])?
}
None => command::format_command(command::SPEECH_RECOGNIZE, &[prompt, &t])?,
};
self.send_command(&cmd).await
}
pub async fn speech_set(&mut self, name: &str, value: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::SPEECH_SET, &[name, value])?;
self.send_command(&cmd).await
}
pub async fn tdd_mode(&mut self, mode: &str) -> Result<AgiResponse> {
let cmd = command::format_command(command::TDD_MODE, &[mode])?;
self.send_command(&cmd).await
}
pub async fn asyncagi_break(&mut self) -> Result<AgiResponse> {
let cmd = command::format_command(command::ASYNCAGI_BREAK, &[])?;
self.send_command(&cmd).await
}
}