summaryrefslogtreecommitdiff
path: root/src/sticker
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2014-01-24 16:15:41 +0100
committerMax Kellermann <max@duempel.org>2014-01-24 16:38:15 +0100
commit973c9872f930d73a8ddc98e4802b242aea9f0dba (patch)
tree2c6a2d086fa3a7b04d98290a3d50fed8694bdb5a /src/sticker
parenteef7ba48ddb060c14caa5be27faeabe6f82b3e08 (diff)
Sticker*: move to sticker/
Diffstat (limited to 'src/sticker')
-rw-r--r--src/sticker/SongSticker.cxx121
-rw-r--r--src/sticker/SongSticker.hxx86
-rw-r--r--src/sticker/StickerDatabase.cxx604
-rw-r--r--src/sticker/StickerDatabase.hxx161
-rw-r--r--src/sticker/StickerPrint.cxx44
-rw-r--r--src/sticker/StickerPrint.hxx38
6 files changed, 1054 insertions, 0 deletions
diff --git a/src/sticker/SongSticker.cxx b/src/sticker/SongSticker.cxx
new file mode 100644
index 000000000..55143d278
--- /dev/null
+++ b/src/sticker/SongSticker.cxx
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2003-2014 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 "SongSticker.hxx"
+#include "StickerDatabase.hxx"
+#include "LightSong.hxx"
+#include "Song.hxx"
+#include "Directory.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+std::string
+sticker_song_get_value(const LightSong &song, const char *name)
+{
+ const auto uri = song.GetURI();
+ return sticker_load_value("song", uri.c_str(), name);
+}
+
+bool
+sticker_song_set_value(const LightSong &song,
+ const char *name, const char *value)
+{
+ const auto uri = song.GetURI();
+ return sticker_store_value("song", uri.c_str(), name, value);
+}
+
+bool
+sticker_song_delete(const LightSong &song)
+{
+ const auto uri = song.GetURI();
+ return sticker_delete("song", uri.c_str());
+}
+
+bool
+sticker_song_delete_value(const LightSong &song, const char *name)
+{
+ const auto uri = song.GetURI();
+ return sticker_delete_value("song", uri.c_str(), name);
+}
+
+struct sticker *
+sticker_song_get(const LightSong &song)
+{
+ const auto uri = song.GetURI();
+ return sticker_load("song", uri.c_str());
+}
+
+struct sticker_song_find_data {
+ Directory *directory;
+ const char *base_uri;
+ size_t base_uri_length;
+
+ void (*func)(const LightSong &song, const char *value,
+ void *user_data);
+ void *user_data;
+};
+
+static void
+sticker_song_find_cb(const char *uri, const char *value, void *user_data)
+{
+ struct sticker_song_find_data *data =
+ (struct sticker_song_find_data *)user_data;
+
+ if (memcmp(uri, data->base_uri, data->base_uri_length) != 0)
+ /* should not happen, ignore silently */
+ return;
+
+ Song *song = data->directory->LookupSong(uri + data->base_uri_length);
+ if (song != nullptr)
+ data->func(song->Export(), value, data->user_data);
+}
+
+bool
+sticker_song_find(Directory &directory, const char *name,
+ void (*func)(const LightSong &song, const char *value,
+ void *user_data),
+ void *user_data)
+{
+ struct sticker_song_find_data data;
+ data.directory = &directory;
+ data.func = func;
+ data.user_data = user_data;
+
+ char *allocated;
+ data.base_uri = directory.GetPath();
+ if (*data.base_uri != 0)
+ /* append slash to base_uri */
+ data.base_uri = allocated =
+ g_strconcat(data.base_uri, "/", nullptr);
+ else
+ /* searching in root directory - no trailing slash */
+ allocated = nullptr;
+
+ data.base_uri_length = strlen(data.base_uri);
+
+ bool success = sticker_find("song", data.base_uri, name,
+ sticker_song_find_cb, &data);
+ g_free(allocated);
+
+ return success;
+}
diff --git a/src/sticker/SongSticker.hxx b/src/sticker/SongSticker.hxx
new file mode 100644
index 000000000..2f977bd21
--- /dev/null
+++ b/src/sticker/SongSticker.hxx
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2003-2014 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_SONG_STICKER_HXX
+#define MPD_SONG_STICKER_HXX
+
+#include "Compiler.h"
+
+#include <string>
+
+struct LightSong;
+struct Directory;
+struct sticker;
+
+/**
+ * Returns one value from a song's sticker record. The caller must
+ * free the return value with g_free().
+ */
+gcc_pure
+std::string
+sticker_song_get_value(const LightSong &song, const char *name);
+
+/**
+ * Sets a sticker value in the specified song. Overwrites existing
+ * values.
+ */
+bool
+sticker_song_set_value(const LightSong &song,
+ const char *name, const char *value);
+
+/**
+ * Deletes a sticker from the database. All values are deleted.
+ */
+bool
+sticker_song_delete(const LightSong &song);
+
+/**
+ * Deletes a sticker value. Does nothing if the sticker did not
+ * exist.
+ */
+bool
+sticker_song_delete_value(const LightSong &song, const char *name);
+
+/**
+ * Loads the sticker for the specified song.
+ *
+ * @param song the song object
+ * @return a sticker object, or NULL on error or if there is no sticker
+ */
+sticker *
+sticker_song_get(const LightSong &song);
+
+/**
+ * Finds stickers with the specified name below the specified
+ * directory.
+ *
+ * Caller must lock the #db_mutex.
+ *
+ * @param directory the base directory to search in
+ * @param name the name of the sticker
+ * @return true on success (even if no sticker was found), false on
+ * failure
+ */
+bool
+sticker_song_find(Directory &directory, const char *name,
+ void (*func)(const LightSong &song, const char *value,
+ void *user_data),
+ void *user_data);
+
+#endif
diff --git a/src/sticker/StickerDatabase.cxx b/src/sticker/StickerDatabase.cxx
new file mode 100644
index 000000000..93eaa900d
--- /dev/null
+++ b/src/sticker/StickerDatabase.cxx
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2003-2014 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 "StickerDatabase.hxx"
+#include "fs/Path.hxx"
+#include "Idle.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+#include "Log.hxx"
+
+#include <string>
+#include <map>
+
+#include <sqlite3.h>
+#include <assert.h>
+
+#if SQLITE_VERSION_NUMBER < 3003009
+#define sqlite3_prepare_v2 sqlite3_prepare
+#endif
+
+struct sticker {
+ std::map<std::string, std::string> table;
+};
+
+enum sticker_sql {
+ STICKER_SQL_GET,
+ STICKER_SQL_LIST,
+ STICKER_SQL_UPDATE,
+ STICKER_SQL_INSERT,
+ STICKER_SQL_DELETE,
+ STICKER_SQL_DELETE_VALUE,
+ STICKER_SQL_FIND,
+};
+
+static const char *const sticker_sql[] = {
+ //[STICKER_SQL_GET] =
+ "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?",
+ //[STICKER_SQL_LIST] =
+ "SELECT name,value FROM sticker WHERE type=? AND uri=?",
+ //[STICKER_SQL_UPDATE] =
+ "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?",
+ //[STICKER_SQL_INSERT] =
+ "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)",
+ //[STICKER_SQL_DELETE] =
+ "DELETE FROM sticker WHERE type=? AND uri=?",
+ //[STICKER_SQL_DELETE_VALUE] =
+ "DELETE FROM sticker WHERE type=? AND uri=? AND name=?",
+ //[STICKER_SQL_FIND] =
+ "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?",
+};
+
+static const char sticker_sql_create[] =
+ "CREATE TABLE IF NOT EXISTS sticker("
+ " type VARCHAR NOT NULL, "
+ " uri VARCHAR NOT NULL, "
+ " name VARCHAR NOT NULL, "
+ " value VARCHAR NOT NULL"
+ ");"
+ "CREATE UNIQUE INDEX IF NOT EXISTS"
+ " sticker_value ON sticker(type, uri, name);"
+ "";
+
+static sqlite3 *sticker_db;
+static sqlite3_stmt *sticker_stmt[ARRAY_SIZE(sticker_sql)];
+
+static constexpr Domain sticker_domain("sticker");
+
+static void
+LogError(sqlite3 *db, const char *msg)
+{
+ FormatError(sticker_domain, "%s: %s", msg, sqlite3_errmsg(db));
+}
+
+static sqlite3_stmt *
+sticker_prepare(const char *sql, Error &error)
+{
+ int ret;
+ sqlite3_stmt *stmt;
+
+ ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr);
+ if (ret != SQLITE_OK) {
+ error.Format(sticker_domain, ret,
+ "sqlite3_prepare_v2() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return nullptr;
+ }
+
+ return stmt;
+}
+
+bool
+sticker_global_init(Path path, Error &error)
+{
+ assert(!path.IsNull());
+
+ int ret;
+
+ /* open/create the sqlite database */
+
+ ret = sqlite3_open(path.c_str(), &sticker_db);
+ if (ret != SQLITE_OK) {
+ const std::string utf8 = path.ToUTF8();
+ error.Format(sticker_domain, ret,
+ "Failed to open sqlite database '%s': %s",
+ utf8.c_str(), sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ /* create the table and index */
+
+ ret = sqlite3_exec(sticker_db, sticker_sql_create,
+ nullptr, nullptr, nullptr);
+ if (ret != SQLITE_OK) {
+ error.Format(sticker_domain, ret,
+ "Failed to create sticker table: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ /* prepare the statements we're going to use */
+
+ for (unsigned i = 0; i < ARRAY_SIZE(sticker_sql); ++i) {
+ assert(sticker_sql[i] != nullptr);
+
+ sticker_stmt[i] = sticker_prepare(sticker_sql[i], error);
+ if (sticker_stmt[i] == nullptr)
+ return false;
+ }
+
+ return true;
+}
+
+void
+sticker_global_finish(void)
+{
+ if (sticker_db == nullptr)
+ /* not configured */
+ return;
+
+ for (unsigned i = 0; i < ARRAY_SIZE(sticker_stmt); ++i) {
+ assert(sticker_stmt[i] != nullptr);
+
+ sqlite3_finalize(sticker_stmt[i]);
+ }
+
+ sqlite3_close(sticker_db);
+}
+
+bool
+sticker_enabled(void)
+{
+ return sticker_db != nullptr;
+}
+
+std::string
+sticker_load_value(const char *type, const char *uri, const char *name)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET];
+ int ret;
+
+ assert(sticker_enabled());
+ assert(type != nullptr);
+ assert(uri != nullptr);
+ assert(name != nullptr);
+
+ if (*name == 0)
+ return std::string();
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return std::string();
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return std::string();
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return std::string();
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ std::string value;
+ if (ret == SQLITE_ROW) {
+ /* record found */
+ value = (const char*)sqlite3_column_text(stmt, 0);
+ } else if (ret == SQLITE_DONE) {
+ /* no record found */
+ } else {
+ /* error */
+ LogError(sticker_db, "sqlite3_step() failed");
+ }
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ return value;
+}
+
+static bool
+sticker_list_values(std::map<std::string, std::string> &table,
+ const char *type, const char *uri)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST];
+ int ret;
+
+ assert(type != nullptr);
+ assert(uri != nullptr);
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ switch (ret) {
+ const char *name, *value;
+
+ case SQLITE_ROW:
+ name = (const char*)sqlite3_column_text(stmt, 0);
+ value = (const char*)sqlite3_column_text(stmt, 1);
+
+ table.insert(std::make_pair(name, value));
+ break;
+ case SQLITE_DONE:
+ break;
+ case SQLITE_BUSY:
+ /* no op */
+ break;
+ default:
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+ } while (ret != SQLITE_DONE);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ return true;
+}
+
+static bool
+sticker_update_value(const char *type, const char *uri,
+ const char *name, const char *value)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE];
+ int ret;
+
+ assert(type != nullptr);
+ assert(uri != nullptr);
+ assert(name != nullptr);
+ assert(*name != 0);
+ assert(value != nullptr);
+
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, value, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 4, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+
+ ret = sqlite3_changes(sticker_db);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ idle_add(IDLE_STICKER);
+ return ret > 0;
+}
+
+static bool
+sticker_insert_value(const char *type, const char *uri,
+ const char *name, const char *value)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT];
+ int ret;
+
+ assert(type != nullptr);
+ assert(uri != nullptr);
+ assert(name != nullptr);
+ assert(*name != 0);
+ assert(value != nullptr);
+
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 4, value, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+
+ idle_add(IDLE_STICKER);
+ return true;
+}
+
+bool
+sticker_store_value(const char *type, const char *uri,
+ const char *name, const char *value)
+{
+ assert(sticker_enabled());
+ assert(type != nullptr);
+ assert(uri != nullptr);
+ assert(name != nullptr);
+ assert(value != nullptr);
+
+ if (*name == 0)
+ return false;
+
+ return sticker_update_value(type, uri, name, value) ||
+ sticker_insert_value(type, uri, name, value);
+}
+
+bool
+sticker_delete(const char *type, const char *uri)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE];
+ int ret;
+
+ assert(sticker_enabled());
+ assert(type != nullptr);
+ assert(uri != nullptr);
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ idle_add(IDLE_STICKER);
+ return true;
+}
+
+bool
+sticker_delete_value(const char *type, const char *uri, const char *name)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE];
+ int ret;
+
+ assert(sticker_enabled());
+ assert(type != nullptr);
+ assert(uri != nullptr);
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+
+ ret = sqlite3_changes(sticker_db);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ idle_add(IDLE_STICKER);
+ return ret > 0;
+}
+
+void
+sticker_free(struct sticker *sticker)
+{
+ delete sticker;
+}
+
+const char *
+sticker_get_value(const struct sticker &sticker, const char *name)
+{
+ auto i = sticker.table.find(name);
+ if (i == sticker.table.end())
+ return nullptr;
+
+ return i->second.c_str();
+}
+
+void
+sticker_foreach(const sticker &sticker,
+ void (*func)(const char *name, const char *value,
+ void *user_data),
+ void *user_data)
+{
+ for (const auto &i : sticker.table)
+ func(i.first.c_str(), i.second.c_str(), user_data);
+}
+
+struct sticker *
+sticker_load(const char *type, const char *uri)
+{
+ sticker s;
+
+ if (!sticker_list_values(s.table, type, uri))
+ return nullptr;
+
+ if (s.table.empty())
+ /* don't return empty sticker objects */
+ return nullptr;
+
+ return new sticker(std::move(s));
+}
+
+bool
+sticker_find(const char *type, const char *base_uri, const char *name,
+ void (*func)(const char *uri, const char *value,
+ void *user_data),
+ void *user_data)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND];
+ int ret;
+
+ assert(type != nullptr);
+ assert(name != nullptr);
+ assert(func != nullptr);
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ if (base_uri == nullptr)
+ base_uri = "";
+
+ ret = sqlite3_bind_text(stmt, 2, base_uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ switch (ret) {
+ case SQLITE_ROW:
+ func((const char*)sqlite3_column_text(stmt, 0),
+ (const char*)sqlite3_column_text(stmt, 1),
+ user_data);
+ break;
+ case SQLITE_DONE:
+ break;
+ case SQLITE_BUSY:
+ /* no op */
+ break;
+ default:
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+ } while (ret != SQLITE_DONE);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ return true;
+}
diff --git a/src/sticker/StickerDatabase.hxx b/src/sticker/StickerDatabase.hxx
new file mode 100644
index 000000000..8993489c4
--- /dev/null
+++ b/src/sticker/StickerDatabase.hxx
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2003-2014 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.
+ */
+
+/*
+ * This is the sticker database library. It is the backend of all the
+ * sticker code in MPD.
+ *
+ * "Stickers" are pieces of information attached to existing MPD
+ * objects (e.g. song files, directories, albums). Clients can create
+ * arbitrary name/value pairs. MPD itself does not assume any special
+ * meaning in them.
+ *
+ * The goal is to allow clients to share additional (possibly dynamic)
+ * information about songs, which is neither stored on the client (not
+ * available to other clients), nor stored in the song files (MPD has
+ * no write access).
+ *
+ * Client developers should create a standard for common sticker
+ * names, to ensure interoperability.
+ *
+ * Examples: song ratings; statistics; deferred tag writes; lyrics;
+ * ...
+ *
+ */
+
+#ifndef MPD_STICKER_DATABASE_HXX
+#define MPD_STICKER_DATABASE_HXX
+
+#include "Compiler.h"
+
+#include <string>
+
+class Error;
+class Path;
+struct sticker;
+
+/**
+ * Opens the sticker database.
+ *
+ * @return true on success, false on error
+ */
+bool
+sticker_global_init(Path path, Error &error);
+
+/**
+ * Close the sticker database.
+ */
+void
+sticker_global_finish(void);
+
+/**
+ * Returns true if the sticker database is configured and available.
+ */
+gcc_const
+bool
+sticker_enabled(void);
+
+/**
+ * Returns one value from an object's sticker record. Returns an
+ * empty string if the value doesn't exist.
+ */
+std::string
+sticker_load_value(const char *type, const char *uri, const char *name);
+
+/**
+ * Sets a sticker value in the specified object. Overwrites existing
+ * values.
+ */
+bool
+sticker_store_value(const char *type, const char *uri,
+ const char *name, const char *value);
+
+/**
+ * Deletes a sticker from the database. All sticker values of the
+ * specified object are deleted.
+ */
+bool
+sticker_delete(const char *type, const char *uri);
+
+/**
+ * Deletes a sticker value. Fails if no sticker with this name
+ * exists.
+ */
+bool
+sticker_delete_value(const char *type, const char *uri, const char *name);
+
+/**
+ * Frees resources held by the sticker object.
+ *
+ * @param sticker the sticker object to be freed
+ */
+void
+sticker_free(sticker *sticker);
+
+/**
+ * Determines a single value in a sticker.
+ *
+ * @param sticker the sticker object
+ * @param name the name of the sticker
+ * @return the sticker value, or nullptr if none was found
+ */
+gcc_pure
+const char *
+sticker_get_value(const sticker &sticker, const char *name);
+
+/**
+ * Iterates over all sticker items in a sticker.
+ *
+ * @param sticker the sticker object
+ * @param func a callback function
+ * @param user_data an opaque pointer for the callback function
+ */
+void
+sticker_foreach(const sticker &sticker,
+ void (*func)(const char *name, const char *value,
+ void *user_data),
+ void *user_data);
+
+/**
+ * Loads the sticker for the specified resource.
+ *
+ * @param type the resource type, e.g. "song"
+ * @param uri the URI of the resource, e.g. the song path
+ * @return a sticker object, or nullptr on error or if there is no sticker
+ */
+sticker *
+sticker_load(const char *type, const char *uri);
+
+/**
+ * Finds stickers with the specified name below the specified URI.
+ *
+ * @param type the resource type, e.g. "song"
+ * @param base_uri the URI prefix of the resources, or nullptr if all
+ * resources should be searched
+ * @param name the name of the sticker
+ * @return true on success (even if no sticker was found), false on
+ * failure
+ */
+bool
+sticker_find(const char *type, const char *base_uri, const char *name,
+ void (*func)(const char *uri, const char *value,
+ void *user_data),
+ void *user_data);
+
+#endif
diff --git a/src/sticker/StickerPrint.cxx b/src/sticker/StickerPrint.cxx
new file mode 100644
index 000000000..a952ff203
--- /dev/null
+++ b/src/sticker/StickerPrint.cxx
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2003-2014 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 "StickerPrint.hxx"
+#include "StickerDatabase.hxx"
+#include "client/Client.hxx"
+
+void
+sticker_print_value(Client &client,
+ const char *name, const char *value)
+{
+ client_printf(client, "sticker: %s=%s\n", name, value);
+}
+
+static void
+print_sticker_cb(const char *name, const char *value, void *data)
+{
+ Client &client = *(Client *)data;
+
+ sticker_print_value(client, name, value);
+}
+
+void
+sticker_print(Client &client, const sticker &sticker)
+{
+ sticker_foreach(sticker, print_sticker_cb, &client);
+}
diff --git a/src/sticker/StickerPrint.hxx b/src/sticker/StickerPrint.hxx
new file mode 100644
index 000000000..39f3dc09e
--- /dev/null
+++ b/src/sticker/StickerPrint.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 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_STICKER_PRINT_HXX
+#define MPD_STICKER_PRINT_HXX
+
+struct sticker;
+class Client;
+
+/**
+ * Sends one sticker value to the client.
+ */
+void
+sticker_print_value(Client &client, const char *name, const char *value);
+
+/**
+ * Sends all sticker values to the client.
+ */
+void
+sticker_print(Client &client, const sticker &sticker);
+
+#endif