lv2-state-2.0.0/.cargo_vcs_info.json0000644000000001121374457223500127200ustar { "git": { "sha1": "c93bfe859a849c5189a27c62c659bd3b724a39b6" } } lv2-state-2.0.0/Cargo.toml0000644000000022661374457223500107320ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "lv2-state" version = "2.0.0" authors = ["Jan-Oliver 'Janonard' Opdenhövel "] description = "rust-lv2's state handling library" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/RustAudio/rust-lv2" [dependencies.lv2-atom] version = "2.0.0" [dependencies.lv2-core] version = "3.0.0" [dependencies.lv2-sys] version = "2.0.0" [dependencies.urid] version = "0.1.0" [dev-dependencies.lv2-urid] version = "2.1.0" [dev-dependencies.mktemp] version = "0.4.0" [badges.maintenance] status = "passively-maintained" [badges.travis-ci] branch = "master" repository = "RustAudio/rust-lv2" lv2-state-2.0.0/Cargo.toml.orig010064400017500001750000000010731374457150200144130ustar 00000000000000[package] name = "lv2-state" version = "2.0.0" authors = ["Jan-Oliver 'Janonard' Opdenhövel "] edition = "2018" license = "MIT OR Apache-2.0" description = "rust-lv2's state handling library" readme = "README.md" repository = "https://github.com/RustAudio/rust-lv2" [badges] travis-ci = { repository = "RustAudio/rust-lv2", branch = "master" } maintenance = { status = "passively-maintained" } [dependencies] lv2-sys = "2.0.0" lv2-core = "3.0.0" lv2-atom = "2.0.0" urid = "0.1.0" [dev-dependencies] lv2-urid = "2.1.0" mktemp = "0.4.0"lv2-state-2.0.0/LICENSE-APACHE010064400017500001731000000251371364504410600134510ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. lv2-state-2.0.0/LICENSE-MIT010064400017500001731000000017761364504410600131640ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.lv2-state-2.0.0/README.md010064400017500001750000000025331374457150200130050ustar 00000000000000# Rust-LV2's state handling library. A wrapper of LV2's State API which allows plugins to save and restore their state. This is a part of [`rust-lv2`](https://crates.io/crates/lv2), a safe, fast, and ergonomic framework to create [LV2 plugins](http://lv2plug.in/) for audio processing, written in Rust. ## Documentation The original LV2 API (in the `C` programming language) is documented by ["the LV2 book"](https://lv2plug.in/book/). This book is in the process of being translated to Rust along with the development of `rust-lv2` [(link)](https://janonard.github.io/rust-lv2-book/) and describes how to properly use `rust-lv2`. ## Features There are two optional features: * `host`: Some of the types defined by some crates are only useful for testing or LV2 hosts. Since the goal of this framework is to provide an easy way to create plugins, these aren't necessary and therefore gated behind that feature. * `wmidi`: Add [`wmidi`](https://crates.io/crates/wmidi) as an optional dependency to `lv2-midi`, which enables a shortcut to read and write MIDI events directly with the types defined by this crate. ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option.lv2-state-2.0.0/src/interface.rs010064400017500001750000000202651374457150200146250ustar 00000000000000use crate::raw::*; use crate::StateErr; use core::extension::ExtensionDescriptor; use core::prelude::*; use std::marker::PhantomData; use urid::*; /// A plugin extension that lets a plugins save and restore it's state. /// /// This extension contains two new methods: [`save`](#tymethod.save) and [`restore`](#tymethod.restore). These are called by the host to save and restore the state of the plugin, which is done with a handle. /// /// You can also add a feature collection to retrieve host features; It works just like the plugin's feature collection: You create a struct with multiple `Feature`s, derive `FeatureCollection` for it, and set the [`StateFeatures`](#associatedtype.StateFeatures) type to it. Then, the framework will try to populate it with the features supplied by the host and pass it to the method. pub trait State: Plugin { /// The feature collection to populate for the [`save`](#tymethod.save) and [`restore`](#tymethod.restore) methods. type StateFeatures: FeatureCollection<'static>; /// Save the state of the plugin. /// /// The storage is done with the store handle. You draft a property, write it using the property handle, and then commit it to the store. fn save(&self, store: StoreHandle, features: Self::StateFeatures) -> Result<(), StateErr>; /// Restore the state of the plugin. /// /// The properties you have previously written can be retrieved with the store handle. fn restore( &mut self, store: RetrieveHandle, features: Self::StateFeatures, ) -> Result<(), StateErr>; } /// Raw wrapper of the [`State`](trait.State.html) extension. /// /// This is a marker type that has the required external methods for the extension. pub struct StateDescriptor { plugin: PhantomData

, } unsafe impl UriBound for StateDescriptor

