From 47911f9544ecd3b028cc044b400536234b17949a Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 7 Jan 2013 10:36:27 +0100 Subject: tag: convert to C++ --- Makefile.am | 24 +-- src/DatabaseSave.cxx | 4 +- src/Tag.cxx | 508 +++++++++++++++++++++++++++++++++++++++++++++++++ src/TagInternal.hxx | 27 +++ src/TagNames.c | 44 +++++ src/TagPool.cxx | 159 ++++++++++++++++ src/TagPool.hxx | 38 ++++ src/TagPrint.cxx | 2 +- src/TagSave.cxx | 2 +- src/tag.c | 523 --------------------------------------------------- src/tag.h | 2 +- src/tag_internal.h | 29 --- src/tag_pool.c | 158 ---------------- src/tag_pool.h | 38 ---- 14 files changed, 793 insertions(+), 765 deletions(-) create mode 100644 src/Tag.cxx create mode 100644 src/TagInternal.hxx create mode 100644 src/TagNames.c create mode 100644 src/TagPool.cxx create mode 100644 src/TagPool.hxx delete mode 100644 src/tag.c delete mode 100644 src/tag_internal.h delete mode 100644 src/tag_pool.c delete mode 100644 src/tag_pool.h diff --git a/Makefile.am b/Makefile.am index 4d1e5620d..1d478c887 100644 --- a/Makefile.am +++ b/Makefile.am @@ -140,7 +140,6 @@ mpd_headers = \ src/stats.h \ src/tag.h \ src/tag_internal.h \ - src/tag_pool.h \ src/tag_table.h \ src/tag_ape.h \ src/tag_id3.h \ @@ -291,8 +290,9 @@ src_mpd_SOURCES = \ src/socket_util.c \ src/StateFile.cxx src/StateFile.hxx \ src/Stats.cxx \ - src/tag.c \ - src/tag_pool.c \ + src/Tag.cxx \ + src/TagNames.c \ + src/TagPool.cxx src/TagPool.hxx \ src/TagPrint.cxx src/TagPrint.hxx \ src/TagSave.cxx src/TagSave.hxx \ src/tag_handler.c src/tag_handler.h \ @@ -1049,7 +1049,7 @@ test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \ src/PlaylistVector.cxx src/PlaylistDatabase.cxx \ src/DatabaseLock.cxx src/DatabaseSave.cxx \ src/Song.cxx src/song_sort.c src/SongSave.cxx \ - src/tag.c src/tag_pool.c src/TagSave.cxx \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagSave.cxx \ src/path.c \ src/SongFilter.cxx \ src/TextFile.cxx \ @@ -1063,7 +1063,7 @@ test_run_input_SOURCES = test/run_input.c \ test/stdbin.h \ src/io_thread.c src/io_thread.h \ src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ - src/tag.c src/tag_pool.c src/TagSave.cxx \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagSave.cxx \ src/fd_util.c test_dump_text_file_LDADD = \ @@ -1074,7 +1074,7 @@ test_dump_text_file_SOURCES = test/dump_text_file.c \ test/stdbin.h \ src/io_thread.c src/io_thread.h \ src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ - src/tag.c src/tag_pool.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx \ src/text_input_stream.c src/fifo_buffer.c \ src/fd_util.c @@ -1092,7 +1092,7 @@ test_dump_playlist_SOURCES = test/dump_playlist.cxx \ src/io_thread.c src/io_thread.h \ src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ src/uri.c \ - src/Song.cxx src/tag.c src/tag_pool.c src/TagSave.cxx \ + src/Song.cxx src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagSave.cxx \ src/tag_handler.c src/tag_file.c \ src/audio_check.c src/pcm_buffer.c \ src/text_input_stream.c src/fifo_buffer.c \ @@ -1117,7 +1117,7 @@ test_run_decoder_SOURCES = test/run_decoder.c \ test/stdbin.h \ src/io_thread.c src/io_thread.h \ src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/tag.c src/tag_pool.c src/tag_handler.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx src/tag_handler.c \ src/ReplayGainInfo.cxx \ src/uri.c \ src/fd_util.c \ @@ -1139,7 +1139,7 @@ test_read_tags_LDADD = \ test_read_tags_SOURCES = test/read_tags.c \ src/io_thread.c src/io_thread.h \ src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/tag.c src/tag_pool.c src/tag_handler.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx src/tag_handler.c \ src/ReplayGainInfo.cxx \ src/uri.c \ src/fd_util.c \ @@ -1192,7 +1192,7 @@ test_run_encoder_SOURCES = test/run_encoder.c \ src/fifo_buffer.c src/growing_fifo.c \ src/conf.c src/tokenizer.c \ src/utils.c src/string_util.c \ - src/tag.c src/tag_pool.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx \ src/audio_check.c \ src/audio_format.c \ src/audio_parser.c @@ -1210,7 +1210,7 @@ test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.c \ src/conf.c src/tokenizer.c \ src/utils.c \ src/string_util.c \ - src/tag.c src/tag_pool.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx \ src/audio_check.c \ src/audio_format.c \ src/audio_parser.c \ @@ -1266,7 +1266,7 @@ test_run_output_SOURCES = test/run_output.cxx \ src/audio_format.c \ src/audio_parser.c \ src/timer.c src/clock.c \ - src/tag.c src/tag_pool.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx \ src/fifo_buffer.c src/growing_fifo.c \ src/page.c \ src/socket_util.c \ diff --git a/src/DatabaseSave.cxx b/src/DatabaseSave.cxx index 78a2c4939..5100c2a0b 100644 --- a/src/DatabaseSave.cxx +++ b/src/DatabaseSave.cxx @@ -24,11 +24,11 @@ #include "DirectorySave.hxx" #include "song.h" #include "TextFile.hxx" +#include "TagInternal.hxx" +#include "tag.h" extern "C" { #include "path.h" -#include "tag.h" -#include "tag_internal.h" } #include diff --git a/src/Tag.cxx b/src/Tag.cxx new file mode 100644 index 000000000..bdaf76f57 --- /dev/null +++ b/src/Tag.cxx @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "tag.h" +#include "TagInternal.hxx" +#include "TagPool.hxx" +#include "conf.h" +#include "song.h" +#include "mpd_error.h" + +#include +#include +#include +#include + +/** + * Maximum number of items managed in the bulk list; if it is + * exceeded, we switch back to "normal" reallocation. + */ +#define BULK_MAX 64 + +static struct { +#ifndef NDEBUG + bool busy; +#endif + struct tag_item *items[BULK_MAX]; +} bulk; + +bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; + +enum tag_type +tag_name_parse(const char *name) +{ + assert(name != nullptr); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + assert(tag_item_names[i] != nullptr); + + if (strcmp(name, tag_item_names[i]) == 0) + return (enum tag_type)i; + } + + return TAG_NUM_OF_ITEM_TYPES; +} + +enum tag_type +tag_name_parse_i(const char *name) +{ + assert(name != nullptr); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + assert(tag_item_names[i] != nullptr); + + if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0) + return (enum tag_type)i; + } + + return TAG_NUM_OF_ITEM_TYPES; +} + +static size_t items_size(const struct tag *tag) +{ + return tag->num_items * sizeof(struct tag_item *); +} + +void tag_lib_init(void) +{ + const char *value; + int quit = 0; + char *temp; + char *s; + char *c; + enum tag_type type; + + /* parse the "metadata_to_use" config parameter below */ + + /* ignore comments by default */ + ignore_tag_items[TAG_COMMENT] = true; + + value = config_get_string(CONF_METADATA_TO_USE, nullptr); + if (value == nullptr) + return; + + memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES); + + if (0 == g_ascii_strcasecmp(value, "none")) + return; + + temp = c = s = g_strdup(value); + while (!quit) { + if (*s == ',' || *s == '\0') { + if (*s == '\0') + quit = 1; + *s = '\0'; + + c = g_strstrip(c); + if (*c == 0) + continue; + + type = tag_name_parse_i(c); + if (type == TAG_NUM_OF_ITEM_TYPES) + MPD_ERROR("error parsing metadata item \"%s\"", + c); + + ignore_tag_items[type] = false; + + s++; + c = s; + } + s++; + } + + g_free(temp); +} + +struct tag *tag_new(void) +{ + struct tag *ret = g_new(struct tag, 1); + ret->items = nullptr; + ret->time = -1; + ret->has_playlist = false; + ret->num_items = 0; + return ret; +} + +static void tag_delete_item(struct tag *tag, unsigned idx) +{ + assert(idx < tag->num_items); + tag->num_items--; + + g_static_mutex_lock(&tag_pool_lock); + tag_pool_put_item(tag->items[idx]); + g_static_mutex_unlock(&tag_pool_lock); + + if (tag->num_items - idx > 0) { + memmove(tag->items + idx, tag->items + idx + 1, + (tag->num_items - idx) * sizeof(tag->items[0])); + } + + if (tag->num_items > 0) { + tag->items = (struct tag_item **) + g_realloc(tag->items, items_size(tag)); + } else { + g_free(tag->items); + tag->items = nullptr; + } +} + +void tag_clear_items_by_type(struct tag *tag, enum tag_type type) +{ + for (unsigned i = 0; i < tag->num_items; i++) { + if (tag->items[i]->type == type) { + tag_delete_item(tag, i); + /* decrement since when just deleted this node */ + i--; + } + } +} + +void tag_free(struct tag *tag) +{ + int i; + + assert(tag != nullptr); + + g_static_mutex_lock(&tag_pool_lock); + for (i = tag->num_items; --i >= 0; ) + tag_pool_put_item(tag->items[i]); + g_static_mutex_unlock(&tag_pool_lock); + + if (tag->items == bulk.items) { +#ifndef NDEBUG + assert(bulk.busy); + bulk.busy = false; +#endif + } else + g_free(tag->items); + + g_free(tag); +} + +struct tag *tag_dup(const struct tag *tag) +{ + struct tag *ret; + + if (!tag) + return nullptr; + + ret = tag_new(); + ret->time = tag->time; + ret->has_playlist = tag->has_playlist; + ret->num_items = tag->num_items; + ret->items = ret->num_items > 0 + ? (struct tag_item **)g_malloc(items_size(tag)) + : nullptr; + + g_static_mutex_lock(&tag_pool_lock); + for (unsigned i = 0; i < tag->num_items; i++) + ret->items[i] = tag_pool_dup_item(tag->items[i]); + g_static_mutex_unlock(&tag_pool_lock); + + return ret; +} + +struct tag * +tag_merge(const struct tag *base, const struct tag *add) +{ + struct tag *ret; + unsigned n; + + assert(base != nullptr); + assert(add != nullptr); + + /* allocate new tag object */ + + ret = tag_new(); + ret->time = add->time > 0 ? add->time : base->time; + ret->num_items = base->num_items + add->num_items; + ret->items = ret->num_items > 0 + ? (struct tag_item **)g_malloc(items_size(ret)) + : nullptr; + + g_static_mutex_lock(&tag_pool_lock); + + /* copy all items from "add" */ + + for (unsigned i = 0; i < add->num_items; ++i) + ret->items[i] = tag_pool_dup_item(add->items[i]); + + n = add->num_items; + + /* copy additional items from "base" */ + + for (unsigned i = 0; i < base->num_items; ++i) + if (!tag_has_type(add, base->items[i]->type)) + ret->items[n++] = tag_pool_dup_item(base->items[i]); + + g_static_mutex_unlock(&tag_pool_lock); + + assert(n <= ret->num_items); + + if (n < ret->num_items) { + /* some tags were not copied - shrink ret->items */ + assert(n > 0); + + ret->num_items = n; + ret->items = (struct tag_item **) + g_realloc(ret->items, items_size(ret)); + } + + return ret; +} + +struct tag * +tag_merge_replace(struct tag *base, struct tag *add) +{ + if (add == nullptr) + return base; + + if (base == nullptr) + return add; + + struct tag *tag = tag_merge(base, add); + tag_free(base); + tag_free(add); + + return tag; +} + +const char * +tag_get_value(const struct tag *tag, enum tag_type type) +{ + assert(tag != nullptr); + assert(type < TAG_NUM_OF_ITEM_TYPES); + + for (unsigned i = 0; i < tag->num_items; i++) + if (tag->items[i]->type == type) + return tag->items[i]->value; + + return nullptr; +} + +bool tag_has_type(const struct tag *tag, enum tag_type type) +{ + return tag_get_value(tag, type) != nullptr; +} + +bool tag_equal(const struct tag *tag1, const struct tag *tag2) +{ + if (tag1 == nullptr && tag2 == nullptr) + return true; + else if (!tag1 || !tag2) + return false; + + if (tag1->time != tag2->time) + return false; + + if (tag1->num_items != tag2->num_items) + return false; + + for (unsigned i = 0; i < tag1->num_items; i++) { + if (tag1->items[i]->type != tag2->items[i]->type) + return false; + if (strcmp(tag1->items[i]->value, tag2->items[i]->value)) { + return false; + } + } + + return true; +} + +/** + * Replace invalid sequences with the question mark. + */ +static char * +patch_utf8(const char *src, size_t length, const gchar *end) +{ + /* duplicate the string, and replace invalid bytes in that + buffer */ + char *dest = g_strdup(src); + + do { + dest[end - src] = '?'; + } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end)); + + return dest; +} + +static char * +fix_utf8(const char *str, size_t length) +{ + const gchar *end; + char *temp; + gsize written; + + assert(str != nullptr); + + /* check if the string is already valid UTF-8 */ + if (g_utf8_validate(str, length, &end)) + return nullptr; + + /* no, it's not - try to import it from ISO-Latin-1 */ + temp = g_convert(str, length, "utf-8", "iso-8859-1", + nullptr, &written, nullptr); + if (temp != nullptr) + /* success! */ + return temp; + + /* no, still broken - there's no medication, just patch + invalid sequences */ + return patch_utf8(str, length, end); +} + +void tag_begin_add(struct tag *tag) +{ + assert(!bulk.busy); + assert(tag != nullptr); + assert(tag->items == nullptr); + assert(tag->num_items == 0); + +#ifndef NDEBUG + bulk.busy = true; +#endif + tag->items = bulk.items; +} + +void tag_end_add(struct tag *tag) +{ + if (tag->items == bulk.items) { + assert(tag->num_items <= BULK_MAX); + + if (tag->num_items > 0) { + /* copy the tag items from the bulk list over + to a new list (which fits exactly) */ + tag->items = (struct tag_item **) + g_malloc(items_size(tag)); + memcpy(tag->items, bulk.items, items_size(tag)); + } else + tag->items = nullptr; + } + +#ifndef NDEBUG + bulk.busy = false; +#endif +} + +static bool +char_is_non_printable(unsigned char ch) +{ + return ch < 0x20; +} + +static const char * +find_non_printable(const char *p, size_t length) +{ + for (size_t i = 0; i < length; ++i) + if (char_is_non_printable(p[i])) + return p + i; + + return nullptr; +} + +/** + * Clears all non-printable characters, convert them to space. + * Returns nullptr if nothing needs to be cleared. + */ +static char * +clear_non_printable(const char *p, size_t length) +{ + const char *first = find_non_printable(p, length); + char *dest; + + if (first == nullptr) + return nullptr; + + dest = g_strndup(p, length); + + for (size_t i = first - p; i < length; ++i) + if (char_is_non_printable(dest[i])) + dest[i] = ' '; + + return dest; +} + +static char * +fix_tag_value(const char *p, size_t length) +{ + char *utf8, *cleared; + + utf8 = fix_utf8(p, length); + if (utf8 != nullptr) { + p = utf8; + length = strlen(p); + } + + cleared = clear_non_printable(p, length); + if (cleared == nullptr) + cleared = utf8; + else + g_free(utf8); + + return cleared; +} + +static void +tag_add_item_internal(struct tag *tag, enum tag_type type, + const char *value, size_t len) +{ + unsigned int i = tag->num_items; + char *p; + + p = fix_tag_value(value, len); + if (p != nullptr) { + value = p; + len = strlen(value); + } + + tag->num_items++; + + if (tag->items != bulk.items) + /* bulk mode disabled */ + tag->items = (struct tag_item **) + g_realloc(tag->items, items_size(tag)); + else if (tag->num_items >= BULK_MAX) { + /* bulk list already full - switch back to non-bulk */ + assert(bulk.busy); + + tag->items = (struct tag_item **)g_malloc(items_size(tag)); + memcpy(tag->items, bulk.items, + items_size(tag) - sizeof(struct tag_item *)); + } + + g_static_mutex_lock(&tag_pool_lock); + tag->items[i] = tag_pool_get_item(type, value, len); + g_static_mutex_unlock(&tag_pool_lock); + + g_free(p); +} + +void tag_add_item_n(struct tag *tag, enum tag_type type, + const char *value, size_t len) +{ + if (ignore_tag_items[type]) + { + return; + } + if (!value || !len) + return; + + tag_add_item_internal(tag, type, value, len); +} diff --git a/src/TagInternal.hxx b/src/TagInternal.hxx new file mode 100644 index 000000000..afce0427e --- /dev/null +++ b/src/TagInternal.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_INTERNAL_HXX +#define MPD_TAG_INTERNAL_HXX + +#include "tag.h" + +extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; + +#endif diff --git a/src/TagNames.c b/src/TagNames.c new file mode 100644 index 000000000..eca320afb --- /dev/null +++ b/src/TagNames.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "tag.h" + +const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { + [TAG_ARTIST] = "Artist", + [TAG_ARTIST_SORT] = "ArtistSort", + [TAG_ALBUM] = "Album", + [TAG_ALBUM_ARTIST] = "AlbumArtist", + [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort", + [TAG_TITLE] = "Title", + [TAG_TRACK] = "Track", + [TAG_NAME] = "Name", + [TAG_GENRE] = "Genre", + [TAG_DATE] = "Date", + [TAG_COMPOSER] = "Composer", + [TAG_PERFORMER] = "Performer", + [TAG_COMMENT] = "Comment", + [TAG_DISC] = "Disc", + + /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */ + [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID", + [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID", + [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID", + [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID", +}; diff --git a/src/TagPool.cxx b/src/TagPool.cxx new file mode 100644 index 000000000..ce715a158 --- /dev/null +++ b/src/TagPool.cxx @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagPool.hxx" + +#include + +#if GCC_CHECK_VERSION(4, 2) +/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +GStaticMutex tag_pool_lock = G_STATIC_MUTEX_INIT; + +#if GCC_CHECK_VERSION(4, 2) +#pragma GCC diagnostic pop +#endif + +#define NUM_SLOTS 4096 + +struct slot { + struct slot *next; + unsigned char ref; + struct tag_item item; +} mpd_packed; + +static struct slot *slots[NUM_SLOTS]; + +static inline unsigned +calc_hash_n(enum tag_type type, const char *p, size_t length) +{ + unsigned hash = 5381; + + assert(p != nullptr); + + while (length-- > 0) + hash = (hash << 5) + hash + *p++; + + return hash ^ type; +} + +static inline unsigned +calc_hash(enum tag_type type, const char *p) +{ + unsigned hash = 5381; + + assert(p != nullptr); + + while (*p != 0) + hash = (hash << 5) + hash + *p++; + + return hash ^ type; +} + +static inline struct slot * +tag_item_to_slot(struct tag_item *item) +{ + return (struct slot*)(((char*)item) - offsetof(struct slot, item)); +} + +static struct slot *slot_alloc(struct slot *next, + enum tag_type type, + const char *value, int length) +{ + struct slot *slot; + + slot = (struct slot *) + g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); + slot->next = next; + slot->ref = 1; + slot->item.type = type; + memcpy(slot->item.value, value, length); + slot->item.value[length] = 0; + return slot; +} + +struct tag_item * +tag_pool_get_item(enum tag_type type, const char *value, size_t length) +{ + struct slot **slot_p, *slot; + + slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; + for (slot = *slot_p; slot != nullptr; slot = slot->next) { + if (slot->item.type == type && + length == strlen(slot->item.value) && + memcmp(value, slot->item.value, length) == 0 && + slot->ref < 0xff) { + assert(slot->ref > 0); + ++slot->ref; + return &slot->item; + } + } + + slot = slot_alloc(*slot_p, type, value, length); + *slot_p = slot; + return &slot->item; +} + +struct tag_item *tag_pool_dup_item(struct tag_item *item) +{ + struct slot *slot = tag_item_to_slot(item); + + assert(slot->ref > 0); + + if (slot->ref < 0xff) { + ++slot->ref; + return item; + } else { + /* the reference counter overflows above 0xff; + duplicate the item, and start with 1 */ + size_t length = strlen(item->value); + struct slot **slot_p = + &slots[calc_hash_n(item->type, item->value, + length) % NUM_SLOTS]; + slot = slot_alloc(*slot_p, item->type, + item->value, strlen(item->value)); + *slot_p = slot; + return &slot->item; + } +} + +void tag_pool_put_item(struct tag_item *item) +{ + struct slot **slot_p, *slot; + + slot = tag_item_to_slot(item); + assert(slot->ref > 0); + --slot->ref; + + if (slot->ref > 0) + return; + + for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS]; + *slot_p != slot; + slot_p = &(*slot_p)->next) { + assert(*slot_p != nullptr); + } + + *slot_p = slot->next; + g_free(slot); +} diff --git a/src/TagPool.hxx b/src/TagPool.hxx new file mode 100644 index 000000000..170b2dae4 --- /dev/null +++ b/src/TagPool.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_POOL_HXX +#define MPD_TAG_POOL_HXX + +#include "tag.h" + +#include + +extern GStaticMutex tag_pool_lock; + +struct tag_item; + +struct tag_item * +tag_pool_get_item(enum tag_type type, const char *value, size_t length); + +struct tag_item *tag_pool_dup_item(struct tag_item *item); + +void tag_pool_put_item(struct tag_item *item); + +#endif diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx index 5a5dbbeff..b3ad07df4 100644 --- a/src/TagPrint.cxx +++ b/src/TagPrint.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "TagPrint.hxx" #include "tag.h" -#include "tag_internal.h" +#include "TagInternal.hxx" #include "song.h" #include "Client.hxx" diff --git a/src/TagSave.cxx b/src/TagSave.cxx index 639dc2d97..15da9fc4b 100644 --- a/src/TagSave.cxx +++ b/src/TagSave.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "TagSave.hxx" #include "tag.h" -#include "tag_internal.h" +#include "TagInternal.hxx" #include "song.h" void tag_save(FILE *file, const struct tag *tag) diff --git a/src/tag.c b/src/tag.c deleted file mode 100644 index b59c23b23..000000000 --- a/src/tag.c +++ /dev/null @@ -1,523 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag.h" -#include "tag_internal.h" -#include "tag_pool.h" -#include "conf.h" -#include "song.h" -#include "mpd_error.h" - -#include -#include -#include -#include - -/** - * Maximum number of items managed in the bulk list; if it is - * exceeded, we switch back to "normal" reallocation. - */ -#define BULK_MAX 64 - -static struct { -#ifndef NDEBUG - bool busy; -#endif - struct tag_item *items[BULK_MAX]; -} bulk; - -const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ARTIST] = "Artist", - [TAG_ARTIST_SORT] = "ArtistSort", - [TAG_ALBUM] = "Album", - [TAG_ALBUM_ARTIST] = "AlbumArtist", - [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort", - [TAG_TITLE] = "Title", - [TAG_TRACK] = "Track", - [TAG_NAME] = "Name", - [TAG_GENRE] = "Genre", - [TAG_DATE] = "Date", - [TAG_COMPOSER] = "Composer", - [TAG_PERFORMER] = "Performer", - [TAG_COMMENT] = "Comment", - [TAG_DISC] = "Disc", - - /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */ - [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID", - [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID", - [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID", - [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID", -}; - -bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; - -enum tag_type -tag_name_parse(const char *name) -{ - assert(name != NULL); - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); - - if (strcmp(name, tag_item_names[i]) == 0) - return (enum tag_type)i; - } - - return TAG_NUM_OF_ITEM_TYPES; -} - -enum tag_type -tag_name_parse_i(const char *name) -{ - assert(name != NULL); - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); - - if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0) - return (enum tag_type)i; - } - - return TAG_NUM_OF_ITEM_TYPES; -} - -static size_t items_size(const struct tag *tag) -{ - return tag->num_items * sizeof(struct tag_item *); -} - -void tag_lib_init(void) -{ - const char *value; - int quit = 0; - char *temp; - char *s; - char *c; - enum tag_type type; - - /* parse the "metadata_to_use" config parameter below */ - - /* ignore comments by default */ - ignore_tag_items[TAG_COMMENT] = true; - - value = config_get_string(CONF_METADATA_TO_USE, NULL); - if (value == NULL) - return; - - memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES); - - if (0 == g_ascii_strcasecmp(value, "none")) - return; - - temp = c = s = g_strdup(value); - while (!quit) { - if (*s == ',' || *s == '\0') { - if (*s == '\0') - quit = 1; - *s = '\0'; - - c = g_strstrip(c); - if (*c == 0) - continue; - - type = tag_name_parse_i(c); - if (type == TAG_NUM_OF_ITEM_TYPES) - MPD_ERROR("error parsing metadata item \"%s\"", - c); - - ignore_tag_items[type] = false; - - s++; - c = s; - } - s++; - } - - g_free(temp); -} - -struct tag *tag_new(void) -{ - struct tag *ret = g_new(struct tag, 1); - ret->items = NULL; - ret->time = -1; - ret->has_playlist = false; - ret->num_items = 0; - return ret; -} - -static void tag_delete_item(struct tag *tag, unsigned idx) -{ - assert(idx < tag->num_items); - tag->num_items--; - - g_static_mutex_lock(&tag_pool_lock); - tag_pool_put_item(tag->items[idx]); - g_static_mutex_unlock(&tag_pool_lock); - - if (tag->num_items - idx > 0) { - memmove(tag->items + idx, tag->items + idx + 1, - (tag->num_items - idx) * sizeof(tag->items[0])); - } - - if (tag->num_items > 0) { - tag->items = g_realloc(tag->items, items_size(tag)); - } else { - g_free(tag->items); - tag->items = NULL; - } -} - -void tag_clear_items_by_type(struct tag *tag, enum tag_type type) -{ - for (unsigned i = 0; i < tag->num_items; i++) { - if (tag->items[i]->type == type) { - tag_delete_item(tag, i); - /* decrement since when just deleted this node */ - i--; - } - } -} - -void tag_free(struct tag *tag) -{ - int i; - - assert(tag != NULL); - - g_static_mutex_lock(&tag_pool_lock); - for (i = tag->num_items; --i >= 0; ) - tag_pool_put_item(tag->items[i]); - g_static_mutex_unlock(&tag_pool_lock); - - if (tag->items == bulk.items) { -#ifndef NDEBUG - assert(bulk.busy); - bulk.busy = false; -#endif - } else - g_free(tag->items); - - g_free(tag); -} - -struct tag *tag_dup(const struct tag *tag) -{ - struct tag *ret; - - if (!tag) - return NULL; - - ret = tag_new(); - ret->time = tag->time; - ret->has_playlist = tag->has_playlist; - ret->num_items = tag->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL; - - g_static_mutex_lock(&tag_pool_lock); - for (unsigned i = 0; i < tag->num_items; i++) - ret->items[i] = tag_pool_dup_item(tag->items[i]); - g_static_mutex_unlock(&tag_pool_lock); - - return ret; -} - -struct tag * -tag_merge(const struct tag *base, const struct tag *add) -{ - struct tag *ret; - unsigned n; - - assert(base != NULL); - assert(add != NULL); - - /* allocate new tag object */ - - ret = tag_new(); - ret->time = add->time > 0 ? add->time : base->time; - ret->num_items = base->num_items + add->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(ret)) : NULL; - - g_static_mutex_lock(&tag_pool_lock); - - /* copy all items from "add" */ - - for (unsigned i = 0; i < add->num_items; ++i) - ret->items[i] = tag_pool_dup_item(add->items[i]); - - n = add->num_items; - - /* copy additional items from "base" */ - - for (unsigned i = 0; i < base->num_items; ++i) - if (!tag_has_type(add, base->items[i]->type)) - ret->items[n++] = tag_pool_dup_item(base->items[i]); - - g_static_mutex_unlock(&tag_pool_lock); - - assert(n <= ret->num_items); - - if (n < ret->num_items) { - /* some tags were not copied - shrink ret->items */ - assert(n > 0); - - ret->num_items = n; - ret->items = g_realloc(ret->items, items_size(ret)); - } - - return ret; -} - -struct tag * -tag_merge_replace(struct tag *base, struct tag *add) -{ - if (add == NULL) - return base; - - if (base == NULL) - return add; - - struct tag *tag = tag_merge(base, add); - tag_free(base); - tag_free(add); - - return tag; -} - -const char * -tag_get_value(const struct tag *tag, enum tag_type type) -{ - assert(tag != NULL); - assert(type < TAG_NUM_OF_ITEM_TYPES); - - for (unsigned i = 0; i < tag->num_items; i++) - if (tag->items[i]->type == type) - return tag->items[i]->value; - - return NULL; -} - -bool tag_has_type(const struct tag *tag, enum tag_type type) -{ - return tag_get_value(tag, type) != NULL; -} - -bool tag_equal(const struct tag *tag1, const struct tag *tag2) -{ - if (tag1 == NULL && tag2 == NULL) - return true; - else if (!tag1 || !tag2) - return false; - - if (tag1->time != tag2->time) - return false; - - if (tag1->num_items != tag2->num_items) - return false; - - for (unsigned i = 0; i < tag1->num_items; i++) { - if (tag1->items[i]->type != tag2->items[i]->type) - return false; - if (strcmp(tag1->items[i]->value, tag2->items[i]->value)) { - return false; - } - } - - return true; -} - -/** - * Replace invalid sequences with the question mark. - */ -static char * -patch_utf8(const char *src, size_t length, const gchar *end) -{ - /* duplicate the string, and replace invalid bytes in that - buffer */ - char *dest = g_strdup(src); - - do { - dest[end - src] = '?'; - } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end)); - - return dest; -} - -static char * -fix_utf8(const char *str, size_t length) -{ - const gchar *end; - char *temp; - gsize written; - - assert(str != NULL); - - /* check if the string is already valid UTF-8 */ - if (g_utf8_validate(str, length, &end)) - return NULL; - - /* no, it's not - try to import it from ISO-Latin-1 */ - temp = g_convert(str, length, "utf-8", "iso-8859-1", - NULL, &written, NULL); - if (temp != NULL) - /* success! */ - return temp; - - /* no, still broken - there's no medication, just patch - invalid sequences */ - return patch_utf8(str, length, end); -} - -void tag_begin_add(struct tag *tag) -{ - assert(!bulk.busy); - assert(tag != NULL); - assert(tag->items == NULL); - assert(tag->num_items == 0); - -#ifndef NDEBUG - bulk.busy = true; -#endif - tag->items = bulk.items; -} - -void tag_end_add(struct tag *tag) -{ - if (tag->items == bulk.items) { - assert(tag->num_items <= BULK_MAX); - - if (tag->num_items > 0) { - /* copy the tag items from the bulk list over - to a new list (which fits exactly) */ - tag->items = g_malloc(items_size(tag)); - memcpy(tag->items, bulk.items, items_size(tag)); - } else - tag->items = NULL; - } - -#ifndef NDEBUG - bulk.busy = false; -#endif -} - -static bool -char_is_non_printable(unsigned char ch) -{ - return ch < 0x20; -} - -static const char * -find_non_printable(const char *p, size_t length) -{ - for (size_t i = 0; i < length; ++i) - if (char_is_non_printable(p[i])) - return p + i; - - return NULL; -} - -/** - * Clears all non-printable characters, convert them to space. - * Returns NULL if nothing needs to be cleared. - */ -static char * -clear_non_printable(const char *p, size_t length) -{ - const char *first = find_non_printable(p, length); - char *dest; - - if (first == NULL) - return NULL; - - dest = g_strndup(p, length); - - for (size_t i = first - p; i < length; ++i) - if (char_is_non_printable(dest[i])) - dest[i] = ' '; - - return dest; -} - -static char * -fix_tag_value(const char *p, size_t length) -{ - char *utf8, *cleared; - - utf8 = fix_utf8(p, length); - if (utf8 != NULL) { - p = utf8; - length = strlen(p); - } - - cleared = clear_non_printable(p, length); - if (cleared == NULL) - cleared = utf8; - else - g_free(utf8); - - return cleared; -} - -static void -tag_add_item_internal(struct tag *tag, enum tag_type type, - const char *value, size_t len) -{ - unsigned int i = tag->num_items; - char *p; - - p = fix_tag_value(value, len); - if (p != NULL) { - value = p; - len = strlen(value); - } - - tag->num_items++; - - if (tag->items != bulk.items) - /* bulk mode disabled */ - tag->items = g_realloc(tag->items, items_size(tag)); - else if (tag->num_items >= BULK_MAX) { - /* bulk list already full - switch back to non-bulk */ - assert(bulk.busy); - - tag->items = g_malloc(items_size(tag)); - memcpy(tag->items, bulk.items, - items_size(tag) - sizeof(struct tag_item *)); - } - - g_static_mutex_lock(&tag_pool_lock); - tag->items[i] = tag_pool_get_item(type, value, len); - g_static_mutex_unlock(&tag_pool_lock); - - g_free(p); -} - -void tag_add_item_n(struct tag *tag, enum tag_type type, - const char *value, size_t len) -{ - if (ignore_tag_items[type]) - { - return; - } - if (!value || !len) - return; - - tag_add_item_internal(tag, type, value, len); -} diff --git a/src/tag.h b/src/tag.h index a94fb0d5a..3b47148a4 100644 --- a/src/tag.h +++ b/src/tag.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_internal.h b/src/tag_internal.h deleted file mode 100644 index af05cc6b6..000000000 --- a/src/tag_internal.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_INTERNAL_H -#define MPD_TAG_INTERNAL_H - -#include "tag.h" - -#include - -extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; - -#endif diff --git a/src/tag_pool.c b/src/tag_pool.c deleted file mode 100644 index 2f9b39486..000000000 --- a/src/tag_pool.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_pool.h" - -#include - -#if GCC_CHECK_VERSION(4, 2) -/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -GStaticMutex tag_pool_lock = G_STATIC_MUTEX_INIT; - -#if GCC_CHECK_VERSION(4, 2) -#pragma GCC diagnostic pop -#endif - -#define NUM_SLOTS 4096 - -struct slot { - struct slot *next; - unsigned char ref; - struct tag_item item; -} mpd_packed; - -static struct slot *slots[NUM_SLOTS]; - -static inline unsigned -calc_hash_n(enum tag_type type, const char *p, size_t length) -{ - unsigned hash = 5381; - - assert(p != NULL); - - while (length-- > 0) - hash = (hash << 5) + hash + *p++; - - return hash ^ type; -} - -static inline unsigned -calc_hash(enum tag_type type, const char *p) -{ - unsigned hash = 5381; - - assert(p != NULL); - - while (*p != 0) - hash = (hash << 5) + hash + *p++; - - return hash ^ type; -} - -static inline struct slot * -tag_item_to_slot(struct tag_item *item) -{ - return (struct slot*)(((char*)item) - offsetof(struct slot, item)); -} - -static struct slot *slot_alloc(struct slot *next, - enum tag_type type, - const char *value, int length) -{ - struct slot *slot; - - slot = g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); - slot->next = next; - slot->ref = 1; - slot->item.type = type; - memcpy(slot->item.value, value, length); - slot->item.value[length] = 0; - return slot; -} - -struct tag_item * -tag_pool_get_item(enum tag_type type, const char *value, size_t length) -{ - struct slot **slot_p, *slot; - - slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; - for (slot = *slot_p; slot != NULL; slot = slot->next) { - if (slot->item.type == type && - length == strlen(slot->item.value) && - memcmp(value, slot->item.value, length) == 0 && - slot->ref < 0xff) { - assert(slot->ref > 0); - ++slot->ref; - return &slot->item; - } - } - - slot = slot_alloc(*slot_p, type, value, length); - *slot_p = slot; - return &slot->item; -} - -struct tag_item *tag_pool_dup_item(struct tag_item *item) -{ - struct slot *slot = tag_item_to_slot(item); - - assert(slot->ref > 0); - - if (slot->ref < 0xff) { - ++slot->ref; - return item; - } else { - /* the reference counter overflows above 0xff; - duplicate the item, and start with 1 */ - size_t length = strlen(item->value); - struct slot **slot_p = - &slots[calc_hash_n(item->type, item->value, - length) % NUM_SLOTS]; - slot = slot_alloc(*slot_p, item->type, - item->value, strlen(item->value)); - *slot_p = slot; - return &slot->item; - } -} - -void tag_pool_put_item(struct tag_item *item) -{ - struct slot **slot_p, *slot; - - slot = tag_item_to_slot(item); - assert(slot->ref > 0); - --slot->ref; - - if (slot->ref > 0) - return; - - for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS]; - *slot_p != slot; - slot_p = &(*slot_p)->next) { - assert(*slot_p != NULL); - } - - *slot_p = slot->next; - g_free(slot); -} diff --git a/src/tag_pool.h b/src/tag_pool.h deleted file mode 100644 index a717f704d..000000000 --- a/src/tag_pool.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_POOL_H -#define MPD_TAG_POOL_H - -#include "tag.h" - -#include - -extern GStaticMutex tag_pool_lock; - -struct tag_item; - -struct tag_item * -tag_pool_get_item(enum tag_type type, const char *value, size_t length); - -struct tag_item *tag_pool_dup_item(struct tag_item *item); - -void tag_pool_put_item(struct tag_item *item); - -#endif -- cgit v1.2.3