hitaki 0.2.0

API bindings for hitaki library
Documentation
// SPDX-License-Identifier: MIT
use super::*;

/// Trait which should be implemented by subclass of [`AlsaFirewire`][crate::AlsaFirewire].
pub trait AlsaFirewireImpl: ObjectImpl {
    fn open(&self, unit: &Self::Type, path: &str, open_flag: i32) -> Result<(), Error>;
    fn lock(&self, unit: &Self::Type) -> Result<(), Error>;
    fn unlock(&self, unit: &Self::Type) -> Result<(), Error>;
    fn create_source(&self, unit: &Self::Type) -> Result<Source, Error>;
}

/// Trait which is automatically implemented to implementator of
/// [`AlsaFirewireImpl`][self::AlsaFirewireImpl]
pub trait AlsaFirewireImplExt: ObjectSubclass {
    fn parent_open(&self, unit: &Self::Type, path: &str, open_flag: i32) -> Result<(), Error>;
    fn parent_lock(&self, unit: &Self::Type) -> Result<(), Error>;
    fn parent_unlock(&self, unit: &Self::Type) -> Result<(), Error>;
    fn parent_create_source(&self, unit: &Self::Type) -> Result<Source, Error>;
}

impl<T: AlsaFirewireImpl> AlsaFirewireImplExt for T {
    fn parent_open(&self, unit: &Self::Type, path: &str, open_flag: i32) -> Result<(), Error> {
        unsafe {
            let type_data = Self::type_data();
            let parent_iface = type_data.as_ref().parent_interface::<AlsaFirewire>()
                as *const ffi::HitakiAlsaFirewireInterface;
            let func = (*parent_iface)
                .open
                .expect("no parent \"open\" implementation");

            let mut error = std::ptr::null_mut();
            let is_ok = func(
                unit.unsafe_cast_ref::<AlsaFirewire>().to_glib_none().0,
                path.to_glib_none().0,
                open_flag,
                &mut error,
            );
            assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null());

            if error.is_null() {
                Ok(())
            } else {
                Err(from_glib_full(error))
            }
        }
    }

    fn parent_lock(&self, unit: &Self::Type) -> Result<(), Error> {
        unsafe {
            let type_data = Self::type_data();
            let parent_iface = type_data.as_ref().parent_interface::<AlsaFirewire>()
                as *const ffi::HitakiAlsaFirewireInterface;
            let func = (*parent_iface)
                .lock
                .expect("no parent \"lock\" implementation");

            let mut error = std::ptr::null_mut();
            let is_ok = func(
                unit.unsafe_cast_ref::<AlsaFirewire>().to_glib_none().0,
                &mut error,
            );
            assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null());

            if error.is_null() {
                Ok(())
            } else {
                Err(from_glib_full(error))
            }
        }
    }

    fn parent_unlock(&self, unit: &Self::Type) -> Result<(), Error> {
        unsafe {
            let type_data = Self::type_data();
            let parent_iface = type_data.as_ref().parent_interface::<AlsaFirewire>()
                as *const ffi::HitakiAlsaFirewireInterface;
            let func = (*parent_iface)
                .unlock
                .expect("no parent \"unlock\" implementation");

            let mut error = std::ptr::null_mut();
            let is_ok = func(
                unit.unsafe_cast_ref::<AlsaFirewire>().to_glib_none().0,
                &mut error,
            );
            assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null());

            if error.is_null() {
                Ok(())
            } else {
                Err(from_glib_full(error))
            }
        }
    }

    fn parent_create_source(&self, unit: &Self::Type) -> Result<Source, Error> {
        unsafe {
            let type_data = Self::type_data();
            let parent_iface = type_data.as_ref().parent_interface::<AlsaFirewire>()
                as *const ffi::HitakiAlsaFirewireInterface;
            let func = (*parent_iface)
                .create_source
                .expect("no parent \"create_source\" implementation");

            let mut source = std::ptr::null_mut();
            let mut error = std::ptr::null_mut();
            let is_ok = func(
                unit.unsafe_cast_ref::<AlsaFirewire>().to_glib_none().0,
                &mut source,
                &mut error,
            );
            assert_eq!(is_ok == glib::ffi::GFALSE, !error.is_null());

            if error.is_null() {
                Ok(from_glib_full(source))
            } else {
                Err(from_glib_full(error))
            }
        }
    }
}

