Skip to main content

gio/subclass/
file_enumerator.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use glib::{prelude::*, subclass::prelude::*, translate::*};
4use std::pin::Pin;
5
6use crate::{
7    AsyncResult, Cancellable, FileEnumerator, FileInfo, GioFuture, IOErrorEnum, LocalTask, ffi,
8    prelude::*,
9};
10
11// Support custom implementation of virtual functions defined in `gio::ffi::GFileEnumeratorClass` except pairs `xxx_async/xxx_finish` for which GIO provides a default implementation (which should be ok).
12// TODO: overriding these default implementations might still be useful for subclasses (if they can do something better than blocking IO).
13pub trait FileEnumeratorImpl: ObjectImpl + ObjectSubclass<Type: IsA<FileEnumerator>> {
14    /// Returns information for the next file in the enumerated object.
15    /// Will block until the information is available. The #GFileInfo
16    /// returned from this function will contain attributes that match the
17    /// attribute string that was passed when the #GFileEnumerator was created.
18    ///
19    /// See the documentation of #GFileEnumerator for information about the
20    /// order of returned files.
21    ///
22    /// On error, returns [`None`] and sets @error to the error. If the
23    /// enumerator is at the end, [`None`] will be returned and @error will
24    /// be unset.
25    /// ## `cancellable`
26    /// optional #GCancellable object, [`None`] to ignore.
27    ///
28    /// # Returns
29    ///
30    /// A #GFileInfo or [`None`] on error
31    ///    or end of enumerator.  Free the returned object with
32    ///    g_object_unref() when no longer needed.
33    fn next_file(
34        &self,
35        cancellable: Option<&Cancellable>,
36    ) -> Result<Option<FileInfo>, glib::Error> {
37        self.parent_next_file(cancellable)
38    }
39
40    fn next_files_future(
41        &self,
42        num_files: i32,
43        priority: glib::Priority,
44    ) -> std::pin::Pin<
45        Box<dyn std::future::Future<Output = Result<glib::List<FileInfo>, glib::Error>> + 'static>,
46    > {
47        self.parent_next_files_future(num_files, priority)
48    }
49
50    // rustdoc-stripper-ignore-next
51    /// Closes the enumerator (see [`FileEnumeratorExt::close`]).
52    ///
53    /// NOTE: If the enumerator has not been explicitly closed, GIO closes it when the object is dropped.
54    /// But GIO does it by calling `close` vfunc in `finalize`, which is not safe and could lead to undefined behavior,
55    /// such as accessing freed memory or resources, which can cause crashes or other unexpected behavior.
56    ///
57    /// An issue has been opened in GLib to address this: <https://gitlab.gnome.org/GNOME/glib/-/issues/3713> and a MR has been opened to fix it: <https://gitlab.gnome.org/GNOME/glib/-/merge_requests/4672>.
58    ///
59    /// Until this is fixed, it is unsafe to rely on the enumerator being closed when the object is dropped.
60    /// It is recommended to close the enumerator explicitly before dropping it, by calling [`FileEnumeratorExt::close`],
61    /// or to implement the [`ObjectImpl::dispose`] method and call [`FileEnumeratorExt::close`] there (it is safe to access the object there):
62    /// ```ignore
63    /// pub struct MyFileEnumerator();
64    ///
65    /// #[glib::object_subclass]
66    /// impl ObjectSubclass for MyFileEnumerator { ... }
67    ///
68    /// impl ObjectImpl for MyFileEnumerator {
69    ///     fn dispose(&self) {
70    ///         // close the enumerator here is safe and avoids `finalize` to call close.
71    ///         let _ = self.obj().close(Cancellable::NONE);
72    ///     }
73    /// }
74    ///
75    /// impl FileEnumeratorImpl for MyFileEnumerator { ... }
76    /// ```
77    ///
78    /// [`FileEnumeratorExt::close`]: ../auto/file_enumerator/trait.FileEnumeratorExt.html#method.close
79    /// [`ObjectImpl::dispose`]: ../../glib/subclass/object/trait.ObjectImpl.html#method.dispose
80    fn close(&self, cancellable: Option<&Cancellable>) -> (bool, Option<glib::Error>) {
81        self.parent_close(cancellable)
82    }
83
84    fn close_future(
85        &self,
86        io_priority: glib::Priority,
87    ) -> Pin<Box<dyn Future<Output = Result<(), glib::Error>> + 'static>> {
88        self.parent_close_future(io_priority)
89    }
90}
91
92// Support parent implementation of virtual functions defined in `gio::ffi::GFileEnumeratorClass` except pairs `xxx_async/xxx_finish` for which GIO provides a default implementation (which should be ok).
93// TODO: add parent implementation of `xxx_async/xxx_finish` virtual functions if overriding these default implementations is supported.
94pub trait FileEnumeratorImplExt: FileEnumeratorImpl {
95    fn parent_next_file(
96        &self,
97        cancellable: Option<&Cancellable>,
98    ) -> Result<Option<FileInfo>, glib::Error> {
99        if self.obj().is_closed() {
100            Err(glib::Error::new::<IOErrorEnum>(
101                IOErrorEnum::Closed,
102                "Enumerator is closed",
103            ))
104        } else {
105            unsafe {
106                let data = Self::type_data();
107                let parent_class = data.as_ref().parent_class() as *const ffi::GFileEnumeratorClass;
108
109                let f = (*parent_class)
110                    .next_file
111                    .expect("No parent class implementation for \"next_file\"");
112
113                let mut error = std::ptr::null_mut();
114                let res = f(
115                    self.obj()
116                        .unsafe_cast_ref::<FileEnumerator>()
117                        .to_glib_none()
118                        .0,
119                    cancellable.as_ref().to_glib_none().0,
120                    &mut error,
121                );
122                if error.is_null() {
123                    Ok(from_glib_full(res))
124                } else {
125                    Err(from_glib_full(error))
126                }
127            }
128        }
129    }
130
131    fn parent_next_files_async<R: FnOnce(Result<glib::List<FileInfo>, glib::Error>) + 'static>(
132        &self,
133        num_files: i32,
134        priority: glib::Priority,
135        cancellable: Option<&Cancellable>,
136        callback: R,
137    ) {
138        unsafe {
139            let main_context = glib::MainContext::ref_thread_default();
140            let is_main_context_owner = main_context.is_owner();
141            let has_acquired_main_context = (!is_main_context_owner)
142                .then(|| main_context.acquire().ok())
143                .flatten();
144            assert!(
145                is_main_context_owner || has_acquired_main_context.is_some(),
146                "Async operations only allowed if the thread is owning the MainContext"
147            );
148
149            let data = Self::type_data();
150            let parent_class = data.as_ref().parent_class() as *const ffi::GFileEnumeratorClass;
151
152            let f = (*parent_class)
153                .next_files_async
154                .expect("No parent class implementation for \"next_files_async\"");
155            let finish = (*parent_class)
156                .next_files_finish
157                .expect("no parent \"next_files_finish\" implementation");
158
159            let user_data: Box<(glib::thread_guard::ThreadGuard<R>, _)> =
160                Box::new((glib::thread_guard::ThreadGuard::new(callback), finish));
161
162            unsafe extern "C" fn next_files_async_trampoline<
163                R: FnOnce(Result<glib::List<FileInfo>, glib::Error>) + 'static,
164            >(
165                source_object_ptr: *mut glib::gobject_ffi::GObject,
166                res: *mut ffi::GAsyncResult,
167                user_data: glib::ffi::gpointer,
168            ) {
169                unsafe {
170                    let mut error = std::ptr::null_mut();
171                    let cb: Box<(
172                        glib::thread_guard::ThreadGuard<R>,
173                        fn(
174                            *mut ffi::GFileEnumerator,
175                            *mut ffi::GAsyncResult,
176                            *mut *mut glib::ffi::GError,
177                        ) -> *mut glib::ffi::GList,
178                    )> = Box::from_raw(user_data as *mut _);
179                    let ret = cb.1(source_object_ptr as _, res, &mut error);
180                    let result = if error.is_null() {
181                        Ok(glib::List::<FileInfo>::from_glib_full(ret))
182                    } else {
183                        Err(from_glib_full(error))
184                    };
185                    let cb = cb.0.into_inner();
186                    cb(result);
187                }
188            }
189
190            f(
191                self.obj()
192                    .unsafe_cast_ref::<FileEnumerator>()
193                    .to_glib_none()
194                    .0,
195                num_files,
196                priority.into_glib(),
197                cancellable.to_glib_none().0,
198                Some(next_files_async_trampoline::<R>),
199                Box::into_raw(user_data) as *mut _,
200            );
201        }
202    }
203
204    fn parent_next_files_future(
205        &self,
206        num_files: i32,
207        priority: glib::Priority,
208    ) -> Pin<
209        Box<dyn std::future::Future<Output = Result<glib::List<FileInfo>, glib::Error>> + 'static>,
210    > {
211        Box::pin(GioFuture::new(
212            &self.ref_counted(),
213            move |imp, cancellable, send| {
214                imp.parent_next_files_async(num_files, priority, Some(cancellable), move |res| {
215                    send.resolve(res);
216                });
217            },
218        ))
219    }
220
221    fn parent_close(&self, cancellable: Option<&Cancellable>) -> (bool, Option<glib::Error>) {
222        unsafe {
223            let data = Self::type_data();
224            let parent_class = data.as_ref().parent_class() as *const ffi::GFileEnumeratorClass;
225
226            let f = (*parent_class)
227                .close_fn
228                .expect("No parent class implementation for \"close_fn\"");
229
230            let mut error = std::ptr::null_mut();
231            let is_ok = f(
232                self.obj()
233                    .unsafe_cast_ref::<FileEnumerator>()
234                    .to_glib_none()
235                    .0,
236                cancellable.as_ref().to_glib_none().0,
237                &mut error,
238            );
239            (from_glib(is_ok), from_glib_full(error))
240        }
241    }
242
243    fn parent_close_async<R: FnOnce(Result<(), glib::Error>) + 'static>(
244        &self,
245        io_priority: glib::Priority,
246        cancellable: Option<&Cancellable>,
247        callback: R,
248    ) {
249        unsafe {
250            let main_context = glib::MainContext::ref_thread_default();
251            let is_main_context_owner = main_context.is_owner();
252            let has_acquired_main_context = (!is_main_context_owner)
253                .then(|| main_context.acquire().ok())
254                .flatten();
255            assert!(
256                is_main_context_owner || has_acquired_main_context.is_some(),
257                "Async operations only allowed if the thread is owning the MainContext"
258            );
259
260            let data = Self::type_data();
261            let parent_class = data.as_ref().parent_class() as *mut ffi::GFileEnumeratorClass;
262            let f = (*parent_class)
263                .close_async
264                .expect("no parent \"close_async\" implementation");
265            let finish = (*parent_class)
266                .close_finish
267                .expect("no parent \"close_finish\" implementation");
268
269            let user_data: Box<(glib::thread_guard::ThreadGuard<R>, _)> =
270                Box::new((glib::thread_guard::ThreadGuard::new(callback), finish));
271
272            unsafe extern "C" fn close_async_trampoline<
273                R: FnOnce(Result<(), glib::Error>) + 'static,
274            >(
275                source_object_ptr: *mut glib::gobject_ffi::GObject,
276                res: *mut ffi::GAsyncResult,
277                user_data: glib::ffi::gpointer,
278            ) {
279                unsafe {
280                    let mut error = std::ptr::null_mut();
281                    let cb: Box<(
282                        glib::thread_guard::ThreadGuard<R>,
283                        fn(
284                            *mut ffi::GFileEnumerator,
285                            *mut ffi::GAsyncResult,
286                            *mut *mut glib::ffi::GError,
287                        ) -> glib::ffi::gboolean,
288                    )> = Box::from_raw(user_data as *mut _);
289                    cb.1(source_object_ptr as _, res, &mut error);
290                    let result = if error.is_null() {
291                        Ok(())
292                    } else {
293                        Err(from_glib_full(error))
294                    };
295                    let cb = cb.0.into_inner();
296                    cb(result);
297                }
298            }
299
300            f(
301                self.obj()
302                    .unsafe_cast_ref::<FileEnumerator>()
303                    .to_glib_none()
304                    .0,
305                io_priority.into_glib(),
306                cancellable.to_glib_none().0,
307                Some(close_async_trampoline::<R>),
308                Box::into_raw(user_data) as *mut _,
309            );
310        }
311    }
312
313    fn parent_close_future(
314        &self,
315        io_priority: glib::Priority,
316    ) -> Pin<Box<dyn Future<Output = Result<(), glib::Error>> + 'static>> {
317        Box::pin(GioFuture::new(
318            &self.ref_counted(),
319            move |imp, cancellable, send| {
320                imp.parent_close_async(io_priority, Some(cancellable), move |res| {
321                    send.resolve(res);
322                });
323            },
324        ))
325    }
326}
327
328impl<T: FileEnumeratorImpl> FileEnumeratorImplExt for T {}
329
330// Implement virtual functions defined in `gio::ffi::GFileEnumeratorClass` except pairs `xxx_async/xxx_finish` for which GIO provides a default implementation.
331unsafe impl<T: FileEnumeratorImpl> IsSubclassable<T> for FileEnumerator {
332    fn class_init(class: &mut ::glib::Class<Self>) {
333        Self::parent_class_init::<T>(class);
334
335        let klass = class.as_mut();
336        klass.next_file = Some(next_file::<T>);
337        klass.close_fn = Some(close_fn::<T>);
338        klass.next_files_async = Some(next_files_async::<T>);
339        klass.next_files_finish = Some(next_files_finish);
340        klass.close_async = Some(close_async::<T>);
341        klass.close_finish = Some(close_finish);
342    }
343}
344
345unsafe extern "C" fn next_file<T: FileEnumeratorImpl>(
346    enumerator: *mut ffi::GFileEnumerator,
347    cancellable: *mut ffi::GCancellable,
348    error: *mut *mut glib::ffi::GError,
349) -> *mut ffi::GFileInfo {
350    unsafe {
351        let instance = &*(enumerator as *mut T::Instance);
352        let imp = instance.imp();
353        let cancellable = Option::<Cancellable>::from_glib_none(cancellable);
354
355        let res = imp.next_file(cancellable.as_ref());
356
357        match res {
358            Ok(fileinfo) => fileinfo.to_glib_full(),
359            Err(err) => {
360                if !error.is_null() {
361                    *error = err.to_glib_full()
362                }
363                std::ptr::null_mut()
364            }
365        }
366    }
367}
368
369unsafe extern "C" fn next_files_async<T: FileEnumeratorImpl>(
370    enumerator: *mut ffi::GFileEnumerator,
371    num_files: i32,
372    priority: i32,
373    cancellable: *mut ffi::GCancellable,
374    callback: ffi::GAsyncReadyCallback,
375    user_data: glib::ffi::gpointer,
376) {
377    unsafe {
378        let instance = &*(enumerator as *mut T::Instance);
379        let imp = instance.imp();
380        let wrap: FileEnumerator = from_glib_none(enumerator);
381        let cancellable: Option<Cancellable> = from_glib_none(cancellable);
382
383        // Closure that will invoke the C callback when the LocalTask completes
384        let closure = move |task: LocalTask<glib::ValueArray>,
385                            source_object: Option<&glib::Object>| {
386            let result: *mut ffi::GAsyncResult = task.upcast_ref::<AsyncResult>().to_glib_none().0;
387            let source_object: *mut glib::gobject_ffi::GObject = source_object.to_glib_none().0;
388            callback.unwrap()(source_object, result, user_data)
389        };
390
391        let t = LocalTask::new(
392            Some(wrap.upcast_ref::<glib::Object>()),
393            cancellable.as_ref(),
394            closure,
395        );
396
397        // Spawn the async work on the main context
398        glib::MainContext::ref_thread_default().spawn_local(async move {
399            // Call the trait method's future version
400            let res = imp.next_files_future(num_files, from_glib(priority)).await;
401
402            // Store result in the task
403            t.return_result(res.map(|files| {
404                let values: Vec<glib::Value> = files
405                    .into_iter()
406                    .map(|file_info| file_info.to_value())
407                    .collect();
408                glib::ValueArray::from_values(values)
409            }));
410        });
411    }
412}
413
414unsafe extern "C" fn next_files_finish(
415    _enumerator: *mut ffi::GFileEnumerator,
416    res_ptr: *mut ffi::GAsyncResult,
417    error_ptr: *mut *mut glib::ffi::GError,
418) -> *mut glib::ffi::GList {
419    unsafe {
420        let res: AsyncResult = from_glib_none(res_ptr);
421        let t = res.downcast::<LocalTask<glib::ValueArray>>().unwrap();
422        let ret = t.propagate();
423        match ret {
424            Ok(files) => {
425                let files = files
426                    .iter()
427                    .map(|v| <FileInfo as glib::value::FromValue>::from_value(v))
428                    .collect::<Vec<FileInfo>>();
429                files.to_glib_full()
430            }
431            Err(e) => {
432                if !error_ptr.is_null() {
433                    *error_ptr = e.into_glib_ptr();
434                }
435                std::ptr::null_mut()
436            }
437        }
438    }
439}
440
441unsafe extern "C" fn close_fn<T: FileEnumeratorImpl>(
442    enumerator: *mut ffi::GFileEnumerator,
443    cancellable: *mut ffi::GCancellable,
444    error: *mut *mut glib::ffi::GError,
445) -> glib::ffi::gboolean {
446    unsafe {
447        let instance = &*(enumerator as *mut T::Instance);
448        let imp = instance.imp();
449        let cancellable = Option::<Cancellable>::from_glib_none(cancellable);
450
451        let res = imp.close(cancellable.as_ref());
452
453        if !error.is_null() {
454            *error = res.1.to_glib_full()
455        }
456
457        res.0.into_glib()
458    }
459}
460
461unsafe extern "C" fn close_async<T: FileEnumeratorImpl>(
462    enumerator: *mut ffi::GFileEnumerator,
463    priority: i32,
464    cancellable: *mut ffi::GCancellable,
465    callback: ffi::GAsyncReadyCallback,
466    user_data: glib::ffi::gpointer,
467) {
468    unsafe {
469        let instance = &*(enumerator as *mut T::Instance);
470        let imp = instance.imp();
471        let wrap: FileEnumerator = from_glib_none(enumerator);
472        let cancellable: Option<Cancellable> = from_glib_none(cancellable);
473
474        // Closure that will invoke the C callback when the LocalTask completes
475        let closure = move |task: LocalTask<bool>, source_object: Option<&glib::Object>| {
476            let result: *mut ffi::GAsyncResult = task.upcast_ref::<AsyncResult>().to_glib_none().0;
477            let source_object: *mut glib::gobject_ffi::GObject = source_object.to_glib_none().0;
478            callback.unwrap()(source_object, result, user_data)
479        };
480
481        let t = LocalTask::new(
482            Some(wrap.upcast_ref::<glib::Object>()),
483            cancellable.as_ref(),
484            closure,
485        );
486
487        // Spawn the async work on the main context
488        glib::MainContext::ref_thread_default().spawn_local(async move {
489            // Call the trait method's future version
490            let res = imp.close_future(from_glib(priority)).await;
491
492            // Store result in the task (bool indicates success/failure)
493            t.return_result(res.map(|_t| true));
494        });
495    }
496}
497
498unsafe extern "C" fn close_finish(
499    _enumerator: *mut ffi::GFileEnumerator,
500    res_ptr: *mut ffi::GAsyncResult,
501    error_ptr: *mut *mut glib::ffi::GError,
502) -> glib::ffi::gboolean {
503    unsafe {
504        let res: AsyncResult = from_glib_none(res_ptr);
505        let t = res.downcast::<LocalTask<bool>>().unwrap();
506        match t.propagate() {
507            Ok(_) => glib::ffi::GTRUE,
508            Err(e) => {
509                if !error_ptr.is_null() {
510                    *error_ptr = e.into_glib_ptr();
511                }
512                glib::ffi::GFALSE
513            }
514        }
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    // The following tests rely on a custom type `MyCustomFileEnumerator` that extends another custom type `MyFileEnumerator`.
521    // For each virtual method defined in class `gio::ffi::GFileEnumeratorClass`, a test checks that `MyCustomFileEnumerator` and `MyFileEnumerator` return the same results.
522
523    use super::*;
524
525    // Define `MyCustomFileEnumerator` as a subclass of `MyFileEnumerator`.
526    mod imp {
527        use std::cell::Cell;
528
529        use super::*;
530
531        #[derive(Default)]
532        pub struct MyFileEnumerator(Cell<i32>);
533
534        #[glib::object_subclass]
535        impl ObjectSubclass for MyFileEnumerator {
536            const NAME: &'static str = "MyFileEnumerator";
537            type Type = super::MyFileEnumerator;
538            type ParentType = FileEnumerator;
539        }
540
541        impl ObjectImpl for MyFileEnumerator {
542            fn dispose(&self) {
543                let _ = self.obj().close(Cancellable::NONE);
544            }
545        }
546
547        // Implements `FileEnumeratorImpl` with custom implementation.
548        impl FileEnumeratorImpl for MyFileEnumerator {
549            fn next_file(
550                &self,
551                cancellable: Option<&Cancellable>,
552            ) -> Result<Option<FileInfo>, glib::Error> {
553                if cancellable.is_some_and(|c| c.is_cancelled()) {
554                    Err(glib::Error::new::<IOErrorEnum>(
555                        IOErrorEnum::Cancelled,
556                        "Operation was cancelled",
557                    ))
558                } else {
559                    match self.0.get() {
560                        -1 => Err(glib::Error::new::<IOErrorEnum>(
561                            IOErrorEnum::Closed,
562                            "Enumerator is closed",
563                        )),
564                        i if i < 3 => {
565                            let file_info = FileInfo::new();
566                            file_info.set_display_name(&format!("file{i}"));
567                            self.0.set(i + 1);
568                            Ok(Some(file_info))
569                        }
570                        _ => Ok(None),
571                    }
572                }
573            }
574
575            fn next_files_future(
576                &self,
577                num_files: i32,
578                _priority: glib::Priority,
579            ) -> std::pin::Pin<
580                Box<
581                    dyn std::future::Future<Output = Result<glib::List<FileInfo>, glib::Error>>
582                        + 'static,
583                >,
584            > {
585                Box::pin(GioFuture::new(
586                    &self.ref_counted(),
587                    move |self_, cancellable, send| {
588                        let mut res: Result<glib::List<FileInfo>, glib::Error> =
589                            Ok(glib::List::new());
590                        for _ in 0..num_files {
591                            match self_.next_file(Some(cancellable)) {
592                                Ok(Some(fi)) => res.as_mut().unwrap().push_back(fi),
593                                Ok(None) => break,
594                                Err(e) => {
595                                    res = Err(e);
596                                    break;
597                                }
598                            }
599                        }
600                        send.resolve(res);
601                    },
602                ))
603            }
604
605            fn close(&self, cancellable: Option<&Cancellable>) -> (bool, Option<glib::Error>) {
606                if cancellable.is_some_and(|c| c.is_cancelled()) {
607                    (
608                        false,
609                        Some(glib::Error::new::<IOErrorEnum>(
610                            IOErrorEnum::Cancelled,
611                            "Operation was cancelled",
612                        )),
613                    )
614                } else {
615                    self.0.set(-1);
616                    (true, None)
617                }
618            }
619
620            fn close_future(
621                &self,
622                _priority: glib::Priority,
623            ) -> std::pin::Pin<
624                Box<dyn std::future::Future<Output = Result<(), glib::Error>> + 'static>,
625            > {
626                Box::pin(GioFuture::new(
627                    &self.ref_counted(),
628                    move |self_, cancellable, send| {
629                        let (is_ok, error) = self_.close(Some(cancellable));
630                        debug_assert_eq!(!is_ok, error.is_some());
631                        let res = if is_ok { Ok(()) } else { Err(error.unwrap()) };
632                        send.resolve(res);
633                    },
634                ))
635            }
636        }
637
638        #[derive(Default)]
639        pub struct MyCustomFileEnumerator;
640
641        #[glib::object_subclass]
642        impl ObjectSubclass for MyCustomFileEnumerator {
643            const NAME: &'static str = "MyCustomFileEnumerator";
644            type Type = super::MyCustomFileEnumerator;
645            type ParentType = super::MyFileEnumerator;
646        }
647
648        impl ObjectImpl for MyCustomFileEnumerator {}
649
650        // Implements `FileEnumeratorImpl` with default implementation, which calls the parent's implementation.
651        impl FileEnumeratorImpl for MyCustomFileEnumerator {}
652
653        impl MyFileEnumeratorImpl for MyCustomFileEnumerator {}
654    }
655
656    glib::wrapper! {
657        pub struct MyFileEnumerator(ObjectSubclass<imp::MyFileEnumerator>) @extends FileEnumerator;
658    }
659
660    pub trait MyFileEnumeratorImpl:
661        ObjectImpl + ObjectSubclass<Type: IsA<MyFileEnumerator> + IsA<FileEnumerator>>
662    {
663    }
664
665    // To make this class subclassable we need to implement IsSubclassable
666    unsafe impl<T: MyFileEnumeratorImpl + FileEnumeratorImpl> IsSubclassable<T> for MyFileEnumerator {}
667
668    glib::wrapper! {
669        pub struct MyCustomFileEnumerator(ObjectSubclass<imp::MyCustomFileEnumerator>) @extends MyFileEnumerator, FileEnumerator;
670    }
671
672    #[test]
673    fn file_enumerator_next_file() {
674        // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file`
675        let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
676        let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
677        assert!(res.as_ref().is_ok_and(|res| res.is_some()));
678        let filename = res.unwrap().unwrap().display_name();
679
680        // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file`
681        let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
682        let res = my_file_enumerator.next_file(Cancellable::NONE);
683        assert!(res.as_ref().is_ok_and(|res| res.is_some()));
684        let expected = res.unwrap().unwrap().display_name();
685
686        // both filenames should equal
687        assert_eq!(filename, expected);
688
689        // and also next results until there is no more file info
690        for res in my_custom_file_enumerator.upcast::<FileEnumerator>() {
691            assert!(res.as_ref().is_ok());
692            let filename = res.unwrap().display_name();
693
694            let res = my_file_enumerator.next_file(Cancellable::NONE);
695            assert!(res.as_ref().is_ok_and(|res| res.is_some()));
696            let expected = res.unwrap().unwrap().display_name();
697
698            // both filenames should equal
699            assert_eq!(filename, expected);
700        }
701    }
702
703    #[test]
704    fn file_enumerator_next_files_future() {
705        // run test in a main context dedicated and configured as the thread default one
706        let _ = glib::MainContext::new().with_thread_default(|| {
707            // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_files_future` asynchronously
708            let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
709            let res = glib::MainContext::ref_thread_default()
710                .block_on(my_custom_file_enumerator.next_files_future(1, glib::Priority::DEFAULT));
711            assert!(res.as_ref().is_ok_and(|res| res.len() == 1));
712            let filename = res.unwrap().first().unwrap().display_name();
713
714            // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_files_future` asynchronously
715            let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
716            let res = glib::MainContext::ref_thread_default()
717                .block_on(my_file_enumerator.next_files_future(1, glib::Priority::DEFAULT));
718            assert!(res.as_ref().is_ok_and(|res| res.len() == 1));
719            let expected = res.unwrap().first().unwrap().display_name();
720
721            // both filenames should equal
722            assert_eq!(filename, expected);
723
724            // and also next results until there is no more file info
725            let res = glib::MainContext::ref_thread_default()
726                .block_on(my_custom_file_enumerator.next_files_future(10, glib::Priority::DEFAULT));
727            assert!(res.as_ref().is_ok());
728            let filenames = res
729                .unwrap()
730                .into_iter()
731                .map(|fi| fi.display_name())
732                .collect::<Vec<_>>();
733
734            let res = glib::MainContext::ref_thread_default()
735                .block_on(my_file_enumerator.next_files_future(10, glib::Priority::DEFAULT));
736            assert!(res.as_ref().is_ok());
737            let expected = res
738                .unwrap()
739                .into_iter()
740                .map(|fi| fi.display_name())
741                .collect::<Vec<_>>();
742
743            // both filenames should equal
744            assert_eq!(filenames, expected);
745        });
746    }
747
748    #[test]
749    fn file_enumerator_close() {
750        // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file`
751        let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
752        let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
753        assert!(res.as_ref().is_ok_and(|res| res.is_some()));
754        let filename = res.unwrap().unwrap().display_name();
755
756        // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file`
757        let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
758        let res = my_file_enumerator.next_file(Cancellable::NONE);
759        assert!(res.as_ref().is_ok_and(|res| res.is_some()));
760        let expected = res.unwrap().unwrap().display_name();
761
762        // both filenames should equal
763        assert_eq!(filename, expected);
764
765        // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::close`
766        let res = my_custom_file_enumerator.close(Cancellable::NONE);
767        assert_eq!(res.1, None);
768        let closed = res.0;
769
770        // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::close`
771        let res = my_file_enumerator.close(Cancellable::NONE);
772        assert_eq!(res.1, None);
773        let expected = res.0;
774
775        // both results should equal
776        assert_eq!(closed, expected);
777
778        // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file`
779        let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
780        assert!(res.is_err());
781        let err = res.unwrap_err();
782
783        // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file`
784        let res = my_file_enumerator.next_file(Cancellable::NONE);
785        assert!(res.is_err());
786        let expected = res.unwrap_err();
787
788        // both errors should equal
789        assert_eq!(err.domain(), expected.domain());
790        assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Closed));
791        assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Closed));
792        assert_eq!(err.message(), expected.message());
793    }
794
795    #[test]
796    fn file_enumerator_close_future() {
797        // run test in a main context dedicated and configured as the thread default one
798        let _ = glib::MainContext::new().with_thread_default(|| {
799            // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_files_future` asynchronously
800            let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
801            let res = glib::MainContext::ref_thread_default()
802                .block_on(my_custom_file_enumerator.next_files_future(1, glib::Priority::DEFAULT));
803            assert!(res.as_ref().is_ok_and(|res| res.len() == 1));
804            let filename = res.unwrap().first().unwrap().display_name();
805
806            // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_files_future` asynchronously
807            let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
808            let res = glib::MainContext::ref_thread_default()
809                .block_on(my_file_enumerator.next_files_future(1, glib::Priority::DEFAULT));
810            assert!(res.as_ref().is_ok_and(|res| res.len() == 1));
811            let expected = res.unwrap().first().unwrap().display_name();
812
813            // both filenames should equal
814            assert_eq!(filename, expected);
815
816            // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::close_future` asynchronously
817            let res = glib::MainContext::ref_thread_default()
818                .block_on(my_custom_file_enumerator.close_future(glib::Priority::DEFAULT));
819            assert!(res.is_ok());
820            let closed = true;
821
822            // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::close_future` asynchronously
823            let res = glib::MainContext::ref_thread_default()
824                .block_on(my_file_enumerator.close_future(glib::Priority::DEFAULT));
825            assert!(res.is_ok());
826            let expected = true;
827
828            // both results should equal
829            assert_eq!(closed, expected);
830
831            // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_files_future` asynchronously
832            let res = glib::MainContext::ref_thread_default()
833                .block_on(my_custom_file_enumerator.next_files_future(1, glib::Priority::DEFAULT));
834            assert!(res.is_err());
835            let err = res.unwrap_err();
836
837            // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_files_future` asynchronously
838            let res = glib::MainContext::ref_thread_default()
839                .block_on(my_file_enumerator.next_files_future(1, glib::Priority::DEFAULT));
840            assert!(res.is_err());
841            let expected = res.unwrap_err();
842
843            // both errors should equal
844            assert_eq!(err.domain(), expected.domain());
845            assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Closed));
846            assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Closed));
847            assert_eq!(err.message(), expected.message());
848        });
849    }
850
851    #[test]
852    fn file_enumerator_cancel() {
853        // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file`
854        let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
855        let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
856        assert!(res.as_ref().is_ok_and(|res| res.is_some()));
857        let filename = res.unwrap().unwrap().display_name();
858
859        // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file`
860        let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
861        let res = my_file_enumerator.next_file(Cancellable::NONE);
862        assert!(res.as_ref().is_ok_and(|res| res.is_some()));
863        let expected = res.unwrap().unwrap().display_name();
864
865        // both filenames should equal
866        assert_eq!(filename, expected);
867
868        // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file` with cancel
869        let cancellable = Cancellable::new();
870        cancellable.cancel();
871        let res = my_custom_file_enumerator.next_file(Some(&cancellable));
872        assert!(res.as_ref().is_err());
873        let err = res.unwrap_err();
874
875        // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::next_file` with cancel
876        let cancellable = Cancellable::new();
877        cancellable.cancel();
878        let res = my_file_enumerator.next_file(Some(&cancellable));
879        assert!(res.as_ref().is_err());
880        let expected = res.unwrap_err();
881
882        // both errors should equal
883        assert_eq!(err.domain(), expected.domain());
884        assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
885        assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
886        assert_eq!(err.message(), expected.message());
887
888        // invoke `MyCustomFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::close` with cancel
889        let cancellable = Cancellable::new();
890        cancellable.cancel();
891        let res = my_custom_file_enumerator.close(Some(&cancellable));
892        assert!(res.1.is_some());
893        let err = res.1.unwrap();
894
895        // invoke `MyFileEnumerator` implementation of `gio::ffi::GFileEnumeratorClass::close` with cancel
896        let cancellable = Cancellable::new();
897        cancellable.cancel();
898        let res = my_file_enumerator.close(Some(&cancellable));
899        assert!(res.1.is_some());
900        let expected = res.1.unwrap();
901
902        // both errors should equal
903        assert_eq!(err.domain(), expected.domain());
904        assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
905        assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
906        assert_eq!(err.message(), expected.message());
907    }
908}