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 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 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 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 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 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}