-1
Cargo.toml
-1
Cargo.toml
···
33
33
owo-colors = "3.5.0"
34
34
serde = "1.0.152"
35
35
serde_json = "1.0.91"
36
-
socket2 = "0.4.7"
37
36
surf = { version = "2.3.2", features = ["h1-client-rustls"], default-features = false}
38
37
tokio = { version = "1.24.2", features = ["tokio-macros", "macros", "rt", "rt-multi-thread"] }
39
38
url = "2.3.1"
+1
-1
examples/discover.rs
+1
-1
examples/discover.rs
+2
-2
examples/media_renderer_client.rs
+2
-2
examples/media_renderer_client.rs
···
10
10
11
11
#[tokio::main]
12
12
async fn main() -> Result<(), Box<dyn std::error::Error>> {
13
-
let devices = discover_pnp_locations();
13
+
let devices = discover_pnp_locations().await?;
14
14
tokio::pin!(devices);
15
15
16
16
let mut kodi_device: Option<Device> = None;
···
23
23
}
24
24
25
25
let kodi_device = kodi_device.unwrap();
26
-
let device_client = DeviceClient::new(&kodi_device.location).connect().await?;
26
+
let device_client = DeviceClient::new(&kodi_device.location)?.connect().await?;
27
27
let mut media_renderer = MediaRendererClient::new(device_client);
28
28
29
29
let options = LoadOptions {
+2
-2
examples/media_server_client.rs
+2
-2
examples/media_server_client.rs
···
8
8
9
9
#[tokio::main]
10
10
async fn main() -> Result<(), Box<dyn std::error::Error>> {
11
-
let devices = discover_pnp_locations();
11
+
let devices = discover_pnp_locations().await?;
12
12
tokio::pin!(devices);
13
13
14
14
let mut kodi_device: Option<Device> = None;
···
21
21
}
22
22
23
23
let kodi_device = kodi_device.unwrap();
24
-
let device_client = DeviceClient::new(&kodi_device.location).connect().await?;
24
+
let device_client = DeviceClient::new(&kodi_device.location)?.connect().await?;
25
25
let media_server_client = MediaServerClient::new(device_client);
26
26
let results = media_server_client
27
27
.browse("0", "BrowseDirectChildren")
+83
-98
src/device_client.rs
+83
-98
src/device_client.rs
···
3
3
env,
4
4
net::TcpListener,
5
5
sync::{
6
-
mpsc::{self, Receiver, Sender},
7
-
Arc, Mutex,
6
+
Arc,
8
7
},
9
8
time::Duration,
10
9
};
···
17
16
types::{AVTransportEvent, Device, Event, Service},
18
17
BROADCAST_EVENT,
19
18
};
20
-
use anyhow::Error;
19
+
use anyhow::{anyhow, Result};
21
20
use hyper::{
22
21
server::conn::AddrStream,
23
22
service::{make_service_fn, service_fn},
24
23
};
25
24
use hyper::{Body, Request, Response, Server};
26
25
use surf::{Client, Config, Url};
26
+
use tokio::sync::Mutex;
27
27
use xml_builder::{XMLBuilder, XMLElement, XMLVersion};
28
28
29
29
#[derive(Clone)]
···
35
35
}
36
36
37
37
impl DeviceClient {
38
-
pub fn new(url: &str) -> Self {
39
-
Self {
40
-
base_url: Url::parse(url).unwrap(),
38
+
pub fn new(url: &str) -> Result<Self> {
39
+
Ok(Self {
40
+
base_url: Url::parse(url)?,
41
41
http_client: Config::new()
42
42
.set_timeout(Some(Duration::from_secs(5)))
43
-
.try_into()
44
-
.unwrap(),
43
+
.try_into()?,
45
44
device: None,
46
45
stop: Arc::new(Mutex::new(false)),
47
-
}
46
+
})
48
47
}
49
48
50
-
pub async fn connect(&mut self) -> Result<Self, Error> {
49
+
pub async fn connect(&mut self) -> Result<Self> {
51
50
self.device = Some(parse_location(self.base_url.as_str()).await?);
52
51
Ok(Self {
53
52
base_url: self.base_url.clone(),
···
66
65
service_id: &str,
67
66
action_name: &str,
68
67
params: HashMap<String, String>,
69
-
) -> Result<String, Error> {
68
+
) -> Result<String> {
70
69
if self.device.is_none() {
71
-
return Err(Error::msg("Device not connected"));
70
+
return Err(anyhow!("Device not connected"));
72
71
}
73
72
let service_id = resolve_service(service_id);
74
73
let service = self.get_service_description(&service_id).await?;
···
80
79
self.call_action_internal(&service, action_name, params)
81
80
.await
82
81
}
83
-
None => Err(Error::msg("Action not found")),
82
+
None => Err(anyhow!("Action not found")),
84
83
}
85
84
}
86
85
···
89
88
service: &Service,
90
89
action_name: &str,
91
90
params: HashMap<String, String>,
92
-
) -> Result<String, Error> {
93
-
let control_url = Url::parse(&service.control_url).unwrap();
91
+
) -> Result<String> {
92
+
let control_url = Url::parse(&service.control_url)?;
94
93
95
94
let mut xml = XMLBuilder::new()
96
95
.version(XMLVersion::XML1_1)
···
111
110
112
111
for (name, value) in params {
113
112
let mut param = XMLElement::new(name.as_str());
114
-
param.add_text(value).unwrap();
115
-
action.add_child(param).unwrap();
113
+
param.add_text(value)?;
114
+
action.add_child(param)?;
116
115
}
117
116
118
-
body.add_child(action).unwrap();
119
-
envelope.add_child(body).unwrap();
117
+
body.add_child(action)?;
118
+
envelope.add_child(body)?;
120
119
121
120
xml.set_root_element(envelope);
122
121
123
122
let mut writer: Vec<u8> = Vec::new();
124
-
xml.generate(&mut writer).unwrap();
125
-
let xml = String::from_utf8(writer).unwrap();
123
+
xml.generate(&mut writer)?;
124
+
let xml = String::from_utf8(writer)?;
126
125
127
126
let soap_action = format!("\"{}#{}\"", service.service_type, action_name);
128
127
···
136
135
.body_string(xml.clone())
137
136
.send()
138
137
.await
139
-
.map_err(|e| Error::msg(e.to_string()))?;
140
-
Ok(res
138
+
.map_err(|e| anyhow!(e.to_string()))?;
139
+
res
141
140
.body_string()
142
141
.await
143
-
.map_err(|e| Error::msg(e.to_string()))?)
142
+
.map_err(|e| anyhow!(e.to_string()))
144
143
}
145
144
146
-
async fn get_service_description(&self, service_id: &str) -> Result<Service, Error> {
145
+
async fn get_service_description(&self, service_id: &str) -> Result<Service> {
147
146
if let Some(device) = &self.device {
148
147
let service = device
149
148
.services
150
149
.iter()
151
150
.find(|s| s.service_id == service_id)
152
-
.unwrap();
151
+
.ok_or_else(|| anyhow!("Service with requested service_id {} does not exist", service_id))?;
153
152
return Ok(service.clone());
154
153
}
155
-
Err(Error::msg("Device not connected"))
154
+
Err(anyhow!("Device not connected"))
156
155
}
157
156
158
-
pub async fn subscribe(&mut self, service_id: &str) -> Result<(), Error> {
157
+
pub async fn subscribe(&mut self, service_id: &str) -> Result<()> {
159
158
if self.device.is_none() {
160
-
return Err(Error::msg("Device not connected"));
159
+
return Err(anyhow!("Device not connected"));
161
160
}
162
161
let service_id = resolve_service(service_id);
163
162
let service = self.get_service_description(&service_id).await?;
···
179
178
.header("NT", "upnp:event")
180
179
.header("TIMEOUT", "Second-1800")
181
180
.header("USER-AGENT", user_agent)
182
-
.body(hyper::Body::empty())
183
-
.unwrap();
181
+
.body(hyper::Body::empty())?;
184
182
client.request(req).await?;
185
183
Ok(())
186
184
}
187
185
188
-
pub async fn unsubscribe(&mut self, service_id: &str, sid: &str) -> Result<(), Error> {
186
+
pub async fn unsubscribe(&mut self, service_id: &str, sid: &str) -> Result<()> {
189
187
if self.device.is_none() {
190
-
return Err(Error::msg("Device not connected"));
188
+
return Err(anyhow!("Device not connected"));
191
189
}
192
190
let service_id = resolve_service(service_id);
193
-
let service = self.get_service_description(&service_id).await.unwrap();
191
+
let service = self.get_service_description(&service_id).await?;
194
192
let client = hyper::Client::new();
195
193
let req = hyper::Request::builder()
196
194
.method("UNSUBSCRIBE")
197
195
.uri(service.event_sub_url.clone())
198
196
.header("SID", sid)
199
-
.body(hyper::Body::empty())
200
-
.unwrap();
197
+
.body(hyper::Body::empty())?;
201
198
202
199
client.request(req).await?;
203
200
···
205
202
Ok(())
206
203
}
207
204
208
-
async fn ensure_eventing_server(&mut self) -> Result<(String, u16), Error> {
205
+
async fn ensure_eventing_server(&mut self) -> Result<(String, u16)> {
209
206
let addr: &str = "0.0.0.0:0";
210
-
let listener = TcpListener::bind(&addr).unwrap();
207
+
let listener = TcpListener::bind(addr)?;
211
208
212
209
let service = make_service_fn(|_: &AddrStream| async {
213
210
Ok::<_, hyper::Error>(service_fn(|req: Request<Body>| async move {
···
231
228
let current_track_metadata =
232
229
parse_current_track_metadata(last_change.as_str()).unwrap();
233
230
234
-
match transport_state {
235
-
Some(state) => {
236
-
let tx = BROADCAST_EVENT.lock().unwrap();
237
-
let tx = tx.as_ref().clone();
238
-
let ev = AVTransportEvent::TransportState {
239
-
sid: sid.clone(),
240
-
transport_state: state,
241
-
};
242
-
tx.unwrap().send(Event::AVTransport(ev)).unwrap();
243
-
}
244
-
None => {}
231
+
if let Some(state) = transport_state {
232
+
let tx = BROADCAST_EVENT.lock().unwrap();
233
+
let tx = tx.as_ref();
234
+
let ev = AVTransportEvent::TransportState {
235
+
sid: sid.clone(),
236
+
transport_state: state,
237
+
};
238
+
tx.unwrap().send(Event::AVTransport(ev)).unwrap();
245
239
}
246
240
247
-
match play_mode {
248
-
Some(mode) => {
249
-
let tx = BROADCAST_EVENT.lock().unwrap();
250
-
let tx = tx.as_ref().clone();
251
-
let ev = AVTransportEvent::CurrentPlayMode {
252
-
sid: sid.clone(),
253
-
play_mode: mode,
254
-
};
255
-
tx.unwrap().send(Event::AVTransport(ev)).unwrap();
256
-
}
257
-
None => {}
241
+
if let Some(mode) = play_mode {
242
+
let tx = BROADCAST_EVENT.lock().unwrap();
243
+
let tx = tx.as_ref();
244
+
let ev = AVTransportEvent::CurrentPlayMode {
245
+
sid: sid.clone(),
246
+
play_mode: mode,
247
+
};
248
+
tx.unwrap().send(Event::AVTransport(ev)).unwrap();
258
249
}
259
250
260
-
match av_transport_uri_metadata {
261
-
Some(metadata) => {
262
-
let tx = BROADCAST_EVENT.lock().unwrap();
263
-
let tx = tx.as_ref().clone();
264
-
let m = deserialize_metadata(metadata.as_str()).unwrap();
265
-
let ev = AVTransportEvent::AVTransportURIMetaData {
266
-
sid: sid.clone(),
267
-
url: m.url,
268
-
title: m.title,
269
-
artist: m.artist,
270
-
album: m.album,
271
-
album_art_uri: m.album_art_uri,
272
-
genre: m.genre,
273
-
};
274
-
tx.unwrap().send(Event::AVTransport(ev)).unwrap();
275
-
}
276
-
None => {}
251
+
if let Some(metadata) = av_transport_uri_metadata {
252
+
let tx = BROADCAST_EVENT.lock().unwrap();
253
+
let tx = tx.as_ref();
254
+
let m = deserialize_metadata(metadata.as_str()).unwrap();
255
+
let ev = AVTransportEvent::AVTransportURIMetaData {
256
+
sid: sid.clone(),
257
+
url: m.url,
258
+
title: m.title,
259
+
artist: m.artist,
260
+
album: m.album,
261
+
album_art_uri: m.album_art_uri,
262
+
genre: m.genre,
263
+
};
264
+
tx.unwrap().send(Event::AVTransport(ev)).unwrap();
277
265
}
278
266
279
-
match current_track_metadata {
280
-
Some(metadata) => {
281
-
let m = deserialize_metadata(metadata.as_str()).unwrap();
282
-
let tx = BROADCAST_EVENT.lock().unwrap();
283
-
let tx = tx.as_ref().clone();
284
-
let ev = AVTransportEvent::CurrentTrackMetadata {
285
-
sid: sid.clone(),
286
-
url: m.url,
287
-
title: m.title,
288
-
artist: m.artist,
289
-
album: m.album,
290
-
album_art_uri: m.album_art_uri,
291
-
genre: m.genre,
292
-
};
293
-
tx.unwrap().send(Event::AVTransport(ev)).unwrap();
294
-
}
295
-
None => {}
267
+
if let Some(metadata) = current_track_metadata {
268
+
let m = deserialize_metadata(metadata.as_str()).unwrap();
269
+
let tx = BROADCAST_EVENT.lock().unwrap();
270
+
let tx = tx.as_ref();
271
+
let ev = AVTransportEvent::CurrentTrackMetadata {
272
+
sid: sid.clone(),
273
+
url: m.url,
274
+
title: m.title,
275
+
artist: m.artist,
276
+
album: m.album,
277
+
album_art_uri: m.album_art_uri,
278
+
genre: m.genre,
279
+
};
280
+
tx.unwrap().send(Event::AVTransport(ev)).unwrap();
296
281
}
297
282
298
283
Ok::<_, hyper::Error>(Response::new(Body::empty()))
···
311
296
});
312
297
313
298
tokio::spawn(async move {
314
-
while !*stop.lock().unwrap() {
299
+
while !*stop.lock().await {
315
300
tokio::time::sleep(Duration::from_millis(100)).await;
316
301
}
317
302
});
···
319
304
Ok((address, port))
320
305
}
321
306
322
-
async fn release_eventing_server(&mut self) -> Result<(), Error> {
323
-
let mut stop = self.stop.lock().unwrap();
307
+
async fn release_eventing_server(&mut self) -> Result<()> {
308
+
let mut stop = self.stop.lock().await;
324
309
*stop = true;
325
310
Ok(())
326
311
}
327
312
}
328
313
329
314
fn resolve_service(service_id: &str) -> String {
330
-
match service_id.contains(":") {
315
+
match service_id.contains(':') {
331
316
true => service_id.to_string(),
332
317
false => format!("urn:upnp-org:serviceId:{}", service_id),
333
318
}
+33
-35
src/discovery.rs
+33
-35
src/discovery.rs
···
1
-
use anyhow::Error;
1
+
use anyhow::{anyhow, Result};
2
2
use async_stream::stream;
3
3
use futures_util::Stream;
4
-
use socket2::{Domain, Protocol, Socket, Type};
5
4
use std::collections::HashMap;
6
-
use std::mem::MaybeUninit;
7
-
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
5
+
use std::net::{Ipv4Addr, SocketAddr};
8
6
use std::str;
9
-
use std::thread::sleep;
10
-
use std::time::Duration;
7
+
use tokio::net::UdpSocket;
11
8
12
9
use crate::parser::parse_location;
13
10
use crate::types::Device;
···
19
16
ST: ssdp:all\r\n\
20
17
\r\n";
21
18
22
-
pub fn discover_pnp_locations() -> impl Stream<Item = Device> {
23
-
// Create a UDP socket
24
-
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)).unwrap();
19
+
pub async fn discover_pnp_locations() -> Result<impl Stream<Item = Device>> {
20
+
let any: SocketAddr = ([0, 0, 0, 0], 0).into();
21
+
let socket = UdpSocket::bind(any).await?;
22
+
socket.join_multicast_v4(Ipv4Addr::new(239, 255, 255, 250), Ipv4Addr::new(0, 0, 0, 0))?;
25
23
26
24
// Set the socket address to the multicast IP and port for UPnP device discovery
27
-
let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(239, 255, 255, 250)), 1900).into();
28
-
29
-
// Join the UPnP multicast group
30
-
socket
31
-
.join_multicast_v4(
32
-
&Ipv4Addr::new(239, 255, 255, 250),
33
-
&Ipv4Addr::new(0, 0, 0, 0),
34
-
)
35
-
.unwrap();
25
+
let socket_addr: SocketAddr = ([239, 255, 255, 250], 1900).into();
36
26
37
27
// Send the discovery request
38
28
socket
39
29
.send_to(DISCOVERY_REQUEST.as_bytes(), &socket_addr)
40
-
.unwrap();
30
+
.await?;
41
31
42
-
stream! {
32
+
Ok(stream! {
43
33
loop {
44
-
// Receive the discovery response
45
-
let mut buf = [MaybeUninit::uninit(); 2048];
46
-
let (size, _) = socket.recv_from(&mut buf).unwrap();
47
-
// Convert the response to a string
48
-
let response =
49
-
str::from_utf8(unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, size) })
50
-
.unwrap();
51
-
let headers = parse_raw_http_response(response).unwrap();
52
-
let location = *headers.get("location").unwrap();
53
-
yield parse_location(location).await.unwrap();
54
-
sleep(Duration::from_millis(500));
55
-
}
56
-
}
34
+
async fn get_next(socket: &UdpSocket) -> Result<String> {
35
+
// Receive the discovery response
36
+
let mut buf = [0; 2048];
37
+
let (size, _) = socket.recv_from(&mut buf).await?;
38
+
// Convert the response to a string
39
+
let response =
40
+
str::from_utf8(unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, size) })?;
41
+
let headers = parse_raw_http_response(response)?;
42
+
let location = headers.get("location")
43
+
.ok_or_else(|| anyhow!("Response header missing location"))?
44
+
.to_string();
45
+
Ok(location)
46
+
}
47
+
48
+
if let Ok(location) = get_next(&socket).await {
49
+
if let Ok(device) = parse_location(&location).await {
50
+
yield device;
51
+
}
52
+
}
53
+
}
54
+
})
57
55
}
58
56
59
-
fn parse_raw_http_response(response_str: &str) -> Result<HashMap<String, &str>, Error> {
57
+
fn parse_raw_http_response(response_str: &str) -> Result<HashMap<String, &str>> {
60
58
let mut headers = HashMap::new();
61
59
62
60
match response_str.split("\r\n\r\n").next() {
···
70
68
}
71
69
Ok(headers)
72
70
}
73
-
None => Err(Error::msg("Invalid HTTP response")),
71
+
None => Err(anyhow!("Invalid HTTP response")),
74
72
}
75
73
}
+1
-1
src/media_renderer.rs
+1
-1
src/media_renderer.rs
+1
-1
src/media_server.rs
+1
-1
src/media_server.rs
+154
-165
src/parser.rs
+154
-165
src/parser.rs
···
1
1
use std::time::Duration;
2
2
3
3
use crate::types::{Action, Argument, Container, Device, Item, Metadata, Service, TransportInfo};
4
-
use anyhow::Error;
4
+
use anyhow::{anyhow, Result};
5
5
use elementtree::Element;
6
6
use surf::{http::Method, Client, Config, Url};
7
7
use xml::reader::XmlEvent;
8
8
use xml::EventReader;
9
9
10
-
pub async fn parse_location(location: &str) -> Result<Device, Error> {
10
+
pub async fn parse_location(location: &str) -> Result<Device> {
11
11
let client: Client = Config::new()
12
12
.set_timeout(Some(Duration::from_secs(5)))
13
-
.try_into()
14
-
.unwrap();
15
-
let req = surf::Request::new(Method::Get, location.parse().unwrap());
16
-
let xml_root = client.recv_string(req).await.unwrap();
17
-
18
-
let mut device: Device = Device::default();
13
+
.try_into()?;
14
+
let req = surf::Request::new(Method::Get, location.parse()?);
15
+
let xml_root = client
16
+
.recv_string(req)
17
+
.await
18
+
.map_err(|e| anyhow!("Failed to retrieve xml from device endpoint: {}", e))?;
19
19
20
-
device.location = location.to_string();
20
+
let mut device = Device {
21
+
location: location.to_string(),
22
+
..Default::default()
23
+
};
21
24
22
25
device.device_type = parse_attribute(
23
26
&xml_root,
···
67
70
)?;
68
71
69
72
let base_url = location.split('/').take(3).collect::<Vec<&str>>().join("/");
70
-
device.services = parse_services(&base_url, &xml_root).await;
73
+
device.services = parse_services(&base_url, &xml_root).await?;
71
74
72
75
Ok(device)
73
76
}
74
77
75
-
fn parse_attribute(xml_root: &str, xml_name: &str) -> Result<String, Error> {
78
+
fn parse_attribute(xml_root: &str, xml_name: &str) -> Result<String> {
76
79
let root = Element::from_reader(xml_root.as_bytes())?;
77
80
let mut xml_name = xml_name.split('/');
78
-
match root.find(xml_name.next().unwrap()) {
81
+
match root.find(
82
+
xml_name
83
+
.next()
84
+
.ok_or_else(|| anyhow!("xml_name ended unexpectedly"))?,
85
+
) {
79
86
Some(element) => {
80
-
let element = element.find(xml_name.next().unwrap());
87
+
let element = element.find(
88
+
xml_name
89
+
.next()
90
+
.ok_or_else(|| anyhow!("xml_name ended unexpectedly"))?,
91
+
);
81
92
match element {
82
93
Some(element) => {
83
94
return Ok(element.text().to_string());
84
95
}
85
96
None => {
86
-
return Ok("".to_string());
97
+
Ok("".to_string())
87
98
}
88
99
}
89
100
}
···
91
102
}
92
103
}
93
104
94
-
pub async fn parse_services(base_url: &str, xml_root: &str) -> Vec<Service> {
95
-
let root = Element::from_reader(xml_root.as_bytes()).unwrap();
105
+
pub async fn parse_services(base_url: &str, xml_root: &str) -> Result<Vec<Service>> {
106
+
let root = Element::from_reader(xml_root.as_bytes())?;
96
107
let device = root
97
108
.find("{urn:schemas-upnp-org:device-1-0}device")
98
-
.unwrap();
109
+
.ok_or_else(|| anyhow!("Invalid response from device"))?;
99
110
100
111
let mut services_with_actions: Vec<Service> = vec![];
101
112
if let Some(service_list) = device.find("{urn:schemas-upnp-org:device-1-0}serviceList") {
102
-
let services = service_list.children();
113
+
let xml_services = service_list.children();
103
114
104
-
let services: Vec<Service> = services
105
-
.into_iter()
106
-
.map(|item| Service {
107
-
service_type: item
115
+
let mut services = Vec::new();
116
+
for xml_service in xml_services {
117
+
let mut service = Service {
118
+
service_type: xml_service
108
119
.find("{urn:schemas-upnp-org:device-1-0}serviceType")
109
-
.unwrap()
120
+
.ok_or_else(|| anyhow!("Service missing serviceType"))?
110
121
.text()
111
122
.to_string(),
112
-
service_id: item
123
+
service_id: xml_service
113
124
.find("{urn:schemas-upnp-org:device-1-0}serviceId")
114
-
.unwrap()
125
+
.ok_or_else(|| anyhow!("Service missing serviceId"))?
115
126
.text()
116
127
.to_string(),
117
-
control_url: item
128
+
control_url: xml_service
118
129
.find("{urn:schemas-upnp-org:device-1-0}controlURL")
119
-
.unwrap()
130
+
.ok_or_else(|| anyhow!("Service missing controlURL"))?
120
131
.text()
121
132
.to_string(),
122
-
event_sub_url: item
133
+
event_sub_url: xml_service
123
134
.find("{urn:schemas-upnp-org:device-1-0}eventSubURL")
124
-
.unwrap()
135
+
.ok_or_else(|| anyhow!("Service missing eventSubURL"))?
125
136
.text()
126
137
.to_string(),
127
-
scpd_url: item
138
+
scpd_url: xml_service
128
139
.find("{urn:schemas-upnp-org:device-1-0}SCPDURL")
129
-
.unwrap()
140
+
.ok_or_else(|| anyhow!("Service missing SCPDURL"))?
130
141
.text()
131
142
.to_string(),
132
143
actions: vec![],
133
-
})
134
-
.map(|mut service| {
135
-
service.control_url = build_absolute_url(base_url, &service.control_url);
136
-
service.event_sub_url = build_absolute_url(base_url, &service.event_sub_url);
137
-
service.scpd_url = build_absolute_url(base_url, &service.scpd_url);
138
-
service
139
-
})
140
-
.collect();
144
+
};
145
+
146
+
service.control_url = build_absolute_url(base_url, &service.control_url)?;
147
+
service.event_sub_url = build_absolute_url(base_url, &service.event_sub_url)?;
148
+
service.scpd_url = build_absolute_url(base_url, &service.scpd_url)?;
149
+
150
+
services.push(service);
151
+
}
141
152
142
153
for service in &services {
143
154
let mut service = service.clone();
144
-
service.actions = parse_service_description(&service.scpd_url).await;
155
+
service.actions = parse_service_description(&service.scpd_url).await?;
145
156
services_with_actions.push(service);
146
157
}
147
158
}
148
159
149
-
services_with_actions
160
+
Ok(services_with_actions)
150
161
}
151
162
152
-
fn build_absolute_url(base_url: &str, relative_url: &str) -> String {
153
-
let base_url = Url::parse(base_url).unwrap();
154
-
base_url.join(relative_url).unwrap().to_string()
163
+
fn build_absolute_url(base_url: &str, relative_url: &str) -> Result<String> {
164
+
let base_url = Url::parse(base_url)?;
165
+
Ok(base_url.join(relative_url)?.to_string())
155
166
}
156
167
157
-
pub async fn parse_service_description(scpd_url: &str) -> Vec<Action> {
168
+
pub async fn parse_service_description(scpd_url: &str) -> Result<Vec<Action>> {
158
169
let client: Client = Config::new()
159
170
.set_timeout(Some(Duration::from_secs(5)))
160
-
.try_into()
161
-
.unwrap();
162
-
let req = surf::Request::new(Method::Get, scpd_url.parse().unwrap());
163
-
if let Ok(xml_root) = client.recv_string(req).await {
164
-
if let Ok(root) = Element::from_reader(xml_root.as_bytes()) {
165
-
let action_list = root.find("{urn:schemas-upnp-org:service-1-0}actionList");
171
+
.try_into()?;
172
+
let req = surf::Request::new(Method::Get, scpd_url.parse()?);
166
173
167
-
if action_list.is_none() {
168
-
return vec![];
169
-
}
174
+
let xml_root = client
175
+
.recv_string(req)
176
+
.await
177
+
.map_err(|e| anyhow!("Failed to retrieve xml response from device: {}", e))?;
178
+
let root = Element::from_reader(xml_root.as_bytes())?;
170
179
171
-
let action_list = action_list.unwrap().children();
172
-
let actions: Vec<Action> = action_list
173
-
.into_iter()
174
-
.map(|item| {
175
-
let name = item
180
+
let action_list = match root.find("{urn:schemas-upnp-org:service-1-0}actionList") {
181
+
Some(action_list) => action_list,
182
+
None => return Ok(vec![]),
183
+
};
184
+
185
+
let mut actions = Vec::new();
186
+
for xml_action in action_list.children() {
187
+
let mut action = Action {
188
+
name: xml_action
189
+
.find("{urn:schemas-upnp-org:service-1-0}name")
190
+
.ok_or_else(|| anyhow!("Service::Action missing name"))?
191
+
.text()
192
+
.to_string(),
193
+
arguments: vec![],
194
+
};
195
+
196
+
if let Some(arguments) = xml_action.find("{urn:schemas-upnp-org:service-1-0}argumentList") {
197
+
for xml_argument in arguments.children() {
198
+
let argument = Argument {
199
+
name: xml_argument
176
200
.find("{urn:schemas-upnp-org:service-1-0}name")
177
-
.unwrap()
178
-
.text();
179
-
let arguments = item.find("{urn:schemas-upnp-org:service-1-0}argumentList");
180
-
let arguments = arguments.unwrap().children();
181
-
let arguments = arguments.into_iter().map(|item| {
182
-
let name = item
183
-
.find("{urn:schemas-upnp-org:service-1-0}name")
184
-
.unwrap()
185
-
.text();
186
-
let direction = item
187
-
.find("{urn:schemas-upnp-org:service-1-0}direction")
188
-
.unwrap()
189
-
.text();
190
-
let related_state_variable = item
191
-
.find("{urn:schemas-upnp-org:service-1-0}relatedStateVariable")
192
-
.unwrap()
193
-
.text();
194
-
Argument {
195
-
name: name.to_string(),
196
-
direction: direction.to_string(),
197
-
related_state_variable: related_state_variable.to_string(),
198
-
}
199
-
});
200
-
Action {
201
-
name: name.to_string(),
202
-
arguments: arguments.collect(),
203
-
}
204
-
})
205
-
.collect();
206
-
return actions;
201
+
.ok_or_else(|| anyhow!("Service::Action::Argument missing name"))?
202
+
.text()
203
+
.to_string(),
204
+
direction: xml_argument
205
+
.find("{urn:schemas-upnp-org:service-1-0}direction")
206
+
.ok_or_else(|| anyhow!("Service::Action::Argument missing direction"))?
207
+
.text()
208
+
.to_string(),
209
+
related_state_variable: xml_argument
210
+
.find("{urn:schemas-upnp-org:service-1-0}relatedStateVariable")
211
+
.ok_or_else(|| {
212
+
anyhow!("Service::Action::Argument missing relatedStateVariable")
213
+
})?
214
+
.text()
215
+
.to_string(),
216
+
};
217
+
action.arguments.push(argument);
218
+
}
207
219
}
220
+
actions.push(action);
208
221
}
209
-
vec![]
222
+
Ok(actions)
210
223
}
211
224
212
-
pub fn parse_volume(xml_root: &str) -> Result<u8, Error> {
225
+
pub fn parse_volume(xml_root: &str) -> Result<u8> {
213
226
let parser = EventReader::from_str(xml_root);
214
227
let mut in_current_volume = false;
215
228
let mut current_volume: Option<u8> = None;
···
227
240
}
228
241
Ok(XmlEvent::Characters(volume)) => {
229
242
if in_current_volume {
230
-
current_volume = Some(volume.parse().unwrap());
243
+
current_volume = Some(volume.parse()?);
231
244
}
232
245
}
233
246
_ => {}
234
247
}
235
248
}
236
-
Ok(current_volume.unwrap())
249
+
current_volume.ok_or_else(|| anyhow!("Invalid response from device"))
237
250
}
238
251
239
-
pub fn parse_duration(xml_root: &str) -> Result<u32, Error> {
252
+
pub fn parse_duration(xml_root: &str) -> Result<u32> {
240
253
let parser = EventReader::from_str(xml_root);
241
254
let mut in_duration = false;
242
255
let mut duration: Option<String> = None;
···
254
267
}
255
268
Ok(XmlEvent::Characters(duration_str)) => {
256
269
if in_duration {
257
-
let duration_str = duration_str.replace(":", "");
270
+
let duration_str = duration_str.replace(':', "");
258
271
duration = Some(duration_str);
259
272
}
260
273
}
···
262
275
}
263
276
}
264
277
265
-
let duration = duration.unwrap();
266
-
let hours = duration[0..2].parse::<u32>().unwrap();
267
-
let minutes = duration[2..4].parse::<u32>().unwrap();
268
-
let seconds = duration[4..6].parse::<u32>().unwrap();
278
+
let duration = duration.ok_or_else(|| anyhow!("Invalid response from device"))?;
279
+
let hours = duration[0..2].parse::<u32>()?;
280
+
let minutes = duration[2..4].parse::<u32>()?;
281
+
let seconds = duration[4..6].parse::<u32>()?;
269
282
Ok(hours * 3600 + minutes * 60 + seconds)
270
283
}
271
284
272
-
pub fn parse_position(xml_root: &str) -> Result<u32, Error> {
285
+
pub fn parse_position(xml_root: &str) -> Result<u32> {
273
286
let parser = EventReader::from_str(xml_root);
274
287
let mut in_position = false;
275
288
let mut position: Option<String> = None;
···
287
300
}
288
301
Ok(XmlEvent::Characters(position_str)) => {
289
302
if in_position {
290
-
let position_str = position_str.replace(":", "");
303
+
let position_str = position_str.replace(':', "");
291
304
position = Some(position_str);
292
305
}
293
306
}
···
295
308
}
296
309
}
297
310
298
-
let position = position.unwrap();
299
-
let hours = position[0..2].parse::<u32>().unwrap();
300
-
let minutes = position[2..4].parse::<u32>().unwrap();
301
-
let seconds = position[4..6].parse::<u32>().unwrap();
311
+
let position = position.ok_or_else(|| anyhow!("Invalid response from device"))?;
312
+
let hours = position[0..2].parse::<u32>()?;
313
+
let minutes = position[2..4].parse::<u32>()?;
314
+
let seconds = position[4..6].parse::<u32>()?;
302
315
Ok(hours * 3600 + minutes * 60 + seconds)
303
316
}
304
317
305
-
pub fn parse_supported_protocols(xml_root: &str) -> Result<Vec<String>, Error> {
318
+
pub fn parse_supported_protocols(xml_root: &str) -> Result<Vec<String>> {
306
319
let parser = EventReader::from_str(xml_root);
307
320
let mut in_protocol = false;
308
321
let mut protocols: String = "".to_string();
···
326
339
_ => {}
327
340
}
328
341
}
329
-
Ok(protocols.split(",").map(|s| s.to_string()).collect())
342
+
Ok(protocols.split(',').map(|s| s.to_string()).collect())
330
343
}
331
344
332
-
pub fn parse_last_change(xml_root: &str) -> Result<Option<String>, Error> {
345
+
pub fn parse_last_change(xml_root: &str) -> Result<Option<String>> {
333
346
let parser = EventReader::from_str(xml_root);
334
347
let mut result = None;
335
348
let mut in_last_change = false;
···
356
369
Ok(result)
357
370
}
358
371
359
-
pub fn parse_current_play_mode(xml_root: &str) -> Result<Option<String>, Error> {
372
+
pub fn parse_current_play_mode(xml_root: &str) -> Result<Option<String>> {
360
373
let parser = EventReader::from_str(xml_root);
361
374
let mut current_play_mode: Option<String> = None;
362
-
for e in parser {
363
-
match e {
364
-
Ok(XmlEvent::StartElement {
365
-
name, attributes, ..
366
-
}) => {
367
-
if name.local_name == "CurrentPlayMode" {
368
-
for attr in attributes {
369
-
if attr.name.local_name == "val" {
370
-
current_play_mode = Some(attr.value);
371
-
}
375
+
for e in parser.into_iter().flatten() {
376
+
if let XmlEvent::StartElement { name, attributes, .. } = e {
377
+
if name.local_name == "CurrentPlayMode" {
378
+
for attr in attributes {
379
+
if attr.name.local_name == "val" {
380
+
current_play_mode = Some(attr.value);
372
381
}
373
382
}
374
383
}
375
-
_ => {}
376
384
}
377
385
}
378
386
Ok(current_play_mode)
379
387
}
380
388
381
-
pub fn parse_transport_state(xml_root: &str) -> Result<Option<String>, Error> {
389
+
pub fn parse_transport_state(xml_root: &str) -> Result<Option<String>> {
382
390
let parser = EventReader::from_str(xml_root);
383
391
let mut transport_state: Option<String> = None;
384
-
for e in parser {
385
-
match e {
386
-
Ok(XmlEvent::StartElement {
387
-
name, attributes, ..
388
-
}) => {
389
-
if name.local_name == "TransportState" {
390
-
for attr in attributes {
391
-
if attr.name.local_name == "val" {
392
-
transport_state = Some(attr.value);
393
-
}
392
+
for e in parser.into_iter().flatten() {
393
+
if let XmlEvent::StartElement { name, attributes, .. } = e {
394
+
if name.local_name == "TransportState" {
395
+
for attr in attributes {
396
+
if attr.name.local_name == "val" {
397
+
transport_state = Some(attr.value);
394
398
}
395
399
}
396
400
}
397
-
_ => {}
398
401
}
399
402
}
400
403
Ok(transport_state)
401
404
}
402
405
403
-
pub fn parse_av_transport_uri_metadata(xml_root: &str) -> Result<Option<String>, Error> {
406
+
pub fn parse_av_transport_uri_metadata(xml_root: &str) -> Result<Option<String>> {
404
407
let parser = EventReader::from_str(xml_root);
405
408
let mut av_transport_uri_metadata: Option<String> = None;
406
-
for e in parser {
407
-
match e {
408
-
Ok(XmlEvent::StartElement {
409
-
name, attributes, ..
410
-
}) => {
411
-
if name.local_name == "AVTransportURIMetaData" {
412
-
for attr in attributes {
413
-
if attr.name.local_name == "val" {
414
-
av_transport_uri_metadata = Some(attr.value);
415
-
}
409
+
for e in parser.into_iter().flatten() {
410
+
if let XmlEvent::StartElement { name, attributes, .. } = e {
411
+
if name.local_name == "AVTransportURIMetaData" {
412
+
for attr in attributes {
413
+
if attr.name.local_name == "val" {
414
+
av_transport_uri_metadata = Some(attr.value);
416
415
}
417
416
}
418
417
}
419
-
_ => {}
420
418
}
421
419
}
422
420
Ok(av_transport_uri_metadata)
423
421
}
424
422
425
-
pub fn parse_current_track_metadata(xml_root: &str) -> Result<Option<String>, Error> {
423
+
pub fn parse_current_track_metadata(xml_root: &str) -> Result<Option<String>> {
426
424
let parser = EventReader::from_str(xml_root);
427
425
let mut current_track_metadata: Option<String> = None;
428
-
for e in parser {
429
-
match e {
430
-
Ok(XmlEvent::StartElement {
431
-
name, attributes, ..
432
-
}) => {
433
-
if name.local_name == "CurrentTrackMetaData" {
434
-
for attr in attributes {
435
-
if attr.name.local_name == "val" {
436
-
current_track_metadata = Some(attr.value);
437
-
}
426
+
for e in parser.into_iter().flatten() {
427
+
if let XmlEvent::StartElement { name, attributes, .. } = e {
428
+
if name.local_name == "CurrentTrackMetaData" {
429
+
for attr in attributes {
430
+
if attr.name.local_name == "val" {
431
+
current_track_metadata = Some(attr.value);
438
432
}
439
433
}
440
434
}
441
-
_ => {}
442
435
}
443
436
}
444
437
Ok(current_track_metadata)
445
438
}
446
439
447
-
pub fn deserialize_metadata(xml: &str) -> Result<Metadata, Error> {
440
+
pub fn deserialize_metadata(xml: &str) -> Result<Metadata> {
448
441
let parser = EventReader::from_str(xml);
449
442
let mut in_title = false;
450
443
let mut in_artist = false;
···
522
515
})
523
516
}
524
517
525
-
pub fn parse_browse_response(xml: &str, ip: &str) -> Result<(Vec<Container>, Vec<Item>), Error> {
518
+
pub fn parse_browse_response(xml: &str, ip: &str) -> Result<(Vec<Container>, Vec<Item>)> {
526
519
let parser = EventReader::from_str(xml);
527
520
let mut in_result = false;
528
521
let mut result: (Vec<Container>, Vec<Item>) = (Vec::new(), Vec::new());
···
550
543
Ok(result)
551
544
}
552
545
553
-
pub fn deserialize_content_directory(
554
-
xml: &str,
555
-
ip: &str,
556
-
) -> Result<(Vec<Container>, Vec<Item>), Error> {
546
+
pub fn deserialize_content_directory(xml: &str, ip: &str) -> Result<(Vec<Container>, Vec<Item>)> {
557
547
let parser = EventReader::from_str(xml);
558
548
let mut in_container = false;
559
549
let mut in_item = false;
···
625
615
items.last_mut().unwrap().protocol_info = attr.value.clone();
626
616
}
627
617
if attr.name.local_name == "size" {
628
-
items.last_mut().unwrap().size =
629
-
Some(attr.value.parse::<u64>().unwrap());
618
+
items.last_mut().unwrap().size = Some(attr.value.parse::<u64>()?);
630
619
}
631
620
if attr.name.local_name == "duration" {
632
621
items.last_mut().unwrap().duration = Some(attr.value.clone());
···
712
701
Ok((containers, items))
713
702
}
714
703
715
-
pub fn parse_transport_info(xml: &str) -> Result<TransportInfo, Error> {
704
+
pub fn parse_transport_info(xml: &str) -> Result<TransportInfo> {
716
705
let parser = EventReader::from_str(xml);
717
706
let mut in_transport_state = false;
718
707
let mut in_transport_status = false;