clepsydre-rebind 0.2.0

Safe bindings for libclepsydre
// rustdoc-stripper-ignore-next
//! Traits intended for subclassing [`Manager`].

use std::{ffi::c_char, ptr};

use ffi::{ClepsydreManager, ClepsydreSubscription};
use gio::ffi::{
    GAsyncReadyCallback, GAsyncResult, GCancellable, GListStore, g_task_new,
    g_task_propagate_pointer, g_task_return_error, g_task_return_pointer,
};
use glib::{
    DateTime,
    ffi::{GDateTime, GError, gpointer},
    gobject_ffi::{GObject, g_object_unref},
    prelude::*,
    subclass::prelude::*,
    translate::*,
};

use crate::{Manager, Subscription, ffi};

pub trait ManagerImpl: ObjectImpl + ObjectSubclass<Type: IsA<Manager>> {
    fn search_events(&self, search: &str) -> crate::Result<gio::ListStore> {
        self.parent_search_events(search)
    }

    fn search_events_future(&self, search: &str) -> crate::PinnedFutureResult<gio::ListStore> {
        self.parent_search_events_future(search)
    }

    fn new_subscription(&self, start: &DateTime, end: &DateTime) -> crate::Result<Subscription> {
        self.parent_new_subscription(start, end)
    }
}

pub trait ManagerImplExt: ManagerImpl {
    fn parent_search_events(&self, search: &str) -> crate::Result<gio::ListStore> {
        unsafe {
            let data = Self::type_data();
            let parent_class = data.as_ref().parent_class() as *mut ffi::ClepsydreManagerClass;
            if let Some(f) = (*parent_class).search_events {
                let mut error = ptr::null_mut();
                let result = f(
                    self.obj().unsafe_cast_ref::<Manager>().to_glib_none().0,
                    search.to_glib_none().0,
                    &mut error,
                );
                if error.is_null() {
                    Ok(from_glib_full(result))
                } else {
                    Err(from_glib_full(error))
                }
            } else {
                unimplemented!()
            }
        }
    }

    fn parent_search_events_future(
        &self,
        search: &str,
    ) -> crate::PinnedFutureResult<gio::ListStore> {
        struct ManagerListStoreAsyncData {
            finish: unsafe extern "C" fn(
                *mut ClepsydreManager,
                *mut GAsyncResult,
                *mut *mut GError,
            ) -> *mut GListStore,
            sender: gio::GioFutureResult<crate::Result<gio::ListStore>>,
        }

        unsafe extern "C" fn manager_parent_search_events_trampoline(
            source_object: *mut GObject,
            result: *mut GAsyncResult,
            user_data: gpointer,
        ) {
            unsafe {
                let data = Box::from_raw(user_data as *mut ManagerListStoreAsyncData);
                let mut error = ptr::null_mut();
                let store_ptr =
                    (data.finish)(source_object as *mut ClepsydreManager, result, &mut error);
                let result = if error.is_null() {
                    Ok(from_glib_full(store_ptr))
                } else {
                    Err(from_glib_full(error))
                };
                data.sender.resolve(result);
            }
        }

        let search = search.to_string();

        let obj = self.obj();
        let manager = unsafe { obj.unsafe_cast_ref::<Manager>() };

        Box::pin(gio::GioFuture::new(
            manager,
            move |obj, cancellable, send| unsafe {
                let data = Self::type_data();
                let parent_class = data.as_ref().parent_class() as *mut ffi::ClepsydreManagerClass;
                match (
                    (*parent_class).search_events_async,
                    (*parent_class).search_events_finish,
                ) {
                    (Some(async_fn), Some(finish_fn)) => {
                        let user_data = Box::new(ManagerListStoreAsyncData {
                            finish: finish_fn,
                            sender: send,
                        });
                        async_fn(
                            obj.to_glib_none().0,
                            search.to_glib_none().0,
                            cancellable.to_glib_none().0,
                            Some(manager_parent_search_events_trampoline),
                            Box::into_raw(user_data) as gpointer,
                        );
                    }
                    _ => unimplemented!(),
                }
            },
        ))
    }

    fn parent_new_subscription(
        &self,
        start: &DateTime,
        end: &DateTime,
    ) -> crate::Result<Subscription> {
        unsafe {
            let data = Self::type_data();
            let parent_class = data.as_ref().parent_class() as *mut ffi::ClepsydreManagerClass;
            if let Some(f) = (*parent_class).new_subscription {
                let mut error = ptr::null_mut();
                let result = f(
                    self.obj().unsafe_cast_ref::<Manager>().to_glib_none().0,
                    start.to_glib_none().0,
                    end.to_glib_none().0,
                    &mut error,
                );
                if error.is_null() {
                    Ok(from_glib_full(result))
                } else {
                    Err(from_glib_full(error))
                }
            } else {
                unimplemented!()
            }
        }
    }
}

