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.
This commit is contained in:
Max Kellermann 2009-01-19 18:51:57 +01:00
parent fbed96dcea
commit 145ab84d51
7 changed files with 470 additions and 0 deletions

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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"

View File

@ -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();

361
src/sticker.c Normal file
View File

@ -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;
}

86
src/sticker.h Normal file
View File

@ -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