From 7823f743ee0c748ff4ced56838cd737516d857ab Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 4 Jan 2012 19:21:12 +0000 Subject: Implement proper support for LV2_STATE_BUNDLE. Test saving state to default bundle path. Fix memory leaks. git-svn-id: http://svn.drobilla.net/lad/trunk/lilv@3915 a436a847-0d15-0410-975c-d299462d15a1 --- src/lilv_internal.h | 1 + src/plugin.c | 17 ++++---- src/state.c | 115 ++++++++++++++++++++++++++++++++++------------------ src/util.c | 39 ++++++++++++++++++ src/world.c | 40 +----------------- test/lilv_test.c | 34 ++++++++++++++++ wscript | 49 ++++++++++++++-------- 7 files changed, 194 insertions(+), 101 deletions(-) diff --git a/src/lilv_internal.h b/src/lilv_internal.h index 9f19c1c..7191daa 100644 --- a/src/lilv_internal.h +++ b/src/lilv_internal.h @@ -330,6 +330,7 @@ LilvNodes* lilv_nodes_from_stream_objects(LilvWorld* w, char* lilv_strjoin(const char* first, ...); char* lilv_strdup(const char* str); char* lilv_get_lang(void); +char* lilv_expand(const char* path); typedef void (*VoidFunc)(void); diff --git a/src/plugin.c b/src/plugin.c index 2a7ccee..2bb70c4 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -217,17 +217,20 @@ lilv_plugin_load_ports_if_necessary(const LilvPlugin* const_p) LilvNode* symbol = lilv_plugin_get_unique( p, port, p->world->lv2_symbol_node); + bool error = false; if (!lilv_node_is_string(symbol) || !is_symbol(symbol->str_val)) { LILV_ERRORF("Plugin <%s> port symbol `%s' is invalid\n", lilv_node_as_uri(p->plugin_uri), lilv_node_as_string(symbol)); - goto error; + error = true; + goto done; } if (!lilv_node_is_int(index)) { LILV_ERRORF("Plugin <%s> port index is not an integer\n", lilv_node_as_uri(p->plugin_uri)); - goto error; + error = true; + goto done; } uint32_t this_index = lilv_node_as_int(index); @@ -265,13 +268,13 @@ lilv_plugin_load_ports_if_necessary(const LilvPlugin* const_p) } lilv_match_end(types); - continue; - - error: + done: lilv_node_free(symbol); lilv_node_free(index); - lilv_plugin_free_ports(p); - break; // Invalid plugin + if (error) { // Invalid plugin + lilv_plugin_free_ports(p); + break; + } } lilv_match_end(ports); diff --git a/src/state.c b/src/state.c index c747937..05570cd 100644 --- a/src/state.c +++ b/src/state.c @@ -307,6 +307,7 @@ new_state_from_model(LilvWorld* world, state->label = lilv_strdup( (const char*)sord_node_get_string(lilv_match_object(i))); sord_iter_free(i); + } else { } // Get port values @@ -609,6 +610,70 @@ pathify(const char* in) return out; } +static int +mkdir_p(const char* dir_path) +{ + char* path = lilv_strdup(dir_path); + const size_t path_len = strlen(path); + for (size_t i = 1; i <= path_len; ++i) { + if (path[i] == LILV_DIR_SEP[0] || path[i] == '\0') { + path[i] = '\0'; + if (mkdir(path, 0755) && errno != EEXIST) { + LILV_ERRORF("Failed to create %s (%s)\n", + path, strerror(errno)); + free(path); + return 1; + } + path[i] = LILV_DIR_SEP[0]; + } + } + + free(path); + return 0; +} + +static int +lilv_default_state_path(LilvWorld* world, + const LilvState* state, + char** path, + char** manifest_path) +{ +#ifdef HAVE_MKDIR + if (!state->label) { + LILV_ERROR("Attempt to save state with no label or path.\n"); + return 1; + } + + 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; +#else + LILV_ERROR("Save to default state path but mkdir is unavailable.\n"); + return 4; +#endif +} + LILV_API int lilv_state_save(LilvWorld* world, @@ -621,49 +686,13 @@ lilv_state_save(LilvWorld* world, char* default_path = NULL; char* default_manifest_path = NULL; if (!path) { -#ifdef HAVE_MKDIR - if (!state->label) { - LILV_ERROR("Attempt to save state with no label or path.\n"); + if (lilv_default_state_path( + world, state, &default_path, &default_manifest_path)) { return 1; } - - const char* const home = getenv("HOME"); - if (!home) { - LILV_ERROR("$HOME is undefined\n"); - return 2; - } - - // Create ~/.lv2/ - char* const lv2dir = lilv_strjoin(home, "/.lv2/", NULL); - if (mkdir(lv2dir, 0755) && errno != EEXIST) { - LILV_ERRORF("Unable to create %s (%s)\n", lv2dir, strerror(errno)); - free(lv2dir); - return 3; - } - - // Create ~/.lv2/presets.lv2/ - char* const bundle = lilv_strjoin(lv2dir, "presets.lv2/", NULL); - if (mkdir(bundle, 0755) && errno != EEXIST) { - LILV_ERRORF("Unable to create %s (%s)\n", lv2dir, strerror(errno)); - free(lv2dir); - free(bundle); - return 4; - } - - char* const filename = pathify(state->label); - default_path = lilv_strjoin(bundle, filename, ".ttl", NULL); - default_manifest_path = lilv_strjoin(bundle, "manifest.ttl", NULL); - + path = default_path; manifest_path = default_manifest_path; - - free(lv2dir); - free(bundle); - free(filename); -#else - LILV_ERROR("Save to default state path but mkdir is unavailable.\n"); - return 1; -#endif } FILE* fd = fopen(path, "w"); @@ -779,6 +808,7 @@ lilv_state_save(LilvWorld* world, serd_writer_write_statement( writer, SERD_ANON_CONT, NULL, &state_node, &p, &o, &t, NULL); + lilv_node_free(node); } else { char name[16]; snprintf(name, sizeof(name), "b%u", i); @@ -835,6 +865,13 @@ void lilv_state_free(LilvState* state) { if (state) { + for (uint32_t i = 0; i < state->num_props; ++i) { + free(state->props[i].value); + } + for (uint32_t i = 0; i < state->num_values; ++i) { + lilv_node_free(state->values[i].value); + free(state->values[i].symbol); + } lilv_node_free(state->plugin_uri); free(state->props); free(state->values); diff --git a/src/util.c b/src/util.c index 0af0e1a..1c9a46e 100644 --- a/src/util.c +++ b/src/util.c @@ -14,6 +14,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define _POSIX_SOURCE 1 /* for wordexp */ + #include #include #include @@ -21,6 +23,10 @@ #include "lilv_internal.h" +#ifdef HAVE_WORDEXP +# include +#endif + char* lilv_strjoin(const char* first, ...) { @@ -99,3 +105,36 @@ lilv_get_lang(void) return lang; } + +/** Expand variables (e.g. POSIX ~ or $FOO, Windows %FOO%) in @a path. */ +char* +lilv_expand(const char* path) +{ +#ifdef HAVE_WORDEXP + char* ret = NULL; + wordexp_t p; + if (wordexp(path, &p, 0)) { + LILV_ERRORF("Error expanding path `%s'\n", path); + return lilv_strdup(path); + } + if (p.we_wordc == 0) { + /* Literal directory path (e.g. no variables or ~) */ + ret = lilv_strdup(path); + } else if (p.we_wordc == 1) { + /* Directory path expands (e.g. contains ~ or $FOO) */ + ret = lilv_strdup(p.we_wordv[0]); + } else { + /* Multiple expansions in a single directory path? */ + LILV_ERRORF("Malformed path `%s'\n", path); + ret = lilv_strdup(path); + } + wordfree(&p); +#elif defined(__WIN32__) + static const size_t len = 32767; + char* ret = malloc(len); + ExpandEnvironmentStrings(path, ret, len); +#else + char* ret = lilv_strdup(path); +#endif + return ret; +} diff --git a/src/world.c b/src/world.c index 2029136..8151a73 100644 --- a/src/world.c +++ b/src/world.c @@ -14,7 +14,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#define _POSIX_SOURCE 1 /* for wordexp */ +#define _POSIX_SOURCE 1 /* for readdir_r */ #include #include @@ -25,10 +25,6 @@ #include "lilv_internal.h" -#ifdef HAVE_WORDEXP -# include -#endif - LILV_API LilvWorld* lilv_world_new(void) @@ -582,43 +578,11 @@ lilv_world_load_bundle(LilvWorld* world, LilvNode* bundle_uri) serd_node_free(&manifest_uri); } -/** Expand variables (e.g. POSIX ~ or $FOO, Windows %FOO%) in @a path. */ -static char* -expand(const char* path) -{ -#ifdef HAVE_WORDEXP - char* ret = NULL; - wordexp_t p; - if (wordexp(path, &p, 0)) { - LILV_ERRORF("Error expanding path `%s'\n", path); - return lilv_strdup(path); - } - if (p.we_wordc == 0) { - /* Literal directory path (e.g. no variables or ~) */ - ret = lilv_strdup(path); - } else if (p.we_wordc == 1) { - /* Directory path expands (e.g. contains ~ or $FOO) */ - ret = lilv_strdup(p.we_wordv[0]); - } else { - /* Multiple expansions in a single directory path? */ - LILV_ERRORF("Malformed path `%s' ignored\n", path); - } - wordfree(&p); -#elif defined(__WIN32__) - static const size_t len = 32767; - char* ret = malloc(len); - ExpandEnvironmentStrings(path, ret, len); -#else - char* ret = lilv_strdup(path); -#endif - return ret; -} - /** Load all bundles in the directory at @a dir_path. */ static void lilv_world_load_directory(LilvWorld* world, const char* dir_path) { - char* path = expand(dir_path); + char* path = lilv_expand(dir_path); if (!path) { LILV_WARNF("Empty path `%s'\n", path); return; diff --git a/test/lilv_test.c b/test/lilv_test.c index 6751be4..0a8a226 100644 --- a/test/lilv_test.c +++ b/test/lilv_test.c @@ -1140,15 +1140,49 @@ test_state(void) // Load state from file LilvState* state5 = lilv_state_new_from_file(world, &map, NULL, "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); + TEST_ASSERT(!ret); + + // Load default bundle into world and load state from it + LilvNode* test_state_bundle = lilv_new_uri(world, "lv2/lilv-test-state.lv2/"); + LilvNode* test_state_node = lilv_new_uri(world, state_uri); + lilv_world_load_bundle(world, test_state_bundle); + lilv_world_load_resource(world, test_state_node); + + LilvState* state6 = lilv_state_new_from_world(world, &map, test_state_node); + TEST_ASSERT(lilv_state_equals(state, state6)); // Round trip accuracy + 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); + + lilv_node_free(num); + lilv_node_free(test_state_bundle); + lilv_node_free(test_state_node); + lilv_state_free(state); lilv_state_free(state2); lilv_state_free(state3); lilv_state_free(state4); lilv_state_free(state5); + lilv_state_free(state6); + + // Free URI map + for (size_t i = 0; i < n_uris; ++i) { + free(uris[i]); + } + free(uris); + n_uris = 0; lilv_instance_free(instance); + lilv_node_free(plugin_uri); lilv_node_free(bundle_uri); lilv_world_free(world); diff --git a/wscript b/wscript index a774f0f..3a047af 100644 --- a/wscript +++ b/wscript @@ -46,6 +46,9 @@ def options(opt): opt.add_option('--default-lv2-path', type='string', default='', dest='default_lv2_path', help="Default LV2 path to use if $LV2_PATH is unset (globs and ~ supported)") + opt.add_option('--default-state-bundle', type='string', default='', + dest='default_state_bundle', + help="Default path to use for user-writable state/preset bundle") opt.add_option('--static', action='store_true', default=False, dest='static', help="Build static library") @@ -114,28 +117,38 @@ def configure(conf): autowaf.define(conf, 'LILV_PATH_SEP', lilv_path_sep) autowaf.define(conf, 'LILV_DIR_SEP', lilv_dir_sep) - if Options.options.default_lv2_path == '': + # Set default LV2 path + lv2_path = Options.options.default_lv2_path + if lv2_path == '': if Options.platform == 'darwin': - Options.options.default_lv2_path = lilv_path_sep.join([ - '~/Library/Audio/Plug-Ins/LV2', - '~/.lv2', - '/usr/local/lib/lv2', - '/usr/lib/lv2', - '/Library/Audio/Plug-Ins/LV2']) + lv2_path = lilv_path_sep.join(['~/Library/Audio/Plug-Ins/LV2', + '~/.lv2', + '/usr/local/lib/lv2', + '/usr/lib/lv2', + '/Library/Audio/Plug-Ins/LV2']) elif Options.platform == 'haiku': - Options.options.default_lv2_path = lilv_path_sep.join([ - '~/.lv2', - '/boot/common/add-ons/lv2']) + lv2_path = lilv_path_sep.join(['~/.lv2', + '/boot/common/add-ons/lv2']) elif Options.platform == 'win32': - Options.options.default_lv2_path = '%APPDATA%\\\\LV2;%PROGRAMFILES%\\\\LV2' + lv2_path = lilv_path_sep.join(['%APPDATA%\\\\LV2', + '%PROGRAMFILES%\\\\LV2']) else: libdirname = os.path.basename(conf.env['LIBDIR']) - Options.options.default_lv2_path = lilv_path_sep.join([ - '~/.lv2', - '/usr/%s/lv2' % libdirname, - '/usr/local/%s/lv2' % libdirname]) - - autowaf.define(conf, 'LILV_DEFAULT_LV2_PATH', Options.options.default_lv2_path) + lv2_path = lilv_path_sep.join(['~/.lv2', + '/usr/%s/lv2' % libdirname, + '/usr/local/%s/lv2' % libdirname]) + autowaf.define(conf, 'LILV_DEFAULT_LV2_PATH', lv2_path) + + # Set default state bundle + state_bundle = Options.options.default_state_bundle + if state_bundle == '': + if Options.platform == 'darwin': + state_bundle = '~/Library/Audio/Plug-Ins/LV2/presets.lv2' + elif Options.platform == 'win32': + state_bundle = '%APPDATA%\\\\LV2\\\\presets.lv2' + else: + state_bundle = '~/.lv2/presets.lv2' + autowaf.define(conf, 'LILV_DEFAULT_STATE_BUNDLE', state_bundle) conf.env['BUILD_TESTS'] = Options.options.build_tests conf.env['BUILD_UTILS'] = not Options.options.no_utils @@ -148,6 +161,8 @@ def configure(conf): autowaf.display_msg(conf, "Default LV2_PATH", conf.env['LILV_DEFAULT_LV2_PATH']) + autowaf.display_msg(conf, "Default state/preset bundle", + conf.env['LILV_DEFAULT_STATE_BUNDLE']) autowaf.display_msg(conf, "Utilities", bool(conf.env['BUILD_UTILS'])) autowaf.display_msg(conf, "Unit tests", -- cgit v1.2.1