use futures_core::Stream;
use tokio::sync::{mpsc, oneshot};
use crate::api::lifecycle::detach_on_drop;
use crate::error::{ErrorKind, LinkError, RecvError, RemoteError};
use crate::ids::{ChannelId, Handle};
use crate::link::Delivery;
use crate::proto::{DriverCommand, LinkEvent};
use crate::types::definitions::Error as AmqpError;
use crate::types::messaging::{
Accepted, DeliveryState, Modified, Outcome, Rejected, Released,
};
#[derive(Debug)]
pub struct Consumer {
commands: mpsc::Sender<DriverCommand>,
channel: ChannelId,
handle: Handle,
events: mpsc::Receiver<LinkEvent>,
credit_window: u32,
replenish_every: u32,
consumed: u32,
}
impl Consumer {
pub(crate) fn new(
commands: mpsc::Sender<DriverCommand>,
channel: ChannelId,
handle: Handle,
events: mpsc::Receiver<LinkEvent>,
credit_window: u32,
low_water: u32,
) -> Self {
Consumer {
commands,
channel,
handle,
events,
credit_window,
replenish_every: credit_window.saturating_sub(low_water).max(1),
consumed: 0,
}
}
pub fn handle(&self) -> Handle {
self.handle
}
pub async fn recv(&mut self) -> Result<Delivery, RecvError> {
loop {
match self.events.recv().await {
Some(LinkEvent::Delivery(d)) => {
let delivery =
Delivery::new(d.delivery_id, d.delivery_tag, d.settled, d.message);
self.after_consume().await;
return Ok(delivery);
}
Some(LinkEvent::Detached { error }) => {
return Err(detached_error(error));
}
Some(_) => continue,
None => return Err(RecvError::msg(ErrorKind::Detached, "link closed")),
}
}
}
async fn after_consume(&mut self) {
if self.credit_window == 0 {
return; }
self.consumed += 1;
if self.consumed >= self.replenish_every {
let grant = self.consumed;
self.consumed = 0;
let _ = self
.commands
.send(DriverCommand::GrantCredit {
channel: self.channel,
handle: self.handle,
credit: grant,
})
.await;
}
}
pub async fn accept(&self, delivery: &Delivery) -> Result<(), RecvError> {
self.dispose(delivery, DeliveryState::Accepted(Accepted::default()))
.await
}
pub async fn reject(&self, delivery: &Delivery, error: Option<AmqpError>) -> Result<(), RecvError> {
self.dispose(delivery, DeliveryState::Rejected(Rejected { error }))
.await
}
pub async fn release(&self, delivery: &Delivery) -> Result<(), RecvError> {
self.dispose(delivery, DeliveryState::Released(Released::default()))
.await
}
pub async fn modify(&self, delivery: &Delivery, modified: Modified) -> Result<(), RecvError> {
self.dispose(delivery, DeliveryState::Modified(modified)).await
}
pub async fn settle(&self, delivery: &Delivery, outcome: Outcome) -> Result<(), RecvError> {
self.dispose(delivery, DeliveryState::from(outcome)).await
}
async fn dispose(&self, delivery: &Delivery, state: DeliveryState) -> Result<(), RecvError> {
let (tx, rx) = oneshot::channel();
self.commands
.send(DriverCommand::SendDisposition {
channel: self.channel,
handle: self.handle,
first: delivery.delivery_id,
last: None,
state,
settled: true,
reply: Some(tx),
})
.await
.map_err(|_| RecvError::msg(ErrorKind::NotConnected, "connection closed"))?;
rx.await
.map_err(|_| RecvError::msg(ErrorKind::Cancelled, "driver dropped"))?
}
pub async fn credit(&self, credit: u32) -> Result<(), RecvError> {
let flow = crate::types::performatives::Flow {
handle: Some(self.handle.value()),
link_credit: Some(credit),
drain: false,
..Default::default()
};
self.commands
.send(DriverCommand::SendFlow {
channel: self.channel,
flow: Box::new(flow),
})
.await
.map_err(|_| RecvError::msg(ErrorKind::NotConnected, "connection closed"))
}
pub async fn detach(mut self) -> Result<(), LinkError> {
let (tx, rx) = oneshot::channel();
let commands = self.commands.clone();
let handle = std::mem::replace(&mut self.handle, Handle(u32::MAX));
commands
.send(DriverCommand::DetachLink {
channel: self.channel,
handle,
closed: true,
error: None,
reply: tx,
})
.await
.map_err(|_| LinkError::msg(ErrorKind::NotConnected, "connection closed"))?;
rx.await
.map_err(|_| LinkError::msg(ErrorKind::Cancelled, "driver dropped"))?
}
pub fn into_stream(self) -> impl Stream<Item = Result<Delivery, RecvError>> {
futures_util::stream::unfold(self, |mut consumer| async move {
match consumer.recv().await {
Ok(delivery) => Some((Ok(delivery), consumer)),
Err(e) if e.kind() == ErrorKind::Detached => None,
Err(e) => Some((Err(e), consumer)),
}
})
}
}
fn detached_error(error: Option<AmqpError>) -> RecvError {
match error {
Some(e) => RecvError::from_remote(ErrorKind::Detached, RemoteError::new(e)),
None => RecvError::msg(ErrorKind::Detached, "link detached"),
}
}
impl Drop for Consumer {
fn drop(&mut self) {
if self.handle != Handle(u32::MAX) {
detach_on_drop(&self.commands, self.channel, self.handle);
}
}
}