rea_rs/
ext_state.rs

1use crate::{
2    utils::{as_c_str, as_string, make_c_string_buf, WithNull},
3    Envelope, GenericSend, Item, KnowsProject, Mutable, ProbablyMutable,
4    Project, Reaper, SendIntType, Take, Track, TrackSend, WithReaperPtr,
5};
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8use std::{
9    ffi::{CStr, CString},
10    fmt::Debug,
11    ptr::null,
12};
13
14/// Serializes extension data.
15///
16/// This struct should be used instead of simple `set_ext_state`
17/// and `get_ext_state` calls.
18///
19/// The features, that make it better are:
20/// - It serializes not strings, but anything, can be serialized
21/// by [serde] crate.
22/// - It provides the similar interface as for global ext state values,
23/// as well as to project or other objects ext data.
24/// Currently supported: [Project], [Track], [TrackSend], [Envelope], [Item],
25/// [Take]
26/// - it erases data, if in process of development
27/// you decided to turn the persistence off.
28/// - it can be initialized with value, but only once, if
29/// persistence is needed.
30///
31/// Also, it is very idiomatic and type-safe, so it ideally suits
32/// as gui state etc.
33///
34/// # Note
35///
36/// Be careful with ext section and key. If two different states
37/// of different types are saved for one key — it will panic at get().
38///
39/// # Usage
40///
41/// ```no_run
42/// use rea_rs::{ExtState, HasExtState, Reaper, Project};
43/// let rpr = Reaper::get();
44/// let mut state =
45///     ExtState::new("test section", "first", Some(10), true, rpr);
46/// assert_eq!(state.get().expect("can not get value"), 10);
47/// state.set(56);
48/// assert_eq!(state.get().expect("can not get value"), 56);
49/// state.delete();
50/// assert!(state.get().is_none());
51///
52/// let mut pr = rpr.current_project();
53/// let mut state: ExtState<u32, Project> =
54///     ExtState::new("test section", "first", None, true, &pr);
55/// assert_eq!(state.get().expect("can not get value"), 10);
56/// state.set(56);
57/// assert_eq!(state.get().expect("can not get value"), 56);
58/// state.delete();
59/// assert!(state.get().is_none());
60///
61/// let tr = pr.get_track_mut(0).unwrap();
62/// let mut state = ExtState::new("testsection", "first", 45, false, &tr);
63/// assert_eq!(state.get().expect("can not get value"), 45);
64/// state.set(15);
65/// assert_eq!(state.get().expect("can not get value"), 15);
66/// state.delete();
67/// assert_eq!(state.get(), None);
68/// ```
69#[derive(Debug, PartialEq)]
70pub struct ExtState<
71    'a,
72    T: Serialize + DeserializeOwned + Clone + Debug,
73    O: HasExtState,
74> {
75    section: String,
76    key: String,
77    value: Option<T>,
78    persist: bool,
79    object: &'a O,
80    buf_size: usize,
81}
82impl<'a, T: Serialize + DeserializeOwned + Clone + Debug, O: HasExtState>
83    ExtState<'a, T, O>
84{
85    /// Create ext state object.
86    ///
87    /// If Some(value) provided, but persist is true,
88    /// only first call to the [ExtState::new] will
89    /// initialize it. Later will keep previous value.
90    pub fn new(
91        section: impl Into<String>,
92        key: impl Into<String>,
93        value: impl Into<Option<T>>,
94        persist: bool,
95        object: &'a O,
96    ) -> Self {
97        let value = value.into();
98        let mut obj = Self {
99            section: section.into(),
100            key: key.into(),
101            value,
102            persist,
103            object: object,
104            buf_size: 4096,
105        };
106        match obj.value.as_ref() {
107            None => {
108                if persist {
109                    match obj.get() {
110                        None => (),
111                        Some(val) => obj.set(val),
112                    }
113                } else {
114                    obj.delete()
115                }
116            }
117            Some(val) => {
118                if persist && obj.get().is_none() || !persist {
119                    obj.set(val.clone())
120                }
121            }
122        }
123        obj
124    }
125
126    fn section(&self) -> String {
127        self.section.clone().with_null().to_string()
128    }
129    fn key(&self) -> String {
130        self.key.clone().with_null().to_string()
131    }
132
133    /// Get value from ext state.
134    ///
135    /// Returns None if no value saved in REAPER.
136    ///
137    /// # Panics
138    ///
139    /// If value of the wrong type stored in the
140    /// same section/key.
141    pub fn get(&self) -> Option<T> {
142        let (section_str, key_str) = (self.section(), self.key());
143        let (section, key) = (as_c_str(&section_str), as_c_str(&key_str));
144        let result = self.object.get_ext_value(section, key, self.buf_size);
145        let value_obj = match result {
146            None => return None,
147            Some(value) => value,
148        };
149        let value = value_obj.to_string_lossy();
150        // let value = value_obj.as_bytes();
151        // let value: T = rmp_serde::decode::from_slice(value)
152        //     .expect("This value was not serialized by ExtState");
153        // let value: T = serde_pickle::from_slice(value, Default::default())
154        //     .expect("This value was not serialized by ExtState");
155        let value: T = serde_json::from_str(&*value)
156            .expect("This value was not serialized by ExtState");
157        Some(value)
158    }
159
160    /// Set the value to ext state.
161    pub fn set(&mut self, value: T) {
162        let (section_str, key_str) = (self.section(), self.key());
163        let (section, key) = (as_c_str(&section_str), as_c_str(&key_str));
164        // let mut value = serde_pickle::to_vec(&value, Default::default())
165        //     .expect("can not serialize value");
166        // let mut value = rmp_serde::encode::to_vec(&value)
167        //     .expect("can not serialize value");
168        // value.push(0);
169        // let value = CString::from_vec_with_nul(value)
170        //     .expect("can not serialize to string");
171        let value =
172            serde_json::to_string(&value).expect("Can not serialize value!");
173        let value = CString::new(value.as_str())
174            .expect("Can not convert ExtValue String to CString");
175        self.object.set_ext_value(section, key, value.into_raw())
176    }
177
178    /// Erase ext value, but keep the object.
179    pub fn delete(&mut self) {
180        let (section_str, key_str) = (self.section(), self.key());
181        let (section, key) = (as_c_str(&section_str), as_c_str(&key_str));
182        self.object.delete_ext_value(section, key)
183    }
184}
185
186pub trait HasExtState {
187    fn set_ext_value(&self, section: &CStr, key: &CStr, value: *mut i8);
188    fn get_ext_value(
189        &self,
190        section: &CStr,
191        key: &CStr,
192        buf_size: usize,
193    ) -> Option<CString>;
194    fn delete_ext_value(&self, section: &CStr, key: &CStr);
195}
196
197impl HasExtState for Reaper {
198    fn set_ext_value(&self, section: &CStr, key: &CStr, value: *mut i8) {
199        let low = Reaper::get().low();
200        unsafe { low.SetExtState(section.as_ptr(), key.as_ptr(), value, true) }
201    }
202
203    fn get_ext_value(
204        &self,
205        section: &CStr,
206        key: &CStr,
207        _buf_size: usize,
208    ) -> Option<CString> {
209        let low = self.low();
210        let has_state =
211            unsafe { low.HasExtState(section.as_ptr(), key.as_ptr()) };
212        match has_state {
213            false => None,
214            true => {
215                let value =
216                    unsafe { low.GetExtState(section.as_ptr(), key.as_ptr()) };
217                let c_str = unsafe { CStr::from_ptr(value) };
218                Some(CString::from(c_str))
219            }
220        }
221    }
222
223    fn delete_ext_value(&self, section: &CStr, key: &CStr) {
224        unsafe {
225            self.low()
226                .DeleteExtState(section.as_ptr(), key.as_ptr(), true)
227        }
228    }
229}
230
231impl HasExtState for Project {
232    fn set_ext_value(&self, section: &CStr, key: &CStr, value: *mut i8) {
233        let low = Reaper::get().low();
234        let _result = unsafe {
235            low.SetProjExtState(
236                self.context().to_raw(),
237                section.as_ptr(),
238                key.as_ptr(),
239                value,
240            )
241        };
242    }
243
244    fn get_ext_value(
245        &self,
246        section: &CStr,
247        key: &CStr,
248        buf_size: usize,
249    ) -> Option<CString> {
250        let low = Reaper::get().low();
251        let buf = make_c_string_buf(buf_size);
252        let ptr = buf.into_raw();
253        let status = unsafe {
254            low.GetProjExtState(
255                self.context().to_raw(),
256                section.as_ptr(),
257                key.as_ptr(),
258                ptr,
259                buf_size as i32,
260            )
261        };
262        if status <= 0 {
263            return None;
264        }
265        unsafe { Some(CString::from_raw(ptr)) }
266    }
267
268    fn delete_ext_value(&self, section: &CStr, key: &CStr) {
269        unsafe {
270            Reaper::get().low().SetProjExtState(
271                self.context().to_raw(),
272                section.as_ptr(),
273                key.as_ptr(),
274                null(),
275            );
276        }
277    }
278}
279
280fn get_track_ext_state<'a, T: ProbablyMutable>(
281    track: &Track<'a, T>,
282    section: &CStr,
283    key: &CStr,
284    buf_size: usize,
285) -> Option<CString> {
286    let mut category = section_key_to_one_category(section, key);
287    let buf = make_c_string_buf(buf_size).into_raw();
288    let result = unsafe {
289        Reaper::get().low().GetSetMediaTrackInfo_String(
290            track.get().as_ptr(),
291            as_c_str(category.with_null()).as_ptr(),
292            buf,
293            false,
294        )
295    };
296    match result {
297        false => None,
298        true => Some(unsafe { CString::from_raw(buf) }),
299    }
300}
301
302fn section_key_to_one_category(section: &CStr, key: &CStr) -> String {
303    let mut category = String::from("P_EXT:");
304    let section =
305        as_string(section.as_ptr()).expect("Can not convert to string");
306    let key = as_string(key.as_ptr()).expect("Can not convert to string");
307    category += &section;
308    category += &key;
309    category
310    // String::from("P_EXT:xyz")
311}
312
313impl<'a> HasExtState for Track<'a, Mutable> {
314    fn set_ext_value(&self, section: &CStr, key: &CStr, value: *mut i8) {
315        let mut category = section_key_to_one_category(section, key);
316        unsafe {
317            Reaper::get().low().GetSetMediaTrackInfo_String(
318                self.get().as_ptr(),
319                as_c_str(category.with_null()).as_ptr(),
320                value,
321                true,
322            )
323        };
324    }
325
326    fn get_ext_value(
327        &self,
328        section: &CStr,
329        key: &CStr,
330        buf_size: usize,
331    ) -> Option<CString> {
332        get_track_ext_state(self, section, key, buf_size)
333    }
334
335    fn delete_ext_value(&self, section: &CStr, key: &CStr) {
336        let mut category = section_key_to_one_category(section, key);
337        unsafe {
338            Reaper::get().low().GetSetMediaTrackInfo_String(
339                self.get().as_ptr(),
340                as_c_str(category.with_null()).as_ptr(),
341                CString::new("").unwrap().into_raw(),
342                true,
343            )
344        };
345    }
346}
347
348impl<'a> HasExtState for TrackSend<'a, Mutable> {
349    fn set_ext_value(&self, section: &CStr, key: &CStr, value: *mut i8) {
350        let mut category = section_key_to_one_category(section, key);
351        unsafe {
352            Reaper::get().low().GetSetTrackSendInfo_String(
353                self.parent_track().get().as_ptr(),
354                self.as_int(),
355                self.index() as i32,
356                as_c_str(category.with_null()).as_ptr(),
357                value,
358                true,
359            );
360        }
361    }
362
363    fn get_ext_value(
364        &self,
365        section: &CStr,
366        key: &CStr,
367        buf_size: usize,
368    ) -> Option<CString> {
369        let mut category = section_key_to_one_category(section, key);
370        let buf = make_c_string_buf(buf_size).into_raw();
371        let result = unsafe {
372            Reaper::get().low().GetSetTrackSendInfo_String(
373                self.parent_track().get().as_ptr(),
374                self.as_int(),
375                self.index() as i32,
376                as_c_str(category.with_null()).as_ptr(),
377                buf,
378                false,
379            )
380        };
381        match result {
382            false => None,
383            true => Some(unsafe { CString::from_raw(buf) }),
384        }
385    }
386
387    fn delete_ext_value(&self, section: &CStr, key: &CStr) {
388        let mut category = section_key_to_one_category(section, key);
389        unsafe {
390            Reaper::get().low().GetSetTrackSendInfo_String(
391                self.parent_track().get().as_ptr(),
392                self.as_int(),
393                self.index() as i32,
394                as_c_str(category.with_null()).as_ptr(),
395                CString::new("").unwrap().into_raw(),
396                true,
397            )
398        };
399    }
400}
401
402impl<'a, P: KnowsProject> HasExtState for Envelope<'a, P, Mutable> {
403    fn set_ext_value(&self, section: &CStr, key: &CStr, value: *mut i8) {
404        let mut category = section_key_to_one_category(section, key);
405        unsafe {
406            Reaper::get().low().GetSetEnvelopeInfo_String(
407                self.get().as_ptr(),
408                as_c_str(category.with_null()).as_ptr(),
409                value,
410                true,
411            );
412        }
413    }
414
415    fn get_ext_value(
416        &self,
417        section: &CStr,
418        key: &CStr,
419        buf_size: usize,
420    ) -> Option<CString> {
421        let mut category = section_key_to_one_category(section, key);
422        let buf = make_c_string_buf(buf_size).into_raw();
423        let result = unsafe {
424            Reaper::get().low().GetSetEnvelopeInfo_String(
425                self.get().as_ptr(),
426                as_c_str(category.with_null()).as_ptr(),
427                buf,
428                false,
429            )
430        };
431        match result {
432            false => None,
433            true => Some(unsafe { CString::from_raw(buf) }),
434        }
435    }
436
437    fn delete_ext_value(&self, section: &CStr, key: &CStr) {
438        let mut category = section_key_to_one_category(section, key);
439        unsafe {
440            Reaper::get().low().GetSetEnvelopeInfo_String(
441                self.get().as_ptr(),
442                as_c_str(category.with_null()).as_ptr(),
443                CString::new("").unwrap().into_raw(),
444                true,
445            )
446        };
447    }
448}
449
450impl<'a> HasExtState for Item<'a, Mutable> {
451    fn set_ext_value(&self, section: &CStr, key: &CStr, value: *mut i8) {
452        let mut category = section_key_to_one_category(section, key);
453        unsafe {
454            Reaper::get().low().GetSetMediaItemInfo_String(
455                self.get().as_ptr(),
456                as_c_str(category.with_null()).as_ptr(),
457                value,
458                true,
459            );
460        }
461    }
462
463    fn get_ext_value(
464        &self,
465        section: &CStr,
466        key: &CStr,
467        buf_size: usize,
468    ) -> Option<CString> {
469        let mut category = section_key_to_one_category(section, key);
470        let buf = make_c_string_buf(buf_size).into_raw();
471        let result = unsafe {
472            Reaper::get().low().GetSetMediaItemInfo_String(
473                self.get().as_ptr(),
474                as_c_str(category.with_null()).as_ptr(),
475                buf,
476                false,
477            )
478        };
479        match result {
480            false => None,
481            true => Some(unsafe { CString::from_raw(buf) }),
482        }
483    }
484
485    fn delete_ext_value(&self, section: &CStr, key: &CStr) {
486        let mut category = section_key_to_one_category(section, key);
487        unsafe {
488            Reaper::get().low().GetSetMediaItemInfo_String(
489                self.get().as_ptr(),
490                as_c_str(category.with_null()).as_ptr(),
491                CString::new("").unwrap().into_raw(),
492                true,
493            )
494        };
495    }
496}
497
498impl<'a> HasExtState for Take<'a, Mutable> {
499    fn set_ext_value(&self, section: &CStr, key: &CStr, value: *mut i8) {
500        let mut category = section_key_to_one_category(section, key);
501        unsafe {
502            Reaper::get().low().GetSetMediaItemTakeInfo_String(
503                self.get().as_ptr(),
504                as_c_str(category.with_null()).as_ptr(),
505                value,
506                true,
507            );
508        }
509    }
510
511    fn get_ext_value(
512        &self,
513        section: &CStr,
514        key: &CStr,
515        buf_size: usize,
516    ) -> Option<CString> {
517        let mut category = section_key_to_one_category(section, key);
518        let buf = make_c_string_buf(buf_size).into_raw();
519        let result = unsafe {
520            Reaper::get().low().GetSetMediaItemTakeInfo_String(
521                self.get().as_ptr(),
522                as_c_str(category.with_null()).as_ptr(),
523                buf,
524                false,
525            )
526        };
527        match result {
528            false => None,
529            true => Some(unsafe { CString::from_raw(buf) }),
530        }
531    }
532
533    fn delete_ext_value(&self, section: &CStr, key: &CStr) {
534        let mut category = section_key_to_one_category(section, key);
535        unsafe {
536            Reaper::get().low().GetSetMediaItemTakeInfo_String(
537                self.get().as_ptr(),
538                as_c_str(category.with_null()).as_ptr(),
539                CString::new("").unwrap().into_raw(),
540                true,
541            )
542        };
543    }
544}