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