unsafe impl<T: AlsaFirewireImpl> IsImplementable<T> for AlsaFirewire {
    fn interface_init(iface: &mut Interface<Self>) {
        let iface = iface.as_mut();
        iface.open = Some(alsa_firewire_open::<T>);
        iface.lock = Some(alsa_firewire_lock::<T>);
        iface.unlock = Some(alsa_firewire_unlock::<T>);
        iface.create_source = Some(alsa_firewire_create_source::<T>);
    }
}

unsafe extern "C" fn alsa_firewire_open<T: AlsaFirewireImpl>(
    unit: *mut ffi::HitakiAlsaFirewire,
    path: *const c_char,
    open_flag: c_int,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    let instance = &*(unit as *mut T::Instance);
    let imp = instance.imp();
    match imp.open(
        from_glib_borrow::<_, AlsaFirewire>(unit).unsafe_cast_ref(),
        std::ffi::CStr::from_ptr(path).to_str().unwrap(),
        open_flag,
    ) {
        Ok(()) => glib::ffi::GTRUE,
        Err(err) => {
            if !error.is_null() {
                *error = err.into_raw();
            }
            glib::ffi::GFALSE
        }
    }
}

unsafe extern "C" fn alsa_firewire_lock<T: AlsaFirewireImpl>(
    unit: *mut ffi::HitakiAlsaFirewire,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    let instance = &*(unit as *mut T::Instance);
    let imp = instance.imp();
    match imp.lock(from_glib_borrow::<_, AlsaFirewire>(unit).unsafe_cast_ref()) {
        Ok(()) => glib::ffi::GTRUE,
        Err(err) => {
            if !error.is_null() {
                *error = err.into_raw();
            }
            glib::ffi::GFALSE
        }
    }
}

unsafe extern "C" fn alsa_firewire_unlock<T: AlsaFirewireImpl>(
    unit: *mut ffi::HitakiAlsaFirewire,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    let instance = &*(unit as *mut T::Instance);
    let imp = instance.imp();
    match imp.unlock(from_glib_borrow::<_, AlsaFirewire>(unit).unsafe_cast_ref()) {
        Ok(()) => glib::ffi::GTRUE,
        Err(err) => {
            if !error.is_null() {
                *error = err.into_raw();
            }
            glib::ffi::GFALSE
        }
    }
}

