Compare commits

...

6 commits

Author SHA1 Message Date
Nick Steel a611cf70f2 Merge branch 'log-trait-adapter' into 'main'
log: `Log` trait adapter around the GStreamer debug system

Closes #187

See merge request gstreamer/gstreamer-rs!1426
2024-04-26 10:25:25 +00:00
François Laignel 953e3747f2 Pad: allow building a Pad with an automatically generated name
For convenience, the `Pad` builder checks a name is provided when a wildcard-
named template is used. For `GhostPad`s, the builder tries to assign the name of
the target `Pad` making sure the provided `name` conforms to the `PadTemplate`.

This commit adds a function to optionally keep the `gst::Object` automatically
generated unique `Pad` name (such as `ghostpad4`) and reorganises name handling
so it is processed when `build` is invoked.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1428>
2024-04-26 09:57:28 +00:00
Nick Steel a77022ba0e log: check category above threshold 2024-04-22 01:05:38 +01:00
Nick Steel 8dd9a2d5ee log: DebugCategoryLogger is optional via 'log' feature 2024-04-21 22:23:36 +01:00
Sebastian Dröge 1438fa88db Apply 1 suggestion(s) to 1 file(s) 2024-04-21 09:22:32 +00:00
Nick Steel 08169aca96 log: Log trait adapter around the GStreamer debug system
Allows usage of normal `log` crate macros, and for other crates
using those macros to have their log messages go to the GStreamer
debug logs.

This implementation is based on the one found in Servo.

Fixes #187
2024-04-21 00:12:31 +01:00
5 changed files with 352 additions and 135 deletions

View file

