gstreamer_validate/
action_type.rs

1use std::{ffi::c_int, ptr};
2
3use crate::ffi;
4use glib::translate::*;
5
6#[derive(Debug)]
7#[repr(transparent)]
8#[doc(alias = "GstValidateActionParameter")]
9pub struct ActionParameter(pub(crate) ffi::GstValidateActionParameter);
10impl Drop for ActionParameter {
11    fn drop(&mut self) {
12        unsafe {
13            if let Some(free_fn) = self.0.free {
14                (free_fn)(self as *const _ as glib::ffi::gpointer);
15            }
16        }
17    }
18}
19
20fn into_glib_content(mut t: Vec<ActionParameter>) -> *mut ffi::GstValidateActionParameter {
21    assert_initialized_main_thread!();
22    if t.is_empty() {
23        return ptr::null_mut();
24    }
25
26    unsafe {
27        let size = std::mem::size_of::<ffi::GstValidateActionParameter>() * (t.len() + 1);
28        let v_ptr = glib::ffi::g_malloc0(size) as *mut ffi::GstValidateActionParameter;
29
30        ptr::copy_nonoverlapping(
31            t.as_ptr() as *const ffi::GstValidateActionParameter,
32            v_ptr,
33            t.len(),
34        );
35
36        // C side is owning the memory now
37        t.set_len(0);
38
39        v_ptr
40    }
41}
42
43unsafe extern "C" fn action_parameter_free(param: glib::ffi::gpointer) {
44    let param = param as *mut ffi::GstValidateActionParameter;
45
46    glib::ffi::g_free((*param).name as *mut _);
47    glib::ffi::g_free((*param).description as *mut _);
48    glib::ffi::g_free((*param).def as *mut _);
49    glib::ffi::g_free((*param).possible_variables as *mut _);
50    glib::ffi::g_free((*param).types as *mut _);
51}
52
53pub struct ActionParameterBuilder<'a> {
54    name: &'a str,
55    description: &'a str,
56    possible_variables: Vec<String>,
57    mandatory: bool,
58    default_value: Option<&'a str>,
59    types: Vec<String>,
60}
61
62impl<'a> ActionParameterBuilder<'a> {
63    pub fn new(name: &'a str, description: &'a str) -> Self {
64        assert_initialized_main_thread!();
65
66        Self {
67            name,
68            description,
69            possible_variables: Default::default(),
70            mandatory: false,
71            default_value: None,
72            types: Default::default(),
73        }
74    }
75
76    // rustdoc-stripper-ignore-next
77    /// The name of the variables that can be used to compute the value of the
78    /// parameter. For example for the start value of a seek action, we will
79    /// accept to take 'duration' which will be replace by the total duration of
80    /// the stream on which the action is executed.
81    pub fn add_possible_variable(mut self, possible_variable: &str) -> Self {
82        self.possible_variables.push(possible_variable.to_owned());
83        self
84    }
85
86    pub fn add_possible_variable_if(self, possible_variable: &str, predicate: bool) -> Self {
87        if predicate {
88            self.add_possible_variable(possible_variable)
89        } else {
90            self
91        }
92    }
93
94    pub fn add_possible_variable_if_some(self, possible_variable: Option<&str>) -> Self {
95        if let Some(possible_variable) = possible_variable {
96            self.add_possible_variable(possible_variable)
97        } else {
98            self
99        }
100    }
101
102    pub fn mandatory(mut self) -> Self {
103        self.mandatory = true;
104        self
105    }
106
107    pub fn default_value(mut self, default_value: &'a str) -> Self {
108        self.default_value = Some(default_value);
109        self
110    }
111
112    pub fn default_value_if(self, default_value: &'a str, predicate: bool) -> Self {
113        if predicate {
114            self.default_value(default_value)
115        } else {
116            self
117        }
118    }
119
120    pub fn default_value_if_some(self, default_value: Option<&'a str>) -> Self {
121        if let Some(default_value) = default_value {
122            self.default_value(default_value)
123        } else {
124            self
125        }
126    }
127
128    // rustdoc-stripper-ignore-next
129    /// The types the parameter can take described as a string.
130    ///
131    /// NOTE: The types should end with `(GstClockTime)` if
132    /// its final type is a GstClockTime, this way it will be processed when
133    /// preparing the actions.
134    pub fn add_type(mut self, types: &str) -> Self {
135        self.types.push(types.to_owned());
136        self
137    }
138
139    pub fn add_type_if(self, types: &str, predicate: bool) -> Self {
140        if predicate {
141            self.add_type(types)
142        } else {
143            self
144        }
145    }
146
147    pub fn add_type_if_some(self, types: Option<&str>) -> Self {
148        if let Some(types) = types {
149            self.add_type(types)
150        } else {
151            self
152        }
153    }
154
155    pub fn build(self) -> ActionParameter {
156        let types = if self.types.is_empty() {
157            ptr::null()
158        } else {
159            self.types.join("\n").to_glib_full()
160        };
161        let possible_variables = if self.possible_variables.is_empty() {
162            ptr::null()
163        } else {
164            self.possible_variables.join("\n").to_glib_full()
165        };
166        ActionParameter(ffi::GstValidateActionParameter {
167            name: self.name.to_glib_full(),
168            description: self.description.to_glib_full(),
169            mandatory: self.mandatory.into_glib(),
170            def: self.default_value.to_glib_full(),
171            possible_variables,
172            types,
173            free: Some(action_parameter_free),
174            _gst_reserved: [ptr::null_mut(); 3],
175        })
176    }
177}
178
179type ActionFunction = dyn Fn(&crate::Scenario, &mut crate::ActionRef) -> Result<crate::ActionSuccess, crate::ActionError>
180    + Sync
181    + Send
182    + 'static;
183
184unsafe extern "C" fn destroy_notify(ptr: glib::ffi::gpointer) {
185    let _ = Box::from_raw(ptr as *mut Box<ActionFunction>);
186}
187
188pub struct ActionTypeBuilder<'a> {
189    type_name: &'a str,
190    implementer_namespace: Option<&'a str>,
191    description: Option<&'a str>,
192    parameters: Vec<ActionParameter>,
193    flags: crate::ActionTypeFlags,
194    function: Box<ActionFunction>,
195}
196
197impl<'a> ActionTypeBuilder<'a> {
198    pub fn new<
199        F: Fn(
200                &crate::Scenario,
201                &mut crate::ActionRef,
202            ) -> Result<crate::ActionSuccess, crate::ActionError>
203            + Send
204            + Sync
205            + 'static,
206    >(
207        type_name: &'a str,
208        func: F,
209    ) -> Self {
210        Self {
211            type_name,
212            implementer_namespace: None,
213            description: None,
214            parameters: Vec::new(),
215            flags: crate::ActionTypeFlags::empty(),
216            function: Box::new(func),
217        }
218    }
219
220    pub fn implementer_namespace(mut self, implementer_namespace: &'a str) -> Self {
221        self.implementer_namespace = Some(implementer_namespace);
222        self
223    }
224
225    pub fn implementer_namespace_if(
226        mut self,
227        implementer_namespace: &'a str,
228        predicate: bool,
229    ) -> Self {
230        if predicate {
231            self.implementer_namespace = Some(implementer_namespace);
232            self
233        } else {
234            self
235        }
236    }
237
238    pub fn implementer_namespace_if_some(self, implementer_namespace: Option<&'a str>) -> Self {
239        if let Some(implementer_namespace) = implementer_namespace {
240            self.implementer_namespace(implementer_namespace)
241        } else {
242            self
243        }
244    }
245
246    pub fn description(mut self, description: &'a str) -> Self {
247        self.description = Some(description);
248        self
249    }
250
251    pub fn description_if(mut self, description: &'a str, predicate: bool) -> Self {
252        if predicate {
253            self.description = Some(description);
254            self
255        } else {
256            self
257        }
258    }
259
260    pub fn description_if_some(self, description: Option<&'a str>) -> Self {
261        if let Some(description) = description {
262            self.description(description)
263        } else {
264            self
265        }
266    }
267
268    pub fn parameter(mut self, parameter: ActionParameter) -> Self {
269        self.parameters.push(parameter);
270        self
271    }
272
273    pub fn parameter_if(mut self, parameter: ActionParameter, predicate: bool) -> Self {
274        if predicate {
275            self.parameters.push(parameter);
276            self
277        } else {
278            self
279        }
280    }
281
282    pub fn parameter_if_some(self, parameter: Option<ActionParameter>) -> Self {
283        if let Some(parameter) = parameter {
284            self.parameter(parameter)
285        } else {
286            self
287        }
288    }
289
290    pub fn flags(mut self, flags: crate::ActionTypeFlags) -> Self {
291        self.flags |= flags;
292        self
293    }
294
295    pub fn flags_if(mut self, flags: crate::ActionTypeFlags, predicate: bool) -> Self {
296        if predicate {
297            self.flags |= flags;
298            self
299        } else {
300            self
301        }
302    }
303
304    pub fn flags_if_some(self, flags: Option<crate::ActionTypeFlags>) -> Self {
305        if let Some(flags) = flags {
306            self.flags(flags)
307        } else {
308            self
309        }
310    }
311
312    pub fn build(self) -> crate::ActionType {
313        static QUARK_ACTION_TYPE_FUNC: std::sync::OnceLock<glib::Quark> =
314            std::sync::OnceLock::new();
315
316        let quark_action_type_func =
317            QUARK_ACTION_TYPE_FUNC.get_or_init(|| glib::Quark::from_str("rs-action-type-function"));
318
319        unsafe extern "C" fn execute_func_trampoline(
320            scenario: *mut ffi::GstValidateScenario,
321            action: *mut ffi::GstValidateAction,
322        ) -> c_int {
323            let action_type = ffi::gst_validate_get_action_type((*action).type_);
324            let scenario = from_glib_borrow(scenario);
325            let action = crate::ActionRef::from_mut_ptr(action);
326
327            let func: &ActionFunction = &*(gst::ffi::gst_mini_object_get_qdata(
328                action_type as *mut gst::ffi::GstMiniObject,
329                QUARK_ACTION_TYPE_FUNC.get().unwrap().into_glib(),
330            ) as *const Box<ActionFunction>);
331
332            (*func)(&scenario, action).into_glib()
333        }
334
335        unsafe {
336            let params = into_glib_content(self.parameters);
337            let action_type = ffi::gst_validate_register_action_type(
338                self.type_name.to_glib_none().0,
339                self.implementer_namespace
340                    .unwrap_or("validaters")
341                    .to_glib_none()
342                    .0,
343                Some(execute_func_trampoline),
344                params,
345                self.description.to_glib_none().0,
346                self.flags.into_glib(),
347            );
348
349            // gst_validate_register_action_type() takes ownership of the content
350            // of the params array but not of the container itself so we need to
351            // free it manually.
352            glib::ffi::g_free(params as *mut _);
353
354            let f = self.function;
355
356            gst::ffi::gst_mini_object_set_qdata(
357                action_type as *mut gst::ffi::GstMiniObject,
358                quark_action_type_func.into_glib(),
359                Box::into_raw(Box::new(f)) as *mut _,
360                Some(destroy_notify),
361            );
362
363            from_glib_none(action_type)
364        }
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use std::{
371        io::Write,
372        sync::{Arc, Mutex},
373    };
374
375    #[test]
376    fn test_action_types() {
377        gst::init().unwrap();
378        crate::init();
379
380        let failling_action_type = crate::ActionTypeBuilder::new("fails", |_, action| {
381            action.structure_mut().set("called", true);
382
383            Err(crate::ActionError::Error)
384        })
385        .build();
386
387        let called = Arc::new(Mutex::new(false));
388        let succeeding_action_type = crate::ActionTypeBuilder::new(
389            "succeeds",
390            glib::clone!(
391                #[strong]
392                called,
393                move |_, _action| {
394                    *called.lock().unwrap() = true;
395
396                    Ok(crate::ActionSuccess::Ok)
397                }
398            ),
399        )
400        .parameter(
401            crate::ActionParameterBuilder::new("always", "Does the action always succeeds")
402                .add_type("boolean")
403                .default_value("true")
404                .build(),
405        )
406        .build();
407
408        // Write scenario to temporary file
409        let mut file = tempfile::NamedTempFile::new().unwrap();
410        file.write_all(b"succeeds").unwrap();
411
412        let runner = crate::Runner::new();
413        let pipeline = gst::Pipeline::new();
414        let scenario =
415            crate::Scenario::factory_create(&runner, &pipeline, file.path().to_str().unwrap())
416                .unwrap();
417
418        let action = crate::Action::new(
419            Some(&scenario),
420            &succeeding_action_type,
421            gst::Structure::builder("succeeds").build().as_ref(),
422            false,
423        );
424
425        assert!(!*called.lock().unwrap());
426        action.execute().expect("Failed to execute action");
427        assert!(*called.lock().unwrap());
428
429        let action = crate::Action::new(
430            Some(&scenario),
431            &failling_action_type,
432            gst::Structure::builder("fails").build().as_ref(),
433            false,
434        );
435
436        assert!(action.structure().get::<bool>("called").is_err());
437        action.execute().expect_err("Action should have failed");
438        assert_eq!(action.structure().get::<bool>("called"), Ok(true));
439
440        crate::ActionParameterBuilder::new("unused", "Verify unused param are properly cleaned")
441            .default_value("true")
442            .add_possible_variable("position")
443            .build();
444    }
445}