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}