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> .B db_file <file>
This specifies where the db file will be stored. This specifies where the db file will be stored.
.TP .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> .B log_file <file>
This specifies where the log file should be located. This specifies where the log file should be located.
The special value "syslog" makes MPD use the local syslog daemon. The special value "syslog" makes MPD use the local syslog daemon.

View File

@ -100,6 +100,7 @@ mpd_headers = \
songvec.h \ songvec.h \
state_file.h \ state_file.h \
stats.h \ stats.h \
sticker.h \
tag.h \ tag.h \
tag_internal.h \ tag_internal.h \
tag_pool.h \ tag_pool.h \
@ -192,6 +193,10 @@ mpd_SOURCES = \
stored_playlist.c \ stored_playlist.c \
timer.c timer.c
if ENABLE_SQLITE
mpd_SOURCES += sticker.c
endif
if HAVE_LIBSAMPLERATE if HAVE_LIBSAMPLERATE
mpd_SOURCES += pcm_resample_libsamplerate.c mpd_SOURCES += pcm_resample_libsamplerate.c
else else

View File

@ -172,6 +172,7 @@ void config_global_init(void)
registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0); registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0);
registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0); registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0);
registerConfigParam(CONF_DB_FILE, 0, 0); registerConfigParam(CONF_DB_FILE, 0, 0);
registerConfigParam(CONF_STICKER_FILE, false, false);
registerConfigParam(CONF_LOG_FILE, 0, 0); registerConfigParam(CONF_LOG_FILE, 0, 0);
registerConfigParam(CONF_ERROR_FILE, 0, 0); registerConfigParam(CONF_ERROR_FILE, 0, 0);
registerConfigParam(CONF_PID_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_INSIDE_SYMLINKS "follow_inside_symlinks"
#define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks" #define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks"
#define CONF_DB_FILE "db_file" #define CONF_DB_FILE "db_file"
#define CONF_STICKER_FILE "sticker_file"
#define CONF_LOG_FILE "log_file" #define CONF_LOG_FILE "log_file"
#define CONF_ERROR_FILE "error_file" #define CONF_ERROR_FILE "error_file"
#define CONF_PID_FILE "pid_file" #define CONF_PID_FILE "pid_file"

View File

@ -54,6 +54,10 @@
#include "songvec.h" #include "songvec.h"
#include "tag_pool.h" #include "tag_pool.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
#endif
#ifdef ENABLE_ARCHIVE #ifdef ENABLE_ARCHIVE
#include "archive_list.h" #include "archive_list.h"
#endif #endif
@ -235,6 +239,10 @@ int main(int argc, char *argv[])
openDB(&options, argv[0]); openDB(&options, argv[0]);
#ifdef ENABLE_SQLITE
sticker_global_init(config_get_path(CONF_STICKER_FILE));
#endif
command_init(); command_init();
initialize_decoder_and_player(); initialize_decoder_and_player();
initAudioConfig(); initAudioConfig();
@ -278,6 +286,10 @@ int main(int argc, char *argv[])
g_debug("db_finish took %f seconds", g_debug("db_finish took %f seconds",
((float)(clock()-start))/CLOCKS_PER_SEC); ((float)(clock()-start))/CLOCKS_PER_SEC);
#ifdef ENABLE_SQLITE
sticker_global_finish();
#endif
notify_deinit(&main_notify); notify_deinit(&main_notify);
event_pipe_deinit(); 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