unsafe extern "C" fn alsa_firewire_create_source<T: AlsaFirewireImpl>(
    unit: *mut ffi::HitakiAlsaFirewire,
    source: *mut *mut glib::ffi::GSource,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    let instance = &*(unit as *mut T::Instance);
    let imp = instance.imp();
    match imp.create_source(from_glib_borrow::<_, AlsaFirewire>(unit).unsafe_cast_ref()) {
        Ok(src) => {
            *source = src.to_glib_none().0;
            glib::ffi::GTRUE
        }
        Err(err) => {
            if !error.is_null() {
                *error = err.into_raw();
            }
            glib::ffi::GFALSE
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{subclass::alsa_firewire::*, traits::*};
    use glib::{subclass::prelude::*, Object, ParamSpec, ParamSpecOverride, ToValue, Value};

    const UNIT_TYPE: AlsaFirewireType = AlsaFirewireType::Tascam;
    const CARD_ID: u32 = 117;
    const NODE_DEVICE: &str = "blank";
    const GUID: u64 = 315;

    pub mod imp {
        use super::*;
        use std::cell::RefCell;

        pub struct AlsaFirewireTest {
            is_locked: RefCell<bool>,
            is_disconnected: RefCell<bool>,
        }

        impl Default for AlsaFirewireTest {
            fn default() -> Self {
                Self {
                    is_locked: Default::default(),
                    is_disconnected: Default::default(),
                }
            }
        }

        #[glib::object_subclass]
        impl ObjectSubclass for AlsaFirewireTest {
            const NAME: &'static str = "AlsaFirewireTest";
            type Type = super::AlsaFirewireTest;
            type Interfaces = (AlsaFirewire,);

            fn new() -> Self {
                Self::default()
            }
        }

        impl ObjectImpl for AlsaFirewireTest {
            fn set_property(
                &self,
                _unit: &Self::Type,
                _id: usize,
                value: &Value,
                pspec: &ParamSpec,
            ) {
                match pspec.name() {
                    "is-locked" => {
                        let is_locked = value.get().unwrap();
                        self.is_locked.replace(is_locked);
                    }
                    "is-disconnected" => {
                        let is_disconnected = value.get().unwrap();
                        self.is_disconnected.replace(is_disconnected);
                    }
                    _ => unimplemented!(),
                }
            }

            fn properties() -> &'static [ParamSpec] {
                use once_cell::sync::Lazy;
                static PROPERTIES: Lazy<Vec<ParamSpec>> = Lazy::new(|| {
                    vec![
                        ParamSpecOverride::for_interface::<AlsaFirewire>("unit-type"),
                        ParamSpecOverride::for_interface::<AlsaFirewire>("card-id"),
                        ParamSpecOverride::for_interface::<AlsaFirewire>("node-device"),
                        ParamSpecOverride::for_interface::<AlsaFirewire>("is-locked"),
                        ParamSpecOverride::for_interface::<AlsaFirewire>("guid"),
                        ParamSpecOverride::for_interface::<AlsaFirewire>("is-disconnected"),
                    ]
                });

                PROPERTIES.as_ref()
            }

            fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value {
                match pspec.name() {
                    "unit-type" => UNIT_TYPE.to_value(),
                    "card-id" => CARD_ID.to_value(),
                    "node-device" => NODE_DEVICE.to_value(),
                    "is-locked" => self.is_locked.borrow().to_value(),
                    "guid" => GUID.to_value(),
                    "is-disconnected" => self.is_disconnected.borrow().to_value(),
                    _ => unimplemented!(),
                }
            }
        }

        impl AlsaFirewireImpl for AlsaFirewireTest {
            fn open(&self, _unit: &Self::Type, _path: &str, _open_flag: i32) -> Result<(), Error> {
                Ok(())
            }

            fn lock(&self, _unit: &Self::Type) -> Result<(), Error> {
                Ok(())
            }

            fn unlock(&self, _unit: &Self::Type) -> Result<(), Error> {
                Ok(())
            }

            fn create_source(&self, _unit: &Self::Type) -> Result<Source, Error> {
                Err(Error::new(AlsaFirewireError::Failed, "expected failure"))
            }
        }
    }

    glib::wrapper! {
        pub struct AlsaFirewireTest(ObjectSubclass<imp::AlsaFirewireTest>)
            @implements AlsaFirewire;
    }

    #[allow(clippy::new_without_default)]
    impl AlsaFirewireTest {
        pub fn new() -> Self {
            Object::new(&[]).expect("Failed creation/initialization of AlsaFirewireTest object")
        }
    }

    #[test]
    fn alsa_firewire_iface() {
        let unit = AlsaFirewireTest::new();

        assert_eq!(unit.open("hoge", 0), Ok(()));
        assert_eq!(unit.lock(), Ok(()));
        assert_eq!(unit.unlock(), Ok(()));
        assert!(unit.create_source().is_err());

        assert_eq!(unit.unit_type(), UNIT_TYPE);
        assert_eq!(unit.card_id(), CARD_ID);
        assert_eq!(unit.node_device().unwrap().as_str(), NODE_DEVICE);
        assert_eq!(unit.guid(), GUID);

        assert_eq!(unit.is_locked(), false);
        unit.set_is_locked(true);
        assert_eq!(unit.is_locked(), true);

        assert_eq!(unit.is_disconnected(), false);
        unit.set_is_disconnected(true);
        assert_eq!(unit.is_disconnected(), true);
    }
}