From ce2c5cc2036154c2b3ce8850c35e34c0bffeef1f Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 28 Jan 2012 01:37:28 +0000 Subject: Update state interface to support multiple state snapshots with shared files. git-svn-id: http://svn.drobilla.net/lad/trunk/lilv@3967 a436a847-0d15-0410-975c-d299462d15a1 --- src/lilv_internal.h | 4 +- src/state.c | 277 ++++++++++++++++++++++++++++++---------------------- src/util.c | 115 +++++++++++++++++++--- 3 files changed, 260 insertions(+), 136 deletions(-) (limited to 'src') diff --git a/src/lilv_internal.h b/src/lilv_internal.h index 0049d01..18aaed5 100644 --- a/src/lilv_internal.h +++ b/src/lilv_internal.h @@ -349,8 +349,9 @@ char* lilv_expand(const char* path); char* lilv_dirname(const char* path); int lilv_copy_file(const char* src, const char* dst); bool lilv_path_exists(const char* path, void* ignored); +char* lilv_path_absolute(const char* path); bool lilv_path_is_absolute(const char* path); -char* lilv_get_latest_copy(const char* path); +char* lilv_get_latest_copy(const char* path, const char* copy_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); @@ -358,6 +359,7 @@ char* lilv_realpath(const char* path); int lilv_symlink(const char* oldpath, const char* newpath); int lilv_mkdir_p(const char* path); char* lilv_path_join(const char* a, const char* b); +bool lilv_file_equals(const char* a_path, const char* b_path); char* lilv_find_free_path(const char* in_path, diff --git a/src/state.c b/src/state.c index 270b9bb..53479ff 100644 --- a/src/state.c +++ b/src/state.c @@ -52,7 +52,9 @@ typedef struct { struct LilvStateImpl { LilvNode* plugin_uri; char* dir; ///< Save directory (if saved) - char* file_dir; ///< Directory of files created by plugin + char* file_dir; + char* copy_dir; + char* link_dir; char* label; ZixTree* abs2rel; ///< PathMap sorted by abs ZixTree* rel2abs; ///< PathMap sorted by rel @@ -101,12 +103,15 @@ append_port_value(LilvState* state, const char* port_symbol, LilvNode* value) { - state->values = (PortValue*)realloc( - state->values, (++state->num_values) * sizeof(PortValue)); - PortValue* pv = &state->values[state->num_values - 1]; - pv->symbol = lilv_strdup(port_symbol); - pv->value = value; - return pv; + if (value) { + state->values = (PortValue*)realloc( + state->values, (++state->num_values) * sizeof(PortValue)); + PortValue* pv = &state->values[state->num_values - 1]; + pv->symbol = lilv_strdup(port_symbol); + pv->value = value; + return pv; + } + return NULL; } static const char* @@ -179,35 +184,58 @@ lilv_state_has_path(const char* path, void* state) return lilv_state_rel2abs((LilvState*)state, path) != path; } +static char* +make_path(LV2_State_Make_Path_Handle handle, + const char* path) +{ + LilvState* state = (LilvState*)handle; + if (!lilv_path_exists(state->dir, NULL)) { + lilv_mkdir_p(state->dir); + } + + return lilv_path_join(state->dir, 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 = lilv_realpath(absolute_path); - const PathMap key = { (char*)real_path, NULL }; - ZixTreeIter* iter = NULL; - - if (!zix_tree_find(state->abs2rel, &key, &iter)) { + LilvState* state = (LilvState*)handle; + char* path = NULL; + char* real_path = lilv_realpath(absolute_path); + const PathMap key = { (char*)real_path, NULL }; + ZixTreeIter* iter = NULL; + + if (absolute_path[0] == '\0') { + return lilv_strdup(absolute_path); + } else 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); - } - free(real_path); - real_path = copy; + } else if (lilv_path_is_child(absolute_path, state->dir)) { + // File in state directory (loaded, or created by plugin during save + path = lilv_path_relative_to(absolute_path, state->dir); + } else if (lilv_path_is_child(absolute_path, state->file_dir)) { + // File created by plugin earlier + path = lilv_path_relative_to(absolute_path, state->file_dir); + if (state->copy_dir) { + if (!lilv_path_exists(state->copy_dir, NULL)) { + lilv_mkdir_p(state->copy_dir); + } + char* cpath = lilv_path_join(state->copy_dir, path); + char* copy = lilv_get_latest_copy(absolute_path, cpath); + if (!copy || !lilv_file_equals(real_path, copy)) { + // No recent enough copy, make a new one + copy = lilv_find_free_path(cpath, lilv_path_exists, NULL); + lilv_copy_file(absolute_path, copy); + } + free(real_path); + free(cpath); - // Refer to the latest copy in plugin state - path = lilv_strdup(copy + file_dir_len + 1); + // Refer to the latest copy in plugin state + real_path = copy; + } } else { // New path outside state directory const char* slash = strrchr(real_path, '/'); @@ -236,9 +264,12 @@ absolute_path(LV2_State_Map_Path_Handle handle, if (lilv_path_is_absolute(abstract_path)) { // Absolute path, return identical path path = lilv_strdup(abstract_path); - } else { + } else if (state->dir) { // Relative path inside state directory path = lilv_path_join(state->dir, abstract_path); + } else { + // State has not been saved, unmap + path = lilv_strdup(lilv_state_rel2abs(state, abstract_path)); } return path; @@ -248,49 +279,78 @@ absolute_path(LV2_State_Map_Path_Handle handle, /** 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) +add_features(const LV2_Feature *const * features, + const LV2_Feature* map, const LV2_Feature* make) { size_t n_features = 0; for (; features && features[n_features]; ++n_features) {} const LV2_Feature** ret = (const LV2_Feature**)malloc( - (n_features + 2) * sizeof(LV2_Feature*)); + (n_features + 3) * sizeof(LV2_Feature*)); - ret[0] = feature; if (features) { - memcpy(ret + 1, features, n_features * sizeof(LV2_Feature*)); + memcpy(ret, features, n_features * sizeof(LV2_Feature*)); } - ret[n_features + 1] = NULL; + + for (size_t i = 0; i < n_features; ++i) { + if (map && !strcmp(ret[i]->URI, map->URI)) { + ret[i] = map; + map = NULL; + } else if (make && !strcmp(ret[i]->URI, make->URI)) { + ret[i] = make; + make = NULL; + } + } + + ret[n_features] = map; + ret[n_features + 1] = make; + ret[n_features + 2] = NULL; return ret; } +static char* +absolute_dir(const char* path, bool resolve) +{ + char* abs_path = resolve ? lilv_realpath(path) : lilv_path_absolute(path); + char* base = lilv_path_join(abs_path, NULL); + free(abs_path); + return base; +} + LILV_API LilvState* lilv_state_new_from_instance(const LilvPlugin* plugin, LilvInstance* instance, LV2_URID_Map* map, - const char* dir, + const char* file_dir, + const char* copy_dir, + const char* link_dir, + const char* save_dir, LilvGetPortValueFunc get_value, void* user_data, uint32_t flags, const LV2_Feature *const * features) { - const LV2_Feature** local_features = NULL; - LilvWorld* const world = plugin->world; - LilvState* const state = (LilvState*)malloc(sizeof(LilvState)); + const LV2_Feature** sfeatures = NULL; + LilvWorld* const world = plugin->world; + LilvState* const state = (LilvState*)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 ? lilv_realpath(dir) : NULL; + state->file_dir = file_dir ? absolute_dir(file_dir, false) : NULL; + state->copy_dir = copy_dir ? absolute_dir(copy_dir, false) : NULL; + state->link_dir = link_dir ? absolute_dir(link_dir, false) : NULL; + state->dir = save_dir ? absolute_dir(save_dir, false) : NULL; #ifdef HAVE_LV2_STATE state->state_Path = map->map(map->handle, LV2_STATE_PATH_URI); - 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); - } + LV2_State_Map_Path pmap = { state, abstract_path, absolute_path }; + LV2_Feature pmap_feature = { LV2_STATE_MAP_PATH_URI, &pmap }; + LV2_State_Make_Path pmake = { state, make_path }; + LV2_Feature pmake_feature = { LV2_STATE_MAKE_PATH_URI, &pmake }; + features = sfeatures = add_features(features, &pmap_feature, + save_dir ? &pmake_feature : NULL); #endif // Store port values @@ -323,7 +383,7 @@ lilv_state_new_from_instance(const LilvPlugin* plugin, qsort(state->props, state->num_props, sizeof(Property), property_cmp); qsort(state->values, state->num_values, sizeof(PortValue), value_cmp); - free(local_features); + free(sfeatures); return state; } @@ -337,14 +397,12 @@ 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_State_Map_Path map_path = { + (LilvState*)state, abstract_path, absolute_path }; + LV2_Feature map_feature = { LV2_STATE_MAP_PATH_URI, &map_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_Feature** sfeatures = add_features(features, &map_feature, NULL); + features = sfeatures; const LV2_Descriptor* descriptor = instance->lv2_descriptor; const LV2_State_Interface* iface = (descriptor->extension_data) @@ -356,7 +414,7 @@ lilv_state_restore(const LilvState* state, (LV2_State_Handle)state, flags, features); } - free(local_features); + free(sfeatures); #endif // HAVE_LV2_STATE @@ -785,35 +843,17 @@ add_state_to_manifest(const LilvNode* plugin_uri, return 0; } -static char* -pathify(const char* in) -{ - const size_t in_len = strlen(in); - - char* out = (char*)calloc(in_len + 1, 1); - for (size_t i = 0; i < in_len; ++i) { - char c = in[i]; - if (!((c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9'))) { - c = '-'; - } - out[i] = c; - } - return out; -} - -static char* -lilv_default_state_dir(LilvWorld* world) +static bool +link_exists(const char* path, void* data) { - // 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; + const char* target = (const char*)data; + if (!lilv_path_exists(path, NULL)) { + return false; } - - // Expand any variables and create if necessary - return lilv_expand(state_bundle); + char* real_path = lilv_realpath(path); + bool matches = !strcmp(real_path, target); + free(real_path); + return !matches; } LILV_API @@ -826,37 +866,27 @@ lilv_state_save(LilvWorld* world, 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 (lilv_mkdir_p(dir)) { - free(default_dir); + if (!filename || !dir || lilv_mkdir_p(dir)) { return 1; } - if (!filename) { - char* gen_name = pathify(state->label); - filename = default_filename = lilv_strjoin(gen_name, ".ttl", NULL); - free(gen_name); - } + char* abs_dir = absolute_dir(dir, true); + dir = abs_dir; char* const path = lilv_path_join(dir, filename); FILE* fd = fopen(path, "w"); if (!fd) { LILV_ERRORF("Failed to open %s (%s)\n", path, strerror(errno)); - free(default_dir); - free(default_filename); + free(abs_dir); free(path); return 4; } // FIXME: make parameter non-const? - if (state->dir) { + if (state->dir && strcmp(state->dir, dir)) { free(state->dir); + ((LilvState*)state)->dir = lilv_strdup(dir); } - ((LilvState*)state)->dir = lilv_strdup(dir); char* const manifest = lilv_path_join(dir, "manifest.ttl"); @@ -919,27 +949,43 @@ lilv_state_save(LilvWorld* world, serd_writer_end_anon(writer, &port); } - // Create symlinks to external files + // Create symlinks to 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_realpath(dir); - char* base = lilv_path_join(real_dir, NULL); - char* rel_path = lilv_path_join(dir, pm->rel); - char* target_path = lilv_path_is_child(pm->abs, state->file_dir) - ? lilv_path_relative_to(pm->abs, base) - : lilv_strdup(pm->abs); - if (lilv_symlink(target_path, rel_path)) { - LILV_ERRORF("Failed to link `%s' => `%s' (%s)\n", - pm->abs, pm->rel, strerror(errno)); + char* path = lilv_path_join(abs_dir, pm->rel); + if (lilv_path_is_child(pm->abs, state->copy_dir) + && strcmp(state->copy_dir, dir)) { + // Link directly to snapshot in the copy directory + char* target = lilv_path_relative_to(pm->abs, dir); + lilv_symlink(target, path); + free(target); + } else if (!lilv_path_is_child(pm->abs, dir)) { + const char* link_dir = state->link_dir ? state->link_dir : dir; + char* pat = lilv_path_join(link_dir, pm->rel); + if (!strcmp(dir, link_dir)) { + // Link directory is save directory, make link at exact path + remove(pat); + lilv_symlink(pm->abs, pat); + } else { + // Make a link in the link directory to external file + char* lpath = lilv_find_free_path(pat, link_exists, pm->abs); + if (!lilv_path_exists(lpath, NULL)) { + lilv_symlink(pm->abs, lpath); + } + + // Make a link in the save directory to the external link + char* target = lilv_path_relative_to(lpath, dir); + lilv_symlink(target, path); + free(target); + free(lpath); + } + free(pat); } - free(target_path); - free(rel_path); - free(base); - free(real_dir); + free(path); } #endif @@ -1024,8 +1070,7 @@ lilv_state_save(LilvWorld* world, add_state_to_manifest(state->plugin_uri, manifest, uri, path); } - free(default_dir); - free(default_filename); + free(abs_dir); free(manifest); free(path); return 0; @@ -1088,14 +1133,8 @@ lilv_state_equals(const LilvState* a, const LilvState* b) } if (ap->type == a->state_Path) { - const char* const a_abs = lilv_state_rel2abs(a, (char*)ap->value); - const char* const b_abs = lilv_state_rel2abs(b, (char*)bp->value); - char* const a_real = lilv_realpath(a_abs); - char* const b_real = lilv_realpath(b_abs); - const int cmp = strcmp(a_real, b_real); - free(a_real); - free(b_real); - if (cmp) { + if (!lilv_file_equals(lilv_state_rel2abs(a, (char*)ap->value), + lilv_state_rel2abs(b, (char*)bp->value))) { return false; } } else if (ap->size != bp->size diff --git a/src/util.c b/src/util.c index 47846da..a8f66be 100644 --- a/src/util.c +++ b/src/util.c @@ -228,8 +228,8 @@ lilv_path_exists(const char* path, void* ignored) } char* -lilv_find_free_path( - const char* in_path, bool (*exists)(const char*, void*), void* user_data) +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 = (char*)malloc(in_path_len + 7); @@ -239,7 +239,7 @@ lilv_find_free_path( if (!exists(path, user_data)) { return path; } - snprintf(path, in_path_len + 7, "%s%u", in_path, i); + snprintf(path, in_path_len + 7, "%s.%u", in_path, i); } return NULL; @@ -305,9 +305,26 @@ lilv_path_is_absolute(const char* path) return false; } +char* +lilv_path_absolute(const char* path) +{ + if (lilv_path_is_absolute(path)) { + return lilv_strdup(path); + } else { + char* cwd = getcwd(NULL, 0); + char* abs_path = lilv_path_join(cwd, path); + free(cwd); + return abs_path; + } +} + char* lilv_path_join(const char* a, const char* b) { + if (!a) { + return lilv_strdup(b); + } + const size_t a_len = strlen(a); const size_t b_len = b ? strlen(b) : 0; const size_t pre_len = a_len - (lilv_is_dir_sep(a[a_len - 1]) ? 1 : 0); @@ -331,8 +348,12 @@ lilv_size_mtime(const char* path, off_t* size, time_t* time) *size = *time = 0; } - *size = buf.st_size; - *time = buf.st_mtime; + if (size) { + *size = buf.st_size; + } + if (time) { + *time = buf.st_mtime; + } } typedef struct { @@ -346,7 +367,7 @@ static void update_latest(const char* path, const char* name, void* data) { Latest* latest = (Latest*)data; - char* entry_path = lilv_strjoin(path, "/", name, NULL); + char* entry_path = lilv_path_join(path, name); unsigned num; if (sscanf(entry_path, latest->pattern, &num) == 1) { off_t entry_size; @@ -364,16 +385,16 @@ update_latest(const char* path, const char* name, void* data) /** Return the latest copy of the file at @c path that is newer. */ char* -lilv_get_latest_copy(const char* path) +lilv_get_latest_copy(const char* path, const char* copy_path) { - char* dirname = lilv_dirname(path); - Latest latest = { lilv_strjoin(path, "%u", NULL), 0, 0, NULL }; + char* copy_dir = lilv_dirname(copy_path); + Latest latest = { lilv_strjoin(copy_path, "%u", NULL), 0, 0, NULL }; lilv_size_mtime(path, &latest.orig_size, &latest.time); - lilv_dir_for_each(dirname, &latest, update_latest); + lilv_dir_for_each(copy_dir, &latest, update_latest); free(latest.pattern); - free(dirname); + free(copy_dir); return latest.latest; } @@ -392,11 +413,19 @@ lilv_realpath(const char* path) int lilv_symlink(const char* oldpath, const char* newpath) { + int ret = 0; + if (strcmp(oldpath, newpath)) { #ifdef _WIN32 - return !CreateSymbolicLink(newpath, oldpath, 0); + ret = !CreateSymbolicLink(newpath, oldpath, 0); #else - return symlink(oldpath, newpath); + ret = symlink(oldpath, newpath); #endif + } + if (ret) { + LILV_ERRORF("Failed to link %s => %s (%s)\n", + newpath, oldpath, strerror(errno)); + } + return ret; } char* @@ -442,9 +471,12 @@ lilv_path_relative_to(const char* path, const char* base) 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); + if (path && 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); + } + return false; } int @@ -504,3 +536,54 @@ lilv_mkdir_p(const char* dir_path) free(path); return 0; } + +bool +lilv_file_equals(const char* a_path, const char* b_path) +{ + if (!strcmp(a_path, b_path)) { + return true; // Paths match + } + + char* const a_real = lilv_realpath(a_path); + char* const b_real = lilv_realpath(b_path); + if (!a_real || !b_real) { + return false; // Missing file matches nothing + } + + if (!strcmp(a_real, b_real)) { + return true; // Real paths match + } + + off_t a_size; + off_t b_size; + lilv_size_mtime(a_path, &a_size, NULL); + lilv_size_mtime(b_path, &b_size, NULL); + if (a_size != b_size) { + return false; // Sizes differ + } + + FILE* a_file = fopen(a_real, "rb"); + if (!a_file) { + return false; // Missing file matches nothing + } + + FILE* b_file = fopen(b_real, "rb"); + if (!b_file) { + fclose(a_file); + return false; // Missing file matches nothing + } + + bool match = true; + + // TODO: Improve performance by reading chunks + while (!feof(a_file) && !feof(b_file)) { + if (fgetc(a_file) != fgetc(b_file)) { + match = false; + break; + } + } + + fclose(a_file); + fclose(b_file); + return match; +} -- cgit v1.2.1