gstreamer-rs/gstreamer-validate/src/action_type.rs
François Laignel ea25c9262b Add field_if_some setter for builders
This commit adds `_if_some()` variants for builder field setters.
The variants aim at improving usability when setting optional fields.
E.g. currently, we need to write something like:

```rust
let opt_value = ...;
let mut sb = gst::Structure::builder("test")
    .field("mandatory_field", "mandatory");

if let Some(value) = opt_value
    sb = sb.field("optional_field", value);
}

let s = sb.build();
```

With `_if_some()`, this can be written like this:

```rust
let opt_value = ...;
let s = gst::Structure::builder("test")
    .field("mandatory_field", "mandatory")
    .field_if_some("optional_field", opt_value)
    .build();
```

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1424>
2024-04-17 17:15:39 +02:00

377 lines
11 KiB
Rust

use std::{ffi::c_int, ptr};
use glib::translate::*;
#[derive(Debug)]
#[repr(transparent)]
#[doc(alias = "GstValidateActionParameter")]
pub struct ActionParameter(pub(crate) ffi::GstValidateActionParameter);
impl Drop for ActionParameter {
fn drop(&mut self) {
unsafe {
if let Some(free_fn) = self.0.free {
(free_fn)(self as *const _ as glib::ffi::gpointer);
}
}
}
}
fn into_glib_content(mut t: Vec<ActionParameter>) -> *mut ffi::GstValidateActionParameter {
assert_initialized_main_thread!();
if t.is_empty() {
return ptr::null_mut();
}
unsafe {
let size = std::mem::size_of::<ffi::GstValidateActionParameter>() * (t.len() + 1);
let v_ptr = glib::ffi::g_malloc0(size) as *mut ffi::GstValidateActionParameter;
ptr::copy_nonoverlapping(
t.as_ptr() as *const ffi::GstValidateActionParameter,
v_ptr,
t.len(),
);
// C side is owning the memory now
t.set_len(0);
v_ptr
}
}
unsafe extern "C" fn action_parameter_free(param: glib::ffi::gpointer) {
let param = param as *mut ffi::GstValidateActionParameter;
glib::ffi::g_free((*param).name as *mut _);
glib::ffi::g_free((*param).description as *mut _);
glib::ffi::g_free((*param).def as *mut _);
glib::ffi::g_free((*param).possible_variables as *mut _);
glib::ffi::g_free((*param).types as *mut _);
}
pub struct ActionParameterBuilder<'a> {
name: &'a str,
description: &'a str,
possible_variables: Vec<String>,
mandatory: bool,
default_value: Option<&'a str>,
types: Vec<String>,
}
impl<'a> ActionParameterBuilder<'a> {
pub fn new(name: &'a str, description: &'a str) -> Self {
assert_initialized_main_thread!();
Self {
name,
description,
possible_variables: Default::default(),
mandatory: false,
default_value: None,
types: Default::default(),
}
}
// rustdoc-stripper-ignore-next
/// The name of the variables that can be used to compute the value of the
/// parameter. For example for the start value of a seek action, we will
/// accept to take 'duration' which will be replace by the total duration of
/// the stream on which the action is executed.
pub fn add_possible_variable(mut self, possible_variable: &str) -> Self {
self.possible_variables.push(possible_variable.to_owned());
self
}
pub fn add_possible_variable_if_some(self, possible_variable: Option<&str>) -> Self {
if let Some(possible_variable) = possible_variable {
self.add_possible_variable(possible_variable)
} else {
self
}
}
pub fn mandatory(mut self) -> Self {
self.mandatory = true;
self
}
pub fn default_value(mut self, default_value: &'a str) -> Self {
self.default_value = Some(default_value);
self
}
pub fn default_value_if_some(self, default_value: Option<&'a str>) -> Self {
if let Some(default_value) = default_value {
self.default_value(default_value)
} else {
self
}
}
// rustdoc-stripper-ignore-next
/// The types the parameter can take described as a string.
///
/// NOTE: The types should end with `(GstClockTime)` if
/// its final type is a GstClockTime, this way it will be processed when
/// preparing the actions.
pub fn add_type(mut self, types: &str) -> Self {
self.types.push(types.to_owned());
self
}
pub fn add_type_if_some(self, types: Option<&str>) -> Self {
if let Some(types) = types {
self.add_type(types)
} else {
self
}
}
pub fn build(self) -> ActionParameter {
let types = if self.types.is_empty() {
ptr::null()
} else {
self.types.join("\n").to_glib_full()
};
let possible_variables = if self.possible_variables.is_empty() {
ptr::null()
} else {
self.possible_variables.join("\n").to_glib_full()
};
ActionParameter(ffi::GstValidateActionParameter {
name: self.name.to_glib_full(),
description: self.description.to_glib_full(),
mandatory: self.mandatory.into_glib(),
def: self.default_value.to_glib_full(),
possible_variables,
types,
free: Some(action_parameter_free),
_gst_reserved: [ptr::null_mut(); 3],
})
}
}
type ActionFunction = dyn Fn(&crate::Scenario, &mut crate::ActionRef) -> Result<crate::ActionSuccess, crate::ActionError>
+ Sync
+ Send
+ 'static;
unsafe extern "C" fn destroy_notify(ptr: glib::ffi::gpointer) {
let _ = Box::from_raw(ptr as *mut Box<ActionFunction>);
}
pub struct ActionTypeBuilder<'a> {
type_name: &'a str,
implementer_namespace: Option<&'a str>,
description: Option<&'a str>,
parameters: Vec<ActionParameter>,
flags: crate::ActionTypeFlags,
function: Box<ActionFunction>,
}
impl<'a> ActionTypeBuilder<'a> {
pub fn new<
F: Fn(
&crate::Scenario,
&mut crate::ActionRef,
) -> Result<crate::ActionSuccess, crate::ActionError>
+ Send
+ Sync
+ 'static,
>(
type_name: &'a str,
func: F,
) -> Self {
Self {
type_name,
implementer_namespace: None,
description: None,
parameters: Vec::new(),
flags: crate::ActionTypeFlags::empty(),
function: Box::new(func),
}
}
pub fn implementer_namespace(mut self, implementer_namespace: &'a str) -> Self {
self.implementer_namespace = Some(implementer_namespace);
self
}
pub fn implementer_namespace_if_some(self, implementer_namespace: Option<&'a str>) -> Self {
if let Some(implementer_namespace) = implementer_namespace {
self.implementer_namespace(implementer_namespace)
} else {
self
}
}
pub fn description(mut self, description: &'a str) -> Self {
self.description = Some(description);
self
}
pub fn description_if_some(self, description: Option<&'a str>) -> Self {
if let Some(description) = description {
self.description(description)
} else {
self
}
}
pub fn parameter(mut self, parameter: ActionParameter) -> Self {
self.parameters.push(parameter);
self
}
pub fn parameter_if_some(self, parameter: Option<ActionParameter>) -> Self {
if let Some(parameter) = parameter {
self.parameter(parameter)
} else {
self
}
}
pub fn flags(mut self, flags: crate::ActionTypeFlags) -> Self {
self.flags |= flags;
self
}
pub fn flags_if_some(self, flags: Option<crate::ActionTypeFlags>) -> Self {
if let Some(flags) = flags {
self.flags(flags)
} else {
self
}
}
pub fn build(self) -> crate::ActionType {
static QUARK_ACTION_TYPE_FUNC: std::sync::OnceLock<glib::Quark> =
std::sync::OnceLock::new();
let quark_action_type_func =
QUARK_ACTION_TYPE_FUNC.get_or_init(|| glib::Quark::from_str("rs-action-type-function"));
unsafe extern "C" fn execute_func_trampoline(
scenario: *mut ffi::GstValidateScenario,
action: *mut ffi::GstValidateAction,
) -> c_int {
let action_type = ffi::gst_validate_get_action_type((*action).type_);
let scenario = from_glib_borrow(scenario);
let action = crate::ActionRef::from_mut_ptr(action);
let func: &ActionFunction = &*(gst::ffi::gst_mini_object_get_qdata(
action_type as *mut gst::ffi::GstMiniObject,
QUARK_ACTION_TYPE_FUNC.get().unwrap().into_glib(),
) as *const Box<ActionFunction>);
(*func)(&scenario, action).into_glib()
}
unsafe {
let params = into_glib_content(self.parameters);
let action_type = ffi::gst_validate_register_action_type(
self.type_name.to_glib_none().0,
self.implementer_namespace
.unwrap_or("validaters")
.to_glib_none()
.0,
Some(execute_func_trampoline),
params,
self.description.to_glib_none().0,
self.flags.into_glib(),
);
// gst_validate_register_action_type() takes ownership of the content
// of the params array but not of the container itself so we need to
// free it manually.
glib::ffi::g_free(params as *mut _);
let f = self.function;
gst::ffi::gst_mini_object_set_qdata(
action_type as *mut gst::ffi::GstMiniObject,
quark_action_type_func.into_glib(),
Box::into_raw(Box::new(f)) as *mut _,
Some(destroy_notify),
);
from_glib_none(action_type)
}
}
}
#[cfg(test)]
mod tests {
use std::{
io::Write,
sync::{Arc, Mutex},
};
#[test]
fn test_action_types() {
gst::init().unwrap();
crate::init();
let failling_action_type = crate::ActionTypeBuilder::new("fails", |_, action| {
action.structure_mut().set("called", true);
Err(crate::ActionError::Error)
})
.build();
let called = Arc::new(Mutex::new(false));
let succeeding_action_type = crate::ActionTypeBuilder::new(
"succeeds",
glib::clone!(@strong called => move |_, _action| {
*called.lock().unwrap() = true;
Ok(crate::ActionSuccess::Ok)
}),
)
.parameter(
crate::ActionParameterBuilder::new("always", "Does the action always succeeds")
.add_type("boolean")
.default_value("true")
.build(),
)
.build();
// Write scenario to temporary file
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(b"succeeds").unwrap();
let runner = crate::Runner::new();
let pipeline = gst::Pipeline::new();
let scenario =
crate::Scenario::factory_create(&runner, &pipeline, file.path().to_str().unwrap())
.unwrap();
let action = crate::Action::new(
Some(&scenario),
&succeeding_action_type,
gst::Structure::builder("succeeds").build().as_ref(),
false,
);
assert!(!*called.lock().unwrap());
action.execute().expect("Failed to execute action");
assert!(*called.lock().unwrap());
let action = crate::Action::new(
Some(&scenario),
&failling_action_type,
gst::Structure::builder("fails").build().as_ref(),
false,
);
assert!(action.structure().get::<bool>("called").is_err());
action.execute().expect_err("Action should have failed");
assert_eq!(action.structure().get::<bool>("called"), Ok(true));
crate::ActionParameterBuilder::new("unused", "Verify unused param are properly cleaned")
.default_value("true")
.add_possible_variable("position")
.build();
}
}