1 // Copyright (C) 2016-2017 Sebastian Dröge <sebastian@centricular.com>
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8 
9 use DebugLevel;
10 
11 use libc::c_char;
12 use std::borrow::Cow;
13 use std::ffi::CStr;
14 use std::fmt;
15 use std::ptr;
16 
17 use gobject_sys;
18 use gst_sys;
19 
20 use glib::translate::{from_glib, from_glib_borrow, ToGlib, ToGlibPtr};
21 use glib::IsA;
22 use glib_sys::gpointer;
23 
24 #[derive(PartialEq, Eq)]
25 pub struct DebugMessage(ptr::NonNull<gst_sys::GstDebugMessage>);
26 
27 impl fmt::Debug for DebugMessage {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result28     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29         f.debug_tuple("DebugMessage").field(&self.get()).finish()
30     }
31 }
32 
33 impl DebugMessage {
get(&self) -> Option<Cow<str>>34     pub fn get(&self) -> Option<Cow<str>> {
35         unsafe {
36             let message = gst_sys::gst_debug_message_get(self.0.as_ptr());
37 
38             if message.is_null() {
39                 None
40             } else {
41                 Some(CStr::from_ptr(message).to_string_lossy())
42             }
43         }
44     }
45 }
46 
47 #[derive(PartialEq, Eq, Clone, Copy)]
48 pub struct DebugCategory(ptr::NonNull<gst_sys::GstDebugCategory>);
49 
50 impl DebugCategory {
new(name: &str, color: ::DebugColorFlags, description: Option<&str>) -> DebugCategory51     pub fn new(name: &str, color: ::DebugColorFlags, description: Option<&str>) -> DebugCategory {
52         extern "C" {
53             fn _gst_debug_category_new(
54                 name: *const c_char,
55                 color: gst_sys::GstDebugColorFlags,
56                 description: *const c_char,
57             ) -> *mut gst_sys::GstDebugCategory;
58         }
59 
60         // Gets the category if it exists already
61         unsafe {
62             let ptr = _gst_debug_category_new(
63                 name.to_glib_none().0,
64                 color.to_glib(),
65                 description.to_glib_none().0,
66             );
67             assert!(!ptr.is_null());
68             DebugCategory(ptr::NonNull::new_unchecked(ptr))
69         }
70     }
71 
get(name: &str) -> Option<DebugCategory>72     pub fn get(name: &str) -> Option<DebugCategory> {
73         unsafe {
74             extern "C" {
75                 fn _gst_debug_get_category(name: *const c_char) -> *mut gst_sys::GstDebugCategory;
76             }
77 
78             let cat = _gst_debug_get_category(name.to_glib_none().0);
79 
80             if cat.is_null() {
81                 None
82             } else {
83                 Some(DebugCategory(ptr::NonNull::new_unchecked(cat)))
84             }
85         }
86     }
87 
get_threshold(self) -> ::DebugLevel88     pub fn get_threshold(self) -> ::DebugLevel {
89         from_glib(unsafe { gst_sys::gst_debug_category_get_threshold(self.0.as_ptr()) })
90     }
91 
set_threshold(self, threshold: ::DebugLevel)92     pub fn set_threshold(self, threshold: ::DebugLevel) {
93         unsafe { gst_sys::gst_debug_category_set_threshold(self.0.as_ptr(), threshold.to_glib()) }
94     }
95 
reset_threshold(self)96     pub fn reset_threshold(self) {
97         unsafe { gst_sys::gst_debug_category_reset_threshold(self.0.as_ptr()) }
98     }
99 
get_color(self) -> ::DebugColorFlags100     pub fn get_color(self) -> ::DebugColorFlags {
101         unsafe { from_glib(gst_sys::gst_debug_category_get_color(self.0.as_ptr())) }
102     }
103 
get_name<'a>(self) -> &'a str104     pub fn get_name<'a>(self) -> &'a str {
105         unsafe {
106             CStr::from_ptr(gst_sys::gst_debug_category_get_name(self.0.as_ptr()))
107                 .to_str()
108                 .unwrap()
109         }
110     }
111 
get_description<'a>(self) -> Option<&'a str>112     pub fn get_description<'a>(self) -> Option<&'a str> {
113         unsafe {
114             let ptr = gst_sys::gst_debug_category_get_description(self.0.as_ptr());
115 
116             if ptr.is_null() {
117                 None
118             } else {
119                 Some(CStr::from_ptr(ptr).to_str().unwrap())
120             }
121         }
122     }
123 
124     #[inline]
log<O: IsA<::Object>>( self, obj: Option<&O>, level: ::DebugLevel, file: &str, module: &str, line: u32, args: fmt::Arguments, )125     pub fn log<O: IsA<::Object>>(
126         self,
127         obj: Option<&O>,
128         level: ::DebugLevel,
129         file: &str,
130         module: &str,
131         line: u32,
132         args: fmt::Arguments,
133     ) {
134         unsafe {
135             if level.to_glib() as i32 > self.0.as_ref().threshold {
136                 return;
137             }
138         }
139 
140         let obj_ptr = match obj {
141             Some(obj) => obj.to_glib_none().0 as *mut gobject_sys::GObject,
142             None => ptr::null_mut(),
143         };
144 
145         unsafe {
146             gst_sys::gst_debug_log(
147                 self.0.as_ptr(),
148                 level.to_glib(),
149                 file.to_glib_none().0,
150                 module.to_glib_none().0,
151                 line as i32,
152                 obj_ptr,
153                 fmt::format(args).replace("%", "%%").to_glib_none().0,
154             );
155         }
156     }
157 }
158 
159 unsafe impl Sync for DebugCategory {}
160 unsafe impl Send for DebugCategory {}
161 
162 impl fmt::Debug for DebugCategory {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result163     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164         f.debug_tuple("DebugCategory")
165             .field(&self.get_name())
166             .finish()
167     }
168 }
169 
170 lazy_static! {
171     pub static ref CAT_RUST: DebugCategory = DebugCategory::new(
172         "GST_RUST",
173         ::DebugColorFlags::UNDERLINE,
174         Some("GStreamer's Rust binding core"),
175     );
176 }
177 
178 macro_rules! declare_debug_category_from_name(
179     ($cat:ident, $cat_name:expr) => (
180         lazy_static! {
181             pub static ref $cat: DebugCategory = DebugCategory::get($cat_name)
182                 .expect(&format!("Unable to find `DebugCategory` with name {}", $cat_name));
183         }
184     );
185 );
186 
187 declare_debug_category_from_name!(CAT_DEFAULT, "default");
188 declare_debug_category_from_name!(CAT_GST_INIT, "GST_INIT");
189 declare_debug_category_from_name!(CAT_MEMORY, "GST_MEMORY");
190 declare_debug_category_from_name!(CAT_PARENTAGE, "GST_PARENTAGE");
191 declare_debug_category_from_name!(CAT_STATES, "GST_STATES");
192 declare_debug_category_from_name!(CAT_SCHEDULING, "GST_SCHEDULING");
193 declare_debug_category_from_name!(CAT_BUFFER, "GST_BUFFER");
194 declare_debug_category_from_name!(CAT_BUFFER_LIST, "GST_BUFFER_LIST");
195 declare_debug_category_from_name!(CAT_BUS, "GST_BUS");
196 declare_debug_category_from_name!(CAT_CAPS, "GST_CAPS");
197 declare_debug_category_from_name!(CAT_CLOCK, "GST_CLOCK");
198 declare_debug_category_from_name!(CAT_ELEMENT_PADS, "GST_ELEMENT_PADS");
199 declare_debug_category_from_name!(CAT_PADS, "GST_PADS");
200 declare_debug_category_from_name!(CAT_PERFORMANCE, "GST_PERFORMANCE");
201 declare_debug_category_from_name!(CAT_PIPELINE, "GST_PIPELINE");
202 declare_debug_category_from_name!(CAT_PLUGIN_LOADING, "GST_PLUGIN_LOADING");
203 declare_debug_category_from_name!(CAT_PLUGIN_INFO, "GST_PLUGIN_INFO");
204 declare_debug_category_from_name!(CAT_PROPERTIES, "GST_PROPERTIES");
205 declare_debug_category_from_name!(CAT_NEGOTIATION, "GST_NEGOTIATION");
206 declare_debug_category_from_name!(CAT_REFCOUNTING, "GST_REFCOUNTING");
207 declare_debug_category_from_name!(CAT_ERROR_SYSTEM, "GST_ERROR_SYSTEM");
208 declare_debug_category_from_name!(CAT_EVENT, "GST_EVENT");
209 declare_debug_category_from_name!(CAT_MESSAGE, "GST_MESSAGE");
210 declare_debug_category_from_name!(CAT_PARAMS, "GST_PARAMS");
211 declare_debug_category_from_name!(CAT_CALL_TRACE, "GST_CALL_TRACE");
212 declare_debug_category_from_name!(CAT_SIGNAL, "GST_SIGNAL");
213 declare_debug_category_from_name!(CAT_PROBE, "GST_PROBE");
214 declare_debug_category_from_name!(CAT_REGISTRY, "GST_REGISTRY");
215 declare_debug_category_from_name!(CAT_QOS, "GST_QOS");
216 declare_debug_category_from_name!(CAT_META, "GST_META");
217 declare_debug_category_from_name!(CAT_LOCKING, "GST_LOCKING");
218 declare_debug_category_from_name!(CAT_CONTEXT, "GST_CONTEXT");
219 
220 #[macro_export]
221 macro_rules! gst_error(
222     ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
223         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Error, obj: $obj, $($args)*)
224     }};
225     ($cat:expr, $($args:tt)*) => { {
226         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Error, $($args)*)
227     }};
228 );
229 
230 #[macro_export]
231 macro_rules! gst_warning(
232     ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
233         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Warning, obj: $obj, $($args)*)
234     }};
235     ($cat:expr, $($args:tt)*) => { {
236         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Warning, $($args)*)
237     }};
238 );
239 
240 #[macro_export]
241 macro_rules! gst_fixme(
242     ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
243         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Fixme, obj: $obj, $($args)*)
244     }};
245     ($cat:expr, $($args:tt)*) => { {
246         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Fixme, $($args)*)
247     }};
248 );
249 
250 #[macro_export]
251 macro_rules! gst_info(
252     ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
253         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Info, obj: $obj, $($args)*)
254     }};
255     ($cat:expr, $($args:tt)*) => { {
256         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Info, $($args)*)
257     }};
258 );
259 
260 #[macro_export]
261 macro_rules! gst_debug(
262     ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
263         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Debug, obj: $obj, $($args)*)
264     }};
265     ($cat:expr, $($args:tt)*) => { {
266         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Debug, $($args)*)
267     }};
268 );
269 
270 #[macro_export]
271 macro_rules! gst_log(
272     ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
273         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Log, obj: $obj, $($args)*)
274     }};
275     ($cat:expr, $($args:tt)*) => { {
276         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Log, $($args)*)
277     }};
278 );
279 
280 #[macro_export]
281 macro_rules! gst_trace(
282     ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
283         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Trace, obj: $obj, $($args)*)
284     }};
285     ($cat:expr, $($args:tt)*) => { {
286         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Trace, $($args)*)
287     }};
288 );
289 
290 #[macro_export]
291 macro_rules! gst_memdump(
292     ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
293         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Memdump, obj: $obj, $($args)*)
294     }};
295     ($cat:expr, $($args:tt)*) => { {
296         $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Memdump, $($args)*)
297     }};
298 );
299 
300 #[macro_export]
301 macro_rules! gst_log_with_level(
302     ($cat:expr, level: $level:expr, obj: $obj:expr, $($args:tt)*) => { {
303         $crate::DebugCategory::log($cat.clone(), Some($obj), $level, file!(),
304             module_path!(), line!(), format_args!($($args)*))
305     }};
306     ($cat:expr, level: $level:expr, $($args:tt)*) => { {
307         $crate::DebugCategory::log($cat.clone(), None as Option<&$crate::Object>, $level, file!(),
308             module_path!(), line!(), format_args!($($args)*))
309     }};
310 );
311 
log_handler<T>( category: *mut gst_sys::GstDebugCategory, level: gst_sys::GstDebugLevel, file: *const c_char, function: *const c_char, line: i32, object: *mut gobject_sys::GObject, message: *mut gst_sys::GstDebugMessage, user_data: gpointer, ) where T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&glib::Object>, &DebugMessage) + Send + Sync + 'static,312 unsafe extern "C" fn log_handler<T>(
313     category: *mut gst_sys::GstDebugCategory,
314     level: gst_sys::GstDebugLevel,
315     file: *const c_char,
316     function: *const c_char,
317     line: i32,
318     object: *mut gobject_sys::GObject,
319     message: *mut gst_sys::GstDebugMessage,
320     user_data: gpointer,
321 ) where
322     T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&glib::Object>, &DebugMessage)
323         + Send
324         + Sync
325         + 'static,
326 {
327     let category = DebugCategory(ptr::NonNull::new_unchecked(category));
328     let level = from_glib(level);
329     let file = CStr::from_ptr(file).to_string_lossy();
330     let function = CStr::from_ptr(function).to_string_lossy();
331     let line = line as u32;
332     let object: Option<glib::Object> = from_glib_borrow(object);
333     let message = DebugMessage(ptr::NonNull::new_unchecked(message));
334     let handler = &*(user_data as *mut T);
335     (handler)(
336         category,
337         level,
338         &file,
339         &function,
340         line,
341         object.as_ref(),
342         &message,
343     );
344 }
345 
log_handler_data_free<T>(data: gpointer)346 unsafe extern "C" fn log_handler_data_free<T>(data: gpointer) {
347     let data = Box::from_raw(data as *mut T);
348     drop(data);
349 }
350 
351 #[derive(Debug)]
352 pub struct DebugLogFunction(ptr::NonNull<std::os::raw::c_void>);
353 
354 // The contained pointer is never dereferenced and has no thread affinity.
355 // It may be convenient to send it or share it between threads to allow cleaning
356 // up log functions from other threads than the one that created it.
357 unsafe impl Send for DebugLogFunction {}
358 unsafe impl Sync for DebugLogFunction {}
359 
debug_add_log_function<T>(function: T) -> DebugLogFunction where T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&glib::Object>, &DebugMessage) + Send + Sync + 'static,360 pub fn debug_add_log_function<T>(function: T) -> DebugLogFunction
361 where
362     T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&glib::Object>, &DebugMessage)
363         + Send
364         + Sync
365         + 'static,
366 {
367     unsafe {
368         let user_data = Box::new(function);
369         let user_data_ptr = Box::into_raw(user_data) as gpointer;
370         gst_sys::gst_debug_add_log_function(
371             Some(log_handler::<T>),
372             user_data_ptr,
373             Some(log_handler_data_free::<T>),
374         );
375         DebugLogFunction(ptr::NonNull::new_unchecked(user_data_ptr))
376     }
377 }
378 
debug_remove_default_log_function()379 pub fn debug_remove_default_log_function() {
380     unsafe {
381         gst_sys::gst_debug_remove_log_function(Some(gst_sys::gst_debug_log_default));
382     }
383 }
384 
debug_remove_log_function(log_fn: DebugLogFunction)385 pub fn debug_remove_log_function(log_fn: DebugLogFunction) {
386     unsafe {
387         let removed = gst_sys::gst_debug_remove_log_function_by_data(log_fn.0.as_ptr());
388         assert_eq!(removed, 1);
389     }
390 }
391 
392 #[cfg(test)]
393 mod tests {
394     use super::*;
395     use std::sync::mpsc;
396     use std::sync::{Arc, Mutex};
397 
398     #[test]
get_existing()399     fn get_existing() {
400         ::init().unwrap();
401 
402         let perf_cat = DebugCategory::get("GST_PERFORMANCE")
403             .expect("Unable to find `DebugCategory` with name \"GST_PERFORMANCE\"");
404         assert_eq!(perf_cat.get_name(), CAT_PERFORMANCE.get_name());
405     }
406 
407     #[test]
new_and_log()408     fn new_and_log() {
409         ::init().unwrap();
410 
411         let cat = DebugCategory::new(
412             "test-cat",
413             ::DebugColorFlags::empty(),
414             Some("some debug category"),
415         );
416 
417         gst_error!(cat, "meh");
418         gst_warning!(cat, "meh");
419         gst_fixme!(cat, "meh");
420         gst_info!(cat, "meh");
421         gst_debug!(cat, "meh");
422         gst_log!(cat, "meh");
423         gst_trace!(cat, "meh");
424         gst_memdump!(cat, "meh");
425 
426         let obj = ::Bin::new(Some("meh"));
427         gst_error!(cat, obj: &obj, "meh");
428         gst_warning!(cat, obj: &obj, "meh");
429         gst_fixme!(cat, obj: &obj, "meh");
430         gst_info!(cat, obj: &obj, "meh");
431         gst_debug!(cat, obj: &obj, "meh");
432         gst_log!(cat, obj: &obj, "meh");
433         gst_trace!(cat, obj: &obj, "meh");
434         gst_memdump!(cat, obj: &obj, "meh");
435     }
436 
437     #[test]
log_handler()438     fn log_handler() {
439         ::init().unwrap();
440 
441         let cat = DebugCategory::new(
442             "test-cat-log",
443             ::DebugColorFlags::empty(),
444             Some("some debug category"),
445         );
446         cat.set_threshold(DebugLevel::Info);
447         let obj = ::Bin::new(Some("meh"));
448 
449         let (sender, receiver) = mpsc::channel();
450 
451         let sender = Arc::new(Mutex::new(sender));
452 
453         let handler = move |category: DebugCategory,
454                             level: DebugLevel,
455                             _file: &str,
456                             _function: &str,
457                             _line: u32,
458                             _object: Option<&glib::Object>,
459                             message: &DebugMessage| {
460             let cat = DebugCategory::get("test-cat-log").unwrap();
461 
462             if category != cat {
463                 // This test can run in parallel with other tests, including new_and_log above.
464                 // We cannot be certain we only see our own messages.
465                 return;
466             }
467 
468             assert_eq!(level, DebugLevel::Info);
469             assert_eq!(&message.get().unwrap(), "meh");
470             let _ = sender.lock().unwrap().send(());
471         };
472 
473         debug_remove_default_log_function();
474         let log_fn = debug_add_log_function(handler);
475         gst_info!(cat, obj: &obj, "meh");
476 
477         receiver.recv().unwrap();
478 
479         debug_remove_log_function(log_fn);
480 
481         gst_info!(cat, obj: &obj, "meh2");
482         assert!(receiver.recv().is_err());
483     }
484 }
485