use incular_platform::ApplicationActivation;
use incular_widgets::{RouteInformation, RouteInformationProvider};
use std::{
cell::{Cell, RefCell},
collections::VecDeque,
fmt,
rc::{Rc, Weak},
};
pub type ApplicationActivationListener = Rc<dyn Fn(ApplicationActivation)>;
#[derive(Default)]
struct ActivationState {
pending: VecDeque<ApplicationActivation>,
listeners: Vec<Weak<dyn Fn(ApplicationActivation)>>,
}
#[derive(Default)]
struct ActivationDrain {
queue: RefCell<ActivationState>,
active: Cell<bool>,
}
#[derive(Clone, Default)]
pub struct ApplicationActivationService {
state: Rc<ActivationDrain>,
}
impl fmt::Debug for ApplicationActivationService {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.state.queue.borrow();
formatter
.debug_struct("ApplicationActivationService")
.field("pending", &state.pending.len())
.field("listeners", &state.listeners.len())
.finish()
}
}
impl ApplicationActivationService {
pub fn subscribe(
&self,
listener: impl Fn(ApplicationActivation) + 'static,
) -> ApplicationActivationSubscription {
let listener: ApplicationActivationListener = Rc::new(listener);
self.state
.queue
.borrow_mut()
.listeners
.push(Rc::downgrade(&listener));
drain(&self.state);
ApplicationActivationSubscription {
_listener: listener,
}
}
#[doc(hidden)]
pub fn publish(&self, activation: ApplicationActivation) {
self.state.queue.borrow_mut().pending.push_back(activation);
drain(&self.state);
}
#[must_use]
pub fn pending_count(&self) -> usize {
self.state.queue.borrow().pending.len()
}
}
struct DispatchGuard {
shared: Rc<ActivationDrain>,
}
impl DispatchGuard {
fn acquire(shared: &Rc<ActivationDrain>) -> Option<Self> {
if shared.active.get() {
return None;
}
{
let mut queue = shared.queue.borrow_mut();
queue
.listeners
.retain(|listener| listener.strong_count() != 0);
if queue.listeners.is_empty() || queue.pending.is_empty() {
return None;
}
}
shared.active.set(true);
Some(Self {
shared: shared.clone(),
})
}
}
impl Drop for DispatchGuard {
fn drop(&mut self) {
self.shared.active.set(false);
}
}
fn drain(state: &Rc<ActivationDrain>) {
let Some(_drain) = DispatchGuard::acquire(state) else {
return;
};
loop {
let next = {
let mut queue = state.queue.borrow_mut();
queue
.listeners
.retain(|listener| listener.strong_count() != 0);
if queue.listeners.is_empty() {
return;
}
queue.pending.pop_front().map(|activation| {
(
activation,
queue
.listeners
.iter()
.filter_map(Weak::upgrade)
.collect::<Vec<_>>(),
)
})
};
let Some((activation, listeners)) = next else {
return;
};
for listener in listeners {
listener(activation.clone());
}
}
}
pub struct ApplicationActivationSubscription {
_listener: ApplicationActivationListener,
}
impl fmt::Debug for ApplicationActivationSubscription {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("ApplicationActivationSubscription(..)")
}
}
pub struct ActivationRouteBridge {
_subscription: ApplicationActivationSubscription,
delivered_routes: Rc<Cell<u64>>,
}
impl fmt::Debug for ActivationRouteBridge {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ActivationRouteBridge")
.field("delivered_routes", &self.delivered_routes.get())
.finish()
}
}
impl ActivationRouteBridge {
pub(crate) fn new(
activations: &ApplicationActivationService,
provider: Rc<dyn RouteInformationProvider>,
map: impl Fn(&str) -> Option<RouteInformation> + 'static,
) -> Self {
let delivered_routes = Rc::new(Cell::new(0_u64));
let delivered = delivered_routes.clone();
let subscription = activations.subscribe(move |activation| {
for url in activation.urls() {
if let Some(information) = map(url.as_str()) {
provider.set_platform_route_information(information);
delivered.set(delivered.get().saturating_add(1));
}
}
});
Self {
_subscription: subscription,
delivered_routes,
}
}
#[must_use]
pub fn delivered_routes(&self) -> u64 {
self.delivered_routes.get()
}
}