WIP
This commit is contained in:
parent
7eec34ce00
commit
39f5833767
|
@ -18,6 +18,7 @@ tokio = { version = "1.37.0", features = ["sync", "macros", "rt", "net"] }
|
|||
tokio-util = { version = "0.7.10", features = ["codec"] }
|
||||
futures = "0.3.30"
|
||||
tokio-stream = { version = "0.1.15", features = ["sync"] }
|
||||
thiserror = "1.0.59"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.10.0"
|
||||
|
|
|
@ -12,7 +12,7 @@ use tokio::{
|
|||
use crate::{
|
||||
ipc::{MpvIpc, MpvIpcCommand, MpvIpcEvent, MpvIpcResponse},
|
||||
message_parser::TypeHandler,
|
||||
Error, ErrorCode, Event,
|
||||
Event, MpvError,
|
||||
};
|
||||
|
||||
/// All possible commands that can be sent to mpv.
|
||||
|
@ -134,14 +134,14 @@ impl IntoRawCommandPart for SeekOptions {
|
|||
pub trait GetPropertyTypeHandler: Sized {
|
||||
// TODO: fix this
|
||||
#[allow(async_fn_in_trait)]
|
||||
async fn get_property_generic(instance: &Mpv, property: &str) -> Result<Self, Error>;
|
||||
async fn get_property_generic(instance: &Mpv, property: &str) -> Result<Self, MpvError>;
|
||||
}
|
||||
|
||||
impl<T> GetPropertyTypeHandler for T
|
||||
where
|
||||
T: TypeHandler,
|
||||
{
|
||||
async fn get_property_generic(instance: &Mpv, property: &str) -> Result<T, Error> {
|
||||
async fn get_property_generic(instance: &Mpv, property: &str) -> Result<T, MpvError> {
|
||||
instance
|
||||
.get_property_value(property)
|
||||
.await
|
||||
|
@ -153,17 +153,22 @@ where
|
|||
pub trait SetPropertyTypeHandler<T> {
|
||||
// TODO: fix this
|
||||
#[allow(async_fn_in_trait)]
|
||||
async fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error>;
|
||||
async fn set_property_generic(instance: &Mpv, property: &str, value: T)
|
||||
-> Result<(), MpvError>;
|
||||
}
|
||||
|
||||
impl<T> SetPropertyTypeHandler<T> for T
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
async fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error> {
|
||||
async fn set_property_generic(
|
||||
instance: &Mpv,
|
||||
property: &str,
|
||||
value: T,
|
||||
) -> Result<(), MpvError> {
|
||||
let (res_tx, res_rx) = oneshot::channel();
|
||||
let value = serde_json::to_value(value)
|
||||
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?;
|
||||
let value = serde_json::to_value(value).map_err(|why| MpvError::JsonParseError(why))?;
|
||||
|
||||
instance
|
||||
.command_sender
|
||||
.send((
|
||||
|
@ -171,15 +176,11 @@ where
|
|||
res_tx,
|
||||
))
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error(ErrorCode::ConnectError(
|
||||
"Failed to send command".to_string(),
|
||||
))
|
||||
})?;
|
||||
.map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
|
||||
|
||||
match res_rx.await {
|
||||
Ok(MpvIpcResponse(response)) => response.map(|_| ()),
|
||||
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
||||
Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,18 +206,18 @@ impl fmt::Debug for Mpv {
|
|||
}
|
||||
|
||||
impl Mpv {
|
||||
pub async fn connect(socket_path: &str) -> Result<Mpv, Error> {
|
||||
pub async fn connect(socket_path: &str) -> Result<Mpv, MpvError> {
|
||||
log::debug!("Connecting to mpv socket at {}", socket_path);
|
||||
|
||||
let socket = match UnixStream::connect(socket_path).await {
|
||||
Ok(stream) => Ok(stream),
|
||||
Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))),
|
||||
Err(err) => Err(MpvError::MpvSocketConnectionError(err.to_string())),
|
||||
}?;
|
||||
|
||||
Self::connect_socket(socket).await
|
||||
}
|
||||
|
||||
pub async fn connect_socket(socket: UnixStream) -> Result<Mpv, Error> {
|
||||
pub async fn connect_socket(socket: UnixStream) -> Result<Mpv, MpvError> {
|
||||
let (com_tx, com_rx) = mpsc::channel(100);
|
||||
let (ev_tx, _) = broadcast::channel(100);
|
||||
let ipc = MpvIpc::new(socket, com_rx, ev_tx.clone());
|
||||
|
@ -230,29 +231,24 @@ impl Mpv {
|
|||
})
|
||||
}
|
||||
|
||||
pub async fn disconnect(&self) -> Result<(), Error> {
|
||||
pub async fn disconnect(&self) -> Result<(), MpvError> {
|
||||
let (res_tx, res_rx) = oneshot::channel();
|
||||
self.command_sender
|
||||
.send((MpvIpcCommand::Exit, res_tx))
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error(ErrorCode::ConnectError(
|
||||
"Failed to send command".to_string(),
|
||||
))
|
||||
})?;
|
||||
.map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
|
||||
|
||||
match res_rx.await {
|
||||
Ok(MpvIpcResponse(response)) => response.map(|_| ()),
|
||||
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
||||
Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_event_stream(&self) -> impl futures::Stream<Item = Result<Event, Error>> {
|
||||
pub async fn get_event_stream(&self) -> impl futures::Stream<Item = Result<Event, MpvError>> {
|
||||
tokio_stream::wrappers::BroadcastStream::new(self.broadcast_channel.subscribe()).map(
|
||||
|event| match event {
|
||||
Ok(event) => crate::event_parser::parse_event(event),
|
||||
Err(_) => Err(Error(ErrorCode::ConnectError(
|
||||
"Failed to receive event".to_string(),
|
||||
))),
|
||||
Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -264,7 +260,7 @@ impl Mpv {
|
|||
&self,
|
||||
command: &str,
|
||||
args: &[&str],
|
||||
) -> Result<Option<Value>, Error> {
|
||||
) -> Result<Option<Value>, MpvError> {
|
||||
let command = Vec::from(
|
||||
[command]
|
||||
.iter()
|
||||
|
@ -277,15 +273,11 @@ impl Mpv {
|
|||
self.command_sender
|
||||
.send((MpvIpcCommand::Command(command), res_tx))
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error(ErrorCode::ConnectError(
|
||||
"Failed to send command".to_string(),
|
||||
))
|
||||
})?;
|
||||
.map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
|
||||
|
||||
match res_rx.await {
|
||||
Ok(MpvIpcResponse(response)) => response,
|
||||
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
||||
Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,7 +285,7 @@ impl Mpv {
|
|||
&self,
|
||||
command: &str,
|
||||
args: &[&str],
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<(), MpvError> {
|
||||
self.run_command_raw(command, args).await.map(|_| ())
|
||||
}
|
||||
|
||||
|
@ -323,7 +315,7 @@ impl Mpv {
|
|||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn run_command(&self, command: MpvCommand) -> Result<(), Error> {
|
||||
pub async fn run_command(&self, command: MpvCommand) -> Result<(), MpvError> {
|
||||
log::trace!("Running command: {:?}", command);
|
||||
let result = match command {
|
||||
MpvCommand::LoadFile { file, option } => {
|
||||
|
@ -345,15 +337,11 @@ impl Mpv {
|
|||
self.command_sender
|
||||
.send((MpvIpcCommand::ObserveProperty(id, property), res_tx))
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error(ErrorCode::ConnectError(
|
||||
"Failed to send command".to_string(),
|
||||
))
|
||||
})?;
|
||||
.map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
|
||||
|
||||
match res_rx.await {
|
||||
Ok(MpvIpcResponse(response)) => response.map(|_| ()),
|
||||
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
||||
Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
|
||||
}
|
||||
}
|
||||
MpvCommand::PlaylistClear => {
|
||||
|
@ -412,10 +400,11 @@ impl Mpv {
|
|||
self.command_sender
|
||||
.send((MpvIpcCommand::UnobserveProperty(id), res_tx))
|
||||
.await
|
||||
.unwrap();
|
||||
.map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
|
||||
|
||||
match res_rx.await {
|
||||
Ok(MpvIpcResponse(response)) => response.map(|_| ()),
|
||||
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
||||
Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -452,7 +441,7 @@ impl Mpv {
|
|||
pub async fn get_property<T: GetPropertyTypeHandler>(
|
||||
&self,
|
||||
property: &str,
|
||||
) -> Result<T, Error> {
|
||||
) -> Result<T, MpvError> {
|
||||
T::get_property_generic(self, property).await
|
||||
}
|
||||
|
||||
|
@ -475,21 +464,18 @@ impl Mpv {
|
|||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_property_value(&self, property: &str) -> Result<Value, Error> {
|
||||
pub async fn get_property_value(&self, property: &str) -> Result<Value, MpvError> {
|
||||
let (res_tx, res_rx) = oneshot::channel();
|
||||
self.command_sender
|
||||
.send((MpvIpcCommand::GetProperty(property.to_owned()), res_tx))
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error(ErrorCode::ConnectError(
|
||||
"Failed to send command".to_string(),
|
||||
))
|
||||
})?;
|
||||
.map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
|
||||
|
||||
match res_rx.await {
|
||||
Ok(MpvIpcResponse(response)) => {
|
||||
response.and_then(|value| value.ok_or(Error(ErrorCode::MissingValue)))
|
||||
response.and_then(|value| value.ok_or(MpvError::MissingMpvData))
|
||||
}
|
||||
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
||||
Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -521,7 +507,7 @@ impl Mpv {
|
|||
&self,
|
||||
property: &str,
|
||||
value: T,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<(), MpvError> {
|
||||
T::set_property_generic(self, property, value).await
|
||||
}
|
||||
}
|
||||
|
|
136
src/error.rs
136
src/error.rs
|
@ -1,72 +1,84 @@
|
|||
//! Library specific error messages.
|
||||
|
||||
use core::fmt;
|
||||
use std::fmt::Display;
|
||||
use thiserror::Error;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// All possible errors that can occur when interacting with mpv.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ErrorCode {
|
||||
MpvError(String),
|
||||
JsonParseError(String),
|
||||
ConnectError(String),
|
||||
JsonContainsUnexptectedType,
|
||||
UnexpectedResult,
|
||||
UnexpectedValue,
|
||||
MissingValue,
|
||||
UnsupportedType,
|
||||
ValueDoesNotContainBool,
|
||||
ValueDoesNotContainF64,
|
||||
ValueDoesNotContainHashMap,
|
||||
ValueDoesNotContainPlaylist,
|
||||
ValueDoesNotContainString,
|
||||
ValueDoesNotContainUsize,
|
||||
}
|
||||
use serde_json::{error, Map, Value};
|
||||
use tokio_util::codec::LinesCodecError;
|
||||
|
||||
/// Any error that can occur when interacting with mpv.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Error(pub ErrorCode);
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MpvError {
|
||||
#[error("MpvError: {0}")]
|
||||
MpvError(String),
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
#[error("Error communicating over mpv socket: {0}")]
|
||||
MpvSocketConnectionError(String),
|
||||
|
||||
#[error("Internal connection error: {0}")]
|
||||
InternalConnectionError(String),
|
||||
|
||||
#[error("JsonParseError: {0}")]
|
||||
JsonParseError(#[from] serde_json::Error),
|
||||
|
||||
#[error("Mpv sent a value with an unexpected type")]
|
||||
ValueContainsUnexpectedType {
|
||||
expected: String,
|
||||
received: Value,
|
||||
},
|
||||
|
||||
#[error("Missing expected data in mpv message")]
|
||||
MissingMpvData,
|
||||
|
||||
#[error("Missing key in object")]
|
||||
MissingKeyInObject {
|
||||
key: String,
|
||||
map: Map<String, Value>,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
// #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
// pub struct Error(pub ErrorCode);
|
||||
|
||||
impl Display for ErrorCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ErrorCode::ConnectError(ref msg) => f.write_str(&format!("ConnectError: {}", msg)),
|
||||
ErrorCode::JsonParseError(ref msg) => f.write_str(&format!("JsonParseError: {}", msg)),
|
||||
ErrorCode::MpvError(ref msg) => f.write_str(&format!("MpvError: {}", msg)),
|
||||
ErrorCode::JsonContainsUnexptectedType => {
|
||||
f.write_str("Mpv sent a value with an unexpected type")
|
||||
}
|
||||
ErrorCode::UnexpectedResult => f.write_str("Unexpected result received"),
|
||||
ErrorCode::UnexpectedValue => f.write_str("Unexpected value received"),
|
||||
ErrorCode::MissingValue => f.write_str("Missing value"),
|
||||
ErrorCode::UnsupportedType => f.write_str("Unsupported type received"),
|
||||
ErrorCode::ValueDoesNotContainBool => {
|
||||
f.write_str("The received value is not of type \'std::bool\'")
|
||||
}
|
||||
ErrorCode::ValueDoesNotContainF64 => {
|
||||
f.write_str("The received value is not of type \'std::f64\'")
|
||||
}
|
||||
ErrorCode::ValueDoesNotContainHashMap => {
|
||||
f.write_str("The received value is not of type \'std::collections::HashMap\'")
|
||||
}
|
||||
ErrorCode::ValueDoesNotContainPlaylist => {
|
||||
f.write_str("The received value is not of type \'mpvipc::Playlist\'")
|
||||
}
|
||||
ErrorCode::ValueDoesNotContainString => {
|
||||
f.write_str("The received value is not of type \'std::string::String\'")
|
||||
}
|
||||
ErrorCode::ValueDoesNotContainUsize => {
|
||||
f.write_str("The received value is not of type \'std::usize\'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// impl Display for Error {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// Display::fmt(&self.0, f)
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl std::error::Error for Error {}
|
||||
|
||||
// impl Display for ErrorCode {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// match *self {
|
||||
// ErrorCode::ConnectError(ref msg) => f.write_str(&format!("ConnectError: {}", msg)),
|
||||
// ErrorCode::JsonParseError(ref msg) => f.write_str(&format!("JsonParseError: {}", msg)),
|
||||
// ErrorCode::MpvError(ref msg) => f.write_str(&format!("MpvError: {}", msg)),
|
||||
// ErrorCode::JsonContainsUnexptectedType => {
|
||||
// f.write_str("Mpv sent a value with an unexpected type")
|
||||
// }
|
||||
// ErrorCode::UnexpectedResult => f.write_str("Unexpected result received"),
|
||||
// ErrorCode::UnexpectedValue => f.write_str("Unexpected value received"),
|
||||
// ErrorCode::MissingValue => f.write_str("Missing value"),
|
||||
// ErrorCode::UnsupportedType => f.write_str("Unsupported type received"),
|
||||
// ErrorCode::ValueDoesNotContainBool => {
|
||||
// f.write_str("The received value is not of type \'std::bool\'")
|
||||
// }
|
||||
// ErrorCode::ValueDoesNotContainF64 => {
|
||||
// f.write_str("The received value is not of type \'std::f64\'")
|
||||
// }
|
||||
// ErrorCode::ValueDoesNotContainHashMap => {
|
||||
// f.write_str("The received value is not of type \'std::collections::HashMap\'")
|
||||
// }
|
||||
// ErrorCode::ValueDoesNotContainPlaylist => {
|
||||
// f.write_str("The received value is not of type \'mpvipc::Playlist\'")
|
||||
// }
|
||||
// ErrorCode::ValueDoesNotContainString => {
|
||||
// f.write_str("The received value is not of type \'std::string::String\'")
|
||||
// }
|
||||
// ErrorCode::ValueDoesNotContainUsize => {
|
||||
// f.write_str("The received value is not of type \'std::usize\'")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::str::FromStr;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::{ipc::MpvIpcEvent, message_parser::json_to_value, Error, ErrorCode, MpvDataType};
|
||||
use crate::{ipc::MpvIpcEvent, message_parser::json_to_value, MpvDataType, MpvError};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
|
@ -147,6 +147,37 @@ pub enum Event {
|
|||
Unimplemented(Map<String, Value>),
|
||||
}
|
||||
|
||||
macro_rules! get_key_as {
|
||||
($as_type:ident, $key:expr, $event:ident) => {{
|
||||
let tmp = $event.get($key).ok_or(MpvError::MissingKeyInObject {
|
||||
key: $key.to_owned(),
|
||||
map: $event.clone(),
|
||||
})?;
|
||||
|
||||
tmp.$as_type()
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: stringify!($as_type).strip_prefix("as_").unwrap().to_owned(),
|
||||
received: tmp.clone(),
|
||||
})?
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! get_optional_key_as {
|
||||
($as_type:ident, $key:expr, $event:ident) => {{
|
||||
if let Some(tmp) = $event.get($key) {
|
||||
Some(
|
||||
tmp.$as_type()
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: stringify!($as_type).strip_prefix("as_").unwrap().to_owned(),
|
||||
received: tmp.clone(),
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// NOTE: I have not been able to test all of these events,
|
||||
// so some of the parsing logic might be incorrect.
|
||||
// In particular, I have not been able to make mpv
|
||||
|
@ -157,19 +188,17 @@ pub enum Event {
|
|||
// If you need this, please open an issue or a PR.
|
||||
|
||||
/// Parse a highlevel [`Event`] objects from json.
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
|
||||
pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, MpvError> {
|
||||
let MpvIpcEvent(event) = raw_event;
|
||||
|
||||
event
|
||||
.as_object()
|
||||
.ok_or(Error(ErrorCode::JsonContainsUnexptectedType))
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "object".to_owned(),
|
||||
received: event.clone(),
|
||||
})
|
||||
.and_then(|event| {
|
||||
let event_name = event
|
||||
.get("event")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
|
||||
let event_name = get_key_as!(as_str, "event", event);
|
||||
|
||||
match event_name {
|
||||
"start-file" => parse_start_file(event),
|
||||
|
@ -200,35 +229,20 @@ pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
|
|||
})
|
||||
}
|
||||
|
||||
fn parse_start_file(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let playlist_entry_id = event
|
||||
.get("playlist_entry_id")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_u64()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
|
||||
fn parse_start_file(event: &Map<String, Value>) -> Result<Event, MpvError> {
|
||||
let playlist_entry_id = get_key_as!(as_u64, "playlist_entry_id", event) as usize;
|
||||
|
||||
Ok(Event::StartFile { playlist_entry_id })
|
||||
}
|
||||
|
||||
fn parse_end_file(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let reason = event
|
||||
.get("reason")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
|
||||
let playlist_entry_id = event
|
||||
.get("playlist_entry_id")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_u64()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
|
||||
let file_error = event
|
||||
.get("file_error")
|
||||
.and_then(|v| v.as_str().map(|s| s.to_string()));
|
||||
let playlist_insert_id = event
|
||||
.get("playlist_insert_id")
|
||||
.and_then(|v| v.as_u64().map(|u| u as usize));
|
||||
let playlist_insert_num_entries = event
|
||||
.get("playlist_insert_num_entries")
|
||||
.and_then(|v| v.as_u64().map(|u| u as usize));
|
||||
fn parse_end_file(event: &Map<String, Value>) -> Result<Event, MpvError> {
|
||||
let reason = get_key_as!(as_str, "reason", event);
|
||||
let playlist_entry_id = get_key_as!(as_u64, "playlist_entry_id", event) as usize;
|
||||
let file_error = get_optional_key_as!(as_str, "file_error", event).map(|s| s.to_string());
|
||||
let playlist_insert_id =
|
||||
get_optional_key_as!(as_u64, "playlist_insert_id", event).map(|i| i as usize);
|
||||
let playlist_insert_num_entries =
|
||||
get_optional_key_as!(as_u64, "playlist_insert_num_entries", event).map(|i| i as usize);
|
||||
|
||||
Ok(Event::EndFile {
|
||||
reason: reason
|
||||
|
@ -241,24 +255,10 @@ fn parse_end_file(event: &Map<String, Value>) -> Result<Event, Error> {
|
|||
})
|
||||
}
|
||||
|
||||
fn parse_log_message(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let prefix = event
|
||||
.get("prefix")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?
|
||||
.to_string();
|
||||
let level = event
|
||||
.get("level")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
|
||||
let text = event
|
||||
.get("text")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?
|
||||
.to_string();
|
||||
fn parse_log_message(event: &Map<String, Value>) -> Result<Event, MpvError> {
|
||||
let prefix = get_key_as!(as_str, "prefix", event).to_owned();
|
||||
let level = get_key_as!(as_str, "level", event);
|
||||
let text = get_key_as!(as_str, "text", event).to_owned();
|
||||
|
||||
Ok(Event::LogMessage {
|
||||
prefix,
|
||||
|
@ -269,45 +269,35 @@ fn parse_log_message(event: &Map<String, Value>) -> Result<Event, Error> {
|
|||
})
|
||||
}
|
||||
|
||||
fn parse_hook(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let hook_id = event
|
||||
.get("hook_id")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_u64()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
|
||||
fn parse_hook(event: &Map<String, Value>) -> Result<Event, MpvError> {
|
||||
let hook_id = get_key_as!(as_u64, "hook_id", event) as usize;
|
||||
Ok(Event::Hook { hook_id })
|
||||
}
|
||||
|
||||
fn parse_client_message(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let args = event
|
||||
.get("args")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_array()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?
|
||||
fn parse_client_message(event: &Map<String, Value>) -> Result<Event, MpvError> {
|
||||
let args = get_key_as!(as_array, "args", event)
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
arg.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "string".to_owned(),
|
||||
received: arg.clone(),
|
||||
})
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
.collect::<Result<Vec<String>, Error>>()?;
|
||||
.collect::<Result<Vec<String>, MpvError>>()?;
|
||||
Ok(Event::ClientMessage { args })
|
||||
}
|
||||
|
||||
fn parse_property_change(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let id = event
|
||||
.get("id")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_u64()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
|
||||
let property_name = event
|
||||
.get("name")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
|
||||
fn parse_property_change(event: &Map<String, Value>) -> Result<Event, MpvError> {
|
||||
let id = get_key_as!(as_u64, "id", event) as usize;
|
||||
let property_name = get_key_as!(as_str, "name", event);
|
||||
let data = event
|
||||
.get("data")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.ok_or(MpvError::MissingKeyInObject {
|
||||
key: "data".to_owned(),
|
||||
map: event.clone(),
|
||||
})?
|
||||
.clone();
|
||||
|
||||
Ok(Event::PropertyChange {
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::collections::HashMap;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Error, ErrorCode, Event, MpvDataType, PlaylistEntry};
|
||||
use crate::{MpvError, Event, MpvDataType, PlaylistEntry};
|
||||
|
||||
/// All possible properties that can be observed through the event system.
|
||||
///
|
||||
|
@ -46,7 +46,7 @@ pub enum LoopProperty {
|
|||
}
|
||||
|
||||
/// Parse a highlevel [`Property`] object from json, used for [`Event::PropertyChange`].
|
||||
pub fn parse_event_property(event: Event) -> Result<(usize, Property), Error> {
|
||||
pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError> {
|
||||
let (id, name, data) = match event {
|
||||
Event::PropertyChange { id, name, data } => (id, name, data),
|
||||
// TODO: return proper error
|
||||
|
@ -60,7 +60,10 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), Error> {
|
|||
let path = match data {
|
||||
MpvDataType::String(s) => Some(s),
|
||||
MpvDataType::Null => None,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
|
||||
_ => return Err(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "String".to_string(),
|
||||
received: data,
|
||||
}),
|
||||
};
|
||||
Ok((id, Property::Path(path)))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! High-level API extension for [`Mpv`].
|
||||
|
||||
use crate::{
|
||||
Error, IntoRawCommandPart, Mpv, MpvCommand, MpvDataType, Playlist, PlaylistAddOptions,
|
||||
MpvError, IntoRawCommandPart, Mpv, MpvCommand, MpvDataType, Playlist, PlaylistAddOptions,
|
||||
PlaylistEntry, SeekOptions,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -44,58 +44,58 @@ pub enum PlaylistAddTypeOptions {
|
|||
// TODO: fix this
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait MpvExt {
|
||||
async fn toggle(&self) -> Result<(), Error>;
|
||||
async fn stop(&self) -> Result<(), Error>;
|
||||
async fn toggle(&self) -> Result<(), MpvError>;
|
||||
async fn stop(&self) -> Result<(), MpvError>;
|
||||
async fn set_volume(&self, input_volume: f64, option: NumberChangeOptions)
|
||||
-> Result<(), Error>;
|
||||
async fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error>;
|
||||
async fn set_mute(&self, option: Switch) -> Result<(), Error>;
|
||||
async fn set_loop_playlist(&self, option: Switch) -> Result<(), Error>;
|
||||
async fn set_loop_file(&self, option: Switch) -> Result<(), Error>;
|
||||
async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), Error>;
|
||||
async fn playlist_shuffle(&self) -> Result<(), Error>;
|
||||
async fn playlist_remove_id(&self, id: usize) -> Result<(), Error>;
|
||||
async fn playlist_play_next(&self, id: usize) -> Result<(), Error>;
|
||||
async fn playlist_play_id(&self, id: usize) -> Result<(), Error>;
|
||||
async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), Error>;
|
||||
async fn playlist_clear(&self) -> Result<(), Error>;
|
||||
-> Result<(), MpvError>;
|
||||
async fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), MpvError>;
|
||||
async fn set_mute(&self, option: Switch) -> Result<(), MpvError>;
|
||||
async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError>;
|
||||
async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError>;
|
||||
async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError>;
|
||||
async fn playlist_shuffle(&self) -> Result<(), MpvError>;
|
||||
async fn playlist_remove_id(&self, id: usize) -> Result<(), MpvError>;
|
||||
async fn playlist_play_next(&self, id: usize) -> Result<(), MpvError>;
|
||||
async fn playlist_play_id(&self, id: usize) -> Result<(), MpvError>;
|
||||
async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), MpvError>;
|
||||
async fn playlist_clear(&self) -> Result<(), MpvError>;
|
||||
async fn playlist_add(
|
||||
&self,
|
||||
file: &str,
|
||||
file_type: PlaylistAddTypeOptions,
|
||||
option: PlaylistAddOptions,
|
||||
) -> Result<(), Error>;
|
||||
async fn restart(&self) -> Result<(), Error>;
|
||||
async fn prev(&self) -> Result<(), Error>;
|
||||
async fn pause(&self) -> Result<(), Error>;
|
||||
async fn unobserve_property(&self, id: isize) -> Result<(), Error>;
|
||||
async fn observe_property(&self, id: isize, property: &str) -> Result<(), Error>;
|
||||
async fn next(&self) -> Result<(), Error>;
|
||||
async fn kill(&self) -> Result<(), Error>;
|
||||
async fn get_playlist(&self) -> Result<Playlist, Error>;
|
||||
async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, Error>;
|
||||
) -> Result<(), MpvError>;
|
||||
async fn restart(&self) -> Result<(), MpvError>;
|
||||
async fn prev(&self) -> Result<(), MpvError>;
|
||||
async fn pause(&self) -> Result<(), MpvError>;
|
||||
async fn unobserve_property(&self, id: isize) -> Result<(), MpvError>;
|
||||
async fn observe_property(&self, id: isize, property: &str) -> Result<(), MpvError>;
|
||||
async fn next(&self) -> Result<(), MpvError>;
|
||||
async fn kill(&self) -> Result<(), MpvError>;
|
||||
async fn get_playlist(&self) -> Result<Playlist, MpvError>;
|
||||
async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError>;
|
||||
}
|
||||
|
||||
impl MpvExt for Mpv {
|
||||
async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, Error> {
|
||||
async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError> {
|
||||
self.get_property("metadata").await
|
||||
}
|
||||
|
||||
async fn get_playlist(&self) -> Result<Playlist, Error> {
|
||||
async fn get_playlist(&self) -> Result<Playlist, MpvError> {
|
||||
self.get_property::<Vec<PlaylistEntry>>("playlist")
|
||||
.await
|
||||
.map(Playlist)
|
||||
}
|
||||
|
||||
async fn kill(&self) -> Result<(), Error> {
|
||||
async fn kill(&self) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::Quit).await
|
||||
}
|
||||
|
||||
async fn next(&self) -> Result<(), Error> {
|
||||
async fn next(&self) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::PlaylistNext).await
|
||||
}
|
||||
|
||||
async fn observe_property(&self, id: isize, property: &str) -> Result<(), Error> {
|
||||
async fn observe_property(&self, id: isize, property: &str) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::Observe {
|
||||
id,
|
||||
property: property.to_string(),
|
||||
|
@ -103,19 +103,19 @@ impl MpvExt for Mpv {
|
|||
.await
|
||||
}
|
||||
|
||||
async fn unobserve_property(&self, id: isize) -> Result<(), Error> {
|
||||
async fn unobserve_property(&self, id: isize) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::Unobserve(id)).await
|
||||
}
|
||||
|
||||
async fn pause(&self) -> Result<(), Error> {
|
||||
async fn pause(&self) -> Result<(), MpvError> {
|
||||
self.set_property("pause", true).await
|
||||
}
|
||||
|
||||
async fn prev(&self) -> Result<(), Error> {
|
||||
async fn prev(&self) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::PlaylistPrev).await
|
||||
}
|
||||
|
||||
async fn restart(&self) -> Result<(), Error> {
|
||||
async fn restart(&self) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::Seek {
|
||||
seconds: 0f64,
|
||||
option: SeekOptions::Absolute,
|
||||
|
@ -128,7 +128,7 @@ impl MpvExt for Mpv {
|
|||
file: &str,
|
||||
file_type: PlaylistAddTypeOptions,
|
||||
option: PlaylistAddOptions,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<(), MpvError> {
|
||||
match file_type {
|
||||
PlaylistAddTypeOptions::File => {
|
||||
self.run_command(MpvCommand::LoadFile {
|
||||
|
@ -148,20 +148,20 @@ impl MpvExt for Mpv {
|
|||
}
|
||||
}
|
||||
|
||||
async fn playlist_clear(&self) -> Result<(), Error> {
|
||||
async fn playlist_clear(&self) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::PlaylistClear).await
|
||||
}
|
||||
|
||||
async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), Error> {
|
||||
async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::PlaylistMove { from, to })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn playlist_play_id(&self, id: usize) -> Result<(), Error> {
|
||||
async fn playlist_play_id(&self, id: usize) -> Result<(), MpvError> {
|
||||
self.set_property("playlist-pos", id).await
|
||||
}
|
||||
|
||||
async fn playlist_play_next(&self, id: usize) -> Result<(), Error> {
|
||||
async fn playlist_play_next(&self, id: usize) -> Result<(), MpvError> {
|
||||
match self.get_property::<usize>("playlist-pos").await {
|
||||
Ok(current_id) => {
|
||||
self.run_command(MpvCommand::PlaylistMove {
|
||||
|
@ -174,19 +174,19 @@ impl MpvExt for Mpv {
|
|||
}
|
||||
}
|
||||
|
||||
async fn playlist_remove_id(&self, id: usize) -> Result<(), Error> {
|
||||
async fn playlist_remove_id(&self, id: usize) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::PlaylistRemove(id)).await
|
||||
}
|
||||
|
||||
async fn playlist_shuffle(&self) -> Result<(), Error> {
|
||||
async fn playlist_shuffle(&self) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::PlaylistShuffle).await
|
||||
}
|
||||
|
||||
async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), Error> {
|
||||
async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::Seek { seconds, option }).await
|
||||
}
|
||||
|
||||
async fn set_loop_file(&self, option: Switch) -> Result<(), Error> {
|
||||
async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError> {
|
||||
let enabled = match option {
|
||||
Switch::On => "inf",
|
||||
Switch::Off => "no",
|
||||
|
@ -203,7 +203,7 @@ impl MpvExt for Mpv {
|
|||
self.set_property("loop-file", enabled).await
|
||||
}
|
||||
|
||||
async fn set_loop_playlist(&self, option: Switch) -> Result<(), Error> {
|
||||
async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError> {
|
||||
let enabled = match option {
|
||||
Switch::On => "inf",
|
||||
Switch::Off => "no",
|
||||
|
@ -220,7 +220,7 @@ impl MpvExt for Mpv {
|
|||
self.set_property("loo-playlist", enabled).await
|
||||
}
|
||||
|
||||
async fn set_mute(&self, option: Switch) -> Result<(), Error> {
|
||||
async fn set_mute(&self, option: Switch) -> Result<(), MpvError> {
|
||||
let enabled = match option {
|
||||
Switch::On => "yes",
|
||||
Switch::Off => "no",
|
||||
|
@ -237,7 +237,7 @@ impl MpvExt for Mpv {
|
|||
self.set_property("mute", enabled).await
|
||||
}
|
||||
|
||||
async fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error> {
|
||||
async fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), MpvError> {
|
||||
match self.get_property::<f64>("speed").await {
|
||||
Ok(speed) => match option {
|
||||
NumberChangeOptions::Increase => {
|
||||
|
@ -258,7 +258,7 @@ impl MpvExt for Mpv {
|
|||
&self,
|
||||
input_volume: f64,
|
||||
option: NumberChangeOptions,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<(), MpvError> {
|
||||
match self.get_property::<f64>("volume").await {
|
||||
Ok(volume) => match option {
|
||||
NumberChangeOptions::Increase => {
|
||||
|
@ -275,11 +275,11 @@ impl MpvExt for Mpv {
|
|||
}
|
||||
}
|
||||
|
||||
async fn stop(&self) -> Result<(), Error> {
|
||||
async fn stop(&self) -> Result<(), MpvError> {
|
||||
self.run_command(MpvCommand::Stop).await
|
||||
}
|
||||
|
||||
async fn toggle(&self) -> Result<(), Error> {
|
||||
async fn toggle(&self) -> Result<(), MpvError> {
|
||||
self.run_command_raw("cycle", &["pause"]).await.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
|
76
src/ipc.rs
76
src/ipc.rs
|
@ -8,7 +8,7 @@ use tokio::{
|
|||
};
|
||||
use tokio_util::codec::{Framed, LinesCodec};
|
||||
|
||||
use crate::{Error, ErrorCode};
|
||||
use crate::MpvError;
|
||||
|
||||
/// Container for all state that regards communication with the mpv IPC socket
|
||||
/// and message passing with [`Mpv`](crate::Mpv) controllers.
|
||||
|
@ -30,8 +30,8 @@ pub(crate) enum MpvIpcCommand {
|
|||
}
|
||||
|
||||
/// [`MpvIpc`]'s response to a [`MpvIpcCommand`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct MpvIpcResponse(pub(crate) Result<Option<Value>, Error>);
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MpvIpcResponse(pub(crate) Result<Option<Value>, MpvError>);
|
||||
|
||||
/// A deserialized and partially parsed event from mpv.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -50,28 +50,33 @@ impl MpvIpc {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_command(&mut self, command: &[Value]) -> Result<Option<Value>, Error> {
|
||||
pub(crate) async fn send_command(
|
||||
&mut self,
|
||||
command: &[Value],
|
||||
) -> Result<Option<Value>, MpvError> {
|
||||
let ipc_command = json!({ "command": command });
|
||||
let ipc_command_str = serde_json::to_string(&ipc_command)
|
||||
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?;
|
||||
let ipc_command_str =
|
||||
serde_json::to_string(&ipc_command).map_err(|why| MpvError::JsonParseError(why))?;
|
||||
|
||||
log::trace!("Sending command: {}", ipc_command_str);
|
||||
|
||||
self.socket
|
||||
.send(ipc_command_str)
|
||||
.await
|
||||
.map_err(|why| Error(ErrorCode::ConnectError(why.to_string())))?;
|
||||
.map_err(|why| MpvError::MpvSocketConnectionError(why.to_string()))?;
|
||||
|
||||
let response = loop {
|
||||
let response = self
|
||||
.socket
|
||||
.next()
|
||||
.await
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.map_err(|why| Error(ErrorCode::ConnectError(why.to_string())))?;
|
||||
.ok_or(MpvError::MpvSocketConnectionError(
|
||||
"Could not receive response from mpv".to_owned(),
|
||||
))?
|
||||
.map_err(|why| MpvError::MpvSocketConnectionError(why.to_string()))?;
|
||||
|
||||
let parsed_response = serde_json::from_str::<Value>(&response)
|
||||
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())));
|
||||
.map_err(|why| MpvError::JsonParseError(why));
|
||||
|
||||
if parsed_response
|
||||
.as_ref()
|
||||
|
@ -93,7 +98,7 @@ impl MpvIpc {
|
|||
pub(crate) async fn get_mpv_property(
|
||||
&mut self,
|
||||
property: &str,
|
||||
) -> Result<Option<Value>, Error> {
|
||||
) -> Result<Option<Value>, MpvError> {
|
||||
self.send_command(&[json!("get_property"), json!(property)])
|
||||
.await
|
||||
}
|
||||
|
@ -102,7 +107,7 @@ impl MpvIpc {
|
|||
&mut self,
|
||||
property: &str,
|
||||
value: Value,
|
||||
) -> Result<Option<Value>, Error> {
|
||||
) -> Result<Option<Value>, MpvError> {
|
||||
self.send_command(&[json!("set_property"), json!(property), value])
|
||||
.await
|
||||
}
|
||||
|
@ -111,17 +116,20 @@ impl MpvIpc {
|
|||
&mut self,
|
||||
id: isize,
|
||||
property: &str,
|
||||
) -> Result<Option<Value>, Error> {
|
||||
) -> Result<Option<Value>, MpvError> {
|
||||
self.send_command(&[json!("observe_property"), json!(id), json!(property)])
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn unobserve_property(&mut self, id: isize) -> Result<Option<Value>, Error> {
|
||||
pub(crate) async fn unobserve_property(
|
||||
&mut self,
|
||||
id: isize,
|
||||
) -> Result<Option<Value>, MpvError> {
|
||||
self.send_command(&[json!("unobserve_property"), json!(id)])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn handle_event(&mut self, event: Result<Value, Error>) {
|
||||
async fn handle_event(&mut self, event: Result<Value, MpvError>) {
|
||||
match &event {
|
||||
Ok(event) => {
|
||||
log::trace!("Parsed event: {:?}", event);
|
||||
|
@ -137,17 +145,17 @@ impl MpvIpc {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(mut self) -> Result<(), Error> {
|
||||
pub(crate) async fn run(mut self) -> Result<(), MpvError> {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(event) = self.socket.next() => {
|
||||
log::trace!("Got event: {:?}", event);
|
||||
|
||||
let parsed_event = event
|
||||
.map_err(|why| Error(ErrorCode::ConnectError(why.to_string())))
|
||||
.map_err(|why| MpvError::MpvSocketConnectionError(why.to_string()))
|
||||
.and_then(|event|
|
||||
serde_json::from_str::<Value>(&event)
|
||||
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string()))));
|
||||
.map_err(|why| MpvError::JsonParseError(why)));
|
||||
|
||||
self.handle_event(parsed_event).await;
|
||||
}
|
||||
|
@ -189,20 +197,40 @@ impl MpvIpc {
|
|||
/// This function does the most basic JSON parsing and error handling
|
||||
/// for status codes and errors that all responses from mpv are
|
||||
/// expected to contain.
|
||||
fn parse_mpv_response_data(value: Value) -> Result<Option<Value>, Error> {
|
||||
fn parse_mpv_response_data(value: Value) -> Result<Option<Value>, MpvError> {
|
||||
log::trace!("Parsing mpv response data: {:?}", value);
|
||||
let result = value
|
||||
.as_object()
|
||||
.map(|o| (o.get("error").and_then(|e| e.as_str()), o.get("data")))
|
||||
.ok_or(Error(ErrorCode::UnexpectedValue))
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "object".to_string(),
|
||||
received: value.clone(),
|
||||
})
|
||||
.and_then(|o| {
|
||||
let error = o
|
||||
.get("error")
|
||||
.ok_or(MpvError::MissingKeyInObject {
|
||||
key: "error".to_string(),
|
||||
map: o.clone(),
|
||||
})?
|
||||
.as_str()
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "string".to_string(),
|
||||
received: o.get("error").unwrap().clone(),
|
||||
})?;
|
||||
|
||||
let data = o.get("data");
|
||||
|
||||
Ok((error, data))
|
||||
})
|
||||
.and_then(|(error, data)| match error {
|
||||
Some("success") => Ok(data),
|
||||
Some(e) => Err(Error(ErrorCode::MpvError(e.to_string()))),
|
||||
None => Err(Error(ErrorCode::UnexpectedValue)),
|
||||
"success" => Ok(data),
|
||||
err => Err(MpvError::MpvError(err.to_string())),
|
||||
});
|
||||
|
||||
match &result {
|
||||
Ok(v) => log::trace!("Successfully parsed mpv response data: {:?}", v),
|
||||
Err(e) => log::trace!("Error parsing mpv response data: {:?}", e),
|
||||
}
|
||||
|
||||
result.map(|opt| opt.cloned())
|
||||
}
|
||||
|
|
|
@ -4,18 +4,21 @@ use std::collections::HashMap;
|
|||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{Error, ErrorCode, MpvDataType, PlaylistEntry};
|
||||
use crate::{MpvDataType, MpvError, PlaylistEntry};
|
||||
|
||||
pub trait TypeHandler: Sized {
|
||||
fn get_value(value: Value) -> Result<Self, Error>;
|
||||
fn get_value(value: Value) -> Result<Self, MpvError>;
|
||||
fn as_string(&self) -> String;
|
||||
}
|
||||
|
||||
impl TypeHandler for String {
|
||||
fn get_value(value: Value) -> Result<String, Error> {
|
||||
fn get_value(value: Value) -> Result<String, MpvError> {
|
||||
value
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "String".to_string(),
|
||||
received: value.clone(),
|
||||
})
|
||||
.map(|s| s.to_string())
|
||||
}
|
||||
|
||||
|
@ -25,10 +28,13 @@ impl TypeHandler for String {
|
|||
}
|
||||
|
||||
impl TypeHandler for bool {
|
||||
fn get_value(value: Value) -> Result<bool, Error> {
|
||||
fn get_value(value: Value) -> Result<bool, MpvError> {
|
||||
value
|
||||
.as_bool()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainBool))
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "bool".to_string(),
|
||||
received: value.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
|
@ -41,10 +47,11 @@ impl TypeHandler for bool {
|
|||
}
|
||||
|
||||
impl TypeHandler for f64 {
|
||||
fn get_value(value: Value) -> Result<f64, Error> {
|
||||
value
|
||||
.as_f64()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainF64))
|
||||
fn get_value(value: Value) -> Result<f64, MpvError> {
|
||||
value.as_f64().ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "f64".to_string(),
|
||||
received: value.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
|
@ -53,11 +60,14 @@ impl TypeHandler for f64 {
|
|||
}
|
||||
|
||||
impl TypeHandler for usize {
|
||||
fn get_value(value: Value) -> Result<usize, Error> {
|
||||
fn get_value(value: Value) -> Result<usize, MpvError> {
|
||||
value
|
||||
.as_u64()
|
||||
.map(|u| u as usize)
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "usize".to_string(),
|
||||
received: value.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
|
@ -65,12 +75,25 @@ impl TypeHandler for usize {
|
|||
}
|
||||
}
|
||||
|
||||
impl TypeHandler for MpvDataType {
|
||||
fn get_value(value: Value) -> Result<MpvDataType, MpvError> {
|
||||
json_to_value(&value)
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeHandler for HashMap<String, MpvDataType> {
|
||||
fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, Error> {
|
||||
fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, MpvError> {
|
||||
value
|
||||
.as_object()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainHashMap))
|
||||
.map(json_map_to_hashmap)
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "Map<String, Value>".to_string(),
|
||||
received: value.clone(),
|
||||
})
|
||||
.and_then(json_map_to_hashmap)
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
|
@ -79,10 +102,13 @@ impl TypeHandler for HashMap<String, MpvDataType> {
|
|||
}
|
||||
|
||||
impl TypeHandler for Vec<PlaylistEntry> {
|
||||
fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, Error> {
|
||||
fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, MpvError> {
|
||||
value
|
||||
.as_array()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainPlaylist))
|
||||
.ok_or(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "Array<Value>".to_string(),
|
||||
received: value.clone(),
|
||||
})
|
||||
.map(|array| json_array_to_playlist(array))
|
||||
}
|
||||
|
||||
|
@ -91,9 +117,9 @@ impl TypeHandler for Vec<PlaylistEntry> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn json_to_value(value: &Value) -> Result<MpvDataType, Error> {
|
||||
pub(crate) fn json_to_value(value: &Value) -> Result<MpvDataType, MpvError> {
|
||||
match value {
|
||||
Value::Array(array) => Ok(MpvDataType::Array(json_array_to_vec(array))),
|
||||
Value::Array(array) => Ok(MpvDataType::Array(json_array_to_vec(array)?)),
|
||||
Value::Bool(b) => Ok(MpvDataType::Bool(*b)),
|
||||
Value::Number(n) => {
|
||||
if n.is_i64() && n.as_i64().unwrap() == -1 {
|
||||
|
@ -103,11 +129,13 @@ pub(crate) fn json_to_value(value: &Value) -> Result<MpvDataType, Error> {
|
|||
} else if n.is_f64() {
|
||||
Ok(MpvDataType::Double(n.as_f64().unwrap()))
|
||||
} else {
|
||||
// TODO: proper error handling
|
||||
panic!("Unexpected number type");
|
||||
Err(MpvError::ValueContainsUnexpectedType {
|
||||
expected: "i64, u64, or f64".to_string(),
|
||||
received: value.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Object(map) => Ok(MpvDataType::HashMap(json_map_to_hashmap(map))),
|
||||
Value::Object(map) => Ok(MpvDataType::HashMap(json_map_to_hashmap(map)?)),
|
||||
Value::String(s) => Ok(MpvDataType::String(s.to_string())),
|
||||
Value::Null => Ok(MpvDataType::Null),
|
||||
}
|
||||
|
@ -115,23 +143,16 @@ pub(crate) fn json_to_value(value: &Value) -> Result<MpvDataType, Error> {
|
|||
|
||||
pub(crate) fn json_map_to_hashmap(
|
||||
map: &serde_json::map::Map<String, Value>,
|
||||
) -> HashMap<String, MpvDataType> {
|
||||
) -> Result<HashMap<String, MpvDataType>, MpvError> {
|
||||
let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
|
||||
for (ref key, value) in map.iter() {
|
||||
// TODO: proper error handling
|
||||
if let Ok(value) = json_to_value(value) {
|
||||
output_map.insert(key.to_string(), value);
|
||||
output_map.insert(key.to_string(), json_to_value(value)?);
|
||||
}
|
||||
}
|
||||
output_map
|
||||
Ok(output_map)
|
||||
}
|
||||
|
||||
pub(crate) fn json_array_to_vec(array: &[Value]) -> Vec<MpvDataType> {
|
||||
array
|
||||
.iter()
|
||||
// TODO: proper error handling
|
||||
.filter_map(|entry| json_to_value(entry).ok())
|
||||
.collect()
|
||||
pub(crate) fn json_array_to_vec(array: &[Value]) -> Result<Vec<MpvDataType>, MpvError> {
|
||||
array.iter().map(|entry| json_to_value(entry)).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn json_array_to_playlist(array: &[Value]) -> Vec<PlaylistEntry> {
|
||||
|
@ -207,7 +228,10 @@ mod test {
|
|||
)])),
|
||||
);
|
||||
|
||||
assert_eq!(json_map_to_hashmap(json.as_object().unwrap()), expected);
|
||||
match json_map_to_hashmap(json.as_object().unwrap()) {
|
||||
Ok(m) => assert_eq!(m, expected),
|
||||
Err(e) => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -246,7 +270,10 @@ mod test {
|
|||
)])),
|
||||
];
|
||||
|
||||
assert_eq!(json_array_to_vec(json.as_array().unwrap()), expected);
|
||||
match json_array_to_vec(json.as_array().unwrap()) {
|
||||
Ok(v) => assert_eq!(v, expected),
|
||||
Err(e) => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in New Issue