From 145ab84d51f262983412143fbb29487a8647adb3 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 19 Jan 2009 18:51:57 +0100 Subject: 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. --- doc/mpd.conf.5 | 4 + src/Makefile.am | 5 + src/conf.c | 1 + src/conf.h | 1 + src/main.c | 12 ++ src/sticker.c | 361 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/sticker.h | 86 ++++++++++++++ 7 files changed, 470 insertions(+) create mode 100644 src/sticker.c create mode 100644 src/sticker.h diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5 index a40ec9467..e375de195 100644 --- a/doc/mpd.conf.5 +++ b/doc/mpd.conf.5 @@ -43,6 +43,10 @@ The default is "yes". .B db_file This specifies where the db file will be stored. .TP +.B sticker_file +The location of the sticker database. This is a database which +manages dynamic information attached to songs. +.TP .B log_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 c4cde6952..8b8ed7e75 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 116a4ea5f..e017f1209 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 2aed9af9a..f94f6554f 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 449a9f747..fa2bc3ac6 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 000000000..e596fd97c --- /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 +#include +#include + +#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 000000000..ab3a35572 --- /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 + +/** + * 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 -- cgit v1.2.3