db_plugin: introducing a plugin API for the song database

First draft, not really pluggable currently - hard-coded to use the
"simple" plugin, and calls several of its internal functions.

The API is very simple currently, all searches are still performed
over the root "directory" object.  Future changes to the API will move
those search implementations into the plugin, to allow more efficient
implementations, or implementations that don't have the whole tree in
memory all the time.
This commit is contained in:
Max Kellermann 2011-09-05 23:03:05 +02:00
parent 7cc6b63aac
commit 7819aa6b20
8 changed files with 531 additions and 191 deletions

View File

@ -272,6 +272,8 @@ src_mpd_SOURCES = \
src/database.c \
src/db_save.c src/db_save.h \
src/db_print.c src/db_print.h \
src/db_plugin.h \
src/db/simple_db_plugin.c src/db/simple_db_plugin.h \
src/dirvec.c \
src/exclude.c \
src/fd_util.c \

View File

@ -20,8 +20,11 @@
#include "config.h"
#include "database.h"
#include "db_save.h"
#include "db_plugin.h"
#include "db/simple_db_plugin.h"
#include "directory.h"
#include "stats.h"
#include "conf.h"
#include <glib.h>
@ -35,11 +38,8 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "database"
static char *database_path;
static struct directory *music_root;
static time_t database_mtime;
static struct db *db;
static bool db_is_open;
/**
* The quark used for GError.domain.
@ -50,49 +50,50 @@ db_quark(void)
return g_quark_from_static_string("database");
}
void
db_init(const char *path)
bool
db_init(const struct config_param *path, GError **error_r)
{
database_path = g_strdup(path);
assert(db == NULL);
assert(!db_is_open);
if (path != NULL)
music_root = directory_new("", NULL);
if (path == NULL)
return true;
struct config_param *param = config_new_param("database", path->line);
config_add_block_param(param, "path", path->value, path->line);
db = db_plugin_new(&simple_db_plugin, param, error_r);
config_param_free(param);
return db != NULL;
}
void
db_finish(void)
{
assert((database_path == NULL) == (music_root == NULL));
if (db_is_open)
db_plugin_close(db);
if (music_root != NULL)
directory_free(music_root);
g_free(database_path);
}
void
db_clear(void)
{
assert(music_root != NULL);
directory_free(music_root);
music_root = directory_new("", NULL);
if (db != NULL)
db_plugin_free(db);
}
struct directory *
db_get_root(void)
{
assert(music_root != NULL);
assert(db != NULL);
return music_root;
return simple_db_get_root(db);
}
struct directory *
db_get_directory(const char *name)
{
if (music_root == NULL)
if (db == NULL)
return NULL;
struct directory *music_root = db_get_root();
if (name == NULL)
return music_root;
@ -106,9 +107,10 @@ db_get_song(const char *file)
g_debug("get song: %s", file);
if (music_root == NULL)
if (db == NULL)
return NULL;
struct directory *music_root = db_get_root();
return directory_lookup_song(music_root, file);
}
@ -119,7 +121,7 @@ db_walk(const char *name,
{
struct directory *directory;
if (music_root == NULL)
if (db == NULL)
return -1;
if ((directory = db_get_directory(name)) == NULL) {
@ -133,157 +135,36 @@ db_walk(const char *name,
return directory_walk(directory, forEachSong, forEachDir, data);
}
bool
db_check(GError **error_r)
{
struct stat st;
assert(database_path != NULL);
/* Check if the file exists */
if (access(database_path, F_OK)) {
/* If the file doesn't exist, we can't check if we can write
* it, so we are going to try to get the directory path, and
* see if we can write a file in that */
char *dirPath = g_path_get_dirname(database_path);
/* Check that the parent part of the path is a directory */
if (stat(dirPath, &st) < 0) {
g_free(dirPath);
g_set_error(error_r, db_quark(), errno,
"Couldn't stat parent directory of db file "
"\"%s\": %s",
database_path, g_strerror(errno));
return false;
}
if (!S_ISDIR(st.st_mode)) {
g_free(dirPath);
g_set_error(error_r, db_quark(), 0,
"Couldn't create db file \"%s\" because the "
"parent path is not a directory",
database_path);
return false;
}
/* Check if we can write to the directory */
if (access(dirPath, X_OK | W_OK)) {
g_set_error(error_r, db_quark(), errno,
"Can't create db file in \"%s\": %s",
dirPath, g_strerror(errno));
g_free(dirPath);
return false;
}
g_free(dirPath);
return true;
}
/* Path exists, now check if it's a regular file */
if (stat(database_path, &st) < 0) {
g_set_error(error_r, db_quark(), errno,
"Couldn't stat db file \"%s\": %s",
database_path, g_strerror(errno));
return false;
}
if (!S_ISREG(st.st_mode)) {
g_set_error(error_r, db_quark(), 0,
"db file \"%s\" is not a regular file",
database_path);
return false;
}
/* And check that we can write to it */
if (access(database_path, R_OK | W_OK)) {
g_set_error(error_r, db_quark(), errno,
"Can't open db file \"%s\" for reading/writing: %s",
database_path, g_strerror(errno));
return false;
}
return true;
}
bool
db_save(GError **error_r)
{
FILE *fp;
struct stat st;
assert(db != NULL);
assert(db_is_open);
assert(database_path != NULL);
assert(music_root != NULL);
g_debug("removing empty directories from DB");
directory_prune_empty(music_root);
g_debug("sorting DB");
directory_sort(music_root);
g_debug("writing DB");
fp = fopen(database_path, "w");
if (!fp) {
g_set_error(error_r, db_quark(), errno,
"unable to write to db file \"%s\": %s",
database_path, g_strerror(errno));
return false;
}
db_save_internal(fp, music_root);
if (ferror(fp)) {
g_set_error(error_r, db_quark(), errno,
"Failed to write to database file: %s",
g_strerror(errno));
fclose(fp);
return false;
}
fclose(fp);
if (stat(database_path, &st) == 0)
database_mtime = st.st_mtime;
return true;
return simple_db_save(db, error_r);
}
bool
db_load(GError **error)
{
FILE *fp = NULL;
struct stat st;
assert(db != NULL);
assert(!db_is_open);
assert(database_path != NULL);
assert(music_root != NULL);
fp = fopen(database_path, "r");
if (fp == NULL) {
g_set_error(error, db_quark(), errno,
"Failed to open database file \"%s\": %s",
database_path, strerror(errno));
if (!db_plugin_open(db, error))
return false;
}
if (!db_load_internal(fp, music_root, error)) {
fclose(fp);
return false;
}
fclose(fp);
db_is_open = true;
stats_update();
if (stat(database_path, &st) == 0)
database_mtime = st.st_mtime;
return true;
}
time_t
db_get_mtime(void)
{
return database_mtime;
assert(db != NULL);
assert(db_is_open);
return simple_db_get_mtime(db);
}