@ -23,6 +23,7 @@ num-rational = { version = "0.4", default-features = false, features = [] }
futures-core = "0.3" futures-core = "0.3"
futures-channel = "0.3" futures-channel = "0.3"
futures-util = { version = "0.3", default-features = false } futures-util = { version = "0.3", default-features = false }
log = { version = "0.4", optional = true }
muldiv = "1" muldiv = "1"
opt-ops = { package = "option-operations", version = "0.5" } opt-ops = { package = "option-operations", version = "0.5" }
serde = { version = "1.0", optional = true, features = ["derive"] } serde = { version = "1.0", optional = true, features = ["derive"] }
@ -48,6 +49,7 @@ v1_20 = ["ffi/v1_20", "v1_18"]
v1_22 = ["ffi/v1_22", "v1_20"] v1_22 = ["ffi/v1_22", "v1_20"]
v1_24 = ["ffi/v1_24", "v1_22"] v1_24 = ["ffi/v1_24", "v1_22"]
serde = ["num-rational/serde", "dep:serde", "serde_bytes"] serde = ["num-rational/serde", "dep:serde", "serde_bytes"]
log = ["dep:log"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View file

@ -56,6 +56,8 @@ impl GhostPad {
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Creates a new [`GhostPad`] with an automatically generated name. /// Creates a new [`GhostPad`] with an automatically generated name.
/// ///
/// The [`Pad`] will be assigned the usual `gst::Object` generated unique name.
///
/// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`](crate::PadBuilder) /// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`](crate::PadBuilder)
/// and define options. /// and define options.
#[doc(alias = "gst_ghost_pad_new_no_target")] #[doc(alias = "gst_ghost_pad_new_no_target")]
@ -65,10 +67,7 @@ impl GhostPad {
} }
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Creates a [`PadBuilder`](crate::PadBuilder) for a [`PadBuilder`] with an automatically generated name. /// Creates a [`PadBuilder`](crate::PadBuilder) with the specified [`PadDirection`](crate::PadDirection).
///
/// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name)
/// to specify a different name.
#[doc(alias = "gst_ghost_pad_new_no_target")] #[doc(alias = "gst_ghost_pad_new_no_target")]
pub fn builder(direction: crate::PadDirection) -> PadBuilder<Self> { pub fn builder(direction: crate::PadDirection) -> PadBuilder<Self> {
skip_assert_initialized!(); skip_assert_initialized!();
@ -82,12 +81,15 @@ impl GhostPad {
/// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`,
/// the `GhostPad` will automatically be named after the `name_template`. /// the `GhostPad` will automatically be named after the `name_template`.
/// ///
/// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`](crate::PadBuilder)
/// and define options.
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
///
/// # Panics /// # Panics
/// ///
/// Panics if the `name_template` is a wildcard-name. /// Panics if the `name_template` is a wildcard-name.
///
/// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`](crate::PadBuilder)
/// and define options.
#[doc(alias = "gst_ghost_pad_new_no_target_from_static_template")] #[doc(alias = "gst_ghost_pad_new_no_target_from_static_template")]
pub fn from_static_template(templ: &StaticPadTemplate) -> Self { pub fn from_static_template(templ: &StaticPadTemplate) -> Self {
skip_assert_initialized!(); skip_assert_initialized!();
@ -103,6 +105,9 @@ impl GhostPad {
/// ///
/// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name) /// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name)
/// to specify a different name. /// to specify a different name.
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
#[doc(alias = "gst_ghost_pad_new_no_target_from_static_template")] #[doc(alias = "gst_ghost_pad_new_no_target_from_static_template")]
pub fn builder_from_static_template(templ: &StaticPadTemplate) -> PadBuilder<Self> { pub fn builder_from_static_template(templ: &StaticPadTemplate) -> PadBuilder<Self> {
skip_assert_initialized!(); skip_assert_initialized!();
@ -116,12 +121,15 @@ impl GhostPad {
/// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`,
/// the `GhostPad` will automatically be named after the `name_template`. /// the `GhostPad` will automatically be named after the `name_template`.
/// ///
/// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`](crate::PadBuilder)
/// and define options.
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
///
/// # Panics /// # Panics
/// ///
/// Panics if the `name_template` is a wildcard-name. /// Panics if the `name_template` is a wildcard-name.
///
/// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`](crate::PadBuilder)
/// and define options.
#[doc(alias = "gst_ghost_pad_new_no_target_from_template")] #[doc(alias = "gst_ghost_pad_new_no_target_from_template")]
pub fn from_template(templ: &crate::PadTemplate) -> Self { pub fn from_template(templ: &crate::PadTemplate) -> Self {
skip_assert_initialized!(); skip_assert_initialized!();
@ -137,6 +145,9 @@ impl GhostPad {
/// ///
/// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name) /// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name)
/// to specify a different name. /// to specify a different name.
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
#[doc(alias = "gst_ghost_pad_new_no_target_from_template")] #[doc(alias = "gst_ghost_pad_new_no_target_from_template")]
pub fn builder_from_template(templ: &crate::PadTemplate) -> PadBuilder<Self> { pub fn builder_from_template(templ: &crate::PadTemplate) -> PadBuilder<Self> {
skip_assert_initialized!(); skip_assert_initialized!();
@ -150,6 +161,9 @@ impl GhostPad {
/// ///
/// Use [`GhostPad::builder_with_target()`] to get a [`PadBuilder`](crate::PadBuilder) /// Use [`GhostPad::builder_with_target()`] to get a [`PadBuilder`](crate::PadBuilder)
/// and define options. /// and define options.
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
#[doc(alias = "gst_ghost_pad_new")] #[doc(alias = "gst_ghost_pad_new")]
pub fn with_target<P: IsA<Pad> + IsA<crate::Object>>( pub fn with_target<P: IsA<Pad> + IsA<crate::Object>>(
target: &P, target: &P,
@ -165,13 +179,15 @@ impl GhostPad {
/// ///
/// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name) /// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name)
/// to specify a different name. /// to specify a different name.
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
#[doc(alias = "gst_ghost_pad_new_no_target_from_template")] #[doc(alias = "gst_ghost_pad_new_no_target_from_template")]
pub fn builder_with_target<P: IsA<Pad> + IsA<crate::Object>>( pub fn builder_with_target<P: IsA<Pad> + IsA<crate::Object>>(
target: &P, target: &P,
) -> Result<PadBuilder<Self>, glib::BoolError> { ) -> Result<PadBuilder<Self>, glib::BoolError> {
skip_assert_initialized!(); skip_assert_initialized!();
let mut builder = Self::builder(target.direction()); let builder = Self::builder(target.direction());
builder.needs_specific_name = true;
builder.with_target(target) builder.with_target(target)
} }
@ -186,6 +202,9 @@ impl GhostPad {
/// If the `name_template` is a wildcard-name, then the `target` `name` is used, /// If the `name_template` is a wildcard-name, then the `target` `name` is used,
/// if it is compatible. Otherwise, a specific name must be provided using /// if it is compatible. Otherwise, a specific name must be provided using
/// [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name). /// [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name).
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
#[doc(alias = "gst_ghost_pad_new_from_template")] #[doc(alias = "gst_ghost_pad_new_from_template")]
pub fn from_template_with_target<P: IsA<Pad> + IsA<crate::Object>>( pub fn from_template_with_target<P: IsA<Pad> + IsA<crate::Object>>(
templ: &crate::PadTemplate, templ: &crate::PadTemplate,
@ -206,6 +225,9 @@ impl GhostPad {
/// If the `name_template` is a wildcard-name, then the `target` `name` is used, /// If the `name_template` is a wildcard-name, then the `target` `name` is used,
/// if it is compatible. Otherwise, a specific name must be provided using /// if it is compatible. Otherwise, a specific name must be provided using
/// [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name). /// [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name).
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
#[doc(alias = "gst_ghost_pad_new_from_template")] #[doc(alias = "gst_ghost_pad_new_from_template")]
pub fn builder_from_template_with_target<P: IsA<Pad> + IsA<crate::Object>>( pub fn builder_from_template_with_target<P: IsA<Pad> + IsA<crate::Object>>(
templ: &crate::PadTemplate, templ: &crate::PadTemplate,
@ -689,6 +711,9 @@ impl<T: IsA<GhostPad> + IsA<Pad>> PadBuilder<T> {
/// If the `name_template` is a wildcard-name, then the `target` `name` is used, /// If the `name_template` is a wildcard-name, then the `target` `name` is used,
/// if it is compatible. Otherwise, a specific name must be provided using /// if it is compatible. Otherwise, a specific name must be provided using
/// [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name). /// [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name).
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
pub fn with_target<P: IsA<Pad> + IsA<crate::Object>>( pub fn with_target<P: IsA<Pad> + IsA<crate::Object>>(
mut self, mut self,
target: &P, target: &P,
@ -696,92 +721,8 @@ impl<T: IsA<GhostPad> + IsA<Pad>> PadBuilder<T> {
assert_eq!(self.pad.direction(), target.direction()); assert_eq!(self.pad.direction(), target.direction());
self.pad.set_target(Some(target))?; self.pad.set_target(Some(target))?;
self.name =
if self.needs_specific_name { crate::pad::PadBuilderName::CandidateForWildcardTemplate(target.name().to_string());
let mut can_assign_target_name = true;
if let Some(pad_template) = self.pad.pad_template() {
if pad_template.presence() == crate::PadPresence::Request {
// Check if the target name is compatible with the name template.
use crate::CAT_RUST;
let target_name = target.name();
let mut target_parts = target_name.split('_');
for template_part in pad_template.name_template().split('_') {
let Some(target_part) = target_parts.next() else {
crate::debug!(
CAT_RUST,
"Not using target Pad name '{target_name}': not enough parts compared to template '{}'",
pad_template.name_template(),
);
can_assign_target_name = false;
break;
};
if let Some(conv_spec_start) = template_part.find('%') {
if conv_spec_start > 0
&& !target_part.starts_with(&template_part[..conv_spec_start])
{
crate::debug!(
CAT_RUST,
"Not using target Pad name '{target_name}': mismatch template '{}' prefix",
pad_template.name_template(),
);
can_assign_target_name = false;
break;
}
let conv_spec_pos = conv_spec_start + 1;
match template_part.get(conv_spec_pos..=conv_spec_pos) {
Some("s") => {
// *There can be only one* %s
break;
}
Some("u") => {
if target_part
.get(conv_spec_start..)
.map_or(true, |s| s.parse::<u32>().is_err())
{
crate::debug!(
CAT_RUST,
"Not using target Pad name '{target_name}': can't parse '%u' from '{target_part}' (template '{}')",
pad_template.name_template(),
);
can_assign_target_name = false;
break;
}
}
Some("d") => {
if target_part
.get(conv_spec_start..)
.map_or(true, |s| s.parse::<i32>().is_err())
{
crate::debug!(
CAT_RUST,
"Not using target Pad name '{target_name}': can't parse '%i' from '{target_part}' (template '{}')",
pad_template.name_template(),
);
can_assign_target_name = false;
break;
}
}
other => unreachable!("Unexpected conversion specifier {other:?}"),
}
} else if target_part != template_part {
can_assign_target_name = false;
break;
}
}
}
}
if can_assign_target_name {
self.pad.set_property("name", target.name());
self.needs_specific_name = false;
}
}
Ok(self) Ok(self)
} }
@ -911,6 +852,13 @@ mod tests {
.name("ghost_test") .name("ghost_test")
.build(); .build();
assert_eq!(ghost_pad.name(), "ghost_test"); assert_eq!(ghost_pad.name(), "ghost_test");
let target = crate::Pad::from_template(&templ);
let ghost_pad = GhostPad::builder_with_target(&target)
.unwrap()
.generated_name()
.build();
assert!(ghost_pad.name().starts_with("ghostpad"));
} }
#[test] #[test]
@ -952,6 +900,13 @@ mod tests {
.build(); .build();
assert_eq!(ghost_pad.name(), "my-sink"); assert_eq!(ghost_pad.name(), "my-sink");
let target = crate::Pad::from_template(&sink_templ);
let ghost_pad = GhostPad::builder_from_template_with_target(&ghost_templ, &target)
.unwrap()
.generated_name()
.build();
assert!(ghost_pad.name().starts_with("ghostpad"));
// # Request template %u // # Request template %u
let wildcard_u_templ = crate::PadTemplate::new( let wildcard_u_templ = crate::PadTemplate::new(
"sink_%u", "sink_%u",
@ -983,6 +938,13 @@ mod tests {
.build(); .build();
assert_eq!(ghost_pad.name(), "sink_0"); assert_eq!(ghost_pad.name(), "sink_0");
let target = crate::Pad::from_template(&sink_0_templ);
let ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_u_templ, &target)
.unwrap()
.generated_name()
.build();
assert!(ghost_pad.name().starts_with("ghostpad"));
// # Request template %d_%u // # Request template %d_%u
let wildcard_u_templ = crate::PadTemplate::new( let wildcard_u_templ = crate::PadTemplate::new(
"sink_%d_%u", "sink_%d_%u",

View file

@ -49,6 +49,8 @@ mod serde_macros;
#[macro_use] #[macro_use]
pub mod log; pub mod log;
#[cfg(feature = "log")]
pub use crate::log::DebugCategoryLogger;
pub use crate::log::{ pub use crate::log::{
DebugCategory, DebugLogFunction, DebugMessage, LoggedObject, CAT_BUFFER, CAT_BUFFER_LIST, DebugCategory, DebugLogFunction, DebugMessage, LoggedObject, CAT_BUFFER, CAT_BUFFER_LIST,
CAT_BUS, CAT_CALL_TRACE, CAT_CAPS, CAT_CLOCK, CAT_CONTEXT, CAT_DEFAULT, CAT_ELEMENT_PADS, CAT_BUS, CAT_CALL_TRACE, CAT_CAPS, CAT_CLOCK, CAT_CONTEXT, CAT_DEFAULT, CAT_ELEMENT_PADS,

View file

@ -4,6 +4,8 @@ use std::{borrow::Cow, ffi::CStr, fmt, ptr};
use glib::{ffi::gpointer, prelude::*, translate::*}; use glib::{ffi::gpointer, prelude::*, translate::*};
use libc::c_char; use libc::c_char;
#[cfg(feature = "log")]
use log;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::DebugLevel; use crate::DebugLevel;
@ -1064,6 +1066,54 @@ macro_rules! log_with_level(
}}; }};
); );
#[cfg(feature = "log")]
#[derive(Debug)]
pub struct DebugCategoryLogger(DebugCategory);
#[cfg(feature = "log")]
impl DebugCategoryLogger {
pub fn new(cat: DebugCategory) -> Self {
Self(cat)
}
fn to_level(level: log::Level) -> crate::DebugLevel {
match level {
log::Level::Error => DebugLevel::Error,
log::Level::Warn => DebugLevel::Warning,
log::Level::Info => DebugLevel::Info,
log::Level::Debug => DebugLevel::Debug,
log::Level::Trace => DebugLevel::Trace,
}
}
}
#[cfg(feature = "log")]
impl log::Log for DebugCategoryLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
let lvl = DebugCategoryLogger::to_level(metadata.level());
is_active() && self.0.above_threshold(lvl)
}
fn log(&self, record: &log::Record) {
if !self.enabled(record.metadata()) {
return;
}
let lvl = DebugCategoryLogger::to_level(record.level());
let file = record.file().unwrap_or("");
let file = glib::GStr::from_str_until_nul(file).unwrap_or_default();
self.0.log(
None::<&glib::Object>,
lvl,
file,
record.module_path().unwrap_or(""),
record.line().unwrap_or(0),
record.args().clone(),
);
}
fn flush(&self) {}
}
unsafe extern "C" fn log_handler<T>( unsafe extern "C" fn log_handler<T>(
category: *mut ffi::GstDebugCategory, category: *mut ffi::GstDebugCategory,
level: ffi::GstDebugLevel, level: ffi::GstDebugLevel,
@ -1308,6 +1358,65 @@ mod tests {
memdump!(cat, obj: obj, "meh"); memdump!(cat, obj: obj, "meh");
} }
#[cfg(feature = "log")]
static LOGGER: Lazy<DebugCategoryLogger> = Lazy::new(|| {
DebugCategoryLogger::new(DebugCategory::new(
"Log_trait",
crate::DebugColorFlags::empty(),
Some("Using the Log trait"),
))
});
#[test]
#[cfg(feature = "log")]
fn log_trait() {
crate::init().unwrap();
log::set_logger(&(*LOGGER)).expect("Failed to set logger");
log::set_max_level(log::LevelFilter::Trace);
log::error!("meh");
log::warn!("fish");
let (sender, receiver) = mpsc::channel();
let sender = Arc::new(Mutex::new(sender));
let handler = move |category: DebugCategory,
level: DebugLevel,
_file: &glib::GStr,
_function: &glib::GStr,
_line: u32,
_object: Option<&LoggedObject>,
message: &DebugMessage| {
let cat = DebugCategory::get("Log_trait").unwrap();
if category != cat {
// This test can run in parallel with other tests, including new_and_log above.
// We cannot be certain we only see our own messages.
return;
}
assert_eq!(level, DebugLevel::Error);
assert_eq!(message.get().unwrap().as_ref(), "meh");
let _ = sender.lock().unwrap().send(());
};
remove_default_log_function();
add_log_function(handler);
let cat = LOGGER.0;
cat.set_threshold(crate::DebugLevel::Warning);
log::error!("meh");
receiver.recv().unwrap();
cat.set_threshold(crate::DebugLevel::Error);
log::error!("meh");
receiver.recv().unwrap();
cat.set_threshold(crate::DebugLevel::None);
log::error!("fish");
log::warn!("meh");
}
#[test] #[test]
fn log_handler() { fn log_handler() {
crate::init().unwrap(); crate::init().unwrap();

View file

@ -1428,7 +1428,7 @@ impl Pad {
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Creates a new [`Pad`] with the specified [`PadDirection`](crate::PadDirection). /// Creates a new [`Pad`] with the specified [`PadDirection`](crate::PadDirection).
/// ///
/// An automatically generated name will be assigned. /// The [`Pad`] will be assigned the usual `gst::Object` generated unique name.
/// ///
/// Use [`Pad::builder()`] to get a [`PadBuilder`] and define options. /// Use [`Pad::builder()`] to get a [`PadBuilder`] and define options.
#[doc(alias = "gst_pad_new")] #[doc(alias = "gst_pad_new")]
@ -1439,8 +1439,6 @@ impl Pad {
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Creates a [`PadBuilder`] with the specified [`PadDirection`](crate::PadDirection). /// Creates a [`PadBuilder`] with the specified [`PadDirection`](crate::PadDirection).
///
/// An automatically generated name will be assigned.
#[doc(alias = "gst_pad_new")] #[doc(alias = "gst_pad_new")]
pub fn builder(direction: crate::PadDirection) -> PadBuilder<Self> { pub fn builder(direction: crate::PadDirection) -> PadBuilder<Self> {
skip_assert_initialized!(); skip_assert_initialized!();
@ -1454,11 +1452,14 @@ impl Pad {
/// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`,
/// the `Pad` will automatically be named after the `name_template`. /// the `Pad` will automatically be named after the `name_template`.
/// ///
/// Use [`Pad::builder_from_static_template()`] to get a [`PadBuilder`] and define options.
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
///
/// # Panics /// # Panics
/// ///
/// Panics if the `name_template` is a wildcard-name. /// Panics if the `name_template` is a wildcard-name.
///
/// Use [`Pad::builder_from_static_template()`] to get a [`PadBuilder`] and define options.
#[doc(alias = "gst_pad_new_from_static_template")] #[doc(alias = "gst_pad_new_from_static_template")]
pub fn from_static_template(templ: &StaticPadTemplate) -> Self { pub fn from_static_template(templ: &StaticPadTemplate) -> Self {
skip_assert_initialized!(); skip_assert_initialized!();
@ -1472,7 +1473,8 @@ impl Pad {
/// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`,
/// the `Pad` will automatically be named after the `name_template`. /// the `Pad` will automatically be named after the `name_template`.
/// ///
/// Use [`PadBuilder::name`] or [`PadBuilder::maybe_name`] to specify a different name. /// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
#[doc(alias = "gst_pad_new_from_static_template")] #[doc(alias = "gst_pad_new_from_static_template")]
pub fn builder_from_static_template(templ: &StaticPadTemplate) -> PadBuilder<Self> { pub fn builder_from_static_template(templ: &StaticPadTemplate) -> PadBuilder<Self> {
skip_assert_initialized!(); skip_assert_initialized!();
@ -1486,11 +1488,11 @@ impl Pad {
/// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`,
/// the `Pad` will automatically be named after the `name_template`. /// the `Pad` will automatically be named after the `name_template`.
/// ///
/// Use [`Pad::builder_from_template()`] to get a [`PadBuilder`] and define options.
///
/// # Panics /// # Panics
/// ///
/// Panics if the `name_template` is a wildcard-name. /// Panics if the `name_template` is a wildcard-name.
///
/// Use [`Pad::builder_from_template()`] to get a [`PadBuilder`] and define options.
#[doc(alias = "gst_pad_new_from_template")] #[doc(alias = "gst_pad_new_from_template")]
pub fn from_template(templ: &crate::PadTemplate) -> Self { pub fn from_template(templ: &crate::PadTemplate) -> Self {
skip_assert_initialized!(); skip_assert_initialized!();
@ -1504,7 +1506,8 @@ impl Pad {
/// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`,
/// the `Pad` will automatically be named after the `name_template`. /// the `Pad` will automatically be named after the `name_template`.
/// ///
/// Use [`PadBuilder::name`] or [`PadBuilder::maybe_name`] to specify a different name. /// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
#[doc(alias = "gst_pad_new_from_template")] #[doc(alias = "gst_pad_new_from_template")]
pub fn builder_from_template(templ: &crate::PadTemplate) -> PadBuilder<Self> { pub fn builder_from_template(templ: &crate::PadTemplate) -> PadBuilder<Self> {
skip_assert_initialized!(); skip_assert_initialized!();
@ -1558,18 +1561,22 @@ impl Pad {
} }
} }
pub(crate) enum PadBuilderName {
Undefined,
KeepGenerated,
UserDefined(String),
CandidateForWildcardTemplate(String),
}
#[must_use = "The builder must be built to be used"] #[must_use = "The builder must be built to be used"]
pub struct PadBuilder<T> { pub struct PadBuilder<T> {
pub(crate) pad: T, pub(crate) pad: T,
pub(crate) needs_specific_name: bool, pub(crate) name: PadBuilderName,
} }
impl<T: IsA<Pad> + IsA<glib::Object> + glib::object::IsClass> PadBuilder<T> { impl<T: IsA<Pad> + IsA<glib::Object> + glib::object::IsClass> PadBuilder<T> {
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Creates a `PadBuilder` with the specified [`PadDirection`](crate::PadDirection). /// Creates a `PadBuilder` with the specified [`PadDirection`](crate::PadDirection).
///
/// An automatically generated name will be assigned. Use [`PadBuilder::name`] or
/// [`PadBuilder::maybe_name`] to define a specific name.
pub fn new(direction: crate::PadDirection) -> Self { pub fn new(direction: crate::PadDirection) -> Self {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
@ -1588,7 +1595,7 @@ impl<T: IsA<Pad> + IsA<glib::Object> + glib::object::IsClass> PadBuilder<T> {
PadBuilder { PadBuilder {
pad, pad,
needs_specific_name: false, name: PadBuilderName::Undefined,
} }
} }
@ -1599,7 +1606,8 @@ impl<T: IsA<Pad> + IsA<glib::Object> + glib::object::IsClass> PadBuilder<T> {
/// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`,
/// the `Pad` will automatically be named after the `name_template`. /// the `Pad` will automatically be named after the `name_template`.
/// ///
/// Use [`PadBuilder::name`] or [`PadBuilder::maybe_name`] to specify a different name. /// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
pub fn from_static_template(templ: &StaticPadTemplate) -> Self { pub fn from_static_template(templ: &StaticPadTemplate) -> Self {
skip_assert_initialized!(); skip_assert_initialized!();
@ -1614,7 +1622,8 @@ impl<T: IsA<Pad> + IsA<glib::Object> + glib::object::IsClass> PadBuilder<T> {
/// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`,
/// the `Pad` will automatically be named after the `name_template`. /// the `Pad` will automatically be named after the `name_template`.
/// ///
/// Use [`PadBuilder::name`] or [`PadBuilder::maybe_name`] to specify a different name. /// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
pub fn from_template(templ: &crate::PadTemplate) -> Self { pub fn from_template(templ: &crate::PadTemplate) -> Self {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
@ -1654,25 +1663,23 @@ impl<T: IsA<Pad> + IsA<glib::Object> + glib::object::IsClass> PadBuilder<T> {
} }
} }
let needs_specific_name = if templ.name().find('%').is_some() {
// Pad needs a specific name
true
} else {
pad.set_property("name", templ.name());
false
};
PadBuilder { PadBuilder {
pad, pad,
needs_specific_name, name: PadBuilderName::Undefined,
} }
} }
// rustdoc-stripper-ignore-next
/// Uses the `gst::Object` generated unique name.
pub fn generated_name(mut self) -> Self {
self.name = PadBuilderName::KeepGenerated;
self
}
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Sets the name of the Pad. /// Sets the name of the Pad.
pub fn name(mut self, name: impl glib::IntoGStr) -> Self { pub fn name(mut self, name: impl Into<String>) -> Self {
name.run_with_gstr(|name| self.pad.set_property("name", name)); self.name = PadBuilderName::UserDefined(name.into());
self.needs_specific_name = false;
self self
} }
@ -1682,7 +1689,7 @@ impl<T: IsA<Pad> + IsA<glib::Object> + glib::object::IsClass> PadBuilder<T> {
/// ///
/// This method is convenient when the `name` is provided as an `Option`. /// This method is convenient when the `name` is provided as an `Option`.
/// If the `name` is `None`, this has no effect. /// If the `name` is `None`, this has no effect.
pub fn maybe_name<N: glib::IntoGStr>(self, name: Option<N>) -> Self { pub fn maybe_name<N: Into<String>>(self, name: Option<N>) -> Self {
if let Some(name) = name { if let Some(name) = name {
self.name(name) self.name(name)
} else { } else {
@ -1695,7 +1702,7 @@ impl<T: IsA<Pad> + IsA<glib::Object> + glib::object::IsClass> PadBuilder<T> {
/// ///
/// This method is convenient when the `name` is provided as an `Option`. /// This method is convenient when the `name` is provided as an `Option`.
/// If the `name` is `None`, this has no effect. /// If the `name` is `None`, this has no effect.
pub fn name_if_some<N: glib::IntoGStr>(self, name: Option<N>) -> Self { pub fn name_if_some<N: Into<String>>(self, name: Option<N>) -> Self {
if let Some(name) = name { if let Some(name) = name {
self.name(name) self.name(name)
} else { } else {
@ -2047,18 +2054,132 @@ impl<T: IsA<Pad> + IsA<glib::Object> + glib::object::IsClass> PadBuilder<T> {
/// and no specific `name` was provided using [`PadBuilder::name`] /// and no specific `name` was provided using [`PadBuilder::name`]
/// or [`PadBuilder::maybe_name`], or for [`GhostPad`s](crate::GhostPad), /// or [`PadBuilder::maybe_name`], or for [`GhostPad`s](crate::GhostPad),
/// by defining a `target`. /// by defining a `target`.
///
/// Use [`generated_name()`](crate::PadBuilder::generated_name`) to keep the `gst::Object`
/// automatically generated unique name.
#[must_use = "Building the pad without using it has no effect"] #[must_use = "Building the pad without using it has no effect"]
#[track_caller] #[track_caller]
pub fn build(self) -> T { pub fn build(self) -> T {
if self.needs_specific_name { let Self { pad, name } = self;
panic!(concat!(
"Attempt to build a Pad from a wildcard-name template", let templ = pad.pad_template();
" or with a target Pad with an incompatible name.",
" Make sure to define a specific name using PadBuilder.", use PadBuilderName::*;
)); match (name, templ) {
(KeepGenerated, _) => (),
(Undefined, None) => (),
(Undefined, Some(templ)) => {
if templ.name().find('%').is_some() {
panic!(concat!(
"Attempt to build a Pad from a wildcard-name template",
" or with a target Pad with an incompatible name.",
" Make sure to define a specific name using PadBuilder",
" or opt-in to keep the automatically generated name.",
));
} else {
pad.set_property("name", templ.name());
}
}
(UserDefined(name), _) | (CandidateForWildcardTemplate(name), None) => {
pad.set_property("name", name);
}
(CandidateForWildcardTemplate(name), Some(templ)) => {
if templ.name().find('%').is_none() {
// Not a widlcard template
pad.set_property("name", templ.name());
} else {
let mut can_assign_name = true;
if templ.presence() == crate::PadPresence::Request {
// Check if the name is compatible with the name template.
use crate::CAT_RUST;
let mut name_parts = name.split('_');
for templ_part in templ.name_template().split('_') {
let Some(name_part) = name_parts.next() else {
crate::debug!(
CAT_RUST,
"Not using Pad name '{name}': not enough parts compared to template '{}'",
templ.name_template(),
);
can_assign_name = false;
break;
};
if let Some(conv_spec_start) = templ_part.find('%') {
if conv_spec_start > 0
&& !name_part.starts_with(&templ_part[..conv_spec_start])
{
crate::debug!(
CAT_RUST,
"Not using Pad name '{name}': mismatch template '{}' prefix",
templ.name_template(),
);
can_assign_name = false;
break;
}
let conv_spec_pos = conv_spec_start + 1;
match templ_part.get(conv_spec_pos..=conv_spec_pos) {
Some("s") => {
// *There can be only one* %s
break;
}
Some("u") => {
if name_part
.get(conv_spec_start..)
.map_or(true, |s| s.parse::<u32>().is_err())
{
crate::debug!(
CAT_RUST,
"Not using Pad name '{name}': can't parse '%u' from '{name_part}' (template '{}')",
templ.name_template(),
);
can_assign_name = false;
break;
}
}
Some("d") => {
if name_part
.get(conv_spec_start..)
.map_or(true, |s| s.parse::<i32>().is_err())
{
crate::debug!(
CAT_RUST,
"Not using target Pad name '{name}': can't parse '%i' from '{name_part}' (template '{}')",
templ.name_template(),
);
can_assign_name = false;
break;
}
}
other => {
unreachable!("Unexpected conversion specifier {other:?}")
}
}
} else if name_part != templ_part {
can_assign_name = false;
}
}
}
if can_assign_name {
pad.set_property("name", name);
} else {
panic!(concat!(
"Attempt to build a Pad from a wildcard-name template",
" with a target Pad with an incompatible name.",
" Make sure to define a specific name using PadBuilder",
" or opt-in to keep the automatically generated name.",
));
}
}
}
} }
self.pad pad
} }
} }
@ -2484,11 +2605,21 @@ mod tests {
let pad = crate::Pad::builder(crate::PadDirection::Unknown).build(); let pad = crate::Pad::builder(crate::PadDirection::Unknown).build();
assert!(pad.name().starts_with("pad")); assert!(pad.name().starts_with("pad"));
let pad = crate::Pad::builder(crate::PadDirection::Unknown)
.generated_name()
.build();
assert!(pad.name().starts_with("pad"));
let pad = crate::Pad::builder(crate::PadDirection::Unknown) let pad = crate::Pad::builder(crate::PadDirection::Unknown)
.maybe_name(None::<&str>) .maybe_name(None::<&str>)
.build(); .build();
assert!(pad.name().starts_with("pad")); assert!(pad.name().starts_with("pad"));
let pad = crate::Pad::builder(crate::PadDirection::Unknown)
.name_if_some(None::<&str>)
.build();
assert!(pad.name().starts_with("pad"));
let pad = crate::Pad::builder(crate::PadDirection::Sink) let pad = crate::Pad::builder(crate::PadDirection::Sink)
.name("sink_0") .name("sink_0")
.build(); .build();
@ -2509,6 +2640,11 @@ mod tests {
.build(); .build();
assert_eq!(pad.name(), "test"); assert_eq!(pad.name(), "test");
let pad = crate::Pad::builder(crate::PadDirection::Unknown)
.name_if_some(Some("test"))
.build();
assert_eq!(pad.name(), "test");
let caps = crate::Caps::new_any(); let caps = crate::Caps::new_any();
let templ = crate::PadTemplate::new( let templ = crate::PadTemplate::new(
"sink", "sink",
@ -2526,6 +2662,9 @@ mod tests {
.build(); .build();
assert!(pad.name().starts_with("audio_sink")); assert!(pad.name().starts_with("audio_sink"));
let pad = Pad::builder_from_template(&templ).generated_name().build();
assert!(pad.name().starts_with("pad"));
let templ = crate::PadTemplate::new( let templ = crate::PadTemplate::new(
"audio_%u", "audio_%u",
crate::PadDirection::Sink, crate::PadDirection::Sink,
@ -2536,6 +2675,9 @@ mod tests {
let pad = Pad::builder_from_template(&templ).name("audio_0").build(); let pad = Pad::builder_from_template(&templ).name("audio_0").build();
assert!(pad.name().starts_with("audio_0")); assert!(pad.name().starts_with("audio_0"));
let pad = Pad::builder_from_template(&templ).generated_name().build();
assert!(pad.name().starts_with("pad"));
} }
#[test] #[test]