Skip to content

Commit

Permalink
Add ApeTag_iter_items for iterating over all items in tag
Browse files Browse the repository at this point in the history
Unlike ApeTag_get_items, this does not require allocation of
additional memory.

While here, fix return codes in the man page, and some now
incorrect comments.
  • Loading branch information
jeremyevans committed Jul 16, 2012
1 parent eb3bb35 commit c17a858
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 32 deletions.
15 changes: 5 additions & 10 deletions c/apeinfo.c
Expand Up @@ -79,23 +79,18 @@ int ApeInfo_process(char *filename) {
return ret; return ret;
} }


int ApeTag_iter_print(struct ApeTag *tag, struct ApeItem *item, void *data) {
ApeItem_print(item);
}

/* Prints all items in the tag, one per line. */ /* Prints all items in the tag, one per line. */
void ApeTag_print(struct ApeTag *tag) { void ApeTag_print(struct ApeTag *tag) {
uint32_t item_count;
struct ApeItem **items, **is;

assert(tag != NULL); assert(tag != NULL);


items = ApeTag_get_items(tag, &item_count); if (ApeTag_iter_items(tag, ApeTag_iter_print, NULL) < 0) {
if (items == NULL) {
printf("Error getting items: %s", ApeTag_error(tag)); printf("Error getting items: %s", ApeTag_error(tag));
} else {
for (is = items; *is; is++) {
ApeItem_print(*is);
}
} }


free(items);
printf("\n"); printf("\n");
} }


