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#[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 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 pub fn get(&self) -> Option<T> {
142 let (section_str, key_str) = (self.section(), self.key());
143 let (section, key) = (as_c_str(§ion_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: T = serde_json::from_str(&*value)
156 .expect("This value was not serialized by ExtState");
157 Some(value)
158 }
159
160 pub fn set(&mut self, value: T) {
162 let (section_str, key_str) = (self.section(), self.key());
163 let (section, key) = (as_c_str(§ion_str), as_c_str(&key_str));
164 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 pub fn delete(&mut self) {
180 let (section_str, key_str) = (self.section(), self.key());
181 let (section, key) = (as_c_str(§ion_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 += §ion;
308 category += &key;
309 category
310 }
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}