obs_wrapper/
data.rs

1#![allow(non_upper_case_globals)]
2use std::{
3    borrow::Cow,
4    ffi::{CStr, CString},
5    marker::PhantomData,
6};
7
8use obs_sys::{
9    obs_data_array_count, obs_data_array_item, obs_data_array_release, obs_data_array_t,
10    obs_data_clear, obs_data_create, obs_data_create_from_json, obs_data_create_from_json_file,
11    obs_data_create_from_json_file_safe, obs_data_erase, obs_data_get_json, obs_data_item_byname,
12    obs_data_item_get_array, obs_data_item_get_bool, obs_data_item_get_double,
13    obs_data_item_get_int, obs_data_item_get_obj, obs_data_item_get_string, obs_data_item_gettype,
14    obs_data_item_numtype, obs_data_item_release, obs_data_item_t, obs_data_number_type,
15    obs_data_number_type_OBS_DATA_NUM_DOUBLE, obs_data_number_type_OBS_DATA_NUM_INT,
16    obs_data_release, obs_data_set_default_bool, obs_data_set_default_double,
17    obs_data_set_default_int, obs_data_set_default_obj, obs_data_set_default_string, obs_data_t,
18    obs_data_type, obs_data_type_OBS_DATA_ARRAY, obs_data_type_OBS_DATA_BOOLEAN,
19    obs_data_type_OBS_DATA_NUMBER, obs_data_type_OBS_DATA_OBJECT, obs_data_type_OBS_DATA_STRING,
20    size_t,
21};
22
23use crate::{string::ObsString, wrapper::PtrWrapper};
24
25#[derive(Debug, Eq, PartialEq, Copy, Clone)]
26pub enum DataType {
27    String,
28    Int,
29    Double,
30    Boolean,
31    /// Map container
32    Object,
33    /// Array container
34    Array,
35}
36
37impl DataType {
38    pub fn new(typ: obs_data_type, numtyp: obs_data_number_type) -> Self {
39        match typ {
40            obs_data_type_OBS_DATA_STRING => Self::String,
41            obs_data_type_OBS_DATA_NUMBER => match numtyp {
42                obs_data_number_type_OBS_DATA_NUM_INT => Self::Int,
43                obs_data_number_type_OBS_DATA_NUM_DOUBLE => Self::Double,
44                _ => unimplemented!(),
45            },
46            obs_data_type_OBS_DATA_BOOLEAN => Self::Boolean,
47            obs_data_type_OBS_DATA_OBJECT => Self::Object,
48            obs_data_type_OBS_DATA_ARRAY => Self::Array,
49            _ => unimplemented!(),
50        }
51    }
52
53    unsafe fn from_item(item_ptr: *mut obs_data_item_t) -> Self {
54        let typ = obs_data_item_gettype(item_ptr);
55        let numtyp = obs_data_item_numtype(item_ptr);
56        Self::new(typ, numtyp)
57    }
58}
59
60pub trait FromDataItem {
61    fn typ() -> DataType;
62    /// # Safety
63    ///
64    /// Pointer must be valid.
65    unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self;
66
67    /// # Safety
68    ///
69    /// Pointer must be valid.
70    unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self);
71}
72
73impl FromDataItem for Cow<'_, str> {
74    fn typ() -> DataType {
75        DataType::String
76    }
77    unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self {
78        let ptr = obs_data_item_get_string(item);
79        CStr::from_ptr(ptr).to_string_lossy()
80    }
81    unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) {
82        let s = CString::new(val.as_ref()).unwrap();
83        obs_data_set_default_string(obj, name.as_ptr(), s.as_ptr());
84    }
85}
86
87impl FromDataItem for ObsString {
88    fn typ() -> DataType {
89        DataType::String
90    }
91    unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self {
92        let ptr = obs_data_item_get_string(item);
93        ObsString::Dynamic(CStr::from_ptr(ptr).into())
94    }
95    unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) {
96        obs_data_set_default_string(obj, name.as_ptr(), val.as_ptr());
97    }
98}
99
100macro_rules! impl_get_int {
101    ($($t:ty)*) => {
102        $(
103            impl FromDataItem for $t {
104                fn typ() -> DataType {
105                    DataType::Int
106                }
107                unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self {
108                    obs_data_item_get_int(item) as $t
109                }
110                unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) {
111                    obs_data_set_default_int(obj, name.as_ptr(), val as i64)
112                }
113            }
114        )*
115    };
116}
117
118impl_get_int!(i64 u64 i32 u32 i16 u16 i8 u8 isize usize);
119
120impl FromDataItem for f64 {
121    fn typ() -> DataType {
122        DataType::Double
123    }
124    unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self {
125        obs_data_item_get_double(item)
126    }
127    unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) {
128        obs_data_set_default_double(obj, name.as_ptr(), val)
129    }
130}
131
132impl FromDataItem for f32 {
133    fn typ() -> DataType {
134        DataType::Double
135    }
136    unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self {
137        obs_data_item_get_double(item) as f32
138    }
139    unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) {
140        obs_data_set_default_double(obj, name.as_ptr(), val as f64)
141    }
142}
143
144impl FromDataItem for bool {
145    fn typ() -> DataType {
146        DataType::Boolean
147    }
148    unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self {
149        obs_data_item_get_bool(item)
150    }
151    unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) {
152        obs_data_set_default_bool(obj, name.as_ptr(), val)
153    }
154}
155
156impl FromDataItem for DataObj<'_> {
157    fn typ() -> DataType {
158        DataType::Object
159    }
160    unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self {
161        Self::from_raw(obs_data_item_get_obj(item))
162    }
163    unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, mut val: Self) {
164        obs_data_set_default_obj(obj, name.as_ptr(), val.as_ptr_mut())
165    }
166}
167
168impl FromDataItem for DataArray<'_> {
169    fn typ() -> DataType {
170        DataType::Array
171    }
172    unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self {
173        Self::from_raw(obs_data_item_get_array(item))
174    }
175    unsafe fn set_default_unchecked(_obj: *mut obs_data_t, _name: ObsString, _val: Self) {
176        unimplemented!("obs_data_set_default_array function doesn't exist")
177    }
178}
179
180/// A smart pointer to `obs_data_t`
181pub struct DataObj<'parent> {
182    raw: *mut obs_data_t,
183    _parent: PhantomData<&'parent DataObj<'parent>>,
184}
185
186impl PtrWrapper for DataObj<'_> {
187    type Pointer = obs_data_t;
188
189    unsafe fn from_raw(raw: *mut Self::Pointer) -> Self {
190        Self {
191            raw,
192            _parent: PhantomData,
193        }
194    }
195
196    fn as_ptr(&self) -> *const Self::Pointer {
197        self.raw
198    }
199}
200
201impl Default for DataObj<'_> {
202    fn default() -> Self {
203        DataObj::new()
204    }
205}
206
207impl DataObj<'_> {
208    /// Creates a empty data object
209    pub fn new() -> Self {
210        unsafe {
211            let raw = obs_data_create();
212            Self::from_raw(raw)
213        }
214    }
215
216    /// Loads data into a object from a JSON string.
217    pub fn from_json(json_str: impl Into<ObsString>) -> Option<Self> {
218        let json_str = json_str.into();
219        unsafe {
220            let raw = obs_data_create_from_json(json_str.as_ptr());
221            if raw.is_null() {
222                None
223            } else {
224                Some(Self::from_raw(raw))
225            }
226        }
227    }
228
229    /// Loads data into a object from a JSON file.
230    /// * `backup_ext`: optional backup file path in case the original file is
231    ///   bad.
232    pub fn from_json_file(
233        json_file: impl Into<ObsString>,
234        backup_ext: impl Into<Option<ObsString>>,
235    ) -> Option<Self> {
236        let json_file = json_file.into();
237
238        unsafe {
239            let raw = if let Some(backup_ext) = backup_ext.into() {
240                obs_data_create_from_json_file_safe(json_file.as_ptr(), backup_ext.as_ptr())
241            } else {
242                obs_data_create_from_json_file(json_file.as_ptr())
243            };
244            if raw.is_null() {
245                None
246            } else {
247                Some(Self::from_raw(raw))
248            }
249        }
250    }
251
252    /// Fetches a property from this object. Numbers are implicitly casted.
253    pub fn get<T: FromDataItem>(&self, name: impl Into<ObsString>) -> Option<T> {
254        let name = name.into();
255        let mut item_ptr = unsafe { obs_data_item_byname(self.as_ptr() as *mut _, name.as_ptr()) };
256        if item_ptr.is_null() {
257            return None;
258        }
259        // Release it immediately since it is also referenced by this object.
260        unsafe {
261            obs_data_item_release(&mut item_ptr);
262        }
263        assert!(!item_ptr.is_null()); // We should not be the last holder
264
265        let typ = unsafe { DataType::from_item(item_ptr) };
266
267        if typ == T::typ() {
268            Some(unsafe { T::from_item_unchecked(item_ptr) })
269        } else {
270            None
271        }
272    }
273
274    /// Sets a default value for the key.
275    ///
276    /// Notes
277    /// -----
278    /// Setting a default value for a [`DataArray`] is not supported and will panic.
279    pub fn set_default<T: FromDataItem>(
280        &mut self,
281        name: impl Into<ObsString>,
282        value: impl Into<T>,
283    ) {
284        unsafe { T::set_default_unchecked(self.as_ptr_mut(), name.into(), value.into()) }
285    }
286
287    /// Creates a JSON representation of this object.
288    pub fn get_json(&self) -> Option<String> {
289        unsafe {
290            let ptr = obs_data_get_json(self.raw);
291            if ptr.is_null() {
292                None
293            } else {
294                let slice = CStr::from_ptr(ptr);
295                Some(slice.to_string_lossy().into_owned())
296            }
297        }
298    }
299
300    /// Clears all values.
301    pub fn clear(&mut self) {
302        unsafe {
303            obs_data_clear(self.raw);
304        }
305    }
306
307    pub fn remove(&mut self, name: impl Into<ObsString>) {
308        let name = name.into();
309        unsafe {
310            obs_data_erase(self.raw, name.as_ptr());
311        }
312    }
313}
314
315impl Drop for DataObj<'_> {
316    fn drop(&mut self) {
317        unsafe {
318            obs_data_release(self.raw);
319        }
320    }
321}
322
323pub struct DataArray<'parent> {
324    raw: *mut obs_data_array_t,
325    _parent: PhantomData<&'parent DataArray<'parent>>,
326}
327
328impl PtrWrapper for DataArray<'_> {
329    type Pointer = obs_data_array_t;
330
331    unsafe fn from_raw(raw: *mut Self::Pointer) -> Self {
332        Self {
333            raw,
334            _parent: PhantomData,
335        }
336    }
337
338    fn as_ptr(&self) -> *const Self::Pointer {
339        self.raw
340    }
341}
342
343impl DataArray<'_> {
344    pub fn len(&self) -> usize {
345        unsafe { obs_data_array_count(self.raw) as usize }
346    }
347
348    pub fn is_empty(&self) -> bool {
349        self.len() == 0
350    }
351
352    pub fn get(&self, index: usize) -> Option<DataObj> {
353        let ptr = unsafe { obs_data_array_item(self.raw, index as size_t) };
354        if ptr.is_null() {
355            None
356        } else {
357            Some(unsafe { DataObj::from_raw(ptr) })
358        }
359    }
360}
361
362impl Drop for DataArray<'_> {
363    fn drop(&mut self) {
364        unsafe {
365            obs_data_array_release(self.raw);
366        }
367    }
368}