Skip to main content

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}