diff options
Diffstat (limited to '')
-rw-r--r-- | libmpv2/src/mpv/protocol.rs | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/libmpv2/src/mpv/protocol.rs b/libmpv2/src/mpv/protocol.rs new file mode 100644 index 0000000..4ae4f16 --- /dev/null +++ b/libmpv2/src/mpv/protocol.rs @@ -0,0 +1,261 @@ +// yt - A fully featured command line YouTube client +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Yt. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +use super::*; + +use std::alloc::{self, Layout}; +use std::marker::PhantomData; +use std::mem; +use std::os::raw as ctype; +use std::panic; +use std::panic::RefUnwindSafe; +use std::slice; +use std::sync::{atomic::Ordering, Mutex}; + +impl Mpv { + /// Create a context with which custom protocols can be registered. + /// + /// # Panics + /// Panics if a context already exists + pub fn create_protocol_context<T, U>(&self) -> ProtocolContext<T, U> + where + T: RefUnwindSafe, + U: RefUnwindSafe, + { + match self.protocols_guard.compare_exchange( + false, + true, + Ordering::AcqRel, + Ordering::Acquire, + ) { + Ok(_) => ProtocolContext::new(self.ctx, PhantomData::<&Self>), + Err(_) => panic!("A protocol context already exists"), + } + } +} + +/// Return a persistent `T` that is passed to all other `Stream*` functions, panic on errors. +pub type StreamOpen<T, U> = fn(&mut U, &str) -> T; +/// Do any necessary cleanup. +pub type StreamClose<T> = fn(Box<T>); +/// Seek to the given offset. Return the new offset, or either `MpvError::Generic` if seeking +/// failed or panic. +pub type StreamSeek<T> = fn(&mut T, i64) -> i64; +/// Target buffer with fixed capacity. +/// Return either the number of read bytes, `0` on EOF, or either `-1` or panic on error. +pub type StreamRead<T> = fn(&mut T, &mut [ctype::c_char]) -> i64; +/// Return the total size of the stream in bytes. Panic on error. +pub type StreamSize<T> = fn(&mut T) -> i64; + +unsafe extern "C" fn open_wrapper<T, U>( + user_data: *mut ctype::c_void, + uri: *mut ctype::c_char, + info: *mut libmpv2_sys::mpv_stream_cb_info, +) -> ctype::c_int +where + T: RefUnwindSafe, + U: RefUnwindSafe, +{ + let data = user_data as *mut ProtocolData<T, U>; + + (*info).cookie = user_data; + (*info).read_fn = Some(read_wrapper::<T, U>); + (*info).seek_fn = Some(seek_wrapper::<T, U>); + (*info).size_fn = Some(size_wrapper::<T, U>); + (*info).close_fn = Some(close_wrapper::<T, U>); + + let ret = panic::catch_unwind(|| { + let uri = mpv_cstr_to_str!(uri as *const _).unwrap(); + ptr::write( + (*data).cookie, + ((*data).open_fn)(&mut (*data).user_data, uri), + ); + }); + + if ret.is_ok() { + 0 + } else { + mpv_error::Generic as _ + } +} + +unsafe extern "C" fn read_wrapper<T, U>( + cookie: *mut ctype::c_void, + buf: *mut ctype::c_char, + nbytes: u64, +) -> i64 +where + T: RefUnwindSafe, + U: RefUnwindSafe, +{ + let data = cookie as *mut ProtocolData<T, U>; + + let ret = panic::catch_unwind(|| { + let slice = slice::from_raw_parts_mut(buf, nbytes as _); + ((*data).read_fn)(&mut *(*data).cookie, slice) + }); + if let Ok(ret) = ret { + ret + } else { + -1 + } +} + +unsafe extern "C" fn seek_wrapper<T, U>(cookie: *mut ctype::c_void, offset: i64) -> i64 +where + T: RefUnwindSafe, + U: RefUnwindSafe, +{ + let data = cookie as *mut ProtocolData<T, U>; + + if (*data).seek_fn.is_none() { + return mpv_error::Unsupported as _; + } + + let ret = + panic::catch_unwind(|| (*(*data).seek_fn.as_ref().unwrap())(&mut *(*data).cookie, offset)); + if let Ok(ret) = ret { + ret + } else { + mpv_error::Generic as _ + } +} + +unsafe extern "C" fn size_wrapper<T, U>(cookie: *mut ctype::c_void) -> i64 +where + T: RefUnwindSafe, + U: RefUnwindSafe, +{ + let data = cookie as *mut ProtocolData<T, U>; + + if (*data).size_fn.is_none() { + return mpv_error::Unsupported as _; + } + + let ret = panic::catch_unwind(|| (*(*data).size_fn.as_ref().unwrap())(&mut *(*data).cookie)); + if let Ok(ret) = ret { + ret + } else { + mpv_error::Unsupported as _ + } +} + +#[allow(unused_must_use)] +unsafe extern "C" fn close_wrapper<T, U>(cookie: *mut ctype::c_void) +where + T: RefUnwindSafe, + U: RefUnwindSafe, +{ + let data = Box::from_raw(cookie as *mut ProtocolData<T, U>); + + panic::catch_unwind(|| ((*data).close_fn)(Box::from_raw((*data).cookie))); +} + +struct ProtocolData<T, U> { + cookie: *mut T, + user_data: U, + + open_fn: StreamOpen<T, U>, + close_fn: StreamClose<T>, + read_fn: StreamRead<T>, + seek_fn: Option<StreamSeek<T>>, + size_fn: Option<StreamSize<T>>, +} + +/// This context holds state relevant to custom protocols. +/// It is created by calling `Mpv::create_protocol_context`. +pub struct ProtocolContext<'parent, T: RefUnwindSafe, U: RefUnwindSafe> { + ctx: NonNull<libmpv2_sys::mpv_handle>, + protocols: Mutex<Vec<Protocol<T, U>>>, + _does_not_outlive: PhantomData<&'parent Mpv>, +} + +unsafe impl<'parent, T: RefUnwindSafe, U: RefUnwindSafe> Send for ProtocolContext<'parent, T, U> {} +unsafe impl<'parent, T: RefUnwindSafe, U: RefUnwindSafe> Sync for ProtocolContext<'parent, T, U> {} + +impl<'parent, T: RefUnwindSafe, U: RefUnwindSafe> ProtocolContext<'parent, T, U> { + fn new( + ctx: NonNull<libmpv2_sys::mpv_handle>, + marker: PhantomData<&'parent Mpv>, + ) -> ProtocolContext<'parent, T, U> { + ProtocolContext { + ctx, + protocols: Mutex::new(Vec::new()), + _does_not_outlive: marker, + } + } + + /// Register a custom `Protocol`. Once a protocol has been registered, it lives as long as + /// `Mpv`. + /// + /// Returns `Error::Mpv(MpvError::InvalidParameter)` if a protocol with the same name has + /// already been registered. + pub fn register(&self, protocol: Protocol<T, U>) -> Result<()> { + let mut protocols = self.protocols.lock().unwrap(); + protocol.register(self.ctx.as_ptr())?; + protocols.push(protocol); + Ok(()) + } +} + +/// `Protocol` holds all state used by a custom protocol. +pub struct Protocol<T: Sized + RefUnwindSafe, U: RefUnwindSafe> { + name: String, + data: *mut ProtocolData<T, U>, +} + +impl<T: RefUnwindSafe, U: RefUnwindSafe> Protocol<T, U> { + /// `name` is the prefix of the protocol, e.g. `name://path`. + /// + /// `user_data` is data that will be passed to `open_fn`. + /// + /// # Safety + /// Do not call libmpv functions in any supplied function. + /// All panics of the provided functions are catched and can be used as generic error returns. + pub unsafe fn new( + name: String, + user_data: U, + open_fn: StreamOpen<T, U>, + close_fn: StreamClose<T>, + read_fn: StreamRead<T>, + seek_fn: Option<StreamSeek<T>>, + size_fn: Option<StreamSize<T>>, + ) -> Protocol<T, U> { + let c_layout = Layout::from_size_align(mem::size_of::<T>(), mem::align_of::<T>()).unwrap(); + let cookie = alloc::alloc(c_layout) as *mut T; + let data = Box::into_raw(Box::new(ProtocolData { + cookie, + user_data, + + open_fn, + close_fn, + read_fn, + seek_fn, + size_fn, + })); + + Protocol { name, data } + } + + fn register(&self, ctx: *mut libmpv2_sys::mpv_handle) -> Result<()> { + let name = CString::new(&self.name[..])?; + unsafe { + mpv_err( + (), + libmpv2_sys::mpv_stream_cb_add_ro( + ctx, + name.as_ptr(), + self.data as *mut _, + Some(open_wrapper::<T, U>), + ), + ) + } + } +} |