Expand Down
38 changes: 27 additions & 11 deletions c/apetag.3
Expand Up @@ -33,6 +33,8 @@
.P .P
.B struct ApeItem ** ApeTag_get_items(struct ApeTag *tag, uint32_t *item_count); .B struct ApeItem ** ApeTag_get_items(struct ApeTag *tag, uint32_t *item_count);
.P .P
.B int ApeTag_iter_items(struct ApeTag *tag, int iterator(struct ApeTag *tag, struct ApeItem *item, void *data), void *data);
.P
.B uint32_t ApeTag_size(struct ApeTag *tag); .B uint32_t ApeTag_size(struct ApeTag *tag);
.P .P
.B uint32_t ApeTag_item_count(struct ApeTag *tag); .B uint32_t ApeTag_item_count(struct ApeTag *tag);
Expand Down Expand Up @@ -205,15 +207,15 @@ Checks if the file associated with
.I tag .I tag
already contains a valid APE tag. already contains a valid APE tag.
.P .P
Returns 1 if an APE tag exists, 0 if it does not, <0 on error. Returns 1 if an APE tag exists, 0 if it does not, -1 on error.
.P .P
.B int ApeTag_exists_id3(struct ApeTag *tag); .B int ApeTag_exists_id3(struct ApeTag *tag);
.P .P
Checks if the file associated with Checks if the file associated with
.I tag .I tag
already contains a valid ID3v1 tag. already contains a valid ID3v1 tag.
.P .P
Returns 1 if an ID3v1 tag exists, 0 if it does not, <0 on error. Returns 1 if an ID3v1 tag exists, 0 if it does not, -1 on error.
.P .P
.B int ApeTag_remove(struct ApeTag *tag); .B int ApeTag_remove(struct ApeTag *tag);
.P .P
Expand All @@ -225,7 +227,7 @@ This function parses the header and footer of the tag and will error instead
of removing a tag if the header or footer of the tag is corrupt. of removing a tag if the header or footer of the tag is corrupt.
.P .P
Returns 1 if the tag doesn't exist, 0 if it does exist and the tag was Returns 1 if the tag doesn't exist, 0 if it does exist and the tag was
removed successfully, <0 on error. removed successfully, -1 on error.
.P .P
.B int ApeTag_raw(struct ApeTag *tag, char **raw, uint32_t *raw_size); .B int ApeTag_raw(struct ApeTag *tag, char **raw, uint32_t *raw_size);
.P .P
Expand All @@ -240,7 +242,7 @@ The caller is responsible for
freeing freeing
.IR *raw. .IR *raw.
.P .P
Returns 0 on success, <0 on error. Returns 0 on success, -1 on error.
.P .P
.B int ApeTag_parse(struct ApeTag *tag); .B int ApeTag_parse(struct ApeTag *tag);
.P .P
Expand All @@ -255,7 +257,7 @@ This is basically the same as calling
.BR ApeTag_add_item .BR ApeTag_add_item
manually with each item already in the tag. manually with each item already in the tag.
.P .P
Returns 0 on success, <0 on error. Returns 0 on success, -1 on error.
.P .P
.B int ApeTag_update(struct ApeTag *tag); .B int ApeTag_update(struct ApeTag *tag);
.P .P
Expand All @@ -276,7 +278,7 @@ Writes an ID3v1 tag as well as an APEv2 tag unless the
flag is used or the file already has an APEv2 flag is used or the file already has an APEv2
tag but doesn't have an ID3v1 tag. tag but doesn't have an ID3v1 tag.
.P .P
Returns 0 on success, <0 on error. Returns 0 on success, -1 on error.
.P .P
.B int ApeTag_add_item(struct ApeTag *tag, struct ApeItem *item); .B int ApeTag_add_item(struct ApeTag *tag, struct ApeItem *item);
.P .P
Expand All @@ -295,7 +297,7 @@ must be created on the heap, as they are all freed when calling
or or
.BR ApeTag_remove_item . .BR ApeTag_remove_item .
.P .P
Returns 0 on success, <0 on error. Returns 0 on success, -1 on error.
.P .P
.B int ApeTag_replace_item(struct ApeTag *tag, struct ApeItem *item); .B int ApeTag_replace_item(struct ApeTag *tag, struct ApeItem *item);
.P .P
Expand All @@ -306,13 +308,13 @@ Otherwise, if the item already exists, remove the existing item
and replace it with the given item. and replace it with the given item.
.P .P
Returns 0 on success if the item doesn't exist, 1 on success if it already Returns 0 on success if the item doesn't exist, 1 on success if it already
existed, <0 on error. existed, -1 on error.
.P .P
.B int ApeTag_remove_item(struct ApeTag *tag, const char *key); .B int ApeTag_remove_item(struct ApeTag *tag, const char *key);
.P .P
Removes the item with a matching key from the tag. Removes the item with a matching key from the tag.
.P .P
Returns 0 on success, 1 if the item did not exist in the tag, <0 on error. Returns 0 on success, 1 if the item did not exist in the tag, -1 on error.
.P .P
.B int ApeTag_clear_items(struct ApeTag *tag); .B int ApeTag_clear_items(struct ApeTag *tag);
.P .P
Expand Down Expand Up @@ -346,7 +348,21 @@ The returned array is always terminated by NULL, and always contains at least
It is the caller's responsibility to free the returned array, but the individual It is the caller's responsibility to free the returned array, but the individual
items in the array should not be freed by the caller. items in the array should not be freed by the caller.
.P .P
Returns 0 on success, <0 on error. Returns 0 on success, -1 on error.
.P
.B int ApeTag_iter_items(struct ApeTag *tag, int iterator(struct ApeTag *tag, struct ApeItem *item, void *data), void *data);
.P
Iterates over all of the items in the tag.
For each item in the tag, calls the iterator function with the tag,
a pointer to the item, and the data pointer passed to the function.
The data pointer is not used by the library, but it allows the iterator
function to communicate back to the calling function.
.P
The iterator function should return 0 to continue iteration. Any other value
will signal the library to stop iterating.
.P
Returns 0 if iteration completed successfully, 1 if the iteration was
terminated early, and -1 if there was an error.
.P .P
.B uint32_t ApeTag_size(struct ApeTag *tag); .B uint32_t ApeTag_size(struct ApeTag *tag);
.P .P
Expand Down Expand Up @@ -417,7 +433,7 @@ If this function is called before creating threads, then libapetag
is thread-safe assuming you do not have multiple threads operating is thread-safe assuming you do not have multiple threads operating
on the same ApeTag or ApeItem struct concurrently. on the same ApeTag or ApeItem struct concurrently.
.P .P
Returns 0 on success, <0 on error. Returns 0 on success, -1 on error.
.SH AUTHOR .SH AUTHOR
.B apetag .B apetag
is written by Jeremy Evans. You can contact the author at is written by Jeremy Evans. You can contact the author at
Expand Down
61 changes: 50 additions & 11 deletions c/apetag.c
Expand Up @@ -135,6 +135,7 @@ static uint32_t ApeTag__tag_length(struct ApeTag *tag);
static uint32_t ApeTag__id3_length(struct ApeTag *tag); static uint32_t ApeTag__id3_length(struct ApeTag *tag);
static struct ApeItem * ApeTag__get_item(struct ApeTag *tag, const char *key); static struct ApeItem * ApeTag__get_item(struct ApeTag *tag, const char *key);
static struct ApeItem **ApeTag__get_items(struct ApeTag *tag, uint32_t *item_count); static struct ApeItem **ApeTag__get_items(struct ApeTag *tag, uint32_t *item_count);
static int ApeTag__iter_items(struct ApeTag *tag, int iterator(struct ApeTag *tag, struct ApeItem *item, void *data), void *data);


