Use new Lilv state API to save LV2 plugin state.
authorDavid Robillard <d@drobilla.net>
Sat, 28 Jan 2012 01:45:15 +0000 (01:45 +0000)
committerDavid Robillard <d@drobilla.net>
Sat, 28 Jan 2012 01:45:15 +0000 (01:45 +0000)
This saves a complete history of plugin state, i.e. save is no longer destructive.  However, data is shared as much as possible, and new state is only written if the plugin state has actually changed.  There is exactly one link in the entire session directory to any external file, so archiving will work with minimal copying.

Not sure sure about the naming of the "externals" directory, but I have nothing better...

git-svn-id: svn://localhost/ardour2/branches/3.0@11372 d708f5d6-7413-0410-9779-e7cbd77b26cf

libs/ardour/ardour/lv2_plugin.h
libs/ardour/ardour/lv2_state.h [deleted file]
libs/ardour/ardour/session.h
libs/ardour/ardour/uri_map.h
libs/ardour/lv2/lv2plug.in/ns/ext/state/state.h
libs/ardour/lv2_plugin.cc
libs/ardour/session_state.cc
libs/ardour/wscript
wscript

index a627c2eb43e7705f02ddc3de701077e9e05bdbbe..6d68ca2674e279e9b21f9a4f4e95c25d961cb908 100644 (file)
@@ -120,16 +120,16 @@ class LV2Plugin : public ARDOUR::Plugin
 
   private:
        struct Impl;
-       Impl*             _impl;
-       void*             _module;
-       LV2_Feature**     _features;
-       framecnt_t        _sample_rate;
-       float*            _control_data;
-       float*            _shadow_data;
-       float*            _defaults;
-       float*            _latency_control_port;
-       bool              _was_activated;
-       bool              _has_state_interface;
+       Impl*         _impl;
+       void*         _module;
+       LV2_Feature** _features;
+       framecnt_t    _sample_rate;
+       float*        _control_data;
+       float*        _shadow_data;
+       float*        _defaults;
+       float*        _latency_control_port;
+       PBD::ID       _insert_id;
+
        std::vector<bool> _port_is_input;
        std::vector<bool> _port_is_output;
        std::vector<bool> _port_is_midi;
@@ -138,8 +138,6 @@ class LV2Plugin : public ARDOUR::Plugin
 
        std::map<std::string,uint32_t> _port_indices;
 
-       PBD::ID _insert_id;
-
        typedef struct {
                const void* (*extension_data) (const char* uri);
        } LV2_DataAccess;
@@ -147,34 +145,21 @@ class LV2Plugin : public ARDOUR::Plugin
        LV2_DataAccess _data_access_extension_data;
        LV2_Feature    _data_access_feature;
        LV2_Feature    _instance_access_feature;
-       LV2_Feature    _map_path_feature;
        LV2_Feature    _make_path_feature;
 
+       mutable unsigned _state_version;
+
+       bool _was_activated;
+       bool _has_state_interface;
+
        static URIMap   _uri_map;
        static uint32_t _midi_event_type;
        static uint32_t _state_path_type;
 
-       const std::string state_dir () const;
-
-       static int
-       lv2_state_store_callback (void*       handle,
-                                 uint32_t    key,
-                                 const void* value,
-                                 size_t      size,
-                                 uint32_t    type,
-                                 uint32_t    flags);
-
-       static const void*
-       lv2_state_retrieve_callback (void*     handle,
-                                    uint32_t  key,
-                                    size_t*   size,
-                                    uint32_t* type,
-                                    uint32_t* flags);
-
-       static char* lv2_state_abstract_path (void*       host_data,
-                                             const char* absolute_path);
-       static char* lv2_state_absolute_path (void*       host_data,
-                                             const char* abstract_path);
+       const std::string scratch_dir () const;
+       const std::string file_dir () const;
+       const std::string state_dir (unsigned num) const;
+
        static char* lv2_state_make_path (void*       host_data,
                                          const char* path);
 
