use crate::services::events::{
RenewOperation, RenewRequest, RenewResponse, SubscribeOperation, SubscribeRequest,
UnsubscribeOperation, UnsubscribeRequest, UnsubscribeResponse,
};
use crate::{ApiError, Result, Service};
use soap_client::SoapClient;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
#[derive(Debug)]
pub struct ManagedSubscription {
sid: String,
device_ip: String,
service: Service,
state: Arc<Mutex<SubscriptionState>>,
soap_client: SoapClient,
}
#[derive(Debug)]
struct SubscriptionState {
expires_at: SystemTime,
active: bool,
timeout_seconds: u32,
}
impl ManagedSubscription {
pub(crate) fn create(
device_ip: String,
service: Service,
callback_url: String,
timeout_seconds: u32,
soap_client: SoapClient,
) -> Result<Self> {
let request = SubscribeRequest {
callback_url,
timeout_seconds,
};
let response = SubscribeOperation::execute(&soap_client, &device_ip, service, &request)?;
let state = SubscriptionState {
expires_at: SystemTime::now() + Duration::from_secs(response.timeout_seconds as u64),
active: true,
timeout_seconds: response.timeout_seconds,
};
Ok(Self {
sid: response.sid,
device_ip,
service,
state: Arc::new(Mutex::new(state)),
soap_client,
})
}
fn unsubscribe_internal(
soap_client: &SoapClient,
device_ip: &str,
service: Service,
request: &UnsubscribeRequest,
) -> Result<UnsubscribeResponse> {
UnsubscribeOperation::execute(soap_client, device_ip, service, request)
}
fn renew_internal(
soap_client: &SoapClient,
device_ip: &str,
service: Service,
request: &RenewRequest,
) -> Result<RenewResponse> {
RenewOperation::execute(soap_client, device_ip, service, request)
}
pub fn subscription_id(&self) -> &str {
&self.sid
}
pub fn is_active(&self) -> bool {
let state = self.state.lock().unwrap();
state.active && SystemTime::now() < state.expires_at
}
pub fn needs_renewal(&self) -> bool {
self.time_until_renewal().is_some()
}
pub fn time_until_renewal(&self) -> Option<Duration> {
let state = self.state.lock().unwrap();
if !state.active {
return None;
}
let now = SystemTime::now();
if now >= state.expires_at {
return Some(Duration::ZERO);
}
let time_until_expiry = state.expires_at.duration_since(now).ok()?;
let renewal_threshold = Duration::from_secs(300);
if time_until_expiry <= renewal_threshold {
Some(time_until_expiry)
} else {
None
}
}
pub fn expires_at(&self) -> SystemTime {
let state = self.state.lock().unwrap();
state.expires_at
}
pub fn renew(&self) -> Result<()> {
let current_timeout = {
let state = self.state.lock().unwrap();
if !state.active {
return Err(ApiError::subscription_expired());
}
state.timeout_seconds
};
let request = RenewRequest {
sid: self.sid.clone(),
timeout_seconds: current_timeout,
};
let response =
Self::renew_internal(&self.soap_client, &self.device_ip, self.service, &request)?;
{
let mut state = self.state.lock().unwrap();
state.expires_at =
SystemTime::now() + Duration::from_secs(response.timeout_seconds as u64);
state.timeout_seconds = response.timeout_seconds;
}
Ok(())
}
pub fn unsubscribe(&self) -> Result<()> {
{
let mut state = self.state.lock().unwrap();
state.active = false;
}
let request = UnsubscribeRequest {
sid: self.sid.clone(),
};
Self::unsubscribe_internal(&self.soap_client, &self.device_ip, self.service, &request)
.map(|_| ())
}
}
impl Drop for ManagedSubscription {
fn drop(&mut self) {
if let Ok(mut state) = self.state.lock() {
if state.active {
state.active = false;
let request = UnsubscribeRequest {
sid: self.sid.clone(),
};
if let Err(e) = Self::unsubscribe_internal(
&self.soap_client,
&self.device_ip,
self.service,
&request,
) {
eprintln!("⚠️ Failed to unsubscribe {} during drop: {}", self.sid, e);
}
}
}
}
}