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