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::{GString, Type, translate::*};
6
7use crate::{IOExtension, ffi};
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        unsafe {
116            debug_assert!(!ptr.is_null());
117            IOExtensionPoint(ptr::NonNull::new_unchecked(ptr))
118        }
119    }
120}
121
122impl<'a> ToGlibPtr<'a, *mut ffi::GIOExtensionPoint> for &'a IOExtensionPoint {
123    type Storage = PhantomData<&'a IOExtensionPoint>;
124
125    #[inline]
126    fn to_glib_none(&self) -> Stash<'a, *mut ffi::GIOExtensionPoint, &'a IOExtensionPoint> {
127        Stash(self.0.as_ptr() as *mut ffi::GIOExtensionPoint, PhantomData)
128    }
129}
130
131impl IOExtensionPoint {
132    // rustdoc-stripper-ignore-next
133    /// Create a new builder for an extension point.
134    #[doc(alias = "g_io_extension_point_register")]
135    pub fn builder(name: impl Into<GString>) -> IOExtensionPointBuilder {
136        IOExtensionPointBuilder::new(name.into())
137    }
138
139    /// Looks up an existing extension point.
140    /// ## `name`
141    /// the name of the extension point
142    ///
143    /// # Returns
144    ///
145    /// the #GIOExtensionPoint, or [`None`] if there
146    ///    is no registered extension point with the given name.
147    #[doc(alias = "g_io_extension_point_lookup")]
148    pub fn lookup(name: impl IntoGStr) -> Option<Self> {
149        name.run_with_gstr(|name| unsafe {
150            let ep = ffi::g_io_extension_point_lookup(name.to_glib_none().0);
151            from_glib_none(ep)
152        })
153    }
154
155    /// Gets a list of all extensions that implement this extension point.
156    /// The list is sorted by priority, beginning with the highest priority.
157    ///
158    /// # Returns
159    ///
160    /// a #GList of
161    ///     #GIOExtensions. The list is owned by GIO and should not be
162    ///     modified.
163    #[doc(alias = "g_io_extension_point_get_extensions")]
164    pub fn extensions(&self) -> Vec<IOExtension> {
165        let mut res = Vec::new();
166        unsafe {
167            let mut l = ffi::g_io_extension_point_get_extensions(self.0.as_ptr());
168            while !l.is_null() {
169                let e: *mut ffi::GIOExtension = Ptr::from((*l).data);
170                res.push(from_glib_none(e));
171                l = (*l).next;
172            }
173        }
174        res
175    }
176
177    /// Finds a #GIOExtension for an extension point by name.
178    /// ## `name`
179    /// the name of the extension to get
180    ///
181    /// # Returns
182    ///
183    /// the #GIOExtension for @self that has the
184    ///    given name, or [`None`] if there is no extension with that name
185    #[doc(alias = "g_io_extension_point_get_extension_by_name")]
186    pub fn extension_by_name(&self, name: impl IntoGStr) -> Option<IOExtension> {
187        name.run_with_gstr(|name| unsafe {
188            let e = ffi::g_io_extension_point_get_extension_by_name(
189                self.0.as_ptr(),
190                name.to_glib_none().0,
191            );
192            from_glib_none(e)
193        })
194    }
195
196    /// Gets the required type for @self.
197    ///
198    /// # Returns
199    ///
200    /// the #GType that all implementations must have,
201    ///   or `G_TYPE_INVALID` if the extension point has no required type
202    #[doc(alias = "g_io_extension_point_get_required_type")]
203    pub fn required_type(&self) -> Type {
204        unsafe { from_glib(ffi::g_io_extension_point_get_required_type(self.0.as_ptr())) }
205    }
206
207    /// Registers @type_ as extension for the extension point with name
208    /// @extension_point_name.
209    ///
210    /// If @type_ has already been registered as an extension for this
211    /// extension point, the existing #GIOExtension object is returned.
212    /// ## `extension_point_name`
213    /// the name of the extension point
214    /// ## `type_`
215    /// the #GType to register as extension
216    /// ## `extension_name`
217    /// the name for the extension
218    /// ## `priority`
219    /// the priority for the extension
220    ///
221    /// # Returns
222    ///
223    /// a #GIOExtension object for #GType
224    #[doc(alias = "g_io_extension_point_implement")]
225    pub fn implement(
226        extension_point_name: impl IntoGStr,
227        type_: Type,
228        extension_name: impl IntoGStr,
229        priority: i32,
230    ) -> Option<IOExtension> {
231        extension_point_name.run_with_gstr(|extension_point_name| {
232            extension_name.run_with_gstr(|extension_name| unsafe {
233                let e = ffi::g_io_extension_point_implement(
234                    extension_point_name.to_glib_none().0,
235                    type_.into_glib(),
236                    extension_name.to_glib_none().0,
237                    priority,
238                );
239                from_glib_none(e)
240            })
241        })
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use glib::prelude::*;
248
249    use super::*;
250
251    #[test]
252    fn extension_point() {
253        let ep = IOExtensionPoint::lookup("test-extension-point");
254        assert!(ep.is_none());
255
256        let ep = IOExtensionPoint::builder("test-extension-point").build();
257        let ep2 = IOExtensionPoint::lookup("test-extension-point");
258        assert_eq!(ep2, Some(ep));
259
260        let req = ep.required_type();
261        assert_eq!(req, Type::INVALID);
262
263        let ep = IOExtensionPoint::builder("test-extension-point")
264            .required_type(Type::OBJECT)
265            .build();
266        let req = ep.required_type();
267        assert_eq!(req, Type::OBJECT);
268
269        let v = ep.extensions();
270        assert!(v.is_empty());
271
272        let e = IOExtensionPoint::implement(
273            "test-extension-point",
274            <crate::Vfs as StaticType>::static_type(),
275            "extension1",
276            10,
277        );
278        assert!(e.is_some());
279
280        let e = IOExtensionPoint::implement("test-extension-point", Type::OBJECT, "extension2", 20);
281        assert!(e.is_some());
282
283        let v = ep.extensions();
284        assert_eq!(v.len(), 2);
285        assert_eq!(v[0].name(), "extension2");
286        assert_eq!(v[0].type_(), Type::OBJECT);
287        assert_eq!(v[0].priority(), 20);
288        assert_eq!(v[1].name(), "extension1");
289        assert_eq!(v[1].type_(), <crate::Vfs as StaticType>::static_type());
290        assert_eq!(v[1].priority(), 10);
291    }
292}