summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2015-03-07 08:42:56 +0000
committerDavid Robillard <d@drobilla.net>2015-03-07 08:42:56 +0000
commit1b420b15b3c88ebbcd6789f78b69bd284e0a98f7 (patch)
tree2871ea66f0651b2a43ff98c6f107e1654ee3aeed
parentcd320a7ccd392a5da2df31ec5edc9b07db5befab (diff)
downloadlilv-1b420b15b3c88ebbcd6789f78b69bd284e0a98f7.tar.gz
lilv-1b420b15b3c88ebbcd6789f78b69bd284e0a98f7.tar.bz2
lilv-1b420b15b3c88ebbcd6789f78b69bd284e0a98f7.zip
Add support for state deletion.
Add lilv_node_get_path(). Add lilv_state_get_uri(). Add lilv_state_delete(). Fix creation of duplicate manifest entries when saving state. git-svn-id: http://svn.drobilla.net/lad/trunk/lilv@5617 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r--NEWS8
-rw-r--r--lilv/lilv.h34
-rw-r--r--src/lilv_internal.h5
-rw-r--r--src/node.c9
-rw-r--r--src/state.c238
-rw-r--r--src/world.c9
-rw-r--r--test/lilv_test.c15
-rw-r--r--wscript2
8 files changed, 267 insertions, 53 deletions
diff --git a/NEWS b/NEWS
index 43e369c..cef9739 100644
--- a/NEWS
+++ b/NEWS
@@ -1,8 +1,12 @@
-lilv (0.21.1) unstable;
+lilv (0.21.3) unstable;
* Fix loading files with spaces in their path
* Add lilv_file_uri_parse() for correct URI to path conversion
+ * Add lilv_node_get_path() for convenient file URI path access
* Add lilv_state_emit_port_values() for special port value handling
+ * Add lilv_state_get_uri()
+ * Add lilv_state_delete() for deleting user saved presets
+ * Fix creation of duplicate manifest entries when saving state
* Expose lilv_world_load_specifications() and
lilv_world_load_plugin_classes()
* Tolerate passing NULL to lilv_state_restore()
@@ -12,7 +16,7 @@ lilv (0.21.1) unstable;
* Windows fixes (thanks John Emmas)
* Minor documentation improvements
- -- David Robillard <d@drobilla.net> Thu, 19 Feb 2015 02:35:18 -0500
+ -- David Robillard <d@drobilla.net> Sat, 07 Mar 2015 03:31:04 -0500
lilv (0.20.0) stable;
diff --git a/lilv/lilv.h b/lilv/lilv.h
index 2248df2..e907789 100644
--- a/lilv/lilv.h
+++ b/lilv/lilv.h
@@ -259,6 +259,14 @@ LILV_API const char*
lilv_node_as_string(const LilvNode* value);
/**
+ Return the path of a file URI node.
+ Returns NULL if `value` is not a file URI.
+ Returned value must be freed by caller.
+*/
+LILV_API char*
+lilv_node_get_path(const LilvNode* value, char** hostname);
+
+/**
Return whether this value is a decimal literal.
*/
LILV_API bool
@@ -1342,6 +1350,14 @@ LILV_API const LilvNode*
lilv_state_get_plugin_uri(const LilvState* state);
/**
+ Get the URI of `state`.
+
+ This may return NULL if the state has not been saved and has no URI.
+*/
+LILV_API const LilvNode*
+lilv_state_get_uri(const LilvState* state);
+
+/**
Get the label of `state`.
*/
LILV_API const char*
@@ -1462,6 +1478,24 @@ lilv_state_to_string(LilvWorld* world,
const char* base_uri);
/**
+ Unload a state from the world and delete all associated files.
+ @param world The world.
+ @param state State to remove from the system.
+
+ This function DELETES FILES/DIRECTORIES FROM THE FILESYSTEM! It is intended
+ for removing user-saved presets, but can delete any state the user has
+ permission to delete, including presets shipped with plugins.
+
+ The rdfs:seeAlso file for the state will be removed. The entry in the
+ bundle's manifest.ttl is removed, and if this results in an empty manifest,
+ then the manifest file is removed. If this results in an empty bundle, then
+ the bundle directory is removed as well.
+*/
+LILV_API int
+lilv_state_delete(LilvWorld* world,
+ const LilvState* state);
+
+/**
@}
@name Scale Point
@{
diff --git a/src/lilv_internal.h b/src/lilv_internal.h
index 2d3fb23..cd4a621 100644
--- a/src/lilv_internal.h
+++ b/src/lilv_internal.h
@@ -30,6 +30,8 @@ extern "C" {
# include <windows.h>
# define dlopen(path, flags) LoadLibrary(path)
# define dlclose(lib) FreeLibrary((HMODULE)lib)
+# define unlink(path) _unlink(path)
+# define rmdir(path) _rmdir(path)
# ifdef _MSC_VER
# define __func__ __FUNCTION__
# define INFINITY DBL_MAX + DBL_MAX
@@ -39,6 +41,7 @@ extern "C" {
static inline char* dlerror(void) { return "Unknown error"; }
#else
# include <dlfcn.h>
+# include <unistd.h>
#endif
#include "serd/serd.h"
@@ -273,6 +276,8 @@ LilvScalePoints* lilv_scale_points_new(void);
LilvPluginClasses* lilv_plugin_classes_new(void);
LilvUIs* lilv_uis_new(void);
+LilvNode* lilv_world_get_manifest_uri(LilvWorld* world, LilvNode* bundle_uri);
+
const uint8_t* lilv_world_blank_node_prefix(LilvWorld* world);
SerdStatus lilv_world_load_file(LilvWorld* world,
diff --git a/src/node.c b/src/node.c
index b57b1b8..d745adb 100644
--- a/src/node.c
+++ b/src/node.c
@@ -385,3 +385,12 @@ lilv_node_as_bool(const LilvNode* value)
{
return lilv_node_is_bool(value) ? value->val.bool_val : false;
}
+
+LILV_API char*
+lilv_node_get_path(const LilvNode* value, char** hostname)
+{
+ if (lilv_node_is_uri(value)) {
+ return lilv_file_uri_parse(lilv_node_as_uri(value), hostname);
+ }
+ return NULL;
+}
diff --git a/src/state.c b/src/state.c
index f75a2bb..3205dcc 100644
--- a/src/state.c
+++ b/src/state.c
@@ -51,6 +51,7 @@ typedef struct {
struct LilvStateImpl {
LilvNode* plugin_uri; ///< Plugin URI
+ LilvNode* uri; ///< State/preset URI
char* dir; ///< Save directory (if saved)
char* file_dir; ///< Directory for files created by plugin
char* copy_dir; ///< Directory for snapshots of external files
@@ -337,8 +338,7 @@ lilv_state_new_from_instance(const LilvPlugin* plugin,
{
const LV2_Feature** sfeatures = NULL;
LilvWorld* const world = plugin->world;
- LilvState* const state = (LilvState*)malloc(sizeof(LilvState));
- memset(state, '\0', sizeof(LilvState));
+ LilvState* const state = (LilvState*)calloc(1, 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);
@@ -458,10 +458,10 @@ new_state_from_model(LilvWorld* world,
}
// Allocate state
- LilvState* const state = (LilvState*)malloc(sizeof(LilvState));
- memset(state, '\0', sizeof(LilvState));
+ LilvState* const state = (LilvState*)calloc(1, sizeof(LilvState));
state->dir = lilv_strdup(dir);
state->atom_Path = map->map(map->handle, LV2_ATOM__Path);
+ state->uri = lilv_node_new_from_node(world, node);
// Get the plugin URI this state applies to
SordIter* i = sord_search(model, node, world->uris.lv2_appliesTo, 0, 0);
@@ -691,7 +691,7 @@ ttl_writer(SerdSink sink, void* stream, const SerdNode* base, SerdEnv** new_env)
serd_uri_parse(base->buf, &base_uri);
}
- SerdEnv* env = serd_env_new(base);
+ SerdEnv* env = *new_env ? *new_env : serd_env_new(base);
set_prefixes(env);
SerdWriter* writer = serd_writer_new(
@@ -702,7 +702,10 @@ ttl_writer(SerdSink sink, void* stream, const SerdNode* base, SerdEnv** new_env)
sink,
stream);
- *new_env = env;
+ if (!*new_env) {
+ *new_env = env;
+ }
+
return writer;
}
@@ -721,53 +724,117 @@ ttl_file_writer(FILE* fd, const SerdNode* node, SerdEnv** env)
return writer;
}
+static void
+add_to_model(SordWorld* world,
+ SerdEnv* env,
+ SordModel* model,
+ const SerdNode s,
+ const SerdNode p,
+ const SerdNode o)
+{
+ SordNode* ss = sord_node_from_serd_node(world, env, &s, NULL, NULL);
+ SordNode* sp = sord_node_from_serd_node(world, env, &p, NULL, NULL);
+ SordNode* so = sord_node_from_serd_node(world, env, &o, NULL, NULL);
+
+ SordQuad quad = { ss, sp, so, NULL };
+ sord_add(model, quad);
+
+ sord_node_free(world, ss);
+ sord_node_free(world, sp);
+ sord_node_free(world, so);
+}
+
+static void
+remove_manifest_entry(SordWorld* world, SordModel* model, const char* subject)
+{
+ SordNode* s = sord_new_uri(world, USTR(subject));
+ SordIter* i = sord_search(model, s, NULL, NULL, NULL);
+ while (!sord_iter_end(i)) {
+ sord_erase(model, i);
+ }
+ sord_iter_free(i);
+ sord_node_free(world, s);
+}
+
static int
-add_state_to_manifest(const LilvNode* plugin_uri,
+add_state_to_manifest(LilvWorld* lworld,
+ const LilvNode* plugin_uri,
const char* manifest_path,
const char* state_uri,
const char* state_path)
{
- FILE* fd = fopen(manifest_path, "a");
- if (!fd) {
- LILV_ERRORF("Failed to open %s (%s)\n",
- manifest_path, strerror(errno));
- return 4;
- }
-
- lilv_flock(fd, true);
-
- SerdNode file = serd_node_new_file_uri(USTR(state_path), 0, 0, 0);
+ SordWorld* world = lworld->world;
SerdNode manifest = serd_node_new_file_uri(USTR(manifest_path), 0, 0, 0);
- SerdEnv* env = NULL;
- SerdWriter* writer = ttl_file_writer(fd, &manifest, &env);
+ SerdNode file = serd_node_new_file_uri(USTR(state_path), 0, 0, 0);
+ SerdEnv* env = serd_env_new(&manifest);
+ SordModel* model = sord_new(world, SORD_SPO, false);
+
+ FILE* rfd = fopen(manifest_path, "r");
+ if (rfd) {
+ // Read manifest into model
+ SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL);
+ lilv_flock(rfd, true);
+ serd_reader_read_file_handle(reader, rfd, manifest.buf);
+ serd_reader_free(reader);
+ }
+ // Choose state URI (use file URI if not given)
if (!state_uri) {
state_uri = (const char*)file.buf;
}
- // <state> a pset:Preset
+ // Remove any existing manifest entries for this state
+ remove_manifest_entry(world, model, state_uri);
+
+ // Add manifest entry for this state to model
SerdNode s = serd_node_from_string(SERD_URI, USTR(state_uri));
- SerdNode p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type"));
- SerdNode o = serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__Preset));
- serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL);
+
+ // <state> a pset:Preset
+ add_to_model(world, env, model,
+ s,
+ serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type")),
+ serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__Preset)));
+
+ // <state> a pset:Preset
+ add_to_model(world, env, model,
+ s,
+ serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type")),
+ serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__Preset)));
// <state> rdfs:seeAlso <file>
- p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDFS "seeAlso"));
- serd_writer_write_statement(writer, 0, NULL, &s, &p, &file, NULL, NULL);
+ add_to_model(world, env, model,
+ s,
+ serd_node_from_string(SERD_URI, USTR(LILV_NS_RDFS "seeAlso")),
+ file);
// <state> lv2:appliesTo <plugin>
- p = serd_node_from_string(SERD_URI, USTR(LV2_CORE__appliesTo));
- o = serd_node_from_string(
- SERD_URI, USTR(lilv_node_as_string(plugin_uri)));
- serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL);
+ add_to_model(world, env, model,
+ s,
+ serd_node_from_string(SERD_URI, USTR(LV2_CORE__appliesTo)),
+ serd_node_from_string(SERD_URI,
+ USTR(lilv_node_as_string(plugin_uri))));
+
+ // Write manifest model to file
+ FILE* wfd = fopen(manifest_path, "w");
+ if (wfd) {
+ SerdWriter* writer = ttl_file_writer(wfd, &manifest, &env);
+ sord_write(model, writer, NULL);
+ serd_writer_free(writer);
+ fclose(wfd);
+ } else {
+ LILV_ERRORF("Failed to open %s for writing (%s)\n",
+ manifest_path, strerror(errno));
+ }
+ sord_free(model);
serd_node_free(&file);
serd_node_free(&manifest);
- serd_writer_free(writer);
serd_env_free(env);
- lilv_flock(fd, false);
- fclose(fd);
+ if (rfd) {
+ lilv_flock(rfd, false);
+ fclose(rfd);
+ }
return 0;
}
@@ -960,31 +1027,32 @@ lilv_state_save(LilvWorld* world,
return 4;
}
- // FIXME: make parameter non-const?
- if (state->dir && strcmp(state->dir, abs_dir)) {
- free(state->dir);
- ((LilvState*)state)->dir = lilv_strdup(abs_dir);
- }
-
// Create symlinks to files if necessary
lilv_state_make_links(state, abs_dir);
// Write state to Turtle file
- SerdNode file = serd_node_new_file_uri(USTR(path), NULL, NULL, false);
- SerdEnv* env = NULL;
- SerdWriter* writer = ttl_file_writer(fd, &file, &env);
-
- SerdNode node = uri ? serd_node_from_string(SERD_URI, USTR(uri)) : file;
- int ret = lilv_state_write(
- world, map, unmap, state, writer, (const char*)node.buf, dir);
+ SerdNode file = serd_node_new_file_uri(USTR(path), NULL, NULL, false);
+ SerdNode node = uri ? serd_node_from_string(SERD_URI, USTR(uri)) : file;
+ SerdEnv* env = NULL;
+ SerdWriter* ttl = ttl_file_writer(fd, &file, &env);
+ int ret = lilv_state_write(
+ world, map, unmap, state, ttl, (const char*)node.buf, dir);
+
+ // Set saved dir and uri (FIXME: const violation)
+ SerdNode dir_uri = serd_node_new_file_uri(USTR(abs_dir), NULL, NULL, false);
+ free(state->dir);
+ lilv_node_free(state->uri);
+ ((LilvState*)state)->dir = (char*)dir_uri.buf;
+ ((LilvState*)state)->uri = lilv_new_uri(world, (const char*)node.buf);
serd_node_free(&file);
- serd_writer_free(writer);
+ serd_writer_free(ttl);
serd_env_free(env);
fclose(fd);
+ // Add entry to manifest
char* const manifest = lilv_path_join(abs_dir, "manifest.ttl");
- add_state_to_manifest(state->plugin_uri, manifest, uri, path);
+ add_state_to_manifest(world, state->plugin_uri, manifest, uri, path);
free(manifest);
free(abs_dir);
@@ -1017,6 +1085,75 @@ lilv_state_to_string(LilvWorld* world,
return (char*)serd_chunk_sink_finish(&chunk);
}
+LILV_API int
+lilv_state_delete(LilvWorld* world,
+ const LilvState* state)
+{
+ if (!state->dir || !state->uri) {
+ LILV_ERROR("Attempt to delete unsaved state\n");
+ return -1;
+ }
+
+ LilvNode* bundle = lilv_new_uri(world, state->dir);
+ LilvNode* manifest = lilv_world_get_manifest_uri(world, bundle);
+ char* manifest_path = lilv_node_get_path(manifest, NULL);
+ SordModel* model = sord_new(world->world, SORD_SPO, false);
+
+ {
+ // Read manifest into model
+ SerdEnv* env = serd_env_new(sord_node_to_serd_node(manifest->node));
+ SerdReader* ttl = sord_new_reader(model, env, SERD_TURTLE, NULL);
+ serd_reader_read_file(ttl, USTR(manifest_path));
+ serd_reader_free(ttl);
+ serd_env_free(env);
+ }
+
+ SordNode* file = sord_get(
+ model, state->uri->node, world->uris.rdfs_seeAlso, NULL, NULL);
+ if (file) {
+ // Remove state file
+ char* file_path = lilv_file_uri_parse(
+ (const char*)sord_node_get_string(file), NULL);
+ if (unlink(file_path)) {
+ LILV_ERRORF("Failed to remove %s (%s)\n", file_path, strerror(errno));
+ }
+ free(file_path);
+ }
+
+ // Remove any existing manifest entries for this state
+ remove_manifest_entry(
+ world->world, model, lilv_node_as_string(state->uri));
+ remove_manifest_entry(
+ world->world, world->model, lilv_node_as_string(state->uri));
+
+ // Drop bundle from model
+ lilv_world_unload_bundle(world, bundle);
+
+ if (sord_num_quads(model) == 0) {
+ // Manifest is empty, attempt to remove bundle entirely
+ if (unlink(manifest_path)) {
+ LILV_ERRORF("Failed to remove %s (%s)\n",
+ manifest_path, strerror(errno));
+ }
+ char* dir_path = lilv_file_uri_parse(state->dir, NULL);
+ if (rmdir(dir_path)) {
+ LILV_ERRORF("Failed to remove %s (%s)\n",
+ dir_path, strerror(errno));
+ }
+ free(dir_path);
+ } else {
+ // Still something in the manifest, reload bundle
+ lilv_world_load_bundle(world, bundle);
+ }
+
+ sord_free(model);
+ free(manifest_path);
+ lilv_node_free(manifest);
+ lilv_node_free(bundle);
+
+ return 0;
+}
+
LILV_API void
lilv_state_free(LilvState* state)
{
@@ -1029,6 +1166,7 @@ lilv_state_free(LilvState* state)
free(state->values[i].symbol);
}
lilv_node_free(state->plugin_uri);
+ lilv_node_free(state->uri);
zix_tree_free(state->abs2rel);
zix_tree_free(state->rel2abs);
free(state->props);
@@ -1097,6 +1235,12 @@ lilv_state_get_plugin_uri(const LilvState* state)
return state->plugin_uri;
}
+LILV_API const LilvNode*
+lilv_state_get_uri(const LilvState* state)
+{
+ return state->uri;
+}
+
LILV_API const char*
lilv_state_get_label(const LilvState* state)
{
diff --git a/src/world.c b/src/world.c
index 566c862..2855885 100644
--- a/src/world.c
+++ b/src/world.c
@@ -564,7 +564,7 @@ lilv_world_load_dyn_manifest(LilvWorld* world,
#endif // LILV_DYN_MANIFEST
}
-static LilvNode*
+LilvNode*
lilv_world_get_manifest_uri(LilvWorld* world, LilvNode* bundle_uri)
{
SerdNode manifest_uri = lilv_new_uri_relative_to_base(
@@ -628,8 +628,7 @@ static int
lilv_world_drop_graph(LilvWorld* world, LilvNode* graph)
{
SordIter* i = sord_search(world->model, NULL, NULL, NULL, graph->node);
- while (!sord_iter_end(i) &&
- sord_node_equals(sord_iter_get_node(i, SORD_GRAPH), graph->node)) {
+ while (!sord_iter_end(i)) {
const SerdStatus st = sord_erase(world->model, i);
if (st) {
LILV_ERRORF("Error removing statement from <%s> (%s)\n",
@@ -657,6 +656,10 @@ lilv_world_unload_file(LilvWorld* world, LilvNode* file)
LILV_API int
lilv_world_unload_bundle(LilvWorld* world, LilvNode* bundle_uri)
{
+ if (!bundle_uri) {
+ return 0;
+ }
+
// Remove loaded_files entry for manifest.ttl
LilvNode* manifest = lilv_world_get_manifest_uri(world, bundle_uri);
lilv_world_unload_file(world, manifest);
diff --git a/test/lilv_test.c b/test/lilv_test.c
index a1b8f04..c40dc76 100644
--- a/test/lilv_test.c
+++ b/test/lilv_test.c
@@ -245,6 +245,7 @@ test_value(void)
TEST_ASSERT(lilv_node_is_literal(sval));
TEST_ASSERT(lilv_node_is_literal(ival));
TEST_ASSERT(lilv_node_is_literal(fval));
+ TEST_ASSERT(!lilv_node_get_path(fval, NULL));
TEST_ASSERT(!strcmp(lilv_node_as_uri(uval), "http://example.org"));
TEST_ASSERT(!strcmp(lilv_node_as_string(sval), "Foo"));
@@ -1523,6 +1524,12 @@ test_state(void)
// Ensure they are equal
TEST_ASSERT(lilv_state_equals(state, state2));
+ // Check that we can't delete unsaved state
+ TEST_ASSERT(lilv_state_delete(world, state));
+
+ // Check that state has no URI
+ TEST_ASSERT(!lilv_state_get_uri(state));
+
// Check that we can't save a state with no URI
char* bad_state_str = lilv_state_to_string(
world, &map, &unmap, state, NULL, NULL);
@@ -1603,6 +1610,11 @@ test_state(void)
LilvState* state6 = lilv_state_new_from_world(world, &map, test_state_node);
TEST_ASSERT(lilv_state_equals(state, state6)); // Round trip accuracy
+ // Check that loaded state has correct URI
+ TEST_ASSERT(lilv_state_get_uri(state6));
+ TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_state_get_uri(state6)),
+ state_uri));
+
lilv_world_unload_resource(world, test_state_node);
lilv_world_unload_bundle(world, test_state_bundle);
@@ -1712,6 +1724,9 @@ test_state(void)
TEST_ASSERT(lilv_state_equals(fstate72, fstate7));
TEST_ASSERT(!lilv_state_equals(fstate6, fstate72));
+ // Delete saved state
+ lilv_state_delete(world, fstate7);
+
lilv_instance_deactivate(instance);
lilv_instance_free(instance);
diff --git a/wscript b/wscript
index 8f546ea..eef6e12 100644
--- a/wscript
+++ b/wscript
@@ -12,7 +12,7 @@ import waflib.Logs as Logs
# major increment <=> incompatible changes
# minor increment <=> compatible changes (additions)
# micro increment <=> no interface changes
-LILV_VERSION = '0.21.2'
+LILV_VERSION = '0.21.3'
LILV_MAJOR_VERSION = '0'
# Mandatory waf variables