aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/AllCommands.cxx391
-rw-r--r--src/AllCommands.h34
-rw-r--r--src/CommandError.cxx137
-rw-r--r--src/CommandError.hxx37
-rw-r--r--src/DatabaseCommands.cxx222
-rw-r--r--src/DatabaseCommands.hxx55
-rw-r--r--src/DatabaseGlue.cxx (renamed from src/database.c)108
-rw-r--r--src/DatabaseGlue.hxx44
-rw-r--r--src/DatabaseHelpers.cxx134
-rw-r--r--src/DatabaseHelpers.hxx41
-rw-r--r--src/DatabasePlaylist.cxx50
-rw-r--r--src/DatabasePlaylist.hxx (renamed from src/db/simple_db_plugin.h)26
-rw-r--r--src/DatabasePlugin.hxx148
-rw-r--r--src/DatabasePrint.cxx234
-rw-r--r--src/DatabasePrint.hxx (renamed from src/db_print.h)30
-rw-r--r--src/DatabaseQueue.cxx60
-rw-r--r--src/DatabaseQueue.hxx34
-rw-r--r--src/DatabaseRegistry.cxx43
-rw-r--r--src/DatabaseRegistry.hxx37
-rw-r--r--src/DatabaseSelection.cxx27
-rw-r--r--src/DatabaseSelection.hxx (renamed from src/db_selection.h)32
-rw-r--r--src/DatabaseVisitor.hxx38
-rw-r--r--src/Directory.cxx (renamed from src/directory.c)66
-rw-r--r--src/Main.cxx (renamed from src/main.c)48
-rw-r--r--src/Main.hxx (renamed from src/main.h)6
-rw-r--r--src/MessageCommands.cxx197
-rw-r--r--src/MessageCommands.hxx40
-rw-r--r--src/OtherCommands.cxx311
-rw-r--r--src/OtherCommands.hxx67
-rw-r--r--src/OutputCommands.cxx77
-rw-r--r--src/OutputCommands.hxx34
-rw-r--r--src/PlayerCommands.cxx391
-rw-r--r--src/PlayerCommands.hxx88
-rw-r--r--src/PlaylistCommands.cxx223
-rw-r--r--src/PlaylistCommands.hxx58
-rw-r--r--src/PlaylistFile.cxx (renamed from src/stored_playlist.c)227
-rw-r--r--src/PlaylistFile.hxx (renamed from src/stored_playlist.h)36
-rw-r--r--src/PlaylistMapper.cxx (renamed from src/playlist_mapper.c)9
-rw-r--r--src/PlaylistMapper.h (renamed from src/playlist_mapper.h)6
-rw-r--r--src/PlaylistPrint.cxx (renamed from src/playlist_print.c)44
-rw-r--r--src/PlaylistPrint.hxx (renamed from src/playlist_print.h)17
-rw-r--r--src/PlaylistSave.cxx (renamed from src/playlist_save.c)39
-rw-r--r--src/PlaylistSave.hxx (renamed from src/playlist_save.h)2
-rw-r--r--src/QueueCommands.cxx393
-rw-r--r--src/QueueCommands.hxx82
-rw-r--r--src/QueuePrint.cxx (renamed from src/queue_print.c)31
-rw-r--r--src/QueuePrint.hxx (renamed from src/queue_print.h)14
-rw-r--r--src/Song.cxx (renamed from src/song.c)92
-rw-r--r--src/SongFilter.cxx170
-rw-r--r--src/SongFilter.hxx108
-rw-r--r--src/Stats.cxx90
-rw-r--r--src/StickerCommands.cxx196
-rw-r--r--src/StickerCommands.hxx28
-rw-r--r--src/Win32Main.cxx (renamed from src/main_win32.c)5
-rw-r--r--src/audio_check.h2
-rw-r--r--src/audio_format.h19
-rw-r--r--src/audio_parser.h2
-rw-r--r--src/client.h15
-rw-r--r--src/client_event.c3
-rw-r--r--src/client_file.c4
-rw-r--r--src/client_file.h3
-rw-r--r--src/client_internal.h2
-rw-r--r--src/client_message.h9
-rw-r--r--src/client_process.c2
-rw-r--r--src/client_subscribe.h6
-rw-r--r--src/clock.h6
-rw-r--r--src/command.c2298
-rw-r--r--src/command.h49
-rw-r--r--src/conf.c1
-rw-r--r--src/database.h46
-rw-r--r--src/db/ProxyDatabasePlugin.cxx482
-rw-r--r--src/db/ProxyDatabasePlugin.hxx27
-rw-r--r--src/db/SimpleDatabasePlugin.cxx (renamed from src/db/simple_db_plugin.c)280
-rw-r--r--src/db/SimpleDatabasePlugin.hxx99
-rw-r--r--src/dbUtils.c209
-rw-r--r--src/dbUtils.h56
-rw-r--r--src/db_lock.h15
-rw-r--r--src/db_plugin.h156
-rw-r--r--src/db_print.c393
-rw-r--r--src/db_save.h3
-rw-r--r--src/db_visitor.h54
-rw-r--r--src/decoder/AdPlugDecoderPlugin.cxx148
-rw-r--r--src/decoder/AdPlugDecoderPlugin.h25
-rw-r--r--src/decoder/FLACCommon.cxx (renamed from src/decoder/_flac_common.c)62
-rw-r--r--src/decoder/FLACCommon.hxx (renamed from src/decoder/_flac_common.h)30
-rw-r--r--src/decoder/FLACDecoderPlugin.cxx (renamed from src/decoder/flac_decoder_plugin.c)292
-rw-r--r--src/decoder/FLACDecoderPlugin.h26
-rw-r--r--src/decoder/FLACIOHandle.cxx114
-rw-r--r--src/decoder/FLACIOHandle.hxx48
-rw-r--r--src/decoder/FLACInput.cxx152
-rw-r--r--src/decoder/FLACInput.hxx72
-rw-r--r--src/decoder/FLACMetaData.cxx (renamed from src/decoder/flac_metadata.c)133
-rw-r--r--src/decoder/FLACMetaData.hxx141
-rw-r--r--src/decoder/FLAC_PCM.cxx (renamed from src/decoder/flac_pcm.c)4
-rw-r--r--src/decoder/FLAC_PCM.hxx (renamed from src/decoder/flac_pcm.h)6
-rw-r--r--src/decoder/OggUtil.cxx56
-rw-r--r--src/decoder/OggUtil.hxx48
-rw-r--r--src/decoder/OpusDecoderPlugin.cxx366
-rw-r--r--src/decoder/OpusDecoderPlugin.h25
-rw-r--r--src/decoder/OpusHead.cxx44
-rw-r--r--src/decoder/OpusHead.hxx30
-rw-r--r--src/decoder/OpusReader.hxx97
-rw-r--r--src/decoder/OpusTags.cxx77
-rw-r--r--src/decoder/OpusTags.hxx31
-rw-r--r--src/decoder/XiphTags.c28
-rw-r--r--src/decoder/XiphTags.h28
-rw-r--r--src/decoder/flac_compat.h114
-rw-r--r--src/decoder/flac_metadata.h64
-rw-r--r--src/decoder/mad_decoder_plugin.c4
-rw-r--r--src/decoder/ogg_codec.c (renamed from src/decoder/_ogg_common.c)18
-rw-r--r--src/decoder/ogg_codec.h (renamed from src/decoder/_ogg_common.h)14
-rw-r--r--src/decoder/sidplay_decoder_plugin.cxx8
-rw-r--r--src/decoder/vorbis_comments.c10
-rw-r--r--src/decoder/vorbis_decoder_plugin.c115
-rw-r--r--src/decoder_control.c33
-rw-r--r--src/decoder_control.h94
-rw-r--r--src/decoder_error.h35
-rw-r--r--src/decoder_list.c11
-rw-r--r--src/decoder_thread.c21
-rw-r--r--src/directory.h41
-rw-r--r--src/encoder/OggStream.hxx128
-rw-r--r--src/encoder/OpusEncoderPlugin.cxx432
-rw-r--r--src/encoder/OpusEncoderPlugin.hxx25
-rw-r--r--src/encoder/VorbisEncoderPlugin.cxx (renamed from src/encoder/vorbis_encoder.c)123
-rw-r--r--src/encoder/VorbisEncoderPlugin.hxx25
-rw-r--r--src/encoder/flac_encoder.c31
-rw-r--r--src/encoder_list.c8
-rw-r--r--src/encoder_plugin.h2
-rw-r--r--src/event_pipe.h2
-rw-r--r--src/fd_util.h8
-rw-r--r--src/filter/null_filter_plugin.c1
-rw-r--r--src/filter_plugin.h2
-rw-r--r--src/gcc.h32
-rw-r--r--src/gerror.h25
-rw-r--r--src/inotify_source.c2
-rw-r--r--src/inotify_source.h2
-rw-r--r--src/input/file_input_plugin.c17
-rw-r--r--src/input_init.h3
-rw-r--r--src/io_error.h44
-rw-r--r--src/listen.c2
-rw-r--r--src/listen.h2
-rw-r--r--src/locate.c239
-rw-r--r--src/locate.h92
-rw-r--r--src/ls.c2
-rw-r--r--src/mapper.c21
-rw-r--r--src/mapper.h27
-rw-r--r--src/mixer_control.h2
-rw-r--r--src/mixer_plugin.h2
-rw-r--r--src/output/httpd_client.h3
-rw-r--r--src/output/pulse_output_plugin.h4
-rw-r--r--src/output/shout_output_plugin.c71
-rw-r--r--src/output_all.c19
-rw-r--r--src/output_all.h7
-rw-r--r--src/output_error.h35
-rw-r--r--src/output_init.c1
-rw-r--r--src/output_internal.h7
-rw-r--r--src/output_plugin.h7
-rw-r--r--src/output_thread.c2
-rw-r--r--src/pcm_channels.c71
-rw-r--r--src/pcm_channels.h17
-rw-r--r--src/pcm_convert.c79
-rw-r--r--src/pcm_mix.h3
-rw-r--r--src/pcm_volume.h1
-rw-r--r--src/pipe.h11
-rw-r--r--src/player_control.c111
-rw-r--r--src/player_control.h100
-rw-r--r--src/player_thread.c70
-rw-r--r--src/playlist.c21
-rw-r--r--src/playlist_any.c2
-rw-r--r--src/playlist_control.c5
-rw-r--r--src/playlist_edit.c20
-rw-r--r--src/playlist_global.c2
-rw-r--r--src/playlist_queue.c6
-rw-r--r--src/playlist_song.c11
-rw-r--r--src/protocol/result.h5
-rw-r--r--src/queue.c11
-rw-r--r--src/queue_save.c3
-rw-r--r--src/resolver.h6
-rw-r--r--src/server_socket.h5
-rw-r--r--src/sig_handlers.c2
-rw-r--r--src/socket_util.c2
-rw-r--r--src/socket_util.h4
-rw-r--r--src/song.h65
-rw-r--r--src/song_print.c29
-rw-r--r--src/stats.c128
-rw-r--r--src/stats.h3
-rw-r--r--src/sticker.h10
-rw-r--r--src/sticker_print.c2
-rw-r--r--src/string_util.h8
-rw-r--r--src/strset.c2
-rw-r--r--src/strset.h6
-rw-r--r--src/tag.c20
-rw-r--r--src/tag_id3.c3
-rw-r--r--src/tag_id3.h10
-rw-r--r--src/tag_pool.c25
-rw-r--r--src/tag_pool.h6
-rw-r--r--src/time_print.c47
-rw-r--r--src/time_print.h (renamed from src/db_internal.h)20
-rw-r--r--src/tokenizer.c2
-rw-r--r--src/tokenizer.h2
-rw-r--r--src/update.c4
-rw-r--r--src/update_archive.h9
-rw-r--r--src/update_remove.c2
-rw-r--r--src/uri.h10
-rw-r--r--src/util/bit_reverse.h5
-rw-r--r--src/util/list.h49
-rw-r--r--src/utils.h3
207 files changed, 9127 insertions, 5431 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..32d3cb5ec
--- /dev/null
+++ b/src/CommandError.cxx
@@ -0,0 +1,137 @@
+/*
+ * 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"
+#include "io_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 == errno_quark()) {
+ command_error(client, ACK_ERROR_SYSTEM, "%s",
+ g_strerror(error->code));
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ } else if (error->domain == g_file_error_quark()) {
+ command_error(client, ACK_ERROR_SYSTEM, "%s", error->message);
+ 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..3223edee3 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,21 @@
*/
#include "config.h"
-#include "stored_playlist.h"
-#include "playlist_save.h"
-#include "text_file.h"
+#include "PlaylistFile.hxx"
+#include "PlaylistSave.hxx"
#include "song.h"
+#include "io_error.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>
@@ -128,96 +133,73 @@ playlist_errno(GError **error_r)
break;
default:
- g_set_error_literal(error_r, g_file_error_quark(), errno,
- g_strerror(errno));
+ set_error_errno(error_r);
break;
}
}
-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;
+ set_error_errno(error_r);
+ 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 +209,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 +259,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 +332,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 +350,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 +376,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 +391,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 +417,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 +431,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 334159e0d..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,34 +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, temp2,
- 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.c b/src/client_file.c
index 2ee433308..5214ff019 100644
--- a/src/client_file.c
+++ b/src/client_file.c
@@ -20,6 +20,7 @@
#include "client_file.h"
#include "client.h"
#include "ack.h"
+#include "io_error.h"
#include <sys/stat.h>
#include <sys/types.h>
@@ -53,8 +54,7 @@ client_allow_file(const struct client *client, const char *path_fs,
struct stat st;
if (stat(path_fs, &st) < 0) {
- g_set_error(error_r, g_file_error_quark(), errno,
- "%s", g_strerror(errno));
+ set_error_errno(error_r);
return false;
}
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..97743f332 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,40 +22,35 @@
*/
#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>
#include <assert.h>
-void
-flac_data_init(struct flac_data *data, struct decoder * decoder,
- struct input_stream *input_stream)
+flac_data::flac_data(struct decoder *_decoder,
+ struct input_stream *_input_stream)
+ :FLACInput(_input_stream, _decoder),
+ initialized(false), unsupported(false),
+ total_frames(0), first_frame(0), next_frame(0), position(0),
+ decoder(_decoder), input_stream(_input_stream),
+ tag(nullptr)
{
- pcm_buffer_init(&data->buffer);
-
- data->unsupported = false;
- data->initialized = false;
- data->total_frames = 0;
- data->first_frame = 0;
- data->next_frame = 0;
-
- data->position = 0;
- data->decoder = decoder;
- data->input_stream = input_stream;
- data->tag = NULL;
+ pcm_buffer_init(&buffer);
}
-void
-flac_data_deinit(struct flac_data *data)
+flac_data::~flac_data()
{
- pcm_buffer_deinit(&data->buffer);
+ pcm_buffer_deinit(&buffer);
- if (data->tag != NULL)
- tag_free(data->tag);
+ if (tag != nullptr)
+ tag_free(tag);
}
static enum sample_format
@@ -86,7 +81,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 +124,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,
&block->data.vorbis_comment);
default:
@@ -138,15 +133,6 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
}
}
-void flac_error_common_cb(const FLAC__StreamDecoderErrorStatus status,
- struct flac_data *data)
-{
- if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP)
- return;
-
- g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]);
-}
-
/**
* This function attempts to call decoder_initialized() in case there
* was no STREAMINFO block. This is allowed for nonseekable streams,
@@ -160,7 +146,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 +185,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..e9b45976d 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,15 @@
* 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
+#include "FLACInput.hxx"
+
+extern "C" {
#include "decoder_api.h"
#include "pcm_buffer.h"
-
-#include <glib.h>
+}
#include <FLAC/stream_decoder.h>
#include <FLAC/metadata.h>
@@ -35,7 +37,7 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "flac"
-struct flac_data {
+struct flac_data : public FLACInput {
struct pcm_buffer buffer;
/**
@@ -78,25 +80,19 @@ struct flac_data {
FLAC__uint64 next_frame;
FLAC__uint64 position;
+
struct decoder *decoder;
struct input_stream *input_stream;
- struct tag *tag;
-};
-/* initializes a given FlacData struct */
-void
-flac_data_init(struct flac_data *data, struct decoder * decoder,
- struct input_stream *input_stream);
+ struct tag *tag;
-void
-flac_data_deinit(struct flac_data *data);
+ flac_data(struct decoder *decoder, struct input_stream *input_stream);
+ ~flac_data();
+};
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
struct flac_data *data);
-void flac_error_common_cb(FLAC__StreamDecoderErrorStatus status,
- struct flac_data *data);
-
FLAC__StreamDecoderWriteStatus
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
const FLAC__int32 *const buf[],
diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/FLACDecoderPlugin.cxx
index fb0b3502d..dbe7f207f 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,114 +34,10 @@
#include <sys/stat.h>
#include <sys/types.h>
-/* 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,
- void *fdata)
-{
- struct flac_data *data = fdata;
- size_t r;
-
- r = decoder_read(data->decoder, data->input_stream,
- (void *)buf, *bytes);
- *bytes = r;
-
- if (r == 0) {
- if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
- input_stream_lock_eof(data->input_stream))
- return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
- else
- return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
- }
-
- return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
-}
-
-static FLAC__StreamDecoderSeekStatus
-flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
- FLAC__uint64 offset, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (!data->input_stream->seekable)
- return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
-
- if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET,
- NULL))
- return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
-
- return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
-}
-
-static FLAC__StreamDecoderTellStatus
-flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
- FLAC__uint64 * offset, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (!data->input_stream->seekable)
- return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
-
- *offset = (long)(data->input_stream->offset);
-
- return FLAC__STREAM_DECODER_TELL_STATUS_OK;
-}
-
-static FLAC__StreamDecoderLengthStatus
-flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
- FLAC__uint64 * length, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (data->input_stream->size < 0)
- return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
-
- *length = (size_t) (data->input_stream->size);
-
- return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
-}
-
-static FLAC__bool
-flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
- decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
- input_stream_lock_eof(data->input_stream);
-}
-
-static void
-flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
- FLAC__StreamDecoderErrorStatus status, void *fdata)
-{
- 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;
- }
+#error libFLAC is too old
+#endif
- g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]);
-}
-#else /* FLAC_API_VERSION_CURRENT >= 7 */
static void flacPrintErroredState(FLAC__StreamDecoderState state)
{
switch (state) {
@@ -162,7 +58,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 +90,30 @@ 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);
+ FLACMetadataChain chain;
+ if (!chain.Read(file)) {
+ g_debug("Failed to read FLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
+
+ chain.Scan(handler, handler_ctx);
+ return true;
+}
+
+static bool
+flac_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FLACMetadataChain chain;
+ if (!chain.Read(is)) {
+ g_debug("Failed to read FLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
+
+ chain.Scan(handler, handler_ctx);
+ return true;
}
/**
@@ -205,15 +123,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 +175,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,34 +216,30 @@ 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,
- flac_tell_cb,
- flac_length_cb,
- flac_eof_cb,
+ FLACInput::Read,
+ FLACInput::Seek,
+ FLACInput::Tell,
+ FLACInput::Length,
+ FLACInput::Eof,
flac_write_cb,
flacMetadata,
- flac_error_cb,
+ FLACInput::Error,
data);
-#else
- (void)flac_dec;
- (void)data;
-
- return FLAC__STREAM_DECODER_INIT_STATUS_ERROR;
-#endif
}
static FLAC__StreamDecoderInitStatus
stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data)
{
return FLAC__stream_decoder_init_stream(flac_dec,
- flac_read_cb, flac_seek_cb,
- flac_tell_cb, flac_length_cb,
- flac_eof_cb, flac_write_cb,
+ FLACInput::Read,
+ FLACInput::Seek,
+ FLACInput::Tell,
+ FLACInput::Length,
+ FLACInput::Eof,
+ flac_write_cb,
flacMetadata,
- flac_error_cb,
+ FLACInput::Error,
data);
}
@@ -345,28 +257,23 @@ flac_decode_internal(struct decoder * decoder,
bool is_ogg)
{
FLAC__StreamDecoder *flac_dec;
- 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);
+ struct flac_data data(decoder, input_stream);
data.tag = tag_new();
FLAC__StreamDecoderInitStatus status =
stream_init(flac_dec, &data, is_ogg);
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;
}
if (!flac_decoder_initialize(&data, flac_dec, 0)) {
- flac_data_deinit(&data);
FLAC__stream_decoder_finish(flac_dec);
FLAC__stream_decoder_delete(flac_dec);
return;
@@ -374,8 +281,6 @@ flac_decode_internal(struct decoder * decoder,
flac_decoder_loop(&data, flac_dec, 0, 0);
- flac_data_deinit(&data);
-
FLAC__stream_decoder_finish(flac_dec);
FLAC__stream_decoder_delete(flac_dec);
}
@@ -386,101 +291,96 @@ 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)
{
- FLAC__Metadata_Iterator *it;
- FLAC__StreamMetadata *block;
- FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
-
- if (!(FLAC__metadata_chain_read_ogg(chain, file))) {
- FLAC__metadata_chain_delete(chain);
+ FLACMetadataChain chain;
+ if (!chain.ReadOgg(file)) {
+ g_debug("Failed to read OggFLAC tags: %s",
+ chain.GetStatusString());
return false;
}
- it = FLAC__metadata_iterator_new();
- FLAC__metadata_iterator_init(it, chain);
-
- do {
- if (!(block = FLAC__metadata_iterator_get_block(it)))
- break;
+ chain.Scan(handler, handler_ctx);
+ return true;
+}
- flac_scan_metadata(NULL, block,
- handler, handler_ctx);
- } while (FLAC__metadata_iterator_next(it));
- FLAC__metadata_iterator_delete(it);
+static bool
+oggflac_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FLACMetadataChain chain;
+ if (!chain.ReadOgg(is)) {
+ g_debug("Failed to read OggFLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
- FLAC__metadata_chain_delete(chain);
+ chain.Scan(handler, handler_ctx);
return true;
}
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,
+ oggflac_scan_stream,
+ 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,
+ flac_scan_stream,
+ 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/FLACIOHandle.cxx b/src/decoder/FLACIOHandle.cxx
new file mode 100644
index 000000000..08ec36e48
--- /dev/null
+++ b/src/decoder/FLACIOHandle.cxx
@@ -0,0 +1,114 @@
+/*
+ * 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 "FLACIOHandle.hxx"
+#include "io_error.h"
+#include "gcc.h"
+
+#include <errno.h>
+
+static size_t
+FLACIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle)
+{
+ input_stream *is = (input_stream *)handle;
+
+ uint8_t *const p0 = (uint8_t *)ptr, *p = p0,
+ *const end = p0 + size * nmemb;
+
+ /* libFLAC is very picky about short reads, and expects the IO
+ callback to fill the whole buffer (undocumented!) */
+
+ GError *error = nullptr;
+ while (p < end) {
+ size_t nbytes = input_stream_lock_read(is, p, end - p, &error);
+ if (nbytes == 0) {
+ if (error == nullptr)
+ /* end of file */
+ break;
+
+ if (error->domain == errno_quark())
+ errno = error->code;
+ else
+ /* just some random non-zero
+ errno value */
+ errno = EINVAL;
+ g_error_free(error);
+ return 0;
+ }
+
+ p += nbytes;
+ }
+
+ /* libFLAC expects a clean errno after returning from the IO
+ callbacks (undocumented!) */
+ errno = 0;
+ return (p - p0) / size;
+}
+
+static int
+FLACIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence)
+{
+ input_stream *is = (input_stream *)handle;
+
+ return input_stream_lock_seek(is, offset, whence, nullptr) ? 0 : -1;
+}
+
+static FLAC__int64
+FLACIOTell(FLAC__IOHandle handle)
+{
+ input_stream *is = (input_stream *)handle;
+
+ return is->offset;
+}
+
+static int
+FLACIOEof(FLAC__IOHandle handle)
+{
+ input_stream *is = (input_stream *)handle;
+
+ return input_stream_lock_eof(is);
+}
+
+static int
+FLACIOClose(gcc_unused FLAC__IOHandle handle)
+{
+ /* no-op because the libFLAC caller is repsonsible for closing
+ the #input_stream */
+
+ return 0;
+}
+
+const FLAC__IOCallbacks flac_io_callbacks = {
+ FLACIORead,
+ nullptr,
+ nullptr,
+ nullptr,
+ FLACIOEof,
+ FLACIOClose,
+};
+
+const FLAC__IOCallbacks flac_io_callbacks_seekable = {
+ FLACIORead,
+ nullptr,
+ FLACIOSeek,
+ FLACIOTell,
+ FLACIOEof,
+ FLACIOClose,
+};
diff --git a/src/decoder/FLACIOHandle.hxx b/src/decoder/FLACIOHandle.hxx
new file mode 100644
index 000000000..193a15ef5
--- /dev/null
+++ b/src/decoder/FLACIOHandle.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_FLAC_IO_HANDLE_HXX
+#define MPD_FLAC_IO_HANDLE_HXX
+
+#include "gcc.h"
+
+extern "C" {
+#include "input_stream.h"
+}
+
+#include <FLAC/callback.h>
+
+extern const FLAC__IOCallbacks flac_io_callbacks;
+extern const FLAC__IOCallbacks flac_io_callbacks_seekable;
+
+static inline FLAC__IOHandle
+ToFLACIOHandle(input_stream *is)
+{
+ return (FLAC__IOHandle)is;
+}
+
+static inline const FLAC__IOCallbacks &
+GetFLACIOCallbacks(const input_stream *is)
+{
+ return is->seekable
+ ? flac_io_callbacks_seekable
+ : flac_io_callbacks;
+}
+
+#endif
diff --git a/src/decoder/FLACInput.cxx b/src/decoder/FLACInput.cxx
new file mode 100644
index 000000000..0383ac4ff
--- /dev/null
+++ b/src/decoder/FLACInput.cxx
@@ -0,0 +1,152 @@
+/*
+ * 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 "FLACInput.hxx"
+#include "gcc.h"
+
+extern "C" {
+#include "input_stream.h"
+#include "decoder_api.h"
+}
+
+FLAC__StreamDecoderReadStatus
+FLACInput::Read(FLAC__byte buffer[], size_t *bytes)
+{
+ size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes);
+ *bytes = r;
+
+ if (r == 0) {
+ if (input_stream_lock_eof(input_stream) ||
+ (decoder != nullptr &&
+ decoder_get_command(decoder) != DECODE_COMMAND_NONE))
+ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+ else
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+ }
+
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+}
+
+FLAC__StreamDecoderSeekStatus
+FLACInput::Seek(FLAC__uint64 absolute_byte_offset)
+{
+ if (!input_stream->seekable)
+ return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
+
+ if (!input_stream_lock_seek(input_stream,
+ absolute_byte_offset, SEEK_SET,
+ nullptr))
+ return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
+
+ return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
+}
+
+FLAC__StreamDecoderTellStatus
+FLACInput::Tell(FLAC__uint64 *absolute_byte_offset)
+{
+ if (!input_stream->seekable)
+ return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
+
+ *absolute_byte_offset = (FLAC__uint64)input_stream->offset;
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+
+FLAC__StreamDecoderLengthStatus
+FLACInput::Length(FLAC__uint64 *stream_length)
+{
+ if (input_stream->size < 0)
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
+
+ *stream_length = (FLAC__uint64)input_stream->size;
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
+}
+
+FLAC__bool
+FLACInput::Eof()
+{
+ return (decoder != nullptr &&
+ decoder_get_command(decoder) != DECODE_COMMAND_NONE &&
+ decoder_get_command(decoder) != DECODE_COMMAND_SEEK) ||
+ input_stream_lock_eof(input_stream);
+}
+
+void
+FLACInput::Error(FLAC__StreamDecoderErrorStatus status)
+{
+ if (decoder == nullptr ||
+ decoder_get_command(decoder) != DECODE_COMMAND_STOP)
+ g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+FLAC__StreamDecoderReadStatus
+FLACInput::Read(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ FLAC__byte buffer[], size_t *bytes,
+ void *client_data)
+{
+ FLACInput *i = (FLACInput *)client_data;
+
+ return i->Read(buffer, bytes);
+}
+
+FLAC__StreamDecoderSeekStatus
+FLACInput::Seek(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 absolute_byte_offset, void *client_data)
+{
+ FLACInput *i = (FLACInput *)client_data;
+
+ return i->Seek(absolute_byte_offset);
+}
+
+FLAC__StreamDecoderTellStatus
+FLACInput::Tell(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 *absolute_byte_offset, void *client_data)
+{
+ FLACInput *i = (FLACInput *)client_data;
+
+ return i->Tell(absolute_byte_offset);
+}
+
+FLAC__StreamDecoderLengthStatus
+FLACInput::Length(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 *stream_length, void *client_data)
+{
+ FLACInput *i = (FLACInput *)client_data;
+
+ return i->Length(stream_length);
+}
+
+FLAC__bool
+FLACInput::Eof(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ void *client_data)
+{
+ FLACInput *i = (FLACInput *)client_data;
+
+ return i->Eof();
+}
+
+void
+FLACInput::Error(gcc_unused const FLAC__StreamDecoder *decoder,
+ FLAC__StreamDecoderErrorStatus status, void *client_data)
+{
+ FLACInput *i = (FLACInput *)client_data;
+
+ i->Error(status);
+}
+
diff --git a/src/decoder/FLACInput.hxx b/src/decoder/FLACInput.hxx
new file mode 100644
index 000000000..7661567d1
--- /dev/null
+++ b/src/decoder/FLACInput.hxx
@@ -0,0 +1,72 @@
+/*
+ * 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_FLAC_INPUT_HXX
+#define MPD_FLAC_INPUT_HXX
+
+#include <FLAC/stream_decoder.h>
+
+/**
+ * This class wraps an #input_stream in libFLAC stream decoder
+ * callbacks.
+ */
+class FLACInput {
+ struct decoder *decoder;
+
+ struct input_stream *input_stream;
+
+public:
+ FLACInput(struct input_stream *_input_stream,
+ struct decoder *_decoder=nullptr)
+ :decoder(_decoder), input_stream(_input_stream) {}
+
+protected:
+ FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes);
+ FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset);
+ FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset);
+ FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length);
+ FLAC__bool Eof();
+ void Error(FLAC__StreamDecoderErrorStatus status);
+
+public:
+ static FLAC__StreamDecoderReadStatus
+ Read(const FLAC__StreamDecoder *flac_decoder,
+ FLAC__byte buffer[], size_t *bytes, void *client_data);
+
+ static FLAC__StreamDecoderSeekStatus
+ Seek(const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 absolute_byte_offset, void *client_data);
+
+ static FLAC__StreamDecoderTellStatus
+ Tell(const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 *absolute_byte_offset, void *client_data);
+
+ static FLAC__StreamDecoderLengthStatus
+ Length(const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 *stream_length, void *client_data);
+
+ static FLAC__bool
+ Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data);
+
+ static void
+ Error(const FLAC__StreamDecoder *decoder,
+ FLAC__StreamDecoderErrorStatus status, void *client_data);
+};
+
+#endif
diff --git a/src/decoder/flac_metadata.c b/src/decoder/FLACMetaData.cxx
index bd1eaf323..158e426db 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)
@@ -128,36 +133,21 @@ flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
*/
static const char *
flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const char *name, const char *char_tnum, size_t *length_r)
+ const char *name, size_t *length_r)
{
size_t name_length = strlen(name);
- size_t char_tnum_length = 0;
const char *comment = (const char*)entry->entry;
if (entry->length <= name_length ||
g_ascii_strncasecmp(comment, name, name_length) != 0)
- return NULL;
-
- if (char_tnum != NULL) {
- char_tnum_length = strlen(char_tnum);
- if (entry->length > name_length + char_tnum_length + 2 &&
- comment[name_length] == '[' &&
- g_ascii_strncasecmp(comment + name_length + 1,
- char_tnum, char_tnum_length) == 0 &&
- comment[name_length + char_tnum_length + 1] == ']')
- name_length = name_length + char_tnum_length + 2;
- else if (entry->length > name_length + char_tnum_length &&
- g_ascii_strncasecmp(comment + name_length,
- char_tnum, char_tnum_length) == 0)
- name_length = name_length + char_tnum_length;
- }
+ return nullptr;
if (comment[name_length] == '=') {
*length_r = entry->length - name_length - 1;
return comment + name_length + 1;
}
- return NULL;
+ return nullptr;
}
/**
@@ -167,14 +157,13 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
static bool
flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const char *name, enum tag_type tag_type,
- const char *char_tnum,
const struct tag_handler *handler, void *handler_ctx)
{
const char *value;
size_t value_length;
- value = flac_comment_value(entry, name, char_tnum, &value_length);
- if (value != NULL) {
+ value = flac_comment_value(entry, name, &value_length);
+ 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 +173,15 @@ 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,
+flac_scan_comment(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,36 +190,34 @@ flac_scan_comment(const char *char_tnum,
g_free(name);
}
- for (const struct tag_table *i = flac_tags; i->name != NULL; ++i)
- if (flac_copy_comment(entry, i->name, i->type, char_tnum,
+ for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i)
+ if (flac_copy_comment(entry, i->name, i->type,
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,
handler, handler_ctx))
return;
}
static void
-flac_scan_comments(const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment *comment,
+flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment,
const struct tag_handler *handler, void *handler_ctx)
{
for (unsigned i = 0; i < comment->num_comments; ++i)
- flac_scan_comment(char_tnum, &comment->comments[i],
+ flac_scan_comment(&comment->comments[i],
handler, handler_ctx);
}
void
-flac_scan_metadata(const char *track,
- const FLAC__StreamMetadata *block,
+flac_scan_metadata(const FLAC__StreamMetadata *block,
const struct tag_handler *handler, void *handler_ctx)
{
switch (block->type) {
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
- flac_scan_comments(track, &block->data.vorbis_comment,
+ flac_scan_comments(&block->data.vorbis_comment,
handler, handler_ctx);
break;
@@ -254,70 +233,22 @@ flac_scan_metadata(const char *track,
}
void
-flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
+flac_vorbis_comments_to_tag(struct tag *tag,
const FLAC__StreamMetadata_VorbisComment *comment)
{
- flac_scan_comments(char_tnum, comment,
- &add_tag_handler, tag);
+ flac_scan_comments(comment, &add_tag_handler, tag);
}
-bool
-flac_scan_file2(const char *file, const char *char_tnum,
- const struct tag_handler *handler, void *handler_ctx)
+void
+FLACMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx)
{
- FLAC__Metadata_SimpleIterator *it;
- FLAC__StreamMetadata *block = NULL;
-
- it = FLAC__metadata_simple_iterator_new();
- if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) {
- const char *err;
- FLAC_API FLAC__Metadata_SimpleIteratorStatus s;
-
- s = FLAC__metadata_simple_iterator_status(it);
-
- switch (s) { /* slightly more human-friendly messages: */
- case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT:
- err = "illegal input";
- break;
- case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE:
- err = "error opening file";
- break;
- case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE:
- err = "not a FLAC file";
- break;
- default:
- err = FLAC__Metadata_SimpleIteratorStatusString[s];
- }
- g_debug("Reading '%s' metadata gave the following error: %s\n",
- file, err);
- FLAC__metadata_simple_iterator_delete(it);
- return false;
- }
+ FLACMetadataIterator iterator(*this);
do {
- block = FLAC__metadata_simple_iterator_get_block(it);
- if (!block)
+ FLAC__StreamMetadata *block = iterator.GetBlock();
+ if (block == nullptr)
break;
- flac_scan_metadata(char_tnum, block, handler, handler_ctx);
- FLAC__metadata_object_delete(block);
- } while (FLAC__metadata_simple_iterator_next(it));
-
- FLAC__metadata_simple_iterator_delete(it);
-
- 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;
+ flac_scan_metadata(block, handler, handler_ctx);
+ } while (iterator.Next());
}
diff --git a/src/decoder/FLACMetaData.hxx b/src/decoder/FLACMetaData.hxx
new file mode 100644
index 000000000..7b5eb8b0c
--- /dev/null
+++ b/src/decoder/FLACMetaData.hxx
@@ -0,0 +1,141 @@
+/*
+ * 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_FLAC_METADATA_H
+#define MPD_FLAC_METADATA_H
+
+#include "gcc.h"
+#include "FLACIOHandle.hxx"
+
+#include <FLAC/metadata.h>
+
+#include <assert.h>
+#include <stdbool.h>
+
+class FLACMetadataChain {
+ FLAC__Metadata_Chain *chain;
+
+public:
+ FLACMetadataChain():chain(::FLAC__metadata_chain_new()) {}
+
+ ~FLACMetadataChain() {
+ ::FLAC__metadata_chain_delete(chain);
+ }
+
+ explicit operator FLAC__Metadata_Chain *() {
+ return chain;
+ }
+
+ bool Read(const char *path) {
+ return ::FLAC__metadata_chain_read(chain, path);
+ }
+
+ bool Read(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) {
+ return ::FLAC__metadata_chain_read_with_callbacks(chain,
+ handle,
+ callbacks);
+ }
+
+ bool Read(input_stream *is) {
+ return Read(::ToFLACIOHandle(is), ::GetFLACIOCallbacks(is));
+ }
+
+ bool ReadOgg(const char *path) {
+ return ::FLAC__metadata_chain_read_ogg(chain, path);
+ }
+
+ bool ReadOgg(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) {
+ return ::FLAC__metadata_chain_read_ogg_with_callbacks(chain,
+ handle,
+ callbacks);
+ }
+
+ bool ReadOgg(input_stream *is) {
+ return ReadOgg(::ToFLACIOHandle(is), ::GetFLACIOCallbacks(is));
+ }
+
+ gcc_pure
+ FLAC__Metadata_ChainStatus GetStatus() const {
+ return ::FLAC__metadata_chain_status(chain);
+ }
+
+ gcc_pure
+ const char *GetStatusString() const {
+ return FLAC__Metadata_ChainStatusString[GetStatus()];
+ }
+
+ void Scan(const struct tag_handler *handler, void *handler_ctx);
+};
+
+class FLACMetadataIterator {
+ FLAC__Metadata_Iterator *iterator;
+
+public:
+ FLACMetadataIterator():iterator(::FLAC__metadata_iterator_new()) {}
+
+ FLACMetadataIterator(FLACMetadataChain &chain)
+ :iterator(::FLAC__metadata_iterator_new()) {
+ ::FLAC__metadata_iterator_init(iterator,
+ (FLAC__Metadata_Chain *)chain);
+ }
+
+ ~FLACMetadataIterator() {
+ ::FLAC__metadata_iterator_delete(iterator);
+ }
+
+ bool Next() {
+ return ::FLAC__metadata_iterator_next(iterator);
+ }
+
+ gcc_pure
+ FLAC__StreamMetadata *GetBlock() {
+ return ::FLAC__metadata_iterator_get_block(iterator);
+ }
+};
+
+struct tag_handler;
+struct tag;
+struct replay_gain_info;
+
+static inline unsigned
+flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
+{
+ assert(stream_info->sample_rate > 0);
+
+ return (stream_info->total_samples + stream_info->sample_rate - 1) /
+ stream_info->sample_rate;
+}
+
+bool
+flac_parse_replay_gain(struct replay_gain_info *rgi,
+ const FLAC__StreamMetadata *block);
+
+bool
+flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
+ const FLAC__StreamMetadata *block);
+
+void
+flac_vorbis_comments_to_tag(struct tag *tag,
+ const FLAC__StreamMetadata_VorbisComment *comment);
+
+void
+flac_scan_metadata(const FLAC__StreamMetadata *block,
+ const struct tag_handler *handler, void *handler_ctx);
+
+#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/flac_metadata.h b/src/decoder/flac_metadata.h
deleted file mode 100644
index 3c463d5d6..000000000
--- a/src/decoder/flac_metadata.h
+++ /dev/null
@@ -1,64 +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_FLAC_METADATA_H
-#define MPD_FLAC_METADATA_H
-
-#include <assert.h>
-#include <stdbool.h>
-#include <FLAC/metadata.h>
-
-struct tag_handler;
-struct tag;
-struct replay_gain_info;
-
-static inline unsigned
-flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
-{
- assert(stream_info->sample_rate > 0);
-
- return (stream_info->total_samples + stream_info->sample_rate - 1) /
- stream_info->sample_rate;
-}
-
-bool
-flac_parse_replay_gain(struct replay_gain_info *rgi,
- const FLAC__StreamMetadata *block);
-
-bool
-flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
- const FLAC__StreamMetadata *block);
-
-void
-flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment *comment);
-
-void
-flac_scan_metadata(const char *track,
- const FLAC__StreamMetadata *block,
- const struct tag_handler *handler, void *handler_ctx);
-
-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/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, &current_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,
+ &current_section);
+#else
+ float **per_channel;
+ long nframes = ov_read_float(&vf, &per_channel,
+ frames_per_buffer,
+ &current_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>
diff --git a/src/gcc.h b/src/gcc.h
index 45f7101f3..e921b4646 100644
--- a/src/gcc.h
+++ b/src/gcc.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/file_input_plugin.c b/src/input/file_input_plugin.c
index 5ee3f200b..e130230a7 100644
--- a/src/input/file_input_plugin.c
+++ b/src/input/file_input_plugin.c
@@ -23,6 +23,7 @@
#include "input_plugin.h"
#include "fd_util.h"
#include "open.h"
+#include "io_error.h"
#include <sys/stat.h>
#include <unistd.h>
@@ -39,12 +40,6 @@ struct file_input_stream {
int fd;
};
-static inline GQuark
-file_quark(void)
-{
- return g_quark_from_static_string("file");
-}
-
static struct input_stream *
input_file_open(const char *filename,
GMutex *mutex, GCond *cond,
@@ -60,7 +55,7 @@ input_file_open(const char *filename,
fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
if (fd < 0) {
if (errno != ENOENT && errno != ENOTDIR)
- g_set_error(error_r, file_quark(), errno,
+ g_set_error(error_r, errno_quark(), errno,
"Failed to open \"%s\": %s",
filename, g_strerror(errno));
return NULL;
@@ -68,7 +63,7 @@ input_file_open(const char *filename,
ret = fstat(fd, &st);
if (ret < 0) {
- g_set_error(error_r, file_quark(), errno,
+ g_set_error(error_r, errno_quark(), errno,
"Failed to stat \"%s\": %s",
filename, g_strerror(errno));
close(fd);
@@ -76,7 +71,7 @@ input_file_open(const char *filename,
}
if (!S_ISREG(st.st_mode)) {
- g_set_error(error_r, file_quark(), 0,
+ g_set_error(error_r, errno_quark(), 0,
"Not a regular file: %s", filename);
close(fd);
return NULL;
@@ -107,7 +102,7 @@ input_file_seek(struct input_stream *is, goffset offset, int whence,
offset = (goffset)lseek(fis->fd, (off_t)offset, whence);
if (offset < 0) {
- g_set_error(error_r, file_quark(), errno,
+ g_set_error(error_r, errno_quark(), errno,
"Failed to seek: %s", g_strerror(errno));
return false;
}
@@ -125,7 +120,7 @@ input_file_read(struct input_stream *is, void *ptr, size_t size,
nbytes = read(fis->fd, ptr, size);
if (nbytes < 0) {
- g_set_error(error_r, file_quark(), errno,
+ g_set_error(error_r, errno_quark(), errno,
"Failed to read: %s", g_strerror(errno));
return 0;
}
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/io_error.h b/src/io_error.h
new file mode 100644
index 000000000..c89748360
--- /dev/null
+++ b/src/io_error.h
@@ -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.
+ */
+
+#ifndef MPD_IO_ERROR_H
+#define MPD_IO_ERROR_H
+
+#include <glib.h>
+
+#include <errno.h>
+
+/**
+ * A GQuark for GError for I/O errors. The code is an errno value.
+ */
+G_GNUC_CONST
+static inline GQuark
+errno_quark(void)
+{
+ return g_quark_from_static_string("errno");
+}
+
+static inline void
+set_error_errno(GError **error_r)
+{
+ g_set_error_literal(error_r, errno_quark(), errno,
+ g_strerror(errno));
+}
+
+#endif
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
diff --git a/src/ls.c b/src/ls.c
index 310c2d7b6..90e5fc66e 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -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 a3d9ab4d9..2e2870d38 100644
--- a/src/playlist_song.c
+++ b/src/playlist_song.c
@@ -86,9 +86,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;
}
@@ -104,10 +102,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);
diff --git a/src/tag.c b/src/tag.c
index c0faa7ab2..b59c23b23 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -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.c b/src/tag_id3.c
index 0971829f0..5f589d58d 100644
--- a/src/tag_id3.c
+++ b/src/tag_id3.c
@@ -25,6 +25,7 @@
#include "riff.h"
#include "aiff.h"
#include "conf.h"
+#include "io_error.h"
#include <glib.h>
#include <id3tag.h>
@@ -546,7 +547,7 @@ tag_id3_load(const char *path_fs, GError **error_r)
{
FILE *file = fopen(path_fs, "rb");
if (file == NULL) {
- g_set_error(error_r, g_file_error_quark(), errno,
+ g_set_error(error_r, errno_quark(), errno,
"Failed to open file %s: %s",
path_fs, g_strerror(errno));
return NULL;
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"
diff --git a/src/uri.h b/src/uri.h
index 5a9b472f5..6713f1698 100644
--- a/src/uri.h
+++ b/src/uri.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