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}