1use std::sync::Arc;
7use std::time::Duration;
8
9use asterisk_rs_ami::action::{HangupAction, OriginateAction};
10use asterisk_rs_ami::event::AmiEvent;
11use asterisk_rs_ami::tracker::{CallTracker, CompletedCall};
12use asterisk_rs_ami::AmiClient;
13use asterisk_rs_core::event::EventSubscription;
14use tokio::sync::{mpsc, Mutex};
15
16#[derive(Debug, Clone)]
21pub struct Call {
22 pub channel: String,
24 pub unique_id: String,
26 client: AmiClient,
27 answer_sub: Arc<Mutex<EventSubscription<AmiEvent>>>,
31}
32
33impl Call {
34 pub async fn hangup(
36 &self,
37 ) -> asterisk_rs_ami::error::Result<asterisk_rs_ami::response::AmiResponse> {
38 self.client.hangup(HangupAction::new(&self.channel)).await
39 }
40
41 pub async fn wait_for_answer(&self, timeout: Duration) -> Result<(), PbxError> {
53 let uid = self.unique_id.clone();
54
55 let result = tokio::time::timeout(timeout, async {
56 let mut sub = self.answer_sub.lock().await;
57 loop {
58 let Some(event) = sub.recv().await else {
59 return Err(PbxError::Disconnected);
60 };
61 match event {
62 AmiEvent::Newstate {
63 unique_id,
64 channel_state_desc,
65 ..
66 } if unique_id == uid => {
67 if channel_state_desc == "Up" {
68 return Ok(());
69 }
70 }
71 AmiEvent::Hangup {
72 unique_id,
73 cause,
74 cause_txt,
75 ..
76 } if unique_id == uid => {
77 return Err(PbxError::CallFailed { cause, cause_txt });
78 }
79 _ => {}
80 }
81 }
82 })
83 .await;
84
85 match result {
86 Ok(inner) => inner,
87 Err(_) => Err(PbxError::Timeout),
88 }
89 }
90}
91
92#[derive(Debug, Clone, Default)]
94#[must_use]
95pub struct DialOptions {
96 pub caller_id: Option<String>,
98 pub timeout_ms: Option<u64>,
100 pub variables: Option<std::collections::HashMap<String, String>>,
102}
103
104impl DialOptions {
105 pub fn new() -> Self {
107 Self::default()
108 }
109
110 pub fn caller_id(mut self, cid: impl Into<String>) -> Self {
112 self.caller_id = Some(cid.into());
113 self
114 }
115
116 pub fn timeout_ms(mut self, ms: u64) -> Self {
118 self.timeout_ms = Some(ms);
119 self
120 }
121}
122
123#[derive(Debug, thiserror::Error)]
125#[non_exhaustive]
126pub enum PbxError {
127 #[error("AMI error: {0}")]
128 Ami(#[from] asterisk_rs_ami::AmiError),
129
130 #[error("call failed: {cause} ({cause_txt})")]
131 CallFailed { cause: u32, cause_txt: String },
132
133 #[error("operation timed out")]
134 Timeout,
135
136 #[error("client disconnected")]
137 Disconnected,
138}
139
140#[derive(Debug)]
145pub struct Pbx {
146 client: AmiClient,
147 tracker: CallTracker,
148 completed_rx: mpsc::Receiver<CompletedCall>,
149}
150
151impl Pbx {
152 pub fn new(client: AmiClient) -> Self {
154 let (tracker, completed_rx) = client.call_tracker();
155 Self {
156 client,
157 tracker,
158 completed_rx,
159 }
160 }
161
162 pub async fn dial(
168 &self,
169 from: impl Into<String>,
170 to: impl Into<String>,
171 options: Option<DialOptions>,
172 ) -> Result<Call, PbxError> {
173 let from = from.into();
174 let to = to.into();
175 let opts = options.unwrap_or_default();
176
177 let mut action = OriginateAction::new(&from)
178 .extension(&to)
179 .context("default")
180 .priority(1)
181 .async_originate(true);
182
183 if let Some(ref cid) = opts.caller_id {
184 action = action.caller_id(cid);
185 }
186 if let Some(ms) = opts.timeout_ms {
187 action = action.timeout_ms(ms);
188 }
189 if let Some(ref vars) = opts.variables {
190 for (k, v) in vars {
191 action = action.variable(k, v);
192 }
193 }
194
195 let answer_sub = Arc::new(Mutex::new(self.client.subscribe()));
199
200 let mut orig_sub = self
204 .client
205 .subscribe_filtered(move |e| matches!(e, AmiEvent::OriginateResponse { .. }));
206
207 let orig_response = self.client.originate(action).await?;
208 let expected_action_id = orig_response.action_id;
209
210 let originate_timeout =
212 Duration::from_secs(opts.timeout_ms.map(|ms| ms / 1000 + 5).unwrap_or(35));
213
214 let event = tokio::time::timeout(originate_timeout, async {
215 loop {
216 let Some(event) = orig_sub.recv().await else {
217 return Err(PbxError::Disconnected);
218 };
219 if let AmiEvent::OriginateResponse {
220 action_id,
221 channel,
222 unique_id,
223 response,
224 ..
225 } = event
226 {
227 if action_id == expected_action_id {
228 return Ok((channel, unique_id, response));
229 }
230 }
231 }
232 })
233 .await
234 .map_err(|_| PbxError::Timeout)??;
235
236 let (channel, unique_id, response) = event;
237
238 if response.eq_ignore_ascii_case("failure") {
239 return Err(PbxError::CallFailed {
240 cause: 0,
241 cause_txt: "originate failed".to_owned(),
242 });
243 }
244
245 Ok(Call {
246 channel,
247 unique_id,
248 client: self.client.clone(),
249 answer_sub,
250 })
251 }
252
253 pub async fn next_completed_call(&mut self) -> Option<CompletedCall> {
255 self.completed_rx.recv().await
256 }
257
258 pub fn client(&self) -> &AmiClient {
260 &self.client
261 }
262
263 pub fn shutdown(self) {
265 self.tracker.shutdown();
266 }
267}