about summary refs log tree commit diff stats
path: root/crates/libmpv2/src/mpv/protocol.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-23 12:57:19 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-23 12:58:02 +0200
commit0ae5018c33cc4bfe27583c9902472b499f4bd269 (patch)
treeafc2fbfcb126215f47afbc32e555d203d4d6d88c /crates/libmpv2/src/mpv/protocol.rs
parentchore(yt_dlp/progress_hook): Also consider the `total_bytes_estimate` field (diff)
downloadyt-0ae5018c33cc4bfe27583c9902472b499f4bd269.tar.gz
yt-0ae5018c33cc4bfe27583c9902472b499f4bd269.zip
refactor(libmpv2): Move to the `crates` directory
Diffstat (limited to 'crates/libmpv2/src/mpv/protocol.rs')
-rw-r--r--crates/libmpv2/src/mpv/protocol.rs261
1 files changed, 261 insertions, 0 deletions
diff --git a/crates/libmpv2/src/mpv/protocol.rs b/crates/libmpv2/src/mpv/protocol.rs
new file mode 100644
index 0000000..4ae4f16
--- /dev/null
+++ b/crates/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>),
+                ),
+            )
+        }
+    }
+}