geos/
context_handle.rs

1use crate::enums::{ByteOrder, OutputDimension};
2use crate::error::{Error, GResult};
3use geos_sys::*;
4use libc::{c_char, c_void, strlen};
5use std::convert::TryFrom;
6use std::ffi::CStr;
7use std::ops::Deref;
8use std::slice;
9use std::sync::Mutex;
10
11thread_local!(
12    static CONTEXT: ContextHandle = ContextHandle::init().unwrap();
13);
14
15/// Provides thread-local geos context to the function `f`.
16///
17/// It is an efficient and thread-safe way of providing geos context to be used in reentrant c api.
18///
19/// # Example
20///
21/// ```ignore
22/// with_context(|ctx| unsafe {
23///     let ptr = GEOSGeom_createEmptyPolygon_r(ctx.as_raw());
24///     GEOSGeom_destroy_r(ctx.as_raw, ptr);
25/// })
26/// ```
27pub(crate) fn with_context<R>(f: impl FnOnce(&ContextHandle) -> R) -> R {
28    CONTEXT.with(f)
29}
30
31pub type HandlerCallback = Box<dyn Fn(&str) + Send + Sync>;
32
33macro_rules! set_callbacks {
34    ($c_func:ident, $kind:ident, $callback_name:ident, $last:ident) => {
35        #[allow(clippy::needless_lifetimes)]
36        fn $kind(ptr: GEOSContextHandle_t, nf: *mut InnerContext) {
37            #[allow(clippy::extra_unused_lifetimes)]
38            unsafe extern "C" fn message_handler_func(message: *const c_char, data: *mut c_void) {
39                let inner_context: &InnerContext = &*(data as *mut _);
40
41                if let Ok(callback) = inner_context.$callback_name.lock() {
42                    let bytes = slice::from_raw_parts(message as *const u8, strlen(message));
43                    let s = CStr::from_bytes_with_nul_unchecked(bytes);
44                    let notif = s.to_str().expect("invalid CStr -> &str conversion");
45                    callback(notif);
46                    if let Ok(mut last) = inner_context.$last.lock() {
47                        *last = Some(notif.to_owned());
48                    }
49                }
50            }
51
52            unsafe {
53                $c_func(ptr, Some(message_handler_func), nf as *mut _);
54            }
55        }
56    };
57}
58
59set_callbacks!(
60    GEOSContext_setNoticeMessageHandler_r,
61    set_notif,
62    notif_callback,
63    last_notification
64);
65set_callbacks!(
66    GEOSContext_setErrorMessageHandler_r,
67    set_error,
68    error_callback,
69    last_error
70);
71
72pub(crate) struct PtrWrap<T>(pub T);
73
74impl<T> Deref for PtrWrap<T> {
75    type Target = T;
76
77    fn deref(&self) -> &Self::Target {
78        &self.0
79    }
80}
81
82unsafe impl<T> Send for PtrWrap<T> {}
83unsafe impl<T> Sync for PtrWrap<T> {}
84
85pub(crate) struct InnerContext {
86    last_notification: Mutex<Option<String>>,
87    last_error: Mutex<Option<String>>,
88    notif_callback: Mutex<HandlerCallback>,
89    error_callback: Mutex<HandlerCallback>,
90}
91
92pub struct ContextHandle {
93    ptr: PtrWrap<GEOSContextHandle_t>,
94    pub(crate) inner: PtrWrap<*mut InnerContext>,
95}
96
97impl ContextHandle {
98    /// Creates a new `ContextHandle`.
99    ///
100    /// # Example
101    ///
102    /// ```
103    /// use geos::ContextHandle;
104    ///
105    /// let context_handle = ContextHandle::init().expect("invalid init");
106    /// ```
107    pub fn init() -> GResult<Self> {
108        let ptr = unsafe { GEOS_init_r() };
109        if ptr.is_null() {
110            return Err(Error::GenericError("GEOS_init_r failed".to_owned()));
111        }
112
113        let last_notification = Mutex::new(None);
114        let last_error = Mutex::new(None);
115
116        let notif_callback: Mutex<HandlerCallback> = Mutex::new(Box::new(|_| {}));
117        let error_callback: Mutex<HandlerCallback> = Mutex::new(Box::new(|_| {}));
118
119        let inner = Box::into_raw(Box::new(InnerContext {
120            last_notification,
121            last_error,
122            notif_callback,
123            error_callback,
124        }));
125
126        set_notif(ptr, inner);
127        set_error(ptr, inner);
128
129        Ok(ContextHandle {
130            ptr: PtrWrap(ptr),
131            inner: PtrWrap(inner),
132        })
133    }
134
135    pub(crate) fn as_raw(&self) -> GEOSContextHandle_t {
136        *self.ptr
137    }
138
139    pub(crate) fn get_inner(&self) -> &InnerContext {
140        unsafe { &*self.inner.0 }
141    }
142
143    /// Allows to set a notice message handler.
144    ///
145    /// Passing [`None`] as parameter will unset this callback.
146    ///
147    /// # Example
148    ///
149    /// ```
150    /// use geos::ContextHandle;
151    ///
152    /// let context_handle = ContextHandle::init().expect("invalid init");
153    ///
154    /// context_handle.set_notice_message_handler(Some(Box::new(|s| println!("new message: {}", s))));
155    /// ```
156    pub fn set_notice_message_handler(&self, nf: Option<HandlerCallback>) {
157        let inner_context = self.get_inner();
158        if let Ok(mut callback) = inner_context.notif_callback.lock() {
159            if let Some(nf) = nf {
160                *callback = nf;
161            } else {
162                *callback = Box::new(|_| {});
163            }
164        }
165    }
166
167    /// Allows to set an error message handler.
168    ///
169    /// Passing [`None`] as parameter will unset this callback.
170    ///
171    /// # Example
172    ///
173    /// ```
174    /// use geos::ContextHandle;
175    ///
176    /// let context_handle = ContextHandle::init().expect("invalid init");
177    ///
178    /// context_handle.set_error_message_handler(Some(Box::new(|s| println!("new message: {}", s))));
179    /// ```
180    pub fn set_error_message_handler(&self, ef: Option<HandlerCallback>) {
181        let inner_context = self.get_inner();
182        if let Ok(mut callback) = inner_context.error_callback.lock() {
183            if let Some(ef) = ef {
184                *callback = ef;
185            } else {
186                *callback = Box::new(|_| {});
187            }
188        }
189    }
190
191    /// Returns the last error encountered.
192    ///
193    /// Please note that calling this function will remove the current last error!
194    ///
195    /// ```
196    /// use geos::ContextHandle;
197    ///
198    /// let context_handle = ContextHandle::init().expect("invalid init");
199    /// // make some functions calls...
200    /// if let Some(last_error) = context_handle.get_last_error() {
201    ///     println!("We have an error: {}", last_error);
202    /// } else {
203    ///     println!("No error occurred!");
204    /// }
205    /// ```
206    pub fn get_last_error(&self) -> Option<String> {
207        let inner_context = self.get_inner();
208        if let Ok(mut last) = inner_context.last_error.lock() {
209            last.take()
210        } else {
211            None
212        }
213    }
214
215    /// Returns the last notification encountered.
216    ///
217    /// Please note that calling this function will remove the current last notification!
218    ///
219    /// ```
220    /// use geos::ContextHandle;
221    ///
222    /// let context_handle = ContextHandle::init().expect("invalid init");
223    /// // make some functions calls...
224    /// if let Some(last_notif) = context_handle.get_last_notification() {
225    ///     println!("We have a notification: {}", last_notif);
226    /// } else {
227    ///     println!("No notifications!");
228    /// }
229    /// ```
230    pub fn get_last_notification(&self) -> Option<String> {
231        let inner_context = self.get_inner();
232        if let Ok(mut last) = inner_context.last_notification.lock() {
233            last.take()
234        } else {
235            None
236        }
237    }
238
239    /// Gets WKB output dimensions.
240    ///
241    /// # Example
242    ///
243    /// ```
244    /// use geos::{ContextHandle, OutputDimension};
245    ///
246    /// let mut context_handle = ContextHandle::init().expect("invalid init");
247    ///
248    /// context_handle.set_wkb_output_dimensions(OutputDimension::TwoD);
249    /// assert_eq!(context_handle.get_wkb_output_dimensions(), Ok(OutputDimension::TwoD));
250    /// ```
251    pub fn get_wkb_output_dimensions(&self) -> GResult<OutputDimension> {
252        unsafe {
253            let out = GEOS_getWKBOutputDims_r(self.as_raw());
254            OutputDimension::try_from(out).map_err(|e| Error::GenericError(e.to_owned()))
255        }
256    }
257
258    /// Sets WKB output dimensions.
259    ///
260    /// # Example
261    ///
262    /// ```
263    /// use geos::{ContextHandle, OutputDimension};
264    ///
265    /// let mut context_handle = ContextHandle::init().expect("invalid init");
266    ///
267    /// context_handle.set_wkb_output_dimensions(OutputDimension::TwoD);
268    /// assert_eq!(context_handle.get_wkb_output_dimensions(), Ok(OutputDimension::TwoD));
269    /// ```
270    pub fn set_wkb_output_dimensions(
271        &mut self,
272        dimensions: OutputDimension,
273    ) -> GResult<OutputDimension> {
274        unsafe {
275            let out = GEOS_setWKBOutputDims_r(self.as_raw(), dimensions.into());
276            OutputDimension::try_from(out).map_err(|e| Error::GenericError(e.to_owned()))
277        }
278    }
279
280    /// Gets WKB byte order.
281    ///
282    /// # Example
283    ///
284    /// ```
285    /// use geos::{ContextHandle, ByteOrder};
286    ///
287    /// let mut context_handle = ContextHandle::init().expect("invalid init");
288    ///
289    /// context_handle.set_wkb_byte_order(ByteOrder::LittleEndian);
290    /// assert!(context_handle.get_wkb_byte_order() == ByteOrder::LittleEndian);
291    /// ```
292    pub fn get_wkb_byte_order(&self) -> ByteOrder {
293        ByteOrder::try_from(unsafe { GEOS_getWKBByteOrder_r(self.as_raw()) })
294            .expect("failed to convert to ByteOrder")
295    }
296
297    /// Sets WKB byte order.
298    ///
299    /// # Example
300    ///
301    /// ```
302    /// use geos::{ContextHandle, ByteOrder};
303    ///
304    /// let mut context_handle = ContextHandle::init().expect("invalid init");
305    ///
306    /// context_handle.set_wkb_byte_order(ByteOrder::LittleEndian);
307    /// assert!(context_handle.get_wkb_byte_order() == ByteOrder::LittleEndian);
308    /// ```
309    pub fn set_wkb_byte_order(&mut self, byte_order: ByteOrder) -> ByteOrder {
310        ByteOrder::try_from(unsafe { GEOS_setWKBByteOrder_r(self.as_raw(), byte_order.into()) })
311            .expect("failed to convert to ByteOrder")
312    }
313}
314
315impl Drop for ContextHandle {
316    fn drop(&mut self) {
317        unsafe {
318            if !self.ptr.is_null() {
319                GEOS_finish_r(self.as_raw());
320            }
321            // Now we just have to clear stuff!
322            let _inner: Box<InnerContext> = Box::from_raw(self.inner.0);
323        }
324    }
325}