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}