gio/subclass/
async_initable.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{future::Future, pin::Pin, ptr};
4
5use glib::{prelude::*, subclass::prelude::*, thread_guard::ThreadGuard, translate::*, Error};
6
7use crate::{
8    ffi, AsyncInitable, AsyncResult, Cancellable, CancellableFuture, GioFutureResult, LocalTask,
9};
10
11pub trait AsyncInitableImpl: ObjectImpl + ObjectSubclass<Type: IsA<AsyncInitable>> {
12    fn init_future(
13        &self,
14        io_priority: glib::Priority,
15    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + 'static>> {
16        self.parent_init_future(io_priority)
17    }
18}
19
20pub trait AsyncInitableImplExt: AsyncInitableImpl {
21    fn parent_init_future(
22        &self,
23        io_priority: glib::Priority,
24    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + 'static>> {
25        unsafe {
26            let type_data = Self::type_data();
27            let parent_iface = type_data.as_ref().parent_interface::<AsyncInitable>()
28                as *const ffi::GAsyncInitableIface;
29
30            let init_async = (*parent_iface)
31                .init_async
32                .expect("no parent \"init_async\" implementation");
33
34            unsafe extern "C" fn parent_init_future_callback<
35                T: ObjectSubclass<Type: IsA<glib::Object>>,
36            >(
37                source_object: *mut glib::gobject_ffi::GObject,
38                res: *mut crate::ffi::GAsyncResult,
39                user_data: glib::ffi::gpointer,
40            ) {
41                let type_data = T::type_data();
42                let parent_iface = type_data.as_ref().parent_interface::<AsyncInitable>()
43                    as *const ffi::GAsyncInitableIface;
44                let init_finish = (*parent_iface)
45                    .init_finish
46                    .expect("no parent \"init_finish\" implementation");
47
48                let r: Box<ThreadGuard<GioFutureResult<Result<(), Error>>>> =
49                    Box::from_raw(user_data as *mut _);
50                let r = r.into_inner();
51
52                let mut error = ptr::null_mut();
53                init_finish(source_object as *mut _, res, &mut error);
54                let result = if error.is_null() {
55                    Ok(())
56                } else {
57                    Err(from_glib_full(error))
58                };
59                r.resolve(result);
60            }
61
62            Box::pin(crate::GioFuture::new(
63                &*self.obj(),
64                move |obj, cancellable, res| {
65                    let user_data: Box<ThreadGuard<GioFutureResult<Result<(), Error>>>> =
66                        Box::new(ThreadGuard::new(res));
67                    let user_data = Box::into_raw(user_data);
68                    init_async(
69                        obj.unsafe_cast_ref::<AsyncInitable>().to_glib_none().0,
70                        io_priority.into_glib(),
71                        cancellable.to_glib_none().0,
72                        Some(parent_init_future_callback::<Self>),
73                        user_data as *mut _,
74                    );
75                },
76            ))
77        }
78    }
79}
80
81impl<T: AsyncInitableImpl> AsyncInitableImplExt for T {}
82
83unsafe impl<T: AsyncInitableImpl> IsImplementable<T> for AsyncInitable {
84    fn interface_init(iface: &mut glib::Interface<Self>) {
85        let iface = iface.as_mut();
86        iface.init_async = Some(async_initable_init_async::<T>);
87        iface.init_finish = Some(async_initable_init_finish);
88    }
89}
90
91unsafe extern "C" fn async_initable_init_async<T: AsyncInitableImpl>(
92    initable: *mut ffi::GAsyncInitable,
93    io_priority: std::os::raw::c_int,
94    cancellable: *mut ffi::GCancellable,
95    callback: ffi::GAsyncReadyCallback,
96    user_data: glib::ffi::gpointer,
97) {
98    let instance = &*(initable as *mut T::Instance);
99    let imp = instance.imp();
100    let cancellable = Option::<Cancellable>::from_glib_none(cancellable);
101
102    let task = callback.map(|callback| {
103        let task = LocalTask::new(
104            Some(imp.obj().unsafe_cast_ref::<glib::Object>()),
105            cancellable.as_ref(),
106            move |task, obj| {
107                let result: *mut crate::ffi::GAsyncResult =
108                    task.upcast_ref::<AsyncResult>().to_glib_none().0;
109                let obj: *mut glib::gobject_ffi::GObject = obj.to_glib_none().0;
110                callback(obj, result, user_data);
111            },
112        );
113        task.set_check_cancellable(true);
114        task.set_return_on_cancel(true);
115        task
116    });
117
118    glib::MainContext::ref_thread_default().spawn_local(async move {
119        let io_priority = from_glib(io_priority);
120        let res = if let Some(cancellable) = cancellable {
121            CancellableFuture::new(imp.init_future(io_priority), cancellable)
122                .await
123                .map_err(|cancelled| cancelled.into())
124                .and_then(|res| res)
125        } else {
126            imp.init_future(io_priority).await
127        };
128        if let Some(task) = task {
129            task.return_result(res.map(|_t| true));
130        }
131    });
132}
133
134unsafe extern "C" fn async_initable_init_finish(
135    initable: *mut ffi::GAsyncInitable,
136    res: *mut ffi::GAsyncResult,
137    error: *mut *mut glib::ffi::GError,
138) -> glib::ffi::gboolean {
139    let res = from_glib_none::<_, AsyncResult>(res);
140
141    let task = res
142        .downcast::<LocalTask<bool>>()
143        .expect("GAsyncResult is not a GTask");
144    if !LocalTask::<bool>::is_valid(
145        &task,
146        Some(from_glib_borrow::<_, AsyncInitable>(initable).as_ref()),
147    ) {
148        panic!("Task is not valid for source object");
149    }
150
151    match task.propagate() {
152        Ok(v) => {
153            debug_assert!(v);
154            true.into_glib()
155        }
156        Err(e) => {
157            if !error.is_null() {
158                *error = e.into_glib_ptr();
159            }
160            false.into_glib()
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::prelude::*;
169
170    pub mod imp {
171        use std::cell::Cell;
172
173        use super::*;
174
175        pub struct AsyncInitableTestType(pub Cell<u64>);
176
177        #[glib::object_subclass]
178        impl ObjectSubclass for AsyncInitableTestType {
179            const NAME: &'static str = "AsyncInitableTestType";
180            type Type = super::AsyncInitableTestType;
181            type Interfaces = (AsyncInitable,);
182
183            fn new() -> Self {
184                Self(Cell::new(0))
185            }
186        }
187
188        impl AsyncInitableImpl for AsyncInitableTestType {
189            fn init_future(
190                &self,
191                _io_priority: glib::Priority,
192            ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + 'static>> {
193                let imp = glib::subclass::ObjectImplRef::new(self);
194                Box::pin(async move {
195                    glib::timeout_future_seconds(0).await;
196                    imp.0.set(0x123456789abcdef);
197                    Ok(())
198                })
199            }
200        }
201
202        impl ObjectImpl for AsyncInitableTestType {}
203    }
204
205    pub mod ffi {
206        use super::*;
207        pub type AsyncInitableTestType = <imp::AsyncInitableTestType as ObjectSubclass>::Instance;
208
209        pub unsafe extern "C" fn async_initable_test_type_get_value(
210            this: *mut AsyncInitableTestType,
211        ) -> u64 {
212            let this = super::AsyncInitableTestType::from_glib_borrow(this);
213            this.imp().0.get()
214        }
215    }
216
217    glib::wrapper! {
218        pub struct AsyncInitableTestType(ObjectSubclass<imp::AsyncInitableTestType>)
219            @implements AsyncInitable;
220    }
221
222    #[allow(clippy::new_without_default)]
223    impl AsyncInitableTestType {
224        pub async fn new() -> Self {
225            AsyncInitable::new_future(glib::Priority::default())
226                .await
227                .expect("Failed creation/initialization of AsyncInitableTestType object")
228        }
229
230        pub unsafe fn new_uninit() -> Self {
231            // This creates an uninitialized AsyncInitableTestType object, for testing
232            // purposes. In real code, using AsyncInitable::new_future (like the new() method
233            // does) is recommended.
234            glib::Object::new_internal(Self::static_type(), &mut [])
235                .downcast()
236                .unwrap()
237        }
238
239        pub fn value(&self) -> u64 {
240            self.imp().0.get()
241        }
242    }
243
244    #[test]
245    fn test_async_initable_with_init() {
246        glib::MainContext::new().block_on(async {
247            let res = unsafe {
248                let test = AsyncInitableTestType::new_uninit();
249
250                assert_ne!(0x123456789abcdef, test.value());
251
252                test.init_future(glib::Priority::default())
253                    .await
254                    .map(|_| test)
255            };
256            assert!(res.is_ok());
257            let test = res.unwrap();
258
259            assert_eq!(0x123456789abcdef, test.value());
260        });
261    }
262
263    #[test]
264    fn test_async_initable_with_initable_new() {
265        glib::MainContext::new().block_on(async {
266            let test = AsyncInitableTestType::new().await;
267            assert_eq!(0x123456789abcdef, test.value());
268        });
269    }
270
271    #[test]
272    #[should_panic = ""]
273    fn test_async_initable_new_failure() {
274        glib::MainContext::new().block_on(async {
275            let value: u32 = 2;
276            let _ = AsyncInitable::builder::<AsyncInitableTestType>()
277                .property("invalid-property", value)
278                .build_future(glib::Priority::default())
279                .await;
280            unreachable!();
281        });
282    }
283
284    #[test]
285    fn test_async_initable_with_initable_with_type() {
286        glib::MainContext::new().block_on(async {
287            let test = AsyncInitable::with_type_future(
288                AsyncInitableTestType::static_type(),
289                glib::Priority::default(),
290            )
291            .await
292            .expect("Failed creation/initialization of AsyncInitableTestType object from type")
293            .downcast::<AsyncInitableTestType>()
294            .expect("Failed downcast of AsyncInitableTestType object");
295            assert_eq!(0x123456789abcdef, test.value());
296        });
297    }
298
299    #[test]
300    fn test_async_initable_with_async_initable_with_values() {
301        glib::MainContext::new().block_on(async {
302            let test = AsyncInitable::with_type_future(
303                AsyncInitableTestType::static_type(),
304                glib::Priority::default(),
305            )
306            .await
307            .expect("Failed creation/initialization of AsyncInitableTestType object from values")
308            .downcast::<AsyncInitableTestType>()
309            .expect("Failed downcast of AsyncInitableTestType object");
310            assert_eq!(0x123456789abcdef, test.value());
311        });
312    }
313
314    #[test]
315    fn test_async_initable_through_ffi() {
316        use futures_channel::oneshot;
317
318        glib::MainContext::new().block_on(async {
319            unsafe {
320                let test = AsyncInitableTestType::new_uninit();
321                let test: *mut ffi::AsyncInitableTestType = test.as_ptr();
322
323                assert_ne!(
324                    0x123456789abcdef,
325                    ffi::async_initable_test_type_get_value(test)
326                );
327
328                let (tx, rx) = oneshot::channel::<Result<(), glib::Error>>();
329                let user_data = Box::new(ThreadGuard::new(tx));
330                unsafe extern "C" fn init_async_callback(
331                    source_object: *mut glib::gobject_ffi::GObject,
332                    res: *mut crate::ffi::GAsyncResult,
333                    user_data: glib::ffi::gpointer,
334                ) {
335                    let tx: Box<ThreadGuard<oneshot::Sender<Result<(), glib::Error>>>> =
336                        Box::from_raw(user_data as *mut _);
337                    let tx = tx.into_inner();
338                    let mut error = ptr::null_mut();
339                    let ret = crate::ffi::g_async_initable_init_finish(
340                        source_object as *mut _,
341                        res,
342                        &mut error,
343                    );
344                    assert_eq!(ret, glib::ffi::GTRUE);
345                    let result = if error.is_null() {
346                        Ok(())
347                    } else {
348                        Err(from_glib_full(error))
349                    };
350                    tx.send(result).unwrap();
351                }
352
353                crate::ffi::g_async_initable_init_async(
354                    test as *mut crate::ffi::GAsyncInitable,
355                    glib::ffi::G_PRIORITY_DEFAULT,
356                    std::ptr::null_mut(),
357                    Some(init_async_callback),
358                    Box::into_raw(user_data) as *mut _,
359                );
360
361                let result = rx.await.unwrap();
362                assert!(result.is_ok());
363                assert_eq!(
364                    0x123456789abcdef,
365                    ffi::async_initable_test_type_get_value(test)
366                );
367            }
368        });
369    }
370}