diff options
Diffstat (limited to '')
-rw-r--r-- | src/OutputAll.cxx (renamed from src/output_all.c) | 58 | ||||
-rw-r--r-- | src/OutputAll.hxx (renamed from src/output_all.h) | 11 | ||||
-rw-r--r-- | src/OutputCommand.cxx (renamed from src/output_command.c) | 11 | ||||
-rw-r--r-- | src/OutputCommand.hxx (renamed from src/output_command.h) | 8 | ||||
-rw-r--r-- | src/OutputControl.cxx (renamed from src/output_control.c) | 23 | ||||
-rw-r--r-- | src/OutputControl.hxx (renamed from src/output_control.h) | 13 | ||||
-rw-r--r-- | src/OutputFinish.cxx (renamed from src/output_finish.c) | 5 | ||||
-rw-r--r-- | src/OutputInit.cxx (renamed from src/output_init.c) | 19 | ||||
-rw-r--r-- | src/OutputList.cxx (renamed from src/output_list.c) | 6 | ||||
-rw-r--r-- | src/OutputList.hxx (renamed from src/output_list.h) | 6 | ||||
-rw-r--r-- | src/OutputPlugin.cxx (renamed from src/output_plugin.c) | 6 | ||||
-rw-r--r-- | src/OutputPrint.cxx (renamed from src/output_print.c) | 12 | ||||
-rw-r--r-- | src/OutputPrint.hxx (renamed from src/output_print.h) | 10 | ||||
-rw-r--r-- | src/OutputState.cxx (renamed from src/output_state.c) | 6 | ||||
-rw-r--r-- | src/OutputState.hxx (renamed from src/output_state.h) | 7 | ||||
-rw-r--r-- | src/OutputThread.cxx (renamed from src/output_thread.c) | 48 | ||||
-rw-r--r-- | src/OutputThread.hxx (renamed from src/output_thread.h) | 6 | ||||
-rw-r--r-- | src/output/HttpdClient.cxx | 592 | ||||
-rw-r--r-- | src/output/HttpdClient.hxx | 215 | ||||
-rw-r--r-- | src/output/HttpdInternal.hxx (renamed from src/output/httpd_internal.h) | 14 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.cxx (renamed from src/output/httpd_output_plugin.c) | 241 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.hxx (renamed from src/output/httpd_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/fifo_output_plugin.c | 1 | ||||
-rw-r--r-- | src/output/httpd_client.c | 764 | ||||
-rw-r--r-- | src/output/httpd_client.h | 71 | ||||
-rw-r--r-- | src/output/osx_output_plugin.c | 2 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.c | 2 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.h | 12 | ||||
-rw-r--r-- | src/output/shout_output_plugin.c | 104 | ||||
-rw-r--r-- | src/output/winmm_output_plugin.c | 1 | ||||
-rw-r--r-- | src/output/winmm_output_plugin.h | 1 | ||||
-rw-r--r-- | src/output_internal.h | 15 | ||||
-rw-r--r-- | src/output_plugin.h | 7 |
33 files changed, 1157 insertions, 1146 deletions
diff --git a/src/output_all.c b/src/OutputAll.cxx index f56cd04ee..a18a63385 100644 --- a/src/output_all.c +++ b/src/OutputAll.cxx @@ -18,20 +18,21 @@ */ #include "config.h" -#include "output_all.h" +#include "OutputAll.hxx" + +extern "C" { #include "output_internal.h" -#include "output_control.h" -#include "chunk.h" -#include "conf.h" -#include "pipe.h" -#include "buffer.h" -#include "player_control.h" -#include "mpd_error.h" -#include "notify.h" +} -#ifndef NDEBUG -#include "chunk.h" -#endif +#include "PlayerControl.hxx" +#include "OutputControl.hxx" +#include "OutputError.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "mpd_error.h" +#include "conf.h" +#include "notify.hxx" #include <assert.h> #include <string.h> @@ -109,8 +110,6 @@ audio_output_all_init(struct player_control *pc) unsigned int i; GError *error = NULL; - notify_init(&audio_output_client_notify); - num_audio_outputs = audio_output_config_count(); audio_outputs = g_new(struct audio_output *, num_audio_outputs); @@ -157,8 +156,6 @@ audio_output_all_finish(void) g_free(audio_outputs); audio_outputs = NULL; num_audio_outputs = 0; - - notify_deinit(&audio_output_client_notify); } void @@ -207,7 +204,7 @@ audio_output_all_finished(void) static void audio_output_wait_all(void) { while (!audio_output_all_finished()) - notify_wait(&audio_output_client_notify); + audio_output_client_notify.Wait(); } /** @@ -269,8 +266,15 @@ audio_output_all_update(void) return ret; } +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_set_replay_gain_mode(audio_outputs[i], mode); +} + 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; @@ -278,11 +282,15 @@ audio_output_all_play(struct music_chunk *chunk) assert(g_music_buffer != NULL); assert(g_mp != NULL); assert(chunk != NULL); - assert(music_chunk_check_format(chunk, &input_audio_format)); + assert(chunk->CheckFormat(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 +302,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 +343,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/OutputAll.hxx index 4eeb94f13..becf4b695 100644 --- a/src/output_all.h +++ b/src/OutputAll.hxx @@ -26,6 +26,9 @@ #ifndef OUTPUT_ALL_H #define OUTPUT_ALL_H +#include "replay_gain_info.h" +#include "gerror.h" + #include <stdbool.h> #include <stddef.h> @@ -84,7 +87,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. @@ -99,6 +103,9 @@ audio_output_all_close(void); void audio_output_all_release(void); +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode); + /** * Enqueue a #music_chunk object for playing, i.e. pushes it to a * #music_pipe. @@ -108,7 +115,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_command.c b/src/OutputCommand.cxx index 3988f350a..f4edaa4a1 100644 --- a/src/output_command.c +++ b/src/OutputCommand.cxx @@ -25,13 +25,16 @@ */ #include "config.h" -#include "output_command.h" -#include "output_all.h" +#include "OutputCommand.hxx" +#include "OutputAll.hxx" +#include "PlayerControl.hxx" +#include "Idle.hxx" + +extern "C" { #include "output_internal.h" #include "output_plugin.h" #include "mixer_control.h" -#include "player_control.h" -#include "idle.h" +} extern unsigned audio_output_state_version; diff --git a/src/output_command.h b/src/OutputCommand.hxx index eda30acc8..74eaf8f1c 100644 --- a/src/output_command.h +++ b/src/OutputCommand.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,10 +24,8 @@ * */ -#ifndef OUTPUT_COMMAND_H -#define OUTPUT_COMMAND_H - -#include <stdbool.h> +#ifndef MPD_OUTPUT_COMMAND_HXX +#define MPD_OUTPUT_COMMAND_HXX /** * Enables an audio output. Returns false if the specified output diff --git a/src/output_control.c b/src/OutputControl.cxx index 7b95be49b..13625ade2 100644 --- a/src/output_control.c +++ b/src/OutputControl.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,19 @@ */ #include "config.h" -#include "output_control.h" +#include "OutputControl.hxx" +#include "OutputThread.hxx" + +extern "C" { #include "output_api.h" #include "output_internal.h" -#include "output_thread.h" #include "mixer_control.h" #include "mixer_plugin.h" +} + +#include "notify.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" #include "filter_plugin.h" -#include "notify.h" #include <assert.h> #include <stdlib.h> @@ -47,7 +52,7 @@ static void ao_command_wait(struct audio_output *ao) { while (ao->command != AO_COMMAND_NONE) { g_mutex_unlock(ao->mutex); - notify_wait(&audio_output_client_notify); + audio_output_client_notify.Wait(); g_mutex_lock(ao->mutex); } } @@ -92,6 +97,14 @@ ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) } void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode) +{ + if (ao->replay_gain_filter != NULL) + replay_gain_filter_set_mode(ao->replay_gain_filter, mode); +} + +void audio_output_enable(struct audio_output *ao) { if (ao->thread == NULL) { diff --git a/src/output_control.h b/src/OutputControl.hxx index 874a53518..cf906d2f0 100644 --- a/src/output_control.h +++ b/src/OutputControl.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OUTPUT_CONTROL_H -#define MPD_OUTPUT_CONTROL_H +#ifndef MPD_OUTPUT_CONTROL_HXX +#define MPD_OUTPUT_CONTROL_HXX + +#include "replay_gain_info.h" #include <glib.h> #include <stddef.h> -#include <stdbool.h> struct audio_output; struct audio_format; @@ -37,6 +38,10 @@ audio_output_quark(void) return g_quark_from_static_string("audio_output"); } +void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode); + /** * Enables the device. */ diff --git a/src/output_finish.c b/src/OutputFinish.cxx index e11b43675..ac6ad6977 100644 --- a/src/output_finish.c +++ b/src/OutputFinish.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,13 @@ */ #include "config.h" + +extern "C" { #include "output_internal.h" #include "output_plugin.h" #include "mixer_control.h" #include "filter_plugin.h" +} #include <assert.h> diff --git a/src/output_init.c b/src/OutputInit.cxx index c3b808e94..3c394ba44 100644 --- a/src/output_init.c +++ b/src/OutputInit.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,12 @@ */ #include "config.h" -#include "output_control.h" +#include "OutputControl.hxx" +#include "OutputList.hxx" + +extern "C" { #include "output_api.h" #include "output_internal.h" -#include "output_list.h" #include "audio_parser.h" #include "mixer_control.h" #include "mixer_type.h" @@ -32,7 +34,9 @@ #include "filter_config.h" #include "filter/chain_filter_plugin.h" #include "filter/autoconvert_filter_plugin.h" -#include "filter/replay_gain_filter_plugin.h" +} + +#include "filter/ReplayGainFilterPlugin.hxx" #include <glib.h> @@ -165,6 +169,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; @@ -297,14 +302,14 @@ audio_output_new(const struct config_param *param, if (p == NULL) { g_set_error(error_r, audio_output_quark(), 0, "Missing \"type\" configuration"); - return false; + return nullptr; } plugin = audio_output_plugin_get(p); if (plugin == NULL) { g_set_error(error_r, audio_output_quark(), 0, "No such audio output plugin: %s", p); - return false; + return nullptr; } } else { g_warning("No \"%s\" defined in config file\n", @@ -312,7 +317,7 @@ audio_output_new(const struct config_param *param, plugin = audio_output_detect(error_r); if (plugin == NULL) - return false; + return nullptr; g_message("Successfully detected a %s audio device", plugin->name); diff --git a/src/output_list.c b/src/OutputList.cxx index 835c02bba..df064219f 100644 --- a/src/output_list.c +++ b/src/OutputList.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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" -#include "output_list.h" +#include "OutputList.hxx" #include "output_api.h" #include "output/alsa_output_plugin.h" #include "output/ao_output_plugin.h" #include "output/ffado_output_plugin.h" #include "output/fifo_output_plugin.h" -#include "output/httpd_output_plugin.h" +#include "output/HttpdOutputPlugin.hxx" #include "output/jack_output_plugin.h" #include "output/mvp_output_plugin.h" #include "output/null_output_plugin.h" diff --git a/src/output_list.h b/src/OutputList.hxx index 185ada716..b7716c67e 100644 --- a/src/output_list.h +++ b/src/OutputList.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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_OUTPUT_LIST_H -#define MPD_OUTPUT_LIST_H +#ifndef MPD_OUTPUT_LIST_HXX +#define MPD_OUTPUT_LIST_HXX extern const struct audio_output_plugin *const audio_output_plugins[]; diff --git a/src/output_plugin.c b/src/OutputPlugin.cxx index 221570c1c..9aa0f7792 100644 --- a/src/output_plugin.c +++ b/src/OutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,11 @@ */ #include "config.h" + +extern "C" { #include "output_plugin.h" +} + #include "output_internal.h" struct audio_output * diff --git a/src/output_print.c b/src/OutputPrint.cxx index 483648ca2..240ea967b 100644 --- a/src/output_print.c +++ b/src/OutputPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,15 +23,15 @@ */ #include "config.h" -#include "output_print.h" +#include "OutputPrint.hxx" +#include "OutputAll.hxx" #include "output_internal.h" -#include "output_all.h" -#include "client.h" +#include "Client.hxx" void -printAudioDevices(struct client *client) +printAudioDevices(Client *client) { - unsigned n = audio_output_count(); + const unsigned n = audio_output_count(); for (unsigned i = 0; i < n; ++i) { const struct audio_output *ao = audio_output_get(i); diff --git a/src/output_print.h b/src/OutputPrint.hxx index e02f4e9f5..78717d0af 100644 --- a/src/output_print.h +++ b/src/OutputPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,12 +22,12 @@ * */ -#ifndef OUTPUT_PRINT_H -#define OUTPUT_PRINT_H +#ifndef MPD_OUTPUT_PRINT_HXX +#define MPD_OUTPUT_PRINT_HXX -struct client; +class Client; void -printAudioDevices(struct client *client); +printAudioDevices(Client *client); #endif diff --git a/src/output_state.c b/src/OutputState.cxx index 7bcafb36b..27fa34f8f 100644 --- a/src/output_state.c +++ b/src/OutputState.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,9 +23,9 @@ */ #include "config.h" -#include "output_state.h" +#include "OutputState.hxx" +#include "OutputAll.hxx" #include "output_internal.h" -#include "output_all.h" #include <glib.h> diff --git a/src/output_state.h b/src/OutputState.hxx index 320a3520a..5ab765ba8 100644 --- a/src/output_state.h +++ b/src/OutputState.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,9 @@ * */ -#ifndef OUTPUT_STATE_H -#define OUTPUT_STATE_H +#ifndef MPD_OUTPUT_STATE_HXX +#define MPD_OUTPUT_STATE_HXX -#include <stdbool.h> #include <stdio.h> bool diff --git a/src/output_thread.c b/src/OutputThread.cxx index 4eef2ccdd..59394e072 100644 --- a/src/output_thread.c +++ b/src/OutputThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,18 +18,23 @@ */ #include "config.h" -#include "output_thread.h" +#include "OutputThread.hxx" + +extern "C" { #include "output_api.h" #include "output_internal.h" -#include "chunk.h" -#include "pipe.h" -#include "player_control.h" #include "pcm_mix.h" #include "filter_plugin.h" #include "filter/convert_filter_plugin.h" -#include "filter/replay_gain_filter_plugin.h" +} + +#include "notify.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "PlayerControl.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" + #include "mpd_error.h" -#include "notify.h" #include "gcc.h" #include <glib.h> @@ -47,7 +52,7 @@ static void ao_command_finished(struct audio_output *ao) ao->command = AO_COMMAND_NONE; g_mutex_unlock(ao->mutex); - notify_signal(&audio_output_client_notify); + audio_output_client_notify.Signal(); g_mutex_lock(ao->mutex); } @@ -315,17 +320,17 @@ ao_wait(struct audio_output *ao) } } -static const char * +static const void * ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, struct filter *replay_gain_filter, unsigned *replay_gain_serial_p, size_t *length_r) { assert(chunk != NULL); - assert(!music_chunk_is_empty(chunk)); - assert(music_chunk_check_format(chunk, &ao->in_audio_format)); + assert(!chunk->IsEmpty()); + assert(chunk->CheckFormat(ao->in_audio_format)); - const char *data = chunk->data; + const void *data = chunk->data; size_t length = chunk->length; (void)ao; @@ -356,14 +361,14 @@ ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, return data; } -static const char * +static const void * ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, size_t *length_r) { GError *error = NULL; size_t length; - const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, + const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, &ao->replay_gain_serial, &length); if (data == NULL) return NULL; @@ -378,7 +383,7 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, if (chunk->other != NULL) { size_t other_length; - const char *other_data = + const void *other_data = ao_chunk_data(ao, chunk->other, ao->other_replay_gain_filter, &ao->other_replay_gain_serial, @@ -399,13 +404,14 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, if (length > other_length) length = other_length; - char *dest = pcm_buffer_get(&ao->cross_fade_buffer, + void *dest = pcm_buffer_get(&ao->cross_fade_buffer, other_length); memcpy(dest, other_data, other_length); - if (!pcm_mix(dest, data, length, ao->in_audio_format.format, + if (!pcm_mix(dest, data, length, + sample_format(ao->in_audio_format.format), 1.0 - chunk->mix_ratio)) { g_warning("Cannot cross-fade format %s", - sample_format_to_string(ao->in_audio_format.format)); + sample_format_to_string(sample_format(ao->in_audio_format.format))); return NULL; } @@ -435,7 +441,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); @@ -446,7 +452,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) /* workaround -Wmaybe-uninitialized false positive */ size = 0; #endif - const char *data = ao_filter_chunk(ao, chunk, &size); + const char *data = (const char *)ao_filter_chunk(ao, chunk, &size); if (data == NULL) { ao_close(ao, false); @@ -578,7 +584,7 @@ static void ao_pause(struct audio_output *ao) static gpointer audio_output_task(gpointer arg) { - struct audio_output *ao = arg; + struct audio_output *ao = (struct audio_output *)arg; g_mutex_lock(ao->mutex); diff --git a/src/output_thread.h b/src/OutputThread.hxx index 5ad9a7527..1a7932162 100644 --- a/src/output_thread.h +++ b/src/OutputThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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_OUTPUT_THREAD_H -#define MPD_OUTPUT_THREAD_H +#ifndef MPD_OUTPUT_THREAD_HXX +#define MPD_OUTPUT_THREAD_HXX struct audio_output; diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx new file mode 100644 index 000000000..33aad1919 --- /dev/null +++ b/src/output/HttpdClient.cxx @@ -0,0 +1,592 @@ +/* + * 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 "HttpdClient.hxx" +#include "HttpdInternal.hxx" +#include "util/fifo_buffer.h" +#include "page.h" +#include "icy_server.h" +#include "glib_socket.h" + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "httpd_output" + +HttpdClient::~HttpdClient() +{ + if (state == RESPONSE) { + if (write_source_id != 0) + g_source_remove(write_source_id); + + if (current_page != nullptr) + page_unref(current_page); + + for (auto page : pages) + page_unref(page); + } else + fifo_buffer_free(input); + + if (metadata) + page_unref(metadata); + + g_source_remove(read_source_id); + g_io_channel_unref(channel); +} + +void +HttpdClient::Close() +{ + httpd_output_remove_client(httpd, this); +} + +void +HttpdClient::LockClose() +{ + const ScopeLock protect(httpd->mutex); + Close(); +} + +void +HttpdClient::BeginResponse() +{ + assert(state != RESPONSE); + + state = RESPONSE; + write_source_id = 0; + current_page = nullptr; + + httpd_output_send_header(httpd, this); +} + +/** + * Handle a line of the HTTP request. + */ +bool +HttpdClient::HandleLine(const char *line) +{ + assert(state != RESPONSE); + + if (state == REQUEST) { + if (strncmp(line, "GET /", 5) != 0) { + /* only GET is supported */ + g_warning("malformed request line from client"); + return false; + } + + line = strchr(line + 5, ' '); + if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) { + /* HTTP/0.9 without request headers */ + BeginResponse(); + return true; + } + + /* after the request line, request headers follow */ + state = HEADERS; + return true; + } else { + if (*line == 0) { + /* empty line: request is finished */ + BeginResponse(); + return true; + } + + if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { + /* Send icy metadata */ + metadata_requested = metadata_supported; + return true; + } + + if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { + /* Send as dlna */ + dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + metadata_supported = false; + metadata_requested = false; + return true; + } + + /* expect more request headers */ + return true; + } +} + +char * +HttpdClient::ReadLine() +{ + assert(state != RESPONSE); + + const ScopeLock protect(httpd->mutex); + + size_t length; + const char *p = (const char *)fifo_buffer_read(input, &length); + if (p == nullptr) + /* empty input buffer */ + return nullptr; + + const char *newline = (const char *)memchr(p, '\n', length); + if (newline == nullptr) + /* incomplete line */ + return nullptr; + + char *line = g_strndup(p, newline - p); + fifo_buffer_consume(input, newline - p + 1); + + /* remove trailing whitespace (e.g. '\r') */ + return g_strchomp(line); +} + +/** + * Sends the status line and response headers to the client. + */ +bool +HttpdClient::SendResponse() +{ + char buffer[1024]; + GError *error = nullptr; + GIOStatus status; + gsize bytes_written; + + assert(state == RESPONSE); + + if (dlna_streaming_requested) { + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" + "Connection: close\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" + "\r\n", + httpd->content_type); + + } else if (metadata_requested) { + gchar *metadata_header; + + metadata_header = + icy_server_metadata_header(httpd->name, httpd->genre, + httpd->website, + httpd->content_type, + metaint); + + g_strlcpy(buffer, metadata_header, sizeof(buffer)); + + g_free(metadata_header); + + } else { /* revert to a normal HTTP request */ + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + httpd->content_type); + } + + status = g_io_channel_write_chars(channel, + buffer, strlen(buffer), + &bytes_written, &error); + + switch (status) { + case G_IO_STATUS_NORMAL: + case G_IO_STATUS_AGAIN: + return true; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + Close(); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + g_warning("failed to write to client: %s", error->message); + g_error_free(error); + + Close(); + return false; + } + + /* unreachable */ + Close(); + return false; +} + +bool +HttpdClient::Received() +{ + assert(state != RESPONSE); + + char *line; + bool success; + + while ((line = ReadLine()) != nullptr) { + success = HandleLine(line); + g_free(line); + if (!success) { + assert(state != RESPONSE); + return false; + } + + if (state == RESPONSE) { + if (!fifo_buffer_is_empty(input)) { + g_warning("unexpected input from client"); + return false; + } + + fifo_buffer_free(input); + + return SendResponse(); + } + } + + return true; +} + +bool +HttpdClient::Read() +{ + size_t max_length; + GError *error = nullptr; + GIOStatus status; + gsize bytes_read; + + if (state == RESPONSE) { + /* the client has already sent the request, and he + must not send more */ + char buffer[1]; + + status = g_io_channel_read_chars(channel, buffer, + sizeof(buffer), &bytes_read, + nullptr); + if (status == G_IO_STATUS_NORMAL) + g_warning("unexpected input from client"); + + return false; + } + + char *p = (char *)fifo_buffer_write(input, &max_length); + if (p == nullptr) { + g_warning("buffer overflow"); + return false; + } + + status = g_io_channel_read_chars(channel, p, max_length, + &bytes_read, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + fifo_buffer_append(input, bytes_read); + return Received(); + + case G_IO_STATUS_AGAIN: + /* try again later, after select() */ + return true; + + case G_IO_STATUS_EOF: + /* peer disconnected */ + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + g_warning("failed to read from client: %s", + error->message); + g_error_free(error); + return false; + } + + /* unreachable */ + return false; +} + +static gboolean +httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, + gpointer data) +{ + HttpdClient *client = (HttpdClient *)data; + + if (condition == G_IO_IN && client->Read()) { + return true; + } else { + client->LockClose(); + return false; + } +} + +HttpdClient::HttpdClient(httpd_output *_httpd, int _fd, + bool _metadata_supported) + :httpd(_httpd), + channel(g_io_channel_new_socket(_fd)), + input(fifo_buffer_new(4096)), + state(REQUEST), + dlna_streaming_requested(false), + metadata_supported(_metadata_supported), + metadata_requested(false), metadata_sent(true), + metaint(8192), /*TODO: just a std value */ + metadata(nullptr), + metadata_current_position(0), metadata_fill(0) +{ + /* GLib is responsible for closing the file descriptor */ + g_io_channel_set_close_on_unref(channel, true); + /* NULL encoding means the stream is binary safe */ + g_io_channel_set_encoding(channel, nullptr, nullptr); + /* we prefer to do buffering */ + g_io_channel_set_buffered(channel, false); + + read_source_id = g_io_add_watch(channel, + GIOCondition(G_IO_IN|G_IO_ERR|G_IO_HUP), + httpd_client_in_event, this); +} + +size_t +HttpdClient::GetQueueSize() const +{ + if (state != RESPONSE) + return 0; + + size_t size = 0; + for (auto page : pages) + size += page->size; + return size; +} + +void +HttpdClient::CancelQueue() +{ + if (state != RESPONSE) + return; + + for (auto page : pages) + page_unref(page); + pages.clear(); + + if (write_source_id != 0 && current_page == nullptr) { + g_source_remove(write_source_id); + write_source_id = 0; + } +} + +static GIOStatus +write_page_to_channel(GIOChannel *channel, + const struct page *page, size_t position, + gsize *bytes_written_r, GError **error) +{ + assert(channel != nullptr); + assert(page != nullptr); + assert(position < page->size); + + return g_io_channel_write_chars(channel, + (const gchar*)page->data + position, + page->size - position, + bytes_written_r, error); +} + +static GIOStatus +write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, + size_t position, gint n, + gsize *bytes_written_r, GError **error) +{ + GIOStatus status; + + assert(channel != nullptr); + assert(page != nullptr); + assert(position < page->size); + + if (n == -1) { + status = write_page_to_channel (channel, page, position, + bytes_written_r, error); + } else { + status = g_io_channel_write_chars(channel, + (const gchar*)page->data + position, + n, bytes_written_r, error); + } + + return status; +} + +int +HttpdClient::GetBytesTillMetaData() const +{ + if (metadata_requested && + current_page->size - current_position > metaint - metadata_fill) + return metaint - metadata_fill; + + return -1; +} + +inline bool +HttpdClient::Write() +{ + GError *error = nullptr; + GIOStatus status; + gsize bytes_written; + + const ScopeLock protect(httpd->mutex); + + assert(state == RESPONSE); + + if (write_source_id == 0) + /* another thread has removed the event source while + this thread was waiting for httpd->mutex */ + return false; + + if (current_page == nullptr) { + current_page = pages.front(); + pages.pop_front(); + current_position = 0; + } + + const gint bytes_to_write = GetBytesTillMetaData(); + if (bytes_to_write == 0) { + gint metadata_to_write; + + metadata_to_write = metadata_current_position; + + if (!metadata_sent) { + status = write_page_to_channel(channel, + metadata, + metadata_to_write, + &bytes_written, &error); + + metadata_current_position += bytes_written; + + if (metadata->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + metadata_sent = true; + } + } else { + struct page *empty_meta; + guchar empty_data = 0; + + empty_meta = page_new_copy(&empty_data, 1); + + status = write_page_to_channel(channel, + empty_meta, + metadata_to_write, + &bytes_written, &error); + + metadata_current_position += bytes_written; + + if (empty_meta->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + } + } + + bytes_written = 0; + } else { + status = write_n_bytes_to_channel(channel, current_page, + current_position, bytes_to_write, + &bytes_written, &error); + } + + switch (status) { + case G_IO_STATUS_NORMAL: + current_position += bytes_written; + assert(current_position <= current_page->size); + + if (metadata_requested) + metadata_fill += bytes_written; + + if (current_position >= current_page->size) { + page_unref(current_page); + current_page = nullptr; + + if (pages.empty()) { + /* all pages are sent: remove the + event source */ + write_source_id = 0; + + return false; + } + } + + return true; + + case G_IO_STATUS_AGAIN: + return true; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + Close(); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + g_warning("failed to write to client: %s", error->message); + g_error_free(error); + + Close(); + return false; + } + + /* unreachable */ + Close(); + return false; +} + +static gboolean +httpd_client_out_event(gcc_unused GIOChannel *source, + gcc_unused GIOCondition condition, gpointer data) +{ + assert(condition == G_IO_OUT); + + HttpdClient *client = (HttpdClient *)data; + return client->Write(); +} + +void +HttpdClient::PushPage(struct page *page) +{ + if (state != RESPONSE) + /* the client is still writing the HTTP request */ + return; + + page_ref(page); + pages.push_back(page); + + if (write_source_id == 0) + write_source_id = g_io_add_watch(channel, G_IO_OUT, + httpd_client_out_event, + this); +} + +void +HttpdClient::PushMetaData(struct page *page) +{ + if (metadata) { + page_unref(metadata); + metadata = nullptr; + } + + g_return_if_fail (page); + + page_ref(page); + metadata = page; + metadata_sent = false; +} diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx new file mode 100644 index 000000000..d3ed2746d --- /dev/null +++ b/src/output/HttpdClient.hxx @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2003-2013 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_HTTPD_CLIENT_HXX +#define MPD_OUTPUT_HTTPD_CLIENT_HXX + +#include "gcc.h" + +#include <glib.h> + +#include <list> + +#include <stddef.h> + +struct httpd_output; +struct page; + +class HttpdClient final { + /** + * The httpd output object this client is connected to. + */ + httpd_output *const httpd; + + /** + * The TCP socket. + */ + GIOChannel *channel; + + /** + * The GLib main loop source id for reading from the socket, + * and to detect errors. + */ + guint read_source_id; + + /** + * The GLib main loop source id for writing to the socket. If + * 0, then there is no event source currently (because there + * are no queued pages). + */ + guint write_source_id; + + /** + * For buffered reading. This pointer is only valid while the + * HTTP request is read. + */ + struct fifo_buffer *input; + + /** + * The current state of the client. + */ + enum { + /** reading the request line */ + REQUEST, + + /** reading the request headers */ + HEADERS, + + /** sending the HTTP response */ + RESPONSE, + } state; + + /** + * A queue of #page objects to be sent to the client. + */ + std::list<page *> pages; + + /** + * The #page which is currently being sent to the client. + */ + page *current_page; + + /** + * The amount of bytes which were already sent from + * #current_page. + */ + size_t current_position; + + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + + /* ICY */ + + /** + * Do we support sending Icy-Metadata to the client? This is + * disabled if the httpd audio output uses encoder tags. + */ + bool metadata_supported; + + /** + * If we should sent icy metadata. + */ + bool metadata_requested; + + /** + * If the current metadata was already sent to the client. + */ + bool metadata_sent; + + /** + * The amount of streaming data between each metadata block + */ + guint metaint; + + /** + * The metadata as #page which is currently being sent to the client. + */ + page *metadata; + + /* + * The amount of bytes which were already sent from the metadata. + */ + size_t metadata_current_position; + + /** + * The amount of streaming data sent to the client + * since the last icy information was sent. + */ + guint metadata_fill; + +public: + /** + * @param httpd the HTTP output device + * @param fd the socket file descriptor + */ + HttpdClient(httpd_output *httpd, int _fd, bool _metadata_supported); + + /** + * Note: this does not remove the client from the + * #httpd_output object. + */ + ~HttpdClient(); + + /** + * Frees the client and removes it from the server's client list. + */ + void Close(); + + void LockClose(); + + /** + * Returns the total size of this client's page queue. + */ + gcc_pure + size_t GetQueueSize() const; + + /** + * Clears the page queue. + */ + void CancelQueue(); + + bool Read(); + + /** + * Data has been received from the client and it is appended + * to the input buffer. + */ + bool Received(); + + /** + * Check if a complete line of input is present in the input + * buffer, and duplicates it. It is removed from the input + * buffer. The return value has to be freed with g_free(). + */ + char *ReadLine(); + + /** + * Handle a line of the HTTP request. + */ + bool HandleLine(const char *line); + + /** + * Switch the client to the "RESPONSE" state. + */ + void BeginResponse(); + + /** + * Sends the status line and response headers to the client. + */ + bool SendResponse(); + + gcc_pure + int GetBytesTillMetaData() const; + + bool Write(); + + /** + * Appends a page to the client's queue. + */ + void PushPage(page *page); + + /** + * Sends the passed metadata. + */ + void PushMetaData(page *page); +}; + +#endif diff --git a/src/output/httpd_internal.h b/src/output/HttpdInternal.hxx index 5dcb8ab9b..601c43162 100644 --- a/src/output/httpd_internal.h +++ b/src/output/HttpdInternal.hxx @@ -25,14 +25,18 @@ #ifndef MPD_OUTPUT_HTTPD_INTERNAL_H #define MPD_OUTPUT_HTTPD_INTERNAL_H +#include "HttpdClient.hxx" #include "output_internal.h" #include "timer.h" +#include "thread/Mutex.hxx" #include <glib.h> +#include <forward_list> + #include <stdbool.h> -struct httpd_client; +class HttpdClient; struct httpd_output { struct audio_output base; @@ -65,7 +69,7 @@ struct httpd_output { * This mutex protects the listener socket and the client * list. */ - GMutex *mutex; + mutable Mutex mutex; /** * A #timer object to synchronize this output with the @@ -105,7 +109,7 @@ struct httpd_output { * A linked list containing all clients which are currently * connected. */ - GList *clients; + std::forward_list<HttpdClient> clients; /** * A temporary buffer for the httpd_output_read_page() @@ -125,7 +129,7 @@ struct httpd_output { */ void httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client); + HttpdClient *client); /** * Sends the encoder header to the client. This is called right after @@ -133,6 +137,6 @@ httpd_output_remove_client(struct httpd_output *httpd, */ void httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client); + HttpdClient *client); #endif diff --git a/src/output/httpd_output_plugin.c b/src/output/HttpdOutputPlugin.cxx index 1d730df7f..d1a1a36df 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/HttpdOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,9 @@ */ #include "config.h" -#include "httpd_output_plugin.h" -#include "httpd_internal.h" -#include "httpd_client.h" +#include "HttpdOutputPlugin.hxx" +#include "HttpdInternal.hxx" +#include "HttpdClient.hxx" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" @@ -28,7 +28,8 @@ #include "page.h" #include "icy_server.h" #include "fd_util.h" -#include "server_socket.h" +#include "ServerSocket.hxx" +#include "Main.hxx" #include <assert.h> @@ -60,9 +61,9 @@ httpd_output_quark(void) */ G_GNUC_PURE static bool -httpd_output_has_clients(const struct httpd_output *httpd) +httpd_output_has_clients(const httpd_output *httpd) { - return httpd->clients != NULL; + return !httpd->clients.empty(); } /** @@ -70,12 +71,10 @@ httpd_output_has_clients(const struct httpd_output *httpd) */ G_GNUC_PURE static bool -httpd_output_lock_has_clients(const struct httpd_output *httpd) +httpd_output_lock_has_clients(const httpd_output *httpd) { - g_mutex_lock(httpd->mutex); - bool result = httpd_output_has_clients(httpd); - g_mutex_unlock(httpd->mutex); - return result; + const ScopeLock protect(httpd->mutex); + return httpd_output_has_clients(httpd); } static void @@ -83,32 +82,28 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, size_t address_length, int uid, void *ctx); static bool -httpd_output_bind(struct httpd_output *httpd, GError **error_r) +httpd_output_bind(httpd_output *httpd, GError **error_r) { httpd->open = false; - g_mutex_lock(httpd->mutex); - bool success = server_socket_open(httpd->server_socket, error_r); - g_mutex_unlock(httpd->mutex); - - return success; + const ScopeLock protect(httpd->mutex); + return server_socket_open(httpd->server_socket, error_r); } static void -httpd_output_unbind(struct httpd_output *httpd) +httpd_output_unbind(httpd_output *httpd) { assert(!httpd->open); - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); server_socket_close(httpd->server_socket); - g_mutex_unlock(httpd->mutex); } static struct audio_output * httpd_output_init(const struct config_param *param, GError **error) { - struct httpd_output *httpd = g_new(struct httpd_output, 1); + httpd_output *httpd = new httpd_output(); if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { g_free(httpd); return NULL; @@ -140,7 +135,8 @@ httpd_output_init(const struct config_param *param, /* set up bind_to_address */ - httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); + httpd->server_socket = server_socket_new(*main_loop, + httpd_listen_in_event, httpd); const char *bind_to_address = config_get_block_string(param, "bind_to_address", NULL); @@ -174,50 +170,44 @@ httpd_output_init(const struct config_param *param, httpd->content_type = "application/octet-stream"; } - httpd->mutex = g_mutex_new(); - return &httpd->base; } static void httpd_output_finish(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (httpd->metadata) page_unref(httpd->metadata); encoder_finish(httpd->encoder); server_socket_free(httpd->server_socket); - g_mutex_free(httpd->mutex); ao_base_finish(&httpd->base); - g_free(httpd); + delete httpd; } /** - * Creates a new #httpd_client object and adds it into the + * Creates a new #HttpdClient object and adds it into the * httpd_output.clients linked list. */ static void -httpd_client_add(struct httpd_output *httpd, int fd) +httpd_client_add(httpd_output *httpd, int fd) { - struct httpd_client *client = - httpd_client_new(httpd, fd, - httpd->encoder->plugin->tag == NULL); - - httpd->clients = g_list_prepend(httpd->clients, client); + httpd->clients.emplace_front(httpd, fd, + httpd->encoder->plugin->tag == NULL); httpd->clients_cnt++; /* pass metadata to client */ if (httpd->metadata) - httpd_client_send_metadata(client, httpd->metadata); + httpd->clients.front().PushMetaData(httpd->metadata); } static void httpd_listen_in_event(int fd, const struct sockaddr *address, size_t address_length, G_GNUC_UNUSED int uid, void *ctx) { - struct httpd_output *httpd = ctx; + httpd_output *httpd = (httpd_output *)ctx; /* the listener socket has become readable - a client has connected */ @@ -238,7 +228,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, progname, hostaddr); g_free(hostaddr); close_socket(fd); - g_mutex_unlock(httpd->mutex); return; } @@ -249,7 +238,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, (void)address_length; #endif /* HAVE_WRAP */ - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); if (fd >= 0) { /* can we allow additional client */ @@ -262,8 +251,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, } else if (fd < 0 && errno != EINTR) { g_warning("accept() failed: %s", g_strerror(errno)); } - - g_mutex_unlock(httpd->mutex); } /** @@ -271,7 +258,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, * as a new #page object. */ static struct page * -httpd_output_read_page(struct httpd_output *httpd) +httpd_output_read_page(httpd_output *httpd) { if (httpd->unflushed_input >= 65536) { /* we have fed a lot of input into the encoder, but it @@ -301,7 +288,7 @@ httpd_output_read_page(struct httpd_output *httpd) } static bool -httpd_output_encoder_open(struct httpd_output *httpd, +httpd_output_encoder_open(httpd_output *httpd, struct audio_format *audio_format, GError **error) { @@ -321,7 +308,7 @@ httpd_output_encoder_open(struct httpd_output *httpd, static bool httpd_output_enable(struct audio_output *ao, GError **error_r) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; return httpd_output_bind(httpd, error_r); } @@ -329,7 +316,7 @@ httpd_output_enable(struct audio_output *ao, GError **error_r) static void httpd_output_disable(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; httpd_output_unbind(httpd); } @@ -338,82 +325,75 @@ static bool httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; + + assert(httpd->clients.empty()); - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); /* open the encoder */ - if (!httpd_output_encoder_open(httpd, audio_format, error)) { - g_mutex_unlock(httpd->mutex); + if (!httpd_output_encoder_open(httpd, audio_format, error)) return false; - } /* initialize other attributes */ - httpd->clients = NULL; httpd->clients_cnt = 0; httpd->timer = timer_new(audio_format); httpd->open = true; - g_mutex_unlock(httpd->mutex); return true; } static void -httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_free(client); -} - -static void httpd_output_close(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); httpd->open = false; timer_free(httpd->timer); - g_list_foreach(httpd->clients, httpd_client_delete, NULL); - g_list_free(httpd->clients); + httpd->clients.clear(); if (httpd->header != NULL) page_unref(httpd->header); encoder_close(httpd->encoder); - - g_mutex_unlock(httpd->mutex); } void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client) +httpd_output_remove_client(httpd_output *httpd, HttpdClient *client) { assert(httpd != NULL); + assert(httpd->clients_cnt > 0); assert(client != NULL); - httpd->clients = g_list_remove(httpd->clients, client); - httpd->clients_cnt--; + for (auto prev = httpd->clients.before_begin(), i = std::next(prev);; + prev = i, i = std::next(prev)) { + assert(i != httpd->clients.end()); + if (&*i == client) { + httpd->clients.erase_after(prev); + httpd->clients_cnt--; + break; + } + } } void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client) +httpd_output_send_header(httpd_output *httpd, HttpdClient *client) { if (httpd->header != NULL) - httpd_client_send(client, httpd->header); + client->PushPage(httpd->header); } static unsigned httpd_output_delay(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) { /* if there's no client and this output is paused, @@ -433,51 +413,35 @@ httpd_output_delay(struct audio_output *ao) : 0; } -static void -httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - if (httpd_client_queue_size(client) > 256 * 1024) { - g_debug("client is too slow, flushing its queue"); - httpd_client_cancel(client); - } -} - -static void -httpd_client_send_page(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *page = user_data; - - httpd_client_send(client, page); -} - /** * Broadcasts a page struct to all clients. */ static void -httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page) +httpd_output_broadcast_page(httpd_output *httpd, struct page *page) { assert(page != NULL); - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_send_page, page); - g_mutex_unlock(httpd->mutex); + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.PushPage(page); } /** * Broadcasts data from the encoder to all clients. */ static void -httpd_output_encoder_to_clients(struct httpd_output *httpd) +httpd_output_encoder_to_clients(httpd_output *httpd) { - struct page *page; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_check_queue, NULL); - g_mutex_unlock(httpd->mutex); + httpd->mutex.lock(); + for (auto &client : httpd->clients) { + if (client.GetQueueSize() > 256 * 1024) { + g_debug("client is too slow, flushing its queue"); + client.CancelQueue(); + } + } + httpd->mutex.unlock(); + struct page *page; while ((page = httpd_output_read_page(httpd)) != NULL) { httpd_output_broadcast_page(httpd, page); page_unref(page); @@ -485,7 +449,7 @@ httpd_output_encoder_to_clients(struct httpd_output *httpd) } static bool -httpd_output_encode_and_play(struct httpd_output *httpd, +httpd_output_encode_and_play(httpd_output *httpd, const void *chunk, size_t size, GError **error) { if (!encoder_write(httpd->encoder, chunk, size, error)) @@ -502,7 +466,7 @@ static size_t httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (httpd_output_lock_has_clients(httpd)) { if (!httpd_output_encode_and_play(httpd, chunk, size, error_r)) @@ -519,10 +483,10 @@ httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, static bool httpd_output_pause(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (httpd_output_lock_has_clients(httpd)) { - static const char silence[1020]; + static const char silence[1020] = { 0 }; return httpd_output_play(ao, silence, sizeof(silence), NULL) > 0; } else { @@ -531,18 +495,9 @@ httpd_output_pause(struct audio_output *ao) } static void -httpd_send_metadata(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *icy_metadata = user_data; - - httpd_client_send_metadata(client, icy_metadata); -} - -static void httpd_output_tag(struct audio_output *ao, const struct tag *tag) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; assert(tag != NULL); @@ -581,43 +536,37 @@ httpd_output_tag(struct audio_output *ao, const struct tag *tag) TAG_ARTIST, TAG_TITLE, TAG_NUM_OF_ITEM_TYPES); if (httpd->metadata != NULL) { - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, - httpd_send_metadata, httpd->metadata); - g_mutex_unlock(httpd->mutex); + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.PushMetaData(httpd->metadata); } } } static void -httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_cancel(client); -} - -static void httpd_output_cancel(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); - g_mutex_unlock(httpd->mutex); + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.CancelQueue(); } const struct audio_output_plugin httpd_output_plugin = { - .name = "httpd", - .init = httpd_output_init, - .finish = httpd_output_finish, - .enable = httpd_output_enable, - .disable = httpd_output_disable, - .open = httpd_output_open, - .close = httpd_output_close, - .delay = httpd_output_delay, - .send_tag = httpd_output_tag, - .play = httpd_output_play, - .pause = httpd_output_pause, - .cancel = httpd_output_cancel, + "httpd", + nullptr, + httpd_output_init, + httpd_output_finish, + httpd_output_enable, + httpd_output_disable, + httpd_output_open, + httpd_output_close, + httpd_output_delay, + httpd_output_tag, + httpd_output_play, + nullptr, + httpd_output_cancel, + httpd_output_pause, + nullptr, }; diff --git a/src/output/httpd_output_plugin.h b/src/output/HttpdOutputPlugin.hxx index d0eb1533f..c74d2bd4a 100644 --- a/src/output/httpd_output_plugin.h +++ b/src/output/HttpdOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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_HTTPD_OUTPUT_PLUGIN_H -#define MPD_HTTPD_OUTPUT_PLUGIN_H +#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX +#define MPD_HTTPD_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin httpd_output_plugin; diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c index 022be0b4a..3d6171ae2 100644 --- a/src/output/fifo_output_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -20,7 +20,6 @@ #include "config.h" #include "fifo_output_plugin.h" #include "output_api.h" -#include "utils.h" #include "timer.h" #include "fd_util.h" #include "open.h" diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c deleted file mode 100644 index 72de90457..000000000 --- a/src/output/httpd_client.c +++ /dev/null @@ -1,764 +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 "httpd_client.h" -#include "httpd_internal.h" -#include "fifo_buffer.h" -#include "page.h" -#include "icy_server.h" -#include "glib_socket.h" - -#include <stdbool.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -struct httpd_client { - /** - * The httpd output object this client is connected to. - */ - struct httpd_output *httpd; - - /** - * The TCP socket. - */ - GIOChannel *channel; - - /** - * The GLib main loop source id for reading from the socket, - * and to detect errors. - */ - guint read_source_id; - - /** - * The GLib main loop source id for writing to the socket. If - * 0, then there is no event source currently (because there - * are no queued pages). - */ - guint write_source_id; - - /** - * For buffered reading. This pointer is only valid while the - * HTTP request is read. - */ - struct fifo_buffer *input; - - /** - * The current state of the client. - */ - enum { - /** reading the request line */ - REQUEST, - - /** reading the request headers */ - HEADERS, - - /** sending the HTTP response */ - RESPONSE, - } state; - - /** - * A queue of #page objects to be sent to the client. - */ - GQueue *pages; - - /** - * The #page which is currently being sent to the client. - */ - struct page *current_page; - - /** - * The amount of bytes which were already sent from - * #current_page. - */ - size_t current_position; - - /** - * If DLNA streaming was an option. - */ - bool dlna_streaming_requested; - - /* ICY */ - - /** - * Do we support sending Icy-Metadata to the client? This is - * disabled if the httpd audio output uses encoder tags. - */ - bool metadata_supported; - - /** - * If we should sent icy metadata. - */ - bool metadata_requested; - - /** - * If the current metadata was already sent to the client. - */ - bool metadata_sent; - - /** - * The amount of streaming data between each metadata block - */ - guint metaint; - - /** - * The metadata as #page which is currently being sent to the client. - */ - struct page *metadata; - - /* - * The amount of bytes which were already sent from the metadata. - */ - size_t metadata_current_position; - - /** - * The amount of streaming data sent to the client - * since the last icy information was sent. - */ - guint metadata_fill; -}; - -static void -httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct page *page = data; - - page_unref(page); -} - -void -httpd_client_free(struct httpd_client *client) -{ - assert(client != NULL); - - if (client->state == RESPONSE) { - if (client->write_source_id != 0) - g_source_remove(client->write_source_id); - - if (client->current_page != NULL) - page_unref(client->current_page); - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_free(client->pages); - } else - fifo_buffer_free(client->input); - - if (client->metadata) - page_unref (client->metadata); - - g_source_remove(client->read_source_id); - g_io_channel_unref(client->channel); - g_free(client); -} - -/** - * Frees the client and removes it from the server's client list. - */ -static void -httpd_client_close(struct httpd_client *client) -{ - assert(client != NULL); - - httpd_output_remove_client(client->httpd, client); - httpd_client_free(client); -} - -/** - * Switch the client to the "RESPONSE" state. - */ -static void -httpd_client_begin_response(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - client->state = RESPONSE; - client->write_source_id = 0; - client->pages = g_queue_new(); - client->current_page = NULL; - - httpd_output_send_header(client->httpd, client); -} - -/** - * Handle a line of the HTTP request. - */ -static bool -httpd_client_handle_line(struct httpd_client *client, const char *line) -{ - assert(client->state != RESPONSE); - - if (client->state == REQUEST) { - if (strncmp(line, "GET /", 5) != 0) { - /* only GET is supported */ - g_warning("malformed request line from client"); - return false; - } - - line = strchr(line + 5, ' '); - if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) { - /* HTTP/0.9 without request headers */ - httpd_client_begin_response(client); - return true; - } - - /* after the request line, request headers follow */ - client->state = HEADERS; - return true; - } else { - if (*line == 0) { - /* empty line: request is finished */ - httpd_client_begin_response(client); - return true; - } - - if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { - /* Send icy metadata */ - client->metadata_requested = - client->metadata_supported; - return true; - } - - if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { - /* Send as dlna */ - client->dlna_streaming_requested = true; - /* metadata is not supported by dlna streaming, so disable it */ - client->metadata_supported = false; - client->metadata_requested = false; - return true; - } - - /* expect more request headers */ - return true; - } -} - -/** - * Check if a complete line of input is present in the input buffer, - * and duplicates it. It is removed from the input buffer. The - * return value has to be freed with g_free(). - */ -static char * -httpd_client_read_line(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - /* empty input buffer */ - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - /* incomplete line */ - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - /* remove trailing whitespace (e.g. '\r') */ - return g_strchomp(line); -} - -/** - * Sends the status line and response headers to the client. - */ -static bool -httpd_client_send_response(struct httpd_client *client) -{ - char buffer[1024]; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->state == RESPONSE); - - if (client->dlna_streaming_requested) { - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 206 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: 10000\r\n" - "Content-RangeX: 0-1000000/1000000\r\n" - "transferMode.dlna.org: Streaming\r\n" - "Accept-Ranges: bytes\r\n" - "Connection: close\r\n" - "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" - "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" - "\r\n", - client->httpd->content_type); - - } else if (client->metadata_requested) { - gchar *metadata_header; - - metadata_header = icy_server_metadata_header( - client->httpd->name, - client->httpd->genre, - client->httpd->website, - client->httpd->content_type, - client->metaint); - - g_strlcpy(buffer, metadata_header, sizeof(buffer)); - - g_free(metadata_header); - - } else { /* revert to a normal HTTP request */ - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - client->httpd->content_type); - } - - status = g_io_channel_write_chars(client->channel, - buffer, strlen(buffer), - &bytes_written, &error); - - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - return false; - } - - /* unreachable */ - httpd_client_close(client); - return false; -} - -/** - * Data has been received from the client and it is appended to the - * input buffer. - */ -static bool -httpd_client_received(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - char *line; - bool success; - - while ((line = httpd_client_read_line(client)) != NULL) { - success = httpd_client_handle_line(client, line); - g_free(line); - if (!success) { - assert(client->state != RESPONSE); - return false; - } - - if (client->state == RESPONSE) { - if (!fifo_buffer_is_empty(client->input)) { - g_warning("unexpected input from client"); - return false; - } - - fifo_buffer_free(client->input); - - return httpd_client_send_response(client); - } - } - - return true; -} - -static bool -httpd_client_read(struct httpd_client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - if (client->state == RESPONSE) { - /* the client has already sent the request, and he - must not send more */ - char buffer[1]; - - status = g_io_channel_read_chars(client->channel, buffer, - sizeof(buffer), &bytes_read, - NULL); - if (status == G_IO_STATUS_NORMAL) - g_warning("unexpected input from client"); - - return false; - } - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("buffer overflow"); - return false; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - fifo_buffer_append(client->input, bytes_read); - return httpd_client_received(client); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return true; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client: %s", - error->message); - g_error_free(error); - return false; - } - - /* unreachable */ - return false; -} - -static gboolean -httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - bool ret; - - g_mutex_lock(httpd->mutex); - - if (condition == G_IO_IN && httpd_client_read(client)) { - ret = true; - } else { - httpd_client_close(client); - ret = false; - } - - g_mutex_unlock(httpd->mutex); - - return ret; -} - -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported) -{ - struct httpd_client *client = g_new(struct httpd_client, 1); - - client->httpd = httpd; - - client->channel = g_io_channel_new_socket(fd); - - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->read_source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - httpd_client_in_event, client); - - client->input = fifo_buffer_new(4096); - client->state = REQUEST; - - client->dlna_streaming_requested = false; - client->metadata_supported = metadata_supported; - client->metadata_requested = false; - client->metadata_sent = true; - client->metaint = 8192; /*TODO: just a std value */ - client->metadata = NULL; - client->metadata_current_position = 0; - client->metadata_fill = 0; - - return client; -} - -static void -httpd_client_add_page_size(gpointer data, gpointer user_data) -{ - struct page *page = data; - size_t *size = user_data; - - *size += page->size; -} - -size_t -httpd_client_queue_size(const struct httpd_client *client) -{ - size_t size = 0; - - if (client->state != RESPONSE) - return 0; - - g_queue_foreach(client->pages, httpd_client_add_page_size, &size); - return size; -} - -void -httpd_client_cancel(struct httpd_client *client) -{ - if (client->state != RESPONSE) - return; - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_clear(client->pages); - - if (client->write_source_id != 0 && client->current_page == NULL) { - g_source_remove(client->write_source_id); - client->write_source_id = 0; - } -} - -static GIOStatus -write_page_to_channel(GIOChannel *channel, - const struct page *page, size_t position, - gsize *bytes_written_r, GError **error) -{ - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - return g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - page->size - position, - bytes_written_r, error); -} - -static GIOStatus -write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, - size_t position, gint n, - gsize *bytes_written_r, GError **error) -{ - GIOStatus status; - - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - if (n == -1) { - status = write_page_to_channel (channel, page, position, - bytes_written_r, error); - } else { - status = g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - n, bytes_written_r, error); - } - - return status; -} - -static gint -bytes_left_till_metadata (struct httpd_client *client) -{ - assert(client != NULL); - - if (client->metadata_requested && - client->current_page->size - client->current_position - > client->metaint - client->metadata_fill) - return client->metaint - client->metadata_fill; - - return -1; -} - -static gboolean -httpd_client_out_event(GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - gint bytes_to_write; - - g_mutex_lock(httpd->mutex); - - assert(condition == G_IO_OUT); - assert(client->state == RESPONSE); - - if (client->write_source_id == 0) { - /* another thread has removed the event source while - this thread was waiting for httpd->mutex */ - g_mutex_unlock(httpd->mutex); - return false; - } - - if (client->current_page == NULL) { - client->current_page = g_queue_pop_head(client->pages); - client->current_position = 0; - } - - bytes_to_write = bytes_left_till_metadata(client); - - if (bytes_to_write == 0) { - gint metadata_to_write; - - metadata_to_write = client->metadata_current_position; - - if (!client->metadata_sent) { - status = write_page_to_channel(source, - client->metadata, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (client->metadata->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - client->metadata_sent = true; - } - } else { - struct page *empty_meta; - guchar empty_data = 0; - - empty_meta = page_new_copy(&empty_data, 1); - - status = write_page_to_channel(source, - empty_meta, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (empty_meta->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - } - } - - bytes_written = 0; - } else { - status = write_n_bytes_to_channel(source, client->current_page, - client->current_position, bytes_to_write, - &bytes_written, &error); - } - - switch (status) { - case G_IO_STATUS_NORMAL: - client->current_position += bytes_written; - assert(client->current_position <= client->current_page->size); - - if (client->metadata_requested) - client->metadata_fill += bytes_written; - - if (client->current_position >= client->current_page->size) { - page_unref(client->current_page); - client->current_page = NULL; - - if (g_queue_is_empty(client->pages)) { - /* all pages are sent: remove the - event source */ - client->write_source_id = 0; - - g_mutex_unlock(httpd->mutex); - return false; - } - } - - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_AGAIN: - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - } - - /* unreachable */ - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; -} - -void -httpd_client_send(struct httpd_client *client, struct page *page) -{ - if (client->state != RESPONSE) - /* the client is still writing the HTTP request */ - return; - - page_ref(page); - g_queue_push_tail(client->pages, page); - - if (client->write_source_id == 0) - client->write_source_id = - g_io_add_watch(client->channel, G_IO_OUT, - httpd_client_out_event, client); -} - -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page) -{ - if (client->metadata) { - page_unref(client->metadata); - client->metadata = NULL; - } - - g_return_if_fail (page); - - page_ref(page); - client->metadata = page; - client->metadata_sent = false; -} diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h deleted file mode 100644 index 739163f42..000000000 --- a/src/output/httpd_client.h +++ /dev/null @@ -1,71 +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_OUTPUT_HTTPD_CLIENT_H -#define MPD_OUTPUT_HTTPD_CLIENT_H - -#include <glib.h> - -#include <stdbool.h> - -struct httpd_client; -struct httpd_output; -struct page; - -/** - * Creates a new #httpd_client object - * - * @param httpd the HTTP output device - * @param fd the socket file descriptor - */ -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported); - -/** - * Frees memory and resources allocated by the #httpd_client object. - * This does not remove it from the #httpd_output object. - */ -void -httpd_client_free(struct httpd_client *client); - -/** - * Returns the total size of this client's page queue. - */ -size_t -httpd_client_queue_size(const struct httpd_client *client); - -/** - * Clears the page queue. - */ -void -httpd_client_cancel(struct httpd_client *client); - -/** - * Appends a page to the client's queue. - */ -void -httpd_client_send(struct httpd_client *client, struct page *page); - -/** - * Sends the passed metadata. - */ -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page); - -#endif diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c index cd51fca20..7b42589f7 100644 --- a/src/output/osx_output_plugin.c +++ b/src/output/osx_output_plugin.c @@ -20,7 +20,7 @@ #include "config.h" #include "osx_output_plugin.h" #include "output_api.h" -#include "fifo_buffer.h" +#include "util/fifo_buffer.h" #include <glib.h> #include <CoreAudio/AudioHardware.h> diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c index e267427df..457fa9f04 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/pulse_output_plugin.c @@ -21,7 +21,7 @@ #include "pulse_output_plugin.h" #include "output_api.h" #include "mixer_list.h" -#include "mixer/pulse_mixer_plugin.h" +#include "mixer/PulseMixerPlugin.h" #include <glib.h> diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 02a51f27b..bcc8004a7 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; @@ -30,6 +30,10 @@ struct pa_cvolume; extern const struct audio_output_plugin pulse_output_plugin; +#ifdef __cplusplus +extern "C" { +#endif + void pulse_output_lock(struct pulse_output *po); @@ -46,4 +50,8 @@ bool pulse_output_set_volume(struct pulse_output *po, const struct pa_cvolume *volume, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c index 56456a0ea..56d7a88b1 100644 --- a/src/output/shout_output_plugin.c +++ b/src/output/shout_output_plugin.c @@ -97,23 +97,22 @@ static void free_shout_data(struct shout_data *sd) g_free(sd); } -#define check_block_param(name) { \ - block_param = config_get_block_param(param, name); \ - if (!block_param) { \ - MPD_ERROR("no \"%s\" defined for shout device defined at line " \ - "%i\n", name, param->line); \ - } \ - } +gcc_pure +static const char * +require_block_string(const struct config_param *param, const char *name) +{ + const char *value = config_get_block_string(param, name, NULL); + if (value == NULL) + MPD_ERROR("no \"%s\" defined for shout device defined at line " \ + "%i\n", name, param->line); \ -static struct audio_output * -my_shout_init_driver(const struct config_param *param, - GError **error) + return value; +} + +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,30 +124,18 @@ 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; - - check_block_param("mount"); - char *mount = block_param->value; + const char *host = require_block_string(param, "host"); + const char *mount = require_block_string(param, "mount"); unsigned port = config_get_block_unsigned(param, "port", 0); if (port == 0) { g_set_error(error, shout_output_quark(), 0, "shout port must be configured"); - goto failure; + return false; } - check_block_param("password"); - const char *passwd = block_param->value; - - check_block_param("name"); - const char *name = block_param->value; + const char *passwd = require_block_string(param, "password"); + const char *name = require_block_string(param, "name"); bool public = config_get_block_bool(param, "public", false); @@ -164,21 +151,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 +174,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 +186,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 +207,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 +219,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 +238,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 +249,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 +288,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/winmm_output_plugin.c b/src/output/winmm_output_plugin.c index 4d95834b9..c1b3af126 100644 --- a/src/output/winmm_output_plugin.c +++ b/src/output/winmm_output_plugin.c @@ -26,7 +26,6 @@ #include <stdlib.h> #include <string.h> -#include <windows.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "winmm_output" diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h index 0605530e1..364356483 100644 --- a/src/output/winmm_output_plugin.h +++ b/src/output/winmm_output_plugin.h @@ -25,6 +25,7 @@ #ifdef ENABLE_WINMM_OUTPUT #include <windows.h> +#include <mmsystem.h> struct winmm_output; diff --git a/src/output_internal.h b/src/output_internal.h index 9d975d789..692233f3c 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? */ @@ -250,6 +257,10 @@ audio_output_command_is_finished(const struct audio_output *ao) return ao->command == AO_COMMAND_NONE; } +#ifdef __cplusplus +extern "C" { +#endif + struct audio_output * audio_output_new(const struct config_param *param, struct player_control *pc, @@ -266,4 +277,8 @@ ao_base_finish(struct audio_output *ao); void audio_output_free(struct audio_output *ao); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/output_plugin.h b/src/output_plugin.h index 209ca6221..a47296566 100644 --- a/src/output_plugin.h +++ b/src/output_plugin.h @@ -20,7 +20,8 @@ #ifndef MPD_OUTPUT_PLUGIN_H #define MPD_OUTPUT_PLUGIN_H -#include <glib.h> +#include "gcc.h" +#include "gerror.h" #include <stdbool.h> #include <stddef.h> @@ -165,7 +166,7 @@ ao_plugin_test_default_device(const struct audio_output_plugin *plugin) : false; } -G_GNUC_MALLOC +gcc_malloc struct audio_output * ao_plugin_init(const struct audio_output_plugin *plugin, const struct config_param *param, @@ -187,7 +188,7 @@ ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, void ao_plugin_close(struct audio_output *ao); -G_GNUC_PURE +gcc_pure unsigned ao_plugin_delay(struct audio_output *ao); |