From 47911f9544ecd3b028cc044b400536234b17949a Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Mon, 7 Jan 2013 10:36:27 +0100
Subject: tag: convert to C++

---
 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 ----
 13 files changed, 781 insertions(+), 753 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

(limited to 'src')

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 <glib.h>
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 <glib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/**
+ * 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 <assert.h>
+
+#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 <glib.h>
+
+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 <glib.h>
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-/**
- * 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 <stdbool.h>
-
-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 <assert.h>
-
-#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 <glib.h>
-
-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