gio/subclass/
vfs.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::path::PathBuf;
4
5use glib::{prelude::*, subclass::prelude::*, translate::*, GString, StrVRef};
6
7use libc::c_char;
8
9use crate::{ffi, File, Vfs};
10
11// Support custom implementation of virtual functions defined in `gio::ffi::GVfsClass`.
12pub trait VfsImpl: ObjectImpl + ObjectSubclass<Type: IsA<Vfs>> {
13    /// Checks if the VFS is active.
14    ///
15    /// # Returns
16    ///
17    /// [`true`] if construction of the @self was successful
18    ///     and it is now active.
19    fn is_active(&self) -> bool {
20        self.parent_is_active()
21    }
22
23    fn get_file_for_path(&self, path: &std::path::Path) -> File {
24        self.parent_get_file_for_path(path)
25    }
26
27    fn get_file_for_uri(&self, uri: &str) -> File {
28        self.parent_get_file_for_uri(uri)
29    }
30
31    fn get_supported_uri_schemes(&self) -> &'static StrVRef {
32        self.parent_get_supported_uri_schemes()
33    }
34
35    /// This operation never fails, but the returned object might
36    /// not support any I/O operations if the @parse_name cannot
37    /// be parsed by the #GVfs module.
38    /// ## `parse_name`
39    /// a string to be parsed by the VFS module.
40    ///
41    /// # Returns
42    ///
43    /// a #GFile for the given @parse_name.
44    ///     Free the returned object with g_object_unref().
45    fn parse_name(&self, parse_name: &str) -> File {
46        self.parent_parse_name(parse_name)
47    }
48}
49
50// Support parent implementation of virtual functions defined in `gio::ffi::GVfsClass`.
51pub trait VfsImplExt: VfsImpl {
52    fn parent_is_active(&self) -> bool {
53        unsafe {
54            let data = Self::type_data();
55            let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass;
56
57            let f = (*parent_class)
58                .is_active
59                .expect("No parent class implementation for \"is_active\"");
60
61            let res = f(self.obj().unsafe_cast_ref::<Vfs>().to_glib_none().0);
62            from_glib(res)
63        }
64    }
65
66    fn parent_get_file_for_path(&self, path: &std::path::Path) -> File {
67        unsafe {
68            let data = Self::type_data();
69            let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass;
70
71            let f = (*parent_class)
72                .get_file_for_path
73                .expect("No parent class implementation for \"get_file_for_path\"");
74
75            let res = f(
76                self.obj().unsafe_cast_ref::<Vfs>().to_glib_none().0,
77                path.to_glib_none().0,
78            );
79            from_glib_full(res)
80        }
81    }
82
83    fn parent_get_file_for_uri(&self, uri: &str) -> File {
84        unsafe {
85            let data = Self::type_data();
86            let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass;
87
88            let f = (*parent_class)
89                .get_file_for_uri
90                .expect("No parent class implementation for \"get_file_for_uri\"");
91
92            let res = f(
93                self.obj().unsafe_cast_ref::<Vfs>().to_glib_none().0,
94                uri.to_glib_none().0,
95            );
96            from_glib_full(res)
97        }
98    }
99
100    fn parent_get_supported_uri_schemes(&self) -> &'static StrVRef {
101        unsafe {
102            let data = Self::type_data();
103            let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass;
104
105            let f = (*parent_class)
106                .get_supported_uri_schemes
107                .expect("No parent class implementation for \"get_supported_uri_schemes\"");
108
109            let res = f(self.obj().unsafe_cast_ref::<Vfs>().to_glib_none().0);
110            StrVRef::from_glib_borrow(res)
111        }
112    }
113
114    fn parent_parse_name(&self, parse_name: &str) -> File {
115        unsafe {
116            let data = Self::type_data();
117            let parent_class = data.as_ref().parent_class() as *const ffi::GVfsClass;
118
119            let f = (*parent_class)
120                .parse_name
121                .expect("No parent class implementation for \"parse_name\"");
122
123            let res = f(
124                self.obj().unsafe_cast_ref::<Vfs>().to_glib_none().0,
125                parse_name.to_glib_none().0,
126            );
127            from_glib_full(res)
128        }
129    }
130}
131
132impl<T: VfsImpl> VfsImplExt for T {}
133
134// Implement virtual functions defined in `gio::ffi::GVfsClass`.
135unsafe impl<T: VfsImpl> IsSubclassable<T> for Vfs {
136    fn class_init(class: &mut ::glib::Class<Self>) {
137        Self::parent_class_init::<T>(class);
138
139        let klass = class.as_mut();
140        klass.is_active = Some(is_active::<T>);
141        klass.get_file_for_path = Some(get_file_for_path::<T>);
142        klass.get_file_for_uri = Some(get_file_for_uri::<T>);
143        klass.get_supported_uri_schemes = Some(get_supported_uri_schemes::<T>);
144        klass.parse_name = Some(parse_name::<T>);
145    }
146}
147
148unsafe extern "C" fn is_active<T: VfsImpl>(vfs: *mut ffi::GVfs) -> glib::ffi::gboolean {
149    let instance = &*(vfs as *mut T::Instance);
150    let imp = instance.imp();
151
152    let res = imp.is_active();
153
154    res.into_glib()
155}
156
157unsafe extern "C" fn get_file_for_path<T: VfsImpl>(
158    vfs: *mut ffi::GVfs,
159    path: *const c_char,
160) -> *mut ffi::GFile {
161    let instance = &*(vfs as *mut T::Instance);
162    let imp = instance.imp();
163
164    let file = imp.get_file_for_path(&PathBuf::from_glib_none(path));
165
166    file.into_glib_ptr()
167}
168
169unsafe extern "C" fn get_file_for_uri<T: VfsImpl>(
170    vfs: *mut ffi::GVfs,
171    uri: *const c_char,
172) -> *mut ffi::GFile {
173    let instance = &*(vfs as *mut T::Instance);
174    let imp = instance.imp();
175
176    let file = imp.get_file_for_uri(&GString::from_glib_borrow(uri));
177
178    file.into_glib_ptr()
179}
180
181unsafe extern "C" fn get_supported_uri_schemes<T: VfsImpl>(
182    vfs: *mut ffi::GVfs,
183) -> *const *const c_char {
184    let instance = &*(vfs as *mut T::Instance);
185    let imp = instance.imp();
186
187    let supported_uri_schemes = imp.get_supported_uri_schemes();
188
189    supported_uri_schemes.as_ptr()
190}
191
192unsafe extern "C" fn parse_name<T: VfsImpl>(
193    vfs: *mut ffi::GVfs,
194    parse_name: *const c_char,
195) -> *mut ffi::GFile {
196    let instance = &*(vfs as *mut T::Instance);
197    let imp = instance.imp();
198
199    let file = imp.parse_name(&GString::from_glib_borrow(parse_name));
200
201    file.into_glib_ptr()
202}
203
204#[cfg(test)]
205mod tests {
206    // The following tests rely on a custom type `MyCustomVfs` that extends another custom type `MyVfs`.
207    // For each virtual method defined in class `gio::ffi::GVfsClass`, a test checks that `MyCustomVfs` and `MyVfs` return the same results.
208
209    use super::*;
210    use crate::prelude::*;
211
212    // Define `MyCustomVfs` as a subclass of `MyVfs`.
213    mod imp {
214        use std::sync::LazyLock;
215
216        use super::*;
217
218        // Defines `MyVfs` as a subclass of `Vfs`.
219        #[derive(Default)]
220        pub struct MyVfs;
221
222        #[glib::object_subclass]
223        impl ObjectSubclass for MyVfs {
224            const NAME: &'static str = "MyVfs";
225            type Type = super::MyVfs;
226            type ParentType = Vfs;
227        }
228
229        impl ObjectImpl for MyVfs {}
230
231        // Implements `VfsImpl` with custom implementation.
232        impl VfsImpl for MyVfs {
233            fn is_active(&self) -> bool {
234                true
235            }
236
237            fn get_file_for_path(&self, path: &std::path::Path) -> File {
238                File::for_path(path)
239            }
240
241            fn get_file_for_uri(&self, uri: &str) -> File {
242                File::for_uri(uri)
243            }
244
245            fn get_supported_uri_schemes(&self) -> &'static StrVRef {
246                static SUPPORTED_URI_SCHEMES: LazyLock<glib::StrV> =
247                    LazyLock::new(|| glib::StrV::from(["file"]));
248                &SUPPORTED_URI_SCHEMES
249            }
250
251            fn parse_name(&self, parse_name: &str) -> File {
252                File::for_parse_name(parse_name)
253            }
254        }
255
256        // Defines `MyCustomVfs` as a subclass of `MyVfs`.
257        #[derive(Default)]
258        pub struct MyCustomVfs;
259
260        #[glib::object_subclass]
261        impl ObjectSubclass for MyCustomVfs {
262            const NAME: &'static str = "MyCustomVfs";
263            type Type = super::MyCustomVfs;
264            type ParentType = super::MyVfs;
265        }
266
267        impl ObjectImpl for MyCustomVfs {}
268
269        // Implements `VfsImpl` with default implementation, which calls the parent's implementation.
270        impl VfsImpl for MyCustomVfs {}
271
272        impl MyVfsImpl for MyCustomVfs {}
273    }
274
275    glib::wrapper! {
276        pub struct MyVfs(ObjectSubclass<imp::MyVfs>) @extends Vfs;
277    }
278
279    pub trait MyVfsImpl: ObjectImpl + ObjectSubclass<Type: IsA<MyVfs> + IsA<Vfs>> {}
280
281    // To make this class subclassable we need to implement IsSubclassable
282    unsafe impl<T: MyVfsImpl + VfsImpl> IsSubclassable<T> for MyVfs {}
283
284    glib::wrapper! {
285        pub struct MyCustomVfs(ObjectSubclass<imp::MyCustomVfs>) @extends MyVfs, Vfs;
286    }
287
288    #[test]
289    fn vfs_is_active() {
290        // invoke `MyCustomVfs` implementation of `gio::ffi::GVfsClass::is_active`
291        let my_custom_vfs = glib::Object::new::<MyCustomVfs>();
292        let active = my_custom_vfs.is_active();
293
294        // invoke `MyVfs` implementation of `gio::ffi::GVfsClass::is_active`
295        let my_vfs = glib::Object::new::<MyVfs>();
296        let expected = my_vfs.is_active();
297
298        // both results should equal
299        assert_eq!(active, expected);
300    }
301
302    #[test]
303    fn vfs_get_file_for_path() {
304        // invoke `MyCustomVfs` implementation of `gio::ffi::GVfsClass::get_file_for_path`
305        let my_custom_vfs = glib::Object::new::<MyCustomVfs>();
306        let file = my_custom_vfs.file_for_path("/path");
307
308        // invoke `MyVfs` implementation of `gio::ffi::GVfsClass::get_file_for_path`
309        let my_vfs = glib::Object::new::<MyVfs>();
310        let expected = my_vfs.file_for_path("/path");
311
312        // both files should equal
313        assert!(file.equal(&expected));
314    }
315
316    #[test]
317    fn vfs_get_file_for_uri() {
318        // invoke `MyCustomVfs` implementation of `gio::ffi::GVfsClass::get_file_for_uri`
319        let my_custom_vfs = glib::Object::new::<MyCustomVfs>();
320        let file = my_custom_vfs.file_for_uri("file:///path");
321
322        // invoke `MyVfs` implementation of `gio::ffi::GVfsClass::get_file_for_uri`
323        let my_vfs = glib::Object::new::<MyVfs>();
324        let expected = my_vfs.file_for_uri("file:///path");
325
326        // both files should equal
327        assert!(file.equal(&expected));
328    }
329
330    #[test]
331    fn vfs_get_supported_uri_schemes() {
332        // invoke `MyCustomVfs` implementation of `gio::ffi::GVfsClass::supported_uri_schemes`
333        let my_custom_vfs = glib::Object::new::<MyCustomVfs>();
334        let schemes = my_custom_vfs.supported_uri_schemes();
335
336        // invoke `MyVfs` implementation of `gio::ffi::GVfsClass::supported_uri_schemes`
337        let my_vfs = glib::Object::new::<MyVfs>();
338        let expected = my_vfs.supported_uri_schemes();
339
340        // both results should equal
341        assert_eq!(schemes, expected);
342    }
343
344    #[test]
345    fn vfs_parse_name() {
346        // invoke `MyCustomVfs` implementation of `gio::ffi::GVfsClass::parse_name`
347        let my_custom_vfs = glib::Object::new::<MyCustomVfs>();
348        let file = my_custom_vfs.parse_name("file:///path");
349
350        // invoke `MyVfs` implementation of `gio::ffi::GVfsClass::parse_name`
351        let my_vfs = glib::Object::new::<MyVfs>();
352        let expected = my_vfs.parse_name("file:///path");
353
354        // both files should equal
355        assert!(file.equal(&expected));
356    }
357}