aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2009-01-19 18:51:57 +0100
committerMax Kellermann <max@duempel.org>2009-01-19 18:51:57 +0100
commit145ab84d51f262983412143fbb29487a8647adb3 (patch)
tree8fc58be92be2178c86484caca17fae23535e8fad
parentfbed96dcea00db7c2f5044929caebfc06684ea15 (diff)
sticker: new library for storing dynamic information about songs
"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.
-rw-r--r--doc/mpd.conf.54
-rw-r--r--src/Makefile.am5
-rw-r--r--src/conf.c1
-rw-r--r--src/conf.h1
-rw-r--r--src/main.c12
-rw-r--r--src/sticker.c361
-rw-r--r--src/sticker.h86
7 files changed, 470 insertions, 0 deletions
diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5
index a40ec946..e375de19 100644
--- a/doc/mpd.conf.5
+++ b/doc/mpd.conf.5
@@ -43,6 +43,10 @@ The default is "yes".
.B db_file <file>
This specifies where the db file will be stored.
.TP
+.B sticker_file <file>
+The location of the sticker database. This is a database which
+manages dynamic information attached to songs.
+.TP
.B log_file <file>
This specifies where the log file should be located.
The special value "syslog" makes MPD use the local syslog daemon.
diff --git a/src/Makefile.am b/src/Makefile.am
index c4cde695..8b8ed7e7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -100,6 +100,7 @@ mpd_headers = \
songvec.h \
state_file.h \
stats.h \
+ sticker.h \
tag.h \
tag_internal.h \
tag_pool.h \
@@ -192,6 +193,10 @@ mpd_SOURCES = \
stored_playlist.c \
timer.c
+if ENABLE_SQLITE
+mpd_SOURCES += sticker.c
+endif
+
if HAVE_LIBSAMPLERATE
mpd_SOURCES += pcm_resample_libsamplerate.c
else
diff --git a/src/conf.c b/src/conf.c
index 116a4ea5..e017f120 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -172,6 +172,7 @@ void config_global_init(void)
registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0);
registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0);
registerConfigParam(CONF_DB_FILE, 0, 0);
+ registerConfigParam(CONF_STICKER_FILE, false, false);
registerConfigParam(CONF_LOG_FILE, 0, 0);
registerConfigParam(CONF_ERROR_FILE, 0, 0);
registerConfigParam(CONF_PID_FILE, 0, 0);
diff --git a/src/conf.h b/src/conf.h
index 2aed9af9..f94f6554 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -27,6 +27,7 @@
#define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks"
#define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks"
#define CONF_DB_FILE "db_file"
+#define CONF_STICKER_FILE "sticker_file"
#define CONF_LOG_FILE "log_file"
#define CONF_ERROR_FILE "error_file"
#define CONF_PID_FILE "pid_file"
diff --git a/src/main.c b/src/main.c
index 449a9f74..fa2bc3ac 100644
--- a/src/main.c
+++ b/src/main.c
@@ -54,6 +54,10 @@
#include "songvec.h"
#include "tag_pool.h"
+#ifdef ENABLE_SQLITE
+#include "sticker.h"
+#endif
+
#ifdef ENABLE_ARCHIVE
#include "archive_list.h"
#endif
@@ -235,6 +239,10 @@ int main(int argc, char *argv[])
openDB(&options, argv[0]);
+#ifdef ENABLE_SQLITE
+ sticker_global_init(config_get_path(CONF_STICKER_FILE));
+#endif
+
command_init();
initialize_decoder_and_player();
initAudioConfig();
@@ -278,6 +286,10 @@ int main(int argc, char *argv[])
g_debug("db_finish took %f seconds",
((float)(clock()-start))/CLOCKS_PER_SEC);
+#ifdef ENABLE_SQLITE
+ sticker_global_finish();
+#endif
+
notify_deinit(&main_notify);
event_pipe_deinit();
diff --git a/src/sticker.c b/src/sticker.c
new file mode 100644
index 00000000..e596fd97
--- /dev/null
+++ b/src/sticker.c
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2003-2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "sticker.h"
+
+#include <glib.h>
+#include <sqlite3.h>
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "sticker"
+
+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 const char sticker_sql_get[] =
+ "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?";
+
+static const char sticker_sql_update[] =
+ "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?";
+
+static const char sticker_sql_insert[] =
+ "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)";
+
+static const char sticker_sql_delete[] =
+ "DELETE FROM sticker WHERE type=? AND uri=?";
+
+static sqlite3 *sticker_db;
+static sqlite3_stmt *sticker_stmt_get, *sticker_stmt_update,
+ *sticker_stmt_insert, *sticker_stmt_delete;
+
+static sqlite3_stmt *
+sticker_prepare(const char *sql)
+{
+ int ret;
+ sqlite3_stmt *stmt;
+
+ ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL);
+ if (ret != SQLITE_OK)
+ g_error("sqlite3_prepare_v2() failed: %s",
+ sqlite3_errmsg(sticker_db));
+
+ return stmt;
+}
+
+void
+sticker_global_init(const char *path)
+{
+ int ret;
+
+ if (path == NULL)
+ /* not configured */
+ return;
+
+ /* open/create the sqlite database */
+
+ ret = sqlite3_open(path, &sticker_db);
+ if (ret != SQLITE_OK)
+ g_error("Failed to open sqlite database '%s': %s",
+ path, sqlite3_errmsg(sticker_db));
+
+ /* create the table and index */
+
+ ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL);
+ if (ret != SQLITE_OK)
+ g_error("Failed to create sticker table: %s",
+ sqlite3_errmsg(sticker_db));
+
+ /* prepare the statements we're going to use */
+
+ sticker_stmt_get = sticker_prepare(sticker_sql_get);
+ sticker_stmt_update = sticker_prepare(sticker_sql_update);
+ sticker_stmt_insert = sticker_prepare(sticker_sql_insert);
+ sticker_stmt_delete = sticker_prepare(sticker_sql_delete);
+
+ if (sticker_stmt_get == NULL || sticker_stmt_update == NULL ||
+ sticker_stmt_insert == NULL || sticker_stmt_delete == NULL)
+ g_error("Failed to prepare sqlite statements");
+}
+
+void
+sticker_global_finish(void)
+{
+ if (sticker_db == NULL)
+ /* not configured */
+ return;
+
+ sqlite3_finalize(sticker_stmt_delete);
+ sqlite3_finalize(sticker_stmt_update);
+ sqlite3_finalize(sticker_stmt_insert);
+ sqlite3_close(sticker_db);
+}
+
+bool
+sticker_enabled(void)
+{
+ return sticker_db != NULL;
+}
+
+char *
+sticker_load_value(const char *type, const char *uri, const char *name)
+{
+ int ret;
+ char *value;
+
+ assert(sticker_enabled());
+ assert(type != NULL);
+ assert(uri != NULL);
+ assert(name != NULL);
+
+ if (*name == 0)
+ return NULL;
+
+ sqlite3_reset(sticker_stmt_get);
+
+ ret = sqlite3_bind_text(sticker_stmt_get, 1, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(sticker_stmt_get, 2, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(sticker_stmt_get, 3, name, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(sticker_stmt_get);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret == SQLITE_ROW) {
+ /* record found */
+ value = g_strdup((const char*)sqlite3_column_text(sticker_stmt_get, 0));
+ } else if (ret == SQLITE_DONE) {
+ /* no record found */
+ value = NULL;
+ } else {
+ /* error */
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ sqlite3_reset(sticker_stmt_get);
+ sqlite3_clear_bindings(sticker_stmt_get);
+
+ return value;
+}
+
+static bool
+sticker_update_value(const char *type, const char *uri,
+ const char *name, const char *value)
+{
+ int ret;
+
+ assert(type != NULL);
+ assert(uri != NULL);
+ assert(name != NULL);
+ assert(*name != 0);
+ assert(value != NULL);
+
+ assert(sticker_enabled());
+
+ sqlite3_reset(sticker_stmt_update);
+
+ ret = sqlite3_bind_text(sticker_stmt_update, 1, value, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(sticker_stmt_update, 2, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(sticker_stmt_update, 3, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(sticker_stmt_update, 4, name, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(sticker_stmt_update);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_changes(sticker_db);
+
+ sqlite3_reset(sticker_stmt_update);
+ sqlite3_clear_bindings(sticker_stmt_update);
+
+ return ret > 0;
+}
+
+static bool
+sticker_insert_value(const char *type, const char *uri,
+ const char *name, const char *value)
+{
+ int ret;
+
+ assert(type != NULL);
+ assert(uri != NULL);
+ assert(name != NULL);
+ assert(*name != 0);
+ assert(value != NULL);
+
+ assert(sticker_enabled());
+
+ sqlite3_reset(sticker_stmt_insert);
+
+ ret = sqlite3_bind_text(sticker_stmt_insert, 1, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(sticker_stmt_insert, 2, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(sticker_stmt_insert, 3, name, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(sticker_stmt_insert, 4, value, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(sticker_stmt_insert);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ sqlite3_reset(sticker_stmt_insert);
+ sqlite3_clear_bindings(sticker_stmt_insert);
+
+ return true;
+}
+
+bool
+sticker_store_value(const char *type, const char *uri,
+ const char *name, const char *value)
+{
+ assert(sticker_enabled());
+ assert(type != NULL);
+ assert(uri != NULL);
+ assert(name != NULL);
+ assert(value != NULL);
+
+ 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)
+{
+ int ret;
+
+ assert(sticker_enabled());
+ assert(type != NULL);
+ assert(uri != NULL);
+
+ sqlite3_reset(sticker_stmt_delete);
+
+ ret = sqlite3_bind_text(sticker_stmt_delete, 1, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(sticker_stmt_delete, 2, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(sticker_stmt_delete);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ sqlite3_reset(sticker_stmt_delete);
+ sqlite3_clear_bindings(sticker_stmt_delete);
+
+ return true;
+}
diff --git a/src/sticker.h b/src/sticker.h
new file mode 100644
index 00000000..ab3a3557
--- /dev/null
+++ b/src/sticker.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2003-2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 STICKER_H
+#define STICKER_H
+
+#include <stdbool.h>
+
+/**
+ * Opens the sticker database (if path is not NULL).
+ */
+void
+sticker_global_init(const char *path);
+
+/**
+ * Close the sticker database.
+ */
+void
+sticker_global_finish(void);
+
+/**
+ * Returns true if the sticker database is configured and available.
+ */
+bool
+sticker_enabled(void);
+
+/**
+ * Returns one value from an object's sticker record. The caller must
+ * free the return value with g_free().
+ */
+char *
+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);
+
+#endif