{ const URI: &'static [u8] = sys::LV2_STATE__interface; } impl StateDescriptor

{ /// Handle a save request by the host. /// /// This involves creating the plugin reference, constructing the store handle and discovering the required host features. /// /// # Safety /// /// This method is unsafe since it is an interface for hosts written in C and since it dereferences raw pointers. pub unsafe extern "C" fn extern_save( instance: sys::LV2_Handle, store: sys::LV2_State_Store_Function, handle: sys::LV2_State_Handle, flags: u32, features: *const *const sys::LV2_Feature, ) -> sys::LV2_State_Status { let flags: u32 = (sys::LV2_State_Flags::from(flags) & sys::LV2_State_Flags::LV2_STATE_IS_POD).into(); if flags == 0 { return sys::LV2_State_Status_LV2_STATE_ERR_BAD_FLAGS; } let plugin: &P = if let Some(plugin) = (instance as *const P).as_ref() { plugin } else { return sys::LV2_State_Status_LV2_STATE_ERR_UNKNOWN; }; let store = StoreHandle::new(store, handle); let mut feature_container = core::feature::FeatureCache::from_raw(features); let features = if let Ok(features) = P::StateFeatures::from_cache(&mut feature_container, ThreadingClass::Other) { features } else { return sys::LV2_State_Status_LV2_STATE_ERR_NO_FEATURE; }; StateErr::into(plugin.save(store, features)) } /// Handle a restore request by the host. /// /// This involves creating the plugin reference, constructing the retrieve handle and discovering the required host features. /// /// # Safety /// /// This method is unsafe since it is an interface for hosts written in C and since it dereferences raw pointers. pub unsafe extern "C" fn extern_restore( instance: sys::LV2_Handle, retrieve: sys::LV2_State_Retrieve_Function, handle: sys::LV2_State_Handle, flags: u32, features: *const *const sys::LV2_Feature, ) -> sys::LV2_State_Status { let flags: u32 = (sys::LV2_State_Flags::from(flags) & sys::LV2_State_Flags::LV2_STATE_IS_POD).into(); if flags == 0 { return sys::LV2_State_Status_LV2_STATE_ERR_BAD_FLAGS; } let plugin: &mut P = if let Some(plugin) = (instance as *mut P).as_mut() { plugin } else { return sys::LV2_State_Status_LV2_STATE_ERR_UNKNOWN; }; let store = RetrieveHandle::new(retrieve, handle); let mut feature_container = core::feature::FeatureCache::from_raw(features); let features = if let Ok(features) = P::StateFeatures::from_cache(&mut feature_container, ThreadingClass::Other) { features } else { return sys::LV2_State_Status_LV2_STATE_ERR_NO_FEATURE; }; StateErr::into(plugin.restore(store, features)) } } impl ExtensionDescriptor for StateDescriptor

{ type ExtensionInterface = sys::LV2_State_Interface; const INTERFACE: &'static sys::LV2_State_Interface = &sys::LV2_State_Interface { save: Some(Self::extern_save), restore: Some(Self::extern_restore), }; } #[cfg(test)] mod tests { use crate::*; use lv2_core::prelude::*; use lv2_urid::*; use urid::*; #[uri("urn:stateful")] struct Stateful; impl Plugin for Stateful { type InitFeatures = (); type AudioFeatures = (); type Ports = (); #[cfg_attr(tarpaulin, skip)] fn new(_: &PluginInfo, _: &mut ()) -> Option { Some(Self) } #[cfg_attr(tarpaulin, skip)] fn run(&mut self, _: &mut (), _: &mut (), _: u32) {} } #[derive(FeatureCollection)] struct Features<'a> { _map: LV2Map<'a>, } impl State for Stateful { type StateFeatures = Features<'static>; #[cfg_attr(tarpaulin, skip)] fn save(&self, _: StoreHandle, _: Features<'static>) -> Result<(), StateErr> { Ok(()) } #[cfg_attr(tarpaulin, skip)] fn restore(&mut self, _: RetrieveHandle, _: Features<'static>) -> Result<(), StateErr> { Ok(()) } } #[test] fn test_illegal_paths() { type Descriptor = StateDescriptor; let mut plugin = Stateful; assert_eq!(sys::LV2_State_Status_LV2_STATE_ERR_BAD_FLAGS, unsafe { Descriptor::extern_save( std::ptr::null_mut(), None, std::ptr::null_mut(), 0, std::ptr::null_mut(), ) }); assert_eq!(sys::LV2_State_Status_LV2_STATE_ERR_BAD_FLAGS, unsafe { Descriptor::extern_restore( std::ptr::null_mut(), None, std::ptr::null_mut(), 0, std::ptr::null_mut(), ) }); assert_eq!(sys::LV2_State_Status_LV2_STATE_ERR_UNKNOWN, unsafe { Descriptor::extern_save( std::ptr::null_mut(), None, std::ptr::null_mut(), sys::LV2_State_Flags::LV2_STATE_IS_POD.into(), std::ptr::null_mut(), ) }); assert_eq!(sys::LV2_State_Status_LV2_STATE_ERR_UNKNOWN, unsafe { Descriptor::extern_restore( std::ptr::null_mut(), None, std::ptr::null_mut(), sys::LV2_State_Flags::LV2_STATE_IS_POD.into(), std::ptr::null_mut(), ) }); assert_eq!(sys::LV2_State_Status_LV2_STATE_ERR_NO_FEATURE, unsafe { Descriptor::extern_save( &mut plugin as *mut Stateful as sys::LV2_Handle, None, std::ptr::null_mut(), sys::LV2_State_Flags::LV2_STATE_IS_POD.into(), std::ptr::null_mut(), ) }); assert_eq!(sys::LV2_State_Status_LV2_STATE_ERR_NO_FEATURE, unsafe { Descriptor::extern_restore( &mut plugin as *mut Stateful as sys::LV2_Handle, None, std::ptr::null_mut(), sys::LV2_State_Flags::LV2_STATE_IS_POD.into(), std::ptr::null_mut(), ) }); } } lv2-state-2.0.0/src/lib.rs010064400017500001750000000164321374457150200134340ustar 00000000000000//! Extension for LV2 plugins to store their state. //! //! This is a rather classic extension to LV2 plugins: There is a trait called [`State`](trait.State.html) which requires the methods [`save`](trait.State.html#tymethod.save) and [`restore`](trait.State.html#tymethiod.restore) to be implemented. These methods will be called by the host to save and restore the state of the plugin. //! //! ## Refering to files //! //! This crate also includes features to create files in a unique namespace for the plugin instance. However, these files will not persist across `save`/`restore` calls and therefore need to be safed together with the state as well. For more information, see the documentation of the [`path` module](path/index.html). //! //! ## Example usage //! //! ``` //! use lv2_atom::prelude::*; //! use lv2_core::prelude::*; //! use lv2_state::*; //! use lv2_urid::*; //! use urid::*; //! //! /// A plugin that stores a float value. //! struct Stateful { //! internal: f32, //! urids: AtomURIDCollection, //! } //! //! /// `Stateful`s implementation of `State`. //! impl State for Stateful { //! type StateFeatures = (); //! //! fn save(&self, mut store: StoreHandle, _: ()) -> Result<(), StateErr> { //! // Try to draft a new property and store the float inside it. //! store //! .draft(URID::new(1000).unwrap()) //! .init(self.urids.float, self.internal)?; //! //! // Commit the written property. //! // Otherwise, it will discarded. //! store.commit_all() //! } //! //! fn restore(&mut self, store: RetrieveHandle, _: ()) -> Result<(), StateErr> { //! // Try to restore the property. //! self.internal = store //! .retrieve(URID::new(1000).unwrap())? //! .read(self.urids.float, ())?; //! //! // We're done. //! Ok(()) //! } //! } //! //! impl Plugin for Stateful { //! type Ports = (); //! type InitFeatures = Features<'static>; //! type AudioFeatures = (); //! //! fn new(_: &PluginInfo, features: &mut Features<'static>) -> Option { //! Some(Stateful { //! internal: 42.0, //! urids: features.map.populate_collection()?, //! }) //! } //! //! fn run(&mut self, _: &mut (), _: &mut (), _: u32) { //! // Set the float to a different value than the previous one. //! self.internal += 1.0; //! } //! //! fn extension_data(uri: &Uri) -> Option<&'static dyn std::any::Any> { //! // Export the store extension. Otherwise, the host won't use it. //! match_extensions!(uri, StateDescriptor) //! } //! } //! //! #[derive(FeatureCollection)] //! pub struct Features<'a> { //! map: LV2Map<'a>, //! } //! //! unsafe impl UriBound for Stateful { //! const URI: &'static [u8] = b"urn:lv2_atom:stateful\0"; //! } //! ``` extern crate lv2_atom as atom; extern crate lv2_core as core; extern crate lv2_sys as sys; mod interface; pub use interface::*; mod raw; pub use raw::*; mod storage; pub use storage::Storage; pub mod path; /// Kinds of errors that may occur in the crate. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StateErr { /// The kind of the error is unknown or doesn't have a representation. Unknown, /// A callback function pointer of a method is bad. BadCallback, /// Retrieved data is invalid. BadData, /// The retrieved data doesn't have the correct type. BadType, /// The flags a method was called with are invalid. BadFlags, /// A feature the plugin requested is missing. NoFeature, /// A property the plugin is requesting doesn't exist. NoProperty, /// There isn't enough memory available to execute the task. NoSpace, /// A path that's been used as a parameter is not encoded in UTF-8. PathNotUTF8, /// The host does not comply to the specification. HostError, } impl StateErr { /// Convert a raw status flag to a result or possible error value. pub fn from(value: sys::LV2_State_Status) -> Result<(), StateErr> { match value { sys::LV2_State_Status_LV2_STATE_SUCCESS => Ok(()), sys::LV2_State_Status_LV2_STATE_ERR_BAD_TYPE => Err(StateErr::BadType), sys::LV2_State_Status_LV2_STATE_ERR_BAD_FLAGS => Err(StateErr::BadFlags), sys::LV2_State_Status_LV2_STATE_ERR_NO_FEATURE => Err(StateErr::NoFeature), sys::LV2_State_Status_LV2_STATE_ERR_NO_PROPERTY => Err(StateErr::NoProperty), sys::LV2_State_Status_LV2_STATE_ERR_NO_SPACE => Err(StateErr::NoSpace), _ => Err(StateErr::Unknown), } } /// Convert a result to a raw status flag. pub fn into(result: Result<(), StateErr>) -> sys::LV2_State_Status { match result { Ok(()) => sys::LV2_State_Status_LV2_STATE_SUCCESS, Err(StateErr::BadType) => sys::LV2_State_Status_LV2_STATE_ERR_BAD_TYPE, Err(StateErr::BadFlags) => sys::LV2_State_Status_LV2_STATE_ERR_BAD_FLAGS, Err(StateErr::NoFeature) => sys::LV2_State_Status_LV2_STATE_ERR_NO_FEATURE, Err(StateErr::NoProperty) => sys::LV2_State_Status_LV2_STATE_ERR_NO_PROPERTY, Err(StateErr::NoSpace) => sys::LV2_State_Status_LV2_STATE_ERR_NO_SPACE, Err(_) => sys::LV2_State_Status_LV2_STATE_ERR_UNKNOWN, } } } #[cfg(test)] mod test { use crate::StateErr; #[test] fn test_state_conversion() { assert_eq!( Ok(()), StateErr::from(sys::LV2_State_Status_LV2_STATE_SUCCESS) ); assert_eq!( Err(StateErr::BadType), StateErr::from(sys::LV2_State_Status_LV2_STATE_ERR_BAD_TYPE) ); assert_eq!( Err(StateErr::BadFlags), StateErr::from(sys::LV2_State_Status_LV2_STATE_ERR_BAD_FLAGS) ); assert_eq!( Err(StateErr::NoFeature), StateErr::from(sys::LV2_State_Status_LV2_STATE_ERR_NO_FEATURE) ); assert_eq!( Err(StateErr::NoProperty), StateErr::from(sys::LV2_State_Status_LV2_STATE_ERR_NO_PROPERTY) ); assert_eq!( Err(StateErr::NoSpace), StateErr::from(sys::LV2_State_Status_LV2_STATE_ERR_NO_SPACE) ); assert_eq!( Err(StateErr::Unknown), StateErr::from(std::i32::MAX as sys::LV2_State_Status) ); assert_eq!( sys::LV2_State_Status_LV2_STATE_SUCCESS, StateErr::into(Ok(())) ); assert_eq!( sys::LV2_State_Status_LV2_STATE_ERR_BAD_TYPE, StateErr::into(Err(StateErr::BadType)) ); assert_eq!( sys::LV2_State_Status_LV2_STATE_ERR_BAD_FLAGS, StateErr::into(Err(StateErr::BadFlags)) ); assert_eq!( sys::LV2_State_Status_LV2_STATE_ERR_NO_FEATURE, StateErr::into(Err(StateErr::NoFeature)) ); assert_eq!( sys::LV2_State_Status_LV2_STATE_ERR_NO_PROPERTY, StateErr::into(Err(StateErr::NoProperty)) ); assert_eq!( sys::LV2_State_Status_LV2_STATE_ERR_NO_SPACE, StateErr::into(Err(StateErr::NoSpace)) ); assert_eq!( sys::LV2_State_Status_LV2_STATE_ERR_UNKNOWN, StateErr::into(Err(StateErr::Unknown)) ); } } lv2-state-2.0.0/src/path.rs010064400017500001750000000510551374457150200136220ustar 00000000000000//! 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`](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. //! //! 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. //! //! 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, //! 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, //! } //! //! // Plugin implementation omitted... //! # impl Plugin for Sampler { //! # type Ports = (); //! # type InitFeatures = Features<'static>; //! # type AudioFeatures = (); //! # //! # fn new(_: &PluginInfo, features: &mut Features<'static>) -> Option { //! # Some(Self { //! # sample: Vec::new(), //! # urids: features.uridMap.populate_collection()?, //! # }) //! # } //! # //! # fn run(&mut self, _: &mut (), _: &mut (), _: u32) {} //! # } //! //! 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`. use crate::StateErr; use lv2_core::feature::Feature; use lv2_core::prelude::*; use lv2_sys as sys; use std::ffi::*; use std::iter::once; use std::marker::PhantomData; use std::os::raw::c_char; use std::path::*; use std::rc::Rc; use std::sync::Mutex; use urid::*; /// A host feature that allocates absolute file paths. /// /// 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. /// /// Please take a look at the [module documentation](index.html) for a usage example. pub struct MakePath<'a> { handle: sys::LV2_State_Make_Path_Handle, function: unsafe extern "C" fn(sys::LV2_State_Make_Path_Handle, *const c_char) -> *mut c_char, lifetime: PhantomData<&'a mut c_void>, } unsafe impl<'a> UriBound for MakePath<'a> { const URI: &'static [u8] = sys::LV2_STATE__makePath; } unsafe impl<'a> Feature for MakePath<'a> { unsafe fn from_feature_ptr(feature: *const c_void, _: ThreadingClass) -> Option { (feature as *const sys::LV2_State_Make_Path) .as_ref() .and_then(|internal| { Some(Self { handle: internal.handle, function: internal.path?, lifetime: PhantomData, }) }) } } impl<'a> MakePath<'a> { fn relative_to_absolute_path(&mut self, relative_path: &Path) -> Result<&'a Path, StateErr> { let relative_path: Vec = relative_path .to_str() .ok_or(StateErr::PathNotUTF8)? .bytes() .chain(once(0)) .map(|b| b as c_char) .collect(); let absolute_path = unsafe { (self.function)(self.handle, relative_path.as_ptr()) }; if absolute_path.is_null() { return Err(StateErr::HostError); } unsafe { CStr::from_ptr(absolute_path) } .to_str() .map(Path::new) .map_err(|_| StateErr::HostError) } } /// A host feature that maps absolute file paths to and from abstract file paths. /// /// 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. /// /// Please take a look at the [module documentation](index.html) for a usage example. pub struct MapPath<'a> { handle: sys::LV2_State_Map_Path_Handle, abstract_path: unsafe extern "C" fn( sys::LV2_State_Map_Path_Handle, absolute_path: *const c_char, ) -> *mut c_char, absolute_path: unsafe extern "C" fn( sys::LV2_State_Map_Path_Handle, abstract_path: *const c_char, ) -> *mut c_char, lifetime: PhantomData<&'a mut c_void>, } unsafe impl<'a> UriBound for MapPath<'a> { const URI: &'static [u8] = sys::LV2_STATE__mapPath; } unsafe impl<'a> Feature for MapPath<'a> { unsafe fn from_feature_ptr(feature: *const c_void, _: ThreadingClass) -> Option { (feature as *const sys::LV2_State_Map_Path) .as_ref() .and_then(|internal| { Some(Self { handle: internal.handle, abstract_path: internal.abstract_path?, absolute_path: internal.absolute_path?, lifetime: PhantomData, }) }) } } impl<'a> MapPath<'a> { fn absolute_to_abstract_path(&mut self, path: &Path) -> Result<&'a str, StateErr> { let path: Vec = path .to_str() .ok_or(StateErr::PathNotUTF8)? .bytes() .chain(once(0)) .map(|b| b as c_char) .collect(); let path = unsafe { (self.abstract_path)(self.handle, path.as_ptr()) }; if path.is_null() { return Err(StateErr::HostError); } unsafe { CStr::from_ptr(path) } .to_str() .map_err(|_| StateErr::HostError) } fn abstract_to_absolute_path(&mut self, path: &str) -> Result<&'a Path, StateErr> { let path: Vec = path.bytes().chain(once(0)).map(|b| b as c_char).collect(); let path = unsafe { (self.absolute_path)(self.handle, path.as_ptr()) }; if path.is_null() { return Err(StateErr::HostError); } unsafe { CStr::from_ptr(path) } .to_str() .map(Path::new) .map_err(|_| StateErr::HostError) } } /// A host feature that deallocates absolute and abstract file paths. /// /// 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. /// /// Please take a look at the [module documentation](index.html) for a usage example. pub struct FreePath<'a> { handle: sys::LV2_State_Free_Path_Handle, free_path: unsafe extern "C" fn(sys::LV2_State_Free_Path_Handle, *mut c_char), lifetime: PhantomData<&'a mut c_void>, } unsafe impl<'a> UriBound for FreePath<'a> { const URI: &'static [u8] = sys::LV2_STATE__freePath; } unsafe impl<'a> Feature for FreePath<'a> { unsafe fn from_feature_ptr(feature: *const c_void, _: ThreadingClass) -> Option { (feature as *const sys::LV2_State_Free_Path) .as_ref() .and_then(|internal| { Some(Self { handle: internal.handle, free_path: internal.free_path?, lifetime: PhantomData, }) }) } } impl<'a> FreePath<'a> { fn free_path(&self, path: &str) { unsafe { (self.free_path)(self.handle, path.as_ptr() as *mut c_char) } } } /// A path that has been allocated by the host. /// /// An instance of this struct can be used just like a [`Path`](https://doc.rust-lang.org/stable/std/path/struct.Path.html) instance. /// /// 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>`, 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. pub struct ManagedPath<'a> { path: &'a Path, free_path: Rc>>, } impl<'a> std::ops::Deref for ManagedPath<'a> { type Target = Path; fn deref(&self) -> &Path { self.path } } impl<'a> AsRef for ManagedPath<'a> { fn as_ref(&self) -> &Path { self.path } } impl<'a> Drop for ManagedPath<'a> { fn drop(&mut self) { self.free_path .lock() .unwrap() .free_path(self.path.to_str().unwrap()) } } /// A string that has been allocated by the host. /// /// An instance of this struct can be used just like a [`str`](https://doc.rust-lang.org/stable/std/primitive.str.html) reference. /// /// 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>`, 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. pub struct ManagedStr<'a> { str: &'a str, free_path: Rc>>, } impl<'a> std::ops::Deref for ManagedStr<'a> { type Target = str; fn deref(&self) -> &str { self.str } } impl<'a> Drop for ManagedStr<'a> { fn drop(&mut self) { self.free_path.lock().unwrap().free_path(self.str) } } impl<'a> AsRef for ManagedStr<'a> { fn as_ref(&self) -> &str { self.str } } /// A safe interface to the path handling features. /// /// 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. /// /// Please take a look at the [module documentation](index.html) for a usage example. pub struct PathManager<'a> { make: MakePath<'a>, map: MapPath<'a>, free: Rc>>, } impl<'a> PathManager<'a> { /// Create a new path manager from the three path handling features. pub fn new(make: MakePath<'a>, map: MapPath<'a>, free: FreePath<'a>) -> Self { Self { make, map, free: Rc::new(Mutex::new(free)), } } /// Allocate a new path. /// /// 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. /// /// 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. /// /// 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. pub fn allocate_path( &mut self, relative_path: &Path, ) -> Result<(ManagedPath<'a>, ManagedStr<'a>), StateErr> { let absolute_path = self .make .relative_to_absolute_path(relative_path) .map(|path| ManagedPath { path, free_path: self.free.clone(), })?; let abstract_path = self .map .absolute_to_abstract_path(absolute_path.as_ref()) .map(|str| ManagedStr { str, free_path: self.free.clone(), })?; Ok((absolute_path, abstract_path)) } /// Map an abstract file path back to an absolute one. /// /// 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. pub fn deabstract_path(&mut self, path: &str) -> Result, StateErr> { self.map .abstract_to_absolute_path(path) .map(|path| ManagedPath { path, free_path: self.free.clone(), }) } } #[cfg(test)] mod tests { use crate::path::*; unsafe extern "C" fn make_path_impl( temp_dir: sys::LV2_State_Make_Path_Handle, relative_path: *const c_char, ) -> *mut c_char { let relative_path = match CStr::from_ptr(relative_path).to_str() { Ok(path) => path, _ => return std::ptr::null_mut(), }; let temp_dir = temp_dir as *const mktemp::Temp; let mut absolute_path = (*temp_dir).as_path().to_path_buf(); absolute_path.push(relative_path); CString::new(absolute_path.to_str().unwrap()) .map(CString::into_raw) .unwrap_or(std::ptr::null_mut()) } unsafe extern "C" fn abstract_path_impl( temp_dir: sys::LV2_State_Map_Path_Handle, absolute_path: *const c_char, ) -> *mut c_char { let absolute_path = match CStr::from_ptr(absolute_path).to_str() { Ok(path) => Path::new(path), _ => return std::ptr::null_mut(), }; let temp_dir = temp_dir as *const mktemp::Temp; let temp_dir = (*temp_dir).as_path(); let abstract_path = absolute_path.strip_prefix(temp_dir).unwrap(); CString::new(abstract_path.to_str().unwrap()) .map(CString::into_raw) .unwrap_or(std::ptr::null_mut()) } unsafe extern "C" fn free_path_impl( free_counter: sys::LV2_State_Free_Path_Handle, path: *mut c_char, ) { *(free_counter as *mut u32).as_mut().unwrap() += 1; CString::from_raw(path); } #[test] fn test_path() { let temp_dir = mktemp::Temp::new_dir().unwrap(); let make_path_feature = sys::LV2_State_Make_Path { handle: &temp_dir as *const _ as *mut c_void, path: Some(make_path_impl), }; let make_path = unsafe { MakePath::from_feature_ptr( &make_path_feature as *const _ as *const c_void, ThreadingClass::Other, ) } .unwrap(); let map_path_feature = sys::LV2_State_Map_Path { handle: &temp_dir as *const _ as *mut c_void, abstract_path: Some(abstract_path_impl), absolute_path: Some(make_path_impl), }; let map_path = unsafe { MapPath::from_feature_ptr( &map_path_feature as *const _ as *const c_void, ThreadingClass::Other, ) } .unwrap(); let mut free_counter: u32 = 0; let free_path_feature = sys::LV2_State_Free_Path { handle: &mut free_counter as *mut _ as *mut c_void, free_path: Some(free_path_impl), }; let free_path = unsafe { FreePath::from_feature_ptr( &free_path_feature as *const _ as *const c_void, ThreadingClass::Other, ) } .unwrap(); let mut manager = PathManager::new(make_path, map_path, free_path); let relative_path = Path::new("sample.wav"); let ref_absolute_path: PathBuf = [temp_dir.as_path(), relative_path].iter().collect(); { let (absolute_path, abstract_path) = manager.allocate_path(relative_path).unwrap(); assert_eq!(ref_absolute_path, &*absolute_path); assert_eq!(relative_path.to_str().unwrap(), &*abstract_path); let absolute_path = manager.deabstract_path(&abstract_path).unwrap(); assert_eq!(ref_absolute_path, &*absolute_path); } assert_eq!(free_counter, 3); } } lv2-state-2.0.0/src/raw.rs010064400017500001750000000265511374457150200134620ustar 00000000000000use crate::StateErr; use atom::prelude::*; use atom::space::*; use std::collections::HashMap; use std::ffi::c_void; use std::marker::PhantomData; use urid::*; /// Property storage handle. /// /// This handle can be used to store the properties of a plugin. It uses the atom system to encode the properties and is backed by a storage callback function. /// /// The written properties a buffered and flushed when requested. Create new properties by calling [`draft`](#method.draft) and write them like any other atom. Once you are done, you can commit your properties by calling [`commit_all`](#method.commit_all) or [`commit`](#method.commit). You have to commit manually: Uncommitted properties will be discarded when the handle is dropped. pub struct StoreHandle<'a> { properties: HashMap, store_fn: sys::LV2_State_Store_Function, handle: sys::LV2_State_Handle, lifetime: PhantomData<&'a mut c_void>, } impl<'a> StoreHandle<'a> { /// Create a new store handle. pub fn new(store_fn: sys::LV2_State_Store_Function, handle: sys::LV2_State_Handle) -> Self { StoreHandle { properties: HashMap::new(), store_fn, handle, lifetime: PhantomData, } } /// Draft a new property. /// /// This will return a new handle to create a property. Once the property is completely written, you can commit it by calling [`commit`](#method.commit) or [`commit_all`](#method.commit_all). Then, and only then, it will be saved by the host. /// /// If you began to write a property and don't want the written things to be stored, you can discard it with [`discard`](#method.discard) or [`discard_all`](#method.discard_all). pub fn draft(&mut self, property_key: URID) -> StatePropertyWriter { let property_key = property_key.into_general(); self.properties .insert(property_key.into_general(), SpaceElement::default()); StatePropertyWriter::new(SpaceHead::new( self.properties .get_mut(&property_key.into_general()) .unwrap(), )) } /// Internal helper function to store a property. pub fn commit_pair( store_fn: sys::LV2_State_Store_Function, handle: sys::LV2_State_Handle, key: URID, space: SpaceElement, ) -> Result<(), StateErr> { let store_fn = store_fn.ok_or(StateErr::BadCallback)?; let space: Vec = space.to_vec(); let space = Space::from_slice(space.as_ref()); let (header, data) = space .split_type::() .ok_or(StateErr::BadData)?; let data = data .split_raw(header.size as usize) .map(|(data, _)| data) .ok_or(StateErr::BadData)?; let key = key.get(); let data_ptr = data as *const _ as *const c_void; let data_size = header.size as usize; let data_type = header.type_; let flags: u32 = (sys::LV2_State_Flags::LV2_STATE_IS_POD | sys::LV2_State_Flags::LV2_STATE_IS_PORTABLE) .into(); StateErr::from(unsafe { (store_fn)(handle, key, data_ptr, data_size, data_type, flags) }) } /// Commit all created properties. /// /// This will also clear the property buffer. pub fn commit_all(&mut self) -> Result<(), StateErr> { for (key, space) in self.properties.drain() { Self::commit_pair(self.store_fn, self.handle, key, space)?; } Ok(()) } /// Commit one specific property. /// /// This method returns `None` if the requested property was not marked for commit, `Some(Ok(()))` if the property was stored and `Some(Err(_))` if an error occured while storing the property. pub fn commit(&mut self, key: URID) -> Option> { let key = key.into_general(); let space = self.properties.remove(&key)?; Some(Self::commit_pair(self.store_fn, self.handle, key, space)) } /// Discard all drafted properties. pub fn discard_all(&mut self) { self.properties.clear(); } /// Discard a drafted property. /// /// If no property with the given key was drafted before, this is a no-op. pub fn discard(&mut self, key: URID) { self.properties.remove(&key.into_general()); } } /// Writing handle for properties. pub struct StatePropertyWriter<'a> { head: SpaceHead<'a>, initialized: bool, } impl<'a> StatePropertyWriter<'a> { /// Create a new property writer that uses the given space head. pub fn new(head: SpaceHead<'a>) -> Self { Self { head, initialized: false, } } /// Initialize the property. /// /// This works like any other atom writer: You have to provide the URID of the atom type you want to write, as well as the type-specific parameter. If the property hasn't been initialized before, it will be initialized and the writing handle is returned. Otherwise, `Err(StateErr::Unknown)` is returned. pub fn init<'b, A: Atom<'a, 'b>>( &'b mut self, urid: URID, parameter: A::WriteParameter, ) -> Result { if !self.initialized { self.initialized = true; (&mut self.head as &mut dyn MutSpace) .init(urid, parameter) .ok_or(StateErr::Unknown) } else { Err(StateErr::Unknown) } } } /// Property retrieval handle. pub struct RetrieveHandle<'a> { retrieve_fn: sys::LV2_State_Retrieve_Function, handle: sys::LV2_State_Handle, lifetime: PhantomData<&'a mut c_void>, } impl<'a> RetrieveHandle<'a> { /// Create a new retrieval handle that uses the given callback function and handle. pub fn new( retrieve_fn: sys::LV2_State_Retrieve_Function, handle: sys::LV2_State_Handle, ) -> Self { RetrieveHandle { retrieve_fn, handle, lifetime: PhantomData, } } /// Try to retrieve a property from the host. /// /// This method calls the internal retrieve callback with the given URID. If there's no property with the given URID, `Err(StateErr::NoProperty)` is returned. Otherwise, a reading handle is returned that contains the type and the data of the property and can interpret it as an atom. pub fn retrieve(&self, key: URID) -> Result { let mut size: usize = 0; let mut type_: u32 = 0; let property_ptr: *const std::ffi::c_void = unsafe { (self.retrieve_fn.ok_or(StateErr::BadCallback)?)( self.handle, key.get(), &mut size, &mut type_, std::ptr::null_mut(), ) }; let type_ = URID::new(type_).ok_or(StateErr::Unknown)?; let space = if !property_ptr.is_null() { unsafe { std::slice::from_raw_parts(property_ptr as *const u8, size) } } else { return Err(StateErr::NoProperty); }; Ok(StatePropertyReader::new(type_, Space::from_slice(space))) } } /// Reading handle for properties. /// /// This handle contains the type and the data of a property retrieved from the [`RetrieveHandle`](struct.RetrieveHandle.html). pub struct StatePropertyReader<'a> { type_: URID, body: Space<'a>, } impl<'a> StatePropertyReader<'a> { /// Create a new reading handle with the given type and data. pub fn new(type_: URID, body: Space<'a>) -> Self { Self { type_: type_.into_general(), body, } } /// Return the type of the property. pub fn type_(&self) -> URID { self.type_ } /// Return the data of the property. pub fn body(&self) -> Space { self.body } /// Try to interpret the property as an atom. /// /// This works like any atom reader: You pass the URID of the atom type as well as the type-specific argument, and if the desired type is the actual type of the data, a read handle is returned. /// /// If the desired and actual data types don't match, `Err(StateErr::BadType)` is returned. pub fn read>( &self, urid: URID, parameter: A::ReadParameter, ) -> Result { if urid == self.type_ { A::read(self.body, parameter).ok_or(StateErr::Unknown) } else { Err(StateErr::BadType) } } } #[cfg(test)] mod tests { use crate::raw::*; use crate::storage::Storage; use atom::space::Space; fn store(storage: &mut Storage, urids: &AtomURIDCollection) { let mut store_handle = storage.store_handle(); store_handle .draft(URID::new(1).unwrap()) .init(urids.int, 17) .unwrap(); store_handle .draft(URID::new(2).unwrap()) .init(urids.float, 1.0) .unwrap(); store_handle.commit(URID::new(1).unwrap()).unwrap().unwrap(); let mut vector_writer = store_handle.draft(URID::new(3).unwrap()); let mut vector_writer = vector_writer.init(urids.vector(), urids.int).unwrap(); vector_writer.append(&[1, 2, 3, 4]).unwrap(); store_handle.commit_all().unwrap(); store_handle .draft(URID::new(4).unwrap()) .init(urids.int, 0) .unwrap(); } fn retrieve(storage: &mut Storage, urids: &AtomURIDCollection) { let retrieve_handle = storage.retrieve_handle(); assert_eq!( 17, retrieve_handle .retrieve(URID::new(1).unwrap()) .unwrap() .read(urids.int, ()) .unwrap() ); assert_eq!( 1.0, retrieve_handle .retrieve(URID::new(2).unwrap()) .unwrap() .read(urids.float, ()) .unwrap() ); assert_eq!( [1, 2, 3, 4], retrieve_handle .retrieve(URID::new(3).unwrap()) .unwrap() .read(urids.vector(), urids.int) .unwrap() ); assert!(retrieve_handle.retrieve(URID::new(4).unwrap()).is_err()); } #[test] fn test_storage() { let map = HashURIDMapper::new(); let urids = AtomURIDCollection::from_map(&map).unwrap(); let mut storage = Storage::default(); store(&mut storage, &urids); for (key, (type_, value)) in storage.iter() { match key.get() { 1 => { assert_eq!(urids.int, *type_); assert_eq!(17, unsafe { *(value.as_slice() as *const _ as *const i32) }); } 2 => { assert_eq!(urids.float, *type_); assert_eq!(1.0, unsafe { *(value.as_slice() as *const _ as *const f32) }); } 3 => { assert_eq!(urids.vector::(), *type_); let space = Space::from_slice(value.as_slice()); let data = Vector::read(space, urids.int).unwrap(); assert_eq!([1, 2, 3, 4], data); } _ => panic!("Invalid key!"), } } retrieve(&mut storage, &urids); } } lv2-state-2.0.0/src/storage.rs010064400017500001750000000100561374457150200143260ustar 00000000000000use crate::raw::{RetrieveHandle, StoreHandle}; use std::collections::HashMap; use std::ffi::c_void; use std::ops::{Deref, DerefMut}; use urid::*; /// A simple property store. /// /// This is mostly used to test this crate, but can be used to store properties too. It contains a map from property URIDs to a tuple of a type URID and a vector of bytes. You can access this map by dereferencing the storage. /// /// You can also directly create [`StoreHandle`s](struct.StoreHandle.html) and [`RetrieveHandle`s](struct.RetrieveHandle.html) that access the storage. pub struct Storage { items: HashMap)>, } impl Default for Storage { fn default() -> Self { Self { items: HashMap::new(), } } } impl Storage { /// Store a property. pub fn store(&mut self, key: URID, type_: URID, value: &[u8]) { self.items .insert(key.into_general(), (type_.into_general(), value.to_owned())); } /// External version of [`store`](#method.store). /// /// This function has the appropriate signature to be used as a storage callback. /// /// # Safety /// /// This method is unsafe since it dereferences raw pointers. /// /// The `handle` has to be a pointer to a `Storage` instance and `value` must point to a slice of bytes with the length of `size`. pub unsafe extern "C" fn extern_store( handle: sys::LV2_State_Handle, key: u32, value: *const c_void, size: usize, type_: u32, _: u32, ) -> sys::LV2_State_Status { let handle = (handle as *mut Self).as_mut().unwrap(); let key = URID::new(key).unwrap(); let value = std::slice::from_raw_parts(value as *const u8, size); let type_ = URID::new(type_).unwrap(); handle.store(key, type_, value); sys::LV2_State_Status_LV2_STATE_SUCCESS } /// Create a `StoreHandle` that saves it's properties to this storage. pub fn store_handle(&mut self) -> StoreHandle { StoreHandle::new(Some(Self::extern_store), self as *mut Self as *mut c_void) } /// Try to retrieve a property. /// /// If the property doesn't exist, `None` is returned. pub fn retrieve(&self, key: URID) -> Option<(URID, &[u8])> { self.items .get(&key.into_general()) .map(|(urid, data)| (*urid, data.as_ref())) } /// External version of [`retrieve`](#method.retrieve). /// /// This function has the appropriate signature to be used as a storage callback. /// /// # Safety /// /// This method is unsafe since it dereferences raw pointers. /// /// The `handle` has to be a pointer to a `Storage` instance and `size`, `type_` and `flags` must be valid pointers to instances of their respective types. pub unsafe extern "C" fn extern_retrieve( handle: sys::LV2_State_Handle, key: u32, size: *mut usize, type_: *mut u32, flags: *mut u32, ) -> *const c_void { if !flags.is_null() { *flags = (sys::LV2_State_Flags::LV2_STATE_IS_POD | sys::LV2_State_Flags::LV2_STATE_IS_PORTABLE) .into(); } let handle = (handle as *mut Self).as_mut().unwrap(); let key = URID::new(key).unwrap(); if let Some((type_urid, data)) = handle.retrieve(key) { *size = data.len(); *type_ = type_urid.get(); data.as_ptr() as *const c_void } else { std::ptr::null() } } /// Create a `RetrieveHandle` that retrieves the properties from this storage. pub fn retrieve_handle(&mut self) -> RetrieveHandle { RetrieveHandle::new( Some(Self::extern_retrieve), self as *mut Self as *mut c_void, ) } } impl Deref for Storage { type Target = HashMap)>; fn deref(&self) -> &Self::Target { &self.items } } impl DerefMut for Storage { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items } } lv2-state-2.0.0/tests/integration.rs010064400017500001750000000105601374457150200155600ustar 00000000000000use lv2_atom::prelude::*; use lv2_core::feature::{FeatureCollection, MissingFeatureError}; use lv2_core::prelude::*; use lv2_state::*; use lv2_urid::*; use std::path::Path; use std::pin::Pin; use urid::*; struct Stateful { internal: f32, audio: Vec, urids: AtomURIDCollection, } #[derive(FeatureCollection)] pub struct Features<'a> { map: LV2Map<'a>, } unsafe impl UriBound for Stateful { const URI: &'static [u8] = b"urn:lv2_atom:stateful\0"; } impl Plugin for Stateful { type Ports = (); type InitFeatures = Features<'static>; type AudioFeatures = (); fn new(_plugin_info: &PluginInfo, features: &mut Features<'static>) -> Option { Some(Stateful { internal: 42.0, audio: Vec::new(), urids: features.map.populate_collection()?, }) } fn run(&mut self, _: &mut (), _: &mut (), _: u32) { self.internal = 17.0; self.audio.extend((0..32).map(|f| f as f32)); } fn extension_data(uri: &Uri) -> Option<&'static dyn std::any::Any> { match_extensions!(uri, StateDescriptor) } } impl State for Stateful { type StateFeatures = (); fn save(&self, mut store: StoreHandle, _: ()) -> Result<(), StateErr> { store .draft(URID::new(1000).unwrap()) .init(self.urids.float, self.internal)?; store .draft(URID::new(1001).unwrap()) .init(self.urids.vector(), self.urids.float)? .append(self.audio.as_ref()); store.commit_all() } fn restore(&mut self, store: RetrieveHandle, _: ()) -> Result<(), StateErr> { self.internal = store .retrieve(URID::new(1000).unwrap())? .read(self.urids.float, ())?; self.audio = Vec::from( store .retrieve(URID::new(1001).unwrap())? .read(self.urids.vector(), self.urids.float)?, ); Ok(()) } } lv2_descriptors! { Stateful } fn create_plugin(mapper: Pin<&mut HostMap>) -> Stateful { let plugin = { // Faking the map's lifetime. let interface = mapper.make_map_interface(); let interface = &interface as *const lv2_sys::LV2_URID_Map; let interface = unsafe { interface.as_ref().unwrap() }; let map = LV2Map::new(interface); // Constructing the plugin. Stateful::new( &PluginInfo::new(Stateful::uri(), Path::new("./"), 44100.0), &mut Features { map: map }, ) .unwrap() }; assert_eq!(42.0, plugin.internal); assert_eq!(0, plugin.audio.len()); plugin } #[test] fn test_save_n_restore() { let mut mapper: Pin>> = Box::pin(HashURIDMapper::new().into()); let mut storage = lv2_state::Storage::default(); let (store_fn, restore_fn) = unsafe { let extension_data_fn = lv2_descriptor(0).as_ref().unwrap().extension_data; let uri = lv2_sys::LV2_STATE__interface.as_ptr() as *const i8; let extension = ((extension_data_fn.unwrap())(uri) as *const lv2_sys::LV2_State_Interface) .as_ref() .unwrap(); (extension.save.unwrap(), extension.restore.unwrap()) }; assert!(store_fn == StateDescriptor::::extern_save); assert!(restore_fn == StateDescriptor::::extern_restore); let mut first_plugin = create_plugin(mapper.as_mut()); first_plugin.run(&mut (), &mut (), 32); assert_eq!(17.0, first_plugin.internal); assert_eq!(32, first_plugin.audio.len()); unsafe { (store_fn)( &mut first_plugin as *mut Stateful as lv2_sys::LV2_Handle, Some(lv2_state::Storage::extern_store), &mut storage as *mut lv2_state::Storage as lv2_sys::LV2_State_Handle, lv2_sys::LV2_State_Flags::LV2_STATE_IS_POD.into(), std::ptr::null_mut(), ) }; let mut second_plugin = create_plugin(mapper.as_mut()); unsafe { (restore_fn)( &mut second_plugin as *mut Stateful as lv2_sys::LV2_Handle, Some(lv2_state::Storage::extern_retrieve), &mut storage as *mut lv2_state::Storage as lv2_sys::LV2_State_Handle, lv2_sys::LV2_State_Flags::LV2_STATE_IS_POD.into(), std::ptr::null_mut(), ) }; assert_eq!(17.0, second_plugin.internal); assert_eq!(32, second_plugin.audio.len()); }