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}