impl<T: ManagerImpl> ManagerImplExt for T {}

unsafe impl<T: ManagerImpl> IsSubclassable<T> for Manager {
    fn class_init(class: &mut ::glib::Class<Self>) {
        Self::parent_class_init::<T>(class);

        let klass = class.as_mut();

        klass.search_events = Some(manager_search_events::<T>);
        klass.search_events_async = Some(manager_search_events_async::<T>);
        klass.search_events_finish = Some(manager_search_events_finish);

        klass.new_subscription = Some(manager_new_subscription::<T>);
    }
}

/// # Safety
///
/// - `manager_ptr` has to be a pointer to a valid ClepsydreManager instance.
/// - `search_ptr` has to be a valid, null-terminated UTF-8 string.
/// - `error_ptr` has to be NULL, or to point to a writable region of memory.
unsafe extern "C" fn manager_search_events<T: ManagerImpl>(
    manager_ptr: *mut ClepsydreManager,
    search_ptr: *const c_char,
    error_ptr: *mut *mut GError,
) -> *mut GListStore {
    unsafe {
        let instance = &*(manager_ptr as *mut T::Instance);
        let imp = instance.imp();
        let search = glib::GStr::from_ptr(search_ptr).as_str();

        match imp.search_events(search) {
            Ok(store) => store.to_glib_full(),
            Err(error) => {
                if !error_ptr.is_null() {
                    *error_ptr = error.into_glib_ptr();
                }
                ptr::null_mut()
            }
        }
    }
}

unsafe extern "C" fn manager_search_events_async<T: ManagerImpl>(
    manager_ptr: *mut ffi::ClepsydreManager,
    search_ptr: *const c_char,
    cancellable: *mut GCancellable,
    callback: GAsyncReadyCallback,
    user_data: gpointer,
) {
    unsafe {
        let task = g_task_new(manager_ptr as *mut _, cancellable, callback, user_data);

        let instance = &*(manager_ptr as *mut T::Instance);
        let imp = instance.imp();
        // TODO: Figure out where to free this string
        let search = glib::GStr::from_ptr(search_ptr).to_string().leak();

        let future = imp.search_events_future(search);

        glib::MainContext::ref_thread_default().spawn_local(async move {
            match future.await {
                Ok(store) => {
                    let store_ptr: *mut GListStore = store.to_glib_full();

                    g_task_return_pointer(
                        task,
                        store_ptr as gpointer,
                        Some(std::mem::transmute::<
                            unsafe extern "C" fn(*mut GObject),
                            unsafe extern "C" fn(gpointer),
                        >(g_object_unref)),
                    );
                }
                Err(error) => g_task_return_error(task, error.into_glib_ptr()),
            }
            g_object_unref(task as *mut _);
        });
    }
}

unsafe extern "C" fn manager_search_events_finish(
    _manager_ptr: *mut ClepsydreManager,
    async_result: *mut GAsyncResult,
    error_ptr: *mut *mut GError,
) -> *mut GListStore {
    unsafe { g_task_propagate_pointer(async_result as *mut _, error_ptr) as *mut _ }
}

unsafe extern "C" fn manager_new_subscription<T: ManagerImpl>(
    manager_ptr: *mut ClepsydreManager,
    start_ptr: *mut GDateTime,
    end_ptr: *mut GDateTime,
    error_ptr: *mut *mut GError,
) -> *mut ClepsydreSubscription {
    unsafe {
        let instance = &*(manager_ptr as *mut T::Instance);
        let imp = instance.imp();
        let start = DateTime::from_glib_borrow(start_ptr);
        let end = DateTime::from_glib_borrow(end_ptr);

        match imp.new_subscription(&start, &end) {
            Ok(subscription) => subscription.to_glib_full(),
            Err(error) => {
                if !error_ptr.is_null() {
                    *error_ptr = error.into_glib_ptr();
                }
                ptr::null_mut()
            }
        }
    }
}

#[allow(clippy::missing_safety_doc)]
pub unsafe trait ManagerClassExt: ClassStruct {}

unsafe impl<T: ClassStruct> ManagerClassExt for T where T::Type: ManagerImpl {}