[][src]Module lv2_state::path

Host features for file path managment.

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.

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, which "makes" an absolute file path from a relative path, MapPath, which maps an absolute path to/from an abstract string that can be stored as a property, and FreePath, which frees the strings/paths created by the features above.

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, which exposes all of their interfaces.

The best way to understand this system is to have an example:

use lv2_core::prelude::*;
use lv2_state::*;
use lv2_state::path::*;
use lv2_atom::prelude::*;
use lv2_urid::*;
use urid::*;
use std::fs::File;
use std::path::Path;
use std::io::{Read, Write};

// First, we need to write out some boilerplate code
// to define a proper plugin. There's no way around it. 😕

/// The plugin we're outlining.
#[uri("urn:my-plugin")]
struct Sampler {
    // A vector of bytes, for simplicity's sake.
    // In a proper sampler, this would be a vector of floats.
    sample: Vec<u8>,
    urids: URIDs,
}

/// The features we need.
#[derive(FeatureCollection)]
struct Features<'a> {
    makePath: MakePath<'a>,
    mapPath: MapPath<'a>,
    freePath: FreePath<'a>,
    uridMap: LV2Map<'a>,
}

// A quick definition to identify the sample
// path in the state property store.
#[uri("urn:my-plugin:sample")]
struct Sample;

/// Some URIDs we need.
#[derive(URIDCollection)]
struct URIDs {
    atom: AtomURIDCollection,
    sample: URID<Sample>,
}

// Plugin implementation omitted...

impl State for Sampler {
    type StateFeatures = Features<'static>;

    fn save(&self, mut store: StoreHandle, features: Features) -> Result<(), StateErr> {
        // Create a path manager, it manages all paths!
        let mut manager = PathManager::new(
            features.makePath,
            features.mapPath,
            features.freePath
        );

        // Allocate a path to store the sample to.
        // The absolute path is the "real" path of the file we may write to
        // and the abstract path is the path we may store in a property.
        let (absolute_path, abstract_path) = manager
            .allocate_path(Path::new("sample.wav"))?;

        // Store the sample. This isn't the correct way to save WAVs!
        let mut file = File::create(absolute_path).map_err(|_| StateErr::Unknown)?;
        file.write_all(self.sample.as_ref()).map_err(|_| StateErr::Unknown)?;

        // Draft a new property to store the abstract path of the sample.
        {
            let mut path_writer = store.draft(self.urids.sample);
            let mut path_writer = path_writer
                .init(self.urids.atom.string, ())
                .map_err(|_| StateErr::Unknown)?;
            path_writer.append(&*abstract_path);
        }

        // Commit everything!
        store.commit_all()
    }

    fn restore(&mut self, store: RetrieveHandle, features: Features) -> Result<(), StateErr> {
        // Again, create a path a path manager.
        let mut manager = PathManager::new(
            features.makePath,
            features.mapPath,
            features.freePath
        );

        // Retrieve the abstract path from the property store.
        let abstract_path = store
            .retrieve(self.urids.sample)?
            .read(self.urids.atom.string, ())
            .map_err(|_| StateErr::Unknown)?;

        // Get the absolute path to the referenced file.
        let absolute_path = manager
            .deabstract_path(abstract_path)?;

        // Open the file.
        let mut file = File::open(absolute_path)
            .map_err(|_| StateErr::Unknown)?;

        // Write it to the sample.
        self.sample.clear();
        file.read_to_end(&mut self.sample)
            .map(|_| ())
            .map_err(|_| StateErr::Unknown)
    }
}

A note on availability

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.

Structs

FreePath

A host feature that deallocates absolute and abstract file paths.

MakePath

A host feature that allocates absolute file paths.

ManagedPath

A path that has been allocated by the host.

ManagedStr

A string that has been allocated by the host.

MapPath

A host feature that maps absolute file paths to and from abstract file paths.

PathManager

A safe interface to the path handling features.