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