use std::future::{Future, poll_fn};
use std::pin::{Pin, pin};
use std::sync::Arc;
use std::task::Poll;
use std::time::Instant;
use zbus::export::futures_core::Stream;
use zbus::zvariant::OwnedValue;
use zbus::{MatchRule, Message, MessageStream};
use super::{ChangeSource, Heard, LISTENED, PORTAL_SETTINGS, Stop, signal_matters};
use crate::linux_portal::{CALL_TIMEOUT, within};
const QUEUED: usize = 64;
pub(crate) struct BusSource {
_connection: zbus::Connection,
streams: Vec<MessageStream>,
stop: Arc<Stop>,
}
impl BusSource {
pub(crate) fn connect(address: Option<&str>, stop: Arc<Stop>) -> Option<BusSource> {
let subscribed = async_io::block_on(until_stopped(&stop, async {
let builder = match address {
Some(address) => zbus::connection::Builder::address(address),
None => zbus::connection::Builder::session(),
};
let connection = within(
CALL_TIMEOUT,
builder.ok()?.method_timeout(CALL_TIMEOUT).build(),
)
.await?
.ok()?;
let mut streams = Vec::with_capacity(LISTENED.len());
for (interface, member, path) in LISTENED {
let rule = MatchRule::builder()
.msg_type(zbus::message::Type::Signal)
.interface(interface)
.ok()?
.member(member)
.ok()?
.path(path)
.ok()?
.build();
streams.push(
MessageStream::for_match_rule(rule, &connection, Some(QUEUED))
.await
.ok()?,
);
}
Some((connection, streams))
}))
.flatten();
let (connection, streams) = subscribed?;
Some(BusSource {
_connection: connection,
streams,
stop,
})
}
}
impl ChangeSource for BusSource {
fn wait(&mut self, deadline: Option<Instant>) -> Heard {
let mut timer = deadline.map(async_io::Timer::at);
let stop = &self.stop;
let streams = &mut self.streams;
async_io::block_on(poll_fn(|cx| {
if stop.poll_stopped(cx) {
return Poll::Ready(Heard::Closed);
}
for stream in streams.iter_mut() {
loop {
match Pin::new(&mut *stream).poll_next(cx) {
Poll::Ready(Some(Ok(message))) => {
if message_matters(&message) {
return Poll::Ready(Heard::Change);
}
}
Poll::Ready(Some(Err(_))) => {}
Poll::Ready(None) => return Poll::Ready(Heard::Closed),
Poll::Pending => break,
}
}
}
if let Some(timer) = timer.as_mut()
&& Pin::new(timer).poll(cx).is_ready()
{
return Poll::Ready(Heard::Deadline);
}
Poll::Pending
}))
}
}
fn message_matters(message: &Message) -> bool {
let header = message.header();
let (Some(interface), Some(member), Some(path)) =
(header.interface(), header.member(), header.path())
else {
return false;
};
let namespace = if interface.as_str() == PORTAL_SETTINGS {
message
.body()
.deserialize::<(String, String, OwnedValue)>()
.ok()
.map(|(namespace, _, _)| namespace)
} else {
None
};
signal_matters(
interface.as_str(),
member.as_str(),
path.as_str(),
namespace.as_deref(),
)
}
async fn until_stopped<T>(stop: &Stop, future: impl Future<Output = T>) -> Option<T> {
let mut future = pin!(future);
poll_fn(|cx| {
if stop.poll_stopped(cx) {
return Poll::Ready(None);
}
future.as_mut().poll(cx).map(Some)
})
.await
}