gio/subclass/
file_monitor.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::*};
4
5use crate::{ffi, File, FileMonitor, FileMonitorEvent};
6
7// Support custom implementation of virtual functions defined in `gio::ffi::GFileMonitorClass`.
8pub trait FileMonitorImpl: ObjectImpl + ObjectSubclass<Type: IsA<FileMonitor>> {
9    fn changed(&self, file: &File, other_file: Option<&File>, event_type: FileMonitorEvent) {
10        self.parent_changed(file, other_file, event_type)
11    }
12
13    fn cancel(&self) {
14        self.parent_cancel()
15    }
16}
17
18// Support parent implementation of virtual functions defined in `gio::ffi::GFileMonitorClass`.
19pub trait FileMonitorImplExt: FileMonitorImpl {
20    fn parent_changed(&self, file: &File, other_file: Option<&File>, event_type: FileMonitorEvent) {
21        unsafe {
22            let data = Self::type_data();
23            let parent_class = data.as_ref().parent_class() as *const ffi::GFileMonitorClass;
24
25            if let Some(f) = (*parent_class).changed {
26                f(
27                    self.obj().unsafe_cast_ref::<FileMonitor>().to_glib_none().0,
28                    file.to_glib_none().0,
29                    other_file.to_glib_none().0,
30                    event_type.into_glib(),
31                );
32            }
33        }
34    }
35
36    fn parent_cancel(&self) {
37        unsafe {
38            let data = Self::type_data();
39            let parent_class = data.as_ref().parent_class() as *const ffi::GFileMonitorClass;
40
41            let f = (*parent_class)
42                .cancel
43                .expect("No parent class implementation for \"cancel\"");
44
45            let _ = f(self.obj().unsafe_cast_ref::<FileMonitor>().to_glib_none().0);
46        }
47    }
48}
49
50impl<T: FileMonitorImpl> FileMonitorImplExt for T {}
51
52// Implement virtual functions defined in `gio::ffi::GFileMonitorClass`.
53unsafe impl<T: FileMonitorImpl> IsSubclassable<T> for FileMonitor {
54    fn class_init(class: &mut ::glib::Class<Self>) {
55        Self::parent_class_init::<T>(class);
56
57        let klass = class.as_mut();
58        klass.changed = Some(changed::<T>);
59        klass.cancel = Some(cancel::<T>);
60    }
61}
62
63unsafe extern "C" fn changed<T: FileMonitorImpl>(
64    monitor: *mut ffi::GFileMonitor,
65    file: *mut ffi::GFile,
66    other_file: *mut ffi::GFile,
67    event_type: ffi::GFileMonitorEvent,
68) {
69    let instance = &*(monitor as *mut T::Instance);
70    let imp = instance.imp();
71    let other_file = Option::<File>::from_glib_none(other_file);
72
73    imp.changed(
74        &from_glib_borrow(file),
75        other_file.as_ref(),
76        from_glib(event_type),
77    );
78}
79
80unsafe extern "C" fn cancel<T: FileMonitorImpl>(
81    monitor: *mut ffi::GFileMonitor,
82) -> glib::ffi::gboolean {
83    let instance = &*(monitor as *mut T::Instance);
84    let imp = instance.imp();
85
86    imp.cancel();
87
88    // vfunc must return true as specified in documentation.
89    // https://docs.gtk.org/gio/vfunc.FileMonitor.cancel.html
90    true.into_glib()
91}
92
93#[cfg(test)]
94mod tests {
95    // The following tests rely on a custom type `MyCustomFileMonitor` that extends another custom type `MyFileMonitor`.
96    // For each virtual method defined in class `gio::ffi::GFileMonitorClass`, a test checks that `MyCustomFileMonitor` and `MyFileMonitor` return the same results.
97
98    use super::*;
99    use crate::prelude::*;
100
101    // Define `MyCustomFileMonitor` as a subclass of `MyFileMonitor`.
102    mod imp {
103        use super::*;
104
105        #[derive(Default)]
106        pub struct MyFileMonitor;
107
108        #[glib::object_subclass]
109        impl ObjectSubclass for MyFileMonitor {
110            const NAME: &'static str = "MyFileMonitor";
111            type Type = super::MyFileMonitor;
112            type ParentType = FileMonitor;
113        }
114
115        impl ObjectImpl for MyFileMonitor {}
116
117        // Implements `FileMonitorImpl` with custom implementation.
118        impl FileMonitorImpl for MyFileMonitor {
119            fn cancel(&self) {}
120        }
121
122        #[derive(Default)]
123        pub struct MyCustomFileMonitor;
124
125        #[glib::object_subclass]
126        impl ObjectSubclass for MyCustomFileMonitor {
127            const NAME: &'static str = "MyCustomFileMonitor";
128            type Type = super::MyCustomFileMonitor;
129            type ParentType = super::MyFileMonitor;
130        }
131
132        impl ObjectImpl for MyCustomFileMonitor {}
133
134        // Implements `FileMonitorImpl` with default implementation, which calls the parent's implementation.
135        impl FileMonitorImpl for MyCustomFileMonitor {}
136
137        impl MyFileMonitorImpl for MyCustomFileMonitor {}
138    }
139
140    glib::wrapper! {
141        pub struct MyFileMonitor(ObjectSubclass<imp::MyFileMonitor>) @extends FileMonitor;
142    }
143
144    pub trait MyFileMonitorImpl:
145        ObjectImpl + ObjectSubclass<Type: IsA<MyFileMonitor> + IsA<FileMonitor>>
146    {
147    }
148
149    // To make this class subclassable we need to implement IsSubclassable
150    unsafe impl<T: MyFileMonitorImpl + FileMonitorImpl> IsSubclassable<T> for MyFileMonitor {}
151
152    glib::wrapper! {
153        pub struct MyCustomFileMonitor(ObjectSubclass<imp::MyCustomFileMonitor>) @extends MyFileMonitor, FileMonitor;
154    }
155
156    #[test]
157    fn file_monitor_changed() {
158        // run test in a main context dedicated and configured as the thread default one
159        let _ = glib::MainContext::new().with_thread_default(|| {
160            // invoke `MyCustomFileMonitor` implementation of `gio::ffi::GFileMonitorClass::cancel`
161            let my_custom_file_monitor = glib::Object::new::<MyCustomFileMonitor>();
162            let rx = {
163                let (tx, rx) = async_channel::bounded(1);
164                my_custom_file_monitor.connect_changed(move |_, file, other_file, event_type| {
165                    let res = glib::MainContext::ref_thread_default().block_on(tx.send((
166                        file.uri(),
167                        other_file.map(File::uri),
168                        event_type,
169                    )));
170                    assert!(res.is_ok(), "{}", res.err().unwrap());
171                });
172                rx
173            };
174            // emit an event
175            my_custom_file_monitor.emit_event(
176                &File::for_uri("child"),
177                None::<&File>,
178                FileMonitorEvent::Created,
179            );
180            let res = glib::MainContext::ref_thread_default().block_on(rx.recv());
181            assert!(res.is_ok(), "{}", res.err().unwrap());
182            let event = res.unwrap();
183
184            // invoke `MyFileMonitor` implementation of `gio::ffi::GFileMonitorClass::cancel`
185            let my_file_monitor = glib::Object::new::<MyFileMonitor>();
186            let expected_rx = {
187                let (tx, rx) = async_channel::bounded(1);
188                my_file_monitor.connect_changed(move |_, file, other_file, event_type| {
189                    let res = glib::MainContext::ref_thread_default().block_on(tx.send((
190                        file.uri(),
191                        other_file.map(File::uri),
192                        event_type,
193                    )));
194                    assert!(res.is_ok(), "{}", res.err().unwrap());
195                });
196                rx
197            };
198            // emit an event
199            my_file_monitor.emit_event(
200                &File::for_uri("child"),
201                None::<&File>,
202                FileMonitorEvent::Created,
203            );
204            let res = glib::MainContext::ref_thread_default().block_on(expected_rx.recv());
205            assert!(res.is_ok(), "{}", res.err().unwrap());
206            let expected_event = res.unwrap();
207
208            // both results should equal
209            assert_eq!(event, expected_event);
210        });
211    }
212
213    #[test]
214    fn file_monitor_cancel() {
215        // run test in a main context dedicated and configured as the thread default one
216        let _ = glib::MainContext::new().with_thread_default(|| {
217            // invoke `MyCustomFileMonitor` implementation of `gio::ffi::GFileMonitorClass::cancel`
218            let my_custom_file_monitor = glib::Object::new::<MyCustomFileMonitor>();
219            let rx = {
220                let (tx, rx) = async_channel::bounded(1);
221                my_custom_file_monitor.connect_cancelled_notify(move |_| {
222                    let res = glib::MainContext::ref_thread_default().block_on(tx.send(true));
223                    assert!(res.is_ok(), "{}", res.err().unwrap());
224                });
225                rx
226            };
227            let cancelled = my_custom_file_monitor.cancel();
228            let res = glib::MainContext::ref_thread_default().block_on(rx.recv());
229            assert!(res.is_ok(), "{}", res.err().unwrap());
230            let notified = res.unwrap();
231            assert_eq!(cancelled, notified);
232
233            // invoke `MyFileMonitor` implementation of `gio::ffi::GFileMonitorClass::cancel`
234            let my_file_monitor = glib::Object::new::<MyFileMonitor>();
235            let expected_rx = {
236                let (tx, rx) = async_channel::bounded(1);
237                my_file_monitor.connect_cancelled_notify(move |_| {
238                    let res = glib::MainContext::ref_thread_default().block_on(tx.send(true));
239                    assert!(res.is_ok(), "{}", res.err().unwrap());
240                });
241                rx
242            };
243            let expected_cancelled = my_file_monitor.cancel();
244            let res = glib::MainContext::ref_thread_default().block_on(expected_rx.recv());
245            assert!(res.is_ok(), "{}", res.err().unwrap());
246            let expected_notified = res.unwrap();
247            assert_eq!(expected_cancelled, expected_notified);
248
249            // both results should equal
250            assert_eq!(cancelled, expected_cancelled);
251            assert_eq!(notified, expected_notified);
252        });
253    }
254}