gio/
io_extension_point.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{marker::PhantomData, ptr};
4
5use glib::{translate::*, GString, Type};
6
7use crate::{ffi, IOExtension};
8
9// rustdoc-stripper-ignore-next
10/// Builder for extension points.
11#[derive(Debug)]
12#[must_use = "The builder must be built to be used"]
13pub struct IOExtensionPointBuilder {
14    name: GString,
15    required_type: Option<Type>,
16}
17
18impl IOExtensionPointBuilder {
19    fn new(name: GString) -> Self {
20        Self {
21            name,
22            required_type: None,
23        }
24    }
25
26    #[doc(alias = "g_io_extension_point_set_required_type")]
27    pub fn required_type(self, required_type: Type) -> Self {
28        Self {
29            required_type: Some(required_type),
30            ..self
31        }
32    }
33
34    #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"]
35    pub fn build(self) -> IOExtensionPoint {
36        unsafe {
37            let ep = IOExtensionPoint::from_glib_none(ffi::g_io_extension_point_register(
38                self.name.to_glib_none().0,
39            ));
40            if let Some(t) = self.required_type {
41                ffi::g_io_extension_point_set_required_type(ep.0.as_ptr(), t.into_glib());
42            }
43            ep
44        }
45    }
46}
47
48// rustdoc-stripper-ignore-next
49/// An extension point provides a mechanism to extend the functionality of a library or application.
50/// Each extension point is identified by a name, and it may optionally require that any implementation
51/// must be of a certain type.
52// rustdoc-stripper-ignore-next-stop
53/// `GIOExtensionPoint` provides a mechanism for modules to extend the
54/// functionality of the library or application that loaded it in an
55/// organized fashion.
56///
57/// An extension point is identified by a name, and it may optionally
58/// require that any implementation must be of a certain type (or derived
59/// thereof). Use [`register()`][Self::register()] to register an
60/// extension point, and [`set_required_type()`][Self::set_required_type()] to
61/// set a required type.
62///
63/// A module can implement an extension point by specifying the
64/// [type@GObject.Type] that implements the functionality. Additionally, each
65/// implementation of an extension point has a name, and a priority. Use
66/// [`implement()`][Self::implement()] to implement an extension point.
67///
68/// **⚠️ The following code is in c ⚠️**
69///
70/// ```c
71/// GIOExtensionPoint *ep;
72///
73/// // Register an extension point
74/// ep = g_io_extension_point_register ("my-extension-point");
75/// g_io_extension_point_set_required_type (ep, MY_TYPE_EXAMPLE);
76/// ```
77///
78/// **⚠️ The following code is in c ⚠️**
79///
80/// ```c
81/// // Implement an extension point
82/// G_DEFINE_TYPE (MyExampleImpl, my_example_impl, MY_TYPE_EXAMPLE)
83/// g_io_extension_point_implement ("my-extension-point",
84///                                 my_example_impl_get_type (),
85///                                 "my-example",
86///                                 10);
87/// ```
88///
89///  It is up to the code that registered the extension point how
90///  it uses the implementations that have been associated with it.
91///  Depending on the use case, it may use all implementations, or
92///  only the one with the highest priority, or pick a specific
93///  one by name.
94///
95///  To avoid opening all modules just to find out what extension
96///  points they implement, GIO makes use of a caching mechanism,
97///  see [gio-querymodules](gio-querymodules.html).
98///  You are expected to run this command after installing a
99///  GIO module.
100///
101///  The `GIO_EXTRA_MODULES` environment variable can be used to
102///  specify additional directories to automatically load modules
103///  from. This environment variable has the same syntax as the
104///  `PATH`. If two modules have the same base name in different
105///  directories, then the latter one will be ignored. If additional
106///  directories are specified GIO will load modules from the built-in
107///  directory last.
108#[doc(alias = "GIOExtensionPoint")]
109#[derive(Debug, Copy, Clone, Eq, PartialEq)]
110pub struct IOExtensionPoint(ptr::NonNull<ffi::GIOExtensionPoint>);
111
112impl FromGlibPtrNone<*mut ffi::GIOExtensionPoint> for IOExtensionPoint {
113    #[inline]
114    unsafe fn from_glib_none(ptr: *mut ffi::GIOExtensionPoint) -> Self {
115        debug_assert!(!ptr.is_null());
116        IOExtensionPoint(ptr::NonNull::new_unchecked(ptr))
117    }
118}
119
120impl<'a> ToGlibPtr<'a, *mut ffi::GIOExtensionPoint> for &'a IOExtensionPoint {
121    type Storage = PhantomData<&'a IOExtensionPoint>;
122
123    #[inline]
124    fn to_glib_none(&self) -> Stash<'a, *mut ffi::GIOExtensionPoint, &'a IOExtensionPoint> {
125        Stash(self.0.as_ptr() as *mut ffi::GIOExtensionPoint, PhantomData)
126    }
127}
128
129impl IOExtensionPoint {
130    // rustdoc-stripper-ignore-next
131    /// Create a new builder for an extension point.
132    #[doc(alias = "g_io_extension_point_register")]
133    pub fn builder(name: impl Into<GString>) -> IOExtensionPointBuilder {
134        IOExtensionPointBuilder::new(name.into())
135    }
136
137    /// Looks up an existing extension point.
138    /// ## `name`
139    /// the name of the extension point
140    ///
141    /// # Returns
142    ///
143    /// the #GIOExtensionPoint, or [`None`] if there
144    ///    is no registered extension point with the given name.
145    #[doc(alias = "g_io_extension_point_lookup")]
146    pub fn lookup(name: impl IntoGStr) -> Option<Self> {
147        name.run_with_gstr(|name| unsafe {
148            let ep = ffi::g_io_extension_point_lookup(name.to_glib_none().0);
149            from_glib_none(ep)
150        })
151    }
152
153    /// Gets a list of all extensions that implement this extension point.
154    /// The list is sorted by priority, beginning with the highest priority.
155    ///
156    /// # Returns
157    ///
158    /// a #GList of
159    ///     #GIOExtensions. The list is owned by GIO and should not be
160    ///     modified.
161    #[doc(alias = "g_io_extension_point_get_extensions")]
162    pub fn extensions(&self) -> Vec<IOExtension> {
163        let mut res = Vec::new();
164        unsafe {
165            let mut l = ffi::g_io_extension_point_get_extensions(self.0.as_ptr());
166            while !l.is_null() {
167                let e: *mut ffi::GIOExtension = Ptr::from((*l).data);
168                res.push(from_glib_none(e));
169                l = (*l).next;
170            }
171        }
172        res
173    }
174
175    /// Finds a #GIOExtension for an extension point by name.
176    /// ## `name`
177    /// the name of the extension to get
178    ///
179    /// # Returns
180    ///
181    /// the #GIOExtension for @self that has the
182    ///    given name, or [`None`] if there is no extension with that name
183    #[doc(alias = "g_io_extension_point_get_extension_by_name")]
184    pub fn extension_by_name(&self, name: impl IntoGStr) -> Option<IOExtension> {
185        name.run_with_gstr(|name| unsafe {
186            let e = ffi::g_io_extension_point_get_extension_by_name(
187                self.0.as_ptr(),
188                name.to_glib_none().0,
189            );
190            from_glib_none(e)
191        })
192    }
193
194    /// Gets the required type for @self.
195    ///
196    /// # Returns
197    ///
198    /// the #GType that all implementations must have,
199    ///   or `G_TYPE_INVALID` if the extension point has no required type
200    #[doc(alias = "g_io_extension_point_get_required_type")]
201    pub fn required_type(&self) -> Type {
202        unsafe { from_glib(ffi::g_io_extension_point_get_required_type(self.0.as_ptr())) }
203    }
204
205    /// Registers @type_ as extension for the extension point with name
206    /// @extension_point_name.
207    ///
208    /// If @type_ has already been registered as an extension for this
209    /// extension point, the existing #GIOExtension object is returned.
210    /// ## `extension_point_name`
211    /// the name of the extension point
212    /// ## `type_`
213    /// the #GType to register as extension
214    /// ## `extension_name`
215    /// the name for the extension
216    /// ## `priority`
217    /// the priority for the extension
218    ///
219    /// # Returns
220    ///
221    /// a #GIOExtension object for #GType
222    #[doc(alias = "g_io_extension_point_implement")]
223    pub fn implement(
224        extension_point_name: impl IntoGStr,
225        type_: Type,
226        extension_name: impl IntoGStr,
227        priority: i32,
228    ) -> Option<IOExtension> {
229        extension_point_name.run_with_gstr(|extension_point_name| {
230            extension_name.run_with_gstr(|extension_name| unsafe {
231                let e = ffi::g_io_extension_point_implement(
232                    extension_point_name.to_glib_none().0,
233                    type_.into_glib(),
234                    extension_name.to_glib_none().0,
235                    priority,
236                );
237                from_glib_none(e)
238            })
239        })
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use glib::prelude::*;
246
247    use super::*;
248
249    #[test]
250    fn extension_point() {
251        let ep = IOExtensionPoint::lookup("test-extension-point");
252        assert!(ep.is_none());
253
254        let ep = IOExtensionPoint::builder("test-extension-point").build();
255        let ep2 = IOExtensionPoint::lookup("test-extension-point");
256        assert_eq!(ep2, Some(ep));
257
258        let req = ep.required_type();
259        assert_eq!(req, Type::INVALID);
260
261        let ep = IOExtensionPoint::builder("test-extension-point")
262            .required_type(Type::OBJECT)
263            .build();
264        let req = ep.required_type();
265        assert_eq!(req, Type::OBJECT);
266
267        let v = ep.extensions();
268        assert!(v.is_empty());
269
270        let e = IOExtensionPoint::implement(
271            "test-extension-point",
272            <crate::Vfs as StaticType>::static_type(),
273            "extension1",
274            10,
275        );
276        assert!(e.is_some());
277
278        let e = IOExtensionPoint::implement("test-extension-point", Type::OBJECT, "extension2", 20);
279        assert!(e.is_some());
280
281        let v = ep.extensions();
282        assert_eq!(v.len(), 2);
283        assert_eq!(v[0].name(), "extension2");
284        assert_eq!(v[0].type_(), Type::OBJECT);
285        assert_eq!(v[0].priority(), 20);
286        assert_eq!(v[1].name(), "extension1");
287        assert_eq!(v[1].type_(), <crate::Vfs as StaticType>::static_type());
288        assert_eq!(v[1].priority(), 10);
289    }
290}