pipewire/properties/
mod.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4use std::{ffi::CString, fmt, ptr};
5
6mod box_;
7pub use box_::*;
8
9/// A collection of key/value pairs.
10#[repr(transparent)]
11pub struct Properties(pw_sys::pw_properties);
12
13impl Properties {
14    pub fn as_raw(&self) -> &pw_sys::pw_properties {
15        &self.0
16    }
17
18    /// Obtain a pointer to the underlying `pw_properties` struct.
19    ///
20    /// The pointer is only valid for the lifetime of the [`Properties`] struct the pointer was obtained from,
21    /// and must not be dereferenced after it is dropped.
22    ///
23    /// Ownership of the `pw_properties` struct is not transferred to the caller and must not be manually freed.
24    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_properties {
25        std::ptr::addr_of!(self.0).cast_mut()
26    }
27
28    pub fn dict(&self) -> &spa::utils::dict::DictRef {
29        unsafe { &*(&self.0.dict as *const spa_sys::spa_dict as *const spa::utils::dict::DictRef) }
30    }
31
32    // TODO: Impl as trait?
33    pub fn to_owned(&self) -> PropertiesBox {
34        unsafe {
35            let ptr = pw_sys::pw_properties_copy(self.as_raw_ptr());
36            PropertiesBox::from_raw(ptr::NonNull::new_unchecked(ptr))
37        }
38    }
39
40    pub fn get(&self, key: &str) -> Option<&str> {
41        let key = CString::new(key).expect("key contains null byte");
42
43        let res =
44            unsafe { pw_sys::pw_properties_get(self.as_raw_ptr().cast_const(), key.as_ptr()) };
45
46        let res = if !res.is_null() {
47            unsafe { Some(std::ffi::CStr::from_ptr(res)) }
48        } else {
49            None
50        };
51
52        // FIXME: Don't return `None` if result is non-utf8
53        res.and_then(|res| res.to_str().ok())
54    }
55
56    pub fn insert<K, V>(&mut self, key: K, value: V)
57    where
58        K: Into<Vec<u8>>,
59        V: Into<Vec<u8>>,
60    {
61        let k = CString::new(key).unwrap();
62        let v = CString::new(value).unwrap();
63        unsafe { pw_sys::pw_properties_set(self.as_raw_ptr(), k.as_ptr(), v.as_ptr()) };
64    }
65
66    pub fn remove<T>(&mut self, key: T)
67    where
68        T: Into<Vec<u8>>,
69    {
70        let key = CString::new(key).unwrap();
71        unsafe { pw_sys::pw_properties_set(self.as_raw_ptr(), key.as_ptr(), std::ptr::null()) };
72    }
73
74    pub fn clear(&mut self) {
75        unsafe { pw_sys::pw_properties_clear(self.as_raw_ptr()) }
76    }
77}
78
79impl AsRef<spa::utils::dict::DictRef> for Properties {
80    fn as_ref(&self) -> &spa::utils::dict::DictRef {
81        self.dict()
82    }
83}
84
85impl fmt::Debug for Properties {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        // FIXME: Debug-print dict key and values directly
88        f.debug_tuple("Properties").field(self.as_ref()).finish()
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn new() {
98        let props = properties! {
99            "K0" => "V0"
100        };
101
102        let mut iter = props.dict().iter();
103        assert_eq!(("K0", "V0"), iter.next().unwrap());
104        assert_eq!(None, iter.next());
105    }
106
107    #[test]
108    fn remove() {
109        let mut props = properties! {
110            "K0" => "V0"
111        };
112
113        assert_eq!(Some("V0"), props.dict().get("K0"));
114        props.remove("K0");
115        assert_eq!(None, props.dict().get("K0"));
116    }
117
118    #[test]
119    fn insert() {
120        let mut props = properties! {
121            "K0" => "V0"
122        };
123
124        assert_eq!(None, props.dict().get("K1"));
125        props.insert("K1", "V1");
126        assert_eq!(Some("V1"), props.dict().get("K1"));
127    }
128
129    #[test]
130    fn clone() {
131        let props1 = properties! {
132            "K0" => "V0"
133        };
134        let mut props2 = props1.clone();
135
136        props2.insert("K1", "V1");
137
138        // Now, props2 should contain ("K1", "V1"), but props1 should not.
139
140        assert_eq!(None, props1.dict().get("K1"));
141        assert_eq!(Some("V1"), props2.dict().get("K1"));
142    }
143
144    #[test]
145    fn from_dict() {
146        use spa::static_dict;
147
148        let mut props = {
149            let dict = static_dict! { "K0" => "V0" };
150
151            PropertiesBox::from_dict(&dict)
152        };
153
154        assert_eq!(props.dict().len(), 1);
155        assert_eq!(props.dict().get("K0"), Some("V0"));
156
157        props.insert("K1", "V1");
158        assert_eq!(props.dict().len(), 2);
159        assert_eq!(props.dict().get("K1"), Some("V1"));
160    }
161
162    #[test]
163    fn properties_ref() {
164        use std::ops::Deref;
165
166        let props = properties! {
167            "K0" => "V0"
168        };
169        println!("{:?}", &props);
170        let props_ref: &Properties = props.deref();
171
172        assert_eq!(props_ref.dict().len(), 1);
173        assert_eq!(props_ref.dict().get("K0"), Some("V0"));
174        dbg!(&props_ref);
175
176        let props_copy = props_ref.to_owned();
177        assert_eq!(props_copy.dict().len(), 1);
178        assert_eq!(props_copy.dict().get("K0"), Some("V0"));
179    }
180}