lv2_state/path.rs
1//! Host features for file path managment.
2//!
3//! There are cases where a plugin needs to store a complete file in it's state. For example, a sampler might want to store the recorded sample in a .wav file. However, chosing a valid path for this file is a delicate problem: First of all, different operating systems have different naming schemes for file paths. This means that the system has to be independent of naming schemes. Secondly, there might be multiple instances of the same plugin, other plugins, or even different hosts competing for a file path. Therefore, the system has to avoid collisions with other programs. Lastly, a path that was available when the state was saved might not be available when the state has to be restored. Therefore, the new absolute path to the file has to be retrievable.
4//!
5//! LV2 handles this problem by leaving it to the host implementors and specifying an interface for it. There are three distinct host features which are necessary to fulfill the tasks from above: [`MakePath`](struct.MakePath.html), which "makes" an absolute file path from a relative path, [`MapPath`](struct.MapPath), which maps an absolute path to/from an abstract string that can be stored as a property, and [`FreePath`](struct.FreePath.html), which frees the strings/paths created by the features above.
6//!
7//! Since all of these features need each other in order to be safe and sound, none of them can be used on their own. Instead, you use them to construct a [`PathManager`](struct.PathManager.html), which exposes all of their interfaces.
8//!
9//! The best way to understand this system is to have an example:
10//!
11//! ```
12//! use lv2_core::prelude::*;
13//! use lv2_state::*;
14//! use lv2_state::path::*;
15//! use lv2_atom::prelude::*;
16//! use lv2_urid::*;
17//! use urid::*;
18//! use std::fs::File;
19//! use std::path::Path;
20//! use std::io::{Read, Write};
21//!
22//! // First, we need to write out some boilerplate code
23//! // to define a proper plugin. There's no way around it. 😕
24//!
25//! /// The plugin we're outlining.
26//! #[uri("urn:my-plugin")]
27//! struct Sampler {
28//! // A vector of bytes, for simplicity's sake.
29//! // In a proper sampler, this would be a vector of floats.
30//! sample: Vec<u8>,
31//! urids: URIDs,
32//! }
33//!
34//! /// The features we need.
35//! #[derive(FeatureCollection)]
36//! struct Features<'a> {
37//! makePath: MakePath<'a>,
38//! mapPath: MapPath<'a>,
39//! freePath: FreePath<'a>,
40//! uridMap: LV2Map<'a>,
41//! }
42//!
43//! // A quick definition to identify the sample
44//! // path in the state property store.
45//! #[uri("urn:my-plugin:sample")]
46//! struct Sample;
47//!
48//! /// Some URIDs we need.
49//! #[derive(URIDCollection)]
50//! struct URIDs {
51//! atom: AtomURIDCollection,
52//! sample: URID<Sample>,
53//! }
54//!
55//! // Plugin implementation omitted...
56//! # impl Plugin for Sampler {
57//! # type Ports = ();
58//! # type InitFeatures = Features<'static>;
59//! # type AudioFeatures = ();
60//! #
61//! # fn new(_: &PluginInfo, features: &mut Features<'static>) -> Option<Self> {
62//! # Some(Self {
63//! # sample: Vec::new(),
64//! # urids: features.uridMap.populate_collection()?,
65//! # })
66//! # }
67//! #
68//! # fn run(&mut self, _: &mut (), _: &mut (), _: u32) {}
69//! # }
70//!
71//! impl State for Sampler {
72//! type StateFeatures = Features<'static>;
73//!
74//! fn save(&self, mut store: StoreHandle, features: Features) -> Result<(), StateErr> {
75//! // Create a path manager, it manages all paths!
76//! let mut manager = PathManager::new(
77//! features.makePath,
78//! features.mapPath,
79//! features.freePath
80//! );
81//!
82//! // Allocate a path to store the sample to.
83//! // The absolute path is the "real" path of the file we may write to
84//! // and the abstract path is the path we may store in a property.
85//! let (absolute_path, abstract_path) = manager
86//! .allocate_path(Path::new("sample.wav"))?;
87//!
88//! // Store the sample. This isn't the correct way to save WAVs!
89//! let mut file = File::create(absolute_path).map_err(|_| StateErr::Unknown)?;
90//! file.write_all(self.sample.as_ref()).map_err(|_| StateErr::Unknown)?;
91//!
92//! // Draft a new property to store the abstract path of the sample.
93//! {
94//! let mut path_writer = store.draft(self.urids.sample);
95//! let mut path_writer = path_writer
96//! .init(self.urids.atom.string, ())
97//! .map_err(|_| StateErr::Unknown)?;
98//! path_writer.append(&*abstract_path);
99//! }
100//!
101//! // Commit everything!
102//! store.commit_all()
103//! }
104//!
105//! fn restore(&mut self, store: RetrieveHandle, features: Features) -> Result<(), StateErr> {
106//! // Again, create a path a path manager.
107//! let mut manager = PathManager::new(
108//! features.makePath,
109//! features.mapPath,
110//! features.freePath
111//! );
112//!
113//! // Retrieve the abstract path from the property store.
114//! let abstract_path = store
115//! .retrieve(self.urids.sample)?
116//! .read(self.urids.atom.string, ())
117//! .map_err(|_| StateErr::Unknown)?;
118//!
119//! // Get the absolute path to the referenced file.
120//! let absolute_path = manager
121//! .deabstract_path(abstract_path)?;
122//!
123//! // Open the file.
124//! let mut file = File::open(absolute_path)
125//! .map_err(|_| StateErr::Unknown)?;
126//!
127//! // Write it to the sample.
128//! self.sample.clear();
129//! file.read_to_end(&mut self.sample)
130//! .map(|_| ())
131//! .map_err(|_| StateErr::Unknown)
132//! }
133//! }
134//! ```
135//!
136//! # A note on availability
137//!
138//! Originally, these path handling features are also meant to be usable outside of the context of `save` and `restore`, for example to create a temporary audio file. However, the specification does not define whether the `FreePath` feature only deallocates the path string or if it deallocates the files pointed by the path too. Therefore, we can not guarantee that files and strings live outside of the scope of a trait function call and had to restrict the usage to `save` and `restore`.
139use crate::StateErr;
140use lv2_core::feature::Feature;
141use lv2_core::prelude::*;
142use lv2_sys as sys;
143use std::ffi::*;
144use std::iter::once;
145use std::marker::PhantomData;
146use std::os::raw::c_char;
147use std::path::*;
148use std::rc::Rc;
149use std::sync::Mutex;
150use urid::*;
151
152/// A host feature that allocates absolute file paths.
153///
154/// This is only useful in conjunction with the [`FreePath`](struct.FreePath.html) and [`MapPath`](struct.MapPath.html) features. Therefore, the interface of this feature is private and only exposed by a [`PathManager`](struct.PathManager.html), which is constructed from these three host features.
155///
156/// Please take a look at the [module documentation](index.html) for a usage example.
157pub struct MakePath<'a> {
158 handle: sys::LV2_State_Make_Path_Handle,
159 function: unsafe extern "C" fn(sys::LV2_State_Make_Path_Handle, *const c_char) -> *mut c_char,
160 lifetime: PhantomData<&'a mut c_void>,
161}
162
163unsafe impl<'a> UriBound for MakePath<'a> {
164 const URI: &'static [u8] = sys::LV2_STATE__makePath;
165}
166
167unsafe impl<'a> Feature for MakePath<'a> {
168 unsafe fn from_feature_ptr(feature: *const c_void, _: ThreadingClass) -> Option<Self> {
169 (feature as *const sys::LV2_State_Make_Path)
170 .as_ref()
171 .and_then(|internal| {
172 Some(Self {
173 handle: internal.handle,
174 function: internal.path?,
175 lifetime: PhantomData,
176 })
177 })
178 }
179}
180
181impl<'a> MakePath<'a> {
182 fn relative_to_absolute_path(&mut self, relative_path: &Path) -> Result<&'a Path, StateErr> {
183 let relative_path: Vec<c_char> = relative_path
184 .to_str()
185 .ok_or(StateErr::PathNotUTF8)?
186 .bytes()
187 .chain(once(0))
188 .map(|b| b as c_char)
189 .collect();
190
191 let absolute_path = unsafe { (self.function)(self.handle, relative_path.as_ptr()) };
192
193 if absolute_path.is_null() {
194 return Err(StateErr::HostError);
195 }
196
197 unsafe { CStr::from_ptr(absolute_path) }
198 .to_str()
199 .map(Path::new)
200 .map_err(|_| StateErr::HostError)
201 }
202}
203
204/// A host feature that maps absolute file paths to and from abstract file paths.
205///
206/// This is only useful in conjunction with the [`FreePath`](struct.FreePath.html) and [`MakePath`](struct.MakePath.html) features. Therefore, the interface of this feature is private and only exposed by a [`PathManager`](struct.PathManager.html), which is constructed from these three host features.
207///
208/// Please take a look at the [module documentation](index.html) for a usage example.
209pub struct MapPath<'a> {
210 handle: sys::LV2_State_Map_Path_Handle,
211 abstract_path: unsafe extern "C" fn(
212 sys::LV2_State_Map_Path_Handle,
213 absolute_path: *const c_char,
214 ) -> *mut c_char,
215 absolute_path: unsafe extern "C" fn(
216 sys::LV2_State_Map_Path_Handle,
217 abstract_path: *const c_char,
218 ) -> *mut c_char,
219 lifetime: PhantomData<&'a mut c_void>,
220}
221
222unsafe impl<'a> UriBound for MapPath<'a> {
223 const URI: &'static [u8] = sys::LV2_STATE__mapPath;
224}
225
226unsafe impl<'a> Feature for MapPath<'a> {
227 unsafe fn from_feature_ptr(feature: *const c_void, _: ThreadingClass) -> Option<Self> {
228 (feature as *const sys::LV2_State_Map_Path)
229 .as_ref()
230 .and_then(|internal| {
231 Some(Self {
232 handle: internal.handle,
233 abstract_path: internal.abstract_path?,
234 absolute_path: internal.absolute_path?,
235 lifetime: PhantomData,
236 })
237 })
238 }
239}
240
241impl<'a> MapPath<'a> {
242 fn absolute_to_abstract_path(&mut self, path: &Path) -> Result<&'a str, StateErr> {
243 let path: Vec<c_char> = path
244 .to_str()
245 .ok_or(StateErr::PathNotUTF8)?
246 .bytes()
247 .chain(once(0))
248 .map(|b| b as c_char)
249 .collect();
250
251 let path = unsafe { (self.abstract_path)(self.handle, path.as_ptr()) };
252
253 if path.is_null() {
254 return Err(StateErr::HostError);
255 }
256
257 unsafe { CStr::from_ptr(path) }
258 .to_str()
259 .map_err(|_| StateErr::HostError)
260 }
261
262 fn abstract_to_absolute_path(&mut self, path: &str) -> Result<&'a Path, StateErr> {
263 let path: Vec<c_char> = path.bytes().chain(once(0)).map(|b| b as c_char).collect();
264
265 let path = unsafe { (self.absolute_path)(self.handle, path.as_ptr()) };
266
267 if path.is_null() {
268 return Err(StateErr::HostError);
269 }
270
271 unsafe { CStr::from_ptr(path) }
272 .to_str()
273 .map(Path::new)
274 .map_err(|_| StateErr::HostError)
275 }
276}
277
278/// A host feature that deallocates absolute and abstract file paths.
279///
280/// This is only useful in conjunction with the [`MapPath`](struct.MapPath.html) and [`MakePath`](struct.MakePath.html) features. Therefore, the interface of this feature is private and only exposed by a [`PathManager`](struct.PathManager.html), which is constructed from these three host features.
281///
282/// Please take a look at the [module documentation](index.html) for a usage example.
283pub struct FreePath<'a> {
284 handle: sys::LV2_State_Free_Path_Handle,
285 free_path: unsafe extern "C" fn(sys::LV2_State_Free_Path_Handle, *mut c_char),
286 lifetime: PhantomData<&'a mut c_void>,
287}
288
289unsafe impl<'a> UriBound for FreePath<'a> {
290 const URI: &'static [u8] = sys::LV2_STATE__freePath;
291}
292
293unsafe impl<'a> Feature for FreePath<'a> {
294 unsafe fn from_feature_ptr(feature: *const c_void, _: ThreadingClass) -> Option<Self> {
295 (feature as *const sys::LV2_State_Free_Path)
296 .as_ref()
297 .and_then(|internal| {
298 Some(Self {
299 handle: internal.handle,
300 free_path: internal.free_path?,
301 lifetime: PhantomData,
302 })
303 })
304 }
305}
306
307impl<'a> FreePath<'a> {
308 fn free_path(&self, path: &str) {
309 unsafe { (self.free_path)(self.handle, path.as_ptr() as *mut c_char) }
310 }
311}
312
313/// A path that has been allocated by the host.
314///
315/// An instance of this struct can be used just like a [`Path`](https://doc.rust-lang.org/stable/std/path/struct.Path.html) instance.
316///
317/// This path has been allocated by the host via a [`PathManager`](struct.PathManager.html) and will be deallocated by the host when dropped. Since it contains an `Rc<Mutex<>>`, it is neither `Send` nor `Sync` and shouldn't be used outside of the scope of a [`save`](../trait.State.html#tymethod.save) or [`restore`](../trait.State.html#tymethod.restore) call.
318pub struct ManagedPath<'a> {
319 path: &'a Path,
320 free_path: Rc<Mutex<FreePath<'a>>>,
321}
322
323impl<'a> std::ops::Deref for ManagedPath<'a> {
324 type Target = Path;
325
326 fn deref(&self) -> &Path {
327 self.path
328 }
329}
330
331impl<'a> AsRef<Path> for ManagedPath<'a> {
332 fn as_ref(&self) -> &Path {
333 self.path
334 }
335}
336
337impl<'a> Drop for ManagedPath<'a> {
338 fn drop(&mut self) {
339 self.free_path
340 .lock()
341 .unwrap()
342 .free_path(self.path.to_str().unwrap())
343 }
344}
345
346/// A string that has been allocated by the host.
347///
348/// An instance of this struct can be used just like a [`str`](https://doc.rust-lang.org/stable/std/primitive.str.html) reference.
349///
350/// This string has been allocated by the host via a [`PathManager`](struct.PathManager.html) and will be deallocated by the host when dropped. Since it contains an `Rc<Mutex<>>`, it is neither `Send` nor `Sync` and shouldn't be used outside of the scope of a [`save`](../trait.State.html#tymethod.save) or [`restore`](../trait.State.html#tymethod.restore) call.
351pub struct ManagedStr<'a> {
352 str: &'a str,
353 free_path: Rc<Mutex<FreePath<'a>>>,
354}
355
356impl<'a> std::ops::Deref for ManagedStr<'a> {
357 type Target = str;
358
359 fn deref(&self) -> &str {
360 self.str
361 }
362}
363
364impl<'a> Drop for ManagedStr<'a> {
365 fn drop(&mut self) {
366 self.free_path.lock().unwrap().free_path(self.str)
367 }
368}
369
370impl<'a> AsRef<str> for ManagedStr<'a> {
371 fn as_ref(&self) -> &str {
372 self.str
373 }
374}
375
376/// A safe interface to the path handling features.
377///
378/// This struct is constructed from the three path handling features, [`MakePath`](struct.MakePath.html), [`MapPath`](struct.MapPath.html), and [`FreePath`](struct.FreePath.html), and exposes them as one safe interface.
379///
380/// Please take a look at the [module documentation](index.html) for a usage example.
381pub struct PathManager<'a> {
382 make: MakePath<'a>,
383 map: MapPath<'a>,
384 free: Rc<Mutex<FreePath<'a>>>,
385}
386
387impl<'a> PathManager<'a> {
388 /// Create a new path manager from the three path handling features.
389 pub fn new(make: MakePath<'a>, map: MapPath<'a>, free: FreePath<'a>) -> Self {
390 Self {
391 make,
392 map,
393 free: Rc::new(Mutex::new(free)),
394 }
395 }
396
397 /// Allocate a new path.
398 ///
399 /// This function maps the given relative file path to an absolute file path as well as an abstract file path. The absolute file path can be used to access the new file and the abstract file path is used to reference it in the state of the plugin. Storing the absolute file path in the plugin state will not work since it might have changed when the state is restored.
400 ///
401 /// The relative file path will be the suffix of the absolute file path and will be contained in a namespace unique to the plugin instance. This means that allocations of the same relative path by different plugin instances will not collide. Apart from that, you can not make any other assumptions about the absolute and abstract file paths.
402 ///
403 /// An abstract file path that has been read from the plugin state can be mapped back to an absolute file path with the [`deabstract_path`](#method.deabstract_path) method.
404 pub fn allocate_path(
405 &mut self,
406 relative_path: &Path,
407 ) -> Result<(ManagedPath<'a>, ManagedStr<'a>), StateErr> {
408 let absolute_path = self
409 .make
410 .relative_to_absolute_path(relative_path)
411 .map(|path| ManagedPath {
412 path,
413 free_path: self.free.clone(),
414 })?;
415
416 let abstract_path = self
417 .map
418 .absolute_to_abstract_path(absolute_path.as_ref())
419 .map(|str| ManagedStr {
420 str,
421 free_path: self.free.clone(),
422 })?;
423
424 Ok((absolute_path, abstract_path))
425 }
426
427 /// Map an abstract file path back to an absolute one.
428 ///
429 /// After reading an abstract file path from the state, you have to map it back to an absolute file path in order to read the file. This is what this method does.
430 pub fn deabstract_path(&mut self, path: &str) -> Result<ManagedPath<'a>, StateErr> {
431 self.map
432 .abstract_to_absolute_path(path)
433 .map(|path| ManagedPath {
434 path,
435 free_path: self.free.clone(),
436 })
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use crate::path::*;
443
444 unsafe extern "C" fn make_path_impl(
445 temp_dir: sys::LV2_State_Make_Path_Handle,
446 relative_path: *const c_char,
447 ) -> *mut c_char {
448 let relative_path = match CStr::from_ptr(relative_path).to_str() {
449 Ok(path) => path,
450 _ => return std::ptr::null_mut(),
451 };
452
453 let temp_dir = temp_dir as *const mktemp::Temp;
454 let mut absolute_path = (*temp_dir).as_path().to_path_buf();
455 absolute_path.push(relative_path);
456
457 CString::new(absolute_path.to_str().unwrap())
458 .map(CString::into_raw)
459 .unwrap_or(std::ptr::null_mut())
460 }
461
462 unsafe extern "C" fn abstract_path_impl(
463 temp_dir: sys::LV2_State_Map_Path_Handle,
464 absolute_path: *const c_char,
465 ) -> *mut c_char {
466 let absolute_path = match CStr::from_ptr(absolute_path).to_str() {
467 Ok(path) => Path::new(path),
468 _ => return std::ptr::null_mut(),
469 };
470
471 let temp_dir = temp_dir as *const mktemp::Temp;
472 let temp_dir = (*temp_dir).as_path();
473 let abstract_path = absolute_path.strip_prefix(temp_dir).unwrap();
474
475 CString::new(abstract_path.to_str().unwrap())
476 .map(CString::into_raw)
477 .unwrap_or(std::ptr::null_mut())
478 }
479
480 unsafe extern "C" fn free_path_impl(
481 free_counter: sys::LV2_State_Free_Path_Handle,
482 path: *mut c_char,
483 ) {
484 *(free_counter as *mut u32).as_mut().unwrap() += 1;
485 CString::from_raw(path);
486 }
487
488 #[test]
489 fn test_path() {
490 let temp_dir = mktemp::Temp::new_dir().unwrap();
491
492 let make_path_feature = sys::LV2_State_Make_Path {
493 handle: &temp_dir as *const _ as *mut c_void,
494 path: Some(make_path_impl),
495 };
496 let make_path = unsafe {
497 MakePath::from_feature_ptr(
498 &make_path_feature as *const _ as *const c_void,
499 ThreadingClass::Other,
500 )
501 }
502 .unwrap();
503
504 let map_path_feature = sys::LV2_State_Map_Path {
505 handle: &temp_dir as *const _ as *mut c_void,
506 abstract_path: Some(abstract_path_impl),
507 absolute_path: Some(make_path_impl),
508 };
509 let map_path = unsafe {
510 MapPath::from_feature_ptr(
511 &map_path_feature as *const _ as *const c_void,
512 ThreadingClass::Other,
513 )
514 }
515 .unwrap();
516
517 let mut free_counter: u32 = 0;
518 let free_path_feature = sys::LV2_State_Free_Path {
519 handle: &mut free_counter as *mut _ as *mut c_void,
520 free_path: Some(free_path_impl),
521 };
522 let free_path = unsafe {
523 FreePath::from_feature_ptr(
524 &free_path_feature as *const _ as *const c_void,
525 ThreadingClass::Other,
526 )
527 }
528 .unwrap();
529
530 let mut manager = PathManager::new(make_path, map_path, free_path);
531 let relative_path = Path::new("sample.wav");
532 let ref_absolute_path: PathBuf = [temp_dir.as_path(), relative_path].iter().collect();
533
534 {
535 let (absolute_path, abstract_path) = manager.allocate_path(relative_path).unwrap();
536 assert_eq!(ref_absolute_path, &*absolute_path);
537 assert_eq!(relative_path.to_str().unwrap(), &*abstract_path);
538
539 let absolute_path = manager.deabstract_path(&abstract_path).unwrap();
540 assert_eq!(ref_absolute_path, &*absolute_path);
541 }
542
543 assert_eq!(free_counter, 3);
544 }
545}