View File

@ -25,6 +25,7 @@
#include <sys/time.h>
#include <stdbool.h>
struct config_param;
struct directory;
/**
@ -32,18 +33,12 @@ struct directory;
*
* @param path the absolute path of the database file
*/
void
db_init(const char *path);
bool
db_init(const struct config_param *path, GError **error_r);
void
db_finish(void);
/**
* Clear the database.
*/
void
db_clear(void);
/**
* Returns the root directory object. Returns NULL if there is no
* configured music directory.

287
src/db/simple_db_plugin.c Normal file
View File

@ -0,0 +1,287 @@
/*
* Copyright (C) 2003-2011 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 "simple_db_plugin.h"
#include "db_internal.h"
#include "db_save.h"
#include "conf.h"
#include "glib_compat.h"
#include "directory.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
struct simple_db {
struct db base;
char *path;
struct directory *root;
time_t mtime;
};
G_GNUC_CONST
static inline GQuark
simple_db_quark(void)
{
return g_quark_from_static_string("simple_db");
}
static struct db *
simple_db_init(const struct config_param *param, GError **error_r)
{
struct simple_db *db = g_malloc(sizeof(*db));
db_base_init(&db->base, &simple_db_plugin);
GError *error = NULL;
db->path = config_dup_block_path(param, "path", error_r);
if (db->path == NULL) {
g_free(db);
if (error != NULL)
g_propagate_error(error_r, error);
else
g_set_error(error_r, simple_db_quark(), 0,
"No \"path\" parameter specified");
return NULL;
}
return &db->base;
}
static void
simple_db_finish(struct db *_db)
{
struct simple_db *db = (struct simple_db *)_db;
g_free(db->path);
g_free(db);
}
static bool
simple_db_check(struct simple_db *db, GError **error_r)
{
assert(db != NULL);
assert(db->path != NULL);
/* Check if the file exists */
if (access(db->path, F_OK)) {
/* If the file doesn't exist, we can't check if we can write
* it, so we are going to try to get the directory path, and
* see if we can write a file in that */
char *dirPath = g_path_get_dirname(db->path);
/* Check that the parent part of the path is a directory */
struct stat st;
if (stat(dirPath, &st) < 0) {
g_free(dirPath);
g_set_error(error_r, simple_db_quark(), errno,
"Couldn't stat parent directory of db file "
"\"%s\": %s",
db->path, g_strerror(errno));
return false;
}
if (!S_ISDIR(st.st_mode)) {
g_free(dirPath);
g_set_error(error_r, simple_db_quark(), 0,
"Couldn't create db file \"%s\" because the "
"parent path is not a directory",
db->path);
return false;
}
/* Check if we can write to the directory */
if (access(dirPath, X_OK | W_OK)) {
g_set_error(error_r, simple_db_quark(), errno,
"Can't create db file in \"%s\": %s",
dirPath, g_strerror(errno));
g_free(dirPath);
return false;
}
g_free(dirPath);
return true;
}
/* Path exists, now check if it's a regular file */
struct stat st;
if (stat(db->path, &st) < 0) {
g_set_error(error_r, simple_db_quark(), errno,
"Couldn't stat db file \"%s\": %s",
db->path, g_strerror(errno));
return false;
}
if (!S_ISREG(st.st_mode)) {
g_set_error(error_r, simple_db_quark(), 0,
"db file \"%s\" is not a regular file",
db->path);
return false;
}
/* And check that we can write to it */
if (access(db->path, R_OK | W_OK)) {
g_set_error(error_r, simple_db_quark(), errno,
"Can't open db file \"%s\" for reading/writing: %s",
db->path, g_strerror(errno));
return false;
}
return true;
}
static bool
simple_db_load(struct simple_db *db, GError **error_r)
{
assert(db != NULL);
assert(db->path != NULL);
assert(db->root != NULL);
FILE *fp = fopen(db->path, "r");
if (fp == NULL) {
g_set_error(error_r, simple_db_quark(), errno,
"Failed to open database file \"%s\": %s",
db->path, g_strerror(errno));
return false;
}
if (!db_load_internal(fp, db->root, error_r)) {
fclose(fp);
return false;
}
fclose(fp);
struct stat st;
if (stat(db->path, &st) == 0)
db->mtime = st.st_mtime;
return true;
}
static bool
simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r)
{
struct simple_db *db = (struct simple_db *)_db;
db->root = directory_new("", NULL);
db->mtime = 0;
GError *error = NULL;
if (!simple_db_load(db, &error)) {
directory_free(db->root);
g_warning("Failed to load database: %s", error->message);
g_error_free(error);
if (!simple_db_check(db, error_r))
return false;
db->root = directory_new("", NULL);
}
return true;
}
static void
simple_db_close(struct db *_db)
{
struct simple_db *db = (struct simple_db *)_db;
assert(db->root != NULL);
directory_free(db->root);
}
const struct db_plugin simple_db_plugin = {
.name = "simple",
.init = simple_db_init,
.finish = simple_db_finish,
.open = simple_db_open,
.close = simple_db_close,
};
struct directory *
simple_db_get_root(struct db *_db)
{
struct simple_db *db = (struct simple_db *)_db;
assert(db != NULL);
assert(db->root != NULL);
return db->root;
}
bool
simple_db_save(struct db *_db, GError **error_r)
{
struct simple_db *db = (struct simple_db *)_db;
struct directory *music_root = db->root;
g_debug("removing empty directories from DB");
directory_prune_empty(music_root);
g_debug("sorting DB");
directory_sort(music_root);
g_debug("writing DB");
FILE *fp = fopen(db->path, "w");
if (!fp) {
g_set_error(error_r, simple_db_quark(), errno,
"unable to write to db file \"%s\": %s",
db->path, g_strerror(errno));
return false;
}
db_save_internal(fp, music_root);
if (ferror(fp)) {
g_set_error(error_r, simple_db_quark(), errno,
"Failed to write to database file: %s",
g_strerror(errno));
fclose(fp);
return false;
}
fclose(fp);
struct stat st;
if (stat(db->path, &st) == 0)
db->mtime = st.st_mtime;
return true;
}
time_t
simple_db_get_mtime(const struct db *_db)
{
const struct simple_db *db = (const struct simple_db *)_db;
assert(db != NULL);
assert(db->root != NULL);
return db->mtime;
}

