summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2012-01-28 01:37:28 +0000
committerDavid Robillard <d@drobilla.net>2012-01-28 01:37:28 +0000
commitce2c5cc2036154c2b3ce8850c35e34c0bffeef1f (patch)
tree4dea45a4376110c75fdb8d1c6b9bf3b87b002635
parente4d3f6140cc167580ec43de6fc3ea4562aaba63e (diff)
downloadlilv-ce2c5cc2036154c2b3ce8850c35e34c0bffeef1f.tar.gz
lilv-ce2c5cc2036154c2b3ce8850c35e34c0bffeef1f.tar.bz2
lilv-ce2c5cc2036154c2b3ce8850c35e34c0bffeef1f.zip
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
-rw-r--r--lilv/lilv.h56
-rw-r--r--src/lilv_internal.h4
-rw-r--r--src/state.c277
-rw-r--r--src/util.c115
-rw-r--r--test/lilv_test.c86
-rw-r--r--test/test_plugin.c50
-rw-r--r--wscript16
7 files changed, 403 insertions, 201 deletions
diff --git a/lilv/lilv.h b/lilv/lilv.h
index f16b5a4..f392b6f 100644
--- a/lilv/lilv.h
+++ b/lilv/lilv.h
@@ -1141,19 +1141,54 @@ 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 file_dir Directory of files created by the plugin earlier (or NULL).
+ This is for hosts that support file creation at any time with state
+ state:makePath. These files will be copied as necessary to @c copy_dir and
+ not be referred to directly in state (a temporary directory is appropriate).
+
+ @param copy_dir Directory of copies of files in @c file_dir (or NULL). This
+ directory will have the same structure as @c file_dir but with possibly
+ modified file names to distinguish different revisions. If you only care
+ about saving one state snapshot, it can be the same as @c save_dir. Plugin
+ state will refer to files in this directory.
+
+ @param save_dir Directory of files created by plugin during save (or NULL).
+ If the state will be saved, this should be the bundle directory later passed
+ to lilv_state_save.
+
+ @param link_dir Directory of links to external files (or NULL). A link will
+ be made in this directory to any external files referred to in plugin state.
+ In turn, links will be created in the save directory to these links (e.g.
+ save_dir/file => link_dir/file => /foo/bar/file). This allows many state
+ snapshots to share a single link to an external file, so archival
+ (e.g. with tar -h) will not create several copies of the file. If this is
+ not required, it can be the same as save_dir.
+
@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().
This function may be called simultaneously with any instance function
(except discovery functions) unless the threading class of that function
explicitly disallows this.
+ To support advanced file functionality, there are several directory
+ parameters. Simple hosts that only wish to save a single plugins state once
+ may simply use the same directory for all of them (or pass NULL to not
+ support files at all). The multiple parameters are necessary to support
+ saving an instances state many times while avoiding any duplication of data.
+
If supported (via state:makePath passed to LV2_Descriptor::instantiate()),
- @c dir should be the directory where any plugin-created files are stored.
+ @c file_dir should be the directory where any files created by the plugin
+ (not during save time, e.g. during instantiation) are stored. These files
+ will be copied to preserve their state at this time.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. Note that this function
does not save the state, use lilv_state_save() for that.
@@ -1166,7 +1201,10 @@ 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,
@@ -1258,18 +1296,16 @@ lilv_state_restore(const LilvState* state,
@param unmap URID unmapper.
@param state State to save.
@param uri URI of state, may be NULL.
- @param dir Path of the bundle directory to save into, may be NULL.
- @param filename Filename for the state file, may be NULL.
+ @param dir Path of the bundle directory to save into.
+ @param filename Path of the state file relative to @c dir.
@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 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.
+ be loaded by any host.
- 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).
+ If @c uri is NULL, the preset URI will be a file URI, but the bundle
+ can safely be moved (i.e. the state file will use "<>" as the subject).
*/
LILV_API
int
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*
@@ -180,34 +185,57 @@ lilv_state_has_path(const char* path, void* state)
}
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;
@@ -306,8 +306,25 @@ lilv_path_is_absolute(const char* path)
}
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;
+}
diff --git a/test/lilv_test.c b/test/lilv_test.c
index 3e497e6..f784fe4 100644
--- a/test/lilv_test.c
+++ b/test/lilv_test.c
@@ -149,7 +149,7 @@ cleanup(void)
test_count++;\
if (!(check)) {\
error_count++;\
- fprintf(stderr, "Failure at lilv_test.c:%d: %s\n", __LINE__, #check);\
+ fprintf(stderr, "lilv_test.c:%d: error: %s\n", __LINE__, #check);\
}\
} while (0)
@@ -835,7 +835,7 @@ test_port(void)
lilv_node_free(rdfs_comment);
setenv("LANG", "C", 1); // Reset locale
-
+
LilvScalePoints* points = lilv_port_get_scale_points(plug, p);
TEST_ASSERT(lilv_scale_points_size(points) == 2);
@@ -1110,13 +1110,13 @@ unmap_uri(LV2_URID_Map_Handle handle,
return NULL;
}
-static const char* file_dir = "files";
+static const char* temp_dir = NULL;
char*
lilv_make_path(LV2_State_Make_Path_Handle handle,
const char* path)
{
- return lilv_path_join(file_dir, path);
+ return lilv_path_join(temp_dir, path);
}
int
@@ -1151,15 +1151,20 @@ test_state(void)
TEST_ASSERT(in == 1.0);
TEST_ASSERT(out == 1.0);
- const char* tmpdir = NULL;
-
+ temp_dir = lilv_realpath("temp");
+
+ const char* file_dir = NULL;
+ const char* copy_dir = NULL;
+ const char* link_dir = NULL;
+ const char* save_dir = NULL;
+
// Get instance state state
LilvState* state = lilv_state_new_from_instance(
- plugin, instance, &map, tmpdir, get_port_value, world, 0, NULL);
+ plugin, instance, &map, file_dir, copy_dir, link_dir, save_dir, get_port_value, world, 0, NULL);
// Get another instance state
LilvState* state2 = lilv_state_new_from_instance(
- plugin, instance, &map, tmpdir, get_port_value, world, 0, NULL);
+ plugin, instance, &map, file_dir, copy_dir, link_dir, save_dir, get_port_value, world, 0, NULL);
// Ensure they are equal
TEST_ASSERT(lilv_state_equals(state, state2));
@@ -1179,7 +1184,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, &map, tmpdir, get_port_value, world, 0, NULL);
+ plugin, instance, &map, file_dir, copy_dir, link_dir, save_dir, get_port_value, world, 0, NULL);
TEST_ASSERT(!lilv_state_equals(state2, state3)); // num_runs changed
// Restore instance state to original state
@@ -1187,7 +1192,7 @@ test_state(void)
// Take a new snapshot and ensure it matches the set state
LilvState* state4 = lilv_state_new_from_instance(
- plugin, instance, &map, tmpdir, get_port_value, world, 0, NULL);
+ plugin, instance, &map, file_dir, copy_dir, link_dir, save_dir, get_port_value, world, 0, NULL);
TEST_ASSERT(lilv_state_equals(state2, state4));
// Save state to a directory
@@ -1200,26 +1205,30 @@ test_state(void)
"./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, NULL);
+ // Save state with URI to a directory
+ const char* state_uri = "http://example.org/state";
+ ret = lilv_state_save(world, &unmap, state, state_uri,
+ "./state6.lv2", "state6.ttl", 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_bundle = lilv_new_uri(world, "./state6.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");
- // Make a temporary directory and test files support
- tmpdir = file_dir;
- mkdir(tmpdir, 0700);
+ // Make directories and test files support
+ mkdir("temp", 0700);
+ file_dir = temp_dir = lilv_realpath("temp");
+ mkdir("files", 0700);
+ copy_dir = lilv_realpath("files");
+ mkdir("links", 0700);
+ link_dir = lilv_realpath("links");
LV2_State_Make_Path make_path = { NULL, lilv_make_path };
LV2_Feature make_path_feature = { LV2_STATE_MAKE_PATH_URI, &make_path };
@@ -1235,11 +1244,15 @@ test_state(void)
// Get instance state state
LilvState* fstate = lilv_state_new_from_instance(
- plugin, instance, &map, tmpdir, get_port_value, world, 0, ffeatures);
+ plugin, instance, &map,
+ file_dir, copy_dir, link_dir, "fstate.lv2",
+ 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);
+ plugin, instance, &map,
+ file_dir, copy_dir, link_dir, "fstate2.lv2",
+ get_port_value, world, 0, ffeatures);
// Should be identical
TEST_ASSERT(lilv_state_equals(fstate, fstate2));
@@ -1249,30 +1262,33 @@ test_state(void)
// Get yet another instance state
LilvState* fstate3 = lilv_state_new_from_instance(
- plugin, instance, &map, tmpdir, get_port_value, world, 0, ffeatures);
+ plugin, instance, &map, file_dir, copy_dir, link_dir, "fstate3.lv2",
+ 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.ttl", ffeatures);
+ "fstate.lv2", "fstate.ttl", ffeatures);
TEST_ASSERT(!ret);
// Load state from directory
LilvState* fstate4 = lilv_state_new_from_file(world, &map, NULL,
- "./fstate.lv2/fstate.ttl");
+ "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);
+ lilv_state_restore(fstate4, 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);
+ plugin, instance, &map,
+ file_dir, copy_dir, link_dir, "fstate5.lv2",
+ get_port_value, world, 0, ffeatures);
TEST_ASSERT(lilv_state_equals(fstate3, fstate5));
- // Save state to a directory again
+ // Save state to a (different) directory again
ret = lilv_state_save(world, &unmap, fstate, NULL,
"./fstate6.lv2", "fstate6.ttl", ffeatures);
TEST_ASSERT(!ret);
@@ -1282,15 +1298,17 @@ test_state(void)
"./fstate6.lv2/fstate6.ttl");
TEST_ASSERT(lilv_state_equals(fstate4, fstate6));
- // Run, writing more to rec file
- lilv_instance_run(instance, 2);
+ // Run, changing rec file (without changing size)
+ lilv_instance_run(instance, 3);
// 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));
+ plugin, instance, &map,
+ file_dir, copy_dir, link_dir, "fstate7.lv2",
+ get_port_value, world, 0, ffeatures);
+ TEST_ASSERT(!lilv_state_equals(fstate6, fstate7));
- // Save the changed state to a directory again
+ // Save the changed state to a (different) directory again
ret = lilv_state_save(world, &unmap, fstate7, NULL,
"./fstate7.lv2", "fstate7.ttl", ffeatures);
TEST_ASSERT(!ret);
@@ -1300,13 +1318,11 @@ test_state(void)
"./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);
- lilv_node_free(test_state_node);
lilv_state_free(state);
lilv_state_free(state2);
diff --git a/test/test_plugin.c b/test/test_plugin.c
index ef66863..9eda8de 100644
--- a/test/test_plugin.c
+++ b/test/test_plugin.c
@@ -140,7 +140,13 @@ run(LV2_Handle instance,
if (sample_count == 1) {
++test->num_runs;
} else if (sample_count == 2 && test->rec_file) {
+ // Append to rec file (changes size)
fprintf(test->rec_file, "run\n");
+ } else if (sample_count == 3 && test->rec_file) {
+ // Change the first byte of rec file (doesn't change size)
+ fseek(test->rec_file, 0, SEEK_SET);
+ fprintf(test->rec_file, "X");
+ fseek(test->rec_file, 0, SEEK_END);
}
}
@@ -160,13 +166,13 @@ save(LV2_Handle instance,
Test* plugin = (Test*)instance;
LV2_State_Map_Path* map_path = NULL;
- //LV2_State_Make_Path* make_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)) {
+ } else if (!strcmp(features[i]->URI, LV2_STATE_MAKE_PATH_URI)) {
make_path = (LV2_State_Make_Path*)features[i]->data;
- }*/
+ }
}
store(callback_data,
@@ -261,6 +267,22 @@ save(LV2_Handle instance,
free(apath);
}
+
+ if (make_path) {
+ char* spath = make_path->path(make_path->handle, "save");
+ FILE* sfile = fopen(spath, "w");
+ fprintf(sfile, "save");
+ fclose(sfile);
+
+ apath = map_path->abstract_path(map_path->handle, spath);
+ store(callback_data,
+ map_uri(plugin, "http://example.org/save-file"),
+ apath,
+ strlen(apath) + 1,
+ map_uri(plugin, LV2_STATE_PATH_URI),
+ LV2_STATE_IS_PORTABLE);
+ free(apath);
+ }
}
}
@@ -289,12 +311,16 @@ restore(LV2_Handle instance,
map_uri(plugin, "http://example.org/num-runs"),
&size, &type, &valflags);
+ if (!map_path) {
+ return;
+ }
+
char* apath = (char*)retrieve(
callback_data,
map_uri(plugin, "http://example.org/extfile"),
&size, &type, &valflags);
- if (map_path && apath) {
+ if (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)) {
@@ -304,6 +330,22 @@ restore(LV2_Handle instance,
free(real_path);
free(path);
}
+
+ apath = (char*)retrieve(
+ callback_data,
+ map_uri(plugin, "http://example.org/save-file"),
+ &size, &type, &valflags);
+ if (apath) {
+ char* spath = map_path->absolute_path(map_path->handle, apath);
+ FILE* sfile = fopen(spath, "r");
+ if (!sfile) {
+ fprintf(stderr, "error: Failed to open save file %s\n", spath);
+ } else {
+ fclose(sfile);
+ }
+ } else {
+ fprintf(stderr, "error: Failed to restore save file.\n");
+ }
}
const void*
diff --git a/wscript b/wscript
index 2ce7819..9576636 100644
--- a/wscript
+++ b/wscript
@@ -46,9 +46,6 @@ 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")
@@ -135,17 +132,6 @@ def configure(conf):
'/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
conf.env['BUILD_STATIC'] = Options.options.static
@@ -157,8 +143,6 @@ 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",