static void ApeItem__free(struct ApeItem **item); static void ApeItem__free(struct ApeItem **item);
static char * ApeTag__strcasecpy(const char *src, size_t size); static char * ApeTag__strcasecpy(const char *src, size_t size);
Expand Down Expand Up @@ -505,6 +506,14 @@ struct ApeItem ** ApeTag_get_items(struct ApeTag *tag, uint32_t *item_count) {
return ApeTag__get_items(tag, item_count); return ApeTag__get_items(tag, item_count);
} }


int ApeTag_iter_items(struct ApeTag *tag, int iterator(struct ApeTag *tag, struct ApeItem *item, void *data), void *data) {
if (ApeTag__get_tag_information(tag) != 0) {
return -1;
}

return ApeTag__iter_items(tag, iterator, data);
}

int ApeTag_mt_init(void) { int ApeTag_mt_init(void) {
struct ApeTag tag; struct ApeTag tag;


Expand Down Expand Up @@ -1587,13 +1596,11 @@ static uint32_t ApeTag__id3_length(struct ApeTag *tag) {
} }


/* /*
Update the passed in **item pointer to point to a struct ApeItem * for the matching Return an ApeItem * corresponding to the passed key, which the caller should not free.
item in the database.
The caller is expected to have checked that tag->items is not NULL. The caller is expected to have checked that tag->items is not NULL.
Returns -1 on error, 1 if the item was not in the database, and 0 if the Returns NULL on error.
item was not in the database.
*/ */
static struct ApeItem * ApeTag__get_item(struct ApeTag *tag, const char *key) { static struct ApeItem * ApeTag__get_item(struct ApeTag *tag, const char *key) {
int ret = 0; int ret = 0;
Expand Down Expand Up @@ -1633,11 +1640,10 @@ static struct ApeItem * ApeTag__get_item(struct ApeTag *tag, const char *key) {
} }


/* /*
Update the passed in ***items pointer to point to a new array of ApeItem* Return an array of ApeItem * for all items in the tag database,
pointers, which the caller is responsible for freeing. which the caller is responsible for freeing.
Returns <0 on error, 1 if the tag has no items, and 0 if the Returns NULL on error.
array was set sucessfully.
*/ */
static struct ApeItem ** ApeTag__get_items(struct ApeTag *tag, uint32_t *num_items) { static struct ApeItem ** ApeTag__get_items(struct ApeTag *tag, uint32_t *num_items) {
uint32_t nitems = tag->item_count; uint32_t nitems = tag->item_count;
Expand All @@ -1659,7 +1665,7 @@ static struct ApeItem ** ApeTag__get_items(struct ApeTag *tag, uint32_t *num_ite


if (tag->items == NULL) { if (tag->items == NULL) {
tag->errcode = APETAG_INTERNALERR; tag->errcode = APETAG_INTERNALERR;
tag->error = "internal consistency error: num_items > 0 but items is NULL"; tag->error = "internal consistency error: item_count > 0 but items is NULL";
free(is); free(is);
return NULL; return NULL;
} }
Expand All @@ -1670,7 +1676,7 @@ static struct ApeItem ** ApeTag__get_items(struct ApeTag *tag, uint32_t *num_ite
while (tag->items->seq(tag->items, &key_dbt, &value_dbt, R_NEXT) == 0) { while (tag->items->seq(tag->items, &key_dbt, &value_dbt, R_NEXT) == 0) {
if (i >= nitems) { if (i >= nitems) {
tag->errcode = APETAG_INTERNALERR; tag->errcode = APETAG_INTERNALERR;
tag->error = "internal consistency error: more items in database than num_items"; tag->error = "internal consistency error: more items in database than item_count";
free(is); free(is);
return NULL; return NULL;
} }
Expand All @@ -1679,7 +1685,7 @@ static struct ApeItem ** ApeTag__get_items(struct ApeTag *tag, uint32_t *num_ite
} }
if (i != nitems) { if (i != nitems) {
tag->errcode = APETAG_INTERNALERR; tag->errcode = APETAG_INTERNALERR;
tag->error = "internal consistency error: fewer items in database than num_items"; tag->error = "internal consistency error: fewer items in database than item_count";
free(is); free(is);
return NULL; return NULL;
} }
Expand All @@ -1692,6 +1698,39 @@ static struct ApeItem ** ApeTag__get_items(struct ApeTag *tag, uint32_t *num_ite
return is; return is;
} }


/*
Iterate over all items in the database, calling the iterator function
with the given tag, the current item, and the given data pointer.
Returns 0 if iteration completes successfully, 1 if iteration is stopped
early, -1 on error.
*/
static int ApeTag__iter_items(struct ApeTag *tag, int iterator(struct ApeTag *tag, struct ApeItem *item, void *data), void *data) {
if (tag->item_count > 0) {
DBT key_dbt, value_dbt;

if (tag->items == NULL) {
tag->errcode = APETAG_INTERNALERR;
tag->error = "internal consistency error: item_count > 0 but items is NULL";
return -1;
}

/* Call iterator with each item in the database */
if (tag->items->seq(tag->items, &key_dbt, &value_dbt, R_FIRST) == 0) {
if (iterator(tag, *(struct ApeItem **)(value_dbt.data), data) != 0) {
return 1;
}
while (tag->items->seq(tag->items, &key_dbt, &value_dbt, R_NEXT) == 0) {
if (iterator(tag, *(struct ApeItem **)(value_dbt.data), data) != 0) {
return 1;
}
}
}
}

return 0;
}

/* /*
Local ASCII-only version of strncasecmp, since default strncasecmp may Local ASCII-only version of strncasecmp, since default strncasecmp may
depend on the locale, and this version is only called with APE item keys depend on the locale, and this version is only called with APE item keys
Expand Down
2 changes: 2 additions & 0 deletions c/apetag.h
Expand Up @@ -69,6 +69,8 @@ int ApeTag_update(struct ApeTag *tag);


struct ApeItem * ApeTag_get_item(struct ApeTag *tag, const char *key); struct ApeItem * ApeTag_get_item(struct ApeTag *tag, const char *key);
struct ApeItem ** ApeTag_get_items(struct ApeTag *tag, uint32_t *item_count); struct ApeItem ** ApeTag_get_items(struct ApeTag *tag, uint32_t *item_count);
int ApeTag_iter_items(struct ApeTag *tag, int iterator(struct ApeTag *tag, struct ApeItem *item, void *data), void *data);

uint32_t ApeTag_size(struct ApeTag *tag); uint32_t ApeTag_size(struct ApeTag *tag);
uint32_t ApeTag_item_count(struct ApeTag *tag); uint32_t ApeTag_item_count(struct ApeTag *tag);
uint32_t ApeTag_file_item_count(struct ApeTag *tag); uint32_t ApeTag_file_item_count(struct ApeTag *tag);
Expand Down
36 changes: 36 additions & 0 deletions c/test/test_apetag.c
Expand Up @@ -26,6 +26,7 @@ int test_ApeTag__strcasecpy(void);
int test_ApeItem__parse_track(void); int test_ApeItem__parse_track(void);
int test_ApeItem__compare(void); int test_ApeItem__compare(void);
int test_ApeTag__lookup_genre(void); int test_ApeTag__lookup_genre(void);
int test_ApeTag_iter_items(struct ApeTag *tag, struct ApeItem *item, void *data);


#ifndef TEST_TAGS_DIR #ifndef TEST_TAGS_DIR
# define TEST_TAGS_DIR "test/tags" # define TEST_TAGS_DIR "test/tags"
Expand Down Expand Up @@ -659,12 +660,30 @@ int test_bad_tags(void) {
return 0; return 0;
} }


struct iter_items {
struct ApeTag *tag;
struct ApeItem *items[64];
int times_called;
};

int test_ApeTag_iter_items(struct ApeTag *tag, struct ApeItem *item, void *data) {
struct iter_items *ii = data;
ii->tag = tag;
ii->items[ii->times_called] = item;
if (ii->times_called >= 0) {
ii->times_called++;
return 0;
}
return 1;
}

int test_ApeTag_add_remove_clear_items_update(void) { int test_ApeTag_add_remove_clear_items_update(void) {
struct ApeTag *tag; struct ApeTag *tag;
FILE *file; FILE *file;
struct ApeItem *item; struct ApeItem *item;
struct ApeItem *check_item; struct ApeItem *check_item;
struct ApeItem **items; struct ApeItem **items;
struct iter_items data;
uint32_t items_size; uint32_t items_size;
int i; int i;


Expand Down Expand Up @@ -712,6 +731,19 @@ int test_ApeTag_add_remove_clear_items_update(void) {
CHECK(memcmp(items[0]->value, "VALUE", 5) == 0); CHECK(memcmp(items[0]->value, "VALUE", 5) == 0);
free(items); free(items);


memset(&data, 0, sizeof(data));
CHECK(ApeTag_iter_items(tag, test_ApeTag_iter_items, &data) == 0);
CHECK(data.times_called == 1);
CHECK(data.tag == tag);
CHECK(data.items[0]->size == 5);
CHECK(data.items[0]->flags == 0);
CHECK(strcmp(data.items[0]->key, "ALBUM") == 0);
CHECK(memcmp(data.items[0]->value, "VALUE", 5) == 0);

data.times_called = -1;
CHECK(ApeTag_iter_items(tag, test_ApeTag_iter_items, &data) == 1);
CHECK(data.times_called == -1);

/* ensure we don't crash if we don't care about the item count */ /* ensure we don't crash if we don't care about the item count */
items = ApeTag_get_items(tag, NULL); items = ApeTag_get_items(tag, NULL);
CHECK(items != NULL); CHECK(items != NULL);
Expand Down Expand Up @@ -779,6 +811,10 @@ int test_ApeTag_add_remove_clear_items_update(void) {
/* Check adding more items than allowed */ /* Check adding more items than allowed */
CHECK(ApeTag_clear_items(tag) == 0); CHECK(ApeTag_clear_items(tag) == 0);
for (i=0; i < 64; i++) { for (i=0; i < 64; i++) {
memset(&data, 0, sizeof(data));
CHECK(ApeTag_iter_items(tag, test_ApeTag_iter_items, &data) == 0);
CHECK(data.times_called == i);

CHECK(item = malloc(sizeof(struct ApeItem))); CHECK(item = malloc(sizeof(struct ApeItem)));
CHECK(item->key = malloc(6)); CHECK(item->key = malloc(6));
CHECK(item->value = malloc(3)); CHECK(item->value = malloc(3));
Expand Down

0 comments on commit c17a858

Please sign in to comment.