From 1debeb77f7986de1b659dcfdc442de6415e1d9f5 Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Wed, 21 Aug 2024 10:49:23 +0200 Subject: chore: Initial Commit This repository was migrated out of my nixos-config. --- libmpv2/src/mpv/protocol.rs | 261 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 libmpv2/src/mpv/protocol.rs (limited to 'libmpv2/src/mpv/protocol.rs') 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 +// 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 . + +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(&self) -> ProtocolContext + 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 = fn(&mut U, &str) -> T; +/// Do any necessary cleanup. +pub type StreamClose = fn(Box); +/// Seek to the given offset. Return the new offset, or either `MpvError::Generic` if seeking +/// failed or panic. +pub type StreamSeek = 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 = fn(&mut T, &mut [ctype::c_char]) -> i64; +/// Return the total size of the stream in bytes. Panic on error. +pub type StreamSize = fn(&mut T) -> i64; + +unsafe extern "C" fn open_wrapper( + 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; + + (*info).cookie = user_data; + (*info).read_fn = Some(read_wrapper::); + (*info).seek_fn = Some(seek_wrapper::); + (*info).size_fn = Some(size_wrapper::); + (*info).close_fn = Some(close_wrapper::); + + 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( + cookie: *mut ctype::c_void, + buf: *mut ctype::c_char, + nbytes: u64, +) -> i64 +where + T: RefUnwindSafe, + U: RefUnwindSafe, +{ + let data = cookie as *mut ProtocolData; + + 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(cookie: *mut ctype::c_void, offset: i64) -> i64 +where + T: RefUnwindSafe, + U: RefUnwindSafe, +{ + let data = cookie as *mut ProtocolData; + + 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(cookie: *mut ctype::c_void) -> i64 +where + T: RefUnwindSafe, + U: RefUnwindSafe, +{ + let data = cookie as *mut ProtocolData; + + 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(cookie: *mut ctype::c_void) +where + T: RefUnwindSafe, + U: RefUnwindSafe, +{ + let data = Box::from_raw(cookie as *mut ProtocolData); + + panic::catch_unwind(|| ((*data).close_fn)(Box::from_raw((*data).cookie))); +} + +struct ProtocolData { + cookie: *mut T, + user_data: U, + + open_fn: StreamOpen, + close_fn: StreamClose, + read_fn: StreamRead, + seek_fn: Option>, + size_fn: Option>, +} + +/// 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, + protocols: Mutex>>, + _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, + 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) -> 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 { + name: String, + data: *mut ProtocolData, +} + +impl Protocol { + /// `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, + close_fn: StreamClose, + read_fn: StreamRead, + seek_fn: Option>, + size_fn: Option>, + ) -> Protocol { + let c_layout = Layout::from_size_align(mem::size_of::(), mem::align_of::()).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::), + ), + ) + } + } +} -- cgit 1.4.1