diff options
Diffstat (limited to '')
198 files changed, 8458 insertions, 5132 deletions
diff --git a/src/AllCommands.cxx b/src/AllCommands.cxx new file mode 100644 index 000000000..28e3d3ebd --- /dev/null +++ b/src/AllCommands.cxx @@ -0,0 +1,391 @@ +/* + * 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" + +extern "C" { +#include "AllCommands.h" +} + +#include "command.h" +#include "QueueCommands.hxx" +#include "PlayerCommands.hxx" +#include "PlaylistCommands.hxx" +#include "DatabaseCommands.hxx" +#include "OutputCommands.hxx" +#include "StickerCommands.hxx" +#include "MessageCommands.hxx" +#include "OtherCommands.hxx" +#include "permission.h" +#include "tag.h" + +extern "C" { +#include "protocol/result.h" +#include "tokenizer.h" +#include "client.h" + +#ifdef ENABLE_SQLITE +#include "sticker.h" +#endif +} + +#include <assert.h> +#include <string.h> + +/* + * The most we ever use is for search/find, and that limits it to the + * number of tags we can have. Add one for the command, and one extra + * to catch errors clients may send us + */ +#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2)) + +/* if min: -1 don't check args * + * if max: -1 no max args */ +struct command { + const char *cmd; + unsigned permission; + int min; + int max; + enum command_return (*handler)(struct client *client, int argc, char **argv); +}; + +/* don't be fooled, this is the command handler for "commands" command */ +static enum command_return +handle_commands(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); + +static enum command_return +handle_not_commands(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); + +/** + * The command registry. + * + * This array must be sorted! + */ +static const struct command commands[] = { + { "add", PERMISSION_ADD, 1, 1, handle_add }, + { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "channels", PERMISSION_READ, 0, 0, handle_channels }, + { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, + { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, + { "close", PERMISSION_NONE, -1, -1, handle_close }, + { "commands", PERMISSION_NONE, 0, 0, handle_commands }, + { "config", PERMISSION_ADMIN, 0, 0, handle_config }, + { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, + { "count", PERMISSION_READ, 2, -1, handle_count }, + { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, + { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong }, + { "decoders", PERMISSION_READ, 0, 0, handle_decoders }, + { "delete", PERMISSION_CONTROL, 1, 1, handle_delete }, + { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid }, + { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, + { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, + { "find", PERMISSION_READ, 2, -1, handle_find }, + { "findadd", PERMISSION_READ, 2, -1, handle_findadd}, + { "idle", PERMISSION_READ, 0, -1, handle_idle }, + { "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, + { "list", PERMISSION_READ, 1, -1, handle_list }, + { "listall", PERMISSION_READ, 0, 1, handle_listall }, + { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, + { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, + { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, + { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, + { "load", PERMISSION_ADD, 1, 2, handle_load }, + { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, + { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, + { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, + { "move", PERMISSION_CONTROL, 2, 2, handle_move }, + { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid }, + { "next", PERMISSION_CONTROL, 0, 0, handle_next }, + { "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands }, + { "outputs", PERMISSION_READ, 0, 0, handle_devices }, + { "password", PERMISSION_NONE, 1, 1, handle_password }, + { "pause", PERMISSION_CONTROL, 0, 1, handle_pause }, + { "ping", PERMISSION_NONE, 0, 0, handle_ping }, + { "play", PERMISSION_CONTROL, 0, 1, handle_play }, + { "playid", PERMISSION_CONTROL, 0, 1, handle_playid }, + { "playlist", PERMISSION_READ, 0, 0, handle_playlist }, + { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd }, + { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear }, + { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete }, + { "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind }, + { "playlistid", PERMISSION_READ, 0, 1, handle_playlistid }, + { "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo }, + { "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove }, + { "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch }, + { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, + { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, + { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, + { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, + { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, + { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, + { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, + { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, + { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, + handle_replay_gain_mode }, + { "replay_gain_status", PERMISSION_READ, 0, 0, + handle_replay_gain_status }, + { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, + { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, + { "save", PERMISSION_CONTROL, 1, 1, handle_save }, + { "search", PERMISSION_READ, 2, -1, handle_search }, + { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd }, + { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl }, + { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, + { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, + { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, + { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, + { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, + { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, + { "single", PERMISSION_CONTROL, 1, 1, handle_single }, + { "stats", PERMISSION_READ, 0, 0, handle_stats }, + { "status", PERMISSION_READ, 0, 0, handle_status }, +#ifdef ENABLE_SQLITE + { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, +#endif + { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, + { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, + { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, + { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, + { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, + { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, + { "update", PERMISSION_CONTROL, 0, 1, handle_update }, + { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, +}; + +static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); + +static bool +command_available(G_GNUC_UNUSED const struct command *cmd) +{ +#ifdef ENABLE_SQLITE + if (strcmp(cmd->cmd, "sticker") == 0) + return sticker_enabled(); +#endif + + return true; +} + +/* don't be fooled, this is the command handler for "commands" command */ +static enum command_return +handle_commands(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + const unsigned permission = client_get_permission(client); + const struct command *cmd; + + for (unsigned i = 0; i < num_commands; ++i) { + cmd = &commands[i]; + + if (cmd->permission == (permission & cmd->permission) && + command_available(cmd)) + client_printf(client, "command: %s\n", cmd->cmd); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_not_commands(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + const unsigned permission = client_get_permission(client); + const struct command *cmd; + + for (unsigned i = 0; i < num_commands; ++i) { + cmd = &commands[i]; + + if (cmd->permission != (permission & cmd->permission)) + client_printf(client, "command: %s\n", cmd->cmd); + } + + return COMMAND_RETURN_OK; +} + +void command_init(void) +{ +#ifndef NDEBUG + /* ensure that the command list is sorted */ + for (unsigned i = 0; i < num_commands - 1; ++i) + assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0); +#endif +} + +void command_finish(void) +{ +} + +static const struct command * +command_lookup(const char *name) +{ + unsigned a = 0, b = num_commands, i; + int cmp; + + /* binary search */ + do { + i = (a + b) / 2; + + cmp = strcmp(name, commands[i].cmd); + if (cmp == 0) + return &commands[i]; + else if (cmp < 0) + b = i; + else if (cmp > 0) + a = i + 1; + } while (a < b); + + return NULL; +} + +static bool +command_check_request(const struct command *cmd, struct client *client, + unsigned permission, int argc, char *argv[]) +{ + int min = cmd->min + 1; + int max = cmd->max + 1; + + if (cmd->permission != (permission & cmd->permission)) { + if (client != NULL) + command_error(client, ACK_ERROR_PERMISSION, + "you don't have permission for \"%s\"", + cmd->cmd); + return false; + } + + if (min == 0) + return true; + + if (min == max && max != argc) { + if (client != NULL) + command_error(client, ACK_ERROR_ARG, + "wrong number of arguments for \"%s\"", + argv[0]); + return false; + } else if (argc < min) { + if (client != NULL) + command_error(client, ACK_ERROR_ARG, + "too few arguments for \"%s\"", argv[0]); + return false; + } else if (argc > max && max /* != 0 */ ) { + if (client != NULL) + command_error(client, ACK_ERROR_ARG, + "too many arguments for \"%s\"", argv[0]); + return false; + } else + return true; +} + +static const struct command * +command_checked_lookup(struct client *client, unsigned permission, + int argc, char *argv[]) +{ + const struct command *cmd; + + current_command = ""; + + if (argc == 0) + return NULL; + + cmd = command_lookup(argv[0]); + if (cmd == NULL) { + if (client != NULL) + command_error(client, ACK_ERROR_UNKNOWN, + "unknown command \"%s\"", argv[0]); + return NULL; + } + + current_command = cmd->cmd; + + if (!command_check_request(cmd, client, permission, argc, argv)) + return NULL; + + return cmd; +} + +enum command_return +command_process(struct client *client, unsigned num, char *line) +{ + GError *error = NULL; + int argc; + char *argv[COMMAND_ARGV_MAX] = { NULL }; + const struct command *cmd; + enum command_return ret = COMMAND_RETURN_ERROR; + + command_list_num = num; + + /* get the command name (first word on the line) */ + + argv[0] = tokenizer_next_word(&line, &error); + if (argv[0] == NULL) { + current_command = ""; + if (*line == 0) + command_error(client, ACK_ERROR_UNKNOWN, + "No command given"); + else { + command_error(client, ACK_ERROR_UNKNOWN, + "%s", error->message); + g_error_free(error); + } + current_command = NULL; + + return COMMAND_RETURN_ERROR; + } + + argc = 1; + + /* now parse the arguments (quoted or unquoted) */ + + while (argc < (int)G_N_ELEMENTS(argv) && + (argv[argc] = + tokenizer_next_param(&line, &error)) != NULL) + ++argc; + + /* some error checks; we have to set current_command because + command_error() expects it to be set */ + + current_command = argv[0]; + + if (argc >= (int)G_N_ELEMENTS(argv)) { + command_error(client, ACK_ERROR_ARG, "Too many arguments"); + current_command = NULL; + return COMMAND_RETURN_ERROR; + } + + if (*line != 0) { + command_error(client, ACK_ERROR_ARG, + "%s", error->message); + current_command = NULL; + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + + /* look up and invoke the command handler */ + + cmd = command_checked_lookup(client, client_get_permission(client), + argc, argv); + if (cmd) + ret = cmd->handler(client, argc, argv); + + current_command = NULL; + command_list_num = 0; + + return ret; +} diff --git a/src/AllCommands.h b/src/AllCommands.h new file mode 100644 index 000000000..8325094f5 --- /dev/null +++ b/src/AllCommands.h @@ -0,0 +1,34 @@ +/* + * 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_ALL_COMMANDS_H +#define MPD_ALL_COMMANDS_H + +#include "command.h" + +struct client; + +void command_init(void); + +void command_finish(void); + +enum command_return +command_process(struct client *client, unsigned num, char *line); + +#endif diff --git a/src/CommandError.cxx b/src/CommandError.cxx new file mode 100644 index 000000000..428a615e5 --- /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.hxx" +#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.hxx b/src/CommandError.hxx new file mode 100644 index 000000000..a90e24427 --- /dev/null +++ b/src/CommandError.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_COMMAND_ERROR_HXX +#define MPD_COMMAND_ERROR_HXX + +#include "command.h" +#include "playlist_error.h" + +#include <glib.h> + +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); + +#endif diff --git a/src/DatabaseCommands.cxx b/src/DatabaseCommands.cxx new file mode 100644 index 000000000..c3c22482e --- /dev/null +++ b/src/DatabaseCommands.cxx @@ -0,0 +1,222 @@ +/* + * 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.hxx" +#include "client_internal.h" +#include "tag.h" +#include "uri.h" +#include "SongFilter.hxx" + +extern "C" { +#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) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + const DatabaseSelection selection("", true, &filter); + + GError *error = NULL; + return db_selection_print(client, selection, true, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +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) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + return findAddIn(client->player_control, "", &filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +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]; + + SongFilter filter; + if (!filter.Parse(argc - 2, argv + 2, true)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + return search_add_to_playlist("", playlist, &filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_count(struct client *client, int argc, char *argv[]) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, false)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + return searchStatsForSongsIn(client, "", &filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +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[]) +{ + 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 */ + SongFilter *filter; + 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; + } + + filter = new SongFilter((unsigned)TAG_ARTIST, argv[2]); + } else if (argc > 2) { + filter = new SongFilter(); + if (!filter->Parse(argc - 2, argv + 2, false)) { + delete filter; + command_error(client, ACK_ERROR_ARG, + "not able to parse args"); + return COMMAND_RETURN_ERROR; + } + } else + filter = nullptr; + + GError *error = NULL; + enum command_return ret = + listAllUniqueTags(client, tagType, filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); + + delete filter; + + 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..2b93faf4a --- /dev/null +++ b/src/DatabaseCommands.hxx @@ -0,0 +1,55 @@ +/* + * 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" + +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[]); + +#endif diff --git a/src/database.c b/src/DatabaseGlue.cxx index 8c903bb45..b980ded83 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,47 @@ 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; +} + +const Database * +GetDatabase(GError **error_r) +{ + assert(db == nullptr || db_is_open); + + if (db == nullptr) + g_set_error_literal(error_r, db_quark(), DB_DISABLED, + "No database"); + + 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 +141,16 @@ db_get_song(const char *file) if (db == NULL) return NULL; - return db_plugin_get_song(db, file, NULL); + return db->GetSong(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) +void +db_return_song(struct song *song) { - struct db_selection selection; - db_selection_init(&selection, uri, true); + assert(db != nullptr); + assert(song != nullptr); - return db_visit(&selection, visitor, ctx, error_r); + db->ReturnSong(song); } bool @@ -140,8 +158,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 +169,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 +184,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..b38ba595a --- /dev/null +++ b/src/DatabaseGlue.hxx @@ -0,0 +1,44 @@ +/* + * 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" +#include "gerror.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(); + +/** + * 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(GError **error_r); + +#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..fb477e83b --- /dev/null +++ b/src/DatabasePlaylist.cxx @@ -0,0 +1,50 @@ +/* + * 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" +#include "PlaylistFile.hxx" +#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 SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection(uri, true, filter); + + using namespace std::placeholders; + const auto f = std::bind(AddSong, playlist_path_utf8, _1, _2); + return db->Visit(selection, f, error_r); +} diff --git a/src/db/simple_db_plugin.h b/src/DatabasePlaylist.hxx index 511505846..7c6952ffa 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); +class SongFilter; +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 SongFilter *filter, + GError **error_r); #endif diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx new file mode 100644 index 000000000..a175b3cd9 --- /dev/null +++ b/src/DatabasePlugin.hxx @@ -0,0 +1,148 @@ +/* + * 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. When + * you don't need this anymore, call ReturnSong(). + * + * @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; + + /** + * Mark the song object as "unused". Call this on objects + * returned by GetSong(). + */ + virtual void ReturnSong(struct song *song) 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..97ff9c12c --- /dev/null +++ b/src/DatabasePrint.cxx @@ -0,0 +1,234 @@ +/* + * 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" +#include "SongFilter.hxx" + +extern "C" { +#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) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto d = selection.filter == nullptr + ? std::bind(PrintDirectory, client, _1) + : VisitDirectory(); + const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, + client, _1); + const auto p = selection.filter == nullptr + ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, + client, _1, _2) + : VisitPlaylist(); + + return db->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 SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection(name, true, filter); + + 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 (!db->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 SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection("", true, filter); + + if (type == LOCATE_TAG_FILE_TYPE) { + using namespace std::placeholders; + const auto f = std::bind(PrintSongURIVisitor, client, _1); + return db->Visit(selection, f, error_r); + } else { + using namespace std::placeholders; + const auto f = std::bind(PrintUniqueTag, client, + (enum tag_type)type, _1); + return db->VisitUniqueTags(selection, (enum tag_type)type, + f, error_r); + } +} diff --git a/src/db_print.h b/src/DatabasePrint.hxx index 1b957da18..4aacd9363 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; +class SongFilter; +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,28 +44,16 @@ 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, + const SongFilter *filter, GError **error_r); -gcc_nonnull(1,3) +gcc_nonnull(1) bool listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria, + const SongFilter *filter, GError **error_r); #endif diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx new file mode 100644 index 000000000..325748d02 --- /dev/null +++ b/src/DatabaseQueue.cxx @@ -0,0 +1,60 @@ +/* + * 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 "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 SongFilter *filter, GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection(uri, true, filter); + + using namespace std::placeholders; + const auto f = std::bind(AddToQueue, pc, _1, _2); + return db->Visit(selection, f, error_r); +} diff --git a/src/DatabaseQueue.hxx b/src/DatabaseQueue.hxx new file mode 100644 index 000000000..21ffe0fb0 --- /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" + +class SongFilter; +struct player_control; + +gcc_nonnull(1,2) +bool +findAddIn(struct player_control *pc, const char *name, + const SongFilter *filter, 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/DatabaseSelection.cxx b/src/DatabaseSelection.cxx new file mode 100644 index 000000000..bd756f5f9 --- /dev/null +++ b/src/DatabaseSelection.cxx @@ -0,0 +1,27 @@ +/* + * 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 "DatabaseSelection.hxx" +#include "SongFilter.hxx" + +bool +DatabaseSelection::Match(const song &song) const +{ + return filter == nullptr || filter->Match(song); +} diff --git a/src/db_selection.h b/src/DatabaseSelection.hxx index 2cebb4907..3a81c01ec 100644 --- a/src/db_selection.h +++ b/src/DatabaseSelection.hxx @@ -17,17 +17,18 @@ * 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; +class SongFilter; struct song; -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 +40,17 @@ 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 SongFilter *filter; + + DatabaseSelection(const char *_uri, bool _recursive, + const SongFilter *_filter=nullptr) + :uri(_uri), recursive(_recursive), filter(_filter) { + assert(uri != NULL); + } + + gcc_pure + bool Match(const song &song) const; +}; #endif diff --git a/src/DatabaseVisitor.hxx b/src/DatabaseVisitor.hxx new file mode 100644 index 000000000..10f907cef --- /dev/null +++ b/src/DatabaseVisitor.hxx @@ -0,0 +1,38 @@ +/* + * 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_VISITOR_HXX +#define MPD_DATABASE_VISITOR_HXX + +#include "gerror.h" + +#include <functional> + +struct directory; +struct song; +struct playlist_metadata; + +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; + +typedef std::function<bool(const char *, GError **)> VisitString; + +#endif diff --git a/src/directory.c b/src/Directory.cxx index e886698d6..eeba903d1 100644 --- a/src/directory.c +++ b/src/Directory.cxx @@ -19,13 +19,16 @@ #include "config.h" #include "directory.h" +#include "SongFilter.hxx" + +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 <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,38 @@ 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 SongFilter *filter, + 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 ((filter == nullptr || filter->Match(*song)) && + !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, filter, + visit_directory, visit_song, visit_playlist, + error_r)) return false; } diff --git a/src/main.c b/src/Main.cxx index 12f8d86f6..057551391 100644 --- a/src/main.c +++ b/src/Main.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" -#include "main.h" +#include "Main.hxx" +#include "PlaylistFile.hxx" +#include "chunk.h" + +extern "C" { #include "daemon.h" #include "io_thread.h" #include "client.h" #include "client_idle.h" #include "idle.h" -#include "command.h" +#include "AllCommands.h" #include "playlist.h" -#include "stored_playlist.h" #include "database.h" #include "update.h" #include "player_thread.h" @@ -35,7 +38,6 @@ #include "conf.h" #include "path.h" #include "mapper.h" -#include "chunk.h" #include "player_control.h" #include "stats.h" #include "sig_handlers.h" @@ -51,12 +53,14 @@ #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" +extern "C" { + #ifdef ENABLE_INOTIFY #include "inotify_update.h" #endif @@ -69,6 +73,8 @@ #include "archive_list.h" #endif +} + #include <glib.h> #include <unistd.h> @@ -153,31 +159,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 +364,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 +548,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/main.h b/src/Main.hxx index 2a7d75910..54916ff4b 100644 --- a/src/main.h +++ b/src/Main.hxx @@ -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 @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MAIN_H -#define MAIN_H +#ifndef MPD_MAIN_HXX +#define MPD_MAIN_HXX #include <glib.h> diff --git a/src/MessageCommands.cxx b/src/MessageCommands.cxx new file mode 100644 index 000000000..428470e60 --- /dev/null +++ b/src/MessageCommands.cxx @@ -0,0 +1,197 @@ +/* + * 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 "MessageCommands.hxx" + +extern "C" { +#include "protocol/argparser.h" +#include "protocol/result.h" +#include "client_internal.h" +#include "client_subscribe.h" +} + +#include <assert.h> + +enum command_return +handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + switch (client_subscribe(client, argv[1])) { + case CLIENT_SUBSCRIBE_OK: + return COMMAND_RETURN_OK; + + case CLIENT_SUBSCRIBE_INVALID: + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_ALREADY: + command_error(client, ACK_ERROR_EXIST, + "already subscribed to this channel"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_FULL: + command_error(client, ACK_ERROR_EXIST, + "subscription list is full"); + return COMMAND_RETURN_ERROR; + } + + /* unreachable */ + return COMMAND_RETURN_OK; +} + +enum command_return +handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + if (client_unsubscribe(client, argv[1])) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "not subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + +struct channels_context { + GStringChunk *chunk; + + GHashTable *channels; +}; + +static void +collect_channels(gpointer data, gpointer user_data) +{ + struct channels_context *context = + (struct channels_context *)user_data; + const struct client *client = (const struct client *)data; + + for (GSList *i = client->subscriptions; i != NULL; + i = g_slist_next(i)) { + const char *channel = (const char *)i->data; + + if (g_hash_table_lookup(context->channels, channel) == NULL) { + char *channel2 = g_string_chunk_insert(context->chunk, + channel); + g_hash_table_insert(context->channels, channel2, + context); + } + } +} + +static void +print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data) +{ + struct client *client = (struct client *)user_data; + const char *channel = (const char *)key; + + client_printf(client, "channel: %s\n", channel); +} + +enum command_return +handle_channels(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + struct channels_context context = { + g_string_chunk_new(1024), + g_hash_table_new(g_str_hash, g_str_equal), + }; + + client_list_foreach(collect_channels, &context); + + g_hash_table_foreach(context.channels, print_channel, client); + + g_hash_table_destroy(context.channels); + g_string_chunk_free(context.chunk); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_read_messages(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + GSList *messages = client_read_messages(client); + + for (GSList *i = messages; i != NULL; i = g_slist_next(i)) { + struct client_message *msg = (struct client_message *)i->data; + + client_printf(client, "channel: %s\nmessage: %s\n", + msg->channel, msg->message); + client_message_free(msg); + } + + g_slist_free(messages); + + return COMMAND_RETURN_OK; +} + +struct send_message_context { + struct client_message msg; + + bool sent; +}; + +static void +send_message(gpointer data, gpointer user_data) +{ + struct send_message_context *context = + (struct send_message_context *)user_data; + struct client *client = (struct client *)data; + + if (client_push_message(client, &context->msg)) + context->sent = true; +} + +enum command_return +handle_send_message(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 3); + + if (!client_message_valid_channel_name(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + } + + struct send_message_context context; + context.sent = false; + + client_message_init(&context.msg, argv[1], argv[2]); + + client_list_foreach(send_message, &context); + + client_message_deinit(&context.msg); + + if (context.sent) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "nobody is subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} diff --git a/src/MessageCommands.hxx b/src/MessageCommands.hxx new file mode 100644 index 000000000..111c06ff7 --- /dev/null +++ b/src/MessageCommands.hxx @@ -0,0 +1,40 @@ +/* + * 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_MESSAGE_COMMANDS_HXX +#define MPD_MESSAGE_COMMANDS_HXX + +#include "command.h" + +enum command_return +handle_subscribe(struct client *client, int argc, char *argv[]); + +enum command_return +handle_unsubscribe(struct client *client, int argc, char *argv[]); + +enum command_return +handle_channels(struct client *client, int argc, char *argv[]); + +enum command_return +handle_read_messages(struct client *client, int argc, char *argv[]); + +enum command_return +handle_send_message(struct client *client, int argc, char *argv[]); + +#endif diff --git a/src/OtherCommands.cxx b/src/OtherCommands.cxx new file mode 100644 index 000000000..a703a2ffe --- /dev/null +++ b/src/OtherCommands.cxx @@ -0,0 +1,311 @@ +/* + * 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 "OtherCommands.hxx" +#include "DatabaseCommands.hxx" +#include "CommandError.hxx" +#include "directory.h" +#include "song.h" + +extern "C" { +#include "protocol/argparser.h" +#include "protocol/result.h" +#include "time_print.h" +#include "ls.h" +#include "uri.h" +#include "decoder_print.h" +#include "update.h" +#include "volume.h" +#include "stats.h" +#include "permission.h" +} + +#include "PlaylistFile.hxx" + +extern "C" { +#include "client.h" +#include "client_idle.h" +#include "client_file.h" +#include "tag_print.h" +#include "idle.h" +#include "mapper.h" +#include "song_print.h" +} + +#ifdef ENABLE_SQLITE +#include "sticker.h" +#endif + +#include <assert.h> +#include <string.h> + +static void +print_spl_list(struct client *client, const PlaylistFileList &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +enum command_return +handle_urlhandlers(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + if (client_is_local(client)) + client_puts(client, "handler: file://\n"); + print_supported_uri_schemes(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_decoders(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + decoder_list_print(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_tagtypes(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + tag_print_types(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_kill(G_GNUC_UNUSED struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + return COMMAND_RETURN_KILL; +} + +enum command_return +handle_close(G_GNUC_UNUSED struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + return COMMAND_RETURN_CLOSE; +} + +enum command_return +handle_lsinfo(struct client *client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + if (strncmp(uri, "file:///", 8) == 0) { + /* print information about an arbitrary local file */ + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + struct song *song = song_file_load(path, NULL); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such file"); + return COMMAND_RETURN_ERROR; + } + + song_print_info(client, song); + song_free(song); + return COMMAND_RETURN_OK; + } + + enum command_return result = handle_lsinfo2(client, argc, argv); + if (result != COMMAND_RETURN_OK) + return result; + + if (isRootDirectory(uri)) { + const auto &list = ListPlaylistFiles(NULL); + print_spl_list(client, list); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *path = NULL; + unsigned ret; + + assert(argc <= 2); + if (argc == 2) { + path = argv[1]; + + if (*path == 0 || strcmp(path, "/") == 0) + /* backwards compatibility with MPD 0.15 */ + path = NULL; + else if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return COMMAND_RETURN_ERROR; + } + } + + ret = update_enqueue(path, false); + if (ret > 0) { + client_printf(client, "updating_db: %i\n", ret); + return COMMAND_RETURN_OK; + } else { + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *path = NULL; + unsigned ret; + + assert(argc <= 2); + if (argc == 2) { + path = argv[1]; + + if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return COMMAND_RETURN_ERROR; + } + } + + ret = update_enqueue(path, true); + if (ret > 0) { + client_printf(client, "updating_db: %i\n", ret); + return COMMAND_RETURN_OK; + } else { + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned level; + bool success; + + if (!check_unsigned(client, &level, argv[1])) + return COMMAND_RETURN_ERROR; + + if (level > 100) { + command_error(client, ACK_ERROR_ARG, "Invalid volume value"); + return COMMAND_RETURN_ERROR; + } + + success = volume_level_change(level); + if (!success) { + command_error(client, ACK_ERROR_SYSTEM, + "problems setting volume"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_stats(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + stats_print(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_ping(G_GNUC_UNUSED struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + return COMMAND_RETURN_OK; +} + +enum command_return +handle_password(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned permission = 0; + + if (getPermissionFromPassword(argv[1], &permission) < 0) { + command_error(client, ACK_ERROR_PASSWORD, "incorrect password"); + return COMMAND_RETURN_ERROR; + } + + client_set_permission(client, permission); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_config(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + if (!client_is_local(client)) { + command_error(client, ACK_ERROR_PERMISSION, + "Command only permitted to local clients"); + return COMMAND_RETURN_ERROR; + } + + const char *path = mapper_get_music_directory_utf8(); + if (path != NULL) + client_printf(client, "music_directory: %s\n", path); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_idle(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + unsigned flags = 0, j; + int i; + const char *const* idle_names; + + idle_names = idle_get_names(); + for (i = 1; i < argc; ++i) { + if (!argv[i]) + continue; + + for (j = 0; idle_names[j]; ++j) { + if (!g_ascii_strcasecmp(argv[i], idle_names[j])) { + flags |= (1 << j); + } + } + } + + /* No argument means that the client wants to receive everything */ + if (flags == 0) + flags = ~0; + + /* enable "idle" mode on this client */ + client_idle_wait(client, flags); + + return COMMAND_RETURN_IDLE; +} diff --git a/src/OtherCommands.hxx b/src/OtherCommands.hxx new file mode 100644 index 000000000..c4cc3ac22 --- /dev/null +++ b/src/OtherCommands.hxx @@ -0,0 +1,67 @@ +/* + * 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_OTHER_COMMANDS_HXX +#define MPD_OTHER_COMMANDS_HXX + +#include "command.h" + +enum command_return +handle_urlhandlers(struct client *client, int argc, char *argv[]); + +enum command_return +handle_decoders(struct client *client, int argc, char *argv[]); + +enum command_return +handle_tagtypes(struct client *client, int argc, char *argv[]); + +enum command_return +handle_kill(struct client *client, int argc, char *argv[]); + +enum command_return +handle_close(struct client *client, int argc, char *argv[]); + +enum command_return +handle_lsinfo(struct client *client, int argc, char *argv[]); + +enum command_return +handle_update(struct client *client, int argc, char *argv[]); + +enum command_return +handle_rescan(struct client *client, int argc, char *argv[]); + +enum command_return +handle_setvol(struct client *client, int argc, char *argv[]); + +enum command_return +handle_stats(struct client *client, int argc, char *argv[]); + +enum command_return +handle_ping(struct client *client, int argc, char *argv[]); + +enum command_return +handle_password(struct client *client, int argc, char *argv[]); + +enum command_return +handle_config(struct client *client, int argc, char *argv[]); + +enum command_return +handle_idle(struct client *client, int argc, char *argv[]); + +#endif diff --git a/src/OutputCommands.cxx b/src/OutputCommands.cxx new file mode 100644 index 000000000..88cb95ac4 --- /dev/null +++ b/src/OutputCommands.cxx @@ -0,0 +1,77 @@ +/* + * 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 "OutputCommands.hxx" + +extern "C" { +#include "protocol/argparser.h" +#include "protocol/result.h" +#include "output_command.h" +#include "output_print.h" +} + +#include <string.h> + +enum command_return +handle_enableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_enable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_disableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_disable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_devices(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + printAudioDevices(client); + + return COMMAND_RETURN_OK; +} diff --git a/src/OutputCommands.hxx b/src/OutputCommands.hxx new file mode 100644 index 000000000..1dc4c5ee6 --- /dev/null +++ b/src/OutputCommands.hxx @@ -0,0 +1,34 @@ +/* + * 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_COMMANDS_HXX +#define MPD_OUTPUT_COMMANDS_HXX + +#include "command.h" + +enum command_return +handle_enableoutput(struct client *client, int argc, char *argv[]); + +enum command_return +handle_disableoutput(struct client *client, int argc, char *argv[]); + +enum command_return +handle_devices(struct client *client, int argc, char *argv[]); + +#endif diff --git a/src/PlayerCommands.cxx b/src/PlayerCommands.cxx new file mode 100644 index 000000000..015ac9a15 --- /dev/null +++ b/src/PlayerCommands.cxx @@ -0,0 +1,391 @@ +/* + * 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 "PlayerCommands.hxx" +#include "CommandError.hxx" +#include "PlaylistPrint.hxx" + +extern "C" { +#include "protocol/argparser.h" +#include "protocol/result.h" +#include "player_control.h" +#include "playlist.h" +#include "update.h" +#include "volume.h" +#include "client.h" +#include "client_internal.h" +#include "replay_gain_config.h" +} + +#include <errno.h> + +#define COMMAND_STATUS_STATE "state" +#define COMMAND_STATUS_REPEAT "repeat" +#define COMMAND_STATUS_SINGLE "single" +#define COMMAND_STATUS_CONSUME "consume" +#define COMMAND_STATUS_RANDOM "random" +#define COMMAND_STATUS_PLAYLIST "playlist" +#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" +#define COMMAND_STATUS_SONG "song" +#define COMMAND_STATUS_SONGID "songid" +#define COMMAND_STATUS_NEXTSONG "nextsong" +#define COMMAND_STATUS_NEXTSONGID "nextsongid" +#define COMMAND_STATUS_TIME "time" +#define COMMAND_STATUS_BITRATE "bitrate" +#define COMMAND_STATUS_ERROR "error" +#define COMMAND_STATUS_CROSSFADE "xfade" +#define COMMAND_STATUS_MIXRAMPDB "mixrampdb" +#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay" +#define COMMAND_STATUS_AUDIO "audio" +#define COMMAND_STATUS_UPDATING_DB "updating_db" + +enum command_return +handle_play(struct client *client, int argc, char *argv[]) +{ + int song = -1; + enum playlist_result result; + + if (argc == 2 && !check_int(client, &song, argv[1])) + return COMMAND_RETURN_ERROR; + result = playlist_play(&g_playlist, client->player_control, song); + return print_playlist_result(client, result); +} + +enum command_return +handle_playid(struct client *client, int argc, char *argv[]) +{ + int id = -1; + enum playlist_result result; + + if (argc == 2 && !check_int(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + result = playlist_play_id(&g_playlist, client->player_control, id); + return print_playlist_result(client, result); +} + +enum command_return +handle_stop(G_GNUC_UNUSED struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_stop(&g_playlist, client->player_control); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_currentsong(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_print_current(client, &g_playlist); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_pause(struct client *client, + int argc, char *argv[]) +{ + if (argc == 2) { + bool pause_flag; + if (!check_bool(client, &pause_flag, argv[1])) + return COMMAND_RETURN_ERROR; + + pc_set_pause(client->player_control, pause_flag); + } else + pc_pause(client->player_control); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_status(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + const char *state = NULL; + struct player_status player_status; + int updateJobId; + char *error; + int song; + + pc_get_status(client->player_control, &player_status); + + switch (player_status.state) { + case PLAYER_STATE_STOP: + state = "stop"; + break; + case PLAYER_STATE_PAUSE: + state = "pause"; + break; + case PLAYER_STATE_PLAY: + state = "play"; + break; + } + + client_printf(client, + "volume: %i\n" + COMMAND_STATUS_REPEAT ": %i\n" + COMMAND_STATUS_RANDOM ": %i\n" + COMMAND_STATUS_SINGLE ": %i\n" + COMMAND_STATUS_CONSUME ": %i\n" + COMMAND_STATUS_PLAYLIST ": %li\n" + COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" + COMMAND_STATUS_CROSSFADE ": %i\n" + COMMAND_STATUS_MIXRAMPDB ": %f\n" + COMMAND_STATUS_MIXRAMPDELAY ": %f\n" + COMMAND_STATUS_STATE ": %s\n", + volume_level_get(), + playlist_get_repeat(&g_playlist), + playlist_get_random(&g_playlist), + playlist_get_single(&g_playlist), + playlist_get_consume(&g_playlist), + playlist_get_version(&g_playlist), + playlist_get_length(&g_playlist), + (int)(pc_get_cross_fade(client->player_control) + 0.5), + pc_get_mixramp_db(client->player_control), + pc_get_mixramp_delay(client->player_control), + state); + + song = playlist_get_current_song(&g_playlist); + if (song >= 0) { + client_printf(client, + COMMAND_STATUS_SONG ": %i\n" + COMMAND_STATUS_SONGID ": %u\n", + song, playlist_get_song_id(&g_playlist, song)); + } + + if (player_status.state != PLAYER_STATE_STOP) { + struct audio_format_string af_string; + + client_printf(client, + COMMAND_STATUS_TIME ": %i:%i\n" + "elapsed: %1.3f\n" + COMMAND_STATUS_BITRATE ": %u\n" + COMMAND_STATUS_AUDIO ": %s\n", + (int)(player_status.elapsed_time + 0.5), + (int)(player_status.total_time + 0.5), + player_status.elapsed_time, + player_status.bit_rate, + audio_format_to_string(&player_status.audio_format, + &af_string)); + } + + if ((updateJobId = isUpdatingDB())) { + client_printf(client, + COMMAND_STATUS_UPDATING_DB ": %i\n", + updateJobId); + } + + error = pc_get_error_message(client->player_control); + if (error != NULL) { + client_printf(client, + COMMAND_STATUS_ERROR ": %s\n", + error); + g_free(error); + } + + song = playlist_get_next_song(&g_playlist); + if (song >= 0) { + client_printf(client, + COMMAND_STATUS_NEXTSONG ": %i\n" + COMMAND_STATUS_NEXTSONGID ": %u\n", + song, playlist_get_song_id(&g_playlist, song)); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_next(G_GNUC_UNUSED struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + /* single mode is not considered when this is user who + * wants to change song. */ + const bool single = g_playlist.queue.single; + g_playlist.queue.single = false; + + playlist_next(&g_playlist, client->player_control); + + g_playlist.queue.single = single; + return COMMAND_RETURN_OK; +} + +enum command_return +handle_previous(G_GNUC_UNUSED struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_previous(&g_playlist, client->player_control); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_set_repeat(&g_playlist, client->player_control, status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_set_single(&g_playlist, client->player_control, status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_set_consume(&g_playlist, status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_set_random(&g_playlist, client->player_control, status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_clearerror(G_GNUC_UNUSED struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + pc_clear_error(client->player_control); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned song, seek_time; + enum playlist_result result; + + if (!check_unsigned(client, &song, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + result = playlist_seek_song(&g_playlist, client->player_control, + song, seek_time); + return print_playlist_result(client, result); +} + +enum command_return +handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id, seek_time; + enum playlist_result result; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + result = playlist_seek_song_id(&g_playlist, client->player_control, + id, seek_time); + return print_playlist_result(client, result); +} + +enum command_return +handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *p = argv[1]; + bool relative = *p == '+' || *p == '-'; + int seek_time; + if (!check_int(client, &seek_time, p)) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + playlist_seek_current(&g_playlist, client->player_control, + seek_time, relative); + return print_playlist_result(client, result); +} + +enum command_return +handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned xfade_time; + + if (!check_unsigned(client, &xfade_time, argv[1])) + return COMMAND_RETURN_ERROR; + pc_set_cross_fade(client->player_control, xfade_time); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + float db; + + if (!check_float(client, &db, argv[1])) + return COMMAND_RETURN_ERROR; + pc_set_mixramp_db(client->player_control, db); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + float delay_secs; + + if (!check_float(client, &delay_secs, argv[1])) + return COMMAND_RETURN_ERROR; + pc_set_mixramp_delay(client->player_control, delay_secs); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_replay_gain_mode(struct client *client, + G_GNUC_UNUSED int argc, char *argv[]) +{ + if (!replay_gain_set_mode_string(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "Unrecognized replay gain mode"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_replay_gain_status(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client_printf(client, "replay_gain_mode: %s\n", + replay_gain_get_mode_string()); + return COMMAND_RETURN_OK; +} diff --git a/src/PlayerCommands.hxx b/src/PlayerCommands.hxx new file mode 100644 index 000000000..40a8a779a --- /dev/null +++ b/src/PlayerCommands.hxx @@ -0,0 +1,88 @@ +/* + * 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_PLAYER_COMMANDS_HXX +#define MPD_PLAYER_COMMANDS_HXX + +#include "command.h" + +enum command_return +handle_play(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playid(struct client *client, int argc, char *argv[]); + +enum command_return +handle_stop(struct client *client, int argc, char *argv[]); + +enum command_return +handle_currentsong(struct client *client, int argc, char *argv[]); + +enum command_return +handle_pause(struct client *client, int argc, char *argv[]); + +enum command_return +handle_status(struct client *client, int argc, char *argv[]); + +enum command_return +handle_next(struct client *client, int argc, char *argv[]); + +enum command_return +handle_previous(struct client *client, int argc, char *avg[]); + +enum command_return +handle_repeat(struct client *client, int argc, char *argv[]); + +enum command_return +handle_single(struct client *client, int argc, char *argv[]); + +enum command_return +handle_consume(struct client *client, int argc, char *argv[]); + +enum command_return +handle_random(struct client *client, int argc, char *argv[]); + +enum command_return +handle_clearerror(struct client *client, int argc, char *argv[]); + +enum command_return +handle_seek(struct client *client, int argc, char *argv[]); + +enum command_return +handle_seekid(struct client *client, int argc, char *argv[]); + +enum command_return +handle_seekcur(struct client *client, int argc, char *argv[]); + +enum command_return +handle_crossfade(struct client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdb(struct client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdelay(struct client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_mode(struct client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_status(struct client *client, int argc, char *argv[]); + +#endif diff --git a/src/PlaylistCommands.cxx b/src/PlaylistCommands.cxx new file mode 100644 index 000000000..2b5f0b2cf --- /dev/null +++ b/src/PlaylistCommands.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 "PlaylistCommands.hxx" +#include "DatabasePlaylist.hxx" +#include "CommandError.hxx" +#include "PlaylistPrint.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" + +extern "C" { +#include "protocol/argparser.h" +#include "protocol/result.h" +#include "playlist.h" +#include "playlist_queue.h" +#include "time_print.h" +#include "ls.h" +#include "uri.h" +#include "client_internal.h" +} + +#include <assert.h> +#include <stdlib.h> + +static void +print_spl_list(struct client *client, const PlaylistFileList &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +enum command_return +handle_save(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + enum playlist_result result; + + result = spl_save_playlist(argv[1], &g_playlist); + return print_playlist_result(client, result); +} + +enum command_return +handle_load(struct client *client, int argc, char *argv[]) +{ + unsigned start_index, end_index; + + if (argc < 3) { + start_index = 0; + end_index = G_MAXUINT; + } else if (!check_range(client, &start_index, &end_index, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result; + + result = playlist_open_into_queue(argv[1], + start_index, end_index, + &g_playlist, + client->player_control, true); + if (result != PLAYLIST_RESULT_NO_SUCH_LIST) + return print_playlist_result(client, result); + + GError *error = NULL; + if (playlist_load_spl(&g_playlist, client->player_control, + argv[1], start_index, end_index, + &error)) + return COMMAND_RETURN_OK; + + if (error->domain == playlist_quark() && + error->code == PLAYLIST_RESULT_BAD_NAME) + /* the message for BAD_NAME is confusing when the + client wants to load a playlist file from the music + directory; patch the GError object to show "no such + playlist" instead */ + error->code = PLAYLIST_RESULT_NO_SUCH_LIST; + + return print_error(client, error); +} + +enum command_return +handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], false)) + return COMMAND_RETURN_OK; + + GError *error = NULL; + return spl_print(client, argv[1], false, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_listplaylistinfo(struct client *client, + G_GNUC_UNUSED int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], true)) + return COMMAND_RETURN_OK; + + GError *error = NULL; + return spl_print(client, argv[1], true, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_rm(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_delete(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_rename(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_rename(argv[1], argv[2], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistdelete(struct client *client, + G_GNUC_UNUSED int argc, char *argv[]) { + char *playlist = argv[1]; + unsigned from; + + if (!check_unsigned(client, &from, argv[2])) + return COMMAND_RETURN_ERROR; + + GError *error = NULL; + return spl_remove_index(playlist, from, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + char *playlist = argv[1]; + unsigned from, to; + + if (!check_unsigned(client, &from, argv[2])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &to, argv[3])) + return COMMAND_RETURN_ERROR; + + GError *error = NULL; + return spl_move_index(playlist, from, to, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_clear(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + char *playlist = argv[1]; + char *uri = argv[2]; + + bool success; + GError *error = NULL; + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + success = spl_append_uri(argv[1], playlist, &error); + } else + success = search_add_to_playlist(uri, playlist, nullptr, + &error); + + if (!success && error == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "directory or file not found"); + return COMMAND_RETURN_ERROR; + } + + return success ? COMMAND_RETURN_OK : print_error(client, error); +} + +enum command_return +handle_listplaylists(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + GError *error = NULL; + const auto list = ListPlaylistFiles(&error); + if (list.empty() && error != NULL) + return print_error(client, error); + + print_spl_list(client, list); + return COMMAND_RETURN_OK; +} diff --git a/src/PlaylistCommands.hxx b/src/PlaylistCommands.hxx new file mode 100644 index 000000000..267ed5c95 --- /dev/null +++ b/src/PlaylistCommands.hxx @@ -0,0 +1,58 @@ +/* + * 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_PLAYLIST_COMMANDS_HXX +#define MPD_PLAYLIST_COMMANDS_HXX + +#include "command.h" + +enum command_return +handle_save(struct client *client, int argc, char *argv[]); + +enum command_return +handle_load(struct client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylist(struct client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylistinfo(struct client *client, int argc, char *argv[]); + +enum command_return +handle_rm(struct client *client, int argc, char *argv[]); + +enum command_return +handle_rename(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playlistdelete(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playlistmove(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playlistclear(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playlistadd(struct client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylists(struct client *client, int argc, char *argv[]); + +#endif diff --git a/src/stored_playlist.c b/src/PlaylistFile.cxx index 39ba2bac1..cb86c13ac 100644 --- a/src/stored_playlist.c +++ b/src/PlaylistFile.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,16 +18,20 @@ */ #include "config.h" -#include "stored_playlist.h" -#include "playlist_save.h" -#include "text_file.h" +#include "PlaylistFile.hxx" +#include "PlaylistSave.hxx" #include "song.h" + +extern "C" { +#include "text_file.h" #include "mapper.h" #include "path.h" #include "uri.h" #include "database.h" #include "idle.h" #include "conf.h" +} + #include "glib_compat.h" #include <assert.h> @@ -134,90 +138,69 @@ playlist_errno(GError **error_r) } } -static struct stored_playlist_info * -load_playlist_info(const char *parent_path_fs, const char *name_fs) +static bool +LoadPlaylistFileInfo(PlaylistFileInfo &info, + const char *parent_path_fs, const char *name_fs) { size_t name_length = strlen(name_fs); - char *path_fs, *name, *name_utf8; - int ret; - struct stat st; - struct stored_playlist_info *playlist; if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) || memchr(name_fs, '\n', name_length) != NULL) - return NULL; + return false; if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX)) - return NULL; + return false; - path_fs = g_build_filename(parent_path_fs, name_fs, NULL); - ret = stat(path_fs, &st); + char *path_fs = g_build_filename(parent_path_fs, name_fs, NULL); + struct stat st; + int ret = stat(path_fs, &st); g_free(path_fs); if (ret < 0 || !S_ISREG(st.st_mode)) - return NULL; + return false; - name = g_strndup(name_fs, - name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); - name_utf8 = fs_charset_to_utf8(name); + char *name = g_strndup(name_fs, + name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); + char *name_utf8 = fs_charset_to_utf8(name); g_free(name); if (name_utf8 == NULL) - return NULL; + return false; - playlist = g_new(struct stored_playlist_info, 1); - playlist->name = name_utf8; - playlist->mtime = st.st_mtime; - return playlist; + info.name = name_utf8; + info.mtime = st.st_mtime; + return true; } -GPtrArray * -spl_list(GError **error_r) +PlaylistFileList +ListPlaylistFiles(GError **error_r) { - const char *parent_path_fs = spl_map(error_r); - DIR *dir; - struct dirent *ent; - GPtrArray *list; - struct stored_playlist_info *playlist; + PlaylistFileList list; + const char *parent_path_fs = spl_map(error_r); if (parent_path_fs == NULL) - return NULL; + return list; - dir = opendir(parent_path_fs); + DIR *dir = opendir(parent_path_fs); if (dir == NULL) { g_set_error_literal(error_r, g_file_error_quark(), errno, g_strerror(errno)); - return NULL; + return list; } - list = g_ptr_array_new(); - + PlaylistFileInfo info; + struct dirent *ent; while ((ent = readdir(dir)) != NULL) { - playlist = load_playlist_info(parent_path_fs, ent->d_name); - if (playlist != NULL) - g_ptr_array_add(list, playlist); + if (LoadPlaylistFileInfo(info, parent_path_fs, ent->d_name)) + list.push_back(std::move(info)); } closedir(dir); return list; } -void -spl_list_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - struct stored_playlist_info *playlist = - g_ptr_array_index(list, i); - g_free(playlist->name); - g_free(playlist); - } - - g_ptr_array_free(list, true); -} - static bool -spl_save(GPtrArray *list, const char *utf8path, GError **error_r) +SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path, + GError **error_r) { - FILE *file; - assert(utf8path != NULL); if (spl_map(error_r) == NULL) @@ -227,45 +210,39 @@ spl_save(GPtrArray *list, const char *utf8path, GError **error_r) if (path_fs == NULL) return false; - file = fopen(path_fs, "w"); + FILE *file = fopen(path_fs, "w"); g_free(path_fs); if (file == NULL) { playlist_errno(error_r); return false; } - for (unsigned i = 0; i < list->len; ++i) { - const char *uri = g_ptr_array_index(list, i); - playlist_print_uri(file, uri); - } + for (const auto &uri_utf8 : contents) + playlist_print_uri(file, uri_utf8.c_str()); fclose(file); return true; } -GPtrArray * -spl_load(const char *utf8path, GError **error_r) +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, GError **error_r) { - FILE *file; - GPtrArray *list; - char *path_fs; + PlaylistFileContents contents; if (spl_map(error_r) == NULL) - return NULL; + return contents; - path_fs = spl_map_to_fs(utf8path, error_r); + char *path_fs = spl_map_to_fs(utf8path, error_r); if (path_fs == NULL) - return NULL; + return contents; - file = fopen(path_fs, "r"); + FILE *file = fopen(path_fs, "r"); g_free(path_fs); if (file == NULL) { playlist_errno(error_r); - return NULL; + return contents; } - list = g_ptr_array_new(); - GString *buffer = g_string_sized_new(1024); char *s; while ((s = read_text_line(file, buffer)) != NULL) { @@ -283,80 +260,46 @@ spl_load(const char *utf8path, GError **error_r) } else s = g_strdup(s); - g_ptr_array_add(list, s); - - if (list->len >= playlist_max_length) + contents.emplace_back(s); + if (contents.size() >= playlist_max_length) break; } fclose(file); - return list; -} - -void -spl_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - char *uri = g_ptr_array_index(list, i); - g_free(uri); - } - - g_ptr_array_free(list, true); -} - -static char * -spl_remove_index_internal(GPtrArray *list, unsigned idx) -{ - char *uri; - - assert(idx < list->len); - - uri = g_ptr_array_remove_index(list, idx); - assert(uri != NULL); - return uri; -} - -static void -spl_insert_index_internal(GPtrArray *list, unsigned idx, char *uri) -{ - assert(idx <= list->len); - - g_ptr_array_add(list, uri); - - memmove(list->pdata + idx + 1, list->pdata + idx, - (list->len - idx - 1) * sizeof(list->pdata[0])); - g_ptr_array_index(list, idx) = uri; + return contents; } bool spl_move_index(const char *utf8path, unsigned src, unsigned dest, GError **error_r) { - char *uri; - if (src == dest) /* this doesn't check whether the playlist exists, but what the hell.. */ return true; - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) + GError *error = nullptr; + auto contents = LoadPlaylistFile(utf8path, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - if (src >= list->len || dest >= list->len) { - spl_free(list); + if (src >= contents.size() || dest >= contents.size()) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_BAD_RANGE, "Bad range"); return false; } - uri = spl_remove_index_internal(list, src); - spl_insert_index_internal(list, dest, uri); + const auto src_i = std::next(contents.begin(), src); + auto value = std::move(*src_i); + contents.erase(src_i); - bool result = spl_save(list, utf8path, error_r); + const auto dest_i = std::next(contents.begin(), dest); + contents.insert(dest_i, std::move(value)); - spl_free(list); + bool result = SavePlaylistFile(contents, utf8path, error_r); idle_add(IDLE_STORED_PLAYLIST); return result; @@ -390,14 +333,11 @@ spl_clear(const char *utf8path, GError **error_r) bool spl_delete(const char *name_utf8, GError **error_r) { - char *path_fs; - int ret; - - path_fs = spl_map_to_fs(name_utf8, error_r); + char *path_fs = spl_map_to_fs(name_utf8, error_r); if (path_fs == NULL) return false; - ret = unlink(path_fs); + int ret = unlink(path_fs); g_free(path_fs); if (ret < 0) { playlist_errno(error_r); @@ -411,25 +351,23 @@ spl_delete(const char *name_utf8, GError **error_r) bool spl_remove_index(const char *utf8path, unsigned pos, GError **error_r) { - char *uri; - - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) + GError *error = nullptr; + auto contents = LoadPlaylistFile(utf8path, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - if (pos >= list->len) { - spl_free(list); + if (pos >= contents.size()) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_BAD_RANGE, "Bad range"); return false; } - uri = spl_remove_index_internal(list, pos); - g_free(uri); - bool result = spl_save(list, utf8path, error_r); + contents.erase(std::next(contents.begin(), pos)); - spl_free(list); + bool result = SavePlaylistFile(contents, utf8path, error_r); idle_add(IDLE_STORED_PLAYLIST); return result; @@ -439,7 +377,6 @@ bool spl_append_song(const char *utf8path, struct song *song, GError **error_r) { FILE *file; - struct stat st; if (spl_map(error_r) == NULL) return false; @@ -455,6 +392,7 @@ spl_append_song(const char *utf8path, struct song *song, GError **error_r) return false; } + struct stat st; if (fstat(fileno(file), &st) < 0) { playlist_errno(error_r); fclose(file); @@ -480,15 +418,13 @@ spl_append_song(const char *utf8path, struct song *song, GError **error_r) bool spl_append_uri(const char *url, const char *utf8file, GError **error_r) { - struct song *song; - if (uri_has_scheme(url)) { - song = song_remote_new(url); + struct song *song = song_remote_new(url); bool success = spl_append_song(utf8file, song, error_r); song_free(song); return success; } else { - song = db_get_song(url); + struct song *song = db_get_song(url); if (song == NULL) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_NO_SUCH_SONG, @@ -496,7 +432,9 @@ spl_append_uri(const char *url, const char *utf8file, GError **error_r) return false; } - return spl_append_song(utf8file, song, error_r); + bool success = spl_append_song(utf8file, song, error_r); + db_return_song(song); + return success; } } diff --git a/src/stored_playlist.h b/src/PlaylistFile.hxx index cfe49633c..b20f0d762 100644 --- a/src/stored_playlist.h +++ b/src/PlaylistFile.hxx @@ -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 @@ -17,8 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_STORED_PLAYLIST_H -#define MPD_STORED_PLAYLIST_H +#ifndef MPD_PLAYLIST_FILE_HXX +#define MPD_PLAYLIST_FILE_HXX + +#include <list> +#include <vector> +#include <string> #include <glib.h> #include <stdbool.h> @@ -26,12 +30,16 @@ struct song; -struct stored_playlist_info { - char *name; +struct PlaylistFileInfo { + std::string name; time_t mtime; }; +typedef std::list<PlaylistFileInfo> PlaylistFileList; + +typedef std::vector<std::string> PlaylistFileContents; + extern bool playlist_saveAbsolutePaths; /** @@ -40,6 +48,8 @@ extern bool playlist_saveAbsolutePaths; void spl_global_init(void); +#ifdef __cplusplus + /** * Determines whether the specified string is a valid name for a * stored playlist. @@ -51,17 +61,11 @@ spl_valid_name(const char *name_utf8); * Returns a list of stored_playlist_info struct pointers. Returns * NULL if an error occurred. */ -GPtrArray * -spl_list(GError **error_r); - -void -spl_list_free(GPtrArray *list); - -GPtrArray * -spl_load(const char *utf8path, GError **error_r); +PlaylistFileList +ListPlaylistFiles(GError **error_r); -void -spl_free(GPtrArray *list); +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, GError **error_r); bool spl_move_index(const char *utf8path, unsigned src, unsigned dest, @@ -86,3 +90,5 @@ bool spl_rename(const char *utf8from, const char *utf8to, GError **error_r); #endif + +#endif diff --git a/src/playlist_mapper.c b/src/PlaylistMapper.cxx index 13adb80d0..39ac043e4 100644 --- a/src/playlist_mapper.c +++ b/src/PlaylistMapper.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,11 +18,14 @@ */ #include "config.h" -#include "playlist_mapper.h" +#include "PlaylistMapper.h" +#include "PlaylistFile.hxx" + +extern "C" { #include "playlist_list.h" -#include "stored_playlist.h" #include "mapper.h" #include "uri.h" +} #include <assert.h> diff --git a/src/playlist_mapper.h b/src/PlaylistMapper.h index 9a7187d93..829aac988 100644 --- a/src/playlist_mapper.h +++ b/src/PlaylistMapper.h @@ -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 @@ -24,6 +24,8 @@ struct input_stream; +G_BEGIN_DECLS + /** * Opens a playlist from an URI relative to the playlist or music * directory. @@ -36,4 +38,6 @@ struct playlist_provider * playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond, struct input_stream **is_r); +G_END_DECLS + #endif diff --git a/src/playlist_print.c b/src/PlaylistPrint.cxx index 59c42f969..40b895f80 100644 --- a/src/playlist_print.c +++ b/src/PlaylistPrint.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,19 +18,22 @@ */ #include "config.h" -#include "playlist_print.h" +#include "PlaylistPrint.hxx" +#include "PlaylistFile.hxx" +#include "QueuePrint.hxx" + +extern "C" { #include "playlist_list.h" #include "playlist_plugin.h" #include "playlist_any.h" #include "playlist_song.h" #include "playlist.h" -#include "queue_print.h" -#include "stored_playlist.h" #include "song_print.h" #include "song.h" #include "database.h" #include "client.h" #include "input_stream.h" +} void playlist_print_uris(struct client *client, const struct playlist *playlist) @@ -88,16 +91,9 @@ playlist_print_current(struct client *client, const struct playlist *playlist) void playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) -{ - queue_find(client, &playlist->queue, list); -} - -void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) + const SongFilter &filter) { - queue_search(client, &playlist->queue, list); + queue_find(client, &playlist->queue, filter); } void @@ -120,30 +116,31 @@ bool spl_print(struct client *client, const char *name_utf8, bool detail, GError **error_r) { - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) + GError *error = NULL; + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - for (unsigned i = 0; i < list->len; ++i) { - const char *temp = g_ptr_array_index(list, i); + for (const auto &uri_utf8 : contents) { bool wrote = false; if (detail) { - struct song *song = db_get_song(temp); + struct song *song = db_get_song(uri_utf8.c_str()); if (song) { song_print_info(client, song); + db_return_song(song); wrote = true; } } if (!wrote) { - client_printf(client, SONG_FILE "%s\n", temp); + client_printf(client, SONG_FILE "%s\n", + uri_utf8.c_str()); } } - spl_free(list); return true; } @@ -164,8 +161,7 @@ playlist_provider_print(struct client *client, const char *uri, else song_print_uri(client, song); - if (!song_in_database(song)) - song_free(song); + song_free(song); } g_free(base_uri); diff --git a/src/playlist_print.h b/src/PlaylistPrint.hxx index d4f1911d2..ac0712f01 100644 --- a/src/playlist_print.h +++ b/src/PlaylistPrint.hxx @@ -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 @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PLAYLIST_PRINT_H -#define PLAYLIST_PRINT_H +#ifndef MPD_PLAYLIST_PRINT_HXX +#define MPD_PLAYLIST_PRINT_HXX #include <glib.h> #include <stdbool.h> @@ -26,7 +26,7 @@ struct client; struct playlist; -struct locate_item_list; +class SongFilter; /** * Sends the whole playlist to the client, song URIs only. @@ -66,14 +66,7 @@ playlist_print_current(struct client *client, const struct playlist *playlist); */ void 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); + const SongFilter &filter); /** * Print detailed changes since the specified playlist version. diff --git a/src/playlist_save.c b/src/PlaylistSave.cxx index 122eca332..8d1908aac 100644 --- a/src/playlist_save.c +++ b/src/PlaylistSave.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,16 +18,18 @@ */ #include "config.h" -#include "playlist_save.h" -#include "playlist.h" -#include "stored_playlist.h" -#include "queue.h" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" #include "song.h" + +extern "C" { +#include "playlist.h" #include "mapper.h" #include "path.h" #include "uri.h" -#include "database.h" #include "idle.h" +} + #include "glib_compat.h" #include <glib.h> @@ -117,33 +119,35 @@ playlist_load_spl(struct playlist *playlist, struct player_control *pc, unsigned start_index, unsigned end_index, GError **error_r) { - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) + GError *error = NULL; + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - if (list->len < end_index) - end_index = list->len; + if (end_index > contents.size()) + end_index = contents.size(); for (unsigned i = start_index; i < end_index; ++i) { - const char *temp = g_ptr_array_index(list, i); - if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + const auto &uri_utf8 = contents[i]; + + if ((playlist_append_uri(playlist, pc, uri_utf8.c_str(), + nullptr)) != PLAYLIST_RESULT_SUCCESS) { /* for windows compatibility, convert slashes */ - char *temp2 = g_strdup(temp); + char *temp2 = g_strdup(uri_utf8.c_str()); char *p = temp2; while (*p) { if (*p == '\\') *p = '/'; p++; } - if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, pc, temp2, NULL)) != PLAYLIST_RESULT_SUCCESS) { g_warning("can't add file \"%s\"", temp2); } g_free(temp2); } } - spl_free(list); return true; } diff --git a/src/playlist_save.h b/src/PlaylistSave.hxx index a6c31a9a6..20b2ca425 100644 --- a/src/playlist_save.h +++ b/src/PlaylistSave.hxx @@ -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 diff --git a/src/QueueCommands.cxx b/src/QueueCommands.cxx new file mode 100644 index 000000000..cdd58d31e --- /dev/null +++ b/src/QueueCommands.cxx @@ -0,0 +1,393 @@ +/* + * 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 "QueueCommands.hxx" +#include "CommandError.hxx" +#include "DatabaseQueue.hxx" +#include "SongFilter.hxx" +#include "PlaylistPrint.hxx" + +extern "C" { +#include "protocol/argparser.h" +#include "protocol/result.h" +#include "playlist.h" +#include "ls.h" +#include "uri.h" +#include "client_internal.h" +#include "client_file.h" +} + +#include <string.h> + +enum command_return +handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + char *uri = argv[1]; + enum playlist_result result; + + if (strncmp(uri, "file:///", 8) == 0) { + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + result = playlist_append_file(&g_playlist, + client->player_control, + path, + NULL); + return print_playlist_result(client, result); + } + + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + result = playlist_append_uri(&g_playlist, + client->player_control, + uri, NULL); + return print_playlist_result(client, result); + } + + GError *error = NULL; + return findAddIn(client->player_control, uri, nullptr, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_addid(struct client *client, int argc, char *argv[]) +{ + char *uri = argv[1]; + unsigned added_id; + enum playlist_result result; + + if (strncmp(uri, "file:///", 8) == 0) { + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + result = playlist_append_file(&g_playlist, + client->player_control, + path, + &added_id); + } else { + if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + result = playlist_append_uri(&g_playlist, + client->player_control, + uri, &added_id); + } + + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + + if (argc == 3) { + unsigned to; + if (!check_unsigned(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + result = playlist_move_id(&g_playlist, client->player_control, + added_id, to); + if (result != PLAYLIST_RESULT_SUCCESS) { + enum command_return ret = + print_playlist_result(client, result); + playlist_delete_id(&g_playlist, client->player_control, + added_id); + return ret; + } + } + + client_printf(client, "Id: %u\n", added_id); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned start, end; + enum playlist_result result; + + if (!check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + result = playlist_delete_range(&g_playlist, client->player_control, + start, end); + return print_playlist_result(client, result); +} + +enum command_return +handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id; + enum playlist_result result; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + result = playlist_delete_id(&g_playlist, client->player_control, id); + return print_playlist_result(client, result); +} + +enum command_return +handle_playlist(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_print_uris(client, &g_playlist); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_shuffle(G_GNUC_UNUSED struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + unsigned start = 0, end = queue_length(&g_playlist.queue); + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_shuffle(&g_playlist, client->player_control, start, end); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_clear(G_GNUC_UNUSED struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_clear(&g_playlist, client->player_control); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_print_changes_info(client, &g_playlist, version); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_print_changes_position(client, &g_playlist, version); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistinfo(struct client *client, int argc, char *argv[]) +{ + unsigned start = 0, end = G_MAXUINT; + bool ret; + + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = playlist_print_info(client, &g_playlist, start, end); + if (!ret) + return print_playlist_result(client, + PLAYLIST_RESULT_BAD_RANGE); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistid(struct client *client, int argc, char *argv[]) +{ + if (argc >= 2) { + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + bool ret = playlist_print_id(client, &g_playlist, id); + if (!ret) + return print_playlist_result(client, + PLAYLIST_RESULT_NO_SUCH_SONG); + } else { + playlist_print_info(client, &g_playlist, 0, G_MAXUINT); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_playlist_match(struct client *client, int argc, char *argv[], + bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + playlist_print_find(client, &g_playlist, filter); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistfind(struct client *client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, false); +} + +enum command_return +handle_playlistsearch(struct client *client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, true); +} + +enum command_return +handle_prio(struct client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned start_position, end_position; + if (!check_range(client, &start_position, &end_position, + argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + playlist_set_priority(&g_playlist, + client->player_control, + start_position, end_position, + priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_prioid(struct client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + playlist_set_priority_id(&g_playlist, + client->player_control, + song_id, priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned start, end; + int to; + enum playlist_result result; + + if (!check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_int(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + result = playlist_move_range(&g_playlist, client->player_control, + start, end, to); + return print_playlist_result(client, result); +} + +enum command_return +handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id; + int to; + enum playlist_result result; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_int(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + result = playlist_move_id(&g_playlist, client->player_control, + id, to); + return print_playlist_result(client, result); +} + +enum command_return +handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned song1, song2; + enum playlist_result result; + + if (!check_unsigned(client, &song1, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &song2, argv[2])) + return COMMAND_RETURN_ERROR; + result = playlist_swap_songs(&g_playlist, client->player_control, + song1, song2); + return print_playlist_result(client, result); +} + +enum command_return +handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id1, id2; + enum playlist_result result; + + if (!check_unsigned(client, &id1, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &id2, argv[2])) + return COMMAND_RETURN_ERROR; + result = playlist_swap_songs_id(&g_playlist, client->player_control, + id1, id2); + return print_playlist_result(client, result); +} diff --git a/src/QueueCommands.hxx b/src/QueueCommands.hxx new file mode 100644 index 000000000..b28608f62 --- /dev/null +++ b/src/QueueCommands.hxx @@ -0,0 +1,82 @@ +/* + * 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_QUEUE_COMMANDS_HXX +#define MPD_QUEUE_COMMANDS_HXX + +#include "command.h" + +enum command_return +handle_add(struct client *client, int argc, char *argv[]); + +enum command_return +handle_addid(struct client *client, int argc, char *argv[]); + +enum command_return +handle_delete(struct client *client, int argc, char *argv[]); + +enum command_return +handle_deleteid(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playlist(struct client *client, int argc, char *argv[]); + +enum command_return +handle_shuffle(struct client *client, int argc, char *argv[]); + +enum command_return +handle_clear(struct client *client, int argc, char *argv[]); + +enum command_return +handle_plchanges(struct client *client, int argc, char *argv[]); + +enum command_return +handle_plchangesposid(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playlistinfo(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playlistid(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playlistfind(struct client *client, int argc, char *argv[]); + +enum command_return +handle_playlistsearch(struct client *client, int argc, char *argv[]); + +enum command_return +handle_prio(struct client *client, int argc, char *argv[]); + +enum command_return +handle_prioid(struct client *client, int argc, char *argv[]); + +enum command_return +handle_move(struct client *client, int argc, char *argv[]); + +enum command_return +handle_moveid(struct client *client, int argc, char *argv[]); + +enum command_return +handle_swap(struct client *client, int argc, char *argv[]); + +enum command_return +handle_swapid(struct client *client, int argc, char *argv[]); + +#endif diff --git a/src/queue_print.c b/src/QueuePrint.cxx index d149e8b6f..edac7c7ad 100644 --- a/src/queue_print.c +++ b/src/QueuePrint.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,13 +18,16 @@ */ #include "config.h" -#include "queue_print.h" +#include "QueuePrint.hxx" +#include "SongFilter.hxx" + +extern "C" { #include "queue.h" #include "song.h" #include "song_print.h" -#include "locate.h" #include "client.h" #include "mapper.h" +} /** * Send detailed information about a range of songs in the queue to a @@ -92,31 +95,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) + const SongFilter &filter) { for (unsigned i = 0; i < queue_length(queue); i++) { const struct song *song = queue_get(queue, i); - if (locate_song_match(song, criteria)) + if (filter.Match(*song)) queue_print_song_info(client, queue, i); } } diff --git a/src/queue_print.h b/src/QueuePrint.hxx index 371e20416..808b8dec1 100644 --- a/src/queue_print.h +++ b/src/QueuePrint.hxx @@ -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 @@ -22,14 +22,14 @@ * client. */ -#ifndef QUEUE_PRINT_H -#define QUEUE_PRINT_H +#ifndef MPD_QUEUE_PRINT_HXX +#define MPD_QUEUE_PRINT_HXX #include <stdint.h> struct client; struct queue; -struct locate_item_list; +class SongFilter; void queue_print_info(struct client *client, const struct queue *queue, @@ -48,11 +48,7 @@ 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); + const SongFilter &filter); #endif 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/SongFilter.cxx b/src/SongFilter.cxx new file mode 100644 index 000000000..6803b453e --- /dev/null +++ b/src/SongFilter.cxx @@ -0,0 +1,170 @@ +/* + * 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 "SongFilter.hxx" +#include "path.h" +#include "song.h" + +extern "C" { +#include "tag.h" +} + +#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" + +unsigned +locate_parse_type(const char *str) +{ + if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) || + 0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD)) + return LOCATE_TAG_FILE_TYPE; + + if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY)) + return LOCATE_TAG_ANY_TYPE; + + return tag_name_parse_i(str); +} + +SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case) + :tag(_tag), fold_case(_fold_case), + value(fold_case + ? g_utf8_casefold(_value, -1) + : g_strdup(_value)) +{ +} + +SongFilter::Item::~Item() +{ + g_free(value); +} + +bool +SongFilter::Item::StringMatch(const char *s) const +{ + assert(value != nullptr); + assert(s != nullptr); + + if (fold_case) { + char *p = g_utf8_casefold(s, -1); + const bool result = strstr(p, value) != NULL; + g_free(p); + return result; + } else { + return strcmp(s, value) == 0; + } +} + +bool +SongFilter::Item::Match(const tag_item &item) const +{ + return (tag == LOCATE_TAG_ANY_TYPE || (unsigned)item.type == tag) && + StringMatch(item.value); +} + +bool +SongFilter::Item::Match(const struct tag &_tag) const +{ + bool visited_types[TAG_NUM_OF_ITEM_TYPES]; + std::fill(visited_types, visited_types + TAG_NUM_OF_ITEM_TYPES, false); + + for (unsigned i = 0; i < _tag.num_items; i++) { + visited_types[_tag.items[i]->type] = true; + + if (Match(*_tag.items[i])) + 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 (*value == 0 && tag < TAG_NUM_OF_ITEM_TYPES && + !visited_types[tag]) + return true; + + return false; +} + +bool +SongFilter::Item::Match(const song &song) const +{ + if (tag == LOCATE_TAG_FILE_TYPE || tag == LOCATE_TAG_ANY_TYPE) { + char *uri = song_get_uri(&song); + const bool result = StringMatch(uri); + g_free(uri); + + if (result || tag == LOCATE_TAG_FILE_TYPE) + return result; + } + + return song.tag != NULL && Match(*song.tag); +} + +SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case) +{ + items.push_back(Item(tag, value, fold_case)); +} + +SongFilter::~SongFilter() +{ + /* this destructor exists here just so it won't get inlined */ +} + +bool +SongFilter::Parse(const char *tag_string, const char *value, bool fold_case) +{ + unsigned tag = locate_parse_type(tag_string); + if (tag == TAG_NUM_OF_ITEM_TYPES) + return false; + + items.push_back(Item(tag, value, fold_case)); + return true; +} + +bool +SongFilter::Parse(unsigned argc, char *argv[], bool fold_case) +{ + if (argc == 0 || argc % 2 != 0) + return false; + + for (unsigned i = 0; i < argc; i += 2) + if (!Parse(argv[i], argv[i + 1], fold_case)) + return false; + + return true; +} + +bool +SongFilter::Match(const song &song) const +{ + for (const auto &i : items) + if (!i.Match(song)) + return false; + + return true; +} diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx new file mode 100644 index 000000000..a3068a970 --- /dev/null +++ b/src/SongFilter.hxx @@ -0,0 +1,108 @@ +/* + * 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_SONG_FILTER_HXX +#define MPD_SONG_FILTER_HXX + +#include "gcc.h" + +#include <list> + +#include <stdint.h> +#include <stdbool.h> + +#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 +#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 + +struct tag; +struct tag_item; +struct song; + +class SongFilter { + class Item { + uint8_t tag; + + bool fold_case; + + char *value; + + public: + gcc_nonnull(3) + Item(unsigned tag, const char *value, bool fold_case=false); + + Item(const Item &other) = delete; + + Item(Item &&other) + :tag(other.tag), fold_case(other.fold_case), + value(other.value) { + other.value = nullptr; + } + + ~Item(); + + Item &operator=(const Item &other) = delete; + + unsigned GetTag() const { + return tag; + } + + gcc_pure gcc_nonnull(2) + bool StringMatch(const char *s) const; + + gcc_pure + bool Match(const tag_item &tag_item) const; + + gcc_pure + bool Match(const struct tag &tag) const; + + gcc_pure + bool Match(const song &song) const; + }; + + std::list<Item> items; + +public: + SongFilter() = default; + + gcc_nonnull(3) + SongFilter(unsigned tag, const char *value, bool fold_case=false); + + ~SongFilter(); + + gcc_nonnull(2,3) + bool Parse(const char *tag, const char *value, bool fold_case=false); + + gcc_nonnull(3) + bool Parse(unsigned argc, char *argv[], bool fold_case=false); + + gcc_pure + bool Match(const tag &tag) const; + + gcc_pure + bool Match(const song &song) const; +}; + +/** + * @return #TAG_NUM_OF_ITEM_TYPES on error + */ +gcc_pure +unsigned +locate_parse_type(const char *str); + +#endif diff --git a/src/Stats.cxx b/src/Stats.cxx new file mode 100644 index 000000000..d08074a8c --- /dev/null +++ b/src/Stats.cxx @@ -0,0 +1,90 @@ +/* + * 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" + +extern "C" { +#include "stats.h" +#include "database.h" +#include "client.h" +#include "player_control.h" +#include "client_internal.h" +} + +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +struct stats stats; + +void stats_global_init(void) +{ + stats.timer = g_timer_new(); +} + +void stats_global_finish(void) +{ + g_timer_destroy(stats.timer); +} + +void stats_update(void) +{ + 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; + } +} + +void +stats_print(struct client *client) +{ + client_printf(client, + "artists: %u\n" + "albums: %u\n" + "songs: %i\n" + "uptime: %li\n" + "playtime: %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); + + if (db_is_simple()) + client_printf(client, + "db_update: %li\n", + (long)db_get_mtime()); +} diff --git a/src/StickerCommands.cxx b/src/StickerCommands.cxx new file mode 100644 index 000000000..ccf18fea6 --- /dev/null +++ b/src/StickerCommands.cxx @@ -0,0 +1,196 @@ +/* + * 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 "StickerCommands.hxx" + +extern "C" { +#include "protocol/result.h" +#include "sticker.h" +#include "sticker_print.h" +#include "song_print.h" +#include "song_sticker.h" +#include "database.h" +#include "db_lock.h" +} + +#include <string.h> + +struct sticker_song_find_data { + struct client *client; + const char *name; +}; + +static void +sticker_song_find_print_cb(struct song *song, const char *value, + gpointer user_data) +{ + struct sticker_song_find_data *data = + (struct sticker_song_find_data *)user_data; + + song_print_uri(data->client, song); + sticker_print_value(data->client, data->name, value); +} + +static enum command_return +handle_sticker_song(struct client *client, int argc, char *argv[]) +{ + /* get song song_id key */ + if (argc == 5 && strcmp(argv[1], "get") == 0) { + struct song *song; + char *value; + + song = db_get_song(argv[3]); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "no such song"); + return COMMAND_RETURN_ERROR; + } + + value = sticker_song_get_value(song, argv[4]); + db_return_song(song); + if (value == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "no such sticker"); + return COMMAND_RETURN_ERROR; + } + + sticker_print_value(client, argv[4], value); + g_free(value); + + return COMMAND_RETURN_OK; + /* list song song_id */ + } else if (argc == 4 && strcmp(argv[1], "list") == 0) { + struct song *song; + struct sticker *sticker; + + song = db_get_song(argv[3]); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "no such song"); + return COMMAND_RETURN_ERROR; + } + + sticker = sticker_song_get(song); + db_return_song(song); + if (sticker) { + sticker_print(client, sticker); + sticker_free(sticker); + } + + return COMMAND_RETURN_OK; + /* set song song_id id key */ + } else if (argc == 6 && strcmp(argv[1], "set") == 0) { + struct song *song; + bool ret; + + song = db_get_song(argv[3]); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "no such song"); + return COMMAND_RETURN_ERROR; + } + + ret = sticker_song_set_value(song, argv[4], argv[5]); + db_return_song(song); + if (!ret) { + command_error(client, ACK_ERROR_SYSTEM, + "failed to set sticker value"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; + /* delete song song_id [key] */ + } else if ((argc == 4 || argc == 5) && + strcmp(argv[1], "delete") == 0) { + struct song *song; + bool ret; + + song = db_get_song(argv[3]); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "no such song"); + return COMMAND_RETURN_ERROR; + } + + ret = argc == 4 + ? sticker_song_delete(song) + : sticker_song_delete_value(song, argv[4]); + db_return_song(song); + if (!ret) { + command_error(client, ACK_ERROR_SYSTEM, + "no such sticker"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; + /* find song dir key */ + } else if (argc == 5 && strcmp(argv[1], "find") == 0) { + /* "sticker find song a/directory name" */ + struct directory *directory; + bool success; + struct sticker_song_find_data data = { + client, + argv[4], + }; + + db_lock(); + directory = db_get_directory(argv[3]); + if (directory == NULL) { + db_unlock(); + command_error(client, ACK_ERROR_NO_EXIST, + "no such directory"); + return COMMAND_RETURN_ERROR; + } + + success = sticker_song_find(directory, data.name, + sticker_song_find_print_cb, &data); + db_unlock(); + if (!success) { + command_error(client, ACK_ERROR_SYSTEM, + "failed to set search sticker database"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; + } else { + command_error(client, ACK_ERROR_ARG, "bad request"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_sticker(struct client *client, int argc, char *argv[]) +{ + assert(argc >= 4); + + if (!sticker_enabled()) { + command_error(client, ACK_ERROR_UNKNOWN, + "sticker database is disabled"); + return COMMAND_RETURN_ERROR; + } + + if (strcmp(argv[2], "song") == 0) + return handle_sticker_song(client, argc, argv); + else { + command_error(client, ACK_ERROR_ARG, + "unknown sticker domain"); + return COMMAND_RETURN_ERROR; + } +} diff --git a/src/StickerCommands.hxx b/src/StickerCommands.hxx new file mode 100644 index 000000000..c253d0bb7 --- /dev/null +++ b/src/StickerCommands.hxx @@ -0,0 +1,28 @@ +/* + * 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_STICKER_COMMANDS_HXX +#define MPD_STICKER_COMMANDS_HXX + +#include "command.h" + +enum command_return +handle_sticker(struct client *client, int argc, char *argv[]); + +#endif diff --git a/src/main_win32.c b/src/Win32Main.cxx index aac7ad886..8543ea108 100644 --- a/src/main_win32.c +++ b/src/Win32Main.cxx @@ -18,12 +18,15 @@ */ #include "config.h" -#include "main.h" +#include "Main.hxx" #ifdef WIN32 #include "mpd_error.h" + +extern "C" { #include "event_pipe.h" +} #include <glib.h> 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_event.c b/src/client_event.c index 4f54ae0a7..5680e557b 100644 --- a/src/client_event.c +++ b/src/client_event.c @@ -19,7 +19,7 @@ #include "config.h" #include "client_internal.h" -#include "main.h" +#include "Main.hxx" #include <assert.h> @@ -77,6 +77,7 @@ client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, ret = client_read(client); switch (ret) { case COMMAND_RETURN_OK: + case COMMAND_RETURN_IDLE: case COMMAND_RETURN_ERROR: break; 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_internal.h b/src/client_internal.h index ba97e4b8f..5c2b9f11c 100644 --- a/src/client_internal.h +++ b/src/client_internal.h @@ -24,6 +24,8 @@ #include "client_message.h" #include "command.h" +#include <glib.h> + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "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_process.c b/src/client_process.c index 57a8a7824..7217b35ab 100644 --- a/src/client_process.c +++ b/src/client_process.c @@ -19,6 +19,8 @@ #include "config.h" #include "client_internal.h" +#include "protocol/result.h" +#include "AllCommands.h" #include <string.h> 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 deleted file mode 100644 index c405925f2..000000000 --- a/src/command.c +++ /dev/null @@ -1,2298 +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 "command.h" -#include "protocol/argparser.h" -#include "protocol/result.h" -#include "player_control.h" -#include "playlist.h" -#include "playlist_print.h" -#include "playlist_save.h" -#include "playlist_queue.h" -#include "playlist_error.h" -#include "queue_print.h" -#include "ls.h" -#include "uri.h" -#include "decoder_print.h" -#include "directory.h" -#include "database.h" -#include "update.h" -#include "volume.h" -#include "stats.h" -#include "permission.h" -#include "tokenizer.h" -#include "stored_playlist.h" -#include "ack.h" -#include "output_command.h" -#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" -#include "client_idle.h" -#include "client_internal.h" -#include "client_subscribe.h" -#include "client_file.h" -#include "tag_print.h" -#include "path.h" -#include "replay_gain_config.h" -#include "idle.h" -#include "mapper.h" -#include "song.h" -#include "song_print.h" - -#ifdef ENABLE_SQLITE -#include "sticker.h" -#include "sticker_print.h" -#include "song_sticker.h" -#endif - -#include <assert.h> -#include <time.h> -#include <stdlib.h> -#include <errno.h> - -#define COMMAND_STATUS_STATE "state" -#define COMMAND_STATUS_REPEAT "repeat" -#define COMMAND_STATUS_SINGLE "single" -#define COMMAND_STATUS_CONSUME "consume" -#define COMMAND_STATUS_RANDOM "random" -#define COMMAND_STATUS_PLAYLIST "playlist" -#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" -#define COMMAND_STATUS_SONG "song" -#define COMMAND_STATUS_SONGID "songid" -#define COMMAND_STATUS_NEXTSONG "nextsong" -#define COMMAND_STATUS_NEXTSONGID "nextsongid" -#define COMMAND_STATUS_TIME "time" -#define COMMAND_STATUS_BITRATE "bitrate" -#define COMMAND_STATUS_ERROR "error" -#define COMMAND_STATUS_CROSSFADE "xfade" -#define COMMAND_STATUS_MIXRAMPDB "mixrampdb" -#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay" -#define COMMAND_STATUS_AUDIO "audio" -#define COMMAND_STATUS_UPDATING_DB "updating_db" - -/* - * The most we ever use is for search/find, and that limits it to the - * number of tags we can have. Add one for the command, and one extra - * to catch errors clients may send us - */ -#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2)) - -/* if min: -1 don't check args * - * if max: -1 no max args */ -struct command { - const char *cmd; - unsigned permission; - int min; - int max; - 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); - } -} - -static enum command_return -handle_urlhandlers(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - if (client_is_local(client)) - client_puts(client, "handler: file://\n"); - print_supported_uri_schemes(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_decoders(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - decoder_list_print(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_tagtypes(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - tag_print_types(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_play(struct client *client, int argc, char *argv[]) -{ - int song = -1; - enum playlist_result result; - - if (argc == 2 && !check_int(client, &song, argv[1])) - return COMMAND_RETURN_ERROR; - result = playlist_play(&g_playlist, client->player_control, song); - return print_playlist_result(client, result); -} - -static enum command_return -handle_playid(struct client *client, int argc, char *argv[]) -{ - int id = -1; - enum playlist_result result; - - if (argc == 2 && !check_int(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - - result = playlist_play_id(&g_playlist, client->player_control, id); - return print_playlist_result(client, result); -} - -static enum command_return -handle_stop(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_stop(&g_playlist, client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_currentsong(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_print_current(client, &g_playlist); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_pause(struct client *client, - int argc, char *argv[]) -{ - if (argc == 2) { - bool pause_flag; - if (!check_bool(client, &pause_flag, argv[1])) - return COMMAND_RETURN_ERROR; - - pc_set_pause(client->player_control, pause_flag); - } else - pc_pause(client->player_control); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_status(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - const char *state = NULL; - struct player_status player_status; - int updateJobId; - char *error; - int song; - - pc_get_status(client->player_control, &player_status); - - switch (player_status.state) { - case PLAYER_STATE_STOP: - state = "stop"; - break; - case PLAYER_STATE_PAUSE: - state = "pause"; - break; - case PLAYER_STATE_PLAY: - state = "play"; - break; - } - - client_printf(client, - "volume: %i\n" - COMMAND_STATUS_REPEAT ": %i\n" - COMMAND_STATUS_RANDOM ": %i\n" - COMMAND_STATUS_SINGLE ": %i\n" - COMMAND_STATUS_CONSUME ": %i\n" - COMMAND_STATUS_PLAYLIST ": %li\n" - COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" - COMMAND_STATUS_CROSSFADE ": %i\n" - COMMAND_STATUS_MIXRAMPDB ": %f\n" - COMMAND_STATUS_MIXRAMPDELAY ": %f\n" - COMMAND_STATUS_STATE ": %s\n", - volume_level_get(), - playlist_get_repeat(&g_playlist), - playlist_get_random(&g_playlist), - playlist_get_single(&g_playlist), - playlist_get_consume(&g_playlist), - playlist_get_version(&g_playlist), - playlist_get_length(&g_playlist), - (int)(pc_get_cross_fade(client->player_control) + 0.5), - pc_get_mixramp_db(client->player_control), - pc_get_mixramp_delay(client->player_control), - state); - - song = playlist_get_current_song(&g_playlist); - if (song >= 0) { - client_printf(client, - COMMAND_STATUS_SONG ": %i\n" - COMMAND_STATUS_SONGID ": %u\n", - song, playlist_get_song_id(&g_playlist, song)); - } - - if (player_status.state != PLAYER_STATE_STOP) { - struct audio_format_string af_string; - - client_printf(client, - COMMAND_STATUS_TIME ": %i:%i\n" - "elapsed: %1.3f\n" - COMMAND_STATUS_BITRATE ": %u\n" - COMMAND_STATUS_AUDIO ": %s\n", - (int)(player_status.elapsed_time + 0.5), - (int)(player_status.total_time + 0.5), - player_status.elapsed_time, - player_status.bit_rate, - audio_format_to_string(&player_status.audio_format, - &af_string)); - } - - if ((updateJobId = isUpdatingDB())) { - client_printf(client, - COMMAND_STATUS_UPDATING_DB ": %i\n", - updateJobId); - } - - error = pc_get_error_message(client->player_control); - if (error != NULL) { - client_printf(client, - COMMAND_STATUS_ERROR ": %s\n", - error); - g_free(error); - } - - song = playlist_get_next_song(&g_playlist); - if (song >= 0) { - client_printf(client, - COMMAND_STATUS_NEXTSONG ": %i\n" - COMMAND_STATUS_NEXTSONGID ": %u\n", - song, playlist_get_song_id(&g_playlist, song)); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_kill(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return COMMAND_RETURN_KILL; -} - -static enum command_return -handle_close(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return COMMAND_RETURN_CLOSE; -} - -static enum command_return -handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - char *uri = argv[1]; - enum playlist_result result; - - if (strncmp(uri, "file:///", 8) == 0) { - const char *path = uri + 7; - - GError *error = NULL; - if (!client_allow_file(client, path, &error)) - return print_error(client, error); - - result = playlist_append_file(&g_playlist, - client->player_control, - path, - NULL); - return print_playlist_result(client, result); - } - - if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return COMMAND_RETURN_ERROR; - } - - result = playlist_append_uri(&g_playlist, - client->player_control, - uri, NULL); - return print_playlist_result(client, result); - } - - GError *error = NULL; - return addAllIn(client->player_control, uri, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_addid(struct client *client, int argc, char *argv[]) -{ - char *uri = argv[1]; - unsigned added_id; - enum playlist_result result; - - if (strncmp(uri, "file:///", 8) == 0) { - const char *path = uri + 7; - - GError *error = NULL; - if (!client_allow_file(client, path, &error)) - return print_error(client, error); - - result = playlist_append_file(&g_playlist, - client->player_control, - path, - &added_id); - } else { - if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return COMMAND_RETURN_ERROR; - } - - result = playlist_append_uri(&g_playlist, - client->player_control, - uri, &added_id); - } - - if (result != PLAYLIST_RESULT_SUCCESS) - return print_playlist_result(client, result); - - if (argc == 3) { - unsigned to; - if (!check_unsigned(client, &to, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, client->player_control, - added_id, to); - if (result != PLAYLIST_RESULT_SUCCESS) { - enum command_return ret = - print_playlist_result(client, result); - playlist_delete_id(&g_playlist, client->player_control, - added_id); - return ret; - } - } - - client_printf(client, "Id: %u\n", added_id); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned start, end; - enum playlist_result result; - - if (!check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - - result = playlist_delete_range(&g_playlist, client->player_control, - start, end); - return print_playlist_result(client, result); -} - -static enum command_return -handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id; - enum playlist_result result; - - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - - result = playlist_delete_id(&g_playlist, client->player_control, id); - return print_playlist_result(client, result); -} - -static enum command_return -handle_playlist(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_print_uris(client, &g_playlist); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_shuffle(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - unsigned start = 0, end = queue_length(&g_playlist.queue); - if (argc == 2 && !check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_shuffle(&g_playlist, client->player_control, start, end); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_clear(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_clear(&g_playlist, client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_save(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) -{ - enum playlist_result result; - - result = spl_save_playlist(argv[1], &g_playlist); - return print_playlist_result(client, result); -} - -static enum command_return -handle_load(struct client *client, int argc, char *argv[]) -{ - unsigned start_index, end_index; - - if (argc < 3) { - start_index = 0; - end_index = G_MAXUINT; - } else if (!check_range(client, &start_index, &end_index, argv[2])) - return COMMAND_RETURN_ERROR; - - enum playlist_result result; - - result = playlist_open_into_queue(argv[1], - start_index, end_index, - &g_playlist, - client->player_control, true); - if (result != PLAYLIST_RESULT_NO_SUCH_LIST) - return print_playlist_result(client, result); - - GError *error = NULL; - if (playlist_load_spl(&g_playlist, client->player_control, - argv[1], start_index, end_index, - &error)) - return COMMAND_RETURN_OK; - - if (error->domain == playlist_quark() && - error->code == PLAYLIST_RESULT_BAD_NAME) - /* the message for BAD_NAME is confusing when the - client wants to load a playlist file from the music - directory; patch the GError object to show "no such - playlist" instead */ - error->code = PLAYLIST_RESULT_NO_SUCH_LIST; - - return print_error(client, error); -} - -static enum command_return -handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - if (playlist_file_print(client, argv[1], false)) - return COMMAND_RETURN_OK; - - GError *error = NULL; - return spl_print(client, argv[1], false, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_listplaylistinfo(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) -{ - if (playlist_file_print(client, argv[1], true)) - return COMMAND_RETURN_OK; - - GError *error = NULL; - return spl_print(client, argv[1], true, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_lsinfo(struct client *client, int argc, char *argv[]) -{ - const char *uri; - - if (argc == 2) - uri = argv[1]; - else - /* default is root directory */ - uri = ""; - - if (strncmp(uri, "file:///", 8) == 0) { - /* print information about an arbitrary local file */ - const char *path = uri + 7; - - GError *error = NULL; - if (!client_allow_file(client, path, &error)) - return print_error(client, error); - - struct song *song = song_file_load(path, NULL); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such file"); - return COMMAND_RETURN_ERROR; - } - - song_print_info(client, song); - song_free(song); - 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); - - if (isRootDirectory(uri)) { - GPtrArray *list = spl_list(NULL); - if (list != NULL) { - print_spl_list(client, list); - spl_list_free(list); - } - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_rm(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - GError *error = NULL; - return spl_delete(argv[1], &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_rename(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - GError *error = NULL; - return spl_rename(argv[1], argv[2], &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - uint32_t version; - - if (!check_uint32(client, &version, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_print_changes_info(client, &g_playlist, version); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - uint32_t version; - - if (!check_uint32(client, &version, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_print_changes_position(client, &g_playlist, version); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistinfo(struct client *client, int argc, char *argv[]) -{ - unsigned start = 0, end = G_MAXUINT; - bool ret; - - if (argc == 2 && !check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - - ret = playlist_print_info(client, &g_playlist, start, end); - if (!ret) - return print_playlist_result(client, - PLAYLIST_RESULT_BAD_RANGE); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistid(struct client *client, int argc, char *argv[]) -{ - if (argc >= 2) { - unsigned id; - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - - bool ret = playlist_print_id(client, &g_playlist, id); - if (!ret) - return print_playlist_result(client, - PLAYLIST_RESULT_NO_SUCH_SONG); - } else { - playlist_print_info(client, &g_playlist, 0, G_MAXUINT); - } - - return COMMAND_RETURN_OK; -} - -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[]) -{ - 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); - - 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); - - locate_item_list_free(list); - - return ret; -} - -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; -} - -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; -} - -static enum command_return -handle_playlistdelete(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) { - char *playlist = argv[1]; - unsigned from; - - if (!check_unsigned(client, &from, argv[2])) - return COMMAND_RETURN_ERROR; - - GError *error = NULL; - return spl_remove_index(playlist, from, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - char *playlist = argv[1]; - unsigned from, to; - - if (!check_unsigned(client, &from, argv[2])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &to, argv[3])) - return COMMAND_RETURN_ERROR; - - GError *error = NULL; - return spl_move_index(playlist, from, to, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *path = NULL; - unsigned ret; - - assert(argc <= 2); - if (argc == 2) { - path = argv[1]; - - if (*path == 0 || strcmp(path, "/") == 0) - /* backwards compatibility with MPD 0.15 */ - path = NULL; - else if (!uri_safe_local(path)) { - command_error(client, ACK_ERROR_ARG, - "Malformed path"); - return COMMAND_RETURN_ERROR; - } - } - - ret = update_enqueue(path, false); - if (ret > 0) { - client_printf(client, "updating_db: %i\n", ret); - return COMMAND_RETURN_OK; - } else { - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); - return COMMAND_RETURN_ERROR; - } -} - -static enum command_return -handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *path = NULL; - unsigned ret; - - assert(argc <= 2); - if (argc == 2) { - path = argv[1]; - - if (!uri_safe_local(path)) { - command_error(client, ACK_ERROR_ARG, - "Malformed path"); - return COMMAND_RETURN_ERROR; - } - } - - ret = update_enqueue(path, true); - if (ret > 0) { - client_printf(client, "updating_db: %i\n", ret); - return COMMAND_RETURN_OK; - } else { - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); - return COMMAND_RETURN_ERROR; - } -} - -static enum command_return -handle_next(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - /* single mode is not considered when this is user who - * wants to change song. */ - const bool single = g_playlist.queue.single; - g_playlist.queue.single = false; - - playlist_next(&g_playlist, client->player_control); - - g_playlist.queue.single = single; - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_previous(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_previous(&g_playlist, client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_prio(struct client *client, int argc, char *argv[]) -{ - unsigned priority; - - if (!check_unsigned(client, &priority, argv[1])) - return COMMAND_RETURN_ERROR; - - if (priority > 0xff) { - command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); - return COMMAND_RETURN_ERROR; - } - - for (int i = 2; i < argc; ++i) { - unsigned start_position, end_position; - if (!check_range(client, &start_position, &end_position, - argv[i])) - return COMMAND_RETURN_ERROR; - - enum playlist_result result = - playlist_set_priority(&g_playlist, - client->player_control, - start_position, end_position, - priority); - if (result != PLAYLIST_RESULT_SUCCESS) - return print_playlist_result(client, result); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_prioid(struct client *client, int argc, char *argv[]) -{ - unsigned priority; - - if (!check_unsigned(client, &priority, argv[1])) - return COMMAND_RETURN_ERROR; - - if (priority > 0xff) { - command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); - return COMMAND_RETURN_ERROR; - } - - for (int i = 2; i < argc; ++i) { - unsigned song_id; - if (!check_unsigned(client, &song_id, argv[i])) - return COMMAND_RETURN_ERROR; - - enum playlist_result result = - playlist_set_priority_id(&g_playlist, - client->player_control, - song_id, priority); - if (result != PLAYLIST_RESULT_SUCCESS) - return print_playlist_result(client, result); - } - - return COMMAND_RETURN_OK; -} - -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; - bool success; - - if (!check_unsigned(client, &level, argv[1])) - return COMMAND_RETURN_ERROR; - - if (level > 100) { - command_error(client, ACK_ERROR_ARG, "Invalid volume value"); - return COMMAND_RETURN_ERROR; - } - - success = volume_level_change(level); - if (!success) { - command_error(client, ACK_ERROR_SYSTEM, - "problems setting volume"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_repeat(&g_playlist, client->player_control, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_single(&g_playlist, client->player_control, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_consume(&g_playlist, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_random(&g_playlist, client->player_control, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_stats(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return stats_print(client); -} - -static enum command_return -handle_clearerror(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - pc_clear_error(client->player_control); - return COMMAND_RETURN_OK; -} - -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; - int to; - enum playlist_result result; - - if (!check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_move_range(&g_playlist, client->player_control, - start, end, to); - return print_playlist_result(client, result); -} - -static enum command_return -handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id; - int to; - enum playlist_result result; - - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, client->player_control, - id, to); - return print_playlist_result(client, result); -} - -static enum command_return -handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned song1, song2; - enum playlist_result result; - - if (!check_unsigned(client, &song1, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &song2, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_swap_songs(&g_playlist, client->player_control, - song1, song2); - return print_playlist_result(client, result); -} - -static enum command_return -handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id1, id2; - enum playlist_result result; - - if (!check_unsigned(client, &id1, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &id2, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_swap_songs_id(&g_playlist, client->player_control, - id1, id2); - return print_playlist_result(client, result); -} - -static enum command_return -handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned song, seek_time; - enum playlist_result result; - - if (!check_unsigned(client, &song, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &seek_time, argv[2])) - return COMMAND_RETURN_ERROR; - - result = playlist_seek_song(&g_playlist, client->player_control, - song, seek_time); - return print_playlist_result(client, result); -} - -static enum command_return -handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id, seek_time; - enum playlist_result result; - - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &seek_time, argv[2])) - return COMMAND_RETURN_ERROR; - - result = playlist_seek_song_id(&g_playlist, client->player_control, - id, seek_time); - return print_playlist_result(client, result); -} - -static enum command_return -handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *p = argv[1]; - bool relative = *p == '+' || *p == '-'; - int seek_time; - if (!check_int(client, &seek_time, p)) - return COMMAND_RETURN_ERROR; - - enum playlist_result result = - playlist_seek_current(&g_playlist, client->player_control, - seek_time, relative); - return print_playlist_result(client, result); -} - -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[]) -{ - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_password(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned permission = 0; - - if (getPermissionFromPassword(argv[1], &permission) < 0) { - command_error(client, ACK_ERROR_PASSWORD, "incorrect password"); - return COMMAND_RETURN_ERROR; - } - - client_set_permission(client, permission); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned xfade_time; - - if (!check_unsigned(client, &xfade_time, argv[1])) - return COMMAND_RETURN_ERROR; - pc_set_cross_fade(client->player_control, xfade_time); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - float db; - - if (!check_float(client, &db, argv[1])) - return COMMAND_RETURN_ERROR; - pc_set_mixramp_db(client->player_control, db); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - float delay_secs; - - if (!check_float(client, &delay_secs, argv[1])) - return COMMAND_RETURN_ERROR; - pc_set_mixramp_delay(client->player_control, delay_secs); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_enableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned device; - bool ret; - - if (!check_unsigned(client, &device, argv[1])) - return COMMAND_RETURN_ERROR; - - ret = audio_output_enable_index(device); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such audio output"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_disableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned device; - bool ret; - - if (!check_unsigned(client, &device, argv[1])) - return COMMAND_RETURN_ERROR; - - ret = audio_output_disable_index(device); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such audio output"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_devices(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - printAudioDevices(client); - - return COMMAND_RETURN_OK; -} - -/* don't be fooled, this is the command handler for "commands" command */ -static enum command_return -handle_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); - -static enum command_return -handle_not_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); - -static enum command_return -handle_config(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - if (!client_is_local(client)) { - command_error(client, ACK_ERROR_PERMISSION, - "Command only permitted to local clients"); - return COMMAND_RETURN_ERROR; - } - - const char *path = mapper_get_music_directory_utf8(); - if (path != NULL) - client_printf(client, "music_directory: %s\n", path); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - GError *error = NULL; - return spl_clear(argv[1], &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - char *playlist = argv[1]; - char *uri = argv[2]; - - bool success; - GError *error = NULL; - if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return COMMAND_RETURN_ERROR; - } - - success = spl_append_uri(argv[1], playlist, &error); - } else - success = addAllInToStoredPlaylist(uri, playlist, &error); - - if (!success && error == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - return COMMAND_RETURN_ERROR; - } - - return success ? COMMAND_RETURN_OK : print_error(client, error); -} - -static enum command_return -handle_listplaylists(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - GError *error = NULL; - GPtrArray *list = spl_list(&error); - if (list == NULL) - return print_error(client, error); - - print_spl_list(client, list); - spl_list_free(list); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_replay_gain_mode(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) -{ - if (!replay_gain_set_mode_string(argv[1])) { - command_error(client, ACK_ERROR_ARG, - "Unrecognized replay gain mode"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_replay_gain_status(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - client_printf(client, "replay_gain_mode: %s\n", - replay_gain_get_mode_string()); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_idle(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - unsigned flags = 0, j; - int i; - const char *const* idle_names; - - idle_names = idle_get_names(); - for (i = 1; i < argc; ++i) { - if (!argv[i]) - continue; - - for (j = 0; idle_names[j]; ++j) { - if (!g_ascii_strcasecmp(argv[i], idle_names[j])) { - flags |= (1 << j); - } - } - } - - /* No argument means that the client wants to receive everything */ - if (flags == 0) - flags = ~0; - - /* enable "idle" mode on this client */ - client_idle_wait(client, flags); - - /* return value is "1" so the caller won't print "OK" */ - return 1; -} - -#ifdef ENABLE_SQLITE -struct sticker_song_find_data { - struct client *client; - const char *name; -}; - -static void -sticker_song_find_print_cb(struct song *song, const char *value, - gpointer user_data) -{ - struct sticker_song_find_data *data = user_data; - - song_print_uri(data->client, song); - sticker_print_value(data->client, data->name, value); -} - -static enum command_return -handle_sticker_song(struct client *client, int argc, char *argv[]) -{ - /* get song song_id key */ - if (argc == 5 && strcmp(argv[1], "get") == 0) { - struct song *song; - char *value; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - value = sticker_song_get_value(song, argv[4]); - if (value == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such sticker"); - return COMMAND_RETURN_ERROR; - } - - sticker_print_value(client, argv[4], value); - g_free(value); - - return COMMAND_RETURN_OK; - /* list song song_id */ - } else if (argc == 4 && strcmp(argv[1], "list") == 0) { - struct song *song; - struct sticker *sticker; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - sticker = sticker_song_get(song); - if (sticker) { - sticker_print(client, sticker); - sticker_free(sticker); - } - - return COMMAND_RETURN_OK; - /* set song song_id id key */ - } else if (argc == 6 && strcmp(argv[1], "set") == 0) { - struct song *song; - bool ret; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - ret = sticker_song_set_value(song, argv[4], argv[5]); - if (!ret) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to set sticker value"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; - /* delete song song_id [key] */ - } else if ((argc == 4 || argc == 5) && - strcmp(argv[1], "delete") == 0) { - struct song *song; - bool ret; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - ret = argc == 4 - ? sticker_song_delete(song) - : sticker_song_delete_value(song, argv[4]); - if (!ret) { - command_error(client, ACK_ERROR_SYSTEM, - "no such sticker"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; - /* find song dir key */ - } else if (argc == 5 && strcmp(argv[1], "find") == 0) { - /* "sticker find song a/directory name" */ - struct directory *directory; - bool success; - struct sticker_song_find_data data = { - .client = client, - .name = argv[4], - }; - - db_lock(); - directory = db_get_directory(argv[3]); - if (directory == NULL) { - db_unlock(); - command_error(client, ACK_ERROR_NO_EXIST, - "no such directory"); - return COMMAND_RETURN_ERROR; - } - - success = sticker_song_find(directory, data.name, - sticker_song_find_print_cb, &data); - db_unlock(); - if (!success) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to set search sticker database"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; - } else { - command_error(client, ACK_ERROR_ARG, "bad request"); - return COMMAND_RETURN_ERROR; - } -} - -static enum command_return -handle_sticker(struct client *client, int argc, char *argv[]) -{ - assert(argc >= 4); - - if (!sticker_enabled()) { - command_error(client, ACK_ERROR_UNKNOWN, - "sticker database is disabled"); - return COMMAND_RETURN_ERROR; - } - - if (strcmp(argv[2], "song") == 0) - return handle_sticker_song(client, argc, argv); - else { - command_error(client, ACK_ERROR_ARG, - "unknown sticker domain"); - return COMMAND_RETURN_ERROR; - } -} -#endif - -static enum command_return -handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - assert(argc == 2); - - switch (client_subscribe(client, argv[1])) { - case CLIENT_SUBSCRIBE_OK: - return COMMAND_RETURN_OK; - - case CLIENT_SUBSCRIBE_INVALID: - command_error(client, ACK_ERROR_ARG, - "invalid channel name"); - return COMMAND_RETURN_ERROR; - - case CLIENT_SUBSCRIBE_ALREADY: - command_error(client, ACK_ERROR_EXIST, - "already subscribed to this channel"); - return COMMAND_RETURN_ERROR; - - case CLIENT_SUBSCRIBE_FULL: - command_error(client, ACK_ERROR_EXIST, - "subscription list is full"); - return COMMAND_RETURN_ERROR; - } - - /* unreachable */ - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - assert(argc == 2); - - if (client_unsubscribe(client, argv[1])) - return COMMAND_RETURN_OK; - else { - command_error(client, ACK_ERROR_NO_EXIST, - "not subscribed to this channel"); - return COMMAND_RETURN_ERROR; - } -} - -struct channels_context { - GStringChunk *chunk; - - GHashTable *channels; -}; - -static void -collect_channels(gpointer data, gpointer user_data) -{ - struct channels_context *context = user_data; - const struct client *client = data; - - for (GSList *i = client->subscriptions; i != NULL; - i = g_slist_next(i)) { - const char *channel = i->data; - - if (g_hash_table_lookup(context->channels, channel) == NULL) { - char *channel2 = g_string_chunk_insert(context->chunk, - channel); - g_hash_table_insert(context->channels, channel2, - context); - } - } -} - -static void -print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data) -{ - struct client *client = user_data; - const char *channel = key; - - client_printf(client, "channel: %s\n", channel); -} - -static enum command_return -handle_channels(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - assert(argc == 1); - - struct channels_context context = { - .chunk = g_string_chunk_new(1024), - .channels = g_hash_table_new(g_str_hash, g_str_equal), - }; - - client_list_foreach(collect_channels, &context); - - g_hash_table_foreach(context.channels, print_channel, client); - - g_hash_table_destroy(context.channels); - g_string_chunk_free(context.chunk); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_read_messages(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - assert(argc == 1); - - GSList *messages = client_read_messages(client); - - for (GSList *i = messages; i != NULL; i = g_slist_next(i)) { - struct client_message *msg = i->data; - - client_printf(client, "channel: %s\nmessage: %s\n", - msg->channel, msg->message); - client_message_free(msg); - } - - g_slist_free(messages); - - return COMMAND_RETURN_OK; -} - -struct send_message_context { - struct client_message msg; - - bool sent; -}; - -static void -send_message(gpointer data, gpointer user_data) -{ - struct send_message_context *context = user_data; - struct client *client = data; - - if (client_push_message(client, &context->msg)) - context->sent = true; -} - -static enum command_return -handle_send_message(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - assert(argc == 3); - - if (!client_message_valid_channel_name(argv[1])) { - command_error(client, ACK_ERROR_ARG, - "invalid channel name"); - return COMMAND_RETURN_ERROR; - } - - struct send_message_context context = { - .sent = false, - }; - - client_message_init(&context.msg, argv[1], argv[2]); - - client_list_foreach(send_message, &context); - - client_message_deinit(&context.msg); - - if (context.sent) - return COMMAND_RETURN_OK; - else { - command_error(client, ACK_ERROR_NO_EXIST, - "nobody is subscribed to this channel"); - return COMMAND_RETURN_ERROR; - } -} - -/** - * The command registry. - * - * This array must be sorted! - */ -static const struct command commands[] = { - { "add", PERMISSION_ADD, 1, 1, handle_add }, - { "addid", PERMISSION_ADD, 1, 2, handle_addid }, - { "channels", PERMISSION_READ, 0, 0, handle_channels }, - { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, - { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, - { "close", PERMISSION_NONE, -1, -1, handle_close }, - { "commands", PERMISSION_NONE, 0, 0, handle_commands }, - { "config", PERMISSION_ADMIN, 0, 0, handle_config }, - { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, - { "count", PERMISSION_READ, 2, -1, handle_count }, - { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, - { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong }, - { "decoders", PERMISSION_READ, 0, 0, handle_decoders }, - { "delete", PERMISSION_CONTROL, 1, 1, handle_delete }, - { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid }, - { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, - { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, - { "find", PERMISSION_READ, 2, -1, handle_find }, - { "findadd", PERMISSION_READ, 2, -1, handle_findadd}, - { "idle", PERMISSION_READ, 0, -1, handle_idle }, - { "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, - { "list", PERMISSION_READ, 1, -1, handle_list }, - { "listall", PERMISSION_READ, 0, 1, handle_listall }, - { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, - { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, - { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, - { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, - { "load", PERMISSION_ADD, 1, 2, handle_load }, - { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, - { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, - { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, - { "move", PERMISSION_CONTROL, 2, 2, handle_move }, - { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid }, - { "next", PERMISSION_CONTROL, 0, 0, handle_next }, - { "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands }, - { "outputs", PERMISSION_READ, 0, 0, handle_devices }, - { "password", PERMISSION_NONE, 1, 1, handle_password }, - { "pause", PERMISSION_CONTROL, 0, 1, handle_pause }, - { "ping", PERMISSION_NONE, 0, 0, handle_ping }, - { "play", PERMISSION_CONTROL, 0, 1, handle_play }, - { "playid", PERMISSION_CONTROL, 0, 1, handle_playid }, - { "playlist", PERMISSION_READ, 0, 0, handle_playlist }, - { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd }, - { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear }, - { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete }, - { "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind }, - { "playlistid", PERMISSION_READ, 0, 1, handle_playlistid }, - { "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo }, - { "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove }, - { "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch }, - { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, - { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, - { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, - { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, - { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, - { "random", PERMISSION_CONTROL, 1, 1, handle_random }, - { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, - { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, - { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, - { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, - handle_replay_gain_mode }, - { "replay_gain_status", PERMISSION_READ, 0, 0, - handle_replay_gain_status }, - { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, - { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, - { "save", PERMISSION_CONTROL, 1, 1, handle_save }, - { "search", PERMISSION_READ, 2, -1, handle_search }, - { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd }, - { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl }, - { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, - { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, - { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, - { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, - { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, - { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, - { "single", PERMISSION_CONTROL, 1, 1, handle_single }, - { "stats", PERMISSION_READ, 0, 0, handle_stats }, - { "status", PERMISSION_READ, 0, 0, handle_status }, -#ifdef ENABLE_SQLITE - { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, -#endif - { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, - { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, - { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, - { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, - { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, - { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, - { "update", PERMISSION_CONTROL, 0, 1, handle_update }, - { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, -}; - -static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); - -static bool -command_available(G_GNUC_UNUSED const struct command *cmd) -{ -#ifdef ENABLE_SQLITE - if (strcmp(cmd->cmd, "sticker") == 0) - return sticker_enabled(); -#endif - - return true; -} - -/* don't be fooled, this is the command handler for "commands" command */ -static enum command_return -handle_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - const unsigned permission = client_get_permission(client); - const struct command *cmd; - - for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; - - if (cmd->permission == (permission & cmd->permission) && - command_available(cmd)) - client_printf(client, "command: %s\n", cmd->cmd); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_not_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - const unsigned permission = client_get_permission(client); - const struct command *cmd; - - for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; - - if (cmd->permission != (permission & cmd->permission)) - client_printf(client, "command: %s\n", cmd->cmd); - } - - return COMMAND_RETURN_OK; -} - -void command_init(void) -{ -#ifndef NDEBUG - /* ensure that the command list is sorted */ - for (unsigned i = 0; i < num_commands - 1; ++i) - assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0); -#endif -} - -void command_finish(void) -{ -} - -static const struct command * -command_lookup(const char *name) -{ - unsigned a = 0, b = num_commands, i; - int cmp; - - /* binary search */ - do { - i = (a + b) / 2; - - cmp = strcmp(name, commands[i].cmd); - if (cmp == 0) - return &commands[i]; - else if (cmp < 0) - b = i; - else if (cmp > 0) - a = i + 1; - } while (a < b); - - return NULL; -} - -static bool -command_check_request(const struct command *cmd, struct client *client, - unsigned permission, int argc, char *argv[]) -{ - int min = cmd->min + 1; - int max = cmd->max + 1; - - if (cmd->permission != (permission & cmd->permission)) { - if (client != NULL) - command_error(client, ACK_ERROR_PERMISSION, - "you don't have permission for \"%s\"", - cmd->cmd); - return false; - } - - if (min == 0) - return true; - - if (min == max && max != argc) { - if (client != NULL) - command_error(client, ACK_ERROR_ARG, - "wrong number of arguments for \"%s\"", - argv[0]); - return false; - } else if (argc < min) { - if (client != NULL) - command_error(client, ACK_ERROR_ARG, - "too few arguments for \"%s\"", argv[0]); - return false; - } else if (argc > max && max /* != 0 */ ) { - if (client != NULL) - command_error(client, ACK_ERROR_ARG, - "too many arguments for \"%s\"", argv[0]); - return false; - } else - return true; -} - -static const struct command * -command_checked_lookup(struct client *client, unsigned permission, - int argc, char *argv[]) -{ - const struct command *cmd; - - current_command = ""; - - if (argc == 0) - return NULL; - - cmd = command_lookup(argv[0]); - if (cmd == NULL) { - if (client != NULL) - command_error(client, ACK_ERROR_UNKNOWN, - "unknown command \"%s\"", argv[0]); - return NULL; - } - - current_command = cmd->cmd; - - if (!command_check_request(cmd, client, permission, argc, argv)) - return NULL; - - return cmd; -} - -enum command_return -command_process(struct client *client, unsigned num, char *line) -{ - GError *error = NULL; - int argc; - char *argv[COMMAND_ARGV_MAX] = { NULL }; - const struct command *cmd; - enum command_return ret = COMMAND_RETURN_ERROR; - - command_list_num = num; - - /* get the command name (first word on the line) */ - - argv[0] = tokenizer_next_word(&line, &error); - if (argv[0] == NULL) { - current_command = ""; - if (*line == 0) - command_error(client, ACK_ERROR_UNKNOWN, - "No command given"); - else { - command_error(client, ACK_ERROR_UNKNOWN, - "%s", error->message); - g_error_free(error); - } - current_command = NULL; - - return COMMAND_RETURN_ERROR; - } - - argc = 1; - - /* now parse the arguments (quoted or unquoted) */ - - while (argc < (int)G_N_ELEMENTS(argv) && - (argv[argc] = - tokenizer_next_param(&line, &error)) != NULL) - ++argc; - - /* some error checks; we have to set current_command because - command_error() expects it to be set */ - - current_command = argv[0]; - - if (argc >= (int)G_N_ELEMENTS(argv)) { - command_error(client, ACK_ERROR_ARG, "Too many arguments"); - current_command = NULL; - return COMMAND_RETURN_ERROR; - } - - if (*line != 0) { - command_error(client, ACK_ERROR_ARG, - "%s", error->message); - current_command = NULL; - g_error_free(error); - return COMMAND_RETURN_ERROR; - } - - /* look up and invoke the command handler */ - - cmd = command_checked_lookup(client, client_get_permission(client), - argc, argv); - if (cmd) - ret = cmd->handler(client, argc, argv); - - current_command = NULL; - command_list_num = 0; - - return ret; -} diff --git a/src/command.h b/src/command.h index 68d1f95e4..9ea5bb52f 100644 --- a/src/command.h +++ b/src/command.h @@ -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 @@ -20,27 +20,34 @@ #ifndef MPD_COMMAND_H #define MPD_COMMAND_H -#include "ack.h" - -#include <glib.h> -#include <stdbool.h> - enum command_return { - COMMAND_RETURN_ERROR = -1, - COMMAND_RETURN_OK = 0, - COMMAND_RETURN_KILL = 10, - COMMAND_RETURN_CLOSE = 20, + /** + * The command has succeeded, but the "OK" response was not + * yet sent to the client. + */ + COMMAND_RETURN_OK, + + /** + * The connection is now in "idle" mode, and no response shall + * be generated. + */ + COMMAND_RETURN_IDLE, + + /** + * There was an error. The "ACK" response was sent to the + * client. + */ + COMMAND_RETURN_ERROR, + + /** + * The connection to this client shall be closed. + */ + COMMAND_RETURN_CLOSE, + + /** + * The MPD process shall be shut down. + */ + COMMAND_RETURN_KILL, }; -struct client; - -void command_init(void); - -void command_finish(void); - -enum command_return -command_process(struct client *client, unsigned num, char *line); - -void command_success(struct client *client); - #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..e18663525 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,41 @@ 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); +gcc_nonnull(1) +void +db_return_song(struct song *song); +/** + * 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..d36cebcfd --- /dev/null +++ b/src/db/ProxyDatabasePlugin.cxx @@ -0,0 +1,482 @@ +/* + * 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 void ReturnSong(struct song *song) const; + + 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(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)); + mpd_connection_clear_error(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); +} + +static song * +Convert(const struct mpd_song *song); + +struct song * +ProxyDatabase::GetSong(const char *uri, GError **error_r) const +{ + // TODO: implement + // TODO: auto-reconnect + + if (!mpd_send_list_meta(connection, uri)) { + CheckError(connection, error_r); + return nullptr; + } + + struct mpd_song *song = mpd_recv_song(connection); + struct song *song2 = song != nullptr + ? Convert(song) + : nullptr; + mpd_song_free(song); + if (!mpd_response_finish(connection)) { + if (song2 != nullptr) + song_free(song2); + + CheckError(connection, error_r); + return nullptr; + } + + if (song2 == nullptr) + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such song: %s", uri); + + return song2; +} + +void +ProxyDatabase::ReturnSong(struct song *song) const +{ + assert(song != nullptr); + assert(song_in_database(song)); + assert(song_is_detached(song)); + + song_free(song); +} + +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..d83c1ca73 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" +#include "SongFilter.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 "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,141 +172,163 @@ 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; + root = directory_new_root(); + mtime = 0; - db->root = directory_new_root(); - db->mtime = 0; +#ifndef NDEBUG + borrowed_song_count = 0; +#endif 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(borrowed_song_count == 0); - 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, "No such song: %s", uri); +#ifndef NDEBUG + else + ++const_cast<unsigned &>(borrowed_song_count); +#endif 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) +void +SimpleDatabase::ReturnSong(gcc_unused struct song *song) const +{ + assert(song != nullptr); + +#ifndef NDEBUG + assert(borrowed_song_count > 0); + --const_cast<unsigned &>(borrowed_song_count); +#endif +} + +G_GNUC_PURE +const struct directory * +SimpleDatabase::LookupDirectory(const char *uri) const +{ + assert(root != NULL); + assert(uri != NULL); + + ScopeDatabaseLock protect; + return directory_lookup_directory(root, uri); +} + +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; + ScopeDatabaseLock protect; + const struct directory *directory = - simple_db_lookup_directory(db, selection->uri); + directory_lookup_directory(root, 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) { + struct song *song = + directory_lookup_song(root, selection.uri); + if (song != nullptr) + return !selection.Match(*song) || + 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); - db_unlock(); - return ret; + return directory->Walk(selection.recursive, selection.filter, + visit_directory, visit_song, visit_playlist, + error_r); } -const struct db_plugin simple_db_plugin = { - .name = "simple", - .init = simple_db_init, - .finish = simple_db_finish, - .open = simple_db_open, - .close = simple_db_close, - .get_song = simple_db_get_song, - .visit = simple_db_visit, -}; - -struct directory * -simple_db_get_root(struct db *_db) +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 +341,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..2ea5c4925 --- /dev/null +++ b/src/db/SimpleDatabasePlugin.hxx @@ -0,0 +1,99 @@ +/* + * 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; + +#ifndef NDEBUG + unsigned borrowed_song_count; +#endif + +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 void ReturnSong(struct song *song) const; + + 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 deleted file mode 100644 index 706c807fd..000000000 --- a/src/dbUtils.h +++ /dev/null @@ -1,56 +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. - */ - -#ifndef MPD_DB_UTILS_H -#define MPD_DB_UTILS_H - -#include "gcc.h" - -#include <glib.h> -#include <stdbool.h> - -struct locate_item_list; -struct player_control; - -gcc_nonnull(1,2) -bool -addAllIn(struct player_control *pc, const char *uri, GError **error_r); - -gcc_nonnull(1,2) -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_lock.h b/src/db_lock.h index 4640502f3..eed71eec0 100644 --- a/src/db_lock.h +++ b/src/db_lock.h @@ -81,4 +81,19 @@ db_unlock(void) g_static_mutex_unlock(&db_mutex); } +#ifdef __cplusplus + +class ScopeDatabaseLock { +public: + ScopeDatabaseLock() { + db_lock(); + } + + ~ScopeDatabaseLock() { + db_unlock(); + } +}; + +#endif + #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/db_visitor.h b/src/db_visitor.h deleted file mode 100644 index 6b90c1868..000000000 --- a/src/db_visitor.h +++ /dev/null @@ -1,54 +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. - */ - -#ifndef MPD_DB_VISITOR_H -#define MPD_DB_VISITOR_H - -struct directory; -struct song; -struct playlist_metadata; - -struct db_visitor { - /** - * Visit a directory. Optional method. - * - * @return true to continue the operation, false on error (set error_r) - */ - bool (*directory)(const struct directory *directory, void *ctx, - GError **error_r); - - /** - * Visit a song. Optional method. - * - * @return true to continue the operation, false on error (set error_r) - */ - bool (*song)(struct song *song, void *ctx, GError **error_r); - - /** - * Visit a playlist. Optional method. - * - * @param directory the directory the playlist resides in - * @return true to continue the operation, false on error (set error_r) - */ - bool (*playlist)(const struct playlist_metadata *playlist, - const struct directory *directory, void *ctx, - GError **error_r); -}; - -#endif diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx new file mode 100644 index 000000000..b3b7f1d73 --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.cxx @@ -0,0 +1,148 @@ +/* + * 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 "AdPlugDecoderPlugin.h" +#include "tag_handler.h" + +extern "C" { +#include "decoder_api.h" +#include "audio_check.h" +} + +#include <adplug/adplug.h> +#include <adplug/emuopl.h> + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "adplug" + +static unsigned sample_rate; + +static bool +adplug_init(const struct config_param *param) +{ + GError *error = NULL; + + sample_rate = config_get_block_unsigned(param, "sample_rate", 48000); + if (!audio_check_sample_rate(sample_rate, &error)) { + g_warning("%s\n", error->message); + g_error_free(error); + return false; + } + + return true; +} + +static void +adplug_file_decode(struct decoder *decoder, const char *path_fs) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return; + + struct audio_format audio_format; + audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); + + decoder_initialized(decoder, &audio_format, false, + player->songlength() / 1000.); + + int16_t buffer[2048]; + const unsigned frames_per_buffer = G_N_ELEMENTS(buffer) / 2; + enum decoder_command cmd; + + do { + if (!player->update()) + break; + + opl.update(buffer, frames_per_buffer); + cmd = decoder_data(decoder, NULL, + buffer, sizeof(buffer), + 0); + } while (cmd == DECODE_COMMAND_NONE); + + delete player; +} + +static void +adplug_scan_tag(enum tag_type type, const std::string &value, + const struct tag_handler *handler, void *handler_ctx) +{ + if (!value.empty()) + tag_handler_invoke_tag(handler, handler_ctx, + type, value.c_str()); +} + +static bool +adplug_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + player->songlength() / 1000); + + if (handler->tag != nullptr) { + adplug_scan_tag(TAG_TITLE, player->gettitle(), + handler, handler_ctx); + adplug_scan_tag(TAG_ARTIST, player->getauthor(), + handler, handler_ctx); + adplug_scan_tag(TAG_COMMENT, player->getdesc(), + handler, handler_ctx); + } + + delete player; + return true; +} + +static const char *const adplug_suffixes[] = { + "amd", + "d00", + "hsc", + "laa", + "rad", + "raw", + "sa2", + nullptr +}; + +const struct decoder_plugin adplug_decoder_plugin = { + "adplug", + adplug_init, + nullptr, + nullptr, + adplug_file_decode, + adplug_scan_file, + nullptr, + nullptr, + adplug_suffixes, + nullptr, +}; diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/AdPlugDecoderPlugin.h new file mode 100644 index 000000000..9fdf438aa --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * 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_ADPLUG_H +#define MPD_DECODER_ADPLUG_H + +extern const struct decoder_plugin adplug_decoder_plugin; + +#endif diff --git a/src/decoder/_flac_common.c b/src/decoder/FLACCommon.cxx index d7f0c4a8a..8e86b6ae4 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/FLACCommon.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 @@ -22,10 +22,13 @@ */ #include "config.h" -#include "_flac_common.h" -#include "flac_metadata.h" -#include "flac_pcm.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" +#include "FLAC_PCM.hxx" + +extern "C" { #include "audio_check.h" +} #include <glib.h> @@ -46,7 +49,7 @@ flac_data_init(struct flac_data *data, struct decoder * decoder, data->position = 0; data->decoder = decoder; data->input_stream = input_stream; - data->tag = NULL; + data->tag = nullptr; } void @@ -54,7 +57,7 @@ flac_data_deinit(struct flac_data *data) { pcm_buffer_deinit(&data->buffer); - if (data->tag != NULL) + if (data->tag != nullptr) tag_free(data->tag); } @@ -86,7 +89,7 @@ flac_got_stream_info(struct flac_data *data, if (data->initialized || data->unsupported) return; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, stream_info->sample_rate, flac_sample_format(stream_info->bits_per_sample), @@ -129,8 +132,8 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, decoder_mixramp(data->decoder, replay_gain_db, mixramp_start, mixramp_end); - if (data->tag != NULL) - flac_vorbis_comments_to_tag(data->tag, NULL, + if (data->tag != nullptr) + flac_vorbis_comments_to_tag(data->tag, nullptr, &block->data.vorbis_comment); default: @@ -160,7 +163,7 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) if (data->unsupported) return false; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, header->sample_rate, flac_sample_format(header->bits_per_sample), @@ -199,7 +202,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame, buffer = pcm_buffer_get(&data->buffer, buffer_size); flac_convert(buffer, frame->header.channels, - data->audio_format.format, buf, + (enum sample_format)data->audio_format.format, buf, 0, frame->header.blocksize); if (nbytes > 0) diff --git a/src/decoder/_flac_common.h b/src/decoder/FLACCommon.hxx index 0d90ba656..3d280cc49 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/FLACCommon.hxx @@ -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 @@ -21,13 +21,13 @@ * Common data structures and functions used by FLAC and OggFLAC */ -#ifndef MPD_FLAC_COMMON_H -#define MPD_FLAC_COMMON_H +#ifndef MPD_FLAC_COMMON_HXX +#define MPD_FLAC_COMMON_HXX +extern "C" { #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/flac_decoder_plugin.c b/src/decoder/FLACDecoderPlugin.cxx index fb0b3502d..0b543ea65 100644 --- a/src/decoder/flac_decoder_plugin.c +++ b/src/decoder/FLACDecoderPlugin.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,13 +18,13 @@ */ #include "config.h" /* must be first for large file support */ -#include "_flac_common.h" -#include "flac_compat.h" -#include "flac_metadata.h" +#include "FLACDecoderPlugin.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 -#include "_ogg_common.h" -#endif +extern "C" { +#include "ogg_codec.h" +} #include <glib.h> @@ -34,14 +34,18 @@ #include <sys/stat.h> #include <sys/types.h> +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#endif + /* this code was based on flac123, from flac-tools */ static FLAC__StreamDecoderReadStatus flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__byte buf[], flac_read_status_size_t *bytes, + FLAC__byte buf[], size_t *bytes, void *fdata) { - struct flac_data *data = fdata; + struct flac_data *data = (struct flac_data *)fdata; size_t r; r = decoder_read(data->decoder, data->input_stream, @@ -69,7 +73,7 @@ flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, - NULL)) + nullptr)) return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; return FLAC__STREAM_DECODER_SEEK_STATUS_OK; @@ -120,28 +124,6 @@ flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, flac_error_common_cb(status, (struct flac_data *) fdata); } -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) -{ - switch (state) { - case FLAC__SEEKABLE_STREAM_DECODER_OK: - case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: - case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: - return; - - case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - break; - } - - g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]); -} -#else /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacPrintErroredState(FLAC__StreamDecoderState state) { switch (state) { @@ -162,7 +144,6 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state) g_warning("%s\n", FLAC__StreamDecoderStateString[state]); } -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, const FLAC__StreamMetadata * block, void *vdata) @@ -195,7 +176,7 @@ static bool flac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) { - return flac_scan_file2(file, NULL, handler, handler_ctx); + return flac_scan_file2(file, nullptr, handler, handler_ctx); } /** @@ -205,15 +186,13 @@ static FLAC__StreamDecoder * flac_decoder_new(void) { FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); - if (sd == NULL) { + if (sd == nullptr) { g_warning("FLAC__stream_decoder_new() failed"); - return NULL; + return nullptr; } -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); -#endif return sd; } @@ -259,7 +238,7 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, data->first_frame = t_start; while (true) { - if (data->tag != NULL && !tag_is_empty(data->tag)) { + if (data->tag != nullptr && !tag_is_empty(data->tag)) { cmd = decoder_tag(data->decoder, data->input_stream, data->tag); tag_free(data->tag); @@ -300,7 +279,6 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, static FLAC__StreamDecoderInitStatus stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 return FLAC__stream_decoder_init_ogg_stream(flac_dec, flac_read_cb, flac_seek_cb, @@ -311,12 +289,6 @@ stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) flacMetadata, flac_error_cb, data); -#else - (void)flac_dec; - (void)data; - - return FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -#endif } static FLAC__StreamDecoderInitStatus @@ -348,7 +320,7 @@ flac_decode_internal(struct decoder * decoder, struct flac_data data; flac_dec = flac_decoder_new(); - if (flac_dec == NULL) + if (flac_dec == nullptr) return; flac_data_init(&data, decoder, input_stream); @@ -359,9 +331,7 @@ flac_decode_internal(struct decoder * decoder, if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { flac_data_deinit(&data); FLAC__stream_decoder_delete(flac_dec); -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 g_warning("%s", FLAC__StreamDecoderInitStatusString[status]); -#endif return; } @@ -386,21 +356,12 @@ flac_decode(struct decoder * decoder, struct input_stream *input_stream) flac_decode_internal(decoder, input_stream, false); } -#ifndef HAVE_OGGFLAC - static bool oggflac_init(G_GNUC_UNUSED const struct config_param *param) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 return !!FLAC_API_SUPPORTS_OGG_FLAC; -#else - /* disable oggflac when libflac is too old */ - return false; -#endif } -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - static bool oggflac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) @@ -421,7 +382,7 @@ oggflac_scan_file(const char *file, if (!(block = FLAC__metadata_iterator_get_block(it))) break; - flac_scan_metadata(NULL, block, + flac_scan_metadata(nullptr, block, handler, handler_ctx); } while (FLAC__metadata_iterator_next(it)); FLAC__metadata_iterator_delete(it); @@ -433,54 +394,57 @@ oggflac_scan_file(const char *file, static void oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) { - if (ogg_stream_type_detect(input_stream) != FLAC) + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_FLAC) return; - /* rewind the stream, because ogg_stream_type_detect() has + /* rewind the stream, because ogg_codec_detect() has moved it */ - input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); flac_decode_internal(decoder, input_stream, true); } -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr }; static const char *const oggflac_mime_types[] = { "application/ogg", "application/x-ogg", "audio/ogg", "audio/x-flac+ogg", "audio/x-ogg", - NULL + nullptr }; -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .init = oggflac_init, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .stream_decode = oggflac_decode, - .scan_file = oggflac_scan_file, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -#endif + "oggflac", + oggflac_init, + nullptr, + oggflac_decode, + nullptr, + oggflac_scan_file, + nullptr, + nullptr, + oggflac_suffixes, + oggflac_mime_types, }; -#endif /* HAVE_OGGFLAC */ - -static const char *const flac_suffixes[] = { "flac", NULL }; +static const char *const flac_suffixes[] = { "flac", nullptr }; static const char *const flac_mime_types[] = { "application/flac", "application/x-flac", "audio/flac", "audio/x-flac", - NULL + nullptr }; const struct decoder_plugin flac_decoder_plugin = { - .name = "flac", - .stream_decode = flac_decode, - .scan_file = flac_scan_file, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, + "flac", + nullptr, + nullptr, + flac_decode, + nullptr, + flac_scan_file, + nullptr, + nullptr, + flac_suffixes, + flac_mime_types, }; diff --git a/src/decoder/FLACDecoderPlugin.h b/src/decoder/FLACDecoderPlugin.h new file mode 100644 index 000000000..c99deeef7 --- /dev/null +++ b/src/decoder/FLACDecoderPlugin.h @@ -0,0 +1,26 @@ +/* + * 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_FLAC_H +#define MPD_DECODER_FLAC_H + +extern const struct decoder_plugin flac_decoder_plugin; +extern const struct decoder_plugin oggflac_decoder_plugin; + +#endif diff --git a/src/decoder/flac_metadata.c b/src/decoder/FLACMetaData.cxx index bd1eaf323..b1d82a93d 100644 --- a/src/decoder/flac_metadata.c +++ b/src/decoder/FLACMetaData.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,9 +18,14 @@ */ #include "config.h" -#include "flac_metadata.h" +#include "FLACMetaData.hxx" + +extern "C" { +#include "XiphTags.h" #include "replay_gain_info.h" #include "tag.h" +} + #include "tag_handler.h" #include "tag_table.h" @@ -91,7 +96,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block, int len; const unsigned char *p; - *str = NULL; + *str = nullptr; offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, cmnt); if (offset < 0) @@ -136,9 +141,9 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, if (entry->length <= name_length || g_ascii_strncasecmp(comment, name, name_length) != 0) - return NULL; + return nullptr; - if (char_tnum != NULL) { + if (char_tnum != nullptr) { char_tnum_length = strlen(char_tnum); if (entry->length > name_length + char_tnum_length + 2 && comment[name_length] == '[' && @@ -157,7 +162,7 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, return comment + name_length + 1; } - return NULL; + return nullptr; } /** @@ -174,7 +179,7 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, size_t value_length; value = flac_comment_value(entry, name, char_tnum, &value_length); - if (value != NULL) { + if (value != nullptr) { char *p = g_strndup(value, value_length); tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); g_free(p); @@ -184,23 +189,16 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, return false; } -static const struct tag_table flac_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void flac_scan_comment(const char *char_tnum, const FLAC__StreamMetadata_VorbisComment_Entry *entry, const struct tag_handler *handler, void *handler_ctx) { - if (handler->pair != NULL) { + if (handler->pair != nullptr) { char *name = g_strdup((const char*)entry->entry); char *value = strchr(name, '='); - if (value != NULL && value > name) { + if (value != nullptr && value > name) { *value++ = 0; tag_handler_invoke_pair(handler, handler_ctx, name, value); @@ -209,14 +207,15 @@ flac_scan_comment(const char *char_tnum, g_free(name); } - for (const struct tag_table *i = flac_tags; i->name != NULL; ++i) + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) if (flac_copy_comment(entry, i->name, i->type, char_tnum, handler, handler_ctx)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) if (flac_copy_comment(entry, - tag_item_names[i], i, char_tnum, + tag_item_names[i], (enum tag_type)i, + char_tnum, handler, handler_ctx)) return; } @@ -266,7 +265,7 @@ flac_scan_file2(const char *file, const char *char_tnum, const struct tag_handler *handler, void *handler_ctx) { FLAC__Metadata_SimpleIterator *it; - FLAC__StreamMetadata *block = NULL; + FLAC__StreamMetadata *block = nullptr; it = FLAC__metadata_simple_iterator_new(); if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { @@ -307,17 +306,3 @@ flac_scan_file2(const char *file, const char *char_tnum, return true; } - -struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag = tag_new(); - - if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) || - tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} diff --git a/src/decoder/flac_metadata.h b/src/decoder/FLACMetaData.hxx index 3c463d5d6..1dbdc9a88 100644 --- a/src/decoder/flac_metadata.h +++ b/src/decoder/FLACMetaData.hxx @@ -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 @@ -58,7 +58,4 @@ bool flac_scan_file2(const char *file, const char *char_tnum, const struct tag_handler *handler, void *handler_ctx); -struct tag * -flac_tag_load(const char *file, const char *char_tnum); - #endif diff --git a/src/decoder/flac_pcm.c b/src/decoder/FLAC_PCM.cxx index 6964d8ac6..303530aa7 100644 --- a/src/decoder/flac_pcm.c +++ b/src/decoder/FLAC_PCM.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,7 +18,7 @@ */ #include "config.h" -#include "flac_pcm.h" +#include "FLAC_PCM.hxx" #include <assert.h> diff --git a/src/decoder/flac_pcm.h b/src/decoder/FLAC_PCM.hxx index a931998c1..97d214c17 100644 --- a/src/decoder/flac_pcm.h +++ b/src/decoder/FLAC_PCM.hxx @@ -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 @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_FLAC_PCM_H -#define MPD_FLAC_PCM_H +#ifndef MPD_FLAC_PCM_HXX +#define MPD_FLAC_PCM_HXX #include "audio_format.h" diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx new file mode 100644 index 000000000..99f73d48e --- /dev/null +++ b/src/decoder/OggUtil.cxx @@ -0,0 +1,56 @@ +/* + * 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 "OggUtil.hxx" + +extern "C" { +#include "decoder_api.h" +} + +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, + struct input_stream *input_stream, size_t size) +{ + char *buffer = ogg_sync_buffer(&oy, size); + if (buffer == nullptr) + return false; + + size_t nbytes = decoder_read(decoder, input_stream, + buffer, size); + if (nbytes == 0) + return false; + + ogg_sync_wrote(&oy, nbytes); + return true; +} + +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + struct decoder *decoder, struct input_stream *input_stream) +{ + while (true) { + int r = ogg_sync_pageout(&oy, &page); + if (r != 0) + return r > 0; + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} diff --git a/src/decoder/OggUtil.hxx b/src/decoder/OggUtil.hxx new file mode 100644 index 000000000..95bf6472f --- /dev/null +++ b/src/decoder/OggUtil.hxx @@ -0,0 +1,48 @@ +/* + * 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_OGG_UTIL_HXX +#define MPD_OGG_UTIL_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <stddef.h> + +/** + * Feed data from the #input_stream into the #ogg_sync_state. + * + * @return false on error or end-of-file + */ +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, + struct input_stream *input_stream, size_t size); + +/** + * Feed into the #ogg_sync_state until a page gets available. Garbage + * data at the beginning is considered a fatal error. + * + * @return true if a page is available + */ +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + struct decoder *decoder, struct input_stream *input_stream); + +#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx new file mode 100644 index 000000000..35e368ca9 --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.cxx @@ -0,0 +1,366 @@ +/* + * 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" /* must be first for large file support */ +#include "OpusDecoderPlugin.h" +#include "OpusHead.hxx" +#include "OpusTags.hxx" +#include "OggUtil.hxx" + +extern "C" { +#include "ogg_codec.h" +#include "decoder_api.h" +} + +#include "audio_check.h" +#include "tag_handler.h" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <glib.h> + +#include <stdio.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "opus" + +static const opus_int32 opus_sample_rate = 48000; + +gcc_pure +static bool +IsOpusHead(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0; +} + +gcc_pure +static bool +IsOpusTags(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0; +} + +static bool +mpd_opus_init(G_GNUC_UNUSED const struct config_param *param) +{ + g_debug("%s", opus_get_version_string()); + + return true; +} + +class MPDOpusDecoder { + struct decoder *decoder; + struct input_stream *input_stream; + + ogg_stream_state os; + + OpusDecoder *opus_decoder = nullptr; + opus_int16 *output_buffer = nullptr; + unsigned output_size = 0; + + bool os_initialized = false; + bool found_opus = false; + + int opus_serialno; + + size_t frame_size; + +public: + MPDOpusDecoder(struct decoder *_decoder, + struct input_stream *_input_stream) + :decoder(_decoder), input_stream(_input_stream) {} + ~MPDOpusDecoder(); + + enum decoder_command HandlePage(ogg_page &page); + enum decoder_command HandlePacket(const ogg_packet &packet); + enum decoder_command HandleBOS(const ogg_packet &packet); + enum decoder_command HandleTags(const ogg_packet &packet); + enum decoder_command HandleAudio(const ogg_packet &packet); +}; + +MPDOpusDecoder::~MPDOpusDecoder() +{ + g_free(output_buffer); + + if (opus_decoder != nullptr) + opus_decoder_destroy(opus_decoder); + + if (os_initialized) + ogg_stream_clear(&os); +} + +enum decoder_command +MPDOpusDecoder::HandlePage(ogg_page &page) +{ + const auto page_serialno = ogg_page_serialno(&page); + if (!os_initialized) { + os_initialized = true; + ogg_stream_init(&os, page_serialno); + } else if (page_serialno != os.serialno) + ogg_stream_reset_serialno(&os, page_serialno); + + ogg_stream_pagein(&os, &page); + + ogg_packet packet; + while (ogg_stream_packetout(&os, &packet) == 1) { + enum decoder_command cmd = HandlePacket(packet); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + return DECODE_COMMAND_NONE; +} + +enum decoder_command +MPDOpusDecoder::HandlePacket(const ogg_packet &packet) +{ + if (packet.e_o_s) + return DECODE_COMMAND_STOP; + + if (packet.b_o_s) + return HandleBOS(packet); + else if (!found_opus) + return DECODE_COMMAND_STOP; + + if (IsOpusTags(packet)) + return HandleTags(packet); + + return HandleAudio(packet); +} + +enum decoder_command +MPDOpusDecoder::HandleBOS(const ogg_packet &packet) +{ + assert(packet.b_o_s); + + if (found_opus || !IsOpusHead(packet)) + return DECODE_COMMAND_STOP; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) + return DECODE_COMMAND_STOP; + + assert(opus_decoder == nullptr); + assert(output_buffer == nullptr); + + opus_serialno = os.serialno; + found_opus = true; + + /* TODO: parse attributes from the OpusHead (sample rate, + channels, ...) */ + + int opus_error; + opus_decoder = opus_decoder_create(opus_sample_rate, channels, + &opus_error); + if (opus_decoder == nullptr) { + g_warning("libopus error: %s", + opus_strerror(opus_error)); + return DECODE_COMMAND_STOP; + } + + struct audio_format audio_format; + audio_format_init(&audio_format, opus_sample_rate, + SAMPLE_FORMAT_S16, channels); + decoder_initialized(decoder, &audio_format, false, -1); + frame_size = audio_format_frame_size(&audio_format); + + /* allocate an output buffer for 16 bit PCM samples big enough + to hold a quarter second, larger than 120ms required by + libopus */ + output_size = audio_format.sample_rate / 4; + output_buffer = (opus_int16 *) + g_malloc(sizeof(*output_buffer) * output_size * + audio_format.channels); + + return decoder_get_command(decoder); +} + +enum decoder_command +MPDOpusDecoder::HandleTags(const ogg_packet &packet) +{ + struct tag *tag = tag_new(); + + enum decoder_command cmd; + if (ScanOpusTags(packet.packet, packet.bytes, &add_tag_handler, tag) && + !tag_is_empty(tag)) + cmd = decoder_tag(decoder, input_stream, tag); + else + cmd = decoder_get_command(decoder); + + tag_free(tag); + return cmd; +} + +enum decoder_command +MPDOpusDecoder::HandleAudio(const ogg_packet &packet) +{ + assert(opus_decoder != nullptr); + + int nframes = opus_decode(opus_decoder, + (const unsigned char*)packet.packet, + packet.bytes, + output_buffer, output_size, + 0); + if (nframes < 0) { + g_warning("%s", opus_strerror(nframes)); + return DECODE_COMMAND_STOP; + } + + if (nframes > 0) { + const size_t nbytes = nframes * frame_size; + enum decoder_command cmd = + decoder_data(decoder, input_stream, + output_buffer, nbytes, + 0); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + return DECODE_COMMAND_NONE; +} + +static void +mpd_opus_stream_decode(struct decoder *decoder, + struct input_stream *input_stream) +{ + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_OPUS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); + + MPDOpusDecoder d(decoder, input_stream); + + ogg_sync_state oy; + ogg_sync_init(&oy); + + while (true) { + if (!OggFeed(oy, decoder, input_stream, 1024)) + break; + + ogg_page page; + while (ogg_sync_pageout(&oy, &page) == 1) { + enum decoder_command cmd = d.HandlePage(page); + if (cmd != DECODE_COMMAND_NONE) + break; + } + } + + ogg_sync_clear(&oy); +} + +static bool +mpd_opus_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + ogg_sync_state oy; + ogg_sync_init(&oy); + + ogg_page page; + if (!OggExpectPage(oy, page, nullptr, is)) { + ogg_sync_clear(&oy); + return false; + } + + /* read at most two more pages */ + unsigned remaining_pages = 2; + + bool result = false; + + ogg_stream_state os; + ogg_stream_init(&os, ogg_page_serialno(&page)); + ogg_stream_pagein(&os, &page); + + ogg_packet packet; + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r < 0) { + result = false; + break; + } + + if (r == 0) { + if (remaining_pages-- == 0) + break; + + if (!OggExpectPage(oy, page, nullptr, is)) { + result = false; + break; + } + + ogg_stream_pagein(&os, &page); + continue; + } + + if (packet.b_o_s) { + if (!IsOpusHead(packet)) + break; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) { + result = false; + break; + } + + result = true; + } else if (!result) + break; + else if (IsOpusTags(packet)) { + if (!ScanOpusTags(packet.packet, packet.bytes, + handler, handler_ctx)) + result = false; + + break; + } + } + + ogg_stream_clear(&os); + ogg_sync_clear(&oy); + + return result; +} + +static const char *const opus_suffixes[] = { + "opus", + "ogg", + "oga", + nullptr +}; + +static const char *const opus_mime_types[] = { + "audio/opus", + nullptr +}; + +const struct decoder_plugin opus_decoder_plugin = { + "opus", + mpd_opus_init, + nullptr, + mpd_opus_stream_decode, + nullptr, + nullptr, + mpd_opus_scan_stream, + nullptr, + opus_suffixes, + opus_mime_types, +}; diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/OpusDecoderPlugin.h new file mode 100644 index 000000000..c95d6ded3 --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * 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_OPUS_H +#define MPD_DECODER_OPUS_H + +extern const struct decoder_plugin opus_decoder_plugin; + +#endif diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx new file mode 100644 index 000000000..c57e08e10 --- /dev/null +++ b/src/decoder/OpusHead.cxx @@ -0,0 +1,44 @@ +/* + * 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 "OpusHead.hxx" + +#include <stdint.h> +#include <string.h> + +struct OpusHead { + char signature[8]; + uint8_t version, channels; + uint16_t pre_skip; + uint32_t sample_rate; + uint16_t output_gain; + uint8_t channel_mapping; +}; + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r) +{ + const OpusHead *h = (const OpusHead *)data; + if (size < 19 || (h->version & 0xf0) != 0) + return false; + + channels_r = h->channels; + return true; +} diff --git a/src/decoder/OpusHead.hxx b/src/decoder/OpusHead.hxx new file mode 100644 index 000000000..9f75c4f70 --- /dev/null +++ b/src/decoder/OpusHead.hxx @@ -0,0 +1,30 @@ +/* + * 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_OPUS_HEAD_HXX +#define MPD_OPUS_HEAD_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r); + +#endif diff --git a/src/decoder/OpusReader.hxx b/src/decoder/OpusReader.hxx new file mode 100644 index 000000000..2cfc14118 --- /dev/null +++ b/src/decoder/OpusReader.hxx @@ -0,0 +1,97 @@ +/* + * 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_OPUS_READER_HXX +#define MPD_OPUS_READER_HXX + +#include "check.h" + +#include <stdint.h> +#include <string.h> + +class OpusReader { + const uint8_t *p, *const end; + +public: + OpusReader(const void *_p, size_t size) + :p((const uint8_t *)_p), end(p + size) {} + + bool Skip(size_t length) { + p += length; + return p <= end; + } + + const void *Read(size_t length) { + const uint8_t *result = p; + return Skip(length) + ? result + : nullptr; + } + + bool Expect(const void *value, size_t length) { + const void *data = Read(length); + return data != nullptr && memcmp(value, data, length) == 0; + } + + bool ReadByte(uint8_t &value_r) { + if (p >= end) + return false; + + value_r = *p++; + return true; + } + + bool ReadShort(uint16_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8); + return true; + } + + bool ReadWord(uint32_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8) + | (value[2] << 16) | (value[3] << 24); + return true; + } + + bool SkipString() { + uint32_t length; + return ReadWord(length) && Skip(length); + } + + char *ReadString() { + uint32_t length; + if (!ReadWord(length)) + return nullptr; + + const char *src = (const char *)Read(length); + if (src == nullptr) + return nullptr; + + return strndup(src, length); + } +}; + +#endif diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx new file mode 100644 index 000000000..cb35a6247 --- /dev/null +++ b/src/decoder/OpusTags.cxx @@ -0,0 +1,77 @@ +/* + * 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 "OpusTags.hxx" +#include "OpusReader.hxx" +#include "XiphTags.h" +#include "tag_handler.h" + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +static void +ScanOneOpusTag(const char *name, const char *value, + const struct tag_handler *handler, void *ctx) +{ + tag_handler_invoke_pair(handler, ctx, name, value); + + if (handler->tag != nullptr) { + enum tag_type t = tag_table_lookup_i(xiph_tags, name); + if (t != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, ctx, t, value); + } +} + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx) +{ + OpusReader r(data, size); + if (!r.Expect("OpusTags", 8)) + return false; + + if (handler->pair == nullptr && handler->tag == nullptr) + return true; + + if (!r.SkipString()) + return false; + + uint32_t n; + if (!r.ReadWord(n)) + return false; + + while (n-- > 0) { + char *p = r.ReadString(); + if (p == nullptr) + return false; + + char *eq = strchr(p, '='); + if (eq != nullptr && eq > p) { + *eq = 0; + + ScanOneOpusTag(p, eq + 1, handler, ctx); + } + + free(p); + } + + return true; +} diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx new file mode 100644 index 000000000..2f3eec844 --- /dev/null +++ b/src/decoder/OpusTags.hxx @@ -0,0 +1,31 @@ +/* + * 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_OPUS_TAGS_HXX +#define MPD_OPUS_TAGS_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx); + +#endif diff --git a/src/decoder/XiphTags.c b/src/decoder/XiphTags.c new file mode 100644 index 000000000..d55787b94 --- /dev/null +++ b/src/decoder/XiphTags.c @@ -0,0 +1,28 @@ +/* + * 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 "XiphTags.h" + +const struct tag_table xiph_tags[] = { + { "tracknumber", TAG_TRACK }, + { "discnumber", TAG_DISC }, + { "album artist", TAG_ALBUM_ARTIST }, + { NULL, TAG_NUM_OF_ITEM_TYPES } +}; diff --git a/src/decoder/XiphTags.h b/src/decoder/XiphTags.h new file mode 100644 index 000000000..22a4e2204 --- /dev/null +++ b/src/decoder/XiphTags.h @@ -0,0 +1,28 @@ +/* + * 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_XIPH_TAGS_H +#define MPD_XIPH_TAGS_H + +#include "check.h" +#include "tag_table.h" + +extern const struct tag_table xiph_tags[]; + +#endif diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h deleted file mode 100644 index 9a30acc26..000000000 --- a/src/decoder/flac_compat.h +++ /dev/null @@ -1,114 +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. - */ - -/* - * Common data structures and functions used by FLAC and OggFLAC - */ - -#ifndef MPD_FLAC_COMPAT_H -#define MPD_FLAC_COMPAT_H - -#include <FLAC/export.h> -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -# include <FLAC/seekable_stream_decoder.h> - -/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been - merged into the StreamDecoder. The following macros try to emulate - the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls - to the old SeekableStreamDecoder API. */ - -#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder -#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new -#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position -#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state -#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single -#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata -#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute -#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish -#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete - -#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM - -typedef unsigned flac_read_status_size_t; - -#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus -#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR - -#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus -#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK -#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR -#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR - -#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus -#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK -#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR -#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR - -#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus -#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK -#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR -#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR - -typedef enum { - FLAC__STREAM_DECODER_INIT_STATUS_OK, - FLAC__STREAM_DECODER_INIT_STATUS_ERROR, -} FLAC__StreamDecoderInitStatus; - -static inline FLAC__StreamDecoderInitStatus -FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder, - FLAC__SeekableStreamDecoderReadCallback read_cb, - FLAC__SeekableStreamDecoderSeekCallback seek_cb, - FLAC__SeekableStreamDecoderTellCallback tell_cb, - FLAC__SeekableStreamDecoderLengthCallback length_cb, - FLAC__SeekableStreamDecoderEofCallback eof_cb, - FLAC__SeekableStreamDecoderWriteCallback write_cb, - FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, - FLAC__SeekableStreamDecoderErrorCallback error_cb, - void *data) -{ - return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) && - FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) && - FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) && - FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) && - FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) && - FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) && - FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) && - FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) && - FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) && - FLAC__seekable_stream_decoder_set_client_data(decoder, data) && - FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK - ? FLAC__STREAM_DECODER_INIT_STATUS_OK - : FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -} - -#else /* FLAC_API_VERSION_CURRENT > 7 */ - -# include <FLAC/stream_decoder.h> - -# define flac_init(a,b,c,d,e,f,g,h,i,j) \ - (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ - == FLAC__STREAM_DECODER_INIT_STATUS_OK) - -typedef size_t flac_read_status_size_t; - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c index 62c371642..c1a0c4b0f 100644 --- a/src/decoder/mad_decoder_plugin.c +++ b/src/decoder/mad_decoder_plugin.c @@ -76,9 +76,9 @@ mad_fixed_to_24_sample(mad_fixed_t sample) sample = sample + (1L << (MAD_F_FRACBITS - bits)); /* clip */ - if (sample > MAX) + if (gcc_unlikely(sample > MAX)) sample = MAX; - else if (sample < MIN) + else if (gcc_unlikely(sample < MIN)) sample = MIN; /* quantize */ diff --git a/src/decoder/_ogg_common.c b/src/decoder/ogg_codec.c index 09d2712da..7416f27da 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/ogg_codec.c @@ -22,25 +22,27 @@ */ #include "config.h" -#include "_ogg_common.h" +#include "ogg_codec.h" -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream) +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is) { /* oggflac detection based on code in ogg123 and this post * http://lists.xiph.org/pipermail/flac/2004-December/000393.html * ogg123 trunk still doesn't have this patch as of June 2005 */ unsigned char buf[41]; - size_t r; - - r = decoder_read(NULL, inStream, buf, sizeof(buf)); + size_t r = decoder_read(decoder, is, buf, sizeof(buf)); if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) - return VORBIS; + return OGG_CODEC_UNKNOWN; if ((memcmp(buf + 29, "FLAC", 4) == 0 && memcmp(buf + 37, "fLaC", 4) == 0) || memcmp(buf + 28, "FLAC", 4) == 0 || memcmp(buf + 28, "fLaC", 4) == 0) - return FLAC; + return OGG_CODEC_FLAC; + + if (memcmp(buf + 28, "Opus", 4) == 0) + return OGG_CODEC_OPUS; - return VORBIS; + return OGG_CODEC_VORBIS; } diff --git a/src/decoder/_ogg_common.h b/src/decoder/ogg_codec.h index 85e4ebba6..fd1fecfbb 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/ogg_codec.h @@ -21,13 +21,19 @@ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) */ -#ifndef MPD_OGG_COMMON_H -#define MPD_OGG_COMMON_H +#ifndef MPD_OGG_CODEC_H +#define MPD_OGG_CODEC_H #include "decoder_api.h" -typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream); +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is); #endif /* _OGG_COMMON_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/vorbis_comments.c b/src/decoder/vorbis_comments.c index 6c2d57b72..84f7c5014 100644 --- a/src/decoder/vorbis_comments.c +++ b/src/decoder/vorbis_comments.c @@ -19,6 +19,7 @@ #include "config.h" #include "vorbis_comments.h" +#include "XiphTags.h" #include "tag.h" #include "tag_table.h" #include "tag_handler.h" @@ -95,13 +96,6 @@ vorbis_copy_comment(const char *comment, return false; } -static const struct tag_table vorbis_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void vorbis_scan_comment(const char *comment, const struct tag_handler *handler, void *handler_ctx) @@ -119,7 +113,7 @@ vorbis_scan_comment(const char *comment, g_free(name); } - for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i) + for (const struct tag_table *i = xiph_tags; i->name != NULL; ++i) if (vorbis_copy_comment(comment, i->name, i->type, handler, handler_ctx)) return; diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c index 15cdc0ca9..f180a6559 100644 --- a/src/decoder/vorbis_decoder_plugin.c +++ b/src/decoder/vorbis_decoder_plugin.c @@ -19,7 +19,7 @@ #include "config.h" #include "vorbis_comments.h" -#include "_ogg_common.h" +#include "ogg_codec.h" #include "audio_check.h" #include "uri.h" #include "tag_handler.h" @@ -48,12 +48,11 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "vorbis" -#define OGG_CHUNK_SIZE 4096 #if G_BYTE_ORDER == G_BIG_ENDIAN -#define OGG_DECODE_USE_BIGENDIAN 1 +#define VORBIS_BIG_ENDIAN true #else -#define OGG_DECODE_USE_BIGENDIAN 0 +#define VORBIS_BIG_ENDIAN false #endif struct vorbis_input_stream { @@ -66,9 +65,8 @@ struct vorbis_input_stream { static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) { struct vorbis_input_stream *vis = data; - size_t ret; - - ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); + size_t ret = decoder_read(vis->decoder, vis->input_stream, + ptr, size * nmemb); errno = 0; @@ -155,9 +153,7 @@ static void vorbis_send_comments(struct decoder *decoder, struct input_stream *is, char **comments) { - struct tag *tag; - - tag = vorbis_comments_to_tag(comments); + struct tag *tag = vorbis_comments_to_tag(comments); if (!tag) return; @@ -165,55 +161,79 @@ vorbis_send_comments(struct decoder *decoder, struct input_stream *is, tag_free(tag); } +#ifndef HAVE_TREMOR +static void +vorbis_interleave(float *dest, const float *const*src, + unsigned nframes, unsigned channels) +{ + for (const float *const*src_end = src + channels; + src != src_end; ++src, ++dest) { + float *d = dest; + for (const float *s = *src, *s_end = s + nframes; + s != s_end; ++s, d += channels) + *d = *s; + } +} +#endif + /* public */ static void vorbis_stream_decode(struct decoder *decoder, struct input_stream *input_stream) { GError *error = NULL; - OggVorbis_File vf; - struct vorbis_input_stream vis; - struct audio_format audio_format; - float total_time; - int current_section; - int prev_section = -1; - long ret; - char chunk[OGG_CHUNK_SIZE]; - long bitRate = 0; - long test; - const vorbis_info *vi; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - if (ogg_stream_type_detect(input_stream) != VORBIS) + + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_VORBIS) return; - /* rewind the stream, because ogg_stream_type_detect() has + /* rewind the stream, because ogg_codec_detect() has moved it */ input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + struct vorbis_input_stream vis; + OggVorbis_File vf; if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) return; - vi = ov_info(&vf, -1); + const vorbis_info *vi = ov_info(&vf, -1); if (vi == NULL) { g_warning("ov_info() has failed"); return; } + struct audio_format audio_format; if (!audio_format_init_checked(&audio_format, vi->rate, +#ifdef HAVE_TREMOR SAMPLE_FORMAT_S16, +#else + SAMPLE_FORMAT_FLOAT, +#endif vi->channels, &error)) { g_warning("%s", error->message); g_error_free(error); return; } - total_time = ov_time_total(&vf, -1); + float total_time = ov_time_total(&vf, -1); if (total_time < 0) total_time = 0; decoder_initialized(decoder, &audio_format, vis.seekable, total_time); + enum decoder_command cmd = decoder_get_command(decoder); + +#ifdef HAVE_TREMOR + char buffer[4096]; +#else + float buffer[2048]; + const int frames_per_buffer = + G_N_ELEMENTS(buffer) / audio_format.channels; + const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; +#endif + + int prev_section = -1; + unsigned kbit_rate = 0; + do { if (cmd == DECODE_COMMAND_SEEK) { double seek_where = decoder_seek_where(decoder); @@ -223,17 +243,33 @@ vorbis_stream_decode(struct decoder *decoder, decoder_seek_error(decoder); } - ret = ov_read(&vf, chunk, sizeof(chunk), - OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); - if (ret == OV_HOLE) /* bad packet */ - ret = 0; - else if (ret <= 0) + int current_section; + +#ifdef HAVE_TREMOR + long nbytes = ov_read(&vf, buffer, sizeof(buffer), + VORBIS_BIG_ENDIAN, 2, 1, + ¤t_section); +#else + float **per_channel; + long nframes = ov_read_float(&vf, &per_channel, + frames_per_buffer, + ¤t_section); + long nbytes = nframes; + if (nframes > 0) { + vorbis_interleave(buffer, + (const float*const*)per_channel, + nframes, audio_format.channels); + nbytes *= frame_size; + } +#endif + + if (nbytes == OV_HOLE) /* bad packet */ + nbytes = 0; + else if (nbytes <= 0) /* break on EOF or other error */ break; if (current_section != prev_section) { - char **comments; - vi = ov_info(&vf, -1); if (vi == NULL) { g_warning("ov_info() has failed"); @@ -248,7 +284,7 @@ vorbis_stream_decode(struct decoder *decoder, break; } - comments = ov_comment(&vf, -1)->user_comments; + char **comments = ov_comment(&vf, -1)->user_comments; vorbis_send_comments(decoder, input_stream, comments); struct replay_gain_info rgi; @@ -258,12 +294,13 @@ vorbis_stream_decode(struct decoder *decoder, prev_section = current_section; } - if ((test = ov_bitrate_instant(&vf)) > 0) - bitRate = test / 1000; + long test = ov_bitrate_instant(&vf); + if (test > 0) + kbit_rate = test / 1000; cmd = decoder_data(decoder, input_stream, - chunk, ret, - bitRate); + buffer, nbytes, + kbit_rate); } while (cmd != DECODE_COMMAND_STOP); ov_clear(&vf); diff --git a/src/decoder_control.c b/src/decoder_control.c index 2ce03b666..33d4e4d44 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_list.c b/src/decoder_list.c index 177b632ad..575346c98 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -26,6 +26,9 @@ #include "decoder/pcm_decoder_plugin.h" #include "decoder/dsdiff_decoder_plugin.h" #include "decoder/dsf_decoder_plugin.h" +#include "decoder/FLACDecoderPlugin.h" +#include "decoder/OpusDecoderPlugin.h" +#include "decoder/AdPlugDecoderPlugin.h" #include <glib.h> @@ -34,8 +37,6 @@ extern const struct decoder_plugin mad_decoder_plugin; extern const struct decoder_plugin mpg123_decoder_plugin; extern const struct decoder_plugin vorbis_decoder_plugin; -extern const struct decoder_plugin flac_decoder_plugin; -extern const struct decoder_plugin oggflac_decoder_plugin; extern const struct decoder_plugin sndfile_decoder_plugin; extern const struct decoder_plugin audiofile_decoder_plugin; extern const struct decoder_plugin mp4ff_decoder_plugin; @@ -66,6 +67,9 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_FLAC &flac_decoder_plugin, #endif +#ifdef HAVE_OPUS + &opus_decoder_plugin, +#endif #ifdef ENABLE_SNDFILE &sndfile_decoder_plugin, #endif @@ -101,6 +105,9 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef ENABLE_FLUIDSYNTH &fluidsynth_decoder_plugin, #endif +#ifdef HAVE_ADPLUG + &adplug_decoder_plugin, +#endif #ifdef HAVE_FFMPEG &ffmpeg_decoder_plugin, #endif diff --git a/src/decoder_thread.c b/src/decoder_thread.c index af80ed45b..b13f2a46a 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..607e812cd 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> @@ -51,6 +56,10 @@ struct song; struct db_visitor; +#ifdef __cplusplus +class SongFilter; +#endif + struct directory { /** * Pointers to the siblings of this directory within the @@ -86,8 +95,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 SongFilter *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 +118,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 +174,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 +193,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 +248,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 +272,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/OggStream.hxx b/src/encoder/OggStream.hxx new file mode 100644 index 000000000..ce847f491 --- /dev/null +++ b/src/encoder/OggStream.hxx @@ -0,0 +1,128 @@ +/* + * 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_OGG_STREAM_HXX +#define MPD_OGG_STREAM_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <assert.h> +#include <string.h> +#include <stdint.h> + +class OggStream { + ogg_stream_state state; + + bool flush; + +#ifndef NDEBUG + bool initialized; +#endif + +public: +#ifndef NDEBUG + OggStream():initialized(false) {} + ~OggStream() { + assert(!initialized); + } +#endif + + void Initialize(int serialno) { + assert(!initialized); + + ogg_stream_init(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + +#ifndef NDEBUG + initialized = true; +#endif + } + + void Reinitialize(int serialno) { + assert(initialized); + + ogg_stream_reset_serialno(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + } + + void Deinitialize() { + assert(initialized); + + ogg_stream_clear(&state); + +#ifndef NDEBUG + initialized = false; +#endif + } + + void Flush() { + assert(initialized); + + flush = true; + } + + void PacketIn(const ogg_packet &packet) { + assert(initialized); + + ogg_stream_packetin(&state, + const_cast<ogg_packet *>(&packet)); + } + + bool PageOut(ogg_page &page) { + int result = ogg_stream_pageout(&state, &page); + if (result == 0 && flush) { + flush = false; + result = ogg_stream_flush(&state, &page); + } + + return result != 0; + } + + size_t PageOut(void *_buffer, size_t size) { + ogg_page page; + if (!PageOut(page)) + return 0; + + assert(page.header_len > 0 || page.body_len > 0); + + size_t header_len = (size_t)page.header_len; + size_t body_len = (size_t)page.body_len; + assert(header_len <= size); + + if (header_len + body_len > size) + /* TODO: better overflow handling */ + body_len = size - header_len; + + uint8_t *buffer = (uint8_t *)_buffer; + memcpy(buffer, page.header, header_len); + memcpy(buffer + header_len, page.body, body_len); + + return header_len + body_len; + } +}; + +#endif diff --git a/src/encoder/OpusEncoderPlugin.cxx b/src/encoder/OpusEncoderPlugin.cxx new file mode 100644 index 000000000..9f46e8681 --- /dev/null +++ b/src/encoder/OpusEncoderPlugin.cxx @@ -0,0 +1,432 @@ +/* + * 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 "OpusEncoderPlugin.hxx" +#include "OggStream.hxx" + +extern "C" { +#include "encoder_api.h" +} + +#include "encoder_plugin.h" +#include "audio_format.h" +#include "mpd_error.h" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "opus_encoder" + +struct opus_encoder { + /** the base class */ + struct encoder encoder; + + /* configuration */ + + opus_int32 bitrate; + int complexity; + int signal; + + /* runtime information */ + + struct audio_format audio_format; + + size_t frame_size; + + size_t buffer_frames, buffer_size, buffer_position; + uint8_t *buffer; + + OpusEncoder *enc; + + unsigned char buffer2[1275 * 3 + 7]; + + OggStream stream; + + int lookahead; + + ogg_int64_t packetno; + + ogg_int64_t granulepos; +}; + +gcc_const +static inline GQuark +opus_encoder_quark(void) +{ + return g_quark_from_static_string("opus_encoder"); +} + +static bool +opus_encoder_configure(struct opus_encoder *encoder, + const struct config_param *param, GError **error_r) +{ + const char *value = config_get_block_string(param, "bitrate", "auto"); + if (strcmp(value, "auto") == 0) + encoder->bitrate = OPUS_AUTO; + else if (strcmp(value, "max") == 0) + encoder->bitrate = OPUS_BITRATE_MAX; + else { + char *endptr; + encoder->bitrate = strtoul(value, &endptr, 10); + if (endptr == value || *endptr != 0 || + encoder->bitrate < 500 || encoder->bitrate > 512000) { + g_set_error(error_r, opus_encoder_quark(), 0, + "Invalid bit rate"); + return false; + } + } + + encoder->complexity = config_get_block_unsigned(param, "complexity", + 10); + if (encoder->complexity > 10) { + g_set_error(error_r, opus_encoder_quark(), 0, + "Invalid complexity"); + return false; + } + + value = config_get_block_string(param, "signal", "auto"); + if (strcmp(value, "auto") == 0) + encoder->bitrate = OPUS_AUTO; + else if (strcmp(value, "voice") == 0) + encoder->bitrate = OPUS_SIGNAL_VOICE; + else if (strcmp(value, "music") == 0) + encoder->bitrate = OPUS_SIGNAL_MUSIC; + else { + g_set_error(error_r, opus_encoder_quark(), 0, + "Invalid signal"); + return false; + } + + return true; +} + +static struct encoder * +opus_encoder_init(const struct config_param *param, GError **error) +{ + struct opus_encoder *encoder; + + encoder = g_new(struct opus_encoder, 1); + encoder_struct_init(&encoder->encoder, &opus_encoder_plugin); + + /* load configuration from "param" */ + if (!opus_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + g_free(encoder); + return NULL; + } + + return &encoder->encoder; +} + +static void +opus_encoder_finish(struct encoder *_encoder) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + /* the real libopus cleanup was already performed by + opus_encoder_close(), so no real work here */ + g_free(encoder); +} + +static bool +opus_encoder_open(struct encoder *_encoder, + struct audio_format *audio_format, + GError **error_r) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + /* libopus supports only 48 kHz */ + audio_format->sample_rate = 48000; + + if (audio_format->channels > 2) + audio_format->channels = 1; + + switch ((enum sample_format)audio_format->format) { + case SAMPLE_FORMAT_S16: + case SAMPLE_FORMAT_FLOAT: + break; + + case SAMPLE_FORMAT_S8: + audio_format->format = SAMPLE_FORMAT_S16; + break; + + default: + audio_format->format = SAMPLE_FORMAT_FLOAT; + break; + } + + encoder->audio_format = *audio_format; + encoder->frame_size = audio_format_frame_size(audio_format); + + int error; + encoder->enc = opus_encoder_create(audio_format->sample_rate, + audio_format->channels, + OPUS_APPLICATION_AUDIO, + &error); + if (encoder->enc == nullptr) { + g_set_error_literal(error_r, opus_encoder_quark(), error, + opus_strerror(error)); + return false; + } + + opus_encoder_ctl(encoder->enc, OPUS_SET_BITRATE(encoder->bitrate)); + opus_encoder_ctl(encoder->enc, + OPUS_SET_COMPLEXITY(encoder->complexity)); + opus_encoder_ctl(encoder->enc, OPUS_SET_SIGNAL(encoder->signal)); + + opus_encoder_ctl(encoder->enc, OPUS_GET_LOOKAHEAD(&encoder->lookahead)); + + encoder->buffer_frames = audio_format->sample_rate / 50; + encoder->buffer_size = encoder->frame_size * encoder->buffer_frames; + encoder->buffer_position = 0; + encoder->buffer = (unsigned char *)g_malloc(encoder->buffer_size); + + encoder->stream.Initialize(g_random_int()); + encoder->packetno = 0; + + return true; +} + +static void +opus_encoder_close(struct encoder *_encoder) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Deinitialize(); + g_free(encoder->buffer); + opus_encoder_destroy(encoder->enc); +} + +static bool +opus_encoder_do_encode(struct opus_encoder *encoder, bool eos, + GError **error_r) +{ + assert(encoder->buffer_position == encoder->buffer_size); + + opus_int32 result = + encoder->audio_format.format == SAMPLE_FORMAT_S16 + ? opus_encode(encoder->enc, + (const opus_int16 *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)) + : opus_encode_float(encoder->enc, + (const float *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)); + if (result < 0) { + g_set_error_literal(error_r, opus_encoder_quark(), 0, + "Opus encoder error"); + return false; + } + + encoder->granulepos += encoder->buffer_frames; + + ogg_packet packet; + packet.packet = encoder->buffer2; + packet.bytes = result; + packet.b_o_s = false; + packet.e_o_s = eos; + packet.granulepos = encoder->granulepos; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + + encoder->buffer_position = 0; + + return true; +} + +static bool +opus_encoder_end(struct encoder *_encoder, GError **error_r) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + + memset(encoder->buffer + encoder->buffer_position, 0, + encoder->buffer_size - encoder->buffer_position); + encoder->buffer_position = encoder->buffer_size; + + return opus_encoder_do_encode(encoder, true, error_r); +} + +static bool +opus_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + return true; +} + +static bool +opus_encoder_write_silence(struct opus_encoder *encoder, unsigned fill_frames, + GError **error_r) +{ + size_t fill_bytes = fill_frames * encoder->frame_size; + + while (fill_bytes > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > fill_bytes) + nbytes = fill_bytes; + + memset(encoder->buffer + encoder->buffer_position, + 0, nbytes); + encoder->buffer_position += nbytes; + fill_bytes -= nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error_r)) + return false; + } + + return true; +} + +static bool +opus_encoder_write(struct encoder *_encoder, + const void *_data, size_t length, + GError **error_r) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + const uint8_t *data = (const uint8_t *)_data; + + if (encoder->lookahead > 0) { + /* generate some silence at the beginning of the + stream */ + + assert(encoder->buffer_position == 0); + + if (!opus_encoder_write_silence(encoder, encoder->lookahead, + error_r)) + return false; + + encoder->lookahead = 0; + } + + while (length > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > length) + nbytes = length; + + memcpy(encoder->buffer + encoder->buffer_position, + data, nbytes); + data += nbytes; + length -= nbytes; + encoder->buffer_position += nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error_r)) + return false; + } + + return true; +} + +static void +opus_encoder_generate_head(struct opus_encoder *encoder) +{ + unsigned char header[19]; + memcpy(header, "OpusHead", 8); + header[8] = 1; + header[9] = encoder->audio_format.channels; + *(uint16_t *)(header + 10) = GUINT16_TO_LE(encoder->lookahead); + *(uint32_t *)(header + 12) = + GUINT32_TO_LE(encoder->audio_format.sample_rate); + header[16] = 0; + header[17] = 0; + header[18] = 0; + + ogg_packet packet; + packet.packet = header; + packet.bytes = 19; + packet.b_o_s = true; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); +} + +static void +opus_encoder_generate_tags(struct opus_encoder *encoder) +{ + const char *version = opus_get_version_string(); + size_t version_length = strlen(version); + + size_t comments_size = 8 + 4 + version_length + 4; + unsigned char *comments = (unsigned char *)g_malloc(comments_size); + memcpy(comments, "OpusTags", 8); + *(uint32_t *)(comments + 8) = GUINT32_TO_LE(version_length); + memcpy(comments + 12, version, version_length); + *(uint32_t *)(comments + 12 + version_length) = GUINT32_TO_LE(0); + + ogg_packet packet; + packet.packet = comments; + packet.bytes = comments_size; + packet.b_o_s = false; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); + + g_free(comments); +} + +static size_t +opus_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + if (encoder->packetno == 0) + opus_encoder_generate_head(encoder); + else if (encoder->packetno == 1) + opus_encoder_generate_tags(encoder); + + return encoder->stream.PageOut(dest, length); +} + +static const char * +opus_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/ogg"; +} + +const struct encoder_plugin opus_encoder_plugin = { + "opus", + opus_encoder_init, + opus_encoder_finish, + opus_encoder_open, + opus_encoder_close, + opus_encoder_end, + opus_encoder_flush, + nullptr, + nullptr, + opus_encoder_write, + opus_encoder_read, + opus_encoder_get_mime_type, +}; diff --git a/src/encoder/OpusEncoderPlugin.hxx b/src/encoder/OpusEncoderPlugin.hxx new file mode 100644 index 000000000..f54377202 --- /dev/null +++ b/src/encoder/OpusEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * 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_ENCODER_OPUS_H +#define MPD_ENCODER_OPUS_H + +extern const struct encoder_plugin opus_encoder_plugin; + +#endif diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/VorbisEncoderPlugin.cxx index 468cf38ee..bc0f47fd0 100644 --- a/src/encoder/vorbis_encoder.c +++ b/src/encoder/VorbisEncoderPlugin.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,7 +18,13 @@ */ #include "config.h" +#include "VorbisEncoderPlugin.hxx" +#include "OggStream.hxx" + +extern "C" { #include "encoder_api.h" +} + #include "encoder_plugin.h" #include "tag.h" #include "audio_format.h" @@ -44,17 +50,13 @@ struct vorbis_encoder { struct audio_format audio_format; - ogg_stream_state os; - vorbis_dsp_state vd; vorbis_block vb; vorbis_info vi; - bool flush; + OggStream stream; }; -extern const struct encoder_plugin vorbis_encoder_plugin; - static inline GQuark vorbis_encoder_quark(void) { @@ -65,8 +67,8 @@ static bool vorbis_encoder_configure(struct vorbis_encoder *encoder, const struct config_param *param, GError **error) { - const char *value = config_get_block_string(param, "quality", NULL); - if (value != NULL) { + const char *value = config_get_block_string(param, "quality", nullptr); + if (value != nullptr) { /* a quality was configured (VBR) */ char *endptr; @@ -81,7 +83,7 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, return false; } - if (config_get_block_string(param, "bitrate", NULL) != NULL) { + if (config_get_block_string(param, "bitrate", nullptr) != nullptr) { g_set_error(error, vorbis_encoder_quark(), 0, "quality and bitrate are " "both defined (line %i)", @@ -91,8 +93,8 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, } else { /* a bit rate was configured */ - value = config_get_block_string(param, "bitrate", NULL); - if (value == NULL) { + value = config_get_block_string(param, "bitrate", nullptr); + if (value == nullptr) { g_set_error(error, vorbis_encoder_quark(), 0, "neither bitrate nor quality defined " "at line %i", @@ -125,7 +127,7 @@ vorbis_encoder_init(const struct config_param *param, GError **error) if (!vorbis_encoder_configure(encoder, param, error)) { /* configuration has failed, roll back and return error */ g_free(encoder); - return NULL; + return nullptr; } return &encoder->encoder; @@ -174,7 +176,7 @@ vorbis_encoder_reinit(struct vorbis_encoder *encoder, GError **error) vorbis_analysis_init(&encoder->vd, &encoder->vi); vorbis_block_init(&encoder->vd, &encoder->vb); - ogg_stream_init(&encoder->os, g_random_int()); + encoder->stream.Initialize(g_random_int()); return true; } @@ -187,9 +189,9 @@ vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) vorbis_analysis_headerout(&encoder->vd, vc, &packet, &comments, &codebooks); - ogg_stream_packetin(&encoder->os, &packet); - ogg_stream_packetin(&encoder->os, &comments); - ogg_stream_packetin(&encoder->os, &codebooks); + encoder->stream.PacketIn(packet); + encoder->stream.PacketIn(comments); + encoder->stream.PacketIn(codebooks); } static void @@ -209,7 +211,7 @@ vorbis_encoder_open(struct encoder *_encoder, { struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - audio_format->format = SAMPLE_FORMAT_S16; + audio_format->format = SAMPLE_FORMAT_FLOAT; encoder->audio_format = *audio_format; @@ -218,17 +220,13 @@ vorbis_encoder_open(struct encoder *_encoder, vorbis_encoder_send_header(encoder); - /* set "flush" to true, so the caller gets the full headers on - the first read() */ - encoder->flush = true; - return true; } static void vorbis_encoder_clear(struct vorbis_encoder *encoder) { - ogg_stream_clear(&encoder->os); + encoder->stream.Deinitialize(); vorbis_block_clear(&encoder->vb); vorbis_dsp_clear(&encoder->vd); vorbis_info_clear(&encoder->vi); @@ -246,12 +244,12 @@ static void vorbis_encoder_blockout(struct vorbis_encoder *encoder) { while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { - vorbis_analysis(&encoder->vb, NULL); + vorbis_analysis(&encoder->vb, nullptr); vorbis_bitrate_addblock(&encoder->vb); ogg_packet packet; while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) - ogg_stream_packetin(&encoder->os, &packet); + encoder->stream.PacketIn(packet); } } @@ -260,7 +258,7 @@ vorbis_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) { struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - encoder->flush = true; + encoder->stream.Flush(); return true; } @@ -279,7 +277,7 @@ vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error) vorbis_analysis_init(&encoder->vd, &encoder->vi); vorbis_block_init(&encoder->vd, &encoder->vb); - encoder->flush = true; + encoder->stream.Flush(); return true; } @@ -308,28 +306,23 @@ vorbis_encoder_tag(struct encoder *_encoder, const struct tag *tag, /* reset ogg_stream_state and begin a new stream */ - ogg_stream_reset_serialno(&encoder->os, g_random_int()); + encoder->stream.Reinitialize(g_random_int()); /* send that vorbis_comment to the ogg_stream_state */ vorbis_encoder_headerout(encoder, &comment); vorbis_comment_clear(&comment); - /* the next vorbis_encoder_read() call should flush the - ogg_stream_state */ - - encoder->flush = true; - return true; } static void -pcm16_to_vorbis_buffer(float **dest, const int16_t *src, - unsigned num_frames, unsigned num_channels) +interleaved_to_vorbis_buffer(float **dest, const float *src, + unsigned num_frames, unsigned num_channels) { for (unsigned i = 0; i < num_frames; i++) for (unsigned j = 0; j < num_channels; j++) - dest[j][i] = *src++ / 32768.0; + dest[j][i] = *src++; } static bool @@ -344,10 +337,11 @@ vorbis_encoder_write(struct encoder *_encoder, /* this is for only 16-bit audio */ - pcm16_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, - num_frames), - (const int16_t *)data, - num_frames, encoder->audio_format.channels); + interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, + num_frames), + (const float *)data, + num_frames, + encoder->audio_format.channels); vorbis_analysis_wrote(&encoder->vd, num_frames); vorbis_encoder_blockout(encoder); @@ -355,34 +349,11 @@ vorbis_encoder_write(struct encoder *_encoder, } static size_t -vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length) +vorbis_encoder_read(struct encoder *_encoder, void *dest, size_t length) { struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - unsigned char *dest = _dest; - - ogg_page page; - int ret = ogg_stream_pageout(&encoder->os, &page); - if (ret == 0 && encoder->flush) { - encoder->flush = false; - ret = ogg_stream_flush(&encoder->os, &page); - - } - - if (ret == 0) - return 0; - - assert(page.header_len > 0 || page.body_len > 0); - - size_t nbytes = (size_t)page.header_len + (size_t)page.body_len; - - if (nbytes > length) - /* XXX better error handling */ - MPD_ERROR("buffer too small"); - - memcpy(dest, page.header, page.header_len); - memcpy(dest + page.header_len, page.body, page.body_len); - return nbytes; + return encoder->stream.PageOut(dest, length); } static const char * @@ -392,16 +363,16 @@ vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) } const struct encoder_plugin vorbis_encoder_plugin = { - .name = "vorbis", - .init = vorbis_encoder_init, - .finish = vorbis_encoder_finish, - .open = vorbis_encoder_open, - .close = vorbis_encoder_close, - .end = vorbis_encoder_pre_tag, - .flush = vorbis_encoder_flush, - .pre_tag = vorbis_encoder_pre_tag, - .tag = vorbis_encoder_tag, - .write = vorbis_encoder_write, - .read = vorbis_encoder_read, - .get_mime_type = vorbis_encoder_get_mime_type, + "vorbis", + vorbis_encoder_init, + vorbis_encoder_finish, + vorbis_encoder_open, + vorbis_encoder_close, + vorbis_encoder_pre_tag, + vorbis_encoder_flush, + vorbis_encoder_pre_tag, + vorbis_encoder_tag, + vorbis_encoder_write, + vorbis_encoder_read, + vorbis_encoder_get_mime_type, }; diff --git a/src/encoder/VorbisEncoderPlugin.hxx b/src/encoder/VorbisEncoderPlugin.hxx new file mode 100644 index 000000000..4cddf1b11 --- /dev/null +++ b/src/encoder/VorbisEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * 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_ENCODER_VORBIS_H +#define MPD_ENCODER_VORBIS_H + +extern const struct encoder_plugin vorbis_encoder_plugin; + +#endif diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c index e32588e29..6b09bcd66 100644 --- a/src/encoder/flac_encoder.c +++ b/src/encoder/flac_encoder.c @@ -30,6 +30,10 @@ #include <FLAC/stream_encoder.h> +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#endif + struct flac_encoder { struct encoder encoder; @@ -98,8 +102,6 @@ static bool flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, GError **error) { -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -#else if ( !FLAC__stream_encoder_set_compression_level(encoder->fse, encoder->compression)) { g_set_error(error, flac_encoder_quark(), 0, @@ -107,7 +109,7 @@ flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, encoder->compression); return false; } -#endif + if ( !FLAC__stream_encoder_set_channels(encoder->fse, encoder->audio_format.channels)) { g_set_error(error, flac_encoder_quark(), 0, @@ -135,11 +137,7 @@ flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, static FLAC__StreamEncoderWriteStatus flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse, const FLAC__byte data[], -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 - unsigned bytes, -#else size_t bytes, -#endif G_GNUC_UNUSED unsigned samples, G_GNUC_UNUSED unsigned current_frame, void *client_data) { @@ -209,24 +207,6 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, /* this immediately outputs data through callback */ -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 - { - FLAC__StreamEncoderState init_status; - - FLAC__stream_encoder_set_write_callback(encoder->fse, - flac_write_callback); - - init_status = FLAC__stream_encoder_init(encoder->fse); - - if (init_status != FLAC__STREAM_ENCODER_OK) { - g_set_error(error, flac_encoder_quark(), 0, - "failed to initialize encoder: %s\n", - FLAC__StreamEncoderStateString[init_status]); - flac_encoder_close(_encoder); - return false; - } - } -#else { FLAC__StreamEncoderInitStatus init_status; @@ -242,7 +222,6 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, return false; } } -#endif return true; } diff --git a/src/encoder_list.c b/src/encoder_list.c index 2326c1099..029b4be34 100644 --- a/src/encoder_list.c +++ b/src/encoder_list.c @@ -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 @@ -20,11 +20,12 @@ #include "config.h" #include "encoder_list.h" #include "encoder_plugin.h" +#include "encoder/VorbisEncoderPlugin.hxx" +#include "encoder/OpusEncoderPlugin.hxx" #include <string.h> extern const struct encoder_plugin null_encoder_plugin; -extern const struct encoder_plugin vorbis_encoder_plugin; extern const struct encoder_plugin lame_encoder_plugin; extern const struct encoder_plugin twolame_encoder_plugin; extern const struct encoder_plugin wave_encoder_plugin; @@ -35,6 +36,9 @@ const struct encoder_plugin *const encoder_plugins[] = { #ifdef ENABLE_VORBIS_ENCODER &vorbis_encoder_plugin, #endif +#ifdef HAVE_OPUS + &opus_encoder_plugin, +#endif #ifdef ENABLE_LAME_ENCODER &lame_encoder_plugin, #endif diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h index 3a42d79f4..e0748a136 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/fd_util.h b/src/fd_util.h index dd4df7a13..c8a17c7ef 100644 --- a/src/fd_util.h +++ b/src/fd_util.h @@ -51,6 +51,10 @@ struct sockaddr; +#ifdef __cplusplus +extern "C" { +#endif + /** * Wrapper for dup(), which sets the CLOEXEC flag on the new * descriptor. @@ -146,4 +150,8 @@ inotify_init_cloexec(void); int close_socket(int fd); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif 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,6) +#error Your gcc version is too old. MPD requires gcc 4.6 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.c b/src/listen.c index 90e13b9c1..28709a0e1 100644 --- a/src/listen.c +++ b/src/listen.c @@ -22,7 +22,7 @@ #include "server_socket.h" #include "client.h" #include "conf.h" -#include "main.h" +#include "Main.hxx" #include <string.h> #include <assert.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 deleted file mode 100644 index c9684d2b6..000000000 --- a/src/locate.c +++ /dev/null @@ -1,239 +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 "locate.h" -#include "path.h" -#include "tag.h" -#include "song.h" - -#include <glib.h> - -#include <stdlib.h> - -#define LOCATE_TAG_FILE_KEY "file" -#define LOCATE_TAG_FILE_KEY_OLD "filename" -#define LOCATE_TAG_ANY_KEY "any" - -int -locate_parse_type(const char *str) -{ - if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) || - 0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD)) - return LOCATE_TAG_FILE_TYPE; - - 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; -} - -static bool -locate_item_init(struct locate_item *item, - const char *type_string, const char *needle) -{ - item->tag = locate_parse_type(type_string); - - if (item->tag < 0) - return false; - - item->needle = g_strdup(needle); - - return true; -} - -void -locate_item_list_free(struct locate_item_list *list) -{ - for (unsigned i = 0; i < list->length; ++i) - g_free(list->items[i].needle); - - g_free(list); -} - -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])); - list->length = length; - - return list; -} - -struct locate_item_list * -locate_item_list_parse(char *argv[], int argc) -{ - if (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])) { - locate_item_list_free(list); - return NULL; - } - } - - return list; -} - -struct locate_item_list * -locate_item_list_casefold(const struct locate_item_list *list) -{ - struct locate_item_list *new_list = locate_item_list_new(list->length); - - 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; - } - - return new_list; -} - -void -locate_item_free(struct locate_item *item) -{ - g_free(item->needle); - g_free(item); -} - -static bool -locate_tag_search(const struct song *song, enum tag_type type, const char *str) -{ - bool ret = false; - - if (type == LOCATE_TAG_FILE_TYPE || (int)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; - - 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 ((int)type != LOCATE_TAG_ANY_TYPE && - song->tag->items[i]->type != type) { - continue; - } - - char *duplicate = g_utf8_casefold(song->tag->items[i]->value, -1); - if (*str && strstr(duplicate, str)) - ret = true; - g_free(duplicate); - } - - /** 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 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; -} - -static bool -locate_tag_match(const struct song *song, enum tag_type type, const char *str) -{ - if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) { - char *uri = song_get_uri(song); - bool matches = strcmp(str, uri) == 0; - g_free(uri); - - if (matches) - return true; - - if (type == LOCATE_TAG_FILE_TYPE) - return false; - } - - 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 ((int)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; -} - -bool -locate_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)) - return false; - - return true; -} diff --git a/src/locate.h b/src/locate.h deleted file mode 100644 index ec20ded24..000000000 --- a/src/locate.h +++ /dev/null @@ -1,92 +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. - */ - -#ifndef MPD_LOCATE_H -#define MPD_LOCATE_H - -#include "gcc.h" - -#include <stdint.h> -#include <stdbool.h> - -#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 -#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 - -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. - */ -struct locate_item_list { - /** number of items */ - unsigned length; - - /** this is a variable length array */ - struct locate_item items[1]; -}; - -int -locate_parse_type(const char *str); - -/** - * Allocates a new struct locate_item_list, and initializes all - * members with zero bytes. - */ -struct locate_item_list * -locate_item_list_new(unsigned length); - -/* 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); - -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_nonnull(1,2) -bool -locate_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/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/shout_output_plugin.c b/src/output/shout_output_plugin.c index 56456a0ea..8f9df2ccf 100644 --- a/src/output/shout_output_plugin.c +++ b/src/output/shout_output_plugin.c @@ -105,15 +105,10 @@ static void free_shout_data(struct shout_data *sd) } \ } -static struct audio_output * -my_shout_init_driver(const struct config_param *param, - GError **error) +static bool +my_shout_configure(struct shout_data *sd, const struct config_param *param, + GError **error) { - struct shout_data *sd = new_shout_data(); - if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { - free_shout_data(sd); - return NULL; - } const struct audio_format *audio_format = &sd->base.config_audio_format; @@ -125,11 +120,6 @@ my_shout_init_driver(const struct config_param *param, return NULL; } - if (shout_init_count == 0) - shout_init(); - - shout_init_count++; - const struct block_param *block_param; check_block_param("host"); char *host = block_param->value; @@ -141,7 +131,7 @@ my_shout_init_driver(const struct config_param *param, if (port == 0) { g_set_error(error, shout_output_quark(), 0, "shout port must be configured"); - goto failure; + return false; } check_block_param("password"); @@ -164,21 +154,21 @@ my_shout_init_driver(const struct config_param *param, "shout quality \"%s\" is not a number in the " "range -1 to 10, line %i", value, param->line); - goto failure; + return false; } if (config_get_block_string(param, "bitrate", NULL) != NULL) { g_set_error(error, shout_output_quark(), 0, "quality and bitrate are " "both defined"); - goto failure; + return false; } } else { value = config_get_block_string(param, "bitrate", NULL); if (value == NULL) { g_set_error(error, shout_output_quark(), 0, "neither bitrate nor quality defined"); - goto failure; + return false; } char *test; @@ -187,7 +177,7 @@ my_shout_init_driver(const struct config_param *param, if (*test != '\0' || sd->bitrate <= 0) { g_set_error(error, shout_output_quark(), 0, "bitrate must be a positive integer"); - goto failure; + return false; } } @@ -199,12 +189,12 @@ my_shout_init_driver(const struct config_param *param, g_set_error(error, shout_output_quark(), 0, "couldn't find shout encoder plugin \"%s\"", encoding); - goto failure; + return false; } sd->encoder = encoder_init(encoder_plugin, param, error); if (sd->encoder == NULL) - goto failure; + return false; unsigned shout_format; if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) @@ -220,7 +210,7 @@ my_shout_init_driver(const struct config_param *param, g_set_error(error, shout_output_quark(), 0, "you cannot stream \"%s\" to shoutcast, use mp3", encoding); - goto failure; + return false; } else if (0 == strcmp(value, "shoutcast")) protocol = SHOUT_PROTOCOL_ICY; else if (0 == strcmp(value, "icecast1")) @@ -232,7 +222,7 @@ my_shout_init_driver(const struct config_param *param, "shout protocol \"%s\" is not \"shoutcast\" or " "\"icecast1\"or \"icecast2\"", value); - goto failure; + return false; } } else { protocol = SHOUT_PROTOCOL_HTTP; @@ -251,7 +241,7 @@ my_shout_init_driver(const struct config_param *param, shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } /* optional paramters */ @@ -262,21 +252,21 @@ my_shout_init_driver(const struct config_param *param, if (value != NULL && shout_set_genre(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } value = config_get_block_string(param, "description", NULL); if (value != NULL && shout_set_description(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } value = config_get_block_string(param, "url", NULL); if (value != NULL && shout_set_url(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } { @@ -301,12 +291,31 @@ my_shout_init_driver(const struct config_param *param, } } - return &sd->base; + return true; +} -failure: - ao_base_finish(&sd->base); - free_shout_data(sd); - return NULL; +static struct audio_output * +my_shout_init_driver(const struct config_param *param, + GError **error) +{ + struct shout_data *sd = new_shout_data(); + if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { + free_shout_data(sd); + return NULL; + } + + if (!my_shout_configure(sd, param, error)) { + ao_base_finish(&sd->base); + free_shout_data(sd); + return NULL; + } + + if (shout_init_count == 0) + shout_init(); + + shout_init_count++; + + return &sd->base; } static bool 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_init.c b/src/output_init.c index c3b808e94..a6d191920 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -165,6 +165,7 @@ ao_base_init(struct audio_output *ao, } ao->plugin = plugin; + ao->tags = config_get_block_bool(param, "tags", true); ao->always_on = config_get_block_bool(param, "always_on", false); ao->enabled = config_get_block_bool(param, "enabled", true); ao->really_enabled = false; diff --git a/src/output_internal.h b/src/output_internal.h index 9d975d789..1a3e8aa1f 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -73,6 +73,13 @@ struct audio_output { struct mixer *mixer; /** + * Will this output receive tags from the decoder? The + * default is true, but it may be configured to false to + * suppress sending tags to the output. + */ + bool tags; + + /** * Shall this output always play something (i.e. silence), * even when playback is stopped? */ 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/output_thread.c b/src/output_thread.c index 4eef2ccdd..cd1a8a878 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -435,7 +435,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) assert(ao != NULL); assert(ao->filter != NULL); - if (chunk->tag != NULL) { + if (ao->tags && gcc_unlikely(chunk->tag != NULL)) { g_mutex_unlock(ao->mutex); ao_plugin_send_tag(ao, chunk->tag); g_mutex_lock(ao->mutex); diff --git a/src/pcm_channels.c b/src/pcm_channels.c index ec2bd69a5..9d166a437 100644 --- a/src/pcm_channels.c +++ b/src/pcm_channels.c @@ -244,3 +244,74 @@ pcm_convert_channels_32(struct pcm_buffer *buffer, return dest; } + +static void +pcm_convert_channels_float_1_to_2(float *dest, const float *src, + const float *src_end) +{ + pcm_convert_channels_24_1_to_2((int32_t *)dest, + (const int32_t *)src, + (const int32_t *)src_end); +} + +static void +pcm_convert_channels_float_2_to_1(float *restrict dest, + const float *restrict src, + const float *restrict src_end) +{ + while (src < src_end) { + double a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_float_n_to_2(float *dest, + unsigned src_channels, const float *src, + const float *src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + double sum = 0; + float value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (double)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const float * +pcm_convert_channels_float(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const float *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % (sizeof(*src) * src_channels) == 0); + + size_t dest_size = src_size / src_channels * dest_channels; + *dest_size_r = dest_size; + + float *dest = pcm_buffer_get(buffer, dest_size); + const float *src_end = pcm_end_pointer(src, src_size); + + if (src_channels == 1 && dest_channels == 2) + pcm_convert_channels_float_1_to_2(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_float_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_float_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} diff --git a/src/pcm_channels.h b/src/pcm_channels.h index 1e4a0991f..6da00316d 100644 --- a/src/pcm_channels.h +++ b/src/pcm_channels.h @@ -77,4 +77,21 @@ pcm_convert_channels_32(struct pcm_buffer *buffer, unsigned src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r); +/** + * Changes the number of channels in 32 bit float PCM data. + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const float * +pcm_convert_channels_float(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const float *src, + size_t src_size, size_t *dest_size_r); + #endif diff --git a/src/pcm_convert.c b/src/pcm_convert.c index 63f9a1b98..32425143a 100644 --- a/src/pcm_convert.c +++ b/src/pcm_convert.c @@ -61,55 +61,6 @@ pcm_convert_reset(struct pcm_convert_state *state) pcm_resample_reset(&state->resample); } -static const void * -pcm_convert_channels(struct pcm_buffer *buffer, enum sample_format format, - uint8_t dest_channels, - uint8_t src_channels, const void *src, - size_t src_size, size_t *dest_size_r, - GError **error_r) -{ - const void *dest = NULL; - - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_S8: - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: - g_set_error(error_r, pcm_convert_quark(), 0, - "Channel conversion not implemented for format '%s'", - sample_format_to_string(format)); - return NULL; - - case SAMPLE_FORMAT_S16: - dest = pcm_convert_channels_16(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - - case SAMPLE_FORMAT_S24_P32: - dest = pcm_convert_channels_24(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - - case SAMPLE_FORMAT_S32: - dest = pcm_convert_channels_32(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - } - - if (dest == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_channels, dest_channels); - return NULL; - } - - return dest; -} - static const int16_t * pcm_convert_16(struct pcm_convert_state *state, const struct audio_format *src_format, @@ -273,19 +224,6 @@ pcm_convert_float(struct pcm_convert_state *state, assert(dest_format->format == SAMPLE_FORMAT_FLOAT); - /* convert channels first, hoping the source format is - supported (float is not) */ - - if (dest_format->channels != src_format->channels) { - buffer = pcm_convert_channels(&state->channels_buffer, - src_format->format, - dest_format->channels, - src_format->channels, - buffer, size, &size, error_r); - if (buffer == NULL) - return NULL; - } - /* convert to float now */ buffer = pcm_convert_to_float(&state->format_buffer, @@ -298,6 +236,23 @@ pcm_convert_float(struct pcm_convert_state *state, return NULL; } + /* convert channels */ + + if (src_format->channels != dest_format->channels) { + buffer = pcm_convert_channels_float(&state->channels_buffer, + dest_format->channels, + src_format->channels, + buffer, size, &size); + if (buffer == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } + } + /* resample with float, because this is the best format for libsamplerate */ 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 90f616d77..5a16520c2 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -26,7 +26,7 @@ #include "song.h" #include "idle.h" #include "pcm_volume.h" -#include "main.h" +#include "Main.hxx" #include <assert.h> #include <stdio.h> @@ -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) { @@ -236,70 +230,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 @@ -328,6 +295,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); @@ -356,12 +327,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) { @@ -370,12 +335,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) { @@ -383,9 +342,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 a77d31ec5..3b536b8ba 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; @@ -194,14 +216,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); /** @@ -228,8 +246,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); @@ -242,8 +276,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); @@ -251,12 +288,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) */ @@ -272,16 +315,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 707fb27ae..561c595eb 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -32,7 +32,7 @@ #include "pipe.h" #include "chunk.h" #include "idle.h" -#include "main.h" +#include "Main.hxx" #include "buffer.h" #include "mpd_error.h" @@ -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 */ @@ -884,6 +904,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); @@ -1049,10 +1071,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; } @@ -1094,7 +1120,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; @@ -1135,7 +1165,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 dc6d8c340..2532d9d46 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -19,13 +19,9 @@ #include "config.h" #include "playlist_internal.h" -#include "playlist_save.h" #include "player_control.h" -#include "command.h" -#include "tag.h" #include "song.h" #include "conf.h" -#include "stored_playlist.h" #include "idle.h" #include <glib.h> @@ -78,14 +74,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); @@ -186,13 +183,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); @@ -257,14 +254,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_any.c b/src/playlist_any.c index 450ca5932..e4017ac0d 100644 --- a/src/playlist_any.c +++ b/src/playlist_any.c @@ -20,7 +20,7 @@ #include "config.h" #include "playlist_any.h" #include "playlist_list.h" -#include "playlist_mapper.h" +#include "PlaylistMapper.h" #include "uri.h" #include "input_stream.h" 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 d10f49451..2b7cdd8ae 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; @@ -136,7 +128,12 @@ playlist_append_uri(struct playlist *playlist, struct player_control *pc, if (song == NULL) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_append_song(playlist, pc, song, added_id); + enum playlist_result result = + playlist_append_song(playlist, pc, song, added_id); + if (song_in_database(song)) + db_return_song(song); + + return result; } enum playlist_result @@ -287,9 +284,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 +357,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_global.c b/src/playlist_global.c index 650b88bb8..43bf26755 100644 --- a/src/playlist_global.c +++ b/src/playlist_global.c @@ -26,7 +26,7 @@ #include "playlist.h" #include "playlist_state.h" #include "event_pipe.h" -#include "main.h" +#include "Main.hxx" struct playlist g_playlist; diff --git a/src/playlist_queue.c b/src/playlist_queue.c index aada94984..8eb535dbd 100644 --- a/src/playlist_queue.c +++ b/src/playlist_queue.c @@ -41,8 +41,7 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, ++i) { if (i < start_index) { /* skip songs before the start index */ - if (!song_in_database(song)) - song_free(song); + song_free(song); continue; } @@ -51,9 +50,8 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, continue; result = playlist_append_song(dest, pc, song, NULL); + song_free(song); if (result != PLAYLIST_RESULT_SUCCESS) { - if (!song_in_database(song)) - song_free(song); g_free(base_uri); return result; } diff --git a/src/playlist_song.c b/src/playlist_song.c index 29efef2e3..3b8de6307 100644 --- a/src/playlist_song.c +++ b/src/playlist_song.c @@ -79,9 +79,7 @@ apply_song_metadata(struct song *dest, const struct song *src) (e.g. last track on a CUE file); fix it up here */ tmp->tag->time = dest->tag->time - src->start_ms / 1000; - if (!song_in_database(dest)) - song_free(dest); - + song_free(dest); return tmp; } @@ -97,10 +95,13 @@ playlist_check_load_song(const struct song *song, const char *uri, bool secure) if (dest == NULL) return NULL; } else { - dest = db_get_song(uri); - if (dest == NULL) + struct song *tmp = db_get_song(uri); + if (tmp == NULL) /* not found in database */ return NULL; + + dest = song_dup_detached(tmp); + db_return_song(tmp); } return apply_song_metadata(dest, song); 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 4fe564a35..098cbcce9 100644 --- a/src/queue.c +++ b/src/queue.c @@ -103,7 +103,7 @@ queue_append(struct queue *queue, struct song *song, uint8_t priority) 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 = priority, @@ -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_save.c b/src/queue_save.c index 16852d3c1..00b5ecbb1 100644 --- a/src/queue_save.c +++ b/src/queue_save.c @@ -120,4 +120,7 @@ queue_load_song(FILE *fp, GString *buffer, const char *line, } queue_append(queue, song, priority); + + if (song_in_database(song)) + db_return_song(song); } 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/sig_handlers.c b/src/sig_handlers.c index b23f9e778..eabca1997 100644 --- a/src/sig_handlers.c +++ b/src/sig_handlers.c @@ -23,7 +23,7 @@ #ifndef WIN32 #include "log.h" -#include "main.h" +#include "Main.hxx" #include "event_pipe.h" #include "mpd_error.h" 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/stats.c b/src/stats.c deleted file mode 100644 index fe6a064a6..000000000 --- a/src/stats.c +++ /dev/null @@ -1,128 +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 "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" - -struct stats stats; - -void stats_global_init(void) -{ - stats.timer = g_timer_new(); -} - -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); -} - -int stats_print(struct client *client) -{ - client_printf(client, - "artists: %u\n" - "albums: %u\n" - "songs: %i\n" - "uptime: %li\n" - "playtime: %li\n" - "db_playtime: %li\n" - "db_update: %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()); - return 0; -} diff --git a/src/stats.h b/src/stats.h index a686477de..adb1c3443 100644 --- a/src/stats.h +++ b/src/stats.h @@ -49,6 +49,7 @@ void stats_global_finish(void); void stats_update(void); -int stats_print(struct client *client); +void +stats_print(struct client *client); #endif 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..e47db8f09 100644 --- a/src/update.c +++ b/src/update.c @@ -30,7 +30,7 @@ #include "update.h" #include "idle.h" #include "stats.h" -#include "main.h" +#include "Main.hxx" #include "mpd_error.h" #include <glib.h> @@ -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; } diff --git a/src/update_remove.c b/src/update_remove.c index f443f5eb2..c2e353c63 100644 --- a/src/update_remove.c +++ b/src/update_remove.c @@ -22,7 +22,7 @@ #include "event_pipe.h" #include "song.h" #include "playlist.h" -#include "main.h" +#include "Main.hxx" #ifdef ENABLE_SQLITE #include "sticker.h" @@ -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 |