diff --git a/libs/ardour/ardour/lv2_state.h b/libs/ardour/ardour/lv2_state.h
deleted file mode 100644 (file)
index 6c37ebc..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
-  Copyright (C) 2011 Paul Davis
-  Author: David Robillard
-
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-*/
-
-#ifndef __ardour_lv2_state_h__
-#define __ardour_lv2_state_h__
-
-#include <stdint.h>
-#include <stdlib.h>
-
-#include <map>
-#include <string>
-
-#include "pbd/error.h"
-
-#include "ardour/uri_map.h"
-#include "lv2/lv2plug.in/ns/ext/state/state.h"
-#include "rdff.h"
-
-namespace ARDOUR {
-
-class LV2Plugin;
-
-struct LV2State {
-       LV2State(const LV2Plugin& plug, URIMap& map) : plugin(plug), uri_map(map) {}
-
-       struct Value {
-               inline Value(uint32_t k, const void* v, size_t s, uint32_t t, uint32_t f)
-                       : key(k), value(v), size(s), type(t), flags(f)
-               {}
-
-               const uint32_t key;
-               const void*    value;
-               const size_t   size;
-               const uint32_t type;
-               const uint32_t flags;
-       };
-
-       typedef std::map<uint32_t, std::string> URIs;
-       typedef std::map<uint32_t, Value>       Values;
-
-       uint32_t file_id_to_runtime_id(uint32_t file_id) const {
-               URIs::const_iterator i = uris.find(file_id);
-               if (i == uris.end()) {
-                       PBD::error << "LV2 state refers to undefined URI ID" << endmsg;
-                       return 0;
-               }
-               return uri_map.uri_to_id(NULL, i->second.c_str());
-       }
-
-       int add_uri(uint32_t file_id, const char* str) {
-               // TODO: check for clashes (invalid file)
-               uris.insert(std::make_pair(file_id, str));
-               return 0;
-       }
-
-       int add_value(uint32_t    file_key,
-                     const void* value,
-                     size_t      size,
-                     uint32_t    file_type,
-                     uint32_t    flags) {
-               const uint32_t key  = file_id_to_runtime_id(file_key);
-               const uint32_t type = file_id_to_runtime_id(file_type);
-               if (!key || !type) {
-                       PBD::error << "Invalid file key or type" << endmsg;
-                       return 1;
-               }
-
-               Values::const_iterator i = values.find(key);
-               if (i != values.end()) {
-                       PBD::error << "LV2 state contains duplicate keys" << endmsg;
-                       return 1;
-               } else {
-                       void* value_copy = malloc(size);
-                       memcpy(value_copy, value, size); // FIXME: leak
-                       values.insert(
-                               std::make_pair(key,
-                                              Value(key, value_copy, size, type, flags)));
-                       return 0;
-               }
-       }
-
-       void read(RDFF file) {
-               RDFFChunk* chunk = (RDFFChunk*)malloc(sizeof(RDFFChunk));
-               chunk->size = 0;
-               while (!rdff_read_chunk(file, &chunk)) {
-                       if (rdff_chunk_is_uri(chunk)) {
-                               RDFFURIChunk* body = (RDFFURIChunk*)chunk->data;
-                               add_uri(body->id, body->uri);
-                       } else if (rdff_chunk_is_triple(chunk)) {
-                               RDFFTripleChunk* body = (RDFFTripleChunk*)chunk->data;
-                               add_value(body->predicate,
-                                         body->object,
-                                         body->object_size,
-                                         body->object_type,
-                                         LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
-                       }
-               }
-               free(chunk);
-       }
-
-       void write(RDFF file) {
-               // Write all referenced URIs to state file
-               for (URIs::const_iterator i = uris.begin(); i != uris.end(); ++i) {
-                       rdff_write_uri(file,
-                                      i->first,
-                                      i->second.length(),
-                                      i->second.c_str());
-               }
-
-               // Write all values to state file
-               for (Values::const_iterator i = values.begin(); i != values.end(); ++i) {
-                       const uint32_t         key = i->first;
-                       const LV2State::Value& val = i->second;
-                       rdff_write_triple(file,
-                                         0,
-                                         key,
-                                         val.type,
-                                         val.size,
-                                         val.value);
-               }
-       }
-
-       const LV2Plugin& plugin;
-       URIMap&          uri_map;
-       URIs             uris;
-       Values           values;
-};
-
-} // namespace ARDOUR
-
-#endif /* __ardour_lv2_state_h__ */
index 823e5e4bbc2d501ef56674ff7c906044f37ab665..e702612338feefc55e8b46cd15c7a47c063dc005 100644 (file)
@@ -182,9 +182,10 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
 
        int ensure_subdirs ();
 
-       std::string automation_dir () const;
-       std::string analysis_dir() const;
-       std::string plugins_dir() const;
+       std::string automation_dir () const;  ///< Automation data
+       std::string analysis_dir () const;    ///< Analysis data
+       std::string plugins_dir () const;     ///< Plugin state
+       std::string externals_dir () const;   ///< Links to external files
 
        std::string peak_path (std::string) const;
 
index 3fb8560022f71d6479063a9cbcfe69e7ba7012fa..a6c9dc7a8b3a39d53c07a73a2e69840237166954 100644 (file)
@@ -41,11 +41,12 @@ public:
        LV2_Feature* urid_map_feature()   { return &_urid_map_feature; }
        LV2_Feature* urid_unmap_feature() { return &_urid_unmap_feature; }
 
-       uint32_t uri_to_id(const char* map,
-                          const char* uri);
+       LV2_URID_Map*   urid_map()   { return &_urid_map_feature_data; }
+       LV2_URID_Unmap* urid_unmap() { return &_urid_unmap_feature_data; }
 
-       const char* id_to_uri(const char* map,
-                             uint32_t    id);
+       uint32_t uri_to_id(const char* map, const char* uri);
+
+       const char* id_to_uri(const char* map, uint32_t id);
 
 private:
        static uint32_t uri_map_uri_to_id(LV2_URI_Map_Callback_Data callback_data,
@@ -64,12 +65,12 @@ private:
        EventToGlobal _event_to_global;
        GlobalToEvent _global_to_event;
 
-       LV2_Feature           _uri_map_feature;
-       LV2_URI_Map_Feature   _uri_map_feature_data;
-       LV2_Feature           _urid_map_feature;
-       LV2_URID_Map          _urid_map_feature_data;
-       LV2_Feature           _urid_unmap_feature;
-       LV2_URID_Unmap        _urid_unmap_feature_data;
+       LV2_Feature         _uri_map_feature;
+       LV2_URI_Map_Feature _uri_map_feature_data;
+       LV2_Feature         _urid_map_feature;
+       LV2_URID_Map        _urid_map_feature_data;
+       LV2_Feature         _urid_unmap_feature;
+       LV2_URID_Unmap      _urid_unmap_feature_data;
 };
 
 
index 3d390120d76018190cc3b79d5dc318210749deba..bbb8fad5b597da307e3617c6582d4900417c3059 100644 (file)
@@ -37,8 +37,8 @@ extern "C" {
 
 #define LV2_STATE_INTERFACE_URI LV2_STATE_URI "#Interface"
 #define LV2_STATE_PATH_URI      LV2_STATE_URI "#Path"
-#define LV2_STATE_MAP_PATH_URI  LV2_STATE_URI "#pathMap"
-#define LV2_STATE_MAKE_PATH_URI LV2_STATE_URI "#newPath"
+#define LV2_STATE_MAP_PATH_URI  LV2_STATE_URI "#mapPath"
+#define LV2_STATE_MAKE_PATH_URI LV2_STATE_URI "#makePath"
 
 typedef void* LV2_State_Handle;
 typedef void* LV2_State_Map_Path_Handle;
@@ -55,7 +55,7 @@ typedef enum {
        /**
           Plain Old Data.
 
-          Values with this flag contain no references to non-stateent or
+          Values with this flag contain no references to non-persistent or
           non-global resources (e.g. pointers, handles, local paths, etc.). It is
           safe to copy POD values with a simple memcpy and store them for use at
           any time in the future on a machine with a compatible architecture
@@ -221,7 +221,6 @@ typedef struct _LV2_State_Interface {
                     uint32_t                   flags,
                     const LV2_Feature *const * features);
 
-
        /**
           Restore plugin state using a host-provided @c retrieve callback.
 
@@ -257,7 +256,7 @@ typedef struct _LV2_State_Interface {
 } LV2_State_Interface;
 
 /**
-   Feature data for state:pathMap (LV2_STATE_MAP_PATH_URI).
+   Feature data for state:mapPath (LV2_STATE_MAP_PATH_URI).
 */
 typedef struct {
 
index 2e9f49c4e7e63d2e5163423b606e345956ad7035..b645f8bfa8c28de047ee5729a12389f9f2434b33 100644 (file)
@@ -15,7 +15,6 @@
     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
 */
 
 #include <string>
@@ -44,7 +43,6 @@
 #include "ardour/debug.h"
 #include "ardour/lv2_event_buffer.h"
 #include "ardour/lv2_plugin.h"
-#include "ardour/lv2_state.h"
 #include "ardour/session.h"
 
 #include "i18n.h"
 #endif
 
 #define NS_DC      "http://dublincore.org/documents/dcmi-namespace/"
-#define NS_LV2     "http://lv2plug.in/ns/lv2core#"
 #define NS_OLDPSET "http://lv2plug.in/ns/dev/presets#"
 #define NS_PSET    "http://lv2plug.in/ns/ext/presets#"
 #define NS_UI      "http://lv2plug.in/ns/extensions/ui#"
-#define NS_RDFS    "http://www.w3.org/2000/01/rdf-schema#"
 
 using namespace std;
 using namespace ARDOUR;
@@ -100,13 +96,20 @@ public:
 static LV2World _world;
 
 struct LV2Plugin::Impl {
-       Impl() : plugin(0), ui(0), ui_type(0), name(0), author(0), instance(0) {}
+       Impl() : plugin(0), ui(0), ui_type(0), name(0), author(0), instance(0)
+#ifdef HAVE_NEW_LILV
+              , state(0)
+#endif
+       {}
        LilvPlugin*     plugin;
        const LilvUI*   ui;
        const LilvNode* ui_type;
        LilvNode*       name;
        LilvNode*       author;
        LilvInstance*   instance;
+#ifdef HAVE_NEW_LILV
+       LilvState*      state;
+#endif
 };
 
 LV2Plugin::LV2Plugin (AudioEngine& engine,
@@ -146,46 +149,36 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate)
        _control_data         = 0;
        _shadow_data          = 0;
        _latency_control_port = 0;
+       _state_version        = 0;
        _was_activated        = false;
-
+       _has_state_interface  = false;
+       
        _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access";
        _data_access_feature.URI     = "http://lv2plug.in/ns/ext/data-access";
-       _map_path_feature.URI        = LV2_STATE_MAP_PATH_URI;
        _make_path_feature.URI       = LV2_STATE_MAKE_PATH_URI;
 
        LilvPlugin* plugin = _impl->plugin;
 
+#ifdef HAVE_NEW_LILV
        LilvNode* state_iface_uri = lilv_new_uri(_world.world, LV2_STATE_INTERFACE_URI);
-#ifdef lilv_plugin_has_extension_data
-       _has_state_interface = lilv_plugin_has_extension_data(plugin, state_iface_uri);
+       LilvNode* state_uri       = lilv_new_uri(_world.world, LV2_STATE_URI);
+       _has_state_interface =
+               // What plugins should have (lv2:extensionData state:Interface)
+               lilv_plugin_has_extension_data(plugin, state_iface_uri)
+               // What some outdated/incorrect ones have
+               || lilv_plugin_has_feature(plugin, state_uri);
+       lilv_node_free(state_uri);
        lilv_node_free(state_iface_uri);
-#else
-       LilvNode* lv2_extensionData = lilv_new_uri(_world.world, NS_LV2 "extensionData");
-       LilvNodes* extdatas = lilv_plugin_get_value(plugin, lv2_extensionData);
-       LILV_FOREACH(nodes, i, extdatas) {
-               if (lilv_node_equals(lilv_nodes_get(extdatas, i), state_iface_uri)) {
-                       _has_state_interface = true;
-                       break;
-               }
-       }
 #endif
 
-       _features    = (LV2_Feature**)malloc(sizeof(LV2_Feature*) * 8);
+       _features    = (LV2_Feature**)malloc(sizeof(LV2_Feature*) * 7);
        _features[0] = &_instance_access_feature;
        _features[1] = &_data_access_feature;
-       _features[2] = &_map_path_feature;
-       _features[3] = &_make_path_feature;
-       _features[4] = _uri_map.uri_map_feature();
-       _features[5] = _uri_map.urid_map_feature();
-       _features[6] = _uri_map.urid_unmap_feature();
-       _features[7] = NULL;
-
-       LV2_State_Map_Path* map_path = (LV2_State_Map_Path*)malloc(
-               sizeof(LV2_State_Map_Path));
-       map_path->handle = this;
-       map_path->abstract_path = &lv2_state_abstract_path;
-       map_path->absolute_path = &lv2_state_absolute_path;
-       _map_path_feature.data = map_path;
+       _features[2] = &_make_path_feature;
+       _features[3] = _uri_map.uri_map_feature();
+       _features[4] = _uri_map.urid_map_feature();
+       _features[5] = _uri_map.urid_unmap_feature();
+       _features[6] = NULL;
 
        LV2_State_Make_Path* make_path = (LV2_State_Make_Path*)malloc(
                sizeof(LV2_State_Make_Path));
@@ -467,148 +460,56 @@ LV2Plugin::c_ui_type ()
        return (void*)_impl->ui_type;
 }
 
+/** Directory for files created by the plugin (except during save). */
 const std::string
-LV2Plugin::state_dir() const
+LV2Plugin::scratch_dir() const
 {
-       return Glib::build_filename(_session.plugins_dir(),
-                                   _insert_id.to_s());
-}
-
-int
-LV2Plugin::lv2_state_store_callback(LV2_State_Handle handle,
-                                    uint32_t         key,
-                                    const void*      value,
-                                    size_t           size,
-                                    uint32_t         type,
-                                    uint32_t         flags)
-{
-       DEBUG_TRACE(DEBUG::LV2, string_compose(
-                           "state store %1 (size: %2, type: %3, flags: %4)\n",
-                           _uri_map.id_to_uri(NULL, key),
-                           size,
-                           _uri_map.id_to_uri(NULL, type),
-                           flags));
-
-       LV2State* state = (LV2State*)handle;
-       state->add_uri(key,  _uri_map.id_to_uri(NULL, key));
-       state->add_uri(type, _uri_map.id_to_uri(NULL, type));
-
-       if (type == _state_path_type) {
-               const LV2Plugin&    me = state->plugin;
-               LV2_State_Map_Path* mp = (LV2_State_Map_Path*)me._map_path_feature.data;
-               return state->add_value(
-                       key,
-                       lv2_state_abstract_path(mp->handle, (const char*)value),
-                       size, type, flags);
-       } else if ((flags & LV2_STATE_IS_POD) && (flags & LV2_STATE_IS_PORTABLE)) {
-               return state->add_value(key, value, size, type, flags);
-       } else {
-               PBD::warning << "LV2 plugin attempted to store non-portable property." << endl;
-               return -1;
-       }
+       return Glib::build_filename(
+               _session.plugins_dir(), _insert_id.to_s(), "scratch");
 }
 
-const void*
-LV2Plugin::lv2_state_retrieve_callback(LV2_State_Handle host_data,
-                                       uint32_t         key,
-                                       size_t*          size,
-                                       uint32_t*        type,
-                                       uint32_t*        flags)
+/** Directory for snapshots of files in the scratch directory. */
+const std::string
+LV2Plugin::file_dir() const
 {
-       LV2State* state = (LV2State*)host_data;
-       LV2State::Values::const_iterator i = state->values.find(key);
-       if (i == state->values.end()) {
-               warning << "LV2 plugin attempted to retrieve nonexistent key: "
-                       << _uri_map.id_to_uri(NULL, key) << endmsg;
-               return NULL;
-       }
-       *size  = i->second.size;
-       *type  = i->second.type;
-       *flags = LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE;
-       DEBUG_TRACE(DEBUG::LV2, string_compose(
-                           "state retrieve %1 = %2 (size: %3, type: %4)\n",
-                           _uri_map.id_to_uri(NULL, key),
-                           i->second.value, *size, *type));
-       if (*type == _state_path_type) {
-               const LV2Plugin&    me = state->plugin;
-               LV2_State_Map_Path* mp = (LV2_State_Map_Path*)me._map_path_feature.data;
-               return lv2_state_absolute_path(mp->handle, (const char*)i->second.value);
-       } else {
-               return i->second.value;
-       }
+       return Glib::build_filename(
+               _session.plugins_dir(), _insert_id.to_s(), "files");
 }
 
-char*
-LV2Plugin::lv2_state_abstract_path(LV2_State_Map_Path_Handle handle,
-                                   const char*               absolute_path)
-{
-       LV2Plugin* me = (LV2Plugin*)handle;
-       if (me->_insert_id == PBD::ID("0")) {
-               return g_strdup(absolute_path);
-       }
-
-       const std::string state_dir = me->state_dir();
-
-       char* ret = NULL;
-       if (strncmp(absolute_path, state_dir.c_str(), state_dir.length())) {
-               // Path outside state directory, make symbolic link
-               const std::string       name = Glib::path_get_basename(absolute_path);
-               const std::string       path = Glib::build_filename(state_dir, name);
-               Gio::File::create_for_path(path)->make_symbolic_link(absolute_path);
-               ret = g_strndup(path.c_str(), path.length());
-       } else {
-               // Path inside the state directory, return relative path
-               const std::string path(absolute_path + state_dir.length() + 1);
-               ret = g_strndup(path.c_str(), path.length());
-       }
-
-       DEBUG_TRACE(DEBUG::LV2, string_compose("abstract path %1 => %2\n",
-                                              absolute_path, ret));
-
-       return ret;
-}
-
-char*
-LV2Plugin::lv2_state_absolute_path(LV2_State_Map_Path_Handle handle,
-                                   const char*               abstract_path)
+/** Directory to save state snapshot version @c num into. */
+const std::string
+LV2Plugin::state_dir(unsigned num) const
 {
-       LV2Plugin* me = (LV2Plugin*)handle;
-       if (me->_insert_id == PBD::ID("0")) {
-               return g_strdup(abstract_path);
-       }
-
-       char* ret = NULL;
-       if (g_path_is_absolute(abstract_path)) {
-               ret = g_strdup(abstract_path);
-       } else {
-               const std::string apath(abstract_path);
-               const std::string path = Glib::build_filename(me->state_dir(), apath);
-               ret = g_strndup(path.c_str(), path.length());
-       }
-
-       DEBUG_TRACE(DEBUG::LV2, string_compose("absolute path %1 => %2\n",
-                                              abstract_path, ret));
-
-       return ret;
+       return Glib::build_filename(_session.plugins_dir(),
+                                   _insert_id.to_s(),
+                                   string_compose("state%1", num));
 }
 
+/** Implementation of state:makePath for files created at instantiation time.
+ * Note this is not used for files created at save time (Lilv deals with that).
+ */
 char*
 LV2Plugin::lv2_state_make_path(LV2_State_Make_Path_Handle handle,
                                const char*                path)
 {
        LV2Plugin* me = (LV2Plugin*)handle;
        if (me->_insert_id == PBD::ID("0")) {
+               warning << string_compose(
+                       "File path \"%1\" requested but LV2 %2 has no insert ID",
+                       path, me->name()) << endmsg;
                return g_strdup(path);
        }
 
-       const std::string abs_path = Glib::build_filename(me->state_dir(), path);
+       const std::string abs_path = Glib::build_filename(me->scratch_dir(), path);
        const std::string dirname  = Glib::path_get_dirname(abs_path);
-
        g_mkdir_with_parents(dirname.c_str(), 0744);
 
        DEBUG_TRACE(DEBUG::LV2, string_compose("new file path %1 => %2\n",
                                               path, abs_path));
 
+       std::cerr << "MAKE PATH " << path
+                 << " => " << g_strndup(abs_path.c_str(), abs_path.length())
+                 << std::endl;
        return g_strndup(abs_path.c_str(), abs_path.length());
 }
 
@@ -616,9 +517,10 @@ static void
 remove_directory(const std::string& path)
 {
        if (!Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
+               warning << string_compose("\"%1\" is not a directory", path) << endmsg;
                return;
        }
-       
+
        Glib::RefPtr<Gio::File>           dir = Gio::File::create_for_path(path);
        Glib::RefPtr<Gio::FileEnumerator> e   = dir->enumerate_children();
        Glib::RefPtr<Gio::FileInfo>       fi;
@@ -652,41 +554,57 @@ LV2Plugin::add_state(XMLNode* root) const
        }
 
        if (_has_state_interface) {
-               // Create state directory for this plugin instance
-               cerr << "Create statefile name from ID " << _insert_id << endl;
-               const std::string state_filename = _insert_id.to_s() + ".rdff";
-               const std::string state_path     = Glib::build_filename(
-                       _session.plugins_dir(), state_filename);
-
-               cout << "Saving LV2 plugin state to " << state_path << endl;
-
-               // Get LV2 State extension data from plugin instance
-               LV2_State_Interface* state_iface = (LV2_State_Interface*)extension_data(
-                       LV2_STATE_INTERFACE_URI);
-               if (!state_iface) {
-                       warning << string_compose(
-                           _("Plugin \"%1\% failed to return LV2 state interface"),
-                           unique_id());
-                       return;
+               cout << "LV2 " << name() << " has state interface" << endl;
+#ifdef HAVE_NEW_LILV
+               // Provisionally increment state version and create directory
+               const std::string new_dir = state_dir(++_state_version);
+               g_mkdir_with_parents(new_dir.c_str(), 0744);
+
+               cout << "NEW DIR: " << new_dir << endl;
+
+               LilvState* state = lilv_state_new_from_instance(
+                       _impl->plugin,
+                       _impl->instance,
+                       _uri_map.urid_map(),
+                       scratch_dir().c_str(),
+                       file_dir().c_str(),
+                       _session.externals_dir().c_str(),
+                       new_dir.c_str(),
+                       NULL,
+                       (void*)this,
+                       0,
+                       NULL);
+
+               if (!_impl->state || !lilv_state_equals(state, _impl->state)) {
+                       lilv_state_save(_world.world,
+                                       _uri_map.urid_unmap(),
+                                       state,
+                                       NULL,
+                                       new_dir.c_str(),
+                                       "state.ttl",
+                                       NULL);
+
+                       lilv_state_free(_impl->state);
+                       _impl->state = state;
+
+                       cout << "Saved LV2 state to " << state_dir(_state_version) << endl;
+               } else {
+                       // State is identical, decrement version and nuke directory
+                       cout << "LV2 state identical, not saving" << endl;
+                       lilv_state_free(state);
+                       remove_directory(new_dir);
+                       --_state_version;
                }
 
-               // Remove old state directory (FIXME: should this be preserved?)
-               remove_directory(state_dir());
-
-               // Save plugin state to state object
-               LV2State state(*this, _uri_map);
-               state_iface->save(_impl->instance->lv2_handle,
-                                 &LV2Plugin::lv2_state_store_callback,
-                                 &state,
-                                 LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE,
-                                 NULL);
-
-               // Write state object to RDFF file
-               RDFF file = rdff_open(state_path.c_str(), true);
-               state.write(file);
-               rdff_close(file);
+               root->add_property("state-dir", string_compose("state%1", _state_version));
 
-               root->add_property("state-file", state_filename);
+#else  /* !HAVE_NEW_LILV */
+               warning << string_compose(
+                       _("Plugin \"%1\" has state, but Lilv is too old to save it"),
+                       unique_id()) << endmsg;
+#endif  /* HAVE_NEW_LILV */
+       } else {
+               cout << "LV2 " << name() << " has no state interface." << endl;
        }
 }
 
@@ -717,7 +635,7 @@ find_presets_helper(LilvWorld*                                   world,
                        warning << string_compose(
                            _("Plugin \"%1\% preset \"%2%\" is missing a label\n"),
                            lilv_node_as_string(lilv_plugin_get_uri(plugin)),
-                           lilv_node_as_string(preset));
+                           lilv_node_as_string(preset)) << endmsg;
                }
        }
        lilv_nodes_free(presets);
@@ -729,7 +647,7 @@ LV2Plugin::find_presets()
        LilvNode* dc_title          = lilv_new_uri(_world.world, NS_DC   "title");
        LilvNode* oldpset_hasPreset = lilv_new_uri(_world.world, NS_OLDPSET "hasPreset");
        LilvNode* pset_hasPreset    = lilv_new_uri(_world.world, NS_PSET "hasPreset");
-       LilvNode* rdfs_label        = lilv_new_uri(_world.world, NS_RDFS "label");
+       LilvNode* rdfs_label        = lilv_new_uri(_world.world, LILV_NS_RDFS "label");
 
        find_presets_helper(_world.world, _impl->plugin, _presets,
                            oldpset_hasPreset, dc_title);
@@ -748,8 +666,8 @@ LV2Plugin::load_preset(PresetRecord r)
 {
        Plugin::load_preset(r);
 
-       LilvNode* lv2_port      = lilv_new_uri(_world.world, NS_LV2 "port");
-       LilvNode* lv2_symbol    = lilv_new_uri(_world.world, NS_LV2 "symbol");
+       LilvNode* lv2_port      = lilv_new_uri(_world.world, LILV_NS_LV2 "port");
+       LilvNode* lv2_symbol    = lilv_new_uri(_world.world, LILV_NS_LV2 "symbol");
        LilvNode* oldpset_value = lilv_new_uri(_world.world, NS_OLDPSET "value");
        LilvNode* preset        = lilv_new_uri(_world.world, r.uri.c_str());
        LilvNode* pset_value    = lilv_new_uri(_world.world, NS_PSET "value");
@@ -853,31 +771,24 @@ LV2Plugin::set_state(const XMLNode& node, int version)
                set_parameter(port_id, atof(value));
        }
 
-       if ((prop = node.property("state-file")) != 0) {
-               std::string state_path = Glib::build_filename(_session.plugins_dir(),
-                                                             prop->value());
-
-               cout << "LV2 state path " << state_path << endl;
-
-               // Get LV2 State extension data from plugin instance
-               LV2_State_Interface* state_iface = (LV2_State_Interface*)extension_data(
-                       LV2_STATE_INTERFACE_URI);
-               if (state_iface) {
-                       cout << "Loading LV2 state from " << state_path << endl;
-                       RDFF     file = rdff_open(state_path.c_str(), false);
-                       LV2State state(*this, _uri_map);
-                       state.read(file);
-                       state_iface->restore(_impl->instance->lv2_handle,
-                                            &LV2Plugin::lv2_state_retrieve_callback,
-                                            &state,
-                                            LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE,
-                                            NULL);
-                       rdff_close(file);
-               } else {
-                       warning << string_compose(
-                           _("Plugin \"%1\% failed to return LV2 state interface"),
-                           unique_id());
+       _state_version = 0;
+       if ((prop = node.property("state-dir")) != 0) {
+               if (sscanf(prop->value().c_str(), "state%u", &_state_version) != 1) {
+                       error << string_compose(
+                               "LV2: failed to parse state version from \"%1\"",
+                               prop->value()) << endmsg;
                }
+
+               std::string state_file = Glib::build_filename(_session.plugins_dir(),
+                                                             _insert_id.to_s(),
+                                                             prop->value(),
+                                                             "state.ttl");
+
+               cout << "Loading LV2 state from " << state_file << endl;
+               LilvState* state = lilv_state_new_from_file(
+                       _world.world, _uri_map.urid_map(), NULL, state_file.c_str());
+
+               lilv_state_restore(state, _impl->instance, NULL, NULL, 0, NULL);
        }
 
        latency_compute_run();
@@ -904,7 +815,7 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c
                desc.lower *= _session.frame_rate ();
                desc.upper *= _session.frame_rate ();
        }
-               
+
        desc.min_unbound  = false; // TODO: LV2 extension required
        desc.max_unbound  = false; // TODO: LV2 extension required
 
index 4cf14cdaf93c03bc264e41adc424b2382ece6738..30590e3665ff8fa849e3d2e0b80bafe37d281ba6 100644 (file)
@@ -503,6 +503,13 @@ Session::ensure_subdirs ()
                return -1;
        }
 
+       dir = externals_dir ();
+
+       if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
+               error << string_compose(_("Session: cannot create session externals folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
+               return -1;
+       }
+
        return 0;
 }
 
@@ -2258,6 +2265,12 @@ Session::plugins_dir () const
        return Glib::build_filename (_path, "plugins");
 }
 
