use futures::{FutureExt, StreamExt, select};
use futures_timer::Delay;
use log::{debug, warn};
use std::pin::pin;
use std::time::Duration;
use zbus::Connection;
use crate::Result;
use crate::api::models::{
ActiveConnectionState, ConnectionError, ConnectionStateReason, connection_state_reason_to_error,
};
use crate::dbus::{NMActiveConnectionProxy, NMDeviceProxy};
use crate::types::constants::{device_state, timeouts};
const CONNECTION_TIMEOUT: Duration = Duration::from_secs(30);
const DISCONNECT_TIMEOUT: Duration = Duration::from_secs(10);
pub(crate) async fn wait_for_connection_activation(
conn: &Connection,
active_conn_path: &zvariant::OwnedObjectPath,
timeout: Option<Duration>,
) -> Result<()> {
let active_conn = NMActiveConnectionProxy::builder(conn)
.path(active_conn_path.clone())?
.build()
.await?;
let mut stream = active_conn.receive_activation_state_changed().await?;
debug!("Subscribed to ActiveConnection StateChanged signal");
let current_state = active_conn.state().await?;
let state = ActiveConnectionState::from(current_state);
debug!("Current active connection state: {state}");
match state {
ActiveConnectionState::Activated => {
debug!("Connection already activated");
return Ok(());
}
ActiveConnectionState::Deactivated => {
warn!("Connection already deactivated");
return Err(ConnectionError::ActivationFailed(
ConnectionStateReason::Unknown,
));
}
_ => {}
}
let timeout_duration = timeout.unwrap_or(CONNECTION_TIMEOUT);
let mut timeout_delay = pin!(Delay::new(timeout_duration).fuse());
loop {
let current_state = active_conn.state().await?;
let state = ActiveConnectionState::from(current_state);
match state {
ActiveConnectionState::Activated => {
debug!("Connection activated during loop");
return Ok(());
}
ActiveConnectionState::Deactivated => {
warn!("Connection deactivated during loop");
return Err(ConnectionError::ActivationFailed(
ConnectionStateReason::Unknown,
));
}
_ => {}
}
select! {
_ = timeout_delay => {
warn!("Connection activation timed out after {:?}", timeout_duration);
return Err(ConnectionError::Timeout);
}
signal_opt = stream.next() => {
match signal_opt {
Some(signal) => {
match signal.args() {
Ok(args) => {
let new_state = ActiveConnectionState::from(args.state);
let reason = ConnectionStateReason::from(args.reason);
debug!("Active connection state changed to: {new_state} (reason: {reason})");
match new_state {
ActiveConnectionState::Activated => {
debug!("Connection activation successful");
return Ok(());
}
ActiveConnectionState::Deactivated => {
debug!("Connection activation failed: {reason}");
return Err(connection_state_reason_to_error(args.reason));
}
_ => {}
}
}
Err(e) => {
warn!("Failed to parse StateChanged signal args: {e}");
}
}
}
None => {
return Err(ConnectionError::Stuck("signal stream ended".into()));
}
}
}
}
}
}
pub(crate) async fn wait_for_device_disconnect(
dev: &NMDeviceProxy<'_>,
timeout: Option<Duration>,
) -> Result<()> {
let mut stream = dev.receive_device_state_changed().await?;
debug!("Subscribed to device StateChanged signal for disconnect");
let current_state = dev.state().await?;
debug!("Current device state for disconnect: {current_state}");
if current_state == device_state::DISCONNECTED || current_state == device_state::UNAVAILABLE {
debug!("Device already disconnected");
return Ok(());
}
let timeout_duration = timeout.unwrap_or(DISCONNECT_TIMEOUT);
let mut timeout_delay = pin!(Delay::new(timeout_duration).fuse());
loop {
let current_state = dev.state().await?;
if current_state == device_state::DISCONNECTED || current_state == device_state::UNAVAILABLE
{
debug!("Device disconnected during loop");
return Ok(());
}
select! {
_ = timeout_delay => {
let final_state = dev.state().await?;
if final_state == device_state::DISCONNECTED || final_state == device_state::UNAVAILABLE {
return Ok(());
} else {
warn!("Disconnect timed out, device still in state: {final_state}");
return Err(ConnectionError::Stuck(format!("state {final_state}")));
}
}
signal_opt = stream.next() => {
match signal_opt {
Some(signal) => {
match signal.args() {
Ok(args) => {
let new_state = args.new_state;
debug!("Device state during disconnect: {new_state}");
if new_state == device_state::DISCONNECTED
|| new_state == device_state::UNAVAILABLE
{
debug!("Device reached disconnected state");
return Ok(());
}
}
Err(e) => {
warn!("Failed to parse StateChanged signal args: {e}");
}
}
}
None => {
return Err(ConnectionError::Stuck("signal stream ended".into()));
}
}
}
}
}
}
pub(crate) async fn wait_for_wifi_device_ready(dev: &NMDeviceProxy<'_>) -> Result<()> {
let mut stream = dev.receive_device_state_changed().await?;
debug!("Subscribed to device StateChanged signal for ready check");
let current_state = dev.state().await?;
debug!("Current device state for ready check: {current_state}");
if current_state == device_state::DISCONNECTED || current_state == device_state::ACTIVATED {
debug!("Device already ready");
return Ok(());
}
let ready_timeout = timeouts::wifi_ready_timeout();
let mut timeout_delay = pin!(Delay::new(ready_timeout).fuse());
loop {
let current_state = dev.state().await?;
if current_state == device_state::DISCONNECTED || current_state == device_state::ACTIVATED {
debug!("Device ready during loop");
return Ok(());
}
select! {
_ = timeout_delay => {
let final_state = dev.state().await?;
if final_state == device_state::DISCONNECTED || final_state == device_state::ACTIVATED {
return Ok(());
} else {
warn!("Wi-Fi device not ready after timeout, state: {final_state}");
return Err(ConnectionError::WifiNotReady);
}
}
signal_opt = stream.next() => {
match signal_opt {
Some(signal) => {
match signal.args() {
Ok(args) => {
let new_state = args.new_state;
debug!("Device state during ready wait: {new_state}");
if new_state == device_state::DISCONNECTED
|| new_state == device_state::ACTIVATED
{
debug!("Device is now ready");
return Ok(());
}
}
Err(e) => {
warn!("Failed to parse StateChanged signal args: {e}");
}
}
}
None => {
return Err(ConnectionError::WifiNotReady);
}
}
}
}
}
}