diff options
author | David Robillard <d@drobilla.net> | 2015-03-07 08:42:56 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2015-03-07 08:42:56 +0000 |
commit | 1b420b15b3c88ebbcd6789f78b69bd284e0a98f7 (patch) | |
tree | 2871ea66f0651b2a43ff98c6f107e1654ee3aeed | |
parent | cd320a7ccd392a5da2df31ec5edc9b07db5befab (diff) | |
download | lilv-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-- | NEWS | 8 | ||||
-rw-r--r-- | lilv/lilv.h | 34 | ||||
-rw-r--r-- | src/lilv_internal.h | 5 | ||||
-rw-r--r-- | src/node.c | 9 | ||||
-rw-r--r-- | src/state.c | 238 | ||||
-rw-r--r-- | src/world.c | 9 | ||||
-rw-r--r-- | test/lilv_test.c | 15 | ||||
-rw-r--r-- | wscript | 2 |
8 files changed, 267 insertions, 53 deletions
@@ -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, @@ -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); @@ -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 |