diff --git a/Cargo.toml b/Cargo.toml index cce3b29..b4a3ec3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" [dependencies] serde_json = "1.0.104" log = "0.4.19" +serde = { version = "1.0.197", features = ["derive"] } [dev-dependencies] env_logger = "0.10.0" diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..d70bef4 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,749 @@ +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::collections::HashMap; +use std::fmt::{self, Display}; +use std::io::{BufReader, Read}; +use std::os::unix::net::UnixStream; + +use crate::ipc::{ + get_mpv_property, get_mpv_property_string, listen, listen_raw, observe_mpv_property, + run_mpv_command, set_mpv_property, unobserve_mpv_property, +}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Event { + Shutdown, + StartFile, + EndFile, + FileLoaded, + TracksChanged, + TrackSwitched, + Idle, + Pause, + Unpause, + Tick, + VideoReconfig, + AudioReconfig, + MetadataUpdate, + Seek, + PlaybackRestart, + PropertyChange { id: usize, property: Property }, + ChapterChange, + ClientMessage { args: Vec }, + Unimplemented, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Property { + Path(Option), + Pause(bool), + PlaybackTime(Option), + Duration(Option), + Metadata(Option>), + Unknown { name: String, data: MpvDataType }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum MpvCommand { + LoadFile { + file: String, + option: PlaylistAddOptions, + }, + LoadList { + file: String, + option: PlaylistAddOptions, + }, + PlaylistClear, + PlaylistMove { + from: usize, + to: usize, + }, + Observe { + id: isize, + property: String, + }, + PlaylistNext, + PlaylistPrev, + PlaylistRemove(usize), + PlaylistShuffle, + Quit, + ScriptMessage(Vec), + ScriptMessageTo { + target: String, + args: Vec, + }, + Seek { + seconds: f64, + option: SeekOptions, + }, + Stop, + Unobserve(isize), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum MpvDataType { + Array(Vec), + Bool(bool), + Double(f64), + HashMap(HashMap), + Null, + Playlist(Playlist), + String(String), + Usize(usize), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum NumberChangeOptions { + Absolute, + Increase, + Decrease, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum PlaylistAddOptions { + Replace, + Append, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum PlaylistAddTypeOptions { + File, + Playlist, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum SeekOptions { + Relative, + Absolute, + RelativePercent, + AbsolutePercent, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum Switch { + On, + Off, + Toggle, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ErrorCode { + MpvError(String), + JsonParseError(String), + ConnectError(String), + JsonContainsUnexptectedType, + UnexpectedResult, + UnexpectedValue, + MissingValue, + UnsupportedType, + ValueDoesNotContainBool, + ValueDoesNotContainF64, + ValueDoesNotContainHashMap, + ValueDoesNotContainPlaylist, + ValueDoesNotContainString, + ValueDoesNotContainUsize, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PlaylistEntry { + pub id: usize, + pub filename: String, + pub title: String, + pub current: bool, +} + +pub struct Mpv { + pub(crate) stream: UnixStream, + pub(crate) reader: BufReader, + pub(crate) name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Playlist(pub Vec); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Error(pub ErrorCode); + +impl Drop for Mpv { + fn drop(&mut self) { + self.disconnect(); + } +} + +impl fmt::Debug for Mpv { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Mpv").field(&self.name).finish() + } +} + +impl Clone for Mpv { + fn clone(&self) -> Self { + let stream = self.stream.try_clone().expect("cloning UnixStream"); + let cloned_stream = stream.try_clone().expect("cloning UnixStream"); + Mpv { + stream, + reader: BufReader::new(cloned_stream), + name: self.name.clone(), + } + } + + fn clone_from(&mut self, source: &Self) { + let stream = source.stream.try_clone().expect("cloning UnixStream"); + let cloned_stream = stream.try_clone().expect("cloning UnixStream"); + *self = Mpv { + stream, + reader: BufReader::new(cloned_stream), + name: source.name.clone(), + } + } +} + +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\'") + } + } + } +} + +pub trait GetPropertyTypeHandler: Sized { + fn get_property_generic(instance: &Mpv, property: &str) -> Result; +} + +impl GetPropertyTypeHandler for bool { + fn get_property_generic(instance: &Mpv, property: &str) -> Result { + get_mpv_property::(instance, property) + } +} + +impl GetPropertyTypeHandler for String { + fn get_property_generic(instance: &Mpv, property: &str) -> Result { + get_mpv_property::(instance, property) + } +} + +impl GetPropertyTypeHandler for f64 { + fn get_property_generic(instance: &Mpv, property: &str) -> Result { + get_mpv_property::(instance, property) + } +} + +impl GetPropertyTypeHandler for usize { + fn get_property_generic(instance: &Mpv, property: &str) -> Result { + get_mpv_property::(instance, property) + } +} + +impl GetPropertyTypeHandler for Vec { + fn get_property_generic(instance: &Mpv, property: &str) -> Result, Error> { + get_mpv_property::>(instance, property) + } +} + +impl GetPropertyTypeHandler for HashMap { + fn get_property_generic( + instance: &Mpv, + property: &str, + ) -> Result, Error> { + get_mpv_property::>(instance, property) + } +} + +pub trait SetPropertyTypeHandler { + fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error>; +} + +impl SetPropertyTypeHandler for bool { + fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), Error> { + set_mpv_property(instance, property, json!(value)) + } +} + +impl SetPropertyTypeHandler for String { + fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), Error> { + set_mpv_property(instance, property, json!(value)) + } +} + +impl SetPropertyTypeHandler for f64 { + fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), Error> { + set_mpv_property(instance, property, json!(value)) + } +} + +impl SetPropertyTypeHandler for usize { + fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), Error> { + set_mpv_property(instance, property, json!(value)) + } +} + +impl Mpv { + pub fn connect(socket: &str) -> Result { + match UnixStream::connect(socket) { + Ok(stream) => { + let cloned_stream = stream.try_clone().expect("cloning UnixStream"); + return Ok(Mpv { + stream, + reader: BufReader::new(cloned_stream), + name: String::from(socket), + }); + } + Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))), + } + } + + pub fn disconnect(&self) { + let mut stream = &self.stream; + stream + .shutdown(std::net::Shutdown::Both) + .expect("socket disconnect"); + let mut buffer = [0; 32]; + for _ in 0..stream.bytes().count() { + stream.read(&mut buffer[..]).unwrap(); + } + } + + pub fn get_stream_ref(&self) -> &UnixStream { + &self.stream + } + + pub fn get_metadata(&self) -> Result, Error> { + match get_mpv_property(self, "metadata") { + Ok(map) => Ok(map), + Err(err) => Err(err), + } + } + + pub fn get_playlist(&self) -> Result { + match get_mpv_property::>(self, "playlist") { + Ok(entries) => Ok(Playlist(entries)), + Err(msg) => Err(msg), + } + } + + /// # Description + /// + /// Retrieves the property value from mpv. + /// + /// ## Supported types + /// - String + /// - bool + /// - HashMap (e.g. for the 'metadata' property) + /// - Vec (for the 'playlist' property) + /// - usize + /// - f64 + /// + /// ## Input arguments + /// + /// - **property** defines the mpv property that should be retrieved + /// + /// # Example + /// ``` + /// use mpvipc::{Mpv, Error}; + /// fn main() -> Result<(), Error> { + /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// let paused: bool = mpv.get_property("pause")?; + /// let title: String = mpv.get_property("media-title")?; + /// Ok(()) + /// } + /// ``` + pub fn get_property(&self, property: &str) -> Result { + T::get_property_generic(self, property) + } + + /// # Description + /// + /// Retrieves the property value from mpv. + /// The result is always of type String, regardless of the type of the value of the mpv property + /// + /// ## Input arguments + /// + /// - **property** defines the mpv property that should be retrieved + /// + /// # Example + /// + /// ``` + /// use mpvipc::{Mpv, Error}; + /// fn main() -> Result<(), Error> { + /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// let title = mpv.get_property_string("media-title")?; + /// Ok(()) + /// } + /// ``` + pub fn get_property_string(&self, property: &str) -> Result { + get_mpv_property_string(self, property) + } + + pub fn kill(&self) -> Result<(), Error> { + self.run_command(MpvCommand::Quit) + } + + /// # Description + /// + /// Waits until an mpv event occurs and returns the Event. + /// + /// # Example + /// + /// ```ignore + /// let mut mpv = Mpv::connect("/tmp/mpvsocket")?; + /// loop { + /// let event = mpv.event_listen()?; + /// println!("{:?}", event); + /// } + /// ``` + pub fn event_listen(&mut self) -> Result { + listen(self) + } + + pub fn event_listen_raw(&mut self) -> String { + listen_raw(self) + } + + pub fn next(&self) -> Result<(), Error> { + self.run_command(MpvCommand::PlaylistNext) + } + + pub fn observe_property(&self, id: isize, property: &str) -> Result<(), Error> { + self.run_command(MpvCommand::Observe { + id: id, + property: property.to_string(), + }) + } + + pub fn unobserve_property(&self, id: isize) -> Result<(), Error> { + self.run_command(MpvCommand::Unobserve(id)) + } + + pub fn pause(&self) -> Result<(), Error> { + set_mpv_property(self, "pause", json!(true)) + } + + pub fn prev(&self) -> Result<(), Error> { + self.run_command(MpvCommand::PlaylistPrev) + } + + pub fn restart(&self) -> Result<(), Error> { + self.run_command(MpvCommand::Seek { + seconds: 0f64, + option: SeekOptions::Absolute, + }) + } + + /// # Description + /// + /// Runs mpv commands. The arguments are passed as a String-Vector reference: + /// + /// ## Input arguments + /// + /// - **command** defines the mpv command that should be executed + /// - **args** a slice of &str's which define the arguments + /// + /// # Example + /// ``` + /// use mpvipc::{Mpv, Error}; + /// fn main() -> Result<(), Error> { + /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// + /// //Run command 'playlist-shuffle' which takes no arguments + /// mpv.run_command(MpvCommand::PlaylistShuffle)?; + /// + /// //Run command 'seek' which in this case takes two arguments + /// mpv.run_command(MpvCommand::Seek { + /// seconds: 0f64, + /// option: SeekOptions::Absolute, + /// })?; + /// Ok(()) + /// } + /// ``` + pub fn run_command(&self, command: MpvCommand) -> Result<(), Error> { + match command { + MpvCommand::LoadFile { file, option } => run_mpv_command( + self, + "loadfile", + &[ + file.as_ref(), + match option { + PlaylistAddOptions::Append => "append", + PlaylistAddOptions::Replace => "replace", + }, + ], + ), + MpvCommand::LoadList { file, option } => run_mpv_command( + self, + "loadlist", + &[ + file.as_ref(), + match option { + PlaylistAddOptions::Append => "append", + PlaylistAddOptions::Replace => "replace", + }, + ], + ), + MpvCommand::Observe { id, property } => observe_mpv_property(self, &id, &property), + MpvCommand::PlaylistClear => run_mpv_command(self, "playlist-clear", &[]), + MpvCommand::PlaylistMove { from, to } => { + run_mpv_command(self, "playlist-move", &[&from.to_string(), &to.to_string()]) + } + MpvCommand::PlaylistNext => run_mpv_command(self, "playlist-next", &[]), + MpvCommand::PlaylistPrev => run_mpv_command(self, "playlist-prev", &[]), + MpvCommand::PlaylistRemove(id) => { + run_mpv_command(self, "playlist-remove", &[&id.to_string()]) + } + MpvCommand::PlaylistShuffle => run_mpv_command(self, "playlist-shuffle", &[]), + MpvCommand::Quit => run_mpv_command(self, "quit", &[]), + MpvCommand::ScriptMessage(args) => { + let str_args: Vec<_> = args.iter().map(String::as_str).collect(); + run_mpv_command(self, "script-message", &str_args) + } + MpvCommand::ScriptMessageTo { target, args } => { + let mut cmd_args: Vec<_> = vec![target.as_str()]; + let mut str_args: Vec<_> = args.iter().map(String::as_str).collect(); + cmd_args.append(&mut str_args); + run_mpv_command(self, "script-message-to", &cmd_args) + } + MpvCommand::Seek { seconds, option } => run_mpv_command( + self, + "seek", + &[ + &seconds.to_string(), + match option { + SeekOptions::Absolute => "absolute", + SeekOptions::Relative => "relative", + SeekOptions::AbsolutePercent => "absolute-percent", + SeekOptions::RelativePercent => "relative-percent", + }, + ], + ), + MpvCommand::Stop => run_mpv_command(self, "stop", &[]), + MpvCommand::Unobserve(id) => unobserve_mpv_property(self, &id), + } + } + + /// Run a custom command. + /// This should only be used if the desired command is not implemented + /// with [MpvCommand]. + pub fn run_command_raw(&self, command: &str, args: &[&str]) -> Result<(), Error> { + run_mpv_command(self, command, args) + } + + pub fn playlist_add( + &self, + file: &str, + file_type: PlaylistAddTypeOptions, + option: PlaylistAddOptions, + ) -> Result<(), Error> { + match file_type { + PlaylistAddTypeOptions::File => self.run_command(MpvCommand::LoadFile { + file: file.to_string(), + option, + }), + + PlaylistAddTypeOptions::Playlist => self.run_command(MpvCommand::LoadList { + file: file.to_string(), + option, + }), + } + } + + pub fn playlist_clear(&self) -> Result<(), Error> { + self.run_command(MpvCommand::PlaylistClear) + } + + pub fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), Error> { + self.run_command(MpvCommand::PlaylistMove { from, to }) + } + + pub fn playlist_play_id(&self, id: usize) -> Result<(), Error> { + set_mpv_property(self, "playlist-pos", json!(id)) + } + + pub fn playlist_play_next(&self, id: usize) -> Result<(), Error> { + match get_mpv_property::(self, "playlist-pos") { + Ok(current_id) => self.run_command(MpvCommand::PlaylistMove { + from: id, + to: current_id + 1, + }), + Err(msg) => Err(msg), + } + } + + pub fn playlist_remove_id(&self, id: usize) -> Result<(), Error> { + self.run_command(MpvCommand::PlaylistRemove(id)) + } + + pub fn playlist_shuffle(&self) -> Result<(), Error> { + self.run_command(MpvCommand::PlaylistShuffle) + } + + pub fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), Error> { + self.run_command(MpvCommand::Seek { seconds, option }) + } + + pub fn set_loop_file(&self, option: Switch) -> Result<(), Error> { + let mut enabled = false; + match option { + Switch::On => enabled = true, + Switch::Off => {} + Switch::Toggle => match get_mpv_property_string(self, "loop-file") { + Ok(value) => match value.as_ref() { + "false" => { + enabled = true; + } + _ => { + enabled = false; + } + }, + Err(msg) => return Err(msg), + }, + } + set_mpv_property(self, "loop-file", json!(enabled)) + } + + pub fn set_loop_playlist(&self, option: Switch) -> Result<(), Error> { + let mut enabled = false; + match option { + Switch::On => enabled = true, + Switch::Off => {} + Switch::Toggle => match get_mpv_property_string(self, "loop-playlist") { + Ok(value) => match value.as_ref() { + "false" => { + enabled = true; + } + _ => { + enabled = false; + } + }, + Err(msg) => return Err(msg), + }, + } + set_mpv_property(self, "loop-playlist", json!(enabled)) + } + + pub fn set_mute(&self, option: Switch) -> Result<(), Error> { + let mut enabled = false; + match option { + Switch::On => enabled = true, + Switch::Off => {} + Switch::Toggle => match get_mpv_property::(self, "mute") { + Ok(value) => { + enabled = !value; + } + Err(msg) => return Err(msg), + }, + } + set_mpv_property(self, "mute", json!(enabled)) + } + + /// # Description + /// + /// Sets the mpv property __ to __. + /// + /// ## Supported types + /// - String + /// - bool + /// - f64 + /// - usize + /// + /// ## Input arguments + /// + /// - **property** defines the mpv property that should be retrieved + /// - **value** defines the value of the given mpv property __ + /// + /// # Example + /// ``` + /// use mpvipc::{Mpv, Error}; + /// fn main() -> Result<(), Error> { + /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// mpv.set_property("pause", true)?; + /// Ok(()) + /// } + /// ``` + pub fn set_property>( + &self, + property: &str, + value: T, + ) -> Result<(), Error> { + T::set_property_generic(self, property, value) + } + + pub fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error> { + match get_mpv_property::(self, "speed") { + Ok(speed) => match option { + NumberChangeOptions::Increase => { + set_mpv_property(self, "speed", json!(speed + input_speed)) + } + + NumberChangeOptions::Decrease => { + set_mpv_property(self, "speed", json!(speed - input_speed)) + } + + NumberChangeOptions::Absolute => { + set_mpv_property(self, "speed", json!(input_speed)) + } + }, + Err(msg) => Err(msg), + } + } + + pub fn set_volume(&self, input_volume: f64, option: NumberChangeOptions) -> Result<(), Error> { + match get_mpv_property::(self, "volume") { + Ok(volume) => match option { + NumberChangeOptions::Increase => { + set_mpv_property(self, "volume", json!(volume + input_volume)) + } + + NumberChangeOptions::Decrease => { + set_mpv_property(self, "volume", json!(volume - input_volume)) + } + + NumberChangeOptions::Absolute => { + set_mpv_property(self, "volume", json!(input_volume)) + } + }, + Err(msg) => Err(msg), + } + } + + pub fn stop(&self) -> Result<(), Error> { + self.run_command(MpvCommand::Stop) + } + + pub fn toggle(&self) -> Result<(), Error> { + run_mpv_command(self, "cycle", &["pause"]) + } +} diff --git a/src/ipc.rs b/src/ipc.rs index 046f6c5..a7ce542 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -1,182 +1,17 @@ +use crate::message_parser::TypeHandler; + +use self::message_parser::extract_mpv_response_data; +use self::message_parser::json_array_to_playlist; +use self::message_parser::json_array_to_vec; +use self::message_parser::json_map_to_hashmap; + use super::*; use log::{debug, warn}; use serde_json::json; -use std::collections::HashMap; -use std::io::prelude::*; +use serde_json::Value; +use std::io::BufRead; use std::io::BufReader; -use std::iter::Iterator; - -#[derive(Debug, Clone)] -pub struct PlaylistEntry { - pub id: usize, - pub filename: String, - pub title: String, - pub current: bool, -} - -pub trait TypeHandler: Sized { - fn get_value(value: Value) -> Result; - fn as_string(&self) -> String; -} - -impl TypeHandler for String { - fn get_value(value: Value) -> Result { - if let Value::Object(map) = value { - if let Value::String(ref error) = map["error"] { - if error == "success" && map.contains_key("data") { - if let Value::String(ref s) = map["data"] { - Ok(s.to_string()) - } else { - Err(Error(ErrorCode::ValueDoesNotContainString)) - } - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } - - fn as_string(&self) -> String { - self.to_string() - } -} - -impl TypeHandler for bool { - fn get_value(value: Value) -> Result { - if let Value::Object(map) = value { - if let Value::String(ref error) = map["error"] { - if error == "success" && map.contains_key("data") { - if let Value::Bool(ref b) = map["data"] { - Ok(*b) - } else { - Err(Error(ErrorCode::ValueDoesNotContainBool)) - } - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } - fn as_string(&self) -> String { - if *self { - "true".to_string() - } else { - "false".to_string() - } - } -} - -impl TypeHandler for f64 { - fn get_value(value: Value) -> Result { - if let Value::Object(map) = value { - if let Value::String(ref error) = map["error"] { - if error == "success" && map.contains_key("data") { - if let Value::Number(ref num) = map["data"] { - Ok(num.as_f64().unwrap()) - } else { - Err(Error(ErrorCode::ValueDoesNotContainF64)) - } - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } - - fn as_string(&self) -> String { - self.to_string() - } -} - -impl TypeHandler for usize { - fn get_value(value: Value) -> Result { - if let Value::Object(map) = value { - if let Value::String(ref error) = map["error"] { - if error == "success" && map.contains_key("data") { - if let Value::Number(ref num) = map["data"] { - Ok(num.as_u64().unwrap() as usize) - } else { - Err(Error(ErrorCode::ValueDoesNotContainUsize)) - } - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } - - fn as_string(&self) -> String { - self.to_string() - } -} - -impl TypeHandler for HashMap { - fn get_value(value: Value) -> Result, Error> { - if let Value::Object(map) = value { - if let Value::String(ref error) = map["error"] { - if error == "success" && map.contains_key("data") { - if let Value::Object(ref inner_map) = map["data"] { - Ok(json_map_to_hashmap(inner_map)) - } else { - Err(Error(ErrorCode::ValueDoesNotContainHashMap)) - } - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } - - fn as_string(&self) -> String { - format!("{:?}", self) - } -} - -impl TypeHandler for Vec { - fn get_value(value: Value) -> Result, Error> { - if let Value::Object(map) = value { - if let Value::String(ref error) = map["error"] { - if error == "success" && map.contains_key("data") { - if let Value::Array(ref playlist_vec) = map["data"] { - Ok(json_array_to_playlist(playlist_vec)) - } else { - Err(Error(ErrorCode::ValueDoesNotContainPlaylist)) - } - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } else { - Err(Error(ErrorCode::UnexpectedValue)) - } - } - - fn as_string(&self) -> String { - format!("{:?}", self) - } -} +use std::io::Write; pub fn get_mpv_property(instance: &Mpv, property: &str) -> Result { let ipc_string = json!({"command": ["get_property", property]}); @@ -191,23 +26,7 @@ pub fn get_mpv_property_string(instance: &Mpv, property: &str) -> Result(&send_command_sync(instance, ipc_string)) .map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?; - let map = if let Value::Object(map) = val { - Ok(map) - } else { - Err(Error(ErrorCode::UnexpectedValue)) - }?; - - let error = if let Value::String(ref error) = map["error"] { - Ok(error) - } else { - Err(Error(ErrorCode::UnexpectedValue)) - }?; - - let data = if error == "success" { - Ok(&map["data"]) - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - }?; + let data = extract_mpv_response_data(&val)?; match data { Value::Bool(b) => Ok(b.to_string()), @@ -219,15 +38,19 @@ pub fn get_mpv_property_string(instance: &Mpv, property: &str) -> Result Result<(), Error> { + serde_json::from_str::(response) + .map_err(|why| Error(ErrorCode::JsonParseError(why.to_string()))) + .and_then(|value| extract_mpv_response_data(&value).map(|_| ())) +} + pub fn set_mpv_property(instance: &Mpv, property: &str, value: Value) -> Result<(), Error> { let ipc_string = json!({ "command": ["set_property", property, value] }); - match serde_json::from_str::(&send_command_sync(instance, ipc_string)) { - Ok(_) => Ok(()), - Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))), - } + let response = &send_command_sync(instance, ipc_string); + validate_mpv_response(response) } pub fn run_mpv_command(instance: &Mpv, command: &str, args: &[&str]) -> Result<(), Error> { @@ -239,60 +62,27 @@ pub fn run_mpv_command(instance: &Mpv, command: &str, args: &[&str]) -> Result<( args_array.push(json!(arg)); } } - match serde_json::from_str::(&send_command_sync(instance, ipc_string)) { - Ok(feedback) => { - if let Value::String(ref error) = feedback["error"] { - if error == "success" { - Ok(()) - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - } - } else { - Err(Error(ErrorCode::UnexpectedResult)) - } - } - Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))), - } + + let response = &send_command_sync(instance, ipc_string); + validate_mpv_response(response) } pub fn observe_mpv_property(instance: &Mpv, id: &isize, property: &str) -> Result<(), Error> { let ipc_string = json!({ "command": ["observe_property", id, property] }); - match serde_json::from_str::(&send_command_sync(instance, ipc_string)) { - Ok(feedback) => { - if let Value::String(ref error) = feedback["error"] { - if error == "success" { - Ok(()) - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - } - } else { - Err(Error(ErrorCode::UnexpectedResult)) - } - } - Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))), - } + + let response = &send_command_sync(instance, ipc_string); + validate_mpv_response(response) } pub fn unobserve_mpv_property(instance: &Mpv, id: &isize) -> Result<(), Error> { let ipc_string = json!({ "command": ["unobserve_property", id] }); - match serde_json::from_str::(&send_command_sync(instance, ipc_string)) { - Ok(feedback) => { - if let Value::String(ref error) = feedback["error"] { - if error == "success" { - Ok(()) - } else { - Err(Error(ErrorCode::MpvError(error.to_string()))) - } - } else { - Err(Error(ErrorCode::UnexpectedResult)) - } - } - Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))), - } + + let response = &send_command_sync(instance, ipc_string); + validate_mpv_response(response) } fn try_convert_property(name: &str, id: usize, data: MpvDataType) -> Event { @@ -456,128 +246,3 @@ fn send_command_sync(instance: &Mpv, command: Value) -> String { } } } - -fn json_map_to_hashmap(map: &serde_json::map::Map) -> HashMap { - let mut output_map: HashMap = HashMap::new(); - for (ref key, ref value) in map.iter() { - match **value { - Value::Array(ref array) => { - output_map.insert( - key.to_string(), - MpvDataType::Array(json_array_to_vec(array)), - ); - } - Value::Bool(ref b) => { - output_map.insert(key.to_string(), MpvDataType::Bool(*b)); - } - Value::Number(ref n) => { - if n.is_u64() { - output_map.insert( - key.to_string(), - MpvDataType::Usize(n.as_u64().unwrap() as usize), - ); - } else if n.is_f64() { - output_map.insert(key.to_string(), MpvDataType::Double(n.as_f64().unwrap())); - } else { - panic!("unimplemented number"); - } - } - Value::String(ref s) => { - output_map.insert(key.to_string(), MpvDataType::String(s.to_string())); - } - Value::Object(ref m) => { - output_map.insert( - key.to_string(), - MpvDataType::HashMap(json_map_to_hashmap(m)), - ); - } - Value::Null => { - unimplemented!(); - } - } - } - output_map -} - -fn json_array_to_vec(array: &Vec) -> Vec { - let mut output: Vec = Vec::new(); - if array.len() > 0 { - match array[0] { - Value::Array(_) => { - for entry in array { - if let Value::Array(ref a) = *entry { - output.push(MpvDataType::Array(json_array_to_vec(a))); - } - } - } - - Value::Bool(_) => { - for entry in array { - if let Value::Bool(ref b) = *entry { - output.push(MpvDataType::Bool(*b)); - } - } - } - - Value::Number(_) => { - for entry in array { - if let Value::Number(ref n) = *entry { - if n.is_u64() { - output.push(MpvDataType::Usize(n.as_u64().unwrap() as usize)); - } else if n.is_f64() { - output.push(MpvDataType::Double(n.as_f64().unwrap())); - } else { - panic!("unimplemented number"); - } - } - } - } - - Value::Object(_) => { - for entry in array { - if let Value::Object(ref map) = *entry { - output.push(MpvDataType::HashMap(json_map_to_hashmap(map))); - } - } - } - - Value::String(_) => { - for entry in array { - if let Value::String(ref s) = *entry { - output.push(MpvDataType::String(s.to_string())); - } - } - } - - Value::Null => { - unimplemented!(); - } - } - } - output -} - -fn json_array_to_playlist(array: &Vec) -> Vec { - let mut output: Vec = Vec::new(); - for (id, entry) in array.iter().enumerate() { - let mut filename: String = String::new(); - let mut title: String = String::new(); - let mut current: bool = false; - if let Value::String(ref f) = entry["filename"] { - filename = f.to_string(); - } - if let Value::String(ref t) = entry["title"] { - title = t.to_string(); - } - if let Value::Bool(ref b) = entry["current"] { - current = *b; - } - output.push(PlaylistEntry { - id, - filename, - title, - current, - }); - } - output -} diff --git a/src/lib.rs b/src/lib.rs index 096e72b..385fa19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,735 +1,5 @@ -pub mod ipc; +mod api; +mod ipc; +mod message_parser; -use ipc::*; -use serde_json::{json, Value}; -use std::collections::HashMap; -use std::fmt::{self, Display}; -use std::io::{BufReader, Read}; -use std::os::unix::net::UnixStream; - -#[derive(Debug, Clone)] -pub enum Event { - Shutdown, - StartFile, - EndFile, - FileLoaded, - TracksChanged, - TrackSwitched, - Idle, - Pause, - Unpause, - Tick, - VideoReconfig, - AudioReconfig, - MetadataUpdate, - Seek, - PlaybackRestart, - PropertyChange { id: usize, property: Property }, - ChapterChange, - ClientMessage { args: Vec }, - Unimplemented, -} - -#[derive(Debug, Clone)] -pub enum Property { - Path(Option), - Pause(bool), - PlaybackTime(Option), - Duration(Option), - Metadata(Option>), - Unknown { name: String, data: MpvDataType }, -} - -#[derive(Debug, Clone)] -pub enum MpvCommand { - LoadFile { - file: String, - option: PlaylistAddOptions, - }, - LoadList { - file: String, - option: PlaylistAddOptions, - }, - PlaylistClear, - PlaylistMove { - from: usize, - to: usize, - }, - Observe { - id: isize, - property: String, - }, - PlaylistNext, - PlaylistPrev, - PlaylistRemove(usize), - PlaylistShuffle, - Quit, - ScriptMessage(Vec), - ScriptMessageTo { - target: String, - args: Vec, - }, - Seek { - seconds: f64, - option: SeekOptions, - }, - Stop, - Unobserve(isize), -} - -#[derive(Debug, Clone)] -pub enum MpvDataType { - Array(Vec), - Bool(bool), - Double(f64), - HashMap(HashMap), - Null, - Playlist(Playlist), - String(String), - Usize(usize), -} - -#[derive(Debug, Clone)] -pub enum NumberChangeOptions { - Absolute, - Increase, - Decrease, -} - -#[derive(Debug, Clone, Copy)] -pub enum PlaylistAddOptions { - Replace, - Append, -} - -#[derive(Debug, Clone, Copy)] -pub enum PlaylistAddTypeOptions { - File, - Playlist, -} - -#[derive(Debug, Clone, Copy)] -pub enum SeekOptions { - Relative, - Absolute, - RelativePercent, - AbsolutePercent, -} - -#[derive(Debug, Clone, Copy)] -pub enum Switch { - On, - Off, - Toggle, -} - -#[derive(Debug, Clone)] -pub enum ErrorCode { - MpvError(String), - JsonParseError(String), - ConnectError(String), - JsonContainsUnexptectedType, - UnexpectedResult, - UnexpectedValue, - MissingValue, - UnsupportedType, - ValueDoesNotContainBool, - ValueDoesNotContainF64, - ValueDoesNotContainHashMap, - ValueDoesNotContainPlaylist, - ValueDoesNotContainString, - ValueDoesNotContainUsize, -} - -pub struct Mpv { - stream: UnixStream, - reader: BufReader, - name: String, -} -#[derive(Debug, Clone)] -pub struct Playlist(pub Vec); -#[derive(Debug, Clone)] -pub struct Error(pub ErrorCode); - -impl Drop for Mpv { - fn drop(&mut self) { - self.disconnect(); - } -} - -impl fmt::Debug for Mpv { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_tuple("Mpv").field(&self.name).finish() - } -} - -impl Clone for Mpv { - fn clone(&self) -> Self { - let stream = self.stream.try_clone().expect("cloning UnixStream"); - let cloned_stream = stream.try_clone().expect("cloning UnixStream"); - Mpv { - stream, - reader: BufReader::new(cloned_stream), - name: self.name.clone(), - } - } - - fn clone_from(&mut self, source: &Self) { - let stream = source.stream.try_clone().expect("cloning UnixStream"); - let cloned_stream = stream.try_clone().expect("cloning UnixStream"); - *self = Mpv { - stream, - reader: BufReader::new(cloned_stream), - name: source.name.clone(), - } - } -} - -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\'") - } - } - } -} - -pub trait GetPropertyTypeHandler: Sized { - fn get_property_generic(instance: &Mpv, property: &str) -> Result; -} - -impl GetPropertyTypeHandler for bool { - fn get_property_generic(instance: &Mpv, property: &str) -> Result { - get_mpv_property::(instance, property) - } -} - -impl GetPropertyTypeHandler for String { - fn get_property_generic(instance: &Mpv, property: &str) -> Result { - get_mpv_property::(instance, property) - } -} - -impl GetPropertyTypeHandler for f64 { - fn get_property_generic(instance: &Mpv, property: &str) -> Result { - get_mpv_property::(instance, property) - } -} - -impl GetPropertyTypeHandler for usize { - fn get_property_generic(instance: &Mpv, property: &str) -> Result { - get_mpv_property::(instance, property) - } -} - -impl GetPropertyTypeHandler for Vec { - fn get_property_generic(instance: &Mpv, property: &str) -> Result, Error> { - get_mpv_property::>(instance, property) - } -} - -impl GetPropertyTypeHandler for HashMap { - fn get_property_generic( - instance: &Mpv, - property: &str, - ) -> Result, Error> { - get_mpv_property::>(instance, property) - } -} - -pub trait SetPropertyTypeHandler { - fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error>; -} - -impl SetPropertyTypeHandler for bool { - fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), Error> { - set_mpv_property(instance, property, json!(value)) - } -} - -impl SetPropertyTypeHandler for String { - fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), Error> { - set_mpv_property(instance, property, json!(value)) - } -} - -impl SetPropertyTypeHandler for f64 { - fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), Error> { - set_mpv_property(instance, property, json!(value)) - } -} - -impl SetPropertyTypeHandler for usize { - fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), Error> { - set_mpv_property(instance, property, json!(value)) - } -} - -impl Mpv { - pub fn connect(socket: &str) -> Result { - match UnixStream::connect(socket) { - Ok(stream) => { - let cloned_stream = stream.try_clone().expect("cloning UnixStream"); - return Ok(Mpv { - stream, - reader: BufReader::new(cloned_stream), - name: String::from(socket), - }); - } - Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))), - } - } - - pub fn disconnect(&self) { - let mut stream = &self.stream; - stream - .shutdown(std::net::Shutdown::Both) - .expect("socket disconnect"); - let mut buffer = [0; 32]; - for _ in 0..stream.bytes().count() { - stream.read(&mut buffer[..]).unwrap(); - } - } - - pub fn get_stream_ref(&self) -> &UnixStream { - &self.stream - } - - pub fn get_metadata(&self) -> Result, Error> { - match get_mpv_property(self, "metadata") { - Ok(map) => Ok(map), - Err(err) => Err(err), - } - } - - pub fn get_playlist(&self) -> Result { - match get_mpv_property::>(self, "playlist") { - Ok(entries) => Ok(Playlist(entries)), - Err(msg) => Err(msg), - } - } - - /// # Description - /// - /// Retrieves the property value from mpv. - /// - /// ## Supported types - /// - String - /// - bool - /// - HashMap (e.g. for the 'metadata' property) - /// - Vec (for the 'playlist' property) - /// - usize - /// - f64 - /// - /// ## Input arguments - /// - /// - **property** defines the mpv property that should be retrieved - /// - /// # Example - /// ``` - /// use mpvipc::{Mpv, Error}; - /// fn main() -> Result<(), Error> { - /// let mpv = Mpv::connect("/tmp/mpvsocket")?; - /// let paused: bool = mpv.get_property("pause")?; - /// let title: String = mpv.get_property("media-title")?; - /// Ok(()) - /// } - /// ``` - pub fn get_property(&self, property: &str) -> Result { T::get_property_generic(self, property) - } - - /// # Description - /// - /// Retrieves the property value from mpv. - /// The result is always of type String, regardless of the type of the value of the mpv property - /// - /// ## Input arguments - /// - /// - **property** defines the mpv property that should be retrieved - /// - /// # Example - /// - /// ``` - /// use mpvipc::{Mpv, Error}; - /// fn main() -> Result<(), Error> { - /// let mpv = Mpv::connect("/tmp/mpvsocket")?; - /// let title = mpv.get_property_string("media-title")?; - /// Ok(()) - /// } - /// ``` - pub fn get_property_string(&self, property: &str) -> Result { - get_mpv_property_string(self, property) - } - - pub fn kill(&self) -> Result<(), Error> { - self.run_command(MpvCommand::Quit) - } - - /// # Description - /// - /// Waits until an mpv event occurs and returns the Event. - /// - /// # Example - /// - /// ```ignore - /// let mut mpv = Mpv::connect("/tmp/mpvsocket")?; - /// loop { - /// let event = mpv.event_listen()?; - /// println!("{:?}", event); - /// } - /// ``` - pub fn event_listen(&mut self) -> Result { - listen(self) - } - - pub fn event_listen_raw(&mut self) -> String { - listen_raw(self) - } - - pub fn next(&self) -> Result<(), Error> { - self.run_command(MpvCommand::PlaylistNext) - } - - pub fn observe_property(&self, id: isize, property: &str) -> Result<(), Error> { - self.run_command(MpvCommand::Observe { - id: id, - property: property.to_string(), - }) - } - - pub fn unobserve_property(&self, id: isize) -> Result<(), Error> { - self.run_command(MpvCommand::Unobserve(id)) - } - - pub fn pause(&self) -> Result<(), Error> { - set_mpv_property(self, "pause", json!(true)) - } - - pub fn prev(&self) -> Result<(), Error> { - self.run_command(MpvCommand::PlaylistPrev) - } - - pub fn restart(&self) -> Result<(), Error> { - self.run_command(MpvCommand::Seek { - seconds: 0f64, - option: SeekOptions::Absolute, - }) - } - - /// # Description - /// - /// Runs mpv commands. The arguments are passed as a String-Vector reference: - /// - /// ## Input arguments - /// - /// - **command** defines the mpv command that should be executed - /// - **args** a slice of &str's which define the arguments - /// - /// # Example - /// ``` - /// use mpvipc::{Mpv, Error}; - /// fn main() -> Result<(), Error> { - /// let mpv = Mpv::connect("/tmp/mpvsocket")?; - /// - /// //Run command 'playlist-shuffle' which takes no arguments - /// mpv.run_command(MpvCommand::PlaylistShuffle)?; - /// - /// //Run command 'seek' which in this case takes two arguments - /// mpv.run_command(MpvCommand::Seek { - /// seconds: 0f64, - /// option: SeekOptions::Absolute, - /// })?; - /// Ok(()) - /// } - /// ``` - pub fn run_command(&self, command: MpvCommand) -> Result<(), Error> { - match command { - MpvCommand::LoadFile { file, option } => run_mpv_command( - self, - "loadfile", - &[ - file.as_ref(), - match option { - PlaylistAddOptions::Append => "append", - PlaylistAddOptions::Replace => "replace", - }, - ], - ), - MpvCommand::LoadList { file, option } => run_mpv_command( - self, - "loadlist", - &[ - file.as_ref(), - match option { - PlaylistAddOptions::Append => "append", - PlaylistAddOptions::Replace => "replace", - }, - ], - ), - MpvCommand::Observe { id, property } => observe_mpv_property(self, &id, &property), - MpvCommand::PlaylistClear => run_mpv_command(self, "playlist-clear", &[]), - MpvCommand::PlaylistMove { from, to } => { - run_mpv_command(self, "playlist-move", &[&from.to_string(), &to.to_string()]) - } - MpvCommand::PlaylistNext => run_mpv_command(self, "playlist-next", &[]), - MpvCommand::PlaylistPrev => run_mpv_command(self, "playlist-prev", &[]), - MpvCommand::PlaylistRemove(id) => { - run_mpv_command(self, "playlist-remove", &[&id.to_string()]) - } - MpvCommand::PlaylistShuffle => run_mpv_command(self, "playlist-shuffle", &[]), - MpvCommand::Quit => run_mpv_command(self, "quit", &[]), - MpvCommand::ScriptMessage(args) => { - let str_args: Vec<_> = args.iter().map(String::as_str).collect(); - run_mpv_command(self, "script-message", &str_args) - } - MpvCommand::ScriptMessageTo { target, args } => { - let mut cmd_args: Vec<_> = vec![target.as_str()]; - let mut str_args: Vec<_> = args.iter().map(String::as_str).collect(); - cmd_args.append(&mut str_args); - run_mpv_command(self, "script-message-to", &cmd_args) - } - MpvCommand::Seek { seconds, option } => run_mpv_command( - self, - "seek", - &[ - &seconds.to_string(), - match option { - SeekOptions::Absolute => "absolute", - SeekOptions::Relative => "relative", - SeekOptions::AbsolutePercent => "absolute-percent", - SeekOptions::RelativePercent => "relative-percent", - }, - ], - ), - MpvCommand::Stop => run_mpv_command(self, "stop", &[]), - MpvCommand::Unobserve(id) => unobserve_mpv_property(self, &id), - } - } - - /// Run a custom command. - /// This should only be used if the desired command is not implemented - /// with [MpvCommand]. - pub fn run_command_raw(&self, command: &str, args: &[&str]) -> Result<(), Error> { - run_mpv_command(self, command, args) - } - - pub fn playlist_add( - &self, - file: &str, - file_type: PlaylistAddTypeOptions, - option: PlaylistAddOptions, - ) -> Result<(), Error> { - match file_type { - PlaylistAddTypeOptions::File => self.run_command(MpvCommand::LoadFile { - file: file.to_string(), - option, - }), - - PlaylistAddTypeOptions::Playlist => self.run_command(MpvCommand::LoadList { - file: file.to_string(), - option, - }), - } - } - - pub fn playlist_clear(&self) -> Result<(), Error> { - self.run_command(MpvCommand::PlaylistClear) - } - - pub fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), Error> { - self.run_command(MpvCommand::PlaylistMove { from, to }) - } - - pub fn playlist_play_id(&self, id: usize) -> Result<(), Error> { - set_mpv_property(self, "playlist-pos", json!(id)) - } - - pub fn playlist_play_next(&self, id: usize) -> Result<(), Error> { - match get_mpv_property::(self, "playlist-pos") { - Ok(current_id) => self.run_command(MpvCommand::PlaylistMove { - from: id, - to: current_id + 1, - }), - Err(msg) => Err(msg), - } - } - - pub fn playlist_remove_id(&self, id: usize) -> Result<(), Error> { - self.run_command(MpvCommand::PlaylistRemove(id)) - } - - pub fn playlist_shuffle(&self) -> Result<(), Error> { - self.run_command(MpvCommand::PlaylistShuffle) - } - - pub fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), Error> { - self.run_command(MpvCommand::Seek { seconds, option }) - } - - pub fn set_loop_file(&self, option: Switch) -> Result<(), Error> { - let mut enabled = false; - match option { - Switch::On => enabled = true, - Switch::Off => {} - Switch::Toggle => match get_mpv_property_string(self, "loop-file") { - Ok(value) => match value.as_ref() { - "false" => { - enabled = true; - } - _ => { - enabled = false; - } - }, - Err(msg) => return Err(msg), - }, - } - set_mpv_property(self, "loop-file", json!(enabled)) - } - - pub fn set_loop_playlist(&self, option: Switch) -> Result<(), Error> { - let mut enabled = false; - match option { - Switch::On => enabled = true, - Switch::Off => {} - Switch::Toggle => match get_mpv_property_string(self, "loop-playlist") { - Ok(value) => match value.as_ref() { - "false" => { - enabled = true; - } - _ => { - enabled = false; - } - }, - Err(msg) => return Err(msg), - }, - } - set_mpv_property(self, "loop-playlist", json!(enabled)) - } - - pub fn set_mute(&self, option: Switch) -> Result<(), Error> { - let mut enabled = false; - match option { - Switch::On => enabled = true, - Switch::Off => {} - Switch::Toggle => match get_mpv_property::(self, "mute") { - Ok(value) => { - enabled = !value; - } - Err(msg) => return Err(msg), - }, - } - set_mpv_property(self, "mute", json!(enabled)) - } - - /// # Description - /// - /// Sets the mpv property __ to __. - /// - /// ## Supported types - /// - String - /// - bool - /// - f64 - /// - usize - /// - /// ## Input arguments - /// - /// - **property** defines the mpv property that should be retrieved - /// - **value** defines the value of the given mpv property __ - /// - /// # Example - /// ``` - /// use mpvipc::{Mpv, Error}; - /// fn main() -> Result<(), Error> { - /// let mpv = Mpv::connect("/tmp/mpvsocket")?; - /// mpv.set_property("pause", true)?; - /// Ok(()) - /// } - /// ``` - pub fn set_property>( - &self, - property: &str, - value: T, - ) -> Result<(), Error> { - T::set_property_generic(self, property, value) - } - - pub fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error> { - match get_mpv_property::(self, "speed") { - Ok(speed) => match option { - NumberChangeOptions::Increase => { - set_mpv_property(self, "speed", json!(speed + input_speed)) - } - - NumberChangeOptions::Decrease => { - set_mpv_property(self, "speed", json!(speed - input_speed)) - } - - NumberChangeOptions::Absolute => { - set_mpv_property(self, "speed", json!(input_speed)) - } - }, - Err(msg) => Err(msg), - } - } - - pub fn set_volume(&self, input_volume: f64, option: NumberChangeOptions) -> Result<(), Error> { - match get_mpv_property::(self, "volume") { - Ok(volume) => match option { - NumberChangeOptions::Increase => { - set_mpv_property(self, "volume", json!(volume + input_volume)) - } - - NumberChangeOptions::Decrease => { - set_mpv_property(self, "volume", json!(volume - input_volume)) - } - - NumberChangeOptions::Absolute => { - set_mpv_property(self, "volume", json!(input_volume)) - } - }, - Err(msg) => Err(msg), - } - } - - pub fn stop(&self) -> Result<(), Error> { - self.run_command(MpvCommand::Stop) - } - - pub fn toggle(&self) -> Result<(), Error> { - run_mpv_command(self, "cycle", &["pause"]) - } -} +pub use api::*; diff --git a/src/message_parser.rs b/src/message_parser.rs new file mode 100644 index 0000000..764add9 --- /dev/null +++ b/src/message_parser.rs @@ -0,0 +1,232 @@ +use std::collections::HashMap; + +use serde_json::Value; + +use crate::{Error, ErrorCode, MpvDataType, PlaylistEntry}; + +pub trait TypeHandler: Sized { + fn get_value(value: Value) -> Result; + fn as_string(&self) -> String; +} + +pub(crate) fn extract_mpv_response_data(value: &Value) -> Result<&Value, Error> { + value + .as_object() + .map(|o| (o.get("error").and_then(|e| e.as_str()), o.get("data"))) + .ok_or(Error(ErrorCode::UnexpectedValue)) + .and_then(|(error, data)| match error { + Some("success") => data.ok_or(Error(ErrorCode::UnexpectedValue)), + Some(e) => Err(Error(ErrorCode::MpvError(e.to_string()))), + None => Err(Error(ErrorCode::UnexpectedValue)), + }) +} + +impl TypeHandler for String { + fn get_value(value: Value) -> Result { + extract_mpv_response_data(&value) + .and_then(|d| { + d.as_str() + .ok_or(Error(ErrorCode::ValueDoesNotContainString)) + }) + .map(|s| s.to_string()) + } + + fn as_string(&self) -> String { + self.to_string() + } +} + +impl TypeHandler for bool { + fn get_value(value: Value) -> Result { + extract_mpv_response_data(&value) + .and_then(|d| d.as_bool().ok_or(Error(ErrorCode::ValueDoesNotContainBool))) + } + + fn as_string(&self) -> String { + if *self { + "true".to_string() + } else { + "false".to_string() + } + } +} + +impl TypeHandler for f64 { + fn get_value(value: Value) -> Result { + extract_mpv_response_data(&value) + .and_then(|d| d.as_f64().ok_or(Error(ErrorCode::ValueDoesNotContainF64))) + } + + fn as_string(&self) -> String { + self.to_string() + } +} + +impl TypeHandler for usize { + fn get_value(value: Value) -> Result { + extract_mpv_response_data(&value) + .and_then(|d| d.as_u64().ok_or(Error(ErrorCode::ValueDoesNotContainUsize))) + .map(|u| u as usize) + } + + fn as_string(&self) -> String { + self.to_string() + } +} + +impl TypeHandler for HashMap { + fn get_value(value: Value) -> Result, Error> { + extract_mpv_response_data(&value) + .and_then(|d| { + d.as_object() + .ok_or(Error(ErrorCode::ValueDoesNotContainHashMap)) + }) + .map(json_map_to_hashmap) + } + + fn as_string(&self) -> String { + format!("{:?}", self) + } +} + +impl TypeHandler for Vec { + fn get_value(value: Value) -> Result, Error> { + extract_mpv_response_data(&value) + .and_then(|d| { + d.as_array() + .ok_or(Error(ErrorCode::ValueDoesNotContainPlaylist)) + }) + .map(json_array_to_playlist) + } + + fn as_string(&self) -> String { + format!("{:?}", self) + } +} + +pub(crate) fn json_map_to_hashmap( + map: &serde_json::map::Map, +) -> HashMap { + let mut output_map: HashMap = HashMap::new(); + for (ref key, ref value) in map.iter() { + match **value { + Value::Array(ref array) => { + output_map.insert( + key.to_string(), + MpvDataType::Array(json_array_to_vec(array)), + ); + } + Value::Bool(ref b) => { + output_map.insert(key.to_string(), MpvDataType::Bool(*b)); + } + Value::Number(ref n) => { + if n.is_u64() { + output_map.insert( + key.to_string(), + MpvDataType::Usize(n.as_u64().unwrap() as usize), + ); + } else if n.is_f64() { + output_map.insert(key.to_string(), MpvDataType::Double(n.as_f64().unwrap())); + } else { + panic!("unimplemented number"); + } + } + Value::String(ref s) => { + output_map.insert(key.to_string(), MpvDataType::String(s.to_string())); + } + Value::Object(ref m) => { + output_map.insert( + key.to_string(), + MpvDataType::HashMap(json_map_to_hashmap(m)), + ); + } + Value::Null => { + unimplemented!(); + } + } + } + output_map +} + +pub(crate) fn json_array_to_vec(array: &Vec) -> Vec { + let mut output: Vec = Vec::new(); + if array.len() > 0 { + match array[0] { + Value::Array(_) => { + for entry in array { + if let Value::Array(ref a) = *entry { + output.push(MpvDataType::Array(json_array_to_vec(a))); + } + } + } + + Value::Bool(_) => { + for entry in array { + if let Value::Bool(ref b) = *entry { + output.push(MpvDataType::Bool(*b)); + } + } + } + + Value::Number(_) => { + for entry in array { + if let Value::Number(ref n) = *entry { + if n.is_u64() { + output.push(MpvDataType::Usize(n.as_u64().unwrap() as usize)); + } else if n.is_f64() { + output.push(MpvDataType::Double(n.as_f64().unwrap())); + } else { + panic!("unimplemented number"); + } + } + } + } + + Value::Object(_) => { + for entry in array { + if let Value::Object(ref map) = *entry { + output.push(MpvDataType::HashMap(json_map_to_hashmap(map))); + } + } + } + + Value::String(_) => { + for entry in array { + if let Value::String(ref s) = *entry { + output.push(MpvDataType::String(s.to_string())); + } + } + } + + Value::Null => { + unimplemented!(); + } + } + } + output +} + +pub(crate) fn json_array_to_playlist(array: &Vec) -> Vec { + let mut output: Vec = Vec::new(); + for (id, entry) in array.iter().enumerate() { + let mut filename: String = String::new(); + let mut title: String = String::new(); + let mut current: bool = false; + if let Value::String(ref f) = entry["filename"] { + filename = f.to_string(); + } + if let Value::String(ref t) = entry["title"] { + title = t.to_string(); + } + if let Value::Bool(ref b) = entry["current"] { + current = *b; + } + output.push(PlaylistEntry { + id, + filename, + title, + current, + }); + } + output +}