Better SOAP error logging
Diff
src/device.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/error.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/integration_test.rs | 7 +++++++
3 files changed, 164 insertions(+), 9 deletions(-)
@@ -38,6 +38,9 @@
Stopped,
Playing,
PausedPlayback,
PausedRecording,
Recording,
NoMediaPresent,
Transitioning,
}
@@ -164,7 +167,7 @@
) -> Result<Element> {
let mut headers = Headers::new();
headers.set(ContentType::xml());
headers.set_raw("SOAPAction", format!("{}#{}", service, action));
headers.set_raw("SOAPAction", format!("\"{}#{}\"", service, action));
let client = reqwest::Client::new();
let coordinator = if coordinator { self.coordinator()? } else { self.ip };
@@ -194,14 +197,28 @@
let element =
Element::parse(request).chain_err(|| "Failed to parse XML from Sonos controller")?;
Ok(
element
.get_child("Body")
.ok_or("Failed to get body element")?
let body = element.get_child("Body")
.ok_or("Failed to get body element")?;
if let Some(fault) = body.get_child("Fault") {
let error_code = element_to_string(fault.get_child("detail")
.chain_err(|| ErrorKind::ParseError)?
.get_child("UPnPError")
.chain_err(|| ErrorKind::ParseError)?
.get_child("errorCode")
.chain_err(|| ErrorKind::ParseError)?)
.parse::<u64>()
.chain_err(|| ErrorKind::ParseError)?;
let state = AVTransportError::from(error_code);
error!("Got state {:?} from {}#{} call.", state, service, action);
Err(ErrorKind::from(state).into())
} else {
Ok(body
.get_child(format!("{}Response", action))
.ok_or("Failed to find response element")?
.clone(),
)
.ok_or(format!("Failed to find response element {:?}", element))?
.clone())
}
}
@@ -340,6 +357,26 @@
<EnqueuedURIMetaData></EnqueuedURIMetaData>
<DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued>
<EnqueueAsNext>0</EnqueueAsNext>"#,
uri
),
true,
)?;
Ok(())
}
pub fn queue_next(&self, uri: &str) -> Result<()> {
self.soap(
"MediaRenderer/AVTransport/Control",
"urn:schemas-upnp-org:service:AVTransport:1",
"AddURIToQueue",
&format!(
r#"
<InstanceID>0</InstanceID>
<EnqueuedURI>{}</EnqueuedURI>
<EnqueuedURIMetaData></EnqueuedURIMetaData>
<DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued>
<EnqueueAsNext>1</EnqueueAsNext>"#,
uri
),
true,
@@ -487,6 +524,9 @@
"STOPPED" => TransportState::Stopped,
"PLAYING" => TransportState::Playing,
"PAUSED_PLAYBACK" => TransportState::PausedPlayback,
"PAUSED_RECORDING" => TransportState::PausedRecording,
"RECORDING" => TransportState::Recording,
"NO_MEDIA_PRESENT" => TransportState::NoMediaPresent,
"TRANSITIONING" => TransportState::Transitioning,
_ => TransportState::Stopped,
},
@@ -1,1 +1,109 @@
error_chain!{}
error_chain! {
errors {
AVTransportError(error: AVTransportError) {
description("An error occurred from AVTransport")
display("Received error {:?} from Sonos speaker", error)
}
ParseError {
description("An error occurred when attempting to parse SOAP XML from Sonos")
display("Failed to parse Sonos response XML")
}
}
}
impl From<AVTransportError> for ErrorKind {
fn from(error: AVTransportError) -> Self {
ErrorKind::AVTransportError(error)
}
}
#[derive(Debug)]
pub enum AVTransportError {
InvalidAction = 401,
InvalidArgs = 402,
InvalidVar = 404,
ActionFailed = 501,
TransitionNotAvailable = 701,
NoContents = 702,
ReadError = 703,
FormatNotSupported = 704,
TransportLocked = 705,
WriteError = 706,
MediaNotWriteable = 707,
RecordingFormatNotSupported = 708,
MediaFull = 709,
SeekModeNotSupported = 710,
IllegalSeekTarget = 711,
PlayModeNotSupported = 712,
RecordQualityNotSupported = 713,
IllegalMimeType = 714,
ContentBusy = 715,
PlaySpeedNotSupported = 717,
InvalidInstanceId = 718,
NoDnsServer = 737,
BadDomainName = 738,
ServerError = 739,
Unknown,
}
impl From<u64> for AVTransportError {
fn from(code: u64) -> AVTransportError {
match code {
401 => AVTransportError::InvalidAction,
402 => AVTransportError::InvalidArgs,
404 => AVTransportError::InvalidVar,
501 => AVTransportError::ActionFailed,
701 => AVTransportError::TransitionNotAvailable,
702 => AVTransportError::NoContents,
703 => AVTransportError::ReadError,
704 => AVTransportError::FormatNotSupported,
705 => AVTransportError::TransportLocked,
706 => AVTransportError::WriteError,
707 => AVTransportError::MediaNotWriteable,
708 => AVTransportError::RecordingFormatNotSupported,
709 => AVTransportError::MediaFull,
710 => AVTransportError::SeekModeNotSupported,
711 => AVTransportError::IllegalSeekTarget,
712 => AVTransportError::PlayModeNotSupported,
713 => AVTransportError::RecordQualityNotSupported,
714 => AVTransportError::IllegalMimeType,
715 => AVTransportError::ContentBusy,
717 => AVTransportError::PlaySpeedNotSupported,
718 => AVTransportError::InvalidInstanceId,
737 => AVTransportError::NoDnsServer,
738 => AVTransportError::BadDomainName,
739 => AVTransportError::ServerError,
_ => AVTransportError::Unknown,
}
}
}
@@ -74,6 +74,13 @@
}
#[test]
fn seek() {
let device = get_speaker();
device.seek(&std::time::Duration::from_secs(30)).expect("Failed to seek to 30 seconds");
assert_eq!(device.track().expect("Failed to get track info").running_time.as_secs(), 30);
}
#[test]
fn play() {
let device = get_speaker();
device.play().expect("Failed to play");