summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lilv/lilv.h39
-rw-r--r--src/lilv_internal.h92
-rw-r--r--src/node.c10
-rw-r--r--src/plugin.c50
-rw-r--r--src/port.c16
-rw-r--r--src/state.c482
-rw-r--r--src/util.c224
-rw-r--r--src/world.c133
-rw-r--r--src/zix/common.h2
-rw-r--r--src/zix/tree.c6
-rw-r--r--test/lilv_test.c159
-rw-r--r--test/test_plugin.c115
12 files changed, 994 insertions, 334 deletions
diff --git a/lilv/lilv.h b/lilv/lilv.h
index 8509b2e..a00f337 100644
--- a/lilv/lilv.h
+++ b/lilv/lilv.h
@@ -1141,19 +1141,22 @@ typedef LilvNode* (*LilvGetPortValueFunc)(const char* port_symbol,
Create a new state snapshot from a plugin instance.
@param plugin The plugin this state applies to.
@param instance An instance of @c plugin.
+ @param dir Directory containing files created by plugin (or NULL).
@param flags Bitwise OR of LV2_State_Flags values.
@param features Features to pass LV2_State_Interface.save().
@return A new LilvState which must be freed with lilv_state_free().
- The returned state will have all properties set from the plugin, but no port
- values set (since it is impossible for Lilv to do this in general). To get
- a complete snapshot of plugin state, call this function, then set the
- appropriate port values with lilv_state_set_port_value.
-
This function may be called simultaneously with any instance function
(except discovery functions) unless the threading class of that function
explicitly disallows this.
+ If supported (via state:makePath passed to LV2_Descriptor::instantiate()),
+ @c dir should be the directory where any plugin-created files are stored.
+ Lilv will assume any files within this directory (recursively) are created
+ by the plugin and all other files are immutable. This function creates a
+ new LilvState, but does not save state to disk. To save the state
+ permanently, use lilv_state_save().
+
See <a href="http://lv2plug.in/ns/ext/state/state.h">state.h</a> from the
LV2 State extension for details on the @c flags and @c features parameters.
*/
@@ -1161,6 +1164,8 @@ LILV_API
LilvState*
lilv_state_new_from_instance(const LilvPlugin* plugin,
LilvInstance* instance,
+ LV2_URID_Map* map,
+ const char* dir,
LilvGetPortValueFunc get_value,
void* user_data,
uint32_t flags,
@@ -1252,26 +1257,28 @@ lilv_state_restore(const LilvState* state,
@param unmap URID unmapper.
@param state State to save.
@param uri URI of state, may be NULL.
- @param path Path of file to save state to, may be NULL.
- @param manifest_path Path of manifest file to add entry to, may be NULL.
+ @param dir Path of the bundle directory to save into, may be NULL.
+ @param filename Filename for the state file (no extension), may be NULL.
+ @param features Host provided features.
The format of state on disk is compatible with that defined in the LV2
preset extension, i.e. this function may be used to save presets which can
- be loaded by any host. If @c path is NULL, then the default user preset
- bundle (~/.lv2/presets.lv2) is used. In this case, the label of @c state
- MUST be set since it is used to generate a filename.
+ be loaded by any host. If @c dir is NULL, then the default user preset
+ bundle (~/.lv2/presets.lv2) is used. If @c filename is NULL, one will be
+ generated from the state's label, so it must be set.
If @c uri is NULL, the state will be saved without an absolute URI (but
the bundle will still work correctly as a preset bundle).
*/
LILV_API
int
-lilv_state_save(LilvWorld* world,
- LV2_URID_Unmap* unmap,
- const LilvState* state,
- const char* uri,
- const char* path,
- const char* manifest_path);
+lilv_state_save(LilvWorld* world,
+ LV2_URID_Unmap* unmap,
+ const LilvState* state,
+ const char* uri,
+ const char* dir,
+ const char* filename,
+ const LV2_Feature *const * features);
/**
@}
diff --git a/src/lilv_internal.h b/src/lilv_internal.h
index 75c08de..9950a61 100644
--- a/src/lilv_internal.h
+++ b/src/lilv_internal.h
@@ -129,38 +129,41 @@ struct LilvWorldImpl {
LilvSpec* specs;
LilvPlugins* plugins;
LilvNodes* loaded_files;
- SordNode* dc_replaces_node;
- SordNode* doap_name_node;
- SordNode* dyn_manifest_node;
- SordNode* lv2_appliesTo_node;
- SordNode* lv2_binary_node;
- SordNode* lv2_default_node;
- SordNode* lv2_extensionData_node;
- SordNode* lv2_index_node;
- SordNode* lv2_maximum_node;
- SordNode* lv2_minimum_node;
- SordNode* lv2_name_node;
- SordNode* lv2_optionalFeature_node;
- SordNode* lv2_plugin_node;
- SordNode* lv2_port_node;
- SordNode* lv2_portproperty_node;
- SordNode* lv2_reportslatency_node;
- SordNode* lv2_requiredFeature_node;
- SordNode* lv2_specification_node;
- SordNode* lv2_symbol_node;
- SordNode* pset_value_node;
- SordNode* rdf_a_node;
- SordNode* rdf_value_node;
- SordNode* rdfs_class_node;
- SordNode* rdfs_label_node;
- SordNode* rdfs_seealso_node;
- SordNode* rdfs_subclassof_node;
- SordNode* xsd_base64Binary_node;
- SordNode* xsd_boolean_node;
- SordNode* xsd_decimal_node;
- SordNode* xsd_double_node;
- SordNode* xsd_integer_node;
- LilvOptions opt;
+ struct {
+ SordNode* dc_replaces;
+ SordNode* doap_name;
+ SordNode* dman_DynManifest;
+ SordNode* lv2_appliesTo;
+ SordNode* lv2_binary;
+ SordNode* lv2_default;
+ SordNode* lv2_extensionData;
+ SordNode* lv2_index;
+ SordNode* lv2_maximum;
+ SordNode* lv2_minimum;
+ SordNode* lv2_name;
+ SordNode* lv2_optionalFeature;
+ SordNode* lv2_Plugin;
+ SordNode* lv2_port;
+ SordNode* lv2_portProperty;
+ SordNode* lv2_reportsLatency;
+ SordNode* lv2_requiredFeature;
+ SordNode* lv2_Specification;
+ SordNode* lv2_symbol;
+ SordNode* pset_value;
+ SordNode* rdf_a;
+ SordNode* rdf_value;
+ SordNode* rdfs_Class;
+ SordNode* rdfs_label;
+ SordNode* rdfs_seeAlso;
+ SordNode* rdfs_subClassOf;
+ SordNode* xsd_base64Binary;
+ SordNode* xsd_boolean;
+ SordNode* xsd_decimal;
+ SordNode* xsd_double;
+ SordNode* xsd_integer;
+ SordNode* null_uri;
+ } uris;
+ LilvOptions opt;
};
typedef enum {
@@ -309,6 +312,13 @@ lilv_match_object(SordIter* iter) {
return tup[SORD_OBJECT];
}
+static inline const SordNode*
+lilv_match_graph(SordIter* iter) {
+ SordQuad tup;
+ sord_iter_get(iter, tup);
+ return tup[SORD_GRAPH];
+}
+
static inline void
lilv_match_end(SordIter* iter)
{
@@ -327,10 +337,20 @@ LilvNodes* lilv_nodes_from_stream_objects(LilvWorld* w,
SordIter* stream,
bool object);
-char* lilv_strjoin(const char* first, ...);
-char* lilv_strdup(const char* str);
-char* lilv_get_lang(void);
-char* lilv_expand(const char* path);
+char* lilv_strjoin(const char* first, ...);
+char* lilv_strdup(const char* str);
+char* lilv_get_lang(void);
+char* lilv_expand(const char* path);
+char* lilv_dirname(const char* path);
+char* lilv_find_free_path(
+ const char* in_path, bool (*exists)(const char*, void*), void* user_data);
+int lilv_copy_file(const char* src, const char* dst);
+bool lilv_path_exists(const char* path, void* ignored);
+bool lilv_path_is_absolute(const char* path);
+char* lilv_get_latest_copy(const char* path);
+char* lilv_path_relative_to(const char* path, const char* base);
+bool lilv_path_is_child(const char* path, const char* dir);
+int lilv_flock(FILE* file, bool lock);
typedef void (*VoidFunc)(void);
diff --git a/src/node.c b/src/node.c
index 38655a4..8f73df6 100644
--- a/src/node.c
+++ b/src/node.c
@@ -104,14 +104,14 @@ lilv_node_new_from_node(LilvWorld* world, const SordNode* node)
case SORD_LITERAL:
datatype_uri = sord_node_get_datatype(node);
if (datatype_uri) {
- if (sord_node_equals(datatype_uri, world->xsd_boolean_node))
+ if (sord_node_equals(datatype_uri, world->uris.xsd_boolean))
type = LILV_VALUE_BOOL;
- else if (sord_node_equals(datatype_uri, world->xsd_decimal_node)
- || sord_node_equals(datatype_uri, world->xsd_double_node))
+ else if (sord_node_equals(datatype_uri, world->uris.xsd_decimal)
+ || sord_node_equals(datatype_uri, world->uris.xsd_double))
type = LILV_VALUE_FLOAT;
- else if (sord_node_equals(datatype_uri, world->xsd_integer_node))
+ else if (sord_node_equals(datatype_uri, world->uris.xsd_integer))
type = LILV_VALUE_INT;
- else if (sord_node_equals(datatype_uri, world->xsd_base64Binary_node))
+ else if (sord_node_equals(datatype_uri, world->uris.xsd_base64Binary))
type = LILV_VALUE_BLOB;
else
LILV_ERRORF("Unknown datatype `%s'\n",
diff --git a/src/plugin.c b/src/plugin.c
index 1daf23d..b3763cb 100644
--- a/src/plugin.c
+++ b/src/plugin.c
@@ -207,15 +207,15 @@ lilv_plugin_load_ports_if_necessary(const LilvPlugin* const_p)
SordIter* ports = lilv_world_query_internal(
p->world,
p->plugin_uri->val.uri_val,
- p->world->lv2_port_node,
+ p->world->uris.lv2_port,
NULL);
FOREACH_MATCH(ports) {
const SordNode* port = lilv_match_object(ports);
LilvNode* index = lilv_plugin_get_unique(
- p, port, p->world->lv2_index_node);
+ p, port, p->world->uris.lv2_index);
LilvNode* symbol = lilv_plugin_get_unique(
- p, port, p->world->lv2_symbol_node);
+ p, port, p->world->uris.lv2_symbol);
bool error = false;
if (!lilv_node_is_string(symbol) || !is_symbol(symbol->str_val)) {
@@ -254,7 +254,7 @@ lilv_plugin_load_ports_if_necessary(const LilvPlugin* const_p)
}
SordIter* types = lilv_world_query_internal(
- p->world, port, p->world->rdf_a_node, NULL);
+ p->world, port, p->world->uris.rdf_a, NULL);
FOREACH_MATCH(types) {
const SordNode* type = lilv_match_object(types);
if (sord_node_get_type(type) == SORD_URI) {
@@ -326,7 +326,7 @@ lilv_plugin_get_library_uri(const LilvPlugin* const_p)
SordIter* results = lilv_world_query_internal(
p->world,
p->plugin_uri->val.uri_val,
- p->world->lv2_binary_node,
+ p->world->uris.lv2_binary,
NULL);
FOREACH_MATCH(results) {
const SordNode* binary_node = lilv_match_object(results);
@@ -362,7 +362,7 @@ lilv_plugin_get_class(const LilvPlugin* const_p)
SordIter* results = lilv_world_query_internal(
p->world,
p->plugin_uri->val.uri_val,
- p->world->rdf_a_node,
+ p->world->uris.rdf_a,
NULL);
FOREACH_MATCH(results) {
const SordNode* class_node = lilv_match_object(results);
@@ -414,7 +414,7 @@ lilv_plugin_verify(const LilvPlugin* plugin)
lilv_nodes_free(results);
results = lilv_plugin_get_value_internal(plugin,
- plugin->world->doap_name_node);
+ plugin->world->uris.doap_name);
if (!results) {
return false;
}
@@ -436,7 +436,7 @@ LilvNode*
lilv_plugin_get_name(const LilvPlugin* plugin)
{
LilvNodes* results = lilv_plugin_get_value_internal(
- plugin, plugin->world->doap_name_node);
+ plugin, plugin->world->uris.doap_name);
LilvNode* ret = NULL;
if (results) {
@@ -561,7 +561,7 @@ lilv_plugin_has_latency(const LilvPlugin* p)
SordIter* ports = lilv_world_query_internal(
p->world,
p->plugin_uri->val.uri_val,
- p->world->lv2_port_node,
+ p->world->uris.lv2_port,
NULL);
bool ret = false;
@@ -570,8 +570,8 @@ lilv_plugin_has_latency(const LilvPlugin* p)
SordIter* reports_latency = lilv_world_query_internal(
p->world,
port,
- p->world->lv2_portproperty_node,
- p->world->lv2_reportslatency_node);
+ p->world->uris.lv2_portProperty,
+ p->world->uris.lv2_reportsLatency);
const bool end = lilv_matches_end(reports_latency);
lilv_match_end(reports_latency);
if (!end) {
@@ -592,7 +592,7 @@ lilv_plugin_get_latency_port_index(const LilvPlugin* p)
SordIter* ports = lilv_world_query_internal(
p->world,
p->plugin_uri->val.uri_val,
- p->world->lv2_port_node,
+ p->world->uris.lv2_port,
NULL);
uint32_t ret = 0;
@@ -601,11 +601,11 @@ lilv_plugin_get_latency_port_index(const LilvPlugin* p)
SordIter* reports_latency = lilv_world_query_internal(
p->world,
port,
- p->world->lv2_portproperty_node,
- p->world->lv2_reportslatency_node);
+ p->world->uris.lv2_portProperty,
+ p->world->uris.lv2_reportsLatency);
if (!lilv_matches_end(reports_latency)) {
LilvNode* index = lilv_plugin_get_unique(
- p, port, p->world->lv2_index_node);
+ p, port, p->world->uris.lv2_index);
ret = lilv_node_as_int(index);
lilv_node_free(index);
@@ -657,14 +657,14 @@ LILV_API
LilvNodes*
lilv_plugin_get_optional_features(const LilvPlugin* p)
{
- return lilv_plugin_get_value_internal(p, p->world->lv2_optionalFeature_node);
+ return lilv_plugin_get_value_internal(p, p->world->uris.lv2_optionalFeature);
}
LILV_API
LilvNodes*
lilv_plugin_get_required_features(const LilvPlugin* p)
{
- return lilv_plugin_get_value_internal(p, p->world->lv2_requiredFeature_node);
+ return lilv_plugin_get_value_internal(p, p->world->uris.lv2_requiredFeature);
}
LILV_API
@@ -680,7 +680,7 @@ lilv_plugin_has_extension_data(const LilvPlugin* p,
SordIter* iter = lilv_world_query_internal(
p->world,
p->plugin_uri->val.uri_val,
- p->world->lv2_extensionData_node,
+ p->world->uris.lv2_extensionData,
uri->val.uri_val);
if (iter) {
@@ -695,7 +695,7 @@ LILV_API
LilvNodes*
lilv_plugin_get_extension_data(const LilvPlugin* p)
{
- return lilv_plugin_get_value_internal(p, p->world->lv2_extensionData_node);
+ return lilv_plugin_get_value_internal(p, p->world->uris.lv2_extensionData);
}
LILV_API
@@ -815,7 +815,7 @@ lilv_plugin_get_uis(const LilvPlugin* p)
FOREACH_MATCH(uis) {
const SordNode* ui = lilv_match_object(uis);
- LilvNode* type = lilv_plugin_get_unique(p, ui, p->world->rdf_a_node);
+ LilvNode* type = lilv_plugin_get_unique(p, ui, p->world->uris.rdf_a);
LilvNode* binary = lilv_plugin_get_unique(p, ui, ui_binary_node);
if (sord_node_get_type(ui) != SORD_URI
@@ -856,7 +856,7 @@ lilv_plugin_get_related(const LilvPlugin* plugin, const LilvNode* type)
LilvNodes* const related = lilv_world_query_values_internal(
world,
NULL,
- world->lv2_appliesTo_node,
+ world->uris.lv2_appliesTo,
lilv_plugin_get_uri(plugin)->val.uri_val);
if (!type) {
@@ -867,7 +867,7 @@ lilv_plugin_get_related(const LilvPlugin* plugin, const LilvNode* type)
LILV_FOREACH(nodes, i, related) {
LilvNode* node = lilv_collection_get(related, i);
SordIter* titer = lilv_world_query_internal(
- world, node->val.uri_val, world->rdf_a_node, type->val.uri_val);
+ world, node->val.uri_val, world->uris.rdf_a, type->val.uri_val);
if (!sord_iter_end(titer)) {
zix_tree_insert(matches,
lilv_node_new_from_node(world, node->val.uri_val),
@@ -976,15 +976,15 @@ lilv_plugin_write_manifest_entry(LilvWorld* world,
serd_writer_write_statement(
writer, 0, NULL,
sord_node_to_serd_node(subject->val.uri_val),
- sord_node_to_serd_node(plugin->world->rdf_a_node),
- sord_node_to_serd_node(plugin->world->lv2_plugin_node), 0, 0);
+ sord_node_to_serd_node(plugin->world->uris.rdf_a),
+ sord_node_to_serd_node(plugin->world->uris.lv2_Plugin), 0, 0);
const SerdNode file_node = serd_node_from_string(
SERD_URI, (const uint8_t*)plugin_file_path);
serd_writer_write_statement(
writer, 0, NULL,
sord_node_to_serd_node(subject->val.uri_val),
- sord_node_to_serd_node(plugin->world->rdfs_seealso_node),
+ sord_node_to_serd_node(plugin->world->uris.rdfs_seeAlso),
&file_node, 0, 0);
serd_writer_free(writer);
diff --git a/src/port.c b/src/port.c
index 727911a..b14f43a 100644
--- a/src/port.c
+++ b/src/port.c
@@ -68,7 +68,7 @@ lilv_port_has_property(const LilvPlugin* p,
SordIter* results = lilv_world_query_internal(
p->world,
port->node,
- p->world->lv2_portproperty_node,
+ p->world->uris.lv2_portProperty,
lilv_node_as_node(property));
const bool ret = !lilv_matches_end(results);
@@ -142,7 +142,7 @@ lilv_port_get_name(const LilvPlugin* p,
const LilvPort* port)
{
LilvNodes* results = lilv_port_get_value_by_node(
- p, port, p->world->lv2_name_node);
+ p, port, p->world->uris.lv2_name);
LilvNode* ret = NULL;
if (results) {
@@ -177,7 +177,7 @@ lilv_port_get_range(const LilvPlugin* p,
{
if (def) {
LilvNodes* defaults = lilv_port_get_value_by_node(
- p, port, p->world->lv2_default_node);
+ p, port, p->world->uris.lv2_default);
*def = defaults
? lilv_node_duplicate(lilv_nodes_get_first(defaults))
: NULL;
@@ -185,7 +185,7 @@ lilv_port_get_range(const LilvPlugin* p,
}
if (min) {
LilvNodes* minimums = lilv_port_get_value_by_node(
- p, port, p->world->lv2_minimum_node);
+ p, port, p->world->uris.lv2_minimum);
*min = minimums
? lilv_node_duplicate(lilv_nodes_get_first(minimums))
: NULL;
@@ -193,7 +193,7 @@ lilv_port_get_range(const LilvPlugin* p,
}
if (max) {
LilvNodes* maximums = lilv_port_get_value_by_node(
- p, port, p->world->lv2_maximum_node);
+ p, port, p->world->uris.lv2_maximum);
*max = maximums
? lilv_node_duplicate(lilv_nodes_get_first(maximums))
: NULL;
@@ -222,12 +222,12 @@ lilv_port_get_scale_points(const LilvPlugin* p,
LilvNode* value = lilv_plugin_get_unique(
p,
point,
- p->world->rdf_value_node);
+ p->world->uris.rdf_value);
LilvNode* label = lilv_plugin_get_unique(
p,
point,
- p->world->rdfs_label_node);
+ p->world->uris.rdfs_label);
if (value && label) {
zix_tree_insert(ret, lilv_scale_point_new(value, label), NULL);
@@ -245,7 +245,7 @@ lilv_port_get_properties(const LilvPlugin* p,
const LilvPort* port)
{
LilvNode* pred = lilv_node_new_from_node(
- p->world, p->world->lv2_portproperty_node);
+ p->world, p->world->uris.lv2_portProperty);
LilvNodes* ret = lilv_port_get_value(p, port, pred);
lilv_node_free(pred);
return ret;
diff --git a/src/state.c b/src/state.c
index 05570cd..512cccf 100644
--- a/src/state.c
+++ b/src/state.c
@@ -14,8 +14,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#define _POSIX_SOURCE 1 /* for fileno */
-#define _BSD_SOURCE 1 /* for lockf */
+#define _BSD_SOURCE 1 /* for realpath, symlink */
#include <errno.h>
#include <stdio.h>
@@ -28,15 +27,13 @@
# include "lv2/lv2plug.in/ns/ext/state/state.h"
#endif
-#if defined(HAVE_LOCKF) && defined(HAVE_FILENO)
-# include <unistd.h>
-#endif
-
#ifdef HAVE_MKDIR
# include <sys/stat.h>
# include <sys/types.h>
#endif
+#include <unistd.h>
+
#define NS_ATOM "http://lv2plug.in/ns/ext/atom#"
#define NS_PSET "http://lv2plug.in/ns/ext/presets#"
#define NS_STATE "http://lv2plug.in/ns/ext/state#"
@@ -56,16 +53,48 @@ typedef struct {
LilvNode* value;
} PortValue;
+typedef struct {
+ char* abs; ///< Absolute path of actual file
+ char* rel; ///< Abstract path (relative path in state dir)
+} PathMap;
+
struct LilvStateImpl {
LilvNode* plugin_uri;
+ char* dir; ///< Save directory (if saved)
+ char* file_dir; ///< Directory of files created by plugin
+ char* label;
+ ZixTree* abs2rel; ///< PathMap sorted by abs
+ ZixTree* rel2abs; ///< PathMap sorted by rel
Property* props;
PortValue* values;
- char* label;
+ uint32_t state_Path;
uint32_t num_props;
uint32_t num_values;
};
static int
+abs_cmp(const void* a, const void* b, void* user_data)
+{
+ return strcmp(((const PathMap*)a)->abs,
+ ((const PathMap*)b)->abs);
+}
+
+static int
+rel_cmp(const void* a, const void* b, void* user_data)
+{
+ return strcmp(((const PathMap*)a)->rel,
+ ((const PathMap*)b)->rel);
+}
+
+static void
+path_rel_free(void* ptr)
+{
+ free(((PathMap*)ptr)->abs);
+ free(((PathMap*)ptr)->rel);
+ free(ptr);
+}
+
+static int
property_cmp(const void* a, const void* b)
{
const Property* pa = (const Property*)a;
@@ -95,6 +124,7 @@ append_port_value(LilvState* state,
}
#ifdef HAVE_LV2_STATE
+
static int
store_callback(void* handle,
uint32_t key,
@@ -103,19 +133,18 @@ store_callback(void* handle,
uint32_t type,
uint32_t flags)
{
- if (!(flags & LV2_STATE_IS_POD)) {
- // TODO: A flag so we know if we can hold a reference would be nice
- LILV_WARN("Non-POD property ignored.\n");
- return 1;
- }
-
LilvState* const state = (LilvState*)handle;
state->props = realloc(state->props,
(++state->num_props) * sizeof(Property));
Property* const prop = &state->props[state->num_props - 1];
- prop->value = malloc(size);
- memcpy(prop->value, value, size);
+ if ((flags & LV2_STATE_IS_POD) || type == state->state_Path) {
+ prop->value = malloc(size);
+ memcpy(prop->value, value, size);
+ } else {
+ LILV_WARN("Storing non-POD value\n");
+ prop->value = (void*)value;
+ }
prop->size = size;
prop->key = key;
@@ -124,21 +153,157 @@ store_callback(void* handle,
return 0;
}
+
+static const void*
+retrieve_callback(void* handle,
+ uint32_t key,
+ size_t* size,
+ uint32_t* type,
+ uint32_t* flags)
+{
+ const LilvState* const state = (LilvState*)handle;
+ const Property search_key = { NULL, 0, key, 0, 0 };
+ const Property* const prop = (Property*)bsearch(
+ &search_key, state->props, state->num_props,
+ sizeof(Property), property_cmp);
+
+ if (prop) {
+ *size = prop->size;
+ *type = prop->type;
+ *flags = prop->flags;
+ return prop->value;
+ }
+ return NULL;
+}
+
+static const char*
+lilv_state_rel2abs(const LilvState* state, const char* path)
+{
+ ZixTreeIter* iter = NULL;
+ const PathMap key = { NULL, (char*)path };
+ if (state->rel2abs && !zix_tree_find(state->rel2abs, &key, &iter)) {
+ return ((const PathMap*)zix_tree_get(iter))->abs;
+ }
+ return path;
+}
+
+static bool
+lilv_state_has_path(const char* path, void* state)
+{
+ return lilv_state_rel2abs((LilvState*)state, path) != path;
+}
+
+static char*
+abstract_path(LV2_State_Map_Path_Handle handle,
+ const char* absolute_path)
+{
+ LilvState* state = (LilvState*)handle;
+ const size_t file_dir_len = state->file_dir ? strlen(state->file_dir) : 0;
+ char* path = NULL;
+ char* real_path = realpath(absolute_path, NULL);
+ const PathMap key = { (char*)real_path, NULL };
+ ZixTreeIter* iter = NULL;
+
+ if (!zix_tree_find(state->abs2rel, &key, &iter)) {
+ // Already mapped path in a previous call
+ PathMap* pm = (PathMap*)zix_tree_get(iter);
+ free(real_path);
+ return lilv_strdup(pm->rel);
+ } else if (lilv_path_is_child(real_path, state->file_dir)) {
+ // File created by plugin
+ char* copy = lilv_get_latest_copy(real_path);
+ if (!copy) {
+ // No recent enough copy, make a new one
+ copy = lilv_find_free_path(real_path, lilv_path_exists, NULL);
+ lilv_copy_file(real_path, copy);
+ }
+ real_path = copy;
+
+ // Refer to the latest copy in plugin state
+ path = lilv_strdup(copy + file_dir_len + 1);
+ } else {
+ // New path outside state directory
+ const char* slash = strrchr(real_path, '/');
+ const char* name = slash ? (slash + 1) : real_path;
+
+ // Find a free name in the (virtual) state directory
+ path = lilv_find_free_path(name, lilv_state_has_path, state);
+ }
+
+ // Add record to path mapping
+ PathMap* pm = malloc(sizeof(PathMap));
+ pm->abs = real_path;
+ pm->rel = lilv_strdup(path);
+ zix_tree_insert(state->abs2rel, pm, NULL);
+ zix_tree_insert(state->rel2abs, pm, NULL);
+
+ return path;
+}
+
+static char*
+absolute_path(LV2_State_Map_Path_Handle handle,
+ const char* abstract_path)
+{
+ LilvState* state = (LilvState*)handle;
+ char* path = NULL;
+ if (lilv_path_is_absolute(abstract_path)) {
+ // Absolute path, return identical path
+ path = lilv_strdup(abstract_path);
+ } else {
+ // Relative path inside state directory
+ path = lilv_strjoin(state->dir, "/", abstract_path, NULL);
+ }
+
+ return path;
+}
+
#endif // HAVE_LV2_STATE
+/** Return a new features array which is @c feature added to @c features. */
+const LV2_Feature**
+add_feature(const LV2_Feature *const * features, const LV2_Feature* feature)
+{
+ size_t n_features = 0;
+ for (; features && features[n_features]; ++n_features) {}
+
+ const LV2_Feature** ret = malloc((n_features + 2) * sizeof(LV2_Feature*));
+
+ ret[0] = feature;
+ if (features) {
+ memcpy(ret + 1, features, n_features * sizeof(LV2_Feature*));
+ }
+ ret[n_features + 1] = NULL;
+ return ret;
+}
+
LILV_API
LilvState*
lilv_state_new_from_instance(const LilvPlugin* plugin,
LilvInstance* instance,
+ LV2_URID_Map* map,
+ const char* dir,
LilvGetPortValueFunc get_value,
void* user_data,
uint32_t flags,
const LV2_Feature *const * features)
{
- LilvWorld* const world = plugin->world;
- LilvState* const state = malloc(sizeof(LilvState));
+ const LV2_Feature** local_features = NULL;
+ LilvWorld* const world = plugin->world;
+ LilvState* const state = malloc(sizeof(LilvState));
memset(state, '\0', sizeof(LilvState));
state->plugin_uri = lilv_node_duplicate(lilv_plugin_get_uri(plugin));
+ state->abs2rel = zix_tree_new(false, abs_cmp, NULL, path_rel_free);
+ state->rel2abs = zix_tree_new(false, rel_cmp, NULL, NULL);
+ state->file_dir = dir ? realpath(dir, NULL) : NULL;
+ state->state_Path = map->map(map->handle, LV2_STATE_PATH_URI);
+
+#ifdef HAVE_LV2_STATE
+ if (dir) {
+ LV2_State_Map_Path map_path = { state, abstract_path, absolute_path };
+ LV2_Feature feature = { LV2_STATE_MAP_PATH_URI, &map_path };
+ features = local_features = add_feature(features, &feature);
+ }
+#endif
// Store port values
LilvNode* lv2_ControlPort = lilv_new_uri(world, LILV_URI_CONTROL_PORT);
@@ -161,39 +326,18 @@ lilv_state_new_from_instance(const LilvPlugin* plugin,
? descriptor->extension_data(LV2_STATE_INTERFACE_URI)
: NULL;
- iface->save(instance->lv2_handle, store_callback, state, flags, features);
+ if (iface) {
+ iface->save(instance->lv2_handle, store_callback, state, flags, features);
+ }
#endif // HAVE_LV2_STATE
qsort(state->props, state->num_props, sizeof(Property), property_cmp);
qsort(state->values, state->num_values, sizeof(PortValue), value_cmp);
+ free(local_features);
return state;
}
-#ifdef HAVE_LV2_STATE
-static const void*
-retrieve_callback(void* handle,
- uint32_t key,
- size_t* size,
- uint32_t* type,
- uint32_t* flags)
-{
- const LilvState* const state = (LilvState*)handle;
- const Property search_key = { NULL, 0, key, 0, 0 };
- const Property* const prop = (Property*)bsearch(
- &search_key, state->props, state->num_props,
- sizeof(Property), property_cmp);
-
- if (prop) {
- *size = prop->size;
- *type = prop->type;
- *flags = prop->flags;
- return prop->value;
- }
- return NULL;
-}
-#endif // HAVE_LV2_STATE
-
LILV_API
void
lilv_state_restore(const LilvState* state,
@@ -204,13 +348,25 @@ lilv_state_restore(const LilvState* state,
const LV2_Feature *const * features)
{
#ifdef HAVE_LV2_STATE
+ LV2_State_Map_Path map_path = { (LilvState*)state, abstract_path, absolute_path };
+ LV2_Feature feature = { LV2_STATE_MAP_PATH_URI, &map_path };
+
+ const LV2_Feature** local_features = add_feature(features, &feature);
+ features = local_features;
+
const LV2_Descriptor* descriptor = instance->lv2_descriptor;
const LV2_State_Interface* iface = (descriptor->extension_data)
? descriptor->extension_data(LV2_STATE_INTERFACE_URI)
: NULL;
- iface->restore(instance->lv2_handle, retrieve_callback,
- (LV2_State_Handle)state, flags, features);
+ if (iface) {
+ iface->restore(instance->lv2_handle, retrieve_callback,
+ (LV2_State_Handle)state, flags, features);
+ }
+
+ free(local_features);
+
+#endif // HAVE_LV2_STATE
if (set_value) {
for (uint32_t i = 0; i < state->num_values; ++i) {
@@ -219,7 +375,6 @@ lilv_state_restore(const LilvState* state,
user_data);
}
}
-#endif // HAVE_LV2_STATE
}
static SordNode*
@@ -278,21 +433,28 @@ property_from_node(LilvWorld* world,
}
static LilvState*
-new_state_from_model(LilvWorld* world,
- LV2_URID_Map* map,
- SordModel* model,
- const SordNode* node)
+new_state_from_model(LilvWorld* world,
+ LV2_URID_Map* map,
+ SordModel* model,
+ const SordNode* node,
+ const char* dir)
{
LilvState* const state = malloc(sizeof(LilvState));
memset(state, '\0', sizeof(LilvState));
+ state->state_Path = map->map(map->handle, LV2_STATE_PATH_URI);
+ state->dir = dir ? lilv_strdup(dir) : NULL;
// Get the plugin URI this state applies to
const SordQuad upat = {
- node, world->lv2_appliesTo_node, NULL, NULL };
+ node, world->uris.lv2_appliesTo, NULL, NULL };
SordIter* i = sord_find(model, upat);
if (i) {
state->plugin_uri = lilv_node_new_from_node(
world, lilv_match_object(i));
+ if (!state->dir) {
+ state->dir = lilv_strdup(
+ (const char*)sord_node_get_string(lilv_match_graph(i)));
+ }
sord_iter_free(i);
} else {
LILV_ERRORF("State %s missing lv2:appliesTo property\n",
@@ -301,23 +463,26 @@ new_state_from_model(LilvWorld* world,
// Get the state label
const SordQuad lpat = {
- node, world->rdfs_label_node, NULL, NULL };
+ node, world->uris.rdfs_label, NULL, NULL };
i = sord_find(model, lpat);
if (i) {
state->label = lilv_strdup(
(const char*)sord_node_get_string(lilv_match_object(i)));
+ if (!state->dir) {
+ state->dir = lilv_strdup(
+ (const char*)sord_node_get_string(lilv_match_graph(i)));
+ }
sord_iter_free(i);
- } else {
}
// Get port values
- const SordQuad ppat = { node, world->lv2_port_node, NULL, NULL };
+ const SordQuad ppat = { node, world->uris.lv2_port, NULL, NULL };
SordIter* ports = sord_find(model, ppat);
FOREACH_MATCH(ports) {
const SordNode* port = lilv_match_object(ports);
- const SordNode* label = get_one(model, port, world->rdfs_label_node);
- const SordNode* symbol = get_one(model, port, world->lv2_symbol_node);
- const SordNode* value = get_one(model, port, world->pset_value_node);
+ const SordNode* label = get_one(model, port, world->uris.rdfs_label);
+ const SordNode* symbol = get_one(model, port, world->uris.lv2_symbol);
+ const SordNode* value = get_one(model, port, world->uris.pset_value);
if (!symbol) {
LILV_ERRORF("State `%s' port missing symbol.\n",
sord_node_get_string(node));
@@ -338,10 +503,12 @@ new_state_from_model(LilvWorld* world,
}
sord_iter_free(ports);
+ SordNode* state_path_node = sord_new_uri(world->world,
+ USTR(LV2_STATE_PATH_URI));
// Get properties
SordNode* statep = sord_new_uri(world->world, USTR(NS_STATE "state"));
const SordNode* state_node = get_one(model, node, statep);
- if (state_node) {
+ if (state) {
const SordQuad spat = { state_node, NULL, NULL };
SordIter* props = sord_find(model, spat);
FOREACH_MATCH(props) {
@@ -357,8 +524,8 @@ new_state_from_model(LilvWorld* world,
(const char*)sord_node_get_string(p));
if (sord_node_get_type(o) == SORD_BLANK) {
- const SordNode* type = get_one(model, o, world->rdf_a_node);
- const SordNode* value = get_one(model, o, world->rdf_value_node);
+ const SordNode* type = get_one(model, o, world->uris.rdf_a);
+ const SordNode* value = get_one(model, o, world->uris.rdf_value);
if (type && value) {
size_t len;
const uint8_t* b64 = sord_node_get_string_counted(value, &len);
@@ -369,6 +536,15 @@ new_state_from_model(LilvWorld* world,
LILV_ERRORF("Unable to parse blank node property <%p>\n",
sord_node_get_string(p));
}
+#ifdef HAVE_LV2_STATE
+ } else if (sord_node_equals(sord_node_get_datatype(o),
+ state_path_node)) {
+ prop.size = strlen((const char*)sord_node_get_string(o)) + 1;
+ prop.type = map->map(map->handle, LV2_STATE_PATH_URI);
+ prop.flags = LV2_STATE_IS_PORTABLE;
+ prop.value = lilv_strjoin(
+ state->dir, "/", sord_node_get_string(o), NULL);
+#endif
} else {
LilvNode* onode = lilv_node_new_from_node(world, o);
property_from_node(world, map, onode, &prop);
@@ -385,6 +561,7 @@ new_state_from_model(LilvWorld* world,
sord_iter_free(props);
}
sord_node_free(world->world, statep);
+ sord_node_free(world->world, state_path_node);
qsort(state->props, state->num_props, sizeof(Property), property_cmp);
qsort(state->values, state->num_values, sizeof(PortValue), value_cmp);
@@ -404,7 +581,10 @@ lilv_state_new_from_world(LilvWorld* world,
return NULL;
}
- return new_state_from_model(world, map, world->model, node->val.uri_val);
+ LilvState* state = new_state_from_model(
+ world, map, world->model, node->val.uri_val, NULL);
+
+ return state;
}
LILV_API
@@ -433,7 +613,12 @@ lilv_state_new_from_file(LilvWorld* world,
? subject->val.uri_val
: sord_node_from_serd_node(world->world, env, &base, NULL, NULL);
- LilvState* state = new_state_from_model(world, map, model, subject_node);
+ char* dirname = lilv_dirname(path);
+ char* real_path = realpath(dirname, NULL);
+ LilvState* state = new_state_from_model(
+ world, map, model, subject_node, real_path);
+ free(dirname);
+ free(real_path);
serd_reader_free(reader);
sord_free(model);
@@ -533,9 +718,7 @@ add_state_to_manifest(const LilvNode* plugin_uri,
serd_env_set_prefix_from_strings(env, USTR("rdf"), USTR(LILV_NS_RDF));
serd_env_set_prefix_from_strings(env, USTR("rdfs"), USTR(LILV_NS_RDFS));
-#if defined(HAVE_LOCKF) && defined(HAVE_FILENO)
- lockf(fileno(fd), F_LOCK, 0);
-#endif
+ lilv_flock(fd, true);
char* const manifest_uri = lilv_strjoin("file://", manifest_path, NULL);
@@ -581,9 +764,7 @@ add_state_to_manifest(const LilvNode* plugin_uri,
serd_writer_free(writer);
serd_node_free(&base);
-#ifdef HAVE_LOCKF
- lockf(fileno(fd), F_ULOCK, 0);
-#endif
+ lilv_flock(fd, false);
fclose(fd);
free(manifest_uri);
@@ -632,77 +813,63 @@ mkdir_p(const char* dir_path)
return 0;
}
-static int
-lilv_default_state_path(LilvWorld* world,
- const LilvState* state,
- char** path,
- char** manifest_path)
+static char*
+lilv_default_state_dir(LilvWorld* world)
{
#ifdef HAVE_MKDIR
- if (!state->label) {
- LILV_ERROR("Attempt to save state with no label or path.\n");
- return 1;
- }
-
+ // Use environment variable or default value if it is unset
char* state_bundle = getenv("LV2_STATE_BUNDLE");
if (!state_bundle) {
state_bundle = LILV_DEFAULT_STATE_BUNDLE;
}
- // Create ~/.lv2/presets.lv2/
- char* const bundle = lilv_expand(state_bundle);
- if (mkdir_p(bundle)) {
- free(bundle);
- return 3;
- }
-
- char* const filename = pathify(state->label);
-
- *path = lilv_strjoin(
- bundle, LILV_DIR_SEP, filename, ".ttl", NULL);
-
- *manifest_path = lilv_strjoin(
- bundle, LILV_DIR_SEP, "manifest.ttl", NULL);
-
- free(bundle);
- free(filename);
-
- return 0;
+ // Expand any variables and create if necessary
+ return lilv_expand(state_bundle);
#else
LILV_ERROR("Save to default state path but mkdir is unavailable.\n");
- return 4;
+ return NULL;
#endif
}
LILV_API
int
-lilv_state_save(LilvWorld* world,
- LV2_URID_Unmap* unmap,
- const LilvState* state,
- const char* uri,
- const char* path,
- const char* manifest_path)
-{
- char* default_path = NULL;
- char* default_manifest_path = NULL;
- if (!path) {
- if (lilv_default_state_path(
- world, state, &default_path, &default_manifest_path)) {
- return 1;
- }
-
- path = default_path;
- manifest_path = default_manifest_path;
+lilv_state_save(LilvWorld* world,
+ LV2_URID_Unmap* unmap,
+ const LilvState* state,
+ const char* uri,
+ const char* dir,
+ const char* filename,
+ const LV2_Feature *const * features)
+{
+ char* default_dir = NULL;
+ char* default_filename = NULL;
+ if (!dir) {
+ dir = default_dir = lilv_default_state_dir(world);
+ }
+ if (mkdir_p(dir)) {
+ free(default_dir);
+ return 1;
}
+ if (!filename) {
+ filename = default_filename = pathify(state->label);
+ }
+
+ char* const path = lilv_strjoin(dir, "/", filename, ".ttl", NULL);
FILE* fd = fopen(path, "w");
if (!fd) {
LILV_ERRORF("Failed to open %s (%s)\n", path, strerror(errno));
- free(default_path);
- free(default_manifest_path);
+ free(default_dir);
+ free(default_filename);
+ free(path);
return 4;
}
+ // FIXME: make parameter non-const?
+ ((LilvState*)state)->dir = lilv_strdup(dir);
+
+ char* const manifest = lilv_strjoin(dir, "/manifest.ttl", NULL);
+
SerdEnv* env = serd_env_new(NULL);
serd_env_set_prefix_from_strings(env, USTR("lv2"), USTR(LILV_NS_LV2));
serd_env_set_prefix_from_strings(env, USTR("pset"), USTR(NS_PSET));
@@ -776,6 +943,27 @@ lilv_state_save(LilvWorld* world,
serd_writer_end_anon(writer, &port);
}
+ // Create symlinks to external files
+#ifdef HAVE_LV2_STATE
+ for (ZixTreeIter* i = zix_tree_begin(state->abs2rel);
+ i != zix_tree_end(state->abs2rel);
+ i = zix_tree_iter_next(i)) {
+ const PathMap* pm = (const PathMap*)zix_tree_get(i);
+
+ char* real_dir = lilv_strjoin(realpath(dir, NULL), "/", NULL);
+ char* rel_path = lilv_strjoin(dir, "/", pm->rel, NULL);
+ char* target_path = lilv_path_is_child(pm->abs, state->file_dir)
+ ? lilv_path_relative_to(pm->abs, real_dir)
+ : lilv_strdup(pm->abs);
+ if (symlink(target_path, rel_path)) {
+ LILV_ERRORF("Failed to link `%s' => `%s' (%s)\n",
+ pm->abs, pm->rel, strerror(errno));
+ }
+ free(target_path);
+ free(rel_path);
+ }
+#endif
+
// Save properties
const SerdNode state_node = serd_node_from_string(SERD_BLANK,
USTR("2state"));
@@ -793,11 +981,6 @@ lilv_state_save(LilvWorld* world,
LILV_WARNF("Failed to unmap property key `%d'\n", prop->key);
} else if (!type) {
LILV_WARNF("Failed to unmap property type `%d'\n", prop->type);
-#ifdef HAVE_LV2_STATE
- } else if (!(prop->flags & LV2_STATE_IS_PORTABLE)
- || !(prop->flags & LV2_STATE_IS_POD)) {
- LILV_WARNF("Unable to save non-portable property <%s>\n", type);
-#endif
} else {
SerdNode t;
p = serd_node_from_string(SERD_URI, USTR(key));
@@ -805,10 +988,20 @@ lilv_state_save(LilvWorld* world,
world, unmap, type, prop->value, prop->size);
if (node) {
node_to_serd(node, &o, &t);
+ // <state> <key> value
serd_writer_write_statement(
writer, SERD_ANON_CONT, NULL,
&state_node, &p, &o, &t, NULL);
lilv_node_free(node);
+#ifdef HAVE_LV2_STATE
+ } else if (!strcmp(type, NS_STATE "Path")) {
+ o = serd_node_from_string(SERD_LITERAL, prop->value);
+ t = serd_node_from_string(SERD_URI, (const uint8_t*)type);
+ // <state> <key> "the/path"^^<state:Path>
+ serd_writer_write_statement(
+ writer, SERD_ANON_CONT, NULL,
+ &state_node, &p, &o, &t, NULL);
+#endif
} else {
char name[16];
snprintf(name, sizeof(name), "b%u", i);
@@ -817,24 +1010,22 @@ lilv_state_save(LilvWorld* world,
// <state> <key> [
serd_writer_write_statement(
- writer, SERD_ANON_O_BEGIN, NULL,
+ writer, SERD_ANON_CONT|SERD_ANON_O_BEGIN, NULL,
&state_node, &p, &blank, NULL, NULL);
// rdf:type <type>
p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type"));
o = serd_node_from_string(SERD_URI, USTR(type));
- serd_writer_write_statement(
- writer, SERD_ANON_CONT, NULL,
- &blank, &p, &o, NULL, NULL);
+ serd_writer_write_statement(writer, SERD_ANON_CONT, NULL,
+ &blank, &p, &o, NULL, NULL);
// rdf:value "string"^^<xsd:base64Binary>
SerdNode blob = serd_node_new_blob(prop->value, prop->size, true);
p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "value"));
t = serd_node_from_string(SERD_URI,
USTR(LILV_NS_XSD "base64Binary"));
- serd_writer_write_statement(
- writer, SERD_ANON_CONT, NULL,
- &blank, &p, &blob, &t, NULL);
+ serd_writer_write_statement(writer, SERD_ANON_CONT, NULL,
+ &blank, &p, &blob, &t, NULL);
serd_node_free(&blob);
serd_writer_end_anon(writer, &blank); // ]
@@ -850,13 +1041,12 @@ lilv_state_save(LilvWorld* world,
fclose(fd);
serd_env_free(env);
- if (manifest_path) {
- add_state_to_manifest(
- state->plugin_uri, manifest_path, uri, path);
+ if (manifest) {
+ add_state_to_manifest(state->plugin_uri, manifest, uri, path);
}
- free(default_path);
- free(default_manifest_path);
+ free(default_dir);
+ free(default_filename);
return 0;
}
@@ -873,9 +1063,13 @@ lilv_state_free(LilvState* state)
free(state->values[i].symbol);
}
lilv_node_free(state->plugin_uri);
+ zix_tree_free(state->abs2rel);
+ zix_tree_free(state->rel2abs);
free(state->props);
free(state->values);
free(state->label);
+ free(state->dir);
+ free(state->file_dir);
free(state);
}
}
@@ -906,11 +1100,25 @@ lilv_state_equals(const LilvState* a, const LilvState* b)
for (uint32_t i = 0; i < a->num_props; ++i) {
Property* const ap = &a->props[i];
Property* const bp = &b->props[i];
- if (ap->size != bp->size
- || ap->key != bp->key
+ if (ap->key != bp->key
|| ap->type != bp->type
- || ap->flags != bp->flags
- || memcmp(ap->value, bp->value, ap->size)) {
+ || ap->flags != bp->flags) {
+ return false;
+ }
+
+ if (ap->type == a->state_Path) {
+ const char* const a_abs = lilv_state_rel2abs(a, ap->value);
+ const char* const b_abs = lilv_state_rel2abs(b, bp->value);
+ char* const a_real = realpath(a_abs, NULL);
+ char* const b_real = realpath(b_abs, NULL);
+ const int cmp = strcmp(a_real, b_real);
+ free(a_real);
+ free(b_real);
+ if (cmp) {
+ return false;
+ }
+ } else if (ap->size != bp->size
+ || memcmp(ap->value, bp->value, ap->size)) {
return false;
}
}
diff --git a/src/util.c b/src/util.c
index 1c9a46e..b421660 100644
--- a/src/util.c
+++ b/src/util.c
@@ -14,13 +14,20 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#define _POSIX_SOURCE 1 /* for wordexp */
+#define _POSIX_SOURCE 1 /* for wordexp, fileno */
+#define _BSD_SOURCE 1 /* for lockf */
#include <assert.h>
+#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
#include "lilv_internal.h"
#ifdef HAVE_WORDEXP
@@ -138,3 +145,218 @@ lilv_expand(const char* path)
#endif
return ret;
}
+
+char*
+lilv_dirname(const char* path)
+{
+ const char* s = path + strlen(path) - 1; // Last character
+ for (; s > path && *s == LILV_DIR_SEP[0]; --s) {} // Last non-slash
+ for (; s > path && *s != LILV_DIR_SEP[0]; --s) {} // Last internal slash
+ for (; s > path && *s == LILV_DIR_SEP[0]; --s) {} // Skip duplicates
+
+ if (s == path) { // Hit beginning
+ return (*s == '/') ? lilv_strdup("/") : lilv_strdup(".");
+ } else { // Pointing to the last character of the result (inclusive)
+ char* dirname = malloc(s - path + 2);
+ memcpy(dirname, path, s - path + 1);
+ dirname[s - path + 1] = '\0';
+ return dirname;
+ }
+}
+
+bool
+lilv_path_exists(const char* path, void* ignored)
+{
+ return !access(path, F_OK);
+}
+
+char*
+lilv_find_free_path(
+ const char* in_path, bool (*exists)(const char*, void*), void* user_data)
+{
+ const size_t in_path_len = strlen(in_path);
+ char* path = malloc(in_path_len + 7);
+ memcpy(path, in_path, in_path_len + 1);
+
+ for (int i = 2; i < 1000000; ++i) {
+ if (!exists(path, user_data)) {
+ return path;
+ }
+ snprintf(path, in_path_len + 7, "%s%u", in_path, i);
+ }
+
+ return NULL;
+}
+
+int
+lilv_copy_file(const char* src, const char* dst)
+{
+ FILE* in = fopen(src, "r");
+ if (!in) {
+ LILV_ERRORF("error opening %s (%s)\n", src, strerror(errno));
+ return 1;
+ }
+
+ FILE* out = fopen(dst, "w");
+ if (!out) {
+ LILV_ERRORF("error opening %s (%s)\n", dst, strerror(errno));
+ fclose(in);
+ return 2;
+ }
+
+ static const size_t PAGE_SIZE = 4096;
+ char* page = malloc(PAGE_SIZE);
+ size_t n_read = 0;
+ while ((n_read = fread(page, 1, PAGE_SIZE, in)) > 0) {
+ if (fwrite(page, 1, n_read, out) != n_read) {
+ LILV_ERRORF("write to %s failed (%s)\n", dst, strerror(errno));
+ break;
+ }
+ }
+
+ const int ret = ferror(in) || ferror(out);
+ if (ferror(in)) {
+ LILV_ERRORF("read from %s failed (%s)\n", src, strerror(errno));
+ }
+
+ free(page);
+ fclose(in);
+ fclose(out);
+
+ return ret;
+}
+
+static bool
+lilv_is_dir_sep(const char c)
+{
+ return c == '/' || c == LILV_DIR_SEP[0];
+}
+
+bool
+lilv_path_is_absolute(const char* path)
+{
+ if (lilv_is_dir_sep(path[0])) {
+ return true;
+ }
+
+#ifdef __WIN32__
+ if (isalpha(path[0]) && path[1] == ':' && lilv_is_dir_sep(path[2])) {
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+static void
+lilv_size_mtime(const char* path, off_t* size, time_t* time)
+{
+ struct stat buf;
+ if (stat(path, &buf)) {
+ LILV_ERRORF("stat(%s) (%s)\n", path, strerror(errno));
+ *size = *time = 0;
+ }
+
+ *size = buf.st_size;
+ *time = buf.st_mtime;
+}
+
+/** Return the latest copy of the file at @c path that is newer. */
+char*
+lilv_get_latest_copy(const char* path)
+{
+ char* dirname = lilv_dirname(path);
+ DIR* dir = opendir(dirname);
+ if (!dir) {
+ free(dirname);
+ return NULL;
+ }
+
+ char* pat = lilv_strjoin(path, "%u", NULL);
+ char* latest = NULL;
+
+ off_t path_size;
+ time_t path_time;
+ lilv_size_mtime(path, &path_size, &path_time);
+
+ struct dirent entry;
+ struct dirent* result;
+ while (!readdir_r(dir, &entry, &result) && result) {
+ char* entry_path = lilv_strjoin(dirname, "/", entry.d_name, NULL);
+ unsigned num;
+ if (sscanf(entry_path, pat, &num) == 1) {
+ off_t entry_size;
+ time_t entry_time;
+ lilv_size_mtime(entry_path, &entry_size, &entry_time);
+ if (entry_size == path_size && entry_time >= path_time) {
+ free(latest);
+ latest = entry_path;
+ }
+ }
+ if (entry_path != latest) {
+ free(entry_path);
+ }
+ }
+ free(dirname);
+ free(pat);
+
+ return latest;
+}
+
+char*
+lilv_path_relative_to(const char* path, const char* base)
+{
+ const size_t path_len = strlen(path);
+ const size_t base_len = strlen(base);
+ const size_t min_len = (path_len < base_len) ? path_len : base_len;
+
+ // Find the last separator common to both paths
+ size_t last_shared_sep = 0;
+ for (size_t i = 0; i < min_len && path[i] == base[i]; ++i) {
+ if (lilv_is_dir_sep(path[i])) {
+ last_shared_sep = i;
+ }
+ }
+
+ if (last_shared_sep == 0) {
+ // No common components, return path
+ return lilv_strdup(path);
+ }
+
+ // Find the number of up references ("..") required
+ size_t up = 0;
+ for (size_t i = last_shared_sep + 1; i < base_len; ++i) {
+ if (lilv_is_dir_sep(base[i])) {
+ ++up;
+ }
+ }
+
+ // Write up references
+ const size_t suffix_len = path_len - last_shared_sep;
+ char* rel = calloc(1, suffix_len + (up * 3) + 1);
+ for (size_t i = 0; i < up; ++i) {
+ memcpy(rel + (i * 3), ".." LILV_DIR_SEP, 3);
+ }
+
+ // Write suffix
+ memcpy(rel + (up * 3), path + last_shared_sep + 1, suffix_len);
+ return rel;
+}
+
+bool
+lilv_path_is_child(const char* path, const char* dir)
+{
+ const size_t path_len = strlen(path);
+ const size_t dir_len = strlen(dir);
+ return dir && path_len >= dir_len && !strncmp(path, dir, dir_len);
+}
+
+int
+lilv_flock(FILE* file, bool lock)
+{
+#if defined(HAVE_LOCKF) && defined(HAVE_FILENO)
+ return lockf(fileno(file), lock ? F_LOCK : F_ULOCK, 0);
+#else
+ return 0;
+#endif
+}
diff --git a/src/world.c b/src/world.c
index 1bf357a..32e4f63 100644
--- a/src/world.c
+++ b/src/world.c
@@ -51,40 +51,41 @@ lilv_world_new(void)
#define NEW_URI(uri) sord_new_uri(world->world, (const uint8_t*)uri)
- world->dc_replaces_node = NEW_URI(NS_DCTERMS "replaces");
- world->doap_name_node = NEW_URI(LILV_NS_DOAP "name");
- world->dyn_manifest_node = NEW_URI(NS_DYNMAN "DynManifest");
- world->lv2_appliesTo_node = NEW_URI(LILV_NS_LV2 "appliesTo");
- world->lv2_binary_node = NEW_URI(LILV_NS_LV2 "binary");
- world->lv2_default_node = NEW_URI(LILV_NS_LV2 "default");
- world->lv2_extensionData_node = NEW_URI(LILV_NS_LV2 "extensionData");
- world->lv2_index_node = NEW_URI(LILV_NS_LV2 "index");
- world->lv2_maximum_node = NEW_URI(LILV_NS_LV2 "maximum");
- world->lv2_minimum_node = NEW_URI(LILV_NS_LV2 "minimum");
- world->lv2_name_node = NEW_URI(LILV_NS_LV2 "name");
- world->lv2_optionalFeature_node = NEW_URI(LILV_NS_LV2 "optionalFeature");
- world->lv2_plugin_node = NEW_URI(LILV_NS_LV2 "Plugin");
- world->lv2_port_node = NEW_URI(LILV_NS_LV2 "port");
- world->lv2_portproperty_node = NEW_URI(LILV_NS_LV2 "portProperty");
- world->lv2_reportslatency_node = NEW_URI(LILV_NS_LV2 "reportsLatency");
- world->lv2_requiredFeature_node = NEW_URI(LILV_NS_LV2 "requiredFeature");
- world->lv2_specification_node = NEW_URI(LILV_NS_LV2 "Specification");
- world->lv2_symbol_node = NEW_URI(LILV_NS_LV2 "symbol");
- world->pset_value_node = NEW_URI(NS_PSET "value");
- world->rdf_a_node = NEW_URI(LILV_NS_RDF "type");
- world->rdf_value_node = NEW_URI(LILV_NS_RDF "value");
- world->rdfs_class_node = NEW_URI(LILV_NS_RDFS "Class");
- world->rdfs_label_node = NEW_URI(LILV_NS_RDFS "label");
- world->rdfs_seealso_node = NEW_URI(LILV_NS_RDFS "seeAlso");
- world->rdfs_subclassof_node = NEW_URI(LILV_NS_RDFS "subClassOf");
- world->xsd_base64Binary_node = NEW_URI(LILV_NS_XSD "base64Binary");
- world->xsd_boolean_node = NEW_URI(LILV_NS_XSD "boolean");
- world->xsd_decimal_node = NEW_URI(LILV_NS_XSD "decimal");
- world->xsd_double_node = NEW_URI(LILV_NS_XSD "double");
- world->xsd_integer_node = NEW_URI(LILV_NS_XSD "integer");
+ world->uris.dc_replaces = NEW_URI(NS_DCTERMS "replaces");
+ world->uris.doap_name = NEW_URI(LILV_NS_DOAP "name");
+ world->uris.dman_DynManifest = NEW_URI(NS_DYNMAN "DynManifest");
+ world->uris.lv2_appliesTo = NEW_URI(LILV_NS_LV2 "appliesTo");
+ world->uris.lv2_binary = NEW_URI(LILV_NS_LV2 "binary");
+ world->uris.lv2_default = NEW_URI(LILV_NS_LV2 "default");
+ world->uris.lv2_extensionData = NEW_URI(LILV_NS_LV2 "extensionData");
+ world->uris.lv2_index = NEW_URI(LILV_NS_LV2 "index");
+ world->uris.lv2_maximum = NEW_URI(LILV_NS_LV2 "maximum");
+ world->uris.lv2_minimum = NEW_URI(LILV_NS_LV2 "minimum");
+ world->uris.lv2_name = NEW_URI(LILV_NS_LV2 "name");
+ world->uris.lv2_optionalFeature = NEW_URI(LILV_NS_LV2 "optionalFeature");
+ world->uris.lv2_Plugin = NEW_URI(LILV_NS_LV2 "Plugin");
+ world->uris.lv2_port = NEW_URI(LILV_NS_LV2 "port");
+ world->uris.lv2_portProperty = NEW_URI(LILV_NS_LV2 "portProperty");
+ world->uris.lv2_reportsLatency = NEW_URI(LILV_NS_LV2 "reportsLatency");
+ world->uris.lv2_requiredFeature = NEW_URI(LILV_NS_LV2 "requiredFeature");
+ world->uris.lv2_Specification = NEW_URI(LILV_NS_LV2 "Specification");
+ world->uris.lv2_symbol = NEW_URI(LILV_NS_LV2 "symbol");
+ world->uris.pset_value = NEW_URI(NS_PSET "value");
+ world->uris.rdf_a = NEW_URI(LILV_NS_RDF "type");
+ world->uris.rdf_value = NEW_URI(LILV_NS_RDF "value");
+ world->uris.rdfs_Class = NEW_URI(LILV_NS_RDFS "Class");
+ world->uris.rdfs_label = NEW_URI(LILV_NS_RDFS "label");
+ world->uris.rdfs_seeAlso = NEW_URI(LILV_NS_RDFS "seeAlso");
+ world->uris.rdfs_subClassOf = NEW_URI(LILV_NS_RDFS "subClassOf");
+ world->uris.xsd_base64Binary = NEW_URI(LILV_NS_XSD "base64Binary");
+ world->uris.xsd_boolean = NEW_URI(LILV_NS_XSD "boolean");
+ world->uris.xsd_decimal = NEW_URI(LILV_NS_XSD "decimal");
+ world->uris.xsd_double = NEW_URI(LILV_NS_XSD "double");
+ world->uris.xsd_integer = NEW_URI(LILV_NS_XSD "integer");
+ world->uris.null_uri = NULL;
world->lv2_plugin_class = lilv_plugin_class_new(
- world, NULL, world->lv2_plugin_node, "Plugin");
+ world, NULL, world->uris.lv2_Plugin, "Plugin");
assert(world->lv2_plugin_class);
world->n_read_files = 0;
@@ -109,37 +110,9 @@ lilv_world_free(LilvWorld* world)
lilv_plugin_class_free(world->lv2_plugin_class);
world->lv2_plugin_class = NULL;
- sord_node_free(world->world, world->dc_replaces_node);
- sord_node_free(world->world, world->doap_name_node);
- sord_node_free(world->world, world->dyn_manifest_node);
- sord_node_free(world->world, world->lv2_appliesTo_node);
- sord_node_free(world->world, world->lv2_binary_node);
- sord_node_free(world->world, world->lv2_default_node);
- sord_node_free(world->world, world->lv2_extensionData_node);
- sord_node_free(world->world, world->lv2_index_node);
- sord_node_free(world->world, world->lv2_maximum_node);
- sord_node_free(world->world, world->lv2_minimum_node);
- sord_node_free(world->world, world->lv2_name_node);
- sord_node_free(world->world, world->lv2_optionalFeature_node);
- sord_node_free(world->world, world->lv2_plugin_node);
- sord_node_free(world->world, world->lv2_port_node);
- sord_node_free(world->world, world->lv2_portproperty_node);
- sord_node_free(world->world, world->lv2_reportslatency_node);
- sord_node_free(world->world, world->lv2_requiredFeature_node);
- sord_node_free(world->world, world->lv2_specification_node);
- sord_node_free(world->world, world->lv2_symbol_node);
- sord_node_free(world->world, world->pset_value_node);
- sord_node_free(world->world, world->rdf_a_node);
- sord_node_free(world->world, world->rdf_value_node);
- sord_node_free(world->world, world->rdfs_class_node);
- sord_node_free(world->world, world->rdfs_label_node);
- sord_node_free(world->world, world->rdfs_seealso_node);
- sord_node_free(world->world, world->rdfs_subclassof_node);
- sord_node_free(world->world, world->xsd_base64Binary_node);
- sord_node_free(world->world, world->xsd_boolean_node);
- sord_node_free(world->world, world->xsd_decimal_node);
- sord_node_free(world->world, world->xsd_double_node);
- sord_node_free(world->world, world->xsd_integer_node);
+ for (SordNode** n = (SordNode**)&world->uris; *n; ++n) {
+ sord_node_free(world->world, *n);
+ }
for (LilvSpec* spec = world->specs; spec;) {
LilvSpec* next = spec->next;
@@ -325,7 +298,7 @@ lilv_world_add_spec(LilvWorld* world,
SordIter* files = lilv_world_find_statements(
world, world->model,
specification_node,
- world->rdfs_seealso_node,
+ world->uris.rdfs_seeAlso,
NULL,
NULL);
FOREACH_MATCH(files) {
@@ -381,7 +354,7 @@ lilv_world_add_plugin(LilvWorld* world,
SordIter* files = lilv_world_find_statements(
world, world->model,
plugin_node,
- world->rdfs_seealso_node,
+ world->uris.rdfs_seeAlso,
NULL,
NULL);
FOREACH_MATCH(files) {
@@ -413,8 +386,8 @@ lilv_world_load_dyn_manifest(LilvWorld* world,
SordIter* dmanifests = lilv_world_find_statements(
world, world->model,
NULL,
- world->rdf_a_node,
- world->dyn_manifest_node,
+ world->uris.rdf_a,
+ world->uris.dyn_manifest,
bundle_node);
FOREACH_MATCH(dmanifests) {
const SordNode* dmanifest = lilv_match_subject(dmanifests);
@@ -423,7 +396,7 @@ lilv_world_load_dyn_manifest(LilvWorld* world,
SordIter* binaries = lilv_world_find_statements(
world, world->model,
dmanifest,
- world->lv2_binary_node,
+ world->uris.lv2_binary,
NULL,
bundle_node);
if (lilv_matches_end(binaries)) {
@@ -500,8 +473,8 @@ lilv_world_load_dyn_manifest(LilvWorld* world,
SordIter* plug_results = lilv_world_find_statements(
world, world->model,
NULL,
- world->rdf_a_node,
- world->lv2_plugin_node,
+ world->uris.rdf_a,
+ world->uris.lv2_plugin,
bundle_node);
FOREACH_MATCH(plug_results) {
const SordNode* plugin_node = lilv_match_subject(plug_results);
@@ -548,8 +521,8 @@ lilv_world_load_bundle(LilvWorld* world, LilvNode* bundle_uri)
SordIter* plug_results = lilv_world_find_statements(
world, world->model,
NULL,
- world->rdf_a_node,
- world->lv2_plugin_node,
+ world->uris.rdf_a,
+ world->uris.lv2_Plugin,
bundle_node);
FOREACH_MATCH(plug_results) {
const SordNode* plugin_node = lilv_match_subject(plug_results);
@@ -564,8 +537,8 @@ lilv_world_load_bundle(LilvWorld* world, LilvNode* bundle_uri)
SordIter* spec_results = lilv_world_find_statements(
world, world->model,
NULL,
- world->rdf_a_node,
- world->lv2_specification_node,
+ world->uris.rdf_a,
+ world->uris.lv2_Specification,
bundle_node);
FOREACH_MATCH(spec_results) {
const SordNode* spec = lilv_match_subject(spec_results);
@@ -695,8 +668,8 @@ lilv_world_load_plugin_classes(LilvWorld* world)
SordIter* classes = lilv_world_find_statements(
world, world->model,
NULL,
- world->rdf_a_node,
- world->rdfs_class_node,
+ world->uris.rdf_a,
+ world->uris.rdfs_Class,
NULL);
FOREACH_MATCH(classes) {
const SordNode* class_node = lilv_match_subject(classes);
@@ -705,7 +678,7 @@ lilv_world_load_plugin_classes(LilvWorld* world)
SordIter* parents = lilv_world_find_statements(
world, world->model,
class_node,
- world->rdfs_subclassof_node,
+ world->uris.rdfs_subClassOf,
NULL,
NULL);
@@ -726,7 +699,7 @@ lilv_world_load_plugin_classes(LilvWorld* world)
SordIter* labels = lilv_world_find_statements(
world, world->model,
class_node,
- world->rdfs_label_node,
+ world->uris.rdfs_label,
NULL,
NULL);
@@ -769,7 +742,7 @@ lilv_world_load_all(LilvWorld* world)
SordIter* replacement = lilv_world_find_statements(
world, world->model,
NULL,
- world->dc_replaces_node,
+ world->uris.dc_replaces,
lilv_node_as_node(plugin_uri),
NULL);
if (!sord_iter_end(replacement)) {
@@ -799,7 +772,7 @@ lilv_world_load_resource(LilvWorld* world,
int n_read = 0;
SordIter* files = lilv_world_find_statements(world, world->model,
resource->val.uri_val,
- world->rdfs_seealso_node,
+ world->uris.rdfs_seeAlso,
NULL, NULL);
FOREACH_MATCH(files) {
const SordNode* file = lilv_match_object(files);
diff --git a/src/zix/common.h b/src/zix/common.h
index 052cb7b..c0dffeb 100644
--- a/src/zix/common.h
+++ b/src/zix/common.h
@@ -64,7 +64,7 @@ typedef bool (*ZixEqualFunc)(const void* a, const void* b);
/**
Function to destroy an element.
*/
-typedef void (*ZixDestroyFunc)(const void* ptr);
+typedef void (*ZixDestroyFunc)(void* ptr);
/**@}
*/
diff --git a/src/zix/tree.c b/src/zix/tree.c
index 1189230..d22f438 100644
--- a/src/zix/tree.c
+++ b/src/zix/tree.c
@@ -101,8 +101,10 @@ ZIX_API
void
zix_tree_free(ZixTree* t)
{
- zix_tree_free_rec(t, t->root);
- free(t);
+ if (t) {
+ zix_tree_free_rec(t, t->root);
+ free(t);
+ }
}
size_t
diff --git a/test/lilv_test.c b/test/lilv_test.c
index 44596f0..0591d20 100644
--- a/test/lilv_test.c
+++ b/test/lilv_test.c
@@ -30,6 +30,7 @@
#include "lilv/lilv.h"
#include "../src/lilv_internal.h"
+#include "lv2/lv2plug.in/ns/ext/state/state.h"
#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
#define TEST_PATH_MAX 1024
@@ -1074,6 +1075,15 @@ unmap_uri(LV2_URID_Map_Handle handle,
return NULL;
}
+static const char* file_dir = "files";
+
+char*
+lilv_make_path(LV2_State_Make_Path_Handle handle,
+ const char* path)
+{
+ return lilv_strjoin(file_dir, "/", path, NULL);
+}
+
int
test_state(void)
{
@@ -1093,23 +1103,28 @@ test_state(void)
LV2_Feature unmap_feature = { LV2_URID_UNMAP_URI, &unmap };
const LV2_Feature* features[] = { &map_feature, &unmap_feature, NULL };
+ LilvNode* num = lilv_new_int(world, 5);
+ LilvState* nostate = lilv_state_new_from_file(world, &map, num, "/junk");
+ TEST_ASSERT(!nostate);
+
LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, features);
TEST_ASSERT(instance);
-
+ lilv_instance_activate(instance);
lilv_instance_connect_port(instance, 0, &in);
lilv_instance_connect_port(instance, 1, &out);
-
lilv_instance_run(instance, 1);
TEST_ASSERT(in == 1.0);
TEST_ASSERT(out == 1.0);
+ const char* tmpdir = NULL;
+
// Get instance state state
LilvState* state = lilv_state_new_from_instance(
- plugin, instance, get_port_value, world, 0, NULL);
+ plugin, instance, &map, tmpdir, get_port_value, world, 0, NULL);
// Get another instance state
LilvState* state2 = lilv_state_new_from_instance(
- plugin, instance, get_port_value, world, 0, NULL);
+ plugin, instance, &map, tmpdir, get_port_value, world, 0, NULL);
// Ensure they are equal
TEST_ASSERT(lilv_state_equals(state, state2));
@@ -1129,7 +1144,7 @@ test_state(void)
// Run and get a new instance state (which should now differ)
lilv_instance_run(instance, 1);
LilvState* state3 = lilv_state_new_from_instance(
- plugin, instance, get_port_value, world, 0, NULL);
+ plugin, instance, &map, tmpdir, get_port_value, world, 0, NULL);
TEST_ASSERT(!lilv_state_equals(state2, state3)); // num_runs changed
// Restore instance state to original state
@@ -1137,22 +1152,23 @@ test_state(void)
// Take a new snapshot and ensure it matches the set state
LilvState* state4 = lilv_state_new_from_instance(
- plugin, instance, get_port_value, world, 0, NULL);
+ plugin, instance, &map, tmpdir, get_port_value, world, 0, NULL);
TEST_ASSERT(lilv_state_equals(state2, state4));
- // Save state to a file
+ // Save state to a directory
int ret = lilv_state_save(world, &unmap, state, NULL,
- "state.ttl", "manifest.ttl");
+ "./state.lv2", "state", NULL);
TEST_ASSERT(!ret);
- // Load state from file
- LilvState* state5 = lilv_state_new_from_file(world, &map, NULL, "state.ttl");
+ // Load state from directory
+ LilvState* state5 = lilv_state_new_from_file(world, &map, NULL,
+ "./state.lv2/state.ttl");
TEST_ASSERT(lilv_state_equals(state, state5)); // Round trip accuracy
// Save state to default bundle
setenv("LV2_STATE_BUNDLE", "lv2/lilv-test-state.lv2", 1);
const char* state_uri = "http://example.org/test-state";
- ret = lilv_state_save(world, &unmap, state, state_uri, NULL, NULL);
+ ret = lilv_state_save(world, &unmap, state, state_uri, NULL, NULL, NULL);
TEST_ASSERT(!ret);
// Load default bundle into world and load state from it
@@ -1166,9 +1182,92 @@ test_state(void)
unsetenv("LV2_STATE_BUNDLE");
- LilvNode* num = lilv_new_int(world, 5);
- LilvState* nostate = lilv_state_new_from_file(world, &map, num, "/junk");
- TEST_ASSERT(!nostate);
+ // Make a temporary directory and test files support
+ tmpdir = file_dir;
+ mkdir(tmpdir, 0700);
+
+ LV2_State_Make_Path make_path = { NULL, lilv_make_path };
+ LV2_Feature make_path_feature = { LV2_STATE_MAKE_PATH_URI, &make_path };
+ const LV2_Feature* ffeatures[] = { &make_path_feature, &map_feature, NULL };
+
+ lilv_instance_deactivate(instance);
+ lilv_instance_free(instance);
+ instance = lilv_plugin_instantiate(plugin, 48000.0, ffeatures);
+ lilv_instance_activate(instance);
+ lilv_instance_connect_port(instance, 0, &in);
+ lilv_instance_connect_port(instance, 1, &out);
+ lilv_instance_run(instance, 1);
+
+ // Get instance state state
+ LilvState* fstate = lilv_state_new_from_instance(
+ plugin, instance, &map, tmpdir, get_port_value, world, 0, ffeatures);
+
+ // Get another instance state
+ LilvState* fstate2 = lilv_state_new_from_instance(
+ plugin, instance, &map, tmpdir, get_port_value, world, 0, ffeatures);
+
+ // Should be identical
+ TEST_ASSERT(lilv_state_equals(fstate, fstate2));
+
+ // Run, writing more to rec file
+ lilv_instance_run(instance, 2);
+
+ // Get yet another instance state
+ LilvState* fstate3 = lilv_state_new_from_instance(
+ plugin, instance, &map, tmpdir, get_port_value, world, 0, ffeatures);
+
+ // Should be different
+ TEST_ASSERT(!lilv_state_equals(fstate, fstate3));
+
+ // Save state to a directory
+ ret = lilv_state_save(world, &unmap, fstate, NULL,
+ "./fstate.lv2", "fstate", ffeatures);
+ TEST_ASSERT(!ret);
+
+ // Load state from directory
+ LilvState* fstate4 = lilv_state_new_from_file(world, &map, NULL,
+ "./fstate.lv2/fstate.ttl");
+ TEST_ASSERT(lilv_state_equals(fstate, fstate4)); // Round trip accuracy
+
+ // Restore instance state to loaded state
+ lilv_state_restore(fstate, instance, set_port_value, NULL, 0, ffeatures);
+
+ // Take a new snapshot and ensure it matches
+ LilvState* fstate5 = lilv_state_new_from_instance(
+ plugin, instance, &map, tmpdir, get_port_value, world, 0, ffeatures);
+ TEST_ASSERT(lilv_state_equals(fstate3, fstate5));
+
+ // Save state to a directory again
+ ret = lilv_state_save(world, &unmap, fstate, NULL,
+ "./fstate6.lv2", "fstate6", ffeatures);
+ TEST_ASSERT(!ret);
+
+ // Reload it and ensure it's identical to the other loaded version
+ LilvState* fstate6 = lilv_state_new_from_file(world, &map, NULL,
+ "./fstate6.lv2/fstate6.ttl");
+ TEST_ASSERT(lilv_state_equals(fstate4, fstate6));
+
+ // Run, writing more to rec file
+ lilv_instance_run(instance, 2);
+
+ // Take a new snapshot
+ LilvState* fstate7 = lilv_state_new_from_instance(
+ plugin, instance, &map, tmpdir, get_port_value, world, 0, ffeatures);
+ TEST_ASSERT(lilv_state_equals(fstate3, fstate5));
+
+ // Save the changed state to a directory again
+ ret = lilv_state_save(world, &unmap, fstate7, NULL,
+ "./fstate7.lv2", "fstate7", ffeatures);
+ TEST_ASSERT(!ret);
+
+ // Reload it and ensure it's changed
+ LilvState* fstate72 = lilv_state_new_from_file(world, &map, NULL,
+ "./fstate7.lv2/fstate7.ttl");
+ TEST_ASSERT(lilv_state_equals(fstate72, fstate7));
+ TEST_ASSERT(!lilv_state_equals(fstate6, fstate72));
+
+ lilv_instance_deactivate(instance);
+ lilv_instance_free(instance);
lilv_node_free(num);
lilv_node_free(test_state_bundle);
@@ -1180,6 +1279,11 @@ test_state(void)
lilv_state_free(state4);
lilv_state_free(state5);
lilv_state_free(state6);
+ lilv_state_free(fstate);
+ lilv_state_free(fstate2);
+ lilv_state_free(fstate3);
+ lilv_state_free(fstate4);
+ lilv_state_free(fstate5);
// Free URI map
for (size_t i = 0; i < n_uris; ++i) {
@@ -1188,8 +1292,6 @@ test_state(void)
free(uris);
n_uris = 0;
- lilv_instance_free(instance);
-
lilv_node_free(plugin_uri);
lilv_node_free(bundle_uri);
lilv_world_free(world);
@@ -1262,6 +1364,29 @@ test_bad_port_index(void)
/*****************************************************************************/
+int
+test_string(void)
+{
+ char* s = NULL;
+
+ TEST_ASSERT(!strcmp((s = lilv_dirname("/foo/bar")), "/foo"));
+ TEST_ASSERT(!strcmp((s = lilv_dirname("/foo/bar/")), "/foo"));
+ TEST_ASSERT(!strcmp((s = lilv_dirname("/foo///bar/")), "/foo"));
+ TEST_ASSERT(!strcmp((s = lilv_dirname("/foo///bar//")), "/foo"));
+ TEST_ASSERT(!strcmp((s = lilv_dirname("foo")), "."));
+ TEST_ASSERT(!strcmp((s = lilv_dirname("/foo")), "/"));
+ TEST_ASSERT(!strcmp((s = lilv_dirname("/")), "/"));
+ TEST_ASSERT(!strcmp((s = lilv_dirname("//")), "/"));
+ TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a/b", "/a/")), "b"));
+ TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a", "/b/c/")), "/a"));
+ TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a/b/c", "/a/b/d/")), "../c"));
+ TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a/b/c", "/a/b/d/e/")), "../../c"));
+
+ return 1;
+}
+
+/*****************************************************************************/
+
/* add tests here */
static struct TestCase tests[] = {
TEST_CASE(utils),
@@ -1276,6 +1401,8 @@ static struct TestCase tests[] = {
TEST_CASE(state),
TEST_CASE(bad_port_symbol),
TEST_CASE(bad_port_index),
+ TEST_CASE(bad_port_index),
+ TEST_CASE(string),
{ NULL, NULL }
};
diff --git a/test/test_plugin.c b/test/test_plugin.c
index 0ef76f2..55df26c 100644
--- a/test/test_plugin.c
+++ b/test/test_plugin.c
@@ -15,6 +15,9 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#define _BSD_SOURCE /* for realpath */
+
+#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -39,6 +42,10 @@ typedef struct {
LV2_URID atom_Float;
} uris;
+ char* tmp_file_path;
+ char* rec_file_path;
+ FILE* rec_file;
+
float* input;
float* output;
unsigned num_runs;
@@ -47,6 +54,10 @@ typedef struct {
static void
cleanup(LV2_Handle instance)
{
+ Test* test = (Test*)instance;
+ if (test->rec_file) {
+ fclose(test->rec_file);
+ }
free(instance);
}
@@ -79,26 +90,43 @@ instantiate(const LV2_Descriptor* descriptor,
return NULL;
}
- test->map = NULL;
- test->input = NULL;
- test->output = NULL;
- test->num_runs = 0;
+ test->map = NULL;
+ test->input = NULL;
+ test->output = NULL;
+ test->num_runs = 0;
+ test->tmp_file_path = malloc(L_tmpnam);
+ test->rec_file_path = NULL;
+ test->rec_file = NULL;
+
+ tmpnam(test->tmp_file_path);
+
+ LV2_State_Make_Path* make_path = NULL;
- /* Scan host features for URID map */
for (int i = 0; features[i]; ++i) {
if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) {
test->map = (LV2_URID_Map*)features[i]->data;
test->uris.atom_Float = test->map->map(
test->map->handle, NS_ATOM "Float");
+ } else if (!strcmp(features[i]->URI, LV2_STATE_MAKE_PATH_URI)) {
+ make_path = (LV2_State_Make_Path*)features[i]->data;
}
}
if (!test->map) {
- fprintf(stderr, "Host does not support urid:map.\n");
+ fprintf(stderr, "Host does not support urid:map\n");
free(test);
return NULL;
}
+ if (make_path) {
+ test->rec_file_path = make_path->path(make_path->handle, "recfile");
+ if (!(test->rec_file = fopen(test->rec_file_path, "w"))) {
+ fprintf(stderr, "ERROR: Failed to open rec file\n");
+ }
+ fprintf(test->rec_file, "instantiate\n");
+
+ }
+
return (LV2_Handle)test;
}
@@ -108,7 +136,11 @@ run(LV2_Handle instance,
{
Test* test = (Test*)instance;
*test->output = *test->input;
- ++test->num_runs;
+ if (sample_count == 1) {
+ ++test->num_runs;
+ } else if (sample_count == 2 && test->rec_file) {
+ fprintf(test->rec_file, "run\n");
+ }
}
static uint32_t
@@ -126,6 +158,16 @@ save(LV2_Handle instance,
{
Test* plugin = (Test*)instance;
+ LV2_State_Map_Path* map_path = NULL;
+ //LV2_State_Make_Path* make_path = NULL;
+ for (int i = 0; features && features[i]; ++i) {
+ if (!strcmp(features[i]->URI, LV2_STATE_MAP_PATH_URI)) {
+ map_path = (LV2_State_Map_Path*)features[i]->data;
+ }/* else if (!strcmp(features[i]->URI, LV2_STATE_MAKE_PATH_URI)) {
+ make_path = (LV2_State_Make_Path*)features[i]->data;
+ }*/
+ }
+
store(callback_data,
map_uri(plugin, "http://example.org/greeting"),
"hello",
@@ -181,6 +223,44 @@ save(LV2_Handle instance,
sizeof(blob),
map_uri(plugin, "http://example.org/SomeUnknownType"),
LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ if (map_path) {
+ FILE* file = fopen(plugin->tmp_file_path, "w");
+ fprintf(file, "Hello\n");
+ fclose(file);
+ char* apath = map_path->abstract_path(map_path->handle,
+ plugin->tmp_file_path);
+ char* apath2 = map_path->abstract_path(map_path->handle,
+ plugin->tmp_file_path);
+ if (strcmp(apath, apath2)) {
+ fprintf(stderr, "ERROR: Path %s != %s\n", apath, apath2);
+ }
+
+ store(callback_data,
+ map_uri(plugin, "http://example.org/extfile"),
+ apath,
+ strlen(apath) + 1,
+ map_uri(plugin, LV2_STATE_PATH_URI),
+ LV2_STATE_IS_PORTABLE);
+
+ free(apath);
+ free(apath2);
+
+ if (plugin->rec_file) {
+ fflush(plugin->rec_file);
+ apath = map_path->abstract_path(map_path->handle,
+ plugin->rec_file_path);
+
+ store(callback_data,
+ map_uri(plugin, "http://example.org/recfile"),
+ apath,
+ strlen(apath) + 1,
+ map_uri(plugin, LV2_STATE_PATH_URI),
+ LV2_STATE_IS_PORTABLE);
+
+ free(apath);
+ }
+ }
}
static void
@@ -192,6 +272,13 @@ restore(LV2_Handle instance,
{
Test* plugin = (Test*)instance;
+ LV2_State_Map_Path* map_path = NULL;
+ for (int i = 0; features && features[i]; ++i) {
+ if (!strcmp(features[i]->URI, LV2_STATE_MAP_PATH_URI)) {
+ map_path = (LV2_State_Map_Path*)features[i]->data;
+ }
+ }
+
size_t size;
uint32_t type;
uint32_t valflags;
@@ -200,6 +287,20 @@ restore(LV2_Handle instance,
callback_data,
map_uri(plugin, "http://example.org/num-runs"),
&size, &type, &valflags);
+
+ char* apath = (char*)retrieve(
+ callback_data,
+ map_uri(plugin, "http://example.org/extfile"),
+ &size, &type, &valflags);
+
+ if (map_path && apath) {
+ char* path = map_path->absolute_path(map_path->handle, apath);
+ char* real_path = realpath(path, NULL);
+ if (strcmp(real_path, plugin->tmp_file_path)) {
+ fprintf(stderr, "ERROR: Restored bad path `%s' != `%s'\n",
+ real_path, plugin->tmp_file_path);
+ }
+ }
}
const void*