Skip to main content

sonos_api/services/
events.rs

1//! UPnP event subscription operations
2//!
3//! This module provides operations for managing UPnP event subscriptions
4//! across all Sonos services. These operations handle the HTTP-based
5//! subscription protocol rather than SOAP.
6//!
7//! Note: This module is being deprecated in favor of the new event framework
8//! in `crate::events`. Service-specific event handling is now done in
9//! individual service modules.
10
11use crate::{ApiError, Result, Service};
12use serde::{Deserialize, Serialize};
13
14/// Subscribe operation for UPnP event subscriptions
15///
16/// This operation handles creating new UPnP event subscriptions for any service.
17/// Unlike regular SOAP operations, this uses HTTP SUBSCRIBE method instead of POST.
18pub struct SubscribeOperation;
19
20/// Request for Subscribe operation
21#[derive(Debug, Clone, Serialize)]
22pub struct SubscribeRequest {
23    /// The callback URL where events should be sent
24    pub callback_url: String,
25    /// Requested subscription timeout in seconds
26    pub timeout_seconds: u32,
27}
28
29/// Response for Subscribe operation
30#[derive(Debug, Clone, Deserialize)]
31pub struct SubscribeResponse {
32    /// Subscription ID returned by the device
33    pub sid: String,
34    /// Actual timeout granted by the device (in seconds)
35    pub timeout_seconds: u32,
36}
37
38impl SubscribeOperation {
39    /// Execute a subscription request for a specific service
40    ///
41    /// This method uses the soap-client's subscribe functionality to create
42    /// a UPnP event subscription for the specified service.
43    ///
44    /// # Arguments
45    /// * `soap_client` - The SOAP client to use for the request
46    /// * `ip` - Device IP address
47    /// * `service` - The service to subscribe to
48    /// * `request` - The subscription request parameters
49    ///
50    /// # Returns
51    /// The subscription response containing SID and timeout
52    pub fn execute(
53        soap_client: &soap_client::SoapClient,
54        ip: &str,
55        service: Service,
56        request: &SubscribeRequest,
57    ) -> Result<SubscribeResponse> {
58        let service_info = service.info();
59
60        let subscription_response = soap_client
61            .subscribe(
62                ip,
63                1400, // Standard Sonos port
64                service_info.event_endpoint,
65                &request.callback_url,
66                request.timeout_seconds,
67            )
68            .map_err(|e| match e {
69                soap_client::SoapError::Network(msg) => ApiError::NetworkError(msg),
70                soap_client::SoapError::Parse(msg) => ApiError::ParseError(msg),
71                soap_client::SoapError::Fault(code) => ApiError::SoapFault(code),
72            })?;
73
74        Ok(SubscribeResponse {
75            sid: subscription_response.sid,
76            timeout_seconds: subscription_response.timeout_seconds,
77        })
78    }
79}
80
81/// Unsubscribe operation for UPnP event subscriptions
82///
83/// This operation handles canceling existing UPnP event subscriptions for any service.
84/// Unlike regular SOAP operations, this uses HTTP UNSUBSCRIBE method instead of POST.
85pub struct UnsubscribeOperation;
86
87/// Request for Unsubscribe operation
88#[derive(Debug, Clone, Serialize)]
89pub struct UnsubscribeRequest {
90    /// The subscription ID to cancel
91    pub sid: String,
92}
93
94/// Response for Unsubscribe operation (empty - success is indicated by no error)
95#[derive(Debug, Clone)]
96pub struct UnsubscribeResponse;
97
98impl UnsubscribeOperation {
99    /// Execute an unsubscribe request for a specific service
100    ///
101    /// This method uses the soap-client's unsubscribe functionality to cancel
102    /// an existing UPnP event subscription for the specified service.
103    ///
104    /// # Arguments
105    /// * `soap_client` - The SOAP client to use for the request
106    /// * `ip` - Device IP address
107    /// * `service` - The service to unsubscribe from
108    /// * `request` - The unsubscribe request parameters
109    ///
110    /// # Returns
111    /// An empty response on success, or an error if the operation failed
112    pub fn execute(
113        soap_client: &soap_client::SoapClient,
114        ip: &str,
115        service: Service,
116        request: &UnsubscribeRequest,
117    ) -> Result<UnsubscribeResponse> {
118        let service_info = service.info();
119
120        soap_client
121            .unsubscribe(
122                ip,
123                1400, // Standard Sonos port
124                service_info.event_endpoint,
125                &request.sid,
126            )
127            .map_err(|e| match e {
128                soap_client::SoapError::Network(msg) => ApiError::NetworkError(msg),
129                soap_client::SoapError::Parse(msg) => ApiError::ParseError(msg),
130                soap_client::SoapError::Fault(code) => ApiError::SoapFault(code),
131            })?;
132
133        Ok(UnsubscribeResponse)
134    }
135}
136
137/// Renew operation for UPnP event subscriptions
138///
139/// This operation handles renewing existing UPnP event subscriptions for any service.
140/// Unlike regular SOAP operations, this uses HTTP SUBSCRIBE method with SID header.
141pub struct RenewOperation;
142
143/// Request for Renew operation
144#[derive(Debug, Clone, Serialize)]
145pub struct RenewRequest {
146    /// The subscription ID to renew
147    pub sid: String,
148    /// Requested renewal timeout in seconds
149    pub timeout_seconds: u32,
150}
151
152/// Response for Renew operation
153#[derive(Debug, Clone, Deserialize)]
154pub struct RenewResponse {
155    /// The actual timeout granted by the device (in seconds)
156    pub timeout_seconds: u32,
157}
158
159impl RenewOperation {
160    /// Execute a subscription renewal request for a specific service
161    ///
162    /// This method uses the soap-client's renew_subscription functionality to
163    /// extend an existing UPnP event subscription for the specified service.
164    ///
165    /// # Arguments
166    /// * `soap_client` - The SOAP client to use for the request
167    /// * `ip` - Device IP address
168    /// * `service` - The service to renew subscription for
169    /// * `request` - The renewal request parameters
170    ///
171    /// # Returns
172    /// The renewal response containing the actual timeout granted
173    pub fn execute(
174        soap_client: &soap_client::SoapClient,
175        ip: &str,
176        service: Service,
177        request: &RenewRequest,
178    ) -> Result<RenewResponse> {
179        let service_info = service.info();
180
181        let actual_timeout_seconds = soap_client
182            .renew_subscription(
183                ip,
184                1400, // Standard Sonos port
185                service_info.event_endpoint,
186                &request.sid,
187                request.timeout_seconds,
188            )
189            .map_err(|e| match e {
190                soap_client::SoapError::Network(msg) => ApiError::NetworkError(msg),
191                soap_client::SoapError::Parse(msg) => ApiError::ParseError(msg),
192                soap_client::SoapError::Fault(code) => ApiError::SoapFault(code),
193            })?;
194
195        Ok(RenewResponse {
196            timeout_seconds: actual_timeout_seconds,
197        })
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_subscribe_request_creation() {
207        let request = SubscribeRequest {
208            callback_url: "http://192.168.1.50:8080/callback".to_string(),
209            timeout_seconds: 1800,
210        };
211
212        assert_eq!(request.callback_url, "http://192.168.1.50:8080/callback");
213        assert_eq!(request.timeout_seconds, 1800);
214    }
215
216    #[test]
217    fn test_subscribe_response_creation() {
218        let response = SubscribeResponse {
219            sid: "uuid:12345678-1234-1234-1234-123456789012".to_string(),
220            timeout_seconds: 1800,
221        };
222
223        assert_eq!(response.sid, "uuid:12345678-1234-1234-1234-123456789012");
224        assert_eq!(response.timeout_seconds, 1800);
225    }
226
227    #[test]
228    fn test_unsubscribe_request_creation() {
229        let request = UnsubscribeRequest {
230            sid: "uuid:12345678-1234-1234-1234-123456789012".to_string(),
231        };
232
233        assert_eq!(request.sid, "uuid:12345678-1234-1234-1234-123456789012");
234    }
235
236    #[test]
237    fn test_unsubscribe_response_creation() {
238        let _response = UnsubscribeResponse;
239        // Just verify it can be created
240    }
241
242    #[test]
243    fn test_renew_request_creation() {
244        let request = RenewRequest {
245            sid: "uuid:12345678-1234-1234-1234-123456789012".to_string(),
246            timeout_seconds: 1800,
247        };
248
249        assert_eq!(request.sid, "uuid:12345678-1234-1234-1234-123456789012");
250        assert_eq!(request.timeout_seconds, 1800);
251    }
252
253    #[test]
254    fn test_renew_response_creation() {
255        let response = RenewResponse {
256            timeout_seconds: 1800,
257        };
258
259        assert_eq!(response.timeout_seconds, 1800);
260    }
261}