diff options
Diffstat (limited to 'src')
114 files changed, 3185 insertions, 2136 deletions
diff --git a/src/CommandError.cxx b/src/CommandError.cxx new file mode 100644 index 000000000..96b0095ae --- /dev/null +++ b/src/CommandError.cxx @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2012 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 "CommandError.h" +#include "db_error.h" + +extern "C" { +#include "protocol/result.h" +} + +#include <assert.h> +#include <errno.h> + +enum command_return +print_playlist_result(struct client *client, enum playlist_result result) +{ + switch (result) { + case PLAYLIST_RESULT_SUCCESS: + return COMMAND_RETURN_OK; + + case PLAYLIST_RESULT_ERRNO: + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(errno)); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DENIED: + command_error(client, ACK_ERROR_PERMISSION, "Access denied"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_SONG: + command_error(client, ACK_ERROR_NO_EXIST, "No such song"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_LIST: + command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_LIST_EXISTS: + command_error(client, ACK_ERROR_EXIST, + "Playlist already exists"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_NAME: + command_error(client, ACK_ERROR_ARG, + "playlist name is invalid: " + "playlist names may not contain slashes," + " newlines or carriage returns"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_RANGE: + command_error(client, ACK_ERROR_ARG, "Bad song index"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NOT_PLAYING: + command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_TOO_LARGE: + command_error(client, ACK_ERROR_PLAYLIST_MAX, + "playlist is at the max size"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DISABLED: + command_error(client, ACK_ERROR_UNKNOWN, + "stored playlist support is disabled"); + return COMMAND_RETURN_ERROR; + } + + assert(0); + return COMMAND_RETURN_ERROR; +} + +/** + * Send the GError to the client and free the GError. + */ +enum command_return +print_error(struct client *client, GError *error) +{ + assert(client != NULL); + assert(error != NULL); + + g_warning("%s", error->message); + + if (error->domain == playlist_quark()) { + enum playlist_result result = (playlist_result)error->code; + g_error_free(error); + return print_playlist_result(client, result); + } else if (error->domain == ack_quark()) { + command_error(client, (ack)error->code, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } else if (error->domain == db_quark()) { + switch ((enum db_error)error->code) { + case DB_DISABLED: + command_error(client, ACK_ERROR_NO_EXIST, "%s", + error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + + case DB_NOT_FOUND: + g_error_free(error); + command_error(client, ACK_ERROR_NO_EXIST, "Not found"); + return COMMAND_RETURN_ERROR; + } + } else if (error->domain == g_file_error_quark()) { + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(error->code)); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + + g_error_free(error); + command_error(client, ACK_ERROR_UNKNOWN, "error"); + return COMMAND_RETURN_ERROR; +} diff --git a/src/CommandError.h b/src/CommandError.h new file mode 100644 index 000000000..f43afdb3d --- /dev/null +++ b/src/CommandError.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2012 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_COMMAND_ERROR_H +#define MPD_COMMAND_ERROR_H + +#include "command.h" +#include "playlist_error.h" + +#include <glib.h> + +G_BEGIN_DECLS + +enum command_return +print_playlist_result(struct client *client, enum playlist_result result); + +/** + * Send the GError to the client and free the GError. + */ +enum command_return +print_error(struct client *client, GError *error); + +G_END_DECLS + +#endif diff --git a/src/DatabaseCommands.cxx b/src/DatabaseCommands.cxx new file mode 100644 index 000000000..288980468 --- /dev/null +++ b/src/DatabaseCommands.cxx @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2003-2012 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 "DatabaseCommands.hxx" +#include "DatabaseQueue.hxx" +#include "DatabasePlaylist.hxx" +#include "DatabasePrint.hxx" +#include "DatabaseSelection.hxx" +#include "CommandError.h" +#include "client_internal.h" +#include "tag.h" +#include "uri.h" + +extern "C" { +#include "locate.h" +#include "protocol/result.h" +} + +#include <assert.h> +#include <string.h> + +enum command_return +handle_lsinfo2(struct client *client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + const DatabaseSelection selection(uri, false); + + GError *error = NULL; + if (!db_selection_print(client, selection, true, &error)) + return print_error(client, error); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_match(struct client *client, int argc, char *argv[], bool fold_case) +{ + struct locate_item_list *list = + locate_item_list_parse(argv + 1, argc - 1, fold_case); + + if (list == NULL) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + const DatabaseSelection selection("", true, list); + + GError *error = NULL; + enum command_return ret = + db_selection_print(client, selection, true, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); + + locate_item_list_free(list); + + return ret; +} + +enum command_return +handle_find(struct client *client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, false); +} + +enum command_return +handle_search(struct client *client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, true); +} + +static enum command_return +handle_match_add(struct client *client, int argc, char *argv[], bool fold_case) +{ + struct locate_item_list *list = + locate_item_list_parse(argv + 1, argc - 1, fold_case); + if (list == NULL) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + enum command_return ret = + findAddIn(client->player_control, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); + + locate_item_list_free(list); + + return ret; +} + +enum command_return +handle_findadd(struct client *client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, false); +} + +enum command_return +handle_searchadd(struct client *client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, true); +} + +enum command_return +handle_searchaddpl(struct client *client, int argc, char *argv[]) +{ + const char *playlist = argv[1]; + + struct locate_item_list *list = + locate_item_list_parse(argv + 2, argc - 2, true); + + if (list == NULL) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + enum command_return ret = + search_add_to_playlist("", playlist, list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); + + locate_item_list_free(list); + + return ret; +} + +enum command_return +handle_count(struct client *client, int argc, char *argv[]) +{ + struct locate_item_list *list = + locate_item_list_parse(argv + 1, argc - 1, false); + + if (list == NULL) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + enum command_return ret = + searchStatsForSongsIn(client, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); + + locate_item_list_free(list); + + return ret; +} + +enum command_return +handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + GError *error = NULL; + return printAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_list(struct client *client, int argc, char *argv[]) +{ + struct locate_item_list *conditionals; + unsigned tagType = locate_parse_type(argv[1]); + + if (tagType == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); + return COMMAND_RETURN_ERROR; + } + + if (tagType == LOCATE_TAG_ANY_TYPE) { + command_error(client, ACK_ERROR_ARG, + "\"any\" is not a valid return tag type"); + return COMMAND_RETURN_ERROR; + } + + /* for compatibility with < 0.12.0 */ + if (argc == 3) { + if (tagType != TAG_ALBUM) { + command_error(client, ACK_ERROR_ARG, + "should be \"%s\" for 3 arguments", + tag_item_names[TAG_ALBUM]); + return COMMAND_RETURN_ERROR; + } + + conditionals = + locate_item_list_new_single((unsigned)TAG_ARTIST, + argv[2]); + } else if (argc > 2) { + conditionals = + locate_item_list_parse(argv + 2, argc - 2, false); + if (conditionals == NULL) { + command_error(client, ACK_ERROR_ARG, + "not able to parse args"); + return COMMAND_RETURN_ERROR; + } + } else + conditionals = nullptr; + + GError *error = NULL; + enum command_return ret = + listAllUniqueTags(client, tagType, conditionals, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); + + if (conditionals != nullptr) + locate_item_list_free(conditionals); + + return ret; +} + +enum command_return +handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + GError *error = NULL; + return printInfoForAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} diff --git a/src/DatabaseCommands.hxx b/src/DatabaseCommands.hxx new file mode 100644 index 000000000..24ff71329 --- /dev/null +++ b/src/DatabaseCommands.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2012 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_DATABASE_COMMANDS_HXX +#define MPD_DATABASE_COMMANDS_HXX + +#include "command.h" + +G_BEGIN_DECLS + +enum command_return +handle_lsinfo2(struct client *client, int argc, char *argv[]); + +enum command_return +handle_find(struct client *client, int argc, char *argv[]); + +enum command_return +handle_findadd(struct client *client, int argc, char *argv[]); + +enum command_return +handle_search(struct client *client, int argc, char *argv[]); + +enum command_return +handle_searchadd(struct client *client, int argc, char *argv[]); + +enum command_return +handle_searchaddpl(struct client *client, int argc, char *argv[]); + +enum command_return +handle_count(struct client *client, int argc, char *argv[]); + +enum command_return +handle_listall(struct client *client, int argc, char *argv[]); + +enum command_return +handle_list(struct client *client, int argc, char *argv[]); + +enum command_return +handle_listallinfo(struct client *client, int argc, char *argv[]); + +G_END_DECLS + +#endif diff --git a/src/database.c b/src/DatabaseGlue.cxx index 8c903bb45..42812973d 100644 --- a/src/database.c +++ b/src/DatabaseGlue.cxx @@ -18,17 +18,22 @@ */ #include "config.h" +#include "DatabaseGlue.hxx" +#include "DatabaseRegistry.hxx" + +extern "C" { #include "database.h" #include "db_error.h" #include "db_save.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "db_plugin.h" -#include "db/simple_db_plugin.h" -#include "directory.h" #include "stats.h" #include "conf.h" #include "glib_compat.h" +} + +#include "directory.h" + +#include "DatabasePlugin.hxx" +#include "db/SimpleDatabasePlugin.hxx" #include <glib.h> @@ -42,25 +47,25 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "database" -static struct db *db; +static Database *db; static bool db_is_open; bool -db_init(const struct config_param *path, GError **error_r) +db_init(const struct config_param *param, GError **error_r) { assert(db == NULL); assert(!db_is_open); - 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); + const char *plugin_name = + config_get_block_string(param, "plugin", "simple"); + const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); + if (plugin == NULL) { + g_set_error(error_r, db_quark(), 0, + "No such database plugin: %s", plugin_name); + return false; + } + db = plugin->create(param, error_r); return db != NULL; } @@ -68,18 +73,35 @@ void db_finish(void) { if (db_is_open) - db_plugin_close(db); + db->Close(); if (db != NULL) - db_plugin_free(db); + delete db; +} + +const Database * +GetDatabase() +{ + assert(db == NULL || db_is_open); + + return db; +} + +bool +db_is_simple(void) +{ + assert(db == NULL || db_is_open); + + return dynamic_cast<SimpleDatabase *>(db) != nullptr; } struct directory * db_get_root(void) { assert(db != NULL); + assert(db_is_simple()); - return simple_db_get_root(db); + return ((SimpleDatabase *)db)->GetRoot(); } struct directory * @@ -107,32 +129,7 @@ db_get_song(const char *file) if (db == NULL) return NULL; - return db_plugin_get_song(db, file, NULL); -} - -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - if (db == NULL) { - g_set_error_literal(error_r, db_quark(), DB_DISABLED, - "No database"); - return false; - } - - return db_plugin_visit(db, selection, visitor, ctx, error_r); -} - -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - struct db_selection selection; - db_selection_init(&selection, uri, true); - - return db_visit(&selection, visitor, ctx, error_r); + return db->GetSong(file, NULL); } bool @@ -140,8 +137,9 @@ db_save(GError **error_r) { assert(db != NULL); assert(db_is_open); + assert(db_is_simple()); - return simple_db_save(db, error_r); + return ((SimpleDatabase *)db)->Save(error_r); } bool @@ -150,7 +148,7 @@ db_load(GError **error) assert(db != NULL); assert(!db_is_open); - if (!db_plugin_open(db, error)) + if (!db->Open(error)) return false; db_is_open = true; @@ -165,6 +163,7 @@ db_get_mtime(void) { assert(db != NULL); assert(db_is_open); + assert(db_is_simple()); - return simple_db_get_mtime(db); + return ((SimpleDatabase *)db)->GetLastModified(); } diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx new file mode 100644 index 000000000..79eea1355 --- /dev/null +++ b/src/DatabaseGlue.hxx @@ -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_DATABASE_GLUE_HXX +#define MPD_DATABASE_GLUE_HXX + +#include "gcc.h" + +class Database; + +/** + * Returns the global #Database instance. May return NULL if this MPD + * configuration has no database (no music_directory was configured). + */ +gcc_pure +const Database * +GetDatabase(); + +#endif diff --git a/src/DatabaseHelpers.cxx b/src/DatabaseHelpers.cxx new file mode 100644 index 000000000..dc31a4bc2 --- /dev/null +++ b/src/DatabaseHelpers.cxx @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2012 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 "DatabaseHelpers.hxx" +#include "DatabasePlugin.hxx" +#include "song.h" +#include "tag.h" + +#include <functional> +#include <set> + +#include <string.h> + +struct StringLess { + gcc_pure + bool operator()(const char *a, const char *b) const { + return strcmp(a, b) < 0; + } +}; + +typedef std::set<const char *, StringLess> StringSet; + +static bool +CollectTags(StringSet &set, enum tag_type tag_type, song &song) +{ + struct tag *tag = song.tag; + if (tag == nullptr) + return true; + + bool found = false; + for (unsigned i = 0; i < tag->num_items; ++i) { + if (tag->items[i]->type == tag_type) { + set.insert(tag->items[i]->value); + found = true; + } + } + + if (!found) + set.insert(""); + + return true; +} + +bool +VisitUniqueTags(const Database &db, const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) +{ + StringSet set; + + using namespace std::placeholders; + const auto f = std::bind(CollectTags, std::ref(set), tag_type, _1); + if (!db.Visit(selection, f, error_r)) + return false; + + for (auto value : set) + if (!visit_string(value, error_r)) + return false; + + return true; +} + +static void +StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums, + const struct tag &tag) +{ + if (tag.time > 0) + stats.total_duration += tag.time; + + for (unsigned i = 0; i < tag.num_items; ++i) { + const struct tag_item &item = *tag.items[i]; + + switch (item.type) { + case TAG_ARTIST: + artists.insert(item.value); + break; + + case TAG_ALBUM: + albums.insert(item.value); + break; + + default: + break; + } + } +} + +static bool +StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums, + song &song) +{ + ++stats.song_count; + + if (song.tag != nullptr) + StatsVisitTag(stats, artists, albums, *song.tag); + + return true; +} + +bool +GetStats(const Database &db, const DatabaseSelection &selection, + DatabaseStats &stats, GError **error_r) +{ + stats.Clear(); + + StringSet artists, albums; + using namespace std::placeholders; + const auto f = std::bind(StatsVisitSong, + std::ref(stats), std::ref(artists), + std::ref(albums), _1); + if (!db.Visit(selection, f, error_r)) + return false; + + stats.artist_count = artists.size(); + stats.album_count = albums.size(); + return true; +} diff --git a/src/DatabaseHelpers.hxx b/src/DatabaseHelpers.hxx new file mode 100644 index 000000000..cfcc94ac7 --- /dev/null +++ b/src/DatabaseHelpers.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2012 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_MEMORY_DATABASE_PLUGIN_HXX +#define MPD_MEMORY_DATABASE_PLUGIN_HXX + +#include "DatabaseVisitor.hxx" +#include "tag.h" +#include "gcc.h" + +class Database; +struct DatabaseSelection; +struct DatabaseStats; + +bool +VisitUniqueTags(const Database &db, const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r); + +bool +GetStats(const Database &db, const DatabaseSelection &selection, + DatabaseStats &stats, GError **error_r); + +#endif diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx new file mode 100644 index 000000000..c6ad9c543 --- /dev/null +++ b/src/DatabasePlaylist.cxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2012 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 "DatabasePlaylist.hxx" +#include "DatabaseSelection.hxx" + +extern "C" { +#include "dbUtils.h" +#include "stored_playlist.h" +} + +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +AddSong(const char *playlist_path_utf8, + song &song, GError **error_r) +{ + return spl_append_song(playlist_path_utf8, &song, error_r); +} + +bool +search_add_to_playlist(const char *uri, const char *playlist_path_utf8, + const struct locate_item_list *criteria, + GError **error_r) +{ + const DatabaseSelection selection(uri, true, criteria); + + using namespace std::placeholders; + const auto f = std::bind(AddSong, playlist_path_utf8, _1, _2); + return GetDatabase()->Visit(selection, f, error_r); +} + +bool +addAllInToStoredPlaylist(const char *uri_utf8, const char *playlist_path_utf8, + GError **error_r) +{ + return search_add_to_playlist(uri_utf8, playlist_path_utf8, nullptr, + error_r); +} diff --git a/src/db/simple_db_plugin.h b/src/DatabasePlaylist.hxx index 511505846..269585c32 100644 --- a/src/db/simple_db_plugin.h +++ b/src/DatabasePlaylist.hxx @@ -17,26 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SIMPLE_DB_PLUGIN_H -#define MPD_SIMPLE_DB_PLUGIN_H +#ifndef MPD_DATABASE_PLAYLIST_HXX +#define MPD_DATABASE_PLAYLIST_HXX -#include <glib.h> -#include <stdbool.h> -#include <time.h> +#include "gcc.h" +#include "gerror.h" -extern const struct db_plugin simple_db_plugin; - -struct db; - -G_GNUC_PURE -struct directory * -simple_db_get_root(struct db *db); +struct locate_item_list; +gcc_nonnull(1,2) bool -simple_db_save(struct db *db, GError **error_r); - -G_GNUC_PURE -time_t -simple_db_get_mtime(const struct db *db); +search_add_to_playlist(const char *uri, const char *path_utf8, + const struct locate_item_list *criteria, + GError **error_r); #endif diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx new file mode 100644 index 000000000..37df7f654 --- /dev/null +++ b/src/DatabasePlugin.hxx @@ -0,0 +1,141 @@ +/* + * 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_DATABASE_PLUGIN_HXX +#define MPD_DATABASE_PLUGIN_HXX + +#include "DatabaseVisitor.hxx" +#include "gcc.h" + +extern "C" { +#include "tag.h" +} + +struct config_param; +struct DatabaseSelection; +struct db_visitor; + +struct DatabaseStats { + /** + * Number of songs. + */ + unsigned song_count; + + /** + * Total duration of all songs (in seconds). + */ + unsigned long total_duration; + + /** + * Number of distinct artist names. + */ + unsigned artist_count; + + /** + * Number of distinct album names. + */ + unsigned album_count; + + void Clear() { + song_count = 0; + total_duration = 0; + artist_count = album_count = 0; + } +}; + +class Database { +public: + /** + * Free instance data. + */ + virtual ~Database() {} + + /** + * Open the database. Read it into memory if applicable. + */ + virtual bool Open(gcc_unused GError **error_r) { + return true; + } + + /** + * Close the database, free allocated memory. + */ + virtual void Close() {} + + /** + * Look up a song (including tag data) in the database. + * + * @param uri_utf8 the URI of the song within the music + * directory (UTF-8) + */ + virtual struct song *GetSong(const char *uri_utf8, + GError **error_r) const = 0; + + /** + * Visit the selected entities. + */ + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const = 0; + + bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + GError **error_r) const { + return Visit(selection, visit_directory, visit_song, + VisitPlaylist(), error_r); + } + + bool Visit(const DatabaseSelection &selection, VisitSong visit_song, + GError **error_r) const { + return Visit(selection, VisitDirectory(), visit_song, error_r); + } + + /** + * Visit all unique tag values. + */ + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const = 0; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + GError **error_r) const = 0; +}; + +struct DatabasePlugin { + const char *name; + + /** + * Allocates and configures a database. + */ + Database *(*create)(const struct config_param *param, + GError **error_r); +}; + +#endif diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx new file mode 100644 index 000000000..b58619a95 --- /dev/null +++ b/src/DatabasePrint.cxx @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2003-2012 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 "DatabasePrint.hxx" +#include "DatabaseSelection.hxx" + +extern "C" { +#include "locate.h" +#include "database.h" +#include "client.h" +#include "song.h" +#include "song_print.h" +#include "time_print.h" +#include "playlist_vector.h" +#include "tag.h" +} + +#include "directory.h" + +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +PrintDirectory(struct client *client, const directory &directory) +{ + if (!directory_is_root(&directory)) + client_printf(client, "directory: %s\n", + directory_get_path(&directory)); + + return true; +} + +static void +print_playlist_in_directory(struct client *client, + const 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 +PrintSongBrief(struct client *client, song &song) +{ + assert(song.parent != NULL); + + song_print_uri(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintSongFull(struct client *client, song &song) +{ + assert(song.parent != NULL); + + song_print_info(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintPlaylistBrief(struct client *client, + const playlist_metadata &playlist, + const directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name); + return true; +} + +static bool +PrintPlaylistFull(struct client *client, + const playlist_metadata &playlist, + const directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name); + + if (playlist.mtime > 0) + time_print(client, "Last-Modified", playlist.mtime); + + return true; +} + +bool +db_selection_print(struct client *client, const DatabaseSelection &selection, + bool full, GError **error_r) +{ + using namespace std::placeholders; + const auto d = selection.match == nullptr + ? std::bind(PrintDirectory, client, _1) + : VisitDirectory(); + const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, + client, _1); + const auto p = selection.match == nullptr + ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, + client, _1, _2) + : VisitPlaylist(); + + return GetDatabase()->Visit(selection, d, s, p, error_r); +} + +struct SearchStats { + int numberOfSongs; + unsigned long playTime; +}; + +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(SearchStats &stats, song &song) +{ + stats.numberOfSongs++; + stats.playTime += song_get_duration(&song); + + return true; +} + +bool +searchStatsForSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r) +{ + const DatabaseSelection selection(name, true, criteria); + + SearchStats stats; + stats.numberOfSongs = 0; + stats.playTime = 0; + + using namespace std::placeholders; + const auto f = std::bind(stats_visitor_song, std::ref(stats), + _1); + if (!GetDatabase()->Visit(selection, f, error_r)) + return false; + + printSearchStats(client, &stats); + return true; +} + +bool +printAllIn(struct client *client, const char *uri_utf8, GError **error_r) +{ + const DatabaseSelection 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) +{ + const DatabaseSelection selection(uri_utf8, true); + return db_selection_print(client, selection, true, error_r); +} + +static bool +PrintSongURIVisitor(struct client *client, song &song) +{ + song_print_uri(client, &song); + + return true; +} + +static bool +PrintUniqueTag(struct client *client, enum tag_type tag_type, + const char *value) +{ + client_printf(client, "%s: %s\n", tag_item_names[tag_type], value); + return true; +} + +bool +listAllUniqueTags(struct client *client, int type, + const struct locate_item_list *criteria, + GError **error_r) +{ + const DatabaseSelection selection("", true, criteria); + + if (type == LOCATE_TAG_FILE_TYPE) { + using namespace std::placeholders; + const auto f = std::bind(PrintSongURIVisitor, client, _1); + return GetDatabase()->Visit(selection, f, error_r); + } else { + using namespace std::placeholders; + const auto f = std::bind(PrintUniqueTag, client, + (enum tag_type)type, _1); + return GetDatabase()->VisitUniqueTags(selection, + (enum tag_type)type, + f, error_r); + } +} diff --git a/src/db_print.h b/src/DatabasePrint.hxx index 1b957da18..e9a19cd52 100644 --- a/src/db_print.h +++ b/src/DatabasePrint.hxx @@ -21,18 +21,18 @@ #define MPD_DB_PRINT_H #include "gcc.h" +#include "gerror.h" -#include <glib.h> #include <stdbool.h> struct client; struct locate_item_list; -struct db_selection; +struct DatabaseSelection; struct db_visitor; -gcc_nonnull(1,2) +gcc_nonnull(1) bool -db_selection_print(struct client *client, const struct db_selection *selection, +db_selection_print(struct client *client, const DatabaseSelection &selection, bool full, GError **error_r); gcc_nonnull(1,2) @@ -44,25 +44,13 @@ 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) +gcc_nonnull(1,2) bool searchStatsForSongsIn(struct client *client, const char *name, const struct locate_item_list *criteria, GError **error_r); -gcc_nonnull(1,3) +gcc_nonnull(1) bool listAllUniqueTags(struct client *client, int type, const struct locate_item_list *criteria, diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx new file mode 100644 index 000000000..26ac2c6fd --- /dev/null +++ b/src/DatabaseQueue.cxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2012 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 "DatabaseQueue.hxx" +#include "DatabaseSelection.hxx" + +extern "C" { +#include "dbUtils.h" +#include "playlist.h" +} + +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +AddToQueue(struct player_control *pc, song &song, GError **error_r) +{ + 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 true; +} + +bool +findAddIn(struct player_control *pc, const char *uri, + const struct locate_item_list *criteria, GError **error_r) +{ + const DatabaseSelection selection(uri, true, criteria); + + using namespace std::placeholders; + const auto f = std::bind(AddToQueue, pc, _1, _2); + return GetDatabase()->Visit(selection, f, error_r); +} + +bool +addAllIn(struct player_control *pc, const char *uri, GError **error_r) +{ + return findAddIn(pc, uri, nullptr, error_r); +} diff --git a/src/DatabaseQueue.hxx b/src/DatabaseQueue.hxx new file mode 100644 index 000000000..cf8c3ba5d --- /dev/null +++ b/src/DatabaseQueue.hxx @@ -0,0 +1,34 @@ +/* + * 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_DATABASE_QUEUE_HXX +#define MPD_DATABASE_QUEUE_HXX + +#include "gcc.h" +#include "gerror.h" + +struct locate_item_list; +struct player_control; + +gcc_nonnull(1,2) +bool +findAddIn(struct player_control *pc, const char *name, + const struct locate_item_list *criteria, GError **error_r); + +#endif diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx new file mode 100644 index 000000000..cf01decdd --- /dev/null +++ b/src/DatabaseRegistry.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2012 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 "DatabaseRegistry.hxx" +#include "db/SimpleDatabasePlugin.hxx" +#include "db/ProxyDatabasePlugin.hxx" + +#include <string.h> + +const DatabasePlugin *const database_plugins[] = { + &simple_db_plugin, +#ifdef HAVE_LIBMPDCLIENT + &proxy_db_plugin, +#endif + NULL +}; + +const DatabasePlugin * +GetDatabasePluginByName(const char *name) +{ + for (auto i = database_plugins; *i != nullptr; ++i) + if (strcmp((*i)->name, name) == 0) + return *i; + + return nullptr; +} diff --git a/src/DatabaseRegistry.hxx b/src/DatabaseRegistry.hxx new file mode 100644 index 000000000..4be581573 --- /dev/null +++ b/src/DatabaseRegistry.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2012 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_DATABASE_REGISTRY_HXX +#define MPD_DATABASE_REGISTRY_HXX + +#include "gcc.h" + +struct DatabasePlugin; + +/** + * NULL terminated list of all database plugins which were enabled at + * compile time. + */ +extern const DatabasePlugin *const database_plugins[]; + +gcc_pure +const DatabasePlugin * +GetDatabasePluginByName(const char *name); + +#endif diff --git a/src/db_selection.h b/src/DatabaseSelection.hxx index 2cebb4907..35e2c27c9 100644 --- a/src/db_selection.h +++ b/src/DatabaseSelection.hxx @@ -17,17 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_SELECTION_H -#define MPD_DB_SELECTION_H +#ifndef MPD_DATABASE_SELECTION_HXX +#define MPD_DATABASE_SELECTION_HXX #include "gcc.h" #include <assert.h> +#include <stddef.h> -struct directory; -struct song; +struct locate_item_list; -struct db_selection { +struct DatabaseSelection { /** * The base URI of the search (UTF-8). Must not begin or end * with a slash. NULL or an empty string searches the whole @@ -39,18 +39,14 @@ struct db_selection { * 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; -} + const locate_item_list *match; + + DatabaseSelection(const char *_uri, bool _recursive, + const locate_item_list *_match=nullptr) + :uri(_uri), recursive(_recursive), match(_match) { + assert(uri != NULL); + } +}; #endif diff --git a/src/db_visitor.h b/src/DatabaseVisitor.hxx index 6b90c1868..10f907cef 100644 --- a/src/db_visitor.h +++ b/src/DatabaseVisitor.hxx @@ -17,38 +17,22 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_VISITOR_H -#define MPD_DB_VISITOR_H +#ifndef MPD_DATABASE_VISITOR_HXX +#define MPD_DATABASE_VISITOR_HXX + +#include "gerror.h" + +#include <functional> 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); +typedef std::function<bool(const directory &, GError **)> VisitDirectory; +typedef std::function<bool(struct song &, GError **)> VisitSong; +typedef std::function<bool(const playlist_metadata &, const directory &, + GError **)> VisitPlaylist; - /** - * 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); -}; +typedef std::function<bool(const char *, GError **)> VisitString; #endif diff --git a/src/directory.c b/src/Directory.cxx index e886698d6..e366906e8 100644 --- a/src/directory.c +++ b/src/Directory.cxx @@ -19,13 +19,16 @@ #include "config.h" #include "directory.h" + +extern "C" { #include "song.h" #include "song_sort.h" #include "playlist_vector.h" #include "path.h" #include "util/list_sort.h" -#include "db_visitor.h" #include "db_lock.h" +#include "locate.h" +} #include <glib.h> @@ -33,23 +36,34 @@ #include <string.h> #include <stdlib.h> -struct directory * -directory_new(const char *path, struct directory *parent) +static directory * +directory_allocate(const char *path) { - struct directory *directory; - size_t pathlen = strlen(path); - assert(path != NULL); - assert((*path == 0) == (parent == NULL)); - directory = g_malloc0(sizeof(*directory) - - sizeof(directory->path) + pathlen + 1); + const size_t path_size = strlen(path) + 1; + directory *directory = + (struct directory *)g_malloc0(sizeof(*directory) + - sizeof(directory->path) + + path_size); INIT_LIST_HEAD(&directory->children); INIT_LIST_HEAD(&directory->songs); INIT_LIST_HEAD(&directory->playlists); + memcpy(directory->path, path, path_size); + + return directory; +} + +struct directory * +directory_new(const char *path, struct directory *parent) +{ + assert(path != NULL); + assert((*path == 0) == (parent == NULL)); + + directory *directory = directory_allocate(path); + directory->parent = parent; - memcpy(directory->path, path, pathlen + 1); return directory; } @@ -277,36 +291,39 @@ directory_sort(struct directory *directory) } bool -directory_walk(const struct directory *directory, bool recursive, - const struct db_visitor *visitor, void *ctx, - GError **error_r) +directory::Walk(bool recursive, const locate_item_list *match, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const { - assert(directory != NULL); - assert(visitor != NULL); assert(error_r == NULL || *error_r == NULL); - if (visitor->song != NULL) { + if (visit_song) { struct song *song; - directory_for_each_song(song, directory) - if (!visitor->song(song, ctx, error_r)) + directory_for_each_song(song, this) + if ((match == NULL || + locate_list_song_match(song, match)) && + !visit_song(*song, error_r)) return false; } - if (visitor->playlist != NULL) { + if (visit_playlist) { struct playlist_metadata *i; - directory_for_each_playlist(i, directory) - if (!visitor->playlist(i, directory, ctx, error_r)) + directory_for_each_playlist(i, this) + if (!visit_playlist(*i, *this, error_r)) return false; } struct directory *child; - directory_for_each_child(child, directory) { - if (visitor->directory != NULL && - !visitor->directory(child, ctx, error_r)) + directory_for_each_child(child, this) { + if (visit_directory && + !visit_directory(*child, error_r)) return false; if (recursive && - !directory_walk(child, recursive, visitor, ctx, error_r)) + !child->Walk(recursive, match, + visit_directory, visit_song, visit_playlist, + error_r)) return false; } diff --git a/src/song.c b/src/Song.cxx index f5cc7c35a..eb4c2e53e 100644 --- a/src/song.c +++ b/src/Song.cxx @@ -19,26 +19,31 @@ #include "config.h" #include "song.h" -#include "uri.h" #include "directory.h" + +extern "C" { #include "tag.h" +} #include <glib.h> #include <assert.h> +struct directory detached_root; + static struct song * song_alloc(const char *uri, struct directory *parent) { size_t uri_length; - struct song *song; assert(uri); uri_length = strlen(uri); assert(uri_length); - song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); - song->tag = NULL; + struct song *song = (struct song *) + g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); + + song->tag = nullptr; memcpy(song->uri, uri, uri_length + 1); song->parent = parent; song->mtime = 0; @@ -50,13 +55,13 @@ song_alloc(const char *uri, struct directory *parent) struct song * song_remote_new(const char *uri) { - return song_alloc(uri, NULL); + return song_alloc(uri, nullptr); } struct song * song_file_new(const char *path, struct directory *parent) { - assert((parent == NULL) == (*path == '/')); + assert((parent == nullptr) == (*path == '/')); return song_alloc(path, parent); } @@ -73,6 +78,35 @@ song_replace_uri(struct song *old_song, const char *uri) return new_song; } +struct song * +song_detached_new(const char *uri) +{ + assert(uri != nullptr); + + return song_alloc(uri, &detached_root); +} + +struct song * +song_dup_detached(const struct song *src) +{ + assert(src != nullptr); + + struct song *song; + if (song_in_database(src)) { + char *uri = song_get_uri(src); + song = song_detached_new(uri); + g_free(uri); + } else + song = song_alloc(src->uri, nullptr); + + song->tag = tag_dup(src->tag); + song->mtime = src->mtime; + song->start_ms = src->start_ms; + song->end_ms = src->end_ms; + + return song; +} + void song_free(struct song *song) { @@ -81,17 +115,57 @@ song_free(struct song *song) g_free(song); } +gcc_pure +static inline bool +directory_equals(const struct directory &a, const struct directory &b) +{ + return strcmp(a.path, b.path) == 0; +} + +gcc_pure +static inline bool +directory_is_same(const struct directory *a, const struct directory *b) +{ + return a == b || + (a != nullptr && b != nullptr && + directory_equals(*a, *b)); + +} + +bool +song_equals(const struct song *a, const struct song *b) +{ + assert(a != nullptr); + assert(b != nullptr); + + if (a->parent != nullptr && b->parent != nullptr && + !directory_equals(*a->parent, *b->parent) && + (a->parent == &detached_root || b->parent == &detached_root)) { + /* must compare the full URI if one of the objects is + "detached" */ + char *au = song_get_uri(a); + char *bu = song_get_uri(b); + const bool result = strcmp(au, bu) == 0; + g_free(bu); + g_free(au); + return result; + } + + return directory_is_same(a->parent, b->parent) && + strcmp(a->uri, b->uri) == 0; +} + char * song_get_uri(const struct song *song) { - assert(song != NULL); + assert(song != nullptr); assert(*song->uri); if (!song_in_database(song) || directory_is_root(song->parent)) return g_strdup(song->uri); else return g_strconcat(directory_get_path(song->parent), - "/", song->uri, NULL); + "/", song->uri, nullptr); } double @@ -100,7 +174,7 @@ song_get_duration(const struct song *song) if (song->end_ms > 0) return (song->end_ms - song->start_ms) / 1000.0; - if (song->tag == NULL) + if (song->tag == nullptr) return 0; return song->tag->time - song->start_ms / 1000.0; diff --git a/src/stats.c b/src/Stats.cxx index fe6a064a6..35c162ef2 100644 --- a/src/stats.c +++ b/src/Stats.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,15 +18,18 @@ */ #include "config.h" + +extern "C" { #include "stats.h" #include "database.h" -#include "db_visitor.h" -#include "tag.h" -#include "song.h" #include "client.h" #include "player_control.h" -#include "strset.h" #include "client_internal.h" +} + +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" struct stats stats; @@ -40,71 +43,27 @@ void stats_global_finish(void) g_timer_destroy(stats.timer); } -struct visit_data { - struct strset *artists; - struct strset *albums; -}; - -static void -visit_tag(struct visit_data *data, const struct tag *tag) -{ - if (tag->time > 0) - stats.song_duration += tag->time; - - for (unsigned i = 0; i < tag->num_items; ++i) { - const struct tag_item *item = tag->items[i]; - - switch (item->type) { - case TAG_ARTIST: - strset_add(data->artists, item->value); - break; - - case TAG_ALBUM: - strset_add(data->albums, item->value); - break; - - default: - break; - } - } -} - -static bool -collect_stats_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct visit_data *data = _data; - - ++stats.song_count; - - if (song->tag != NULL) - visit_tag(data, song->tag); - - return true; -} - -static const struct db_visitor collect_stats_visitor = { - .song = collect_stats_song, -}; - void stats_update(void) { - struct visit_data data; - - stats.song_count = 0; - stats.song_duration = 0; - stats.artist_count = 0; - - data.artists = strset_new(); - data.albums = strset_new(); - - db_walk("", &collect_stats_visitor, &data, NULL); - - stats.artist_count = strset_size(data.artists); - stats.album_count = strset_size(data.albums); - - strset_free(data.artists); - strset_free(data.albums); + GError *error = nullptr; + + DatabaseStats stats2; + + const DatabaseSelection selection("", true); + if (GetDatabase()->GetStats(selection, stats2, &error)) { + stats.song_count = stats2.song_count; + stats.song_duration = stats2.total_duration; + stats.artist_count = stats2.artist_count; + stats.album_count = stats2.album_count; + } else { + g_warning("%s", error->message); + g_error_free(error); + + stats.song_count = 0; + stats.song_duration = 0; + stats.artist_count = 0; + stats.album_count = 0; + } } int stats_print(struct client *client) @@ -115,14 +74,18 @@ int stats_print(struct client *client) "songs: %i\n" "uptime: %li\n" "playtime: %li\n" - "db_playtime: %li\n" - "db_update: %li\n", + "db_playtime: %li\n", stats.artist_count, stats.album_count, stats.song_count, (long)g_timer_elapsed(stats.timer, NULL), (long)(pc_get_total_play_time(client->player_control) + 0.5), - stats.song_duration, - (long)db_get_mtime()); + stats.song_duration); + + if (db_is_simple()) + client_printf(client, + "db_update: %li\n", + (long)db_get_mtime()); + return 0; } diff --git a/src/audio_check.h b/src/audio_check.h index 9f71cf9c0..e2302126f 100644 --- a/src/audio_check.h +++ b/src/audio_check.h @@ -28,7 +28,7 @@ /** * The GLib quark used for errors reported by this library. */ -G_GNUC_CONST +gcc_const static inline GQuark audio_format_quark(void) { diff --git a/src/audio_format.h b/src/audio_format.h index bf77add3b..d86ade5ee 100644 --- a/src/audio_format.h +++ b/src/audio_format.h @@ -20,7 +20,8 @@ #ifndef MPD_AUDIO_FORMAT_H #define MPD_AUDIO_FORMAT_H -#include <glib.h> +#include "gcc.h" + #include <stdint.h> #include <stdbool.h> #include <assert.h> @@ -189,7 +190,7 @@ audio_valid_channel_count(unsigned channels) * Returns false if the format is not valid for playback with MPD. * This function performs some basic validity checks. */ -G_GNUC_PURE +gcc_pure static inline bool audio_format_valid(const struct audio_format *af) { return audio_valid_sample_rate(af->sample_rate) && @@ -201,7 +202,7 @@ static inline bool audio_format_valid(const struct audio_format *af) * Returns false if the format mask is not valid for playback with * MPD. This function performs some basic validity checks. */ -G_GNUC_PURE +gcc_pure static inline bool audio_format_mask_valid(const struct audio_format *af) { return (af->sample_rate == 0 || @@ -223,7 +224,7 @@ void audio_format_mask_apply(struct audio_format *af, const struct audio_format *mask); -G_GNUC_CONST +gcc_const static inline unsigned sample_format_size(enum sample_format format) { @@ -254,7 +255,7 @@ sample_format_size(enum sample_format format) /** * Returns the size of each (mono) sample in bytes. */ -G_GNUC_PURE +gcc_pure static inline unsigned audio_format_sample_size(const struct audio_format *af) { return sample_format_size((enum sample_format)af->format); @@ -263,7 +264,7 @@ static inline unsigned audio_format_sample_size(const struct audio_format *af) /** * Returns the size of each full frame in bytes. */ -G_GNUC_PURE +gcc_pure static inline unsigned audio_format_frame_size(const struct audio_format *af) { @@ -274,7 +275,7 @@ audio_format_frame_size(const struct audio_format *af) * Returns the floating point factor which converts a time span to a * storage size in bytes. */ -G_GNUC_PURE +gcc_pure static inline double audio_format_time_to_size(const struct audio_format *af) { return af->sample_rate * audio_format_frame_size(af); @@ -287,7 +288,7 @@ static inline double audio_format_time_to_size(const struct audio_format *af) * @param format a #sample_format enum value * @return the string */ -G_GNUC_PURE G_GNUC_MALLOC +gcc_pure gcc_malloc const char * sample_format_to_string(enum sample_format format); @@ -299,7 +300,7 @@ sample_format_to_string(enum sample_format format); * @param s a buffer to print into * @return the string, or NULL if the #audio_format object is invalid */ -G_GNUC_PURE G_GNUC_MALLOC +gcc_pure gcc_malloc const char * audio_format_to_string(const struct audio_format *af, struct audio_format_string *s); diff --git a/src/audio_parser.h b/src/audio_parser.h index a963eb467..49926999e 100644 --- a/src/audio_parser.h +++ b/src/audio_parser.h @@ -25,7 +25,7 @@ #ifndef AUDIO_PARSER_H #define AUDIO_PARSER_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> diff --git a/src/client.h b/src/client.h index 0302a2e0a..51ad1eb2a 100644 --- a/src/client.h +++ b/src/client.h @@ -20,7 +20,8 @@ #ifndef MPD_CLIENT_H #define MPD_CLIENT_H -#include <glib.h> +#include "gcc.h" + #include <stdbool.h> #include <stddef.h> #include <stdarg.h> @@ -35,28 +36,28 @@ void client_manager_deinit(void); void client_new(struct player_control *player_control, int fd, const struct sockaddr *sa, size_t sa_length, int uid); -G_GNUC_PURE +gcc_pure bool client_is_expired(const struct client *client); /** * returns the uid of the client process, or a negative value if the * uid is unknown */ -G_GNUC_PURE +gcc_pure int client_get_uid(const struct client *client); /** * Is this client running on the same machine, connected with a local * (UNIX domain) socket? */ -G_GNUC_PURE +gcc_pure static inline bool client_is_local(const struct client *client) { return client_get_uid(client) > 0; } -G_GNUC_PURE +gcc_pure unsigned client_get_permission(const struct client *client); void client_set_permission(struct client *client, unsigned permission); @@ -74,6 +75,8 @@ void client_vprintf(struct client *client, const char *fmt, va_list args); /** * Write a printf-like formatted string to the client. */ -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...); +gcc_fprintf +void +client_printf(struct client *client, const char *fmt, ...); #endif diff --git a/src/client_file.h b/src/client_file.h index bc64bd041..2dd07dede 100644 --- a/src/client_file.h +++ b/src/client_file.h @@ -20,7 +20,8 @@ #ifndef MPD_CLIENT_FILE_H #define MPD_CLIENT_FILE_H -#include <glib.h> +#include "gerror.h" + #include <stdbool.h> struct client; diff --git a/src/client_message.h b/src/client_message.h index 38c2e7615..6d1e734be 100644 --- a/src/client_message.h +++ b/src/client_message.h @@ -20,10 +20,11 @@ #ifndef MPD_CLIENT_MESSAGE_H #define MPD_CLIENT_MESSAGE_H +#include "gcc.h" + #include <assert.h> #include <stdbool.h> #include <stddef.h> -#include <glib.h> /** * A client-to-client message. @@ -34,11 +35,11 @@ struct client_message { char *message; }; -G_GNUC_PURE +gcc_pure bool client_message_valid_channel_name(const char *name); -G_GNUC_PURE +gcc_pure static inline bool client_message_defined(const struct client_message *msg) { @@ -59,7 +60,7 @@ void client_message_copy(struct client_message *dest, const struct client_message *src); -G_GNUC_MALLOC +gcc_malloc struct client_message * client_message_dup(const struct client_message *src); diff --git a/src/client_subscribe.h b/src/client_subscribe.h index 09f864417..2dff982ba 100644 --- a/src/client_subscribe.h +++ b/src/client_subscribe.h @@ -20,9 +20,11 @@ #ifndef MPD_CLIENT_SUBSCRIBE_H #define MPD_CLIENT_SUBSCRIBE_H +#include "gcc.h" + #include <stdbool.h> -#include <glib.h> +typedef struct _GSList GSList; struct client; struct client_message; @@ -52,7 +54,7 @@ client_unsubscribe_all(struct client *client); bool client_push_message(struct client *client, const struct client_message *msg); -G_GNUC_MALLOC +gcc_malloc GSList * client_read_messages(struct client *client); diff --git a/src/clock.h b/src/clock.h index 4ece35ab1..f1338938f 100644 --- a/src/clock.h +++ b/src/clock.h @@ -20,21 +20,21 @@ #ifndef MPD_CLOCK_H #define MPD_CLOCK_H -#include <glib.h> +#include "gcc.h" #include <stdint.h> /** * Returns the value of a monotonic clock in milliseconds. */ -G_GNUC_PURE +gcc_pure unsigned monotonic_clock_ms(void); /** * Returns the value of a monotonic clock in microseconds. */ -G_GNUC_PURE +gcc_pure uint64_t monotonic_clock_us(void); diff --git a/src/command.c b/src/command.c index 329b7d51a..5bc6934c5 100644 --- a/src/command.c +++ b/src/command.c @@ -19,6 +19,8 @@ #include "config.h" #include "command.h" +#include "DatabaseCommands.hxx" +#include "CommandError.h" #include "protocol/argparser.h" #include "protocol/result.h" #include "player_control.h" @@ -28,6 +30,7 @@ #include "playlist_queue.h" #include "playlist_error.h" #include "queue_print.h" +#include "time_print.h" #include "ls.h" #include "uri.h" #include "decoder_print.h" @@ -44,9 +47,6 @@ #include "output_print.h" #include "locate.h" #include "dbUtils.h" -#include "db_error.h" -#include "db_print.h" -#include "db_selection.h" #include "db_lock.h" #include "tag.h" #include "client.h" @@ -110,135 +110,17 @@ struct command { enum command_return (*handler)(struct client *client, int argc, char **argv); }; -static enum command_return -print_playlist_result(struct client *client, - enum playlist_result result) -{ - switch (result) { - case PLAYLIST_RESULT_SUCCESS: - return COMMAND_RETURN_OK; - - case PLAYLIST_RESULT_ERRNO: - command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(errno)); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_DENIED: - command_error(client, ACK_ERROR_PERMISSION, "Access denied"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_NO_SUCH_SONG: - command_error(client, ACK_ERROR_NO_EXIST, "No such song"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_NO_SUCH_LIST: - command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_LIST_EXISTS: - command_error(client, ACK_ERROR_EXIST, - "Playlist already exists"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_BAD_NAME: - command_error(client, ACK_ERROR_ARG, - "playlist name is invalid: " - "playlist names may not contain slashes," - " newlines or carriage returns"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_BAD_RANGE: - command_error(client, ACK_ERROR_ARG, "Bad song index"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_NOT_PLAYING: - command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_TOO_LARGE: - command_error(client, ACK_ERROR_PLAYLIST_MAX, - "playlist is at the max size"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_DISABLED: - command_error(client, ACK_ERROR_UNKNOWN, - "stored playlist support is disabled"); - return COMMAND_RETURN_ERROR; - } - - assert(0); - return COMMAND_RETURN_ERROR; -} - -/** - * Send the GError to the client and free the GError. - */ -static enum command_return -print_error(struct client *client, GError *error) -{ - assert(client != NULL); - assert(error != NULL); - - g_warning("%s", error->message); - - if (error->domain == playlist_quark()) { - enum playlist_result result = error->code; - g_error_free(error); - return print_playlist_result(client, result); - } else if (error->domain == ack_quark()) { - command_error(client, error->code, "%s", error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } else if (error->domain == db_quark()) { - switch ((enum db_error)error->code) { - case DB_DISABLED: - command_error(client, ACK_ERROR_NO_EXIST, "%s", - error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - - case DB_NOT_FOUND: - g_error_free(error); - command_error(client, ACK_ERROR_NO_EXIST, "Not found"); - return COMMAND_RETURN_ERROR; - } - } else if (error->domain == g_file_error_quark()) { - command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(error->code)); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } - - g_error_free(error); - command_error(client, ACK_ERROR_UNKNOWN, "error"); - return COMMAND_RETURN_ERROR; -} - static void print_spl_list(struct client *client, GPtrArray *list) { for (unsigned i = 0; i < list->len; ++i) { struct stored_playlist_info *playlist = g_ptr_array_index(list, i); - time_t t; -#ifndef WIN32 - struct tm tm; -#endif - char timestamp[32]; client_printf(client, "playlist: %s\n", playlist->name); - 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); + if (playlist->mtime > 0) + time_print(client, "Last-Modified", playlist->mtime); } } @@ -685,12 +567,9 @@ handle_lsinfo(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_OK; } - struct db_selection selection; - db_selection_init(&selection, uri, false); - - GError *error = NULL; - if (!db_selection_print(client, &selection, true, &error)) - return print_error(client, error); + enum command_return result = handle_lsinfo2(client, argc, argv); + if (result != COMMAND_RETURN_OK) + return result; if (isRootDirectory(uri)) { GPtrArray *list = spl_list(NULL); @@ -782,194 +661,34 @@ handle_playlistid(struct client *client, int argc, char *argv[]) } static enum command_return -handle_find(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = findSongsIn(client, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_findadd(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = - findAddIn(client->player_control, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_search(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = searchForSongsIn(client, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_searchadd(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = search_add_songs(client->player_control, - "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_searchaddpl(struct client *client, int argc, char *argv[]) +handle_playlist_match(struct client *client, int argc, char *argv[], + bool fold_case) { - const char *playlist = argv[1]; - struct locate_item_list *list = - locate_item_list_parse(argv + 2, argc - 2); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); + locate_item_list_parse(argv + 1, argc - 1, fold_case); + if (list == NULL) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return COMMAND_RETURN_ERROR; } - GError *error = NULL; - enum command_return ret = - search_add_to_playlist("", playlist, list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_count(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = - searchStatsForSongsIn(client, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); + playlist_print_find(client, &g_playlist, list); locate_item_list_free(list); - return ret; + return COMMAND_RETURN_OK; } static enum command_return handle_playlistfind(struct client *client, int argc, char *argv[]) { - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - playlist_print_find(client, &g_playlist, list); - - locate_item_list_free(list); - - return COMMAND_RETURN_OK; + return handle_playlist_match(client, argc, argv, false); } static enum command_return handle_playlistsearch(struct client *client, int argc, char *argv[]) { - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - playlist_print_search(client, &g_playlist, list); - - locate_item_list_free(list); - - return COMMAND_RETURN_OK; + return handle_playlist_match(client, argc, argv, true); } static enum command_return @@ -1149,20 +868,6 @@ handle_prioid(struct client *client, int argc, char *argv[]) } static enum command_return -handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; - - GError *error = NULL; - return printAllIn(client, directory, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { unsigned level; @@ -1246,58 +951,6 @@ handle_clearerror(G_GNUC_UNUSED struct client *client, } static enum command_return -handle_list(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *conditionals; - int tagType = locate_parse_type(argv[1]); - - if (tagType < 0) { - command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); - return COMMAND_RETURN_ERROR; - } - - if (tagType == LOCATE_TAG_ANY_TYPE) { - command_error(client, ACK_ERROR_ARG, - "\"any\" is not a valid return tag type"); - return COMMAND_RETURN_ERROR; - } - - /* for compatibility with < 0.12.0 */ - if (argc == 3) { - if (tagType != TAG_ALBUM) { - command_error(client, ACK_ERROR_ARG, - "should be \"%s\" for 3 arguments", - tag_item_names[TAG_ALBUM]); - return COMMAND_RETURN_ERROR; - } - - locate_item_list_parse(argv + 1, argc - 1); - - conditionals = locate_item_list_new(1); - conditionals->items[0].tag = TAG_ARTIST; - conditionals->items[0].needle = g_strdup(argv[2]); - } else { - conditionals = - locate_item_list_parse(argv + 2, argc - 2); - if (conditionals == NULL) { - command_error(client, ACK_ERROR_ARG, - "not able to parse args"); - return COMMAND_RETURN_ERROR; - } - } - - GError *error = NULL; - enum command_return ret = - listAllUniqueTags(client, tagType, conditionals, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(conditionals); - - return ret; -} - -static enum command_return handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { unsigned start, end; @@ -1407,20 +1060,6 @@ handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) } static enum command_return -handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; - - GError *error = NULL; - return printInfoForAllIn(client, directory, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return handle_ping(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { diff --git a/src/command.h b/src/command.h index 68d1f95e4..2dfc11758 100644 --- a/src/command.h +++ b/src/command.h @@ -22,7 +22,6 @@ #include "ack.h" -#include <glib.h> #include <stdbool.h> enum command_return { @@ -34,6 +33,8 @@ enum command_return { struct client; +G_BEGIN_DECLS + void command_init(void); void command_finish(void); @@ -43,4 +44,6 @@ command_process(struct client *client, unsigned num, char *line); void command_success(struct client *client); +G_END_DECLS + #endif diff --git a/src/conf.c b/src/conf.c index 167f2da92..5f12d84d9 100644 --- a/src/conf.c +++ b/src/conf.c @@ -102,6 +102,7 @@ static struct config_entry config_entries[] = { { .name = CONF_DESPOTIFY_PASSWORD, false, false}, { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false }, { .name = "filter", true, true }, + { .name = "database", false, true }, }; static bool diff --git a/src/database.h b/src/database.h index f877b74d3..d8d86fa5d 100644 --- a/src/database.h +++ b/src/database.h @@ -35,19 +35,29 @@ struct db_visitor; /** * Initialize the database library. * - * @param path the absolute path of the database file + * @param param the database configuration block */ bool -db_init(const struct config_param *path, GError **error_r); +db_init(const struct config_param *param, GError **error_r); void db_finish(void); /** + * Check whether the default #SimpleDatabasePlugin is used. This + * allows using db_get_root(), db_save(), db_get_mtime() and + * db_exists(). + */ +bool +db_is_simple(void); + +/** * Returns the root directory object. Returns NULL if there is no * configured music directory. + * + * May only be used if db_is_simple() returns true. */ -G_GNUC_PURE +gcc_pure struct directory * db_get_root(void); @@ -55,41 +65,37 @@ db_get_root(void); * Caller must lock the #db_mutex. */ gcc_nonnull(1) -G_GNUC_PURE +gcc_pure struct directory * db_get_directory(const char *name); gcc_nonnull(1) -G_GNUC_PURE +gcc_pure struct song * db_get_song(const char *file); -gcc_nonnull(1,2) -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -gcc_nonnull(1,2) -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - +/** + * May only be used if db_is_simple() returns true. + */ bool db_save(GError **error_r); bool db_load(GError **error); -G_GNUC_PURE +/** + * May only be used if db_is_simple() returns true. + */ +gcc_pure time_t db_get_mtime(void); /** * Returns true if there is a valid database file on the disk. + * + * May only be used if db_is_simple() returns true. */ -G_GNUC_PURE +gcc_pure static inline bool db_exists(void) { diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx new file mode 100644 index 000000000..26e7b4b84 --- /dev/null +++ b/src/db/ProxyDatabasePlugin.cxx @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2003-2012 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 "ProxyDatabasePlugin.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "gcc.h" + +extern "C" { +#include "db_error.h" +#include "conf.h" +#include "song.h" +#include "tag.h" +} + +#include "directory.h" +#include "playlist_vector.h" + +#undef MPD_DIRECTORY_H +#undef MPD_SONG_H +#include <mpd/client.h> + +#include <cassert> +#include <string> +#include <list> + +class ProxyDatabase : public Database { + std::string host; + unsigned port; + + struct mpd_connection *connection; + struct directory *root; + +public: + static Database *Create(const struct config_param *param, + GError **error_r); + + virtual bool Open(GError **error_r) override; + virtual void Close() override; + virtual struct song *GetSong(const char *uri_utf8, + GError **error_r) const override; + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + GError **error_r) const override; + +protected: + bool Configure(const struct config_param *param, GError **error_r); +}; + +G_GNUC_CONST +static inline GQuark +libmpdclient_quark(void) +{ + return g_quark_from_static_string("libmpdclient"); +} + +static constexpr struct { + enum tag_type d; + enum mpd_tag_type s; +} tag_table[] = { + { TAG_ARTIST, MPD_TAG_ARTIST }, + { TAG_ALBUM, MPD_TAG_ALBUM }, + { TAG_ALBUM_ARTIST, MPD_TAG_ALBUM_ARTIST }, + { TAG_TITLE, MPD_TAG_TITLE }, + { TAG_TRACK, MPD_TAG_TRACK }, + { TAG_NAME, MPD_TAG_NAME }, + { TAG_GENRE, MPD_TAG_GENRE }, + { TAG_DATE, MPD_TAG_DATE }, + { TAG_COMPOSER, MPD_TAG_COMPOSER }, + { TAG_PERFORMER, MPD_TAG_PERFORMER }, + { TAG_COMMENT, MPD_TAG_COMMENT }, + { TAG_DISC, MPD_TAG_DISC }, + { TAG_MUSICBRAINZ_ARTISTID, MPD_TAG_MUSICBRAINZ_ARTISTID }, + { TAG_MUSICBRAINZ_ALBUMID, MPD_TAG_MUSICBRAINZ_ALBUMID }, + { TAG_MUSICBRAINZ_ALBUMARTISTID, + MPD_TAG_MUSICBRAINZ_ALBUMARTISTID }, + { TAG_MUSICBRAINZ_TRACKID, MPD_TAG_MUSICBRAINZ_TRACKID }, + { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT } +}; + +G_GNUC_CONST +static enum mpd_tag_type +Convert(enum tag_type tag_type) +{ + for (auto i = tag_table; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + if (i->d == tag_type) + return i->s; + + return MPD_TAG_COUNT; +} + +static bool +CheckError(const struct mpd_connection *connection, GError **error_r) +{ + const auto error = mpd_connection_get_error(connection); + if (error == MPD_ERROR_SUCCESS) + return true; + + g_set_error_literal(error_r, libmpdclient_quark(), (int)error, + mpd_connection_get_error_message(connection)); + return false; +} + +Database * +ProxyDatabase::Create(const struct config_param *param, GError **error_r) +{ + ProxyDatabase *db = new ProxyDatabase(); + if (!db->Configure(param, error_r)) { + delete db; + db = NULL; + } + + return db; +} + +bool +ProxyDatabase::Configure(const struct config_param *param, GError **) +{ + host = config_get_block_string(param, "host", ""); + port = config_get_block_unsigned(param, "port", 0); + + return true; +} + +bool +ProxyDatabase::Open(GError **error_r) +{ + connection = mpd_connection_new(host.empty() ? NULL : host.c_str(), + port, 0); + if (connection == NULL) { + g_set_error_literal(error_r, libmpdclient_quark(), + (int)MPD_ERROR_OOM, "Out of memory"); + return false; + } + + if (!CheckError(connection, error_r)) { + mpd_connection_free(connection); + return false; + } + + root = directory_new_root(); + + return true; +} + +void +ProxyDatabase::Close() +{ + assert(connection != nullptr); + + directory_free(root); + mpd_connection_free(connection); +} + +struct song * +ProxyDatabase::GetSong(const char *uri, GError **error_r) const +{ + // TODO: implement + // TODO: auto-reconnect + + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such song: %s", uri); + return nullptr; +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, GError **error_r); + +static bool +Visit(struct mpd_connection *connection, + bool recursive, const struct mpd_directory *directory, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, GError **error_r) +{ + const char *path = mpd_directory_get_path(directory); + + if (visit_directory) { + struct directory *d = + directory_new(path, &detached_root); + bool success = visit_directory(*d, error_r); + directory_free(d); + if (!success) + return false; + } + + if (recursive && + !Visit(connection, path, recursive, + visit_directory, visit_song, visit_playlist, error_r)) + return false; + + return true; +} + +static void +Copy(struct tag *tag, enum tag_type d_tag, + const struct mpd_song *song, enum mpd_tag_type s_tag) +{ + + for (unsigned i = 0;; ++i) { + const char *value = mpd_song_get_tag(song, s_tag, i); + if (value == NULL) + break; + + tag_add_item(tag, d_tag, value); + } +} + +static song * +Convert(const struct mpd_song *song) +{ + struct song *s = song_detached_new(mpd_song_get_uri(song)); + + s->mtime = mpd_song_get_last_modified(song); + s->start_ms = mpd_song_get_start(song) * 1000; + s->end_ms = mpd_song_get_end(song) * 1000; + + struct tag *tag = tag_new(); + tag->time = mpd_song_get_duration(song); + + tag_begin_add(tag); + for (auto i = tag_table; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + Copy(tag, i->d, song, i->s); + tag_end_add(tag); + + s->tag = tag; + + return s; +} + +static bool +Visit(const struct mpd_song *song, + VisitSong visit_song, GError **error_r) +{ + if (!visit_song) + return true; + + struct song *s = Convert(song); + bool success = visit_song(*s, error_r); + song_free(s); + + return success; +} + +static bool +Visit(const struct mpd_playlist *playlist, + VisitPlaylist visit_playlist, GError **error_r) +{ + if (!visit_playlist) + return true; + + struct playlist_metadata p; + p.name = g_strdup(mpd_playlist_get_path(playlist)); + p.mtime = mpd_playlist_get_last_modified(playlist); + + bool success = visit_playlist(p, detached_root, error_r); + g_free(p.name); + + return success; +} + +class ProxyEntity { + struct mpd_entity *entity; + +public: + explicit ProxyEntity(struct mpd_entity *_entity) + :entity(_entity) {} + + ProxyEntity(const ProxyEntity &other) = delete; + + ProxyEntity(ProxyEntity &&other) + :entity(other.entity) { + other.entity = nullptr; + } + + ~ProxyEntity() { + if (entity != nullptr) + mpd_entity_free(entity); + } + + ProxyEntity &operator=(const ProxyEntity &other) = delete; + + operator const struct mpd_entity *() const { + return entity; + } +}; + +static std::list<ProxyEntity> +ReceiveEntities(struct mpd_connection *connection) +{ + std::list<ProxyEntity> entities; + struct mpd_entity *entity; + while ((entity = mpd_recv_entity(connection)) != NULL) + entities.push_back(ProxyEntity(entity)); + + mpd_response_finish(connection); + return entities; +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, GError **error_r) +{ + if (!mpd_send_list_meta(connection, uri)) + return CheckError(connection, error_r); + + std::list<ProxyEntity> entities(ReceiveEntities(connection)); + if (!CheckError(connection, error_r)) + return false; + + for (const auto &entity : entities) { + switch (mpd_entity_get_type(entity)) { + case MPD_ENTITY_TYPE_UNKNOWN: + break; + + case MPD_ENTITY_TYPE_DIRECTORY: + if (!Visit(connection, recursive, + mpd_entity_get_directory(entity), + visit_directory, visit_song, visit_playlist, + error_r)) + return false; + break; + + case MPD_ENTITY_TYPE_SONG: + if (!Visit(mpd_entity_get_song(entity), visit_song, + error_r)) + return false; + break; + + case MPD_ENTITY_TYPE_PLAYLIST: + if (!Visit(mpd_entity_get_playlist(entity), + visit_playlist, error_r)) + return false; + break; + } + } + + return CheckError(connection, error_r); +} + +bool +ProxyDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const +{ + // TODO: match + // TODO: auto-reconnect + + return ::Visit(connection, selection.uri, selection.recursive, + visit_directory, visit_song, visit_playlist, + error_r); +} + +bool +ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const +{ + enum mpd_tag_type tag_type2 = Convert(tag_type); + if (tag_type2 == MPD_TAG_COUNT) { + g_set_error_literal(error_r, libmpdclient_quark(), 0, + "Unsupported tag"); + return false; + } + + if (!mpd_search_db_tags(connection, tag_type2)) + return CheckError(connection, error_r); + + // TODO: match + (void)selection; + + if (!mpd_search_commit(connection)) + return CheckError(connection, error_r); + + bool result = true; + + struct mpd_pair *pair; + while (result && + (pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) { + result = visit_string(pair->value, error_r); + mpd_return_pair(connection, pair); + } + + return mpd_response_finish(connection) && + CheckError(connection, error_r) && + result; +} + +bool +ProxyDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, GError **error_r) const +{ + // TODO: match + (void)selection; + + struct mpd_stats *stats2 = + mpd_run_stats(connection); + if (stats2 == nullptr) + return CheckError(connection, error_r); + + stats.song_count = mpd_stats_get_number_of_songs(stats2); + stats.total_duration = mpd_stats_get_db_play_time(stats2); + stats.artist_count = mpd_stats_get_number_of_artists(stats2); + stats.album_count = mpd_stats_get_number_of_albums(stats2); + mpd_stats_free(stats2); + + return true; +} + +const DatabasePlugin proxy_db_plugin = { + "proxy", + ProxyDatabase::Create, +}; diff --git a/src/db/ProxyDatabasePlugin.hxx b/src/db/ProxyDatabasePlugin.hxx new file mode 100644 index 000000000..8e878baca --- /dev/null +++ b/src/db/ProxyDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2012 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_PROXY_DATABASE_PLUGIN_HXX +#define MPD_PROXY_DATABASE_PLUGIN_HXX + +struct DatabasePlugin; + +extern const DatabasePlugin proxy_db_plugin; + +#endif diff --git a/src/db/simple_db_plugin.c b/src/db/SimpleDatabasePlugin.cxx index 697e8da5f..c1de70d3e 100644 --- a/src/db/simple_db_plugin.c +++ b/src/db/SimpleDatabasePlugin.cxx @@ -18,14 +18,18 @@ */ #include "config.h" -#include "simple_db_plugin.h" -#include "db_internal.h" +#include "SimpleDatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseHelpers.hxx" + +extern "C" { #include "db_error.h" -#include "db_selection.h" -#include "db_visitor.h" #include "db_save.h" #include "db_lock.h" #include "conf.h" +#include "locate.h" +} + #include "directory.h" #include <sys/types.h> @@ -33,16 +37,6 @@ #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) @@ -50,63 +44,50 @@ 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) +Database * +SimpleDatabase::Create(const struct config_param *param, GError **error_r) { - assert(db != NULL); - assert(db->root != NULL); - assert(uri != NULL); + SimpleDatabase *db = new SimpleDatabase(); + if (!db->Configure(param, error_r)) { + delete db; + db = NULL; + } - db_lock(); - struct directory *directory = - directory_lookup_directory(db->root, uri); - db_unlock(); - return directory; + return db; } -static struct db * -simple_db_init(const struct config_param *param, GError **error_r) +bool +SimpleDatabase::Configure(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); - if (db->path == NULL) { - g_free(db); + + char *_path = config_dup_block_path(param, "path", &error); + if (_path == NULL) { 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 false; } - return &db->base; -} + path = _path; + free(_path); -static void -simple_db_finish(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - g_free(db->path); - g_free(db); + return true; } -static bool -simple_db_check(struct simple_db *db, GError **error_r) +bool +SimpleDatabase::Check(GError **error_r) const { - assert(db != NULL); - assert(db->path != NULL); + assert(!path.empty()); /* Check if the file exists */ - if (access(db->path, F_OK)) { + if (access(path.c_str(), 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); + char *dirPath = g_path_get_dirname(path.c_str()); /* Check that the parent part of the path is a directory */ struct stat st; @@ -115,7 +96,7 @@ simple_db_check(struct simple_db *db, GError **error_r) g_set_error(error_r, simple_db_quark(), errno, "Couldn't stat parent directory of db file " "\"%s\": %s", - db->path, g_strerror(errno)); + path.c_str(), g_strerror(errno)); return false; } @@ -124,7 +105,7 @@ simple_db_check(struct simple_db *db, GError **error_r) 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); + path.c_str()); return false; } @@ -144,47 +125,46 @@ simple_db_check(struct simple_db *db, GError **error_r) /* Path exists, now check if it's a regular file */ struct stat st; - if (stat(db->path, &st) < 0) { + if (stat(path.c_str(), &st) < 0) { g_set_error(error_r, simple_db_quark(), errno, "Couldn't stat db file \"%s\": %s", - db->path, g_strerror(errno)); + path.c_str(), 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); + path.c_str()); return false; } /* And check that we can write to it */ - if (access(db->path, R_OK | W_OK)) { + if (access(path.c_str(), 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)); + path.c_str(), g_strerror(errno)); return false; } return true; } -static bool -simple_db_load(struct simple_db *db, GError **error_r) +bool +SimpleDatabase::Load(GError **error_r) { - assert(db != NULL); - assert(db->path != NULL); - assert(db->root != NULL); + assert(!path.empty()); + assert(root != NULL); - FILE *fp = fopen(db->path, "r"); + FILE *fp = fopen(path.c_str(), "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)); + path.c_str(), g_strerror(errno)); return false; } - if (!db_load_internal(fp, db->root, error_r)) { + if (!db_load_internal(fp, root, error_r)) { fclose(fp); return false; } @@ -192,55 +172,49 @@ simple_db_load(struct simple_db *db, GError **error_r) fclose(fp); struct stat st; - if (stat(db->path, &st) == 0) - db->mtime = st.st_mtime; + if (stat(path.c_str(), &st) == 0) + mtime = st.st_mtime; return true; } -static bool -simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r) +bool +SimpleDatabase::Open(GError **error_r) { - struct simple_db *db = (struct simple_db *)_db; - - db->root = directory_new_root(); - db->mtime = 0; + root = directory_new_root(); + mtime = 0; GError *error = NULL; - if (!simple_db_load(db, &error)) { - directory_free(db->root); + if (!Load(&error)) { + directory_free(root); g_warning("Failed to load database: %s", error->message); g_error_free(error); - if (!simple_db_check(db, error_r)) + if (!Check(error_r)) return false; - db->root = directory_new_root(); + root = directory_new_root(); } return true; } -static void -simple_db_close(struct db *_db) +void +SimpleDatabase::Close() { - struct simple_db *db = (struct simple_db *)_db; + assert(root != NULL); - assert(db->root != NULL); - - directory_free(db->root); + directory_free(root); } -static struct song * -simple_db_get_song(struct db *_db, const char *uri, GError **error_r) +struct song * +SimpleDatabase::GetSong(const char *uri, GError **error_r) const { - struct simple_db *db = (struct simple_db *)_db; - - assert(db->root != NULL); + assert(root != NULL); db_lock(); - struct song *song = directory_lookup_song(db->root, uri); + struct song *song = directory_lookup_song(root, uri); db_unlock(); if (song == NULL) g_set_error(error_r, db_quark(), DB_NOT_FOUND, @@ -249,84 +223,94 @@ simple_db_get_song(struct db *_db, const char *uri, GError **error_r) 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) +G_GNUC_PURE +const struct directory * +SimpleDatabase::LookupDirectory(const char *uri) const +{ + assert(root != NULL); + assert(uri != NULL); + + db_lock(); + struct directory *directory = + directory_lookup_directory(root, uri); + db_unlock(); + return directory; +} + +bool +SimpleDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const { - const struct simple_db *db = (const struct simple_db *)_db; - const struct directory *directory = - simple_db_lookup_directory(db, selection->uri); + const struct directory *directory = LookupDirectory(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); + if (visit_song && + (song = GetSong(selection.uri, NULL)) != NULL && + (selection.match == NULL || + locate_list_song_match(song, selection.match))) + return visit_song(*song, 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)) + if (selection.recursive && visit_directory && + !visit_directory(*directory, error_r)) return false; db_lock(); - bool ret = directory_walk(directory, selection->recursive, - visitor, ctx, error_r); + bool ret = directory->Walk(selection.recursive, selection.match, + visit_directory, visit_song, visit_playlist, + error_r); db_unlock(); return ret; } -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) +bool +SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const { - struct simple_db *db = (struct simple_db *)_db; - - assert(db != NULL); - assert(db->root != NULL); - - return db->root; + return ::VisitUniqueTags(*this, selection, tag_type, visit_string, + error_r); } bool -simple_db_save(struct db *_db, GError **error_r) +SimpleDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, GError **error_r) const { - struct simple_db *db = (struct simple_db *)_db; - struct directory *music_root = db->root; + return ::GetStats(*this, selection, stats, error_r); +} +bool +SimpleDatabase::Save(GError **error_r) +{ db_lock(); g_debug("removing empty directories from DB"); - directory_prune_empty(music_root); + directory_prune_empty(root); g_debug("sorting DB"); - directory_sort(music_root); + directory_sort(root); db_unlock(); g_debug("writing DB"); - FILE *fp = fopen(db->path, "w"); + FILE *fp = fopen(path.c_str(), "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)); + path.c_str(), g_strerror(errno)); return false; } - db_save_internal(fp, music_root); + db_save_internal(fp, root); if (ferror(fp)) { g_set_error(error_r, simple_db_quark(), errno, @@ -339,19 +323,13 @@ simple_db_save(struct db *_db, GError **error_r) fclose(fp); struct stat st; - if (stat(db->path, &st) == 0) - db->mtime = st.st_mtime; + if (stat(path.c_str(), &st) == 0) + 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; -} +const DatabasePlugin simple_db_plugin = { + "simple", + SimpleDatabase::Create, +}; diff --git a/src/db/SimpleDatabasePlugin.hxx b/src/db/SimpleDatabasePlugin.hxx new file mode 100644 index 000000000..7e3f5d2db --- /dev/null +++ b/src/db/SimpleDatabasePlugin.hxx @@ -0,0 +1,93 @@ +/* + * 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_DATABASE_PLUGIN_HXX +#define MPD_SIMPLE_DATABASE_PLUGIN_HXX + +#include "DatabasePlugin.hxx" +#include "gcc.h" + +#include <cassert> +#include <string> + +#include <stdbool.h> +#include <time.h> + +struct directory; + +class SimpleDatabase : public Database { + std::string path; + + struct directory *root; + + time_t mtime; + +public: + gcc_pure + struct directory *GetRoot() { + assert(root != NULL); + + return root; + } + + bool Save(GError **error_r); + + gcc_pure + time_t GetLastModified() const { + return mtime; + } + + static Database *Create(const struct config_param *param, + GError **error_r); + + virtual bool Open(GError **error_r) override; + virtual void Close() override; + + virtual struct song *GetSong(const char *uri_utf8, + GError **error_r) const override; + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + GError **error_r) const override; + +protected: + bool Configure(const struct config_param *param, GError **error_r); + + gcc_pure + bool Check(GError **error_r) const; + + bool Load(GError **error_r); + + gcc_pure + const struct directory *LookupDirectory(const char *uri) const; +}; + +extern const DatabasePlugin simple_db_plugin; + +#endif diff --git a/src/dbUtils.c b/src/dbUtils.c deleted file mode 100644 index c212d9f9c..000000000 --- a/src/dbUtils.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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 "dbUtils.h" -#include "locate.h" -#include "database.h" -#include "db_visitor.h" -#include "playlist.h" -#include "stored_playlist.h" - -#include <glib.h> - -static bool -add_to_queue_song(struct song *song, void *ctx, GError **error_r) -{ - struct player_control *pc = ctx; - - 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 true; -} - -static const struct db_visitor add_to_queue_visitor = { - .song = add_to_queue_song, -}; - -bool -addAllIn(struct player_control *pc, const char *uri, GError **error_r) -{ - return db_walk(uri, &add_to_queue_visitor, pc, error_r); -} - -struct add_data { - const char *path; -}; - -static bool -add_to_spl_song(struct song *song, void *ctx, GError **error_r) -{ - struct add_data *data = ctx; - - if (!spl_append_song(data->path, song, error_r)) - return false; - - return true; -} - -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 = path_utf8, - }; - - return db_walk(uri_utf8, &add_to_spl_visitor, &data, error_r); -} - -struct find_add_data { - struct player_control *pc; - const struct locate_item_list *criteria; -}; - -static bool -find_add_song(struct song *song, void *ctx, GError **error_r) -{ - struct find_add_data *data = ctx; - - if (!locate_song_match(song, data->criteria)) - return 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; - } - - return true; -} - -static const struct db_visitor find_add_visitor = { - .song = find_add_song, -}; - -bool -findAddIn(struct player_control *pc, const char *name, - const struct locate_item_list *criteria, GError **error_r) -{ - struct find_add_data data; - data.pc = pc; - data.criteria = criteria; - - return db_walk(name, &find_add_visitor, &data, error_r); -} - -static bool -searchadd_visitor_song(struct song *song, void *_data, GError **error_r) -{ - struct find_add_data *data = _data; - - if (!locate_song_search(song, data->criteria)) - return 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; - } - - return true; -} - -static const struct db_visitor searchadd_visitor = { - .song = searchadd_visitor_song, -}; - -bool -search_add_songs(struct player_control *pc, const char *uri, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list = - locate_item_list_casefold(criteria); - struct find_add_data data = { - .pc = pc, - .criteria = new_list, - }; - - bool success = db_walk(uri, &searchadd_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} - -struct search_add_playlist_data { - const char *playlist; - const struct locate_item_list *criteria; -}; - -static bool -searchaddpl_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_add_playlist_data *data = _data; - - if (!locate_song_search(song, data->criteria)) - return true; - - if (!spl_append_song(data->playlist, song, error_r)) - return false; - - return true; -} - -static const struct db_visitor searchaddpl_visitor = { - .song = searchaddpl_visitor_song, -}; - -bool -search_add_to_playlist(const char *uri, const char *path_utf8, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list - = locate_item_list_casefold(criteria); - struct search_add_playlist_data data = { - .playlist = path_utf8, - .criteria = new_list, - }; - - bool success = db_walk(uri, &searchaddpl_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} diff --git a/src/dbUtils.h b/src/dbUtils.h index 706c807fd..b26422ef8 100644 --- a/src/dbUtils.h +++ b/src/dbUtils.h @@ -21,8 +21,8 @@ #define MPD_DB_UTILS_H #include "gcc.h" +#include "gerror.h" -#include <glib.h> #include <stdbool.h> struct locate_item_list; @@ -37,20 +37,4 @@ bool addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, GError **error_r); -gcc_nonnull(1,2,3) -bool -findAddIn(struct player_control *pc, const char *name, - const struct locate_item_list *criteria, GError **error_r); - -gcc_nonnull(1,2,3) -bool -search_add_songs(struct player_control *pc, const char *uri, - const struct locate_item_list *criteria, GError **error_r); - -gcc_nonnull(1,2,3) -bool -search_add_to_playlist(const char *uri, const char *path_utf8, - const struct locate_item_list *criteria, - GError **error_r); - #endif diff --git a/src/db_plugin.h b/src/db_plugin.h deleted file mode 100644 index 1c7e14ede..000000000 --- a/src/db_plugin.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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 deleted file mode 100644 index 4d7e3f5ef..000000000 --- a/src/db_print.c +++ /dev/null @@ -1,393 +0,0 @@ -/* - * 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 "playlist_vector.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 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_song(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - assert(song != NULL); - assert(song->parent != NULL); - - struct client *client = data; - song_print_uri(client, song); - - if (song->tag != NULL && song->tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, song->parent, - song->uri); - - return true; -} - -static bool -print_visitor_song_info(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - assert(song != NULL); - assert(song->parent != NULL); - - struct client *client = data; - song_print_info(client, song); - - if (song->tag != NULL && song->tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, song->parent, - song->uri); - - return true; -} - -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_save.h b/src/db_save.h index e760ec881..d232d7331 100644 --- a/src/db_save.h +++ b/src/db_save.h @@ -20,7 +20,8 @@ #ifndef MPD_DB_SAVE_H #define MPD_DB_SAVE_H -#include <glib.h> +#include "gerror.h" + #include <stdbool.h> #include <stdio.h> diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h index 0d90ba656..c898a47cf 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/_flac_common.h @@ -27,8 +27,6 @@ #include "decoder_api.h" #include "pcm_buffer.h" -#include <glib.h> - #include <FLAC/stream_decoder.h> #include <FLAC/metadata.h> diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx index 5d162f179..de2e599e9 100644 --- a/src/decoder/sidplay_decoder_plugin.cxx +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -104,7 +104,7 @@ sidplay_init(const struct config_param *param) return true; } -void +static void sidplay_finish() { g_pattern_spec_free(path_with_subtune); @@ -136,7 +136,7 @@ get_container_name(const char *path_fs) * returns tune number from file.sid/tune_xxx.sid style path or 1 if * no subtune is appended */ -static int +static unsigned get_song_num(const char *path_fs) { if(g_pattern_match(path_with_subtune, @@ -172,7 +172,7 @@ get_song_length(const char *path_fs) char md5sum[SIDTUNE_MD5_LENGTH+1]; tune.createMD5(md5sum); - int song_num=get_song_num(path_fs); + const unsigned song_num = get_song_num(path_fs); gsize num_items; gchar **values=g_key_file_get_string_list(songlength_database, @@ -330,7 +330,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) decoder_command_finished(decoder); } - if (song_len > 0 && player.time() >= song_len) + if (song_len > 0 && player.time() >= (unsigned)song_len) break; } while (cmd != DECODE_COMMAND_STOP); diff --git a/src/decoder_control.c b/src/decoder_control.c index 70f34b331..afcb16cdf 100644 --- a/src/decoder_control.c +++ b/src/decoder_control.c @@ -20,6 +20,7 @@ #include "config.h" #include "decoder_control.h" #include "pipe.h" +#include "song.h" #include <assert.h> @@ -40,6 +41,8 @@ dc_new(GCond *client_cond) dc->state = DECODE_STATE_STOP; dc->command = DECODE_COMMAND_NONE; + dc->song = NULL; + dc->replay_gain_db = 0; dc->replay_gain_prev_db = 0; dc->mixramp_start = NULL; @@ -52,6 +55,11 @@ dc_new(GCond *client_cond) void dc_free(struct decoder_control *dc) { + dc_clear_error(dc); + + if (dc->song != NULL) + song_free(dc->song); + g_cond_free(dc->cond); g_mutex_free(dc->mutex); g_free(dc->mixramp_start); @@ -79,6 +87,7 @@ static void dc_command(struct decoder_control *dc, enum decoder_command cmd) { decoder_lock(dc); + dc_clear_error(dc); dc_command_locked(dc, cmd); decoder_unlock(dc); } @@ -94,6 +103,27 @@ dc_command_async(struct decoder_control *dc, enum decoder_command cmd) decoder_unlock(dc); } +bool +decoder_is_current_song(const struct decoder_control *dc, + const struct song *song) +{ + assert(dc != NULL); + assert(song != NULL); + + switch (dc->state) { + case DECODE_STATE_STOP: + case DECODE_STATE_ERROR: + return false; + + case DECODE_STATE_START: + case DECODE_STATE_DECODE: + return song_equals(dc->song, song); + } + + assert(false); + return false; +} + void dc_start(struct decoder_control *dc, struct song *song, unsigned start_ms, unsigned end_ms, @@ -104,6 +134,9 @@ dc_start(struct decoder_control *dc, struct song *song, assert(pipe != NULL); assert(music_pipe_empty(pipe)); + if (dc->song != NULL) + song_free(dc->song); + dc->song = song; dc->start_ms = start_ms; dc->end_ms = end_ms; diff --git a/src/decoder_control.h b/src/decoder_control.h index 566b153ee..9ecbde73e 100644 --- a/src/decoder_control.h +++ b/src/decoder_control.h @@ -67,6 +67,14 @@ struct decoder_control { enum decoder_state state; enum decoder_command command; + /** + * The error that occurred in the decoder thread. This + * attribute is only valid if #state is #DECODE_STATE_ERROR. + * The object must be freed when this object transitions to + * any other state (usually #DECODE_STATE_START). + */ + GError *error; + bool quit; bool seek_error; bool seekable; @@ -82,8 +90,11 @@ struct decoder_control { * The song currently being decoded. This attribute is set by * the player thread, when it sends the #DECODE_COMMAND_START * command. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. */ - const struct song *song; + struct song *song; /** * The initial seek position (in milliseconds), e.g. to the @@ -188,6 +199,50 @@ decoder_has_failed(const struct decoder_control *dc) return dc->state == DECODE_STATE_ERROR; } +/** + * Checks whether an error has occurred, and if so, returns a newly + * allocated copy of the #GError object. + * + * Caller must lock the object. + */ +static inline GError * +dc_get_error(const struct decoder_control *dc) +{ + assert(dc != NULL); + assert(dc->command == DECODE_COMMAND_NONE); + assert(dc->state != DECODE_STATE_ERROR || dc->error != NULL); + + return dc->state == DECODE_STATE_ERROR + ? g_error_copy(dc->error) + : NULL; +} + +/** + * Like dc_get_error(), but locks and unlocks the object. + */ +static inline GError * +dc_lock_get_error(struct decoder_control *dc) +{ + decoder_lock(dc); + GError *error = dc_get_error(dc); + decoder_unlock(dc); + return error; +} + +/** + * Clear the error condition and free the #GError object (if any). + * + * Caller must lock the object. + */ +static inline void +dc_clear_error(struct decoder_control *dc) +{ + if (dc->state == DECODE_STATE_ERROR) { + g_error_free(dc->error); + dc->state = DECODE_STATE_STOP; + } +} + static inline bool decoder_lock_is_idle(struct decoder_control *dc) { @@ -224,28 +279,35 @@ decoder_lock_has_failed(struct decoder_control *dc) return ret; } -static inline const struct song * -decoder_current_song(const struct decoder_control *dc) -{ - switch (dc->state) { - case DECODE_STATE_STOP: - case DECODE_STATE_ERROR: - return NULL; - - case DECODE_STATE_START: - case DECODE_STATE_DECODE: - return dc->song; - } +/** + * Check if the specified song is currently being decoded. If the + * decoder is not running currently (or being started), then this + * function returns false in any case. + * + * Caller must lock the object. + */ +gcc_pure +bool +decoder_is_current_song(const struct decoder_control *dc, + const struct song *song); - assert(false); - return NULL; +gcc_pure +static inline bool +decoder_lock_is_current_song(struct decoder_control *dc, + const struct song *song) +{ + decoder_lock(dc); + const bool result = decoder_is_current_song(dc, song); + decoder_unlock(dc); + return result; } /** * Start the decoder. * * @param the decoder - * @param song the song to be decoded + * @param song the song to be decoded; the given instance will be + * owned and freed by the decoder * @param start_ms see #decoder_control * @param end_ms see #decoder_control * @param pipe the pipe which receives the decoded chunks (owned by diff --git a/src/decoder_error.h b/src/decoder_error.h new file mode 100644 index 000000000..a12a31937 --- /dev/null +++ b/src/decoder_error.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2012 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_DECODER_ERROR_H +#define MPD_DECODER_ERROR_H + +#include <glib.h> + +/** + * Quark for GError.domain. + */ +G_GNUC_CONST +static inline GQuark +decoder_quark(void) +{ + return g_quark_from_static_string("decoder"); +} + +#endif diff --git a/src/decoder_thread.c b/src/decoder_thread.c index 1440fc272..46349f53b 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -19,6 +19,7 @@ #include "config.h" #include "decoder_thread.h" +#include "decoder_error.h" #include "decoder_control.h" #include "decoder_internal.h" #include "decoder_list.h" @@ -428,12 +429,27 @@ decoder_run_song(struct decoder_control *dc, decoder_lock(dc); - dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; + if (ret) + dc->state = DECODE_STATE_STOP; + else { + dc->state = DECODE_STATE_ERROR; + + const char *error_uri = song->uri; + char *allocated = uri_remove_auth(error_uri); + if (allocated != NULL) + error_uri = allocated; + + dc->error = g_error_new(decoder_quark(), 0, + "Failed to decode %s", error_uri); + g_free(allocated); + } } static void decoder_run(struct decoder_control *dc) { + dc_clear_error(dc); + const struct song *song = dc->song; char *uri; @@ -446,6 +462,9 @@ decoder_run(struct decoder_control *dc) if (uri == NULL) { dc->state = DECODE_STATE_ERROR; + dc->error = g_error_new(decoder_quark(), 0, + "Failed to map song"); + decoder_command_finished_locked(dc); return; } diff --git a/src/directory.h b/src/directory.h index b3cd9c8c9..d569fee9e 100644 --- a/src/directory.h +++ b/src/directory.h @@ -22,6 +22,11 @@ #include "check.h" #include "util/list.h" +#include "gcc.h" + +#ifdef __cplusplus +#include "DatabaseVisitor.hxx" +#endif #include <glib.h> #include <stdbool.h> @@ -50,6 +55,7 @@ struct song; struct db_visitor; +struct locate_item_list; struct directory { /** @@ -86,8 +92,20 @@ struct directory { dev_t device; bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */ char path[sizeof(long)]; + +#ifdef __cplusplus + /** + * Caller must lock #db_mutex. + */ + bool Walk(bool recursive, const locate_item_list *match, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const; +#endif }; +G_BEGIN_DECLS + static inline bool isRootDirectory(const char *name) { @@ -97,14 +115,14 @@ isRootDirectory(const char *name) /** * Generic constructor for #directory object. */ -G_GNUC_MALLOC +gcc_malloc struct directory * directory_new(const char *dirname, struct directory *parent); /** * Create a new root #directory object. */ -G_GNUC_MALLOC +gcc_malloc static inline struct directory * directory_new_root(void) { @@ -153,14 +171,14 @@ directory_is_root(const struct directory *directory) /** * Returns the base name of the directory. */ -G_GNUC_PURE +gcc_pure const char * directory_get_name(const struct directory *directory); /** * Caller must lock the #db_mutex. */ -G_GNUC_PURE +gcc_pure struct directory * directory_get_child(const struct directory *directory, const char *name); @@ -172,7 +190,7 @@ directory_get_child(const struct directory *directory, const char *name); * @param parent the parent directory the new one will be added to * @param name_utf8 the UTF-8 encoded name of the new sub directory */ -G_GNUC_MALLOC +gcc_malloc struct directory * directory_new_child(struct directory *parent, const char *name_utf8); @@ -227,7 +245,7 @@ directory_remove_song(struct directory *directory, struct song *song); * * Caller must lock the #db_mutex. */ -G_GNUC_PURE +gcc_pure struct song * directory_get_song(const struct directory *directory, const char *name_utf8); @@ -251,12 +269,6 @@ directory_lookup_song(struct directory *directory, const char *uri); void directory_sort(struct directory *directory); -/** - * Caller must lock #db_mutex. - */ -bool -directory_walk(const struct directory *directory, bool recursive, - const struct db_visitor *visitor, void *ctx, - GError **error_r); +G_END_DECLS #endif diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h index 33a379115..68a89d573 100644 --- a/src/encoder_plugin.h +++ b/src/encoder_plugin.h @@ -20,7 +20,7 @@ #ifndef MPD_ENCODER_PLUGIN_H #define MPD_ENCODER_PLUGIN_H -#include <glib.h> +#include "gerror.h" #include <assert.h> #include <stdbool.h> diff --git a/src/event_pipe.h b/src/event_pipe.h index 3734bb86c..3749ccf79 100644 --- a/src/event_pipe.h +++ b/src/event_pipe.h @@ -20,8 +20,6 @@ #ifndef EVENT_PIPE_H #define EVENT_PIPE_H -#include <glib.h> - enum pipe_event { /** database update was finished */ PIPE_EVENT_UPDATE, diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c index e7c998827..7728c55bf 100644 --- a/src/filter/null_filter_plugin.c +++ b/src/filter/null_filter_plugin.c @@ -29,6 +29,7 @@ #include "filter_internal.h" #include "filter_registry.h" +#include <glib.h> #include <assert.h> struct null_filter { diff --git a/src/filter_plugin.h b/src/filter_plugin.h index 58e34dfb2..d45faee1f 100644 --- a/src/filter_plugin.h +++ b/src/filter_plugin.h @@ -26,7 +26,7 @@ #ifndef MPD_FILTER_PLUGIN_H #define MPD_FILTER_PLUGIN_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> #include <stddef.h> @@ -32,6 +32,9 @@ */ #if GCC_CHECK_VERSION(3,0) +# define gcc_const __attribute__((const)) +# define gcc_pure __attribute__((pure)) +# define gcc_malloc __attribute__((malloc)) # define gcc_must_check __attribute__ ((warn_unused_result)) # define gcc_packed __attribute__ ((packed)) /* these are very useful for type checking */ @@ -41,11 +44,21 @@ # define gcc_fprintf__ __attribute__ ((format(printf,4,5))) # define gcc_scanf __attribute__ ((format(scanf,1,2))) # define gcc_used __attribute__ ((used)) +# define gcc_unused __attribute__((unused)) +# define gcc_warn_unused_result __attribute__((warn_unused_result)) /* # define inline inline __attribute__ ((always_inline)) */ # define gcc_noinline __attribute__ ((noinline)) # define gcc_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) # define gcc_nonnull_all __attribute__((nonnull)) + +# define gcc_likely(x) __builtin_expect (!!(x), 1) +# define gcc_unlikely(x) __builtin_expect (!!(x), 0) + #else +# define gcc_unused +# define gcc_const +# define gcc_pure +# define gcc_malloc # define gcc_must_check # define gcc_packed # define gcc_printf @@ -54,10 +67,29 @@ # define gcc_fprintf__ # define gcc_scanf # define gcc_used +# define gcc_unused +# define gcc_warn_unused_result /* # define inline */ # define gcc_noinline # define gcc_nonnull(...) # define gcc_nonnull_all + +# define gcc_likely(x) (x) +# define gcc_unlikely(x) (x) + +#endif + +#ifdef __cplusplus + +#if !defined(__clang__) && defined(__GNUC__) && !GCC_CHECK_VERSION(4,5) +#error Your gcc version is too old. MPD requires gcc 4.5 or newer. +#endif + +/* support for C++11 "override" was added in gcc 4.7 */ +#if !defined(__clang__) && defined(__GNUC__) && !GCC_CHECK_VERSION(4,7) +#define override +#endif + #endif #endif /* MPD_GCC_H */ diff --git a/src/gerror.h b/src/gerror.h new file mode 100644 index 000000000..fe4c54da9 --- /dev/null +++ b/src/gerror.h @@ -0,0 +1,25 @@ +/* + * 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_GERROR_H +#define MPD_GERROR_H + +typedef struct _GError GError; + +#endif diff --git a/src/inotify_source.c b/src/inotify_source.c index e415f5e72..2fbfdff7e 100644 --- a/src/inotify_source.c +++ b/src/inotify_source.c @@ -23,6 +23,8 @@ #include "fd_util.h" #include "mpd_error.h" +#include <glib.h> + #include <sys/inotify.h> #include <unistd.h> #include <errno.h> diff --git a/src/inotify_source.h b/src/inotify_source.h index f92e18e39..7aec18b3d 100644 --- a/src/inotify_source.h +++ b/src/inotify_source.h @@ -20,7 +20,7 @@ #ifndef MPD_INOTIFY_SOURCE_H #define MPD_INOTIFY_SOURCE_H -#include <glib.h> +#include "gerror.h" typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, const char *name, void *ctx); diff --git a/src/input_init.h b/src/input_init.h index ad92cda08..1a73e5ef9 100644 --- a/src/input_init.h +++ b/src/input_init.h @@ -20,9 +20,8 @@ #ifndef MPD_INPUT_INIT_H #define MPD_INPUT_INIT_H -#include "check.h" +#include "gerror.h" -#include <glib.h> #include <stdbool.h> /** diff --git a/src/listen.h b/src/listen.h index 246e83706..100fe252e 100644 --- a/src/listen.h +++ b/src/listen.h @@ -20,7 +20,7 @@ #ifndef MPD_LISTEN_H #define MPD_LISTEN_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> diff --git a/src/locate.c b/src/locate.c index 9645da9cd..3cf842e90 100644 --- a/src/locate.c +++ b/src/locate.c @@ -25,13 +25,35 @@ #include <glib.h> +#include <assert.h> #include <stdlib.h> #define LOCATE_TAG_FILE_KEY "file" #define LOCATE_TAG_FILE_KEY_OLD "filename" #define LOCATE_TAG_ANY_KEY "any" -int +/* struct used for search, find, list queries */ +struct locate_item { + uint8_t tag; + + bool fold_case; + + /* what we are looking for */ + char *needle; +}; + +/** + * An array of struct locate_item objects. + */ +struct locate_item_list { + /** number of items */ + unsigned length; + + /** this is a variable length array */ + struct locate_item items[1]; +}; + +unsigned locate_parse_type(const char *str) { if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) || @@ -41,23 +63,23 @@ locate_parse_type(const char *str) if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY)) return LOCATE_TAG_ANY_TYPE; - enum tag_type i = tag_name_parse_i(str); - if (i != TAG_NUM_OF_ITEM_TYPES) - return i; - - return -1; + return tag_name_parse_i(str); } static bool locate_item_init(struct locate_item *item, - const char *type_string, const char *needle) + const char *type_string, const char *needle, + bool fold_case) { item->tag = locate_parse_type(type_string); - if (item->tag < 0) + if (item->tag == TAG_NUM_OF_ITEM_TYPES) return false; - item->needle = g_strdup(needle); + item->fold_case = fold_case; + item->needle = fold_case + ? g_utf8_casefold(needle, -1) + : g_strdup(needle); return true; } @@ -71,28 +93,38 @@ locate_item_list_free(struct locate_item_list *list) g_free(list); } -struct locate_item_list * +static struct locate_item_list * locate_item_list_new(unsigned length) { struct locate_item_list *list = - g_malloc0(sizeof(*list) - sizeof(list->items[0]) + - length * sizeof(list->items[0])); + g_malloc(sizeof(*list) - sizeof(list->items[0]) + + length * sizeof(list->items[0])); list->length = length; return list; } struct locate_item_list * -locate_item_list_parse(char *argv[], int argc) +locate_item_list_new_single(unsigned tag, const char *needle) { - if (argc % 2 != 0) + struct locate_item_list *list = locate_item_list_new(1); + list->items[0].tag = tag; + list->items[0].fold_case = false; + list->items[0].needle = g_strdup(needle); + return list; +} + +struct locate_item_list * +locate_item_list_parse(char *argv[], unsigned argc, bool fold_case) +{ + if (argc == 0 || argc % 2 != 0) return NULL; struct locate_item_list *list = locate_item_list_new(argc / 2); for (unsigned i = 0; i < list->length; ++i) { if (!locate_item_init(&list->items[i], argv[i * 2], - argv[i * 2 + 1])) { + argv[i * 2 + 1], fold_case)) { locate_item_list_free(list); return NULL; } @@ -101,61 +133,41 @@ locate_item_list_parse(char *argv[], int argc) return list; } -struct locate_item_list * -locate_item_list_casefold(const struct locate_item_list *list) +gcc_pure +static bool +locate_string_match(const struct locate_item *item, const char *value) { - struct locate_item_list *new_list = locate_item_list_new(list->length); + assert(item != NULL); + assert(value != NULL); - for (unsigned i = 0; i < list->length; i++){ - new_list->items[i].needle = - g_utf8_casefold(list->items[i].needle, -1); - new_list->items[i].tag = list->items[i].tag; + if (item->fold_case) { + char *p = g_utf8_casefold(value, -1); + const bool result = strstr(p, item->needle) != NULL; + g_free(p); + return result; + } else { + return strcmp(value, item->needle) == 0; } - - return new_list; -} - -void -locate_item_free(struct locate_item *item) -{ - g_free(item->needle); - g_free(item); } +gcc_pure static bool -locate_tag_search(const struct song *song, enum tag_type type, const char *str) +locate_tag_match(const struct locate_item *item, const struct tag *tag) { - bool ret = false; - - if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) { - char *uri = song_get_uri(song); - char *p = g_utf8_casefold(uri, -1); - g_free(uri); - - if (strstr(p, str)) - ret = true; - g_free(p); - if (ret == 1 || type == LOCATE_TAG_FILE_TYPE) - return ret; - } - - if (!song->tag) - return false; + assert(item != NULL); + assert(tag != NULL); bool visited_types[TAG_NUM_OF_ITEM_TYPES]; memset(visited_types, 0, sizeof(visited_types)); - for (unsigned i = 0; i < song->tag->num_items && !ret; i++) { - visited_types[song->tag->items[i]->type] = true; - if (type != LOCATE_TAG_ANY_TYPE && - song->tag->items[i]->type != type) { + for (unsigned i = 0; i < tag->num_items; i++) { + visited_types[tag->items[i]->type] = true; + if (item->tag != LOCATE_TAG_ANY_TYPE && + tag->items[i]->type != item->tag) continue; - } - char *duplicate = g_utf8_casefold(song->tag->items[i]->value, -1); - if (*str && strstr(duplicate, str)) - ret = true; - g_free(duplicate); + if (locate_string_match(item, tag->items[i]->value)) + return true; } /** If the search critieron was not visited during the sweep @@ -164,75 +176,36 @@ locate_tag_search(const struct song *song, enum tag_type type, const char *str) * empty (first char is a \0), then it's a match as well and * we should return true. */ - if (!*str && !visited_types[type]) + if (*item->needle == 0 && item->tag != LOCATE_TAG_ANY_TYPE && + !visited_types[item->tag]) return true; - return ret; -} - -bool -locate_song_search(const struct song *song, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < criteria->length; i++) - if (!locate_tag_search(song, criteria->items[i].tag, - criteria->items[i].needle)) - return false; - - return true; + return false; } +gcc_pure static bool -locate_tag_match(const struct song *song, enum tag_type type, const char *str) +locate_song_match(const struct locate_item *item, const struct song *song) { - if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) { + if (item->tag == LOCATE_TAG_FILE_TYPE || + item->tag == LOCATE_TAG_ANY_TYPE) { char *uri = song_get_uri(song); - bool matches = strcmp(str, uri) == 0; + const bool result = locate_string_match(item, uri); g_free(uri); - if (matches) - return true; - - if (type == LOCATE_TAG_FILE_TYPE) - return false; + if (result || item->tag == LOCATE_TAG_FILE_TYPE) + return result; } - if (!song->tag) - return false; - - bool visited_types[TAG_NUM_OF_ITEM_TYPES]; - memset(visited_types, 0, sizeof(visited_types)); - - for (unsigned i = 0; i < song->tag->num_items; i++) { - visited_types[song->tag->items[i]->type] = true; - if (type != LOCATE_TAG_ANY_TYPE && - song->tag->items[i]->type != type) { - continue; - } - - if (0 == strcmp(str, song->tag->items[i]->value)) - return true; - } - - /** If the search critieron was not visited during the sweep - * through the song's tag, it means this field is absent from - * the tag or empty. Thus, if the searched string is also - * empty (first char is a \0), then it's a match as well and - * we should return true. - */ - if (!*str && !visited_types[type]) - return true; - - return false; + return song->tag != NULL && locate_tag_match(item, song->tag); } bool -locate_song_match(const struct song *song, - const struct locate_item_list *criteria) +locate_list_song_match(const struct song *song, + const struct locate_item_list *criteria) { for (unsigned i = 0; i < criteria->length; i++) - if (!locate_tag_match(song, criteria->items[i].tag, - criteria->items[i].needle)) + if (!locate_song_match(&criteria->items[i], song)) return false; return true; diff --git a/src/locate.h b/src/locate.h index ec20ded24..c012a9c63 100644 --- a/src/locate.h +++ b/src/locate.h @@ -28,65 +28,33 @@ #define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 #define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 +struct locate_item_list; struct song; -/* struct used for search, find, list queries */ -struct locate_item { - int8_t tag; - /* what we are looking for */ - char *needle; -}; - /** - * An array of struct locate_item objects. + * @return #TAG_NUM_OF_ITEM_TYPES on error */ -struct locate_item_list { - /** number of items */ - unsigned length; - - /** this is a variable length array */ - struct locate_item items[1]; -}; - -int +gcc_pure +unsigned locate_parse_type(const char *str); -/** - * Allocates a new struct locate_item_list, and initializes all - * members with zero bytes. - */ +gcc_malloc struct locate_item_list * -locate_item_list_new(unsigned length); +locate_item_list_new_single(unsigned tag, const char *needle); /* return number of items or -1 on error */ gcc_nonnull(1) struct locate_item_list * -locate_item_list_parse(char *argv[], int argc); - -/** - * Duplicate the struct locate_item_list object and convert all - * needles with g_utf8_casefold(). - */ -gcc_nonnull(1) -struct locate_item_list * -locate_item_list_casefold(const struct locate_item_list *list); +locate_item_list_parse(char *argv[], unsigned argc, bool fold_case); gcc_nonnull(1) void locate_item_list_free(struct locate_item_list *list); -gcc_nonnull(1) -void -locate_item_free(struct locate_item *item); - -gcc_nonnull(1,2) -bool -locate_song_search(const struct song *song, - const struct locate_item_list *criteria); - +gcc_pure gcc_nonnull(1,2) bool -locate_song_match(const struct song *song, - const struct locate_item_list *criteria); +locate_list_song_match(const struct song *song, + const struct locate_item_list *criteria); #endif @@ -22,6 +22,8 @@ #include "uri.h" #include "client.h" +#include <glib.h> + #include <assert.h> #include <string.h> diff --git a/src/main.c b/src/main.c index 12f8d86f6..fea31782f 100644 --- a/src/main.c +++ b/src/main.c @@ -51,10 +51,8 @@ #include "playlist_list.h" #include "state_file.h" #include "tag.h" -#include "dbUtils.h" #include "zeroconf.h" #include "event_pipe.h" -#include "tag_pool.h" #include "mpd_error.h" #ifdef ENABLE_INOTIFY @@ -153,31 +151,47 @@ glue_mapper_init(GError **error_r) static bool glue_db_init_and_load(void) { + const struct config_param *param = config_get_param("database"); const struct config_param *path = config_get_param(CONF_DB_FILE); + if (param != NULL && path != NULL) + g_message("Found both 'database' and '" CONF_DB_FILE + "' setting - ignoring the latter"); + GError *error = NULL; bool ret; if (!mapper_has_music_directory()) { + if (param != NULL) + g_message("Found database setting without " + CONF_MUSIC_DIR " - disabling database"); if (path != NULL) g_message("Found " CONF_DB_FILE " setting without " CONF_MUSIC_DIR " - disabling database"); - db_init(NULL, NULL); return true; } - if (path == NULL) - MPD_ERROR(CONF_DB_FILE " setting missing"); + struct config_param *allocated = NULL; - if (!db_init(path, &error)) + if (param == NULL && path != NULL) { + allocated = config_new_param("database", path->line); + config_add_block_param(allocated, "path", + path->value, path->line); + param = allocated; + } + + if (!db_init(param, &error)) MPD_ERROR("%s", error->message); + if (allocated != NULL) + config_param_free(allocated); + ret = db_load(&error); if (!ret) MPD_ERROR("%s", error->message); /* run database update after daemonization? */ - return db_exists(); + return !db_is_simple() || db_exists(); } /** @@ -342,7 +356,6 @@ int mpd_main(int argc, char *argv[]) io_thread_init(); winsock_init(); idle_init(); - tag_pool_init(); config_global_init(); success = parse_cmdline(argc, argv, &options, &error); @@ -527,7 +540,6 @@ int mpd_main(int argc, char *argv[]) archive_plugin_deinit_all(); #endif config_global_finish(); - tag_pool_deinit(); idle_deinit(); stats_global_finish(); io_thread_deinit(); diff --git a/src/mapper.c b/src/mapper.c index 7db74b1af..1f8a54b46 100644 --- a/src/mapper.c +++ b/src/mapper.c @@ -219,13 +219,32 @@ map_directory_child_fs(const struct directory *directory, const char *name) return path; } +/** + * Map a song object that was created by song_dup_detached(). It does + * not have a real parent directory, only the dummy object + * #detached_root. + */ +static char * +map_detached_song_fs(const char *uri_utf8) +{ + char *uri_fs = utf8_to_fs_charset(uri_utf8); + if (uri_fs == NULL) + return NULL; + + char *path = g_build_filename(music_dir_fs, uri_fs, NULL); + g_free(uri_fs); + return path; +} + char * map_song_fs(const struct song *song) { assert(song_is_file(song)); if (song_in_database(song)) - return map_directory_child_fs(song->parent, song->uri); + return song_is_detached(song) + ? map_detached_song_fs(song->uri) + : map_directory_child_fs(song->parent, song->uri); else return utf8_to_fs_charset(song->uri); } diff --git a/src/mapper.h b/src/mapper.h index d6184a175..b4e314569 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -24,8 +24,11 @@ #ifndef MPD_MAPPER_H #define MPD_MAPPER_H -#include <glib.h> +#include "gcc.h" +#include "gerror.h" + #include <stdbool.h> +#include <stddef.h> #define PLAYLIST_FILE_SUFFIX ".m3u" @@ -39,7 +42,7 @@ void mapper_finish(void); /** * Return the absolute path of the music directory encoded in UTF-8. */ -G_GNUC_CONST +gcc_const const char * mapper_get_music_directory_utf8(void); @@ -47,14 +50,14 @@ mapper_get_music_directory_utf8(void); * Return the absolute path of the music directory encoded in the * filesystem character set. */ -G_GNUC_CONST +gcc_const const char * mapper_get_music_directory_fs(void); /** * Returns true if a music directory was configured. */ -G_GNUC_CONST +gcc_const static inline bool mapper_has_music_directory(void) { @@ -66,7 +69,7 @@ mapper_has_music_directory(void) * this function converts it to a relative path. If not, it returns * the unmodified string pointer. */ -G_GNUC_PURE +gcc_pure const char * map_to_relative_path(const char *path_utf8); @@ -75,7 +78,7 @@ map_to_relative_path(const char *path_utf8); * is basically done by converting the URI to the file system charset * and prepending the music directory. */ -G_GNUC_MALLOC +gcc_malloc char * map_uri_fs(const char *uri); @@ -85,7 +88,7 @@ map_uri_fs(const char *uri); * @param directory the directory object * @return the path in file system encoding, or NULL if mapping failed */ -G_GNUC_MALLOC +gcc_malloc char * map_directory_fs(const struct directory *directory); @@ -97,7 +100,7 @@ map_directory_fs(const struct directory *directory); * @param name the child's name in UTF-8 * @return the path in file system encoding, or NULL if mapping failed */ -G_GNUC_MALLOC +gcc_malloc char * map_directory_child_fs(const struct directory *directory, const char *name); @@ -108,7 +111,7 @@ map_directory_child_fs(const struct directory *directory, const char *name); * @param song the song object * @return the path in file system encoding, or NULL if mapping failed */ -G_GNUC_MALLOC +gcc_malloc char * map_song_fs(const struct song *song); @@ -119,14 +122,14 @@ map_song_fs(const struct song *song); * @param path_fs a path in file system encoding * @return the relative path in UTF-8, or NULL if mapping failed */ -G_GNUC_MALLOC +gcc_malloc char * map_fs_to_utf8(const char *path_fs); /** * Returns the playlist directory. */ -G_GNUC_CONST +gcc_const const char * map_spl_path(void); @@ -137,7 +140,7 @@ map_spl_path(void); * * @return the path in file system encoding, or NULL if mapping failed */ -G_GNUC_PURE +gcc_pure char * map_spl_utf8_to_fs(const char *name); diff --git a/src/mixer_control.h b/src/mixer_control.h index 6c3468aca..307298e47 100644 --- a/src/mixer_control.h +++ b/src/mixer_control.h @@ -25,7 +25,7 @@ #ifndef MPD_MIXER_CONTROL_H #define MPD_MIXER_CONTROL_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h index 9532b95cb..2f3beed1d 100644 --- a/src/mixer_plugin.h +++ b/src/mixer_plugin.h @@ -27,7 +27,7 @@ #ifndef MPD_MIXER_PLUGIN_H #define MPD_MIXER_PLUGIN_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h index 739163f42..f0df829db 100644 --- a/src/output/httpd_client.h +++ b/src/output/httpd_client.h @@ -20,9 +20,8 @@ #ifndef MPD_OUTPUT_HTTPD_CLIENT_H #define MPD_OUTPUT_HTTPD_CLIENT_H -#include <glib.h> - #include <stdbool.h> +#include <stddef.h> struct httpd_client; struct httpd_output; diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 02a51f27b..b285b5e4d 100644 --- a/src/output/pulse_output_plugin.h +++ b/src/output/pulse_output_plugin.h @@ -20,9 +20,9 @@ #ifndef MPD_PULSE_OUTPUT_PLUGIN_H #define MPD_PULSE_OUTPUT_PLUGIN_H -#include <stdbool.h> +#include "gerror.h" -#include <glib.h> +#include <stdbool.h> struct pulse_output; struct pulse_mixer; diff --git a/src/output_all.c b/src/output_all.c index f56cd04ee..b2ef1561f 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -19,6 +19,7 @@ #include "config.h" #include "output_all.h" +#include "output_error.h" #include "output_internal.h" #include "output_control.h" #include "chunk.h" @@ -270,7 +271,7 @@ audio_output_all_update(void) } bool -audio_output_all_play(struct music_chunk *chunk) +audio_output_all_play(struct music_chunk *chunk, GError **error_r) { bool ret; unsigned int i; @@ -281,8 +282,12 @@ audio_output_all_play(struct music_chunk *chunk) assert(music_chunk_check_format(chunk, &input_audio_format)); ret = audio_output_all_update(); - if (!ret) + if (!ret) { + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); return false; + } music_pipe_push(g_mp, chunk); @@ -294,7 +299,8 @@ audio_output_all_play(struct music_chunk *chunk) bool audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer) + struct music_buffer *buffer, + GError **error_r) { bool ret = false, enabled = false; unsigned int i; @@ -334,7 +340,12 @@ audio_output_all_open(const struct audio_format *audio_format, } if (!enabled) - g_warning("All audio outputs are disabled"); + g_set_error(error_r, output_quark(), 0, + "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); if (!ret) /* close all devices if there was an error */ diff --git a/src/output_all.h b/src/output_all.h index 4eeb94f13..00864c9ba 100644 --- a/src/output_all.h +++ b/src/output_all.h @@ -26,6 +26,8 @@ #ifndef OUTPUT_ALL_H #define OUTPUT_ALL_H +#include "gerror.h" + #include <stdbool.h> #include <stddef.h> @@ -84,7 +86,8 @@ audio_output_all_enable_disable(void); */ bool audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer); + struct music_buffer *buffer, + GError **error_r); /** * Closes all audio outputs. @@ -108,7 +111,7 @@ audio_output_all_release(void); * (all closed then) */ bool -audio_output_all_play(struct music_chunk *chunk); +audio_output_all_play(struct music_chunk *chunk, GError **error_r); /** * Checks if the output devices have drained their music pipe, and diff --git a/src/output_error.h b/src/output_error.h new file mode 100644 index 000000000..ccc784f89 --- /dev/null +++ b/src/output_error.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2012 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_OUTPUT_ERROR_H +#define MPD_OUTPUT_ERROR_H + +#include <glib.h> + +/** + * Quark for GError.domain. + */ +G_GNUC_CONST +static inline GQuark +output_quark(void) +{ + return g_quark_from_static_string("output"); +} + +#endif diff --git a/src/output_plugin.h b/src/output_plugin.h index 209ca6221..a47296566 100644 --- a/src/output_plugin.h +++ b/src/output_plugin.h @@ -20,7 +20,8 @@ #ifndef MPD_OUTPUT_PLUGIN_H #define MPD_OUTPUT_PLUGIN_H -#include <glib.h> +#include "gcc.h" +#include "gerror.h" #include <stdbool.h> #include <stddef.h> @@ -165,7 +166,7 @@ ao_plugin_test_default_device(const struct audio_output_plugin *plugin) : false; } -G_GNUC_MALLOC +gcc_malloc struct audio_output * ao_plugin_init(const struct audio_output_plugin *plugin, const struct config_param *param, @@ -187,7 +188,7 @@ ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, void ao_plugin_close(struct audio_output *ao); -G_GNUC_PURE +gcc_pure unsigned ao_plugin_delay(struct audio_output *ao); diff --git a/src/pcm_mix.h b/src/pcm_mix.h index 0e58d01ee..0cf557680 100644 --- a/src/pcm_mix.h +++ b/src/pcm_mix.h @@ -21,6 +21,7 @@ #define PCM_MIX_H #include "audio_format.h" +#include "gcc.h" #include <stdbool.h> #include <stddef.h> @@ -41,7 +42,7 @@ * * @return true on success, false if the format is not supported */ -G_GNUC_WARN_UNUSED_RESULT +gcc_warn_unused_result bool pcm_mix(void *buffer1, const void *buffer2, size_t size, enum sample_format format, float portion1); diff --git a/src/pcm_volume.h b/src/pcm_volume.h index 64e3c7641..4a4a4e45a 100644 --- a/src/pcm_volume.h +++ b/src/pcm_volume.h @@ -25,6 +25,7 @@ #include <stdint.h> #include <stdbool.h> +#include <stddef.h> enum { /** this value means "100% volume" */ diff --git a/src/pipe.h b/src/pipe.h index 84b9869e0..6b5bbf0c7 100644 --- a/src/pipe.h +++ b/src/pipe.h @@ -20,7 +20,8 @@ #ifndef MPD_PIPE_H #define MPD_PIPE_H -#include <glib.h> +#include "gcc.h" + #include <stdbool.h> #ifndef NDEBUG @@ -39,7 +40,7 @@ struct music_pipe; /** * Creates a new #music_pipe object. It is empty. */ -G_GNUC_MALLOC +gcc_malloc struct music_pipe * music_pipe_new(void); @@ -72,7 +73,7 @@ music_pipe_contains(const struct music_pipe *mp, * Returns the first #music_chunk from the pipe. Returns NULL if the * pipe is empty. */ -G_GNUC_PURE +gcc_pure const struct music_chunk * music_pipe_peek(const struct music_pipe *mp); @@ -99,11 +100,11 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk); /** * Returns the number of chunks currently in this pipe. */ -G_GNUC_PURE +gcc_pure unsigned music_pipe_size(const struct music_pipe *mp); -G_GNUC_PURE +gcc_pure static inline bool music_pipe_empty(const struct music_pipe *mp) { diff --git a/src/player_control.c b/src/player_control.c index d8d54dfd6..28134ccdc 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -47,7 +47,7 @@ pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) pc->cond = g_cond_new(); pc->command = PLAYER_COMMAND_NONE; - pc->error = PLAYER_ERROR_NOERROR; + pc->error_type = PLAYER_ERROR_NONE; pc->state = PLAYER_STATE_STOP; pc->cross_fade_seconds = 0; pc->mixramp_db = 0; @@ -59,6 +59,9 @@ pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) void pc_free(struct player_control *pc) { + if (pc->next_song != NULL) + song_free(pc->next_song); + g_cond_free(pc->cond); g_mutex_free(pc->mutex); g_free(pc); @@ -76,15 +79,6 @@ player_wait_decoder(struct player_control *pc, struct decoder_control *dc) g_cond_wait(pc->cond, dc->mutex); } -void -pc_song_deleted(struct player_control *pc, const struct song *song) -{ - if (pc->errored_song == song) { - pc->error = PLAYER_ERROR_NOERROR; - pc->errored_song = NULL; - } -} - static void player_command_wait_locked(struct player_control *pc) { @@ -228,70 +222,43 @@ pc_get_status(struct player_control *pc, struct player_status *status) player_unlock(pc); } -enum player_state -pc_get_state(struct player_control *pc) +void +pc_set_error(struct player_control *pc, enum player_error type, + GError *error) { - return pc->state; + assert(pc != NULL); + assert(type != PLAYER_ERROR_NONE); + assert(error != NULL); + + if (pc->error_type != PLAYER_ERROR_NONE) + g_error_free(pc->error); + + pc->error_type = type; + pc->error = error; } void pc_clear_error(struct player_control *pc) { player_lock(pc); - pc->error = PLAYER_ERROR_NOERROR; - pc->errored_song = NULL; - player_unlock(pc); -} -enum player_error -pc_get_error(struct player_control *pc) -{ - return pc->error; -} + if (pc->error_type != PLAYER_ERROR_NONE) { + pc->error_type = PLAYER_ERROR_NONE; + g_error_free(pc->error); + } -static char * -pc_errored_song_uri(struct player_control *pc) -{ - return song_get_uri(pc->errored_song); + player_unlock(pc); } char * pc_get_error_message(struct player_control *pc) { - char *error; - char *uri; - - switch (pc->error) { - case PLAYER_ERROR_NOERROR: - return NULL; - - case PLAYER_ERROR_FILENOTFOUND: - uri = pc_errored_song_uri(pc); - error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri); - g_free(uri); - return error; - - case PLAYER_ERROR_FILE: - uri = pc_errored_song_uri(pc); - error = g_strdup_printf("problems decoding \"%s\"", uri); - g_free(uri); - return error; - - case PLAYER_ERROR_AUDIO: - return g_strdup("problems opening audio device"); - - case PLAYER_ERROR_SYSTEM: - return g_strdup("system error occurred"); - - case PLAYER_ERROR_UNKTYPE: - uri = pc_errored_song_uri(pc); - error = g_strdup_printf("file type of \"%s\" is unknown", uri); - g_free(uri); - return error; - } - - assert(false); - return NULL; + player_lock(pc); + char *message = pc->error_type != PLAYER_ERROR_NONE + ? g_strdup(pc->error->message) + : NULL; + player_unlock(pc); + return message; } static void @@ -320,6 +287,10 @@ pc_seek(struct player_control *pc, struct song *song, float seek_time) assert(song != NULL); player_lock(pc); + + if (pc->next_song != NULL) + song_free(pc->next_song); + pc->next_song = song; pc->seek_where = seek_time; player_command_locked(pc, PLAYER_COMMAND_SEEK); @@ -348,12 +319,6 @@ pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds) idle_add(IDLE_OPTIONS); } -float -pc_get_mixramp_db(const struct player_control *pc) -{ - return pc->mixramp_db; -} - void pc_set_mixramp_db(struct player_control *pc, float mixramp_db) { @@ -362,12 +327,6 @@ pc_set_mixramp_db(struct player_control *pc, float mixramp_db) idle_add(IDLE_OPTIONS); } -float -pc_get_mixramp_delay(const struct player_control *pc) -{ - return pc->mixramp_delay_seconds; -} - void pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds) { @@ -375,9 +334,3 @@ pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds) idle_add(IDLE_OPTIONS); } - -double -pc_get_total_play_time(const struct player_control *pc) -{ - return pc->total_play_time; -} diff --git a/src/player_control.h b/src/player_control.h index 5a04ab0f9..c16dbcb9f 100644 --- a/src/player_control.h +++ b/src/player_control.h @@ -66,12 +66,17 @@ enum player_command { }; enum player_error { - PLAYER_ERROR_NOERROR = 0, - PLAYER_ERROR_FILE, - PLAYER_ERROR_AUDIO, - PLAYER_ERROR_SYSTEM, - PLAYER_ERROR_UNKTYPE, - PLAYER_ERROR_FILENOTFOUND, + PLAYER_ERROR_NONE = 0, + + /** + * The decoder has failed to decode the song. + */ + PLAYER_ERROR_DECODER, + + /** + * The audio output has failed. + */ + PLAYER_ERROR_OUTPUT, }; struct player_status { @@ -103,13 +108,30 @@ struct player_control { enum player_command command; enum player_state state; - enum player_error error; + + enum player_error error_type; + + /** + * The error that occurred in the player thread. This + * attribute is only valid if #error is not + * #PLAYER_ERROR_NONE. The object must be freed when this + * object transitions back to #PLAYER_ERROR_NONE. + */ + GError *error; + uint16_t bit_rate; struct audio_format audio_format; float total_time; float elapsed_time; + + /** + * The next queued song. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. + */ struct song *next_song; - const struct song *errored_song; + double seek_where; float cross_fade_seconds; float mixramp_db; @@ -185,14 +207,10 @@ player_lock_signal(struct player_control *pc) } /** - * Call this function when the specified song pointer is about to be - * invalidated. This makes sure that player_control.errored_song does - * not point to an invalid pointer. + * @param song the song to be queued; the given instance will be owned + * and freed by the player */ void -pc_song_deleted(struct player_control *pc, const struct song *song); - -void pc_play(struct player_control *pc, struct song *song); /** @@ -213,8 +231,24 @@ pc_kill(struct player_control *pc); void pc_get_status(struct player_control *pc, struct player_status *status); -enum player_state -pc_get_state(struct player_control *pc); +static inline enum player_state +pc_get_state(struct player_control *pc) +{ + return pc->state; +} + +/** + * Set the error. Discards any previous error condition. + * + * Caller must lock the object. + * + * @param type the error type; must not be #PLAYER_ERROR_NONE + * @param error detailed error information; must not be NULL; the + * #player_control takes over ownership of this #GError instance + */ +void +pc_set_error(struct player_control *pc, enum player_error type, + GError *error); void pc_clear_error(struct player_control *pc); @@ -227,8 +261,11 @@ pc_clear_error(struct player_control *pc); char * pc_get_error_message(struct player_control *pc); -enum player_error -pc_get_error(struct player_control *pc); +static inline enum player_error +pc_get_error_type(struct player_control *pc) +{ + return pc->error_type; +} void pc_stop(struct player_control *pc); @@ -236,12 +273,18 @@ pc_stop(struct player_control *pc); void pc_update_audio(struct player_control *pc); +/** + * @param song the song to be queued; the given instance will be owned + * and freed by the player + */ void pc_enqueue_song(struct player_control *pc, struct song *song); /** * Makes the player thread seek the specified song to a position. * + * @param song the song to be queued; the given instance will be owned + * and freed by the player * @return true on success, false on failure (e.g. if MPD isn't * playing currently) */ @@ -257,16 +300,25 @@ pc_get_cross_fade(const struct player_control *pc); void pc_set_mixramp_db(struct player_control *pc, float mixramp_db); -float -pc_get_mixramp_db(const struct player_control *pc); +static inline float +pc_get_mixramp_db(const struct player_control *pc) +{ + return pc->mixramp_db; +} void pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds); -float -pc_get_mixramp_delay(const struct player_control *pc); +static inline float +pc_get_mixramp_delay(const struct player_control *pc) +{ + return pc->mixramp_delay_seconds; +} -double -pc_get_total_play_time(const struct player_control *pc); +static inline double +pc_get_total_play_time(const struct player_control *pc) +{ + return pc->total_play_time; +} #endif diff --git a/src/player_thread.c b/src/player_thread.c index eaf6df303..d410984f6 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -162,7 +162,7 @@ player_dc_start(struct player *player, struct music_pipe *pipe) if (pc->command == PLAYER_COMMAND_SEEK) start_ms += (unsigned)(pc->seek_where * 1000); - dc_start(dc, pc->next_song, + dc_start(dc, song_dup_detached(pc->next_song), start_ms, pc->next_song->end_ms, player_buffer, pipe); } @@ -235,16 +235,22 @@ player_wait_for_decoder(struct player *player) player->queued = false; - if (decoder_lock_has_failed(dc)) { + GError *error = dc_lock_get_error(dc); + if (error != NULL) { player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; + pc_set_error(pc, PLAYER_ERROR_DECODER, error); + + song_free(pc->next_song); pc->next_song = NULL; + player_unlock(pc); return false; } + if (player->song != NULL) + song_free(player->song); + player->song = pc->next_song; player->elapsed_time = 0.0; @@ -305,7 +311,9 @@ player_open_output(struct player *player) assert(pc->state == PLAYER_STATE_PLAY || pc->state == PLAYER_STATE_PAUSE); - if (audio_output_all_open(&player->play_audio_format, player_buffer)) { + GError *error = NULL; + if (audio_output_all_open(&player->play_audio_format, player_buffer, + &error)) { player->output_open = true; player->paused = false; @@ -315,6 +323,8 @@ player_open_output(struct player *player) return true; } else { + g_warning("%s", error->message); + player->output_open = false; /* pause: the user may resume playback as soon as an @@ -322,7 +332,7 @@ player_open_output(struct player *player) player->paused = true; player_lock(pc); - pc->error = PLAYER_ERROR_AUDIO; + pc_set_error(pc, PLAYER_ERROR_OUTPUT, error); pc->state = PLAYER_STATE_PAUSE; player_unlock(pc); @@ -347,13 +357,13 @@ player_check_decoder_startup(struct player *player) decoder_lock(dc); - if (decoder_has_failed(dc)) { + GError *error = dc_get_error(dc); + if (error != NULL) { /* the decoder failed */ decoder_unlock(dc); player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; + pc_set_error(pc, PLAYER_ERROR_DECODER, error); player_unlock(pc); return false; @@ -429,7 +439,11 @@ player_send_silence(struct player *player) chunk->length = num_frames * frame_size; memset(chunk->data, 0, chunk->length); - if (!audio_output_all_play(chunk)) { + GError *error = NULL; + if (!audio_output_all_play(chunk, &error)) { + g_warning("%s", error->message); + g_error_free(error); + music_buffer_return(player_buffer, chunk); return false; } @@ -452,7 +466,7 @@ static bool player_seek_decoder(struct player *player) const unsigned start_ms = song->start_ms; - if (decoder_current_song(dc) != song) { + if (!decoder_lock_is_current_song(dc, song)) { /* the decoder is already decoding the "next" song - stop it and start the previous song again */ @@ -478,6 +492,7 @@ static bool player_seek_decoder(struct player *player) player->pipe = dc->pipe; } + song_free(pc->next_song); pc->next_song = NULL; player->queued = false; } @@ -598,6 +613,7 @@ static void player_process_command(struct player *player) player_lock(pc); } + song_free(pc->next_song); pc->next_song = NULL; player->queued = false; player_command_finished_locked(pc); @@ -652,7 +668,8 @@ update_song_tag(struct song *song, const struct tag *new_tag) static bool play_chunk(struct player_control *pc, struct song *song, struct music_chunk *chunk, - const struct audio_format *format) + const struct audio_format *format, + GError **error_r) { assert(music_chunk_check_format(chunk, format)); @@ -670,7 +687,7 @@ play_chunk(struct player_control *pc, /* send the chunk to the audio outputs */ - if (!audio_output_all_play(chunk)) + if (!audio_output_all_play(chunk, error_r)) return false; pc->total_play_time += (double)chunk->length / @@ -785,13 +802,16 @@ play_next_chunk(struct player *player) /* play the current chunk */ + GError *error = NULL; if (!play_chunk(player->pc, player->song, chunk, - &player->play_audio_format)) { + &player->play_audio_format, &error)) { + g_warning("%s", error->message); + music_buffer_return(player_buffer, chunk); player_lock(pc); - pc->error = PLAYER_ERROR_AUDIO; + pc_set_error(pc, PLAYER_ERROR_OUTPUT, error); /* pause: the user may resume playback as soon as an audio output becomes available */ @@ -874,6 +894,8 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) player_dc_start(&player, player.pipe); if (!player_wait_for_decoder(&player)) { + assert(player.song == NULL); + player_dc_stop(&player); player_command_finished(pc); music_pipe_free(player.pipe); @@ -1036,10 +1058,14 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) if (player.cross_fade_tag != NULL) tag_free(player.cross_fade_tag); + if (player.song != NULL) + song_free(player.song); + player_lock(pc); if (player.queued) { assert(pc->next_song != NULL); + song_free(pc->next_song); pc->next_song = NULL; } @@ -1081,7 +1107,11 @@ player_task(gpointer arg) /* fall through */ case PLAYER_COMMAND_PAUSE: - pc->next_song = NULL; + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + player_command_finished_locked(pc); break; @@ -1122,7 +1152,11 @@ player_task(gpointer arg) return NULL; case PLAYER_COMMAND_CANCEL: - pc->next_song = NULL; + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + player_command_finished_locked(pc); break; diff --git a/src/playlist.c b/src/playlist.c index d62865dd1..4d4f0c1f6 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -78,14 +78,15 @@ static void playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, unsigned order) { - struct song *song; char *uri; assert(queue_valid_order(&playlist->queue, order)); playlist->queued = order; - song = queue_get_order(&playlist->queue, order); + struct song *song = + song_dup_detached(queue_get_order(&playlist->queue, order)); + uri = song_get_uri(song); g_debug("queue song %i:\"%s\"", playlist->queued, uri); g_free(uri); @@ -191,13 +192,13 @@ void playlist_play_order(struct playlist *playlist, struct player_control *pc, int orderNum) { - struct song *song; char *uri; playlist->playing = true; playlist->queued = -1; - song = queue_get_order(&playlist->queue, orderNum); + struct song *song = + song_dup_detached(queue_get_order(&playlist->queue, orderNum)); uri = song_get_uri(song); g_debug("play %i:\"%s\"", orderNum, uri); @@ -262,14 +263,14 @@ playlist_resume_playback(struct playlist *playlist, struct player_control *pc) assert(playlist->playing); assert(pc_get_state(pc) == PLAYER_STATE_STOP); - error = pc_get_error(pc); - if (error == PLAYER_ERROR_NOERROR) + error = pc_get_error_type(pc); + if (error == PLAYER_ERROR_NONE) playlist->error_count = 0; else ++playlist->error_count; - if ((playlist->stop_on_error && error != PLAYER_ERROR_NOERROR) || - error == PLAYER_ERROR_AUDIO || error == PLAYER_ERROR_SYSTEM || + if ((playlist->stop_on_error && error != PLAYER_ERROR_NONE) || + error == PLAYER_ERROR_OUTPUT || playlist->error_count >= queue_length(&playlist->queue)) /* too many errors, or critical error: stop playback */ diff --git a/src/playlist_control.c b/src/playlist_control.c index 0dea7676a..57cc428fe 100644 --- a/src/playlist_control.c +++ b/src/playlist_control.c @@ -25,6 +25,7 @@ #include "config.h" #include "playlist_internal.h" #include "player_control.h" +#include "song.h" #include "idle.h" #include <glib.h> @@ -239,7 +240,9 @@ playlist_seek_song(struct playlist *playlist, struct player_control *pc, queued = NULL; } - success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time); + struct song *the_song = + song_dup_detached(queue_get_order(&playlist->queue, i)); + success = pc_seek(pc, the_song, seek_time); if (!success) { playlist_update_queued_song(playlist, pc, queued); diff --git a/src/playlist_edit.c b/src/playlist_edit.c index 7adbccd7c..1dfe68daa 100644 --- a/src/playlist_edit.c +++ b/src/playlist_edit.c @@ -45,14 +45,6 @@ playlist_clear(struct playlist *playlist, struct player_control *pc) { playlist_stop(playlist, pc); - /* make sure there are no references to allocated songs - anymore */ - for (unsigned i = 0; i < queue_length(&playlist->queue); i++) { - const struct song *song = queue_get(&playlist->queue, i); - if (!song_in_database(song)) - pc_song_deleted(pc, song); - } - queue_clear(&playlist->queue); playlist->current = -1; @@ -287,9 +279,6 @@ playlist_delete_internal(struct playlist *playlist, struct player_control *pc, /* now do it: remove the song */ - if (!song_in_database(queue_get(&playlist->queue, song))) - pc_song_deleted(pc, queue_get(&playlist->queue, song)); - queue_delete(&playlist->queue, song); /* update the "current" and "queued" variables */ @@ -363,8 +352,6 @@ playlist_delete_song(struct playlist *playlist, struct player_control *pc, for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i) if (song == queue_get(&playlist->queue, i)) playlist_delete(playlist, pc, i); - - pc_song_deleted(pc, song); } enum playlist_result diff --git a/src/playlist_print.c b/src/playlist_print.c index 59c42f969..204ce3582 100644 --- a/src/playlist_print.c +++ b/src/playlist_print.c @@ -94,13 +94,6 @@ playlist_print_find(struct client *client, const struct playlist *playlist, } void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) -{ - queue_search(client, &playlist->queue, list); -} - -void playlist_print_changes_info(struct client *client, const struct playlist *playlist, uint32_t version) @@ -164,7 +157,7 @@ playlist_provider_print(struct client *client, const char *uri, else song_print_uri(client, song); - if (!song_in_database(song)) + if (!song_in_database(song) || song_is_detached(song)) song_free(song); } diff --git a/src/playlist_print.h b/src/playlist_print.h index d4f1911d2..20d3703d9 100644 --- a/src/playlist_print.h +++ b/src/playlist_print.h @@ -69,13 +69,6 @@ playlist_print_find(struct client *client, const struct playlist *playlist, const struct locate_item_list *list); /** - * Search for songs in the playlist. - */ -void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list); - -/** * Print detailed changes since the specified playlist version. */ void diff --git a/src/protocol/result.h b/src/protocol/result.h index 8b9e44bfd..09ea6c418 100644 --- a/src/protocol/result.h +++ b/src/protocol/result.h @@ -21,10 +21,9 @@ #define MPD_PROTOCOL_RESULT_H #include "check.h" +#include "gcc.h" #include "ack.h" -#include <glib.h> - struct client; extern const char *current_command; @@ -37,7 +36,7 @@ void command_error_v(struct client *client, enum ack error, const char *fmt, va_list args); -G_GNUC_PRINTF(3, 4) +gcc_fprintf_ void command_error(struct client *client, enum ack error, const char *fmt, ...); diff --git a/src/queue.c b/src/queue.c index cd932875e..2a489242c 100644 --- a/src/queue.c +++ b/src/queue.c @@ -103,7 +103,7 @@ queue_append(struct queue *queue, struct song *song) assert(!queue_is_full(queue)); queue->items[queue->length] = (struct queue_item){ - .song = song, + .song = song_dup_detached(song), .id = id, .version = queue->version, .priority = 0, @@ -256,8 +256,8 @@ queue_delete(struct queue *queue, unsigned position) assert(position < queue->length); song = queue_get(queue, position); - if (!song_in_database(song)) - song_free(song); + assert(!song_in_database(song) || song_is_detached(song)); + song_free(song); id = queue_position_to_id(queue, position); order = queue_position_to_order(queue, position); @@ -291,8 +291,9 @@ queue_clear(struct queue *queue) for (unsigned i = 0; i < queue->length; i++) { struct queue_item *item = &queue->items[i]; - if (!song_in_database(item->song)) - song_free(item->song); + assert(!song_in_database(item->song) || + song_is_detached(item->song)); + song_free(item->song); queue->id_to_position[item->id] = -1; } diff --git a/src/queue_print.c b/src/queue_print.c index d149e8b6f..d956bbf98 100644 --- a/src/queue_print.c +++ b/src/queue_print.c @@ -92,31 +92,13 @@ queue_print_changes_position(struct client *client, const struct queue *queue, } void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria) -{ - unsigned i; - struct locate_item_list *new_list = - locate_item_list_casefold(criteria); - - for (i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - - if (locate_song_search(song, new_list)) - queue_print_song_info(client, queue, i); - } - - locate_item_list_free(new_list); -} - -void queue_find(struct client *client, const struct queue *queue, const struct locate_item_list *criteria) { for (unsigned i = 0; i < queue_length(queue); i++) { const struct song *song = queue_get(queue, i); - if (locate_song_match(song, criteria)) + if (locate_list_song_match(song, criteria)) queue_print_song_info(client, queue, i); } } diff --git a/src/queue_print.h b/src/queue_print.h index 371e20416..28620c41f 100644 --- a/src/queue_print.h +++ b/src/queue_print.h @@ -48,10 +48,6 @@ queue_print_changes_position(struct client *client, const struct queue *queue, uint32_t version); void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria); - -void queue_find(struct client *client, const struct queue *queue, const struct locate_item_list *criteria); diff --git a/src/resolver.h b/src/resolver.h index e5ad06754..666b6d5b2 100644 --- a/src/resolver.h +++ b/src/resolver.h @@ -20,12 +20,14 @@ #ifndef MPD_RESOLVER_H #define MPD_RESOLVER_H +#include "gcc.h" + #include <glib.h> struct sockaddr; struct addrinfo; -G_GNUC_CONST +gcc_const static inline GQuark resolver_quark(void) { @@ -42,7 +44,7 @@ resolver_quark(void) * @param error location to store the error occurring, or NULL to * ignore errors */ -G_GNUC_MALLOC +gcc_malloc char * sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error); diff --git a/src/server_socket.h b/src/server_socket.h index 7caa4bbf2..f7e9aa4cf 100644 --- a/src/server_socket.h +++ b/src/server_socket.h @@ -20,9 +20,10 @@ #ifndef MPD_SERVER_SOCKET_H #define MPD_SERVER_SOCKET_H -#include <stdbool.h> +#include "gerror.h" -#include <glib.h> +#include <stdbool.h> +#include <stddef.h> struct sockaddr; diff --git a/src/socket_util.c b/src/socket_util.c index a06a0cbd5..ee8bf7e1a 100644 --- a/src/socket_util.c +++ b/src/socket_util.c @@ -21,6 +21,8 @@ #include "socket_util.h" #include "fd_util.h" +#include <glib.h> + #include <errno.h> #include <unistd.h> diff --git a/src/socket_util.h b/src/socket_util.h index 93bd27362..4cf845d8e 100644 --- a/src/socket_util.h +++ b/src/socket_util.h @@ -26,7 +26,9 @@ #ifndef SOCKET_UTIL_H #define SOCKET_UTIL_H -#include <glib.h> +#include "gerror.h" + +#include <stddef.h> struct sockaddr; diff --git a/src/song.h b/src/song.h index 8b97d45d0..39f916a6a 100644 --- a/src/song.h +++ b/src/song.h @@ -21,7 +21,9 @@ #define MPD_SONG_H #include "util/list.h" +#include "gcc.h" +#include <assert.h> #include <stddef.h> #include <stdbool.h> #include <sys/time.h> @@ -58,6 +60,14 @@ struct song { char uri[sizeof(int)]; }; +/** + * A dummy #directory instance that is used for "detached" song + * copies. + */ +extern struct directory detached_root; + +G_BEGIN_DECLS + /** allocate a new song with a remote URL */ struct song * song_remote_new(const char *uri); @@ -83,9 +93,52 @@ song_file_load(const char *path, struct directory *parent); struct song * song_replace_uri(struct song *song, const char *uri); +/** + * Creates a "detached" song object. + */ +struct song * +song_detached_new(const char *uri); + +/** + * Creates a duplicate of the song object. If the object is in the + * database, it creates a "detached" copy of this song, see + * song_is_detached(). + */ +gcc_malloc +struct song * +song_dup_detached(const struct song *src); + void song_free(struct song *song); +static inline bool +song_in_database(const struct song *song) +{ + return song->parent != NULL; +} + +static inline bool +song_is_file(const struct song *song) +{ + return song_in_database(song) || song->uri[0] == '/'; +} + +static inline bool +song_is_detached(const struct song *song) +{ + assert(song != NULL); + assert(song_in_database(song)); + + return song->parent == &detached_root; +} + +/** + * Returns true if both objects refer to the same physical song. + */ +gcc_pure +bool +song_equals(const struct song *a, const struct song *b); + bool song_file_update(struct song *song); @@ -105,16 +158,6 @@ song_get_uri(const struct song *song); double song_get_duration(const struct song *song); -static inline bool -song_in_database(const struct song *song) -{ - return song->parent != NULL; -} - -static inline bool -song_is_file(const struct song *song) -{ - return song_in_database(song) || song->uri[0] == '/'; -} +G_END_DECLS #endif diff --git a/src/song_print.c b/src/song_print.c index fb608a8b2..d876b85a8 100644 --- a/src/song_print.c +++ b/src/song_print.c @@ -19,6 +19,7 @@ #include "config.h" #include "song_print.h" +#include "time_print.h" #include "song.h" #include "directory.h" #include "tag_print.h" @@ -63,32 +64,8 @@ song_print_info(struct client *client, struct song *song) song->start_ms / 1000, song->start_ms % 1000); - if (song->mtime > 0) { -#ifndef G_OS_WIN32 - struct tm tm; -#endif - const struct tm *tm2; - -#ifdef G_OS_WIN32 - tm2 = gmtime(&song->mtime); -#else - tm2 = gmtime_r(&song->mtime, &tm); -#endif - - if (tm2 != NULL) { - char timestamp[32]; - - strftime(timestamp, sizeof(timestamp), -#ifdef G_OS_WIN32 - "%Y-%m-%dT%H:%M:%SZ", -#else - "%FT%TZ", -#endif - tm2); - client_printf(client, "Last-Modified: %s\n", - timestamp); - } - } + if (song->mtime > 0) + time_print(client, "Last-Modified", song->mtime); if (song->tag) tag_print(client, song->tag); diff --git a/src/sticker.h b/src/sticker.h index 5545206a5..66f12294b 100644 --- a/src/sticker.h +++ b/src/sticker.h @@ -42,7 +42,7 @@ #ifndef STICKER_H #define STICKER_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> @@ -127,8 +127,8 @@ sticker_get_value(const struct sticker *sticker, const char *name); void sticker_foreach(const struct sticker *sticker, void (*func)(const char *name, const char *value, - gpointer user_data), - gpointer user_data); + void *user_data), + void *user_data); /** * Loads the sticker for the specified resource. @@ -153,7 +153,7 @@ sticker_load(const char *type, const char *uri); bool sticker_find(const char *type, const char *base_uri, const char *name, void (*func)(const char *uri, const char *value, - gpointer user_data), - gpointer user_data); + void *user_data), + void *user_data); #endif diff --git a/src/sticker_print.c b/src/sticker_print.c index 65e79513c..b19dcdc9c 100644 --- a/src/sticker_print.c +++ b/src/sticker_print.c @@ -30,7 +30,7 @@ sticker_print_value(struct client *client, } static void -print_sticker_cb(const char *name, const char *value, gpointer data) +print_sticker_cb(const char *name, const char *value, void *data) { struct client *client = data; diff --git a/src/string_util.h b/src/string_util.h index dc80a46ef..683fada1b 100644 --- a/src/string_util.h +++ b/src/string_util.h @@ -20,7 +20,7 @@ #ifndef MPD_STRING_UTIL_H #define MPD_STRING_UTIL_H -#include <glib.h> +#include "gcc.h" #include <stdbool.h> @@ -28,7 +28,7 @@ * Remove the "const" attribute from a string pointer. This is a * dirty hack, don't use it unless you know what you're doing! */ -G_GNUC_CONST +gcc_const static inline char * deconst_string(const char *p) { @@ -49,14 +49,14 @@ deconst_string(const char *p) * This is a faster version of g_strchug(), because it does not move * data. */ -G_GNUC_PURE +gcc_pure const char * strchug_fast_c(const char *p); /** * Same as strchug_fast_c(), but works with a writable pointer. */ -G_GNUC_PURE +gcc_pure static inline char * strchug_fast(char *p) { diff --git a/src/strset.c b/src/strset.c index 5862e4075..f1a5197d1 100644 --- a/src/strset.c +++ b/src/strset.c @@ -20,6 +20,8 @@ #include "config.h" #include "strset.h" +#include <glib.h> + #include <assert.h> #include <string.h> #include <stdlib.h> diff --git a/src/strset.h b/src/strset.h index 5382e59b8..6965a5823 100644 --- a/src/strset.h +++ b/src/strset.h @@ -29,11 +29,13 @@ #ifndef MPD_STRSET_H #define MPD_STRSET_H -#include <glib.h> +#include "gcc.h" struct strset; -G_GNUC_MALLOC struct strset *strset_new(void); +gcc_malloc +struct strset * +strset_new(void); void strset_free(struct strset *set); @@ -168,9 +168,9 @@ static void tag_delete_item(struct tag *tag, unsigned idx) assert(idx < tag->num_items); tag->num_items--; - g_mutex_lock(tag_pool_lock); + g_static_mutex_lock(&tag_pool_lock); tag_pool_put_item(tag->items[idx]); - g_mutex_unlock(tag_pool_lock); + g_static_mutex_unlock(&tag_pool_lock); if (tag->num_items - idx > 0) { memmove(tag->items + idx, tag->items + idx + 1, @@ -202,10 +202,10 @@ void tag_free(struct tag *tag) assert(tag != NULL); - g_mutex_lock(tag_pool_lock); + g_static_mutex_lock(&tag_pool_lock); for (i = tag->num_items; --i >= 0; ) tag_pool_put_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); + g_static_mutex_unlock(&tag_pool_lock); if (tag->items == bulk.items) { #ifndef NDEBUG @@ -231,10 +231,10 @@ struct tag *tag_dup(const struct tag *tag) ret->num_items = tag->num_items; ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL; - g_mutex_lock(tag_pool_lock); + g_static_mutex_lock(&tag_pool_lock); for (unsigned i = 0; i < tag->num_items; i++) ret->items[i] = tag_pool_dup_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); + g_static_mutex_unlock(&tag_pool_lock); return ret; } @@ -255,7 +255,7 @@ tag_merge(const struct tag *base, const struct tag *add) ret->num_items = base->num_items + add->num_items; ret->items = ret->num_items > 0 ? g_malloc(items_size(ret)) : NULL; - g_mutex_lock(tag_pool_lock); + g_static_mutex_lock(&tag_pool_lock); /* copy all items from "add" */ @@ -270,7 +270,7 @@ tag_merge(const struct tag *base, const struct tag *add) if (!tag_has_type(add, base->items[i]->type)) ret->items[n++] = tag_pool_dup_item(base->items[i]); - g_mutex_unlock(tag_pool_lock); + g_static_mutex_unlock(&tag_pool_lock); assert(n <= ret->num_items); @@ -502,9 +502,9 @@ tag_add_item_internal(struct tag *tag, enum tag_type type, items_size(tag) - sizeof(struct tag_item *)); } - g_mutex_lock(tag_pool_lock); + g_static_mutex_lock(&tag_pool_lock); tag->items[i] = tag_pool_get_item(type, value, len); - g_mutex_unlock(tag_pool_lock); + g_static_mutex_unlock(&tag_pool_lock); g_free(p); } diff --git a/src/tag_id3.h b/src/tag_id3.h index 049c53ad9..473e33843 100644 --- a/src/tag_id3.h +++ b/src/tag_id3.h @@ -21,8 +21,8 @@ #define MPD_TAG_ID3_H #include "check.h" - -#include <glib.h> +#include "gcc.h" +#include "gerror.h" #include <stdbool.h> @@ -51,9 +51,9 @@ tag_id3_load(const char *path_fs, GError **error_r); #else static inline bool -tag_id3_scan(G_GNUC_UNUSED const char *path_fs, - G_GNUC_UNUSED const struct tag_handler *handler, - G_GNUC_UNUSED void *handler_ctx) +tag_id3_scan(gcc_unused const char *path_fs, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) { return false; } diff --git a/src/tag_pool.c b/src/tag_pool.c index eabf3e369..2f9b39486 100644 --- a/src/tag_pool.c +++ b/src/tag_pool.c @@ -22,7 +22,17 @@ #include <assert.h> -GMutex *tag_pool_lock = NULL; +#if GCC_CHECK_VERSION(4, 2) +/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +GStaticMutex tag_pool_lock = G_STATIC_MUTEX_INIT; + +#if GCC_CHECK_VERSION(4, 2) +#pragma GCC diagnostic pop +#endif #define NUM_SLOTS 4096 @@ -81,19 +91,6 @@ static struct slot *slot_alloc(struct slot *next, return slot; } -void tag_pool_init(void) -{ - g_assert(tag_pool_lock == NULL); - tag_pool_lock = g_mutex_new(); -} - -void tag_pool_deinit(void) -{ - g_assert(tag_pool_lock != NULL); - g_mutex_free(tag_pool_lock); - tag_pool_lock = NULL; -} - struct tag_item * tag_pool_get_item(enum tag_type type, const char *value, size_t length) { diff --git a/src/tag_pool.h b/src/tag_pool.h index a96c00d85..a717f704d 100644 --- a/src/tag_pool.h +++ b/src/tag_pool.h @@ -24,14 +24,10 @@ #include <glib.h> -extern GMutex *tag_pool_lock; +extern GStaticMutex tag_pool_lock; struct tag_item; -void tag_pool_init(void); - -void tag_pool_deinit(void); - struct tag_item * tag_pool_get_item(enum tag_type type, const char *value, size_t length); diff --git a/src/time_print.c b/src/time_print.c new file mode 100644 index 000000000..6fb569d96 --- /dev/null +++ b/src/time_print.c @@ -0,0 +1,47 @@ +/* + * 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 "time_print.h" +#include "client.h" + +#include <glib.h> + +void +time_print(struct client *client, const char *name, time_t t) +{ +#ifdef G_OS_WIN32 + const struct tm *tm2 = gmtime(&t); +#else + struct tm tm; + const struct tm *tm2 = gmtime_r(&t, &tm); +#endif + if (tm2 == NULL) + return; + + char buffer[32]; + strftime(buffer, sizeof(buffer), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", +#else + "%FT%TZ", +#endif + tm2); + client_printf(client, "%s: %s\n", name, buffer); +} diff --git a/src/db_internal.h b/src/time_print.h index a33351524..7eff446b2 100644 --- a/src/db_internal.h +++ b/src/time_print.h @@ -17,19 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_INTERNAL_H -#define MPD_DB_INTERNAL_H +#ifndef MPD_TIME_PRINT_H +#define MPD_TIME_PRINT_H -#include "db_plugin.h" +#include <time.h> -#include <assert.h> +struct client; -static inline void -db_base_init(struct db *db, const struct db_plugin *plugin) -{ - assert(plugin != NULL); - - db->plugin = plugin; -} +/** + * Write a line with a time stamp to the client. + */ +void +time_print(struct client *client, const char *name, time_t t); #endif diff --git a/src/tokenizer.c b/src/tokenizer.c index bbb34e100..4a98e882f 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -21,6 +21,8 @@ #include "tokenizer.h" #include "string_util.h" +#include <glib.h> + #include <stdbool.h> #include <assert.h> #include <string.h> diff --git a/src/tokenizer.h b/src/tokenizer.h index d55eb3ca6..2026e5ad6 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -20,7 +20,7 @@ #ifndef MPD_TOKENIZER_H #define MPD_TOKENIZER_H -#include <glib.h> +#include "gerror.h" /** * Reads the next word from the input string. This function modifies diff --git a/src/update.c b/src/update.c index 12eec40c4..ef4dfe749 100644 --- a/src/update.c +++ b/src/update.c @@ -118,7 +118,7 @@ update_enqueue(const char *path, bool _discard) { assert(g_thread_self() == main_task); - if (!mapper_has_music_directory()) + if (!db_is_simple() || !mapper_has_music_directory()) return 0; if (progress != UPDATE_PROGRESS_IDLE) { diff --git a/src/update_archive.h b/src/update_archive.h index 838697dd9..a9139b0e0 100644 --- a/src/update_archive.h +++ b/src/update_archive.h @@ -21,6 +21,7 @@ #define MPD_UPDATE_ARCHIVE_H #include "check.h" +#include "gcc.h" #include <stdbool.h> #include <sys/stat.h> @@ -40,10 +41,10 @@ update_archive_file(struct directory *directory, #include <glib.h> static inline bool -update_archive_file(G_GNUC_UNUSED struct directory *directory, - G_GNUC_UNUSED const char *name, - G_GNUC_UNUSED const char *suffix, - G_GNUC_UNUSED const struct stat *st) +update_archive_file(gcc_unused struct directory *directory, + gcc_unused const char *name, + gcc_unused const char *suffix, + gcc_unused const struct stat *st) { return false; } @@ -20,7 +20,7 @@ #ifndef MPD_URI_H #define MPD_URI_H -#include <glib.h> +#include "gcc.h" #include <stdbool.h> @@ -28,10 +28,10 @@ * Checks whether the specified URI has a scheme in the form * "scheme://". */ -G_GNUC_PURE +gcc_pure bool uri_has_scheme(const char *uri); -G_GNUC_PURE +gcc_pure const char * uri_get_suffix(const char *uri); @@ -43,7 +43,7 @@ uri_get_suffix(const char *uri); * - no double slashes * - no path component begins with a dot */ -G_GNUC_PURE +gcc_pure bool uri_safe_local(const char *uri); @@ -53,7 +53,7 @@ uri_safe_local(const char *uri); * NULL if nothing needs to be removed, or if the URI is not * recognized. */ -G_GNUC_MALLOC +gcc_malloc char * uri_remove_auth(const char *uri); diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h index e44693b1d..54cb789bb 100644 --- a/src/util/bit_reverse.h +++ b/src/util/bit_reverse.h @@ -20,12 +20,13 @@ #ifndef MPD_BIT_REVERSE_H #define MPD_BIT_REVERSE_H -#include <glib.h> +#include "gcc.h" + #include <stdint.h> extern const uint8_t bit_reverse_table[256]; -G_GNUC_CONST +gcc_const static inline uint8_t bit_reverse(uint8_t x) { diff --git a/src/util/list.h b/src/util/list.h index fdab47675..28c70954f 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -47,8 +47,8 @@ * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ -#define LIST_POISON1 ((void *) 0x00100100) -#define LIST_POISON2 ((void *) 0x00200200) +#define LIST_POISON1 ((struct list_head *)(void *) 0x00100100) +#define LIST_POISON2 ((struct list_head *)(void *) 0x00200200) /* * Simple doubly linked list implementation. @@ -82,46 +82,47 @@ static inline void INIT_LIST_HEAD(struct list_head *list) * the prev/next entries already! */ #ifndef CONFIG_DEBUG_LIST -static inline void __list_add(struct list_head *new, +static inline void __list_add(struct list_head *new_item, struct list_head *prev, struct list_head *next) { - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; + next->prev = new_item; + new_item->next = next; + new_item->prev = prev; + prev->next = new_item; } #else -extern void __list_add(struct list_head *new, - struct list_head *prev, - struct list_head *next); +extern void __list_add(struct list_head *new_item, + struct list_head *prev, + struct list_head *next); #endif /** * list_add - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ -static inline void list_add(struct list_head *new, struct list_head *head) +static inline void list_add(struct list_head *new_item, struct list_head *head) { - __list_add(new, head, head->next); + __list_add(new_item, head, head->next); } /** * list_add_tail - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ -static inline void list_add_tail(struct list_head *new, struct list_head *head) +static inline void +list_add_tail(struct list_head *new_item, struct list_head *head) { - __list_add(new, head->prev, head); + __list_add(new_item, head->prev, head); } /* @@ -163,23 +164,23 @@ extern void list_del(struct list_head *entry); /** * list_replace - replace old entry by new one * @old : the element to be replaced - * @new : the new element to insert + * @new_item : the new element to insert * * If @old was empty, it will be overwritten. */ static inline void list_replace(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - new->next = old->next; - new->next->prev = new; - new->prev = old->prev; - new->prev->next = new; + new_item->next = old->next; + new_item->next->prev = new_item; + new_item->prev = old->prev; + new_item->prev->next = new_item; } static inline void list_replace_init(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - list_replace(old, new); + list_replace(old, new_item); INIT_LIST_HEAD(old); } diff --git a/src/utils.h b/src/utils.h index f8d6657f2..ba7307ded 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,7 +20,8 @@ #ifndef MPD_UTILS_H #define MPD_UTILS_H -#include <glib.h> +#include "gerror.h" + #include <stdbool.h> #ifndef assert_static |