diff options
Diffstat (limited to '')
-rw-r--r-- | src/db/simple_db_plugin.c | 345 | ||||
-rw-r--r-- | src/db/simple_db_plugin.h | 42 | ||||
-rw-r--r-- | src/dbUtils.c | 324 | ||||
-rw-r--r-- | src/dbUtils.h | 45 | ||||
-rw-r--r-- | src/db_error.h | 45 | ||||
-rw-r--r-- | src/db_internal.h | 35 | ||||
-rw-r--r-- | src/db_plugin.h | 156 | ||||
-rw-r--r-- | src/db_print.c | 374 | ||||
-rw-r--r-- | src/db_print.h | 71 | ||||
-rw-r--r-- | src/db_save.c | 176 | ||||
-rw-r--r-- | src/db_save.h | 35 | ||||
-rw-r--r-- | src/db_selection.h | 56 | ||||
-rw-r--r-- | src/db_visitor.h | 54 |
13 files changed, 1465 insertions, 293 deletions
diff --git a/src/db/simple_db_plugin.c b/src/db/simple_db_plugin.c new file mode 100644 index 000000000..e359b5e65 --- /dev/null +++ b/src/db/simple_db_plugin.c @@ -0,0 +1,345 @@ +/* + * 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_error.h" +#include "db_selection.h" +#include "db_visitor.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"); +} + +G_GNUC_PURE +static const struct directory * +simple_db_lookup_directory(const struct simple_db *db, const char *uri) +{ + assert(db != NULL); + assert(db->root != NULL); + assert(uri != NULL); + + return directory_lookup_directory(db->root, uri); +} + +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); +} + +static struct song * +simple_db_get_song(struct db *_db, const char *uri, GError **error_r) +{ + struct simple_db *db = (struct simple_db *)_db; + + assert(db->root != NULL); + + struct song *song = directory_lookup_song(db->root, uri); + if (song == NULL) + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such song: %s", uri); + + return song; +} + +static bool +simple_db_visit(struct db *_db, const struct db_selection *selection, + const struct db_visitor *visitor, void *ctx, + GError **error_r) +{ + const struct simple_db *db = (const struct simple_db *)_db; + const struct directory *directory = + simple_db_lookup_directory(db, selection->uri); + if (directory == NULL) { + struct song *song; + if (visitor->song != NULL && + (song = simple_db_get_song(_db, selection->uri, NULL)) != NULL) + return visitor->song(song, ctx, error_r); + + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such directory"); + return false; + } + + if (selection->recursive && visitor->directory != NULL && + !visitor->directory(directory, ctx, error_r)) + return false; + + return directory_walk(directory, selection->recursive, + visitor, ctx, error_r); +} + +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, + .get_song = simple_db_get_song, + .visit = simple_db_visit, +}; + +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; +} diff --git a/src/db/simple_db_plugin.h b/src/db/simple_db_plugin.h new file mode 100644 index 000000000..511505846 --- /dev/null +++ b/src/db/simple_db_plugin.h @@ -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 diff --git a/src/dbUtils.c b/src/dbUtils.c index f950d42cc..827d0a0c1 100644 --- a/src/dbUtils.c +++ b/src/dbUtils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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 @@ -20,311 +20,105 @@ #include "config.h" #include "dbUtils.h" #include "locate.h" -#include "directory.h" #include "database.h" -#include "client.h" +#include "db_visitor.h" #include "playlist.h" -#include "song.h" -#include "song_print.h" -#include "tag.h" -#include "strset.h" #include "stored_playlist.h" #include <glib.h> -#include <stdlib.h> - -typedef struct _ListCommandItem { - int8_t tagType; - const struct locate_item_list *criteria; -} ListCommandItem; - -typedef struct _SearchStats { - const struct locate_item_list *criteria; - int numberOfSongs; - unsigned long playTime; -} SearchStats; - -static int -printDirectoryInDirectory(struct directory *directory, void *data) -{ - struct client *client = data; - - if (!directory_is_root(directory)) - client_printf(client, "directory: %s\n", directory_get_path(directory)); - - return 0; -} - -static int -printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data) -{ - struct client *client = data; - song_print_uri(client, song); - return 0; -} - -struct search_data { - struct client *client; - const struct locate_item_list *criteria; -}; - -static int -searchInDirectory(struct song *song, void *_data) -{ - struct search_data *data = _data; - - if (locate_song_search(song, data->criteria)) - song_print_info(data->client, song); - - return 0; -} - -int -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria) -{ - int ret; - struct locate_item_list *new_list - = locate_item_list_casefold(criteria); - struct search_data data; - - data.client = client; - data.criteria = new_list; - - ret = db_walk(name, searchInDirectory, NULL, &data); - - locate_item_list_free(new_list); - - return ret; -} - -static int -findInDirectory(struct song *song, void *_data) +static bool +add_to_queue_song(struct song *song, void *ctx, GError **error_r) { - struct search_data *data = _data; - - if (locate_song_match(song, data->criteria)) - song_print_info(data->client, song); + struct player_control *pc = ctx; - return 0; -} - -int -findSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria) -{ - struct search_data data; - - data.client = client; - data.criteria = criteria; - - return db_walk(name, findInDirectory, NULL, &data); -} - -static void printSearchStats(struct client *client, SearchStats *stats) -{ - client_printf(client, "songs: %i\n", stats->numberOfSongs); - client_printf(client, "playtime: %li\n", stats->playTime); -} - -static int -searchStatsInDirectory(struct song *song, void *data) -{ - SearchStats *stats = data; - - if (locate_song_match(song, stats->criteria)) { - stats->numberOfSongs++; - stats->playTime += song_get_duration(song); + enum playlist_result result = + playlist_append_song(&g_playlist, pc, song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + g_set_error(error_r, playlist_quark(), result, + "Playlist error"); + return false; } - return 0; -} - -int -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria) -{ - SearchStats stats; - int ret; - - stats.criteria = criteria; - stats.numberOfSongs = 0; - stats.playTime = 0; - - ret = db_walk(name, searchStatsInDirectory, NULL, &stats); - if (ret == 0) - printSearchStats(client, &stats); - - return ret; + return true; } -int printAllIn(struct client *client, const char *name) -{ - return db_walk(name, printSongInDirectory, - printDirectoryInDirectory, client); -} +static const struct db_visitor add_to_queue_visitor = { + .song = add_to_queue_song, +}; -static int -directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data) +bool +addAllIn(struct player_control *pc, const char *uri, GError **error_r) { - return playlist_append_song(&g_playlist, song, NULL); + return db_walk(uri, &add_to_queue_visitor, pc, error_r); } struct add_data { const char *path; }; -static int -directoryAddSongToStoredPlaylist(struct song *song, void *_data) +static bool +add_to_spl_song(struct song *song, void *ctx, GError **error_r) { - struct add_data *data = _data; + struct add_data *data = ctx; - if (spl_append_song(data->path, song) != 0) - return -1; - return 0; -} + if (!spl_append_song(data->path, song, error_r)) + return false; -int addAllIn(const char *name) -{ - return db_walk(name, directoryAddSongToPlaylist, NULL, NULL); + return true; } -int addAllInToStoredPlaylist(const char *name, const char *utf8file) +static const struct db_visitor add_to_spl_visitor = { + .song = add_to_spl_song, +}; + +bool +addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, + GError **error_r) { struct add_data data = { - .path = utf8file, + .path = path_utf8, }; - return db_walk(name, directoryAddSongToStoredPlaylist, NULL, &data); + return db_walk(uri_utf8, &add_to_spl_visitor, &data, error_r); } -static int -findAddInDirectory(struct song *song, void *_data) -{ - struct search_data *data = _data; - - if (locate_song_match(song, data->criteria)) - return directoryAddSongToPlaylist(song, data); - - return 0; -} - -int findAddIn(struct client *client, const char *name, - const struct locate_item_list *criteria) -{ - struct search_data data; - - data.client = client; - data.criteria = criteria; - - return db_walk(name, findAddInDirectory, NULL, &data); -} - -static int -directoryPrintSongInfo(struct song *song, void *data) -{ - struct client *client = data; - song_print_info(client, song); - return 0; -} - -int printInfoForAllIn(struct client *client, const char *name) -{ - return db_walk(name, directoryPrintSongInfo, - printDirectoryInDirectory, client); -} - -static ListCommandItem * -newListCommandItem(int tagType, const struct locate_item_list *criteria) -{ - ListCommandItem *item = g_new(ListCommandItem, 1); - - item->tagType = tagType; - item->criteria = criteria; - - return item; -} - -static void freeListCommandItem(ListCommandItem * item) -{ - g_free(item); -} +struct find_add_data { + struct player_control *pc; + const struct locate_item_list *criteria; +}; -static void -visitTag(struct client *client, struct strset *set, - struct song *song, enum tag_type tagType) +static bool +find_add_song(struct song *song, void *ctx, GError **error_r) { - struct tag *tag = song->tag; - bool found = false; + struct find_add_data *data = ctx; - if (tagType == LOCATE_TAG_FILE_TYPE) { - song_print_uri(client, song); - return; - } - - if (!tag) - return; + if (!locate_song_match(song, data->criteria)) + return true; - for (unsigned i = 0; i < tag->num_items; i++) { - if (tag->items[i]->type == tagType) { - strset_add(set, tag->items[i]->value); - found = true; - } + enum playlist_result result = + playlist_append_song(&g_playlist, data->pc, + song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + g_set_error(error_r, playlist_quark(), result, + "Playlist error"); + return false; } - if (!found) - strset_add(set, ""); + return true; } -struct list_tags_data { - struct client *client; - ListCommandItem *item; - struct strset *set; +static const struct db_visitor find_add_visitor = { + .song = find_add_song, }; -static int -listUniqueTagsInDirectory(struct song *song, void *_data) -{ - struct list_tags_data *data = _data; - ListCommandItem *item = data->item; - - if (locate_song_match(song, item->criteria)) - visitTag(data->client, data->set, song, item->tagType); - - return 0; -} - -int listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria) +bool +findAddIn(struct player_control *pc, const char *name, + const struct locate_item_list *criteria, GError **error_r) { - int ret; - ListCommandItem *item = newListCommandItem(type, criteria); - struct list_tags_data data = { - .client = client, - .item = item, - }; - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - data.set = strset_new(); - } - - ret = db_walk(NULL, listUniqueTagsInDirectory, NULL, &data); - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - const char *value; - - strset_rewind(data.set); - - while ((value = strset_next(data.set)) != NULL) - client_printf(client, "%s: %s\n", - tag_item_names[type], - value); - - strset_free(data.set); - } - - freeListCommandItem(item); + struct find_add_data data; + data.pc = pc; + data.criteria = criteria; - return ret; + return db_walk(name, &find_add_visitor, &data, error_r); } diff --git a/src/dbUtils.h b/src/dbUtils.h index bba253154..40594652b 100644 --- a/src/dbUtils.h +++ b/src/dbUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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 @@ -20,37 +20,26 @@ #ifndef MPD_DB_UTILS_H #define MPD_DB_UTILS_H -struct client; -struct locate_item_list; - -int printAllIn(struct client *client, const char *name); - -int addAllIn(const char *name); - -int addAllInToStoredPlaylist(const char *name, const char *utf8file); +#include "gcc.h" -int printInfoForAllIn(struct client *client, const char *name); +#include <glib.h> +#include <stdbool.h> -int -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria); - -int -findSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria); - -int -findAddIn(struct client *client, const char *name, - const struct locate_item_list *criteria); +struct locate_item_list; +struct player_control; -int -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria); +gcc_nonnull(1,2) +bool +addAllIn(struct player_control *pc, const char *uri, GError **error_r); -unsigned long sumSongTimesIn(const char *name); +gcc_nonnull(1,2) +bool +addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, + GError **error_r); -int -listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria); +gcc_nonnull(1,2,3) +bool +findAddIn(struct player_control *pc, const char *name, + const struct locate_item_list *criteria, GError **error_r); #endif diff --git a/src/db_error.h b/src/db_error.h new file mode 100644 index 000000000..d3be582cf --- /dev/null +++ b/src/db_error.h @@ -0,0 +1,45 @@ +/* + * 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_ERROR_H +#define MPD_DB_ERROR_H + +#include <glib.h> + +enum db_error { + /** + * The database is disabled, i.e. none is configured in this + * MPD instance. + */ + DB_DISABLED, + + DB_NOT_FOUND, +}; + +/** + * Quark for GError.domain; the code is an enum #db_error. + */ +G_GNUC_CONST +static inline GQuark +db_quark(void) +{ + return g_quark_from_static_string("db"); +} + +#endif diff --git a/src/db_internal.h b/src/db_internal.h new file mode 100644 index 000000000..a33351524 --- /dev/null +++ b/src/db_internal.h @@ -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 diff --git a/src/db_plugin.h b/src/db_plugin.h new file mode 100644 index 000000000..1c7e14ede --- /dev/null +++ b/src/db_plugin.h @@ -0,0 +1,156 @@ +/* + * 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_selection; +struct db_visitor; + +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); + + /** + * Look up a song (including tag data) in the database. + * + * @param the URI of the song within the music directory + * (UTF-8) + */ + struct song *(*get_song)(struct db *db, const char *uri, + GError **error_r); + + /** + * Visit the selected entities. + */ + bool (*visit)(struct db *db, const struct db_selection *selection, + const struct db_visitor *visitor, void *ctx, + GError **error_r); +}; + +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(plugin->get_song != NULL); + assert(plugin->visit != 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); +} + +static inline struct song * +db_plugin_get_song(struct db *db, const char *uri, GError **error_r) +{ + assert(db != NULL); + assert(db->plugin != NULL); + assert(db->plugin->get_song != NULL); + assert(uri != NULL); + + return db->plugin->get_song(db, uri, error_r); +} + +static inline bool +db_plugin_visit(struct db *db, const struct db_selection *selection, + const struct db_visitor *visitor, void *ctx, + GError **error_r) +{ + assert(db != NULL); + assert(db->plugin != NULL); + assert(selection != NULL); + assert(visitor != NULL); + assert(error_r == NULL || *error_r == NULL); + + return db->plugin->visit(db, selection, visitor, ctx, error_r); +} + +#endif diff --git a/src/db_print.c b/src/db_print.c new file mode 100644 index 000000000..067d1c60f --- /dev/null +++ b/src/db_print.c @@ -0,0 +1,374 @@ +/* + * 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 "db_print.h" +#include "db_selection.h" +#include "db_visitor.h" +#include "locate.h" +#include "directory.h" +#include "database.h" +#include "client.h" +#include "song.h" +#include "song_print.h" +#include "tag.h" +#include "strset.h" + +#include <glib.h> + +typedef struct _ListCommandItem { + int8_t tagType; + const struct locate_item_list *criteria; +} ListCommandItem; + +typedef struct _SearchStats { + const struct locate_item_list *criteria; + int numberOfSongs; + unsigned long playTime; +} SearchStats; + +static bool +print_visitor_directory(const struct directory *directory, void *data, + G_GNUC_UNUSED GError **error_r) +{ + struct client *client = data; + + if (!directory_is_root(directory)) + client_printf(client, "directory: %s\n", directory_get_path(directory)); + + return true; +} + +static bool +print_visitor_song(struct song *song, void *data, + G_GNUC_UNUSED GError **error_r) +{ + struct client *client = data; + song_print_uri(client, song); + return true; +} + +static bool +print_visitor_song_info(struct song *song, void *data, + G_GNUC_UNUSED GError **error_r) +{ + struct client *client = data; + song_print_info(client, song); + return true; +} + +static void +print_playlist_in_directory(struct client *client, + const struct directory *directory, + const char *name_utf8) +{ + if (directory_is_root(directory)) + client_printf(client, "playlist: %s\n", name_utf8); + else + client_printf(client, "playlist: %s/%s\n", + directory_get_path(directory), name_utf8); +} + +static bool +print_visitor_playlist(const struct playlist_metadata *playlist, + const struct directory *directory, void *ctx, + G_GNUC_UNUSED GError **error_r) +{ + struct client *client = ctx; + print_playlist_in_directory(client, directory, playlist->name); + return true; +} + +static bool +print_visitor_playlist_info(const struct playlist_metadata *playlist, + const struct directory *directory, + void *ctx, G_GNUC_UNUSED GError **error_r) +{ + struct client *client = ctx; + print_playlist_in_directory(client, directory, playlist->name); + +#ifndef G_OS_WIN32 + struct tm tm; +#endif + char timestamp[32]; + time_t t = playlist->mtime; + strftime(timestamp, sizeof(timestamp), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", + gmtime(&t) +#else + "%FT%TZ", + gmtime_r(&t, &tm) +#endif + ); + client_printf(client, "Last-Modified: %s\n", timestamp); + + return true; +} + +static const struct db_visitor print_visitor = { + .directory = print_visitor_directory, + .song = print_visitor_song, + .playlist = print_visitor_playlist, +}; + +static const struct db_visitor print_info_visitor = { + .directory = print_visitor_directory, + .song = print_visitor_song_info, + .playlist = print_visitor_playlist_info, +}; + +bool +db_selection_print(struct client *client, const struct db_selection *selection, + bool full, GError **error_r) +{ + return db_visit(selection, full ? &print_info_visitor : &print_visitor, + client, error_r); +} + +struct search_data { + struct client *client; + const struct locate_item_list *criteria; +}; + +static bool +search_visitor_song(struct song *song, void *_data, + G_GNUC_UNUSED GError **error_r) +{ + struct search_data *data = _data; + + if (locate_song_search(song, data->criteria)) + song_print_info(data->client, song); + + return true; +} + +static const struct db_visitor search_visitor = { + .song = search_visitor_song, +}; + +bool +searchForSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r) +{ + struct locate_item_list *new_list + = locate_item_list_casefold(criteria); + struct search_data data; + + data.client = client; + data.criteria = new_list; + + bool success = db_walk(name, &search_visitor, &data, error_r); + + locate_item_list_free(new_list); + + return success; +} + +static bool +find_visitor_song(struct song *song, void *_data, + G_GNUC_UNUSED GError **error_r) +{ + struct search_data *data = _data; + + if (locate_song_match(song, data->criteria)) + song_print_info(data->client, song); + + return true; +} + +static const struct db_visitor find_visitor = { + .song = find_visitor_song, +}; + +bool +findSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r) +{ + struct search_data data; + + data.client = client; + data.criteria = criteria; + + return db_walk(name, &find_visitor, &data, error_r); +} + +static void printSearchStats(struct client *client, SearchStats *stats) +{ + client_printf(client, "songs: %i\n", stats->numberOfSongs); + client_printf(client, "playtime: %li\n", stats->playTime); +} + +static bool +stats_visitor_song(struct song *song, void *data, + G_GNUC_UNUSED GError **error_r) +{ + SearchStats *stats = data; + + if (locate_song_match(song, stats->criteria)) { + stats->numberOfSongs++; + stats->playTime += song_get_duration(song); + } + + return true; +} + +static const struct db_visitor stats_visitor = { + .song = stats_visitor_song, +}; + +bool +searchStatsForSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r) +{ + SearchStats stats; + + stats.criteria = criteria; + stats.numberOfSongs = 0; + stats.playTime = 0; + + if (!db_walk(name, &stats_visitor, &stats, error_r)) + return false; + + printSearchStats(client, &stats); + return true; +} + +bool +printAllIn(struct client *client, const char *uri_utf8, GError **error_r) +{ + struct db_selection selection; + db_selection_init(&selection, uri_utf8, true); + return db_selection_print(client, &selection, false, error_r); +} + +bool +printInfoForAllIn(struct client *client, const char *uri_utf8, + GError **error_r) +{ + struct db_selection selection; + db_selection_init(&selection, uri_utf8, true); + return db_selection_print(client, &selection, true, error_r); +} + +static ListCommandItem * +newListCommandItem(int tagType, const struct locate_item_list *criteria) +{ + ListCommandItem *item = g_new(ListCommandItem, 1); + + item->tagType = tagType; + item->criteria = criteria; + + return item; +} + +static void freeListCommandItem(ListCommandItem * item) +{ + g_free(item); +} + +static void +visitTag(struct client *client, struct strset *set, + struct song *song, enum tag_type tagType) +{ + struct tag *tag = song->tag; + bool found = false; + + if (tagType == LOCATE_TAG_FILE_TYPE) { + song_print_uri(client, song); + return; + } + + if (!tag) + return; + + for (unsigned i = 0; i < tag->num_items; i++) { + if (tag->items[i]->type == tagType) { + strset_add(set, tag->items[i]->value); + found = true; + } + } + + if (!found) + strset_add(set, ""); +} + +struct list_tags_data { + struct client *client; + ListCommandItem *item; + struct strset *set; +}; + +static bool +unique_tags_visitor_song(struct song *song, void *_data, + G_GNUC_UNUSED GError **error_r) +{ + struct list_tags_data *data = _data; + ListCommandItem *item = data->item; + + if (locate_song_match(song, item->criteria)) + visitTag(data->client, data->set, song, item->tagType); + + return true; +} + +static const struct db_visitor unique_tags_visitor = { + .song = unique_tags_visitor_song, +}; + +bool +listAllUniqueTags(struct client *client, int type, + const struct locate_item_list *criteria, + GError **error_r) +{ + ListCommandItem *item = newListCommandItem(type, criteria); + struct list_tags_data data = { + .client = client, + .item = item, + }; + + if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { + data.set = strset_new(); + } + + if (!db_walk("", &unique_tags_visitor, &data, error_r)) { + freeListCommandItem(item); + return false; + } + + if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { + const char *value; + + strset_rewind(data.set); + + while ((value = strset_next(data.set)) != NULL) + client_printf(client, "%s: %s\n", + tag_item_names[type], + value); + + strset_free(data.set); + } + + freeListCommandItem(item); + + return true; +} diff --git a/src/db_print.h b/src/db_print.h new file mode 100644 index 000000000..1b957da18 --- /dev/null +++ b/src/db_print.h @@ -0,0 +1,71 @@ +/* + * 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_PRINT_H +#define MPD_DB_PRINT_H + +#include "gcc.h" + +#include <glib.h> +#include <stdbool.h> + +struct client; +struct locate_item_list; +struct db_selection; +struct db_visitor; + +gcc_nonnull(1,2) +bool +db_selection_print(struct client *client, const struct db_selection *selection, + bool full, GError **error_r); + +gcc_nonnull(1,2) +bool +printAllIn(struct client *client, const char *uri_utf8, GError **error_r); + +gcc_nonnull(1,2) +bool +printInfoForAllIn(struct client *client, const char *uri_utf8, + GError **error_r); + +gcc_nonnull(1,2,3) +bool +searchForSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r); + +gcc_nonnull(1,2,3) +bool +findSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r); + +gcc_nonnull(1,2,3) +bool +searchStatsForSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r); + +gcc_nonnull(1,3) +bool +listAllUniqueTags(struct client *client, int type, + const struct locate_item_list *criteria, + GError **error_r); + +#endif diff --git a/src/db_save.c b/src/db_save.c new file mode 100644 index 000000000..00967f4f2 --- /dev/null +++ b/src/db_save.c @@ -0,0 +1,176 @@ +/* + * 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 "db_save.h" +#include "directory.h" +#include "directory_save.h" +#include "song.h" +#include "path.h" +#include "text_file.h" +#include "tag.h" +#include "tag_internal.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "database" + +#define DIRECTORY_INFO_BEGIN "info_begin" +#define DIRECTORY_INFO_END "info_end" +#define DB_FORMAT_PREFIX "format: " +#define DIRECTORY_MPD_VERSION "mpd_version: " +#define DIRECTORY_FS_CHARSET "fs_charset: " +#define DB_TAG_PREFIX "tag: " + +enum { + DB_FORMAT = 1, +}; + +G_GNUC_CONST +static GQuark +db_quark(void) +{ + return g_quark_from_static_string("database"); +} + +void +db_save_internal(FILE *fp, const struct directory *music_root) +{ + assert(music_root != NULL); + + fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); + fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT); + fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); + fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset()); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (!ignore_tag_items[i]) + fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]); + + fprintf(fp, "%s\n", DIRECTORY_INFO_END); + + directory_save(fp, music_root); +} + +bool +db_load_internal(FILE *fp, struct directory *music_root, GError **error) +{ + GString *buffer = g_string_sized_new(1024); + char *line; + int format = 0; + bool found_charset = false, found_version = false; + bool success; + bool tags[TAG_NUM_OF_ITEM_TYPES]; + + assert(music_root != NULL); + + /* get initial info */ + line = read_text_line(fp, buffer); + if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { + g_set_error(error, db_quark(), 0, "Database corrupted"); + g_string_free(buffer, true); + return false; + } + + memset(tags, false, sizeof(tags)); + + while ((line = read_text_line(fp, buffer)) != NULL && + strcmp(line, DIRECTORY_INFO_END) != 0) { + if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { + format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); + } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) { + if (found_version) { + g_set_error(error, db_quark(), 0, + "Duplicate version line"); + g_string_free(buffer, true); + return false; + } + + found_version = true; + } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) { + const char *new_charset, *old_charset; + + if (found_charset) { + g_set_error(error, db_quark(), 0, + "Duplicate charset line"); + g_string_free(buffer, true); + return false; + } + + found_charset = true; + + new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; + old_charset = path_get_fs_charset(); + if (old_charset != NULL + && strcmp(new_charset, old_charset)) { + g_set_error(error, db_quark(), 0, + "Existing database has charset " + "\"%s\" instead of \"%s\"; " + "discarding database file", + new_charset, old_charset); + g_string_free(buffer, true); + return false; + } + } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { + const char *name = line + sizeof(DB_TAG_PREFIX) - 1; + enum tag_type tag = tag_name_parse(name); + if (tag == TAG_NUM_OF_ITEM_TYPES) { + g_set_error(error, db_quark(), 0, + "Unrecognized tag '%s', " + "discarding database file", + name); + return false; + } + + tags[tag] = true; + } else { + g_set_error(error, db_quark(), 0, + "Malformed line: %s", line); + g_string_free(buffer, true); + return false; + } + } + + if (format != DB_FORMAT) { + g_set_error(error, db_quark(), 0, + "Database format mismatch, " + "discarding database file"); + return false; + } + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + if (!ignore_tag_items[i] && !tags[i]) { + g_set_error(error, db_quark(), 0, + "Tag list mismatch, " + "discarding database file"); + return false; + } + } + + g_debug("reading DB"); + + success = directory_load(fp, music_root, buffer, error); + g_string_free(buffer, true); + + return success; +} diff --git a/src/db_save.h b/src/db_save.h new file mode 100644 index 000000000..e760ec881 --- /dev/null +++ b/src/db_save.h @@ -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_SAVE_H +#define MPD_DB_SAVE_H + +#include <glib.h> +#include <stdbool.h> +#include <stdio.h> + +struct directory; + +void +db_save_internal(FILE *file, const struct directory *root); + +bool +db_load_internal(FILE *file, struct directory *root, GError **error); + +#endif diff --git a/src/db_selection.h b/src/db_selection.h new file mode 100644 index 000000000..2cebb4907 --- /dev/null +++ b/src/db_selection.h @@ -0,0 +1,56 @@ +/* + * 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_SELECTION_H +#define MPD_DB_SELECTION_H + +#include "gcc.h" + +#include <assert.h> + +struct directory; +struct song; + +struct db_selection { + /** + * The base URI of the search (UTF-8). Must not begin or end + * with a slash. NULL or an empty string searches the whole + * database. + */ + const char *uri; + + /** + * Recursively search all sub directories? + */ + bool recursive; +}; + +gcc_nonnull(1,2) +static inline void +db_selection_init(struct db_selection *selection, + const char *uri, bool recursive) +{ + assert(selection != NULL); + assert(uri != NULL); + + selection->uri = uri; + selection->recursive = recursive; +} + +#endif diff --git a/src/db_visitor.h b/src/db_visitor.h new file mode 100644 index 000000000..6b90c1868 --- /dev/null +++ b/src/db_visitor.h @@ -0,0 +1,54 @@ +/* + * 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_VISITOR_H +#define MPD_DB_VISITOR_H + +struct directory; +struct song; +struct playlist_metadata; + +struct db_visitor { + /** + * Visit a directory. Optional method. + * + * @return true to continue the operation, false on error (set error_r) + */ + bool (*directory)(const struct directory *directory, void *ctx, + GError **error_r); + + /** + * Visit a song. Optional method. + * + * @return true to continue the operation, false on error (set error_r) + */ + bool (*song)(struct song *song, void *ctx, GError **error_r); + + /** + * Visit a playlist. Optional method. + * + * @param directory the directory the playlist resides in + * @return true to continue the operation, false on error (set error_r) + */ + bool (*playlist)(const struct playlist_metadata *playlist, + const struct directory *directory, void *ctx, + GError **error_r); +}; + +#endif |