42
src/db/simple_db_plugin.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2003-2011 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_SIMPLE_DB_PLUGIN_H
#define MPD_SIMPLE_DB_PLUGIN_H
#include <glib.h>
#include <stdbool.h>
#include <time.h>
extern const struct db_plugin simple_db_plugin;
struct db;
G_GNUC_PURE
struct directory *
simple_db_get_root(struct db *db);
bool
simple_db_save(struct db *db, GError **error_r);
G_GNUC_PURE
time_t
simple_db_get_mtime(const struct db *db);
#endif

35
src/db_internal.h Normal file
View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2003-2011 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_DB_INTERNAL_H
#define MPD_DB_INTERNAL_H
#include "db_plugin.h"
#include <assert.h>
static inline void
db_base_init(struct db *db, const struct db_plugin *plugin)
{
assert(plugin != NULL);
db->plugin = plugin;
}
#endif

111
src/db_plugin.h Normal file
View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2003-2011 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.
*/
/** \file
*
* This header declares the db_plugin class. It describes a
* plugin API for databases of song metadata.
*/
#ifndef MPD_DB_PLUGIN_H
#define MPD_DB_PLUGIN_H
#include <glib.h>
#include <assert.h>
#include <stdbool.h>
struct config_param;
struct db {
const struct db_plugin *plugin;
};
struct db_plugin {
const char *name;
/**
* Allocates and configures a database.
*/
struct db *(*init)(const struct config_param *param, GError **error_r);
/**
* Free instance data.
*/
void (*finish)(struct db *db);
/**
* Open the database. Read it into memory if applicable.
*/
bool (*open)(struct db *db, GError **error_r);
/**
* Close the database, free allocated memory.
*/
void (*close)(struct db *db);
};
G_GNUC_MALLOC
static inline struct db *
db_plugin_new(const struct db_plugin *plugin, const struct config_param *param,
GError **error_r)
{
assert(plugin != NULL);
assert(plugin->init != NULL);
assert(plugin->finish != NULL);
assert(error_r == NULL || *error_r == NULL);
struct db *db = plugin->init(param, error_r);
assert(db == NULL || db->plugin == plugin);
assert(db != NULL || error_r == NULL || *error_r != NULL);
return db;
}
static inline void
db_plugin_free(struct db *db)
{
assert(db != NULL);
assert(db->plugin != NULL);
assert(db->plugin->finish != NULL);
db->plugin->finish(db);
}
static inline bool
db_plugin_open(struct db *db, GError **error_r)
{
assert(db != NULL);
assert(db->plugin != NULL);
return db->plugin->open != NULL
? db->plugin->open(db, error_r)
: true;
}
static inline void
db_plugin_close(struct db *db)
{
assert(db != NULL);
assert(db->plugin != NULL);
if (db->plugin->close != NULL)
db->plugin->close(db);
}
#endif

View File

@ -156,44 +156,31 @@ glue_mapper_init(GError **error_r)
static bool
glue_db_init_and_load(void)
{
GError *error = NULL;
char *path = config_dup_path(CONF_DB_FILE, &error);
if (path == NULL && error != NULL)
MPD_ERROR("%s", error->message);
const struct config_param *path = config_get_param(CONF_DB_FILE);
GError *error = NULL;
bool ret;
if (!mapper_has_music_directory()) {
g_free(path);
if (path != NULL)
g_message("Found " CONF_DB_FILE " setting without "
CONF_MUSIC_DIR " - disabling database");
db_init(NULL);
db_init(NULL, NULL);
return true;
}
if (path == NULL)
MPD_ERROR(CONF_DB_FILE " setting missing");
db_init(path);
g_free(path);
if (!db_init(path, &error))
MPD_ERROR("%s", error->message);
ret = db_load(&error);
if (!ret) {
g_warning("Failed to load database: %s", error->message);
g_error_free(error);
error = NULL;
if (!ret)
MPD_ERROR("%s", error->message);
if (!db_check(&error))
MPD_ERROR("%s", error->message);
db_clear();
/* run database update after daemonization */
return false;
}
return true;
/* run database update after daemonization? */
return db_exists();
}
/**