+string
+Session::externals_dir () const
+{
+       return Glib::build_filename (_path, "externals");
+}
+
 int
 Session::load_bundles (XMLNode const & node)
 {
index b31981ffe0f84da9aef56767130504cc4fb6457a..dccf53735f6eccd41d4162993a4baa4512c44b83 100644 (file)
@@ -258,9 +258,12 @@ def configure(conf):
     if Options.options.lv2:
         autowaf.check_pkg(conf, 'lilv-0', uselib_store='LILV',
                           atleast_version='0.0.0', mandatory=False)
+        autowaf.check_pkg(conf, 'lilv-0', uselib_store='NEW_LILV',
+                          atleast_version='0.9.0', mandatory=False)
         if conf.is_defined('HAVE_LILV'):
             autowaf.check_pkg(conf, 'suil-0', uselib_store='SUIL',
                               atleast_version='0.2.0', mandatory=False)
+
 #    autowaf.check_pkg(conf, 'soundtouch-1.0', uselib_store='SOUNDTOUCH',
 #                      mandatory=False)
     autowaf.check_pkg(conf, 'cppunit', uselib_store='CPPUNIT',
diff --git a/wscript b/wscript
index 4604b1d4bf3f6cbe1077ceaecc8463303a5cbd7b..f2d226a09be363522a9eaff3858102698b52b455 100644 (file)
--- a/wscript
+++ b/wscript
@@ -635,6 +635,7 @@ const char* const ardour_config_info = "\\n\\
     write_config_text('JACK session support',  conf.is_defined('JACK_SESSION'))
     write_config_text('LV2 UI embedding',      conf.is_defined('HAVE_SUIL'))
     write_config_text('LV2 support',           conf.is_defined('LV2_SUPPORT'))
+    write_config_text('LV2 state support',     conf.is_defined('HAVE_NEW_LILV'))
     write_config_text('LXVST support',         conf.is_defined('LXVST_SUPPORT'))
     write_config_text('OGG',                   conf.is_defined('HAVE_OGG'))
     write_config_text('Phone home',            conf.is_defined('PHONE_HOME'))