From 9c386085e3799a4dd4670f5e4cea337ace0a7daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Laignel?= Date: Tue, 19 Nov 2024 10:11:32 +0100 Subject: [PATCH] all: GObject builders: generalise property_from_str Part-of: --- gstreamer-app/src/app_sink.rs | 52 ++++-- gstreamer-app/src/app_src.rs | 85 +++++++-- gstreamer/src/bin.rs | 70 +++++--- gstreamer/src/element_factory.rs | 167 ++++++----------- gstreamer/src/gobject.rs | 295 +++++++++++++++++++++++++++---- gstreamer/src/lib.rs | 2 +- gstreamer/src/pipeline.rs | 54 +++--- gstreamer/src/value.rs | 63 ++++++- 8 files changed, 551 insertions(+), 237 deletions(-) diff --git a/gstreamer-app/src/app_sink.rs b/gstreamer-app/src/app_sink.rs index 2b4e9927e..bf05494d2 100644 --- a/gstreamer-app/src/app_sink.rs +++ b/gstreamer-app/src/app_sink.rs @@ -512,9 +512,13 @@ impl AppSink { /// Creates a new builder-pattern struct instance to construct [`AppSink`] objects. /// /// This method returns an instance of [`AppSinkBuilder`](crate::builders::AppSinkBuilder) which can be used to create [`AppSink`] objects. - pub fn builder() -> AppSinkBuilder { + pub fn builder<'a>() -> AppSinkBuilder<'a> { assert_initialized_main_thread!(); - AppSinkBuilder::new() + AppSinkBuilder { + builder: gst::Object::builder(), + callbacks: None, + drop_out_of_segment: None, + } } #[doc(alias = "gst_app_sink_set_callbacks")] @@ -1173,26 +1177,23 @@ impl AppSink { /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html #[must_use = "The builder must be built to be used"] -pub struct AppSinkBuilder { - builder: glib::object::ObjectBuilder<'static, AppSink>, +pub struct AppSinkBuilder<'a> { + builder: gst::gobject::GObjectBuilder<'a, AppSink>, callbacks: Option, drop_out_of_segment: Option, } -impl AppSinkBuilder { - fn new() -> Self { - Self { - builder: glib::Object::builder(), - callbacks: None, - drop_out_of_segment: None, - } - } - +impl<'a> AppSinkBuilder<'a> { // rustdoc-stripper-ignore-next /// Build the [`AppSink`]. + /// + /// # Panics + /// + /// This panics if the [`AppSink`] doesn't have all the given properties or + /// property values of the wrong type are provided. #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"] pub fn build(self) -> AppSink { - let appsink = self.builder.build(); + let appsink = self.builder.build().unwrap(); if let Some(callbacks) = self.callbacks { appsink.set_callbacks(callbacks); @@ -1226,7 +1227,7 @@ impl AppSinkBuilder { } } - pub fn caps(self, caps: &gst::Caps) -> Self { + pub fn caps(self, caps: &'a gst::Caps) -> Self { Self { builder: self.builder.property("caps", caps), ..self @@ -1350,12 +1351,29 @@ impl AppSinkBuilder { } } - pub fn name(self, name: impl Into) -> Self { + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given value `value`. + /// + /// Overrides any default or previously defined value for `name`. + #[inline] + pub fn property(self, name: &'a str, value: impl Into + 'a) -> Self { Self { - builder: self.builder.property("name", name.into()), + builder: self.builder.property(name, value), ..self } } + + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given string value `value`. + #[inline] + pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self { + Self { + builder: self.builder.property_from_str(name, value), + ..self + } + } + + gst::impl_builder_gvalue_extra_setters!(property_and_name); } #[derive(Debug)] diff --git a/gstreamer-app/src/app_src.rs b/gstreamer-app/src/app_src.rs index 87ecf2218..8ba1c72ea 100644 --- a/gstreamer-app/src/app_src.rs +++ b/gstreamer-app/src/app_src.rs @@ -311,9 +311,13 @@ impl AppSrc { /// Creates a new builder-pattern struct instance to construct [`AppSrc`] objects. /// /// This method returns an instance of [`AppSrcBuilder`](crate::builders::AppSrcBuilder) which can be used to create [`AppSrc`] objects. - pub fn builder() -> AppSrcBuilder { + pub fn builder<'a>() -> AppSrcBuilder<'a> { assert_initialized_main_thread!(); - AppSrcBuilder::new() + AppSrcBuilder { + builder: gst::Object::builder(), + callbacks: None, + automatic_eos: None, + } } #[doc(alias = "gst_app_src_set_callbacks")] @@ -453,26 +457,23 @@ impl AppSrc { /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html #[must_use = "The builder must be built to be used"] -pub struct AppSrcBuilder { - builder: glib::object::ObjectBuilder<'static, AppSrc>, +pub struct AppSrcBuilder<'a> { + builder: gst::gobject::GObjectBuilder<'a, AppSrc>, callbacks: Option, automatic_eos: Option, } -impl AppSrcBuilder { - fn new() -> Self { - Self { - builder: glib::Object::builder(), - callbacks: None, - automatic_eos: None, - } - } - +impl<'a> AppSrcBuilder<'a> { // rustdoc-stripper-ignore-next /// Build the [`AppSrc`]. + /// + /// # Panics + /// + /// This panics if the [`AppSrc`] doesn't have all the given properties or + /// property values of the wrong type are provided. #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"] pub fn build(self) -> AppSrc { - let appsrc = self.builder.build(); + let appsrc = self.builder.build().unwrap(); if let Some(callbacks) = self.callbacks { appsrc.set_callbacks(callbacks); @@ -506,7 +507,7 @@ impl AppSrcBuilder { } } - pub fn caps(self, caps: &gst::Caps) -> Self { + pub fn caps(self, caps: &'a gst::Caps) -> Self { Self { builder: self.builder.property("caps", caps), ..self @@ -621,12 +622,29 @@ impl AppSrcBuilder { } } - pub fn name(self, name: impl Into) -> Self { + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given value `value`. + /// + /// Overrides any default or previously defined value for `name`. + #[inline] + pub fn property(self, name: &'a str, value: impl Into + 'a) -> Self { Self { - builder: self.builder.property("name", name.into()), + builder: self.builder.property(name, value), ..self } } + + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given string value `value`. + #[inline] + pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self { + Self { + builder: self.builder.property_from_str(name, value), + ..self + } + } + + gst::impl_builder_gvalue_extra_setters!(property_and_name); } #[derive(Debug)] @@ -792,4 +810,37 @@ mod tests { sample_quantity ); } + + #[test] + fn builder_caps_lt() { + gst::init().unwrap(); + + let caps = &gst::Caps::new_any(); + { + let stream_type = "random-access".to_owned(); + let appsrc = AppSrc::builder() + .property_from_str("stream-type", &stream_type) + .caps(caps) + .build(); + assert_eq!( + appsrc.property::("stream-type"), + crate::AppStreamType::RandomAccess + ); + assert!(appsrc.property::("caps").is_any()); + } + + let stream_type = &"random-access".to_owned(); + { + let caps = &gst::Caps::new_any(); + let appsrc = AppSrc::builder() + .property_from_str("stream-type", stream_type) + .caps(caps) + .build(); + assert_eq!( + appsrc.property::("stream-type"), + crate::AppStreamType::RandomAccess + ); + assert!(appsrc.property::("caps").is_any()); + } + } } diff --git a/gstreamer/src/bin.rs b/gstreamer/src/bin.rs index 4e2e91f94..3f0edf619 100644 --- a/gstreamer/src/bin.rs +++ b/gstreamer/src/bin.rs @@ -37,8 +37,11 @@ impl Bin { /// Creates a new builder-pattern struct instance to construct [`Bin`] objects. /// /// This method returns an instance of [`BinBuilder`] which can be used to create [`Bin`] objects. - pub fn builder() -> BinBuilder { - BinBuilder::new() + pub fn builder<'a>() -> BinBuilder<'a> { + assert_initialized_main_thread!(); + BinBuilder { + builder: crate::Object::builder(), + } } } @@ -221,22 +224,21 @@ impl Default for Bin { /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html #[must_use = "The builder must be built to be used"] -pub struct BinBuilder { - builder: glib::object::ObjectBuilder<'static, Bin>, +pub struct BinBuilder<'a> { + builder: crate::gobject::GObjectBuilder<'a, Bin>, } -impl BinBuilder { - fn new() -> Self { - Self { - builder: glib::Object::builder(), - } - } - +impl<'a> BinBuilder<'a> { // rustdoc-stripper-ignore-next /// Build the [`Bin`]. + /// + /// # Panics + /// + /// This panics if the [`Bin`] doesn't have all the given properties or + /// property values of the wrong type are provided. #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"] pub fn build(self) -> Bin { - self.builder.build() + self.builder.build().unwrap() } pub fn async_handling(self, async_handling: bool) -> Self { @@ -267,27 +269,27 @@ impl BinBuilder { } } - pub fn name(self, name: impl Into) -> Self { + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given value `value`. + /// + /// Overrides any default or previously defined value for `name`. + #[inline] + pub fn property(self, name: &'a str, value: impl Into + 'a) -> Self { Self { - builder: self.builder.property("name", name.into()), + builder: self.builder.property(name, value), } } - pub fn name_if(self, name: impl Into, predicate: bool) -> Self { - if predicate { - self.name(name) - } else { - self + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given string value `value`. + #[inline] + pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self { + Self { + builder: self.builder.property_from_str(name, value), } } - pub fn name_if_some(self, name: Option>) -> Self { - if let Some(name) = name { - self.name(name) - } else { - self - } - } + impl_builder_gvalue_extra_setters!(property_and_name); } unsafe extern "C" fn do_latency_trampoline< @@ -346,4 +348,20 @@ mod tests { vec![String::from("identity0"), String::from("identity1")] ); } + + #[test] + fn builder() { + crate::init().unwrap(); + + let msg_fwd = "message-forward"; + let bin = Bin::builder() + .name("test-bin") + .property("async-handling", true) + .property_from_str(msg_fwd, "True") + .build(); + + assert_eq!(bin.name(), "test-bin"); + assert!(bin.property::("async-handling")); + assert!(bin.property::("message-forward")); + } } diff --git a/gstreamer/src/element_factory.rs b/gstreamer/src/element_factory.rs index 34ed8362d..9fcfb278d 100644 --- a/gstreamer/src/element_factory.rs +++ b/gstreamer/src/element_factory.rs @@ -16,10 +16,9 @@ impl ElementFactory { #[track_caller] pub fn create(&self) -> ElementBuilder { assert_initialized_main_thread!(); - ElementBuilder { name_or_factory: NameOrFactory::Factory(self), - properties: smallvec::SmallVec::new(), + builder: crate::Object::builder_for_deferred_type(), } } @@ -28,10 +27,9 @@ impl ElementFactory { #[track_caller] pub fn make(factoryname: &str) -> ElementBuilder { assert_initialized_main_thread!(); - ElementBuilder { name_or_factory: NameOrFactory::Name(factoryname), - properties: smallvec::SmallVec::new(), + builder: crate::Object::builder_for_deferred_type(), } } @@ -190,7 +188,7 @@ impl ElementFactory { #[must_use = "The builder must be built to be used"] pub struct ElementBuilder<'a> { name_or_factory: NameOrFactory<'a>, - properties: smallvec::SmallVec<[(&'a str, ValueOrStr<'a>); 16]>, + builder: crate::gobject::GObjectBuilder<'a, Element>, } #[derive(Copy, Clone)] @@ -199,30 +197,7 @@ enum NameOrFactory<'a> { Factory(&'a ElementFactory), } -enum ValueOrStr<'a> { - Value(glib::Value), - Str(&'a str), -} - impl<'a> ElementBuilder<'a> { - // rustdoc-stripper-ignore-next - /// Sets the name property to the given `name`. - #[inline] - pub fn name(self, name: impl Into) -> Self { - self.property("name", name.into()) - } - - // rustdoc-stripper-ignore-next - /// Sets the name property to the given `name` if it is `Some`. - #[inline] - pub fn name_if_some(self, name: Option>) -> Self { - if let Some(name) = name { - self.name(name) - } else { - self - } - } - // rustdoc-stripper-ignore-next /// Sets property `name` to the given value `value`. /// @@ -230,51 +205,34 @@ impl<'a> ElementBuilder<'a> { #[inline] pub fn property(self, name: &'a str, value: impl Into + 'a) -> Self { Self { - name_or_factory: self.name_or_factory, - properties: { - let mut properties = self.properties; - properties.push((name, ValueOrStr::Value(value.into()))); - properties - }, + builder: self.builder.property(name, value), + ..self } } - impl_builder_gvalue_extra_setters!(property); - // rustdoc-stripper-ignore-next /// Sets property `name` to the given string value `value`. #[inline] pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self { Self { - name_or_factory: self.name_or_factory, - properties: { - let mut properties = self.properties; - properties.push((name, ValueOrStr::Str(value))); - properties - }, + builder: self.builder.property_from_str(name, value), + ..self } } - // rustdoc-stripper-ignore-next - /// Sets property `name` to the given string value `value` if it is `Some`. - #[inline] - pub fn property_from_str_if_some(self, name: &'a str, value: Option<&'a str>) -> Self { - if let Some(value) = value { - self.property_from_str(name, value) - } else { - self - } - } + impl_builder_gvalue_extra_setters!(property_and_name); // rustdoc-stripper-ignore-next - /// Builds the element with the provided properties. + /// Builds the [`Element`] with the provided properties. /// - /// This fails if there is no such element factory or the element factory can't be loaded. + /// This fails if there is no such [`ElementFactory`] or the [`ElementFactory`] can't be loaded. /// /// # Panics /// - /// This panics if the element is not instantiable, doesn't have all the given properties or + /// This panics if the [`Element`] is not instantiable, doesn't have all the given properties or /// property values of the wrong type are provided. + /// + /// [`Element`]: crate::Element #[track_caller] #[must_use = "Building the element without using it has no effect"] pub fn build(self) -> Result { @@ -328,68 +286,21 @@ impl<'a> ElementBuilder<'a> { )); } - let mut properties = smallvec::SmallVec::<[_; 16]>::with_capacity(self.properties.len()); - let klass = glib::Class::::from_type(element_type).unwrap(); - for (name, value) in self.properties { - match value { - ValueOrStr::Value(value) => { - properties.push((name, value)); + let element = self + .builder + .type_(element_type) + .build() + .map_err(|err| { + use crate::gobject::GObjectError::*; + match err { + PropertyNotFound { property, .. } => { + format!("property '{property}' of element factory '{}' not found", factory.name()) + }, + PropertyFromStr { property, value, .. } => { + format!("property '{property}' of element factory '{}' can't be set from string '{value}'", factory.name()) + }, } - ValueOrStr::Str(value) => { - use crate::value::GstValueExt; - - let pspec = match klass.find_property(name) { - Some(pspec) => pspec, - None => { - panic!( - "property '{}' of element factory '{}' not found", - name, - factory.name() - ); - } - }; - - let value = { - if pspec.value_type() == crate::Structure::static_type() && value == "NULL" - { - None::.to_value() - } else { - #[cfg(feature = "v1_20")] - { - glib::Value::deserialize_with_pspec(value, &pspec) - .unwrap_or_else(|_| { - panic!( - "property '{}' of element factory '{}' can't be set from string '{}'", - name, - factory.name(), - value, - ) - }) - } - #[cfg(not(feature = "v1_20"))] - { - glib::Value::deserialize(value, pspec.value_type()) - .unwrap_or_else(|_| { - panic!( - "property '{}' of element factory '{}' can't be set from string '{}'", - name, - factory.name(), - value, - ) - }) - } - } - }; - - properties.push((name, value)); - } - } - } - - let element = unsafe { - glib::Object::with_mut_values(element_type, &mut properties) - .unsafe_cast::() - }; + }).unwrap(); unsafe { use std::sync::atomic; @@ -430,3 +341,27 @@ impl<'a> ElementBuilder<'a> { Ok(element) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::prelude::*; + + #[test] + fn builder() { + crate::init().unwrap(); + + let fakesink = ElementFactory::make("fakesink") + .name("test-fakesink") + .property("can-activate-pull", true) + .property_from_str("state-error", "ready-to-paused") + .build() + .unwrap(); + + assert_eq!(fakesink.name(), "test-fakesink"); + assert!(fakesink.property::("can-activate-pull")); + let v = fakesink.property_value("state-error"); + let (_klass, e) = glib::EnumValue::from_value(&v).unwrap(); + assert_eq!(e.nick(), "ready-to-paused"); + } +} diff --git a/gstreamer/src/gobject.rs b/gstreamer/src/gobject.rs index c3d52fc13..4bddf751e 100644 --- a/gstreamer/src/gobject.rs +++ b/gstreamer/src/gobject.rs @@ -1,8 +1,98 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use glib::prelude::*; +use std::marker::PhantomData; -use crate::value::GstValueExt; +use glib::{object::IsClass, prelude::*, Type}; + +use crate::{value::GstValueExt, IdStr}; + +impl crate::Object { + // rustdoc-stripper-ignore-next + /// Builds a `GObjectBuilder` targeting type `O`. + #[inline] + pub fn builder<'a, O>() -> GObjectBuilder<'a, O> + where + O: IsA + IsClass, + { + assert_initialized_main_thread!(); + GObjectBuilder { + type_: Some(O::static_type()), + properties: smallvec::SmallVec::new(), + phantom: PhantomData, + } + } + + // rustdoc-stripper-ignore-next + /// Builds a `GObjectBuilder` targeting base class of type `O` and concrete `type_`. + #[inline] + pub fn builder_for<'a, O>(type_: Type) -> GObjectBuilder<'a, O> + where + O: IsA + IsClass, + { + assert_initialized_main_thread!(); + GObjectBuilder { + type_: Some(type_), + properties: smallvec::SmallVec::new(), + phantom: PhantomData, + } + } + + // rustdoc-stripper-ignore-next + /// Builds a `GObjectBuilder` targeting base class of type `O` + /// and a concrete `Type` that will be specified later. + /// + /// This is useful when the concrete type of the object is dynamically determined + /// when calling the `build()` method of a wrapping builder. + #[inline] + pub fn builder_for_deferred_type<'a, O>() -> GObjectBuilder<'a, O> + where + O: IsA + IsClass, + { + assert_initialized_main_thread!(); + GObjectBuilder { + type_: None, + properties: smallvec::SmallVec::new(), + phantom: PhantomData, + } + } +} + +#[derive(Debug, Eq, PartialEq, thiserror::Error)] +pub enum GObjectError { + #[error("property {property} for type {type_} not found")] + PropertyNotFound { type_: Type, property: IdStr }, + + #[error("property {property} for type {type_} can't be set from string {value}")] + PropertyFromStr { + type_: Type, + property: IdStr, + value: IdStr, + }, +} + +fn value_from_property_str( + pspec: glib::ParamSpec, + value: &str, +) -> Result { + skip_assert_initialized!(); // Already checked transitively by caller + + if pspec.value_type() == crate::Structure::static_type() && value == "NULL" { + Ok(None::.to_value()) + } else { + cfg_if::cfg_if! { + if #[cfg(feature = "v1_20")] { + let res = glib::Value::deserialize_with_pspec(value, &pspec); + } else { + let res = glib::Value::deserialize(value, pspec.value_type()); + } + } + res.map_err(|_| GObjectError::PropertyFromStr { + type_: pspec.owner_type(), + property: pspec.name().into(), + value: value.into(), + }) + } +} pub trait GObjectExtManualGst: IsA + 'static { #[doc(alias = "gst_util_set_object_arg")] @@ -12,53 +102,192 @@ pub trait GObjectExtManualGst: IsA + 'static { panic!("property '{}' of type '{}' not found", name, self.type_()); }); - let value = { - if pspec.value_type() == crate::Structure::static_type() && value == "NULL" { - None::.to_value() - } else { - #[cfg(feature = "v1_20")] - { - glib::Value::deserialize_with_pspec(value, &pspec).unwrap_or_else(|_| { - panic!( - "property '{}' of type '{}' can't be set from string '{}'", - name, - self.type_(), - value, - ) - }) - } - #[cfg(not(feature = "v1_20"))] - { - glib::Value::deserialize(value, pspec.value_type()).unwrap_or_else(|_| { - panic!( - "property '{}' of type '{}' can't be set from string '{}'", - name, - self.type_(), - value, - ) - }) - } - } - }; - - self.set_property(name, value) + self.set_property(name, value_from_property_str(pspec, value).unwrap()) } } impl> GObjectExtManualGst for O {} +// rustdoc-stripper-ignore-next +/// Builder for `GObject`s. +#[must_use = "The builder must be built to be used"] +pub struct GObjectBuilder<'a, O> { + type_: Option, + properties: smallvec::SmallVec<[(&'a str, ValueOrStr<'a>); 16]>, + phantom: PhantomData, +} + +enum ValueOrStr<'a> { + Value(glib::Value), + Str(&'a str), +} + +impl<'a, O: IsA + IsClass> GObjectBuilder<'a, O> { + // rustdoc-stripper-ignore-next + /// Sets the concrete `Type`. + /// + /// This should be used on an `GObjectBuilder` created with + /// [`GObjectBuilder::for_deferred_type`]. + #[inline] + pub fn type_(mut self, type_: Type) -> Self { + self.type_ = Some(type_); + self + } + + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given value `value`. + /// + /// Overrides any default or previously defined value for `name`. + #[inline] + pub fn property(self, name: &'a str, value: impl Into + 'a) -> Self { + Self { + properties: { + let mut properties = self.properties; + properties.push((name, ValueOrStr::Value(value.into()))); + properties + }, + ..self + } + } + + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given string value `value`. + #[inline] + pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self { + Self { + properties: { + let mut properties = self.properties; + properties.push((name, ValueOrStr::Str(value))); + properties + }, + ..self + } + } + + impl_builder_gvalue_extra_setters!(property_and_name); + + // rustdoc-stripper-ignore-next + /// Builds the [`Object`] with the provided properties. + /// + /// This fails if there is no such element factory or the element factory can't be loaded. + /// + /// # Panics + /// + /// This panics if: + /// + /// * The [`Object`] is not instantiable, doesn't have all the given properties or + /// property values of the wrong type are provided. + /// * The [`GObjectBuilder`] was created for a deferred concrete `Type` but + /// the `Type` was not set. + /// + /// [`Object`]: crate::Object + #[track_caller] + #[must_use = "Building the element without using it has no effect"] + pub fn build(self) -> Result { + let type_ = self.type_.expect("Deferred Type must be set"); + + let mut properties = smallvec::SmallVec::<[_; 16]>::with_capacity(self.properties.len()); + let klass = glib::Class::::from_type(type_).unwrap(); + for (name, value) in self.properties { + let pspec = + klass + .find_property(name) + .ok_or_else(|| GObjectError::PropertyNotFound { + type_, + property: name.into(), + })?; + + match value { + ValueOrStr::Value(value) => properties.push((name, value)), + ValueOrStr::Str(value) => { + properties.push((name, value_from_property_str(pspec, value)?)); + } + } + } + + let object = + unsafe { glib::Object::with_mut_values(type_, &mut properties).unsafe_cast::() }; + + Ok(object) + } +} + #[cfg(test)] mod tests { use super::*; + use crate::{prelude::*, Bin, Element, ElementFactory, Object}; #[test] fn test_set_property_from_str() { crate::init().unwrap(); - let fakesink = crate::ElementFactory::make("fakesink").build().unwrap(); + let fakesink = ElementFactory::make("fakesink").build().unwrap(); fakesink.set_property_from_str("state-error", "ready-to-paused"); let v = fakesink.property_value("state-error"); let (_klass, e) = glib::EnumValue::from_value(&v).unwrap(); assert_eq!(e.nick(), "ready-to-paused"); } + + #[test] + fn builder() { + crate::init().unwrap(); + + let msg_fwd = "message-forward"; + let bin = Object::builder::() + .name("test-bin") + .property("async-handling", true) + .property_from_str(msg_fwd, "True") + .build() + .unwrap(); + + assert_eq!(bin.name(), "test-bin"); + assert!(bin.property::("async-handling")); + assert!(bin.property::("message-forward")); + } + + #[test] + fn builder_err() { + crate::init().unwrap(); + + assert_eq!( + Object::builder::() + .property("not-a-prop", true) + .build(), + Err(GObjectError::PropertyNotFound { + type_: Bin::static_type(), + property: idstr!("not-a-prop") + }) + ); + + assert_eq!( + Object::builder::() + .property_from_str("async-handling", "not-a-bool") + .build(), + Err(GObjectError::PropertyFromStr { + type_: Bin::static_type(), + property: idstr!("async-handling"), + value: idstr!("not-a-bool") + }) + ); + } + + #[test] + fn builder_for() { + crate::init().unwrap(); + + let fakesink = ElementFactory::make("fakesink").build().unwrap(); + + let fakesink = Object::builder_for::(fakesink.type_()) + .name("test-fakesink") + .property("can-activate-pull", true) + .property_from_str("state-error", "ready-to-paused") + .build() + .unwrap(); + + assert_eq!(fakesink.name(), "test-fakesink"); + assert!(fakesink.property::("can-activate-pull")); + let v = fakesink.property_value("state-error"); + let (_klass, e) = glib::EnumValue::from_value(&v).unwrap(); + assert_eq!(e.nick(), "ready-to-paused"); + } } diff --git a/gstreamer/src/lib.rs b/gstreamer/src/lib.rs index 9d9a61856..b4d96c45c 100644 --- a/gstreamer/src/lib.rs +++ b/gstreamer/src/lib.rs @@ -200,7 +200,7 @@ mod device_provider; mod device_provider_factory; mod enums; mod ghost_pad; -mod gobject; +pub mod gobject; mod iterator; mod object; mod pad; diff --git a/gstreamer/src/pipeline.rs b/gstreamer/src/pipeline.rs index f68fc0a74..eb9ce12e9 100644 --- a/gstreamer/src/pipeline.rs +++ b/gstreamer/src/pipeline.rs @@ -35,8 +35,11 @@ impl Pipeline { /// Creates a new builder-pattern struct instance to construct [`Pipeline`] objects. /// /// This method returns an instance of [`PipelineBuilder`] which can be used to create [`Pipeline`] objects. - pub fn builder() -> PipelineBuilder { - PipelineBuilder::new() + pub fn builder<'a>() -> PipelineBuilder<'a> { + assert_initialized_main_thread!(); + PipelineBuilder { + builder: crate::Object::builder(), + } } } @@ -80,22 +83,21 @@ impl Default for Pipeline { /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html #[must_use = "The builder must be built to be used"] -pub struct PipelineBuilder { - builder: glib::object::ObjectBuilder<'static, Pipeline>, +pub struct PipelineBuilder<'a> { + builder: crate::gobject::GObjectBuilder<'a, Pipeline>, } -impl PipelineBuilder { - fn new() -> Self { - Self { - builder: glib::Object::builder(), - } - } - +impl<'a> PipelineBuilder<'a> { // rustdoc-stripper-ignore-next /// Build the [`Pipeline`]. + /// + /// # Panics + /// + /// This panics if the [`Pipeline`] doesn't have all the given properties or + /// property values of the wrong type are provided. #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"] pub fn build(self) -> Pipeline { - self.builder.build() + self.builder.build().unwrap() } pub fn auto_flush_bus(self, auto_flush_bus: bool) -> Self { @@ -188,25 +190,25 @@ impl PipelineBuilder { } } - pub fn name(self, name: impl Into) -> Self { + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given value `value`. + /// + /// Overrides any default or previously defined value for `name`. + #[inline] + pub fn property(self, name: &'a str, value: impl Into + 'a) -> Self { Self { - builder: self.builder.property("name", name.into()), + builder: self.builder.property(name, value), } } - pub fn name_if(self, name: impl Into, predicate: bool) -> Self { - if predicate { - self.name(name) - } else { - self + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given string value `value`. + #[inline] + pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self { + Self { + builder: self.builder.property_from_str(name, value), } } - pub fn name_if_some(self, name: Option>) -> Self { - if let Some(name) = name { - self.name(name) - } else { - self - } - } + impl_builder_gvalue_extra_setters!(property_and_name); } diff --git a/gstreamer/src/value.rs b/gstreamer/src/value.rs index c1310051c..b3366f688 100644 --- a/gstreamer/src/value.rs +++ b/gstreamer/src/value.rs @@ -1714,7 +1714,7 @@ macro_rules! impl_builder_gvalue_extra_setters ( } }; - (property) => { + (property_and_name) => { // rustdoc-stripper-ignore-next /// Sets property `name` to the given inner value if the `predicate` evaluates to `true`. /// @@ -1775,6 +1775,67 @@ macro_rules! impl_builder_gvalue_extra_setters ( self } } + + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given string value `value` if the `predicate` evaluates to `true`. + /// + /// This has no effect if the `predicate` evaluates to `false`, + /// i.e. default or previous value for `name` is kept. + #[inline] + pub fn property_from_str_if(self, name: &'a str, value: &'a str, predicate: bool) -> Self { + if predicate { + self.property_from_str(name, value) + } else { + self + } + } + + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given string value `value` if it is `Some`. + /// + /// This has no effect if the value is `None`, i.e. default or previous value for `name` is kept. + #[inline] + pub fn property_from_str_if_some(self, name: &'a str, value: Option<&'a str>) -> Self { + if let Some(value) = value { + self.property_from_str(name, value) + } else { + self + } + } + + // rustdoc-stripper-ignore-next + /// Sets the name property to the given `name`. + #[inline] + pub fn name(self, name: impl Into<$crate::glib::GString>) -> Self { + self.property("name", name.into()) + } + + // rustdoc-stripper-ignore-next + /// Sets the name property to the given `name` if the `predicate` evaluates to `true`. + /// + /// This has no effect if the `predicate` evaluates to `false`, + /// i.e. default or previous name is kept. + #[inline] + pub fn name_if(self, name: impl Into<$crate::glib::GString>, predicate: bool) -> Self { + if predicate { + self.name(name) + } else { + self + } + } + + // rustdoc-stripper-ignore-next + /// Sets the name property to the given `name` if it is `Some`. + /// + /// This has no effect if the value is `None`, i.e. default or previous name is kept. + #[inline] + pub fn name_if_some(self, name: Option>) -> Self { + if let Some(name) = name { + self.name(name) + } else { + self + } + } }; );