aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/archive/zip_plugin.c2
-rw-r--r--src/archive_list.c3
-rw-r--r--src/audio.c14
-rw-r--r--src/audio.h2
-rw-r--r--src/audio_format.h111
-rw-r--r--src/audio_parser.c132
-rw-r--r--src/audio_parser.h6
-rw-r--r--src/buffer2array.c133
-rw-r--r--src/client.c884
-rw-r--r--src/client_event.c107
-rw-r--r--src/client_expire.c89
-rw-r--r--src/client_global.c72
-rw-r--r--src/client_idle.c88
-rw-r--r--src/client_internal.h145
-rw-r--r--src/client_list.c68
-rw-r--r--src/client_new.c123
-rw-r--r--src/client_process.c145
-rw-r--r--src/client_read.c112
-rw-r--r--src/client_write.c270
-rw-r--r--src/cmdline.c78
-rw-r--r--src/cmdline.h13
-rw-r--r--src/command.c356
-rw-r--r--src/command.h6
-rw-r--r--src/conf.c486
-rw-r--r--src/conf.h72
-rw-r--r--src/cue/cue_tag.c94
-rw-r--r--src/daemon.c105
-rw-r--r--src/daemon.h37
-rw-r--r--src/database.c90
-rw-r--r--src/dbUtils.c28
-rw-r--r--src/dbUtils.h4
-rw-r--r--src/decoder/_flac_common.c38
-rw-r--r--src/decoder/audiofile_plugin.c8
-rw-r--r--src/decoder/faad_plugin.c6
-rw-r--r--src/decoder/ffmpeg_plugin.c42
-rw-r--r--src/decoder/flac_plugin.c21
-rw-r--r--src/decoder/mad_plugin.c18
-rw-r--r--src/decoder/mikmod_plugin.c6
-rw-r--r--src/decoder/modplug_plugin.c8
-rw-r--r--src/decoder/mp4ff_plugin.c22
-rw-r--r--src/decoder/mpcdec_plugin.c4
-rw-r--r--src/decoder/mpg123_decoder_plugin.c211
-rw-r--r--src/decoder/sidplay_plugin.cxx280
-rw-r--r--src/decoder/sndfile_decoder_plugin.c244
-rwxr-xr-x[-rw-r--r--]src/decoder/vorbis_plugin.c12
-rw-r--r--src/decoder/wavpack_plugin.c28
-rw-r--r--src/decoder_api.c177
-rw-r--r--src/decoder_api.h100
-rw-r--r--src/decoder_control.c118
-rw-r--r--src/decoder_control.h169
-rw-r--r--src/decoder_internal.c47
-rw-r--r--src/decoder_internal.h2
-rw-r--r--src/decoder_list.c81
-rw-r--r--src/decoder_list.h17
-rw-r--r--src/decoder_plugin.c46
-rw-r--r--src/decoder_plugin.h14
-rw-r--r--src/decoder_print.c53
-rw-r--r--src/decoder_print.h (renamed from src/input/lastfm_input_plugin.h)9
-rw-r--r--src/decoder_thread.c394
-rw-r--r--src/decoder_thread.h5
-rw-r--r--src/directory_save.c162
-rw-r--r--src/directory_save.h5
-rw-r--r--src/encoder/null_encoder.c115
-rw-r--r--src/encoder/twolame_encoder.c299
-rw-r--r--src/encoder_list.c6
-rw-r--r--src/event_pipe.c12
-rw-r--r--src/event_pipe.h5
-rw-r--r--src/exclude.c94
-rw-r--r--src/exclude.h51
-rw-r--r--src/fd_util.c241
-rw-r--r--src/fd_util.h90
-rw-r--r--src/filter/chain_filter_plugin.c177
-rw-r--r--src/filter/chain_filter_plugin.h48
-rw-r--r--src/filter/convert_filter_plugin.c147
-rw-r--r--src/filter/convert_filter_plugin.h36
-rw-r--r--src/filter/null_filter_plugin.c93
-rw-r--r--src/filter/volume_filter_plugin.c174
-rw-r--r--src/filter/volume_filter_plugin.h31
-rw-r--r--src/filter_internal.h (renamed from src/buffer2array.h)25
-rw-r--r--src/filter_plugin.c106
-rw-r--r--src/filter_plugin.h145
-rw-r--r--src/filter_registry.c41
-rw-r--r--src/filter_registry.h37
-rw-r--r--src/glib_compat.h43
-rw-r--r--src/icy_metadata.c2
-rw-r--r--src/idle.c1
-rw-r--r--src/idle.h3
-rw-r--r--src/inotify_queue.c134
-rw-r--r--src/inotify_queue.h32
-rw-r--r--src/inotify_source.c164
-rw-r--r--src/inotify_source.h61
-rw-r--r--src/inotify_update.c349
-rw-r--r--src/inotify_update.h47
-rw-r--r--src/input/curl_input_plugin.c27
-rw-r--r--src/input/file_input_plugin.c10
-rw-r--r--src/input/lastfm_input_plugin.c229
-rw-r--r--src/input/mms_input_plugin.c2
-rw-r--r--src/input_plugin.h2
-rw-r--r--src/input_stream.c11
-rw-r--r--src/input_stream.h10
-rw-r--r--src/listen.c32
-rw-r--r--src/listen.h7
-rw-r--r--src/locate.c6
-rw-r--r--src/log.c8
-rw-r--r--src/ls.c5
-rw-r--r--src/main.c152
-rw-r--r--src/main.h2
-rw-r--r--src/mapper.c52
-rw-r--r--src/mapper.h2
-rw-r--r--src/mixer/alsa_mixer_plugin.c (renamed from src/mixer/alsa_mixer.c)63
-rw-r--r--src/mixer/oss_mixer_plugin.c (renamed from src/mixer/oss_mixer.c)54
-rw-r--r--src/mixer/pulse_mixer.c382
-rw-r--r--src/mixer/pulse_mixer_plugin.c233
-rw-r--r--src/mixer/pulse_mixer_plugin.h39
-rw-r--r--src/mixer/software_mixer_plugin.c108
-rw-r--r--src/mixer/software_mixer_plugin.h33
-rw-r--r--src/mixer_all.c97
-rw-r--r--src/mixer_all.h21
-rw-r--r--src/mixer_control.c53
-rw-r--r--src/mixer_control.h15
-rw-r--r--src/mixer_list.h7
-rw-r--r--src/mixer_plugin.h33
-rw-r--r--src/mixer_type.c38
-rw-r--r--src/mixer_type.h47
-rw-r--r--src/output/alsa_plugin.c116
-rw-r--r--src/output/fifo_output_plugin.c (renamed from src/output/fifo_plugin.c)11
-rw-r--r--src/output/httpd_client.c5
-rw-r--r--src/output/httpd_internal.h13
-rw-r--r--src/output/httpd_output_plugin.c117
-rw-r--r--src/output/jack_output_plugin.c697
-rw-r--r--src/output/jack_plugin.c450
-rw-r--r--src/output/mvp_plugin.c8
-rw-r--r--src/output/openal_plugin.c273
-rw-r--r--src/output/oss_plugin.c13
-rw-r--r--src/output/pulse_output_plugin.c823
-rw-r--r--src/output/pulse_output_plugin.h72
-rw-r--r--src/output/pulse_plugin.c179
-rw-r--r--src/output/recorder_output_plugin.c216
-rw-r--r--src/output/shout_plugin.c13
-rw-r--r--src/output/solaris_output_plugin.c3
-rw-r--r--src/output_all.c111
-rw-r--r--src/output_all.h21
-rw-r--r--src/output_command.c15
-rw-r--r--src/output_control.c152
-rw-r--r--src/output_control.h17
-rw-r--r--src/output_init.c143
-rw-r--r--src/output_internal.h60
-rw-r--r--src/output_list.c16
-rw-r--r--src/output_plugin.h46
-rw-r--r--src/output_state.c55
-rw-r--r--src/output_state.h15
-rw-r--r--src/output_thread.c368
-rw-r--r--src/pcm_byteswap.c69
-rw-r--r--src/pcm_byteswap.h50
-rw-r--r--src/pcm_channels.c32
-rw-r--r--src/pcm_channels.h12
-rw-r--r--src/pcm_convert.c127
-rw-r--r--src/pcm_convert.h16
-rw-r--r--src/pcm_format.c5
-rw-r--r--src/pcm_resample.c24
-rw-r--r--src/pcm_resample.h16
-rw-r--r--src/pcm_resample_fallback.c4
-rw-r--r--src/pcm_resample_internal.h8
-rw-r--r--src/pcm_resample_libsamplerate.c71
-rw-r--r--src/permission.c2
-rw-r--r--src/permission.h2
-rw-r--r--src/player_control.c179
-rw-r--r--src/player_control.h168
-rw-r--r--src/player_thread.c465
-rw-r--r--src/playlist.c76
-rw-r--r--src/playlist.h110
-rw-r--r--src/playlist/asx_playlist_plugin.c313
-rw-r--r--src/playlist/asx_playlist_plugin.h25
-rw-r--r--src/playlist/extm3u_playlist_plugin.c160
-rw-r--r--src/playlist/extm3u_playlist_plugin.h25
-rw-r--r--src/playlist/lastfm_playlist_plugin.c291
-rw-r--r--src/playlist/lastfm_playlist_plugin.h25
-rw-r--r--src/playlist/m3u_playlist_plugin.c91
-rw-r--r--src/playlist/m3u_playlist_plugin.h25
-rw-r--r--src/playlist/pls_playlist_plugin.c209
-rw-r--r--src/playlist/pls_playlist_plugin.h25
-rw-r--r--src/playlist/xspf_playlist_plugin.c333
-rw-r--r--src/playlist/xspf_playlist_plugin.h25
-rw-r--r--src/playlist_control.c80
-rw-r--r--src/playlist_edit.c166
-rw-r--r--src/playlist_global.c18
-rw-r--r--src/playlist_internal.h2
-rw-r--r--src/playlist_list.c243
-rw-r--r--src/playlist_list.h65
-rw-r--r--src/playlist_plugin.h137
-rw-r--r--src/playlist_print.c2
-rw-r--r--src/playlist_queue.c134
-rw-r--r--src/playlist_queue.h48
-rw-r--r--src/playlist_save.c6
-rw-r--r--src/playlist_state.c111
-rw-r--r--src/playlist_state.h14
-rw-r--r--src/queue.c24
-rw-r--r--src/queue.h10
-rw-r--r--src/replay_gain.c95
-rw-r--r--src/replay_gain.h16
-rw-r--r--src/socket_util.c3
-rw-r--r--src/song.c171
-rw-r--r--src/song.h9
-rw-r--r--src/song_print.c31
-rw-r--r--src/song_print.h2
-rw-r--r--src/song_save.c137
-rw-r--r--src/song_save.h18
-rw-r--r--src/song_update.c172
-rw-r--r--src/songvec.c61
-rw-r--r--src/songvec.h2
-rw-r--r--src/state_file.c72
-rw-r--r--src/stats.c6
-rw-r--r--src/sticker.c47
-rw-r--r--src/sticker.h8
-rw-r--r--src/tag.c78
-rw-r--r--src/tag.h42
-rw-r--r--src/tag_ape.c64
-rw-r--r--src/tag_id3.c298
-rw-r--r--src/text_file.c62
-rw-r--r--src/text_file.h39
-rw-r--r--src/text_input_stream.c88
-rw-r--r--src/text_input_stream.h52
-rw-r--r--src/tokenizer.c221
-rw-r--r--src/tokenizer.h83
-rw-r--r--src/update.c816
-rw-r--r--src/update.h14
-rw-r--r--src/update_internal.h64
-rw-r--r--src/update_queue.c65
-rw-r--r--src/update_remove.c93
-rw-r--r--src/update_walk.c827
-rw-r--r--src/uri.h5
-rw-r--r--src/utils.c46
-rw-r--r--src/utils.h15
-rw-r--r--src/volume.c220
-rw-r--r--src/volume.h20
235 files changed, 16812 insertions, 6132 deletions
diff --git a/src/archive/zip_plugin.c b/src/archive/zip_plugin.c
index dbd2534fa..ad3b403b4 100644
--- a/src/archive/zip_plugin.c
+++ b/src/archive/zip_plugin.c
@@ -157,7 +157,7 @@ zip_is_eof(struct input_stream *is)
static bool
zip_is_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence)
{
zip_context *context = (zip_context *) is->data;
zzip_off_t ofs = zzip_seek(context->file, offset, whence);
diff --git a/src/archive_list.c b/src/archive_list.c
index 8228fc961..e71b3036a 100644
--- a/src/archive_list.c
+++ b/src/archive_list.c
@@ -60,7 +60,8 @@ archive_plugin_from_suffix(const char *suffix)
for (i=0; i < num_archive_plugins; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] &&
- stringFoundInStringArray(plugin->suffixes, suffix)) {
+ plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
++i;
return plugin;
}
diff --git a/src/audio.c b/src/audio.c
index d48558e46..a0259cb37 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -35,9 +35,8 @@ static struct audio_format configured_audio_format;
void getOutputAudioFormat(const struct audio_format *inAudioFormat,
struct audio_format *outAudioFormat)
{
- *outAudioFormat = audio_format_defined(&configured_audio_format)
- ? configured_audio_format
- : *inAudioFormat;
+ *outAudioFormat = *inAudioFormat;
+ audio_format_mask_apply(outAudioFormat, &configured_audio_format);
}
void initAudioConfig(void)
@@ -46,17 +45,12 @@ void initAudioConfig(void)
GError *error = NULL;
bool ret;
- if (NULL == param || NULL == param->value)
+ if (param == NULL)
return;
ret = audio_format_parse(&configured_audio_format, param->value,
- &error);
+ true, &error);
if (!ret)
g_error("error parsing \"%s\" at line %i: %s",
CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
}
-
-void finishAudioConfig(void)
-{
- audio_format_clear(&configured_audio_format);
-}
diff --git a/src/audio.h b/src/audio.h
index 4d80ee0e6..4b80cfd27 100644
--- a/src/audio.h
+++ b/src/audio.h
@@ -30,6 +30,4 @@ void getOutputAudioFormat(const struct audio_format *inFormat,
/* make sure initPlayerData is called before this function!! */
void initAudioConfig(void);
-void finishAudioConfig(void);
-
#endif
diff --git a/src/audio_format.h b/src/audio_format.h
index 64087d070..a4f5ba2e0 100644
--- a/src/audio_format.h
+++ b/src/audio_format.h
@@ -23,25 +23,95 @@
#include <stdint.h>
#include <stdbool.h>
+/**
+ * This structure describes the format of a raw PCM stream.
+ */
struct audio_format {
+ /**
+ * The sample rate in Hz. A better name for this attribute is
+ * "frame rate", because technically, you have two samples per
+ * frame in stereo sound.
+ */
uint32_t sample_rate;
+
+ /**
+ * The number of significant bits per sample. Samples are
+ * currently always signed. Supported values are 8, 16, 24,
+ * 32. 24 bit samples are packed in 32 bit integers.
+ */
uint8_t bits;
+
+ /**
+ * The number of channels. Only mono (1) and stereo (2) are
+ * fully supported currently.
+ */
uint8_t channels;
+
+ /**
+ * If zero, then samples are stored in host byte order. If
+ * nonzero, then samples are stored in the reverse host byte
+ * order.
+ */
+ uint8_t reverse_endian;
};
+/**
+ * Clears the #audio_format object, i.e. sets all attributes to an
+ * undefined (invalid) value.
+ */
static inline void audio_format_clear(struct audio_format *af)
{
af->sample_rate = 0;
af->bits = 0;
af->channels = 0;
+ af->reverse_endian = 0;
+}
+
+/**
+ * Initializes an #audio_format object, i.e. sets all
+ * attributes to valid values.
+ */
+static inline void audio_format_init(struct audio_format *af,
+ uint32_t sample_rate,
+ uint8_t bits, uint8_t channels)
+{
+ af->sample_rate = sample_rate;
+ af->bits = bits;
+ af->channels = channels;
+ af->reverse_endian = 0;
}
+/**
+ * Checks whether the specified #audio_format object has a defined
+ * value.
+ */
static inline bool audio_format_defined(const struct audio_format *af)
{
return af->sample_rate != 0;
}
/**
+ * Checks whether the specified #audio_format object is full, i.e. all
+ * attributes are defined. This is more complete than
+ * audio_format_defined(), but slower.
+ */
+static inline bool
+audio_format_fully_defined(const struct audio_format *af)
+{
+ return af->sample_rate != 0 && af->bits != 0 && af->channels != 0;
+}
+
+/**
+ * Checks whether the specified #audio_format object has at least one
+ * defined value.
+ */
+static inline bool
+audio_format_mask_defined(const struct audio_format *af)
+{
+ return af->sample_rate != 0 || af->bits != 0 || af->channels != 0;
+}
+
+/**
* Checks whether the sample rate is valid.
*
* @param sample_rate the sample rate in Hz
@@ -83,12 +153,39 @@ static inline bool audio_format_valid(const struct audio_format *af)
audio_valid_channel_count(af->channels);
}
+/**
+ * Returns false if the format mask is not valid for playback with
+ * MPD. This function performs some basic validity checks.
+ */
+static inline bool audio_format_mask_valid(const struct audio_format *af)
+{
+ return (af->sample_rate == 0 ||
+ audio_valid_sample_rate(af->sample_rate)) &&
+ (af->bits == 0 || audio_valid_sample_format(af->bits)) &&
+ (af->channels == 0 || audio_valid_channel_count(af->channels));
+}
+
static inline bool audio_format_equals(const struct audio_format *a,
const struct audio_format *b)
{
return a->sample_rate == b->sample_rate &&
a->bits == b->bits &&
- a->channels == b->channels;
+ a->channels == b->channels &&
+ a->reverse_endian == b->reverse_endian;
+}
+
+static inline void
+audio_format_mask_apply(struct audio_format *af,
+ const struct audio_format *mask)
+{
+ if (mask->sample_rate != 0)
+ af->sample_rate = mask->sample_rate;
+
+ if (mask->bits != 0)
+ af->bits = mask->bits;
+
+ if (mask->channels != 0)
+ af->channels = mask->channels;
}
/**
@@ -104,20 +201,22 @@ static inline unsigned audio_format_sample_size(const struct audio_format *af)
return 4;
}
+/**
+ * Returns the size of each full frame in bytes.
+ */
static inline unsigned
audio_format_frame_size(const struct audio_format *af)
{
return audio_format_sample_size(af) * af->channels;
}
+/**
+ * Returns the floating point factor which converts a time span to a
+ * storage size in bytes.
+ */
static inline double audio_format_time_to_size(const struct audio_format *af)
{
return af->sample_rate * audio_format_frame_size(af);
}
-static inline double audioFormatSizeToTime(const struct audio_format *af)
-{
- return 1.0 / audio_format_time_to_size(af);
-}
-
#endif
diff --git a/src/audio_parser.c b/src/audio_parser.c
index 906b0f819..7c0d45ddc 100644
--- a/src/audio_parser.c
+++ b/src/audio_parser.c
@@ -36,64 +36,136 @@ audio_parser_quark(void)
return g_quark_from_static_string("audio_parser");
}
-bool
-audio_format_parse(struct audio_format *dest, const char *src, GError **error)
+static bool
+parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r,
+ const char **endptr_r, GError **error_r)
{
- char *endptr;
unsigned long value;
+ char *endptr;
- audio_format_clear(dest);
-
- /* parse sample rate */
+ if (mask && *src == '*') {
+ *sample_rate_r = 0;
+ *endptr_r = src + 1;
+ return true;
+ }
value = strtoul(src, &endptr, 10);
if (endptr == src) {
- g_set_error(error, audio_parser_quark(), 0,
- "Sample rate missing");
- return false;
- } else if (*endptr != ':') {
- g_set_error(error, audio_parser_quark(), 0,
- "Sample format missing");
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the sample rate");
return false;
} else if (!audio_valid_sample_rate(value)) {
- g_set_error(error, audio_parser_quark(), 0,
+ g_set_error(error_r, audio_parser_quark(), 0,
"Invalid sample rate: %lu", value);
return false;
}
- dest->sample_rate = value;
+ *sample_rate_r = value;
+ *endptr_r = endptr;
+ return true;
+}
- /* parse sample format */
+static bool
+parse_sample_format(const char *src, bool mask, uint8_t *bits_r,
+ const char **endptr_r, GError **error_r)
+{
+ unsigned long value;
+ char *endptr;
+
+ if (mask && *src == '*') {
+ *bits_r = 0;
+ *endptr_r = src + 1;
+ return true;
+ }
- src = endptr + 1;
value = strtoul(src, &endptr, 10);
if (endptr == src) {
- g_set_error(error, audio_parser_quark(), 0,
- "Sample format missing");
- return false;
- } else if (*endptr != ':') {
- g_set_error(error, audio_parser_quark(), 0,
- "Channel count missing");
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the sample format");
return false;
} else if (!audio_valid_sample_format(value)) {
- g_set_error(error, audio_parser_quark(), 0,
+ g_set_error(error_r, audio_parser_quark(), 0,
"Invalid sample format: %lu", value);
return false;
}
- dest->bits = value;
+ *bits_r = value;
+ *endptr_r = endptr;
+ return true;
+}
- /* parse channel count */
+static bool
+parse_channel_count(const char *src, bool mask, uint8_t *channels_r,
+ const char **endptr_r, GError **error_r)
+{
+ unsigned long value;
+ char *endptr;
+
+ if (mask && *src == '*') {
+ *channels_r = 0;
+ *endptr_r = src + 1;
+ return true;
+ }
- src = endptr + 1;
value = strtoul(src, &endptr, 10);
- if (*endptr != 0 || !audio_valid_channel_count(value)) {
- g_set_error(error, audio_parser_quark(), 0,
- "Invalid channel count: %s", src);
+ if (endptr == src) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the channel count");
+ return false;
+ } else if (!audio_valid_channel_count(value)) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Invalid channel count: %lu", value);
+ return false;
+ }
+
+ *channels_r = value;
+ *endptr_r = endptr;
+ return true;
+}
+
+bool
+audio_format_parse(struct audio_format *dest, const char *src,
+ bool mask, GError **error_r)
+{
+ uint32_t rate;
+ uint8_t bits, channels;
+
+ audio_format_clear(dest);
+
+ /* parse sample rate */
+
+ if (!parse_sample_rate(src, mask, &rate, &src, error_r))
+ return false;
+
+ if (*src++ != ':') {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Sample format missing");
+ return false;
+ }
+
+ /* parse sample format */
+
+ if (!parse_sample_format(src, mask, &bits, &src, error_r))
+ return false;
+
+ if (*src++ != ':') {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Channel count missing");
+ return false;
+ }
+
+ /* parse channel count */
+
+ if (!parse_channel_count(src, mask, &channels, &src, error_r))
+ return false;
+
+ if (*src != 0) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Extra data after channel count: %s", src);
return false;
}
- dest->channels = value;
+ audio_format_init(dest, rate, bits, channels);
return true;
}
diff --git a/src/audio_parser.h b/src/audio_parser.h
index 30a927456..d50c17489 100644
--- a/src/audio_parser.h
+++ b/src/audio_parser.h
@@ -37,11 +37,13 @@ struct audio_format;
*
* @param dest the destination #audio_format struct
* @param src the input string
- * @param error location to store the error occuring, or NULL to
+ * @param mask if true, then "*" is allowed for any number of items
+ * @param error_r location to store the error occuring, or NULL to
* ignore errors
* @return true on success
*/
bool
-audio_format_parse(struct audio_format *dest, const char *src, GError **error);
+audio_format_parse(struct audio_format *dest, const char *src,
+ bool mask, GError **error_r);
#endif
diff --git a/src/buffer2array.c b/src/buffer2array.c
deleted file mode 100644
index b6029d754..000000000
--- a/src/buffer2array.c
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2003-2009 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 "buffer2array.h"
-
-#include <glib.h>
-
-#include <string.h>
-
-int buffer2array(char *buffer, char *array[], const int max)
-{
- int i = 0;
- char *c = buffer;
-
- while (*c != '\0' && i < max) {
- if (*c == '\"') {
- array[i++] = ++c;
- while (*c != '\0') {
- if (*c == '\"') {
- *(c++) = '\0';
- break;
- }
- else if (*(c++) == '\\' && *c != '\0') {
- memmove(c - 1, c, strlen(c) + 1);
- }
- }
- } else {
- c = g_strchug(c);
- if (*c == '\0')
- return i;
-
- array[i++] = c++;
-
- while (!g_ascii_isspace(*c) && *c != '\0')
- ++c;
- }
- if (*c == '\0')
- return i;
- *(c++) = '\0';
-
- c = g_strchug(c);
- }
- return i;
-}
-
-#ifdef UNIT_TEST
-
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-int main()
-{
- char *a[4] = { NULL };
- char *b;
- int max;
-
- b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir/name \"test\"", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir/name \"test\" something else", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir\\\\name\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir\\name", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir name\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir name", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"\\\"/some/dir\\\"\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("\"/some/dir\"", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"\\\"/some/dir\\\" x\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("\"/some/dir\" x", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"single quote\\'d from php magicquotes\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("single quote\'d from php magicquotes", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"double quote\\\"d from php magicquotes\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("double quote\"d from php magicquotes", a[1]) );
- assert( !a[2] );
-
- return 0;
-}
-
-#endif
diff --git a/src/client.c b/src/client.c
index 6a256998f..827f1d752 100644
--- a/src/client.c
+++ b/src/client.c
@@ -17,110 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "client.h"
-#include "fifo_buffer.h"
-#include "command.h"
-#include "conf.h"
-#include "listen.h"
-#include "socket_util.h"
-#include "permission.h"
-#include "event_pipe.h"
-#include "idle.h"
-#include "main.h"
-#include "config.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-
-#ifdef WIN32
-#include <ws2tcpip.h>
-#include <winsock.h>
-#else
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "client"
-#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
-
-static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
-
-#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
-#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
-#define CLIENT_LIST_MODE_END "command_list_end"
-#define CLIENT_TIMEOUT_DEFAULT (60)
-#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
-#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
-#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
-
-/* set this to zero to indicate we have no possible clients */
-static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */
-static int client_timeout;
-static size_t client_max_command_list_size;
-static size_t client_max_output_buffer_size;
-
-struct deferred_buffer {
- size_t size;
- char data[sizeof(long)];
-};
-
-struct client {
- GIOChannel *channel;
- guint source_id;
-
- /** the buffer for reading lines from the #channel */
- struct fifo_buffer *input;
-
- unsigned permission;
-
- /** the uid of the client process, or -1 if unknown */
- int uid;
-
- /**
- * How long since the last activity from this client?
- */
- GTimer *last_activity;
-
- GSList *cmd_list; /* for when in list mode */
- int cmd_list_OK; /* print OK after each command execution */
- size_t cmd_list_size; /* mem cmd_list consumes */
- GQueue *deferred_send; /* for output if client is slow */
- size_t deferred_bytes; /* mem deferred_send consumes */
- unsigned int num; /* client number */
-
- char send_buf[4096];
- size_t send_buf_used; /* bytes used this instance */
-
- /** is this client waiting for an "idle" response? */
- bool idle_waiting;
-
- /** idle flags pending on this client, to be sent as soon as
- the client enters "idle" */
- unsigned idle_flags;
-
- /** idle flags that the client wants to receive */
- unsigned idle_subscriptions;
-};
-
-static GList *clients;
-static unsigned num_clients;
-static guint expire_source_id;
-
-static void client_write_deferred(struct client *client);
-
-static void client_write_output(struct client *client);
-
-static void client_manager_expire(void);
-
-static gboolean
-client_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
+#include "client_internal.h"
bool client_is_expired(const struct client *client)
{
@@ -141,782 +38,3 @@ void client_set_permission(struct client *client, unsigned permission)
{
client->permission = permission;
}
-
-/**
- * An idle event which calls client_manager_expire().
- */
-static gboolean
-client_manager_expire_event(G_GNUC_UNUSED gpointer data)
-{
- expire_source_id = 0;
- client_manager_expire();
- return false;
-}
-
-static inline void client_set_expired(struct client *client)
-{
- if (expire_source_id == 0 && !client_is_expired(client))
- /* delayed deletion */
- expire_source_id = g_idle_add(client_manager_expire_event,
- NULL);
-
- if (client->source_id != 0) {
- g_source_remove(client->source_id);
- client->source_id = 0;
- }
-
- if (client->channel != NULL) {
- g_io_channel_unref(client->channel);
- client->channel = NULL;
- }
-}
-
-static void client_init(struct client *client, int fd)
-{
- static unsigned int next_client_num;
-
- assert(fd >= 0);
-
- client->cmd_list_size = 0;
- client->cmd_list_OK = -1;
-
-#ifndef G_OS_WIN32
- client->channel = g_io_channel_unix_new(fd);
-#else
- client->channel = g_io_channel_win32_new_socket(fd);
-#endif
- /* 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; the MPD
- protocol is UTF-8 only, but we are doing this call anyway
- to prevent GLib from messing around with the stream */
- g_io_channel_set_encoding(client->channel, NULL, NULL);
- /* we prefer to do buffering */
- g_io_channel_set_buffered(client->channel, false);
-
- client->source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- client_in_event, client);
-
- client->input = fifo_buffer_new(4096);
-
- client->cmd_list = NULL;
- client->deferred_send = g_queue_new();
- client->deferred_bytes = 0;
- client->num = next_client_num++;
- client->send_buf_used = 0;
-
- client->permission = getDefaultPermissions();
-
- (void)write(fd, GREETING, sizeof(GREETING) - 1);
-}
-
-static void free_cmd_list(GSList *list)
-{
- for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
- g_free(tmp->data);
-
- g_slist_free(list);
-}
-
-static void new_cmd_list_ptr(struct client *client, char *s)
-{
- client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
-}
-
-static void
-deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct deferred_buffer *buffer = data;
- g_free(buffer);
-}
-
-static void client_close(struct client *client)
-{
- assert(num_clients > 0);
- assert(clients != NULL);
-
- clients = g_list_remove(clients, client);
- --num_clients;
-
- client_set_expired(client);
-
- g_timer_destroy(client->last_activity);
-
- if (client->cmd_list) {
- free_cmd_list(client->cmd_list);
- client->cmd_list = NULL;
- }
-
- g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
- g_queue_free(client->deferred_send);
-
- fifo_buffer_free(client->input);
-
- g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
- "[%u] closed", client->num);
- g_free(client);
-}
-
-void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
-{
- struct client *client;
- char *remote;
-
- if (num_clients >= client_max_connections) {
- g_warning("Max Connections Reached!");
- close(fd);
- return;
- }
-
- client = g_new0(struct client, 1);
- clients = g_list_prepend(clients, client);
- ++num_clients;
-
- client_init(client, fd);
- client->uid = uid;
-
- client->last_activity = g_timer_new();
-
- remote = sockaddr_to_string(sa, sa_length, NULL);
- g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
- "[%u] opened from %s", client->num, remote);
- g_free(remote);
-}
-
-static int client_process_line(struct client *client, char *line)
-{
- int ret = 1;
-
- if (strcmp(line, "noidle") == 0) {
- if (client->idle_waiting) {
- /* send empty idle response and leave idle mode */
- client->idle_waiting = false;
- command_success(client);
- client_write_output(client);
- }
-
- /* do nothing if the client wasn't idling: the client
- has already received the full idle response from
- client_idle_notify(), which he can now evaluate */
-
- return 0;
- } else if (client->idle_waiting) {
- /* during idle mode, clients must not send anything
- except "noidle" */
- g_warning("[%u] command \"%s\" during idle",
- client->num, line);
- return COMMAND_RETURN_CLOSE;
- }
-
- if (client->cmd_list_OK >= 0) {
- if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
- g_debug("[%u] process command list",
- client->num);
-
- /* for scalability reasons, we have prepended
- each new command; now we have to reverse it
- to restore the correct order */
- client->cmd_list = g_slist_reverse(client->cmd_list);
-
- ret = command_process_list(client,
- client->cmd_list_OK,
- client->cmd_list);
- g_debug("[%u] process command "
- "list returned %i", client->num, ret);
-
- if (ret == COMMAND_RETURN_CLOSE ||
- client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
-
- if (ret == 0)
- command_success(client);
-
- client_write_output(client);
- free_cmd_list(client->cmd_list);
- client->cmd_list = NULL;
- client->cmd_list_OK = -1;
- } else {
- size_t len = strlen(line) + 1;
- client->cmd_list_size += len;
- if (client->cmd_list_size >
- client_max_command_list_size) {
- g_warning("[%u] command list size (%lu) "
- "is larger than the max (%lu)",
- client->num,
- (unsigned long)client->cmd_list_size,
- (unsigned long)client_max_command_list_size);
- return COMMAND_RETURN_CLOSE;
- } else
- new_cmd_list_ptr(client, line);
- }
- } else {
- if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
- client->cmd_list_OK = 0;
- ret = 1;
- } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
- client->cmd_list_OK = 1;
- ret = 1;
- } else {
- g_debug("[%u] process command \"%s\"",
- client->num, line);
- ret = command_process(client, line);
- g_debug("[%u] command returned %i",
- client->num, ret);
-
- if (ret == COMMAND_RETURN_CLOSE ||
- client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
-
- if (ret == 0)
- command_success(client);
-
- client_write_output(client);
- }
- }
-
- return ret;
-}
-
-static char *
-client_read_line(struct client *client)
-{
- const char *p, *newline;
- size_t length;
- char *line;
-
- p = fifo_buffer_read(client->input, &length);
- if (p == NULL)
- return NULL;
-
- newline = memchr(p, '\n', length);
- if (newline == NULL)
- return NULL;
-
- line = g_strndup(p, newline - p);
- fifo_buffer_consume(client->input, newline - p + 1);
-
- return g_strchomp(line);
-}
-
-static int client_input_received(struct client *client, size_t bytesRead)
-{
- char *line;
- int ret;
-
- fifo_buffer_append(client->input, bytesRead);
-
- /* process all lines */
-
- while ((line = client_read_line(client)) != NULL) {
- ret = client_process_line(client, line);
- g_free(line);
-
- if (ret == COMMAND_RETURN_KILL ||
- ret == COMMAND_RETURN_CLOSE)
- return ret;
- if (client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
- }
-
- return 0;
-}
-
-static int client_read(struct client *client)
-{
- char *p;
- size_t max_length;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_read;
-
- assert(client != NULL);
- assert(client->channel != NULL);
-
- p = fifo_buffer_write(client->input, &max_length);
- if (p == NULL) {
- g_warning("[%u] buffer overflow", client->num);
- return COMMAND_RETURN_CLOSE;
- }
-
- status = g_io_channel_read_chars(client->channel, p, max_length,
- &bytes_read, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- return client_input_received(client, bytes_read);
-
- case G_IO_STATUS_AGAIN:
- /* try again later, after select() */
- return 0;
-
- case G_IO_STATUS_EOF:
- /* peer disconnected */
- return COMMAND_RETURN_CLOSE;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
- g_warning("failed to read from client %d: %s",
- client->num, error->message);
- g_error_free(error);
- return COMMAND_RETURN_CLOSE;
- }
-
- /* unreachable */
- return COMMAND_RETURN_CLOSE;
-}
-
-static gboolean
-client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data);
-
-static gboolean
-client_in_event(G_GNUC_UNUSED GIOChannel *source,
- GIOCondition condition,
- gpointer data)
-{
- struct client *client = data;
- int ret;
-
- assert(!client_is_expired(client));
-
- if (condition != G_IO_IN) {
- client_set_expired(client);
- return false;
- }
-
- g_timer_start(client->last_activity);
-
- ret = client_read(client);
- switch (ret) {
- case COMMAND_RETURN_KILL:
- client_close(client);
- g_main_loop_quit(main_loop);
- return false;
-
- case COMMAND_RETURN_CLOSE:
- client_close(client);
- return false;
- }
-
- if (client_is_expired(client)) {
- client_close(client);
- return false;
- }
-
- if (!g_queue_is_empty(client->deferred_send)) {
- /* deferred buffers exist: schedule write */
- client->source_id = g_io_add_watch(client->channel,
- G_IO_OUT|G_IO_ERR|G_IO_HUP,
- client_out_event, client);
- return false;
- }
-
- /* read more */
- return true;
-}
-
-static gboolean
-client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- struct client *client = data;
-
- assert(!client_is_expired(client));
-
- if (condition != G_IO_OUT) {
- client_set_expired(client);
- return false;
- }
-
- client_write_deferred(client);
-
- if (client_is_expired(client)) {
- client_close(client);
- return false;
- }
-
- g_timer_start(client->last_activity);
-
- if (g_queue_is_empty(client->deferred_send)) {
- /* done sending deferred buffers exist: schedule
- read */
- client->source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- client_in_event, client);
- return false;
- }
-
- /* write more */
- return true;
-}
-
-void client_manager_init(void)
-{
- client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
- CLIENT_TIMEOUT_DEFAULT);
- client_max_connections =
- config_get_positive(CONF_MAX_CONN,
- CLIENT_MAX_CONNECTIONS_DEFAULT);
- client_max_command_list_size =
- config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
- CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
- * 1024;
-
- client_max_output_buffer_size =
- config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
- CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
- * 1024;
-}
-
-static void client_close_all(void)
-{
- while (clients != NULL) {
- struct client *client = clients->data;
-
- client_close(client);
- }
-
- assert(num_clients == 0);
-}
-
-void client_manager_deinit(void)
-{
- client_close_all();
-
- client_max_connections = 0;
-
- if (expire_source_id != 0)
- g_source_remove(expire_source_id);
-}
-
-static void
-client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct client *client = data;
-
- if (client_is_expired(client)) {
- g_debug("[%u] expired", client->num);
- client_close(client);
- } else if (!client->idle_waiting && /* idle clients
- never expire */
- (int)g_timer_elapsed(client->last_activity, NULL) >
- client_timeout) {
- g_debug("[%u] timeout", client->num);
- client_close(client);
- }
-}
-
-static void
-client_manager_expire(void)
-{
- g_list_foreach(clients, client_check_expired_callback, NULL);
-}
-
-static size_t
-client_write_deferred_buffer(struct client *client,
- const struct deferred_buffer *buffer)
-{
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->channel != NULL);
- assert(buffer != NULL);
-
- status = g_io_channel_write_chars
- (client->channel, buffer->data, buffer->size,
- &bytes_written, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- return bytes_written;
-
- case G_IO_STATUS_AGAIN:
- return 0;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- client_set_expired(client);
- return 0;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- client_set_expired(client);
- g_warning("failed to flush buffer for %i: %s",
- client->num, error->message);
- g_error_free(error);
- return 0;
- }
-
- /* unreachable */
- return 0;
-}
-
-static void client_write_deferred(struct client *client)
-{
- size_t ret;
-
- while (!g_queue_is_empty(client->deferred_send)) {
- struct deferred_buffer *buf =
- g_queue_peek_head(client->deferred_send);
-
- assert(buf->size > 0);
- assert(buf->size <= client->deferred_bytes);
-
- ret = client_write_deferred_buffer(client, buf);
- if (ret == 0)
- break;
-
- if (ret < buf->size) {
- assert(client->deferred_bytes >= (size_t)ret);
- client->deferred_bytes -= ret;
- buf->size -= ret;
- memmove(buf->data, buf->data + ret, buf->size);
- break;
- } else {
- size_t decr = sizeof(*buf) -
- sizeof(buf->data) + buf->size;
-
- assert(client->deferred_bytes >= decr);
- client->deferred_bytes -= decr;
- g_free(buf);
- g_queue_pop_head(client->deferred_send);
- }
-
- g_timer_start(client->last_activity);
- }
-
- if (g_queue_is_empty(client->deferred_send)) {
- g_debug("[%u] buffer empty %lu", client->num,
- (unsigned long)client->deferred_bytes);
- assert(client->deferred_bytes == 0);
- }
-}
-
-static void client_defer_output(struct client *client,
- const void *data, size_t length)
-{
- size_t alloc;
- struct deferred_buffer *buf;
-
- assert(length > 0);
-
- alloc = sizeof(*buf) - sizeof(buf->data) + length;
- client->deferred_bytes += alloc;
- if (client->deferred_bytes > client_max_output_buffer_size) {
- g_warning("[%u] output buffer size (%lu) is "
- "larger than the max (%lu)",
- client->num,
- (unsigned long)client->deferred_bytes,
- (unsigned long)client_max_output_buffer_size);
- /* cause client to close */
- client_set_expired(client);
- return;
- }
-
- buf = g_malloc(alloc);
- buf->size = length;
- memcpy(buf->data, data, length);
-
- g_queue_push_tail(client->deferred_send, buf);
-}
-
-static void client_write_direct(struct client *client,
- const char *data, size_t length)
-{
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->channel != NULL);
- assert(data != NULL);
- assert(length > 0);
- assert(g_queue_is_empty(client->deferred_send));
-
- status = g_io_channel_write_chars(client->channel, data, length,
- &bytes_written, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- case G_IO_STATUS_AGAIN:
- break;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- client_set_expired(client);
- return;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- client_set_expired(client);
- g_warning("failed to write to %i: %s",
- client->num, error->message);
- g_error_free(error);
- return;
- }
-
- if (bytes_written < length)
- client_defer_output(client, data + bytes_written,
- length - bytes_written);
-
- if (!g_queue_is_empty(client->deferred_send))
- g_debug("[%u] buffer created", client->num);
-}
-
-static void client_write_output(struct client *client)
-{
- if (client_is_expired(client) || !client->send_buf_used)
- return;
-
- if (!g_queue_is_empty(client->deferred_send)) {
- client_defer_output(client, client->send_buf,
- client->send_buf_used);
-
- if (client_is_expired(client))
- return;
-
- /* try to flush the deferred buffers now; the current
- server command may take too long to finish, and
- meanwhile try to feed output to the client,
- otherwise it will time out. One reason why
- deferring is slow might be that currently each
- client_write() allocates a new deferred buffer.
- This should be optimized after MPD 0.14. */
- client_write_deferred(client);
- } else
- client_write_direct(client, client->send_buf,
- client->send_buf_used);
-
- client->send_buf_used = 0;
-}
-
-/**
- * Write a block of data to the client.
- */
-static void client_write(struct client *client, const char *buffer, size_t buflen)
-{
- /* if the client is going to be closed, do nothing */
- if (client_is_expired(client))
- return;
-
- while (buflen > 0 && !client_is_expired(client)) {
- size_t copylen;
-
- assert(client->send_buf_used < sizeof(client->send_buf));
-
- copylen = sizeof(client->send_buf) - client->send_buf_used;
- if (copylen > buflen)
- copylen = buflen;
-
- memcpy(client->send_buf + client->send_buf_used, buffer,
- copylen);
- buflen -= copylen;
- client->send_buf_used += copylen;
- buffer += copylen;
- if (client->send_buf_used >= sizeof(client->send_buf))
- client_write_output(client);
- }
-}
-
-void client_puts(struct client *client, const char *s)
-{
- client_write(client, s, strlen(s));
-}
-
-void client_vprintf(struct client *client, const char *fmt, va_list args)
-{
- va_list tmp;
- int length;
- char *buffer;
-
- va_copy(tmp, args);
- length = vsnprintf(NULL, 0, fmt, tmp);
- va_end(tmp);
-
- if (length <= 0)
- /* wtf.. */
- return;
-
- buffer = g_malloc(length + 1);
- vsnprintf(buffer, length + 1, fmt, args);
- client_write(client, buffer, length);
- g_free(buffer);
-}
-
-G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
-{
- va_list args;
-
- va_start(args, fmt);
- client_vprintf(client, fmt, args);
- va_end(args);
-}
-
-/**
- * Send "idle" response to this client.
- */
-static void
-client_idle_notify(struct client *client)
-{
- unsigned flags, i;
- const char *const* idle_names;
-
- assert(client->idle_waiting);
- assert(client->idle_flags != 0);
-
- flags = client->idle_flags;
- client->idle_flags = 0;
- client->idle_waiting = false;
-
- idle_names = idle_get_names();
- for (i = 0; idle_names[i]; ++i) {
- if (flags & (1 << i) & client->idle_subscriptions)
- client_printf(client, "changed: %s\n",
- idle_names[i]);
- }
-
- client_puts(client, "OK\n");
- g_timer_start(client->last_activity);
-}
-
-static void
-client_idle_callback(gpointer data, gpointer user_data)
-{
- struct client *client = data;
- unsigned flags = GPOINTER_TO_UINT(user_data);
-
- if (client_is_expired(client))
- return;
-
- client->idle_flags |= flags;
- if (client->idle_waiting
- && (client->idle_flags & client->idle_subscriptions)) {
- client_idle_notify(client);
- client_write_output(client);
- }
-}
-
-void client_manager_idle_add(unsigned flags)
-{
- assert(flags != 0);
-
- g_list_foreach(clients, client_idle_callback, GUINT_TO_POINTER(flags));
-}
-
-bool client_idle_wait(struct client *client, unsigned flags)
-{
- assert(!client->idle_waiting);
-
- client->idle_waiting = true;
- client->idle_subscriptions = flags;
-
- if (client->idle_flags & client->idle_subscriptions) {
- client_idle_notify(client);
- return true;
- } else
- return false;
-}
diff --git a/src/client_event.c b/src/client_event.c
new file mode 100644
index 000000000..93279b283
--- /dev/null
+++ b/src/client_event.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2003-2009 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 "client_internal.h"
+#include "main.h"
+
+#include <assert.h>
+
+static gboolean
+client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct client *client = data;
+
+ assert(!client_is_expired(client));
+
+ if (condition != G_IO_OUT) {
+ client_set_expired(client);
+ return false;
+ }
+
+ client_write_deferred(client);
+
+ if (client_is_expired(client)) {
+ client_close(client);
+ return false;
+ }
+
+ g_timer_start(client->last_activity);
+
+ if (g_queue_is_empty(client->deferred_send)) {
+ /* done sending deferred buffers exist: schedule
+ read */
+ client->source_id = g_io_add_watch(client->channel,
+ G_IO_IN|G_IO_ERR|G_IO_HUP,
+ client_in_event, client);
+ return false;
+ }
+
+ /* write more */
+ return true;
+}
+
+gboolean
+client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct client *client = data;
+ enum command_return ret;
+
+ assert(!client_is_expired(client));
+
+ if (condition != G_IO_IN) {
+ client_set_expired(client);
+ return false;
+ }
+
+ g_timer_start(client->last_activity);
+
+ ret = client_read(client);
+ switch (ret) {
+ case COMMAND_RETURN_OK:
+ case COMMAND_RETURN_ERROR:
+ break;
+
+ case COMMAND_RETURN_KILL:
+ client_close(client);
+ g_main_loop_quit(main_loop);
+ return false;
+
+ case COMMAND_RETURN_CLOSE:
+ client_close(client);
+ return false;
+ }
+
+ if (client_is_expired(client)) {
+ client_close(client);
+ return false;
+ }
+
+ if (!g_queue_is_empty(client->deferred_send)) {
+ /* deferred buffers exist: schedule write */
+ client->source_id = g_io_add_watch(client->channel,
+ G_IO_OUT|G_IO_ERR|G_IO_HUP,
+ client_out_event, client);
+ return false;
+ }
+
+ /* read more */
+ return true;
+}
diff --git a/src/client_expire.c b/src/client_expire.c
new file mode 100644
index 000000000..372af1774
--- /dev/null
+++ b/src/client_expire.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2009 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 "client_internal.h"
+
+static guint expire_source_id;
+
+void
+client_set_expired(struct client *client)
+{
+ if (!client_is_expired(client))
+ client_schedule_expire();
+
+ if (client->source_id != 0) {
+ g_source_remove(client->source_id);
+ client->source_id = 0;
+ }
+
+ if (client->channel != NULL) {
+ g_io_channel_unref(client->channel);
+ client->channel = NULL;
+ }
+}
+
+static void
+client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct client *client = data;
+
+ if (client_is_expired(client)) {
+ g_debug("[%u] expired", client->num);
+ client_close(client);
+ } else if (!client->idle_waiting && /* idle clients
+ never expire */
+ (int)g_timer_elapsed(client->last_activity, NULL) >
+ client_timeout) {
+ g_debug("[%u] timeout", client->num);
+ client_close(client);
+ }
+}
+
+static void
+client_manager_expire(void)
+{
+ client_list_foreach(client_check_expired_callback, NULL);
+}
+
+/**
+ * An idle event which calls client_manager_expire().
+ */
+static gboolean
+client_manager_expire_event(G_GNUC_UNUSED gpointer data)
+{
+ expire_source_id = 0;
+ client_manager_expire();
+ return false;
+}
+
+void
+client_schedule_expire(void)
+{
+ if (expire_source_id == 0)
+ /* delayed deletion */
+ expire_source_id = g_idle_add(client_manager_expire_event,
+ NULL);
+}
+
+void
+client_deinit_expire(void)
+{
+ if (expire_source_id != 0)
+ g_source_remove(expire_source_id);
+}
diff --git a/src/client_global.c b/src/client_global.c
new file mode 100644
index 000000000..d99e00b82
--- /dev/null
+++ b/src/client_global.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2003-2009 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 "client_internal.h"
+#include "conf.h"
+
+#include <assert.h>
+
+#define CLIENT_TIMEOUT_DEFAULT (60)
+#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
+#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
+#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
+
+/* set this to zero to indicate we have no possible clients */
+unsigned int client_max_connections;
+int client_timeout;
+size_t client_max_command_list_size;
+size_t client_max_output_buffer_size;
+
+void client_manager_init(void)
+{
+ client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
+ CLIENT_TIMEOUT_DEFAULT);
+ client_max_connections =
+ config_get_positive(CONF_MAX_CONN,
+ CLIENT_MAX_CONNECTIONS_DEFAULT);
+ client_max_command_list_size =
+ config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
+ CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
+ * 1024;
+
+ client_max_output_buffer_size =
+ config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
+ CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
+ * 1024;
+}
+
+static void client_close_all(void)
+{
+ while (!client_list_is_empty()) {
+ struct client *client = client_list_get_first();
+
+ client_close(client);
+ }
+
+ assert(client_list_is_empty());
+}
+
+void client_manager_deinit(void)
+{
+ client_close_all();
+
+ client_max_connections = 0;
+
+ client_deinit_expire();
+}
diff --git a/src/client_idle.c b/src/client_idle.c
new file mode 100644
index 000000000..c6d0bc006
--- /dev/null
+++ b/src/client_idle.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2009 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 "client_internal.h"
+#include "idle.h"
+
+#include <assert.h>
+
+/**
+ * Send "idle" response to this client.
+ */
+static void
+client_idle_notify(struct client *client)
+{
+ unsigned flags, i;
+ const char *const* idle_names;
+
+ assert(client->idle_waiting);
+ assert(client->idle_flags != 0);
+
+ flags = client->idle_flags;
+ client->idle_flags = 0;
+ client->idle_waiting = false;
+
+ idle_names = idle_get_names();
+ for (i = 0; idle_names[i]; ++i) {
+ if (flags & (1 << i) & client->idle_subscriptions)
+ client_printf(client, "changed: %s\n",
+ idle_names[i]);
+ }
+
+ client_puts(client, "OK\n");
+ g_timer_start(client->last_activity);
+}
+
+static void
+client_idle_callback(gpointer data, gpointer user_data)
+{
+ struct client *client = data;
+ unsigned flags = GPOINTER_TO_UINT(user_data);
+
+ if (client_is_expired(client))
+ return;
+
+ client->idle_flags |= flags;
+ if (client->idle_waiting
+ && (client->idle_flags & client->idle_subscriptions)) {
+ client_idle_notify(client);
+ client_write_output(client);
+ }
+}
+
+void client_manager_idle_add(unsigned flags)
+{
+ assert(flags != 0);
+
+ client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags));
+}
+
+bool client_idle_wait(struct client *client, unsigned flags)
+{
+ assert(!client->idle_waiting);
+
+ client->idle_waiting = true;
+ client->idle_subscriptions = flags;
+
+ if (client->idle_flags & client->idle_subscriptions) {
+ client_idle_notify(client);
+ return true;
+ } else
+ return false;
+}
diff --git a/src/client_internal.h b/src/client_internal.h
new file mode 100644
index 000000000..91e360fe0
--- /dev/null
+++ b/src/client_internal.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2003-2009 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_CLIENT_INTERNAL_H
+#define MPD_CLIENT_INTERNAL_H
+
+#include "client.h"
+#include "command.h"
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "client"
+
+struct deferred_buffer {
+ size_t size;
+ char data[sizeof(long)];
+};
+
+struct client {
+ GIOChannel *channel;
+ guint source_id;
+
+ /** the buffer for reading lines from the #channel */
+ struct fifo_buffer *input;
+
+ unsigned permission;
+
+ /** the uid of the client process, or -1 if unknown */
+ int uid;
+
+ /**
+ * How long since the last activity from this client?
+ */
+ GTimer *last_activity;
+
+ GSList *cmd_list; /* for when in list mode */
+ int cmd_list_OK; /* print OK after each command execution */
+ size_t cmd_list_size; /* mem cmd_list consumes */
+ GQueue *deferred_send; /* for output if client is slow */
+ size_t deferred_bytes; /* mem deferred_send consumes */
+ unsigned int num; /* client number */
+
+ char send_buf[4096];
+ size_t send_buf_used; /* bytes used this instance */
+
+ /** is this client waiting for an "idle" response? */
+ bool idle_waiting;
+
+ /** idle flags pending on this client, to be sent as soon as
+ the client enters "idle" */
+ unsigned idle_flags;
+
+ /** idle flags that the client wants to receive */
+ unsigned idle_subscriptions;
+};
+
+extern unsigned int client_max_connections;
+extern int client_timeout;
+extern size_t client_max_command_list_size;
+extern size_t client_max_output_buffer_size;
+
+bool
+client_list_is_empty(void);
+
+bool
+client_list_is_full(void);
+
+struct client *
+client_list_get_first(void);
+
+void
+client_list_add(struct client *client);
+
+void
+client_list_foreach(GFunc func, gpointer user_data);
+
+void
+client_list_remove(struct client *client);
+
+void
+client_close(struct client *client);
+
+static inline void
+new_cmd_list_ptr(struct client *client, const char *s)
+{
+ client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
+}
+
+static inline void
+free_cmd_list(GSList *list)
+{
+ for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
+ g_free(tmp->data);
+
+ g_slist_free(list);
+}
+
+void
+client_set_expired(struct client *client);
+
+/**
+ * Schedule an "expired" check for all clients: permanently delete
+ * clients which have been set "expired" with client_set_expired().
+ */
+void
+client_schedule_expire(void);
+
+/**
+ * Removes a scheduled "expired" check.
+ */
+void
+client_deinit_expire(void);
+
+enum command_return
+client_read(struct client *client);
+
+enum command_return
+client_process_line(struct client *client, char *line);
+
+void
+client_write_deferred(struct client *client);
+
+void
+client_write_output(struct client *client);
+
+gboolean
+client_in_event(GIOChannel *source, GIOCondition condition,
+ gpointer data);
+
+#endif
diff --git a/src/client_list.c b/src/client_list.c
new file mode 100644
index 000000000..80eb7a29d
--- /dev/null
+++ b/src/client_list.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2009 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 "client_internal.h"
+
+#include <assert.h>
+
+static GList *clients;
+static unsigned num_clients;
+
+bool
+client_list_is_empty(void)
+{
+ return num_clients == 0;
+}
+
+bool
+client_list_is_full(void)
+{
+ return num_clients >= client_max_connections;
+}
+
+struct client *
+client_list_get_first(void)
+{
+ assert(clients != NULL);
+
+ return clients->data;
+}
+
+void
+client_list_add(struct client *client)
+{
+ clients = g_list_prepend(clients, client);
+ ++num_clients;
+}
+
+void
+client_list_foreach(GFunc func, gpointer user_data)
+{
+ g_list_foreach(clients, func, user_data);
+}
+
+void
+client_list_remove(struct client *client)
+{
+ assert(num_clients > 0);
+ assert(clients != NULL);
+
+ clients = g_list_remove(clients, client);
+ --num_clients;
+}
diff --git a/src/client_new.c b/src/client_new.c
new file mode 100644
index 000000000..bc7ee2140
--- /dev/null
+++ b/src/client_new.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2003-2009 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 "client_internal.h"
+#include "fifo_buffer.h"
+#include "socket_util.h"
+#include "permission.h"
+#include "config.h"
+
+#include <assert.h>
+#include <unistd.h>
+
+#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
+
+static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
+
+void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
+{
+ static unsigned int next_client_num;
+ struct client *client;
+ char *remote;
+
+ assert(fd >= 0);
+
+ if (client_list_is_full()) {
+ g_warning("Max Connections Reached!");
+ close(fd);
+ return;
+ }
+
+ client = g_new0(struct client, 1);
+
+#ifndef G_OS_WIN32
+ client->channel = g_io_channel_unix_new(fd);
+#else
+ client->channel = g_io_channel_win32_new_socket(fd);
+#endif
+ /* 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; the MPD
+ protocol is UTF-8 only, but we are doing this call anyway
+ to prevent GLib from messing around with the stream */
+ g_io_channel_set_encoding(client->channel, NULL, NULL);
+ /* we prefer to do buffering */
+ g_io_channel_set_buffered(client->channel, false);
+
+ client->source_id = g_io_add_watch(client->channel,
+ G_IO_IN|G_IO_ERR|G_IO_HUP,
+ client_in_event, client);
+
+ client->input = fifo_buffer_new(4096);
+
+ client->permission = getDefaultPermissions();
+ client->uid = uid;
+
+ client->last_activity = g_timer_new();
+
+ client->cmd_list = NULL;
+ client->cmd_list_OK = -1;
+ client->cmd_list_size = 0;
+
+ client->deferred_send = g_queue_new();
+ client->deferred_bytes = 0;
+ client->num = next_client_num++;
+
+ client->send_buf_used = 0;
+
+ (void)write(fd, GREETING, sizeof(GREETING) - 1);
+
+ client_list_add(client);
+
+ remote = sockaddr_to_string(sa, sa_length, NULL);
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
+ "[%u] opened from %s", client->num, remote);
+ g_free(remote);
+}
+
+static void
+deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct deferred_buffer *buffer = data;
+ g_free(buffer);
+}
+
+void
+client_close(struct client *client)
+{
+ client_list_remove(client);
+
+ client_set_expired(client);
+
+ g_timer_destroy(client->last_activity);
+
+ if (client->cmd_list) {
+ free_cmd_list(client->cmd_list);
+ client->cmd_list = NULL;
+ }
+
+ g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
+ g_queue_free(client->deferred_send);
+
+ fifo_buffer_free(client->input);
+
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
+ "[%u] closed", client->num);
+ g_free(client);
+}
diff --git a/src/client_process.c b/src/client_process.c
new file mode 100644
index 000000000..2f69cc6a8
--- /dev/null
+++ b/src/client_process.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2003-2009 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 "client_internal.h"
+
+#include <string.h>
+
+#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
+#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
+#define CLIENT_LIST_MODE_END "command_list_end"
+
+static enum command_return
+client_process_command_list(struct client *client, bool list_ok, GSList *list)
+{
+ enum command_return ret = COMMAND_RETURN_OK;
+ unsigned num = 0;
+
+ for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
+ char *cmd = cur->data;
+
+ g_debug("command_process_list: process command \"%s\"",
+ cmd);
+ ret = command_process(client, num++, cmd);
+ g_debug("command_process_list: command returned %i", ret);
+ if (ret != COMMAND_RETURN_OK || client_is_expired(client))
+ break;
+ else if (list_ok)
+ client_puts(client, "list_OK\n");
+ }
+
+ return ret;
+}
+
+enum command_return
+client_process_line(struct client *client, char *line)
+{
+ enum command_return ret;
+
+ if (strcmp(line, "noidle") == 0) {
+ if (client->idle_waiting) {
+ /* send empty idle response and leave idle mode */
+ client->idle_waiting = false;
+ command_success(client);
+ client_write_output(client);
+ }
+
+ /* do nothing if the client wasn't idling: the client
+ has already received the full idle response from
+ client_idle_notify(), which he can now evaluate */
+
+ return COMMAND_RETURN_OK;
+ } else if (client->idle_waiting) {
+ /* during idle mode, clients must not send anything
+ except "noidle" */
+ g_warning("[%u] command \"%s\" during idle",
+ client->num, line);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ if (client->cmd_list_OK >= 0) {
+ if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
+ g_debug("[%u] process command list",
+ client->num);
+
+ /* for scalability reasons, we have prepended
+ each new command; now we have to reverse it
+ to restore the correct order */
+ client->cmd_list = g_slist_reverse(client->cmd_list);
+
+ ret = client_process_command_list(client,
+ client->cmd_list_OK,
+ client->cmd_list);
+ g_debug("[%u] process command "
+ "list returned %i", client->num, ret);
+
+ if (ret == COMMAND_RETURN_CLOSE ||
+ client_is_expired(client))
+ return COMMAND_RETURN_CLOSE;
+
+ if (ret == COMMAND_RETURN_OK)
+ command_success(client);
+
+ client_write_output(client);
+ free_cmd_list(client->cmd_list);
+ client->cmd_list = NULL;
+ client->cmd_list_OK = -1;
+ } else {
+ size_t len = strlen(line) + 1;
+ client->cmd_list_size += len;
+ if (client->cmd_list_size >
+ client_max_command_list_size) {
+ g_warning("[%u] command list size (%lu) "
+ "is larger than the max (%lu)",
+ client->num,
+ (unsigned long)client->cmd_list_size,
+ (unsigned long)client_max_command_list_size);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ new_cmd_list_ptr(client, line);
+ ret = COMMAND_RETURN_OK;
+ }
+ } else {
+ if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
+ client->cmd_list_OK = 0;
+ ret = COMMAND_RETURN_OK;
+ } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
+ client->cmd_list_OK = 1;
+ ret = COMMAND_RETURN_OK;
+ } else {
+ g_debug("[%u] process command \"%s\"",
+ client->num, line);
+ ret = command_process(client, 0, line);
+ g_debug("[%u] command returned %i",
+ client->num, ret);
+
+ if (ret == COMMAND_RETURN_CLOSE ||
+ client_is_expired(client))
+ return COMMAND_RETURN_CLOSE;
+
+ if (ret == COMMAND_RETURN_OK)
+ command_success(client);
+
+ client_write_output(client);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/client_read.c b/src/client_read.c
new file mode 100644
index 000000000..1d64035e3
--- /dev/null
+++ b/src/client_read.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2003-2009 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 "client_internal.h"
+#include "fifo_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+
+static char *
+client_read_line(struct client *client)
+{
+ const char *p, *newline;
+ size_t length;
+ char *line;
+
+ p = fifo_buffer_read(client->input, &length);
+ if (p == NULL)
+ return NULL;
+
+ newline = memchr(p, '\n', length);
+ if (newline == NULL)
+ return NULL;
+
+ line = g_strndup(p, newline - p);
+ fifo_buffer_consume(client->input, newline - p + 1);
+
+ return g_strchomp(line);
+}
+
+static enum command_return
+client_input_received(struct client *client, size_t bytesRead)
+{
+ char *line;
+
+ fifo_buffer_append(client->input, bytesRead);
+
+ /* process all lines */
+
+ while ((line = client_read_line(client)) != NULL) {
+ enum command_return ret = client_process_line(client, line);
+ g_free(line);
+
+ if (ret == COMMAND_RETURN_KILL ||
+ ret == COMMAND_RETURN_CLOSE)
+ return ret;
+ if (client_is_expired(client))
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+client_read(struct client *client)
+{
+ char *p;
+ size_t max_length;
+ GError *error = NULL;
+ GIOStatus status;
+ gsize bytes_read;
+
+ assert(client != NULL);
+ assert(client->channel != NULL);
+
+ p = fifo_buffer_write(client->input, &max_length);
+ if (p == NULL) {
+ g_warning("[%u] buffer overflow", client->num);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ status = g_io_channel_read_chars(client->channel, p, max_length,
+ &bytes_read, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ return client_input_received(client, bytes_read);
+
+ case G_IO_STATUS_AGAIN:
+ /* try again later, after select() */
+ return COMMAND_RETURN_OK;
+
+ case G_IO_STATUS_EOF:
+ /* peer disconnected */
+ return COMMAND_RETURN_CLOSE;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+ g_warning("failed to read from client %d: %s",
+ client->num, error->message);
+ g_error_free(error);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ /* unreachable */
+ return COMMAND_RETURN_CLOSE;
+}
diff --git a/src/client_write.c b/src/client_write.c
new file mode 100644
index 000000000..686c7d96a
--- /dev/null
+++ b/src/client_write.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2003-2009 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 "client_internal.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+static size_t
+client_write_deferred_buffer(struct client *client,
+ const struct deferred_buffer *buffer)
+{
+ GError *error = NULL;
+ GIOStatus status;
+ gsize bytes_written;
+
+ assert(client != NULL);
+ assert(client->channel != NULL);
+ assert(buffer != NULL);
+
+ status = g_io_channel_write_chars
+ (client->channel, buffer->data, buffer->size,
+ &bytes_written, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ return bytes_written;
+
+ case G_IO_STATUS_AGAIN:
+ return 0;
+
+ case G_IO_STATUS_EOF:
+ /* client has disconnected */
+
+ client_set_expired(client);
+ return 0;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+
+ client_set_expired(client);
+ g_warning("failed to flush buffer for %i: %s",
+ client->num, error->message);
+ g_error_free(error);
+ return 0;
+ }
+
+ /* unreachable */
+ return 0;
+}
+
+void
+client_write_deferred(struct client *client)
+{
+ size_t ret;
+
+ while (!g_queue_is_empty(client->deferred_send)) {
+ struct deferred_buffer *buf =
+ g_queue_peek_head(client->deferred_send);
+
+ assert(buf->size > 0);
+ assert(buf->size <= client->deferred_bytes);
+
+ ret = client_write_deferred_buffer(client, buf);
+ if (ret == 0)
+ break;
+
+ if (ret < buf->size) {
+ assert(client->deferred_bytes >= (size_t)ret);
+ client->deferred_bytes -= ret;
+ buf->size -= ret;
+ memmove(buf->data, buf->data + ret, buf->size);
+ break;
+ } else {
+ size_t decr = sizeof(*buf) -
+ sizeof(buf->data) + buf->size;
+
+ assert(client->deferred_bytes >= decr);
+ client->deferred_bytes -= decr;
+ g_free(buf);
+ g_queue_pop_head(client->deferred_send);
+ }
+
+ g_timer_start(client->last_activity);
+ }
+
+ if (g_queue_is_empty(client->deferred_send)) {
+ g_debug("[%u] buffer empty %lu", client->num,
+ (unsigned long)client->deferred_bytes);
+ assert(client->deferred_bytes == 0);
+ }
+}
+
+static void client_defer_output(struct client *client,
+ const void *data, size_t length)
+{
+ size_t alloc;
+ struct deferred_buffer *buf;
+
+ assert(length > 0);
+
+ alloc = sizeof(*buf) - sizeof(buf->data) + length;
+ client->deferred_bytes += alloc;
+ if (client->deferred_bytes > client_max_output_buffer_size) {
+ g_warning("[%u] output buffer size (%lu) is "
+ "larger than the max (%lu)",
+ client->num,
+ (unsigned long)client->deferred_bytes,
+ (unsigned long)client_max_output_buffer_size);
+ /* cause client to close */
+ client_set_expired(client);
+ return;
+ }
+
+ buf = g_malloc(alloc);
+ buf->size = length;
+ memcpy(buf->data, data, length);
+
+ g_queue_push_tail(client->deferred_send, buf);
+}
+
+static void client_write_direct(struct client *client,
+ const char *data, size_t length)
+{
+ GError *error = NULL;
+ GIOStatus status;
+ gsize bytes_written;
+
+ assert(client != NULL);
+ assert(client->channel != NULL);
+ assert(data != NULL);
+ assert(length > 0);
+ assert(g_queue_is_empty(client->deferred_send));
+
+ status = g_io_channel_write_chars(client->channel, data, length,
+ &bytes_written, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ case G_IO_STATUS_AGAIN:
+ break;
+
+ case G_IO_STATUS_EOF:
+ /* client has disconnected */
+
+ client_set_expired(client);
+ return;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+
+ client_set_expired(client);
+ g_warning("failed to write to %i: %s",
+ client->num, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ if (bytes_written < length)
+ client_defer_output(client, data + bytes_written,
+ length - bytes_written);
+
+ if (!g_queue_is_empty(client->deferred_send))
+ g_debug("[%u] buffer created", client->num);
+}
+
+void
+client_write_output(struct client *client)
+{
+ if (client_is_expired(client) || !client->send_buf_used)
+ return;
+
+ if (!g_queue_is_empty(client->deferred_send)) {
+ client_defer_output(client, client->send_buf,
+ client->send_buf_used);
+
+ if (client_is_expired(client))
+ return;
+
+ /* try to flush the deferred buffers now; the current
+ server command may take too long to finish, and
+ meanwhile try to feed output to the client,
+ otherwise it will time out. One reason why
+ deferring is slow might be that currently each
+ client_write() allocates a new deferred buffer.
+ This should be optimized after MPD 0.14. */
+ client_write_deferred(client);
+ } else
+ client_write_direct(client, client->send_buf,
+ client->send_buf_used);
+
+ client->send_buf_used = 0;
+}
+
+/**
+ * Write a block of data to the client.
+ */
+static void client_write(struct client *client, const char *buffer, size_t buflen)
+{
+ /* if the client is going to be closed, do nothing */
+ if (client_is_expired(client))
+ return;
+
+ while (buflen > 0 && !client_is_expired(client)) {
+ size_t copylen;
+
+ assert(client->send_buf_used < sizeof(client->send_buf));
+
+ copylen = sizeof(client->send_buf) - client->send_buf_used;
+ if (copylen > buflen)
+ copylen = buflen;
+
+ memcpy(client->send_buf + client->send_buf_used, buffer,
+ copylen);
+ buflen -= copylen;
+ client->send_buf_used += copylen;
+ buffer += copylen;
+ if (client->send_buf_used >= sizeof(client->send_buf))
+ client_write_output(client);
+ }
+}
+
+void client_puts(struct client *client, const char *s)
+{
+ client_write(client, s, strlen(s));
+}
+
+void client_vprintf(struct client *client, const char *fmt, va_list args)
+{
+ va_list tmp;
+ int length;
+ char *buffer;
+
+ va_copy(tmp, args);
+ length = vsnprintf(NULL, 0, fmt, tmp);
+ va_end(tmp);
+
+ if (length <= 0)
+ /* wtf.. */
+ return;
+
+ buffer = g_malloc(length + 1);
+ vsnprintf(buffer, length + 1, fmt, args);
+ client_write(client, buffer, length);
+ g_free(buffer);
+}
+
+G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ client_vprintf(client, fmt, args);
+ va_end(args);
+}
diff --git a/src/cmdline.c b/src/cmdline.c
index e0274ef36..695857d54 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -22,6 +22,7 @@
#include "log.h"
#include "conf.h"
#include "decoder_list.h"
+#include "decoder_plugin.h"
#include "config.h"
#include "output_list.h"
#include "ls.h"
@@ -35,10 +36,34 @@
#include <stdio.h>
#include <stdlib.h>
-#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf"
#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
+static GQuark
+cmdline_quark(void)
+{
+ return g_quark_from_static_string("cmdline");
+}
+
+static void
+print_all_decoders(FILE *fp)
+{
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
+ const struct decoder_plugin *plugin = decoder_plugins[i];
+ const char *const*suffixes;
+
+ fprintf(fp, "[%s]", plugin->name);
+
+ for (suffixes = plugin->suffixes;
+ suffixes != NULL && *suffixes != NULL;
+ ++suffixes) {
+ fprintf(fp, " %s", *suffixes);
+ }
+
+ fprintf(fp, "\n");
+ }
+}
+
G_GNUC_NORETURN
static void version(void)
{
@@ -51,8 +76,7 @@ static void version(void)
"\n"
"Supported decoders:\n");
- decoder_plugin_init_all();
- decoder_plugin_print_all_decoders(stdout);
+ print_all_decoders(stdout);
puts("\n"
"Supported outputs:\n");
@@ -72,31 +96,29 @@ static void version(void)
exit(EXIT_SUCCESS);
}
-#if GLIB_CHECK_VERSION(2,12,0)
static const char *summary =
"Music Player Daemon - a daemon for playing music.";
-#endif
-void parseOptions(int argc, char **argv, Options *options)
+bool
+parse_cmdline(int argc, char **argv, struct options *options,
+ GError **error_r)
{
GError *error = NULL;
GOptionContext *context;
bool ret;
static gboolean option_version,
- option_create_db, option_no_create_db, option_no_daemon,
+ option_no_daemon,
option_no_config;
const GOptionEntry entries[] = {
- { "create-db", 0, 0, G_OPTION_ARG_NONE, &option_create_db,
- "force (re)creation of database", NULL },
{ "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill,
"kill the currently running mpd session", NULL },
{ "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
"don't read from config", NULL },
- { "no-create-db", 0, 0, G_OPTION_ARG_NONE, &option_no_create_db,
- "don't create database, even if it doesn't exist", NULL },
{ "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
"don't detach from console", NULL },
- { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->stdOutput,
+ { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
+ NULL, NULL },
+ { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
"print messages to stderr", NULL },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
"verbose logging", NULL },
@@ -107,16 +129,13 @@ void parseOptions(int argc, char **argv, Options *options)
options->kill = false;
options->daemon = true;
- options->stdOutput = false;
+ options->log_stderr = false;
options->verbose = false;
- options->createDB = 0;
context = g_option_context_new("[path/to/mpd.conf]");
g_option_context_add_main_entries(context, entries, NULL);
-#if GLIB_CHECK_VERSION(2,12,0)
g_option_context_set_summary(context, summary);
-#endif
ret = g_option_context_parse(context, &argc, &argv, &error);
g_option_context_free(context);
@@ -133,18 +152,11 @@ void parseOptions(int argc, char **argv, Options *options)
parser can use it already */
log_early_init(options->verbose);
- if (option_create_db && option_no_create_db)
- g_error("Cannot use both --create-db and --no-create-db\n");
-
- if (option_no_create_db)
- options->createDB = -1;
- else if (option_create_db)
- options->createDB = 1;
-
options->daemon = !option_no_daemon;
if (option_no_config) {
g_debug("Ignoring config, using daemon defaults\n");
+ return true;
} else if (argc <= 1) {
/* default configuration file path */
char *path1;
@@ -155,17 +167,23 @@ void parseOptions(int argc, char **argv, Options *options)
path2 = g_build_filename(g_get_home_dir(),
USER_CONFIG_FILE_LOCATION2, NULL);
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
- config_read_file(path1);
+ ret = config_read_file(path1, error_r);
else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR))
- config_read_file(path2);
+ ret = config_read_file(path2, error_r);
else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION,
G_FILE_TEST_IS_REGULAR))
- config_read_file(SYSTEM_CONFIG_FILE_LOCATION);
+ ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION,
+ error_r);
g_free(path1);
g_free(path2);
+
+ return ret;
} else if (argc == 2) {
/* specified configuration file */
- config_read_file(argv[1]);
- } else
- g_error("too many arguments");
+ return config_read_file(argv[1], error_r);
+ } else {
+ g_set_error(error_r, cmdline_quark(), 0,
+ "too many arguments");
+ return false;
+ }
}
diff --git a/src/cmdline.h b/src/cmdline.h
index 673701055..945868b8c 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -22,14 +22,17 @@
#include <glib.h>
-typedef struct _Options {
+#include <stdbool.h>
+
+struct options {
gboolean kill;
gboolean daemon;
- gboolean stdOutput;
+ gboolean log_stderr;
gboolean verbose;
- int createDB;
-} Options;
+};
-void parseOptions(int argc, char **argv, Options *options);
+bool
+parse_cmdline(int argc, char **argv, struct options *options,
+ GError **error_r);
#endif
diff --git a/src/command.c b/src/command.c
index d30b63594..2bd84d3a3 100644
--- a/src/command.c
+++ b/src/command.c
@@ -22,9 +22,11 @@
#include "playlist.h"
#include "playlist_print.h"
#include "playlist_save.h"
+#include "playlist_queue.h"
#include "queue_print.h"
#include "ls.h"
#include "uri.h"
+#include "decoder_print.h"
#include "directory.h"
#include "directory_print.h"
#include "database.h"
@@ -32,7 +34,7 @@
#include "volume.h"
#include "stats.h"
#include "permission.h"
-#include "buffer2array.h"
+#include "tokenizer.h"
#include "stored_playlist.h"
#include "ack.h"
#include "output_command.h"
@@ -43,6 +45,7 @@
#include "client.h"
#include "tag_print.h"
#include "path.h"
+#include "replay_gain.h"
#include "idle.h"
#include "config.h"
@@ -58,7 +61,6 @@
#include <stdlib.h>
#include <errno.h>
-#define COMMAND_STATUS_VOLUME "volume"
#define COMMAND_STATUS_STATE "state"
#define COMMAND_STATUS_REPEAT "repeat"
#define COMMAND_STATUS_SINGLE "single"
@@ -166,8 +168,8 @@ check_int(struct client *client, int *value_r,
return false;
}
-#if LONG_MAX > INT_MAX
- if (value < INT_MIN || value > INT_MAX) {
+#if G_MAXLONG > G_MAXINT
+ if (value < G_MININT || value > G_MAXINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -198,7 +200,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
/* compatibility with older MPD versions: specifying
"-1" makes MPD display the whole list */
*value_r1 = 0;
- *value_r2 = UINT_MAX;
+ *value_r2 = G_MAXUINT;
return true;
}
@@ -208,8 +210,8 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
return false;
}
-#if LONG_MAX > UINT_MAX
- if (value > UINT_MAX) {
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -220,7 +222,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
if (*test == ':') {
value = strtol(++test, &test2, 10);
- if (*test2 != '\0' || test == test2) {
+ if (*test2 != '\0') {
va_list args;
va_start(args, fmt);
command_error_v(client, ACK_ERROR_ARG, fmt, args);
@@ -228,14 +230,17 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
return false;
}
+ if (test == test2)
+ value = G_MAXUINT;
+
if (value < 0) {
command_error(client, ACK_ERROR_ARG,
"Number is negative: %s", s);
return false;
}
-#if LONG_MAX > UINT_MAX
- if (value > UINT_MAX) {
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -262,7 +267,7 @@ check_unsigned(struct client *client, unsigned *value_r, const char *s)
return false;
}
- if (value > UINT_MAX) {
+ if (value > G_MAXUINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -385,6 +390,14 @@ handle_urlhandlers(struct client *client,
}
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[])
{
@@ -400,7 +413,7 @@ handle_play(struct client *client, int argc, char *argv[])
if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = playPlaylist(&g_playlist, song);
+ result = playlist_play(&g_playlist, song);
return print_playlist_result(client, result);
}
@@ -413,7 +426,7 @@ handle_playid(struct client *client, int argc, char *argv[])
if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = playPlaylistById(&g_playlist, id);
+ result = playlist_play_id(&g_playlist, id);
return print_playlist_result(client, result);
}
@@ -421,7 +434,7 @@ static enum command_return
handle_stop(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- stopPlaylist(&g_playlist);
+ playlist_stop(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -441,11 +454,11 @@ handle_pause(struct client *client,
bool pause_flag;
if (!check_bool(client, &pause_flag, argv[1]))
return COMMAND_RETURN_ERROR;
- playerSetPause(pause_flag);
- return COMMAND_RETURN_OK;
- }
- playerPause();
+ pc_set_pause(pause_flag);
+ } else
+ pc_pause();
+
return COMMAND_RETURN_OK;
}
@@ -454,10 +467,14 @@ 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;
- switch (getPlayerState()) {
+ pc_get_status(&player_status);
+
+ switch (player_status.state) {
case PLAYER_STATE_STOP:
state = "stop";
break;
@@ -470,7 +487,7 @@ handle_status(struct client *client,
}
client_printf(client,
- COMMAND_STATUS_VOLUME ": %i\n"
+ "volume: %i\n"
COMMAND_STATUS_REPEAT ": %i\n"
COMMAND_STATUS_RANDOM ": %i\n"
COMMAND_STATUS_SINGLE ": %i\n"
@@ -480,32 +497,36 @@ handle_status(struct client *client,
COMMAND_STATUS_CROSSFADE ": %i\n"
COMMAND_STATUS_STATE ": %s\n",
volume_level_get(),
- getPlaylistRepeatStatus(&g_playlist),
- getPlaylistRandomStatus(&g_playlist),
- getPlaylistSingleStatus(&g_playlist),
- getPlaylistConsumeStatus(&g_playlist),
- getPlaylistVersion(&g_playlist),
- getPlaylistLength(&g_playlist),
- (int)(getPlayerCrossFade() + 0.5),
+ 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() + 0.5),
state);
- song = getPlaylistCurrentSong(&g_playlist);
+ song = playlist_get_current_song(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_SONG ": %i\n"
COMMAND_STATUS_SONGID ": %u\n",
- song, getPlaylistSongId(&g_playlist, song));
+ song, playlist_get_song_id(&g_playlist, song));
}
- if (getPlayerState() != PLAYER_STATE_STOP) {
- const struct audio_format *af = player_get_audio_format();
+ if (player_status.state != PLAYER_STATE_STOP) {
client_printf(client,
COMMAND_STATUS_TIME ": %i:%i\n"
- COMMAND_STATUS_BITRATE ": %li\n"
+ "elapsed: %1.3f\n"
+ COMMAND_STATUS_BITRATE ": %u\n"
COMMAND_STATUS_AUDIO ": %u:%u:%u\n",
- getPlayerElapsedTime(), getPlayerTotalTime(),
- getPlayerBitRate(),
- af->sample_rate, af->bits, af->channels);
+ (int)(player_status.elapsed_time + 0.5),
+ (int)(player_status.total_time + 0.5),
+ player_status.elapsed_time,
+ player_status.bit_rate,
+ player_status.audio_format.sample_rate,
+ player_status.audio_format.bits,
+ player_status.audio_format.channels);
}
if ((updateJobId = isUpdatingDB())) {
@@ -514,18 +535,20 @@ handle_status(struct client *client,
updateJobId);
}
- if (getPlayerError() != PLAYER_ERROR_NOERROR) {
+ error = pc_get_error_message();
+ if (error != NULL) {
client_printf(client,
COMMAND_STATUS_ERROR ": %s\n",
- getPlayerErrorStr());
+ error);
+ g_free(error);
}
- song = getPlaylistNextSong(&g_playlist);
+ song = playlist_get_next_song(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_NEXTSONG ": %i\n"
COMMAND_STATUS_NEXTSONGID ": %u\n",
- song, getPlaylistSongId(&g_playlist, song));
+ song, playlist_get_song_id(&g_playlist, song));
}
return COMMAND_RETURN_OK;
@@ -569,7 +592,7 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- result = addToPlaylist(&g_playlist, uri, NULL);
+ result = playlist_append_uri(&g_playlist, uri, NULL);
return print_playlist_result(client, result);
}
@@ -605,7 +628,7 @@ handle_addid(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- result = addToPlaylist(&g_playlist, uri, &added_id);
+ result = playlist_append_uri(&g_playlist, uri, &added_id);
}
if (result != PLAYLIST_RESULT_SUCCESS)
@@ -615,11 +638,11 @@ handle_addid(struct client *client, int argc, char *argv[])
int to;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = moveSongInPlaylistById(&g_playlist, added_id, to);
+ result = playlist_move_id(&g_playlist, added_id, to);
if (result != PLAYLIST_RESULT_SUCCESS) {
enum command_return ret =
print_playlist_result(client, result);
- deleteFromPlaylistById(&g_playlist, added_id);
+ playlist_delete_id(&g_playlist, added_id);
return ret;
}
}
@@ -631,13 +654,13 @@ handle_addid(struct client *client, int argc, char *argv[])
static enum command_return
handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int song;
+ unsigned start, end;
enum playlist_result result;
- if (!check_int(client, &song, argv[1], need_positive))
+ if (!check_range(client, &start, &end, argv[1], need_range))
return COMMAND_RETURN_ERROR;
- result = deleteFromPlaylist(&g_playlist, song);
+ result = playlist_delete_range(&g_playlist, start, end);
return print_playlist_result(client, result);
}
@@ -650,7 +673,7 @@ handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &id, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = deleteFromPlaylistById(&g_playlist, id);
+ result = playlist_delete_id(&g_playlist, id);
return print_playlist_result(client, result);
}
@@ -671,7 +694,7 @@ handle_shuffle(G_GNUC_UNUSED struct client *client,
argv[1], need_range))
return COMMAND_RETURN_ERROR;
- shufflePlaylist(&g_playlist, start, end);
+ playlist_shuffle(&g_playlist, start, end);
return COMMAND_RETURN_OK;
}
@@ -679,7 +702,7 @@ static enum command_return
handle_clear(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- clearPlaylist(&g_playlist);
+ playlist_clear(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -698,6 +721,10 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
enum playlist_result result;
+ result = playlist_open_into_queue(argv[1], &g_playlist);
+ if (result != PLAYLIST_RESULT_NO_SUCH_LIST)
+ return result;
+
result = playlist_load_spl(&g_playlist, argv[1]);
return print_playlist_result(client, result);
}
@@ -808,7 +835,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[
static enum command_return
handle_playlistinfo(struct client *client, int argc, char *argv[])
{
- unsigned start = 0, end = UINT_MAX;
+ unsigned start = 0, end = G_MAXUINT;
bool ret;
if (argc == 2 && !check_range(client, &start, &end,
@@ -837,7 +864,7 @@ handle_playlistid(struct client *client, int argc, char *argv[])
return print_playlist_result(client,
PLAYLIST_RESULT_NO_SUCH_SONG);
} else {
- playlist_print_info(client, &g_playlist, 0, UINT_MAX);
+ playlist_print_info(client, &g_playlist, 0, G_MAXUINT);
}
return COMMAND_RETURN_OK;
@@ -869,6 +896,30 @@ handle_find(struct client *client, int argc, char *argv[])
}
static enum command_return
+handle_findadd(struct client *client, int argc, char *argv[])
+{
+ int ret;
+ 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;
+ }
+
+ ret = findAddIn(client, NULL, list);
+ if (ret == -1)
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "directory or file not found");
+
+ locate_item_list_free(list);
+
+ return ret;
+}
+
+static enum command_return
handle_search(struct client *client, int argc, char *argv[])
{
int ret;
@@ -993,14 +1044,35 @@ handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
static enum command_return
handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- char *path = NULL;
+ const char *path = NULL;
unsigned ret;
assert(argc <= 2);
if (argc == 2)
- path = g_strdup(argv[1]);
+ path = argv[1];
- ret = directory_update_init(path);
+ 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];
+
+ ret = update_enqueue(path, true);
if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret);
return COMMAND_RETURN_OK;
@@ -1020,7 +1092,7 @@ handle_next(G_GNUC_UNUSED struct client *client,
int single = g_playlist.queue.single;
g_playlist.queue.single = false;
- nextSongInPlaylist(&g_playlist);
+ playlist_next(&g_playlist);
g_playlist.queue.single = single;
return COMMAND_RETURN_OK;
@@ -1030,7 +1102,7 @@ static enum command_return
handle_previous(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- previousSongInPlaylist(&g_playlist);
+ playlist_previous(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -1052,25 +1124,6 @@ handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
}
static enum command_return
-handle_volume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- int change;
- bool success;
-
- if (!check_int(client, &change, argv[1], need_integer))
- return COMMAND_RETURN_ERROR;
-
- success = volume_level_change(change, true);
- if (!success) {
- command_error(client, ACK_ERROR_SYSTEM,
- "problems setting volume");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
int level;
@@ -1079,7 +1132,12 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &level, argv[1], need_integer))
return COMMAND_RETURN_ERROR;
- success = volume_level_change(level, 0);
+ if (level < 0 || 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");
@@ -1103,7 +1161,7 @@ handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistRepeatStatus(&g_playlist, status);
+ playlist_set_repeat(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1121,7 +1179,7 @@ handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistSingleStatus(&g_playlist, status);
+ playlist_set_single(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1139,7 +1197,7 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistConsumeStatus(&g_playlist, status);
+ playlist_set_consume(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1157,7 +1215,7 @@ handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistRandomStatus(&g_playlist, status);
+ playlist_set_random(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1172,7 +1230,7 @@ static enum command_return
handle_clearerror(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- clearPlayerError();
+ pc_clear_error();
return COMMAND_RETURN_OK;
}
@@ -1196,17 +1254,17 @@ handle_list(struct client *client, int argc, char *argv[])
/* for compatibility with < 0.12.0 */
if (argc == 3) {
- if (tagType != TAG_ITEM_ALBUM) {
+ if (tagType != TAG_ALBUM) {
command_error(client, ACK_ERROR_ARG,
"should be \"%s\" for 3 arguments",
- tag_item_names[TAG_ITEM_ALBUM]);
+ 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_ITEM_ARTIST;
+ conditionals->items[0].tag = TAG_ARTIST;
conditionals->items[0].needle = g_strdup(argv[2]);
} else {
conditionals =
@@ -1241,7 +1299,7 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = moveSongRangeInPlaylist(&g_playlist, start, end, to);
+ result = playlist_move_range(&g_playlist, start, end, to);
return print_playlist_result(client, result);
}
@@ -1255,7 +1313,7 @@ handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = moveSongInPlaylistById(&g_playlist, id, to);
+ result = playlist_move_id(&g_playlist, id, to);
return print_playlist_result(client, result);
}
@@ -1269,7 +1327,7 @@ handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &song2, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = swapSongsInPlaylist(&g_playlist, song1, song2);
+ result = playlist_swap_songs(&g_playlist, song1, song2);
return print_playlist_result(client, result);
}
@@ -1283,7 +1341,7 @@ handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &id2, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = swapSongsInPlaylistById(&g_playlist, id1, id2);
+ result = playlist_swap_songs_id(&g_playlist, id1, id2);
return print_playlist_result(client, result);
}
@@ -1298,7 +1356,7 @@ handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = seekSongInPlaylist(&g_playlist, song, seek_time);
+ result = playlist_seek_song(&g_playlist, song, seek_time);
return print_playlist_result(client, result);
}
@@ -1313,7 +1371,7 @@ handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = seekSongInPlaylistById(&g_playlist, id, seek_time);
+ result = playlist_seek_song_id(&g_playlist, id, seek_time);
return print_playlist_result(client, result);
}
@@ -1363,7 +1421,7 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_unsigned(client, &xfade_time, argv[1]))
return COMMAND_RETURN_ERROR;
- setPlayerCrossFade(xfade_time);
+ pc_set_cross_fade(xfade_time);
return COMMAND_RETURN_OK;
}
@@ -1477,6 +1535,28 @@ handle_listplaylists(struct client *client,
}
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[])
{
@@ -1519,13 +1599,14 @@ sticker_song_find_print_cb(struct song *song, const char *value,
{
struct sticker_song_find_data *data = user_data;
- song_print_url(data->client, song);
+ 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;
@@ -1548,6 +1629,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
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;
@@ -1570,6 +1652,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
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;
@@ -1589,6 +1672,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
return COMMAND_RETURN_OK;
+ /* delete song song_id [key] */
} else if ((argc == 4 || argc == 5) &&
strcmp(argv[1], "delete") == 0) {
struct song *song;
@@ -1611,6 +1695,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
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;
@@ -1679,11 +1764,13 @@ static const struct command commands[] = {
{ "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 },
@@ -1719,6 +1806,11 @@ static const struct command commands[] = {
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
{ "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_ADMIN, 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 },
@@ -1738,7 +1830,6 @@ static const struct command commands[] = {
{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
{ "update", PERMISSION_ADMIN, 0, 1, handle_update },
{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
- { "volume", PERMISSION_CONTROL, 1, 1, handle_volume },
};
static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
@@ -1892,48 +1983,71 @@ command_checked_lookup(struct client *client, unsigned permission,
}
enum command_return
-command_process(struct client *client, char *commandString)
+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;
- if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX)))
- return COMMAND_RETURN_OK;
+ command_list_num = num;
- cmd = command_checked_lookup(client, client_get_permission(client),
- argc, argv);
- if (cmd)
- ret = cmd->handler(client, argc, argv);
+ /* get the command name (first word on the line) */
- current_command = NULL;
+ 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 ret;
-}
+ return COMMAND_RETURN_ERROR;
+ }
-enum command_return
-command_process_list(struct client *client,
- bool list_ok, GSList *list)
-{
- enum command_return ret = COMMAND_RETURN_OK;
+ argc = 1;
- command_list_num = 0;
+ /* 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];
- for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
- char *cmd = cur->data;
-
- g_debug("command_process_list: process command \"%s\"",
- cmd);
- ret = command_process(client, cmd);
- g_debug("command_process_list: command returned %i", ret);
- if (ret != COMMAND_RETURN_OK || client_is_expired(client))
- break;
- else if (list_ok)
- client_puts(client, "list_OK\n");
- command_list_num++;
+ 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 a7c408ed7..614a414b6 100644
--- a/src/command.h
+++ b/src/command.h
@@ -39,11 +39,7 @@ void command_init(void);
void command_finish(void);
enum command_return
-command_process_list(struct client *client,
- bool list_ok, GSList *list);
-
-enum command_return
-command_process(struct client *client, char *commandString);
+command_process(struct client *client, unsigned num, char *line);
void command_success(struct client *client);
diff --git a/src/conf.c b/src/conf.c
index cce7dbf27..4da44c775 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -19,7 +19,7 @@
#include "conf.h"
#include "utils.h"
-#include "buffer2array.h"
+#include "tokenizer.h"
#include "path.h"
#include <glib.h>
@@ -36,37 +36,82 @@
#define MAX_STRING_SIZE MPD_PATH_MAX+80
#define CONF_COMMENT '#'
-#define CONF_BLOCK_BEGIN "{"
-#define CONF_BLOCK_END "}"
-
-#define CONF_REPEATABLE_MASK 0x01
-#define CONF_BLOCK_MASK 0x02
-#define CONF_LINE_TOKEN_MAX 3
struct config_entry {
- const char *name;
- unsigned char mask;
+ const char *const name;
+ const bool repeatable;
+ const bool block;
GSList *params;
};
-static GSList *config_entries;
+static struct config_entry config_entries[] = {
+ { .name = CONF_MUSIC_DIR, false, false },
+ { .name = CONF_PLAYLIST_DIR, false, false },
+ { .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false },
+ { .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false },
+ { .name = CONF_DB_FILE, false, false },
+ { .name = CONF_STICKER_FILE, false, false },
+ { .name = CONF_LOG_FILE, false, false },
+ { .name = CONF_PID_FILE, false, false },
+ { .name = CONF_STATE_FILE, false, false },
+ { .name = CONF_USER, false, false },
+ { .name = CONF_GROUP, false, false },
+ { .name = CONF_BIND_TO_ADDRESS, true, false },
+ { .name = CONF_PORT, false, false },
+ { .name = CONF_LOG_LEVEL, false, false },
+ { .name = CONF_ZEROCONF_NAME, false, false },
+ { .name = CONF_ZEROCONF_ENABLED, false, false },
+ { .name = CONF_PASSWORD, true, false },
+ { .name = CONF_DEFAULT_PERMS, false, false },
+ { .name = CONF_AUDIO_OUTPUT, true, true },
+ { .name = CONF_AUDIO_OUTPUT_FORMAT, false, false },
+ { .name = CONF_MIXER_TYPE, false, false },
+ { .name = CONF_REPLAYGAIN, false, false },
+ { .name = CONF_REPLAYGAIN_PREAMP, false, false },
+ { .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false },
+ { .name = CONF_VOLUME_NORMALIZATION, false, false },
+ { .name = CONF_SAMPLERATE_CONVERTER, false, false },
+ { .name = CONF_AUDIO_BUFFER_SIZE, false, false },
+ { .name = CONF_BUFFER_BEFORE_PLAY, false, false },
+ { .name = CONF_HTTP_PROXY_HOST, false, false },
+ { .name = CONF_HTTP_PROXY_PORT, false, false },
+ { .name = CONF_HTTP_PROXY_USER, false, false },
+ { .name = CONF_HTTP_PROXY_PASSWORD, false, false },
+ { .name = CONF_CONN_TIMEOUT, false, false },
+ { .name = CONF_MAX_CONN, false, false },
+ { .name = CONF_MAX_PLAYLIST_LENGTH, false, false },
+ { .name = CONF_MAX_COMMAND_LIST_SIZE, false, false },
+ { .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false },
+ { .name = CONF_FS_CHARSET, false, false },
+ { .name = CONF_ID3V1_ENCODING, false, false },
+ { .name = CONF_METADATA_TO_USE, false, false },
+ { .name = CONF_SAVE_ABSOLUTE_PATHS, false, false },
+ { .name = CONF_DECODER, true, true },
+ { .name = CONF_INPUT, true, true },
+ { .name = CONF_GAPLESS_MP3_PLAYBACK, false, false },
+ { .name = CONF_PLAYLIST_PLUGIN, true, true },
+ { .name = CONF_AUTO_UPDATE, false, false },
+ { .name = "filter", true, true },
+};
-static int get_bool(const char *value)
+static bool
+get_bool(const char *value, bool *value_r)
{
- const char **x;
static const char *t[] = { "yes", "true", "1", NULL };
static const char *f[] = { "no", "false", "0", NULL };
- for (x = t; *x; x++) {
- if (!g_ascii_strcasecmp(*x, value))
- return 1;
+ if (string_array_contains(t, value)) {
+ *value_r = true;
+ return true;
}
- for (x = f; *x; x++) {
- if (!g_ascii_strcasecmp(*x, value))
- return 0;
+
+ if (string_array_contains(f, value)) {
+ *value_r = false;
+ return true;
}
- return CONF_BOOL_INVALID;
+
+ return false;
}
struct config_param *
@@ -83,15 +128,14 @@ config_new_param(const char *value, int line)
ret->num_block_params = 0;
ret->block_params = NULL;
+ ret->used = false;
return ret;
}
static void
-config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
+config_param_free(struct config_param *param)
{
- struct config_param *param = data;
-
g_free(param->value);
for (unsigned i = 0; i < param->num_block_params; i++) {
@@ -105,42 +149,19 @@ config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
g_free(param);
}
-static struct config_entry *
-newConfigEntry(const char *name, int repeatable, int block)
-{
- struct config_entry *ret = g_new(struct config_entry, 1);
-
- ret->name = name;
- ret->mask = 0;
- ret->params = NULL;
-
- if (repeatable)
- ret->mask |= CONF_REPEATABLE_MASK;
- if (block)
- ret->mask |= CONF_BLOCK_MASK;
-
- return ret;
-}
-
static void
-config_entry_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
+config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
- struct config_entry *entry = data;
-
- g_slist_foreach(entry->params, config_param_free, NULL);
- g_slist_free(entry->params);
+ struct config_param *param = data;
- g_free(entry);
+ config_param_free(param);
}
static struct config_entry *
config_entry_get(const char *name)
{
- GSList *list;
-
- for (list = config_entries; list != NULL;
- list = g_slist_next(list)) {
- struct config_entry *entry = list->data;
+ for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
+ struct config_entry *entry = &config_entries[i];
if (strcmp(entry->name, name) == 0)
return entry;
}
@@ -148,82 +169,65 @@ config_entry_get(const char *name)
return NULL;
}
-static void registerConfigParam(const char *name, int repeatable, int block)
+void config_global_finish(void)
{
- struct config_entry *entry;
+ for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
+ struct config_entry *entry = &config_entries[i];
- entry = config_entry_get(name);
- if (entry != NULL)
- g_error("config parameter \"%s\" already registered\n", name);
+ g_slist_foreach(entry->params,
+ config_param_free_callback, NULL);
+ g_slist_free(entry->params);
+ }
+}
- entry = newConfigEntry(name, repeatable, block);
- config_entries = g_slist_prepend(config_entries, entry);
+void config_global_init(void)
+{
}
-void config_global_finish(void)
+static void
+config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
- g_slist_foreach(config_entries, config_entry_free, NULL);
- g_slist_free(config_entries);
+ struct config_param *param = data;
+
+ if (!param->used)
+ /* this whole config_param was not queried at all -
+ the feature might be disabled at compile time?
+ Silently ignore it here. */
+ return;
+
+ for (unsigned i = 0; i < param->num_block_params; i++) {
+ struct block_param *bp = &param->block_params[i];
+
+ if (!bp->used)
+ g_warning("option '%s' on line %i was not recognized",
+ bp->name, bp->line);
+ }
}
-void config_global_init(void)
+void config_global_check(void)
{
- config_entries = NULL;
-
- /* registerConfigParam(name, repeatable, block); */
- registerConfigParam(CONF_MUSIC_DIR, 0, 0);
- registerConfigParam(CONF_PLAYLIST_DIR, 0, 0);
- registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0);
- registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0);
- registerConfigParam(CONF_DB_FILE, 0, 0);
- registerConfigParam(CONF_STICKER_FILE, false, false);
- registerConfigParam(CONF_LOG_FILE, 0, 0);
- registerConfigParam(CONF_ERROR_FILE, 0, 0);
- registerConfigParam(CONF_PID_FILE, 0, 0);
- registerConfigParam(CONF_STATE_FILE, 0, 0);
- registerConfigParam(CONF_USER, 0, 0);
- registerConfigParam(CONF_BIND_TO_ADDRESS, 1, 0);
- registerConfigParam(CONF_PORT, 0, 0);
- registerConfigParam(CONF_LOG_LEVEL, 0, 0);
- registerConfigParam(CONF_ZEROCONF_NAME, 0, 0);
- registerConfigParam(CONF_ZEROCONF_ENABLED, 0, 0);
- registerConfigParam(CONF_PASSWORD, 1, 0);
- registerConfigParam(CONF_DEFAULT_PERMS, 0, 0);
- registerConfigParam(CONF_AUDIO_OUTPUT, 1, 1);
- registerConfigParam(CONF_AUDIO_OUTPUT_FORMAT, 0, 0);
- registerConfigParam(CONF_MIXER_TYPE, 0, 0);
- registerConfigParam(CONF_MIXER_DEVICE, 0, 0);
- registerConfigParam(CONF_MIXER_CONTROL, 0, 0);
- registerConfigParam(CONF_REPLAYGAIN, 0, 0);
- registerConfigParam(CONF_REPLAYGAIN_PREAMP, 0, 0);
- registerConfigParam(CONF_VOLUME_NORMALIZATION, 0, 0);
- registerConfigParam(CONF_SAMPLERATE_CONVERTER, 0, 0);
- registerConfigParam(CONF_AUDIO_BUFFER_SIZE, 0, 0);
- registerConfigParam(CONF_BUFFER_BEFORE_PLAY, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_HOST, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_PORT, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_USER, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_PASSWORD, 0, 0);
- registerConfigParam(CONF_CONN_TIMEOUT, 0, 0);
- registerConfigParam(CONF_MAX_CONN, 0, 0);
- registerConfigParam(CONF_MAX_PLAYLIST_LENGTH, 0, 0);
- registerConfigParam(CONF_MAX_COMMAND_LIST_SIZE, 0, 0);
- registerConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE, 0, 0);
- registerConfigParam(CONF_FS_CHARSET, 0, 0);
- registerConfigParam(CONF_ID3V1_ENCODING, 0, 0);
- registerConfigParam(CONF_METADATA_TO_USE, 0, 0);
- registerConfigParam(CONF_SAVE_ABSOLUTE_PATHS, 0, 0);
- registerConfigParam(CONF_DECODER, true, true);
- registerConfigParam(CONF_INPUT, true, true);
- registerConfigParam(CONF_GAPLESS_MP3_PLAYBACK, 0, 0);
+ for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
+ struct config_entry *entry = &config_entries[i];
+
+ g_slist_foreach(entry->params, config_param_check, NULL);
+ }
}
-void
+bool
config_add_block_param(struct config_param * param, const char *name,
- const char *value, int line)
+ const char *value, int line, GError **error_r)
{
struct block_param *bp;
+ bp = config_get_block_param(param, name);
+ if (bp != NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "\"%s\" first defined on line %i, and "
+ "redefined on line %i\n", name,
+ bp->line, line);
+ return false;
+ }
+
param->num_block_params++;
param->block_params = g_realloc(param->block_params,
@@ -235,67 +239,97 @@ config_add_block_param(struct config_param * param, const char *name,
bp->name = g_strdup(name);
bp->value = g_strdup(value);
bp->line = line;
+ bp->used = false;
+
+ return true;
}
static struct config_param *
-config_read_block(FILE *fp, int *count, char *string)
+config_read_block(FILE *fp, int *count, char *string, GError **error_r)
{
struct config_param *ret = config_new_param(NULL, *count);
-
- int i;
- int numberOfArgs;
- int argsMinusComment;
-
- while (fgets(string, MAX_STRING_SIZE, fp)) {
- char *array[CONF_LINE_TOKEN_MAX] = { NULL };
+ GError *error = NULL;
+ bool success;
+
+ while (true) {
+ char *line;
+ const char *name, *value;
+
+ line = fgets(string, MAX_STRING_SIZE, fp);
+ if (line == NULL) {
+ config_param_free(ret);
+ g_set_error(error_r, config_quark(), 0,
+ "Expected '}' before end-of-file");
+ return NULL;
+ }
(*count)++;
+ line = g_strchug(line);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
- numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
+ if (*line == '}') {
+ /* end of this block; return from the function
+ (and from this "while" loop) */
+
+ line = g_strchug(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ config_param_free(ret);
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after '}'",
+ *count);
+ return false;
+ }
- for (i = 0; i < numberOfArgs; i++) {
- if (array[i][0] == CONF_COMMENT)
- break;
+ return ret;
}
- argsMinusComment = i;
+ /* parse name and value */
- if (0 == argsMinusComment) {
- continue;
+ name = tokenizer_next_word(&line, &error);
+ if (name == NULL) {
+ assert(*line != 0);
+ config_param_free(ret);
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ", *count);
+ return NULL;
}
- if (1 == argsMinusComment &&
- 0 == strcmp(array[0], CONF_BLOCK_END)) {
- break;
+ value = tokenizer_next_string(&line, &error);
+ if (value == NULL) {
+ config_param_free(ret);
+ if (*line == 0)
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Value missing", *count);
+ else
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ",
+ *count);
+ return NULL;
}
- if (2 != argsMinusComment) {
- g_error("improperly formatted config file at line %i:"
- " %s\n", *count, string);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ config_param_free(ret);
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after value",
+ *count);
+ return NULL;
}
- if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) ||
- 0 == strcmp(array[1], CONF_BLOCK_BEGIN) ||
- 0 == strcmp(array[0], CONF_BLOCK_END) ||
- 0 == strcmp(array[1], CONF_BLOCK_END)) {
- g_error("improperly formatted config file at line %i: %s "
- "in block beginning at line %i\n",
- *count, string, ret->line);
+ success = config_add_block_param(ret, name, value, *count,
+ error_r);
+ if (!success) {
+ config_param_free(ret);
+ return false;
}
-
- config_add_block_param(ret, array[0], array[1], *count);
}
-
- return ret;
}
-void config_read_file(const char *file)
+bool
+config_read_file(const char *file, GError **error_r)
{
FILE *fp;
char string[MAX_STRING_SIZE + 1];
- int i;
- int numberOfArgs;
- int argsMinusComment;
int count = 0;
struct config_entry *entry;
struct config_param *param;
@@ -303,67 +337,110 @@ void config_read_file(const char *file)
g_debug("loading file %s", file);
if (!(fp = fopen(file, "r"))) {
- g_error("problems opening file %s for reading: %s\n",
- file, strerror(errno));
+ g_set_error(error_r, config_quark(), errno,
+ "Failed to open %s: %s",
+ file, strerror(errno));
+ return false;
}
while (fgets(string, MAX_STRING_SIZE, fp)) {
- char *array[CONF_LINE_TOKEN_MAX] = { NULL };
+ char *line;
+ const char *name, *value;
+ GError *error = NULL;
count++;
- numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
+ line = g_strchug(string);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
+
+ /* the first token in each line is the name, followed
+ by either the value or '{' */
- for (i = 0; i < numberOfArgs; i++) {
- if (array[i][0] == CONF_COMMENT)
- break;
+ name = tokenizer_next_word(&line, &error);
+ if (name == NULL) {
+ assert(*line != 0);
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ", count);
+ return false;
}
- argsMinusComment = i;
+ /* get the definition of that option, and check the
+ "repeatable" flag */
- if (0 == argsMinusComment) {
- continue;
+ entry = config_entry_get(name);
+ if (entry == NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "unrecognized parameter in config file at "
+ "line %i: %s\n", count, name);
+ return false;
}
- if (2 != argsMinusComment) {
- g_error("improperly formatted config file at line %i:"
- " %s\n", count, string);
+ if (entry->params != NULL && !entry->repeatable) {
+ param = entry->params->data;
+ g_set_error(error_r, config_quark(), 0,
+ "config parameter \"%s\" is first defined "
+ "on line %i and redefined on line %i\n",
+ name, param->line, count);
+ return false;
}
- entry = config_entry_get(array[0]);
- if (entry == NULL)
- g_error("unrecognized parameter in config file at "
- "line %i: %s\n", count, string);
+ /* now parse the block or the value */
- if (!(entry->mask & CONF_REPEATABLE_MASK) &&
- entry->params != NULL) {
- param = entry->params->data;
- g_error("config parameter \"%s\" is first defined on "
- "line %i and redefined on line %i\n",
- array[0], param->line, count);
- }
+ if (entry->block) {
+ /* it's a block, call config_read_block() */
- if (entry->mask & CONF_BLOCK_MASK) {
- if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) {
- g_error("improperly formatted config file at "
- "line %i: %s\n", count, string);
+ if (*line != '{') {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: '{' expected", count);
+ return false;
}
- param = config_read_block(fp, &count, string);
- } else
- param = config_new_param(array[1], count);
+
+ line = g_strchug(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after '{'",
+ count);
+ return false;
+ }
+
+ param = config_read_block(fp, &count, string, error_r);
+ if (param == NULL)
+ return false;
+ } else {
+ /* a string value */
+
+ value = tokenizer_next_string(&line, &error);
+ if (value == NULL) {
+ if (*line == 0)
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Value missing",
+ count);
+ else {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: %s", count,
+ error->message);
+ g_error_free(error);
+ }
+
+ return false;
+ }
+
+ if (*line != 0 && *line != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after value",
+ count);
+ return false;
+ }
+
+ param = config_new_param(value, count);
+ }
entry->params = g_slist_append(entry->params, param);
}
fclose(fp);
-}
-
-void
-config_add_param(const char *name, struct config_param *param)
-{
- struct config_entry *entry = config_entry_get(name);
- assert(entry != NULL);
- entry->params = g_slist_append(entry->params, param);
+ return true;
}
struct config_param *
@@ -391,7 +468,7 @@ config_get_next_param(const char *name, const struct config_param * last)
return NULL;
param = node->data;
-
+ param->used = true;
return param;
}
@@ -447,43 +524,35 @@ config_get_positive(const char *name, unsigned default_value)
struct block_param *
config_get_block_param(const struct config_param * param, const char *name)
{
- struct block_param *ret = NULL;
-
if (param == NULL)
return NULL;
for (unsigned i = 0; i < param->num_block_params; i++) {
if (0 == strcmp(name, param->block_params[i].name)) {
- if (ret) {
- g_warning("\"%s\" first defined on line %i, and "
- "redefined on line %i\n", name,
- ret->line, param->block_params[i].line);
- }
- ret = param->block_params + i;
+ struct block_param *bp = &param->block_params[i];
+ bp->used = true;
+ return bp;
}
}
- return ret;
+ return NULL;
}
bool config_get_bool(const char *name, bool default_value)
{
const struct config_param *param = config_get_param(name);
- int value;
+ bool success, value;
if (param == NULL)
return default_value;
- value = get_bool(param->value);
- if (value == CONF_BOOL_INVALID)
+ success = get_bool(param->value, &value);
+ if (!success)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, param->line);
- if (value == CONF_BOOL_UNSET)
- return default_value;
-
- return !!value;
+ return value;
}
const char *
@@ -524,19 +593,16 @@ config_get_block_bool(const struct config_param *param, const char *name,
bool default_value)
{
struct block_param *bp = config_get_block_param(param, name);
- int value;
+ bool success, value;
if (bp == NULL)
return default_value;
- value = get_bool(bp->value);
- if (value == CONF_BOOL_INVALID)
+ success = get_bool(bp->value, &value);
+ if (!success)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, bp->line);
- if (value == CONF_BOOL_UNSET)
- return default_value;
-
- return !!value;
+ return value;
}
diff --git a/src/conf.h b/src/conf.h
index c5e49960e..7dd12afda 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -30,10 +30,10 @@
#define CONF_DB_FILE "db_file"
#define CONF_STICKER_FILE "sticker_file"
#define CONF_LOG_FILE "log_file"
-#define CONF_ERROR_FILE "error_file"
#define CONF_PID_FILE "pid_file"
#define CONF_STATE_FILE "state_file"
#define CONF_USER "user"
+#define CONF_GROUP "group"
#define CONF_BIND_TO_ADDRESS "bind_to_address"
#define CONF_PORT "port"
#define CONF_LOG_LEVEL "log_level"
@@ -44,10 +44,9 @@
#define CONF_AUDIO_OUTPUT "audio_output"
#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format"
#define CONF_MIXER_TYPE "mixer_type"
-#define CONF_MIXER_DEVICE "mixer_device"
-#define CONF_MIXER_CONTROL "mixer_control"
#define CONF_REPLAYGAIN "replaygain"
#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
+#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp"
#define CONF_VOLUME_NORMALIZATION "volume_normalization"
#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
@@ -68,9 +67,8 @@
#define CONF_DECODER "decoder"
#define CONF_INPUT "input"
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
-
-#define CONF_BOOL_UNSET -1
-#define CONF_BOOL_INVALID -2
+#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
+#define CONF_AUTO_UPDATE "auto_update"
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
@@ -79,6 +77,12 @@ struct block_param {
char *name;
char *value;
int line;
+
+ /**
+ * This flag is false when nobody has queried the value of
+ * this option yet.
+ */
+ bool used;
};
struct config_param {
@@ -87,31 +91,57 @@ struct config_param {
struct block_param *block_params;
unsigned num_block_params;
+
+ /**
+ * This flag is false when nobody has queried the value of
+ * this option yet.
+ */
+ bool used;
};
+/**
+ * A GQuark for GError instances, resulting from malformed
+ * configuration.
+ */
+static inline GQuark
+config_quark(void)
+{
+ return g_quark_from_static_string("config");
+}
+
void config_global_init(void);
void config_global_finish(void);
-void config_read_file(const char *file);
-
/**
- * Adds a new configuration parameter. The name must be registered
- * with registerConfigParam().
+ * Call this function after all configuration has been evaluated. It
+ * checks for unused parameters, and logs warnings.
*/
-void
-config_add_param(const char *name, struct config_param *param);
+void config_global_check(void);
+
+bool
+config_read_file(const char *file, GError **error_r);
/* don't free the returned value
set _last_ to NULL to get first entry */
+G_GNUC_PURE
struct config_param *
config_get_next_param(const char *name, const struct config_param *last);
+G_GNUC_PURE
static inline struct config_param *
config_get_param(const char *name)
{
return config_get_next_param(name, NULL);
}
+/* Note on G_GNUC_PURE: Some of the functions declared pure are not
+ really pure in strict sense. They have side effect such that they
+ validate parameter's value and signal an error if it's invalid.
+ However, if the argument was already validated or we don't care
+ about the argument at all, this may be ignored so in the end, we
+ should be fine with calling those functions pure. */
+
+G_GNUC_PURE
const char *
config_get_string(const char *name, const char *default_value);
@@ -120,17 +150,27 @@ config_get_string(const char *name, const char *default_value);
* absolute path. If there is a tilde prefix, it is expanded. Aborts
* MPD if the path is not a valid absolute path.
*/
+/* We lie here really. This function is not pure as it has side
+ effects -- it parse the value and creates new string freeing
+ previous one. However, because this works the very same way each
+ time (ie. from the outside it appears as if function had no side
+ effects) we should be in the clear declaring it pure. */
+G_GNUC_PURE
const char *
config_get_path(const char *name);
+G_GNUC_PURE
unsigned
config_get_positive(const char *name, unsigned default_value);
+G_GNUC_PURE
struct block_param *
config_get_block_param(const struct config_param *param, const char *name);
+G_GNUC_PURE
bool config_get_bool(const char *name, bool default_value);
+G_GNUC_PURE
const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value);
@@ -142,10 +182,12 @@ config_dup_block_string(const struct config_param *param, const char *name,
return g_strdup(config_get_block_string(param, name, default_value));
}
+G_GNUC_PURE
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value);
+G_GNUC_PURE
bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value);
@@ -153,8 +195,8 @@ config_get_block_bool(const struct config_param *param, const char *name,
struct config_param *
config_new_param(const char *value, int line);
-void
-config_add_block_param(struct config_param *param, const char *name,
- const char *value, int line);
+bool
+config_add_block_param(struct config_param * param, const char *name,
+ const char *value, int line, GError **error_r);
#endif
diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c
index ce8202a81..e45b26a7e 100644
--- a/src/cue/cue_tag.c
+++ b/src/cue/cue_tag.c
@@ -13,64 +13,64 @@ cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem)
tag_begin_add(tag);
- { /* TAG_ITEM_ALBUM_ARTIST */
+ { /* TAG_ALBUM_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
- /* TAG_ITEM_ALBUM_ARTIST */ }
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
+ /* TAG_ALBUM_ARTIST */ }
- { /* TAG_ITEM_ARTIST */
+ { /* TAG_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
- /* TAG_ITEM_ARTIST */ }
+ tag_add_item(tag, TAG_ARTIST, tmp);
+ /* TAG_ARTIST */ }
- /* TAG_ITEM_PERFORMER */
+ /* TAG_PERFORMER */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
+ tag_add_item(tag, TAG_PERFORMER, tmp);
- /* TAG_ITEM_COMPOSER */
+ /* TAG_COMPOSER */
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
+ tag_add_item(tag, TAG_COMPOSER, tmp);
- /* TAG_ITEM_ALBUM */
+ /* TAG_ALBUM */
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM, tmp);
+ tag_add_item(tag, TAG_ALBUM, tmp);
- /* TAG_ITEM_GENRE */
+ /* TAG_GENRE */
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_GENRE, tmp);
+ tag_add_item(tag, TAG_GENRE, tmp);
- /* TAG_ITEM_DATE */
+ /* TAG_DATE */
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
- tag_add_item(tag, TAG_ITEM_DATE, tmp);
+ tag_add_item(tag, TAG_DATE, tmp);
- /* TAG_ITEM_COMMENT */
+ /* TAG_COMMENT */
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
+ tag_add_item(tag, TAG_COMMENT, tmp);
- /* TAG_ITEM_DISC */
+ /* TAG_DISC */
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_DISC, tmp);
+ tag_add_item(tag, TAG_DISC, tmp);
/* stream name, usually empty
- * tag_add_item(tag, TAG_ITEM_NAME,);
+ * tag_add_item(tag, TAG_NAME,);
*/
/* REM MUSICBRAINZ entry?
@@ -109,47 +109,47 @@ cue_tag_track(struct Cdtext* cdtext, struct Rem* rem)
tag_begin_add(tag);
- { /* TAG_ITEM_ARTIST */
+ { /* TAG_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
- /* TAG_ITEM_ARTIST */ }
+ tag_add_item(tag, TAG_ARTIST, tmp);
+ /* TAG_ARTIST */ }
- /* TAG_ITEM_TITLE */
+ /* TAG_TITLE */
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_TITLE, tmp);
+ tag_add_item(tag, TAG_TITLE, tmp);
- /* TAG_ITEM_GENRE */
+ /* TAG_GENRE */
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_GENRE, tmp);
+ tag_add_item(tag, TAG_GENRE, tmp);
- /* TAG_ITEM_DATE */
+ /* TAG_DATE */
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
- tag_add_item(tag, TAG_ITEM_DATE, tmp);
+ tag_add_item(tag, TAG_DATE, tmp);
- /* TAG_ITEM_COMPOSER */
+ /* TAG_COMPOSER */
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
+ tag_add_item(tag, TAG_COMPOSER, tmp);
- /* TAG_ITEM_PERFORMER */
+ /* TAG_PERFORMER */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
+ tag_add_item(tag, TAG_PERFORMER, tmp);
- /* TAG_ITEM_COMMENT */
+ /* TAG_COMMENT */
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
+ tag_add_item(tag, TAG_COMMENT, tmp);
- /* TAG_ITEM_DISC */
+ /* TAG_DISC */
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_DISC, tmp);
+ tag_add_item(tag, TAG_DISC, tmp);
tag_end_add(tag);
diff --git a/src/daemon.c b/src/daemon.c
index 33b2953a9..192fa8d37 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "daemon.h"
#include <glib.h>
@@ -45,20 +46,21 @@
static char *user_name;
/** the Unix user id which MPD runs as */
-static uid_t user_uid;
+static uid_t user_uid = (uid_t)-1;
/** the Unix group id which MPD runs as */
-static gid_t user_gid;
+static gid_t user_gid = (pid_t)-1;
/** the absolute path of the pidfile */
static char *pidfile;
-#endif
+/* whether "group" conf. option was given */
+static bool had_group = false;
+
void
daemonize_kill(void)
{
-#ifndef WIN32
FILE *fp;
int pid, ret;
@@ -82,41 +84,34 @@ daemonize_kill(void)
pid, g_strerror(errno));
exit(EXIT_SUCCESS);
-#else
- g_error("--kill is not available on WIN32");
-#endif
}
void
daemonize_close_stdin(void)
{
- int fd = open("/dev/null", O_RDONLY);
-
- if (fd < 0)
- close(STDIN_FILENO);
- else if (fd != STDIN_FILENO) {
- dup2(fd, STDIN_FILENO);
- close(fd);
- }
+ close(STDIN_FILENO);
+ open("/dev/null", O_RDONLY);
}
void
daemonize_set_user(void)
{
-#ifndef WIN32
if (user_name == NULL)
return;
- /* get uid */
- if (setgid(user_gid) == -1) {
- g_error("cannot setgid for user \"%s\": %s",
- user_name, g_strerror(errno));
+ /* set gid */
+ if (user_gid != (gid_t)-1 && user_gid != getgid()) {
+ if (setgid(user_gid) == -1) {
+ g_error("cannot setgid to %d: %s",
+ (int)user_gid, g_strerror(errno));
+ }
}
+
#ifdef _BSD_SOURCE
/* init suplementary groups
* (must be done before we change our uid)
*/
- if (initgroups(user_name, user_gid) == -1) {
+ if (!had_group && initgroups(user_name, user_gid) == -1) {
g_warning("cannot init supplementary groups "
"of user \"%s\": %s",
user_name, g_strerror(errno));
@@ -124,32 +119,38 @@ daemonize_set_user(void)
#endif
/* set uid */
- if (setuid(user_uid) == -1) {
+ if (user_uid != (uid_t)-1 && user_uid != getuid() &&
+ setuid(user_uid) == -1) {
g_error("cannot change to uid of user \"%s\": %s",
user_name, g_strerror(errno));
}
-#endif
}
-#ifndef G_OS_WIN32
static void
daemonize_detach(void)
{
- pid_t pid;
-
/* flush all file handles before duplicating the buffers */
fflush(NULL);
+#ifdef HAVE_DAEMON
+
+ if (daemon(0, 1))
+ g_error("daemon() failed: %s", g_strerror(errno));
+
+#elif defined(HAVE_FORK)
+
/* detach from parent process */
- pid = fork();
- if (pid < 0)
+ switch (fork()) {
+ case -1:
g_error("fork() failed: %s", g_strerror(errno));
-
- if (pid > 0)
+ case 0:
+ break;
+ default:
/* exit the parent process */
_exit(EXIT_SUCCESS);
+ }
/* release the current working directory */
@@ -160,14 +161,16 @@ daemonize_detach(void)
setsid();
+#else
+ g_error("no support for daemonizing");
+#endif
+
g_debug("daemonized!");
}
-#endif
void
daemonize(bool detach)
{
-#ifndef WIN32
FILE *fp = NULL;
if (pidfile != NULL) {
@@ -189,47 +192,45 @@ daemonize(bool detach)
fprintf(fp, "%lu\n", (unsigned long)getpid());
fclose(fp);
}
-#else
- /* no daemonization on WIN32 */
- (void)detach;
-#endif
}
void
-daemonize_init(const char *user, const char *_pidfile)
+daemonize_init(const char *user, const char *group, const char *_pidfile)
{
-#ifndef WIN32
- if (user != NULL && strcmp(user, g_get_user_name()) != 0) {
- struct passwd *pwd;
-
- user_name = g_strdup(user);
-
- pwd = getpwnam(user_name);
- if (pwd == NULL)
- g_error("no such user \"%s\"", user_name);
+ if (user) {
+ struct passwd *pwd = getpwnam(user);
+ if (!pwd)
+ g_error("no such user \"%s\"", user);
user_uid = pwd->pw_uid;
user_gid = pwd->pw_gid;
+ user_name = g_strdup(user);
+
/* this is needed by libs such as arts */
g_setenv("HOME", pwd->pw_dir, true);
}
+ if (group) {
+ struct group *grp = grp = getgrnam(group);
+ if (!grp)
+ g_error("no such group \"%s\"", group);
+ user_gid = grp->gr_gid;
+ had_group = true;
+ }
+
+
pidfile = g_strdup(_pidfile);
-#else
- (void)user;
- (void)_pidfile;
-#endif
}
void
daemonize_finish(void)
{
-#ifndef WIN32
if (pidfile != NULL)
unlink(pidfile);
g_free(user_name);
g_free(pidfile);
-#endif
}
+
+#endif
diff --git a/src/daemon.h b/src/daemon.h
index 5b3f9a7dc..a29945607 100644
--- a/src/daemon.h
+++ b/src/daemon.h
@@ -22,32 +22,67 @@
#include <stdbool.h>
+#ifndef WIN32
void
-daemonize_init(const char *user, const char *pidfile);
+daemonize_init(const char *user, const char *group, const char *pidfile);
+#else
+static inline void
+daemonize_init(const char *user, const char *group, const char *pidfile)
+{ (void)user; (void)group; (void)pidfile; }
+#endif
+#ifndef WIN32
void
daemonize_finish(void);
+#else
+static inline void
+daemonize_finish(void)
+{ /* nop */ }
+#endif
/**
* Kill the MPD which is currently running, pid determined from the
* pid file.
*/
+#ifndef WIN32
void
daemonize_kill(void);
+#else
+static inline void
+daemonize_kill(void)
+{ g_error("--kill is not available on WIN32"); }
+#endif
/**
* Close stdin (fd 0) and re-open it as /dev/null.
*/
+#ifndef WIN32
void
daemonize_close_stdin(void);
+#else
+static inline void
+daemonize_close_stdin(void) {}
+#endif
/**
* Change to the configured Unix user.
*/
+#ifndef WIN32
void
daemonize_set_user(void);
+#else
+static inline void
+daemonize_set_user(void)
+{ /* nop */ }
+#endif
+#ifndef WIN32
void
daemonize(bool detach);
+#else
+static inline void
+daemonize(bool detach)
+{ (void)detach; }
+#endif
#endif
diff --git a/src/database.c b/src/database.c
index 5a06dda98..2164a69b7 100644
--- a/src/database.c
+++ b/src/database.c
@@ -23,6 +23,9 @@
#include "song.h"
#include "path.h"
#include "stats.h"
+#include "text_file.h"
+#include "tag.h"
+#include "tag_internal.h"
#include "config.h"
#include <glib.h>
@@ -40,8 +43,14 @@
#define DIRECTORY_INFO_BEGIN "info_begin"
#define DIRECTORY_INFO_END "info_end"
+#define DB_FORMAT_PREFIX "format: "
#define DIRECTORY_MPD_VERSION "mpd_version: "
#define DIRECTORY_FS_CHARSET "fs_charset: "
+#define DB_TAG_PREFIX "tag: "
+
+enum {
+ DB_FORMAT = 1,
+};
static char *database_path;
@@ -232,11 +241,19 @@ db_save(void)
/* block signals when writing the db so we don't get a corrupted db */
fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
+ fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset());
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (!ignore_tag_items[i])
+ fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
+
fprintf(fp, "%s\n", DIRECTORY_INFO_END);
- if (directory_save(fp, music_root) < 0) {
+ directory_save(fp, music_root);
+
+ if (ferror(fp)) {
g_warning("Failed to write to database file: %s",
strerror(errno));
while (fclose(fp) && errno == EINTR);
@@ -256,64 +273,64 @@ db_load(GError **error)
{
FILE *fp = NULL;
struct stat st;
- char buffer[100];
+ GString *buffer = g_string_sized_new(1024);
+ char *line;
+ int format = 0;
bool found_charset = false, found_version = false;
bool success;
+ bool tags[TAG_NUM_OF_ITEM_TYPES];
assert(database_path != NULL);
assert(music_root != NULL);
- if (!music_root)
- music_root = directory_new("", NULL);
while (!(fp = fopen(database_path, "r")) && errno == EINTR) ;
if (fp == NULL) {
g_set_error(error, db_quark(), errno,
"Failed to open database file \"%s\": %s",
database_path, strerror(errno));
+ g_string_free(buffer, true);
return false;
}
/* get initial info */
- if (!fgets(buffer, sizeof(buffer), fp)) {
- fclose(fp);
- g_set_error(error, db_quark(), 0, "Unexpected end of file");
- return false;
- }
-
- g_strchomp(buffer);
-
- if (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) {
+ line = read_text_line(fp, buffer);
+ if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
fclose(fp);
g_set_error(error, db_quark(), 0, "Database corrupted");
+ g_string_free(buffer, true);
return false;
}
- while (fgets(buffer, sizeof(buffer), fp) &&
- !g_str_has_prefix(buffer, DIRECTORY_INFO_END)) {
- g_strchomp(buffer);
+ memset(tags, false, sizeof(tags));
- if (g_str_has_prefix(buffer, DIRECTORY_MPD_VERSION)) {
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ strcmp(line, DIRECTORY_INFO_END) != 0) {
+ if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) {
+ format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
+ } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
if (found_version) {
fclose(fp);
g_set_error(error, db_quark(), 0,
"Duplicate version line");
+ g_string_free(buffer, true);
return false;
}
found_version = true;
- } else if (g_str_has_prefix(buffer, DIRECTORY_FS_CHARSET)) {
+ } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
const char *new_charset, *old_charset;
if (found_charset) {
fclose(fp);
g_set_error(error, db_quark(), 0,
"Duplicate charset line");
+ g_string_free(buffer, true);
return false;
}
found_charset = true;
- new_charset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]);
+ new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
old_charset = path_get_fs_charset();
if (old_charset != NULL
&& strcmp(new_charset, old_charset)) {
@@ -323,19 +340,50 @@ db_load(GError **error)
"\"%s\" instead of \"%s\"; "
"discarding database file",
new_charset, old_charset);
+ g_string_free(buffer, true);
return false;
}
+ } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
+ const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
+ enum tag_type tag = tag_name_parse(name);
+ if (tag == TAG_NUM_OF_ITEM_TYPES) {
+ g_set_error(error, db_quark(), 0,
+ "Unrecognized tag '%s', "
+ "discarding database file",
+ name);
+ return false;
+ }
+
+ tags[tag] = true;
} else {
fclose(fp);
g_set_error(error, db_quark(), 0,
- "Malformed line: %s", buffer);
+ "Malformed line: %s", line);
+ g_string_free(buffer, true);
+ return false;
+ }
+ }
+
+ if (format != DB_FORMAT) {
+ g_set_error(error, db_quark(), 0,
+ "Database format mismatch, "
+ "discarding database file");
+ return false;
+ }
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ if (!ignore_tag_items[i] && !tags[i]) {
+ g_set_error(error, db_quark(), 0,
+ "Tag list mismatch, "
+ "discarding database file");
return false;
}
}
g_debug("reading DB");
- success = directory_load(fp, music_root, error);
+ success = directory_load(fp, music_root, buffer, error);
+ g_string_free(buffer, true);
while (fclose(fp) && errno == EINTR) ;
if (!success)
diff --git a/src/dbUtils.c b/src/dbUtils.c
index f89148c1b..9978daa43 100644
--- a/src/dbUtils.c
+++ b/src/dbUtils.c
@@ -59,7 +59,7 @@ static int
printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data)
{
struct client *client = data;
- song_print_url(client, song);
+ song_print_uri(client, song);
return 0;
}
@@ -168,7 +168,7 @@ int printAllIn(struct client *client, const char *name)
static int
directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data)
{
- return addSongToPlaylist(&g_playlist, song, NULL);
+ return playlist_append_song(&g_playlist, song, NULL);
}
struct add_data {
@@ -200,6 +200,28 @@ int addAllInToStoredPlaylist(const char *name, const char *utf8file)
}
static int
+findAddInDirectory(struct song *song, void *_data)
+{
+ struct search_data *data = _data;
+
+ if (locate_song_match(song, data->criteria))
+ return directoryAddSongToPlaylist(song, data);
+
+ return 0;
+}
+
+int findAddIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria)
+{
+ struct search_data data;
+
+ data.client = client;
+ data.criteria = criteria;
+
+ return db_walk(name, findAddInDirectory, NULL, &data);
+}
+
+static int
directoryPrintSongInfo(struct song *song, void *data)
{
struct client *client = data;
@@ -236,7 +258,7 @@ visitTag(struct client *client, struct strset *set,
struct tag *tag = song->tag;
if (tagType == LOCATE_TAG_FILE_TYPE) {
- song_print_url(client, song);
+ song_print_uri(client, song);
return;
}
diff --git a/src/dbUtils.h b/src/dbUtils.h
index 1382c243e..914b6fa84 100644
--- a/src/dbUtils.h
+++ b/src/dbUtils.h
@@ -40,6 +40,10 @@ findSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
int
+findAddIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria);
+
+int
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c
index e096750f3..2eaeb2449 100644
--- a/src/decoder/_flac_common.c
+++ b/src/decoder/_flac_common.c
@@ -170,11 +170,11 @@ flac_parse_comment(struct tag *tag, const char *char_tnum,
assert(tag != NULL);
if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
- TAG_ITEM_TRACK, char_tnum) ||
+ TAG_TRACK, char_tnum) ||
flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
- TAG_ITEM_DISC, char_tnum) ||
+ TAG_DISC, char_tnum) ||
flac_copy_comment(tag, entry, "album artist",
- TAG_ITEM_ALBUM_ARTIST, char_tnum))
+ TAG_ALBUM_ARTIST, char_tnum))
return;
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
@@ -201,9 +201,8 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
switch (block->type) {
case FLAC__METADATA_TYPE_STREAMINFO:
- data->audio_format.bits = (int8_t)si->bits_per_sample;
- data->audio_format.sample_rate = si->sample_rate;
- data->audio_format.channels = (int8_t)si->channels;
+ audio_format_init(&data->audio_format, si->sample_rate,
+ si->bits_per_sample, si->channels);
data->total_time = ((float)si->total_samples) / (si->sample_rate);
break;
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
@@ -291,14 +290,14 @@ flac_convert_8(int8_t *dest,
*dest++ = buf[c_chan][position];
}
-static void flac_convert(unsigned char *dest,
- unsigned int num_channels,
- unsigned int bytes_per_sample,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
+static void
+flac_convert(void *dest,
+ unsigned int num_channels, unsigned sample_format,
+ const FLAC__int32 *const buf[],
+ unsigned int position, unsigned int end)
{
- switch (bytes_per_sample) {
- case 2:
+ switch (sample_format) {
+ case 16:
if (num_channels == 2)
flac_convert_stereo16((int16_t*)dest, buf,
position, end);
@@ -307,12 +306,13 @@ static void flac_convert(unsigned char *dest,
position, end);
break;
- case 4:
+ case 24:
+ case 32:
flac_convert_32((int32_t*)dest, num_channels, buf,
position, end);
break;
- case 1:
+ case 8:
flac_convert_8((int8_t*)dest, num_channels, buf,
position, end);
break;
@@ -324,6 +324,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
const FLAC__int32 *const buf[])
{
unsigned int c_samp;
+ const unsigned sample_format = data->audio_format.bits;
const unsigned int num_channels = frame->header.channels;
const unsigned int bytes_per_sample =
audio_format_sample_size(&data->audio_format);
@@ -333,11 +334,6 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
unsigned int num_samples;
enum decoder_command cmd;
- if (bytes_per_sample != 1 && bytes_per_sample != 2 &&
- bytes_per_sample != 4)
- /* exotic unsupported bit rate */
- return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
-
for (c_samp = 0; c_samp < frame->header.blocksize;
c_samp += num_samples) {
num_samples = frame->header.blocksize - c_samp;
@@ -345,7 +341,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
num_samples = max_samples;
flac_convert(data->chunk,
- num_channels, bytes_per_sample, buf,
+ num_channels, sample_format, buf,
c_samp, c_samp + num_samples);
cmd = decoder_data(data->decoder, data->input_stream,
diff --git a/src/decoder/audiofile_plugin.c b/src/decoder/audiofile_plugin.c
index f66d90dc1..b4959f6c2 100644
--- a/src/decoder/audiofile_plugin.c
+++ b/src/decoder/audiofile_plugin.c
@@ -136,11 +136,9 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
AF_SAMPFMT_TWOSCOMP, bits);
afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
- audio_format.bits = (uint8_t)bits;
- audio_format.sample_rate =
- (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK);
- audio_format.channels =
- (uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK);
+
+ audio_format_init(&audio_format, afGetRate(af_fp, AF_DEFAULT_TRACK),
+ bits, afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK));
if (!audio_format_valid(&audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
diff --git a/src/decoder/faad_plugin.c b/src/decoder/faad_plugin.c
index 7b2806a4c..516f741c7 100644
--- a/src/decoder/faad_plugin.c
+++ b/src/decoder/faad_plugin.c
@@ -266,11 +266,7 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
decoder_buffer_consume(buffer, nbytes);
- *audio_format = (struct audio_format){
- .bits = 16,
- .channels = channels,
- .sample_rate = sample_rate,
- };
+ audio_format_init(audio_format, sample_rate, 16, channels);
return true;
}
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index 2f7ed6d1f..63f0f7ece 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_plugin.c
@@ -267,6 +267,7 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
struct audio_format audio_format;
enum decoder_command cmd;
int total_time;
+ uint8_t bits;
total_time = 0;
@@ -275,13 +276,13 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
}
#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
- audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
+ bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
#else
/* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
- audio_format.bits = (uint8_t) 16;
+ bits = (uint8_t) 16;
#endif
- audio_format.sample_rate = (unsigned int)codec_context->sample_rate;
- audio_format.channels = codec_context->channels;
+ audio_format_init(&audio_format, codec_context->sample_rate, bits,
+ codec_context->channels);
if (!audio_format_valid(&audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
@@ -342,8 +343,9 @@ static bool
ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
enum tag_type type, const char *name)
{
- AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0);
- if (mt != NULL)
+ AVMetadataTag *mt = NULL;
+
+ while ((mt = av_metadata_get(m, name, mt, 0)) != NULL)
tag_add_item(tag, type, mt->value);
return mt != NULL;
}
@@ -361,35 +363,35 @@ static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_TITLE, "title");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ARTIST, "author");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ALBUM, "album");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_COMMENT, "comment");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_GENRE, "genre");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_TRACK, "track");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_DATE, "year");
#else
if (f->author[0])
- tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
+ tag_add_item(tag, TAG_ARTIST, f->author);
if (f->title[0])
- tag_add_item(tag, TAG_ITEM_TITLE, f->title);
+ tag_add_item(tag, TAG_TITLE, f->title);
if (f->album[0])
- tag_add_item(tag, TAG_ITEM_ALBUM, f->album);
+ tag_add_item(tag, TAG_ALBUM, f->album);
if (f->track > 0) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", f->track);
- tag_add_item(tag, TAG_ITEM_TRACK, buffer);
+ tag_add_item(tag, TAG_TRACK, buffer);
}
if (f->comment[0])
- tag_add_item(tag, TAG_ITEM_COMMENT, f->comment);
+ tag_add_item(tag, TAG_COMMENT, f->comment);
if (f->genre[0])
- tag_add_item(tag, TAG_ITEM_GENRE, f->genre);
+ tag_add_item(tag, TAG_GENRE, f->genre);
if (f->year > 0) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", f->year);
- tag_add_item(tag, TAG_ITEM_DATE, buffer);
+ tag_add_item(tag, TAG_DATE, buffer);
}
#endif
diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c
index 1d5d48d09..9692ba49f 100644
--- a/src/decoder/flac_plugin.c
+++ b/src/decoder/flac_plugin.c
@@ -300,6 +300,8 @@ flac_cue_tag_load(const char *file)
FLAC__uint64 track_time = 0;
#ifdef HAVE_CUE /* libcue */
FLAC__StreamMetadata* vc;
+ char* cs_filename;
+ FILE* cs_file;
#endif /* libcue */
FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO);
FLAC__StreamMetadata* cs;
@@ -329,16 +331,25 @@ flac_cue_tag_load(const char *file)
FLAC__metadata_object_delete(vc);
}
+
+ if (tag == NULL) {
+ cs_filename = g_strconcat(file, ".cue", NULL);
+
+ cs_file = fopen(cs_filename, "rt");
+ g_free(cs_filename);
+
+ if (cs_file != NULL) {
+ tag = cue_tag_file(cs_file, tnum);
+ fclose(cs_file);
+ }
+ }
#endif /* libcue */
if (tag == NULL)
tag = flac_tag_load(file, char_tnum);
- if (char_tnum != NULL)
- {
- tag_add_item( tag,
- TAG_ITEM_TRACK,
- char_tnum);
+ if (char_tnum != NULL) {
+ tag_add_item(tag, TAG_TRACK, char_tnum);
g_free(char_tnum);
}
diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_plugin.c
index 1ef7183fa..27ddf655d 100644
--- a/src/decoder/mad_plugin.c
+++ b/src/decoder/mad_plugin.c
@@ -779,10 +779,10 @@ mp3_frame_duration(const struct mad_frame *frame)
MAD_UNITS_MILLISECONDS) / 1000.0;
}
-static off_t
+static goffset
mp3_this_frame_offset(const struct mp3_data *data)
{
- off_t offset = data->input_stream->offset;
+ goffset offset = data->input_stream->offset;
if (data->stream.this_frame != NULL)
offset -= data->stream.bufend - data->stream.this_frame;
@@ -792,7 +792,7 @@ mp3_this_frame_offset(const struct mp3_data *data)
return offset;
}
-static off_t
+static goffset
mp3_rest_including_this_frame(const struct mp3_data *data)
{
return data->input_stream->size - mp3_this_frame_offset(data);
@@ -804,7 +804,7 @@ mp3_rest_including_this_frame(const struct mp3_data *data)
static void
mp3_filesize_to_song_length(struct mp3_data *data)
{
- off_t rest = mp3_rest_including_this_frame(data);
+ goffset rest = mp3_rest_including_this_frame(data);
if (rest > 0) {
float frame_duration = mp3_frame_duration(&data->frame);
@@ -1170,13 +1170,6 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r)
return ret != DECODE_BREAK;
}
-static void mp3_audio_format(struct mp3_data *data, struct audio_format *af)
-{
- af->bits = 24;
- af->sample_rate = (data->frame).header.samplerate;
- af->channels = MAD_NCHANNELS(&(data->frame).header);
-}
-
static void
mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
{
@@ -1192,7 +1185,8 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
return;
}
- mp3_audio_format(&data, &audio_format);
+ audio_format_init(&audio_format, data.frame.header.samplerate, 24,
+ MAD_NCHANNELS(&data.frame.header));
decoder_initialized(decoder, &audio_format,
data.input_stream->seekable, data.total_time);
diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_plugin.c
index 065c34319..850232f34 100644
--- a/src/decoder/mikmod_plugin.c
+++ b/src/decoder/mikmod_plugin.c
@@ -175,9 +175,7 @@ mod_decode(struct decoder *decoder, const char *path)
return;
}
- audio_format.bits = 16;
- audio_format.sample_rate = 44100;
- audio_format.channels = 2;
+ audio_format_init(&audio_format, 44100, 16, 2);
secPerByte =
1.0 / ((audio_format.bits * audio_format.channels / 8.0) *
@@ -222,7 +220,7 @@ static struct tag *modTagDup(const char *file)
title = g_strdup(Player_LoadTitle(path2));
g_free(path2);
if (title)
- tag_add_item(ret, TAG_ITEM_TITLE, title);
+ tag_add_item(ret, TAG_TITLE, title);
return ret;
}
diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_plugin.c
index f636f2fa6..301c80674 100644
--- a/src/decoder/modplug_plugin.c
+++ b/src/decoder/modplug_plugin.c
@@ -121,9 +121,7 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
return;
}
- audio_format.bits = 16;
- audio_format.sample_rate = 44100;
- audio_format.channels = 2;
+ audio_format_init(&audio_format, 44100, 16, 2);
sec_perbyte =
1.0 / ((audio_format.bits * audio_format.channels / 8.0) *
@@ -186,11 +184,11 @@ static struct tag *mod_tagdup(const char *file)
return NULL;
}
ret = tag_new();
- ret->time = 0;
+ ret->time = ModPlug_GetLength(f) / 1000;
title = g_strdup(ModPlug_GetName(f));
if (title)
- tag_add_item(ret, TAG_ITEM_TITLE, title);
+ tag_add_item(ret, TAG_TITLE, title);
g_free(title);
ModPlug_Unload(f);
diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c
index cf9382904..745d1e619 100644
--- a/src/decoder/mp4ff_plugin.c
+++ b/src/decoder/mp4ff_plugin.c
@@ -131,11 +131,7 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
}
*track_r = track;
- *audio_format = (struct audio_format){
- .bits = 16,
- .channels = channels,
- .sample_rate = sample_rate,
- };
+ audio_format_init(audio_format, sample_rate, 16, channels);
if (!audio_format_valid(audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
@@ -395,22 +391,22 @@ mp4_tag_dup(const char *file)
mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
if (0 == g_ascii_strcasecmp("artist", item)) {
- tag_add_item(ret, TAG_ITEM_ARTIST, value);
+ tag_add_item(ret, TAG_ARTIST, value);
} else if (0 == g_ascii_strcasecmp("title", item)) {
- tag_add_item(ret, TAG_ITEM_TITLE, value);
+ tag_add_item(ret, TAG_TITLE, value);
} else if (0 == g_ascii_strcasecmp("album", item)) {
- tag_add_item(ret, TAG_ITEM_ALBUM, value);
+ tag_add_item(ret, TAG_ALBUM, value);
} else if (0 == g_ascii_strcasecmp("track", item)) {
- tag_add_item(ret, TAG_ITEM_TRACK, value);
+ tag_add_item(ret, TAG_TRACK, value);
} else if (0 == g_ascii_strcasecmp("disc", item)) {
/* Is that the correct id? */
- tag_add_item(ret, TAG_ITEM_DISC, value);
+ tag_add_item(ret, TAG_DISC, value);
} else if (0 == g_ascii_strcasecmp("genre", item)) {
- tag_add_item(ret, TAG_ITEM_GENRE, value);
+ tag_add_item(ret, TAG_GENRE, value);
} else if (0 == g_ascii_strcasecmp("date", item)) {
- tag_add_item(ret, TAG_ITEM_DATE, value);
+ tag_add_item(ret, TAG_DATE, value);
} else if (0 == g_ascii_strcasecmp("writer", item)) {
- tag_add_item(ret, TAG_ITEM_COMPOSER, value);
+ tag_add_item(ret, TAG_COMPOSER, value);
}
free(item);
diff --git a/src/decoder/mpcdec_plugin.c b/src/decoder/mpcdec_plugin.c
index 26349f93a..a684da104 100644
--- a/src/decoder/mpcdec_plugin.c
+++ b/src/decoder/mpcdec_plugin.c
@@ -193,9 +193,7 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
mpc_demux_get_info(demux, &info);
#endif
- audio_format.bits = 24;
- audio_format.channels = info.channels;
- audio_format.sample_rate = info.sample_freq;
+ audio_format_init(&audio_format, info.sample_freq, 24, info.channels);
if (!audio_format_valid(&audio_format)) {
#ifndef MPC_IS_OLD_API
diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c
new file mode 100644
index 000000000..20d9c4a54
--- /dev/null
+++ b/src/decoder/mpg123_decoder_plugin.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2003-2009 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 "decoder_api.h"
+
+#include <glib.h>
+
+#include <mpg123.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "mpg123"
+
+static bool
+mpd_mpg123_init(G_GNUC_UNUSED const struct config_param *param)
+{
+ mpg123_init();
+
+ return true;
+}
+
+static void
+mpd_mpg123_finish(void)
+{
+ mpg123_exit();
+}
+
+/**
+ * Opens a file with an existing #mpg123_handle.
+ *
+ * @param handle a handle which was created before; on error, this
+ * function will not free it
+ * @param audio_format this parameter is filled after successful
+ * return
+ * @return true on success
+ */
+static bool
+mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
+ struct audio_format *audio_format)
+{
+ char *path_dup;
+ int error;
+ int channels, encoding;
+ long rate;
+
+ /* mpg123_open() wants a writable string :-( */
+ path_dup = g_strdup(path_fs);
+
+ error = mpg123_open(handle, path_dup);
+ g_free(path_dup);
+ if (error != MPG123_OK) {
+ g_warning("libmpg123 failed to open %s: %s",
+ path_fs, mpg123_plain_strerror(error));
+ return false;
+ }
+
+ /* obtain the audio format */
+
+ error = mpg123_getformat(handle, &rate, &channels, &encoding);
+ if (error != MPG123_OK) {
+ g_warning("mpg123_getformat() failed: %s",
+ mpg123_plain_strerror(error));
+ return false;
+ }
+
+ if (encoding != MPG123_ENC_SIGNED_16) {
+ /* other formats not yet implemented */
+ g_warning("expected MPG123_ENC_SIGNED_16, got %d", encoding);
+ return false;
+ }
+
+ audio_format_init(audio_format, rate, 16, channels);
+ if (!audio_format_valid(audio_format)) {
+ g_warning("invalid audio format");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ struct audio_format audio_format;
+ mpg123_handle *handle;
+ int error;
+ off_t num_samples, position;
+ enum decoder_command cmd;
+
+ /* open the file */
+
+ handle = mpg123_new(NULL, &error);
+ if (handle == NULL) {
+ g_warning("mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return;
+ }
+
+ if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
+ mpg123_delete(handle);
+ return;
+ }
+
+ num_samples = mpg123_length(handle);
+
+ /* tell MPD core we're ready */
+
+ decoder_initialized(decoder, &audio_format, false,
+ (float)num_samples /
+ (float)audio_format.sample_rate);
+
+ /* the decoder main loop */
+
+ do {
+ unsigned char buffer[8192];
+ size_t nbytes;
+
+ position = mpg123_tell(handle);
+
+ /* decode */
+
+ error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes);
+ if (error != MPG123_OK) {
+ if (error != MPG123_DONE)
+ g_warning("mpg123_read() failed: %s",
+ mpg123_plain_strerror(error));
+ break;
+ }
+
+ /* send to MPD */
+
+ cmd = decoder_data(decoder, NULL, buffer, nbytes,
+ (float)position /
+ (float)audio_format.sample_rate,
+ 0, NULL);
+
+ /* seeking not yet implemented */
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ /* cleanup */
+
+ mpg123_delete(handle);
+}
+
+static struct tag *
+mpd_mpg123_tag_dup(const char *path_fs)
+{
+ struct audio_format audio_format;
+ mpg123_handle *handle;
+ int error;
+ off_t num_samples;
+ struct tag *tag;
+
+ handle = mpg123_new(NULL, &error);
+ if (handle == NULL) {
+ g_warning("mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return NULL;
+ }
+
+ if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
+ mpg123_delete(handle);
+ return NULL;
+ }
+
+ num_samples = mpg123_length(handle);
+ if (num_samples <= 0) {
+ mpg123_delete(handle);
+ return NULL;
+ }
+
+ tag = tag_new();
+
+ tag->time = num_samples / audio_format.sample_rate;
+
+ /* ID3 tag support not yet implemented */
+
+ mpg123_delete(handle);
+ return tag;
+}
+
+static const char *const mpg123_suffixes[] = {
+ "mp3",
+ NULL
+};
+
+const struct decoder_plugin mpg123_decoder_plugin = {
+ .name = "mpg123",
+ .init = mpd_mpg123_init,
+ .finish = mpd_mpg123_finish,
+ .file_decode = mpd_mpg123_file_decode,
+ /* streaming not yet implemented */
+ .tag_dup = mpd_mpg123_tag_dup,
+ .suffixes = mpg123_suffixes,
+};
diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx
index c62e6b4b6..079b178fb 100644
--- a/src/decoder/sidplay_plugin.cxx
+++ b/src/decoder/sidplay_plugin.cxx
@@ -21,14 +21,180 @@ extern "C" {
#include "../decoder_api.h"
}
+#include <errno.h>
+#include <stdlib.h>
#include <glib.h>
#include <sidplay/sidplay2.h>
#include <sidplay/builders/resid.h>
+#include <sidplay/utils/SidTuneMod.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "sidplay"
+#define SUBTUNE_PREFIX "tune_"
+
+static GPatternSpec *path_with_subtune;
+static const char *songlength_file;
+static GKeyFile *songlength_database;
+
+static bool all_files_are_containers;
+static unsigned default_songlength;
+
+static bool filter_setting;
+
+static GKeyFile *
+sidplay_load_songlength_db(const char *path)
+{
+ GError *error = NULL;
+ gchar *data;
+ gsize size;
+
+ if (!g_file_get_contents(path, &data, &size, &error)) {
+ g_warning("unable to read songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ /* replace any ; comment characters with # */
+ for (gsize i = 0; i < size; i++)
+ if (data[i] == ';')
+ data[i] = '#';
+
+ GKeyFile *db = g_key_file_new();
+ bool success = g_key_file_load_from_data(db, data, size,
+ G_KEY_FILE_NONE, &error);
+ g_free(data);
+ if (!success) {
+ g_warning("unable to parse songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ g_key_file_free(db);
+ return NULL;
+ }
+
+ g_key_file_set_list_separator(db, ' ');
+ return db;
+}
+
+static bool
+sidplay_init(const struct config_param *param)
+{
+ /* read the songlengths database file */
+ songlength_file=config_get_block_string(param,
+ "songlength_database", NULL);
+ if (songlength_file != NULL)
+ songlength_database = sidplay_load_songlength_db(songlength_file);
+
+ default_songlength=config_get_block_unsigned(param,
+ "default_songlength", 0);
+
+ all_files_are_containers=config_get_block_bool(param,
+ "all_files_are_containers", true);
+
+ path_with_subtune=g_pattern_spec_new(
+ "*/" SUBTUNE_PREFIX "???.sid");
+
+ filter_setting=config_get_block_bool(param, "filter", true);
+
+ return true;
+}
+
+void
+sidplay_finish()
+{
+ g_pattern_spec_free(path_with_subtune);
+
+ if(songlength_database)
+ g_key_file_free(songlength_database);
+}
+
+/**
+ * returns the file path stripped of any /tune_xxx.sid subtune
+ * suffix
+ */
+static char *
+get_container_name(const char *path_fs)
+{
+ char *path_container=g_strdup(path_fs);
+
+ if(!g_pattern_match(path_with_subtune,
+ strlen(path_container), path_container, NULL))
+ return path_container;
+
+ char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
+ if(ptr) *ptr='\0';
+
+ return path_container;
+}
+
+/**
+ * returns tune number from file.sid/tune_xxx.sid style path or 1 if
+ * no subtune is appended
+ */
+static int
+get_song_num(const char *path_fs)
+{
+ if(g_pattern_match(path_with_subtune,
+ strlen(path_fs), path_fs, NULL)) {
+ char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
+ if(!sub) return 1;
+
+ sub+=strlen("/" SUBTUNE_PREFIX);
+ int song_num=strtol(sub, NULL, 10);
+
+ if (errno == EINVAL)
+ return 1;
+ else
+ return song_num;
+ } else
+ return 1;
+}
+
+/* get the song length in seconds */
+static int
+get_song_length(const char *path_fs)
+{
+ if (songlength_database == NULL)
+ return -1;
+
+ gchar *sid_file=get_container_name(path_fs);
+ SidTuneMod tune(sid_file);
+ g_free(sid_file);
+ if(!tune) {
+ g_warning("failed to load file for calculating md5 sum");
+ return -1;
+ }
+ char md5sum[SIDTUNE_MD5_LENGTH+1];
+ tune.createMD5(md5sum);
+
+ int song_num=get_song_num(path_fs);
+
+ gsize num_items;
+ gchar **values=g_key_file_get_string_list(songlength_database,
+ "Database", md5sum, &num_items, NULL);
+ if(!values || song_num>num_items) {
+ g_strfreev(values);
+ return -1;
+ }
+
+ int minutes=strtol(values[song_num-1], NULL, 10);
+ if(errno==EINVAL) minutes=0;
+
+ int seconds;
+ char *ptr=strchr(values[song_num-1], ':');
+ if(ptr) {
+ seconds=strtol(ptr+1, NULL, 10);
+ if(errno==EINVAL) seconds=0;
+ } else
+ seconds=0;
+
+ g_strfreev(values);
+
+ return (minutes*60)+seconds;
+}
+
static void
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
{
@@ -36,13 +202,19 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
/* load the tune */
- SidTune tune(path_fs, NULL, true);
+ char *path_container=get_container_name(path_fs);
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
if (!tune) {
g_warning("failed to load file");
return;
}
- tune.selectSong(1);
+ int song_num=get_song_num(path_fs);
+ tune.selectSong(song_num);
+
+ int song_len=get_song_length(path_fs);
+ if(song_len==-1) song_len=default_songlength;
/* initialize the player */
@@ -67,7 +239,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
return;
}
- builder.filter(false);
+ builder.filter(filter_setting);
if (!builder) {
g_warning("ReSIDBuilder.filter() failed");
return;
@@ -103,14 +275,14 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
/* initialize the MPD decoder */
struct audio_format audio_format;
- audio_format.sample_rate = 48000;
- audio_format.bits = 16;
- audio_format.channels = 2;
+ audio_format_init(&audio_format, 48000, 16, 2);
- decoder_initialized(decoder, &audio_format, false, -1);
+ decoder_initialized(decoder, &audio_format, true, (float)song_len);
/* .. and play */
+ float data_time=0;
+ int timebase=player.timebase();
enum decoder_command cmd;
do {
char buffer[4096];
@@ -121,29 +293,105 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
break;
cmd = decoder_data(decoder, NULL, buffer, nbytes,
- 0, 0, NULL);
- } while (cmd == DECODE_COMMAND_NONE);
+ data_time, 0, NULL);
+
+ data_time=player.time()/timebase;
+
+ if(cmd==DECODE_COMMAND_SEEK) {
+ int target_time=decoder_seek_where(decoder);
+
+ /* can't rewind so return to zero and seek forward */
+ if(target_time<data_time) {
+ player.stop();
+ data_time=0;
+ }
+
+ /* ignore data until target time is reached */
+ while(data_time<target_time) {
+ nbytes=player.play(buffer, sizeof(buffer));
+ if(nbytes==0)
+ break;
+ data_time=player.time()/timebase;
+ }
+
+ decoder_command_finished(decoder);
+ }
+
+ if(song_len && data_time>=(float)song_len)
+ break;
+
+ } while (cmd != DECODE_COMMAND_STOP);
}
static struct tag *
sidplay_tag_dup(const char *path_fs)
{
- SidTune tune(path_fs, NULL, true);
+ int song_num=get_song_num(path_fs);
+ char *path_container=get_container_name(path_fs);
+
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
if (!tune)
return NULL;
const SidTuneInfo &info = tune.getInfo();
struct tag *tag = tag_new();
+ /* title */
+ const char *title;
if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL)
- tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]);
-
+ title=info.infoString[0];
+ else
+ title="";
+
+ if(info.songs>1) {
+ char *tag_title=g_strdup_printf("%s (%d/%d)",
+ title, song_num, info.songs);
+ tag_add_item(tag, TAG_TITLE, tag_title);
+ g_free(tag_title);
+ } else
+ tag_add_item(tag, TAG_TITLE, title);
+
+ /* artist */
if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]);
+ tag_add_item(tag, TAG_ARTIST, info.infoString[1]);
+
+ /* track */
+ char *track=g_strdup_printf("%d", song_num);
+ tag_add_item(tag, TAG_TRACK, track);
+ g_free(track);
+
+ /* time */
+ int song_len=get_song_length(path_fs);
+ if(song_len!=-1) tag->time=song_len;
return tag;
}
+static char *
+sidplay_container_scan(const char *path_fs, const unsigned int tnum)
+{
+ SidTune tune(path_fs, NULL, true);
+ if (!tune)
+ return NULL;
+
+ const SidTuneInfo &info=tune.getInfo();
+
+ /* Don't treat sids containing a single tune
+ as containers */
+ if(!all_files_are_containers && info.songs<2)
+ return NULL;
+
+ /* Construct container/tune path names, eg.
+ Delta.sid/tune_001.sid */
+ if(tnum<=info.songs) {
+ char *subtune= g_strdup_printf(
+ SUBTUNE_PREFIX "%03u.sid", tnum);
+ return subtune;
+ } else
+ return NULL;
+}
+
static const char *const sidplay_suffixes[] = {
"sid",
NULL
@@ -152,12 +400,12 @@ static const char *const sidplay_suffixes[] = {
extern const struct decoder_plugin sidplay_decoder_plugin;
const struct decoder_plugin sidplay_decoder_plugin = {
"sidplay",
- NULL, /* init() */
- NULL, /* finish() */
+ sidplay_init,
+ sidplay_finish,
NULL, /* stream_decode() */
sidplay_file_decode,
sidplay_tag_dup,
- NULL, /* container_scan */
+ sidplay_container_scan,
sidplay_suffixes,
NULL, /* mime_types */
};
diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c
new file mode 100644
index 000000000..59993ef89
--- /dev/null
+++ b/src/decoder/sndfile_decoder_plugin.c
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2003-2009 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 "decoder_api.h"
+
+#include <sndfile.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "sndfile"
+
+static sf_count_t
+sndfile_vio_get_filelen(void *user_data)
+{
+ const struct input_stream *is = user_data;
+
+ return is->size;
+}
+
+static sf_count_t
+sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
+{
+ struct input_stream *is = user_data;
+ bool success;
+
+ success = input_stream_seek(is, offset, whence);
+ if (!success)
+ return -1;
+
+ return is->offset;
+}
+
+static sf_count_t
+sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
+{
+ struct input_stream *is = user_data;
+ size_t nbytes;
+
+ nbytes = input_stream_read(is, ptr, count);
+ if (nbytes == 0 && is->error != 0)
+ return -1;
+
+ return nbytes;
+}
+
+static sf_count_t
+sndfile_vio_write(G_GNUC_UNUSED const void *ptr,
+ G_GNUC_UNUSED sf_count_t count,
+ G_GNUC_UNUSED void *user_data)
+{
+ /* no writing! */
+ return -1;
+}
+
+static sf_count_t
+sndfile_vio_tell(void *user_data)
+{
+ const struct input_stream *is = user_data;
+
+ return is->offset;
+}
+
+/**
+ * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a
+ * libsndfile stream.
+ */
+static SF_VIRTUAL_IO vio = {
+ .get_filelen = sndfile_vio_get_filelen,
+ .seek = sndfile_vio_seek,
+ .read = sndfile_vio_read,
+ .write = sndfile_vio_write,
+ .tell = sndfile_vio_tell,
+};
+
+/**
+ * Converts a frame number to a timestamp (in seconds).
+ */
+static float
+frame_to_time(sf_count_t frame, const struct audio_format *audio_format)
+{
+ return (float)frame / (float)audio_format->sample_rate;
+}
+
+/**
+ * Converts a timestamp (in seconds) to a frame number.
+ */
+static sf_count_t
+time_to_frame(float t, const struct audio_format *audio_format)
+{
+ return (sf_count_t)(t * audio_format->sample_rate);
+}
+
+static void
+sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ SNDFILE *sf;
+ SF_INFO info;
+ struct audio_format audio_format;
+ size_t frame_size;
+ sf_count_t read_frames, num_frames, position = 0;
+ int buffer[4096];
+ enum decoder_command cmd;
+
+ info.format = 0;
+
+ sf = sf_open_virtual(&vio, SFM_READ, &info, is);
+ if (sf == NULL) {
+ g_warning("sf_open_virtual() failed");
+ return;
+ }
+
+ /* for now, always read 32 bit samples. Later, we could lower
+ MPD's CPU usage by reading 16 bit samples with
+ sf_readf_short() on low-quality source files. */
+ audio_format_init(&audio_format, info.samplerate, 32, info.channels);
+
+ if (!audio_format_valid(&audio_format)) {
+ g_warning("invalid audio format");
+ return;
+ }
+
+ decoder_initialized(decoder, &audio_format, info.seekable,
+ frame_to_time(info.frames, &audio_format));
+
+ frame_size = audio_format_frame_size(&audio_format);
+ read_frames = sizeof(buffer) / frame_size;
+
+ do {
+ num_frames = sf_readf_int(sf, buffer, read_frames);
+ if (num_frames <= 0)
+ break;
+
+ cmd = decoder_data(decoder, is,
+ buffer, num_frames * frame_size,
+ frame_to_time(position, &audio_format),
+ 0, NULL);
+ if (cmd == DECODE_COMMAND_SEEK) {
+ sf_count_t c =
+ time_to_frame(decoder_seek_where(decoder),
+ &audio_format);
+ c = sf_seek(sf, c, SEEK_SET);
+ if (c < 0)
+ decoder_seek_error(decoder);
+ else
+ decoder_command_finished(decoder);
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ sf_close(sf);
+}
+
+static struct tag *
+sndfile_tag_dup(const char *path_fs)
+{
+ SNDFILE *sf;
+ SF_INFO info;
+ struct tag *tag;
+ const char *p;
+
+ info.format = 0;
+
+ sf = sf_open(path_fs, SFM_READ, &info);
+ if (sf == NULL)
+ return NULL;
+
+ if (!audio_valid_sample_rate(info.samplerate)) {
+ sf_close(sf);
+ g_warning("Invalid sample rate in %s\n", path_fs);
+ return NULL;
+ }
+
+ tag = tag_new();
+ tag->time = info.frames / info.samplerate;
+
+ p = sf_get_string(sf, SF_STR_TITLE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_TITLE, p);
+
+ p = sf_get_string(sf, SF_STR_ARTIST);
+ if (p != NULL)
+ tag_add_item(tag, TAG_ARTIST, p);
+
+ p = sf_get_string(sf, SF_STR_DATE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_DATE, p);
+
+ sf_close(sf);
+
+ return tag;
+}
+
+static const char *const sndfile_suffixes[] = {
+ "wav", "aiff", "aif", /* Microsoft / SGI / Apple */
+ "au", "snd", /* Sun / DEC / NeXT */
+ "paf", /* Paris Audio File */
+ "iff", "svx", /* Commodore Amiga IFF / SVX */
+ "sf", /* IRCAM */
+ "voc", /* Creative */
+ "w64", /* Soundforge */
+ "pvf", /* Portable Voice Format */
+ "xi", /* Fasttracker */
+ "htk", /* HMM Tool Kit */
+ "caf", /* Apple */
+ "sd2", /* Sound Designer II */
+
+ /* libsndfile also supports FLAC and Ogg Vorbis, but only by
+ linking with libFLAC and libvorbis - we can do better, we
+ have native plugins for these libraries */
+
+ NULL
+};
+
+static const char *const sndfile_mime_types[] = {
+ "audio/x-wav",
+ "audio/x-aiff",
+
+ /* what are the MIME types of the other supported formats? */
+
+ NULL
+};
+
+const struct decoder_plugin sndfile_decoder_plugin = {
+ .name = "sndfile",
+ .stream_decode = sndfile_stream_decode,
+ .tag_dup = sndfile_tag_dup,
+ .suffixes = sndfile_suffixes,
+ .mime_types = sndfile_mime_types,
+};
diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c
index d4f81e91f..2ac9c3bcb 100644..100755
--- a/src/decoder/vorbis_plugin.c
+++ b/src/decoder/vorbis_plugin.c
@@ -24,6 +24,7 @@
#include "uri.h"
#ifndef HAVE_TREMOR
+#define OV_EXCLUDE_STATIC_CALLBACKS
#include <vorbis/vorbisfile.h>
#else
#include <tremor/ivorbisfile.h>
@@ -176,11 +177,11 @@ vorbis_parse_comment(struct tag *tag, const char *comment)
assert(tag != NULL);
if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY,
- TAG_ITEM_TRACK) ||
+ TAG_TRACK) ||
vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY,
- TAG_ITEM_DISC) ||
+ TAG_DISC) ||
vorbis_copy_comment(tag, comment, "album artist",
- TAG_ITEM_ALBUM_ARTIST))
+ TAG_ALBUM_ARTIST))
return;
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
@@ -324,8 +325,7 @@ vorbis_stream_decode(struct decoder *decoder,
vorbis_info *vi = ov_info(&vf, -1);
struct replay_gain_info *new_rgi;
- audio_format.channels = vi->channels;
- audio_format.sample_rate = vi->rate;
+ audio_format_init(&audio_format, vi->rate, 16, vi->channels);
if (!audio_format_valid(&audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
@@ -378,7 +378,7 @@ vorbis_tag_dup(const char *file)
FILE *fp;
OggVorbis_File vf;
- fp = fopen(file, "r");
+ fp = fopen(file, "rb");
if (!fp) {
return NULL;
}
diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_plugin.c
index 821536fb5..3e7c0cef7 100644
--- a/src/decoder/wavpack_plugin.c
+++ b/src/decoder/wavpack_plugin.c
@@ -41,17 +41,17 @@ static struct {
const char *name;
enum tag_type type;
} tagtypes[] = {
- { "artist", TAG_ITEM_ARTIST },
- { "album", TAG_ITEM_ALBUM },
- { "title", TAG_ITEM_TITLE },
- { "track", TAG_ITEM_TRACK },
- { "name", TAG_ITEM_NAME },
- { "genre", TAG_ITEM_GENRE },
- { "date", TAG_ITEM_DATE },
- { "composer", TAG_ITEM_COMPOSER },
- { "performer", TAG_ITEM_PERFORMER },
- { "comment", TAG_ITEM_COMMENT },
- { "disc", TAG_ITEM_DISC },
+ { "artist", TAG_ARTIST },
+ { "album", TAG_ALBUM },
+ { "title", TAG_TITLE },
+ { "track", TAG_TRACK },
+ { "name", TAG_NAME },
+ { "genre", TAG_GENRE },
+ { "date", TAG_DATE },
+ { "composer", TAG_COMPOSER },
+ { "performer", TAG_PERFORMER },
+ { "comment", TAG_COMMENT },
+ { "disc", TAG_DISC },
};
/** A pointer type for format converter function. */
@@ -145,9 +145,9 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
int bytes_per_sample, output_sample_size;
int position;
- audio_format.sample_rate = WavpackGetSampleRate(wpc);
- audio_format.channels = WavpackGetReducedChannels(wpc);
- audio_format.bits = WavpackGetBitsPerSample(wpc);
+ audio_format_init(&audio_format, WavpackGetSampleRate(wpc),
+ WavpackGetBitsPerSample(wpc),
+ WavpackGetReducedChannels(wpc));
/* round bitwidth to 8-bit units */
audio_format.bits = (audio_format.bits + 7) & (~7);
diff --git a/src/decoder_api.c b/src/decoder_api.c
index 2ece3bb98..2350396a9 100644
--- a/src/decoder_api.c
+++ b/src/decoder_api.c
@@ -37,12 +37,15 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "decoder"
-void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
- const struct audio_format *audio_format,
- bool seekable, float total_time)
+void
+decoder_initialized(struct decoder *decoder,
+ const struct audio_format *audio_format,
+ bool seekable, float total_time)
{
- assert(dc.state == DECODE_STATE_START);
- assert(dc.pipe != NULL);
+ struct decoder_control *dc = decoder->dc;
+
+ assert(dc->state == DECODE_STATE_START);
+ assert(dc->pipe != NULL);
assert(decoder != NULL);
assert(decoder->stream_tag == NULL);
assert(decoder->decoder_tag == NULL);
@@ -51,79 +54,98 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
assert(audio_format_defined(audio_format));
assert(audio_format_valid(audio_format));
- dc.in_audio_format = *audio_format;
- getOutputAudioFormat(audio_format, &dc.out_audio_format);
+ dc->in_audio_format = *audio_format;
+ getOutputAudioFormat(audio_format, &dc->out_audio_format);
+
+ dc->seekable = seekable;
+ dc->total_time = total_time;
- dc.seekable = seekable;
- dc.total_time = total_time;
+ decoder_lock(dc);
+ dc->state = DECODE_STATE_DECODE;
+ decoder_unlock(dc);
- dc.state = DECODE_STATE_DECODE;
- notify_signal(&pc.notify);
+ player_lock_signal();
g_debug("audio_format=%u:%u:%u, seekable=%s",
- dc.in_audio_format.sample_rate, dc.in_audio_format.bits,
- dc.in_audio_format.channels,
+ dc->in_audio_format.sample_rate,
+ dc->in_audio_format.bits,
+ dc->in_audio_format.channels,
seekable ? "true" : "false");
- if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format))
+ if (!audio_format_equals(&dc->in_audio_format,
+ &dc->out_audio_format))
g_debug("converting to %u:%u:%u",
- dc.out_audio_format.sample_rate,
- dc.out_audio_format.bits,
- dc.out_audio_format.channels);
+ dc->out_audio_format.sample_rate,
+ dc->out_audio_format.bits,
+ dc->out_audio_format.channels);
}
char *decoder_get_uri(G_GNUC_UNUSED struct decoder *decoder)
{
- assert(dc.pipe != NULL);
+ const struct decoder_control *dc = decoder->dc;
- return song_get_uri(dc.current_song);
+ assert(dc->pipe != NULL);
+
+ return song_get_uri(dc->song);
}
enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder)
{
- assert(dc.pipe != NULL);
+ const struct decoder_control *dc = decoder->dc;
+
+ assert(dc->pipe != NULL);
- return dc.command;
+ return dc->command;
}
void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
{
- assert(dc.command != DECODE_COMMAND_NONE);
- assert(dc.command != DECODE_COMMAND_SEEK ||
- dc.seek_error || decoder->seeking);
- assert(dc.pipe != NULL);
+ struct decoder_control *dc = decoder->dc;
- if (dc.command == DECODE_COMMAND_SEEK) {
+ decoder_lock(dc);
+
+ assert(dc->command != DECODE_COMMAND_NONE);
+ assert(dc->command != DECODE_COMMAND_SEEK ||
+ dc->seek_error || decoder->seeking);
+ assert(dc->pipe != NULL);
+
+ if (dc->command == DECODE_COMMAND_SEEK) {
/* delete frames from the old song position */
if (decoder->chunk != NULL) {
- music_buffer_return(dc.buffer, decoder->chunk);
+ music_buffer_return(dc->buffer, decoder->chunk);
decoder->chunk = NULL;
}
- music_pipe_clear(dc.pipe, dc.buffer);
+ music_pipe_clear(dc->pipe, dc->buffer);
}
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ dc->command = DECODE_COMMAND_NONE;
+ decoder_unlock(dc);
+
+ player_lock_signal();
}
double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
{
- assert(dc.command == DECODE_COMMAND_SEEK);
- assert(dc.pipe != NULL);
+ const struct decoder_control *dc = decoder->dc;
+
+ assert(dc->command == DECODE_COMMAND_SEEK);
+ assert(dc->pipe != NULL);
decoder->seeking = true;
- return dc.seek_where;
+ return dc->seek_where;
}
void decoder_seek_error(struct decoder * decoder)
{
- assert(dc.command == DECODE_COMMAND_SEEK);
- assert(dc.pipe != NULL);
+ struct decoder_control *dc = decoder->dc;
+
+ assert(dc->command == DECODE_COMMAND_SEEK);
+ assert(dc->pipe != NULL);
- dc.seek_error = true;
+ dc->seek_error = true;
decoder_command_finished(decoder);
}
@@ -131,11 +153,13 @@ size_t decoder_read(struct decoder *decoder,
struct input_stream *is,
void *buffer, size_t length)
{
+ const struct decoder_control *dc =
+ decoder != NULL ? decoder->dc : NULL;
size_t nbytes;
assert(decoder == NULL ||
- dc.state == DECODE_STATE_START ||
- dc.state == DECODE_STATE_DECODE);
+ dc->state == DECODE_STATE_START ||
+ dc->state == DECODE_STATE_DECODE);
assert(is != NULL);
assert(buffer != NULL);
@@ -148,9 +172,9 @@ size_t decoder_read(struct decoder *decoder,
/* ignore the SEEK command during initialization,
the plugin should handle that after it has
initialized successfully */
- (dc.command != DECODE_COMMAND_SEEK ||
- (dc.state != DECODE_STATE_START && !decoder->seeking)) &&
- dc.command != DECODE_COMMAND_NONE)
+ (dc->command != DECODE_COMMAND_SEEK ||
+ (dc->state != DECODE_STATE_START && !decoder->seeking)) &&
+ dc->command != DECODE_COMMAND_NONE)
return 0;
nbytes = input_stream_read(is, buffer, length);
@@ -177,15 +201,15 @@ do_send_tag(struct decoder *decoder, struct input_stream *is,
/* there is a partial chunk - flush it, we want the
tag in a new chunk */
decoder_flush_chunk(decoder);
- notify_signal(&pc.notify);
+ player_lock_signal();
}
assert(decoder->chunk == NULL);
chunk = decoder_get_chunk(decoder, is);
if (chunk == NULL) {
- assert(dc.command != DECODE_COMMAND_NONE);
- return dc.command;
+ assert(decoder->dc->command != DECODE_COMMAND_NONE);
+ return decoder->dc->command;
}
chunk->tag = tag_dup(tag);
@@ -224,22 +248,26 @@ decoder_data(struct decoder *decoder,
float data_time, uint16_t bitRate,
struct replay_gain_info *replay_gain_info)
{
+ struct decoder_control *dc = decoder->dc;
const char *data = _data;
+ GError *error = NULL;
+ enum decoder_command cmd;
- assert(dc.state == DECODE_STATE_DECODE);
- assert(dc.pipe != NULL);
- assert(length % audio_format_frame_size(&dc.in_audio_format) == 0);
+ assert(dc->state == DECODE_STATE_DECODE);
+ assert(dc->pipe != NULL);
+ assert(length % audio_format_frame_size(&dc->in_audio_format) == 0);
- if (dc.command == DECODE_COMMAND_STOP ||
- dc.command == DECODE_COMMAND_SEEK ||
+ decoder_lock(dc);
+ cmd = dc->command;
+ decoder_unlock(dc);
+
+ if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK ||
length == 0)
- return dc.command;
+ return cmd;
/* send stream tags */
if (update_stream_tag(decoder, is)) {
- enum decoder_command cmd;
-
if (decoder->decoder_tag != NULL) {
/* merge with tag from decoder plugin */
struct tag *tag;
@@ -256,17 +284,18 @@ decoder_data(struct decoder *decoder,
return cmd;
}
- if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format)) {
+ if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) {
data = pcm_convert(&decoder->conv_state,
- &dc.in_audio_format, data, length,
- &dc.out_audio_format, &length);
-
- /* under certain circumstances, pcm_convert() may
- return an empty buffer - this condition should be
- investigated further, but for now, do this check as
- a workaround: */
- if (data == NULL)
- return DECODE_COMMAND_NONE;
+ &dc->in_audio_format, data, length,
+ &dc->out_audio_format, &length,
+ &error);
+ if (data == NULL) {
+ /* the PCM conversion has failed - stop
+ playback, since we have no better way to
+ bail out */
+ g_warning("%s", error->message);
+ return DECODE_COMMAND_STOP;
+ }
}
while (length > 0) {
@@ -277,16 +306,16 @@ decoder_data(struct decoder *decoder,
chunk = decoder_get_chunk(decoder, is);
if (chunk == NULL) {
- assert(dc.command != DECODE_COMMAND_NONE);
- return dc.command;
+ assert(dc->command != DECODE_COMMAND_NONE);
+ return dc->command;
}
- dest = music_chunk_write(chunk, &dc.out_audio_format,
+ dest = music_chunk_write(chunk, &dc->out_audio_format,
data_time, bitRate, &nbytes);
if (dest == NULL) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
- notify_signal(&pc.notify);
+ player_lock_signal();
continue;
}
@@ -301,20 +330,19 @@ decoder_data(struct decoder *decoder,
/* apply replay gain or normalization */
- if (replay_gain_info != NULL &&
- replay_gain_mode != REPLAY_GAIN_OFF)
+ if (replay_gain_mode != REPLAY_GAIN_OFF)
replay_gain_apply(replay_gain_info, dest, nbytes,
- &dc.out_audio_format);
+ &dc->out_audio_format);
else if (normalizationEnabled)
- normalizeData(dest, nbytes, &dc.out_audio_format);
+ normalizeData(dest, nbytes, &dc->out_audio_format);
/* expand the music pipe chunk */
- full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes);
+ full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes);
if (full) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
- notify_signal(&pc.notify);
+ player_lock_signal();
}
data += nbytes;
@@ -328,10 +356,11 @@ enum decoder_command
decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
const struct tag *tag)
{
+ const struct decoder_control *dc = decoder->dc;
enum decoder_command cmd;
- assert(dc.state == DECODE_STATE_DECODE);
- assert(dc.pipe != NULL);
+ assert(dc->state == DECODE_STATE_DECODE);
+ assert(dc->pipe != NULL);
assert(tag != NULL);
/* save the tag */
diff --git a/src/decoder_api.h b/src/decoder_api.h
index 37090d8d0..2ecd98ce7 100644
--- a/src/decoder_api.h
+++ b/src/decoder_api.h
@@ -17,15 +17,16 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_DECODER_API_H
-#define MPD_DECODER_API_H
-
-/*
+/*! \file
+ * \brief The MPD Decoder API
+ *
* This is the public API which is used by decoder plugins to
* communicate with the mpd core.
- *
*/
+#ifndef MPD_DECODER_API_H
+#define MPD_DECODER_API_H
+
#include "decoder_command.h"
#include "decoder_plugin.h"
#include "input_stream.h"
@@ -36,56 +37,97 @@
#include <stdbool.h>
-
/**
* Notify the player thread that it has finished initialization and
* that it has read the song's meta data.
+ *
+ * @param decoder the decoder object
+ * @param audio_format the audio format which is going to be sent to
+ * decoder_data()
+ * @param seekable true if the song is seekable
+ * @param total_time the total number of seconds in this song; -1 if unknown
*/
-void decoder_initialized(struct decoder * decoder,
- const struct audio_format *audio_format,
- bool seekable, float total_time);
+void
+decoder_initialized(struct decoder *decoder,
+ const struct audio_format *audio_format,
+ bool seekable, float total_time);
/**
* Returns the URI of the current song in UTF-8 encoding.
*
- * The return value is allocated on the heap, and must be freed by the
- * caller.
+ * @param decoder the decoder object
+ * @return an allocated string which must be freed with g_free()
*/
-char *decoder_get_uri(struct decoder *decoder);
+char *
+decoder_get_uri(struct decoder *decoder);
-enum decoder_command decoder_get_command(struct decoder * decoder);
+/**
+ * Determines the pending decoder command.
+ *
+ * @param decoder the decoder object
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
+ */
+enum decoder_command
+decoder_get_command(struct decoder *decoder);
/**
* Called by the decoder when it has performed the requested command
* (dc->command). This function resets dc->command and wakes up the
* player thread.
+ *
+ * @param decoder the decoder object
*/
-void decoder_command_finished(struct decoder * decoder);
+void
+decoder_command_finished(struct decoder *decoder);
-double decoder_seek_where(struct decoder * decoder);
+/**
+ * Call this when you have received the DECODE_COMMAND_SEEK command.
+ *
+ * @param decoder the decoder object
+ * @return the destination position for the week
+ */
+double
+decoder_seek_where(struct decoder *decoder);
-void decoder_seek_error(struct decoder * decoder);
+/**
+ * Call this right before decoder_command_finished() when seeking has
+ * failed.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_seek_error(struct decoder *decoder);
/**
- * Blocking read from the input stream. Returns the number of bytes
- * read, or 0 if one of the following occurs: end of file; error;
- * command (like SEEK or STOP).
+ * Blocking read from the input stream.
+ *
+ * @param decoder the decoder object
+ * @param is the input stream to read from
+ * @param buffer the destination buffer
+ * @param length the maximum number of bytes to read
+ * @return the number of bytes read, or 0 if one of the following
+ * occurs: end of file; error; command (like SEEK or STOP).
*/
-size_t decoder_read(struct decoder *decoder,
- struct input_stream *inStream,
- void *buffer, size_t length);
+size_t
+decoder_read(struct decoder *decoder, struct input_stream *is,
+ void *buffer, size_t length);
/**
* This function is called by the decoder plugin when it has
* successfully decoded block of input data.
*
- * We send inStream for buffering the inputStream while waiting to
- * send the next chunk
+ * @param decoder the decoder object
+ * @param is an input stream which is buffering while we are waiting
+ * for the player
+ * @param data the source buffer
+ * @param length the number of bytes in the buffer
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
*/
enum decoder_command
-decoder_data(struct decoder *decoder,
- struct input_stream *inStream,
- const void *data, size_t datalen,
+decoder_data(struct decoder *decoder, struct input_stream *is,
+ const void *data, size_t length,
float data_time, uint16_t bitRate,
struct replay_gain_info *replay_gain_info);
@@ -93,8 +135,12 @@ decoder_data(struct decoder *decoder,
* This function is called by the decoder plugin when it has
* successfully decoded a tag.
*
+ * @param decoder the decoder object
* @param is an input stream which is buffering while we are waiting
* for the player
+ * @param tag the tag to send
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
*/
enum decoder_command
decoder_tag(struct decoder *decoder, struct input_stream *is,
diff --git a/src/decoder_control.c b/src/decoder_control.c
index 44bb63e15..7fe18088b 100644
--- a/src/decoder_control.c
+++ b/src/decoder_control.c
@@ -18,107 +18,131 @@
*/
#include "decoder_control.h"
+#include "player_control.h"
#include <assert.h>
-struct decoder_control dc;
+void
+dc_init(struct decoder_control *dc)
+{
+ dc->thread = NULL;
-void dc_init(void)
+ dc->mutex = g_mutex_new();
+ dc->cond = g_cond_new();
+
+ dc->state = DECODE_STATE_STOP;
+ dc->command = DECODE_COMMAND_NONE;
+}
+
+void
+dc_deinit(struct decoder_control *dc)
{
- notify_init(&dc.notify);
- dc.state = DECODE_STATE_STOP;
- dc.command = DECODE_COMMAND_NONE;
+ g_cond_free(dc->cond);
+ g_mutex_free(dc->mutex);
}
-void dc_deinit(void)
+static void
+dc_command_wait_locked(struct decoder_control *dc)
{
- notify_deinit(&dc.notify);
+ while (dc->command != DECODE_COMMAND_NONE)
+ player_wait_decoder(dc);
}
void
-dc_command_wait(struct notify *notify)
+dc_command_wait(struct decoder_control *dc)
{
- while (dc.command != DECODE_COMMAND_NONE) {
- notify_signal(&dc.notify);
- notify_wait(notify);
- }
+ decoder_lock(dc);
+ dc_command_wait_locked(dc);
+ decoder_unlock(dc);
}
static void
-dc_command(struct notify *notify, enum decoder_command cmd)
+dc_command_locked(struct decoder_control *dc, enum decoder_command cmd)
{
- dc.command = cmd;
- dc_command_wait(notify);
+ dc->command = cmd;
+ decoder_signal(dc);
+ dc_command_wait_locked(dc);
}
-static void dc_command_async(enum decoder_command cmd)
+static void
+dc_command(struct decoder_control *dc, enum decoder_command cmd)
{
- dc.command = cmd;
- notify_signal(&dc.notify);
+ decoder_lock(dc);
+ dc_command_locked(dc, cmd);
+ decoder_unlock(dc);
}
-void
-dc_start(struct notify *notify, struct song *song)
+static void
+dc_command_async(struct decoder_control *dc, enum decoder_command cmd)
{
- assert(dc.pipe != NULL);
- assert(song != NULL);
+ decoder_lock(dc);
- dc.next_song = song;
- dc_command(notify, DECODE_COMMAND_START);
+ dc->command = cmd;
+ decoder_signal(dc);
+
+ decoder_unlock(dc);
}
void
-dc_start_async(struct song *song)
+dc_start(struct decoder_control *dc, struct song *song,
+ struct music_buffer *buffer, struct music_pipe *pipe)
{
- assert(dc.pipe != NULL);
assert(song != NULL);
+ assert(buffer != NULL);
+ assert(pipe != NULL);
- dc.next_song = song;
- dc_command_async(DECODE_COMMAND_START);
+ dc->song = song;
+ dc->buffer = buffer;
+ dc->pipe = pipe;
+ dc_command(dc, DECODE_COMMAND_START);
}
void
-dc_stop(struct notify *notify)
+dc_stop(struct decoder_control *dc)
{
- if (dc.command != DECODE_COMMAND_NONE)
+ decoder_lock(dc);
+
+ if (dc->command != DECODE_COMMAND_NONE)
/* Attempt to cancel the current command. If it's too
late and the decoder thread is already executing
the old command, we'll call STOP again in this
function (see below). */
- dc_command(notify, DECODE_COMMAND_STOP);
+ dc_command_locked(dc, DECODE_COMMAND_STOP);
+
+ if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR)
+ dc_command_locked(dc, DECODE_COMMAND_STOP);
- if (dc.state != DECODE_STATE_STOP && dc.state != DECODE_STATE_ERROR)
- dc_command(notify, DECODE_COMMAND_STOP);
+ decoder_unlock(dc);
}
bool
-dc_seek(struct notify *notify, double where)
+dc_seek(struct decoder_control *dc, double where)
{
- assert(dc.state != DECODE_STATE_START);
+ assert(dc->state != DECODE_STATE_START);
assert(where >= 0.0);
- if (dc.state == DECODE_STATE_STOP ||
- dc.state == DECODE_STATE_ERROR || !dc.seekable)
+ if (dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR || !dc->seekable)
return false;
- dc.seek_where = where;
- dc.seek_error = false;
- dc_command(notify, DECODE_COMMAND_SEEK);
+ dc->seek_where = where;
+ dc->seek_error = false;
+ dc_command(dc, DECODE_COMMAND_SEEK);
- if (dc.seek_error)
+ if (dc->seek_error)
return false;
return true;
}
void
-dc_quit(void)
+dc_quit(struct decoder_control *dc)
{
- assert(dc.thread != NULL);
+ assert(dc->thread != NULL);
- dc.quit = true;
- dc_command_async(DECODE_COMMAND_STOP);
+ dc->quit = true;
+ dc_command_async(dc, DECODE_COMMAND_STOP);
- g_thread_join(dc.thread);
- dc.thread = NULL;
+ g_thread_join(dc->thread);
+ dc->thread = NULL;
}
diff --git a/src/decoder_control.h b/src/decoder_control.h
index 6a04a1617..38c4f0d83 100644
--- a/src/decoder_control.h
+++ b/src/decoder_control.h
@@ -22,7 +22,8 @@
#include "decoder_command.h"
#include "audio_format.h"
-#include "notify.h"
+
+#include <glib.h>
#include <assert.h>
@@ -45,14 +46,25 @@ struct decoder_control {
thread isn't running */
GThread *thread;
- struct notify notify;
+ /**
+ * This lock protects #state and #command.
+ */
+ GMutex *mutex;
+
+ /**
+ * Trigger this object after you have modified #command. This
+ * is also used by the decoder thread to notify the caller
+ * when it has finished a command.
+ */
+ GCond *cond;
+
+ enum decoder_state state;
+ enum decoder_command command;
- volatile enum decoder_state state;
- volatile enum decoder_command command;
bool quit;
bool seek_error;
bool seekable;
- volatile double seek_where;
+ double seek_where;
/** the format of the song file */
struct audio_format in_audio_format;
@@ -60,54 +72,139 @@ struct decoder_control {
/** the format being sent to the music pipe */
struct audio_format out_audio_format;
- struct song *current_song;
- struct song *next_song;
+ /**
+ * The song currently being decoded. This attribute is set by
+ * the player thread, when it sends the #DECODE_COMMAND_START
+ * command.
+ */
+ const struct song *song;
+
float total_time;
/** the #music_chunk allocator */
struct music_buffer *buffer;
- /** the destination pipe for decoded chunks */
+ /**
+ * The destination pipe for decoded chunks. The caller thread
+ * owns this object, and is responsible for freeing it.
+ */
struct music_pipe *pipe;
};
-extern struct decoder_control dc;
+void
+dc_init(struct decoder_control *dc);
+
+void
+dc_deinit(struct decoder_control *dc);
+
+/**
+ * Locks the #decoder_control object.
+ */
+static inline void
+decoder_lock(struct decoder_control *dc)
+{
+ g_mutex_lock(dc->mutex);
+}
+
+/**
+ * Unlocks the #decoder_control object.
+ */
+static inline void
+decoder_unlock(struct decoder_control *dc)
+{
+ g_mutex_unlock(dc->mutex);
+}
+
+/**
+ * Waits for a signal on the #decoder_control object. This function
+ * is only valid in the decoder thread. The object must be locked
+ * prior to calling this function.
+ */
+static inline void
+decoder_wait(struct decoder_control *dc)
+{
+ g_cond_wait(dc->cond, dc->mutex);
+}
+
+/**
+ * Signals the #decoder_control object. This function is only valid
+ * in the player thread. The object should be locked prior to calling
+ * this function.
+ */
+static inline void
+decoder_signal(struct decoder_control *dc)
+{
+ g_cond_signal(dc->cond);
+}
+
+static inline bool
+decoder_is_idle(const struct decoder_control *dc)
+{
+ return dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR;
+}
+
+static inline bool
+decoder_is_starting(const struct decoder_control *dc)
+{
+ return dc->state == DECODE_STATE_START;
+}
-void dc_init(void);
+static inline bool
+decoder_has_failed(const struct decoder_control *dc)
+{
+ assert(dc->command == DECODE_COMMAND_NONE);
-void dc_deinit(void);
+ return dc->state == DECODE_STATE_ERROR;
+}
-static inline bool decoder_is_idle(void)
+static inline bool
+decoder_lock_is_idle(struct decoder_control *dc)
{
- return (dc.state == DECODE_STATE_STOP ||
- dc.state == DECODE_STATE_ERROR) &&
- dc.command != DECODE_COMMAND_START;
+ bool ret;
+
+ decoder_lock(dc);
+ ret = decoder_is_idle(dc);
+ decoder_unlock(dc);
+
+ return ret;
}
-static inline bool decoder_is_starting(void)
+static inline bool
+decoder_lock_is_starting(struct decoder_control *dc)
{
- return dc.command == DECODE_COMMAND_START ||
- dc.state == DECODE_STATE_START;
+ bool ret;
+
+ decoder_lock(dc);
+ ret = decoder_is_starting(dc);
+ decoder_unlock(dc);
+
+ return ret;
}
-static inline bool decoder_has_failed(void)
+static inline bool
+decoder_lock_has_failed(struct decoder_control *dc)
{
- assert(dc.command == DECODE_COMMAND_NONE);
+ bool ret;
+
+ decoder_lock(dc);
+ ret = decoder_has_failed(dc);
+ decoder_unlock(dc);
- return dc.state == DECODE_STATE_ERROR;
+ return ret;
}
-static inline struct song *
-decoder_current_song(void)
+static inline const struct song *
+decoder_current_song(const struct decoder_control *dc)
{
- switch (dc.state) {
+ switch (dc->state) {
case DECODE_STATE_STOP:
case DECODE_STATE_ERROR:
return NULL;
case DECODE_STATE_START:
case DECODE_STATE_DECODE:
- return dc.current_song;
+ return dc->song;
}
assert(false);
@@ -115,21 +212,27 @@ decoder_current_song(void)
}
void
-dc_command_wait(struct notify *notify);
-
-void
-dc_start(struct notify *notify, struct song *song);
+dc_command_wait(struct decoder_control *dc);
+/**
+ * Start the decoder.
+ *
+ * @param the decoder
+ * @param song the song to be decoded
+ * @param pipe the pipe which receives the decoded chunks (owned by
+ * the caller)
+ */
void
-dc_start_async(struct song *song);
+dc_start(struct decoder_control *dc, struct song *song,
+ struct music_buffer *buffer, struct music_pipe *pipe);
void
-dc_stop(struct notify *notify);
+dc_stop(struct decoder_control *dc);
bool
-dc_seek(struct notify *notify, double where);
+dc_seek(struct decoder_control *dc, double where);
void
-dc_quit(void);
+dc_quit(struct decoder_control *dc);
#endif
diff --git a/src/decoder_internal.c b/src/decoder_internal.c
index 4a56fa5f3..d40ef89d7 100644
--- a/src/decoder_internal.c
+++ b/src/decoder_internal.c
@@ -28,21 +28,39 @@
#include <assert.h>
/**
+ * This is a wrapper for input_stream_buffer(). It assumes that the
+ * decoder is currently locked, and temporarily unlocks it while
+ * calling input_stream_buffer(). We shouldn't hold the lock during a
+ * potentially blocking operation.
+ */
+static int
+decoder_input_buffer(struct decoder_control *dc, struct input_stream *is)
+{
+ int ret;
+
+ decoder_unlock(dc);
+ ret = input_stream_buffer(is) > 0;
+ decoder_lock(dc);
+
+ return ret;
+}
+
+/**
* All chunks are full of decoded data; wait for the player to free
* one.
*/
static enum decoder_command
-need_chunks(struct input_stream *is, bool do_wait)
+need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait)
{
- if (dc.command == DECODE_COMMAND_STOP ||
- dc.command == DECODE_COMMAND_SEEK)
- return dc.command;
+ if (dc->command == DECODE_COMMAND_STOP ||
+ dc->command == DECODE_COMMAND_SEEK)
+ return dc->command;
- if ((is == NULL || input_stream_buffer(is) <= 0) && do_wait) {
- notify_wait(&dc.notify);
- notify_signal(&pc.notify);
+ if ((is == NULL || decoder_input_buffer(dc, is) <= 0) && do_wait) {
+ decoder_wait(dc);
+ player_signal();
- return dc.command;
+ return dc->command;
}
return DECODE_COMMAND_NONE;
@@ -51,6 +69,7 @@ need_chunks(struct input_stream *is, bool do_wait)
struct music_chunk *
decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
{
+ struct decoder_control *dc = decoder->dc;
enum decoder_command cmd;
assert(decoder != NULL);
@@ -59,11 +78,13 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
return decoder->chunk;
do {
- decoder->chunk = music_buffer_allocate(dc.buffer);
+ decoder->chunk = music_buffer_allocate(dc->buffer);
if (decoder->chunk != NULL)
return decoder->chunk;
- cmd = need_chunks(is, true);
+ decoder_lock(dc);
+ cmd = need_chunks(dc, is, true);
+ decoder_unlock(dc);
} while (cmd == DECODE_COMMAND_NONE);
return NULL;
@@ -72,13 +93,15 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
void
decoder_flush_chunk(struct decoder *decoder)
{
+ struct decoder_control *dc = decoder->dc;
+
assert(decoder != NULL);
assert(decoder->chunk != NULL);
if (music_chunk_is_empty(decoder->chunk))
- music_buffer_return(dc.buffer, decoder->chunk);
+ music_buffer_return(dc->buffer, decoder->chunk);
else
- music_pipe_push(dc.pipe, decoder->chunk);
+ music_pipe_push(dc->pipe, decoder->chunk);
decoder->chunk = NULL;
}
diff --git a/src/decoder_internal.h b/src/decoder_internal.h
index cf54dbf6d..9d422d253 100644
--- a/src/decoder_internal.h
+++ b/src/decoder_internal.h
@@ -26,6 +26,8 @@
struct input_stream;
struct decoder {
+ struct decoder_control *dc;
+
struct pcm_convert_state conv_state;
bool seeking;
diff --git a/src/decoder_list.c b/src/decoder_list.c
index a42585e34..2ee4f3ec9 100644
--- a/src/decoder_list.c
+++ b/src/decoder_list.c
@@ -28,9 +28,11 @@
#include <string.h>
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;
extern const struct decoder_plugin faad_decoder_plugin;
@@ -43,10 +45,13 @@ extern const struct decoder_plugin wildmidi_decoder_plugin;
extern const struct decoder_plugin fluidsynth_decoder_plugin;
extern const struct decoder_plugin ffmpeg_decoder_plugin;
-static const struct decoder_plugin *const decoder_plugins[] = {
+const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_MAD
&mad_decoder_plugin,
#endif
+#ifdef HAVE_MPG123
+ &mpg123_decoder_plugin,
+#endif
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif
@@ -56,6 +61,9 @@ static const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_FLAC
&flac_decoder_plugin,
#endif
+#ifdef ENABLE_SNDFILE
+ &sndfile_decoder_plugin,
+#endif
#ifdef HAVE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
@@ -89,32 +97,48 @@ static const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
+ NULL
};
enum {
- num_decoder_plugins = G_N_ELEMENTS(decoder_plugins),
+ num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1,
};
/** which plugins have been initialized successfully? */
-static bool decoder_plugins_enabled[num_decoder_plugins];
+bool decoder_plugins_enabled[num_decoder_plugins];
-const struct decoder_plugin *
-decoder_plugin_from_suffix(const char *suffix, unsigned int next)
+static unsigned
+decoder_plugin_index(const struct decoder_plugin *plugin)
{
- static unsigned i = num_decoder_plugins;
+ unsigned i = 0;
+
+ while (decoder_plugins[i] != plugin)
+ ++i;
+
+ return i;
+}
+static unsigned
+decoder_plugin_next_index(const struct decoder_plugin *plugin)
+{
+ return plugin == 0
+ ? 0 /* start with first plugin */
+ : decoder_plugin_index(plugin) + 1;
+}
+
+const struct decoder_plugin *
+decoder_plugin_from_suffix(const char *suffix,
+ const struct decoder_plugin *plugin)
+{
if (suffix == NULL)
return NULL;
- if (!next)
- i = 0;
- for (; i < num_decoder_plugins; ++i) {
- const struct decoder_plugin *plugin = decoder_plugins[i];
+ for (unsigned i = decoder_plugin_next_index(plugin);
+ decoder_plugins[i] != NULL; ++i) {
+ plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i] &&
- stringFoundInStringArray(plugin->suffixes, suffix)) {
- ++i;
+ decoder_plugin_supports_suffix(plugin, suffix))
return plugin;
- }
}
return NULL;
@@ -130,10 +154,10 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
if (!next)
i = 0;
- for (; i < num_decoder_plugins; ++i) {
+ for (; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i] &&
- stringFoundInStringArray(plugin->mime_types, mimeType)) {
+ decoder_plugin_supports_mime_type(plugin, mimeType)) {
++i;
return plugin;
}
@@ -145,7 +169,7 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
const struct decoder_plugin *
decoder_plugin_from_name(const char *name)
{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i] &&
strcmp(plugin->name, name) == 0)
@@ -155,27 +179,6 @@ decoder_plugin_from_name(const char *name)
return NULL;
}
-void decoder_plugin_print_all_decoders(FILE * fp)
-{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
- const struct decoder_plugin *plugin = decoder_plugins[i];
- const char *const*suffixes;
-
- if (!decoder_plugins_enabled[i])
- continue;
-
- fprintf(fp, "[%s]", plugin->name);
-
- for (suffixes = plugin->suffixes;
- suffixes != NULL && *suffixes != NULL;
- ++suffixes) {
- fprintf(fp, " %s", *suffixes);
- }
-
- fprintf(fp, "\n");
- }
-}
-
/**
* Find the "decoder" configuration block for the specified plugin.
*
@@ -203,7 +206,7 @@ decoder_plugin_config(const char *plugin_name)
void decoder_plugin_init_all(void)
{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
const struct config_param *param =
decoder_plugin_config(plugin->name);
@@ -219,7 +222,7 @@ void decoder_plugin_init_all(void)
void decoder_plugin_deinit_all(void)
{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i])
diff --git a/src/decoder_list.h b/src/decoder_list.h
index 23788189c..a5fe6b99f 100644
--- a/src/decoder_list.h
+++ b/src/decoder_list.h
@@ -20,14 +20,25 @@
#ifndef MPD_DECODER_LIST_H
#define MPD_DECODER_LIST_H
-#include <stdio.h>
+#include <stdbool.h>
struct decoder_plugin;
+extern const struct decoder_plugin *const decoder_plugins[];
+extern bool decoder_plugins_enabled[];
+
/* interface for using plugins */
+/**
+ * Find the next enabled decoder plugin which supports the specified suffix.
+ *
+ * @param suffix the file name suffix
+ * @param plugin the previous plugin, or NULL to find the first plugin
+ * @return a plugin, or NULL if none matches
+ */
const struct decoder_plugin *
-decoder_plugin_from_suffix(const char *suffix, unsigned int next);
+decoder_plugin_from_suffix(const char *suffix,
+ const struct decoder_plugin *plugin);
const struct decoder_plugin *
decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
@@ -35,8 +46,6 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
const struct decoder_plugin *
decoder_plugin_from_name(const char *name);
-void decoder_plugin_print_all_decoders(FILE * fp);
-
/* this is where we "load" all the "plugins" ;-) */
void decoder_plugin_init_all(void);
diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c
new file mode 100644
index 000000000..79b0d5322
--- /dev/null
+++ b/src/decoder_plugin.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2009 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 "decoder_plugin.h"
+#include "utils.h"
+
+#include <assert.h>
+
+bool
+decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
+ const char *suffix)
+{
+ assert(plugin != NULL);
+ assert(suffix != NULL);
+
+ return plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix);
+
+}
+
+bool
+decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
+ const char *mime_type)
+{
+ assert(plugin != NULL);
+ assert(mime_type != NULL);
+
+ return plugin->mime_types != NULL &&
+ string_array_contains(plugin->mime_types, mime_type);
+}
diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h
index 66501a0a1..a078540a6 100644
--- a/src/decoder_plugin.h
+++ b/src/decoder_plugin.h
@@ -161,4 +161,18 @@ decoder_plugin_container_scan( const struct decoder_plugin *plugin,
return plugin->container_scan(pathname, tnum);
}
+/**
+ * Does the plugin announce the specified file name suffix?
+ */
+bool
+decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
+ const char *suffix);
+
+/**
+ * Does the plugin announce the specified MIME type?
+ */
+bool
+decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
+ const char *mime_type);
+
#endif
diff --git a/src/decoder_print.c b/src/decoder_print.c
new file mode 100644
index 000000000..b2d394093
--- /dev/null
+++ b/src/decoder_print.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2003-2009 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 "decoder_print.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "client.h"
+
+#include <assert.h>
+
+static void
+decoder_plugin_print(struct client *client,
+ const struct decoder_plugin *plugin)
+{
+ const char *const*p;
+
+ assert(plugin != NULL);
+ assert(plugin->name != NULL);
+
+ client_printf(client, "plugin: %s\n", plugin->name);
+
+ if (plugin->suffixes != NULL)
+ for (p = plugin->suffixes; *p != NULL; ++p)
+ client_printf(client, "suffix: %s\n", *p);
+
+ if (plugin->mime_types != NULL)
+ for (p = plugin->mime_types; *p != NULL; ++p)
+ client_printf(client, "mime_type: %s\n", *p);
+}
+
+void
+decoder_list_print(struct client *client)
+{
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i)
+ if (decoder_plugins_enabled[i])
+ decoder_plugin_print(client, decoder_plugins[i]);
+}
diff --git a/src/input/lastfm_input_plugin.h b/src/decoder_print.h
index d0eaf5a55..6ba5dd081 100644
--- a/src/input/lastfm_input_plugin.h
+++ b/src/decoder_print.h
@@ -17,9 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef LASTFM_INPUT_PLUGIN_H
-#define LASTFM_INPUT_PLUGIN_H
+#ifndef MPD_DECODER_PRINT_H
+#define MPD_DECODER_PRINT_H
-extern const struct input_plugin lastfm_input_plugin;
+struct client;
+
+void
+decoder_list_print(struct client *client);
#endif
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
index d6ff058ec..9c12ecb07 100644
--- a/src/decoder_thread.c
+++ b/src/decoder_thread.c
@@ -35,6 +35,53 @@
#include <unistd.h>
+static enum decoder_command
+decoder_lock_get_command(struct decoder_control *dc)
+{
+ enum decoder_command command;
+
+ decoder_lock(dc);
+ command = dc->command;
+ decoder_unlock(dc);
+
+ return command;
+}
+
+/**
+ * Opens the input stream with input_stream_open(), and waits until
+ * the stream gets ready. If a decoder STOP command is received
+ * during that, it cancels the operation (but does not close the
+ * stream).
+ *
+ * Unlock the decoder before calling this function.
+ *
+ * @return true on success of if #DECODE_COMMAND_STOP is received,
+ * false on error
+ */
+static bool
+decoder_input_stream_open(struct decoder_control *dc,
+ struct input_stream *is, const char *uri)
+{
+ if (!input_stream_open(is, uri))
+ return false;
+
+ /* wait for the input stream to become ready; its metadata
+ will be available then */
+
+ while (!is->ready &&
+ decoder_lock_get_command(dc) != DECODE_COMMAND_STOP) {
+ int ret;
+
+ ret = input_stream_buffer(is);
+ if (ret < 0) {
+ input_stream_close(is);
+ return false;
+ }
+ }
+
+ return true;
+}
+
static bool
decoder_stream_decode(const struct decoder_plugin *plugin,
struct decoder *decoder,
@@ -47,17 +94,24 @@ decoder_stream_decode(const struct decoder_plugin *plugin,
assert(decoder->decoder_tag == NULL);
assert(input_stream != NULL);
assert(input_stream->ready);
- assert(dc.state == DECODE_STATE_START);
+ assert(decoder->dc->state == DECODE_STATE_START);
+
+ if (decoder->dc->command == DECODE_COMMAND_STOP)
+ return true;
+
+ decoder_unlock(decoder->dc);
/* rewind the stream, so each plugin gets a fresh start */
input_stream_seek(input_stream, 0, SEEK_SET);
decoder_plugin_stream_decode(plugin, decoder, input_stream);
- assert(dc.state == DECODE_STATE_START ||
- dc.state == DECODE_STATE_DECODE);
+ decoder_lock(decoder->dc);
- return dc.state != DECODE_STATE_START;
+ assert(decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
+
+ return decoder->dc->state != DECODE_STATE_START;
}
static bool
@@ -70,150 +124,200 @@ decoder_file_decode(const struct decoder_plugin *plugin,
assert(decoder->stream_tag == NULL);
assert(decoder->decoder_tag == NULL);
assert(path != NULL);
- assert(path[0] == '/');
- assert(dc.state == DECODE_STATE_START);
+ assert(g_path_is_absolute(path));
+ assert(decoder->dc->state == DECODE_STATE_START);
+
+ if (decoder->dc->command == DECODE_COMMAND_STOP)
+ return true;
+
+ decoder_unlock(decoder->dc);
decoder_plugin_file_decode(plugin, decoder, path);
- assert(dc.state == DECODE_STATE_START ||
- dc.state == DECODE_STATE_DECODE);
+ decoder_lock(decoder->dc);
+
+ assert(decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
- return dc.state != DECODE_STATE_START;
+ return decoder->dc->state != DECODE_STATE_START;
}
-static void decoder_run_song(const struct song *song, const char *uri)
+/**
+ * Try decoding a stream, using plugins matching the stream's MIME type.
+ */
+static bool
+decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is)
{
- struct decoder decoder;
- int ret;
- bool close_instream = true;
- struct input_stream input_stream;
const struct decoder_plugin *plugin;
+ unsigned int next = 0;
- if (!input_stream_open(&input_stream, uri)) {
- dc.state = DECODE_STATE_ERROR;
- return;
- }
+ if (is->mime == NULL)
+ return false;
- decoder.seeking = false;
- decoder.song_tag = song->tag != NULL && song_is_file(song)
- ? tag_dup(song->tag) : NULL;
- decoder.stream_tag = NULL;
- decoder.decoder_tag = NULL;
- decoder.chunk = NULL;
+ while ((plugin = decoder_plugin_from_mime_type(is->mime, next++)))
+ if (plugin->stream_decode != NULL &&
+ decoder_stream_decode(plugin, decoder, is))
+ return true;
- dc.state = DECODE_STATE_START;
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ return false;
+}
- /* wait for the input stream to become ready; its metadata
- will be available then */
+/**
+ * Try decoding a stream, using plugins matching the stream's URI
+ * suffix.
+ */
+static bool
+decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is,
+ const char *uri)
+{
+ const char *suffix = uri_get_suffix(uri);
+ const struct decoder_plugin *plugin = NULL;
- while (!input_stream.ready) {
- if (dc.command == DECODE_COMMAND_STOP) {
- input_stream_close(&input_stream);
- dc.state = DECODE_STATE_STOP;
- return;
- }
+ if (suffix == NULL)
+ return false;
- ret = input_stream_buffer(&input_stream);
- if (ret < 0) {
- input_stream_close(&input_stream);
- dc.state = DECODE_STATE_ERROR;
- return;
- }
- }
+ while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL)
+ if (plugin->stream_decode != NULL &&
+ decoder_stream_decode(plugin, decoder, is))
+ return true;
- if (dc.command == DECODE_COMMAND_STOP) {
- input_stream_close(&input_stream);
- dc.state = DECODE_STATE_STOP;
- return;
- }
+ return false;
+}
- pcm_convert_init(&decoder.conv_state);
+/**
+ * Try decoding a stream, using the fallback plugin.
+ */
+static bool
+decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is)
+{
+ const struct decoder_plugin *plugin;
+
+ plugin = decoder_plugin_from_name("mad");
+ return plugin != NULL && plugin->stream_decode != NULL &&
+ decoder_stream_decode(plugin, decoder, is);
+}
+
+/**
+ * Try decoding a stream.
+ */
+static bool
+decoder_run_stream(struct decoder *decoder, const char *uri)
+{
+ struct decoder_control *dc = decoder->dc;
+ struct input_stream input_stream;
+ bool success;
- ret = false;
- if (!song_is_file(song)) {
- unsigned int next = 0;
+ decoder_unlock(dc);
+
+ if (!decoder_input_stream_open(dc, &input_stream, uri)) {
+ decoder_lock(dc);
+ return false;
+ }
+ decoder_lock(dc);
+
+ success = dc->command == DECODE_COMMAND_STOP ||
/* first we try mime types: */
- while ((plugin = decoder_plugin_from_mime_type(input_stream.mime, next++))) {
- if (plugin->stream_decode == NULL)
+ decoder_run_stream_mime_type(decoder, &input_stream) ||
+ /* if that fails, try suffix matching the URL: */
+ decoder_run_stream_suffix(decoder, &input_stream, uri) ||
+ /* fallback to mp3: this is needed for bastard streams
+ that don't have a suffix or set the mimeType */
+ decoder_run_stream_fallback(decoder, &input_stream);
+
+ decoder_unlock(dc);
+ input_stream_close(&input_stream);
+ decoder_lock(dc);
+
+ return success;
+}
+
+/**
+ * Try decoding a file.
+ */
+static bool
+decoder_run_file(struct decoder *decoder, const char *path_fs)
+{
+ struct decoder_control *dc = decoder->dc;
+ const char *suffix = uri_get_suffix(path_fs);
+ const struct decoder_plugin *plugin = NULL;
+
+ if (suffix == NULL)
+ return false;
+
+ decoder_unlock(dc);
+
+ while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
+ if (plugin->file_decode != NULL) {
+ decoder_lock(dc);
+
+ if (decoder_file_decode(plugin, decoder, path_fs))
+ return true;
+
+ decoder_unlock(dc);
+ } else if (plugin->stream_decode != NULL) {
+ struct input_stream input_stream;
+ bool success;
+
+ if (!decoder_input_stream_open(dc, &input_stream,
+ path_fs))
continue;
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- if (ret)
- break;
- plugin = NULL;
- }
+ decoder_lock(dc);
- /* if that fails, try suffix matching the URL: */
- if (plugin == NULL) {
- const char *s = uri_get_suffix(uri);
- next = 0;
- while ((plugin = decoder_plugin_from_suffix(s, next++))) {
- if (plugin->stream_decode == NULL)
- continue;
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- if (ret)
- break;
-
- assert(dc.state == DECODE_STATE_START);
- plugin = NULL;
- }
- }
- /* fallback to mp3: */
- /* this is needed for bastard streams that don't have a suffix
- or set the mimeType */
- if (plugin == NULL) {
- /* we already know our mp3Plugin supports streams, no
- * need to check for stream{Types,DecodeFunc} */
- if ((plugin = decoder_plugin_from_name("mad"))) {
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- }
- }
- } else {
- unsigned int next = 0;
- const char *s = uri_get_suffix(uri);
- while ((plugin = decoder_plugin_from_suffix(s, next++))) {
- if (plugin->file_decode != NULL) {
- input_stream_close(&input_stream);
- close_instream = false;
- ret = decoder_file_decode(plugin,
- &decoder, uri);
- if (ret)
- break;
- } else if (plugin->stream_decode != NULL) {
- if (!close_instream) {
- /* the input_stream object has
- been closed before
- decoder_file_decode() -
- reopen it */
- if (input_stream_open(&input_stream, uri))
- close_instream = true;
- else
- continue;
- }
-
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- if (ret)
- break;
+ success = decoder_stream_decode(plugin, decoder,
+ &input_stream);
+
+ decoder_unlock(dc);
+
+ input_stream_close(&input_stream);
+
+ if (success) {
+ decoder_lock(dc);
+ return true;
}
}
}
+ decoder_lock(dc);
+ return false;
+}
+
+static void
+decoder_run_song(struct decoder_control *dc,
+ const struct song *song, const char *uri)
+{
+ struct decoder decoder = {
+ .dc = dc,
+ };
+ int ret;
+
+ decoder.seeking = false;
+ decoder.song_tag = song->tag != NULL && song_is_file(song)
+ ? tag_dup(song->tag) : NULL;
+ decoder.stream_tag = NULL;
+ decoder.decoder_tag = NULL;
+ decoder.chunk = NULL;
+
+ dc->state = DECODE_STATE_START;
+ dc->command = DECODE_COMMAND_NONE;
+
+ player_signal();
+
+ pcm_convert_init(&decoder.conv_state);
+
+ ret = song_is_file(song)
+ ? decoder_run_file(&decoder, uri)
+ : decoder_run_stream(&decoder, uri);
+
+ decoder_unlock(dc);
+
pcm_convert_deinit(&decoder.conv_state);
/* flush the last chunk */
if (decoder.chunk != NULL)
decoder_flush_chunk(&decoder);
- if (close_instream)
- input_stream_close(&input_stream);
-
if (decoder.song_tag != NULL)
tag_free(decoder.song_tag);
@@ -223,66 +327,82 @@ static void decoder_run_song(const struct song *song, const char *uri)
if (decoder.decoder_tag != NULL)
tag_free(decoder.decoder_tag);
- dc.state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR;
+ decoder_lock(dc);
+
+ dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR;
}
-static void decoder_run(void)
+static void
+decoder_run(struct decoder_control *dc)
{
- struct song *song = dc.next_song;
+ const struct song *song = dc->song;
char *uri;
+ assert(song != NULL);
+
if (song_is_file(song))
uri = map_song_fs(song);
else
uri = song_get_uri(song);
if (uri == NULL) {
- dc.state = DECODE_STATE_ERROR;
+ dc->state = DECODE_STATE_ERROR;
return;
}
- dc.current_song = dc.next_song; /* NEED LOCK */
- decoder_run_song(song, uri);
+ decoder_run_song(dc, song, uri);
g_free(uri);
}
-static gpointer decoder_task(G_GNUC_UNUSED gpointer arg)
+static gpointer
+decoder_task(gpointer arg)
{
+ struct decoder_control *dc = arg;
+
+ decoder_lock(dc);
+
do {
- assert(dc.state == DECODE_STATE_STOP ||
- dc.state == DECODE_STATE_ERROR);
+ assert(dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR);
- switch (dc.command) {
+ switch (dc->command) {
case DECODE_COMMAND_START:
case DECODE_COMMAND_SEEK:
- decoder_run();
+ decoder_run(dc);
+
+ dc->command = DECODE_COMMAND_NONE;
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ player_signal();
break;
case DECODE_COMMAND_STOP:
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ dc->command = DECODE_COMMAND_NONE;
+
+ player_signal();
break;
case DECODE_COMMAND_NONE:
- notify_wait(&dc.notify);
+ decoder_wait(dc);
break;
}
- } while (dc.command != DECODE_COMMAND_NONE || !dc.quit);
+ } while (dc->command != DECODE_COMMAND_NONE || !dc->quit);
+
+ decoder_unlock(dc);
return NULL;
}
-void decoder_thread_start(void)
+void
+decoder_thread_start(struct decoder_control *dc)
{
GError *e = NULL;
- assert(dc.thread == NULL);
+ assert(dc->thread == NULL);
+
+ dc->quit = false;
- dc.thread = g_thread_create(decoder_task, NULL, true, &e);
- if (dc.thread == NULL)
+ dc->thread = g_thread_create(decoder_task, dc, true, &e);
+ if (dc->thread == NULL)
g_error("Failed to spawn decoder task: %s", e->message);
}
diff --git a/src/decoder_thread.h b/src/decoder_thread.h
index 50ed7116e..a25564b87 100644
--- a/src/decoder_thread.h
+++ b/src/decoder_thread.h
@@ -20,6 +20,9 @@
#ifndef MPD_DECODER_THREAD_H
#define MPD_DECODER_THREAD_H
-void decoder_thread_start(void);
+struct decoder_control;
+
+void
+decoder_thread_start(struct decoder_control *dc);
#endif
diff --git a/src/directory_save.c b/src/directory_save.c
index 132508447..317f64bb4 100644
--- a/src/directory_save.c
+++ b/src/directory_save.c
@@ -20,7 +20,7 @@
#include "directory_save.h"
#include "directory.h"
#include "song.h"
-#include "path.h"
+#include "text_file.h"
#include "song_save.h"
#include <assert.h>
@@ -39,109 +39,137 @@ directory_quark(void)
return g_quark_from_static_string("directory");
}
-/* TODO error checking */
-int
+void
directory_save(FILE *fp, struct directory *directory)
{
struct dirvec *children = &directory->children;
size_t i;
- int retv;
if (!directory_is_root(directory)) {
fprintf(fp, DIRECTORY_MTIME "%lu\n",
(unsigned long)directory->mtime);
- retv = fprintf(fp, "%s%s\n", DIRECTORY_BEGIN,
- directory_get_path(directory));
- if (retv < 0)
- return -1;
+ fprintf(fp, "%s%s\n", DIRECTORY_BEGIN,
+ directory_get_path(directory));
}
for (i = 0; i < children->nr; ++i) {
struct directory *cur = children->base[i];
char *base = g_path_get_basename(cur->path);
- retv = fprintf(fp, DIRECTORY_DIR "%s\n", base);
+ fprintf(fp, DIRECTORY_DIR "%s\n", base);
g_free(base);
- if (retv < 0)
- return -1;
- if (directory_save(fp, cur) < 0)
- return -1;
+
+ directory_save(fp, cur);
+
+ if (ferror(fp))
+ return;
}
songvec_save(fp, &directory->songs);
- if (!directory_is_root(directory) &&
- fprintf(fp, DIRECTORY_END "%s\n",
- directory_get_path(directory)) < 0)
- return -1;
- return 0;
+ if (!directory_is_root(directory))
+ fprintf(fp, DIRECTORY_END "%s\n",
+ directory_get_path(directory));
}
-bool
-directory_load(FILE *fp, struct directory *directory, GError **error)
+static struct directory *
+directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
+ GString *buffer, GError **error_r)
{
- char buffer[MPD_PATH_MAX * 2];
- char key[MPD_PATH_MAX * 2];
- char *name;
+ struct directory *directory;
+ const char *line;
bool success;
- while (fgets(buffer, sizeof(buffer), fp)
- && !g_str_has_prefix(buffer, DIRECTORY_END)) {
- if (g_str_has_prefix(buffer, DIRECTORY_DIR)) {
- struct directory *subdir;
+ if (directory_get_child(parent, name) != NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Duplicate subdirectory '%s'", name);
+ return NULL;
+ }
- g_strchomp(buffer);
- strcpy(key, &(buffer[strlen(DIRECTORY_DIR)]));
- if (!fgets(buffer, sizeof(buffer), fp)) {
- g_set_error(error, directory_quark(), 0,
- "Unexpected end of file");
- return false;
- }
+ if (directory_is_root(parent)) {
+ directory = directory_new(name, parent);
+ } else {
+ char *path = g_strconcat(directory_get_path(parent), "/",
+ name, NULL);
+ directory = directory_new(path, parent);
+ g_free(path);
+ }
- if (g_str_has_prefix(buffer, DIRECTORY_MTIME)) {
- directory->mtime =
- g_ascii_strtoull(buffer + sizeof(DIRECTORY_MTIME) - 1,
- NULL, 10);
+ line = read_text_line(fp, buffer);
+ if (line == NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Unexpected end of file");
+ directory_free(directory);
+ return NULL;
+ }
- if (!fgets(buffer, sizeof(buffer), fp)) {
- g_set_error(error, directory_quark(), 0,
- "Unexpected end of file");
- return false;
- }
- }
+ if (g_str_has_prefix(line, DIRECTORY_MTIME)) {
+ directory->mtime =
+ g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1,
+ NULL, 10);
+
+ line = read_text_line(fp, buffer);
+ if (line == NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Unexpected end of file");
+ directory_free(directory);
+ return NULL;
+ }
+ }
- if (!g_str_has_prefix(buffer, DIRECTORY_BEGIN)) {
- g_set_error(error, directory_quark(), 0,
- "Malformed line: %s", buffer);
- return false;
- }
+ if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Malformed line: %s", line);
+ directory_free(directory);
+ return NULL;
+ }
- g_strchomp(buffer);
- name = &(buffer[strlen(DIRECTORY_BEGIN)]);
- if (!g_str_has_prefix(name, directory->path) != 0) {
- g_set_error(error, directory_quark(), 0,
- "Wrong path in database: '%s' in '%s'",
- name, directory->path);
+ success = directory_load(fp, directory, buffer, error_r);
+ if (!success) {
+ directory_free(directory);
+ return NULL;
+ }
+
+ return directory;
+}
+
+bool
+directory_load(FILE *fp, struct directory *directory,
+ GString *buffer, GError **error)
+{
+ const char *line;
+
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ !g_str_has_prefix(line, DIRECTORY_END)) {
+ if (g_str_has_prefix(line, DIRECTORY_DIR)) {
+ struct directory *subdir =
+ directory_load_subdir(fp, directory,
+ line + sizeof(DIRECTORY_DIR) - 1,
+ buffer, error);
+ if (subdir == NULL)
return false;
- }
- subdir = directory_get_child(directory, name);
- if (subdir != NULL) {
- assert(subdir->parent == directory);
- } else {
- subdir = directory_new(name, directory);
- dirvec_add(&directory->children, subdir);
+ dirvec_add(&directory->children, subdir);
+ } else if (g_str_has_prefix(line, SONG_BEGIN)) {
+ const char *name = line + sizeof(SONG_BEGIN) - 1;
+ struct song *song;
+
+ if (songvec_find(&directory->songs, name) != NULL) {
+ g_set_error(error, directory_quark(), 0,
+ "Duplicate song '%s'", name);
+ return NULL;
}
- success = directory_load(fp, subdir, error);
- if (!success)
+ song = song_load(fp, directory, name,
+ buffer, error);
+ if (song == NULL)
return false;
- } else if (g_str_has_prefix(buffer, SONG_BEGIN)) {
- readSongInfoIntoList(fp, &directory->songs, directory);
+
+ songvec_add(&directory->songs, song);
} else {
g_set_error(error, directory_quark(), 0,
- "Malformed line: %s", buffer);
+ "Malformed line: %s", line);
return false;
}
}
diff --git a/src/directory_save.h b/src/directory_save.h
index 28ec094ad..fa2775624 100644
--- a/src/directory_save.h
+++ b/src/directory_save.h
@@ -27,10 +27,11 @@
struct directory;
-int
+void
directory_save(FILE *fp, struct directory *directory);
bool
-directory_load(FILE *fp, struct directory *directory, GError **error);
+directory_load(FILE *fp, struct directory *directory,
+ GString *buffer, GError **error);
#endif
diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c
new file mode 100644
index 000000000..92052b095
--- /dev/null
+++ b/src/encoder/null_encoder.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2003-2009 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 "encoder_api.h"
+#include "encoder_plugin.h"
+
+#include <assert.h>
+#include <string.h>
+
+#define MAX_BUFFER 32768
+
+struct null_encoder {
+ struct encoder encoder;
+
+ unsigned char buffer[MAX_BUFFER];
+ size_t buffer_length;
+};
+
+extern const struct encoder_plugin null_encoder_plugin;
+
+static inline GQuark
+null_encoder_quark(void)
+{
+ return g_quark_from_static_string("null_encoder");
+}
+
+static struct encoder *
+null_encoder_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ struct null_encoder *encoder;
+
+ encoder = g_new(struct null_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &null_encoder_plugin);
+
+ return &encoder->encoder;
+}
+
+static void
+null_encoder_finish(struct encoder *_encoder)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ g_free(encoder);
+}
+
+static bool
+null_encoder_open(struct encoder *_encoder,
+ G_GNUC_UNUSED struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ encoder->buffer_length = 0;
+
+ return true;
+}
+
+static bool
+null_encoder_write(struct encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ assert(length + encoder->buffer_length < MAX_BUFFER);
+
+ memcpy(encoder->buffer+encoder->buffer_length,
+ data, length);
+
+ encoder->buffer_length += length;
+ return true;
+}
+
+static size_t
+null_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, encoder->buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(encoder->buffer, encoder->buffer + length,
+ encoder->buffer_length);
+
+ return length;
+}
+
+const struct encoder_plugin null_encoder_plugin = {
+ .name = "null",
+ .init = null_encoder_init,
+ .finish = null_encoder_finish,
+ .open = null_encoder_open,
+ .write = null_encoder_write,
+ .read = null_encoder_read,
+};
diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c
new file mode 100644
index 000000000..5a8a82d81
--- /dev/null
+++ b/src/encoder/twolame_encoder.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2003-2009 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 "encoder_api.h"
+#include "encoder_plugin.h"
+#include "audio_format.h"
+
+#include <twolame.h>
+#include <assert.h>
+#include <string.h>
+
+struct twolame_encoder {
+ struct encoder encoder;
+
+ struct audio_format audio_format;
+ float quality;
+ int bitrate;
+
+ twolame_options *options;
+
+ unsigned char buffer[32768];
+ size_t buffer_length;
+
+ /**
+ * Call libtwolame's flush function when the buffer is empty?
+ */
+ bool flush;
+};
+
+extern const struct encoder_plugin twolame_encoder_plugin;
+
+static inline GQuark
+twolame_encoder_quark(void)
+{
+ return g_quark_from_static_string("twolame_encoder");
+}
+
+static bool
+twolame_encoder_configure(struct twolame_encoder *encoder,
+ const struct config_param *param, GError **error)
+{
+ const char *value;
+ char *endptr;
+
+ value = config_get_block_string(param, "quality", NULL);
+ if (value != NULL) {
+ /* a quality was configured (VBR) */
+
+ encoder->quality = g_ascii_strtod(value, &endptr);
+
+ if (*endptr != '\0' || encoder->quality < -1.0 ||
+ encoder->quality > 10.0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "quality \"%s\" is not a number in the "
+ "range -1 to 10, line %i",
+ value, param->line);
+ return false;
+ }
+
+ if (config_get_block_string(param, "bitrate", NULL) != NULL) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "quality and bitrate are "
+ "both defined (line %i)",
+ param->line);
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ value = config_get_block_string(param, "bitrate", NULL);
+ if (value == NULL) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "neither bitrate nor quality defined "
+ "at line %i",
+ param->line);
+ return false;
+ }
+
+ encoder->quality = -2.0;
+ encoder->bitrate = g_ascii_strtoll(value, &endptr, 10);
+
+ if (*endptr != '\0' || encoder->bitrate <= 0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "bitrate at line %i should be a positive integer",
+ param->line);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static struct encoder *
+twolame_encoder_init(const struct config_param *param, GError **error)
+{
+ struct twolame_encoder *encoder;
+
+ g_debug("libtwolame version %s", get_twolame_version());
+
+ encoder = g_new(struct twolame_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &twolame_encoder_plugin);
+
+ /* load configuration from "param" */
+ if (!twolame_encoder_configure(encoder, param, error)) {
+ /* configuration has failed, roll back and return error */
+ g_free(encoder);
+ return NULL;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+twolame_encoder_finish(struct encoder *_encoder)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ /* the real libtwolame cleanup was already performed by
+ twolame_encoder_close(), so no real work here */
+ g_free(encoder);
+}
+
+static bool
+twolame_encoder_setup(struct twolame_encoder *encoder, GError **error)
+{
+ if (encoder->quality >= -1.0) {
+ /* a quality was configured (VBR) */
+
+ if (0 != twolame_set_VBR(encoder->options, true)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame VBR mode");
+ return false;
+ }
+ if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame VBR quality");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame bitrate");
+ return false;
+ }
+ }
+
+ if (0 != twolame_set_num_channels(encoder->options,
+ encoder->audio_format.channels)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame num channels");
+ return false;
+ }
+
+ if (0 != twolame_set_in_samplerate(encoder->options,
+ encoder->audio_format.sample_rate)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame sample rate");
+ return false;
+ }
+
+ if (0 > twolame_init_params(encoder->options)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error initializing twolame params");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+twolame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
+ GError **error)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ audio_format->bits = 16;
+ audio_format->channels = 2;
+
+ encoder->audio_format = *audio_format;
+
+ encoder->options = twolame_init();
+ if (encoder->options == NULL) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "twolame_init() failed");
+ return false;
+ }
+
+ if (!twolame_encoder_setup(encoder, error)) {
+ twolame_close(&encoder->options);
+ return false;
+ }
+
+ encoder->buffer_length = 0;
+ encoder->flush = false;
+
+ return true;
+}
+
+static void
+twolame_encoder_close(struct encoder *_encoder)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ twolame_close(&encoder->options);
+}
+
+static bool
+twolame_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ encoder->flush = true;
+ return true;
+}
+
+static bool
+twolame_encoder_write(struct encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+ unsigned num_frames;
+ const int16_t *src = (const int16_t*)data;
+ int bytes_out;
+
+ assert(encoder->buffer_length == 0);
+
+ num_frames =
+ length / audio_format_frame_size(&encoder->audio_format);
+
+ bytes_out = twolame_encode_buffer_interleaved(encoder->options,
+ src, num_frames,
+ encoder->buffer,
+ sizeof(encoder->buffer));
+ if (bytes_out < 0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "twolame encoder failed");
+ return false;
+ }
+
+ encoder->buffer_length = (size_t)bytes_out;
+ return true;
+}
+
+static size_t
+twolame_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ if (encoder->buffer_length == 0 && encoder->flush) {
+ int ret = twolame_encode_flush(encoder->options,
+ encoder->buffer,
+ sizeof(encoder->buffer));
+ if (ret > 0)
+ encoder->buffer_length = (size_t)ret;
+
+ encoder->flush = false;
+ }
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, encoder->buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(encoder->buffer, encoder->buffer + length,
+ encoder->buffer_length);
+
+ return length;
+}
+
+const struct encoder_plugin twolame_encoder_plugin = {
+ .name = "twolame",
+ .init = twolame_encoder_init,
+ .finish = twolame_encoder_finish,
+ .open = twolame_encoder_open,
+ .close = twolame_encoder_close,
+ .flush = twolame_encoder_flush,
+ .write = twolame_encoder_write,
+ .read = twolame_encoder_read,
+};
diff --git a/src/encoder_list.c b/src/encoder_list.c
index d563b6bc8..ad10e4e28 100644
--- a/src/encoder_list.c
+++ b/src/encoder_list.c
@@ -23,16 +23,22 @@
#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;
static const struct encoder_plugin *encoder_plugins[] = {
+ &null_encoder_plugin,
#ifdef ENABLE_VORBIS_ENCODER
&vorbis_encoder_plugin,
#endif
#ifdef ENABLE_LAME_ENCODER
&lame_encoder_plugin,
#endif
+#ifdef ENABLE_TWOLAME_ENCODER
+ &twolame_encoder_plugin,
+#endif
NULL
};
diff --git a/src/event_pipe.c b/src/event_pipe.c
index 3e5009150..4b7e741ff 100644
--- a/src/event_pipe.c
+++ b/src/event_pipe.c
@@ -18,7 +18,7 @@
*/
#include "event_pipe.h"
-#include "utils.h"
+#include "fd_util.h"
#include <stdbool.h>
#include <assert.h>
@@ -84,17 +84,9 @@ void event_pipe_init(void)
GIOChannel *channel;
int ret;
-#ifdef WIN32
- ret = _pipe(event_pipe, 512, _O_BINARY);
-#else
- ret = pipe(event_pipe);
-#endif
+ ret = pipe_cloexec_nonblock(event_pipe);
if (ret < 0)
g_error("Couldn't open pipe: %s", strerror(errno));
-#ifndef WIN32
- if (set_nonblocking(event_pipe[1]) < 0)
- g_error("Couldn't set non-blocking I/O: %s", strerror(errno));
-#endif
channel = g_io_channel_unix_new(event_pipe[0]);
event_pipe_source_id = g_io_add_watch(channel, G_IO_IN,
diff --git a/src/event_pipe.h b/src/event_pipe.h
index ecb7ec9e8..4614ef25c 100644
--- a/src/event_pipe.h
+++ b/src/event_pipe.h
@@ -32,7 +32,7 @@ enum pipe_event {
/** an idle event was emitted */
PIPE_EVENT_IDLE,
- /** must call syncPlayerAndPlaylist() */
+ /** must call playlist_sync() */
PIPE_EVENT_PLAYLIST,
/** the current song's tag has changed */
@@ -41,6 +41,9 @@ enum pipe_event {
/** SIGHUP received: reload configuration, roll log file */
PIPE_EVENT_RELOAD,
+ /** a hardware mixer plugin has detected a change */
+ PIPE_EVENT_MIXER,
+
PIPE_EVENT_MAX
};
diff --git a/src/exclude.c b/src/exclude.c
new file mode 100644
index 000000000..59354fa82
--- /dev/null
+++ b/src/exclude.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2003-2009 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.
+ */
+
+/*
+ * The .mpdignore backend code.
+ *
+ */
+
+#include "exclude.h"
+#include "path.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+GSList *
+exclude_list_load(const char *path_fs)
+{
+ FILE *file;
+ char line[1024];
+ GSList *list = NULL;
+
+ assert(path_fs != NULL);
+
+ file = fopen(path_fs, "r");
+ if (file == NULL) {
+ if (errno != ENOENT) {
+ char *path_utf8 = fs_charset_to_utf8(path_fs);
+ g_debug("Failed to open %s: %s",
+ path_utf8, g_strerror(errno));
+ g_free(path_utf8);
+ }
+
+ return NULL;
+ }
+
+ while (fgets(line, sizeof(line), file) != NULL) {
+ char *p = strchr(line, '#');
+ if (p != NULL)
+ *p = 0;
+
+ p = g_strstrip(line);
+ if (*p != 0)
+ list = g_slist_prepend(list, g_pattern_spec_new(p));
+ }
+
+ fclose(file);
+
+ return list;
+}
+
+void
+exclude_list_free(GSList *list)
+{
+ while (list != NULL) {
+ GPatternSpec *pattern = list->data;
+ g_pattern_spec_free(pattern);
+ list = g_slist_remove(list, list->data);
+ }
+}
+
+bool
+exclude_list_check(GSList *list, const char *name_fs)
+{
+ assert(name_fs != NULL);
+
+ /* XXX include full path name in check */
+
+ for (; list != NULL; list = list->next) {
+ GPatternSpec *pattern = list->data;
+
+ if (g_pattern_match_string(pattern, name_fs))
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/exclude.h b/src/exclude.h
new file mode 100644
index 000000000..637feb846
--- /dev/null
+++ b/src/exclude.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2003-2009 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.
+ */
+
+/*
+ * The .mpdignore backend code.
+ *
+ */
+
+#ifndef MPD_EXCLUDE_H
+#define MPD_EXCLUDE_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+
+/**
+ * Loads and parses a .mpdignore file.
+ */
+GSList *
+exclude_list_load(const char *path_fs);
+
+/**
+ * Frees a list returned by exclude_list_load().
+ */
+void
+exclude_list_free(GSList *list);
+
+/**
+ * Checks whether one of the patterns in the .mpdignore file matches
+ * the specified file name.
+ */
+bool
+exclude_list_check(GSList *list, const char *name_fs);
+
+#endif
diff --git a/src/fd_util.c b/src/fd_util.c
new file mode 100644
index 000000000..f78b8ed8b
--- /dev/null
+++ b/src/fd_util.c
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fd_util.h"
+#include "config.h"
+
+#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4))
+#define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_INOTIFY_INIT
+#include <sys/inotify.h>
+#endif
+
+#ifndef WIN32
+
+static int
+fd_mask_flags(int fd, int and_mask, int xor_mask)
+{
+ int ret;
+
+ assert(fd >= 0);
+
+ ret = fcntl(fd, F_GETFD, 0);
+ if (ret < 0)
+ return ret;
+
+ return fcntl(fd, F_SETFD, (ret & and_mask) ^ xor_mask);
+}
+
+#endif /* !WIN32 */
+
+static int
+fd_set_cloexec(int fd, bool enable)
+{
+#ifndef WIN32
+ return fd_mask_flags(fd, ~FD_CLOEXEC, enable ? FD_CLOEXEC : 0);
+#else
+ (void)fd;
+ (void)enable;
+#endif
+}
+
+/**
+ * Enables non-blocking mode for the specified file descriptor. On
+ * WIN32, this function only works for sockets.
+ */
+static int
+fd_set_nonblock(int fd)
+{
+#ifdef WIN32
+ u_long val = 1;
+ return ioctlsocket(fd, FIONBIO, &val);
+#else
+ int flags;
+
+ assert(fd >= 0);
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return flags;
+
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+#endif
+}
+
+int
+open_cloexec(const char *path_fs, int flags, int mode)
+{
+ int fd;
+
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+
+#ifdef O_NOCTTY
+ flags |= O_NOCTTY;
+#endif
+
+ fd = open(path_fs, flags, mode);
+ fd_set_cloexec(fd, true);
+
+ return fd;
+}
+
+int
+pipe_cloexec(int fd[2])
+{
+#ifdef WIN32
+ return _pipe(event_pipe, 512, _O_BINARY);
+#else
+ int ret;
+
+#ifdef HAVE_PIPE2
+ ret = pipe2(fd, O_CLOEXEC);
+ if (ret >= 0 || errno != ENOSYS)
+ return ret;
+#endif
+
+ ret = pipe(fd);
+ if (ret >= 0) {
+ fd_set_cloexec(fd[0], true);
+ fd_set_cloexec(fd[1], true);
+ }
+
+ return ret;
+#endif
+}
+
+int
+pipe_cloexec_nonblock(int fd[2])
+{
+#ifdef WIN32
+ return _pipe(event_pipe, 512, _O_BINARY);
+#else
+ int ret;
+
+#ifdef HAVE_PIPE2
+ ret = pipe2(fd, O_CLOEXEC|O_NONBLOCK);
+ if (ret >= 0 || errno != ENOSYS)
+ return ret;
+#endif
+
+ ret = pipe(fd);
+ if (ret >= 0) {
+ fd_set_cloexec(fd[0], true);
+ fd_set_cloexec(fd[1], true);
+
+ fd_set_nonblock(fd[0]);
+ fd_set_nonblock(fd[1]);
+ }
+
+ return ret;
+#endif
+}
+
+int
+socket_cloexec_nonblock(int domain, int type, int protocol)
+{
+ int fd;
+
+#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
+ fd = socket(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol);
+ if (fd >= 0 || errno != EINVAL)
+ return fd;
+#endif
+
+ fd = socket(domain, type, protocol);
+ if (fd >= 0) {
+ fd_set_cloexec(fd, true);
+ fd_set_nonblock(fd);
+ }
+
+ return fd;
+}
+
+int
+accept_cloexec_nonblock(int fd, struct sockaddr *address,
+ size_t *address_length_r)
+{
+ int ret;
+ socklen_t address_length = *address_length_r;
+
+#ifdef HAVE_ACCEPT4
+ ret = accept4(fd, address, &address_length,
+ SOCK_CLOEXEC|SOCK_NONBLOCK);
+ if (ret >= 0 || errno != ENOSYS) {
+ if (ret >= 0)
+ *address_length_r = address_length;
+
+ return ret;
+ }
+#endif
+
+ ret = accept(fd, address, &address_length);
+ if (ret >= 0) {
+ fd_set_cloexec(ret, true);
+ fd_set_nonblock(ret);
+ *address_length_r = address_length;
+ }
+
+ return ret;
+}
+
+#ifdef HAVE_INOTIFY_INIT
+
+int
+inotify_init_cloexec(void)
+{
+ int fd;
+
+#ifdef HAVE_INOTIFY_INIT1
+ fd = inotify_init1(IN_CLOEXEC);
+ if (fd >= 0 || errno != ENOSYS)
+ return fd;
+#endif
+
+ fd = inotify_init();
+ if (fd >= 0)
+ fd_set_cloexec(fd, true);
+
+ return fd;
+}
+
+#endif
diff --git a/src/fd_util.h b/src/fd_util.h
new file mode 100644
index 000000000..57ad63288
--- /dev/null
+++ b/src/fd_util.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This library provides easy helper functions for working with file
+ * descriptors. It has wrappers for taking advantage of Linux 2.6
+ * specific features like O_CLOEXEC.
+ *
+ */
+
+#ifndef FD_UTIL_H
+#define FD_UTIL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct sockaddr;
+
+/**
+ * Wrapper for open(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ */
+int
+open_cloexec(const char *path_fs, int flags, int mode);
+
+/**
+ * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ */
+int
+pipe_cloexec(int fd[2]);
+
+/**
+ * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ *
+ * On systems that supports it (everybody except for Windows), it also
+ * sets the NONBLOCK flag.
+ */
+int
+pipe_cloexec_nonblock(int fd[2]);
+
+/**
+ * Wrapper for socket(), which sets the CLOEXEC and the NONBLOCK flag
+ * (atomically if supported by the OS).
+ */
+int
+socket_cloexec_nonblock(int domain, int type, int protocol);
+
+/**
+ * Wrapper for accept(), which sets the CLOEXEC and the NONBLOCK flags
+ * (atomically if supported by the OS).
+ */
+int
+accept_cloexec_nonblock(int fd, struct sockaddr *address,
+ size_t *address_length_r);
+
+/**
+ * Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically
+ * if supported by the OS).
+ */
+int
+inotify_init_cloexec(void);
+
+#endif
diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c
new file mode 100644
index 000000000..ec8bef5c0
--- /dev/null
+++ b/src/filter/chain_filter_plugin.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2003-2009 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 "filter/chain_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+
+#include <assert.h>
+
+struct filter_chain {
+ /** the base class */
+ struct filter base;
+
+ GSList *children;
+};
+
+static struct filter *
+chain_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct filter_chain *chain = g_new(struct filter_chain, 1);
+
+ filter_init(&chain->base, &chain_filter_plugin);
+ chain->children = NULL;
+
+ return &chain->base;
+}
+
+static void
+chain_free_child(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct filter *filter = data;
+
+ filter_free(filter);
+}
+
+static void
+chain_filter_finish(struct filter *_filter)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ g_slist_foreach(chain->children, chain_free_child, NULL);
+ g_slist_free(chain->children);
+
+ g_free(chain);
+}
+
+/**
+ * Close all filters in the chain until #until is reached. #until
+ * itself is not closed.
+ */
+static void
+chain_close_until(struct filter_chain *chain, const struct filter *until)
+{
+ GSList *i = chain->children;
+ struct filter *filter;
+
+ while (true) {
+ /* this assertion fails if #until does not exist
+ (anymore) */
+ assert(i != NULL);
+
+ if (i->data == until)
+ /* don't close this filter */
+ break;
+
+ /* close this filter */
+ filter = i->data;
+ filter_close(filter);
+
+ i = g_slist_next(i);
+ }
+}
+
+static const struct audio_format *
+chain_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) {
+ struct filter *filter = i->data;
+
+ audio_format = filter_open(filter, audio_format, error_r);
+ if (audio_format == NULL) {
+ /* rollback, close all children */
+ chain_close_until(chain, filter);
+ return NULL;
+ }
+ }
+
+ /* return the output format of the last filter */
+ return audio_format;
+}
+
+static void
+chain_close_child(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct filter *filter = data;
+
+ filter_close(filter);
+}
+
+static void
+chain_filter_close(struct filter *_filter)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ g_slist_foreach(chain->children, chain_close_child, NULL);
+}
+
+static const void *
+chain_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) {
+ struct filter *filter = i->data;
+
+ /* feed the output of the previous filter as input
+ into the current one */
+ src = filter_filter(filter, src, src_size, &src_size, error_r);
+ if (src == NULL)
+ chain_close_until(chain, filter);
+ }
+
+ /* return the output of the last filter */
+ *dest_size_r = src_size;
+ return src;
+}
+
+const struct filter_plugin chain_filter_plugin = {
+ .name = "chain",
+ .init = chain_filter_init,
+ .finish = chain_filter_finish,
+ .open = chain_filter_open,
+ .close = chain_filter_close,
+ .filter = chain_filter_filter,
+};
+
+struct filter *
+filter_chain_new(void)
+{
+ struct filter *filter = filter_new(&chain_filter_plugin, NULL, NULL);
+ /* chain_filter_init() never fails */
+ assert(filter != NULL);
+
+ return filter;
+}
+
+void
+filter_chain_append(struct filter *_chain, struct filter *filter)
+{
+ struct filter_chain *chain = (struct filter_chain *)_chain;
+
+ chain->children = g_slist_append(chain->children, filter);
+}
diff --git a/src/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h
new file mode 100644
index 000000000..f8462b22d
--- /dev/null
+++ b/src/filter/chain_filter_plugin.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2009 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
+ *
+ * A filter chain is a container for several filters. They are
+ * chained together, i.e. called in a row, one filter passing its
+ * output to the next one.
+ */
+
+#ifndef MPD_FILTER_CHAIN_H
+#define MPD_FILTER_CHAIN_H
+
+struct filter;
+
+/**
+ * Creates a new filter chain.
+ */
+struct filter *
+filter_chain_new(void);
+
+/**
+ * Appends a new filter at the end of the filter chain. You must call
+ * this function before the first filter_open() call.
+ *
+ * @param chain the filter chain created with filter_chain_new()
+ * @param filter the filter to be appended to #chain
+ */
+void
+filter_chain_append(struct filter *chain, struct filter *filter);
+
+#endif
diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c
new file mode 100644
index 000000000..d197dbdb9
--- /dev/null
+++ b/src/filter/convert_filter_plugin.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2003-2009 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 "filter/convert_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+#include "pcm_convert.h"
+#include "audio_format.h"
+#include "poison.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct convert_filter {
+ struct filter base;
+
+ /**
+ * The current convert, from 0 to #PCM_CONVERT_1.
+ */
+ unsigned convert;
+
+ /**
+ * The input audio format; PCM data is passed to the filter()
+ * method in this format.
+ */
+ struct audio_format in_audio_format;
+
+ /**
+ * The output audio format; the consumer of this plugin
+ * expects PCM data in this format. This defaults to
+ * #in_audio_format, and can be set with convert_filter_set().
+ */
+ struct audio_format out_audio_format;
+
+ struct pcm_convert_state state;
+};
+
+static struct filter *
+convert_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct convert_filter *filter = g_new(struct convert_filter, 1);
+
+ filter_init(&filter->base, &convert_filter_plugin);
+ return &filter->base;
+}
+
+static void
+convert_filter_finish(struct filter *filter)
+{
+ g_free(filter);
+}
+
+static const struct audio_format *
+convert_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+
+ assert(audio_format_valid(audio_format));
+
+ filter->in_audio_format = filter->out_audio_format = *audio_format;
+ pcm_convert_init(&filter->state);
+
+ return audio_format;
+}
+
+static void
+convert_filter_close(struct filter *_filter)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+
+ pcm_convert_deinit(&filter->state);
+
+ poison_undefined(&filter->in_audio_format,
+ sizeof(filter->in_audio_format));
+ poison_undefined(&filter->out_audio_format,
+ sizeof(filter->out_audio_format));
+}
+
+static const void *
+convert_filter_filter(struct filter *_filter, const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+ const void *dest;
+
+ if (audio_format_equals(&filter->in_audio_format,
+ &filter->out_audio_format)) {
+ /* optimized special case: no-op */
+ *dest_size_r = src_size;
+ return src;
+ }
+
+ dest = pcm_convert(&filter->state, &filter->in_audio_format,
+ src, src_size,
+ &filter->out_audio_format, dest_size_r,
+ error_r);
+ if (dest == NULL)
+ return NULL;
+
+ return dest;
+}
+
+const struct filter_plugin convert_filter_plugin = {
+ .name = "convert",
+ .init = convert_filter_init,
+ .finish = convert_filter_finish,
+ .open = convert_filter_open,
+ .close = convert_filter_close,
+ .filter = convert_filter_filter,
+};
+
+void
+convert_filter_set(struct filter *_filter,
+ const struct audio_format *out_audio_format)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+
+ assert(filter != NULL);
+ assert(audio_format_valid(&filter->in_audio_format));
+ assert(audio_format_valid(&filter->out_audio_format));
+ assert(out_audio_format != NULL);
+ assert(audio_format_valid(out_audio_format));
+ assert(filter->in_audio_format.reverse_endian == 0);
+
+ filter->out_audio_format = *out_audio_format;
+}
diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h
new file mode 100644
index 000000000..8d370b0cb
--- /dev/null
+++ b/src/filter/convert_filter_plugin.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2009 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 CONVERT_FILTER_PLUGIN_H
+#define CONVERT_FILTER_PLUGIN_H
+
+struct filter;
+struct audio_format;
+
+/**
+ * Sets the output audio format for the specified filter. You must
+ * call this after the filter has been opened. Since this audio
+ * format switch is a violation of the filter API, this filter must be
+ * the last in a chain.
+ */
+void
+convert_filter_set(struct filter *filter,
+ const struct audio_format *out_audio_format);
+
+#endif
diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c
new file mode 100644
index 000000000..689388558
--- /dev/null
+++ b/src/filter/null_filter_plugin.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2009 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 filter plugin does nothing. That is not quite useful, except
+ * for testing the filter core, or as a template for new filter
+ * plugins.
+ */
+
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+
+#include <assert.h>
+
+struct null_filter {
+ struct filter filter;
+};
+
+static struct filter *
+null_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct null_filter *filter = g_new(struct null_filter, 1);
+
+ filter_init(&filter->filter, &null_filter_plugin);
+ return &filter->filter;
+}
+
+static void
+null_filter_finish(struct filter *_filter)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+
+ g_free(filter);
+}
+
+static const struct audio_format *
+null_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+ (void)filter;
+
+ return audio_format;
+}
+
+static void
+null_filter_close(struct filter *_filter)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+ (void)filter;
+}
+
+static const void *
+null_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size,
+ size_t *dest_size_r, G_GNUC_UNUSED GError **error_r)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+ (void)filter;
+
+ /* return the unmodified source buffer */
+ *dest_size_r = src_size;
+ return src;
+}
+
+const struct filter_plugin null_filter_plugin = {
+ .name = "null",
+ .init = null_filter_init,
+ .finish = null_filter_finish,
+ .open = null_filter_open,
+ .close = null_filter_close,
+ .filter = null_filter_filter,
+};
diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c
new file mode 100644
index 000000000..298ca2f36
--- /dev/null
+++ b/src/filter/volume_filter_plugin.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2003-2009 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 "filter/volume_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+#include "pcm_buffer.h"
+#include "pcm_volume.h"
+#include "audio_format.h"
+#include "player_control.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct volume_filter {
+ struct filter filter;
+
+ /**
+ * The current volume, from 0 to #PCM_VOLUME_1.
+ */
+ unsigned volume;
+
+ struct audio_format audio_format;
+
+ struct pcm_buffer buffer;
+};
+
+static inline GQuark
+volume_quark(void)
+{
+ return g_quark_from_static_string("pcm_volume");
+}
+
+static struct filter *
+volume_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct volume_filter *filter = g_new(struct volume_filter, 1);
+
+ filter_init(&filter->filter, &volume_filter_plugin);
+ filter->volume = PCM_VOLUME_1;
+
+ return &filter->filter;
+}
+
+static void
+volume_filter_finish(struct filter *filter)
+{
+ g_free(filter);
+}
+
+static const struct audio_format *
+volume_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+
+ if (audio_format->bits != 8 && audio_format->bits != 16 &&
+ audio_format->bits != 24) {
+ g_set_error(error_r, volume_quark(), 0,
+ "Unsupported audio format");
+ return false;
+ }
+
+ if (audio_format->reverse_endian) {
+ g_set_error(error_r, volume_quark(), 0,
+ "Software volume for reverse endian "
+ "samples is not implemented");
+ return false;
+ }
+
+ filter->audio_format = *audio_format;
+ pcm_buffer_init(&filter->buffer);
+
+ return &filter->audio_format;
+}
+
+static void
+volume_filter_close(struct filter *_filter)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+
+ pcm_buffer_deinit(&filter->buffer);
+}
+
+static const void *
+volume_filter_filter(struct filter *_filter, const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+ bool success;
+ void *dest;
+
+ if (filter->volume >= PCM_VOLUME_1) {
+ /* optimized special case: 100% volume = no-op */
+ *dest_size_r = src_size;
+ return src;
+ }
+
+ dest = pcm_buffer_get(&filter->buffer, src_size);
+ *dest_size_r = src_size;
+
+ if (filter->volume <= 0) {
+ /* optimized special case: 0% volume = memset(0) */
+ /* XXX is this valid for all sample formats? What
+ about floating point? */
+ memset(dest, 0, src_size);
+ return dest;
+ }
+
+ memcpy(dest, src, src_size);
+
+ success = pcm_volume(dest, src_size, &filter->audio_format,
+ filter->volume);
+ if (!success) {
+ g_set_error(error_r, volume_quark(), 0,
+ "pcm_volume() has failed");
+ return NULL;
+ }
+
+ return dest;
+}
+
+const struct filter_plugin volume_filter_plugin = {
+ .name = "volume",
+ .init = volume_filter_init,
+ .finish = volume_filter_finish,
+ .open = volume_filter_open,
+ .close = volume_filter_close,
+ .filter = volume_filter_filter,
+};
+
+unsigned
+volume_filter_get(const struct filter *_filter)
+{
+ const struct volume_filter *filter =
+ (const struct volume_filter *)_filter;
+
+ assert(filter->filter.plugin == &volume_filter_plugin);
+ assert(filter->volume <= PCM_VOLUME_1);
+
+ return filter->volume;
+}
+
+void
+volume_filter_set(struct filter *_filter, unsigned volume)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+
+ assert(filter->filter.plugin == &volume_filter_plugin);
+ assert(volume <= PCM_VOLUME_1);
+
+ filter->volume = volume;
+}
+
diff --git a/src/filter/volume_filter_plugin.h b/src/filter/volume_filter_plugin.h
new file mode 100644
index 000000000..c064741a2
--- /dev/null
+++ b/src/filter/volume_filter_plugin.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2009 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 VOLUME_FILTER_PLUGIN_H
+#define VOLUME_FILTER_PLUGIN_H
+
+struct filter;
+
+unsigned
+volume_filter_get(const struct filter *filter);
+
+void
+volume_filter_set(struct filter *filter, unsigned volume);
+
+#endif
diff --git a/src/buffer2array.h b/src/filter_internal.h
index bed23a29f..b086e31b1 100644
--- a/src/buffer2array.h
+++ b/src/filter_internal.h
@@ -17,15 +17,22 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_BUFFER_2_ARRAY_H
-#define MPD_BUFFER_2_ARRAY_H
-
-/* tokenizes up to max elements in buffer (a null-terminated string) and
- * stores the result in array (which must be capable of holding up to
- * max elements). Tokenization is based on C string quoting rules.
- * The arguments buffer and array are modified.
- * Returns the number of elements tokenized.
+/** \file
+ *
+ * Internal stuff for the filter core and filter plugins.
*/
-int buffer2array(char *buffer, char *array[], const int max);
+
+#ifndef MPD_FILTER_INTERNAL_H
+#define MPD_FILTER_INTERNAL_H
+
+struct filter {
+ const struct filter_plugin *plugin;
+};
+
+static inline void
+filter_init(struct filter *filter, const struct filter_plugin *plugin)
+{
+ filter->plugin = plugin;
+}
#endif
diff --git a/src/filter_plugin.c b/src/filter_plugin.c
new file mode 100644
index 000000000..e5c1d5cd8
--- /dev/null
+++ b/src/filter_plugin.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2003-2009 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 "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+
+#ifndef NDEBUG
+#include "audio_format.h"
+#endif
+
+#include <assert.h>
+
+struct filter *
+filter_new(const struct filter_plugin *plugin,
+ const struct config_param *param, GError **error_r)
+{
+ assert(plugin != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ return plugin->init(param, error_r);
+}
+
+struct filter *
+filter_configured_new(const struct config_param *param, GError **error_r)
+{
+ const char *plugin_name;
+ const struct filter_plugin *plugin;
+
+ assert(param != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ plugin_name = config_get_block_string(param, "plugin", NULL);
+ if (plugin_name == NULL)
+ g_set_error(error_r, config_quark(), 0,
+ "No filter plugin specified");
+
+ plugin = filter_plugin_by_name(plugin_name);
+ if (plugin == NULL)
+ g_set_error(error_r, config_quark(), 0,
+ "No such filter plugin: %s", plugin_name);
+
+ return filter_new(plugin, param, error_r);
+}
+
+void
+filter_free(struct filter *filter)
+{
+ assert(filter != NULL);
+
+ filter->plugin->finish(filter);
+}
+
+const struct audio_format *
+filter_open(struct filter *filter, const struct audio_format *audio_format,
+ GError **error_r)
+{
+ assert(filter != NULL);
+ assert(audio_format != NULL);
+ assert(audio_format_valid(audio_format));
+ assert(error_r == NULL || *error_r == NULL);
+
+ audio_format = filter->plugin->open(filter, audio_format, error_r);
+ assert(audio_format == NULL || audio_format_valid(audio_format));
+
+ return audio_format;
+}
+
+void
+filter_close(struct filter *filter)
+{
+ assert(filter != NULL);
+
+ filter->plugin->close(filter);
+}
+
+const void *
+filter_filter(struct filter *filter, const void *src, size_t src_size,
+ size_t *dest_size_r,
+ GError **error_r)
+{
+ assert(filter != NULL);
+ assert(src != NULL);
+ assert(src_size > 0);
+ assert(dest_size_r != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ return filter->plugin->filter(filter, src, src_size, dest_size_r, error_r);
+}
diff --git a/src/filter_plugin.h b/src/filter_plugin.h
new file mode 100644
index 000000000..0043246d1
--- /dev/null
+++ b/src/filter_plugin.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2003-2009 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 filter_plugin class. It describes a
+ * plugin API for objects which filter raw PCM data.
+ */
+
+#ifndef MPD_FILTER_PLUGIN_H
+#define MPD_FILTER_PLUGIN_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct config_param;
+struct filter;
+
+struct filter_plugin {
+ const char *name;
+
+ /**
+ * Allocates and configures a filter.
+ */
+ struct filter *(*init)(const struct config_param *param,
+ GError **error_r);
+
+ /**
+ * Free instance data.
+ */
+ void (*finish)(struct filter *filter);
+
+ /**
+ * Opens a filter.
+ */
+ const struct audio_format *
+ (*open)(struct filter *filter,
+ const struct audio_format *audio_format,
+ GError **error_r);
+
+ /**
+ * Closes a filter.
+ */
+ void (*close)(struct filter *filter);
+
+ /**
+ * Filters a block of PCM data.
+ */
+ const void *(*filter)(struct filter *filter,
+ const void *src, size_t src_size,
+ size_t *dest_buffer_r,
+ GError **error_r);
+};
+
+/**
+ * Creates a new instance of the specified filter plugin.
+ *
+ * @param plugin the filter plugin
+ * @param param optional configuration section
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return a new filter object, or NULL on error
+ */
+struct filter *
+filter_new(const struct filter_plugin *plugin,
+ const struct config_param *param, GError **error_r);
+
+/**
+ * Creates a new filter, loads configuration and the plugin name from
+ * the specified configuration section.
+ *
+ * @param param the configuration section
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return a new filter object, or NULL on error
+ */
+struct filter *
+filter_configured_new(const struct config_param *param, GError **error_r);
+
+/**
+ * Deletes a filter. It must be closed prior to calling this
+ * function, see filter_close().
+ *
+ * @param filter the filter object
+ */
+void
+filter_free(struct filter *filter);
+
+/**
+ * Opens the filter, preparing it for filter_filter().
+ *
+ * @param filter the filter object
+ * @param audio_format the audio format of incoming and outgoing data
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return true on success, false on error
+ */
+const struct audio_format *
+filter_open(struct filter *filter, const struct audio_format *audio_format,
+ GError **error_r);
+
+/**
+ * Closes the filter. After that, you may call filter_open() again.
+ *
+ * @param filter the filter object
+ */
+void
+filter_close(struct filter *filter);
+
+/**
+ * Filters a block of PCM data.
+ *
+ * @param filter the filter object
+ * @param src the input buffer
+ * @param src_size the size of #src_buffer in bytes
+ * @param dest_size_r the size of the returned buffer
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return the destination buffer on success (will be invalidated by
+ * filter_close() or filter_filter()), NULL on error
+ */
+const void *
+filter_filter(struct filter *filter, const void *src, size_t src_size,
+ size_t *dest_size_r,
+ GError **error_r);
+
+#endif
diff --git a/src/filter_registry.c b/src/filter_registry.c
new file mode 100644
index 000000000..c8887aabf
--- /dev/null
+++ b/src/filter_registry.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2009 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 "filter_registry.h"
+#include "filter_plugin.h"
+
+#include <stddef.h>
+#include <string.h>
+
+const struct filter_plugin *const filter_plugins[] = {
+ &null_filter_plugin,
+ &chain_filter_plugin,
+ &volume_filter_plugin,
+ NULL,
+};
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name)
+{
+ for (unsigned i = 0; filter_plugins[i] != NULL; ++i)
+ if (strcmp(filter_plugins[i]->name, name) == 0)
+ return filter_plugins[i];
+
+ return NULL;
+}
diff --git a/src/filter_registry.h b/src/filter_registry.h
new file mode 100644
index 000000000..7eb7f7038
--- /dev/null
+++ b/src/filter_registry.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2009 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 library manages all filter plugins which are enabled at
+ * compile time.
+ */
+
+#ifndef MPD_FILTER_REGISTRY_H
+#define MPD_FILTER_REGISTRY_H
+
+extern const struct filter_plugin null_filter_plugin;
+extern const struct filter_plugin chain_filter_plugin;
+extern const struct filter_plugin convert_filter_plugin;
+extern const struct filter_plugin volume_filter_plugin;
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name);
+
+#endif
diff --git a/src/glib_compat.h b/src/glib_compat.h
new file mode 100644
index 000000000..679307fb3
--- /dev/null
+++ b/src/glib_compat.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2009 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.
+ */
+
+/*
+ * Compatibility with older GLib versions. Some of this isn't
+ * implemented properly, just "good enough" to allow users with older
+ * operating systems to run MPD.
+ */
+
+#ifndef MPD_GLIB_COMPAT_H
+#define MPD_GLIB_COMPAT_H
+
+#include <glib.h>
+
+#if !GLIB_CHECK_VERSION(2,16,0)
+
+static char *
+g_uri_escape_string(const char *unescaped,
+ G_GNUC_UNUSED const char *reserved_chars_allowed,
+ G_GNUC_UNUSED gboolean allow_utf8)
+{
+ return g_strdup(unescaped);
+}
+
+#endif /* !2.16 */
+
+#endif
diff --git a/src/icy_metadata.c b/src/icy_metadata.c
index 69aa89092..4089e9c6f 100644
--- a/src/icy_metadata.c
+++ b/src/icy_metadata.c
@@ -95,7 +95,7 @@ icy_parse_tag_item(struct tag *tag, const char *item)
if (p[0] != NULL && p[1] != NULL) {
if (strcmp(p[0], "StreamTitle") == 0)
- icy_add_item(tag, TAG_ITEM_TITLE, p[1]);
+ icy_add_item(tag, TAG_TITLE, p[1]);
else
g_debug("unknown icy-tag: '%s'", p[0]);
}
diff --git a/src/idle.c b/src/idle.c
index 11b57376d..c0bb7a908 100644
--- a/src/idle.c
+++ b/src/idle.c
@@ -40,6 +40,7 @@ static const char *const idle_names[] = {
"output",
"options",
"sticker",
+ "update",
NULL
};
diff --git a/src/idle.h b/src/idle.h
index a69acabb0..c8ed57f74 100644
--- a/src/idle.h
+++ b/src/idle.h
@@ -50,6 +50,9 @@ enum {
/** a sticker has been modified. */
IDLE_STICKER = 0x80,
+
+ /** a database update has started or finished. */
+ IDLE_UPDATE = 0x100,
};
/**
diff --git a/src/inotify_queue.c b/src/inotify_queue.c
new file mode 100644
index 000000000..4941f4905
--- /dev/null
+++ b/src/inotify_queue.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2003-2009 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 "inotify_queue.h"
+#include "update.h"
+
+#include <glib.h>
+
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+enum {
+ /**
+ * Wait this long after the last change before calling
+ * update_enqueue(). This increases the probability that
+ * updates can be bundled.
+ */
+ INOTIFY_UPDATE_DELAY_S = 5,
+};
+
+static GSList *inotify_queue;
+static guint queue_source_id;
+
+void
+mpd_inotify_queue_init(void)
+{
+}
+
+static void
+free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ g_free(data);
+}
+
+void
+mpd_inotify_queue_finish(void)
+{
+ if (queue_source_id != 0)
+ g_source_remove(queue_source_id);
+
+ g_slist_foreach(inotify_queue, free_callback, NULL);
+ g_slist_free(inotify_queue);
+}
+
+static gboolean
+mpd_inotify_run_update(G_GNUC_UNUSED gpointer data)
+{
+ unsigned id;
+
+ while (inotify_queue != NULL) {
+ char *uri_utf8 = inotify_queue->data;
+
+ id = update_enqueue(uri_utf8, false);
+ if (id == 0)
+ /* retry later */
+ return true;
+
+ g_debug("updating '%s' job=%u", uri_utf8, id);
+
+ g_free(uri_utf8);
+ inotify_queue = g_slist_delete_link(inotify_queue,
+ inotify_queue);
+ }
+
+ /* done, remove the timer event by returning false */
+ queue_source_id = 0;
+ return false;
+}
+
+static bool
+path_in(const char *path, const char *possible_parent)
+{
+ size_t length = strlen(possible_parent);
+
+ return path[0] == 0 ||
+ (memcmp(possible_parent, path, length) == 0 &&
+ (path[length] == 0 || path[length] == '/'));
+}
+
+void
+mpd_inotify_enqueue(char *uri_utf8)
+{
+ GSList *old_queue = inotify_queue;
+
+ if (queue_source_id != 0)
+ g_source_remove(queue_source_id);
+ queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S,
+ mpd_inotify_run_update, NULL);
+
+ inotify_queue = NULL;
+ while (old_queue != NULL) {
+ char *current_uri = old_queue->data;
+
+ if (path_in(uri_utf8, current_uri)) {
+ /* already enqueued */
+ g_free(uri_utf8);
+ inotify_queue = g_slist_concat(inotify_queue,
+ old_queue);
+ return;
+ }
+
+ old_queue = g_slist_delete_link(old_queue, old_queue);
+
+ if (path_in(current_uri, uri_utf8))
+ /* existing path is a sub-path of the new
+ path; we can dequeue the existing path and
+ update the new path instead */
+ g_free(current_uri);
+ else
+ /* move the existing path to the new queue */
+ inotify_queue = g_slist_prepend(inotify_queue,
+ current_uri);
+ }
+
+ inotify_queue = g_slist_prepend(inotify_queue, uri_utf8);
+}
diff --git a/src/inotify_queue.h b/src/inotify_queue.h
new file mode 100644
index 000000000..6e12e9bda
--- /dev/null
+++ b/src/inotify_queue.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2003-2009 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_INOTIFY_QUEUE_H
+#define MPD_INOTIFY_QUEUE_H
+
+void
+mpd_inotify_queue_init(void);
+
+void
+mpd_inotify_queue_finish(void);
+
+void
+mpd_inotify_enqueue(char *uri_utf8);
+
+#endif
diff --git a/src/inotify_source.c b/src/inotify_source.c
new file mode 100644
index 000000000..fd39f8481
--- /dev/null
+++ b/src/inotify_source.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2003-2009 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 "inotify_source.h"
+#include "fifo_buffer.h"
+#include "fd_util.h"
+
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+struct mpd_inotify_source {
+ int fd;
+
+ GIOChannel *channel;
+
+ /**
+ * The channel's source id in the GLib main loop.
+ */
+ guint id;
+
+ struct fifo_buffer *buffer;
+
+ mpd_inotify_callback_t callback;
+ void *callback_ctx;
+};
+
+/**
+ * A GQuark for GError instances.
+ */
+static inline GQuark
+mpd_inotify_quark(void)
+{
+ return g_quark_from_static_string("inotify");
+}
+
+static gboolean
+mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data)
+{
+ struct mpd_inotify_source *source = data;
+ void *dest;
+ size_t length;
+ ssize_t nbytes;
+ const struct inotify_event *event;
+
+ dest = fifo_buffer_write(source->buffer, &length);
+ if (dest == NULL)
+ g_error("buffer full");
+
+ nbytes = read(source->fd, dest, length);
+ if (nbytes < 0)
+ g_error("failed to read from inotify: %s", g_strerror(errno));
+ if (nbytes == 0)
+ g_error("end of file from inotify");
+
+ fifo_buffer_append(source->buffer, nbytes);
+
+ while (true) {
+ const char *name;
+
+ event = fifo_buffer_read(source->buffer, &length);
+ if (event == NULL || length < sizeof(*event) ||
+ length < sizeof(*event) + event->len)
+ break;
+
+ if (event->len > 0 && event->name[event->len - 1] == 0)
+ name = event->name;
+ else
+ name = NULL;
+
+ source->callback(event->wd, event->mask, name,
+ source->callback_ctx);
+ fifo_buffer_consume(source->buffer,
+ sizeof(*event) + event->len);
+ }
+
+ return true;
+}
+
+struct mpd_inotify_source *
+mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx,
+ GError **error_r)
+{
+ struct mpd_inotify_source *source =
+ g_new(struct mpd_inotify_source, 1);
+
+ source->fd = inotify_init_cloexec();
+ if (source->fd < 0) {
+ g_set_error(error_r, mpd_inotify_quark(), errno,
+ "inotify_init() has failed: %s",
+ g_strerror(errno));
+ g_free(source);
+ return NULL;
+ }
+
+ source->buffer = fifo_buffer_new(4096);
+
+ source->channel = g_io_channel_unix_new(source->fd);
+ source->id = g_io_add_watch(source->channel, G_IO_IN,
+ mpd_inotify_in_event, source);
+
+ source->callback = callback;
+ source->callback_ctx = callback_ctx;
+
+ return source;
+}
+
+void
+mpd_inotify_source_free(struct mpd_inotify_source *source)
+{
+ g_source_remove(source->id);
+ g_io_channel_unref(source->channel);
+ fifo_buffer_free(source->buffer);
+ close(source->fd);
+ g_free(source);
+}
+
+int
+mpd_inotify_source_add(struct mpd_inotify_source *source,
+ const char *path_fs, unsigned mask,
+ GError **error_r)
+{
+ int wd = inotify_add_watch(source->fd, path_fs, mask);
+ if (wd < 0)
+ g_set_error(error_r, mpd_inotify_quark(), errno,
+ "inotify_add_watch() has failed: %s",
+ g_strerror(errno));
+
+ return wd;
+}
+
+void
+mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd)
+{
+ int ret = inotify_rm_watch(source->fd, wd);
+ if (ret < 0 && errno != EINVAL)
+ g_warning("inotify_rm_watch() has failed: %s",
+ g_strerror(errno));
+
+ /* EINVAL may happen here when the file has been deleted; the
+ kernel seems to auto-unregister deleted files */
+}
diff --git a/src/inotify_source.h b/src/inotify_source.h
new file mode 100644
index 000000000..eb609ccfc
--- /dev/null
+++ b/src/inotify_source.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2003-2009 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_INOTIFY_SOURCE_H
+#define MPD_INOTIFY_SOURCE_H
+
+#include <glib.h>
+
+typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
+ const char *name, void *ctx);
+
+struct mpd_inotify_source;
+
+/**
+ * Creates a new inotify source and registers it in the GLib main
+ * loop.
+ *
+ * @param a callback invoked for events received from the kernel
+ */
+struct mpd_inotify_source *
+mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx,
+ GError **error_r);
+
+void
+mpd_inotify_source_free(struct mpd_inotify_source *source);
+
+/**
+ * Adds a path to the notify list.
+ *
+ * @return a watch descriptor or -1 on error
+ */
+int
+mpd_inotify_source_add(struct mpd_inotify_source *source,
+ const char *path_fs, unsigned mask,
+ GError **error_r);
+
+/**
+ * Removes a path from the notify list.
+ *
+ * @param wd the watch descriptor returned by mpd_inotify_source_add()
+ */
+void
+mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd);
+
+#endif
diff --git a/src/inotify_update.c b/src/inotify_update.c
new file mode 100644
index 000000000..996afb0aa
--- /dev/null
+++ b/src/inotify_update.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2003-2009 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 "inotify_update.h"
+#include "inotify_source.h"
+#include "inotify_queue.h"
+#include "database.h"
+#include "mapper.h"
+#include "path.h"
+
+#include <assert.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+enum {
+ IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
+ |IN_MOVE|IN_MOVE_SELF
+#ifdef IN_ONLYDIR
+ |IN_ONLYDIR
+#endif
+};
+
+struct watch_directory {
+ struct watch_directory *parent;
+
+ char *name;
+
+ int descriptor;
+
+ GList *children;
+};
+
+static struct mpd_inotify_source *inotify_source;
+
+static struct watch_directory inotify_root;
+static GTree *inotify_directories;
+
+static gint
+compare(gconstpointer a, gconstpointer b)
+{
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+tree_add_watch_directory(struct watch_directory *directory)
+{
+ g_tree_insert(inotify_directories,
+ GINT_TO_POINTER(directory->descriptor), directory);
+}
+
+static void
+tree_remove_watch_directory(struct watch_directory *directory)
+{
+ bool found = g_tree_remove(inotify_directories,
+ GINT_TO_POINTER(directory->descriptor));
+ assert(found);
+}
+
+static struct watch_directory *
+tree_find_watch_directory(int wd)
+{
+ return g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd));
+}
+
+static void
+remove_watch_directory(struct watch_directory *directory)
+{
+ assert(directory != NULL);
+ assert(directory->parent != NULL);
+ assert(directory->parent->children != NULL);
+
+ tree_remove_watch_directory(directory);
+
+ while (directory->children != NULL)
+ remove_watch_directory(directory->children->data);
+
+ directory->parent->children =
+ g_list_remove(directory->parent->children, directory);
+
+ mpd_inotify_source_rm(inotify_source, directory->descriptor);
+ g_free(directory->name);
+ g_slice_free(struct watch_directory, directory);
+}
+
+static char *
+watch_directory_get_uri_fs(const struct watch_directory *directory)
+{
+ char *parent_uri, *uri;
+
+ if (directory->parent == NULL)
+ return NULL;
+
+ parent_uri = watch_directory_get_uri_fs(directory->parent);
+ if (parent_uri == NULL)
+ return g_strdup(directory->name);
+
+ uri = g_strconcat(parent_uri, "/", directory->name, NULL);
+ g_free(parent_uri);
+
+ return uri;
+}
+
+/* we don't look at "." / ".." nor files with newlines in their name */
+static bool skip_path(const char *path)
+{
+ return (path[0] == '.' && path[1] == 0) ||
+ (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
+ strchr(path, '\n') != NULL;
+}
+
+static void
+recursive_watch_subdirectories(struct watch_directory *directory,
+ const char *path_fs)
+{
+ GError *error = NULL;
+ DIR *dir;
+ struct dirent *ent;
+
+ assert(directory != NULL);
+ assert(path_fs != NULL);
+
+ dir = opendir(path_fs);
+ if (dir == NULL) {
+ g_warning("Failed to open directory %s: %s",
+ path_fs, g_strerror(errno));
+ return;
+ }
+
+ while ((ent = readdir(dir))) {
+ char *child_path_fs;
+ struct stat st;
+ int ret;
+ struct watch_directory *child;
+
+ if (skip_path(ent->d_name))
+ continue;
+
+ child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL);
+ /* XXX what about symlinks? */
+ ret = lstat(child_path_fs, &st);
+ if (ret < 0) {
+ g_warning("Failed to stat %s: %s",
+ child_path_fs, g_strerror(errno));
+ g_free(child_path_fs);
+ continue;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ g_free(child_path_fs);
+ continue;
+ }
+
+ ret = mpd_inotify_source_add(inotify_source, child_path_fs,
+ IN_MASK, &error);
+ if (ret < 0) {
+ g_warning("Failed to register %s: %s",
+ child_path_fs, error->message);
+ g_error_free(error);
+ error = NULL;
+ g_free(child_path_fs);
+ continue;
+ }
+
+ child = tree_find_watch_directory(ret);
+ if (child != NULL) {
+ /* already being watched */
+ g_free(child_path_fs);
+ continue;
+ }
+
+ child = g_slice_new(struct watch_directory);
+ child->parent = directory;
+ child->name = g_strdup(ent->d_name);
+ child->descriptor = ret;
+ child->children = NULL;
+
+ directory->children = g_list_prepend(directory->children,
+ child);
+
+ tree_add_watch_directory(child);
+
+ recursive_watch_subdirectories(child, child_path_fs);
+ g_free(child_path_fs);
+ }
+
+ closedir(dir);
+}
+
+static void
+mpd_inotify_callback(int wd, unsigned mask,
+ G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx)
+{
+ struct watch_directory *directory;
+ char *uri_fs;
+
+ /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/
+
+ directory = tree_find_watch_directory(wd);
+ if (directory == NULL)
+ return;
+
+ uri_fs = watch_directory_get_uri_fs(directory);
+
+ if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) {
+ g_free(uri_fs);
+ remove_watch_directory(directory);
+ return;
+ }
+
+ if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 &&
+ (mask & IN_ISDIR) != 0) {
+ /* a sub directory was changed: register those in
+ inotify */
+ char *root = map_directory_fs(db_get_root());
+ char *path_fs;
+
+ if (uri_fs != NULL) {
+ path_fs = g_strconcat(root, "/", uri_fs, NULL);
+ g_free(root);
+ } else
+ path_fs = root;
+
+ recursive_watch_subdirectories(directory, path_fs);
+ g_free(path_fs);
+ }
+
+ if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0) {
+ /* a file was changed, or a direectory was
+ moved/deleted: queue a database update */
+ char *uri_utf8 = uri_fs != NULL
+ ? fs_charset_to_utf8(uri_fs)
+ : g_strdup("");
+
+ if (uri_utf8 != NULL)
+ /* this function will take care of freeing
+ uri_utf8 */
+ mpd_inotify_enqueue(uri_utf8);
+ }
+
+ g_free(uri_fs);
+}
+
+void
+mpd_inotify_init(void)
+{
+ struct directory *root;
+ char *path;
+ GError *error = NULL;
+
+ g_debug("initializing inotify");
+
+ root = db_get_root();
+ if (root == NULL) {
+ g_debug("no music directory configured");
+ return;
+ }
+
+ path = map_directory_fs(root);
+ if (path == NULL) {
+ g_warning("mapper has failed");
+ return;
+ }
+
+ inotify_source = mpd_inotify_source_new(mpd_inotify_callback, NULL,
+ &error);
+ if (inotify_source == NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ g_free(path);
+ return;
+ }
+
+ inotify_root.name = path;
+ inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path,
+ IN_MASK, &error);
+ if (inotify_root.descriptor < 0) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ mpd_inotify_source_free(inotify_source);
+ inotify_source = NULL;
+ g_free(path);
+ return;
+ }
+
+ inotify_directories = g_tree_new(compare);
+ tree_add_watch_directory(&inotify_root);
+
+ recursive_watch_subdirectories(&inotify_root, path);
+
+ mpd_inotify_queue_init();
+
+ g_debug("watching music directory");
+}
+
+static gboolean
+free_watch_directory(G_GNUC_UNUSED gpointer key, gpointer value,
+ G_GNUC_UNUSED gpointer data)
+{
+ struct watch_directory *directory = value;
+
+ g_free(directory->name);
+ g_list_free(directory->children);
+
+ if (directory != &inotify_root)
+ g_slice_free(struct watch_directory, directory);
+
+ return false;
+}
+
+void
+mpd_inotify_finish(void)
+{
+ if (inotify_source == NULL)
+ return;
+
+ mpd_inotify_queue_finish();
+ mpd_inotify_source_free(inotify_source);
+
+ g_tree_foreach(inotify_directories, free_watch_directory, NULL);
+ g_tree_destroy(inotify_directories);
+}
diff --git a/src/inotify_update.h b/src/inotify_update.h
new file mode 100644
index 000000000..45466afae
--- /dev/null
+++ b/src/inotify_update.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2009 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_INOTIFY_UPDATE_H
+#define MPD_INOTIFY_UPDATE_H
+
+#include "config.h"
+
+#ifdef HAVE_INOTIFY_INIT
+
+void
+mpd_inotify_init(void);
+
+void
+mpd_inotify_finish(void);
+
+#else /* !HAVE_INOTIFY_INIT */
+
+static inline void
+mpd_inotify_init(void)
+{
+}
+
+static inline void
+mpd_inotify_finish(void)
+{
+}
+
+#endif /* !HAVE_INOTIFY_INIT */
+
+#endif
diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c
index 95d269ce5..c3928a09b 100644
--- a/src/input/curl_input_plugin.c
+++ b/src/input/curl_input_plugin.c
@@ -42,7 +42,7 @@
#define G_LOG_DOMAIN "input_curl"
/** rewinding is possible after up to 64 kB */
-static const off_t max_rewind_size = 64 * 1024;
+static const goffset max_rewind_size = 64 * 1024;
/**
* Buffers created by input_curl_writefunction().
@@ -150,11 +150,6 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
g_free(data);
}
-/* g_queue_clear() was introduced in GLib 2.14 */
-#if !GLIB_CHECK_VERSION(2,14,0)
-#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
-#endif
-
/**
* Frees the current "libcurl easy" handle, and everything associated
* with it.
@@ -407,8 +402,8 @@ copy_icy_tag(struct input_curl *c)
if (c->tag != NULL)
tag_free(c->tag);
- if (c->meta_name != NULL && !tag_has_type(tag, TAG_ITEM_NAME))
- tag_add_item(tag, TAG_ITEM_NAME, c->meta_name);
+ if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME))
+ tag_add_item(tag, TAG_NAME, c->meta_name);
c->tag = tag;
}
@@ -425,7 +420,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
#ifndef NDEBUG
if (c->rewind != NULL &&
(!g_queue_is_empty(c->rewind) || is->offset == 0)) {
- off_t offset = 0;
+ goffset offset = 0;
struct buffer *buffer;
for (GList *list = g_queue_peek_head_link(c->rewind);
@@ -474,11 +469,11 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
if (icy_defined(&c->icy_metadata))
copy_icy_tag(c);
- is->offset += (off_t)nbytes;
+ is->offset += (goffset)nbytes;
#ifndef NDEBUG
if (rewind_buffers != NULL) {
- off_t offset = 0;
+ goffset offset = 0;
struct buffer *buffer;
for (GList *list = g_queue_peek_head_link(c->rewind);
@@ -614,7 +609,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
tag_free(c->tag);
c->tag = tag_new();
- tag_add_item(c->tag, TAG_ITEM_NAME, c->meta_name);
+ tag_add_item(c->tag, TAG_NAME, c->meta_name);
} else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) {
char buffer[64];
size_t icy_metaint;
@@ -772,7 +767,7 @@ input_curl_can_rewind(struct input_stream *is)
/* rewind is possible if this is the very first buffer of the
resource */
buffer = (struct buffer*)g_queue_peek_head(c->buffers);
- return (off_t)buffer->consumed == is->offset;
+ return (goffset)buffer->consumed == is->offset;
}
static void
@@ -780,7 +775,7 @@ input_curl_rewind(struct input_stream *is)
{
struct input_curl *c = is->data;
#ifndef NDEBUG
- off_t offset = 0;
+ goffset offset = 0;
#endif
assert(c->rewind != NULL);
@@ -818,7 +813,7 @@ input_curl_rewind(struct input_stream *is)
}
static bool
-input_curl_seek(struct input_stream *is, off_t offset, int whence)
+input_curl_seek(struct input_stream *is, goffset offset, int whence)
{
struct input_curl *c = is->data;
bool ret;
@@ -884,7 +879,7 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
buffer = (struct buffer *)g_queue_pop_head(c->buffers);
length = buffer->size - buffer->consumed;
- if (offset - is->offset < (off_t)length)
+ if (offset - is->offset < (goffset)length)
length = offset - is->offset;
buffer = consume_buffer(buffer, length, rewind_buffers);
diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c
index 64a4030ab..8561d04e7 100644
--- a/src/input/file_input_plugin.c
+++ b/src/input/file_input_plugin.c
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
#include "input/file_input_plugin.h"
#include "input_plugin.h"
+#include "fd_util.h"
#include <sys/stat.h>
#include <fcntl.h>
@@ -38,7 +40,7 @@ input_file_open(struct input_stream *is, const char *filename)
char* pathname = g_strdup(filename);
- if (filename[0] != '/')
+ if (!g_path_is_absolute(filename))
{
g_free(pathname);
return false;
@@ -49,7 +51,7 @@ input_file_open(struct input_stream *is, const char *filename)
*slash = '\0';
}
- fd = open(pathname, O_RDONLY);
+ fd = open_cloexec(pathname, O_RDONLY, 0);
if (fd < 0) {
is->error = errno;
g_debug("Failed to open \"%s\": %s",
@@ -92,11 +94,11 @@ input_file_open(struct input_stream *is, const char *filename)
}
static bool
-input_file_seek(struct input_stream *is, off_t offset, int whence)
+input_file_seek(struct input_stream *is, goffset offset, int whence)
{
int fd = GPOINTER_TO_INT(is->data);
- offset = lseek(fd, offset, whence);
+ offset = (goffset)lseek(fd, (off_t)offset, whence);
if (offset < 0) {
is->error = errno;
return false;
diff --git a/src/input/lastfm_input_plugin.c b/src/input/lastfm_input_plugin.c
deleted file mode 100644
index 8e13a60a9..000000000
--- a/src/input/lastfm_input_plugin.c
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2003-2009 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 "input/lastfm_input_plugin.h"
-#include "input/curl_input_plugin.h"
-#include "input_plugin.h"
-#include "conf.h"
-
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_lastfm"
-
-static const char *lastfm_user, *lastfm_password;
-
-static bool
-lastfm_input_init(const struct config_param *param)
-{
- lastfm_user = config_get_block_string(param, "user", NULL);
- lastfm_password = config_get_block_string(param, "password", NULL);
-
- return lastfm_user != NULL && lastfm_password != NULL;
-}
-
-static char *
-lastfm_get(const char *url)
-{
- struct input_stream input_stream;
- bool success;
- int ret;
- char buffer[4096];
- size_t length = 0, nbytes;
-
- success = input_stream_open(&input_stream, url);
- if (!success)
- return NULL;
-
- while (!input_stream.ready) {
- ret = input_stream_buffer(&input_stream);
- if (ret < 0) {
- input_stream_close(&input_stream);
- return NULL;
- }
- }
-
- do {
- nbytes = input_stream_read(&input_stream, buffer + length,
- sizeof(buffer) - length);
- if (nbytes == 0) {
- if (input_stream_eof(&input_stream))
- break;
-
- /* I/O error */
- input_stream_close(&input_stream);
- return NULL;
- }
-
- length += nbytes;
- } while (length < sizeof(buffer));
-
- input_stream_close(&input_stream);
- return g_strndup(buffer, length);
-}
-
-static char *
-lastfm_find(const char *response, const char *name)
-{
- size_t name_length = strlen(name);
-
- while (true) {
- const char *eol = strchr(response, '\n');
- if (eol == NULL)
- return NULL;
-
- if (strncmp(response, name, name_length) == 0 &&
- response[name_length] == '=') {
- response += name_length + 1;
- return g_strndup(response, eol - response);
- }
-
- response = eol + 1;
- }
-}
-
-static bool
-lastfm_input_open(struct input_stream *is, const char *url)
-{
- char *md5, *p, *q, *response, *session, *stream_url;
- bool success;
-
- if (strncmp(url, "lastfm://", 9) != 0)
- return false;
-
- /* handshake */
-
-#if GLIB_CHECK_VERSION(2,16,0)
- q = g_uri_escape_string(lastfm_user, NULL, false);
-#else
- q = g_strdup(lastfm_user);
-#endif
-
-#if GLIB_CHECK_VERSION(2,16,0)
- if (strlen(lastfm_password) != 32)
- md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
- lastfm_password,
- strlen(lastfm_password));
- else
-#endif
- md5 = g_strdup(lastfm_password);
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?"
- "version=1.1.1&platform=linux&"
- "username=", q, "&"
- "passwordmd5=", md5, "&debug=0&partner=", NULL);
- g_free(q);
- g_free(md5);
-
- response = lastfm_get(p);
- g_free(p);
- if (response == NULL)
- return false;
-
- /* extract session id from response */
-
- session = lastfm_find(response, "session");
- stream_url = lastfm_find(response, "stream_url");
- g_free(response);
- if (session == NULL || stream_url == NULL) {
- g_free(session);
- g_free(stream_url);
- return false;
- }
-
-#if GLIB_CHECK_VERSION(2,16,0)
- q = g_uri_escape_string(session, NULL, false);
- g_free(session);
- session = q;
-#endif
-
- /* "adjust" last.fm radio */
-
- if (strlen(url) > 9) {
- char *escaped_url;
-
-#if GLIB_CHECK_VERSION(2,16,0)
- escaped_url = g_uri_escape_string(url, NULL, false);
-#else
- escaped_url = g_strdup(url);
-#endif
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?"
- "session=", session, "&url=", escaped_url, "&debug=0",
- NULL);
- g_free(escaped_url);
-
- response = lastfm_get(p);
- g_free(response);
- g_free(p);
-
- if (response == NULL) {
- g_free(session);
- g_free(stream_url);
- return false;
- }
- }
-
- /* load the last.fm playlist */
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?"
- "sk=", session, "&discovery=0&desktop=1.5.1.31879",
- NULL);
- g_free(session);
-
- response = lastfm_get(p);
- g_free(p);
-
- if (response == NULL) {
- g_free(stream_url);
- return false;
- }
-
- p = strstr(response, "<location>");
- if (p == NULL) {
- g_free(response);
- g_free(stream_url);
- return false;
- }
-
- p += 10;
- q = strchr(p, '<');
-
- if (q == NULL) {
- g_free(response);
- g_free(stream_url);
- return false;
- }
-
- g_free(stream_url);
- stream_url = g_strndup(p, q - p);
- g_free(response);
-
- /* now really open the last.fm radio stream */
-
- success = input_stream_open(is, stream_url);
- g_free(stream_url);
- return success;
-}
-
-const struct input_plugin lastfm_input_plugin = {
- .name = "lastfm",
- .init = lastfm_input_init,
- .open = lastfm_input_open,
-};
diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c
index 2a3c53776..335571bef 100644
--- a/src/input/mms_input_plugin.c
+++ b/src/input/mms_input_plugin.c
@@ -110,7 +110,7 @@ input_mms_buffer(G_GNUC_UNUSED struct input_stream *is)
static bool
input_mms_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence)
{
return false;
}
diff --git a/src/input_plugin.h b/src/input_plugin.h
index 8fe852bc6..f38174d42 100644
--- a/src/input_plugin.h
+++ b/src/input_plugin.h
@@ -53,7 +53,7 @@ struct input_plugin {
int (*buffer)(struct input_stream *is);
size_t (*read)(struct input_stream *is, void *ptr, size_t size);
bool (*eof)(struct input_stream *is);
- bool (*seek)(struct input_stream *is, off_t offset, int whence);
+ bool (*seek)(struct input_stream *is, goffset offset, int whence);
};
#endif
diff --git a/src/input_stream.c b/src/input_stream.c
index 69dc644a2..c6da87996 100644
--- a/src/input_stream.c
+++ b/src/input_stream.c
@@ -27,12 +27,10 @@
#include "input/archive_input_plugin.h"
#endif
-#ifdef HAVE_CURL
+#ifdef ENABLE_CURL
#include "input/curl_input_plugin.h"
#endif
-#include "input/lastfm_input_plugin.h"
-
#ifdef ENABLE_MMS
#include "input/mms_input_plugin.h"
#endif
@@ -46,12 +44,9 @@ static const struct input_plugin *const input_plugins[] = {
#ifdef ENABLE_ARCHIVE
&input_plugin_archive,
#endif
-#ifdef HAVE_CURL
+#ifdef ENABLE_CURL
&input_plugin_curl,
#endif
-#ifdef ENABLE_LASTFM
- &lastfm_input_plugin,
-#endif
#ifdef ENABLE_MMS
&input_plugin_mms,
#endif
@@ -139,7 +134,7 @@ input_stream_open(struct input_stream *is, const char *url)
}
bool
-input_stream_seek(struct input_stream *is, off_t offset, int whence)
+input_stream_seek(struct input_stream *is, goffset offset, int whence)
{
if (is->plugin->seek == NULL)
return false;
diff --git a/src/input_stream.h b/src/input_stream.h
index 35b0d44fd..9e4a526ef 100644
--- a/src/input_stream.h
+++ b/src/input_stream.h
@@ -20,12 +20,12 @@
#ifndef MPD_INPUT_STREAM_H
#define MPD_INPUT_STREAM_H
+#include <glib.h>
+
#include <stddef.h>
#include <stdbool.h>
#include <sys/types.h>
-struct input_stream;
-
struct input_stream {
/**
* the plugin which implements this input stream
@@ -56,12 +56,12 @@ struct input_stream {
/**
* the size of the resource, or -1 if unknown
*/
- off_t size;
+ goffset size;
/**
* the current offset within the stream
*/
- off_t offset;
+ goffset offset;
/**
* the MIME content type of the resource, or NULL if unknown
@@ -106,7 +106,7 @@ input_stream_close(struct input_stream *is);
* @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END
*/
bool
-input_stream_seek(struct input_stream *is, off_t offset, int whence);
+input_stream_seek(struct input_stream *is, goffset offset, int whence);
/**
* Returns true if the stream has reached end-of-file.
diff --git a/src/listen.c b/src/listen.c
index 98108d9da..668a8077c 100644
--- a/src/listen.c
+++ b/src/listen.c
@@ -21,7 +21,7 @@
#include "socket_util.h"
#include "client.h"
#include "conf.h"
-#include "utils.h"
+#include "fd_util.h"
#include "config.h"
#include <sys/types.h>
@@ -347,7 +347,8 @@ listen_add_config_param(unsigned int port,
}
}
-void listen_global_init(void)
+bool
+listen_global_init(GError **error_r)
{
int port = config_get_positive(CONF_PORT, DEFAULT_PORT);
const struct config_param *param =
@@ -361,10 +362,12 @@ void listen_global_init(void)
do {
success = listen_add_config_param(port, param, &error);
- if (!success)
- g_error("Failed to listen on %s (line %i): %s",
- param->value, param->line,
- error->message);
+ if (!success) {
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to listen on %s (line %i): ",
+ param->value, param->line);
+ return false;
+ }
param = config_get_next_param(CONF_BIND_TO_ADDRESS,
param);
@@ -374,12 +377,16 @@ void listen_global_init(void)
configured port on all interfaces */
success = listen_add_port(port, &error);
- if (!success)
- g_error("Failed to listen on *:%d: %s",
- port, error->message);
+ if (!success) {
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to listen on *:%d: ",
+ port);
+ return false;
+ }
}
listen_port = port;
+ return true;
}
void listen_global_finish(void)
@@ -419,12 +426,11 @@ listen_in_event(G_GNUC_UNUSED GIOChannel *source,
{
int listen_fd = GPOINTER_TO_INT(data), fd;
struct sockaddr_storage sa;
- socklen_t sa_length = sizeof(sa);
+ size_t sa_length = sizeof(sa);
- fd = accept(listen_fd, (struct sockaddr*)&sa, &sa_length);
+ fd = accept_cloexec_nonblock(listen_fd, (struct sockaddr*)&sa,
+ &sa_length);
if (fd >= 0) {
- set_nonblocking(fd);
-
client_new(fd, (struct sockaddr*)&sa, sa_length,
get_remote_uid(fd));
} else if (fd < 0 && errno != EINTR) {
diff --git a/src/listen.h b/src/listen.h
index 63253fc53..9f55edb88 100644
--- a/src/listen.h
+++ b/src/listen.h
@@ -20,9 +20,14 @@
#ifndef MPD_LISTEN_H
#define MPD_LISTEN_H
+#include <glib.h>
+
+#include <stdbool.h>
+
extern int listen_port;
-void listen_global_init(void);
+bool
+listen_global_init(GError **error_r);
void listen_global_finish(void);
diff --git a/src/locate.c b/src/locate.c
index 175bca35a..7b4721fa9 100644
--- a/src/locate.c
+++ b/src/locate.c
@@ -42,9 +42,9 @@ locate_parse_type(const char *str)
if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
return LOCATE_TAG_ANY_TYPE;
- for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
- if (0 == g_ascii_strcasecmp(str, tag_item_names[i]))
- return i;
+ i = tag_name_parse_i(str);
+ if (i != TAG_NUM_OF_ITEM_TYPES)
+ return i;
return -1;
}
diff --git a/src/log.c b/src/log.c
index 94691ab64..bb1f1f112 100644
--- a/src/log.c
+++ b/src/log.c
@@ -20,6 +20,7 @@
#include "log.h"
#include "conf.h"
#include "utils.h"
+#include "fd_util.h"
#include "config.h"
#include <assert.h>
@@ -128,7 +129,7 @@ open_log_file(void)
{
assert(out_filename != NULL);
- return open(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
+ return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
}
static void
@@ -271,7 +272,10 @@ void setup_log_output(bool use_stdout)
{
fflush(NULL);
if (!use_stdout) {
- if (out_filename != NULL) {
+ if (out_filename == NULL)
+ out_fd = open("/dev/null", O_WRONLY);
+
+ if (out_fd >= 0) {
redirect_logs(out_fd);
close(out_fd);
}
diff --git a/src/ls.c b/src/ls.c
index fd8f22fd4..413fbac94 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -32,12 +32,9 @@
* connected by IPC socket.
*/
static const char *remoteUrlPrefixes[] = {
-#ifdef HAVE_CURL
+#ifdef ENABLE_CURL
"http://",
#endif
-#ifdef ENABLE_LASTFM
- "lastfm://",
-#endif
#ifdef ENABLE_MMS
"mms://",
"mmsh://",
diff --git a/src/main.c b/src/main.c
index 5035a4836..70166fefb 100644
--- a/src/main.c
+++ b/src/main.c
@@ -33,7 +33,6 @@
#include "path.h"
#include "mapper.h"
#include "chunk.h"
-#include "decoder_control.h"
#include "player_control.h"
#include "stats.h"
#include "sig_handlers.h"
@@ -45,6 +44,7 @@
#include "replay_gain.h"
#include "decoder_list.h"
#include "input_stream.h"
+#include "playlist_list.h"
#include "state_file.h"
#include "tag.h"
#include "dbUtils.h"
@@ -56,6 +56,10 @@
#include "songvec.h"
#include "tag_pool.h"
+#ifdef ENABLE_INOTIFY
+#include "inotify_update.h"
+#endif
+
#ifdef ENABLE_SQLITE
#include "sticker.h"
#endif
@@ -88,7 +92,32 @@ enum {
GThread *main_task;
GMainLoop *main_loop;
-struct notify main_notify;
+GCond *main_cond;
+
+static void
+glue_daemonize_init(const struct options *options)
+{
+ daemonize_init(config_get_string(CONF_USER, NULL),
+ config_get_string(CONF_GROUP, NULL),
+ config_get_path(CONF_PID_FILE));
+
+ if (options->kill)
+ daemonize_kill();
+}
+
+static void
+glue_mapper_init(void)
+{
+ const char *music_dir, *playlist_dir;
+
+ music_dir = config_get_path(CONF_MUSIC_DIR);
+ if (music_dir == NULL)
+ music_dir = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
+
+ playlist_dir = config_get_path(CONF_PLAYLIST_DIR);
+
+ mapper_init(music_dir, playlist_dir);
+}
/**
* Returns the database. If this function returns false, this has not
@@ -96,7 +125,7 @@ struct notify main_notify;
* process has been daemonized.
*/
static bool
-openDB(const Options *options)
+glue_db_init_and_load(void)
{
const char *path = config_get_path(CONF_DB_FILE);
bool ret;
@@ -115,19 +144,11 @@ openDB(const Options *options)
db_init(path);
- if (options->createDB > 0)
- /* don't attempt to load the old database */
- return false;
-
ret = db_load(&error);
if (!ret) {
g_warning("Failed to load database: %s", error->message);
g_error_free(error);
- if (options->createDB < 0)
- g_error("can't open db file and using "
- "\"--no-create-db\" command line option");
-
if (!db_check())
exit(EXIT_FAILURE);
@@ -141,11 +162,34 @@ openDB(const Options *options)
}
/**
+ * Configure and initialize the sticker subsystem.
+ */
+static void
+glue_sticker_init(void)
+{
+#ifdef ENABLE_SQLITE
+ bool success;
+ GError *error = NULL;
+
+ success = sticker_global_init(config_get_path(CONF_STICKER_FILE),
+ &error);
+ if (!success)
+ g_error("%s", error->message);
+#endif
+}
+
+static void
+glue_state_file_init(void)
+{
+ state_file_init(config_get_path(CONF_STATE_FILE));
+}
+
+/**
* Windows-only initialization of the Winsock2 library.
*/
-#ifdef WIN32
static void winsock_init(void)
{
+#ifdef WIN32
WSADATA sockinfo;
int retval;
@@ -161,9 +205,8 @@ static void winsock_init(void)
g_error("We use Winsock2 but your version is either too new or "
"old; please install Winsock 2.x\n");
}
-
-}
#endif
+}
/**
* Initialize the decoder and player core, including the music pipe.
@@ -210,7 +253,6 @@ initialize_decoder_and_player(void)
buffered_before_play = buffered_chunks;
pc_init(buffered_chunks, buffered_before_play);
- dc_init();
}
/**
@@ -228,9 +270,11 @@ idle_event_emitted(void)
int main(int argc, char *argv[])
{
- Options options;
+ struct options options;
clock_t start;
bool create_db;
+ GError *error = NULL;
+ bool success;
daemonize_close_stdin();
@@ -239,45 +283,51 @@ int main(int argc, char *argv[])
setlocale(LC_CTYPE,"");
#endif
+ g_set_application_name("Music Player Daemon");
+
/* enable GLib's thread safety code */
g_thread_init(NULL);
-#ifdef WIN32
winsock_init();
-#endif
idle_init();
dirvec_init();
songvec_init();
tag_pool_init();
config_global_init();
- parseOptions(argc, argv, &options);
-
- daemonize_init(config_get_string(CONF_USER, NULL),
- config_get_path(CONF_PID_FILE));
+ success = parse_cmdline(argc, argv, &options, &error);
+ if (!success) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
- if (options.kill)
- daemonize_kill();
+ glue_daemonize_init(&options);
stats_global_init();
tag_lib_init();
- log_init(options.verbose, options.stdOutput);
+ log_init(options.verbose, options.log_stderr);
- listen_global_init();
+ success = listen_global_init(&error);
+ if (!success) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
daemonize_set_user();
main_task = g_thread_self();
main_loop = g_main_loop_new(NULL, FALSE);
- notify_init(&main_notify);
+ main_cond = g_cond_new();
event_pipe_init();
event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
path_global_init();
- mapper_init();
+ glue_mapper_init();
initPermissions();
- initPlaylist();
+ playlist_global_init();
spl_global_init();
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
@@ -285,11 +335,9 @@ int main(int argc, char *argv[])
decoder_plugin_init_all();
update_global_init();
- create_db = !openDB(&options);
+ create_db = !glue_db_init_and_load();
-#ifdef ENABLE_SQLITE
- sticker_global_init(config_get_path(CONF_STICKER_FILE));
-#endif
+ glue_sticker_init();
command_init();
initialize_decoder_and_player();
@@ -300,10 +348,11 @@ int main(int argc, char *argv[])
replay_gain_global_init();
initNormalization();
input_stream_global_init();
+ playlist_list_global_init();
daemonize(options.daemon);
- setup_log_output(options.stdOutput);
+ setup_log_output(options.log_stderr);
initSigHandlers();
@@ -312,15 +361,29 @@ int main(int argc, char *argv[])
player_create();
if (create_db) {
- /* the database failed to load, or MPD was started
- with --create-db: recreate a new database */
- unsigned job = directory_update_init(NULL);
+ /* the database failed to load: recreate the
+ database */
+ unsigned job = update_enqueue(NULL, true);
if (job == 0)
g_error("directory update failed");
}
+ glue_state_file_init();
- state_file_init(config_get_path(CONF_STATE_FILE));
+ success = config_get_bool(CONF_AUTO_UPDATE, false);
+#ifdef ENABLE_INOTIFY
+ if (success && mapper_has_music_directory())
+ mpd_inotify_init();
+#else
+ if (success)
+ g_warning("inotify: auto_update was disabled. enable during compilation phase");
+#endif
+
+ config_global_check();
+
+ /* enable all audio outputs (if not already done by
+ playlist_state_restore() */
+ pc_update_audio();
/* run the main loop */
@@ -330,12 +393,16 @@ int main(int argc, char *argv[])
g_main_loop_unref(main_loop);
+#ifdef ENABLE_INOTIFY
+ mpd_inotify_finish();
+#endif
+
state_file_finish();
- playerKill();
+ pc_kill();
finishZeroconf();
client_manager_deinit();
listen_global_finish();
- finishPlaylist();
+ playlist_global_finish();
start = clock();
db_finish();
@@ -346,18 +413,17 @@ int main(int argc, char *argv[])
sticker_global_finish();
#endif
- notify_deinit(&main_notify);
+ g_cond_free(main_cond);
event_pipe_deinit();
+ playlist_list_global_finish();
input_stream_global_finish();
finishNormalization();
audio_output_all_finish();
- finishAudioConfig();
volume_finish();
mapper_finish();
path_global_finish();
finishPermissions();
- dc_deinit();
pc_deinit();
command_finish();
update_global_finish();
diff --git a/src/main.h b/src/main.h
index 8ed02bf5d..de673829d 100644
--- a/src/main.h
+++ b/src/main.h
@@ -26,6 +26,6 @@ extern GThread *main_task;
extern GMainLoop *main_loop;
-extern struct notify main_notify;
+extern GCond *main_cond;
#endif
diff --git a/src/mapper.c b/src/mapper.c
index aac7c0c48..a48e12a35 100644
--- a/src/mapper.c
+++ b/src/mapper.c
@@ -25,16 +25,11 @@
#include "directory.h"
#include "song.h"
#include "path.h"
-#include "conf.h"
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
#include <string.h>
-#include <errno.h>
static char *music_dir;
static size_t music_dir_length;
@@ -58,17 +53,10 @@ strdup_chop_slash(const char *path_fs)
static void
mapper_set_music_dir(const char *path)
{
- int ret;
- struct stat st;
-
music_dir = strdup_chop_slash(path);
music_dir_length = strlen(music_dir);
- ret = stat(music_dir, &st);
- if (ret < 0)
- g_warning("failed to stat music directory \"%s\": %s",
- music_dir, g_strerror(errno));
- else if (!S_ISDIR(st.st_mode))
+ if (!g_file_test(music_dir, G_FILE_TEST_IS_DIR))
g_warning("music directory is not a directory: \"%s\"",
music_dir);
}
@@ -76,38 +64,20 @@ mapper_set_music_dir(const char *path)
static void
mapper_set_playlist_dir(const char *path)
{
- int ret;
- struct stat st;
-
playlist_dir = g_strdup(path);
- ret = stat(playlist_dir, &st);
- if (ret < 0)
- g_warning("failed to stat playlist directory \"%s\": %s",
- playlist_dir, g_strerror(errno));
- else if (!S_ISDIR(st.st_mode))
+ if (!g_file_test(playlist_dir, G_FILE_TEST_IS_DIR))
g_warning("playlist directory is not a directory: \"%s\"",
playlist_dir);
}
-void mapper_init(void)
+void mapper_init(const char *_music_dir, const char *_playlist_dir)
{
- const char *path;
-
- path = config_get_path(CONF_MUSIC_DIR);
- if (path != NULL)
- mapper_set_music_dir(path);
-#if GLIB_CHECK_VERSION(2,14,0)
- else {
- path = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
- if (path != NULL)
- mapper_set_music_dir(path);
- }
-#endif
+ if (_music_dir != NULL)
+ mapper_set_music_dir(_music_dir);
- path = config_get_path(CONF_PLAYLIST_DIR);
- if (path != NULL)
- mapper_set_playlist_dir(path);
+ if (_playlist_dir != NULL)
+ mapper_set_playlist_dir(_playlist_dir);
}
void mapper_finish(void)
@@ -189,9 +159,9 @@ 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->url);
+ return map_directory_child_fs(song->parent, song->uri);
else
- return utf8_to_fs_charset(song->url);
+ return utf8_to_fs_charset(song->uri);
}
char *
@@ -199,10 +169,10 @@ map_fs_to_utf8(const char *path_fs)
{
if (music_dir != NULL &&
strncmp(path_fs, music_dir, music_dir_length) == 0 &&
- path_fs[music_dir_length] == '/')
+ G_IS_DIR_SEPARATOR(path_fs[music_dir_length]))
/* remove musicDir prefix */
path_fs += music_dir_length + 1;
- else if (path_fs[0] == '/')
+ else if (G_IS_DIR_SEPARATOR(path_fs[0]))
/* not within musicDir */
return NULL;
diff --git a/src/mapper.h b/src/mapper.h
index 2667f1eee..d63243ff7 100644
--- a/src/mapper.h
+++ b/src/mapper.h
@@ -31,7 +31,7 @@
struct directory;
struct song;
-void mapper_init(void);
+void mapper_init(const char *_music_dir, const char *_playlist_dir);
void mapper_finish(void);
diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer_plugin.c
index 52e553cc5..baf12030c 100644
--- a/src/mixer/alsa_mixer.c
+++ b/src/mixer/alsa_mixer_plugin.c
@@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../mixer_api.h"
+#include "mixer_api.h"
+#include "output_api.h"
#include <glib.h>
#include <alsa/asoundlib.h>
@@ -42,12 +42,22 @@ struct alsa_mixer {
int volume_set;
};
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+alsa_mixer_quark(void)
+{
+ return g_quark_from_static_string("alsa_mixer");
+}
+
static struct mixer *
-alsa_mixer_init(const struct config_param *param)
+alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
{
struct alsa_mixer *am = g_new(struct alsa_mixer, 1);
- mixer_init(&am->base, &alsa_mixer);
+ mixer_init(&am->base, &alsa_mixer_plugin);
am->device = config_get_block_string(param, "mixer_device",
VOLUME_MIXER_ALSA_DEFAULT);
@@ -81,7 +91,7 @@ alsa_mixer_close(struct mixer *data)
}
static bool
-alsa_mixer_open(struct mixer *data)
+alsa_mixer_open(struct mixer *data, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
int err;
@@ -91,29 +101,33 @@ alsa_mixer_open(struct mixer *data)
err = snd_mixer_open(&am->handle, 0);
if (err < 0) {
- g_warning("problems opening alsa mixer: %s\n", snd_strerror(err));
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_open() failed: %s", snd_strerror(err));
return false;
}
if ((err = snd_mixer_attach(am->handle, am->device)) < 0) {
- g_warning("problems attaching alsa mixer: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to attach to %s: %s",
+ am->device, snd_strerror(err));
return false;
}
if ((err = snd_mixer_selem_register(am->handle, NULL,
NULL)) < 0) {
- g_warning("problems snd_mixer_selem_register'ing: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_selem_register() failed: %s",
+ snd_strerror(err));
return false;
}
if ((err = snd_mixer_load(am->handle)) < 0) {
- g_warning("problems snd_mixer_selem_register'ing: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_load() failed: %s\n",
+ snd_strerror(err));
return false;
}
@@ -138,14 +152,14 @@ alsa_mixer_open(struct mixer *data)
return true;
}
- g_warning("can't find alsa mixer control \"%s\"\n", am->control);
-
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), 0,
+ "no such mixer control: %s", am->control);
return false;
}
static int
-alsa_mixer_get_volume(struct mixer *mixer)
+alsa_mixer_get_volume(struct mixer *mixer, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)mixer;
int err;
@@ -156,8 +170,9 @@ alsa_mixer_get_volume(struct mixer *mixer)
err = snd_mixer_handle_events(am->handle);
if (err < 0) {
- g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
- snd_strerror(err), "handle_events");
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_handle_events() failed: %s",
+ snd_strerror(err));
return false;
}
@@ -165,8 +180,9 @@ alsa_mixer_get_volume(struct mixer *mixer)
SND_MIXER_SCHN_FRONT_LEFT,
&level);
if (err < 0) {
- g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
- snd_strerror(err), "selem_get_playback_volume");
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to read ALSA volume: %s",
+ snd_strerror(err));
return false;
}
@@ -183,7 +199,7 @@ alsa_mixer_get_volume(struct mixer *mixer)
}
static bool
-alsa_mixer_set_volume(struct mixer *mixer, unsigned volume)
+alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)mixer;
float vol;
@@ -203,15 +219,16 @@ alsa_mixer_set_volume(struct mixer *mixer, unsigned volume)
err = snd_mixer_selem_set_playback_volume_all(am->elem, level);
if (err < 0) {
- g_warning("problems setting alsa volume: %s\n",
- snd_strerror(err));
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to set ALSA volume: %s",
+ snd_strerror(err));
return false;
}
return true;
}
-const struct mixer_plugin alsa_mixer = {
+const struct mixer_plugin alsa_mixer_plugin = {
.init = alsa_mixer_init,
.finish = alsa_mixer_finish,
.open = alsa_mixer_open,
diff --git a/src/mixer/oss_mixer.c b/src/mixer/oss_mixer_plugin.c
index f2db01ff4..631107b70 100644
--- a/src/mixer/oss_mixer.c
+++ b/src/mixer/oss_mixer_plugin.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../mixer_api.h"
+#include "mixer_api.h"
+#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -49,6 +50,15 @@ struct oss_mixer {
int volume_control;
};
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+oss_mixer_quark(void)
+{
+ return g_quark_from_static_string("oss_mixer");
+}
+
static int
oss_find_mixer(const char *name)
{
@@ -65,11 +75,12 @@ oss_find_mixer(const char *name)
}
static struct mixer *
-oss_mixer_init(const struct config_param *param)
+oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
+ GError **error_r)
{
struct oss_mixer *om = g_new(struct oss_mixer, 1);
- mixer_init(&om->base, &oss_mixer);
+ mixer_init(&om->base, &oss_mixer_plugin);
om->device = config_get_block_string(param, "mixer_device",
VOLUME_MIXER_OSS_DEFAULT);
@@ -78,9 +89,9 @@ oss_mixer_init(const struct config_param *param)
if (om->control != NULL) {
om->volume_control = oss_find_mixer(om->control);
if (om->volume_control < 0) {
- g_warning("mixer control \"%s\" not found",
- om->control);
g_free(om);
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "no such mixer control: %s", om->control);
return NULL;
}
} else
@@ -108,13 +119,15 @@ oss_mixer_close(struct mixer *data)
}
static bool
-oss_mixer_open(struct mixer *data)
+oss_mixer_open(struct mixer *data, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *) data;
- om->device_fd = open(om->device, O_RDONLY);
+ om->device_fd = open_cloexec(om->device, O_RDONLY, 0);
if (om->device_fd < 0) {
- g_warning("Unable to open oss mixer \"%s\"\n", om->device);
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to open %s: %s",
+ om->device, g_strerror(errno));
return false;
}
@@ -122,14 +135,17 @@ oss_mixer_open(struct mixer *data)
int devmask = 0;
if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
- g_warning("errors getting read_devmask for oss mixer\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "READ_DEVMASK failed: %s",
+ g_strerror(errno));
oss_mixer_close(data);
return false;
}
if (((1 << om->volume_control) & devmask) == 0) {
- g_warning("mixer control \"%s\" not usable\n",
- om->control);
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "mixer control \"%s\" not usable",
+ om->control);
oss_mixer_close(data);
return false;
}
@@ -138,7 +154,7 @@ oss_mixer_open(struct mixer *data)
}
static int
-oss_mixer_get_volume(struct mixer *mixer)
+oss_mixer_get_volume(struct mixer *mixer, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *)mixer;
int left, right, level;
@@ -148,7 +164,9 @@ oss_mixer_get_volume(struct mixer *mixer)
ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level);
if (ret < 0) {
- g_warning("unable to read oss volume\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to read OSS volume: %s",
+ g_strerror(errno));
return false;
}
@@ -164,7 +182,7 @@ oss_mixer_get_volume(struct mixer *mixer)
}
static bool
-oss_mixer_set_volume(struct mixer *mixer, unsigned volume)
+oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *)mixer;
int level;
@@ -177,14 +195,16 @@ oss_mixer_set_volume(struct mixer *mixer, unsigned volume)
ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level);
if (ret < 0) {
- g_warning("unable to set oss volume\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to set OSS volume: %s",
+ g_strerror(errno));
return false;
}
return true;
}
-const struct mixer_plugin oss_mixer = {
+const struct mixer_plugin oss_mixer_plugin = {
.init = oss_mixer_init,
.finish = oss_mixer_finish,
.open = oss_mixer_open,
diff --git a/src/mixer/pulse_mixer.c b/src/mixer/pulse_mixer.c
deleted file mode 100644
index 5d9ce0475..000000000
--- a/src/mixer/pulse_mixer.c
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright (C) 2003-2009 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 "mixer_api.h"
-#include "conf.h"
-
-#include <glib.h>
-#include <pulse/volume.h>
-#include <pulse/pulseaudio.h>
-
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pulse_mixer"
-
-struct pulse_mixer {
- struct mixer base;
-
- const char *server;
- const char *sink;
- const char *output_name;
-
- uint32_t index;
- bool online;
-
- struct pa_context *context;
- struct pa_threaded_mainloop *mainloop;
- struct pa_cvolume volume;
-
-};
-
-/**
- * \brief waits for a pulseaudio operation to finish, frees it and
- * unlocks the mainloop
- * \param operation the operation to wait for
- * \return true if operation has finished normally (DONE state),
- * false otherwise
- */
-static bool
-pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
- struct pa_operation *operation)
-{
- pa_operation_state_t state;
-
- assert(mainloop != NULL);
- assert(operation != NULL);
-
- state = pa_operation_get_state(operation);
- while (state == PA_OPERATION_RUNNING) {
- pa_threaded_mainloop_wait(mainloop);
- state = pa_operation_get_state(operation);
- }
-
- pa_operation_unref(operation);
-
- return state == PA_OPERATION_DONE;
-}
-
-static void
-sink_input_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
- int eol, void *userdata)
-{
-
- struct pulse_mixer *pm = userdata;
-
- if (eol) {
- g_debug("eol error sink_input_cb");
- return;
- }
-
- if (i == NULL) {
- g_debug("Sink input callback failure");
- return;
- }
-
- g_debug("sink input cb %s, index %d ",i->name,i->index);
-
- if (strcmp(i->name,pm->output_name) == 0) {
- pm->index = i->index;
- pm->online = true;
- pm->volume = i->volume;
- } else
- g_debug("bad name");
-}
-
-static void
-sink_input_vol(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
- int eol, void *userdata)
-{
-
- struct pulse_mixer *pm = userdata;
-
- if (eol) {
- g_debug("eol error sink_input_vol");
- return;
- }
-
- if (i == NULL) {
- g_debug("Sink input callback failure");
- return;
- }
-
- g_debug("sink input vol %s, index %d ", i->name, i->index);
-
- pm->volume = i->volume;
-
- pa_threaded_mainloop_signal(pm->mainloop, 0);
-}
-
-static void
-subscribe_cb(pa_context *c, pa_subscription_event_type_t t,
- uint32_t idx, void *userdata)
-{
-
- struct pulse_mixer *pm = userdata;
-
- g_debug("subscribe call back");
-
- switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
- case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
- if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
- PA_SUBSCRIPTION_EVENT_REMOVE &&
- pm->index == idx)
- pm->online = false;
- else {
- pa_operation *o;
-
- o = pa_context_get_sink_input_info(c, idx,
- sink_input_cb, pm);
- if (o == NULL) {
- g_debug("pa_context_get_sink_input_info() failed");
- return;
- }
-
- pa_operation_unref(o);
- }
-
- break;
- }
-}
-
-static void
-context_state_cb(pa_context *context, void *userdata)
-{
- struct pulse_mixer *pm = userdata;
-
- switch (pa_context_get_state(context)) {
- case PA_CONTEXT_READY: {
- pa_operation *o;
-
- pa_context_set_subscribe_callback(context, subscribe_cb, pm);
-
- o = pa_context_subscribe(context,
- (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
- NULL, NULL);
- if (o == NULL) {
- g_debug("pa_context_subscribe() failed");
- return;
- }
-
- pa_operation_unref(o);
-
- o = pa_context_get_sink_input_info_list(context,
- sink_input_cb, pm);
- if (o == NULL) {
- g_debug("pa_context_get_sink_input_info_list() failed");
- return;
- }
-
- pa_operation_unref(o);
-
- pa_threaded_mainloop_signal(pm->mainloop, 0);
- break;
- }
-
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- break;
- case PA_CONTEXT_TERMINATED:
- case PA_CONTEXT_FAILED:
- pa_threaded_mainloop_signal(pm->mainloop, 0);
- break;
- }
-}
-
-
-static struct mixer *
-pulse_mixer_init(const struct config_param *param)
-{
- struct pulse_mixer *pm = g_new(struct pulse_mixer,1);
- mixer_init(&pm->base, &pulse_mixer);
-
- pm->online = false;
-
- pm->server = config_get_block_string(param, "server", NULL);
- pm->sink = config_get_block_string(param, "sink", NULL);
- pm->output_name = config_get_block_string(param, "name", NULL);
-
- return &pm->base;
-}
-
-static void
-pulse_mixer_finish(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- g_free(pm);
-}
-
-static bool
-pulse_mixer_setup(struct pulse_mixer *pm)
-{
- pa_context_set_state_callback(pm->context, context_state_cb, pm);
-
- if (pa_context_connect(pm->context, pm->server,
- (pa_context_flags_t)0, NULL) < 0) {
- g_debug("context server fail");
- return false;
- }
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (pa_threaded_mainloop_start(pm->mainloop) < 0) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("error start mainloop");
- return false;
- }
-
- pa_threaded_mainloop_wait(pm->mainloop);
-
- if (pa_context_get_state(pm->context) != PA_CONTEXT_READY) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("error context not ready");
- return false;
- }
-
- pa_threaded_mainloop_unlock(pm->mainloop);
-
- return true;
-}
-
-static bool
-pulse_mixer_open(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- g_debug("pulse mixer open");
-
- pm->index = 0;
- pm->online = false;
-
- pm->mainloop = pa_threaded_mainloop_new();
- if (pm->mainloop == NULL) {
- g_debug("failed mainloop");
- return false;
- }
-
- pm->context = pa_context_new(pa_threaded_mainloop_get_api(pm->mainloop),
- "Mixer mpd");
- if (pm->context == NULL) {
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_threaded_mainloop_free(pm->mainloop);
- g_debug("failed context");
- return false;
- }
-
- if (!pulse_mixer_setup(pm)) {
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_context_disconnect(pm->context);
- pa_context_unref(pm->context);
- pa_threaded_mainloop_free(pm->mainloop);
- return false;
- }
-
- return true;
-}
-
-static void
-pulse_mixer_close(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_context_disconnect(pm->context);
- pa_context_unref(pm->context);
- pa_threaded_mainloop_free(pm->mainloop);
-
- pm->online = false;
-}
-
-static int
-pulse_mixer_get_volume(struct mixer *mixer)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
- int ret;
- pa_operation *o;
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (!pm->online) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- o = pa_context_get_sink_input_info(pm->context, pm->index,
- sink_input_vol, pm);
- if (o == NULL) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("pa_context_get_sink_input_info() failed");
- return false;
- }
-
- if (!pulse_wait_for_operation(pm->mainloop, o)) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- ret = pm->online
- ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
- : -1;
-
- pa_threaded_mainloop_unlock(pm->mainloop);
-
- return ret;
-}
-
-static bool
-pulse_mixer_set_volume(struct mixer *mixer, unsigned volume)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
- struct pa_cvolume cvolume;
- pa_operation *o;
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (!pm->online) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- pa_cvolume_set(&cvolume, pm->volume.channels,
- (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5);
-
- o = pa_context_set_sink_input_volume(pm->context, pm->index,
- &cvolume, NULL, NULL);
- pa_threaded_mainloop_unlock(pm->mainloop);
- if (o == NULL) {
- g_debug("pa_context_set_sink_input_volume() failed");
- return false;
- }
-
- pa_operation_unref(o);
-
- return true;
-}
-
-const struct mixer_plugin pulse_mixer = {
- .init = pulse_mixer_init,
- .finish = pulse_mixer_finish,
- .open = pulse_mixer_open,
- .close = pulse_mixer_close,
- .get_volume = pulse_mixer_get_volume,
- .set_volume = pulse_mixer_set_volume,
-};
diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c
new file mode 100644
index 000000000..53f4436ea
--- /dev/null
+++ b/src/mixer/pulse_mixer_plugin.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2003-2009 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 "pulse_mixer_plugin.h"
+#include "mixer_api.h"
+#include "output/pulse_output_plugin.h"
+#include "conf.h"
+#include "event_pipe.h"
+
+#include <glib.h>
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/introspect.h>
+#include <pulse/stream.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pulse_mixer"
+
+struct pulse_mixer {
+ struct mixer base;
+
+ struct pulse_output *output;
+
+ bool online;
+ struct pa_cvolume volume;
+
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_mixer_quark(void)
+{
+ return g_quark_from_static_string("pulse_mixer");
+}
+
+static void
+pulse_mixer_offline(struct pulse_mixer *pm)
+{
+ if (!pm->online)
+ return;
+
+ pm->online = false;
+
+ event_pipe_emit(PIPE_EVENT_MIXER);
+}
+
+/**
+ * Callback invoked by pulse_mixer_update(). Receives the new mixer
+ * value.
+ */
+static void
+pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
+ int eol, void *userdata)
+{
+ struct pulse_mixer *pm = userdata;
+
+ if (eol)
+ return;
+
+ if (i == NULL) {
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pm->online = true;
+ pm->volume = i->volume;
+
+ event_pipe_emit(PIPE_EVENT_MIXER);
+}
+
+static void
+pulse_mixer_update(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+ assert(stream != NULL);
+ assert(pa_stream_get_state(stream) == PA_STREAM_READY);
+
+ o = pa_context_get_sink_input_info(context,
+ pa_stream_get_index(stream),
+ pulse_mixer_volume_cb, pm);
+ if (o == NULL) {
+ g_warning("pa_context_get_sink_input_info() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_connect(G_GNUC_UNUSED struct pulse_mixer *pm,
+ struct pa_context *context)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+
+ o = pa_context_subscribe(context,
+ (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
+ NULL, NULL);
+ if (o == NULL) {
+ g_warning("pa_context_subscribe() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_disconnect(struct pulse_mixer *pm)
+{
+ pulse_mixer_offline(pm);
+}
+
+void
+pulse_mixer_on_change(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pulse_mixer_update(pm, context, stream);
+}
+
+static struct mixer *
+pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
+ GError **error_r)
+{
+ struct pulse_mixer *pm;
+ struct pulse_output *po = ao;
+
+ if (ao == NULL) {
+ g_set_error(error_r, pulse_mixer_quark(), 0,
+ "The pulse mixer cannot work without the audio output");
+ return false;
+ }
+
+ pm = g_new(struct pulse_mixer,1);
+ mixer_init(&pm->base, &pulse_mixer_plugin);
+
+ pm->online = false;
+ pm->output = po;
+
+ pulse_output_set_mixer(po, pm);
+
+ return &pm->base;
+}
+
+static void
+pulse_mixer_finish(struct mixer *data)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) data;
+
+ pulse_output_clear_mixer(pm->output, pm);
+
+ /* free resources */
+
+ g_free(pm);
+}
+
+static int
+pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
+ int ret;
+
+ pa_threaded_mainloop_lock(pm->output->mainloop);
+
+ ret = pm->online
+ ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
+ : -1;
+
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+
+ return ret;
+}
+
+static bool
+pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
+ struct pa_cvolume cvolume;
+ bool success;
+
+ pa_threaded_mainloop_lock(pm->output->mainloop);
+ if (!pm->online) {
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+ g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected");
+ return false;
+ }
+
+ pa_cvolume_set(&cvolume, pm->volume.channels,
+ (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5);
+ success = pulse_output_set_volume(pm->output, &cvolume, error_r);
+ if (success)
+ pm->volume = cvolume;
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+
+ return success;
+}
+
+const struct mixer_plugin pulse_mixer_plugin = {
+ .init = pulse_mixer_init,
+ .finish = pulse_mixer_finish,
+ .get_volume = pulse_mixer_get_volume,
+ .set_volume = pulse_mixer_set_volume,
+};
diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h
new file mode 100644
index 000000000..2318b94be
--- /dev/null
+++ b/src/mixer/pulse_mixer_plugin.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2009 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_PULSE_MIXER_PLUGIN_H
+#define MPD_PULSE_MIXER_PLUGIN_H
+
+#include <pulse/def.h>
+
+struct pulse_mixer;
+struct pa_context;
+struct pa_stream;
+
+void
+pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context);
+
+void
+pulse_mixer_on_disconnect(struct pulse_mixer *pm);
+
+void
+pulse_mixer_on_change(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream);
+
+#endif
diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c
new file mode 100644
index 000000000..062b9b3ef
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2003-2009 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 "software_mixer_plugin.h"
+#include "mixer_api.h"
+#include "filter_plugin.h"
+#include "filter_registry.h"
+#include "filter/volume_filter_plugin.h"
+#include "pcm_volume.h"
+
+#include <assert.h>
+#include <math.h>
+
+struct software_mixer {
+ /** the base mixer class */
+ struct mixer base;
+
+ struct filter *filter;
+
+ unsigned volume;
+};
+
+static struct mixer *
+software_mixer_init(G_GNUC_UNUSED void *ao,
+ G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = g_new(struct software_mixer, 1);
+
+ mixer_init(&sm->base, &software_mixer_plugin);
+
+ sm->filter = filter_new(&volume_filter_plugin, NULL, NULL);
+ assert(sm->filter != NULL);
+
+ sm->volume = 100;
+
+ return &sm->base;
+}
+
+static void
+software_mixer_finish(struct mixer *data)
+{
+ struct software_mixer *sm = (struct software_mixer *)data;
+
+ g_free(sm);
+}
+
+static int
+software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ return sm->volume;
+}
+
+static bool
+software_mixer_set_volume(struct mixer *mixer, unsigned volume,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ assert(volume <= 100);
+
+ sm->volume = volume;
+
+ if (volume >= 100)
+ volume = PCM_VOLUME_1;
+ else if (volume > 0)
+ volume = pcm_float_to_volume((exp(volume / 25.0) - 1) /
+ (54.5981500331F - 1));
+
+ volume_filter_set(sm->filter, volume);
+ return true;
+}
+
+const struct mixer_plugin software_mixer_plugin = {
+ .init = software_mixer_init,
+ .finish = software_mixer_finish,
+ .get_volume = software_mixer_get_volume,
+ .set_volume = software_mixer_set_volume,
+ .global = true,
+};
+
+struct filter *
+software_mixer_get_filter(struct mixer *mixer)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ assert(sm->base.plugin == &software_mixer_plugin);
+
+ return sm->filter;
+}
diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h
new file mode 100644
index 000000000..a59f7edeb
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2009 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 SOFTWARE_MIXER_PLUGIN_H
+#define SOFTWARE_MIXER_PLUGIN_H
+
+struct mixer;
+struct filter;
+
+/**
+ * Returns the (volume) filter associated with this mixer. All users
+ * of this mixer plugin should install this filter.
+ */
+struct filter *
+software_mixer_get_filter(struct mixer *mixer);
+
+#endif
diff --git a/src/mixer_all.c b/src/mixer_all.c
index 252cb61ab..b9c1afdad 100644
--- a/src/mixer_all.c
+++ b/src/mixer_all.c
@@ -22,6 +22,9 @@
#include "output_all.h"
#include "output_plugin.h"
#include "output_internal.h"
+#include "pcm_volume.h"
+#include "mixer_api.h"
+#include "mixer_list.h"
#include <glib.h>
@@ -35,6 +38,8 @@ output_mixer_get_volume(unsigned i)
{
struct audio_output *output;
struct mixer *mixer;
+ int volume;
+ GError *error = NULL;
assert(i < audio_output_count());
@@ -46,7 +51,14 @@ output_mixer_get_volume(unsigned i)
if (mixer == NULL)
return -1;
- return mixer_get_volume(mixer);
+ volume = mixer_get_volume(mixer, &error);
+ if (volume < 0 && error != NULL) {
+ g_warning("Failed to read mixer for '%s': %s",
+ output->name, error->message);
+ g_error_free(error);
+ }
+
+ return volume;
}
int
@@ -70,12 +82,15 @@ mixer_all_get_volume(void)
}
static bool
-output_mixer_set_volume(unsigned i, int volume, bool relative)
+output_mixer_set_volume(unsigned i, unsigned volume)
{
struct audio_output *output;
struct mixer *mixer;
+ bool success;
+ GError *error = NULL;
assert(i < audio_output_count());
+ assert(volume <= 100);
output = audio_output_get(i);
if (!output->enabled)
@@ -85,31 +100,81 @@ output_mixer_set_volume(unsigned i, int volume, bool relative)
if (mixer == NULL)
return false;
- if (relative) {
- int prev = mixer_get_volume(mixer);
- if (prev < 0)
- return false;
-
- volume += prev;
+ success = mixer_set_volume(mixer, volume, &error);
+ if (!success && error != NULL) {
+ g_warning("Failed to set mixer for '%s': %s",
+ output->name, error->message);
+ g_error_free(error);
}
- if (volume > 100)
- volume = 100;
- else if (volume < 0)
- volume = 0;
-
- return mixer_set_volume(mixer, volume);
+ return success;
}
bool
-mixer_all_set_volume(int volume, bool relative)
+mixer_all_set_volume(unsigned volume)
{
bool success = false;
unsigned count = audio_output_count();
+ assert(volume <= 100);
+
for (unsigned i = 0; i < count; i++)
- success = output_mixer_set_volume(i, volume, relative)
+ success = output_mixer_set_volume(i, volume)
|| success;
return success;
}
+
+static int
+output_mixer_get_software_volume(unsigned i)
+{
+ struct audio_output *output;
+ struct mixer *mixer;
+
+ assert(i < audio_output_count());
+
+ output = audio_output_get(i);
+ if (!output->enabled)
+ return -1;
+
+ mixer = output->mixer;
+ if (mixer == NULL || mixer->plugin != &software_mixer_plugin)
+ return -1;
+
+ return mixer_get_volume(mixer, NULL);
+}
+
+int
+mixer_all_get_software_volume(void)
+{
+ unsigned count = audio_output_count(), ok = 0;
+ int volume, total = 0;
+
+ for (unsigned i = 0; i < count; i++) {
+ volume = output_mixer_get_software_volume(i);
+ if (volume >= 0) {
+ total += volume;
+ ++ok;
+ }
+ }
+
+ if (ok == 0)
+ return -1;
+
+ return total / ok;
+}
+
+void
+mixer_all_set_software_volume(unsigned volume)
+{
+ unsigned count = audio_output_count();
+
+ assert(volume <= PCM_VOLUME_1);
+
+ for (unsigned i = 0; i < count; i++) {
+ struct audio_output *output = audio_output_get(i);
+ if (output->mixer != NULL &&
+ output->mixer->plugin == &software_mixer_plugin)
+ mixer_set_volume(output->mixer, volume, NULL);
+ }
+}
diff --git a/src/mixer_all.h b/src/mixer_all.h
index 66c4988de..ebe8fed68 100644
--- a/src/mixer_all.h
+++ b/src/mixer_all.h
@@ -37,11 +37,26 @@ mixer_all_get_volume(void);
/**
* Sets the volume on all available mixers.
*
- * @param volume the volume (range 0..100 or -100..100 if #relative)
- * @param relative if true, then the #volume is added to the current value
+ * @param volume the volume (range 0..100)
* @return true on success, false on failure
*/
bool
-mixer_all_set_volume(int volume, bool relative);
+mixer_all_set_volume(unsigned volume);
+
+/**
+ * Similar to mixer_all_get_volume(), but gets the volume only for
+ * software mixers. See #software_mixer_plugin. This function fails
+ * if no software mixer is configured.
+ */
+int
+mixer_all_get_software_volume(void);
+
+/**
+ * Similar to mixer_all_set_volume(), but sets the volume only for
+ * software mixers. See #software_mixer_plugin. This function cannot
+ * fail, because the underlying software mixers cannot fail either.
+ */
+void
+mixer_all_set_software_volume(unsigned volume);
#endif
diff --git a/src/mixer_control.c b/src/mixer_control.c
index a17885935..3369f9ae8 100644
--- a/src/mixer_control.c
+++ b/src/mixer_control.c
@@ -20,35 +20,22 @@
#include "mixer_control.h"
#include "mixer_api.h"
-#include <glib.h>
-
#include <assert.h>
#include <stddef.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mixer"
-static bool mixers_enabled = true;
-
-void
-mixer_disable_all(void)
-{
- g_debug("mixer api is disabled");
- mixers_enabled = false;
-}
-
struct mixer *
-mixer_new(const struct mixer_plugin *plugin, const struct config_param *param)
+mixer_new(const struct mixer_plugin *plugin, void *ao,
+ const struct config_param *param,
+ GError **error_r)
{
struct mixer *mixer;
- //mixers are disabled (by using software volume)
- if (!mixers_enabled) {
- return NULL;
- }
assert(plugin != NULL);
- mixer = plugin->init(param);
+ mixer = plugin->init(ao, param, error_r);
assert(mixer == NULL || mixer->plugin == plugin);
@@ -68,7 +55,7 @@ mixer_free(struct mixer *mixer)
}
bool
-mixer_open(struct mixer *mixer)
+mixer_open(struct mixer *mixer, GError **error_r)
{
bool success;
@@ -79,8 +66,10 @@ mixer_open(struct mixer *mixer)
if (mixer->open)
success = true;
+ else if (mixer->plugin->open == NULL)
+ success = mixer->open = true;
else
- success = mixer->open = mixer->plugin->open(mixer);
+ success = mixer->open = mixer->plugin->open(mixer, error_r);
mixer->failed = !success;
@@ -96,7 +85,9 @@ mixer_close_internal(struct mixer *mixer)
assert(mixer->plugin != NULL);
assert(mixer->open);
- mixer->plugin->close(mixer);
+ if (mixer->plugin->close != NULL)
+ mixer->plugin->close(mixer);
+
mixer->open = false;
}
@@ -136,21 +127,26 @@ mixer_failed(struct mixer *mixer)
}
int
-mixer_get_volume(struct mixer *mixer)
+mixer_get_volume(struct mixer *mixer, GError **error_r)
{
int volume;
assert(mixer != NULL);
- if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer))
+ if (mixer->plugin->global && !mixer->failed &&
+ !mixer_open(mixer, error_r))
return -1;
g_mutex_lock(mixer->mutex);
if (mixer->open) {
- volume = mixer->plugin->get_volume(mixer);
- if (volume < 0)
+ GError *error = NULL;
+
+ volume = mixer->plugin->get_volume(mixer, &error);
+ if (volume < 0 && error != NULL) {
+ g_propagate_error(error_r, error);
mixer_failed(mixer);
+ }
} else
volume = -1;
@@ -160,22 +156,21 @@ mixer_get_volume(struct mixer *mixer)
}
bool
-mixer_set_volume(struct mixer *mixer, unsigned volume)
+mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
bool success;
assert(mixer != NULL);
assert(volume <= 100);
- if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer))
+ if (mixer->plugin->global && !mixer->failed &&
+ !mixer_open(mixer, error_r))
return false;
g_mutex_lock(mixer->mutex);
if (mixer->open) {
- success = mixer->plugin->set_volume(mixer, volume);
- if (!success)
- mixer_failed(mixer);
+ success = mixer->plugin->set_volume(mixer, volume, error_r);
} else
success = false;
diff --git a/src/mixer_control.h b/src/mixer_control.h
index 0f73e8f75..a550e0864 100644
--- a/src/mixer_control.h
+++ b/src/mixer_control.h
@@ -25,23 +25,24 @@
#ifndef MPD_MIXER_CONTROL_H
#define MPD_MIXER_CONTROL_H
+#include <glib.h>
+
#include <stdbool.h>
struct mixer;
struct mixer_plugin;
struct config_param;
-void
-mixer_disable_all(void);
-
struct mixer *
-mixer_new(const struct mixer_plugin *plugin, const struct config_param *param);
+mixer_new(const struct mixer_plugin *plugin, void *ao,
+ const struct config_param *param,
+ GError **error_r);
void
mixer_free(struct mixer *mixer);
bool
-mixer_open(struct mixer *mixer);
+mixer_open(struct mixer *mixer, GError **error_r);
void
mixer_close(struct mixer *mixer);
@@ -54,9 +55,9 @@ void
mixer_auto_close(struct mixer *mixer);
int
-mixer_get_volume(struct mixer *mixer);
+mixer_get_volume(struct mixer *mixer, GError **error_r);
bool
-mixer_set_volume(struct mixer *mixer, unsigned volume);
+mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r);
#endif
diff --git a/src/mixer_list.h b/src/mixer_list.h
index 7db4a00d8..2d9c773f6 100644
--- a/src/mixer_list.h
+++ b/src/mixer_list.h
@@ -25,8 +25,9 @@
#ifndef MPD_MIXER_LIST_H
#define MPD_MIXER_LIST_H
-extern const struct mixer_plugin alsa_mixer;
-extern const struct mixer_plugin oss_mixer;
-extern const struct mixer_plugin pulse_mixer;
+extern const struct mixer_plugin software_mixer_plugin;
+extern const struct mixer_plugin alsa_mixer_plugin;
+extern const struct mixer_plugin oss_mixer_plugin;
+extern const struct mixer_plugin pulse_mixer_plugin;
#endif
diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h
index 2b9b440e5..b6e67e8ff 100644
--- a/src/mixer_plugin.h
+++ b/src/mixer_plugin.h
@@ -27,6 +27,8 @@
#ifndef MPD_MIXER_PLUGIN_H
#define MPD_MIXER_PLUGIN_H
+#include <glib.h>
+
#include <stdbool.h>
struct config_param;
@@ -35,8 +37,16 @@ struct mixer;
struct mixer_plugin {
/**
* Alocates and configures a mixer device.
+ *
+ * @param ao the pointer returned by audio_output_plugin.init
+ * @param param the configuration section, or NULL if there is
+ * no configuration
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
+ * @return a mixer object, or NULL on error
*/
- struct mixer *(*init)(const struct config_param *param);
+ struct mixer *(*init)(void *ao, const struct config_param *param,
+ GError **error_r);
/**
* Finish and free mixer data
@@ -45,8 +55,12 @@ struct mixer_plugin {
/**
* Open mixer device
+ *
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
+ * @return true on success, false on error
*/
- bool (*open)(struct mixer *data);
+ bool (*open)(struct mixer *data, GError **error_r);
/**
* Close mixer device
@@ -56,18 +70,23 @@ struct mixer_plugin {
/**
* Reads the current volume.
*
- * @return the current volume (0..100 including) or -1 on
- * error
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
+ * @return the current volume (0..100 including) or -1 if
+ * unavailable or on error (error_r set, mixer will be closed)
*/
- int (*get_volume)(struct mixer *mixer);
+ int (*get_volume)(struct mixer *mixer, GError **error_r);
/**
* Sets the volume.
*
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
* @param volume the new volume (0..100 including)
- * @return true on success
+ * @return true on success, false on error
*/
- bool (*set_volume)(struct mixer *mixer, unsigned volume);
+ bool (*set_volume)(struct mixer *mixer, unsigned volume,
+ GError **error_r);
/**
* If true, then the mixer is automatically opened, even if
diff --git a/src/mixer_type.c b/src/mixer_type.c
new file mode 100644
index 000000000..6cf007856
--- /dev/null
+++ b/src/mixer_type.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2009 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 "mixer_type.h"
+
+#include <assert.h>
+#include <string.h>
+
+enum mixer_type
+mixer_type_parse(const char *input)
+{
+ assert(input != NULL);
+
+ if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0)
+ return MIXER_TYPE_NONE;
+ else if (strcmp(input, "hardware") == 0)
+ return MIXER_TYPE_HARDWARE;
+ else if (strcmp(input, "software") == 0)
+ return MIXER_TYPE_SOFTWARE;
+ else
+ return MIXER_TYPE_UNKNOWN;
+}
diff --git a/src/mixer_type.h b/src/mixer_type.h
new file mode 100644
index 000000000..ec3cbf9cc
--- /dev/null
+++ b/src/mixer_type.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2009 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_MIXER_TYPE_H
+#define MPD_MIXER_TYPE_H
+
+enum mixer_type {
+ /** parser error */
+ MIXER_TYPE_UNKNOWN,
+
+ /** mixer disabled */
+ MIXER_TYPE_NONE,
+
+ /** software mixer with pcm_volume() */
+ MIXER_TYPE_SOFTWARE,
+
+ /** hardware mixer (output's plugin) */
+ MIXER_TYPE_HARDWARE,
+};
+
+/**
+ * Parses a "mixer_type" setting from the configuration file.
+ *
+ * @param input the configured string value; must not be NULL
+ * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could
+ * not be parsed
+ */
+enum mixer_type
+mixer_type_parse(const char *input);
+
+#endif
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c
index 818c83ca2..64a8127ba 100644
--- a/src/output/alsa_plugin.c
+++ b/src/output/alsa_plugin.c
@@ -69,6 +69,16 @@ struct alsa_data {
/** the size of one audio frame */
size_t frame_size;
+
+ /**
+ * The size of one period, in number of frames.
+ */
+ snd_pcm_uframes_t period_frames;
+
+ /**
+ * The number of frames written in the current period.
+ */
+ snd_pcm_uframes_t period_position;
};
/**
@@ -183,6 +193,19 @@ get_bitformat(const struct audio_format *af)
return SND_PCM_FORMAT_UNKNOWN;
}
+static snd_pcm_format_t
+byteswap_bitformat(snd_pcm_format_t fmt)
+{
+ switch(fmt) {
+ case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
+ case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
+ case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
+ case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
+ case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
+ case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
+ default: return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
/**
* Set up the snd_pcm_t object which was opened by the caller. Set up
* the configured settings and the audio format.
@@ -208,7 +231,6 @@ alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
configure_hw:
/* configure HW params */
snd_pcm_hw_params_alloca(&hwparams);
-
cmd = "snd_pcm_hw_params_any";
err = snd_pcm_hw_params_any(ad->pcm, hwparams);
if (err < 0)
@@ -236,13 +258,38 @@ configure_hw:
}
err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, bitformat);
+ if (err == -EINVAL &&
+ byteswap_bitformat(bitformat) != SND_PCM_FORMAT_UNKNOWN) {
+ err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
+ byteswap_bitformat(bitformat));
+ if (err == 0) {
+ g_debug("ALSA device \"%s\": converting %u bit to reverse-endian\n",
+ alsa_device(ad), audio_format->bits);
+ audio_format->reverse_endian = 1;
+ }
+ }
if (err == -EINVAL && (audio_format->bits == 24 ||
audio_format->bits == 16)) {
/* fall back to 32 bit, let pcm_convert.c do the conversion */
err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
SND_PCM_FORMAT_S32);
- if (err == 0)
+ if (err == 0) {
+ g_debug("ALSA device \"%s\": converting %u bit to 32 bit\n",
+ alsa_device(ad), audio_format->bits);
+ audio_format->bits = 32;
+ }
+ }
+ if (err == -EINVAL && (audio_format->bits == 24 ||
+ audio_format->bits == 16)) {
+ /* fall back to 32 bit, let pcm_convert.c do the conversion */
+ err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
+ byteswap_bitformat(SND_PCM_FORMAT_S32));
+ if (err == 0) {
+ g_debug("ALSA device \"%s\": converting %u bit to 32 bit backward-endian\n",
+ alsa_device(ad), audio_format->bits);
audio_format->bits = 32;
+ audio_format->reverse_endian = 1;
+ }
}
if (err == -EINVAL && audio_format->bits != 16) {
@@ -255,6 +302,17 @@ configure_hw:
audio_format->bits = 16;
}
}
+ if (err == -EINVAL && audio_format->bits != 16) {
+ /* fall back to 16 bit, let pcm_convert.c do the conversion */
+ err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
+ byteswap_bitformat(SND_PCM_FORMAT_S16));
+ if (err == 0) {
+ g_debug("ALSA device \"%s\": converting %u bit to 16 bit backward-endian\n",
+ alsa_device(ad), audio_format->bits);
+ audio_format->bits = 16;
+ audio_format->reverse_endian = 1;
+ }
+ }
if (err < 0) {
g_set_error(error, alsa_output_quark(), err,
@@ -365,6 +423,9 @@ configure_hw:
g_debug("buffer_size=%u period_size=%u",
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
+ ad->period_frames = alsa_period_size;
+ ad->period_position = 0;
+
return true;
error:
@@ -431,6 +492,7 @@ alsa_recover(struct alsa_data *ad, int err)
/* fall-through to snd_pcm_prepare: */
case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN:
+ ad->period_position = 0;
err = snd_pcm_prepare(ad->pcm);
break;
case SND_PCM_STATE_DISCONNECTED:
@@ -448,11 +510,47 @@ alsa_recover(struct alsa_data *ad, int err)
}
static void
+alsa_drain(void *data)
+{
+ struct alsa_data *ad = data;
+
+ if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
+ return;
+
+ if (ad->period_position > 0) {
+ /* generate some silence to finish the partial
+ period */
+ snd_pcm_uframes_t nframes =
+ ad->period_frames - ad->period_position;
+ size_t nbytes = nframes * ad->frame_size;
+ void *buffer = g_malloc(nbytes);
+ snd_pcm_hw_params_t *params;
+ snd_pcm_format_t format;
+ unsigned channels;
+
+ snd_pcm_hw_params_alloca(&params);
+ snd_pcm_hw_params_current(ad->pcm, params);
+ snd_pcm_hw_params_get_format(params, &format);
+ snd_pcm_hw_params_get_channels(params, &channels);
+
+ snd_pcm_format_set_silence(format, buffer, nframes * channels);
+ ad->writei(ad->pcm, buffer, nframes);
+ g_free(buffer);
+ }
+
+ snd_pcm_drain(ad->pcm);
+
+ ad->period_position = 0;
+}
+
+static void
alsa_cancel(void *data)
{
struct alsa_data *ad = data;
- alsa_recover(ad, snd_pcm_drop(ad->pcm));
+ ad->period_position = 0;
+
+ snd_pcm_drop(ad->pcm);
}
static void
@@ -460,9 +558,6 @@ alsa_close(void *data)
{
struct alsa_data *ad = data;
- if (snd_pcm_state(ad->pcm) == SND_PCM_STATE_RUNNING)
- snd_pcm_drain(ad->pcm);
-
snd_pcm_close(ad->pcm);
}
@@ -475,8 +570,11 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error)
while (true) {
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
- if (ret > 0)
+ if (ret > 0) {
+ ad->period_position = (ad->period_position + ret)
+ % ad->period_frames;
return ret * ad->frame_size;
+ }
if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
alsa_recover(ad, ret) < 0) {
@@ -494,7 +592,9 @@ const struct audio_output_plugin alsaPlugin = {
.finish = alsa_finish,
.open = alsa_open,
.play = alsa_play,
+ .drain = alsa_drain,
.cancel = alsa_cancel,
.close = alsa_close,
- .mixer_plugin = &alsa_mixer,
+
+ .mixer_plugin = &alsa_mixer_plugin,
};
diff --git a/src/output/fifo_plugin.c b/src/output/fifo_output_plugin.c
index 76bbe8cfa..b5e6f5314 100644
--- a/src/output/fifo_plugin.c
+++ b/src/output/fifo_output_plugin.c
@@ -17,9 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../utils.h"
-#include "../timer.h"
+#include "output_api.h"
+#include "utils.h"
+#include "timer.h"
+#include "fd_util.h"
#include <glib.h>
@@ -152,7 +153,7 @@ fifo_open(struct fifo_data *fd, GError **error)
if (!fifo_check(fd, error))
return false;
- fd->input = open(fd->path, O_RDONLY|O_NONBLOCK);
+ fd->input = open_cloexec(fd->path, O_RDONLY|O_NONBLOCK, 0);
if (fd->input < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for reading: %s",
@@ -161,7 +162,7 @@ fifo_open(struct fifo_data *fd, GError **error)
return false;
}
- fd->output = open(fd->path, O_WRONLY|O_NONBLOCK);
+ fd->output = open_cloexec(fd->path, O_WRONLY|O_NONBLOCK, 0);
if (fd->output < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for writing: %s",
diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
index 52a398e3b..8157ebb44 100644
--- a/src/output/httpd_client.c
+++ b/src/output/httpd_client.c
@@ -482,11 +482,6 @@ httpd_client_queue_size(const struct httpd_client *client)
return size;
}
-/* g_queue_clear() was introduced in GLib 2.14 */
-#if !GLIB_CHECK_VERSION(2,14,0)
-#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
-#endif
-
void
httpd_client_cancel(struct httpd_client *client)
{
diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h
index 2257e27a2..22155b7ba 100644
--- a/src/output/httpd_internal.h
+++ b/src/output/httpd_internal.h
@@ -30,11 +30,18 @@
#include <glib.h>
#include <sys/socket.h>
+#include <stdbool.h>
struct httpd_client;
struct httpd_output {
/**
+ * True if the audio output is open and accepts client
+ * connections.
+ */
+ bool open;
+
+ /**
* The configured encoder plugin.
*/
struct encoder *encoder;
@@ -97,6 +104,12 @@ struct httpd_output {
* function.
*/
char buffer[32768];
+
+ /**
+ * The maximum and current number of clients connected
+ * at the same time.
+ */
+ guint clients_max, clients_cnt;
};
/**
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
index 9fdf46456..53bcc2deb 100644
--- a/src/output/httpd_output_plugin.c
+++ b/src/output/httpd_output_plugin.c
@@ -25,6 +25,7 @@
#include "socket_util.h"
#include "page.h"
#include "icy_server.h"
+#include "fd_util.h"
#include <assert.h>
@@ -46,6 +47,52 @@ httpd_output_quark(void)
return g_quark_from_static_string("httpd_output");
}
+static gboolean
+httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data);
+
+static bool
+httpd_output_bind(struct httpd_output *httpd, GError **error_r)
+{
+ GIOChannel *channel;
+
+ httpd->open = false;
+
+ /* create and set up listener socket */
+
+ httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
+ (struct sockaddr *)&httpd->address,
+ httpd->address_size,
+ 16, error_r);
+ if (httpd->fd < 0)
+ return false;
+
+ g_mutex_lock(httpd->mutex);
+
+ channel = g_io_channel_unix_new(httpd->fd);
+ httpd->source_id = g_io_add_watch(channel, G_IO_IN,
+ httpd_listen_in_event, httpd);
+ g_io_channel_unref(channel);
+
+ g_mutex_unlock(httpd->mutex);
+
+ return true;
+}
+
+static void
+httpd_output_unbind(struct httpd_output *httpd)
+{
+ assert(!httpd->open);
+
+ g_mutex_lock(httpd->mutex);
+
+ g_source_remove(httpd->source_id);
+ close(httpd->fd);
+
+ g_mutex_unlock(httpd->mutex);
+}
+
static void *
httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
const struct config_param *param,
@@ -76,6 +123,8 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
else
httpd->content_type = "application/octet-stream";
+ httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
+
/* initialize listen address */
sin = (struct sockaddr_in *)&httpd->address;
@@ -124,6 +173,7 @@ httpd_client_add(struct httpd_output *httpd, int fd)
httpd->encoder->plugin->tag == NULL);
httpd->clients = g_list_prepend(httpd->clients, client);
+ httpd->clients_cnt++;
/* pass metadata to client */
if (httpd->metadata)
@@ -138,18 +188,26 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
struct httpd_output *httpd = data;
int fd;
struct sockaddr_storage sa;
- socklen_t sa_length = sizeof(sa);
+ size_t sa_length = sizeof(sa);
g_mutex_lock(httpd->mutex);
/* the listener socket has become readable - a client has
connected */
- fd = accept(httpd->fd, (struct sockaddr*)&sa, &sa_length);
- if (fd >= 0)
- httpd_client_add(httpd, fd);
- else if (fd < 0 && errno != EINTR)
+ fd = accept_cloexec_nonblock(httpd->fd, (struct sockaddr*)&sa,
+ &sa_length);
+ if (fd >= 0) {
+ /* can we allow additional client */
+ if (httpd->open &&
+ (httpd->clients_max == 0 ||
+ httpd->clients_cnt < httpd->clients_max))
+ httpd_client_add(httpd, fd);
+ else
+ close(fd);
+ } else if (fd < 0 && errno != EINTR) {
g_warning("accept() failed: %s", g_strerror(errno));
+ }
g_mutex_unlock(httpd->mutex);
@@ -199,31 +257,30 @@ httpd_output_encoder_open(struct httpd_output *httpd,
}
static bool
+httpd_output_enable(void *data, GError **error_r)
+{
+ struct httpd_output *httpd = data;
+
+ return httpd_output_bind(httpd, error_r);
+}
+
+static void
+httpd_output_disable(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ httpd_output_unbind(httpd);
+}
+
+static bool
httpd_output_open(void *data, struct audio_format *audio_format,
GError **error)
{
struct httpd_output *httpd = data;
bool success;
- GIOChannel *channel;
g_mutex_lock(httpd->mutex);
- /* create and set up listener socket */
-
- httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
- (struct sockaddr *)&httpd->address,
- httpd->address_size,
- 16, error);
- if (httpd->fd < 0) {
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- channel = g_io_channel_unix_new(httpd->fd);
- httpd->source_id = g_io_add_watch(channel, G_IO_IN,
- httpd_listen_in_event, httpd);
- g_io_channel_unref(channel);
-
/* open the encoder */
success = httpd_output_encoder_open(httpd, audio_format, error);
@@ -237,8 +294,11 @@ httpd_output_open(void *data, struct audio_format *audio_format,
/* 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;
}
@@ -257,6 +317,8 @@ static void httpd_output_close(void *data)
g_mutex_lock(httpd->mutex);
+ httpd->open = false;
+
timer_free(httpd->timer);
g_list_foreach(httpd->clients, httpd_client_delete, NULL);
@@ -267,9 +329,6 @@ static void httpd_output_close(void *data)
encoder_close(httpd->encoder);
- g_source_remove(httpd->source_id);
- close(httpd->fd);
-
g_mutex_unlock(httpd->mutex);
}
@@ -281,6 +340,7 @@ httpd_output_remove_client(struct httpd_output *httpd,
assert(client != NULL);
httpd->clients = g_list_remove(httpd->clients, client);
+ httpd->clients_cnt--;
}
void
@@ -433,9 +493,8 @@ httpd_output_tag(void *data, const struct tag *tag)
page_unref (httpd->metadata);
httpd->metadata =
- icy_server_metadata_page(tag, TAG_ITEM_ALBUM,
- TAG_ITEM_ARTIST,
- TAG_ITEM_TITLE,
+ icy_server_metadata_page(tag, TAG_ALBUM,
+ TAG_ARTIST, TAG_TITLE,
TAG_NUM_OF_ITEM_TYPES);
if (httpd->metadata != NULL) {
g_mutex_lock(httpd->mutex);
@@ -468,6 +527,8 @@ 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,
.send_tag = httpd_output_tag,
diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c
new file mode 100644
index 000000000..b099cdb9e
--- /dev/null
+++ b/src/output/jack_output_plugin.c
@@ -0,0 +1,697 @@
+/*
+ * Copyright (C) 2003-2009 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 "output_api.h"
+#include "config.h"
+
+#include <assert.h>
+
+#include <glib.h>
+#include <jack/jack.h>
+#include <jack/types.h>
+#include <jack/ringbuffer.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "jack"
+
+enum {
+ MAX_PORTS = 16,
+};
+
+static const size_t sample_size = sizeof(jack_default_audio_sample_t);
+
+struct jack_data {
+ /**
+ * libjack options passed to jack_client_open().
+ */
+ jack_options_t options;
+
+ const char *name;
+
+ const char *server_name;
+
+ /* configuration */
+
+ char *source_ports[MAX_PORTS];
+ unsigned num_source_ports;
+
+ char *destination_ports[MAX_PORTS];
+ unsigned num_destination_ports;
+
+ size_t ringbuffer_size;
+
+ /* the current audio format */
+ struct audio_format audio_format;
+
+ /* jack library stuff */
+ jack_port_t *ports[MAX_PORTS];
+ jack_client_t *client;
+ jack_ringbuffer_t *ringbuffer[MAX_PORTS];
+
+ bool shutdown;
+
+ /**
+ * While this flag is set, the "process" callback generates
+ * silence.
+ */
+ bool pause;
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+jack_output_quark(void)
+{
+ return g_quark_from_static_string("jack_output");
+}
+
+static int
+mpd_jack_process(jack_nframes_t nframes, void *arg)
+{
+ struct jack_data *jd = (struct jack_data *) arg;
+ jack_default_audio_sample_t *out;
+ size_t available;
+
+ if (nframes <= 0)
+ return 0;
+
+ if (jd->pause) {
+ /* generate silence while MPD is paused */
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+ }
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ available = jack_ringbuffer_read_space(jd->ringbuffer[i]);
+ assert(available % sample_size == 0);
+ available /= sample_size;
+ if (available > nframes)
+ available = nframes;
+
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+ jack_ringbuffer_read(jd->ringbuffer[i],
+ (char *)out, available * sample_size);
+
+ while (available < nframes)
+ /* ringbuffer underrun, fill with silence */
+ out[available++] = 0.0;
+ }
+
+ /* generate silence for the unused source ports */
+
+ for (unsigned i = jd->audio_format.channels;
+ i < jd->num_source_ports; ++i) {
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+}
+
+static void
+mpd_jack_shutdown(void *arg)
+{
+ struct jack_data *jd = (struct jack_data *) arg;
+ jd->shutdown = true;
+}
+
+static void
+set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
+{
+ audio_format->sample_rate = jack_get_sample_rate(jd->client);
+
+ if (jd->num_source_ports == 1)
+ audio_format->channels = 1;
+ else if (audio_format->channels > jd->num_source_ports)
+ audio_format->channels = 2;
+
+ if (audio_format->bits != 16 && audio_format->bits != 24)
+ audio_format->bits = 24;
+}
+
+static void
+mpd_jack_error(const char *msg)
+{
+ g_warning("%s", msg);
+}
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+static void
+mpd_jack_info(const char *msg)
+{
+ g_message("%s", msg);
+}
+#endif
+
+/**
+ * Disconnect the JACK client.
+ */
+static void
+mpd_jack_disconnect(struct jack_data *jd)
+{
+ assert(jd != NULL);
+ assert(jd->client != NULL);
+
+ jack_deactivate(jd->client);
+ jack_client_close(jd->client);
+ jd->client = NULL;
+}
+
+/**
+ * Connect the JACK client and performs some basic setup
+ * (e.g. register callbacks).
+ */
+static bool
+mpd_jack_connect(struct jack_data *jd, GError **error_r)
+{
+ jack_status_t status;
+
+ assert(jd != NULL);
+
+ jd->shutdown = false;
+
+ jd->client = jack_client_open(jd->name, jd->options, &status,
+ jd->server_name);
+ if (jd->client == NULL) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Failed to connect to JACK server, status=%d",
+ status);
+ return false;
+ }
+
+ jack_set_process_callback(jd->client, mpd_jack_process, jd);
+ jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ jd->ports[i] = jack_port_register(jd->client,
+ jd->source_ports[i],
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ if (jd->ports[i] == NULL) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Cannot register output port \"%s\"",
+ jd->source_ports[i]);
+ mpd_jack_disconnect(jd);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+mpd_jack_test_default_device(void)
+{
+ return true;
+}
+
+static unsigned
+parse_port_list(int line, const char *source, char **dest, GError **error_r)
+{
+ char **list = g_strsplit(source, ",", 0);
+ unsigned n = 0;
+
+ for (n = 0; list[n] != NULL; ++n) {
+ if (n >= MAX_PORTS) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "too many port names in line %d",
+ line);
+ return 0;
+ }
+
+ dest[n] = list[n];
+ }
+
+ g_free(list);
+
+ if (n == 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "at least one port name expected in line %d",
+ line);
+ return 0;
+ }
+
+ return n;
+}
+
+static void *
+mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param, GError **error_r)
+{
+ struct jack_data *jd;
+ const char *value;
+
+ jd = g_new(struct jack_data, 1);
+ jd->options = JackNullOption;
+
+ jd->name = config_get_block_string(param, "client_name", NULL);
+ if (jd->name != NULL)
+ jd->options |= JackUseExactName;
+ else
+ /* if there's a no configured client name, we don't
+ care about the JackUseExactName option */
+ jd->name = "Music Player Daemon";
+
+ jd->server_name = config_get_block_string(param, "server_name", NULL);
+ if (jd->server_name != NULL)
+ jd->options |= JackServerName;
+
+ if (!config_get_block_bool(param, "autostart", false))
+ jd->options |= JackNoStartServer;
+
+ /* configure the source ports */
+
+ value = config_get_block_string(param, "source_ports", "left,right");
+ jd->num_source_ports = parse_port_list(param->line, value,
+ jd->source_ports, error_r);
+ if (jd->num_source_ports == 0)
+ return NULL;
+
+ /* configure the destination ports */
+
+ value = config_get_block_string(param, "destination_ports", NULL);
+ if (value == NULL) {
+ /* compatibility with MPD < 0.16 */
+ value = config_get_block_string(param, "ports", NULL);
+ if (value != NULL)
+ g_warning("deprecated option 'ports' in line %d",
+ param->line);
+ }
+
+ if (value != NULL) {
+ jd->num_destination_ports =
+ parse_port_list(param->line, value,
+ jd->destination_ports, error_r);
+ if (jd->num_destination_ports == 0)
+ return NULL;
+ } else {
+ jd->num_destination_ports = 0;
+ }
+
+ if (jd->num_destination_ports > 0 &&
+ jd->num_destination_ports != jd->num_source_ports)
+ g_warning("number of source ports (%u) mismatches the "
+ "number of destination ports (%u) in line %d",
+ jd->num_source_ports, jd->num_destination_ports,
+ param->line);
+
+ jd->ringbuffer_size =
+ config_get_block_unsigned(param, "ringbuffer_size", 32768);
+
+ jack_set_error_function(mpd_jack_error);
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+ jack_set_info_function(mpd_jack_info);
+#endif
+
+ return jd;
+}
+
+static void
+mpd_jack_finish(void *data)
+{
+ struct jack_data *jd = data;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ g_free(jd->source_ports[i]);
+
+ for (unsigned i = 0; i < jd->num_destination_ports; ++i)
+ g_free(jd->destination_ports[i]);
+
+ g_free(jd);
+}
+
+static bool
+mpd_jack_enable(void *data, GError **error_r)
+{
+ struct jack_data *jd = (struct jack_data *)data;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ jd->ringbuffer[i] = NULL;
+
+ return mpd_jack_connect(jd, error_r);
+}
+
+static void
+mpd_jack_disable(void *data)
+{
+ struct jack_data *jd = (struct jack_data *)data;
+
+ if (jd->client != NULL)
+ mpd_jack_disconnect(jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] != NULL) {
+ jack_ringbuffer_free(jd->ringbuffer[i]);
+ jd->ringbuffer[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Stops the playback on the JACK connection.
+ */
+static void
+mpd_jack_stop(struct jack_data *jd)
+{
+ assert(jd != NULL);
+
+ if (jd->client == NULL)
+ return;
+
+ if (jd->shutdown)
+ /* the connection has failed; close it */
+ mpd_jack_disconnect(jd);
+ else
+ /* the connection is alive: just stop playback */
+ jack_deactivate(jd->client);
+}
+
+static bool
+mpd_jack_start(struct jack_data *jd, GError **error_r)
+{
+ const char *destination_ports[MAX_PORTS], **jports;
+ const char *duplicate_port = NULL;
+ unsigned num_destination_ports;
+
+ assert(jd->client != NULL);
+ assert(jd->audio_format.channels <= jd->num_source_ports);
+
+ /* allocate the ring buffers on the first open(); these
+ persist until MPD exits. It's too unsafe to delete them
+ because we can never know when mpd_jack_process() gets
+ called */
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] == NULL)
+ jd->ringbuffer[i] =
+ jack_ringbuffer_create(jd->ringbuffer_size);
+
+ /* clear the ring buffer to be sure that data from
+ previous playbacks are gone */
+ jack_ringbuffer_reset(jd->ringbuffer[i]);
+ }
+
+ if ( jack_activate(jd->client) ) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "cannot activate client");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ if (jd->num_destination_ports == 0) {
+ /* no output ports were configured - ask libjack for
+ defaults */
+ jports = jack_get_ports(jd->client, NULL, NULL,
+ JackPortIsPhysical | JackPortIsInput);
+ if (jports == NULL) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "no ports found");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ assert(*jports != NULL);
+
+ for (num_destination_ports = 0;
+ num_destination_ports < MAX_PORTS &&
+ jports[num_destination_ports] != NULL;
+ ++num_destination_ports) {
+ g_debug("destination_port[%u] = '%s'\n",
+ num_destination_ports,
+ jports[num_destination_ports]);
+ destination_ports[num_destination_ports] =
+ jports[num_destination_ports];
+ }
+ } else {
+ /* use the configured output ports */
+
+ num_destination_ports = jd->num_destination_ports;
+ memcpy(destination_ports, jd->destination_ports,
+ num_destination_ports * sizeof(*destination_ports));
+
+ jports = NULL;
+ }
+
+ assert(num_destination_ports > 0);
+
+ if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
+ /* mix stereo signal on one speaker */
+
+ while (num_destination_ports < jd->audio_format.channels)
+ destination_ports[num_destination_ports++] =
+ destination_ports[0];
+ } else if (num_destination_ports > jd->audio_format.channels) {
+ if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
+ /* mono input file: connect the one source
+ channel to the both destination channels */
+ duplicate_port = destination_ports[1];
+ num_destination_ports = 1;
+ } else
+ /* connect only as many ports as we need */
+ num_destination_ports = jd->audio_format.channels;
+ }
+
+ assert(num_destination_ports <= jd->num_source_ports);
+
+ for (unsigned i = 0; i < num_destination_ports; ++i) {
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
+ destination_ports[i]);
+ if (ret != 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Not a valid JACK port: %s",
+ destination_ports[i]);
+
+ if (jports != NULL)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (duplicate_port != NULL) {
+ /* mono input file: connect the one source channel to
+ the both destination channels */
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
+ duplicate_port);
+ if (ret != 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Not a valid JACK port: %s",
+ duplicate_port);
+
+ if (jports != NULL)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (jports != NULL)
+ free(jports);
+
+ return true;
+}
+
+static bool
+mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r)
+{
+ struct jack_data *jd = data;
+
+ assert(jd != NULL);
+
+ jd->pause = false;
+
+ if (jd->client == NULL && !mpd_jack_connect(jd, error_r))
+ return false;
+
+ set_audioformat(jd, audio_format);
+ jd->audio_format = *audio_format;
+
+ if (!mpd_jack_start(jd, error_r))
+ return false;
+
+ return true;
+}
+
+static void
+mpd_jack_close(G_GNUC_UNUSED void *data)
+{
+ struct jack_data *jd = data;
+
+ mpd_jack_stop(jd);
+}
+
+static inline jack_default_audio_sample_t
+sample_16_to_jack(int16_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
+}
+
+static void
+mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_16_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static inline jack_default_audio_sample_t
+sample_24_to_jack(int32_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
+}
+
+static void
+mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_24_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static void
+mpd_jack_write_samples(struct jack_data *jd, const void *src,
+ unsigned num_samples)
+{
+ switch (jd->audio_format.bits) {
+ case 16:
+ mpd_jack_write_samples_16(jd, (const int16_t*)src,
+ num_samples);
+ break;
+
+ case 24:
+ mpd_jack_write_samples_24(jd, (const int32_t*)src,
+ num_samples);
+ break;
+
+ default:
+ assert(false);
+ }
+}
+
+static size_t
+mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct jack_data *jd = data;
+ const size_t frame_size = audio_format_frame_size(&jd->audio_format);
+ size_t space = 0, space1;
+
+ jd->pause = false;
+
+ assert(size % frame_size == 0);
+ size /= frame_size;
+
+ while (true) {
+ if (jd->shutdown) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Refusing to play, because "
+ "there is no client thread");
+ return 0;
+ }
+
+ space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
+ if (space > space1)
+ /* send data symmetrically */
+ space = space1;
+ }
+
+ if (space >= frame_size)
+ break;
+
+ /* XXX do something more intelligent to
+ synchronize */
+ g_usleep(1000);
+ }
+
+ space /= sample_size;
+ if (space < size)
+ size = space;
+
+ mpd_jack_write_samples(jd, chunk, size);
+ return size * frame_size;
+}
+
+static bool
+mpd_jack_pause(void *data)
+{
+ struct jack_data *jd = data;
+
+ if (jd->shutdown)
+ return false;
+
+ jd->pause = true;
+
+ /* due to a MPD API limitation, we have to sleep a little bit
+ here, to avoid hogging the CPU */
+ g_usleep(50000);
+
+ return true;
+}
+
+const struct audio_output_plugin jack_output_plugin = {
+ .name = "jack",
+ .test_default_device = mpd_jack_test_default_device,
+ .init = mpd_jack_init,
+ .finish = mpd_jack_finish,
+ .enable = mpd_jack_enable,
+ .disable = mpd_jack_disable,
+ .open = mpd_jack_open,
+ .play = mpd_jack_play,
+ .pause = mpd_jack_pause,
+ .close = mpd_jack_close,
+};
diff --git a/src/output/jack_plugin.c b/src/output/jack_plugin.c
deleted file mode 100644
index 5dc1eca24..000000000
--- a/src/output/jack_plugin.c
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- * Copyright (C) 2003-2009 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 "../output_api.h"
-#include "config.h"
-
-#include <assert.h>
-
-#include <glib.h>
-#include <jack/jack.h>
-#include <jack/types.h>
-#include <jack/ringbuffer.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "jack"
-
-static const size_t sample_size = sizeof(jack_default_audio_sample_t);
-
-static const char *const port_names[2] = {
- "left", "right",
-};
-
-struct jack_data {
- const char *name;
-
- /* configuration */
- char *output_ports[2];
- int ringbuffer_size;
-
- /* the current audio format */
- struct audio_format audio_format;
-
- /* jack library stuff */
- jack_port_t *ports[2];
- jack_client_t *client;
- jack_ringbuffer_t *ringbuffer[2];
-
- bool shutdown;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-jack_output_quark(void)
-{
- return g_quark_from_static_string("jack_output");
-}
-
-static void
-mpd_jack_client_free(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- if (jd->client != NULL) {
- jack_deactivate(jd->client);
- jack_client_close(jd->client);
- jd->client = NULL;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
- if (jd->ringbuffer[i] != NULL) {
- jack_ringbuffer_free(jd->ringbuffer[i]);
- jd->ringbuffer[i] = NULL;
- }
- }
-}
-
-static void
-mpd_jack_free(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->output_ports); ++i)
- g_free(jd->output_ports[i]);
-
- g_free(jd);
-}
-
-static void
-mpd_jack_finish(void *data)
-{
- struct jack_data *jd = data;
- mpd_jack_free(jd);
-}
-
-static int
-mpd_jack_process(jack_nframes_t nframes, void *arg)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jack_default_audio_sample_t *out;
- size_t available;
-
- if (nframes <= 0)
- return 0;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
- available = jack_ringbuffer_read_space(jd->ringbuffer[i]);
- assert(available % sample_size == 0);
- available /= sample_size;
- if (available > nframes)
- available = nframes;
-
- out = jack_port_get_buffer(jd->ports[i], nframes);
- jack_ringbuffer_read(jd->ringbuffer[i],
- (char *)out, available * sample_size);
-
- while (available < nframes)
- /* ringbuffer underrun, fill with silence */
- out[available++] = 0.0;
- }
-
- return 0;
-}
-
-static void
-mpd_jack_shutdown(void *arg)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jd->shutdown = true;
-}
-
-static void
-set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
-{
- audio_format->sample_rate = jack_get_sample_rate(jd->client);
- audio_format->channels = 2;
-
- if (audio_format->bits != 16 && audio_format->bits != 24)
- audio_format->bits = 24;
-}
-
-static void
-mpd_jack_error(const char *msg)
-{
- g_warning("%s", msg);
-}
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
-static void
-mpd_jack_info(const char *msg)
-{
- g_message("%s", msg);
-}
-#endif
-
-static void *
-mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, GError **error)
-{
- struct jack_data *jd;
- const char *value;
-
- jd = g_new(struct jack_data, 1);
- jd->name = config_get_block_string(param, "name", "mpd_jack");
-
- g_debug("mpd_jack_init (pid=%d)", getpid());
-
- value = config_get_block_string(param, "ports", NULL);
- if (value != NULL) {
- char **ports = g_strsplit(value, ",", 0);
-
- if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "two port names expected in line %d",
- param->line);
- return NULL;
- }
-
- jd->output_ports[0] = ports[0];
- jd->output_ports[1] = ports[1];
-
- g_free(ports);
- } else {
- jd->output_ports[0] = NULL;
- jd->output_ports[1] = NULL;
- }
-
- jd->ringbuffer_size =
- config_get_block_unsigned(param, "ringbuffer_size", 32768);
-
- jack_set_error_function(mpd_jack_error);
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
- jack_set_info_function(mpd_jack_info);
-#endif
-
- return jd;
-}
-
-static bool
-mpd_jack_test_default_device(void)
-{
- return true;
-}
-
-static bool
-mpd_jack_connect(struct jack_data *jd, GError **error)
-{
- const char *output_ports[2], **jports;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i)
- jd->ringbuffer[i] =
- jack_ringbuffer_create(jd->ringbuffer_size);
-
- jd->shutdown = false;
-
- if ((jd->client = jack_client_new(jd->name)) == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "Failed to connect to JACK server");
- return false;
- }
-
- jack_set_process_callback(jd->client, mpd_jack_process, jd);
- jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
- jd->ports[i] = jack_port_register(jd->client, port_names[i],
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput, 0);
- if (jd->ports[i] == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "Cannot register output port \"%s\"",
- port_names[i]);
- return false;
- }
- }
-
- if ( jack_activate(jd->client) ) {
- g_set_error(error, jack_output_quark(), 0,
- "cannot activate client");
- return false;
- }
-
- if (jd->output_ports[1] == NULL) {
- /* no output ports were configured - ask libjack for
- defaults */
- jports = jack_get_ports(jd->client, NULL, NULL,
- JackPortIsPhysical | JackPortIsInput);
- if (jports == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "no ports found");
- return false;
- }
-
- output_ports[0] = jports[0];
- output_ports[1] = jports[1] != NULL ? jports[1] : jports[0];
-
- g_debug("output_ports: %s %s", jports[0], jports[1]);
- } else {
- /* use the configured output ports */
-
- output_ports[0] = jd->output_ports[0];
- output_ports[1] = jd->output_ports[1];
-
- jports = NULL;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
- int ret;
-
- ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
- output_ports[i]);
- if (ret != 0) {
- g_set_error(error, jack_output_quark(), 0,
- "Not a valid JACK port: %s",
- output_ports[i]);
-
- if (jports != NULL)
- free(jports);
-
- return false;
- }
- }
-
- if (jports != NULL)
- free(jports);
-
- return true;
-}
-
-static bool
-mpd_jack_open(void *data, struct audio_format *audio_format, GError **error)
-{
- struct jack_data *jd = data;
-
- assert(jd != NULL);
-
- if (!mpd_jack_connect(jd, error)) {
- mpd_jack_client_free(jd);
- return false;
- }
-
- set_audioformat(jd, audio_format);
- jd->audio_format = *audio_format;
-
- return true;
-}
-
-static void
-mpd_jack_close(G_GNUC_UNUSED void *data)
-{
- struct jack_data *jd = data;
-
- mpd_jack_client_free(jd);
-}
-
-static void
-mpd_jack_cancel (G_GNUC_UNUSED void *data)
-{
-}
-
-static inline jack_default_audio_sample_t
-sample_16_to_jack(int16_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
-}
-
-static void
-mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
-
- while (num_samples-- > 0) {
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
- sizeof(sample));
- }
-}
-
-static inline jack_default_audio_sample_t
-sample_24_to_jack(int32_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
-}
-
-static void
-mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
-
- while (num_samples-- > 0) {
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
- sizeof(sample));
- }
-}
-
-static void
-mpd_jack_write_samples(struct jack_data *jd, const void *src,
- unsigned num_samples)
-{
- switch (jd->audio_format.bits) {
- case 16:
- mpd_jack_write_samples_16(jd, (const int16_t*)src,
- num_samples);
- break;
-
- case 24:
- mpd_jack_write_samples_24(jd, (const int32_t*)src,
- num_samples);
- break;
-
- default:
- assert(false);
- }
-}
-
-static size_t
-mpd_jack_play(void *data, const void *chunk, size_t size, GError **error)
-{
- struct jack_data *jd = data;
- const size_t frame_size = audio_format_frame_size(&jd->audio_format);
- size_t space = 0, space1;
-
- assert(size % frame_size == 0);
- size /= frame_size;
-
- while (true) {
- if (jd->shutdown) {
- g_set_error(error, jack_output_quark(), 0,
- "Refusing to play, because "
- "there is no client thread");
- return 0;
- }
-
- space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
- space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]);
- if (space > space1)
- /* send data symmetrically */
- space = space1;
-
- if (space >= frame_size)
- break;
-
- /* XXX do something more intelligent to
- synchronize */
- g_usleep(1000);
- }
-
- space /= sample_size;
- if (space < size)
- size = space;
-
- mpd_jack_write_samples(jd, chunk, size);
- return size * frame_size;
-}
-
-const struct audio_output_plugin jackPlugin = {
- .name = "jack",
- .test_default_device = mpd_jack_test_default_device,
- .init = mpd_jack_init,
- .finish = mpd_jack_finish,
- .open = mpd_jack_open,
- .play = mpd_jack_play,
- .cancel = mpd_jack_cancel,
- .close = mpd_jack_close,
-};
diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c
index 96f9435a8..7e6dd6d31 100644
--- a/src/output/mvp_plugin.c
+++ b/src/output/mvp_plugin.c
@@ -22,7 +22,8 @@
* http://mvpmc.sourceforge.net/
*/
-#include "../output_api.h"
+#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -115,7 +116,7 @@ mvp_output_test_default_device(void)
{
int fd;
- fd = open("/dev/adec_pcm", O_WRONLY);
+ fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0);
if (fd >= 0) {
close(fd);
@@ -230,7 +231,8 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
int mix[5] = { 0, 2, 7, 1, 0 };
bool success;
- if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) {
+ md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0);
+ if (md->fd < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error opening /dev/adec_pcm: %s",
strerror(errno));
diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c
new file mode 100644
index 000000000..92ee82ef3
--- /dev/null
+++ b/src/output/openal_plugin.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2003-2009 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 "../output_api.h"
+#include "../timer.h"
+#include "config.h"
+
+#include <glib.h>
+
+#ifndef HAVE_OSX
+#include <AL/al.h>
+#include <AL/alc.h>
+#else
+#include <OpenAL/al.h>
+#include <OpenAL/alc.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "openal"
+
+/* should be enough for buffer size = 2048 */
+#define NUM_BUFFERS 16
+
+struct openal_data {
+ const char *device_name;
+ ALCdevice *device;
+ ALCcontext *context;
+ Timer *timer;
+ ALuint buffers[NUM_BUFFERS];
+ int filled;
+ ALuint source;
+ ALenum format;
+ ALuint frequency;
+};
+
+static inline GQuark
+openal_output_quark(void)
+{
+ return g_quark_from_static_string("openal_output");
+}
+
+static ALenum
+openal_audio_format(struct audio_format *audio_format)
+{
+ /* Only 8 and 16 bit samples are supported */
+ if (audio_format->bits != 16 && audio_format->bits != 8)
+ audio_format->bits = 16;
+
+ switch (audio_format->bits)
+ {
+ case 16:
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO16;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO16;
+ break;
+
+ case 8:
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO8;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO8;
+ break;
+ }
+
+ return 0;
+}
+
+static bool
+openal_setup_context(struct openal_data *od,
+ GError **error)
+{
+ od->device = alcOpenDevice(od->device_name);
+
+ if (od->device == NULL) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Error opening OpenAL device \"%s\"\n",
+ od->device_name);
+ return false;
+ }
+
+ od->context = alcCreateContext(od->device, NULL);
+
+ if (od->context == NULL) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Error creating context for \"%s\"\n",
+ od->device_name);
+ alcCloseDevice(od->device);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+openal_unqueue_buffers(struct openal_data *od)
+{
+ ALint num;
+ ALuint buffer;
+
+ alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num);
+
+ while (num--) {
+ alSourceUnqueueBuffers(od->source, 1, &buffer);
+ }
+}
+
+static void *
+openal_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ const char *device_name = config_get_block_string(param, "device", NULL);
+ struct openal_data *od;
+
+ if (device_name == NULL) {
+ device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
+ }
+
+ od = g_new(struct openal_data, 1);
+ od->device_name = device_name;
+
+ return od;
+}
+
+static void
+openal_finish(void *data)
+{
+ struct openal_data *od = data;
+
+ g_free(od);
+}
+
+static bool
+openal_open(void *data, struct audio_format *audio_format,
+ GError **error)
+{
+ struct openal_data *od = data;
+
+ od->format = openal_audio_format(audio_format);
+
+ if (!od->format) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Unsupported audio format (%i channels, %i bps)",
+ audio_format->channels,
+ audio_format->bits);
+ return false;
+ }
+
+ if (!openal_setup_context(od, error)) {
+ return false;
+ }
+
+ alcMakeContextCurrent(od->context);
+ alGenBuffers(NUM_BUFFERS, od->buffers);
+
+ if (alGetError() != AL_NO_ERROR) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Failed to generate buffers");
+ return false;
+ }
+
+ alGenSources(1, &od->source);
+
+ if (alGetError() != AL_NO_ERROR) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Failed to generate source");
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ return false;
+ }
+
+ od->filled = 0;
+ od->timer = timer_new(audio_format);
+ od->frequency = audio_format->sample_rate;
+
+ return true;
+}
+
+static void
+openal_close(void *data)
+{
+ struct openal_data *od = data;
+
+ timer_free(od->timer);
+ alcMakeContextCurrent(od->context);
+ alDeleteSources(1, &od->source);
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ alcDestroyContext(od->context);
+ alcCloseDevice(od->device);
+}
+
+static size_t
+openal_play(void *data, const void *chunk, size_t size,
+ G_GNUC_UNUSED GError **error)
+{
+ struct openal_data *od = data;
+ ALuint buffer;
+ ALint num, state;
+
+ if (alcGetCurrentContext() != od->context) {
+ alcMakeContextCurrent(od->context);
+ }
+
+ alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
+
+ if (od->filled < NUM_BUFFERS) {
+ /* fill all buffers */
+ buffer = od->buffers[od->filled];
+ od->filled++;
+ } else {
+ /* wait for processed buffer */
+ while (num < 1) {
+ if (!od->timer->started) {
+ timer_start(od->timer);
+ } else {
+ timer_sync(od->timer);
+ }
+
+ timer_add(od->timer, size);
+
+ alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
+ }
+
+ alSourceUnqueueBuffers(od->source, 1, &buffer);
+ }
+
+ alBufferData(buffer, od->format, chunk, size, od->frequency);
+ alSourceQueueBuffers(od->source, 1, &buffer);
+ alGetSourcei(od->source, AL_SOURCE_STATE, &state);
+
+ if (state != AL_PLAYING) {
+ alSourcePlay(od->source);
+ }
+
+ return size;
+}
+
+static void
+openal_cancel(void *data)
+{
+ struct openal_data *od = data;
+
+ od->filled = 0;
+ alcMakeContextCurrent(od->context);
+ alSourceStop(od->source);
+ openal_unqueue_buffers(od);
+}
+
+const struct audio_output_plugin openal_output_plugin = {
+ .name = "openal",
+ .init = openal_init,
+ .finish = openal_finish,
+ .open = openal_open,
+ .close = openal_close,
+ .play = openal_play,
+ .cancel = openal_cancel,
+};
diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c
index a66bc0598..6518c3f49 100644
--- a/src/output/oss_plugin.c
+++ b/src/output/oss_plugin.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "output_api.h"
#include "mixer_list.h"
+#include "fd_util.h"
#include <glib.h>
@@ -343,7 +344,9 @@ oss_output_test_default_device(void)
int fd, i;
for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
- if ((fd = open(default_devices[i], O_WRONLY)) >= 0) {
+ fd = open_cloexec(default_devices[i], O_WRONLY, 0);
+
+ if (fd >= 0) {
close(fd);
return true;
}
@@ -516,7 +519,8 @@ oss_open(struct oss_data *od, GError **error)
{
bool success;
- if ((od->fd = open(od->device, O_WRONLY)) < 0) {
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
g_set_error(error, oss_output_quark(), errno,
"Error opening OSS device \"%s\": %s",
od->device, strerror(errno));
@@ -601,5 +605,6 @@ const struct audio_output_plugin oss_output_plugin = {
.close = oss_output_close,
.play = oss_output_play,
.cancel = oss_output_cancel,
- .mixer_plugin = &oss_mixer,
+
+ .mixer_plugin = &oss_mixer_plugin,
};
diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c
new file mode 100644
index 000000000..13e1b6624
--- /dev/null
+++ b/src/output/pulse_output_plugin.c
@@ -0,0 +1,823 @@
+/*
+ * Copyright (C) 2003-2009 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 "pulse_output_plugin.h"
+#include "output_api.h"
+#include "mixer_list.h"
+#include "mixer/pulse_mixer_plugin.h"
+
+#include <glib.h>
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/introspect.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+
+#include <assert.h>
+
+#define MPD_PULSE_NAME "Music Player Daemon"
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_output_quark(void)
+{
+ return g_quark_from_static_string("pulse_output");
+}
+
+void
+pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm)
+{
+ assert(po != NULL);
+ assert(po->mixer == NULL);
+ assert(pm != NULL);
+
+ po->mixer = pm;
+
+ if (po->mainloop == NULL)
+ return;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context != NULL &&
+ pa_context_get_state(po->context) == PA_CONTEXT_READY) {
+ pulse_mixer_on_connect(pm, po->context);
+
+ if (po->stream != NULL &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY)
+ pulse_mixer_on_change(pm, po->context, po->stream);
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+void
+pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm)
+{
+ assert(po != NULL);
+ assert(pm != NULL);
+ assert(po->mixer == pm);
+
+ po->mixer = NULL;
+}
+
+bool
+pulse_output_set_volume(struct pulse_output *po,
+ const struct pa_cvolume *volume, GError **error_r)
+{
+ pa_operation *o;
+
+ if (po->context == NULL || po->stream == NULL ||
+ pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ g_set_error(error_r, pulse_output_quark(), 0, "disconnected");
+ return false;
+ }
+
+ o = pa_context_set_sink_input_volume(po->context,
+ pa_stream_get_index(po->stream),
+ volume, NULL, NULL);
+ if (o == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to set PulseAudio volume: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ pa_operation_unref(o);
+ return true;
+}
+
+/**
+ * \brief waits for a pulseaudio operation to finish, frees it and
+ * unlocks the mainloop
+ * \param operation the operation to wait for
+ * \return true if operation has finished normally (DONE state),
+ * false otherwise
+ */
+static bool
+pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
+ struct pa_operation *operation)
+{
+ pa_operation_state_t state;
+
+ assert(mainloop != NULL);
+ assert(operation != NULL);
+
+ state = pa_operation_get_state(operation);
+ while (state == PA_OPERATION_RUNNING) {
+ pa_threaded_mainloop_wait(mainloop);
+ state = pa_operation_get_state(operation);
+ }
+
+ pa_operation_unref(operation);
+
+ return state == PA_OPERATION_DONE;
+}
+
+/**
+ * Callback function for stream operation. It just sends a signal to
+ * the caller thread, to wake pulse_wait_for_operation() up.
+ */
+static void
+pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s,
+ G_GNUC_UNUSED int success, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static void
+pulse_output_context_state_cb(struct pa_context *context, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ switch (pa_context_get_state(context)) {
+ case PA_CONTEXT_READY:
+ if (po->mixer != NULL)
+ pulse_mixer_on_connect(po->mixer, context);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ if (po->mixer != NULL)
+ pulse_mixer_on_disconnect(po->mixer);
+
+ /* the caller thread might be waiting for these
+ states */
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+static void
+pulse_output_subscribe_cb(pa_context *context,
+ pa_subscription_event_type_t t,
+ uint32_t idx, void *userdata)
+{
+ struct pulse_output *po = userdata;
+ pa_subscription_event_type_t facility
+ = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
+ pa_subscription_event_type_t type
+ = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
+
+ if (po->mixer != NULL &&
+ facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
+ po->stream != NULL &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY &&
+ idx == pa_stream_get_index(po->stream) &&
+ (type == PA_SUBSCRIPTION_EVENT_NEW ||
+ type == PA_SUBSCRIPTION_EVENT_CHANGE))
+ pulse_mixer_on_change(po->mixer, context, po->stream);
+}
+
+/**
+ * Attempt to connect asynchronously to the PulseAudio server.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_connect(struct pulse_output *po, GError **error_r)
+{
+ int error;
+
+ error = pa_context_connect(po->context, po->server,
+ (pa_context_flags_t)0, NULL);
+ if (error < 0) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_context_connect() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_context(struct pulse_output *po, GError **error_r)
+{
+ po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
+ MPD_PULSE_NAME);
+ if (po->context == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_context_new() has failed");
+ return false;
+ }
+
+ pa_context_set_state_callback(po->context,
+ pulse_output_context_state_cb, po);
+ pa_context_set_subscribe_callback(po->context,
+ pulse_output_subscribe_cb, po);
+
+ if (!pulse_output_connect(po, error_r)) {
+ pa_context_unref(po->context);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Frees and clears the context.
+ */
+static void
+pulse_output_delete_context(struct pulse_output *po)
+{
+ pa_context_disconnect(po->context);
+ pa_context_unref(po->context);
+ po->context = NULL;
+}
+
+static void *
+pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct pulse_output *po;
+
+ g_setenv("PULSE_PROP_media.role", "music", true);
+
+ po = g_new(struct pulse_output, 1);
+ po->name = config_get_block_string(param, "name", "mpd_pulse");
+ po->server = config_get_block_string(param, "server", NULL);
+ po->sink = config_get_block_string(param, "sink", NULL);
+
+ po->mixer = NULL;
+ po->mainloop = NULL;
+ po->context = NULL;
+ po->stream = NULL;
+
+ return po;
+}
+
+static void
+pulse_output_finish(void *data)
+{
+ struct pulse_output *po = data;
+
+ g_free(po);
+}
+
+static bool
+pulse_output_enable(void *data, GError **error_r)
+{
+ struct pulse_output *po = data;
+
+ assert(po->mainloop == NULL);
+ assert(po->context == NULL);
+
+ /* create the libpulse mainloop and start the thread */
+
+ po->mainloop = pa_threaded_mainloop_new();
+ if (po->mainloop == NULL) {
+ g_free(po);
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_threaded_mainloop_new() has failed");
+ return false;
+ }
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_threaded_mainloop_start(po->mainloop) < 0) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ g_free(po);
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_threaded_mainloop_start() has failed");
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ /* create the libpulse context and connect it */
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (!pulse_output_setup_context(po, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_stop(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ g_free(po);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static void
+pulse_output_disable(void *data)
+{
+ struct pulse_output *po = data;
+
+ pa_threaded_mainloop_stop(po->mainloop);
+ if (po->context != NULL)
+ pulse_output_delete_context(po);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = NULL;
+}
+
+/**
+ * Check if the context is (already) connected, and waits if not. If
+ * the context has been disconnected, retry to connect.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
+{
+ pa_context_state_t state;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context == NULL && !pulse_output_setup_context(po, error_r))
+ return false;
+
+ while (true) {
+ state = pa_context_get_state(po->context);
+ switch (state) {
+ case PA_CONTEXT_READY:
+ /* nothing to do */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return true;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* failure */
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to connect: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pulse_output_delete_context(po);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ /* wait some more */
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+static void
+pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ switch (pa_stream_get_state(stream)) {
+ case PA_STREAM_READY:
+ if (po->mixer != NULL)
+ pulse_mixer_on_change(po->mixer, po->context, stream);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ if (po->mixer != NULL)
+ pulse_mixer_on_disconnect(po->mixer);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ }
+}
+
+static void
+pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes,
+ void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ po->writable = nbytes;
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static bool
+pulse_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct pulse_output *po = data;
+ pa_sample_spec ss;
+ int error;
+
+ if (po->context != NULL) {
+ switch (pa_context_get_state(po->context)) {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* the connection was closed meanwhile; delete
+ it, and pulse_output_wait_connection() will
+ reopen it */
+ pulse_output_delete_context(po);
+ break;
+
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+ }
+
+ if (!pulse_output_wait_connection(po, error_r))
+ return false;
+
+ /* MPD doesn't support the other pulseaudio sample formats, so
+ we just force MPD to send us everything as 16 bit */
+ audio_format->bits = 16;
+
+ ss.format = PA_SAMPLE_S16NE;
+ ss.rate = audio_format->sample_rate;
+ ss.channels = audio_format->channels;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* create a stream .. */
+
+ po->stream = pa_stream_new(po->context, po->name, &ss, NULL);
+ if (po->stream == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_new() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ pa_stream_set_state_callback(po->stream,
+ pulse_output_stream_state_cb, po);
+ pa_stream_set_write_callback(po->stream,
+ pulse_output_stream_write_cb, po);
+
+ /* .. and connect it (asynchronously) */
+
+ error = pa_stream_connect_playback(po->stream, po->sink,
+ NULL, 0, NULL, NULL);
+ if (error < 0) {
+ pa_stream_unref(po->stream);
+ po->stream = NULL;
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_connect_playback() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+#if !PA_CHECK_VERSION(0,9,11)
+ po->pause = false;
+#endif
+
+ return true;
+}
+
+static void
+pulse_output_close(void *data)
+{
+ struct pulse_output *po = data;
+ pa_operation *o;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
+ o = pa_stream_drain(po->stream,
+ pulse_output_stream_success_cb, po);
+ if (o == NULL) {
+ g_warning("pa_stream_drain() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ } else
+ pulse_wait_for_operation(po->mainloop, o);
+ }
+
+ pa_stream_disconnect(po->stream);
+ pa_stream_unref(po->stream);
+ po->stream = NULL;
+
+ if (po->context != NULL &&
+ pa_context_get_state(po->context) != PA_CONTEXT_READY)
+ pulse_output_delete_context(po);
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+/**
+ * Check if the stream is (already) connected, and waits for a signal
+ * if not. The mainloop must be locked before calling this function.
+ *
+ * @return the current stream state
+ */
+static pa_stream_state_t
+pulse_output_check_stream(struct pulse_output *po)
+{
+ pa_stream_state_t state = pa_stream_get_state(po->stream);
+
+ switch (state) {
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ break;
+
+ case PA_STREAM_CREATING:
+ pa_threaded_mainloop_wait(po->mainloop);
+ state = pa_stream_get_state(po->stream);
+ break;
+ }
+
+ return state;
+}
+
+/**
+ * Check if the stream is (already) connected, and waits if not. The
+ * mainloop must be locked before calling this function.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_stream(struct pulse_output *po, GError **error_r)
+{
+ pa_stream_state_t state = pa_stream_get_state(po->stream);
+
+ switch (state) {
+ case PA_STREAM_READY:
+ return true;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "disconnected");
+ return false;
+
+ case PA_STREAM_CREATING:
+ break;
+ }
+
+ do {
+ state = pulse_output_check_stream(po);
+ } while (state == PA_STREAM_CREATING);
+
+ if (state != PA_STREAM_READY) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to connect the stream: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Determines whether the stream is paused. On libpulse older than
+ * 0.9.11, it uses a custom pause flag.
+ */
+static bool
+pulse_output_stream_is_paused(struct pulse_output *po)
+{
+ assert(po->stream != NULL);
+
+#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11)
+ return po->pause;
+#else
+ return pa_stream_is_corked(po->stream);
+#endif
+}
+
+/**
+ * Sets cork mode on the stream.
+ */
+static bool
+pulse_output_stream_pause(struct pulse_output *po, bool pause,
+ GError **error_r)
+{
+ pa_operation *o;
+
+ assert(po->stream != NULL);
+
+ o = pa_stream_cork(po->stream, pause,
+ pulse_output_stream_success_cb, po);
+ if (o == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_cork() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ if (!pulse_wait_for_operation(po->mainloop, o)) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_cork() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+#if !PA_CHECK_VERSION(0,9,11)
+ po->pause = pause;
+#endif
+ return true;
+}
+
+static size_t
+pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct pulse_output *po = data;
+ int error;
+
+ assert(po->stream != NULL);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already) connected */
+
+ if (!pulse_output_wait_stream(po, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ assert(po->context != NULL);
+
+ /* unpause if previously paused */
+
+ if (pulse_output_stream_is_paused(po) &&
+ !pulse_output_stream_pause(po, false, error_r))
+ return 0;
+
+ /* wait until the server allows us to write */
+
+ while (po->writable == 0) {
+ pa_threaded_mainloop_wait(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "disconnected");
+ return false;
+ }
+ }
+
+ /* now write */
+
+ if (size > po->writable)
+ /* don't send more than possible */
+ size = po->writable;
+
+ po->writable -= size;
+
+ error = pa_stream_write(po->stream, chunk, size, NULL,
+ 0, PA_SEEK_RELATIVE);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ if (error < 0) {
+ g_set_error(error_r, pulse_output_quark(), error,
+ "%s", pa_strerror(error));
+ return 0;
+ }
+
+ return size;
+}
+
+static void
+pulse_output_cancel(void *data)
+{
+ struct pulse_output *po = data;
+ pa_operation *o;
+
+ assert(po->stream != NULL);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ /* no need to flush when the stream isn't connected
+ yet */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ assert(po->context != NULL);
+
+ o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
+ if (o == NULL) {
+ g_warning("pa_stream_flush() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ pulse_wait_for_operation(po->mainloop, o);
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+static bool
+pulse_output_pause(void *data)
+{
+ struct pulse_output *po = data;
+ GError *error = NULL;
+
+ assert(po->stream != NULL);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already/still) connected */
+
+ if (!pulse_output_wait_stream(po, &error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ assert(po->context != NULL);
+
+ /* cork the stream */
+
+ if (pulse_output_stream_is_paused(po)) {
+ /* already paused; due to a MPD API limitation, we
+ have to sleep a little bit here, to avoid hogging
+ the CPU */
+
+ g_usleep(50000);
+ } else if (!pulse_output_stream_pause(po, true, &error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static bool
+pulse_output_test_default_device(void)
+{
+ struct pulse_output *po;
+ bool success;
+
+ po = pulse_output_init(NULL, NULL, NULL);
+ if (po == NULL)
+ return false;
+
+ success = pulse_output_wait_connection(po, NULL);
+ pulse_output_finish(po);
+
+ return success;
+}
+
+const struct audio_output_plugin pulse_output_plugin = {
+ .name = "pulse",
+
+ .test_default_device = pulse_output_test_default_device,
+ .init = pulse_output_init,
+ .finish = pulse_output_finish,
+ .enable = pulse_output_enable,
+ .disable = pulse_output_disable,
+ .open = pulse_output_open,
+ .play = pulse_output_play,
+ .cancel = pulse_output_cancel,
+ .pause = pulse_output_pause,
+ .close = pulse_output_close,
+
+ .mixer_plugin = &pulse_mixer_plugin,
+};
diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h
new file mode 100644
index 000000000..e6b37443f
--- /dev/null
+++ b/src/output/pulse_output_plugin.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2003-2009 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_PULSE_OUTPUT_PLUGIN_H
+#define MPD_PULSE_OUTPUT_PLUGIN_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <glib.h>
+
+#include <pulse/version.h>
+
+#if !defined(PA_CHECK_VERSION)
+/**
+ * This macro was implemented in libpulse 0.9.16.
+ */
+#define PA_CHECK_VERSION(a,b,c) false
+#endif
+
+struct pa_operation;
+struct pa_cvolume;
+
+struct pulse_output {
+ const char *name;
+ const char *server;
+ const char *sink;
+
+ struct pulse_mixer *mixer;
+
+ struct pa_threaded_mainloop *mainloop;
+ struct pa_context *context;
+ struct pa_stream *stream;
+
+ size_t writable;
+
+#if !PA_CHECK_VERSION(0,9,11)
+ /**
+ * We need this variable because pa_stream_is_corked() wasn't
+ * added before 0.9.11.
+ */
+ bool pause;
+#endif
+};
+
+void
+pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm);
+
+void
+pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm);
+
+bool
+pulse_output_set_volume(struct pulse_output *po,
+ const struct pa_cvolume *volume, GError **error_r);
+
+#endif
diff --git a/src/output/pulse_plugin.c b/src/output/pulse_plugin.c
deleted file mode 100644
index ffc7abc8b..000000000
--- a/src/output/pulse_plugin.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2003-2009 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 "../output_api.h"
-#include "mixer_list.h"
-
-#include <glib.h>
-#include <pulse/simple.h>
-#include <pulse/error.h>
-
-#define MPD_PULSE_NAME "mpd"
-
-struct pulse_data {
- const char *name;
- const char *server;
- const char *sink;
-
- pa_simple *s;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-pulse_output_quark(void)
-{
- return g_quark_from_static_string("pulse_output");
-}
-
-static struct pulse_data *pulse_new_data(void)
-{
- struct pulse_data *ret;
-
- ret = g_new(struct pulse_data, 1);
-
- ret->server = NULL;
- ret->sink = NULL;
-
- return ret;
-}
-
-static void pulse_free_data(struct pulse_data *pd)
-{
- g_free(pd);
-}
-
-static void *
-pulse_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, G_GNUC_UNUSED GError **error)
-{
- struct pulse_data *pd;
-
- pd = pulse_new_data();
- pd->name = config_get_block_string(param, "name", "mpd_pulse");
- pd->server = config_get_block_string(param, "server", NULL);
- pd->sink = config_get_block_string(param, "sink", NULL);
-
- return pd;
-}
-
-static void pulse_finish(void *data)
-{
- struct pulse_data *pd = data;
-
- pulse_free_data(pd);
-}
-
-static bool pulse_test_default_device(void)
-{
- pa_simple *s;
- pa_sample_spec ss;
- int error;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = 44100;
- ss.channels = 2;
-
- s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL,
- MPD_PULSE_NAME, &ss, NULL, NULL, &error);
- if (!s) {
- g_message("Cannot connect to default PulseAudio server: %s\n",
- pa_strerror(error));
- return false;
- }
-
- pa_simple_free(s);
-
- return true;
-}
-
-static bool
-pulse_open(void *data, struct audio_format *audio_format, GError **error_r)
-{
- struct pulse_data *pd = data;
- pa_sample_spec ss;
- int error;
-
- /* MPD doesn't support the other pulseaudio sample formats, so
- we just force MPD to send us everything as 16 bit */
- audio_format->bits = 16;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = audio_format->sample_rate;
- ss.channels = audio_format->channels;
-
- pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK,
- pd->sink, pd->name,
- &ss, NULL, NULL,
- &error);
- if (!pd->s) {
- g_set_error(error_r, pulse_output_quark(), error,
- "Cannot connect to PulseAudio server: %s",
- pa_strerror(error));
- return false;
- }
-
- return true;
-}
-
-static void pulse_cancel(void *data)
-{
- struct pulse_data *pd = data;
- int error;
-
- if (pa_simple_flush(pd->s, &error) < 0)
- g_warning("Flush failed in PulseAudio output \"%s\": %s\n",
- pd->name, pa_strerror(error));
-}
-
-static void pulse_close(void *data)
-{
- struct pulse_data *pd = data;
-
- pa_simple_drain(pd->s, NULL);
- pa_simple_free(pd->s);
-}
-
-static size_t
-pulse_play(void *data, const void *chunk, size_t size, GError **error_r)
-{
- struct pulse_data *pd = data;
- int error;
-
- if (pa_simple_write(pd->s, chunk, size, &error) < 0) {
- g_set_error(error_r, pulse_output_quark(), error,
- "%s", pa_strerror(error));
- return 0;
- }
-
- return size;
-}
-
-const struct audio_output_plugin pulse_plugin = {
- .name = "pulse",
- .test_default_device = pulse_test_default_device,
- .init = pulse_init,
- .finish = pulse_finish,
- .open = pulse_open,
- .play = pulse_play,
- .cancel = pulse_cancel,
- .close = pulse_close,
- .mixer_plugin = &pulse_mixer,
-};
diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c
new file mode 100644
index 000000000..11dd6c041
--- /dev/null
+++ b/src/output/recorder_output_plugin.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2003-2009 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 "output_api.h"
+#include "encoder_plugin.h"
+#include "encoder_list.h"
+#include "fd_util.h"
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "recorder"
+
+struct recorder_output {
+ /**
+ * The configured encoder plugin.
+ */
+ struct encoder *encoder;
+
+ /**
+ * The destination file name.
+ */
+ const char *path;
+
+ /**
+ * The destination file descriptor.
+ */
+ int fd;
+
+ /**
+ * The buffer for encoder_read().
+ */
+ char buffer[32768];
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+recorder_output_quark(void)
+{
+ return g_quark_from_static_string("recorder_output");
+}
+
+static void *
+recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param, GError **error_r)
+{
+ struct recorder_output *recorder = g_new(struct recorder_output, 1);
+ const char *encoder_name;
+ const struct encoder_plugin *encoder_plugin;
+
+ /* read configuration */
+
+ encoder_name = config_get_block_string(param, "encoder", "vorbis");
+ encoder_plugin = encoder_plugin_get(encoder_name);
+ if (encoder_plugin == NULL) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "No such encoder: %s", encoder_name);
+ return NULL;
+ }
+
+ recorder->path = config_get_block_string(param, "path", NULL);
+ if (recorder->path == NULL) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "'path' not configured");
+ return NULL;
+ }
+
+ /* initialize encoder */
+
+ recorder->encoder = encoder_init(encoder_plugin, param, error_r);
+ if (recorder->encoder == NULL)
+ return NULL;
+
+ return recorder;
+}
+
+static void
+recorder_output_finish(void *data)
+{
+ struct recorder_output *recorder = data;
+
+ encoder_finish(recorder->encoder);
+ g_free(recorder);
+}
+
+/**
+ * Writes pending data from the encoder to the output file.
+ */
+static bool
+recorder_output_encoder_to_file(struct recorder_output *recorder,
+ GError **error_r)
+{
+ size_t size = 0, position, nbytes;
+
+ assert(recorder->fd >= 0);
+
+ /* read from the encoder */
+
+ size = encoder_read(recorder->encoder, recorder->buffer,
+ sizeof(recorder->buffer));
+ if (size == 0)
+ return true;
+
+ /* write everything into the file */
+
+ position = 0;
+ while (true) {
+ nbytes = write(recorder->fd, recorder->buffer + position,
+ size - position);
+ if (nbytes > 0) {
+ position += (size_t)nbytes;
+ if (position >= size)
+ return true;
+ } else if (nbytes == 0) {
+ /* shouldn't happen for files */
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "write() returned 0");
+ return false;
+ } else if (errno != EINTR) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "Failed to write to '%s': %s",
+ recorder->path, g_strerror(errno));
+ return false;
+ }
+ }
+}
+
+static bool
+recorder_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct recorder_output *recorder = data;
+ bool success;
+
+ /* create the output file */
+
+ recorder->fd = open_cloexec(recorder->path, O_CREAT|O_WRONLY|O_TRUNC,
+ 0666);
+ if (recorder->fd < 0) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "Failed to create '%s': %s",
+ recorder->path, g_strerror(errno));
+ return false;
+ }
+
+ /* open the encoder */
+
+ success = encoder_open(recorder->encoder, audio_format, error_r);
+ if (!success) {
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+recorder_output_close(void *data)
+{
+ struct recorder_output *recorder = data;
+
+ /* flush the encoder and write the rest to the file */
+
+ if (encoder_flush(recorder->encoder, NULL))
+ recorder_output_encoder_to_file(recorder, NULL);
+
+ /* now really close everything */
+
+ encoder_close(recorder->encoder);
+
+ close(recorder->fd);
+}
+
+static size_t
+recorder_output_play(void *data, const void *chunk, size_t size,
+ GError **error_r)
+{
+ struct recorder_output *recorder = data;
+
+ return encoder_write(recorder->encoder, chunk, size, error_r) &&
+ recorder_output_encoder_to_file(recorder, error_r)
+ ? size : 0;
+}
+
+const struct audio_output_plugin recorder_output_plugin = {
+ .name = "recorder",
+ .init = recorder_output_init,
+ .finish = recorder_output_finish,
+ .open = recorder_output_open,
+ .close = recorder_output_close,
+ .play = recorder_output_play,
+};
diff --git a/src/output/shout_plugin.c b/src/output/shout_plugin.c
index 4412d26ff..da90efd2d 100644
--- a/src/output/shout_plugin.c
+++ b/src/output/shout_plugin.c
@@ -126,6 +126,13 @@ my_shout_init_driver(const struct audio_format *audio_format,
struct block_param *block_param;
int public;
+ if (audio_format == NULL ||
+ !audio_format_fully_defined(audio_format)) {
+ g_set_error(error, shout_output_quark(), 0,
+ "Need full audio format specification");
+ return NULL;
+ }
+
sd = new_shout_data();
if (shout_init_count == 0)
@@ -191,8 +198,6 @@ my_shout_init_driver(const struct audio_format *audio_format,
}
}
- check_block_param("format");
-
encoding = config_get_block_string(param, "encoding", "ogg");
encoder_plugin = shout_encoder_plugin_get(encoding);
if (encoder_plugin == NULL) {
@@ -471,10 +476,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
for (unsigned i = 0; i < tag->num_items; i++) {
switch (tag->items[i]->type) {
- case TAG_ITEM_ARTIST:
+ case TAG_ARTIST:
strncpy(artist, tag->items[i]->value, size);
break;
- case TAG_ITEM_TITLE:
+ case TAG_TITLE:
strncpy(title, tag->items[i]->value, size);
break;
diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c
index 5febf0afc..4f3d86835 100644
--- a/src/output/solaris_output_plugin.c
+++ b/src/output/solaris_output_plugin.c
@@ -18,6 +18,7 @@
*/
#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -91,7 +92,7 @@ solaris_output_open(void *data, struct audio_format *audio_format,
/* open the device in non-blocking mode */
- so->fd = open(so->device, O_WRONLY|O_NONBLOCK);
+ so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK);
if (so->fd < 0) {
g_set_error(error, solaris_output_quark(), errno,
"Failed to open %s: %s",
diff --git a/src/output_all.c b/src/output_all.c
index 4b5ba3a6f..29590abf0 100644
--- a/src/output_all.c
+++ b/src/output_all.c
@@ -52,6 +52,11 @@ static struct music_buffer *g_music_buffer;
*/
static struct music_pipe *g_mp;
+/**
+ * The "elapsed_time" stamp of the most recently finished chunk.
+ */
+static float audio_output_all_elapsed_time = -1.0;
+
unsigned int audio_output_count(void)
{
return num_audio_outputs;
@@ -148,6 +153,25 @@ audio_output_all_finish(void)
notify_deinit(&audio_output_client_notify);
}
+void
+audio_output_all_enable_disable(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; i++) {
+ struct audio_output *ao = &audio_outputs[i];
+ bool enabled;
+
+ g_mutex_lock(ao->mutex);
+ enabled = ao->really_enabled;
+ g_mutex_unlock(ao->mutex);
+
+ if (ao->enabled != enabled) {
+ if (ao->enabled)
+ audio_output_enable(ao);
+ else
+ audio_output_disable(ao);
+ }
+ }
+}
/**
* Determine if all (active) outputs have finished the current
@@ -156,10 +180,18 @@ audio_output_all_finish(void)
static bool
audio_output_all_finished(void)
{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]) &&
- !audio_output_command_is_finished(&audio_outputs[i]))
+ for (unsigned i = 0; i < num_audio_outputs; ++i) {
+ struct audio_output *ao = &audio_outputs[i];
+ bool not_finished;
+
+ g_mutex_lock(ao->mutex);
+ not_finished = audio_output_is_open(ao) &&
+ !audio_output_command_is_finished(ao);
+ g_mutex_unlock(ao->mutex);
+
+ if (not_finished)
return false;
+ }
return true;
}
@@ -170,6 +202,29 @@ static void audio_output_wait_all(void)
notify_wait(&audio_output_client_notify);
}
+/**
+ * Signals the audio output if it is open. This function locks the
+ * mutex.
+ */
+static void
+audio_output_lock_signal(struct audio_output *ao)
+{
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ g_cond_signal(ao->cond);
+ g_mutex_unlock(ao->mutex);
+}
+
+/**
+ * Signals all audio outputs which are open.
+ */
+static void
+audio_output_signal_all(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_lock_signal(&audio_outputs[i]);
+}
+
static void
audio_output_reset_reopen(struct audio_output *ao)
{
@@ -237,8 +292,7 @@ audio_output_all_play(struct music_chunk *chunk)
music_pipe_push(g_mp, chunk);
for (i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_play(&audio_outputs[i]);
+ audio_output_play(&audio_outputs[i]);
return true;
}
@@ -273,6 +327,7 @@ audio_output_all_open(const struct audio_format *audio_format,
input_audio_format = *audio_format;
audio_output_all_reset_reopen();
+ audio_output_all_enable_disable();
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i) {
@@ -385,6 +440,8 @@ audio_output_all_check(void)
this chunk */
return music_pipe_size(g_mp);
+ audio_output_all_elapsed_time = chunk->times;
+
is_tail = chunk->next == NULL;
if (is_tail)
/* this is the tail of the pipe - clear the
@@ -412,10 +469,15 @@ audio_output_all_check(void)
bool
audio_output_all_wait(unsigned threshold)
{
- if (audio_output_all_check() < threshold)
+ player_lock();
+
+ if (audio_output_all_check() < threshold) {
+ player_unlock();
return true;
+ }
- notify_wait(&pc.notify);
+ player_wait();
+ player_unlock();
return audio_output_all_check() < threshold;
}
@@ -428,8 +490,16 @@ audio_output_all_pause(void)
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_pause(&audio_outputs[i]);
+ audio_output_pause(&audio_outputs[i]);
+
+ audio_output_wait_all();
+}
+
+void
+audio_output_all_drain(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_drain_async(&audio_outputs[i]);
audio_output_wait_all();
}
@@ -441,10 +511,8 @@ audio_output_all_cancel(void)
/* send the cancel() command to all audio outputs */
- for (i = 0; i < num_audio_outputs; ++i) {
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_cancel(&audio_outputs[i]);
- }
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_cancel(&audio_outputs[i]);
audio_output_wait_all();
@@ -452,6 +520,15 @@ audio_output_all_cancel(void)
if (g_mp != NULL)
music_pipe_clear(g_mp, g_music_buffer);
+
+ /* the audio outputs are now waiting for a signal, to
+ synchronize the cleared music pipe */
+
+ audio_output_signal_all();
+
+ /* invalidate elapsed_time */
+
+ audio_output_all_elapsed_time = -1.0;
}
void
@@ -473,4 +550,12 @@ audio_output_all_close(void)
g_music_buffer = NULL;
audio_format_clear(&input_audio_format);
+
+ audio_output_all_elapsed_time = -1.0;
+}
+
+float
+audio_output_all_get_elapsed_time(void)
+{
+ return audio_output_all_elapsed_time;
}
diff --git a/src/output_all.h b/src/output_all.h
index 2a09514b2..6ff45fb79 100644
--- a/src/output_all.h
+++ b/src/output_all.h
@@ -66,6 +66,13 @@ struct audio_output *
audio_output_find(const char *name);
/**
+ * Checks the "enabled" flag of all audio outputs, and if one has
+ * changed, commit the change.
+ */
+void
+audio_output_all_enable_disable(void);
+
+/**
* Opens all audio outputs which are not disabled.
*
* @param audio_format the preferred audio format, or NULL to reuse
@@ -123,9 +130,23 @@ void
audio_output_all_pause(void);
/**
+ * Drain all audio outputs.
+ */
+void
+audio_output_all_drain(void);
+
+/**
* Try to cancel data which may still be in the device's buffers.
*/
void
audio_output_all_cancel(void);
+/**
+ * Returns the "elapsed_time" stamp of the most recently finished
+ * chunk. A negative value is returned when no chunk has been
+ * finished yet.
+ */
+float
+audio_output_all_get_elapsed_time(void);
+
#endif
diff --git a/src/output_command.c b/src/output_command.c
index 5da176dde..b47890043 100644
--- a/src/output_command.c
+++ b/src/output_command.c
@@ -29,8 +29,11 @@
#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;
+
bool
audio_output_enable_index(unsigned idx)
{
@@ -40,10 +43,16 @@ audio_output_enable_index(unsigned idx)
return false;
ao = audio_output_get(idx);
+ if (ao->enabled)
+ return true;
ao->enabled = true;
idle_add(IDLE_OUTPUT);
+ pc_update_audio();
+
+ ++audio_output_state_version;
+
return true;
}
@@ -57,6 +66,8 @@ audio_output_disable_index(unsigned idx)
return false;
ao = audio_output_get(idx);
+ if (!ao->enabled)
+ return true;
ao->enabled = false;
idle_add(IDLE_OUTPUT);
@@ -67,5 +78,9 @@ audio_output_disable_index(unsigned idx)
idle_add(IDLE_MIXER);
}
+ pc_update_audio();
+
+ ++audio_output_state_version;
+
return true;
}
diff --git a/src/output_control.c b/src/output_control.c
index 16c0dbb75..795d04a8c 100644
--- a/src/output_control.c
+++ b/src/output_control.c
@@ -23,6 +23,8 @@
#include "output_thread.h"
#include "mixer_control.h"
#include "mixer_plugin.h"
+#include "filter_plugin.h"
+#include "notify.h"
#include <assert.h>
#include <stdlib.h>
@@ -38,8 +40,9 @@ struct notify audio_output_client_notify;
static void ao_command_wait(struct audio_output *ao)
{
while (ao->command != AO_COMMAND_NONE) {
- notify_signal(&ao->notify);
+ g_mutex_unlock(ao->mutex);
notify_wait(&audio_output_client_notify);
+ g_mutex_lock(ao->mutex);
}
}
@@ -47,6 +50,7 @@ static void ao_command(struct audio_output *ao, enum audio_output_command cmd)
{
assert(ao->command == AO_COMMAND_NONE);
ao->command = cmd;
+ g_cond_signal(ao->cond);
ao_command_wait(ao);
}
@@ -55,7 +59,46 @@ static void ao_command_async(struct audio_output *ao,
{
assert(ao->command == AO_COMMAND_NONE);
ao->command = cmd;
- notify_signal(&ao->notify);
+ g_cond_signal(ao->cond);
+}
+
+void
+audio_output_enable(struct audio_output *ao)
+{
+ if (ao->thread == NULL) {
+ if (ao->plugin->enable == NULL) {
+ /* don't bother to start the thread now if the
+ device doesn't even have a enable() method;
+ just assign the variable and we're done */
+ ao->really_enabled = true;
+ return;
+ }
+
+ audio_output_thread_start(ao);
+ }
+
+ g_mutex_lock(ao->mutex);
+ ao_command(ao, AO_COMMAND_ENABLE);
+ g_mutex_unlock(ao->mutex);
+}
+
+void
+audio_output_disable(struct audio_output *ao)
+{
+ if (ao->thread == NULL) {
+ if (ao->plugin->disable == NULL)
+ ao->really_enabled = false;
+ else
+ /* if there's no thread yet, the device cannot
+ be enabled */
+ assert(!ao->really_enabled);
+
+ return;
+ }
+
+ g_mutex_lock(ao->mutex);
+ ao_command(ao, AO_COMMAND_DISABLE);
+ g_mutex_unlock(ao->mutex);
}
static bool
@@ -85,6 +128,10 @@ audio_output_open(struct audio_output *ao,
/* we're not using audio_output_cancel() here,
because that function is asynchronous */
ao_command(ao, AO_COMMAND_CANCEL);
+
+ /* the audio output is now waiting for a
+ signal; wake it up immediately */
+ g_cond_signal(ao->cond);
}
return true;
@@ -93,33 +140,47 @@ audio_output_open(struct audio_output *ao,
ao->in_audio_format = *audio_format;
ao->chunk = NULL;
- if (!ao->config_audio_format) {
- if (ao->open)
- audio_output_close(ao);
-
- /* no audio format is configured: copy in->out, let
- the output's open() method determine the effective
- out_audio_format */
- ao->out_audio_format = ao->in_audio_format;
- }
-
ao->pipe = mp;
if (ao->thread == NULL)
audio_output_thread_start(ao);
+ ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
open = ao->open;
- if (!open) {
- ao_command(ao, AO_COMMAND_OPEN);
- open = ao->open;
- }
- if (open && ao->mixer != NULL)
- mixer_open(ao->mixer);
+ if (open && ao->mixer != NULL) {
+ GError *error = NULL;
+
+ if (!mixer_open(ao->mixer, &error)) {
+ g_warning("Failed to open mixer for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
+ }
+ }
return open;
}
+/**
+ * Same as audio_output_close(), but expects the lock to be held by
+ * the caller.
+ */
+static void
+audio_output_close_locked(struct audio_output *ao)
+{
+ if (ao->mixer != NULL)
+ mixer_auto_close(ao->mixer);
+
+ assert(!ao->open || ao->fail_timer == NULL);
+
+ if (ao->open)
+ ao_command(ao, AO_COMMAND_CLOSE);
+ else if (ao->fail_timer != NULL) {
+ g_timer_destroy(ao->fail_timer);
+ ao->fail_timer = NULL;
+ }
+}
+
bool
audio_output_update(struct audio_output *ao,
const struct audio_format *audio_format,
@@ -127,23 +188,31 @@ audio_output_update(struct audio_output *ao,
{
assert(mp != NULL);
- if (ao->enabled) {
+ g_mutex_lock(ao->mutex);
+
+ if (ao->enabled && ao->really_enabled) {
if (ao->fail_timer == NULL ||
- g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER)
- return audio_output_open(ao, audio_format, mp);
+ g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) {
+ bool success = audio_output_open(ao, audio_format, mp);
+ g_mutex_unlock(ao->mutex);
+ return success;
+ }
} else if (audio_output_is_open(ao))
- audio_output_close(ao);
+ audio_output_close_locked(ao);
+ g_mutex_unlock(ao->mutex);
return false;
}
void
audio_output_play(struct audio_output *ao)
{
- if (!ao->open)
- return;
+ g_mutex_lock(ao->mutex);
+
+ if (audio_output_is_open(ao))
+ g_cond_signal(ao->cond);
- notify_signal(&ao->notify);
+ g_mutex_unlock(ao->mutex);
}
void audio_output_pause(struct audio_output *ao)
@@ -154,27 +223,46 @@ void audio_output_pause(struct audio_output *ao)
mixer_auto_close()) */
mixer_auto_close(ao->mixer);
- ao_command_async(ao, AO_COMMAND_PAUSE);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_PAUSE);
+ g_mutex_unlock(ao->mutex);
+}
+
+void
+audio_output_drain_async(struct audio_output *ao)
+{
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_DRAIN);
+ g_mutex_unlock(ao->mutex);
}
void audio_output_cancel(struct audio_output *ao)
{
- ao_command_async(ao, AO_COMMAND_CANCEL);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_CANCEL);
+ g_mutex_unlock(ao->mutex);
}
void audio_output_close(struct audio_output *ao)
{
- assert(!ao->open || ao->fail_timer == NULL);
-
if (ao->mixer != NULL)
mixer_auto_close(ao->mixer);
+ g_mutex_lock(ao->mutex);
+
+ assert(!ao->open || ao->fail_timer == NULL);
+
if (ao->open)
ao_command(ao, AO_COMMAND_CLOSE);
else if (ao->fail_timer != NULL) {
g_timer_destroy(ao->fail_timer);
ao->fail_timer = NULL;
}
+
+ g_mutex_unlock(ao->mutex);
}
void audio_output_finish(struct audio_output *ao)
@@ -184,7 +272,9 @@ void audio_output_finish(struct audio_output *ao)
assert(ao->fail_timer == NULL);
if (ao->thread != NULL) {
+ g_mutex_lock(ao->mutex);
ao_command(ao, AO_COMMAND_KILL);
+ g_mutex_unlock(ao->mutex);
g_thread_join(ao->thread);
}
@@ -193,6 +283,8 @@ void audio_output_finish(struct audio_output *ao)
ao_plugin_finish(ao->plugin, ao->data);
- notify_deinit(&ao->notify);
+ g_cond_free(ao->cond);
g_mutex_free(ao->mutex);
+
+ filter_free(ao->filter);
}
diff --git a/src/output_control.h b/src/output_control.h
index ce3abe3f6..692c11676 100644
--- a/src/output_control.h
+++ b/src/output_control.h
@@ -38,7 +38,19 @@ audio_output_quark(void)
bool
audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error);
+ GError **error_r);
+
+/**
+ * Enables the device.
+ */
+void
+audio_output_enable(struct audio_output *ao);
+
+/**
+ * Disables the device.
+ */
+void
+audio_output_disable(struct audio_output *ao);
/**
* Opens or closes the device, depending on the "enabled" flag.
@@ -55,6 +67,9 @@ audio_output_play(struct audio_output *ao);
void audio_output_pause(struct audio_output *ao);
+void
+audio_output_drain_async(struct audio_output *ao);
+
void audio_output_cancel(struct audio_output *ao);
void audio_output_close(struct audio_output *ao);
void audio_output_finish(struct audio_output *ao);
diff --git a/src/output_init.c b/src/output_init.c
index 927424324..a7272bfc2 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -23,9 +23,17 @@
#include "output_list.h"
#include "audio_parser.h"
#include "mixer_control.h"
+#include "mixer_type.h"
+#include "mixer_list.h"
+#include "mixer/software_mixer_plugin.h"
+#include "filter_plugin.h"
+#include "filter_registry.h"
+#include "filter/chain_filter_plugin.h"
#include <glib.h>
+#include <assert.h>
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "output"
@@ -56,46 +64,109 @@ audio_output_detect(GError **error)
return NULL;
}
+/**
+ * Determines the mixer type which should be used for the specified
+ * configuration block.
+ *
+ * This handles the deprecated options mixer_type (global) and
+ * mixer_enabled, if the mixer_type setting is not configured.
+ */
+static enum mixer_type
+audio_output_mixer_type(const struct config_param *param)
+{
+ /* read the local "mixer_type" setting */
+ const char *p = config_get_block_string(param, "mixer_type", NULL);
+ if (p != NULL)
+ return mixer_type_parse(p);
+
+ /* try the local "mixer_enabled" setting next (deprecated) */
+ if (!config_get_block_bool(param, "mixer_enabled", true))
+ return MIXER_TYPE_NONE;
+
+ /* fall back to the global "mixer_type" setting (also
+ deprecated) */
+ return mixer_type_parse(config_get_string("mixer_type", "hardware"));
+}
+
+static struct mixer *
+audio_output_load_mixer(void *ao, const struct config_param *param,
+ const struct mixer_plugin *plugin,
+ struct filter *filter_chain,
+ GError **error_r)
+{
+ struct mixer *mixer;
+
+ switch (audio_output_mixer_type(param)) {
+ case MIXER_TYPE_NONE:
+ case MIXER_TYPE_UNKNOWN:
+ return NULL;
+
+ case MIXER_TYPE_HARDWARE:
+ if (plugin == NULL)
+ return NULL;
+
+ return mixer_new(plugin, ao, param, error_r);
+
+ case MIXER_TYPE_SOFTWARE:
+ mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL);
+ assert(mixer != NULL);
+
+ filter_chain_append(filter_chain,
+ software_mixer_get_filter(mixer));
+ return mixer;
+ }
+
+ assert(false);
+ return NULL;
+}
+
bool
audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error)
+ GError **error_r)
{
- const char *format;
const struct audio_output_plugin *plugin = NULL;
+ GError *error = NULL;
if (param) {
- const char *type = NULL;
+ const char *p;
- type = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
- if (type == NULL) {
- g_set_error(error, audio_output_quark(), 0,
+ p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
+ if (p == NULL) {
+ g_set_error(error_r, audio_output_quark(), 0,
"Missing \"type\" configuration");
return false;
}
- plugin = audio_output_plugin_get(type);
+ plugin = audio_output_plugin_get(p);
if (plugin == NULL) {
- g_set_error(error, audio_output_quark(), 0,
- "No such audio output plugin: %s",
- type);
+ g_set_error(error_r, audio_output_quark(), 0,
+ "No such audio output plugin: %s", p);
return false;
}
ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME,
NULL);
if (ao->name == NULL) {
- g_set_error(error, audio_output_quark(), 0,
+ g_set_error(error_r, audio_output_quark(), 0,
"Missing \"name\" configuration");
return false;
}
- format = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
+ p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
NULL);
+ if (p != NULL) {
+ bool success =
+ audio_format_parse(&ao->config_audio_format,
+ p, true, error_r);
+ if (!success)
+ return false;
+ } else
+ audio_format_clear(&ao->config_audio_format);
} else {
g_warning("No \"%s\" defined in config file\n",
CONF_AUDIO_OUTPUT);
- plugin = audio_output_detect(error);
+ plugin = audio_output_detect(error_r);
if (plugin == NULL)
return false;
@@ -103,44 +174,50 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
plugin->name);
ao->name = "default detected output";
- format = NULL;
+
+ audio_format_clear(&ao->config_audio_format);
}
ao->plugin = plugin;
ao->enabled = config_get_block_bool(param, "enabled", true);
+ ao->really_enabled = false;
ao->open = false;
ao->pause = false;
ao->fail_timer = NULL;
- pcm_convert_init(&ao->convert_state);
+ /* set up the filter chain */
- ao->config_audio_format = format != NULL;
- if (ao->config_audio_format) {
- bool ret;
-
- ret = audio_format_parse(&ao->out_audio_format, format,
- error);
- if (!ret)
- return false;
- }
+ ao->filter = filter_chain_new();
+ assert(ao->filter != NULL);
ao->thread = NULL;
- notify_init(&ao->notify);
ao->command = AO_COMMAND_NONE;
ao->mutex = g_mutex_new();
+ ao->cond = g_cond_new();
ao->data = ao_plugin_init(plugin,
- ao->config_audio_format
- ? &ao->out_audio_format : NULL,
- param, error);
+ &ao->config_audio_format,
+ param, error_r);
if (ao->data == NULL)
return false;
- if (plugin->mixer_plugin != NULL &&
- config_get_block_bool(param, "mixer_enabled", true))
- ao->mixer = mixer_new(plugin->mixer_plugin, param);
- else
- ao->mixer = NULL;
+ ao->mixer = audio_output_load_mixer(ao->data, param,
+ plugin->mixer_plugin,
+ ao->filter, &error);
+ if (ao->mixer == NULL && error != NULL) {
+ g_warning("Failed to initialize hardware mixer for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
+ }
+
+ /* the "convert" filter must be the last one in the chain */
+
+ ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL);
+ assert(ao->convert_filter != NULL);
+
+ filter_chain_append(ao->filter, ao->convert_filter);
+
+ /* done */
return true;
}
diff --git a/src/output_internal.h b/src/output_internal.h
index 72596c1c3..de1b15c29 100644
--- a/src/output_internal.h
+++ b/src/output_internal.h
@@ -21,16 +21,32 @@
#define MPD_OUTPUT_INTERNAL_H
#include "audio_format.h"
-#include "pcm_convert.h"
-#include "notify.h"
+
+#include <glib.h>
#include <time.h>
enum audio_output_command {
AO_COMMAND_NONE = 0,
+ AO_COMMAND_ENABLE,
+ AO_COMMAND_DISABLE,
AO_COMMAND_OPEN,
+
+ /**
+ * This command is invoked when the input audio format
+ * changes.
+ */
+ AO_COMMAND_REOPEN,
+
AO_COMMAND_CLOSE,
AO_COMMAND_PAUSE,
+
+ /**
+ * Drains the internal (hardware) buffers of the device. This
+ * operation may take a while to complete.
+ */
+ AO_COMMAND_DRAIN,
+
AO_COMMAND_CANCEL,
AO_COMMAND_KILL
};
@@ -60,15 +76,15 @@ struct audio_output {
struct mixer *mixer;
/**
- * This flag is true, when the audio_format of this device is
- * configured in mpd.conf.
+ * Has the user enabled this device?
*/
- bool config_audio_format;
+ bool enabled;
/**
- * Has the user enabled this device?
+ * Is this device actually enabled, i.e. the "enable" method
+ * has succeeded?
*/
- bool enabled;
+ bool really_enabled;
/**
* Is the device (already) open and functional?
@@ -94,6 +110,11 @@ struct audio_output {
GTimer *fail_timer;
/**
+ * The configured audio format.
+ */
+ struct audio_format config_audio_format;
+
+ /**
* The audio_format in which audio data is received from the
* player thread (which in turn receives it from the decoder).
*/
@@ -107,18 +128,25 @@ struct audio_output {
*/
struct audio_format out_audio_format;
- struct pcm_convert_state convert_state;
+ /**
+ * The filter object of this audio output. This is an
+ * instance of chain_filter_plugin.
+ */
+ struct filter *filter;
/**
- * The thread handle, or NULL if the output thread isn't
- * running.
+ * The convert_filter_plugin instance of this audio output.
+ * It is the last item in the filter chain, and is responsible
+ * for converting the input data into the appropriate format
+ * for this audio output.
*/
- GThread *thread;
+ struct filter *convert_filter;
/**
- * Notify object for the thread.
+ * The thread handle, or NULL if the output thread isn't
+ * running.
*/
- struct notify notify;
+ GThread *thread;
/**
* The next command to be performed by the output thread.
@@ -136,6 +164,12 @@ struct audio_output {
GMutex *mutex;
/**
+ * This condition object wakes up the output thread after
+ * #command has been set.
+ */
+ GCond *cond;
+
+ /**
* The #music_chunk which is currently being played. All
* chunks before this one may be returned to the
* #music_buffer, because they are not going to be used by
diff --git a/src/output_list.c b/src/output_list.c
index 81de16649..fcb796751 100644
--- a/src/output_list.c
+++ b/src/output_list.c
@@ -28,12 +28,14 @@ extern const struct audio_output_plugin pipe_output_plugin;
extern const struct audio_output_plugin alsaPlugin;
extern const struct audio_output_plugin ao_output_plugin;
extern const struct audio_output_plugin oss_output_plugin;
+extern const struct audio_output_plugin openal_output_plugin;
extern const struct audio_output_plugin osxPlugin;
extern const struct audio_output_plugin solaris_output_plugin;
-extern const struct audio_output_plugin pulse_plugin;
+extern const struct audio_output_plugin pulse_output_plugin;
extern const struct audio_output_plugin mvp_output_plugin;
-extern const struct audio_output_plugin jackPlugin;
+extern const struct audio_output_plugin jack_output_plugin;
extern const struct audio_output_plugin httpd_output_plugin;
+extern const struct audio_output_plugin recorder_output_plugin;
const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_SHOUT
@@ -55,6 +57,9 @@ const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_OSS
&oss_output_plugin,
#endif
+#ifdef HAVE_OPENAL
+ &openal_output_plugin,
+#endif
#ifdef HAVE_OSX
&osxPlugin,
#endif
@@ -62,17 +67,20 @@ const struct audio_output_plugin *audio_output_plugins[] = {
&solaris_output_plugin,
#endif
#ifdef HAVE_PULSE
- &pulse_plugin,
+ &pulse_output_plugin,
#endif
#ifdef HAVE_MVP
&mvp_output_plugin,
#endif
#ifdef HAVE_JACK
- &jackPlugin,
+ &jack_output_plugin,
#endif
#ifdef ENABLE_HTTPD_OUTPUT
&httpd_output_plugin,
#endif
+#ifdef ENABLE_RECORDER_OUTPUT
+ &recorder_output_plugin,
+#endif
NULL
};
diff --git a/src/output_plugin.h b/src/output_plugin.h
index 13dba0d0b..ee7f7c73d 100644
--- a/src/output_plugin.h
+++ b/src/output_plugin.h
@@ -67,6 +67,24 @@ struct audio_output_plugin {
void (*finish)(void *data);
/**
+ * Enable the device. This may allocate resources, preparing
+ * for the device to be opened. Enabling a device cannot
+ * fail: if an error occurs during that, it should be reported
+ * by the open() method.
+ *
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
+ * @return true on success, false on error
+ */
+ bool (*enable)(void *data, GError **error_r);
+
+ /**
+ * Disables the device. It is closed before this method is
+ * called.
+ */
+ void (*disable)(void *data);
+
+ /**
* Really open the device.
*
* @param audio_format the audio format in which data is going
@@ -99,6 +117,11 @@ struct audio_output_plugin {
GError **error);
/**
+ * Wait until the device has finished playing.
+ */
+ void (*drain)(void *data);
+
+ /**
* Try to cancel data which may still be in the device's
* buffers.
*/
@@ -150,6 +173,22 @@ ao_plugin_finish(const struct audio_output_plugin *plugin, void *data)
}
static inline bool
+ao_plugin_enable(const struct audio_output_plugin *plugin, void *data,
+ GError **error_r)
+{
+ return plugin->enable != NULL
+ ? plugin->enable(data, error_r)
+ : true;
+}
+
+static inline void
+ao_plugin_disable(const struct audio_output_plugin *plugin, void *data)
+{
+ if (plugin->disable != NULL)
+ plugin->disable(data);
+}
+
+static inline bool
ao_plugin_open(const struct audio_output_plugin *plugin,
void *data, struct audio_format *audio_format,
GError **error)
@@ -180,6 +219,13 @@ ao_plugin_play(const struct audio_output_plugin *plugin,
}
static inline void
+ao_plugin_drain(const struct audio_output_plugin *plugin, void *data)
+{
+ if (plugin->drain != NULL)
+ plugin->drain(data);
+}
+
+static inline void
ao_plugin_cancel(const struct audio_output_plugin *plugin, void *data)
{
if (plugin->cancel != NULL)
diff --git a/src/output_state.c b/src/output_state.c
index c7e6c8579..be135f6d0 100644
--- a/src/output_state.c
+++ b/src/output_state.c
@@ -34,8 +34,10 @@
#define AUDIO_DEVICE_STATE "audio_device_state:"
+unsigned audio_output_state_version;
+
void
-saveAudioDevicesState(FILE *fp)
+audio_output_state_save(FILE *fp)
{
unsigned n = audio_output_count();
@@ -49,35 +51,40 @@ saveAudioDevicesState(FILE *fp)
}
}
-void
-readAudioDevicesState(FILE *fp)
+bool
+audio_output_state_read(const char *line)
{
- char buffer[1024];
+ long value;
+ char *endptr;
+ const char *name;
+ struct audio_output *ao;
- while (fgets(buffer, sizeof(buffer), fp)) {
- char *c, *name;
- struct audio_output *ao;
+ if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
+ return false;
- g_strchomp(buffer);
+ line += sizeof(AUDIO_DEVICE_STATE) - 1;
- if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE))
- continue;
+ value = strtol(line, &endptr, 10);
+ if (*endptr != ':' || (value != 0 && value != 1))
+ return false;
- c = strchr(buffer, ':');
- if (!c || !(++c))
- goto errline;
+ if (value != 0)
+ /* state is "enabled": no-op */
+ return true;
- name = strchr(c, ':');
- if (!name || !(++name))
- goto errline;
+ name = endptr + 1;
+ ao = audio_output_find(name);
+ if (ao == NULL) {
+ g_debug("Ignoring device state for '%s'", name);
+ return true;
+ }
- ao = audio_output_find(name);
- if (ao != NULL && atoi(c) == 0)
- ao->enabled = false;
+ ao->enabled = false;
+ return true;
+}
- continue;
-errline:
- /* nonfatal */
- g_warning("invalid line in state_file: %s\n", buffer);
- }
+unsigned
+audio_output_state_get_version(void)
+{
+ return audio_output_state_version;
}
diff --git a/src/output_state.h b/src/output_state.h
index 8592574ab..3b865f5fe 100644
--- a/src/output_state.h
+++ b/src/output_state.h
@@ -25,12 +25,21 @@
#ifndef OUTPUT_STATE_H
#define OUTPUT_STATE_H
+#include <stdbool.h>
#include <stdio.h>
-void
-readAudioDevicesState(FILE *fp);
+bool
+audio_output_state_read(const char *line);
void
-saveAudioDevicesState(FILE *fp);
+audio_output_state_save(FILE *fp);
+
+/**
+ * Generates a version number for the current state of the audio
+ * outputs. This is used by timer_save_state_file() to determine
+ * whether the state has changed and the state file should be saved.
+ */
+unsigned
+audio_output_state_get_version(void);
#endif
diff --git a/src/output_thread.c b/src/output_thread.c
index 770b377e8..fb1701591 100644
--- a/src/output_thread.c
+++ b/src/output_thread.c
@@ -23,6 +23,8 @@
#include "chunk.h"
#include "pipe.h"
#include "player_control.h"
+#include "filter_plugin.h"
+#include "filter/convert_filter_plugin.h"
#include <glib.h>
@@ -37,27 +39,212 @@ static void ao_command_finished(struct audio_output *ao)
{
assert(ao->command != AO_COMMAND_NONE);
ao->command = AO_COMMAND_NONE;
+
+ g_mutex_unlock(ao->mutex);
notify_signal(&audio_output_client_notify);
+ g_mutex_lock(ao->mutex);
+}
+
+static bool
+ao_enable(struct audio_output *ao)
+{
+ GError *error = NULL;
+ bool success;
+
+ if (ao->really_enabled)
+ return true;
+
+ g_mutex_unlock(ao->mutex);
+ success = ao_plugin_enable(ao->plugin, ao->data, &error);
+ g_mutex_lock(ao->mutex);
+ if (!success) {
+ g_warning("Failed to enable \"%s\" [%s]: %s\n",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ ao->really_enabled = true;
+ return true;
+}
+
+static void
+ao_close(struct audio_output *ao, bool drain);
+
+static void
+ao_disable(struct audio_output *ao)
+{
+ if (ao->open)
+ ao_close(ao, false);
+
+ if (ao->really_enabled) {
+ ao->really_enabled = false;
+
+ g_mutex_unlock(ao->mutex);
+ ao_plugin_disable(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+ }
+}
+
+static void
+ao_open(struct audio_output *ao)
+{
+ bool success;
+ GError *error = NULL;
+ const struct audio_format *filter_audio_format;
+
+ assert(!ao->open);
+ assert(ao->fail_timer == NULL);
+ assert(ao->pipe != NULL);
+ assert(ao->chunk == NULL);
+
+ /* enable the device (just in case the last enable has failed) */
+
+ if (!ao_enable(ao))
+ /* still no luck */
+ return;
+
+ /* open the filter */
+
+ filter_audio_format = filter_open(ao->filter, &ao->in_audio_format,
+ &error);
+ if (filter_audio_format == NULL) {
+ g_warning("Failed to open filter for \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ ao->out_audio_format = *filter_audio_format;
+ audio_format_mask_apply(&ao->out_audio_format,
+ &ao->config_audio_format);
+
+ g_mutex_unlock(ao->mutex);
+ success = ao_plugin_open(ao->plugin, ao->data,
+ &ao->out_audio_format,
+ &error);
+ g_mutex_lock(ao->mutex);
+
+ assert(!ao->open);
+
+ if (!success) {
+ g_warning("Failed to open \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ filter_close(ao->filter);
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, &ao->out_audio_format);
+
+ ao->open = true;
+
+ g_debug("opened plugin=%s name=\"%s\" "
+ "audio_format=%u:%u:%u:%u",
+ ao->plugin->name, ao->name,
+ ao->out_audio_format.sample_rate,
+ ao->out_audio_format.bits,
+ ao->out_audio_format.channels,
+ ao->out_audio_format.reverse_endian);
+
+ if (!audio_format_equals(&ao->in_audio_format,
+ &ao->out_audio_format))
+ g_debug("converting from %u:%u:%u:%u",
+ ao->in_audio_format.sample_rate,
+ ao->in_audio_format.bits,
+ ao->in_audio_format.channels,
+ ao->in_audio_format.reverse_endian);
}
static void
-ao_close(struct audio_output *ao)
+ao_close(struct audio_output *ao, bool drain)
{
assert(ao->open);
ao->pipe = NULL;
- g_mutex_lock(ao->mutex);
ao->chunk = NULL;
ao->open = false;
+
g_mutex_unlock(ao->mutex);
+ if (drain)
+ ao_plugin_drain(ao->plugin, ao->data);
+ else
+ ao_plugin_cancel(ao->plugin, ao->data);
+
ao_plugin_close(ao->plugin, ao->data);
- pcm_convert_deinit(&ao->convert_state);
+ filter_close(ao->filter);
+
+ g_mutex_lock(ao->mutex);
g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name);
}
+static void
+ao_reopen_filter(struct audio_output *ao)
+{
+ const struct audio_format *filter_audio_format;
+ GError *error = NULL;
+
+ filter_close(ao->filter);
+ filter_audio_format = filter_open(ao->filter, &ao->in_audio_format,
+ &error);
+ if (filter_audio_format == NULL) {
+ g_warning("Failed to open filter for \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ /* this is a little code duplication fro ao_close(),
+ but we cannot call this function because we must
+ not call filter_close(ao->filter) again */
+
+ ao->pipe = NULL;
+
+ ao->chunk = NULL;
+ ao->open = false;
+ ao->fail_timer = g_timer_new();
+
+ g_mutex_unlock(ao->mutex);
+ ao_plugin_close(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, &ao->out_audio_format);
+}
+
+static void
+ao_reopen(struct audio_output *ao)
+{
+ if (!audio_format_fully_defined(&ao->config_audio_format)) {
+ if (ao->open) {
+ const struct music_pipe *mp = ao->pipe;
+ ao_close(ao, true);
+ ao->pipe = mp;
+ }
+
+ /* no audio format is configured: copy in->out, let
+ the output's open() method determine the effective
+ out_audio_format */
+ ao->out_audio_format = ao->in_audio_format;
+ audio_format_mask_apply(&ao->out_audio_format,
+ &ao->config_audio_format);
+ }
+
+ if (ao->open)
+ /* the audio format has changed, and all filters have
+ to be reconfigured */
+ ao_reopen_filter(ao);
+ else
+ ao_open(ao);
+}
+
static bool
ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
{
@@ -65,43 +252,49 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
size_t size = chunk->length;
GError *error = NULL;
+ assert(ao != NULL);
+ assert(ao->filter != NULL);
assert(!music_chunk_is_empty(chunk));
assert(music_chunk_check_format(chunk, &ao->in_audio_format));
assert(size % audio_format_frame_size(&ao->in_audio_format) == 0);
- if (chunk->tag != NULL)
+ if (chunk->tag != NULL) {
+ g_mutex_unlock(ao->mutex);
ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag);
+ g_mutex_lock(ao->mutex);
+ }
if (size == 0)
return true;
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format)) {
- data = pcm_convert(&ao->convert_state,
- &ao->in_audio_format, data, size,
- &ao->out_audio_format, &size);
-
- /* under certain circumstances, pcm_convert() may
- return an empty buffer - this condition should be
- investigated further, but for now, do this check as
- a workaround: */
- if (data == NULL)
- return true;
+ data = filter_filter(ao->filter, data, size, &size, &error);
+ if (data == NULL) {
+ g_warning("\"%s\" [%s] failed to filter: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ ao_close(ao, false);
+
+ /* don't automatically reopen this device for 10
+ seconds */
+ ao->fail_timer = g_timer_new();
+ return false;
}
while (size > 0 && ao->command == AO_COMMAND_NONE) {
size_t nbytes;
+ g_mutex_unlock(ao->mutex);
nbytes = ao_plugin_play(ao->plugin, ao->data, data, size,
&error);
+ g_mutex_lock(ao->mutex);
if (nbytes == 0) {
/* play()==0 means failure */
g_warning("\"%s\" [%s] failed to play: %s",
ao->name, ao->plugin->name, error->message);
g_error_free(error);
- ao_plugin_cancel(ao->plugin, ao->data);
- ao_close(ao);
+ ao_close(ao, false);
/* don't automatically reopen this device for
10 seconds */
@@ -119,32 +312,45 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
return true;
}
-static void ao_play(struct audio_output *ao)
+static const struct music_chunk *
+ao_next_chunk(struct audio_output *ao)
+{
+ return ao->chunk != NULL
+ /* continue the previous play() call */
+ ? ao->chunk->next
+ /* get the first chunk from the pipe */
+ : music_pipe_peek(ao->pipe);
+}
+
+/**
+ * Plays all remaining chunks, until the tail of the pipe has been
+ * reached (and no more chunks are queued), or until a command is
+ * received.
+ *
+ * @return true if at least one chunk has been available, false if the
+ * tail of the pipe was already reached
+ */
+static bool
+ao_play(struct audio_output *ao)
{
bool success;
const struct music_chunk *chunk;
assert(ao->pipe != NULL);
- g_mutex_lock(ao->mutex);
- chunk = ao->chunk;
- if (chunk != NULL)
- /* continue the previous play() call */
- chunk = chunk->next;
- else
- chunk = music_pipe_peek(ao->pipe);
+ chunk = ao_next_chunk(ao);
+ if (chunk == NULL)
+ /* no chunk available */
+ return false;
+
ao->chunk_finished = false;
while (chunk != NULL && ao->command == AO_COMMAND_NONE) {
assert(!ao->chunk_finished);
ao->chunk = chunk;
- g_mutex_unlock(ao->mutex);
success = ao_play_chunk(ao, chunk);
-
- g_mutex_lock(ao->mutex);
-
if (!success) {
assert(ao->chunk == NULL);
break;
@@ -155,23 +361,32 @@ static void ao_play(struct audio_output *ao)
}
ao->chunk_finished = true;
+
g_mutex_unlock(ao->mutex);
+ player_lock_signal();
+ g_mutex_lock(ao->mutex);
- notify_signal(&pc.notify);
+ return true;
}
static void ao_pause(struct audio_output *ao)
{
bool ret;
+ g_mutex_unlock(ao->mutex);
ao_plugin_cancel(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
ao->pause = true;
ao_command_finished(ao);
do {
+ g_mutex_unlock(ao->mutex);
ret = ao_plugin_pause(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
if (!ret) {
- ao_close(ao);
+ ao_close(ao, false);
break;
}
} while (ao->command == AO_COMMAND_NONE);
@@ -182,56 +397,31 @@ static void ao_pause(struct audio_output *ao)
static gpointer audio_output_task(gpointer arg)
{
struct audio_output *ao = arg;
- bool ret;
- GError *error;
+
+ g_mutex_lock(ao->mutex);
while (1) {
switch (ao->command) {
case AO_COMMAND_NONE:
break;
- case AO_COMMAND_OPEN:
- assert(!ao->open);
- assert(ao->fail_timer == NULL);
- assert(ao->pipe != NULL);
- assert(ao->chunk == NULL);
-
- error = NULL;
- ret = ao_plugin_open(ao->plugin, ao->data,
- &ao->out_audio_format,
- &error);
-
- assert(!ao->open);
- if (ret) {
- pcm_convert_init(&ao->convert_state);
+ case AO_COMMAND_ENABLE:
+ ao_enable(ao);
+ ao_command_finished(ao);
+ break;
- g_mutex_lock(ao->mutex);
- ao->open = true;
- g_mutex_unlock(ao->mutex);
+ case AO_COMMAND_DISABLE:
+ ao_disable(ao);
+ ao_command_finished(ao);
+ break;
- g_debug("opened plugin=%s name=\"%s\" "
- "audio_format=%u:%u:%u",
- ao->plugin->name,
- ao->name,
- ao->out_audio_format.sample_rate,
- ao->out_audio_format.bits,
- ao->out_audio_format.channels);
-
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format))
- g_debug("converting from %u:%u:%u",
- ao->in_audio_format.sample_rate,
- ao->in_audio_format.bits,
- ao->in_audio_format.channels);
- } else {
- g_warning("Failed to open \"%s\" [%s]: %s",
- ao->name, ao->plugin->name,
- error->message);
- g_error_free(error);
-
- ao->fail_timer = g_timer_new();
- }
+ case AO_COMMAND_OPEN:
+ ao_open(ao);
+ ao_command_finished(ao);
+ break;
+ case AO_COMMAND_REOPEN:
+ ao_reopen(ao);
ao_command_finished(ao);
break;
@@ -239,11 +429,7 @@ static gpointer audio_output_task(gpointer arg)
assert(ao->open);
assert(ao->pipe != NULL);
- ao->pipe = NULL;
- ao->chunk = NULL;
-
- ao_plugin_cancel(ao->plugin, ao->data);
- ao_close(ao);
+ ao_close(ao, false);
ao_command_finished(ao);
break;
@@ -264,6 +450,19 @@ static gpointer audio_output_task(gpointer arg)
the new command first */
continue;
+ case AO_COMMAND_DRAIN:
+ if (ao->open) {
+ assert(ao->chunk == NULL);
+ assert(music_pipe_peek(ao->pipe) == NULL);
+
+ g_mutex_unlock(ao->mutex);
+ ao_plugin_drain(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+ }
+
+ ao_command_finished(ao);
+ continue;
+
case AO_COMMAND_CANCEL:
ao->chunk = NULL;
if (ao->open)
@@ -273,19 +472,24 @@ static gpointer audio_output_task(gpointer arg)
/* the player thread will now clear our music
pipe - wait for a notify, to give it some
time */
- notify_wait(&ao->notify);
+ if (ao->command == AO_COMMAND_NONE)
+ g_cond_wait(ao->cond, ao->mutex);
continue;
case AO_COMMAND_KILL:
ao->chunk = NULL;
ao_command_finished(ao);
+ g_mutex_unlock(ao->mutex);
return NULL;
}
- if (ao->open)
- ao_play(ao);
+ if (ao->open && ao_play(ao))
+ /* don't wait for an event if there are more
+ chunks in the pipe */
+ continue;
- notify_wait(&ao->notify);
+ if (ao->command == AO_COMMAND_NONE)
+ g_cond_wait(ao->cond, ao->mutex);
}
}
diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c
new file mode 100644
index 000000000..1c1caeeca
--- /dev/null
+++ b/src/pcm_byteswap.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2003-2009 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 "pcm_byteswap.h"
+#include "pcm_buffer.h"
+
+#include <glib.h>
+
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pcm"
+
+static inline uint16_t swab16(uint16_t x)
+{
+ return (x << 8) | (x >> 8);
+}
+
+const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer,
+ const int16_t *src, size_t len)
+{
+ unsigned i;
+ int16_t *buf = pcm_buffer_get(buffer, len);
+
+ assert(buf != NULL);
+
+ for (i = 0; i < len / 2; i++)
+ buf[i] = swab16(src[i]);
+
+ return buf;
+}
+
+static inline uint32_t swab32(uint32_t x)
+{
+ return (x << 24) |
+ ((x & 0xff00) << 8) |
+ ((x & 0xff0000) >> 8) |
+ (x >> 24);
+}
+
+const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer,
+ const int32_t *src, size_t len)
+{
+ unsigned i;
+ int32_t *buf = pcm_buffer_get(buffer, len);
+
+ assert(buf != NULL);
+
+ for (i = 0; i < len / 4; i++)
+ buf[i] = swab32(src[i]);
+
+ return buf;
+}
diff --git a/src/pcm_byteswap.h b/src/pcm_byteswap.h
new file mode 100644
index 000000000..e1196d9b2
--- /dev/null
+++ b/src/pcm_byteswap.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2003-2009 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_PCM_BYTESWAP_H
+#define MPD_PCM_BYTESWAP_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+struct pcm_buffer;
+
+/**
+ * Changes the endianness of 16 bit PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @param src the source PCM buffer
+ * @param src_size the number of bytes in #src
+ * @return the destination buffer
+ */
+const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer,
+ const int16_t *src, size_t len);
+
+/**
+ * Changes the endianness of 32-bit (or 24-bit) PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @param src the source PCM buffer
+ * @param src_size the number of bytes in #src
+ * @return the destination buffer
+ */
+const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer,
+ const int32_t *src, size_t len);
+
+#endif
diff --git a/src/pcm_channels.c b/src/pcm_channels.c
index 969ddff32..38445f958 100644
--- a/src/pcm_channels.c
+++ b/src/pcm_channels.c
@@ -20,13 +20,8 @@
#include "pcm_channels.h"
#include "pcm_buffer.h"
-#include <glib.h>
-
#include <assert.h>
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
static void
pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src,
unsigned num_frames)
@@ -75,8 +70,8 @@ pcm_convert_channels_16_n_to_2(int16_t *dest,
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int16_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int16_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
@@ -92,11 +87,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer,
else if (dest_channels == 2)
pcm_convert_channels_16_n_to_2(dest, src_channels, src,
num_frames);
- else {
- g_warning("conversion %u->%u channels is not supported",
- src_channels, dest_channels);
+ else
return NULL;
- }
return dest;
}
@@ -149,8 +141,8 @@ pcm_convert_channels_24_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_24(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
@@ -166,11 +158,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer,
else if (dest_channels == 2)
pcm_convert_channels_24_n_to_2(dest, src_channels, src,
num_frames);
- else {
- g_warning("conversion %u->%u channels is not supported",
- src_channels, dest_channels);
+ else
return NULL;
- }
return dest;
}
@@ -218,8 +207,8 @@ pcm_convert_channels_32_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_32(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
@@ -235,11 +224,8 @@ pcm_convert_channels_32(struct pcm_buffer *buffer,
else if (dest_channels == 2)
pcm_convert_channels_32_n_to_2(dest, src_channels, src,
num_frames);
- else {
- g_warning("conversion %u->%u channels is not supported",
- src_channels, dest_channels);
+ else
return NULL;
- }
return dest;
}
diff --git a/src/pcm_channels.h b/src/pcm_channels.h
index accf4b07b..13b7ffbf3 100644
--- a/src/pcm_channels.h
+++ b/src/pcm_channels.h
@@ -38,8 +38,8 @@ struct pcm_buffer;
*/
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int16_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int16_t *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -56,8 +56,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_channels_24(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -73,8 +73,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_channels_32(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r);
#endif
diff --git a/src/pcm_convert.c b/src/pcm_convert.c
index ebb4adff5..4879dc0ab 100644
--- a/src/pcm_convert.c
+++ b/src/pcm_convert.c
@@ -20,6 +20,7 @@
#include "pcm_convert.h"
#include "pcm_channels.h"
#include "pcm_format.h"
+#include "pcm_byteswap.h"
#include "audio_format.h"
#include <assert.h>
@@ -39,6 +40,7 @@ void pcm_convert_init(struct pcm_convert_state *state)
pcm_buffer_init(&state->format_buffer);
pcm_buffer_init(&state->channels_buffer);
+ pcm_buffer_init(&state->byteswap_buffer);
}
void pcm_convert_deinit(struct pcm_convert_state *state)
@@ -47,14 +49,15 @@ void pcm_convert_deinit(struct pcm_convert_state *state)
pcm_buffer_deinit(&state->format_buffer);
pcm_buffer_deinit(&state->channels_buffer);
+ pcm_buffer_deinit(&state->byteswap_buffer);
}
static const int16_t *
pcm_convert_16(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r)
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
{
const int16_t *buf;
size_t len;
@@ -64,24 +67,42 @@ pcm_convert_16(struct pcm_convert_state *state,
buf = pcm_convert_to_16(&state->format_buffer, &state->dither,
src_format->bits, src_buffer, src_size,
&len);
- if (!buf)
- g_error("pcm_convert_to_16() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to 16 bit is not implemented",
+ src_format->bits);
+ return NULL;
+ }
if (src_format->channels != dest_format->channels) {
buf = pcm_convert_channels_16(&state->channels_buffer,
dest_format->channels,
src_format->channels,
buf, len, &len);
- if (!buf)
- g_error("pcm_convert_channels_16() failed");
+ if (buf == 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;
+ }
}
- if (src_format->sample_rate != dest_format->sample_rate)
+ if (src_format->sample_rate != dest_format->sample_rate) {
buf = pcm_resample_16(&state->resample,
dest_format->channels,
src_format->sample_rate, buf, len,
- dest_format->sample_rate,
- &len);
+ dest_format->sample_rate, &len,
+ error_r);
+ if (buf == NULL)
+ return NULL;
+ }
+
+ if (dest_format->reverse_endian) {
+ buf = pcm_byteswap_16(&state->byteswap_buffer, buf, len);
+ assert(buf != NULL);
+ }
*dest_size_r = len;
return buf;
@@ -91,8 +112,8 @@ static const int32_t *
pcm_convert_24(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r)
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
{
const int32_t *buf;
size_t len;
@@ -101,24 +122,42 @@ pcm_convert_24(struct pcm_convert_state *state,
buf = pcm_convert_to_24(&state->format_buffer, src_format->bits,
src_buffer, src_size, &len);
- if (!buf)
- g_error("pcm_convert_to_24() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to 24 bit is not implemented",
+ src_format->bits);
+ return NULL;
+ }
if (src_format->channels != dest_format->channels) {
buf = pcm_convert_channels_24(&state->channels_buffer,
dest_format->channels,
src_format->channels,
buf, len, &len);
- if (!buf)
- g_error("pcm_convert_channels_24() failed");
+ if (buf == 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;
+ }
}
- if (src_format->sample_rate != dest_format->sample_rate)
+ if (src_format->sample_rate != dest_format->sample_rate) {
buf = pcm_resample_24(&state->resample,
dest_format->channels,
src_format->sample_rate, buf, len,
- dest_format->sample_rate,
- &len);
+ dest_format->sample_rate, &len,
+ error_r);
+ if (buf == NULL)
+ return NULL;
+ }
+
+ if (dest_format->reverse_endian) {
+ buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len);
+ assert(buf != NULL);
+ }
*dest_size_r = len;
return buf;
@@ -128,8 +167,8 @@ static const int32_t *
pcm_convert_32(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r)
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
{
const int32_t *buf;
size_t len;
@@ -138,24 +177,42 @@ pcm_convert_32(struct pcm_convert_state *state,
buf = pcm_convert_to_32(&state->format_buffer, src_format->bits,
src_buffer, src_size, &len);
- if (!buf)
- g_error("pcm_convert_to_32() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to 24 bit is not implemented",
+ src_format->bits);
+ return NULL;
+ }
if (src_format->channels != dest_format->channels) {
buf = pcm_convert_channels_32(&state->channels_buffer,
dest_format->channels,
src_format->channels,
buf, len, &len);
- if (!buf)
- g_error("pcm_convert_channels_32() failed");
+ if (buf == 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;
+ }
}
- if (src_format->sample_rate != dest_format->sample_rate)
+ if (src_format->sample_rate != dest_format->sample_rate) {
buf = pcm_resample_32(&state->resample,
dest_format->channels,
src_format->sample_rate, buf, len,
- dest_format->sample_rate,
- &len);
+ dest_format->sample_rate, &len,
+ error_r);
+ if (buf == NULL)
+ return buf;
+ }
+
+ if (dest_format->reverse_endian) {
+ buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len);
+ assert(buf != NULL);
+ }
*dest_size_r = len;
return buf;
@@ -166,26 +223,32 @@ pcm_convert(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src, size_t src_size,
const struct audio_format *dest_format,
- size_t *dest_size_r)
+ size_t *dest_size_r,
+ GError **error_r)
{
switch (dest_format->bits) {
case 16:
return pcm_convert_16(state,
src_format, src, src_size,
- dest_format, dest_size_r);
+ dest_format, dest_size_r,
+ error_r);
case 24:
return pcm_convert_24(state,
src_format, src, src_size,
- dest_format, dest_size_r);
+ dest_format, dest_size_r,
+ error_r);
case 32:
return pcm_convert_32(state,
src_format, src, src_size,
- dest_format, dest_size_r);
+ dest_format, dest_size_r,
+ error_r);
default:
- g_error("cannot convert to %u bit\n", dest_format->bits);
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "PCM conversion to %u bit is not implemented",
+ dest_format->bits);
return NULL;
}
}
diff --git a/src/pcm_convert.h b/src/pcm_convert.h
index be08ad8a8..7ef0782df 100644
--- a/src/pcm_convert.h
+++ b/src/pcm_convert.h
@@ -41,8 +41,17 @@ struct pcm_convert_state {
/** the buffer for converting the channel count */
struct pcm_buffer channels_buffer;
+
+ /** the buffer for swapping the byte order */
+ struct pcm_buffer byteswap_buffer;
};
+static inline GQuark
+pcm_convert_quark(void)
+{
+ return g_quark_from_static_string("pcm_convert");
+}
+
/**
* Initializes a pcm_convert_state object.
*/
@@ -63,13 +72,16 @@ void pcm_convert_deinit(struct pcm_convert_state *state);
* @param src_size the size of #src in bytes
* @param dest_format the requested destination audio format
* @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return the destination buffer, or NULL on error
*/
const void *
pcm_convert(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src, size_t src_size,
const struct audio_format *dest_format,
- size_t *dest_size_r);
+ size_t *dest_size_r,
+ GError **error_r);
#endif
diff --git a/src/pcm_format.c b/src/pcm_format.c
index 0e686e17c..64e5167b5 100644
--- a/src/pcm_format.c
+++ b/src/pcm_format.c
@@ -21,8 +21,6 @@
#include "pcm_dither.h"
#include "pcm_buffer.h"
-#include <glib.h>
-
static void
pcm_convert_8_to_16(int16_t *out, const int8_t *in,
unsigned num_samples)
@@ -93,7 +91,6 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
return dest;
}
- g_warning("only 8 or 16 bits are supported for conversion!\n");
return NULL;
}
@@ -168,7 +165,6 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
return dest;
}
- g_warning("only 8 or 24 bits are supported for conversion!\n");
return NULL;
}
@@ -243,6 +239,5 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
return src;
}
- g_warning("only 8 or 32 bits are supported for conversion!\n");
return NULL;
}
diff --git a/src/pcm_resample.c b/src/pcm_resample.c
index d1360d02a..f09c65a32 100644
--- a/src/pcm_resample.c
+++ b/src/pcm_resample.c
@@ -62,16 +62,18 @@ void pcm_resample_deinit(struct pcm_resample_state *state)
const int16_t *
pcm_resample_16(struct pcm_resample_state *state,
uint8_t channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned src_rate, const int16_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
#ifdef HAVE_LIBSAMPLERATE
if (pcm_resample_lsr_enabled())
return pcm_resample_lsr_16(state, channels,
src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
#endif
return pcm_resample_fallback_16(state, channels,
@@ -82,16 +84,18 @@ pcm_resample_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_32(struct pcm_resample_state *state,
uint8_t channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned src_rate, const int32_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
#ifdef HAVE_LIBSAMPLERATE
if (pcm_resample_lsr_enabled())
return pcm_resample_lsr_32(state, channels,
src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
#endif
return pcm_resample_fallback_32(state, channels,
diff --git a/src/pcm_resample.h b/src/pcm_resample.h
index 44720f7b2..9d03bbfbf 100644
--- a/src/pcm_resample.h
+++ b/src/pcm_resample.h
@@ -48,7 +48,7 @@ struct pcm_resample_state {
uint8_t channels;
} prev;
- bool error;
+ int error;
#endif
struct pcm_buffer buffer;
@@ -82,8 +82,8 @@ pcm_resample_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
/**
* Resamples 32 bit PCM data.
@@ -102,8 +102,8 @@ pcm_resample_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
/**
* Resamples 24 bit PCM data.
@@ -122,14 +122,14 @@ pcm_resample_24(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
/* reuse the 32 bit code - the resampler code doesn't care if
the upper 8 bits are actually used */
return pcm_resample_32(state, channels,
src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
+ dest_rate, dest_size_r, error_r);
}
#endif
diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c
index 36af51ad0..9a403ddc7 100644
--- a/src/pcm_resample_fallback.c
+++ b/src/pcm_resample_fallback.c
@@ -20,7 +20,6 @@
#include "pcm_resample_internal.h"
#include <assert.h>
-#include <glib.h>
void
pcm_resample_fallback_deinit(struct pcm_resample_state *state)
@@ -74,8 +73,7 @@ const int32_t *
pcm_resample_fallback_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
- const int32_t *src_buffer,
- G_GNUC_UNUSED size_t src_size,
+ const int32_t *src_buffer, size_t src_size,
unsigned dest_rate,
size_t *dest_size_r)
{
diff --git a/src/pcm_resample_internal.h b/src/pcm_resample_internal.h
index a10ba08cd..74363a590 100644
--- a/src/pcm_resample_internal.h
+++ b/src/pcm_resample_internal.h
@@ -40,8 +40,8 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
const int32_t *
pcm_resample_lsr_32(struct pcm_resample_state *state,
@@ -49,8 +49,8 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
unsigned src_rate,
const int32_t *src_buffer,
G_GNUC_UNUSED size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
#endif
diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c
index 6d019e892..66a1c3193 100644
--- a/src/pcm_resample_libsamplerate.c
+++ b/src/pcm_resample_libsamplerate.c
@@ -30,6 +30,12 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "pcm"
+static inline GQuark
+libsamplerate_quark(void)
+{
+ return g_quark_from_static_string("libsamplerate");
+}
+
void
pcm_resample_lsr_deinit(struct pcm_resample_state *state)
{
@@ -77,9 +83,10 @@ out:
return convalgo;
}
-static void
+static bool
pcm_resample_set(struct pcm_resample_state *state,
- uint8_t channels, unsigned src_rate, unsigned dest_rate)
+ uint8_t channels, unsigned src_rate, unsigned dest_rate,
+ GError **error_r)
{
static int convalgo = -1;
int error;
@@ -92,9 +99,9 @@ pcm_resample_set(struct pcm_resample_state *state,
if (channels == state->prev.channels &&
src_rate == state->prev.src_rate &&
dest_rate == state->prev.dest_rate)
- return;
+ return true;
- state->error = false;
+ state->error = 0;
state->prev.channels = channels;
state->prev.src_rate = src_rate;
state->prev.dest_rate = dest_rate;
@@ -104,16 +111,18 @@ pcm_resample_set(struct pcm_resample_state *state,
state->state = src_new(convalgo, channels, &error);
if (!state->state) {
- g_warning("cannot create new libsamplerate state: %s",
- src_strerror(error));
- state->error = true;
- return;
+ g_set_error(error_r, libsamplerate_quark(), state->error,
+ "libsamplerate initialization has failed: %s",
+ src_strerror(error));
+ return false;
}
data->src_ratio = (double)dest_rate / (double)src_rate;
g_debug("setting samplerate conversion ratio to %.2lf",
data->src_ratio);
src_set_ratio(state->state, data->src_ratio);
+
+ return true;
}
const int16_t *
@@ -121,9 +130,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
+ bool success;
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
@@ -132,11 +142,18 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
- pcm_resample_set(state, channels, src_rate, dest_rate);
+ success = pcm_resample_set(state, channels, src_rate, dest_rate,
+ error_r);
+ if (!success)
+ return NULL;
/* there was an error previously, and nothing has changed */
- if (state->error)
+ if (state->error) {
+ g_set_error(error_r, libsamplerate_quark(), state->error,
+ "libsamplerate has failed: %s",
+ src_strerror(state->error));
return NULL;
+ }
data->input_frames = src_size / sizeof(*src_buffer) / channels;
data_in_size = data->input_frames * sizeof(float) * channels;
@@ -151,9 +168,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
error = src_process(state->state, data);
if (error) {
- g_warning("error processing samples with libsamplerate: %s",
- src_strerror(error));
- state->error = true;
+ g_set_error(error_r, libsamplerate_quark(), error,
+ "libsamplerate has failed: %s",
+ src_strerror(error));
+ state->error = error;
return NULL;
}
@@ -191,9 +209,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
+ bool success;
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
@@ -202,11 +221,18 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
- pcm_resample_set(state, channels, src_rate, dest_rate);
+ success = pcm_resample_set(state, channels, src_rate, dest_rate,
+ error_r);
+ if (!success)
+ return NULL;
/* there was an error previously, and nothing has changed */
- if (state->error)
+ if (state->error) {
+ g_set_error(error_r, libsamplerate_quark(), state->error,
+ "libsamplerate has failed: %s",
+ src_strerror(state->error));
return NULL;
+ }
data->input_frames = src_size / sizeof(*src_buffer) / channels;
data_in_size = data->input_frames * sizeof(float) * channels;
@@ -221,9 +247,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
error = src_process(state->state, data);
if (error) {
- g_warning("error processing samples with libsamplerate: %s",
- src_strerror(error));
- state->error = true;
+ g_set_error(error_r, libsamplerate_quark(), error,
+ "libsamplerate has failed: %s",
+ src_strerror(error));
+ state->error = error;
return NULL;
}
diff --git a/src/permission.c b/src/permission.c
index 7df4e27fc..a65941771 100644
--- a/src/permission.c
+++ b/src/permission.c
@@ -111,7 +111,7 @@ void initPermissions(void)
permission_default = parsePermissions(param->value);
}
-int getPermissionFromPassword(char *password, unsigned *permission)
+int getPermissionFromPassword(char const* password, unsigned* permission)
{
bool found;
gpointer key, value;
diff --git a/src/permission.h b/src/permission.h
index bad26aa3c..2a5d999ce 100644
--- a/src/permission.h
+++ b/src/permission.h
@@ -27,7 +27,7 @@
#define PERMISSION_ADMIN 8
-int getPermissionFromPassword(char *password, unsigned *permission);
+int getPermissionFromPassword(char const* password, unsigned* permission);
void finishPermissions(void);
diff --git a/src/player_control.c b/src/player_control.c
index ac4b006dd..220d39a22 100644
--- a/src/player_control.c
+++ b/src/player_control.c
@@ -18,6 +18,7 @@
*/
#include "player_control.h"
+#include "decoder_control.h"
#include "path.h"
#include "log.h"
#include "tag.h"
@@ -35,17 +36,28 @@ void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play)
{
pc.buffer_chunks = buffer_chunks;
pc.buffered_before_play = buffered_before_play;
- notify_init(&pc.notify);
+
+ pc.mutex = g_mutex_new();
+ pc.cond = g_cond_new();
+
pc.command = PLAYER_COMMAND_NONE;
pc.error = PLAYER_ERROR_NOERROR;
pc.state = PLAYER_STATE_STOP;
pc.cross_fade_seconds = 0;
- pc.software_volume = PCM_VOLUME_1;
}
void pc_deinit(void)
{
- notify_deinit(&pc.notify);
+ g_cond_free(pc.cond);
+ g_mutex_free(pc.mutex);
+}
+
+void
+player_wait_decoder(struct decoder_control *dc)
+{
+ /* during this function, the decoder lock is held, because
+ we're waiting for the decoder thread */
+ g_cond_wait(pc.cond, dc->mutex);
}
void
@@ -57,27 +69,44 @@ pc_song_deleted(const struct song *song)
}
}
-static void player_command(enum player_command cmd)
+static void
+player_command_wait_locked(void)
+{
+ while (pc.command != PLAYER_COMMAND_NONE)
+ g_cond_wait(main_cond, pc.mutex);
+}
+
+static void
+player_command_locked(enum player_command cmd)
{
assert(pc.command == PLAYER_COMMAND_NONE);
pc.command = cmd;
- while (pc.command != PLAYER_COMMAND_NONE) {
- notify_signal(&pc.notify);
- notify_wait(&main_notify);
- }
+ player_signal();
+ player_command_wait_locked();
+}
+
+static void
+player_command(enum player_command cmd)
+{
+ player_lock();
+ player_command_locked(cmd);
+ player_unlock();
}
void
-playerPlay(struct song *song)
+pc_play(struct song *song)
{
assert(song != NULL);
if (pc.state != PLAYER_STATE_STOP)
player_command(PLAYER_COMMAND_STOP);
- pc.next_song = song;
- player_command(PLAYER_COMMAND_PLAY);
+ assert(pc.next_song == NULL);
+
+ pc_enqueue_song(song);
+
+ assert(pc.next_song == NULL);
idle_add(IDLE_PLAYER);
}
@@ -85,16 +114,26 @@ playerPlay(struct song *song)
void pc_cancel(void)
{
player_command(PLAYER_COMMAND_CANCEL);
+ assert(pc.next_song == NULL);
}
-void playerWait(void)
+void
+pc_stop(void)
{
player_command(PLAYER_COMMAND_CLOSE_AUDIO);
+ assert(pc.next_song == NULL);
idle_add(IDLE_PLAYER);
}
-void playerKill(void)
+void
+pc_update_audio(void)
+{
+ player_command(PLAYER_COMMAND_UPDATE_AUDIO);
+}
+
+void
+pc_kill(void)
{
assert(pc.thread != NULL);
@@ -105,7 +144,8 @@ void playerKill(void)
idle_add(IDLE_PLAYER);
}
-void playerPause(void)
+void
+pc_pause(void)
{
if (pc.state != PLAYER_STATE_STOP) {
player_command(PLAYER_COMMAND_PAUSE);
@@ -113,7 +153,8 @@ void playerPause(void)
}
}
-void playerSetPause(int pause_flag)
+void
+pc_set_pause(bool pause_flag)
{
switch (pc.state) {
case PLAYER_STATE_STOP:
@@ -121,41 +162,46 @@ void playerSetPause(int pause_flag)
case PLAYER_STATE_PLAY:
if (pause_flag)
- playerPause();
+ pc_pause();
break;
+
case PLAYER_STATE_PAUSE:
if (!pause_flag)
- playerPause();
+ pc_pause();
break;
}
}
-int getPlayerElapsedTime(void)
+void
+pc_get_status(struct player_status *status)
{
- return (int)(pc.elapsed_time + 0.5);
-}
+ player_command(PLAYER_COMMAND_REFRESH);
-unsigned long getPlayerBitRate(void)
-{
- return pc.bit_rate;
-}
+ status->state = pc.state;
-int getPlayerTotalTime(void)
-{
- return (int)(pc.total_time + 0.5);
+ if (pc.state != PLAYER_STATE_STOP) {
+ status->bit_rate = pc.bit_rate;
+ status->audio_format = pc.audio_format;
+ status->total_time = pc.total_time;
+ status->elapsed_time = pc.elapsed_time;
+ }
}
-enum player_state getPlayerState(void)
+enum player_state
+pc_get_state(void)
{
return pc.state;
}
-void clearPlayerError(void)
+void
+pc_clear_error(void)
{
- pc.error = 0;
+ pc.error = PLAYER_ERROR_NOERROR;
+ pc.errored_song = NULL;
}
-enum player_error getPlayerError(void)
+enum player_error
+pc_get_error(void)
{
return pc.error;
}
@@ -166,58 +212,56 @@ pc_errored_song_uri(void)
return song_get_uri(pc.errored_song);
}
-char *getPlayerErrorStr(void)
+char *
+pc_get_error_message(void)
{
- /* static OK here, only one user in main task */
- static char error[MPD_PATH_MAX + 64]; /* still too much */
- static const size_t errorlen = sizeof(error);
+ char *error;
char *uri;
- *error = '\0'; /* likely */
-
switch (pc.error) {
case PLAYER_ERROR_NOERROR:
- break;
+ return NULL;
case PLAYER_ERROR_FILENOTFOUND:
uri = pc_errored_song_uri();
- snprintf(error, errorlen,
- "file \"%s\" does not exist or is inaccessible", uri);
+ error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri);
g_free(uri);
- break;
+ return error;
case PLAYER_ERROR_FILE:
uri = pc_errored_song_uri();
- snprintf(error, errorlen, "problems decoding \"%s\"", uri);
+ error = g_strdup_printf("problems decoding \"%s\"", uri);
g_free(uri);
- break;
+ return error;
case PLAYER_ERROR_AUDIO:
- strcpy(error, "problems opening audio device");
- break;
+ return g_strdup("problems opening audio device");
case PLAYER_ERROR_SYSTEM:
- strcpy(error, "system error occured");
- break;
+ return g_strdup("system error occured");
case PLAYER_ERROR_UNKTYPE:
uri = pc_errored_song_uri();
- snprintf(error, errorlen,
- "file type of \"%s\" is unknown", uri);
+ error = g_strdup_printf("file type of \"%s\" is unknown", uri);
g_free(uri);
- break;
+ return error;
}
- return *error ? error : NULL;
+
+ assert(false);
+ return NULL;
}
void
-queueSong(struct song *song)
+pc_enqueue_song(struct song *song)
{
assert(song != NULL);
+
+ player_lock();
assert(pc.next_song == NULL);
pc.next_song = song;
- player_command(PLAYER_COMMAND_QUEUE);
+ player_command_locked(PLAYER_COMMAND_QUEUE);
+ player_unlock();
}
bool
@@ -228,9 +272,11 @@ pc_seek(struct song *song, float seek_time)
if (pc.state == PLAYER_STATE_STOP)
return false;
+ player_lock();
pc.next_song = song;
pc.seek_where = seek_time;
- player_command(PLAYER_COMMAND_SEEK);
+ player_command_locked(PLAYER_COMMAND_SEEK);
+ player_unlock();
assert(pc.next_song == NULL);
@@ -239,31 +285,24 @@ pc_seek(struct song *song, float seek_time)
return true;
}
-float getPlayerCrossFade(void)
+float
+pc_get_cross_fade(void)
{
return pc.cross_fade_seconds;
}
-void setPlayerCrossFade(float crossFadeInSeconds)
+void
+pc_set_cross_fade(float cross_fade_seconds)
{
- if (crossFadeInSeconds < 0)
- crossFadeInSeconds = 0;
- pc.cross_fade_seconds = crossFadeInSeconds;
+ if (cross_fade_seconds < 0)
+ cross_fade_seconds = 0;
+ pc.cross_fade_seconds = cross_fade_seconds;
idle_add(IDLE_OPTIONS);
}
-void setPlayerSoftwareVolume(int volume)
-{
- if (volume > PCM_VOLUME_1)
- volume = PCM_VOLUME_1;
- else if (volume < 0)
- volume = 0;
-
- pc.software_volume = volume;
-}
-
-double getPlayerTotalPlayTime(void)
+double
+pc_get_total_play_time(void)
{
return pc.total_play_time;
}
diff --git a/src/player_control.h b/src/player_control.h
index b1f7481cd..e279067d5 100644
--- a/src/player_control.h
+++ b/src/player_control.h
@@ -25,6 +25,8 @@
#include <stdint.h>
+struct decoder_control;
+
enum player_state {
PLAYER_STATE_STOP = 0,
PLAYER_STATE_PAUSE,
@@ -35,11 +37,16 @@ enum player_command {
PLAYER_COMMAND_NONE = 0,
PLAYER_COMMAND_EXIT,
PLAYER_COMMAND_STOP,
- PLAYER_COMMAND_PLAY,
PLAYER_COMMAND_PAUSE,
PLAYER_COMMAND_SEEK,
PLAYER_COMMAND_CLOSE_AUDIO,
+ /**
+ * At least one audio_output.enabled flag has been modified;
+ * commit those changes to the output threads.
+ */
+ PLAYER_COMMAND_UPDATE_AUDIO,
+
/** player_control.next_song has been updated */
PLAYER_COMMAND_QUEUE,
@@ -49,6 +56,12 @@ enum player_command {
* stop
*/
PLAYER_COMMAND_CANCEL,
+
+ /**
+ * Refresh status information in the #player_control struct,
+ * e.g. elapsed_time.
+ */
+ PLAYER_COMMAND_REFRESH,
};
enum player_error {
@@ -60,6 +73,14 @@ enum player_error {
PLAYER_ERROR_FILENOTFOUND,
};
+struct player_status {
+ enum player_state state;
+ uint16_t bit_rate;
+ struct audio_format audio_format;
+ float total_time;
+ float elapsed_time;
+};
+
struct player_control {
unsigned buffer_chunks;
@@ -69,19 +90,27 @@ struct player_control {
thread isn't running */
GThread *thread;
- struct notify notify;
- volatile enum player_command command;
- volatile enum player_state state;
- volatile enum player_error error;
+ /**
+ * This lock protects #command, #state, #error.
+ */
+ GMutex *mutex;
+
+ /**
+ * Trigger this object after you have modified #command.
+ */
+ GCond *cond;
+
+ enum player_command command;
+ enum player_state state;
+ enum player_error error;
uint16_t bit_rate;
struct audio_format audio_format;
float total_time;
float elapsed_time;
- struct song *volatile next_song;
- struct song *errored_song;
- volatile double seek_where;
+ struct song *next_song;
+ const struct song *errored_song;
+ double seek_where;
float cross_fade_seconds;
- uint16_t software_volume;
double total_play_time;
};
@@ -92,6 +121,67 @@ void pc_init(unsigned buffer_chunks, unsigned buffered_before_play);
void pc_deinit(void);
/**
+ * Locks the #player_control object.
+ */
+static inline void
+player_lock(void)
+{
+ g_mutex_lock(pc.mutex);
+}
+
+/**
+ * Unlocks the #player_control object.
+ */
+static inline void
+player_unlock(void)
+{
+ g_mutex_unlock(pc.mutex);
+}
+
+/**
+ * Waits for a signal on the #player_control object. This function is
+ * only valid in the player thread. The object must be locked prior
+ * to calling this function.
+ */
+static inline void
+player_wait(void)
+{
+ g_cond_wait(pc.cond, pc.mutex);
+}
+
+/**
+ * Waits for a signal on the #player_control object. This function is
+ * only valid in the player thread. The #decoder_control object must
+ * be locked prior to calling this function.
+ *
+ * Note the small difference to the player_wait() function!
+ */
+void
+player_wait_decoder(struct decoder_control *dc);
+
+/**
+ * Signals the #player_control object. The object should be locked
+ * prior to calling this function.
+ */
+static inline void
+player_signal(void)
+{
+ g_cond_signal(pc.cond);
+}
+
+/**
+ * Signals the #player_control object. The object is temporarily
+ * locked by this function.
+ */
+static inline void
+player_lock_signal(void)
+{
+ player_lock();
+ player_signal();
+ player_unlock();
+}
+
+/**
* 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.
@@ -100,37 +190,50 @@ void
pc_song_deleted(const struct song *song);
void
-playerPlay(struct song *song);
+pc_play(struct song *song);
/**
* see PLAYER_COMMAND_CANCEL
*/
void pc_cancel(void);
-void playerSetPause(int pause_flag);
-
-void playerPause(void);
+void
+pc_set_pause(bool pause_flag);
-void playerKill(void);
+void
+pc_pause(void);
-int getPlayerTotalTime(void);
+void
+pc_kill(void);
-int getPlayerElapsedTime(void);
+void
+pc_get_status(struct player_status *status);
-unsigned long getPlayerBitRate(void);
+enum player_state
+pc_get_state(void);
-enum player_state getPlayerState(void);
+void
+pc_clear_error(void);
-void clearPlayerError(void);
+/**
+ * Returns the human-readable message describing the last error during
+ * playback, NULL if no error occurred. The caller has to free the
+ * returned string.
+ */
+char *
+pc_get_error_message(void);
-char *getPlayerErrorStr(void);
+enum player_error
+pc_get_error(void);
-enum player_error getPlayerError(void);
+void
+pc_stop(void);
-void playerWait(void);
+void
+pc_update_audio(void);
void
-queueSong(struct song *song);
+pc_enqueue_song(struct song *song);
/**
* Makes the player thread seek the specified song to a position.
@@ -141,20 +244,13 @@ queueSong(struct song *song);
bool
pc_seek(struct song *song, float seek_time);
-void setPlayerCrossFade(float crossFadeInSeconds);
-
-float getPlayerCrossFade(void);
-
-void setPlayerSoftwareVolume(int volume);
-
-double getPlayerTotalPlayTime(void);
+void
+pc_set_cross_fade(float cross_fade_seconds);
-static inline const struct audio_format *
-player_get_audio_format(void)
-{
- return &pc.audio_format;
-}
+float
+pc_get_cross_fade(void);
-void playerInit(void);
+double
+pc_get_total_play_time(void);
#endif
diff --git a/src/player_thread.c b/src/player_thread.c
index 7fc55d3d1..c527c57b6 100644
--- a/src/player_thread.c
+++ b/src/player_thread.c
@@ -46,6 +46,8 @@ enum xfade_state {
};
struct player {
+ struct decoder_control *dc;
+
struct music_pipe *pipe;
/**
@@ -95,74 +97,129 @@ struct player {
struct audio_format play_audio_format;
/**
- * Coefficient for converting a PCM buffer size into a time
- * span.
+ * The time stamp of the chunk most recently sent to the
+ * output thread. This attribute is only used if
+ * audio_output_all_get_elapsed_time() didn't return a usable
+ * value; the output thread can estimate the elapsed time more
+ * precisly.
*/
- double size_to_time;
+ float elapsed_time;
};
static struct music_buffer *player_buffer;
-static void player_command_finished(void)
+static void player_command_finished_locked(void)
{
assert(pc.command != PLAYER_COMMAND_NONE);
pc.command = PLAYER_COMMAND_NONE;
- notify_signal(&main_notify);
+ g_cond_signal(main_cond);
+}
+
+static void player_command_finished(void)
+{
+ player_lock();
+ player_command_finished_locked();
+ player_unlock();
+}
+
+/**
+ * Start the decoder.
+ *
+ * Player lock is not held.
+ */
+static void
+player_dc_start(struct player *player, struct music_pipe *pipe)
+{
+ struct decoder_control *dc = player->dc;
+
+ assert(player->queued);
+ assert(pc.next_song != NULL);
+
+ dc_start(dc, pc.next_song, player_buffer, pipe);
}
/**
* Stop the decoder and clears (and frees) its music pipe.
+ *
+ * Player lock is not held.
*/
static void
player_dc_stop(struct player *player)
{
- dc_stop(&pc.notify);
+ struct decoder_control *dc = player->dc;
- if (dc.pipe != NULL) {
+ dc_stop(dc);
+
+ if (dc->pipe != NULL) {
/* clear and free the decoder pipe */
- music_pipe_clear(dc.pipe, player_buffer);
+ music_pipe_clear(dc->pipe, player_buffer);
- if (dc.pipe != player->pipe)
- music_pipe_free(dc.pipe);
+ if (dc->pipe != player->pipe)
+ music_pipe_free(dc->pipe);
- dc.pipe = NULL;
+ dc->pipe = NULL;
}
}
/**
+ * Returns true if the decoder is decoding the next song (or has begun
+ * decoding it, or has finished doing it), and the player hasn't
+ * switched to that song yet.
+ */
+static bool
+decoding_next_song(const struct player *player)
+{
+ return player->dc->pipe != NULL && player->dc->pipe != player->pipe;
+}
+
+/**
* After the decoder has been started asynchronously, wait for the
* "START" command to finish. The decoder may not be initialized yet,
* i.e. there is no audio_format information yet.
+ *
+ * The player lock is not held.
*/
static bool
player_wait_for_decoder(struct player *player)
{
- dc_command_wait(&pc.notify);
+ struct decoder_control *dc = player->dc;
+
+ assert(player->queued);
+ assert(pc.next_song != NULL);
+
+ player->queued = false;
- if (decoder_has_failed()) {
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
- pc.errored_song = dc.next_song;
+ if (decoder_lock_has_failed(dc)) {
+ player_lock();
+ pc.errored_song = dc->song;
pc.error = PLAYER_ERROR_FILE;
pc.next_song = NULL;
- player->queued = false;
+ player_unlock();
+
return false;
}
+ player->song = pc.next_song;
+ player->elapsed_time = 0.0;
+
+ /* set the "starting" flag, which will be cleared by
+ player_check_decoder_startup() */
+ player->decoder_starting = true;
+
+ player_lock();
+
+ /* update player_control's song information */
pc.total_time = pc.next_song->tag != NULL
? pc.next_song->tag->time : 0;
pc.bit_rate = 0;
audio_format_clear(&pc.audio_format);
- player->song = pc.next_song;
+ /* clear the queued song */
pc.next_song = NULL;
- pc.elapsed_time = 0;
- player->queued = false;
- /* set the "starting" flag, which will be cleared by
- player_check_decoder_startup() */
- player->decoder_starting = true;
+ player_unlock();
/* call syncPlaylistWithQueue() in the main thread */
event_pipe_emit(PIPE_EVENT_PLAYLIST);
@@ -174,51 +231,63 @@ player_wait_for_decoder(struct player *player)
* The decoder has acknowledged the "START" command (see
* player_wait_for_decoder()). This function checks if the decoder
* initialization has completed yet.
+ *
+ * The player lock is not held.
*/
static bool
player_check_decoder_startup(struct player *player)
{
+ struct decoder_control *dc = player->dc;
+
assert(player->decoder_starting);
- if (decoder_has_failed()) {
+ decoder_lock(dc);
+
+ if (decoder_has_failed(dc)) {
/* the decoder failed */
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
+ decoder_unlock(dc);
- pc.errored_song = dc.next_song;
+ player_lock();
+ pc.errored_song = dc->song;
pc.error = PLAYER_ERROR_FILE;
+ player_unlock();
return false;
- } else if (!decoder_is_starting()) {
+ } else if (!decoder_is_starting(dc)) {
/* the decoder is ready and ok */
+ decoder_unlock(dc);
+
if (audio_format_defined(&player->play_audio_format) &&
!audio_output_all_wait(1))
/* the output devices havn't finished playing
all chunks yet - wait for that */
return true;
- pc.total_time = dc.total_time;
- pc.audio_format = dc.in_audio_format;
- player->play_audio_format = dc.out_audio_format;
- player->size_to_time =
- audioFormatSizeToTime(&dc.out_audio_format);
+ player_lock();
+ pc.total_time = dc->total_time;
+ pc.audio_format = dc->in_audio_format;
+ player_unlock();
+
+ player->play_audio_format = dc->out_audio_format;
player->decoder_starting = false;
if (!player->paused &&
- !audio_output_all_open(&dc.out_audio_format,
+ !audio_output_all_open(&dc->out_audio_format,
player_buffer)) {
- char *uri = song_get_uri(dc.next_song);
+ char *uri = song_get_uri(dc->song);
g_warning("problems opening audio device "
"while playing \"%s\"", uri);
g_free(uri);
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
- pc.errored_song = dc.next_song;
+ player_lock();
pc.error = PLAYER_ERROR_AUDIO;
/* pause: the user may resume playback as soon
as an audio output becomes available */
pc.state = PLAYER_STATE_PAUSE;
+ player_unlock();
+
player->paused = true;
return true;
}
@@ -227,7 +296,8 @@ player_check_decoder_startup(struct player *player)
} else {
/* the decoder is not yet ready; wait
some more */
- notify_wait(&pc.notify);
+ player_wait_decoder(dc);
+ decoder_unlock(dc);
return true;
}
@@ -237,6 +307,8 @@ player_check_decoder_startup(struct player *player)
* Sends a chunk of silence to the audio outputs. This is called when
* there is not enough decoded data in the pipe yet, to prevent
* underruns in the hardware buffers.
+ *
+ * The player lock is not held.
*/
static bool
player_send_silence(struct player *player)
@@ -273,15 +345,18 @@ player_send_silence(struct player *player)
/**
* This is the handler for the #PLAYER_COMMAND_SEEK command.
+ *
+ * The player lock is not held.
*/
static bool player_seek_decoder(struct player *player)
{
+ struct decoder_control *dc = player->dc;
double where;
bool ret;
assert(pc.next_song != NULL);
- if (decoder_current_song() != pc.next_song) {
+ if (decoder_current_song(dc) != pc.next_song) {
/* the decoder is already decoding the "next" song -
stop it and start the previous song again */
@@ -290,10 +365,9 @@ static bool player_seek_decoder(struct player *player)
/* clear music chunks which might still reside in the
pipe */
music_pipe_clear(player->pipe, player_buffer);
- dc.pipe = player->pipe;
/* re-start the decoder */
- dc_start_async(pc.next_song);
+ player_dc_start(player, player->pipe);
ret = player_wait_for_decoder(player);
if (!ret) {
/* decoder failure */
@@ -324,14 +398,15 @@ static bool player_seek_decoder(struct player *player)
if (where < 0.0)
where = 0.0;
- ret = dc_seek(&pc.notify, where);
+ ret = dc_seek(dc, where);
if (!ret) {
/* decoder failure */
player_command_finished();
return false;
}
- pc.elapsed_time = where;
+ player->elapsed_time = where;
+
player_command_finished();
player->xfade = XFADE_UNKNOWN;
@@ -344,53 +419,73 @@ static bool player_seek_decoder(struct player *player)
return true;
}
+/**
+ * Player lock must be held before calling.
+ */
static void player_process_command(struct player *player)
{
+ struct decoder_control *dc = player->dc;
+
switch (pc.command) {
case PLAYER_COMMAND_NONE:
- case PLAYER_COMMAND_PLAY:
case PLAYER_COMMAND_STOP:
case PLAYER_COMMAND_EXIT:
case PLAYER_COMMAND_CLOSE_AUDIO:
break;
+ case PLAYER_COMMAND_UPDATE_AUDIO:
+ player_unlock();
+ audio_output_all_enable_disable();
+ player_lock();
+ player_command_finished_locked();
+ break;
+
case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL);
assert(!player->queued);
- assert(dc.pipe == NULL || dc.pipe == player->pipe);
+ assert(dc->pipe == NULL || dc->pipe == player->pipe);
player->queued = true;
- player_command_finished();
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_PAUSE:
+ player_unlock();
+
player->paused = !player->paused;
if (player->paused) {
audio_output_all_pause();
+ player_lock();
+
pc.state = PLAYER_STATE_PAUSE;
} else if (!audio_format_defined(&player->play_audio_format)) {
/* the decoder hasn't provided an audio format
yet - don't open the audio device yet */
+ player_lock();
pc.state = PLAYER_STATE_PLAY;
} else if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
/* unpaused, continue playing */
+ player_lock();
+
pc.state = PLAYER_STATE_PLAY;
} else {
/* the audio device has failed - rollback to
pause mode */
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
- pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_AUDIO;
player->paused = true;
+
+ player_lock();
}
- player_command_finished();
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_SEEK:
+ player_unlock();
player_seek_decoder(player);
+ player_lock();
break;
case PLAYER_COMMAND_CANCEL:
@@ -402,80 +497,91 @@ static void player_process_command(struct player *player)
return;
}
- if (dc.pipe != NULL && dc.pipe != player->pipe)
+ if (decoding_next_song(player)) {
/* the decoder is already decoding the song -
stop it and reset the position */
+ player_unlock();
player_dc_stop(player);
+ player_lock();
+ }
pc.next_song = NULL;
player->queued = false;
- player_command_finished();
+ player_command_finished_locked();
+ break;
+
+ case PLAYER_COMMAND_REFRESH:
+ if (audio_format_defined(&player->play_audio_format) &&
+ !player->paused) {
+ player_unlock();
+ audio_output_all_check();
+ player_lock();
+ }
+
+ pc.elapsed_time = audio_output_all_get_elapsed_time();
+ if (pc.elapsed_time < 0.0)
+ pc.elapsed_time = player->elapsed_time;
+
+ player_command_finished_locked();
break;
}
}
+static void
+update_song_tag(struct song *song, const struct tag *new_tag)
+{
+ struct tag *old_tag;
+
+ if (song_is_file(song))
+ /* don't update tags of local files, only remote
+ streams may change tags dynamically */
+ return;
+
+ old_tag = song->tag;
+ song->tag = tag_dup(new_tag);
+
+ if (old_tag != NULL)
+ tag_free(old_tag);
+
+ /* the main thread will update the playlist version when he
+ receives this event */
+ event_pipe_emit(PIPE_EVENT_TAG);
+
+ /* notify all clients that the tag of the current song has
+ changed */
+ idle_add(IDLE_PLAYER);
+}
+
/**
* Plays a #music_chunk object (after applying software volume). If
* it contains a (stream) tag, copy it to the current song, so MPD's
* playlist reflects the new stream tag.
+ *
+ * Player lock is not held.
*/
static bool
play_chunk(struct song *song, struct music_chunk *chunk,
- const struct audio_format *format, double sizeToTime)
+ const struct audio_format *format)
{
- bool success;
-
assert(music_chunk_check_format(chunk, format));
- if (chunk->tag != NULL) {
- if (!song_is_file(song)) {
- /* always update the tag of remote streams */
- struct tag *old_tag = song->tag;
-
- song->tag = tag_dup(chunk->tag);
-
- if (old_tag != NULL)
- tag_free(old_tag);
-
- /* the main thread will update the playlist
- version when he receives this event */
- event_pipe_emit(PIPE_EVENT_TAG);
-
- /* notify all clients that the tag of the
- current song has changed */
- idle_add(IDLE_PLAYER);
- }
- }
+ if (chunk->tag != NULL)
+ update_song_tag(song, chunk->tag);
if (chunk->length == 0) {
music_buffer_return(player_buffer, chunk);
return true;
}
- pc.elapsed_time = chunk->times;
pc.bit_rate = chunk->bit_rate;
- /* apply software volume */
-
- success = pcm_volume(chunk->data, chunk->length,
- format, pc.software_volume);
- if (!success) {
- g_warning("pcm_volume() failed on %u:%u:%u",
- format->sample_rate, format->bits, format->channels);
- pc.errored_song = dc.current_song;
- pc.error = PLAYER_ERROR_AUDIO;
- return false;
- }
-
/* send the chunk to the audio outputs */
- if (!audio_output_all_play(chunk)) {
- pc.errored_song = dc.current_song;
- pc.error = PLAYER_ERROR_AUDIO;
+ if (!audio_output_all_play(chunk))
return false;
- }
- pc.total_play_time += sizeToTime * chunk->length;
+ pc.total_play_time += (double)chunk->length /
+ audio_format_time_to_size(format);
return true;
}
@@ -488,6 +594,7 @@ play_chunk(struct song *song, struct music_chunk *chunk,
static bool
play_next_chunk(struct player *player)
{
+ struct decoder_control *dc = player->dc;
struct music_chunk *chunk = NULL;
unsigned cross_fade_position;
bool success;
@@ -498,12 +605,12 @@ play_next_chunk(struct player *player)
return true;
if (player->xfade == XFADE_ENABLED &&
- dc.pipe != NULL && dc.pipe != player->pipe &&
+ decoding_next_song(player) &&
(cross_fade_position = music_pipe_size(player->pipe))
<= player->cross_fade_chunks) {
/* perform cross fade */
struct music_chunk *other_chunk =
- music_pipe_shift(dc.pipe);
+ music_pipe_shift(dc->pipe);
if (!player->cross_fading) {
/* beginning of the cross fade - adjust
@@ -519,20 +626,26 @@ play_next_chunk(struct player *player)
assert(chunk != NULL);
cross_fade_apply(chunk, other_chunk,
- &dc.out_audio_format,
+ &dc->out_audio_format,
cross_fade_position,
player->cross_fade_chunks);
music_buffer_return(player_buffer, other_chunk);
} else {
/* there are not enough decoded chunks yet */
- if (decoder_is_idle()) {
+
+ decoder_lock(dc);
+
+ if (decoder_is_idle(dc)) {
/* the decoder isn't running, abort
cross fading */
+ decoder_unlock(dc);
+
player->xfade = XFADE_DISABLED;
} else {
/* wait for the decoder */
- notify_signal(&dc.notify);
- notify_wait(&pc.notify);
+ decoder_signal(dc);
+ player_wait_decoder(dc);
+ decoder_unlock(dc);
return true;
}
@@ -546,27 +659,34 @@ play_next_chunk(struct player *player)
/* play the current chunk */
- success = play_chunk(player->song, chunk, &player->play_audio_format,
- player->size_to_time);
+ success = play_chunk(player->song, chunk, &player->play_audio_format);
if (!success) {
music_buffer_return(player_buffer, chunk);
+ player_lock();
+
+ pc.error = PLAYER_ERROR_AUDIO;
+
/* pause: the user may resume playback as soon as an
audio output becomes available */
pc.state = PLAYER_STATE_PAUSE;
player->paused = true;
+ player_unlock();
+
return false;
}
/* this formula should prevent that the decoder gets woken up
with each chunk; it is more efficient to make it decode a
larger block at a time */
- if (!decoder_is_idle() &&
- music_pipe_size(dc.pipe) <= (pc.buffered_before_play +
+ decoder_lock(dc);
+ if (!decoder_is_idle(dc) &&
+ music_pipe_size(dc->pipe) <= (pc.buffered_before_play +
music_buffer_size(player_buffer) * 3) / 4)
- notify_signal(&dc.notify);
+ decoder_signal(dc);
+ decoder_unlock(dc);
return true;
}
@@ -576,15 +696,23 @@ play_next_chunk(struct player *player)
* has consumed all chunks of the current song, and we should start
* sending chunks from the next one.
*
+ * The player lock is not held.
+ *
* @return true on success, false on error (playback will be stopped)
*/
static bool
player_song_border(struct player *player)
{
+ char *uri;
+
player->xfade = XFADE_UNKNOWN;
+ uri = song_get_uri(player->song);
+ g_message("played \"%s\"", uri);
+ g_free(uri);
+
music_pipe_free(player->pipe);
- player->pipe = dc.pipe;
+ player->pipe = player->dc->pipe;
if (!player_wait_for_decoder(player))
return false;
@@ -597,53 +725,58 @@ player_song_border(struct player *player)
* basically a state machine, which multiplexes data between the
* decoder thread and the output threads.
*/
-static void do_play(void)
+static void do_play(struct decoder_control *dc)
{
struct player player = {
+ .dc = dc,
.buffering = true,
.decoder_starting = false,
.paused = false,
- .queued = false,
+ .queued = true,
.song = NULL,
.xfade = XFADE_UNKNOWN,
.cross_fading = false,
.cross_fade_chunks = 0,
- .size_to_time = 0.0,
+ .elapsed_time = 0.0,
};
+ player_unlock();
+
player.pipe = music_pipe_new();
- dc.buffer = player_buffer;
- dc.pipe = player.pipe;
- dc_start(&pc.notify, pc.next_song);
+ player_dc_start(&player, player.pipe);
if (!player_wait_for_decoder(&player)) {
player_dc_stop(&player);
player_command_finished();
music_pipe_free(player.pipe);
event_pipe_emit(PIPE_EVENT_PLAYLIST);
+ player_lock();
return;
}
- pc.elapsed_time = 0;
+ player_lock();
pc.state = PLAYER_STATE_PLAY;
- player_command_finished();
+ player_command_finished_locked();
while (true) {
player_process_command(&player);
if (pc.command == PLAYER_COMMAND_STOP ||
pc.command == PLAYER_COMMAND_EXIT ||
pc.command == PLAYER_COMMAND_CLOSE_AUDIO) {
+ player_unlock();
audio_output_all_cancel();
break;
}
+ player_unlock();
+
if (player.buffering) {
/* buffering at the start of the song - wait
until the buffer is large enough, to
prevent stuttering on slow machines */
if (music_pipe_size(player.pipe) < pc.buffered_before_play &&
- !decoder_is_idle()) {
+ !decoder_lock_is_idle(dc)) {
/* not enough decoded buffer space yet */
if (!player.paused &&
@@ -652,7 +785,11 @@ static void do_play(void)
!player_send_silence(&player))
break;
- notify_wait(&pc.notify);
+ decoder_lock(dc);
+ /* XXX race condition: check decoder again */
+ player_wait_decoder(dc);
+ decoder_unlock(dc);
+ player_lock();
continue;
} else {
/* buffering is complete */
@@ -667,6 +804,8 @@ static void do_play(void)
success = player_check_decoder_startup(&player);
if (!success)
break;
+
+ player_lock();
continue;
}
@@ -674,30 +813,28 @@ static void do_play(void)
/*
music_pipe_check_format(&play_audio_format,
player.next_song_chunk,
- &dc.out_audio_format);
+ &dc->out_audio_format);
*/
#endif
- if (decoder_is_idle() && player.queued) {
+ if (decoder_lock_is_idle(dc) && player.queued &&
+ dc->pipe == player.pipe) {
/* the decoder has finished the current song;
make it decode the next song */
- assert(pc.next_song != NULL);
- assert(dc.pipe == NULL || dc.pipe == player.pipe);
+ assert(dc->pipe == NULL || dc->pipe == player.pipe);
- player.queued = false;
- dc.pipe = music_pipe_new();
- dc_start_async(pc.next_song);
+ player_dc_start(&player, music_pipe_new());
}
- if (dc.pipe != NULL && dc.pipe != player.pipe &&
+ if (decoding_next_song(&player) &&
player.xfade == XFADE_UNKNOWN &&
- !decoder_is_starting()) {
+ !decoder_lock_is_starting(dc)) {
/* enable cross fading in this song? if yes,
calculate how many chunks will be required
for it */
player.cross_fade_chunks =
- cross_fade_calc(pc.cross_fade_seconds, dc.total_time,
- &dc.out_audio_format,
+ cross_fade_calc(pc.cross_fade_seconds, dc->total_time,
+ &dc->out_audio_format,
&player.play_audio_format,
music_buffer_size(player_buffer) -
pc.buffered_before_play);
@@ -710,9 +847,13 @@ static void do_play(void)
player.xfade = XFADE_DISABLED;
}
- if (player.paused)
- notify_wait(&pc.notify);
- else if (music_pipe_size(player.pipe) > 0) {
+ if (player.paused) {
+ player_lock();
+
+ if (pc.command == PLAYER_COMMAND_NONE)
+ player_wait();
+ continue;
+ } else if (music_pipe_size(player.pipe) > 0) {
/* at least one music chunk is ready - send it
to the audio output */
@@ -724,17 +865,21 @@ static void do_play(void)
/* XXX synchronize in a better way */
g_usleep(10000);
- } else if (dc.pipe != NULL && dc.pipe != player.pipe) {
+ } else if (decoding_next_song(&player)) {
/* at the beginning of a new song */
if (!player_song_border(&player))
break;
- } else if (decoder_is_idle()) {
+ } else if (decoder_lock_is_idle(dc)) {
/* check the size of the pipe again, because
the decoder thread may have added something
since we last checked */
- if (music_pipe_size(player.pipe) == 0)
+ if (music_pipe_size(player.pipe) == 0) {
+ /* wait for the hardware to finish
+ playback */
+ audio_output_all_drain();
break;
+ }
} else {
/* the decoder is too busy and hasn't provided
new PCM data in time: send silence (if the
@@ -742,11 +887,8 @@ static void do_play(void)
if (!player_send_silence(&player))
break;
}
- }
- if (player.queued) {
- assert(pc.next_song != NULL);
- pc.next_song = NULL;
+ player_lock();
}
player_dc_stop(&player);
@@ -754,38 +896,61 @@ static void do_play(void)
music_pipe_clear(player.pipe, player_buffer);
music_pipe_free(player.pipe);
+ player_lock();
+
+ if (player.queued) {
+ assert(pc.next_song != NULL);
+ pc.next_song = NULL;
+ }
+
pc.state = PLAYER_STATE_STOP;
+
+ player_unlock();
+
event_pipe_emit(PIPE_EVENT_PLAYLIST);
+
+ player_lock();
}
static gpointer player_task(G_GNUC_UNUSED gpointer arg)
{
- decoder_thread_start();
+ struct decoder_control dc;
+
+ dc_init(&dc);
+ decoder_thread_start(&dc);
player_buffer = music_buffer_new(pc.buffer_chunks);
+ player_lock();
+
while (1) {
switch (pc.command) {
- case PLAYER_COMMAND_PLAY:
case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL);
- do_play();
+ do_play(&dc);
break;
case PLAYER_COMMAND_STOP:
+ player_unlock();
audio_output_all_cancel();
+ player_lock();
+
/* fall through */
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_PAUSE:
pc.next_song = NULL;
- player_command_finished();
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_CLOSE_AUDIO:
+ player_unlock();
+
audio_output_all_close();
- player_command_finished();
+
+ player_lock();
+ player_command_finished_locked();
#ifndef NDEBUG
/* in the DEBUG build, check for leaked
@@ -797,25 +962,39 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
break;
+ case PLAYER_COMMAND_UPDATE_AUDIO:
+ player_unlock();
+ audio_output_all_enable_disable();
+ player_lock();
+ player_command_finished_locked();
+ break;
+
case PLAYER_COMMAND_EXIT:
- dc_quit();
+ player_unlock();
+
+ dc_quit(&dc);
+ dc_deinit(&dc);
audio_output_all_close();
music_buffer_free(player_buffer);
+
player_command_finished();
- g_thread_exit(NULL);
- break;
+ return NULL;
case PLAYER_COMMAND_CANCEL:
pc.next_song = NULL;
- player_command_finished();
+ player_command_finished_locked();
+ break;
+
+ case PLAYER_COMMAND_REFRESH:
+ /* no-op when not playing */
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_NONE:
- notify_wait(&pc.notify);
+ player_wait();
break;
}
}
- return NULL;
}
void player_create(void)
diff --git a/src/playlist.c b/src/playlist.c
index 35c09329a..6ca9d014b 100644
--- a/src/playlist.c
+++ b/src/playlist.c
@@ -34,7 +34,8 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
-void playlistVersionChange(struct playlist *playlist)
+void
+playlist_increment_version_all(struct playlist *playlist)
{
queue_modify_all(&playlist->queue);
idle_add(IDLE_PLAYLIST);
@@ -61,16 +62,12 @@ playlist_init(struct playlist *playlist)
playlist->queued = -1;
playlist->current = -1;
-
- playlist->prev_elapsed = g_timer_new();
}
void
playlist_finish(struct playlist *playlist)
{
queue_finish(&playlist->queue);
-
- g_timer_destroy(playlist->prev_elapsed);
}
/**
@@ -91,14 +88,15 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order)
g_debug("queue song %i:\"%s\"", playlist->queued, uri);
g_free(uri);
- queueSong(song);
+ pc_enqueue_song(song);
}
/**
* Check if the player thread has already started playing the "queued"
* song.
*/
-static void syncPlaylistWithQueue(struct playlist *playlist)
+static void
+playlist_sync_with_queue(struct playlist *playlist)
{
if (pc.next_song == NULL && playlist->queued != -1) {
/* queued song has started: copy queued to current,
@@ -109,7 +107,7 @@ static void syncPlaylistWithQueue(struct playlist *playlist)
playlist->queued = -1;
if(playlist->queue.consume)
- deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current));
+ playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
idle_add(IDLE_PLAYER);
}
@@ -178,7 +176,7 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
}
void
-playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
+playlist_play_order(struct playlist *playlist, int orderNum)
{
struct song *song;
char *uri;
@@ -192,34 +190,35 @@ playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
g_debug("play %i:\"%s\"", orderNum, uri);
g_free(uri);
- playerPlay(song);
+ pc_play(song);
playlist->current = orderNum;
}
static void
-playPlaylistIfPlayerStopped(struct playlist *playlist);
+playlist_resume_playback(struct playlist *playlist);
/**
* This is the "PLAYLIST" event handler. It is invoked by the player
* thread whenever it requests a new queued song, or when it exits.
*/
-void syncPlayerAndPlaylist(struct playlist *playlist)
+void
+playlist_sync(struct playlist *playlist)
{
if (!playlist->playing)
/* this event has reached us out of sync: we aren't
playing anymore; ignore the event */
return;
- if (getPlayerState() == PLAYER_STATE_STOP)
+ if (pc_get_state() == PLAYER_STATE_STOP)
/* the player thread has stopped: check if playback
should be restarted with the next song. That can
happen if the playlist isn't filling the queue fast
enough */
- playPlaylistIfPlayerStopped(playlist);
+ playlist_resume_playback(playlist);
else {
/* check if the player thread has already started
playing the queued song */
- syncPlaylistWithQueue(playlist);
+ playlist_sync_with_queue(playlist);
/* make sure the queued song is always set (if
possible) */
@@ -233,14 +232,14 @@ void syncPlayerAndPlaylist(struct playlist *playlist)
* decide whether to re-start playback
*/
static void
-playPlaylistIfPlayerStopped(struct playlist *playlist)
+playlist_resume_playback(struct playlist *playlist)
{
enum player_error error;
assert(playlist->playing);
- assert(getPlayerState() == PLAYER_STATE_STOP);
+ assert(pc_get_state() == PLAYER_STATE_STOP);
- error = getPlayerError();
+ error = pc_get_error();
if (error == PLAYER_ERROR_NOERROR)
playlist->error_count = 0;
else
@@ -251,37 +250,38 @@ playPlaylistIfPlayerStopped(struct playlist *playlist)
playlist->error_count >= queue_length(&playlist->queue))
/* too many errors, or critical error: stop
playback */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
else
/* continue playback at the next song */
- nextSongInPlaylist(playlist);
+ playlist_next(playlist);
}
bool
-getPlaylistRepeatStatus(const struct playlist *playlist)
+playlist_get_repeat(const struct playlist *playlist)
{
return playlist->queue.repeat;
}
bool
-getPlaylistRandomStatus(const struct playlist *playlist)
+playlist_get_random(const struct playlist *playlist)
{
return playlist->queue.random;
}
bool
-getPlaylistSingleStatus(const struct playlist *playlist)
+playlist_get_single(const struct playlist *playlist)
{
return playlist->queue.single;
}
bool
-getPlaylistConsumeStatus(const struct playlist *playlist)
+playlist_get_consume(const struct playlist *playlist)
{
return playlist->queue.consume;
}
-void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
+void
+playlist_set_repeat(struct playlist *playlist, bool status)
{
if (status == playlist->queue.repeat)
return;
@@ -296,7 +296,8 @@ void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-static void orderPlaylist(struct playlist *playlist)
+static void
+playlist_order(struct playlist *playlist)
{
if (playlist->current >= 0)
/* update playlist.current, order==position now */
@@ -306,7 +307,8 @@ static void orderPlaylist(struct playlist *playlist)
queue_restore_order(&playlist->queue);
}
-void setPlaylistSingleStatus(struct playlist *playlist, bool status)
+void
+playlist_set_single(struct playlist *playlist, bool status)
{
if (status == playlist->queue.single)
return;
@@ -321,7 +323,8 @@ void setPlaylistSingleStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-void setPlaylistConsumeStatus(struct playlist *playlist, bool status)
+void
+playlist_set_consume(struct playlist *playlist, bool status)
{
if (status == playlist->queue.consume)
return;
@@ -330,7 +333,8 @@ void setPlaylistConsumeStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-void setPlaylistRandomStatus(struct playlist *playlist, bool status)
+void
+playlist_set_random(struct playlist *playlist, bool status)
{
const struct song *queued;
@@ -365,14 +369,15 @@ void setPlaylistRandomStatus(struct playlist *playlist, bool status)
} else
playlist->current = -1;
} else
- orderPlaylist(playlist);
+ playlist_order(playlist);
playlist_update_queued_song(playlist, queued);
idle_add(IDLE_OPTIONS);
}
-int getPlaylistCurrentSong(const struct playlist *playlist)
+int
+playlist_get_current_song(const struct playlist *playlist)
{
if (playlist->current >= 0)
return queue_order_to_position(&playlist->queue,
@@ -381,7 +386,8 @@ int getPlaylistCurrentSong(const struct playlist *playlist)
return -1;
}
-int getPlaylistNextSong(const struct playlist *playlist)
+int
+playlist_get_next_song(const struct playlist *playlist)
{
if (playlist->current >= 0)
{
@@ -404,19 +410,19 @@ int getPlaylistNextSong(const struct playlist *playlist)
}
unsigned long
-getPlaylistVersion(const struct playlist *playlist)
+playlist_get_version(const struct playlist *playlist)
{
return playlist->queue.version;
}
int
-getPlaylistLength(const struct playlist *playlist)
+playlist_get_length(const struct playlist *playlist)
{
return queue_length(&playlist->queue);
}
unsigned
-getPlaylistSongId(const struct playlist *playlist, unsigned song)
+playlist_get_song_id(const struct playlist *playlist, unsigned song)
{
return queue_position_to_id(&playlist->queue, song);
}
diff --git a/src/playlist.h b/src/playlist.h
index 57b2450fa..f8f5bd942 100644
--- a/src/playlist.h
+++ b/src/playlist.h
@@ -23,7 +23,6 @@
#include "queue.h"
#include <stdbool.h>
-#include <stdio.h>
#define PLAYLIST_COMMENT '#'
@@ -82,21 +81,16 @@ struct playlist {
* This variable is only valid if #playing is true.
*/
int queued;
-
- /**
- * This timer tracks the time elapsed since the last "prev"
- * command. If that is less than one second ago, "prev" jumps
- * to the previous song instead of rewinding the current song.
- */
- GTimer *prev_elapsed;
};
/** the global playlist object */
extern struct playlist g_playlist;
-void initPlaylist(void);
+void
+playlist_global_init(void);
-void finishPlaylist(void);
+void
+playlist_global_finish(void);
void
playlist_init(struct playlist *playlist);
@@ -116,11 +110,8 @@ playlist_get_queue(const struct playlist *playlist)
return &playlist->queue;
}
-void readPlaylistState(FILE *);
-
-void savePlaylistState(FILE *);
-
-void clearPlaylist(struct playlist *playlist);
+void
+playlist_clear(struct playlist *playlist);
#ifndef WIN32
/**
@@ -133,90 +124,111 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid,
#endif
enum playlist_result
-addToPlaylist(struct playlist *playlist, const char *file, unsigned *added_id);
+playlist_append_uri(struct playlist *playlist, const char *file,
+ unsigned *added_id);
enum playlist_result
-addSongToPlaylist(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist,
struct song *song, unsigned *added_id);
enum playlist_result
-deleteFromPlaylist(struct playlist *playlist, unsigned song);
+playlist_delete(struct playlist *playlist, unsigned song);
+
+/**
+ * Deletes a range of songs from the playlist.
+ *
+ * @param start the position of the first song to delete
+ * @param end the position after the last song to delete
+ */
+enum playlist_result
+playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end);
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned song);
+playlist_delete_id(struct playlist *playlist, unsigned song);
-void stopPlaylist(struct playlist *playlist);
+void
+playlist_stop(struct playlist *playlist);
enum playlist_result
-playPlaylist(struct playlist *playlist, int song);
+playlist_play(struct playlist *playlist, int song);
enum playlist_result
-playPlaylistById(struct playlist *playlist, int song);
+playlist_play_id(struct playlist *playlist, int song);
-void nextSongInPlaylist(struct playlist *playlist);
+void
+playlist_next(struct playlist *playlist);
-void syncPlayerAndPlaylist(struct playlist *playlist);
+void
+playlist_sync(struct playlist *playlist);
-void previousSongInPlaylist(struct playlist *playlist);
+void
+playlist_previous(struct playlist *playlist);
-void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end);
+void
+playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end);
void
-deleteASongFromPlaylist(struct playlist *playlist, const struct song *song);
+playlist_delete_song(struct playlist *playlist, const struct song *song);
enum playlist_result
-moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to);
+playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to);
enum playlist_result
-moveSongInPlaylistById(struct playlist *playlist, unsigned id, int to);
+playlist_move_id(struct playlist *playlist, unsigned id, int to);
enum playlist_result
-swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2);
+playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2);
enum playlist_result
-swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2);
+playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2);
bool
-getPlaylistRepeatStatus(const struct playlist *playlist);
+playlist_get_repeat(const struct playlist *playlist);
-void setPlaylistRepeatStatus(struct playlist *playlist, bool status);
+void
+playlist_set_repeat(struct playlist *playlist, bool status);
bool
-getPlaylistRandomStatus(const struct playlist *playlist);
+playlist_get_random(const struct playlist *playlist);
-void setPlaylistRandomStatus(struct playlist *playlist, bool status);
+void
+playlist_set_random(struct playlist *playlist, bool status);
bool
-getPlaylistSingleStatus(const struct playlist *playlist);
+playlist_get_single(const struct playlist *playlist);
-void setPlaylistSingleStatus(struct playlist *playlist, bool status);
+void
+playlist_set_single(struct playlist *playlist, bool status);
bool
-getPlaylistConsumeStatus(const struct playlist *playlist);
+playlist_get_consume(const struct playlist *playlist);
-void setPlaylistConsumeStatus(struct playlist *playlist, bool status);
+void
+playlist_set_consume(struct playlist *playlist, bool status);
-int getPlaylistCurrentSong(const struct playlist *playlist);
+int
+playlist_get_current_song(const struct playlist *playlist);
-int getPlaylistNextSong(const struct playlist *playlist);
+int
+playlist_get_next_song(const struct playlist *playlist);
unsigned
-getPlaylistSongId(const struct playlist *playlist, unsigned song);
+playlist_get_song_id(const struct playlist *playlist, unsigned song);
-int getPlaylistLength(const struct playlist *playlist);
+int
+playlist_get_length(const struct playlist *playlist);
unsigned long
-getPlaylistVersion(const struct playlist *playlist);
+playlist_get_version(const struct playlist *playlist);
enum playlist_result
-seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time);
+playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time);
enum playlist_result
-seekSongInPlaylistById(struct playlist *playlist,
+playlist_seek_song_id(struct playlist *playlist,
unsigned id, float seek_time);
-void playlistVersionChange(struct playlist *playlist);
-
-int is_valid_playlist_name(const char *utf8path);
+void
+playlist_increment_version_all(struct playlist *playlist);
#endif
diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c
new file mode 100644
index 000000000..f79e3280d
--- /dev/null
+++ b/src/playlist/asx_playlist_plugin.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2003-2009 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 "playlist/asx_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "asx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct asx_parser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ GSList *songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ENTRY,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ struct song *song;
+};
+
+static const gchar *
+get_attribute(const gchar **attribute_names, const gchar **attribute_values,
+ const gchar *name)
+{
+ for (unsigned i = 0; attribute_names[i] != NULL; ++i)
+ if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
+ return attribute_values[i];
+
+ return NULL;
+}
+
+static void
+asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ parser->state = ENTRY;
+ parser->song = song_remote_new("asx:");
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case ENTRY:
+ if (g_ascii_strcasecmp(element_name, "ref") == 0) {
+ const gchar *href = get_attribute(attribute_names,
+ attribute_values,
+ "href");
+ if (href != NULL) {
+ /* create new song object, and copy
+ the existing tag over; we cannot
+ replace the existing song's URI,
+ because that attribute is
+ immutable */
+ struct song *song = song_remote_new(href);
+
+ if (parser->song != NULL) {
+ song->tag = parser->song->tag;
+ parser->song->tag = NULL;
+ song_free(parser->song);
+ }
+
+ parser->song = song;
+ }
+ } else if (g_ascii_strcasecmp(element_name, "author") == 0)
+ /* is that correct? or should it be COMPOSER
+ or PERFORMER? */
+ parser->tag = TAG_ARTIST;
+ else if (g_ascii_strcasecmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+
+ break;
+ }
+}
+
+static void
+asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ENTRY:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ if (strcmp(parser->song->uri, "asx:") != 0)
+ parser->songs = g_slist_prepend(parser->songs,
+ parser->song);
+ else
+ song_free(parser->song);
+
+ parser->state = ROOT;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void
+asx_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ENTRY:
+ if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = tag_new();
+ tag_add_item_n(parser->song->tag, parser->tag,
+ text, text_len);
+ }
+
+ break;
+ }
+}
+
+static const GMarkupParser asx_parser = {
+ .start_element = asx_start_element,
+ .end_element = asx_end_element,
+ .text = asx_text,
+};
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+asx_parser_destroy(gpointer data)
+{
+ struct asx_parser *parser = data;
+
+ if (parser->state >= ENTRY)
+ song_free(parser->song);
+
+ g_slist_foreach(parser->songs, song_free_callback, NULL);
+ g_slist_free(parser->songs);
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+struct asx_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct playlist_provider *
+asx_open_stream(struct input_stream *is)
+{
+ struct asx_parser parser = {
+ .songs = NULL,
+ .state = ROOT,
+ };
+ struct asx_playlist *playlist;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* parse the ASX XML file */
+
+ context = g_markup_parse_context_new(&asx_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parser, asx_parser_destroy);
+
+ while (true) {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer));
+ if (nbytes == 0)
+ break;
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ /* create a #asx_playlist object from the parsed song list */
+
+ playlist = g_new(struct asx_playlist, 1);
+ playlist_provider_init(&playlist->base, &asx_playlist_plugin);
+ playlist->songs = g_slist_reverse(parser.songs);
+ parser.songs = NULL;
+
+ g_markup_parse_context_free(context);
+
+ return &playlist->base;
+}
+
+static void
+asx_close(struct playlist_provider *_playlist)
+{
+ struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+ g_free(playlist);
+}
+
+static struct song *
+asx_read(struct playlist_provider *_playlist)
+{
+ struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const asx_suffixes[] = {
+ "asx",
+ NULL
+};
+
+static const char *const asx_mime_types[] = {
+ "video/x-ms-asf",
+ NULL
+};
+
+const struct playlist_plugin asx_playlist_plugin = {
+ .name = "asx",
+
+ .open_stream = asx_open_stream,
+ .close = asx_close,
+ .read = asx_read,
+
+ .suffixes = asx_suffixes,
+ .mime_types = asx_mime_types,
+};
diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h
new file mode 100644
index 000000000..021e97f56
--- /dev/null
+++ b/src/playlist/asx_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2009 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_ASX_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin asx_playlist_plugin;
+
+#endif
diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c
new file mode 100644
index 000000000..3f55e5ad4
--- /dev/null
+++ b/src/playlist/extm3u_playlist_plugin.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2003-2009 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 "playlist/extm3u_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "text_input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+struct extm3u_playlist {
+ struct playlist_provider base;
+
+ struct text_input_stream *tis;
+};
+
+static struct playlist_provider *
+extm3u_open_stream(struct input_stream *is)
+{
+ struct extm3u_playlist *playlist;
+ const char *line;
+
+ playlist = g_new(struct extm3u_playlist, 1);
+ playlist->tis = text_input_stream_new(is);
+
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL || strcmp(line, "#EXTM3U") != 0) {
+ /* no EXTM3U header: fall back to the plain m3u
+ plugin */
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+ return NULL;
+ }
+
+ playlist_provider_init(&playlist->base, &extm3u_playlist_plugin);
+ return &playlist->base;
+}
+
+static void
+extm3u_close(struct playlist_provider *_playlist)
+{
+ struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
+
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+}
+
+/**
+ * Parse a EXTINF line.
+ *
+ * @param line the rest of the input line after the colon
+ */
+static struct tag *
+extm3u_parse_tag(const char *line)
+{
+ long duration;
+ char *endptr;
+ const char *name;
+ struct tag *tag;
+
+ duration = strtol(line, &endptr, 10);
+ if (endptr[0] != ',')
+ /* malformed line */
+ return NULL;
+
+ if (duration < 0)
+ /* 0 means unknown duration */
+ duration = 0;
+
+ name = g_strchug(endptr + 1);
+ if (*name == 0 && duration == 0)
+ /* no information available; don't allocate a tag
+ object */
+ return NULL;
+
+ tag = tag_new();
+ tag->time = duration;
+
+ /* unfortunately, there is no real specification for the
+ EXTM3U format, so we must assume that the string after the
+ comma is opaque, and is just the song name*/
+ if (*name != 0)
+ tag_add_item(tag, TAG_NAME, name);
+
+ return tag;
+}
+
+static struct song *
+extm3u_read(struct playlist_provider *_playlist)
+{
+ struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
+ struct tag *tag = NULL;
+ const char *line;
+ struct song *song;
+
+ do {
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL) {
+ if (tag != NULL)
+ tag_free(tag);
+ return NULL;
+ }
+
+ if (g_str_has_prefix(line, "#EXTINF:")) {
+ if (tag != NULL)
+ tag_free(tag);
+ tag = extm3u_parse_tag(line + 8);
+ continue;
+ }
+
+ while (*line != 0 && g_ascii_isspace(*line))
+ ++line;
+ } while (line[0] == '#' || *line == 0);
+
+ song = song_remote_new(line);
+ song->tag = tag;
+ return song;
+}
+
+static const char *const extm3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const extm3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin extm3u_playlist_plugin = {
+ .name = "extm3u",
+
+ .open_stream = extm3u_open_stream,
+ .close = extm3u_close,
+ .read = extm3u_read,
+
+ .suffixes = extm3u_suffixes,
+ .mime_types = extm3u_mime_types,
+};
diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h
new file mode 100644
index 000000000..07f18eafa
--- /dev/null
+++ b/src/playlist/extm3u_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2009 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_EXTM3U_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin extm3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c
new file mode 100644
index 000000000..70e51c2fc
--- /dev/null
+++ b/src/playlist/lastfm_playlist_plugin.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2003-2009 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 "playlist/lastfm_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "playlist_list.h"
+#include "conf.h"
+#include "uri.h"
+#include "song.h"
+#include "input_stream.h"
+#include "glib_compat.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct lastfm_playlist {
+ struct playlist_provider base;
+
+ struct input_stream is;
+
+ struct playlist_provider *xspf;
+};
+
+static struct {
+ char *user;
+ char *md5;
+} lastfm_config;
+
+static bool
+lastfm_init(const struct config_param *param)
+{
+ const char *user = config_get_block_string(param, "user", NULL);
+ const char *passwd = config_get_block_string(param, "password", NULL);
+
+ if (user == NULL || passwd == NULL) {
+ g_debug("disabling the last.fm playlist plugin "
+ "because account is not configured");
+ return false;
+ }
+
+ lastfm_config.user = g_uri_escape_string(user, NULL, false);
+
+#if GLIB_CHECK_VERSION(2,16,0)
+ if (strlen(passwd) != 32)
+ lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
+ passwd, strlen(passwd));
+ else
+#endif
+ lastfm_config.md5 = g_strdup(passwd);
+
+ return true;
+}
+
+static void
+lastfm_finish(void)
+{
+ g_free(lastfm_config.user);
+ g_free(lastfm_config.md5);
+}
+
+/**
+ * Simple data fetcher.
+ * @param url path or url of data to fetch.
+ * @return data fetched, or NULL on error. Must be freed with g_free.
+ */
+static char *
+lastfm_get(const char *url)
+{
+ struct input_stream input_stream;
+ bool success;
+ int ret;
+ char buffer[4096];
+ size_t length = 0, nbytes;
+
+ success = input_stream_open(&input_stream, url);
+ if (!success)
+ return NULL;
+
+ while (!input_stream.ready) {
+ ret = input_stream_buffer(&input_stream);
+ if (ret < 0) {
+ input_stream_close(&input_stream);
+ return NULL;
+ }
+ }
+
+ do {
+ nbytes = input_stream_read(&input_stream, buffer + length,
+ sizeof(buffer) - length);
+ if (nbytes == 0) {
+ if (input_stream_eof(&input_stream))
+ break;
+
+ /* I/O error */
+ input_stream_close(&input_stream);
+ return NULL;
+ }
+
+ length += nbytes;
+ } while (length < sizeof(buffer));
+
+ input_stream_close(&input_stream);
+ return g_strndup(buffer, length);
+}
+
+/**
+ * Ini-style value fetcher.
+ * @param response data through which to search.
+ * @param name name of value to search for.
+ * @return value for param name in param reponse or NULL on error. Free with g_free.
+ */
+static char *
+lastfm_find(const char *response, const char *name)
+{
+ size_t name_length = strlen(name);
+
+ while (true) {
+ const char *eol = strchr(response, '\n');
+ if (eol == NULL)
+ return NULL;
+
+ if (strncmp(response, name, name_length) == 0 &&
+ response[name_length] == '=') {
+ response += name_length + 1;
+ return g_strndup(response, eol - response);
+ }
+
+ response = eol + 1;
+ }
+}
+
+static struct playlist_provider *
+lastfm_open_uri(const char *uri)
+{
+ struct lastfm_playlist *playlist;
+ char *p, *q, *response, *session;
+ bool success;
+
+ /* handshake */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?"
+ "version=1.1.1&platform=linux&"
+ "username=", lastfm_config.user, "&"
+ "passwordmd5=", lastfm_config.md5, "&"
+ "debug=0&partner=", NULL);
+ response = lastfm_get(p);
+ g_free(p);
+ if (response == NULL)
+ return NULL;
+
+ /* extract session id from response */
+
+ session = lastfm_find(response, "session");
+ g_free(response);
+ if (session == NULL) {
+ g_warning("last.fm handshake failed");
+ return NULL;
+ }
+
+ q = g_uri_escape_string(session, NULL, false);
+ g_free(session);
+ session = q;
+
+ g_debug("session='%s'", session);
+
+ /* "adjust" last.fm radio */
+
+ if (strlen(uri) > 9) {
+ char *escaped_uri;
+
+ escaped_uri = g_uri_escape_string(uri, NULL, false);
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?"
+ "session=", session, "&url=", escaped_uri, "&debug=0",
+ NULL);
+ g_free(escaped_uri);
+
+ response = lastfm_get(p);
+ g_free(response);
+ g_free(p);
+
+ if (response == NULL) {
+ g_free(session);
+ return NULL;
+ }
+ }
+
+ /* create the playlist object */
+
+ playlist = g_new(struct lastfm_playlist, 1);
+ playlist_provider_init(&playlist->base, &lastfm_playlist_plugin);
+
+ /* open the last.fm playlist */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?"
+ "sk=", session, "&discovery=0&desktop=1.5.1.31879",
+ NULL);
+ g_free(session);
+
+ success = input_stream_open(&playlist->is, p);
+ g_free(p);
+
+ if (!success) {
+ g_warning("Failed to load XSPF playlist");
+ g_free(playlist);
+ return NULL;
+ }
+
+ while (!playlist->is.ready) {
+ int ret = input_stream_buffer(&playlist->is);
+ if (ret < 0) {
+ input_stream_close(&playlist->is);
+ g_free(playlist);
+ return NULL;
+ }
+
+ if (ret == 0)
+ /* nothing was buffered - wait */
+ g_usleep(10000);
+ }
+
+ /* last.fm does not send a MIME type, we have to fake it here
+ :-( */
+ g_free(playlist->is.mime);
+ playlist->is.mime = g_strdup("application/xspf+xml");
+
+ /* parse the XSPF playlist */
+
+ playlist->xspf = playlist_list_open_stream(&playlist->is, NULL);
+ if (playlist->xspf == NULL) {
+ input_stream_close(&playlist->is);
+ g_free(playlist);
+ g_warning("Failed to parse XSPF playlist");
+ return NULL;
+ }
+
+ return &playlist->base;
+}
+
+static void
+lastfm_close(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ playlist_plugin_close(playlist->xspf);
+ input_stream_close(&playlist->is);
+ g_free(playlist);
+}
+
+static struct song *
+lastfm_read(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ return playlist_plugin_read(playlist->xspf);
+}
+
+static const char *const lastfm_schemes[] = {
+ "lastfm",
+ NULL
+};
+
+const struct playlist_plugin lastfm_playlist_plugin = {
+ .name = "lastfm",
+
+ .init = lastfm_init,
+ .finish = lastfm_finish,
+ .open_uri = lastfm_open_uri,
+ .close = lastfm_close,
+ .read = lastfm_read,
+
+ .schemes = lastfm_schemes,
+};
diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h
new file mode 100644
index 000000000..871a4a043
--- /dev/null
+++ b/src/playlist/lastfm_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2009 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_LASTFM_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin lastfm_playlist_plugin;
+
+#endif
diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c
new file mode 100644
index 000000000..db5db99fc
--- /dev/null
+++ b/src/playlist/m3u_playlist_plugin.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2003-2009 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 "playlist/m3u_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "text_input_stream.h"
+#include "uri.h"
+#include "song.h"
+
+#include <glib.h>
+
+struct m3u_playlist {
+ struct playlist_provider base;
+
+ struct text_input_stream *tis;
+};
+
+static struct playlist_provider *
+m3u_open_stream(struct input_stream *is)
+{
+ struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1);
+
+ playlist_provider_init(&playlist->base, &m3u_playlist_plugin);
+ playlist->tis = text_input_stream_new(is);
+
+ return &playlist->base;
+}
+
+static void
+m3u_close(struct playlist_provider *_playlist)
+{
+ struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
+
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+}
+
+static struct song *
+m3u_read(struct playlist_provider *_playlist)
+{
+ struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
+ const char *line;
+
+ do {
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL)
+ return NULL;
+
+ while (*line != 0 && g_ascii_isspace(*line))
+ ++line;
+ } while (line[0] == '#' || *line == 0);
+
+ return song_remote_new(line);
+}
+
+static const char *const m3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const m3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin m3u_playlist_plugin = {
+ .name = "m3u",
+
+ .open_stream = m3u_open_stream,
+ .close = m3u_close,
+ .read = m3u_read,
+
+ .suffixes = m3u_suffixes,
+ .mime_types = m3u_mime_types,
+};
diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h
new file mode 100644
index 000000000..3cb4b8874
--- /dev/null
+++ b/src/playlist/m3u_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2009 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_M3U_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin m3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c
new file mode 100644
index 000000000..a5d8bbcbe
--- /dev/null
+++ b/src/playlist/pls_playlist_plugin.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2003-2009 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 "playlist/pls_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+#include <glib.h>
+
+struct pls_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist)
+{
+ gchar *key;
+ gchar *value;
+ int length;
+ GError *error = NULL;
+ int num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "NumberOfEntries", &error);
+ if (error) {
+ g_debug("Invalid PLS file: '%s'", error->message);
+ g_error_free(error);
+ error = NULL;
+
+ /* Hack to work around shoutcast failure to comform to spec */
+ num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "numberofentries", &error);
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ }
+ }
+
+ while (num_entries > 0) {
+ struct song *song;
+ key = g_strdup_printf("File%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ if(error) {
+ g_debug("Invalid PLS entry %s: '%s'",key, error->message);
+ g_error_free(error);
+ g_free(key);
+ return;
+ }
+ g_free(key);
+
+ song = song_remote_new(value);
+ g_free(value);
+
+ key = g_strdup_printf("Title%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && value){
+ if (song->tag == NULL)
+ song->tag = tag_new();
+ tag_add_item(song->tag,TAG_TITLE, value);
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+ g_free(value);
+
+ key = g_strdup_printf("Length%i", num_entries);
+ length = g_key_file_get_integer(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && length > 0){
+ if (song->tag == NULL)
+ song->tag = tag_new();
+ song->tag->time = length;
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+
+ playlist->songs = g_slist_prepend(playlist->songs, song);
+ num_entries--;
+ }
+
+}
+
+static struct playlist_provider *
+pls_open_stream(struct input_stream *is)
+{
+ GError *error = NULL;
+ size_t nbytes;
+ char buffer[1024];
+ bool success;
+ GKeyFile *keyfile;
+ struct pls_playlist *playlist;
+ GString *kf_data = g_string_new("");
+
+ do {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer));
+ if(nbytes ==0)
+ break;
+ kf_data = g_string_append_len(kf_data, buffer,nbytes);
+ /* Limit to 64k */
+ } while(kf_data->len < 65536);
+
+ if (kf_data->len == 0) {
+ g_warning("KeyFile parser failed: No Data");
+ g_string_free(kf_data, TRUE);
+ return NULL;
+ }
+
+ keyfile = g_key_file_new();
+ success = g_key_file_load_from_data(keyfile,
+ kf_data->str, kf_data->len,
+ G_KEY_FILE_NONE, &error);
+
+ g_string_free(kf_data, TRUE);
+
+ if (!success) {
+ g_warning("KeyFile parser failed: %s", error->message);
+ g_error_free(error);
+ g_key_file_free(keyfile);
+ return NULL;
+ }
+
+ playlist = g_new(struct pls_playlist, 1);
+ playlist_provider_init(&playlist->base, &pls_playlist_plugin);
+ playlist->songs = NULL;
+
+ pls_parser(keyfile, playlist);
+
+ g_key_file_free(keyfile);
+ return &playlist->base;
+}
+
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+pls_close(struct playlist_provider *_playlist)
+{
+ struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+
+ g_free(playlist);
+
+}
+
+static struct song *
+pls_read(struct playlist_provider *_playlist)
+{
+ struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const pls_suffixes[] = {
+ "pls",
+ NULL
+};
+
+static const char *const pls_mime_types[] = {
+ "audio/x-scpls",
+ NULL
+};
+
+const struct playlist_plugin pls_playlist_plugin = {
+ .name = "pls",
+
+ .open_stream = pls_open_stream,
+ .close = pls_close,
+ .read = pls_read,
+
+ .suffixes = pls_suffixes,
+ .mime_types = pls_mime_types,
+};
diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h
new file mode 100644
index 000000000..7bf1951b8
--- /dev/null
+++ b/src/playlist/pls_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2009 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_PLS_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin pls_playlist_plugin;
+
+#endif
diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c
new file mode 100644
index 000000000..6c81d979d
--- /dev/null
+++ b/src/playlist/xspf_playlist_plugin.c
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2003-2009 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 "playlist/xspf_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "xspf"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct xspf_parser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ GSList *songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, PLAYLIST, TRACKLIST, TRACK,
+ LOCATION,
+ } state;
+
+ /**
+ * The current tag within the "track" element. This is only
+ * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ struct song *song;
+};
+
+static void
+xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ G_GNUC_UNUSED const gchar **attribute_names,
+ G_GNUC_UNUSED const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = PLAYLIST;
+
+ break;
+
+ case PLAYLIST:
+ if (strcmp(element_name, "trackList") == 0)
+ parser->state = TRACKLIST;
+
+ break;
+
+ case TRACKLIST:
+ if (strcmp(element_name, "track") == 0) {
+ parser->state = TRACK;
+ parser->song = NULL;
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case TRACK:
+ if (strcmp(element_name, "location") == 0)
+ parser->state = LOCATION;
+ else if (strcmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+ else if (strcmp(element_name, "creator") == 0)
+ /* TAG_COMPOSER would be more correct
+ according to the XSPF spec */
+ parser->tag = TAG_ARTIST;
+ else if (strcmp(element_name, "annotation") == 0)
+ parser->tag = TAG_COMMENT;
+ else if (strcmp(element_name, "album") == 0)
+ parser->tag = TAG_ALBUM;
+ else if (strcmp(element_name, "trackNum") == 0)
+ parser->tag = TAG_TRACK;
+
+ break;
+
+ case LOCATION:
+ break;
+ }
+}
+
+static void
+xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case PLAYLIST:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = ROOT;
+
+ break;
+
+ case TRACKLIST:
+ if (strcmp(element_name, "tracklist") == 0)
+ parser->state = PLAYLIST;
+
+ break;
+
+ case TRACK:
+ if (strcmp(element_name, "track") == 0) {
+ if (parser->song != NULL)
+ parser->songs = g_slist_prepend(parser->songs,
+ parser->song);
+
+ parser->state = TRACKLIST;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+
+ case LOCATION:
+ parser->state = TRACK;
+ break;
+ }
+}
+
+static void
+xspf_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ case PLAYLIST:
+ case TRACKLIST:
+ break;
+
+ case TRACK:
+ if (parser->song != NULL &&
+ parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = tag_new();
+ tag_add_item_n(parser->song->tag, parser->tag,
+ text, text_len);
+ }
+
+ break;
+
+ case LOCATION:
+ if (parser->song == NULL) {
+ char *uri = g_strndup(text, text_len);
+ parser->song = song_remote_new(uri);
+ g_free(uri);
+ }
+
+ break;
+ }
+}
+
+static const GMarkupParser xspf_parser = {
+ .start_element = xspf_start_element,
+ .end_element = xspf_end_element,
+ .text = xspf_text,
+};
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+xspf_parser_destroy(gpointer data)
+{
+ struct xspf_parser *parser = data;
+
+ if (parser->state >= TRACK && parser->song != NULL)
+ song_free(parser->song);
+
+ g_slist_foreach(parser->songs, song_free_callback, NULL);
+ g_slist_free(parser->songs);
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+struct xspf_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct playlist_provider *
+xspf_open_stream(struct input_stream *is)
+{
+ struct xspf_parser parser = {
+ .songs = NULL,
+ .state = ROOT,
+ };
+ struct xspf_playlist *playlist;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* parse the XSPF XML file */
+
+ context = g_markup_parse_context_new(&xspf_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parser, xspf_parser_destroy);
+
+ while (true) {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer));
+ if (nbytes == 0)
+ break;
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ /* create a #xspf_playlist object from the parsed song list */
+
+ playlist = g_new(struct xspf_playlist, 1);
+ playlist_provider_init(&playlist->base, &xspf_playlist_plugin);
+ playlist->songs = g_slist_reverse(parser.songs);
+ parser.songs = NULL;
+
+ g_markup_parse_context_free(context);
+
+ return &playlist->base;
+}
+
+static void
+xspf_close(struct playlist_provider *_playlist)
+{
+ struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+ g_free(playlist);
+}
+
+static struct song *
+xspf_read(struct playlist_provider *_playlist)
+{
+ struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const xspf_suffixes[] = {
+ "xspf",
+ NULL
+};
+
+static const char *const xspf_mime_types[] = {
+ "application/xspf+xml",
+ NULL
+};
+
+const struct playlist_plugin xspf_playlist_plugin = {
+ .name = "xspf",
+
+ .open_stream = xspf_open_stream,
+ .close = xspf_close,
+ .read = xspf_read,
+
+ .suffixes = xspf_suffixes,
+ .mime_types = xspf_mime_types,
+};
diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h
new file mode 100644
index 000000000..c2c36fbed
--- /dev/null
+++ b/src/playlist/xspf_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2009 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_XSPF_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin xspf_playlist_plugin;
+
+#endif
diff --git a/src/playlist_control.c b/src/playlist_control.c
index 4359611fd..66e7934dc 100644
--- a/src/playlist_control.c
+++ b/src/playlist_control.c
@@ -30,15 +30,7 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
-enum {
- /**
- * When the "prev" command is received, rewind the current
- * track if this number of seconds has already elapsed.
- */
- PLAYLIST_PREV_UNLESS_ELAPSED = 10,
-};
-
-void stopPlaylist(struct playlist *playlist)
+void playlist_stop(struct playlist *playlist)
{
if (!playlist->playing)
return;
@@ -46,7 +38,7 @@ void stopPlaylist(struct playlist *playlist)
assert(playlist->current >= 0);
g_debug("stop");
- playerWait();
+ pc_stop();
playlist->queued = -1;
playlist->playing = false;
@@ -68,11 +60,11 @@ void stopPlaylist(struct playlist *playlist)
}
}
-enum playlist_result playPlaylist(struct playlist *playlist, int song)
+enum playlist_result playlist_play(struct playlist *playlist, int song)
{
unsigned i = song;
- clearPlayerError();
+ pc_clear_error();
if (song == -1) {
/* play any song ("current" song, or the first song */
@@ -83,7 +75,7 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song)
if (playlist->playing) {
/* already playing: unpause playback, just in
case it was paused, and return */
- playerSetPause(0);
+ pc_set_pause(false);
return PLAYLIST_RESULT_SUCCESS;
}
@@ -115,28 +107,28 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song)
playlist->stop_on_error = false;
playlist->error_count = 0;
- playPlaylistOrderNumber(playlist, i);
+ playlist_play_order(playlist, i);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playPlaylistById(struct playlist *playlist, int id)
+playlist_play_id(struct playlist *playlist, int id)
{
int song;
if (id == -1) {
- return playPlaylist(playlist, id);
+ return playlist_play(playlist, id);
}
song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playPlaylist(playlist, song);
+ return playlist_play(playlist, song);
}
void
-nextSongInPlaylist(struct playlist *playlist)
+playlist_next(struct playlist *playlist)
{
int next_order;
int current;
@@ -157,7 +149,7 @@ nextSongInPlaylist(struct playlist *playlist)
/* cancel single */
playlist->queue.single = false;
/* no song after this one: stop playback */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
/* reset "current song" */
playlist->current = -1;
@@ -174,50 +166,42 @@ nextSongInPlaylist(struct playlist *playlist)
queue_shuffle_order(&playlist->queue);
/* note that playlist->current and playlist->queued are
- now invalid, but playPlaylistOrderNumber() will
+ now invalid, but playlist_play_order() will
discard them anyway */
}
- playPlaylistOrderNumber(playlist, next_order);
+ playlist_play_order(playlist, next_order);
}
/* Consume mode removes each played songs. */
if(playlist->queue.consume)
- deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current));
+ playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
}
-void previousSongInPlaylist(struct playlist *playlist)
+void playlist_previous(struct playlist *playlist)
{
if (!playlist->playing)
return;
- if (g_timer_elapsed(playlist->prev_elapsed, NULL) >= 1.0 &&
- getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) {
- /* re-start playing the current song (just like the
- "prev" button on CD players) */
+ assert(queue_length(&playlist->queue) > 0);
- playPlaylistOrderNumber(playlist, playlist->current);
+ if (playlist->current > 0) {
+ /* play the preceding song */
+ playlist_play_order(playlist,
+ playlist->current - 1);
+ } else if (playlist->queue.repeat) {
+ /* play the last song in "repeat" mode */
+ playlist_play_order(playlist,
+ queue_length(&playlist->queue) - 1);
} else {
- if (playlist->current > 0) {
- /* play the preceding song */
- playPlaylistOrderNumber(playlist,
- playlist->current - 1);
- } else if (playlist->queue.repeat) {
- /* play the last song in "repeat" mode */
- playPlaylistOrderNumber(playlist,
- queue_length(&playlist->queue) - 1);
- } else {
- /* re-start playing the current song if it's
- the first one */
- playPlaylistOrderNumber(playlist, playlist->current);
- }
+ /* re-start playing the current song if it's
+ the first one */
+ playlist_play_order(playlist, playlist->current);
}
-
- g_timer_start(playlist->prev_elapsed);
}
enum playlist_result
-seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
+playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time)
{
const struct song *queued;
unsigned i;
@@ -233,7 +217,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
else
i = song;
- clearPlayerError();
+ pc_clear_error();
playlist->stop_on_error = true;
playlist->error_count = 0;
@@ -241,7 +225,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
/* seeking is not within the current song - first
start playing the new song */
- playPlaylistOrderNumber(playlist, i);
+ playlist_play_order(playlist, i);
queued = NULL;
}
@@ -259,11 +243,11 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
}
enum playlist_result
-seekSongInPlaylistById(struct playlist *playlist, unsigned id, float seek_time)
+playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return seekSongInPlaylist(playlist, song, seek_time);
+ return playlist_seek_song(playlist, song, seek_time);
}
diff --git a/src/playlist_edit.c b/src/playlist_edit.c
index b83dc0933..8052f8470 100644
--- a/src/playlist_edit.c
+++ b/src/playlist_edit.c
@@ -35,16 +35,16 @@
#include <unistd.h>
#include <stdlib.h>
-static void incrPlaylistVersion(struct playlist *playlist)
+static void playlist_increment_version(struct playlist *playlist)
{
queue_increment_version(&playlist->queue);
idle_add(IDLE_PLAYLIST);
}
-void clearPlaylist(struct playlist *playlist)
+void playlist_clear(struct playlist *playlist)
{
- stopPlaylist(playlist);
+ playlist_stop(playlist);
/* make sure there are no references to allocated songs
anymore */
@@ -58,7 +58,7 @@ void clearPlaylist(struct playlist *playlist)
playlist->current = -1;
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
}
#ifndef WIN32
@@ -86,41 +86,12 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid,
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return addSongToPlaylist(playlist, song, added_id);
+ return playlist_append_song(playlist, song, added_id);
}
#endif
-static struct song *
-song_by_url(const char *url)
-{
- struct song *song;
-
- song = db_get_song(url);
- if (song != NULL)
- return song;
-
- if (uri_has_scheme(url))
- return song_remote_new(url);
-
- return NULL;
-}
-
-enum playlist_result
-addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id)
-{
- struct song *song;
-
- g_debug("add to playlist: %s", url);
-
- song = song_by_url(url);
- if (song == NULL)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return addSongToPlaylist(playlist, song, added_id);
-}
-
enum playlist_result
-addSongToPlaylist(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist,
struct song *song, unsigned *added_id)
{
const struct song *queued;
@@ -147,7 +118,7 @@ addSongToPlaylist(struct playlist *playlist,
queue_length(&playlist->queue));
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -157,8 +128,38 @@ addSongToPlaylist(struct playlist *playlist,
return PLAYLIST_RESULT_SUCCESS;
}
+static struct song *
+song_by_uri(const char *uri)
+{
+ struct song *song;
+
+ song = db_get_song(uri);
+ if (song != NULL)
+ return song;
+
+ if (uri_has_scheme(uri))
+ return song_remote_new(uri);
+
+ return NULL;
+}
+
+enum playlist_result
+playlist_append_uri(struct playlist *playlist, const char *uri,
+ unsigned *added_id)
+{
+ struct song *song;
+
+ g_debug("add to playlist: %s", uri);
+
+ song = song_by_uri(uri);
+ if (song == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return playlist_append_song(playlist, song, added_id);
+}
+
enum playlist_result
-swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
+playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2)
{
const struct song *queued;
@@ -188,7 +189,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
playlist->current = song1;
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -196,7 +197,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
}
enum playlist_result
-swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
+playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2)
{
int song1 = queue_id_to_position(&playlist->queue, id1);
int song2 = queue_id_to_position(&playlist->queue, id2);
@@ -204,28 +205,25 @@ swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
if (song1 < 0 || song2 < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return swapSongsInPlaylist(playlist, song1, song2);
+ return playlist_swap_songs(playlist, song1, song2);
}
-enum playlist_result
-deleteFromPlaylist(struct playlist *playlist, unsigned song)
+static void
+playlist_delete_internal(struct playlist *playlist, unsigned song,
+ const struct song **queued_p)
{
- const struct song *queued;
unsigned songOrder;
- if (song >= queue_length(&playlist->queue))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- queued = playlist_get_queued_song(playlist);
+ assert(song < queue_length(&playlist->queue));
songOrder = queue_position_to_order(&playlist->queue, song);
if (playlist->playing && playlist->current == (int)songOrder) {
- bool paused = getPlayerState() == PLAYER_STATE_PAUSE;
+ bool paused = pc_get_state() == PLAYER_STATE_PAUSE;
/* the current song is going to be deleted: stop the player */
- playerWait();
+ pc_stop();
playlist->playing = false;
/* see which song is going to be played instead */
@@ -237,13 +235,13 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
if (playlist->current >= 0 && !paused)
/* play the song after the deleted one */
- playPlaylistOrderNumber(playlist, playlist->current);
+ playlist_play_order(playlist, playlist->current);
else
/* no songs left to play, stop playback
completely */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
- queued = NULL;
+ *queued_p = NULL;
} else if (playlist->current == (int)songOrder)
/* there's a "current song" but we're not playing
currently - clear "current" */
@@ -256,41 +254,80 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
queue_delete(&playlist->queue, song);
- incrPlaylistVersion(playlist);
-
/* update the "current" and "queued" variables */
if (playlist->current > (int)songOrder) {
playlist->current--;
}
+}
+
+enum playlist_result
+playlist_delete(struct playlist *playlist, unsigned song)
+{
+ const struct song *queued;
+
+ if (song >= queue_length(&playlist->queue))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ queued = playlist_get_queued_song(playlist);
+
+ playlist_delete_internal(playlist, song, &queued);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned id)
+playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end)
+{
+ const struct song *queued;
+
+ if (start >= queue_length(&playlist->queue))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if (end > queue_length(&playlist->queue))
+ end = queue_length(&playlist->queue);
+
+ if (start >= end)
+ return PLAYLIST_RESULT_SUCCESS;
+
+ queued = playlist_get_queued_song(playlist);
+
+ do {
+ playlist_delete_internal(playlist, --end, &queued);
+ } while (end != start);
+
+ playlist_increment_version(playlist);
+ playlist_update_queued_song(playlist, queued);
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist_delete_id(struct playlist *playlist, unsigned id)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return deleteFromPlaylist(playlist, song);
+ return playlist_delete(playlist, song);
}
void
-deleteASongFromPlaylist(struct playlist *playlist, const struct song *song)
+playlist_delete_song(struct playlist *playlist, const struct song *song)
{
for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i)
if (song == queue_get(&playlist->queue, i))
- deleteFromPlaylist(playlist, i);
+ playlist_delete(playlist, i);
pc_song_deleted(song);
}
enum playlist_result
-moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to)
+playlist_move_range(struct playlist *playlist,
+ unsigned start, unsigned end, int to)
{
const struct song *queued;
int currentSong;
@@ -342,7 +379,7 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end,
}
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -350,16 +387,17 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end,
}
enum playlist_result
-moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to)
+playlist_move_id(struct playlist *playlist, unsigned id1, int to)
{
int song = queue_id_to_position(&playlist->queue, id1);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return moveSongRangeInPlaylist(playlist, song, song+1, to);
+ return playlist_move_range(playlist, song, song+1, to);
}
-void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
+void
+playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end)
{
const struct song *queued;
@@ -399,7 +437,7 @@ void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
queue_shuffle_range(&playlist->queue, start, end);
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
}
diff --git a/src/playlist_global.c b/src/playlist_global.c
index fa810bbc3..dcb972490 100644
--- a/src/playlist_global.c
+++ b/src/playlist_global.c
@@ -37,10 +37,11 @@ playlist_tag_event(void)
static void
playlist_event(void)
{
- syncPlayerAndPlaylist(&g_playlist);
+ playlist_sync(&g_playlist);
}
-void initPlaylist(void)
+void
+playlist_global_init(void)
{
playlist_init(&g_playlist);
@@ -48,17 +49,8 @@ void initPlaylist(void)
event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event);
}
-void finishPlaylist(void)
+void
+playlist_global_finish(void)
{
playlist_finish(&g_playlist);
}
-
-void savePlaylistState(FILE *fp)
-{
- playlist_state_save(fp, &g_playlist);
-}
-
-void readPlaylistState(FILE *fp)
-{
- playlist_state_restore(fp, &g_playlist);
-}
diff --git a/src/playlist_internal.h b/src/playlist_internal.h
index af880691b..8b2f780cc 100644
--- a/src/playlist_internal.h
+++ b/src/playlist_internal.h
@@ -47,6 +47,6 @@ playlist_update_queued_song(struct playlist *playlist,
const struct song *prev);
void
-playPlaylistOrderNumber(struct playlist *playlist, int orderNum);
+playlist_play_order(struct playlist *playlist, int orderNum);
#endif
diff --git a/src/playlist_list.c b/src/playlist_list.c
new file mode 100644
index 000000000..2c6237ed4
--- /dev/null
+++ b/src/playlist_list.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2003-2009 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 "playlist_list.h"
+#include "playlist_plugin.h"
+#include "playlist/extm3u_playlist_plugin.h"
+#include "playlist/m3u_playlist_plugin.h"
+#include "playlist/xspf_playlist_plugin.h"
+#include "playlist/lastfm_playlist_plugin.h"
+#include "playlist/pls_playlist_plugin.h"
+#include "playlist/asx_playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "utils.h"
+#include "conf.h"
+#include "config.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+static const struct playlist_plugin *const playlist_plugins[] = {
+ &extm3u_playlist_plugin,
+ &m3u_playlist_plugin,
+ &xspf_playlist_plugin,
+ &pls_playlist_plugin,
+ &asx_playlist_plugin,
+#ifdef ENABLE_LASTFM
+ &lastfm_playlist_plugin,
+#endif
+ NULL
+};
+
+/** which plugins have been initialized successfully? */
+static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)];
+
+/**
+ * Find the "playlist" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the playlist plugin
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+playlist_plugin_config(const char *plugin_name)
+{
+ const struct config_param *param = NULL;
+
+ assert(plugin_name != NULL);
+
+ while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) {
+ const char *name =
+ config_get_block_string(param, "name", NULL);
+ if (name == NULL)
+ g_error("playlist configuration without 'plugin' name in line %d",
+ param->line);
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+void
+playlist_list_global_init(void)
+{
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+ const struct config_param *param =
+ playlist_plugin_config(plugin->name);
+
+ if (!config_get_block_bool(param, "enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ playlist_plugins_enabled[i] =
+ playlist_plugin_init(playlist_plugins[i], param);
+ }
+}
+
+void
+playlist_list_global_finish(void)
+{
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i)
+ if (playlist_plugins_enabled[i])
+ playlist_plugin_finish(playlist_plugins[i]);
+}
+
+struct playlist_provider *
+playlist_list_open_uri(const char *uri)
+{
+ char *scheme;
+ struct playlist_provider *playlist = NULL;
+
+ assert(uri != NULL);
+
+ scheme = g_uri_parse_scheme(uri);
+ if (scheme == NULL)
+ return NULL;
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && plugin->schemes != NULL &&
+ string_array_contains(plugin->schemes, scheme)) {
+ playlist = playlist_plugin_open_uri(plugin, uri);
+ if (playlist != NULL)
+ break;
+ }
+ }
+
+ g_free(scheme);
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_mime(struct input_stream *is)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(is->mime != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] &&
+ plugin->mime_types != NULL &&
+ string_array_contains(plugin->mime_types, is->mime)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(suffix != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+struct playlist_provider *
+playlist_list_open_stream(struct input_stream *is, const char *uri)
+{
+ const char *suffix;
+ struct playlist_provider *playlist;
+
+ if (is->mime != NULL) {
+ playlist = playlist_list_open_stream_mime(is);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ suffix = uri != NULL ? uri_get_suffix(uri) : NULL;
+ if (suffix != NULL) {
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ return NULL;
+}
+
+static bool
+playlist_suffix_supported(const char *suffix)
+{
+ assert(suffix != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix))
+ return true;
+ }
+
+ return false;
+}
+
+struct playlist_provider *
+playlist_list_open_path(struct input_stream *is, const char *path_fs)
+{
+ const char *suffix;
+ struct playlist_provider *playlist;
+
+ assert(path_fs != NULL);
+
+ suffix = uri_get_suffix(path_fs);
+ if (suffix == NULL || !playlist_suffix_supported(suffix) ||
+ !input_stream_open(is, path_fs))
+ return NULL;
+
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist == NULL)
+ input_stream_close(is);
+
+ return playlist;
+}
diff --git a/src/playlist_list.h b/src/playlist_list.h
new file mode 100644
index 000000000..ebfc5c509
--- /dev/null
+++ b/src/playlist_list.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2003-2009 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_LIST_H
+#define MPD_PLAYLIST_LIST_H
+
+struct playlist_provider;
+struct input_stream;
+
+/**
+ * Initializes all playlist plugins.
+ */
+void
+playlist_list_global_init(void);
+
+/**
+ * Deinitializes all playlist plugins.
+ */
+void
+playlist_list_global_finish(void);
+
+/**
+ * Opens a playlist by its URI.
+ */
+struct playlist_provider *
+playlist_list_open_uri(const char *uri);
+
+/**
+ * Opens a playlist from an input stream.
+ *
+ * @param is an #input_stream object which is open and ready
+ * @param uri optional URI which was used to open the stream; may be
+ * used to select the appropriate playlist plugin
+ */
+struct playlist_provider *
+playlist_list_open_stream(struct input_stream *is, const char *uri);
+
+/**
+ * Opens a playlist from a local file.
+ *
+ * @param is an uninitialized #input_stream object (must be closed
+ * with input_stream_close() if this function succeeds)
+ * @param path_fs the path of the playlist file
+ * @return a playlist, or NULL on error
+ */
+struct playlist_provider *
+playlist_list_open_path(struct input_stream *is, const char *path_fs);
+
+#endif
diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h
new file mode 100644
index 000000000..3515af109
--- /dev/null
+++ b/src/playlist_plugin.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2003-2009 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_PLUGIN_H
+#define MPD_PLAYLIST_PLUGIN_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct config_param;
+struct input_stream;
+struct tag;
+
+/**
+ * An object which provides the contents of a playlist.
+ */
+struct playlist_provider {
+ const struct playlist_plugin *plugin;
+};
+
+static inline void
+playlist_provider_init(struct playlist_provider *playlist,
+ const struct playlist_plugin *plugin)
+{
+ playlist->plugin = plugin;
+}
+
+struct playlist_plugin {
+ const char *name;
+
+ /**
+ * Initialize the plugin. Optional method.
+ *
+ * @param param a configuration block for this plugin, or NULL
+ * if none is configured
+ * @return true if the plugin was initialized successfully,
+ * false if the plugin is not available
+ */
+ bool (*init)(const struct config_param *param);
+
+ /**
+ * Deinitialize a plugin which was initialized successfully.
+ * Optional method.
+ */
+ void (*finish)(void);
+
+ /**
+ * Opens the playlist on the specified URI. This URI has
+ * either matched one of the schemes or one of the suffixes.
+ */
+ struct playlist_provider *(*open_uri)(const char *uri);
+
+ /**
+ * Opens the playlist in the specified input stream. It has
+ * either matched one of the suffixes or one of the MIME
+ * types.
+ */
+ struct playlist_provider *(*open_stream)(struct input_stream *is);
+
+ void (*close)(struct playlist_provider *playlist);
+
+ struct song *(*read)(struct playlist_provider *playlist);
+
+ const char *const*schemes;
+ const char *const*suffixes;
+ const char *const*mime_types;
+};
+
+/**
+ * Initialize a plugin.
+ *
+ * @param param a configuration block for this plugin, or NULL if none
+ * is configured
+ * @return true if the plugin was initialized successfully, false if
+ * the plugin is not available
+ */
+static inline bool
+playlist_plugin_init(const struct playlist_plugin *plugin,
+ const struct config_param *param)
+{
+ return plugin->init != NULL
+ ? plugin->init(param)
+ : true;
+}
+
+/**
+ * Deinitialize a plugin which was initialized successfully.
+ */
+static inline void
+playlist_plugin_finish(const struct playlist_plugin *plugin)
+{
+ if (plugin->finish != NULL)
+ plugin->finish();
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri)
+{
+ return plugin->open_uri(uri);
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_stream(const struct playlist_plugin *plugin,
+ struct input_stream *is)
+{
+ return plugin->open_stream(is);
+}
+
+static inline void
+playlist_plugin_close(struct playlist_provider *playlist)
+{
+ playlist->plugin->close(playlist);
+}
+
+static inline struct song *
+playlist_plugin_read(struct playlist_provider *playlist)
+{
+ return playlist->plugin->read(playlist);
+}
+
+#endif
diff --git a/src/playlist_print.c b/src/playlist_print.c
index fd61ab62c..1ca11e4c1 100644
--- a/src/playlist_print.c
+++ b/src/playlist_print.c
@@ -69,7 +69,7 @@ playlist_print_id(struct client *client, const struct playlist *playlist,
bool
playlist_print_current(struct client *client, const struct playlist *playlist)
{
- int current_position = getPlaylistCurrentSong(playlist);
+ int current_position = playlist_get_current_song(playlist);
if (current_position < 0)
return false;
diff --git a/src/playlist_queue.c b/src/playlist_queue.c
new file mode 100644
index 000000000..55c394bd7
--- /dev/null
+++ b/src/playlist_queue.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2003-2009 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 "playlist_queue.h"
+#include "playlist_list.h"
+#include "playlist_plugin.h"
+#include "stored_playlist.h"
+#include "mapper.h"
+#include "song.h"
+#include "uri.h"
+#include "ls.h"
+#include "input_stream.h"
+
+/**
+ * Determins if it's allowed to add this song to the playlist. For
+ * safety reasons, we disallow local files.
+ */
+static inline bool
+accept_song(const struct song *song)
+{
+ return !song_is_file(song) && uri_has_scheme(song->uri) &&
+ uri_supported_scheme(song->uri);
+}
+
+enum playlist_result
+playlist_load_into_queue(struct playlist_provider *source,
+ struct playlist *dest)
+{
+ enum playlist_result result;
+ struct song *song;
+
+ while ((song = playlist_plugin_read(source)) != NULL) {
+ if (!accept_song(song)) {
+ song_free(song);
+ continue;
+ }
+
+ result = playlist_append_song(dest, song, NULL);
+ if (result != PLAYLIST_RESULT_SUCCESS) {
+ song_free(song);
+ return result;
+ }
+ }
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+static enum playlist_result
+playlist_open_remote_into_queue(const char *uri, struct playlist *dest)
+{
+ struct playlist_provider *playlist;
+ bool stream = false;
+ struct input_stream is;
+ enum playlist_result result;
+
+ assert(uri_has_scheme(uri));
+
+ playlist = playlist_list_open_uri(uri);
+ if (playlist == NULL) {
+ stream = input_stream_open(&is, uri);
+ if (!stream)
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ playlist = playlist_list_open_stream(&is, uri);
+ if (playlist == NULL) {
+ input_stream_close(&is);
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+ }
+ }
+
+ result = playlist_load_into_queue(playlist, dest);
+ playlist_plugin_close(playlist);
+
+ if (stream)
+ input_stream_close(&is);
+
+ return result;
+}
+
+static enum playlist_result
+playlist_open_local_into_queue(const char *uri, struct playlist *dest)
+{
+ struct playlist_provider *playlist;
+ const char *playlist_directory_fs;
+ char *path_fs;
+ struct input_stream is;
+ enum playlist_result result;
+
+ assert(spl_valid_name(uri));
+
+ playlist_directory_fs = map_spl_path();
+ if (playlist_directory_fs == NULL)
+ return PLAYLIST_RESULT_DISABLED;
+
+ path_fs = g_build_filename(playlist_directory_fs, uri, NULL);
+ playlist = playlist_list_open_path(&is, path_fs);
+ g_free(path_fs);
+ if (playlist == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ result = playlist_load_into_queue(playlist, dest);
+ playlist_plugin_close(playlist);
+
+ input_stream_close(&is);
+
+ return result;
+}
+
+enum playlist_result
+playlist_open_into_queue(const char *uri, struct playlist *dest)
+{
+ if (uri_has_scheme(uri))
+ return playlist_open_remote_into_queue(uri, dest);
+ else if (spl_valid_name(uri))
+ return playlist_open_local_into_queue(uri, dest);
+ else
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+}
diff --git a/src/playlist_queue.h b/src/playlist_queue.h
new file mode 100644
index 000000000..b571cd63a
--- /dev/null
+++ b/src/playlist_queue.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2009 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
+ * \brief Glue between playlist plugin and the play queue
+ */
+
+#ifndef MPD_PLAYLIST_QUEUE_H
+#define MPD_PLAYLIST_QUEUE_H
+
+#include "playlist.h"
+
+struct playlist_provider;
+struct playlist;
+
+/**
+ * Loads the contents of a playlist and append it to the specified
+ * play queue.
+ */
+enum playlist_result
+playlist_load_into_queue(struct playlist_provider *source,
+ struct playlist *dest);
+
+/**
+ * Opens a playlist with a playlist plugin and append to the specified
+ * play queue.
+ */
+enum playlist_result
+playlist_open_into_queue(const char *uri, struct playlist *dest);
+
+#endif
+
diff --git a/src/playlist_save.c b/src/playlist_save.c
index 776d3c385..d56c0f324 100644
--- a/src/playlist_save.c
+++ b/src/playlist_save.c
@@ -54,7 +54,7 @@ playlist_print_uri(FILE *file, const char *uri)
char *s;
if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
- uri[0] != '/')
+ !g_path_is_absolute(uri))
s = map_uri_fs(uri);
else
s = utf8_to_fs_charset(uri);
@@ -115,7 +115,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
for (unsigned i = 0; i < list->len; ++i) {
const char *temp = g_ptr_array_index(list, i);
- if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
/* for windows compatibility, convert slashes */
char *temp2 = g_strdup(temp);
char *p = temp2;
@@ -124,7 +124,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
*p = '/';
p++;
}
- if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
g_warning("can't add file \"%s\"", temp2);
}
g_free(temp2);
diff --git a/src/playlist_state.c b/src/playlist_state.c
index af0f7982b..b0cf961f7 100644
--- a/src/playlist_state.c
+++ b/src/playlist_state.c
@@ -51,10 +51,14 @@
void
playlist_state_save(FILE *fp, const struct playlist *playlist)
{
+ struct player_status player_status;
+
+ pc_get_status(&player_status);
+
fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE);
if (playlist->playing) {
- switch (getPlayerState()) {
+ switch (player_status.state) {
case PLAYER_STATE_PAUSE:
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE);
break;
@@ -65,10 +69,16 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
queue_order_to_position(&playlist->queue,
playlist->current));
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
- getPlayerElapsedTime());
- } else
+ (int)player_status.elapsed_time);
+ } else {
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP);
+ if (playlist->current >= 0)
+ fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT,
+ queue_order_to_position(&playlist->queue,
+ playlist->current));
+ }
+
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM,
playlist->queue.random);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT,
@@ -78,7 +88,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CONSUME,
playlist->queue.consume);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE,
- (int)(getPlayerCrossFade()));
+ (int)(pc_get_cross_fade()));
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN);
queue_save(fp, &playlist->queue);
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END);
@@ -109,8 +119,8 @@ playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer)
queue_increment_version(&playlist->queue);
}
-void
-playlist_state_restore(FILE *fp, struct playlist *playlist)
+bool
+playlist_state_restore(const char *line, FILE *fp, struct playlist *playlist)
{
int current = -1;
int seek_time = 0;
@@ -118,50 +128,45 @@ playlist_state_restore(FILE *fp, struct playlist *playlist)
char buffer[PLAYLIST_BUFFER_SIZE];
bool random_mode = false;
+ if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE))
+ return false;
+
+ line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
+
+ if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
+ state = PLAYER_STATE_PLAY;
+ else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
+ state = PLAYER_STATE_PAUSE;
+
while (fgets(buffer, sizeof(buffer), fp)) {
g_strchomp(buffer);
- if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_STATE)) {
- if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
- PLAYLIST_STATE_FILE_STATE_PLAY) == 0) {
- state = PLAYER_STATE_PLAY;
- } else
- if (strcmp
- (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
- PLAYLIST_STATE_FILE_STATE_PAUSE)
- == 0) {
- state = PLAYER_STATE_PAUSE;
- }
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) {
+ if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) {
seek_time =
atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)]));
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
"1") == 0) {
- setPlaylistRepeatStatus(playlist, true);
+ playlist_set_repeat(playlist, true);
} else
- setPlaylistRepeatStatus(playlist, false);
+ playlist_set_repeat(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_SINGLE)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
"1") == 0) {
- setPlaylistSingleStatus(playlist, true);
+ playlist_set_single(playlist, true);
} else
- setPlaylistSingleStatus(playlist, false);
+ playlist_set_single(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CONSUME)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
"1") == 0) {
- setPlaylistConsumeStatus(playlist, true);
+ playlist_set_consume(playlist, true);
} else
- setPlaylistConsumeStatus(playlist, false);
+ playlist_set_consume(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) {
- setPlayerCrossFade(atoi
- (&
- (buffer
- [strlen
- (PLAYLIST_STATE_FILE_CROSSFADE)])));
+ pc_set_cross_fade(atoi(buffer + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) {
random_mode =
strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM),
@@ -172,24 +177,56 @@ playlist_state_restore(FILE *fp, struct playlist *playlist)
(PLAYLIST_STATE_FILE_CURRENT)]));
} else if (g_str_has_prefix(buffer,
PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
- if (state == PLAYER_STATE_STOP)
- current = -1;
playlist_state_load(fp, playlist, buffer);
}
}
- setPlaylistRandomStatus(playlist, random_mode);
+ playlist_set_random(playlist, random_mode);
- if (state != PLAYER_STATE_STOP && !queue_is_empty(&playlist->queue)) {
+ if (!queue_is_empty(&playlist->queue)) {
if (!queue_valid_position(&playlist->queue, current))
current = 0;
- if (seek_time == 0)
- playPlaylist(playlist, current);
+ /* enable all devices for the first time; this must be
+ called here, after the audio output states were
+ restored, before playback begins */
+ if (state != PLAYER_STATE_STOP)
+ pc_update_audio();
+
+ if (state == PLAYER_STATE_STOP /* && config_option */)
+ playlist->current = current;
+ else if (seek_time == 0)
+ playlist_play(playlist, current);
else
- seekSongInPlaylist(playlist, current, seek_time);
+ playlist_seek_song(playlist, current, seek_time);
if (state == PLAYER_STATE_PAUSE)
- playerPause();
+ pc_pause();
}
+
+ return true;
+}
+
+unsigned
+playlist_state_get_hash(const struct playlist *playlist)
+{
+ struct player_status player_status;
+
+ pc_get_status(&player_status);
+
+ return playlist->queue.version ^
+ (player_status.state != PLAYER_STATE_STOP
+ ? ((int)player_status.elapsed_time << 8)
+ : 0) ^
+ (playlist->current >= 0
+ ? (queue_order_to_position(&playlist->queue,
+ playlist->current) << 16)
+ : 0) ^
+ ((int)pc_get_cross_fade() << 20) ^
+ (player_status.state << 24) ^
+ (playlist->queue.random << 27) ^
+ (playlist->queue.repeat << 28) ^
+ (playlist->queue.single << 29) ^
+ (playlist->queue.consume << 30) ^
+ (playlist->queue.random << 31);
}
diff --git a/src/playlist_state.h b/src/playlist_state.h
index 989430264..d116aaeb1 100644
--- a/src/playlist_state.h
+++ b/src/playlist_state.h
@@ -25,6 +25,7 @@
#ifndef PLAYLIST_STATE_H
#define PLAYLIST_STATE_H
+#include <stdbool.h>
#include <stdio.h>
struct playlist;
@@ -32,7 +33,16 @@ struct playlist;
void
playlist_state_save(FILE *fp, const struct playlist *playlist);
-void
-playlist_state_restore(FILE *fp, struct playlist *playlist);
+bool
+playlist_state_restore(const char *line, FILE *fp, struct playlist *playlist);
+
+/**
+ * Generates a hash number for the current state of the playlist and
+ * the playback options. This is used by timer_save_state_file() to
+ * determine whether the state has changed and the state file should
+ * be saved.
+ */
+unsigned
+playlist_state_get_hash(const struct playlist *playlist);
#endif
diff --git a/src/queue.c b/src/queue.c
index 141222a80..c43d1c137 100644
--- a/src/queue.c
+++ b/src/queue.c
@@ -33,7 +33,7 @@ queue_generate_id(const struct queue *queue)
if (cur >= queue->max_length * QUEUE_HASH_MULT)
cur = 0;
- } while (queue->idToPosition[cur] != -1);
+ } while (queue->id_to_position[cur] != -1);
return cur;
}
@@ -111,7 +111,7 @@ queue_append(struct queue *queue, struct song *song)
};
queue->order[queue->length] = queue->length;
- queue->idToPosition[id] = queue->length;
+ queue->id_to_position[id] = queue->length;
++queue->length;
@@ -132,8 +132,8 @@ queue_swap(struct queue *queue, unsigned position1, unsigned position2)
queue->items[position1].version = queue->version;
queue->items[position2].version = queue->version;
- queue->idToPosition[id1] = position2;
- queue->idToPosition[id2] = position1;
+ queue->id_to_position[id1] = position2;
+ queue->id_to_position[id2] = position1;
}
static void
@@ -143,7 +143,7 @@ queue_move_song_to(struct queue *queue, unsigned from, unsigned to)
queue->items[to] = queue->items[from];
queue->items[to].version = queue->version;
- queue->idToPosition[from_id] = to;
+ queue->id_to_position[from_id] = to;
}
void
@@ -163,7 +163,7 @@ queue_move(struct queue *queue, unsigned from, unsigned to)
/* put song at _to_ */
- queue->idToPosition[item.id] = to;
+ queue->id_to_position[item.id] = to;
queue->items[to] = item;
queue->items[to].version = queue->version;
@@ -203,7 +203,7 @@ queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to)
// Copy the original block back in, starting at to.
for (unsigned i = start; i< end; i++)
{
- queue->idToPosition[items[i-start].id] = to + i - start;
+ queue->id_to_position[items[i-start].id] = to + i - start;
queue->items[to + i - start] = items[i-start];
queue->items[to + i - start].version = queue->version;
}
@@ -243,7 +243,7 @@ queue_delete(struct queue *queue, unsigned position)
/* release the song id */
- queue->idToPosition[id] = -1;
+ queue->id_to_position[id] = -1;
/* delete song from songs array */
@@ -271,7 +271,7 @@ queue_clear(struct queue *queue)
if (!song_in_database(item->song))
song_free(item->song);
- queue->idToPosition[item->id] = -1;
+ queue->id_to_position[item->id] = -1;
}
queue->length = 0;
@@ -291,11 +291,11 @@ queue_init(struct queue *queue, unsigned max_length)
queue->items = g_new(struct queue_item, max_length);
queue->order = g_malloc(sizeof(queue->order[0]) *
max_length);
- queue->idToPosition = g_malloc(sizeof(queue->idToPosition[0]) *
+ queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) *
max_length * QUEUE_HASH_MULT);
for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i)
- queue->idToPosition[i] = -1;
+ queue->id_to_position[i] = -1;
queue->rand = g_rand_new();
}
@@ -307,7 +307,7 @@ queue_finish(struct queue *queue)
g_free(queue->items);
g_free(queue->order);
- g_free(queue->idToPosition);
+ g_free(queue->id_to_position);
g_rand_free(queue->rand);
}
diff --git a/src/queue.h b/src/queue.h
index 9c7228fd8..032a812c2 100644
--- a/src/queue.h
+++ b/src/queue.h
@@ -74,8 +74,8 @@ struct queue {
/** map order numbers to positions */
unsigned *order;
- /** map song ids to posiitons */
- int *idToPosition;
+ /** map song ids to positions */
+ int *id_to_position;
/** repeat playback when the end of the queue has been
reached? */
@@ -146,10 +146,10 @@ queue_id_to_position(const struct queue *queue, unsigned id)
if (id >= queue->max_length * QUEUE_HASH_MULT)
return -1;
- assert(queue->idToPosition[id] >= -1);
- assert(queue->idToPosition[id] < (int)queue->length);
+ assert(queue->id_to_position[id] >= -1);
+ assert(queue->id_to_position[id] < (int)queue->length);
- return queue->idToPosition[id];
+ return queue->id_to_position[id];
}
static inline int
diff --git a/src/replay_gain.c b/src/replay_gain.c
index bcb501e54..020671c8a 100644
--- a/src/replay_gain.c
+++ b/src/replay_gain.c
@@ -24,8 +24,11 @@
#include "conf.h"
#include "audio_format.h"
#include "pcm_volume.h"
+#include "idle.h"
#include <glib.h>
+
+#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
@@ -38,19 +41,51 @@ static const char *const replay_gain_mode_names[] = {
enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF;
static float replay_gain_preamp = 1.0;
+static float replay_gain_missing_preamp = 1.0;
-void replay_gain_global_init(void)
+const char *
+replay_gain_get_mode_string(void)
{
- const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
+ switch (replay_gain_mode) {
+ case REPLAY_GAIN_OFF:
+ return "off";
- if (!param)
- return;
+ case REPLAY_GAIN_TRACK:
+ return "track";
+
+ case REPLAY_GAIN_ALBUM:
+ return "album";
+ }
+
+ /* unreachable */
+ assert(false);
+ return "off";
+}
+
+bool
+replay_gain_set_mode_string(const char *p)
+{
+ assert(p != NULL);
- if (strcmp(param->value, "track") == 0) {
+ if (strcmp(p, "off") == 0)
+ replay_gain_mode = REPLAY_GAIN_OFF;
+ else if (strcmp(p, "track") == 0)
replay_gain_mode = REPLAY_GAIN_TRACK;
- } else if (strcmp(param->value, "album") == 0) {
+ else if (strcmp(p, "album") == 0)
replay_gain_mode = REPLAY_GAIN_ALBUM;
- } else {
+ else
+ return false;
+
+ idle_add(IDLE_OPTIONS);
+
+ return true;
+}
+
+void replay_gain_global_init(void)
+{
+ const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
+
+ if (param != NULL && !replay_gain_set_mode_string(param->value)) {
g_error("replaygain value \"%s\" at line %i is invalid\n",
param->value, param->line);
}
@@ -73,6 +108,25 @@ void replay_gain_global_init(void)
replay_gain_preamp = pow(10, f / 20.0);
}
+
+ param = config_get_param(CONF_REPLAYGAIN_MISSING_PREAMP);
+
+ if (param) {
+ char *test;
+ float f = strtod(param->value, &test);
+
+ if (*test != '\0') {
+ g_error("Replaygain missing preamp \"%s\" is not a number at "
+ "line %i\n", param->value, param->line);
+ }
+
+ if (f < -15 || f > 15) {
+ g_error("Replaygain missing preamp \"%s\" is not between -15 and"
+ "15 at line %i\n", param->value, param->line);
+ }
+
+ replay_gain_missing_preamp = pow(10, f / 20.0);
+ }
}
static float calc_replay_gain_scale(float gain, float peak)
@@ -116,19 +170,28 @@ void
replay_gain_apply(struct replay_gain_info *info, char *buffer, int size,
const struct audio_format *format)
{
- if (replay_gain_mode == REPLAY_GAIN_OFF || !info)
+ float scale;
+
+ if (replay_gain_mode == REPLAY_GAIN_OFF)
return;
- if (info->scale < 0) {
- const struct replay_gain_tuple *tuple =
- &info->tuples[replay_gain_mode];
+ if (info) {
+ if (info->scale < 0) {
+ const struct replay_gain_tuple *tuple =
+ &info->tuples[replay_gain_mode];
- g_debug("computing ReplayGain %s scale with gain %f, peak %f\n",
- replay_gain_mode_names[replay_gain_mode],
- tuple->gain, tuple->peak);
+ g_debug("computing ReplayGain %s scale with gain %f, peak %f\n",
+ replay_gain_mode_names[replay_gain_mode],
+ tuple->gain, tuple->peak);
- info->scale = calc_replay_gain_scale(tuple->gain, tuple->peak);
+ info->scale = calc_replay_gain_scale(tuple->gain, tuple->peak);
+ }
+ scale = info->scale;
+ }
+ else {
+ scale = replay_gain_missing_preamp;
+ g_debug("ReplayGain is missing, computing scale %f\n", scale);
}
- pcm_volume(buffer, size, format, pcm_float_to_volume(info->scale));
+ pcm_volume(buffer, size, format, pcm_float_to_volume(scale));
}
diff --git a/src/replay_gain.h b/src/replay_gain.h
index aa48f3f14..5d0492ad8 100644
--- a/src/replay_gain.h
+++ b/src/replay_gain.h
@@ -23,6 +23,8 @@
#ifndef MPD_REPLAY_GAIN_H
#define MPD_REPLAY_GAIN_H
+#include <stdbool.h>
+
enum replay_gain_mode {
REPLAY_GAIN_OFF = -1,
REPLAY_GAIN_ALBUM,
@@ -52,6 +54,20 @@ void replay_gain_info_free(struct replay_gain_info *info);
void replay_gain_global_init(void);
+/**
+ * Returns the current replay gain mode as a machine-readable string.
+ */
+const char *
+replay_gain_get_mode_string(void);
+
+/**
+ * Sets the replay gain mode, parsed from a string.
+ *
+ * @return true on success, false if the string could not be parsed
+ */
+bool
+replay_gain_set_mode_string(const char *p);
+
void
replay_gain_apply(struct replay_gain_info *info, char *buffer, int bufferSize,
const struct audio_format *format);
diff --git a/src/socket_util.c b/src/socket_util.c
index da4e414b6..e737fdf06 100644
--- a/src/socket_util.c
+++ b/src/socket_util.c
@@ -18,6 +18,7 @@
*/
#include "socket_util.h"
+#include "fd_util.h"
#include "config.h"
#include <errno.h>
@@ -102,7 +103,7 @@ socket_bind_listen(int domain, int type, int protocol,
int passcred = 1;
#endif
- fd = socket(domain, type, protocol);
+ fd = socket_cloexec_nonblock(domain, type, protocol);
if (fd < 0) {
g_set_error(error, listen_quark(), errno,
"Failed to create socket: %s", g_strerror(errno));
diff --git a/src/song.c b/src/song.c
index 76c25f44f..942beb5d7 100644
--- a/src/song.c
+++ b/src/song.c
@@ -20,34 +20,25 @@
#include "song.h"
#include "uri.h"
#include "directory.h"
-#include "mapper.h"
-#include "decoder_list.h"
-#include "decoder_plugin.h"
-#include "tag_ape.h"
-#include "tag_id3.h"
#include "tag.h"
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <string.h>
static struct song *
-song_alloc(const char *url, struct directory *parent)
+song_alloc(const char *uri, struct directory *parent)
{
- size_t urllen;
+ size_t uri_length;
struct song *song;
- assert(url);
- urllen = strlen(url);
- assert(urllen);
- song = g_malloc(sizeof(*song) - sizeof(song->url) + urllen + 1);
+ assert(uri);
+ uri_length = strlen(uri);
+ assert(uri_length);
+ song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1);
song->tag = NULL;
- memcpy(song->url, url, urllen + 1);
+ memcpy(song->uri, uri, uri_length + 1);
song->parent = parent;
song->mtime = 0;
@@ -55,9 +46,9 @@ song_alloc(const char *url, struct directory *parent)
}
struct song *
-song_remote_new(const char *url)
+song_remote_new(const char *uri)
{
- return song_alloc(url, NULL);
+ return song_alloc(uri, NULL);
}
struct song *
@@ -68,32 +59,6 @@ song_file_new(const char *path, struct directory *parent)
return song_alloc(path, parent);
}
-struct song *
-song_file_load(const char *path, struct directory *parent)
-{
- struct song *song;
- bool ret;
-
- assert((parent == NULL) == (*path == '/'));
- assert(!uri_has_scheme(path));
- assert(strchr(path, '\n') == NULL);
-
- song = song_file_new(path, parent);
-
- //in archive ?
- if (parent != NULL && parent->device == DEVICE_INARCHIVE) {
- ret = song_file_update_inarchive(song);
- } else {
- ret = song_file_update(song);
- }
- if (!ret) {
- song_free(song);
- return NULL;
- }
-
- return song;
-}
-
void
song_free(struct song *song)
{
@@ -102,127 +67,15 @@ song_free(struct song *song)
g_free(song);
}
-/**
- * Attempts to load APE or ID3 tags from the specified file.
- */
-static struct tag *
-tag_load_fallback(const char *path)
-{
- struct tag *tag = tag_ape_load(path);
- if (tag == NULL)
- tag = tag_id3_load(path);
- return tag;
-}
-
-/**
- * The decoder plugin failed to load any tags: fall back to the APE or
- * ID3 tag loader.
- */
-static struct tag *
-tag_fallback(const char *path, struct tag *tag)
-{
- struct tag *fallback = tag_load_fallback(path);
-
- if (fallback != NULL) {
- /* tag was successfully loaded: copy the song
- duration, and destroy the old (empty) tag */
- fallback->time = tag->time;
- tag_free(tag);
- return fallback;
- } else
- /* no APE/ID3 tag found: return the empty tag */
- return tag;
-}
-
-bool
-song_file_update(struct song *song)
-{
- const char *suffix;
- char *path_fs;
- const struct decoder_plugin *plugin;
- struct stat st;
-
- assert(song_is_file(song));
-
- /* check if there's a suffix and a plugin */
-
- suffix = uri_get_suffix(song->url);
- if (suffix == NULL)
- return false;
-
- plugin = decoder_plugin_from_suffix(suffix, false);
- if (plugin == NULL)
- return false;
-
- path_fs = map_song_fs(song);
- if (path_fs == NULL)
- return false;
-
- if (song->tag != NULL) {
- tag_free(song->tag);
- song->tag = NULL;
- }
-
- if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) {
- g_free(path_fs);
- return false;
- }
-
- song->mtime = st.st_mtime;
-
- do {
- song->tag = plugin->tag_dup(path_fs);
- if (song->tag != NULL)
- break;
-
- plugin = decoder_plugin_from_suffix(suffix, true);
- } while (plugin != NULL);
-
- if (song->tag != NULL && tag_is_empty(song->tag))
- song->tag = tag_fallback(path_fs, song->tag);
-
- g_free(path_fs);
- return song->tag != NULL;
-}
-
-bool
-song_file_update_inarchive(struct song *song)
-{
- const char *suffix;
- const struct decoder_plugin *plugin;
-
- assert(song_is_file(song));
-
- /* check if there's a suffix and a plugin */
-
- suffix = uri_get_suffix(song->url);
- if (suffix == NULL)
- return false;
-
- plugin = decoder_plugin_from_suffix(suffix, false);
- if (plugin == NULL)
- return false;
-
- if (song->tag != NULL)
- tag_free(song->tag);
-
- //accept every file that has music suffix
- //because we dont support tag reading throught
- //input streams
- song->tag = tag_new();
-
- return true;
-}
-
char *
song_get_uri(const struct song *song)
{
assert(song != NULL);
- assert(*song->url);
+ assert(*song->uri);
if (!song_in_database(song) || directory_is_root(song->parent))
- return g_strdup(song->url);
+ return g_strdup(song->uri);
else
return g_strconcat(directory_get_path(song->parent),
- "/", song->url, NULL);
+ "/", song->uri, NULL);
}
diff --git a/src/song.h b/src/song.h
index 3044e910f..c8e80b0d1 100644
--- a/src/song.h
+++ b/src/song.h
@@ -24,9 +24,6 @@
#include <stdbool.h>
#include <sys/time.h>
-#define SONG_BEGIN "songList begin"
-#define SONG_END "songList end"
-
#define SONG_FILE "file: "
#define SONG_TIME "Time: "
@@ -34,12 +31,12 @@ struct song {
struct tag *tag;
struct directory *parent;
time_t mtime;
- char url[sizeof(int)];
+ char uri[sizeof(int)];
};
/** allocate a new song with a remote URL */
struct song *
-song_remote_new(const char *url);
+song_remote_new(const char *uri);
/** allocate a new song with a local file name */
struct song *
@@ -81,7 +78,7 @@ song_in_database(const struct song *song)
static inline bool
song_is_file(const struct song *song)
{
- return song_in_database(song) || song->url[0] == '/';
+ return song_in_database(song) || song->uri[0] == '/';
}
#endif
diff --git a/src/song_print.c b/src/song_print.c
index 64ab9f6b1..3420b0d34 100644
--- a/src/song_print.c
+++ b/src/song_print.c
@@ -26,18 +26,18 @@
#include "uri.h"
void
-song_print_url(struct client *client, struct song *song)
+song_print_uri(struct client *client, struct song *song)
{
if (song_in_database(song) && !directory_is_root(song->parent)) {
client_printf(client, "%s%s/%s\n", SONG_FILE,
- directory_get_path(song->parent), song->url);
+ directory_get_path(song->parent), song->uri);
} else {
char *allocated;
const char *uri;
- uri = allocated = uri_remove_auth(song->url);
+ uri = allocated = uri_remove_auth(song->uri);
if (uri == NULL)
- uri = song->url;
+ uri = song->uri;
client_printf(client, "%s%s\n", SONG_FILE, uri);
@@ -48,7 +48,28 @@ song_print_url(struct client *client, struct song *song)
int
song_print_info(struct client *client, struct song *song)
{
- song_print_url(client, song);
+ song_print_uri(client, song);
+
+ 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), "%FT%TZ", tm2);
+ client_printf(client, "Last-Modified: %s\n",
+ timestamp);
+ }
+ }
if (song->tag)
tag_print(client, song->tag);
diff --git a/src/song_print.h b/src/song_print.h
index 291fd81c8..c16bc7387 100644
--- a/src/song_print.h
+++ b/src/song_print.h
@@ -30,6 +30,6 @@ song_print_info(struct client *client, struct song *song);
int songvec_print(struct client *client, const struct songvec *sv);
void
-song_print_url(struct client *client, struct song *song);
+song_print_uri(struct client *client, struct song *song);
#endif
diff --git a/src/song_save.c b/src/song_save.c
index 2d6297f3e..075fb6e7a 100644
--- a/src/song_save.c
+++ b/src/song_save.c
@@ -22,6 +22,7 @@
#include "tag_save.h"
#include "directory.h"
#include "tag.h"
+#include "text_file.h"
#include <glib.h>
@@ -30,18 +31,13 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "song"
-#define SONG_KEY "key: "
-#define SONG_MTIME "mtime: "
+#define SONG_MTIME "mtime"
+#define SONG_END "song_end"
-static void
-song_save_url(FILE *fp, struct song *song)
+static GQuark
+song_save_quark(void)
{
- if (song->parent != NULL && song->parent->path != NULL)
- fprintf(fp, SONG_FILE "%s/%s\n",
- directory_get_path(song->parent), song->url);
- else
- fprintf(fp, SONG_FILE "%s\n",
- song->url);
+ return g_quark_from_static_string("song_save");
}
static int
@@ -49,117 +45,76 @@ song_save(struct song *song, void *data)
{
FILE *fp = data;
- fprintf(fp, SONG_KEY "%s\n", song->url);
-
- song_save_url(fp, song);
+ fprintf(fp, SONG_BEGIN "%s\n", song->uri);
if (song->tag != NULL)
tag_save(fp, song->tag);
- fprintf(fp, SONG_MTIME "%li\n", (long)song->mtime);
+ fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime);
+ fprintf(fp, SONG_END "\n");
return 0;
}
void songvec_save(FILE *fp, struct songvec *sv)
{
- fprintf(fp, "%s\n", SONG_BEGIN);
songvec_for_each(sv, song_save, fp);
- fprintf(fp, "%s\n", SONG_END);
}
-static void
-insertSongIntoList(struct songvec *sv, struct song *newsong)
+struct song *
+song_load(FILE *fp, struct directory *parent, const char *uri,
+ GString *buffer, GError **error_r)
{
- struct song *existing = songvec_find(sv, newsong->url);
-
- if (!existing) {
- songvec_add(sv, newsong);
- if (newsong->tag)
- tag_end_add(newsong->tag);
- } else { /* prevent dupes, just update the existing song info */
- if (existing->mtime != newsong->mtime) {
- if (existing->tag != NULL)
- tag_free(existing->tag);
- if (newsong->tag)
- tag_end_add(newsong->tag);
- existing->tag = newsong->tag;
- existing->mtime = newsong->mtime;
- newsong->tag = NULL;
- }
- song_free(newsong);
- }
-}
-
-static char *
-matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType)
-{
- int i;
-
- for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
- size_t len = strlen(tag_item_names[i]);
+ struct song *song = song_file_new(uri, parent);
+ char *line, *colon;
+ enum tag_type type;
+ const char *value;
- if (0 == strncmp(tag_item_names[i], buffer, len) &&
- buffer[len] == ':') {
- *itemType = i;
- return g_strchug(buffer + len + 1);
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ strcmp(line, SONG_END) != 0) {
+ colon = strchr(line, ':');
+ if (colon == NULL || colon == line) {
+ if (song->tag != NULL)
+ tag_end_add(song->tag);
+ song_free(song);
+
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", line);
+ return false;
}
- }
- return NULL;
-}
+ *colon++ = 0;
+ value = g_strchug(colon);
-void readSongInfoIntoList(FILE *fp, struct songvec *sv,
- struct directory *parent)
-{
- enum {
- buffer_size = 32768,
- };
- char *buffer = g_malloc(buffer_size);
- struct song *song = NULL;
- enum tag_type itemType;
- const char *value;
-
- while (fgets(buffer, buffer_size, fp) &&
- !g_str_has_prefix(buffer, SONG_END)) {
- g_strchomp(buffer);
-
- if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) {
- if (song)
- insertSongIntoList(sv, song);
-
- song = song_file_new(buffer + strlen(SONG_KEY),
- parent);
- } else if (*buffer == 0) {
- /* ignore empty lines (starting with '\0') */
- } else if (song == NULL) {
- g_error("Problems reading song info");
- } else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) {
- /* we don't need this info anymore */
- } else if ((value = matchesAnMpdTagItemKey(buffer,
- &itemType)) != NULL) {
+ if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
if (!song->tag) {
song->tag = tag_new();
tag_begin_add(song->tag);
}
- tag_add_item(song->tag, itemType, value);
- } else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) {
+ tag_add_item(song->tag, type, value);
+ } else if (strcmp(line, "Time") == 0) {
if (!song->tag) {
song->tag = tag_new();
tag_begin_add(song->tag);
}
- song->tag->time = atoi(&(buffer[strlen(SONG_TIME)]));
- } else if (0 == strncmp(SONG_MTIME, buffer, strlen(SONG_MTIME))) {
- song->mtime = atoi(&(buffer[strlen(SONG_MTIME)]));
+ song->tag->time = atoi(value);
+ } else if (strcmp(line, SONG_MTIME) == 0) {
+ song->mtime = atoi(value);
+ } else {
+ if (song->tag != NULL)
+ tag_end_add(song->tag);
+ song_free(song);
+
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", line);
+ return false;
}
- else
- g_error("unknown line in db: %s", buffer);
}
- g_free(buffer);
+ if (song->tag != NULL)
+ tag_end_add(song->tag);
- if (song)
- insertSongIntoList(sv, song);
+ return song;
}
diff --git a/src/song_save.h b/src/song_save.h
index 370e42730..70ddb249e 100644
--- a/src/song_save.h
+++ b/src/song_save.h
@@ -20,14 +20,28 @@
#ifndef MPD_SONG_SAVE_H
#define MPD_SONG_SAVE_H
+#include <glib.h>
+
+#include <stdbool.h>
#include <stdio.h>
+#define SONG_BEGIN "song_begin: "
+
struct songvec;
struct directory;
void songvec_save(FILE *fp, struct songvec *sv);
-void readSongInfoIntoList(FILE * fp, struct songvec *sv,
- struct directory *parent);
+/**
+ * Loads a song from the input file. Reading stops after the
+ * "song_end" line.
+ *
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success, false on error
+ */
+struct song *
+song_load(FILE *fp, struct directory *parent, const char *uri,
+ GString *buffer, GError **error_r);
#endif
diff --git a/src/song_update.c b/src/song_update.c
new file mode 100644
index 000000000..2239f9aa6
--- /dev/null
+++ b/src/song_update.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2003-2009 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 "song.h"
+#include "uri.h"
+#include "directory.h"
+#include "mapper.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "tag_ape.h"
+#include "tag_id3.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+struct song *
+song_file_load(const char *path, struct directory *parent)
+{
+ struct song *song;
+ bool ret;
+
+ assert((parent == NULL) == g_path_is_absolute(path));
+ assert(!uri_has_scheme(path));
+ assert(strchr(path, '\n') == NULL);
+
+ song = song_file_new(path, parent);
+
+ //in archive ?
+ if (parent != NULL && parent->device == DEVICE_INARCHIVE) {
+ ret = song_file_update_inarchive(song);
+ } else {
+ ret = song_file_update(song);
+ }
+ if (!ret) {
+ song_free(song);
+ return NULL;
+ }
+
+ return song;
+}
+
+/**
+ * Attempts to load APE or ID3 tags from the specified file.
+ */
+static struct tag *
+tag_load_fallback(const char *path)
+{
+ struct tag *tag = tag_ape_load(path);
+ if (tag == NULL)
+ tag = tag_id3_load(path);
+ return tag;
+}
+
+/**
+ * The decoder plugin failed to load any tags: fall back to the APE or
+ * ID3 tag loader.
+ */
+static struct tag *
+tag_fallback(const char *path, struct tag *tag)
+{
+ struct tag *fallback = tag_load_fallback(path);
+
+ if (fallback != NULL) {
+ /* tag was successfully loaded: copy the song
+ duration, and destroy the old (empty) tag */
+ fallback->time = tag->time;
+ tag_free(tag);
+ return fallback;
+ } else
+ /* no APE/ID3 tag found: return the empty tag */
+ return tag;
+}
+
+bool
+song_file_update(struct song *song)
+{
+ const char *suffix;
+ char *path_fs;
+ const struct decoder_plugin *plugin;
+ struct stat st;
+
+ assert(song_is_file(song));
+
+ /* check if there's a suffix and a plugin */
+
+ suffix = uri_get_suffix(song->uri);
+ if (suffix == NULL)
+ return false;
+
+ plugin = decoder_plugin_from_suffix(suffix, NULL);
+ if (plugin == NULL)
+ return false;
+
+ path_fs = map_song_fs(song);
+ if (path_fs == NULL)
+ return false;
+
+ if (song->tag != NULL) {
+ tag_free(song->tag);
+ song->tag = NULL;
+ }
+
+ if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) {
+ g_free(path_fs);
+ return false;
+ }
+
+ song->mtime = st.st_mtime;
+
+ do {
+ song->tag = plugin->tag_dup(path_fs);
+ if (song->tag != NULL)
+ break;
+
+ plugin = decoder_plugin_from_suffix(suffix, plugin);
+ } while (plugin != NULL);
+
+ if (song->tag != NULL && tag_is_empty(song->tag))
+ song->tag = tag_fallback(path_fs, song->tag);
+
+ g_free(path_fs);
+ return song->tag != NULL;
+}
+
+bool
+song_file_update_inarchive(struct song *song)
+{
+ const char *suffix;
+ const struct decoder_plugin *plugin;
+
+ assert(song_is_file(song));
+
+ /* check if there's a suffix and a plugin */
+
+ suffix = uri_get_suffix(song->uri);
+ if (suffix == NULL)
+ return false;
+
+ plugin = decoder_plugin_from_suffix(suffix, false);
+ if (plugin == NULL)
+ return false;
+
+ if (song->tag != NULL)
+ tag_free(song->tag);
+
+ //accept every file that has music suffix
+ //because we dont support tag reading throught
+ //input streams
+ song->tag = tag_new();
+
+ return true;
+}
diff --git a/src/songvec.c b/src/songvec.c
index efef02216..f8f83e6e8 100644
--- a/src/songvec.c
+++ b/src/songvec.c
@@ -29,6 +29,38 @@
static GMutex *nr_lock = NULL;
+static const char *
+tag_get_value_checked(const struct tag *tag, enum tag_type type)
+{
+ return tag != NULL
+ ? tag_get_value(tag, type)
+ : NULL;
+}
+
+static int
+compare_utf8_string(const char *a, const char *b)
+{
+ if (a == NULL)
+ return b == NULL ? 0 : -1;
+
+ if (b == NULL)
+ return 1;
+
+ return g_utf8_collate(a, b);
+}
+
+/**
+ * Compare two string tag values, ignoring case. Either one may be
+ * NULL.
+ */
+static int
+compare_string_tag_item(const struct tag *a, const struct tag *b,
+ enum tag_type type)
+{
+ return compare_utf8_string(tag_get_value_checked(a, type),
+ tag_get_value_checked(b, type));
+}
+
/**
* Compare two tag values which should contain an integer value
* (e.g. disc or track number). Either one may be NULL.
@@ -51,14 +83,8 @@ compare_number_string(const char *a, const char *b)
static int
compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type)
{
- if (a == NULL)
- return b == NULL ? 0 : -1;
-
- if (b == NULL)
- return 1;
-
- return compare_number_string(tag_get_value(a, type),
- tag_get_value(b, type));
+ return compare_number_string(tag_get_value_checked(a, type),
+ tag_get_value_checked(b, type));
}
/* Only used for sorting/searchin a songvec, not general purpose compares */
@@ -68,18 +94,23 @@ static int songvec_cmp(const void *s1, const void *s2)
const struct song *b = ((const struct song * const *)s2)[0];
int ret;
- /* first sort by disc */
- ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_DISC);
+ /* first sort by album */
+ ret = compare_string_tag_item(a->tag, b->tag, TAG_ALBUM);
+ if (ret != 0)
+ return ret;
+
+ /* then sort by disc */
+ ret = compare_tag_item(a->tag, b->tag, TAG_DISC);
if (ret != 0)
return ret;
/* then by track number */
- ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_TRACK);
+ ret = compare_tag_item(a->tag, b->tag, TAG_TRACK);
if (ret != 0)
return ret;
/* still no difference? compare file name */
- return g_utf8_collate(a->url, b->url);
+ return g_utf8_collate(a->uri, b->uri);
}
static size_t sv_size(const struct songvec *sv)
@@ -108,14 +139,14 @@ void songvec_sort(struct songvec *sv)
}
struct song *
-songvec_find(const struct songvec *sv, const char *url)
+songvec_find(const struct songvec *sv, const char *uri)
{
int i;
struct song *ret = NULL;
g_mutex_lock(nr_lock);
for (i = sv->nr; --i >= 0; ) {
- if (strcmp(sv->base[i]->url, url))
+ if (strcmp(sv->base[i]->uri, uri))
continue;
ret = sv->base[i];
break;
@@ -182,7 +213,7 @@ songvec_for_each(const struct songvec *sv,
struct song *song = sv->base[i];
assert(song);
- assert(*song->url);
+ assert(*song->uri);
prev_nr = sv->nr;
g_mutex_unlock(nr_lock); /* fn() may block */
diff --git a/src/songvec.h b/src/songvec.h
index 0fd207ed0..1d07dd6e9 100644
--- a/src/songvec.h
+++ b/src/songvec.h
@@ -34,7 +34,7 @@ void songvec_deinit(void);
void songvec_sort(struct songvec *sv);
struct song *
-songvec_find(const struct songvec *sv, const char *url);
+songvec_find(const struct songvec *sv, const char *uri);
int
songvec_delete(struct songvec *sv, const struct song *del);
diff --git a/src/state_file.c b/src/state_file.c
index 9c6475cc8..aa66720c1 100644
--- a/src/state_file.c
+++ b/src/state_file.c
@@ -20,6 +20,7 @@
#include "state_file.h"
#include "output_state.h"
#include "playlist.h"
+#include "playlist_state.h"
#include "volume.h"
#include <glib.h>
@@ -30,28 +31,26 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "state_file"
-static struct _sf_cb {
- void (*reader)(FILE *);
- void (*writer)(FILE *);
-} sf_callbacks [] = {
- { read_sw_volume_state, save_sw_volume_state },
- { readAudioDevicesState, saveAudioDevicesState },
- { readPlaylistState, savePlaylistState },
-};
-
static char *state_file_path;
/** the GLib source id for the save timer */
static guint save_state_source_id;
+/**
+ * These version numbers determine whether we need to save the state
+ * file. If nothing has changed, we won't let the hard drive spin up.
+ */
+static unsigned prev_volume_version, prev_output_version,
+ prev_playlist_version;
+
static void
state_file_write(void)
{
- unsigned int i;
FILE *fp;
- if (state_file_path == NULL)
- return;
+ assert(state_file_path != NULL);
+
+ g_debug("Saving state file %s", state_file_path);
fp = fopen(state_file_path, "w");
if (G_UNLIKELY(!fp)) {
@@ -60,21 +59,27 @@ state_file_write(void)
return;
}
- for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++)
- sf_callbacks[i].writer(fp);
+ save_sw_volume_state(fp);
+ audio_output_state_save(fp);
+ playlist_state_save(fp, &g_playlist);
while(fclose(fp) && errno == EINTR) /* nothing */;
+
+ prev_volume_version = sw_volume_state_get_hash();
+ prev_output_version = audio_output_state_get_version();
+ prev_playlist_version = playlist_state_get_hash(&g_playlist);
}
static void
state_file_read(void)
{
- unsigned int i;
FILE *fp;
+ char line[1024];
+ bool success;
assert(state_file_path != NULL);
- g_debug("Saving state file");
+ g_debug("Loading state file %s", state_file_path);
fp = fopen(state_file_path, "r");
if (G_UNLIKELY(!fp)) {
@@ -82,12 +87,22 @@ state_file_read(void)
state_file_path, strerror(errno));
return;
}
- for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++) {
- sf_callbacks[i].reader(fp);
- rewind(fp);
+
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ g_strchomp(line);
+
+ success = read_sw_volume_state(line) ||
+ audio_output_state_read(line) ||
+ playlist_state_restore(line, fp, &g_playlist);
+ if (!success)
+ g_warning("Unrecognized line in state file: %s", line);
}
while(fclose(fp) && errno == EINTR) /* nothing */;
+
+ prev_volume_version = sw_volume_state_get_hash();
+ prev_output_version = audio_output_state_get_version();
+ prev_playlist_version = playlist_state_get_hash(&g_playlist);
}
/**
@@ -97,6 +112,13 @@ state_file_read(void)
static gboolean
timer_save_state_file(G_GNUC_UNUSED gpointer data)
{
+ if (prev_volume_version == sw_volume_state_get_hash() &&
+ prev_output_version == audio_output_state_get_version() &&
+ prev_playlist_version == playlist_state_get_hash(&g_playlist))
+ /* nothing has changed - don't save the state file,
+ don't spin up the hard disk */
+ return true;
+
state_file_write();
return true;
}
@@ -112,18 +134,22 @@ state_file_init(const char *path)
state_file_path = g_strdup(path);
state_file_read();
- save_state_source_id = g_timeout_add(5 * 60 * 1000,
- timer_save_state_file, NULL);
+ save_state_source_id = g_timeout_add_seconds(5 * 60,
+ timer_save_state_file,
+ NULL);
}
void
state_file_finish(void)
{
+ if (state_file_path == NULL)
+ /* no state file configured, no cleanup required */
+ return;
+
if (save_state_source_id != 0)
g_source_remove(save_state_source_id);
- if (state_file_path != NULL)
- state_file_write();
+ state_file_write();
g_free(state_file_path);
}
diff --git a/src/stats.c b/src/stats.c
index 01f6761f3..303d23c8e 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -52,11 +52,11 @@ visit_tag(struct visit_data *data, const struct tag *tag)
const struct tag_item *item = tag->items[i];
switch (item->type) {
- case TAG_ITEM_ARTIST:
+ case TAG_ARTIST:
strset_add(data->artists, item->value);
break;
- case TAG_ITEM_ALBUM:
+ case TAG_ALBUM:
strset_add(data->albums, item->value);
break;
@@ -113,7 +113,7 @@ int stats_print(struct client *client)
stats.album_count,
stats.song_count,
(long)g_timer_elapsed(stats.timer, NULL),
- (long)(getPlayerTotalPlayTime() + 0.5),
+ (long)(pc_get_total_play_time() + 0.5),
stats.song_duration,
db_get_mtime());
return 0;
diff --git a/src/sticker.c b/src/sticker.c
index cded09fca..4135e6293 100644
--- a/src/sticker.c
+++ b/src/sticker.c
@@ -76,50 +76,69 @@ static const char sticker_sql_create[] =
static sqlite3 *sticker_db;
static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)];
+static GQuark
+sticker_quark(void)
+{
+ return g_quark_from_static_string("sticker");
+}
+
static sqlite3_stmt *
-sticker_prepare(const char *sql)
+sticker_prepare(const char *sql, GError **error_r)
{
int ret;
sqlite3_stmt *stmt;
ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL);
- if (ret != SQLITE_OK)
- g_error("sqlite3_prepare_v2() failed: %s",
- sqlite3_errmsg(sticker_db));
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "sqlite3_prepare_v2() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return NULL;
+ }
return stmt;
}
-void
-sticker_global_init(const char *path)
+bool
+sticker_global_init(const char *path, GError **error_r)
{
int ret;
if (path == NULL)
/* not configured */
- return;
+ return true;
/* open/create the sqlite database */
ret = sqlite3_open(path, &sticker_db);
- if (ret != SQLITE_OK)
- g_error("Failed to open sqlite database '%s': %s",
- path, sqlite3_errmsg(sticker_db));
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "Failed to open sqlite database '%s': %s",
+ path, sqlite3_errmsg(sticker_db));
+ return false;
+ }
/* create the table and index */
ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL);
- if (ret != SQLITE_OK)
- g_error("Failed to create sticker table: %s",
- sqlite3_errmsg(sticker_db));
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "Failed to create sticker table: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
/* prepare the statements we're going to use */
for (unsigned i = 0; i < G_N_ELEMENTS(sticker_sql); ++i) {
assert(sticker_sql[i] != NULL);
- sticker_stmt[i] = sticker_prepare(sticker_sql[i]);
+ sticker_stmt[i] = sticker_prepare(sticker_sql[i], error_r);
+ if (sticker_stmt[i] == NULL)
+ return false;
}
+
+ return true;
}
void
diff --git a/src/sticker.h b/src/sticker.h
index 8e6410914..30d85fa18 100644
--- a/src/sticker.h
+++ b/src/sticker.h
@@ -50,9 +50,13 @@ struct sticker;
/**
* Opens the sticker database (if path is not NULL).
+ *
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success, false on error
*/
-void
-sticker_global_init(const char *path);
+bool
+sticker_global_init(const char *path, GError **error_r);
/**
* Close the sticker database.
diff --git a/src/tag.c b/src/tag.c
index 34205d20d..3e0bfc559 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -42,18 +42,20 @@ static struct {
} bulk;
const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
- "Artist",
- "Album",
- "AlbumArtist",
- "Title",
- "Track",
- "Name",
- "Genre",
- "Date",
- "Composer",
- "Performer",
- "Comment",
- "Disc",
+ [TAG_ARTIST] = "Artist",
+ [TAG_ARTIST_SORT] = "ArtistSort",
+ [TAG_ALBUM] = "Album",
+ [TAG_ALBUM_ARTIST] = "AlbumArtist",
+ [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort",
+ [TAG_TITLE] = "Title",
+ [TAG_TRACK] = "Track",
+ [TAG_NAME] = "Name",
+ [TAG_GENRE] = "Genre",
+ [TAG_DATE] = "Date",
+ [TAG_COMPOSER] = "Composer",
+ [TAG_PERFORMER] = "Performer",
+ [TAG_COMMENT] = "Comment",
+ [TAG_DISC] = "Disc",
/* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */
[TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID",
@@ -64,6 +66,36 @@ const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
+enum tag_type
+tag_name_parse(const char *name)
+{
+ assert(name != NULL);
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ assert(tag_item_names[i] != NULL);
+
+ if (strcmp(name, tag_item_names[i]) == 0)
+ return (enum tag_type)i;
+ }
+
+ return TAG_NUM_OF_ITEM_TYPES;
+}
+
+enum tag_type
+tag_name_parse_i(const char *name)
+{
+ assert(name != NULL);
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ assert(tag_item_names[i] != NULL);
+
+ if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0)
+ return (enum tag_type)i;
+ }
+
+ return TAG_NUM_OF_ITEM_TYPES;
+}
+
static size_t items_size(const struct tag *tag)
{
return tag->num_items * sizeof(struct tag_item *);
@@ -76,12 +108,12 @@ void tag_lib_init(void)
char *temp;
char *s;
char *c;
- int i;
+ enum tag_type type;
/* parse the "metadata_to_use" config parameter below */
/* ignore comments by default */
- ignore_tag_items[TAG_ITEM_COMMENT] = true;
+ ignore_tag_items[TAG_COMMENT] = true;
value = config_get_string(CONF_METADATA_TO_USE, NULL);
if (value == NULL)
@@ -98,16 +130,18 @@ void tag_lib_init(void)
if (*s == '\0')
quit = 1;
*s = '\0';
- for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
- if (g_ascii_strcasecmp(c, tag_item_names[i]) == 0) {
- ignore_tag_items[i] = false;
- break;
- }
- }
- if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) {
+
+ c = g_strstrip(c);
+ if (*c == 0)
+ continue;
+
+ type = tag_name_parse_i(c);
+ if (type == TAG_NUM_OF_ITEM_TYPES)
g_error("error parsing metadata item \"%s\"",
c);
- }
+
+ ignore_tag_items[type] = false;
+
s++;
c = s;
}
diff --git a/src/tag.h b/src/tag.h
index 4b72dd187..8cd09003e 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -31,18 +31,20 @@
* Codes for the type of a tag item.
*/
enum tag_type {
- TAG_ITEM_ARTIST,
- TAG_ITEM_ALBUM,
- TAG_ITEM_ALBUM_ARTIST,
- TAG_ITEM_TITLE,
- TAG_ITEM_TRACK,
- TAG_ITEM_NAME,
- TAG_ITEM_GENRE,
- TAG_ITEM_DATE,
- TAG_ITEM_COMPOSER,
- TAG_ITEM_PERFORMER,
- TAG_ITEM_COMMENT,
- TAG_ITEM_DISC,
+ TAG_ARTIST,
+ TAG_ARTIST_SORT,
+ TAG_ALBUM,
+ TAG_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST_SORT,
+ TAG_TITLE,
+ TAG_TRACK,
+ TAG_NAME,
+ TAG_GENRE,
+ TAG_DATE,
+ TAG_COMPOSER,
+ TAG_PERFORMER,
+ TAG_COMMENT,
+ TAG_DISC,
TAG_MUSICBRAINZ_ARTISTID,
TAG_MUSICBRAINZ_ALBUMID,
@@ -94,6 +96,22 @@ struct tag {
};
/**
+ * Parse the string, and convert it into a #tag_type. Returns
+ * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
+ */
+enum tag_type
+tag_name_parse(const char *name);
+
+/**
+ * Parse the string, and convert it into a #tag_type. Returns
+ * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
+ *
+ * Case does not matter.
+ */
+enum tag_type
+tag_name_parse_i(const char *name);
+
+/**
* Creates an empty #tag.
*/
struct tag *tag_new(void);
diff --git a/src/tag_ape.c b/src/tag_ape.c
index 7cbf32208..fb210ca46 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -25,6 +25,36 @@
#include <assert.h>
#include <stdio.h>
+static const char *const ape_tag_names[] = {
+ [TAG_TITLE] = "title",
+ [TAG_ARTIST] = "artist",
+ [TAG_ALBUM] = "album",
+ [TAG_COMMENT] = "comment",
+ [TAG_GENRE] = "genre",
+ [TAG_TRACK] = "track",
+ [TAG_DATE] = "year"
+};
+
+static struct tag *
+tag_ape_import_item(struct tag *tag, unsigned long flags,
+ const char *key, const char *value, size_t value_length)
+{
+ /* we only care about utf-8 text tags */
+ if ((flags & (0x3 << 1)) != 0)
+ return tag;
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(ape_tag_names); i++) {
+ if (ape_tag_names[i] != NULL &&
+ g_ascii_strcasecmp(key, ape_tag_names[i]) == 0) {
+ if (tag == NULL)
+ tag = tag_new();
+ tag_add_item_n(tag, i, value, value_length);
+ }
+ }
+
+ return tag;
+}
+
struct tag *
tag_ape_load(const char *file)
{
@@ -36,7 +66,6 @@ tag_ape_load(const char *file)
size_t tagLen;
size_t size;
unsigned long flags;
- int i;
char *key;
struct {
@@ -48,26 +77,6 @@ tag_ape_load(const char *file)
unsigned char reserved[8];
} footer;
- const char *apeItems[7] = {
- "title",
- "artist",
- "album",
- "comment",
- "genre",
- "track",
- "year"
- };
-
- int tagItems[7] = {
- TAG_ITEM_TITLE,
- TAG_ITEM_ARTIST,
- TAG_ITEM_ALBUM,
- TAG_ITEM_COMMENT,
- TAG_ITEM_GENRE,
- TAG_ITEM_TRACK,
- TAG_ITEM_DATE,
- };
-
fp = fopen(file, "r");
if (!fp)
return NULL;
@@ -127,17 +136,8 @@ tag_ape_load(const char *file)
if (tagLen < size)
goto fail;
- /* we only care about utf-8 text tags */
- if (!(flags & (0x3 << 1))) {
- for (i = 0; i < 7; i++) {
- if (g_ascii_strcasecmp(key, apeItems[i]) == 0) {
- if (!ret)
- ret = tag_new();
- tag_add_item_n(ret, tagItems[i],
- p, size);
- }
- }
- }
+ ret = tag_ape_import_item(ret, flags, key, p, size);
+
p += size;
tagLen -= size;
}
diff --git a/src/tag_id3.c b/src/tag_id3.c
index ce0386a51..ad19062bf 100644
--- a/src/tag_id3.c
+++ b/src/tag_id3.c
@@ -34,25 +34,31 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "id3"
-# define isId3v1(tag) (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1)
# ifndef ID3_FRAME_COMPOSER
# define ID3_FRAME_COMPOSER "TCOM"
# endif
-# ifndef ID3_FRAME_PERFORMER
-# define ID3_FRAME_PERFORMER "TOPE"
-# endif
# ifndef ID3_FRAME_DISC
# define ID3_FRAME_DISC "TPOS"
# endif
+#ifndef ID3_FRAME_ARTIST_SORT
+#define ID3_FRAME_ARTIST_SORT "TSOP"
+#endif
+
#ifndef ID3_FRAME_ALBUM_ARTIST_SORT
-#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2"
+#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */
#endif
#ifndef ID3_FRAME_ALBUM_ARTIST
#define ID3_FRAME_ALBUM_ARTIST "TPE2"
#endif
+static inline bool
+tag_is_id3v1(struct id3_tag *tag)
+{
+ return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0;
+}
+
static id3_utf8_t *
tag_id3_getstring(const struct id3_frame *frame, unsigned i)
{
@@ -72,14 +78,13 @@ tag_id3_getstring(const struct id3_frame *frame, unsigned i)
/* This will try to convert a string to utf-8,
*/
-static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4, int type)
+static id3_utf8_t *
+import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
{
id3_utf8_t *utf8, *utf8_stripped;
id3_latin1_t *isostr;
const char *encoding;
- if (type == TAG_ITEM_GENRE)
- ucs4 = id3_genre_name(ucs4);
/* use encoding field here? */
if (is_id3v1 &&
(encoding = config_get_string(CONF_ID3V1_ENCODING, NULL)) != NULL) {
@@ -112,8 +117,16 @@ static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4,
return utf8_stripped;
}
+/**
+ * Import a "Text information frame" (ID3v2.4.0 section 4.2). It
+ * contains 2 fields:
+ *
+ * - encoding
+ * - string list
+ */
static void
-getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag)
+tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
+ enum tag_type type)
{
struct id3_frame const *frame;
id3_ucs4_t const *ucs4;
@@ -122,108 +135,77 @@ getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag)
unsigned int nstrings, i;
frame = id3_tag_findframe(tag, id, 0);
- /* Check frame */
- if (!frame)
- {
+ if (frame == NULL || frame->nfields != 2)
return;
- }
- /* Check fields in frame */
- if(frame->nfields == 0)
- {
- g_debug("Frame has no fields");
+
+ /* check the encoding field */
+
+ field = id3_frame_field(frame, 0);
+ if (field == NULL || field->type != ID3_FIELD_TYPE_TEXTENCODING)
return;
- }
- /* Starting with T is a stringlist */
- if (id[0] == 'T')
- {
- /* This one contains 2 fields:
- * 1st: Text encoding
- * 2: Stringlist
- * Shamefully this isn't the RL case.
- * But I am going to enforce it anyway.
- */
- if(frame->nfields != 2)
- {
- g_debug("Invalid number '%i' of fields for TXX frame",
- frame->nfields);
- return;
- }
- field = &frame->fields[0];
- /**
- * First field is encoding field.
- * This is ignored by mpd.
- */
- if(field->type != ID3_FIELD_TYPE_TEXTENCODING)
- {
- g_debug("Expected encoding, found: %i",
- field->type);
- }
- /* Process remaining fields, should be only one */
- field = &frame->fields[1];
- /* Encoding field */
- if(field->type == ID3_FIELD_TYPE_STRINGLIST) {
- /* Get the number of strings available */
- nstrings = id3_field_getnstrings(field);
- for (i = 0; i < nstrings; i++) {
- ucs4 = id3_field_getstrings(field,i);
- if(!ucs4)
- continue;
- utf8 = processID3FieldString(isId3v1(tag),ucs4, type);
- if(!utf8)
- continue;
-
- tag_add_item(mpdTag, type, (char *)utf8);
- g_free(utf8);
- }
- }
- else {
- g_warning("Field type not processed: %i",
- (int)id3_field_gettextencoding(field));
- }
- }
- /* A comment frame */
- else if(!strcmp(ID3_FRAME_COMMENT, id))
- {
- /* A comment frame is different... */
- /* 1st: encoding
- * 2nd: Language
- * 3rd: String
- * 4th: FullString.
- * The 'value' we want is in the 4th field
- */
- if(frame->nfields == 4)
- {
- /* for now I only read the 4th field, with the fullstring */
- field = &frame->fields[3];
- if(field->type == ID3_FIELD_TYPE_STRINGFULL)
- {
- ucs4 = id3_field_getfullstring(field);
- if(ucs4)
- {
- utf8 = processID3FieldString(isId3v1(tag),ucs4, type);
- if(utf8)
- {
- tag_add_item(mpdTag, type, (char *)utf8);
- g_free(utf8);
- }
- }
- }
- else
- {
- g_debug("4th field in comment frame differs from expected, got '%i': ignoring",
- field->type);
- }
- }
- else
- {
- g_debug("Invalid 'comments' tag, got '%i' fields instead of 4",
- frame->nfields);
- }
+ /* process the value(s) */
+
+ field = id3_frame_field(frame, 1);
+ if (field == NULL || field->type != ID3_FIELD_TYPE_STRINGLIST)
+ return;
+
+ /* Get the number of strings available */
+ nstrings = id3_field_getnstrings(field);
+ for (i = 0; i < nstrings; i++) {
+ ucs4 = id3_field_getstrings(field, i);
+ if (ucs4 == NULL)
+ continue;
+
+ if (type == TAG_GENRE)
+ ucs4 = id3_genre_name(ucs4);
+
+ utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ if (utf8 == NULL)
+ continue;
+
+ tag_add_item(dest, type, (char *)utf8);
+ g_free(utf8);
}
- /* Unsupported */
- else
- g_debug("Unsupported tag type requrested");
+}
+
+/**
+ * Import a "Comment frame" (ID3v2.4.0 section 4.10). It
+ * contains 4 fields:
+ *
+ * - encoding
+ * - language
+ * - string
+ * - full string (we use this one)
+ */
+static void
+tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
+ enum tag_type type)
+{
+ struct id3_frame const *frame;
+ id3_ucs4_t const *ucs4;
+ id3_utf8_t *utf8;
+ union id3_field const *field;
+
+ frame = id3_tag_findframe(tag, id, 0);
+ if (frame == NULL || frame->nfields != 4)
+ return;
+
+ /* for now I only read the 4th field, with the fullstring */
+ field = id3_frame_field(frame, 3);
+ if (field == NULL)
+ return;
+
+ ucs4 = id3_field_getfullstring(field);
+ if (ucs4 == NULL)
+ return;
+
+ utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ if (utf8 == NULL)
+ return;
+
+ tag_add_item(dest, type, (char *)utf8);
+ g_free(utf8);
}
/**
@@ -237,6 +219,7 @@ tag_id3_parse_txxx_name(const char *name)
enum tag_type type;
const char *name;
} musicbrainz_txxx[] = {
+ { TAG_ALBUM_ARTIST_SORT, "ALBUMARTISTSORT" },
{ TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id" },
{ TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id" },
{ TAG_MUSICBRAINZ_ALBUMARTISTID,
@@ -328,20 +311,23 @@ struct tag *tag_id3_import(struct id3_tag * tag)
{
struct tag *ret = tag_new();
- getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret);
- getID3Info(tag, ID3_FRAME_ALBUM_ARTIST,
- TAG_ITEM_ALBUM_ARTIST, ret);
- getID3Info(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
- TAG_ITEM_ALBUM_ARTIST, ret);
- getID3Info(tag, ID3_FRAME_TITLE, TAG_ITEM_TITLE, ret);
- getID3Info(tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM, ret);
- getID3Info(tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK, ret);
- getID3Info(tag, ID3_FRAME_YEAR, TAG_ITEM_DATE, ret);
- getID3Info(tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE, ret);
- getID3Info(tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER, ret);
- getID3Info(tag, ID3_FRAME_PERFORMER, TAG_ITEM_PERFORMER, ret);
- getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret);
- getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST, TAG_ARTIST);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST_SORT,
+ TAG_ARTIST_SORT);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST_SORT,
+ TAG_ALBUM_ARTIST_SORT);
+ tag_id3_import_text(ret, tag, ID3_FRAME_TITLE, TAG_TITLE);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM, TAG_ALBUM);
+ tag_id3_import_text(ret, tag, ID3_FRAME_TRACK, TAG_TRACK);
+ tag_id3_import_text(ret, tag, ID3_FRAME_YEAR, TAG_DATE);
+ tag_id3_import_text(ret, tag, ID3_FRAME_GENRE, TAG_GENRE);
+ tag_id3_import_text(ret, tag, ID3_FRAME_COMPOSER, TAG_COMPOSER);
+ tag_id3_import_text(ret, tag, "TPE3", TAG_PERFORMER);
+ tag_id3_import_text(ret, tag, "TPE4", TAG_PERFORMER);
+ tag_id3_import_comment(ret, tag, ID3_FRAME_COMMENT, TAG_COMMENT);
+ tag_id3_import_text(ret, tag, ID3_FRAME_DISC, TAG_DISC);
tag_id3_import_musicbrainz(ret, tag);
tag_id3_import_ufid(ret, tag);
@@ -354,69 +340,72 @@ struct tag *tag_id3_import(struct id3_tag * tag)
return ret;
}
-static int fillBuffer(void *buf, size_t size, FILE * stream,
- long offset, int whence)
+static int
+fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence)
{
if (fseek(stream, offset, whence) != 0) return 0;
return fread(buf, 1, size, stream);
}
-static int getId3v2FooterSize(FILE * stream, long offset, int whence)
+static int
+get_id3v2_footer_size(FILE *stream, long offset, int whence)
{
id3_byte_t buf[ID3_TAG_QUERYSIZE];
int bufsize;
- bufsize = fillBuffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
+ bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
if (bufsize <= 0) return 0;
return id3_tag_query(buf, bufsize);
}
-static struct id3_tag *getId3Tag(FILE * stream, long offset, int whence)
+static struct id3_tag *
+tag_id3_read(FILE *stream, long offset, int whence)
{
struct id3_tag *tag;
- id3_byte_t queryBuf[ID3_TAG_QUERYSIZE];
- id3_byte_t *tagBuf;
- int tagSize;
- int queryBufSize;
- int tagBufSize;
+ id3_byte_t query_buffer[ID3_TAG_QUERYSIZE];
+ id3_byte_t *tag_buffer;
+ int tag_size;
+ int query_buffer_size;
+ int tag_buffer_size;
/* It's ok if we get less than we asked for */
- queryBufSize = fillBuffer(queryBuf, ID3_TAG_QUERYSIZE,
- stream, offset, whence);
- if (queryBufSize <= 0) return NULL;
+ query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE,
+ stream, offset, whence);
+ if (query_buffer_size <= 0) return NULL;
/* Look for a tag header */
- tagSize = id3_tag_query(queryBuf, queryBufSize);
- if (tagSize <= 0) return NULL;
+ tag_size = id3_tag_query(query_buffer, query_buffer_size);
+ if (tag_size <= 0) return NULL;
/* Found a tag. Allocate a buffer and read it in. */
- tagBuf = g_malloc(tagSize);
- if (!tagBuf) return NULL;
+ tag_buffer = g_malloc(tag_size);
+ if (!tag_buffer) return NULL;
- tagBufSize = fillBuffer(tagBuf, tagSize, stream, offset, whence);
- if (tagBufSize < tagSize) {
- g_free(tagBuf);
+ tag_buffer_size = fill_buffer(tag_buffer, tag_size, stream, offset, whence);
+ if (tag_buffer_size < tag_size) {
+ g_free(tag_buffer);
return NULL;
}
- tag = id3_tag_parse(tagBuf, tagBufSize);
+ tag = id3_tag_parse(tag_buffer, tag_buffer_size);
- g_free(tagBuf);
+ g_free(tag_buffer);
return tag;
}
-static struct id3_tag *findId3TagFromBeginning(FILE * stream)
+static struct id3_tag *
+tag_id3_find_from_beginning(FILE *stream)
{
struct id3_tag *tag;
struct id3_tag *seektag;
struct id3_frame *frame;
int seek;
- tag = getId3Tag(stream, 0, SEEK_SET);
+ tag = tag_id3_read(stream, 0, SEEK_SET);
if (!tag) {
return NULL;
- } else if (isId3v1(tag)) {
+ } else if (tag_is_id3v1(tag)) {
/* id3v1 tags don't belong here */
id3_tag_delete(tag);
return NULL;
@@ -430,8 +419,8 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream)
break;
/* Get the tag specified by the SEEK frame */
- seektag = getId3Tag(stream, seek, SEEK_CUR);
- if (!seektag || isId3v1(seektag))
+ seektag = tag_id3_read(stream, seek, SEEK_CUR);
+ if (!seektag || tag_is_id3v1(seektag))
break;
/* Replace the old tag with the new one */
@@ -442,22 +431,23 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream)
return tag;
}
-static struct id3_tag *findId3TagFromEnd(FILE * stream)
+static struct id3_tag *
+tag_id3_find_from_end(FILE *stream)
{
struct id3_tag *tag;
struct id3_tag *v1tag;
int tagsize;
/* Get an id3v1 tag from the end of file for later use */
- v1tag = getId3Tag(stream, -128, SEEK_END);
+ v1tag = tag_id3_read(stream, -128, SEEK_END);
/* Get the id3v2 tag size from the footer (located before v1tag) */
- tagsize = getId3v2FooterSize(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
+ tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
if (tagsize >= 0)
return v1tag;
/* Get the tag which the footer belongs to */
- tag = getId3Tag(stream, tagsize, SEEK_CUR);
+ tag = tag_id3_read(stream, tagsize, SEEK_CUR);
if (!tag)
return v1tag;
@@ -511,11 +501,11 @@ struct tag *tag_id3_load(const char *file)
return NULL;
}
- tag = findId3TagFromBeginning(stream);
+ tag = tag_id3_find_from_beginning(stream);
if (tag == NULL)
tag = tag_id3_riff_aiff_load(stream);
if (!tag)
- tag = findId3TagFromEnd(stream);
+ tag = tag_id3_find_from_end(stream);
fclose(stream);
diff --git a/src/text_file.c b/src/text_file.c
new file mode 100644
index 000000000..16698fc57
--- /dev/null
+++ b/src/text_file.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2009 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 "text_file.h"
+
+#include <assert.h>
+#include <string.h>
+
+char *
+read_text_line(FILE *file, GString *buffer)
+{
+ enum {
+ max_length = 512 * 1024,
+ step = 1024,
+ };
+
+ gsize length = 0, i;
+ char *p;
+
+ assert(file != NULL);
+ assert(buffer != NULL);
+
+ if (buffer->allocated_len < step)
+ g_string_set_size(buffer, step);
+
+ while (buffer->len < max_length) {
+ p = fgets(buffer->str + length,
+ buffer->allocated_len - length, file);
+ if (p == NULL) {
+ if (length == 0 || ferror(file))
+ return NULL;
+ break;
+ }
+
+ i = strlen(buffer->str + length);
+ length += i;
+ if (i < step - 1 || buffer->str[length - 1] == '\n')
+ break;
+
+ g_string_set_size(buffer, length + step);
+ }
+
+ g_string_set_size(buffer, length);
+ g_strchomp(buffer->str);
+ return buffer->str;
+}
diff --git a/src/text_file.h b/src/text_file.h
new file mode 100644
index 000000000..bc5c92870
--- /dev/null
+++ b/src/text_file.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2009 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_TEXT_FILE_H
+#define MPD_TEXT_FILE_H
+
+#include <glib.h>
+
+#include <stdio.h>
+
+/**
+ * Reads a line from the input file, and strips trailing space. There
+ * is a reasonable maximum line length, only to prevent denial of
+ * service.
+ *
+ * @param file the source file, opened in text mode
+ * @param buffer an allocator for the buffer
+ * @return a pointer to the line, or NULL on end-of-file or error
+ */
+char *
+read_text_line(FILE *file, GString *buffer);
+
+#endif
diff --git a/src/text_input_stream.c b/src/text_input_stream.c
new file mode 100644
index 000000000..ec81c9a27
--- /dev/null
+++ b/src/text_input_stream.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2009 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 "text_input_stream.h"
+#include "input_stream.h"
+#include "fifo_buffer.h"
+
+#include <glib.h>
+
+#include <string.h>
+
+struct text_input_stream {
+ struct input_stream *is;
+
+ struct fifo_buffer *buffer;
+
+ char *line;
+};
+
+struct text_input_stream *
+text_input_stream_new(struct input_stream *is)
+{
+ struct text_input_stream *tis = g_new(struct text_input_stream, 1);
+
+ tis->is = is;
+ tis->buffer = fifo_buffer_new(4096);
+ tis->line = NULL;
+
+ return tis;
+}
+
+void
+text_input_stream_free(struct text_input_stream *tis)
+{
+ fifo_buffer_free(tis->buffer);
+ g_free(tis->line);
+ g_free(tis);
+}
+
+const char *
+text_input_stream_read(struct text_input_stream *tis)
+{
+ void *dest;
+ const char *src, *p;
+ size_t length, nbytes;
+
+ g_free(tis->line);
+ tis->line = NULL;
+
+ do {
+ dest = fifo_buffer_write(tis->buffer, &length);
+ if (dest != NULL) {
+ nbytes = input_stream_read(tis->is, dest, length);
+ if (nbytes > 0)
+ fifo_buffer_append(tis->buffer, nbytes);
+ }
+
+ src = fifo_buffer_read(tis->buffer, &length);
+ if (src == NULL)
+ return NULL;
+
+ p = memchr(src, '\n', length);
+ } while (p == NULL);
+
+ length = p - src + 1;
+ while (p > src && g_ascii_isspace(p[-1]))
+ --p;
+
+ tis->line = g_strndup(src, p - src);
+ fifo_buffer_consume(tis->buffer, length);
+ return tis->line;
+}
diff --git a/src/text_input_stream.h b/src/text_input_stream.h
new file mode 100644
index 000000000..2b93ae180
--- /dev/null
+++ b/src/text_input_stream.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2009 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_TEXT_INPUT_STREAM_H
+#define MPD_TEXT_INPUT_STREAM_H
+
+struct input_stream;
+struct text_input_stream;
+
+/**
+ * Wraps an existing #input_stream object into a #text_input_stream,
+ * to read its contents as text lines.
+ *
+ * @param is an open #input_stream object
+ * @return the new #text_input_stream object
+ */
+struct text_input_stream *
+text_input_stream_new(struct input_stream *is);
+
+/**
+ * Frees the #text_input_stream object. Does not close or free the
+ * underlying #input_stream.
+ */
+void
+text_input_stream_free(struct text_input_stream *tis);
+
+/**
+ * Reads the next line from the stream.
+ *
+ * @return a line (newline character stripped), or NULL on end of file
+ * or error
+ */
+const char *
+text_input_stream_read(struct text_input_stream *tis);
+
+#endif
diff --git a/src/tokenizer.c b/src/tokenizer.c
new file mode 100644
index 000000000..c1b64f959
--- /dev/null
+++ b/src/tokenizer.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2003-2009 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 "tokenizer.h"
+
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+
+G_GNUC_CONST
+static GQuark
+tokenizer_quark(void)
+{
+ return g_quark_from_static_string("tokenizer");
+}
+
+static inline bool
+valid_word_first_char(char ch)
+{
+ return g_ascii_isalpha(ch);
+}
+
+static inline bool
+valid_word_char(char ch)
+{
+ return g_ascii_isalnum(ch) || ch == '_';
+}
+
+char *
+tokenizer_next_word(char **input_p, GError **error_r)
+{
+ char *word, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = input = *input_p;
+
+ if (*input == 0)
+ return NULL;
+
+ /* check the first character */
+
+ if (!valid_word_first_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Letter expected");
+ return NULL;
+ }
+
+ /* now iterate over the other characters until we find a
+ whitespace or end-of-string */
+
+ while (*++input != 0) {
+ if (g_ascii_isspace(*input)) {
+ /* a whitespace: the word ends here */
+ *input = 0;
+ /* skip all following spaces, too */
+ input = g_strchug(input + 1);
+ break;
+ }
+
+ if (!valid_word_char(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid word character");
+ return NULL;
+ }
+ }
+
+ /* end of string: the string is already null-terminated
+ here */
+
+ *input_p = input;
+ return word;
+}
+
+static inline bool
+valid_unquoted_char(char ch)
+{
+ return (unsigned char)ch > 0x20 && ch != '"' && ch != '\'';
+}
+
+char *
+tokenizer_next_unquoted(char **input_p, GError **error_r)
+{
+ char *word, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = input = *input_p;
+
+ if (*input == 0)
+ return NULL;
+
+ /* check the first character */
+
+ if (!valid_unquoted_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid unquoted character");
+ return NULL;
+ }
+
+ /* now iterate over the other characters until we find a
+ whitespace or end-of-string */
+
+ while (*++input != 0) {
+ if (g_ascii_isspace(*input)) {
+ /* a whitespace: the word ends here */
+ *input = 0;
+ /* skip all following spaces, too */
+ input = g_strchug(input + 1);
+ break;
+ }
+
+ if (!valid_unquoted_char(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid unquoted character");
+ return NULL;
+ }
+ }
+
+ /* end of string: the string is already null-terminated
+ here */
+
+ *input_p = input;
+ return word;
+}
+
+char *
+tokenizer_next_string(char **input_p, GError **error_r)
+{
+ char *word, *dest, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = dest = input = *input_p;
+
+ if (*input == 0)
+ /* end of line */
+ return NULL;
+
+ /* check for the opening " */
+
+ if (*input != '"') {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "'\"' expected");
+ return NULL;
+ }
+
+ ++input;
+
+ /* copy all characters */
+
+ while (*input != '"') {
+ if (*input == '\\')
+ /* the backslash escapes the following
+ character */
+ ++input;
+
+ if (*input == 0) {
+ /* return input-1 so the caller can see the
+ difference between "end of line" and
+ "error" */
+ *input_p = input - 1;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Missing closing '\"'");
+ return NULL;
+ }
+
+ /* copy one character */
+ *dest++ = *input++;
+ }
+
+ /* the following character must be a whitespace (or end of
+ line) */
+
+ ++input;
+ if (*input != 0 && !g_ascii_isspace(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Space expected after closing '\"'");
+ return NULL;
+ }
+
+ /* finish the string and return it */
+
+ *dest = 0;
+ *input_p = g_strchug(input);
+ return word;
+}
+
+char *
+tokenizer_next_param(char **input_p, GError **error_r)
+{
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ if (**input_p == '"')
+ return tokenizer_next_string(input_p, error_r);
+ else
+ return tokenizer_next_unquoted(input_p, error_r);
+}
diff --git a/src/tokenizer.h b/src/tokenizer.h
new file mode 100644
index 000000000..ce4c37ccd
--- /dev/null
+++ b/src/tokenizer.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2003-2009 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_TOKENIZER_H
+#define MPD_TOKENIZER_H
+
+#include <glib.h>
+
+/**
+ * Reads the next word from the input string. This function modifies
+ * the input string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated word, or NULL on error or
+ * end of line
+ */
+char *
+tokenizer_next_word(char **input_p, GError **error_r);
+
+/**
+ * Reads the next unquoted word from the input string. This function
+ * modifies the input string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated word, or NULL on error or
+ * end of line
+ */
+char *
+tokenizer_next_unquoted(char **input_p, GError **error_r);
+
+/**
+ * Reads the next quoted string from the input string. A backslash
+ * escapes the following character. This function modifies the input
+ * string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated string, or NULL on error
+ * or end of line
+ */
+char *
+tokenizer_next_string(char **input_p, GError **error_r);
+
+/**
+ * Reads the next unquoted word or quoted string from the input. This
+ * is a wrapper for tokenizer_next_unquoted() and
+ * tokenizer_next_string().
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated string, or NULL on error
+ * or end of line
+ */
+char *
+tokenizer_next_param(char **input_p, GError **error_r);
+
+#endif
diff --git a/src/update.c b/src/update.c
index d5c9779c8..2bab38d9d 100644
--- a/src/update.c
+++ b/src/update.c
@@ -17,45 +17,22 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "update_internal.h"
#include "update.h"
#include "database.h"
-#include "directory.h"
-#include "song.h"
-#include "uri.h"
#include "mapper.h"
-#include "path.h"
-#include "decoder_list.h"
#include "archive_list.h"
#include "playlist.h"
#include "event_pipe.h"
-#include "notify.h"
#include "update.h"
#include "idle.h"
-#include "conf.h"
#include "stats.h"
#include "main.h"
#include "config.h"
-#ifdef ENABLE_SQLITE
-#include "sticker.h"
-#include "song_sticker.h"
-#endif
-
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include "decoder_plugin.h"
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "update"
static enum update_progress {
UPDATE_PROGRESS_IDLE = 0,
@@ -65,32 +42,14 @@ static enum update_progress {
static bool modified;
-/* make this dynamic?, or maybe this is big enough... */
-static char *update_paths[32];
-static size_t update_paths_nr;
-
static GThread *update_thr;
static const unsigned update_task_id_max = 1 << 15;
static unsigned update_task_id;
-static struct song *delete;
-
-/** used by the main thread to notify the update thread */
-static struct notify update_notify;
-
-#ifndef WIN32
-
-enum {
- DEFAULT_FOLLOW_INSIDE_SYMLINKS = true,
- DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true,
-};
-
-static bool follow_inside_symlinks;
-static bool follow_outside_symlinks;
-
-#endif
+/* XXX this flag is passed to update_task() */
+static bool discard;
unsigned
isUpdatingDB(void)
@@ -98,692 +57,11 @@ isUpdatingDB(void)
return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0;
}
-static void
-directory_set_stat(struct directory *dir, const struct stat *st)
-{
- dir->inode = st->st_ino;
- dir->device = st->st_dev;
- dir->stat = 1;
-}
-
-static void
-delete_song(struct directory *dir, struct song *del)
-{
- /* first, prevent traversers in main task from getting this */
- songvec_delete(&dir->songs, del);
-
- /* now take it out of the playlist (in the main_task) */
- assert(!delete);
- delete = del;
- event_pipe_emit(PIPE_EVENT_DELETE);
-
- do {
- notify_wait(&update_notify);
- } while (delete != NULL);
-
- /* finally, all possible references gone, free it */
- song_free(del);
-}
-
-static int
-delete_each_song(struct song *song, G_GNUC_UNUSED void *data)
-{
- struct directory *directory = data;
- assert(song->parent == directory);
- delete_song(directory, song);
- return 0;
-}
-
-static void
-delete_directory(struct directory *directory);
-
-/**
- * Recursively remove all sub directories and songs from a directory,
- * leaving an empty directory.
- */
-static void
-clear_directory(struct directory *directory)
-{
- int i;
-
- for (i = directory->children.nr; --i >= 0;)
- delete_directory(directory->children.base[i]);
-
- assert(directory->children.nr == 0);
-
- songvec_for_each(&directory->songs, delete_each_song, directory);
-}
-
-/**
- * Recursively free a directory and all its contents.
- */
-static void
-delete_directory(struct directory *directory)
-{
- assert(directory->parent != NULL);
-
- clear_directory(directory);
-
- dirvec_delete(&directory->parent->children, directory);
- directory_free(directory);
-}
-
-static void
-delete_name_in(struct directory *parent, const char *name)
-{
- struct directory *directory = directory_get_child(parent, name);
- struct song *song = songvec_find(&parent->songs, name);
-
- if (directory != NULL) {
- delete_directory(directory);
- modified = true;
- }
-
- if (song != NULL) {
- delete_song(parent, song);
- modified = true;
- }
-}
-
-/* passed to songvec_for_each */
-static int
-delete_song_if_removed(struct song *song, void *_data)
-{
- struct directory *dir = _data;
- char *path;
- struct stat st;
-
- if ((path = map_song_fs(song)) == NULL ||
- stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
- delete_song(dir, song);
- modified = true;
- }
-
- g_free(path);
- return 0;
-}
-
-static bool
-directory_exists(const struct directory *directory)
-{
- char *path_fs;
- GFileTest test;
- bool exists;
-
- path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- /* invalid path: cannot exist */
- return false;
-
- test = directory->device == DEVICE_INARCHIVE ||
- directory->device == DEVICE_CONTAINER
- ? G_FILE_TEST_IS_REGULAR
- : G_FILE_TEST_IS_DIR;
-
- exists = g_file_test(path_fs, test);
- g_free(path_fs);
-
- return exists;
-}
-
-static void
-removeDeletedFromDirectory(struct directory *directory)
-{
- int i;
- struct dirvec *dv = &directory->children;
-
- for (i = dv->nr; --i >= 0; ) {
- if (directory_exists(dv->base[i]))
- continue;
-
- g_debug("removing directory: %s", dv->base[i]->path);
- delete_directory(dv->base[i]);
- modified = true;
- }
-
- songvec_for_each(&directory->songs, delete_song_if_removed, directory);
-}
-
-static int
-stat_directory(const struct directory *directory, struct stat *st)
-{
- char *path_fs;
- int ret;
-
- path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- return -1;
- ret = stat(path_fs, st);
- g_free(path_fs);
- return ret;
-}
-
-static int
-stat_directory_child(const struct directory *parent, const char *name,
- struct stat *st)
-{
- char *path_fs;
- int ret;
-
- path_fs = map_directory_child_fs(parent, name);
- if (path_fs == NULL)
- return -1;
-
- ret = stat(path_fs, st);
- g_free(path_fs);
- return ret;
-}
-
-static int
-statDirectory(struct directory *dir)
-{
- struct stat st;
-
- if (stat_directory(dir, &st) < 0)
- return -1;
-
- directory_set_stat(dir, &st);
-
- return 0;
-}
-
-static int
-inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
-{
- while (parent) {
- if (!parent->stat && statDirectory(parent) < 0)
- return -1;
- if (parent->inode == inode && parent->device == device) {
- g_debug("recursive directory found");
- return 1;
- }
- parent = parent->parent;
- }
-
- return 0;
-}
-
-static struct directory *
-make_subdir(struct directory *parent, const char *name)
-{
- struct directory *directory;
-
- directory = directory_get_child(parent, name);
- if (directory == NULL) {
- char *path;
-
- if (directory_is_root(parent))
- path = NULL;
- else
- name = path = g_strconcat(directory_get_path(parent),
- "/", name, NULL);
-
- directory = directory_new_child(parent, name);
- g_free(path);
- }
-
- return directory;
-}
-
-#ifdef ENABLE_ARCHIVE
-static void
-update_archive_tree(struct directory *directory, char *name)
-{
- struct directory *subdir;
- struct song *song;
- char *tmp;
-
- tmp = strchr(name, '/');
- if (tmp) {
- *tmp = 0;
- //add dir is not there already
- if ((subdir = dirvec_find(&directory->children, name)) == NULL) {
- //create new directory
- subdir = make_subdir(directory, name);
- subdir->device = DEVICE_INARCHIVE;
- }
- //create directories first
- update_archive_tree(subdir, tmp+1);
- } else {
- if (strlen(name) == 0) {
- g_warning("archive returned directory only");
- return;
- }
- //add file
- song = songvec_find(&directory->songs, name);
- if (song == NULL) {
- song = song_file_load(name, directory);
- if (song != NULL) {
- songvec_add(&directory->songs, song);
- modified = true;
- g_message("added %s/%s",
- directory_get_path(directory), name);
- }
- }
- }
-}
-
-/**
- * Updates the file listing from an archive file.
- *
- * @param parent the parent directory the archive file resides in
- * @param name the UTF-8 encoded base name of the archive file
- * @param st stat() information on the archive file
- * @param plugin the archive plugin which fits this archive type
- */
-static void
-update_archive_file(struct directory *parent, const char *name,
- const struct stat *st,
- const struct archive_plugin *plugin)
-{
- char *path_fs;
- struct archive_file *file;
- struct directory *directory;
- char *filepath;
-
- directory = dirvec_find(&parent->children, name);
- if (directory != NULL && directory->mtime == st->st_mtime)
- /* MPD has already scanned the archive, and it hasn't
- changed since - don't consider updating it */
- return;
-
- path_fs = map_directory_child_fs(parent, name);
-
- /* open archive */
- file = plugin->open(path_fs);
- if (file == NULL) {
- g_warning("unable to open archive %s", path_fs);
- g_free(path_fs);
- return;
- }
-
- g_debug("archive %s opened", path_fs);
- g_free(path_fs);
-
- if (directory == NULL) {
- g_debug("creating archive directory: %s", name);
- directory = make_subdir(parent, name);
- /* mark this directory as archive (we use device for
- this) */
- directory->device = DEVICE_INARCHIVE;
- }
-
- directory->mtime = st->st_mtime;
-
- plugin->scan_reset(file);
-
- while ((filepath = plugin->scan_next(file)) != NULL) {
- /* split name into directory and file */
- g_debug("adding archive file: %s", filepath);
- update_archive_tree(directory, filepath);
- }
-
- plugin->close(file);
-}
-#endif
-
-static bool
-update_container_file( struct directory* directory,
- const char* name,
- const struct stat* st,
- const struct decoder_plugin* plugin)
-{
- char* vtrack = NULL;
- unsigned int tnum = 0;
- char* pathname = map_directory_child_fs(directory, name);
- struct directory* contdir = dirvec_find(&directory->children, name);
-
- // directory exists already
- if (contdir != NULL)
- {
- // modification time not eq. file mod. time
- if (contdir->mtime != st->st_mtime)
- {
- g_message("removing container file: %s", pathname);
-
- delete_directory(contdir);
- contdir = NULL;
-
- modified = true;
- }
- else {
- g_free(pathname);
- return true;
- }
- }
-
- contdir = make_subdir(directory, name);
- contdir->mtime = st->st_mtime;
- contdir->device = DEVICE_CONTAINER;
-
- while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL)
- {
- struct song* song = song_file_new(vtrack, contdir);
- char *child_path_fs;
-
- // shouldn't be necessary but it's there..
- song->mtime = st->st_mtime;
-
- child_path_fs = map_directory_child_fs(contdir, vtrack);
- g_free(vtrack);
-
- song->tag = plugin->tag_dup(child_path_fs);
- g_free(child_path_fs);
-
- songvec_add(&contdir->songs, song);
-
- modified = true;
- }
-
- g_free(pathname);
-
- if (tnum == 1)
- {
- delete_directory(contdir);
- return false;
- }
- else
- return true;
-}
-
-static void
-update_regular_file(struct directory *directory,
- const char *name, const struct stat *st)
-{
- const char *suffix = uri_get_suffix(name);
- const struct decoder_plugin* plugin;
-#ifdef ENABLE_ARCHIVE
- const struct archive_plugin *archive;
-#endif
- if (suffix == NULL)
- return;
-
- if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL)
- {
- struct song* song = songvec_find(&directory->songs, name);
-
- if (!(song != NULL && st->st_mtime == song->mtime) &&
- plugin->container_scan != NULL)
- {
- if (update_container_file(directory, name, st, plugin))
- {
- if (song != NULL)
- delete_song(directory, song);
-
- return;
- }
- }
-
- if (song == NULL) {
- song = song_file_load(name, directory);
- if (song == NULL)
- return;
-
- songvec_add(&directory->songs, song);
- modified = true;
- g_message("added %s/%s",
- directory_get_path(directory), name);
- } else if (st->st_mtime != song->mtime) {
- g_message("updating %s/%s",
- directory_get_path(directory), name);
- if (!song_file_update(song))
- delete_song(directory, song);
- modified = true;
- }
-#ifdef ENABLE_ARCHIVE
- } else if ((archive = archive_plugin_from_suffix(suffix))) {
- update_archive_file(directory, name, st, archive);
-#endif
- }
-}
-
-static bool
-updateDirectory(struct directory *directory, const struct stat *st);
-
-static void
-updateInDirectory(struct directory *directory,
- const char *name, const struct stat *st)
-{
- assert(strchr(name, '/') == NULL);
-
- if (S_ISREG(st->st_mode)) {
- update_regular_file(directory, name, st);
- } else if (S_ISDIR(st->st_mode)) {
- struct directory *subdir;
- bool ret;
-
- if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
- return;
-
- subdir = make_subdir(directory, name);
- assert(directory == subdir->parent);
-
- ret = updateDirectory(subdir, st);
- if (!ret)
- delete_directory(subdir);
- } else {
- g_debug("update: %s is not a directory, archive or music", name);
- }
-}
-
-/* we don't look at "." / ".." nor files with newlines in their name */
-static bool skip_path(const char *path)
-{
- return (path[0] == '.' && path[1] == 0) ||
- (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
- strchr(path, '\n') != NULL;
-}
-
-static bool
-skip_symlink(const struct directory *directory, const char *utf8_name)
-{
-#ifndef WIN32
- char buffer[MPD_PATH_MAX];
- char *path_fs;
- const char *p;
- ssize_t ret;
-
- path_fs = map_directory_child_fs(directory, utf8_name);
- if (path_fs == NULL)
- return true;
-
- ret = readlink(path_fs, buffer, sizeof(buffer));
- g_free(path_fs);
- if (ret < 0)
- /* don't skip if this is not a symlink */
- return errno != EINVAL;
-
- if (!follow_inside_symlinks && !follow_outside_symlinks) {
- /* ignore all symlinks */
- return true;
- } else if (follow_inside_symlinks && follow_outside_symlinks) {
- /* consider all symlinks */
- return false;
- }
-
- if (buffer[0] == '/')
- return !follow_outside_symlinks;
-
- p = buffer;
- while (*p == '.') {
- if (p[1] == '.' && p[2] == '/') {
- /* "../" moves to parent directory */
- directory = directory->parent;
- if (directory == NULL) {
- /* we have moved outside the music
- directory - skip this symlink
- if such symlinks are not allowed */
- return !follow_outside_symlinks;
- }
- p += 3;
- } else if (p[1] == '/')
- /* eliminate "./" */
- p += 2;
- else
- break;
- }
-
- /* we are still in the music directory, so this symlink points
- to a song which is already in the database - skip according
- to the follow_inside_symlinks param*/
- return !follow_inside_symlinks;
-#else
- /* no symlink checking on WIN32 */
-
- (void)directory;
- (void)utf8_name;
-
- return false;
-#endif
-}
-
-static bool
-updateDirectory(struct directory *directory, const struct stat *st)
-{
- DIR *dir;
- struct dirent *ent;
- char *path_fs;
-
- assert(S_ISDIR(st->st_mode));
-
- directory_set_stat(directory, st);
-
- path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- return false;
-
- dir = opendir(path_fs);
- if (!dir) {
- g_warning("Failed to open directory %s: %s",
- path_fs, g_strerror(errno));
- g_free(path_fs);
- return false;
- }
-
- g_free(path_fs);
-
- removeDeletedFromDirectory(directory);
-
- while ((ent = readdir(dir))) {
- char *utf8;
- struct stat st2;
-
- if (skip_path(ent->d_name))
- continue;
-
- utf8 = fs_charset_to_utf8(ent->d_name);
- if (utf8 == NULL)
- continue;
-
- if (skip_symlink(directory, utf8)) {
- delete_name_in(directory, utf8);
- g_free(utf8);
- continue;
- }
-
- if (stat_directory_child(directory, utf8, &st2) == 0)
- updateInDirectory(directory, utf8, &st2);
- else
- delete_name_in(directory, utf8);
-
- g_free(utf8);
- }
-
- closedir(dir);
-
- directory->mtime = st->st_mtime;
-
- return true;
-}
-
-static struct directory *
-directory_make_child_checked(struct directory *parent, const char *path)
-{
- struct directory *directory;
- char *base;
- struct stat st;
- struct song *conflicting;
-
- directory = directory_get_child(parent, path);
- if (directory != NULL)
- return directory;
-
- base = g_path_get_basename(path);
-
- if (stat_directory_child(parent, base, &st) < 0 ||
- inodeFoundInParent(parent, st.st_ino, st.st_dev)) {
- g_free(base);
- return NULL;
- }
-
- /* if we're adding directory paths, make sure to delete filenames
- with potentially the same name */
- conflicting = songvec_find(&parent->songs, base);
- if (conflicting)
- delete_song(parent, conflicting);
-
- g_free(base);
-
- directory = directory_new_child(parent, path);
- directory_set_stat(directory, &st);
- return directory;
-}
-
-static struct directory *
-addParentPathToDB(const char *utf8path)
-{
- struct directory *directory = db_get_root();
- char *duplicated = g_strdup(utf8path);
- char *slash = duplicated;
-
- while ((slash = strchr(slash, '/')) != NULL) {
- *slash = 0;
-
- directory = directory_make_child_checked(directory,
- duplicated);
- if (directory == NULL || slash == NULL)
- break;
-
- *slash++ = '/';
- }
-
- g_free(duplicated);
- return directory;
-}
-
-static void
-updatePath(const char *path)
-{
- struct directory *parent;
- char *name;
- struct stat st;
-
- parent = addParentPathToDB(path);
- if (parent == NULL)
- return;
-
- name = g_path_get_basename(path);
-
- if (stat_directory_child(parent, name, &st) == 0)
- updateInDirectory(parent, name, &st);
- else
- delete_name_in(parent, name);
-
- g_free(name);
-}
-
static void * update_task(void *_path)
{
- if (_path != NULL && !isRootDirectory(_path)) {
- updatePath((char *)_path);
- } else {
- struct directory *directory = db_get_root();
- struct stat st;
-
- if (stat_directory(directory, &st) == 0)
- updateDirectory(directory, &st);
- }
+ const char *path = _path;
+ modified = update_walk(path, discard);
g_free(_path);
if (modified || !db_exists())
@@ -794,7 +72,8 @@ static void * update_task(void *_path)
return NULL;
}
-static void spawn_update_task(char *path)
+static void
+spawn_update_task(const char *path)
{
GError *e = NULL;
@@ -802,15 +81,18 @@ static void spawn_update_task(char *path)
progress = UPDATE_PROGRESS_RUNNING;
modified = false;
- if (!(update_thr = g_thread_create(update_task, path, TRUE, &e)))
+
+ update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e);
+ if (update_thr == NULL)
g_error("Failed to spawn update task: %s", e->message);
+
if (++update_task_id > update_task_id_max)
update_task_id = 1;
g_debug("spawned thread for update job id %i", update_task_id);
}
unsigned
-directory_update_init(char *path)
+update_enqueue(const char *path, bool _discard)
{
assert(g_thread_self() == main_task);
@@ -818,48 +100,20 @@ directory_update_init(char *path)
return 0;
if (progress != UPDATE_PROGRESS_IDLE) {
- unsigned next_task_id;
-
- if (update_paths_nr == G_N_ELEMENTS(update_paths)) {
- g_free(path);
+ unsigned next_task_id =
+ update_queue_push(path, discard, update_task_id);
+ if (next_task_id == 0)
return 0;
- }
-
- assert(update_paths_nr < G_N_ELEMENTS(update_paths));
- update_paths[update_paths_nr++] = path;
- next_task_id = update_task_id + update_paths_nr;
return next_task_id > update_task_id_max ? 1 : next_task_id;
}
- spawn_update_task(path);
- return update_task_id;
-}
-/**
- * Safely delete a song from the database. This must be done in the
- * main task, to be sure that there is no pointer left to it.
- */
-static void song_delete_event(void)
-{
- char *uri;
-
- assert(progress == UPDATE_PROGRESS_RUNNING);
- assert(delete != NULL);
-
- uri = song_get_uri(delete);
- g_debug("removing: %s", uri);
- g_free(uri);
-
-#ifdef ENABLE_SQLITE
- /* if the song has a sticker, delete it */
- if (sticker_enabled())
- sticker_song_delete(delete);
-#endif
+ discard = _discard;
+ spawn_update_task(path);
- deleteASongFromPlaylist(&g_playlist, delete);
- delete = NULL;
+ idle_add(IDLE_UPDATE);
- notify_signal(&update_notify);
+ return update_task_id;
}
/**
@@ -867,22 +121,25 @@ static void song_delete_event(void)
*/
static void update_finished_event(void)
{
+ char *path;
+
assert(progress == UPDATE_PROGRESS_DONE);
g_thread_join(update_thr);
+ idle_add(IDLE_UPDATE);
+
if (modified) {
/* send "idle" events */
- playlistVersionChange(&g_playlist);
+ playlist_increment_version_all(&g_playlist);
idle_add(IDLE_DATABASE);
}
- if (update_paths_nr) {
+ path = update_queue_shift(&discard);
+ if (path != NULL) {
/* schedule the next path */
- char *path = update_paths[0];
- memmove(&update_paths[0], &update_paths[1],
- --update_paths_nr * sizeof(char *));
spawn_update_task(path);
+ g_free(path);
} else {
progress = UPDATE_PROGRESS_IDLE;
@@ -892,23 +149,14 @@ static void update_finished_event(void)
void update_global_init(void)
{
-#ifndef WIN32
- follow_inside_symlinks =
- config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS,
- DEFAULT_FOLLOW_INSIDE_SYMLINKS);
-
- follow_outside_symlinks =
- config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS,
- DEFAULT_FOLLOW_OUTSIDE_SYMLINKS);
-#endif
-
- notify_init(&update_notify);
-
- event_pipe_register(PIPE_EVENT_DELETE, song_delete_event);
event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event);
+
+ update_remove_global_init();
+ update_walk_global_init();
}
void update_global_finish(void)
{
- notify_deinit(&update_notify);
+ update_walk_global_finish();
+ update_remove_global_finish();
}
diff --git a/src/update.h b/src/update.h
index 3b7a5a332..15ff4bad1 100644
--- a/src/update.h
+++ b/src/update.h
@@ -20,6 +20,8 @@
#ifndef MPD_UPDATE_H
#define MPD_UPDATE_H
+#include <stdbool.h>
+
void update_global_init(void);
void update_global_finish(void);
@@ -27,12 +29,14 @@ void update_global_finish(void);
unsigned
isUpdatingDB(void);
-/*
- * returns the positive update job ID on success,
- * returns 0 if busy
- * @path will be freed by this function and should not be reused
+/**
+ * Add this path to the database update queue.
+ *
+ * @param path a path to update; if NULL or an empty string,
+ * the whole music directory is updated
+ * @return the job id, or 0 on error
*/
unsigned
-directory_update_init(char *path);
+update_enqueue(const char *path, bool discard);
#endif
diff --git a/src/update_internal.h b/src/update_internal.h
new file mode 100644
index 000000000..0655c727d
--- /dev/null
+++ b/src/update_internal.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2003-2009 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_UPDATE_INTERNAL_H
+#define MPD_UPDATE_INTERNAL_H
+
+#include <stdbool.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "update"
+
+struct stat;
+struct song;
+struct directory;
+
+unsigned
+update_queue_push(const char *path, bool discard, unsigned base);
+
+char *
+update_queue_shift(bool *discard_r);
+
+void
+update_walk_global_init(void);
+
+void
+update_walk_global_finish(void);
+
+/**
+ * Returns true if the database was modified.
+ */
+bool
+update_walk(const char *path, bool discard);
+
+void
+update_remove_global_init(void);
+
+void
+update_remove_global_finish(void);
+
+/**
+ * Sends a signal to the main thread which will in turn remove the
+ * song: from the sticker database and from the playlist. This
+ * serialized access is implemented to avoid excessive locking.
+ */
+void
+update_remove_song(const struct song *song);
+
+#endif
diff --git a/src/update_queue.c b/src/update_queue.c
new file mode 100644
index 000000000..60e752a62
--- /dev/null
+++ b/src/update_queue.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2003-2009 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 "update_internal.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+/* make this dynamic?, or maybe this is big enough... */
+static struct {
+ char *path;
+ bool discard;
+} update_queue[32];
+
+static size_t update_queue_length;
+
+unsigned
+update_queue_push(const char *path, bool discard, unsigned base)
+{
+ assert(update_queue_length <= G_N_ELEMENTS(update_queue));
+
+ if (update_queue_length == G_N_ELEMENTS(update_queue))
+ return 0;
+
+ update_queue[update_queue_length].path = g_strdup(path);
+ update_queue[update_queue_length].discard = discard;
+
+ ++update_queue_length;
+
+ return base + update_queue_length;
+}
+
+char *
+update_queue_shift(bool *discard_r)
+{
+ char *path;
+
+ if (update_queue_length == 0)
+ return NULL;
+
+ path = update_queue[0].path;
+ *discard_r = update_queue[0].discard;
+
+ memmove(&update_queue[0], &update_queue[1],
+ --update_queue_length * sizeof(update_queue[0]));
+ return path;
+}
diff --git a/src/update_remove.c b/src/update_remove.c
new file mode 100644
index 000000000..bf3d88db6
--- /dev/null
+++ b/src/update_remove.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2009 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 "update_internal.h"
+#include "notify.h"
+#include "event_pipe.h"
+#include "song.h"
+#include "playlist.h"
+
+#ifdef ENABLE_SQLITE
+#include "sticker.h"
+#include "song_sticker.h"
+#endif
+
+#include <glib.h>
+
+#include <assert.h>
+
+static const struct song *removed_song;
+
+static struct notify remove_notify;
+
+/**
+ * Safely remove a song from the database. This must be done in the
+ * main task, to be sure that there is no pointer left to it.
+ */
+static void
+song_remove_event(void)
+{
+ char *uri;
+
+ assert(removed_song != NULL);
+
+ uri = song_get_uri(removed_song);
+ g_debug("removing: %s", uri);
+ g_free(uri);
+
+#ifdef ENABLE_SQLITE
+ /* if the song has a sticker, remove it */
+ if (sticker_enabled())
+ sticker_song_remove(song);
+#endif
+
+ playlist_delete_song(&g_playlist, removed_song);
+ removed_song = NULL;
+
+ notify_signal(&remove_notify);
+}
+
+void
+update_remove_global_init(void)
+{
+ notify_init(&remove_notify);
+
+ event_pipe_register(PIPE_EVENT_DELETE, song_remove_event);
+}
+
+void
+update_remove_global_finish(void)
+{
+ notify_deinit(&remove_notify);
+}
+
+void
+update_remove_song(const struct song *song)
+{
+ assert(removed_song == NULL);
+
+ removed_song = song;
+
+ event_pipe_emit(PIPE_EVENT_DELETE);
+
+ do {
+ notify_wait(&remove_notify);
+ } while (removed_song != NULL);
+
+}
diff --git a/src/update_walk.c b/src/update_walk.c
new file mode 100644
index 000000000..2c1049472
--- /dev/null
+++ b/src/update_walk.c
@@ -0,0 +1,827 @@
+/*
+ * Copyright (C) 2003-2009 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 "update_internal.h"
+#include "database.h"
+#include "exclude.h"
+#include "directory.h"
+#include "song.h"
+#include "uri.h"
+#include "mapper.h"
+#include "path.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "conf.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+static bool walk_discard;
+static bool modified;
+
+#ifndef WIN32
+
+enum {
+ DEFAULT_FOLLOW_INSIDE_SYMLINKS = true,
+ DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true,
+};
+
+static bool follow_inside_symlinks;
+static bool follow_outside_symlinks;
+
+#endif
+
+void
+update_walk_global_init(void)
+{
+#ifndef WIN32
+ follow_inside_symlinks =
+ config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS,
+ DEFAULT_FOLLOW_INSIDE_SYMLINKS);
+
+ follow_outside_symlinks =
+ config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS,
+ DEFAULT_FOLLOW_OUTSIDE_SYMLINKS);
+#endif
+}
+
+void
+update_walk_global_finish(void)
+{
+}
+
+static void
+directory_set_stat(struct directory *dir, const struct stat *st)
+{
+ dir->inode = st->st_ino;
+ dir->device = st->st_dev;
+ dir->stat = 1;
+}
+
+static void
+delete_song(struct directory *dir, struct song *del)
+{
+ /* first, prevent traversers in main task from getting this */
+ songvec_delete(&dir->songs, del);
+
+ /* now take it out of the playlist (in the main_task) */
+ update_remove_song(del);
+
+ /* finally, all possible references gone, free it */
+ song_free(del);
+}
+
+static int
+delete_each_song(struct song *song, G_GNUC_UNUSED void *data)
+{
+ struct directory *directory = data;
+ assert(song->parent == directory);
+ delete_song(directory, song);
+ return 0;
+}
+
+static void
+delete_directory(struct directory *directory);
+
+/**
+ * Recursively remove all sub directories and songs from a directory,
+ * leaving an empty directory.
+ */
+static void
+clear_directory(struct directory *directory)
+{
+ int i;
+
+ for (i = directory->children.nr; --i >= 0;)
+ delete_directory(directory->children.base[i]);
+
+ assert(directory->children.nr == 0);
+
+ songvec_for_each(&directory->songs, delete_each_song, directory);
+}
+
+/**
+ * Recursively free a directory and all its contents.
+ */
+static void
+delete_directory(struct directory *directory)
+{
+ assert(directory->parent != NULL);
+
+ clear_directory(directory);
+
+ dirvec_delete(&directory->parent->children, directory);
+ directory_free(directory);
+}
+
+static void
+delete_name_in(struct directory *parent, const char *name)
+{
+ struct directory *directory = directory_get_child(parent, name);
+ struct song *song = songvec_find(&parent->songs, name);
+
+ if (directory != NULL) {
+ delete_directory(directory);
+ modified = true;
+ }
+
+ if (song != NULL) {
+ delete_song(parent, song);
+ modified = true;
+ }
+}
+
+/* passed to songvec_for_each */
+static int
+delete_song_if_excluded(struct song *song, void *_data)
+{
+ GSList *exclude_list = _data;
+ char *name_fs;
+
+ assert(song->parent != NULL);
+
+ name_fs = utf8_to_fs_charset(song->uri);
+ if (exclude_list_check(exclude_list, name_fs)) {
+ delete_song(song->parent, song);
+ modified = true;
+ }
+
+ g_free(name_fs);
+ return 0;
+}
+
+static void
+remove_excluded_from_directory(struct directory *directory,
+ GSList *exclude_list)
+{
+ int i;
+ struct dirvec *dv = &directory->children;
+
+ for (i = dv->nr; --i >= 0; ) {
+ struct directory *child = dv->base[i];
+ char *name_fs = utf8_to_fs_charset(directory_get_name(child));
+
+ if (exclude_list_check(exclude_list, name_fs)) {
+ delete_directory(child);
+ modified = true;
+ }
+
+ g_free(name_fs);
+ }
+
+ songvec_for_each(&directory->songs,
+ delete_song_if_excluded, exclude_list);
+}
+
+/* passed to songvec_for_each */
+static int
+delete_song_if_removed(struct song *song, void *_data)
+{
+ struct directory *dir = _data;
+ char *path;
+ struct stat st;
+
+ if ((path = map_song_fs(song)) == NULL ||
+ stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
+ delete_song(dir, song);
+ modified = true;
+ }
+
+ g_free(path);
+ return 0;
+}
+
+static bool
+directory_exists(const struct directory *directory)
+{
+ char *path_fs;
+ GFileTest test;
+ bool exists;
+
+ path_fs = map_directory_fs(directory);
+ if (path_fs == NULL)
+ /* invalid path: cannot exist */
+ return false;
+
+ test = directory->device == DEVICE_INARCHIVE ||
+ directory->device == DEVICE_CONTAINER
+ ? G_FILE_TEST_IS_REGULAR
+ : G_FILE_TEST_IS_DIR;
+
+ exists = g_file_test(path_fs, test);
+ g_free(path_fs);
+
+ return exists;
+}
+
+static void
+removeDeletedFromDirectory(struct directory *directory)
+{
+ int i;
+ struct dirvec *dv = &directory->children;
+
+ for (i = dv->nr; --i >= 0; ) {
+ if (directory_exists(dv->base[i]))
+ continue;
+
+ g_debug("removing directory: %s", dv->base[i]->path);
+ delete_directory(dv->base[i]);
+ modified = true;
+ }
+
+ songvec_for_each(&directory->songs, delete_song_if_removed, directory);
+}
+
+static int
+stat_directory(const struct directory *directory, struct stat *st)
+{
+ char *path_fs;
+ int ret;
+
+ path_fs = map_directory_fs(directory);
+ if (path_fs == NULL)
+ return -1;
+ ret = stat(path_fs, st);
+ g_free(path_fs);
+ return ret;
+}
+
+static int
+stat_directory_child(const struct directory *parent, const char *name,
+ struct stat *st)
+{
+ char *path_fs;
+ int ret;
+
+ path_fs = map_directory_child_fs(parent, name);
+ if (path_fs == NULL)
+ return -1;
+
+ ret = stat(path_fs, st);
+ g_free(path_fs);
+ return ret;
+}
+
+static int
+statDirectory(struct directory *dir)
+{
+ struct stat st;
+
+ if (stat_directory(dir, &st) < 0)
+ return -1;
+
+ directory_set_stat(dir, &st);
+
+ return 0;
+}
+
+static int
+inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
+{
+ while (parent) {
+ if (!parent->stat && statDirectory(parent) < 0)
+ return -1;
+ if (parent->inode == inode && parent->device == device) {
+ g_debug("recursive directory found");
+ return 1;
+ }
+ parent = parent->parent;
+ }
+
+ return 0;
+}
+
+static struct directory *
+make_subdir(struct directory *parent, const char *name)
+{
+ struct directory *directory;
+
+ directory = directory_get_child(parent, name);
+ if (directory == NULL) {
+ char *path;
+
+ if (directory_is_root(parent))
+ path = NULL;
+ else
+ name = path = g_strconcat(directory_get_path(parent),
+ "/", name, NULL);
+
+ directory = directory_new_child(parent, name);
+ g_free(path);
+ }
+
+ return directory;
+}
+
+#ifdef ENABLE_ARCHIVE
+static void
+update_archive_tree(struct directory *directory, char *name)
+{
+ struct directory *subdir;
+ struct song *song;
+ char *tmp;
+
+ tmp = strchr(name, '/');
+ if (tmp) {
+ *tmp = 0;
+ //add dir is not there already
+ if ((subdir = dirvec_find(&directory->children, name)) == NULL) {
+ //create new directory
+ subdir = make_subdir(directory, name);
+ subdir->device = DEVICE_INARCHIVE;
+ }
+ //create directories first
+ update_archive_tree(subdir, tmp+1);
+ } else {
+ if (strlen(name) == 0) {
+ g_warning("archive returned directory only");
+ return;
+ }
+ //add file
+ song = songvec_find(&directory->songs, name);
+ if (song == NULL) {
+ song = song_file_load(name, directory);
+ if (song != NULL) {
+ songvec_add(&directory->songs, song);
+ modified = true;
+ g_message("added %s/%s",
+ directory_get_path(directory), name);
+ }
+ }
+ }
+}
+
+/**
+ * Updates the file listing from an archive file.
+ *
+ * @param parent the parent directory the archive file resides in
+ * @param name the UTF-8 encoded base name of the archive file
+ * @param st stat() information on the archive file
+ * @param plugin the archive plugin which fits this archive type
+ */
+static void
+update_archive_file(struct directory *parent, const char *name,
+ const struct stat *st,
+ const struct archive_plugin *plugin)
+{
+ char *path_fs;
+ struct archive_file *file;
+ struct directory *directory;
+ char *filepath;
+
+ directory = dirvec_find(&parent->children, name);
+ if (directory != NULL && directory->mtime == st->st_mtime &&
+ !walk_discard)
+ /* MPD has already scanned the archive, and it hasn't
+ changed since - don't consider updating it */
+ return;
+
+ path_fs = map_directory_child_fs(parent, name);
+
+ /* open archive */
+ file = plugin->open(path_fs);
+ if (file == NULL) {
+ g_warning("unable to open archive %s", path_fs);
+ g_free(path_fs);
+ return;
+ }
+
+ g_debug("archive %s opened", path_fs);
+ g_free(path_fs);
+
+ if (directory == NULL) {
+ g_debug("creating archive directory: %s", name);
+ directory = make_subdir(parent, name);
+ /* mark this directory as archive (we use device for
+ this) */
+ directory->device = DEVICE_INARCHIVE;
+ }
+
+ directory->mtime = st->st_mtime;
+
+ plugin->scan_reset(file);
+
+ while ((filepath = plugin->scan_next(file)) != NULL) {
+ /* split name into directory and file */
+ g_debug("adding archive file: %s", filepath);
+ update_archive_tree(directory, filepath);
+ }
+
+ plugin->close(file);
+}
+#endif
+
+static bool
+update_container_file( struct directory* directory,
+ const char* name,
+ const struct stat* st,
+ const struct decoder_plugin* plugin)
+{
+ char* vtrack = NULL;
+ unsigned int tnum = 0;
+ char* pathname = map_directory_child_fs(directory, name);
+ struct directory* contdir = dirvec_find(&directory->children, name);
+
+ // directory exists already
+ if (contdir != NULL)
+ {
+ // modification time not eq. file mod. time
+ if (contdir->mtime != st->st_mtime || walk_discard)
+ {
+ g_message("removing container file: %s", pathname);
+
+ delete_directory(contdir);
+ contdir = NULL;
+
+ modified = true;
+ }
+ else {
+ g_free(pathname);
+ return true;
+ }
+ }
+
+ contdir = make_subdir(directory, name);
+ contdir->mtime = st->st_mtime;
+ contdir->device = DEVICE_CONTAINER;
+
+ while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL)
+ {
+ struct song* song = song_file_new(vtrack, contdir);
+ char *child_path_fs;
+
+ // shouldn't be necessary but it's there..
+ song->mtime = st->st_mtime;
+
+ child_path_fs = map_directory_child_fs(contdir, vtrack);
+
+ song->tag = plugin->tag_dup(child_path_fs);
+ g_free(child_path_fs);
+
+ songvec_add(&contdir->songs, song);
+
+ modified = true;
+
+ g_message("added %s/%s",
+ directory_get_path(directory), vtrack);
+ g_free(vtrack);
+ }
+
+ g_free(pathname);
+
+ if (tnum == 1)
+ {
+ delete_directory(contdir);
+ return false;
+ }
+ else
+ return true;
+}
+
+static void
+update_regular_file(struct directory *directory,
+ const char *name, const struct stat *st)
+{
+ const char *suffix = uri_get_suffix(name);
+ const struct decoder_plugin* plugin;
+#ifdef ENABLE_ARCHIVE
+ const struct archive_plugin *archive;
+#endif
+ if (suffix == NULL)
+ return;
+
+ if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL)
+ {
+ struct song* song = songvec_find(&directory->songs, name);
+
+ if (!(song != NULL && st->st_mtime == song->mtime &&
+ !walk_discard) &&
+ plugin->container_scan != NULL)
+ {
+ if (update_container_file(directory, name, st, plugin))
+ {
+ if (song != NULL)
+ delete_song(directory, song);
+
+ return;
+ }
+ }
+
+ if (song == NULL) {
+ song = song_file_load(name, directory);
+ if (song == NULL) {
+ g_debug("ignoring unrecognized file %s/%s",
+ directory_get_path(directory), name);
+ return;
+ }
+
+ songvec_add(&directory->songs, song);
+ modified = true;
+ g_message("added %s/%s",
+ directory_get_path(directory), name);
+ } else if (st->st_mtime != song->mtime || walk_discard) {
+ g_message("updating %s/%s",
+ directory_get_path(directory), name);
+ if (!song_file_update(song)) {
+ g_debug("deleting unrecognized file %s/%s",
+ directory_get_path(directory), name);
+ delete_song(directory, song);
+ }
+
+ modified = true;
+ }
+#ifdef ENABLE_ARCHIVE
+ } else if ((archive = archive_plugin_from_suffix(suffix))) {
+ update_archive_file(directory, name, st, archive);
+#endif
+ }
+}
+
+static bool
+updateDirectory(struct directory *directory, const struct stat *st);
+
+static void
+updateInDirectory(struct directory *directory,
+ const char *name, const struct stat *st)
+{
+ assert(strchr(name, '/') == NULL);
+
+ if (S_ISREG(st->st_mode)) {
+ update_regular_file(directory, name, st);
+ } else if (S_ISDIR(st->st_mode)) {
+ struct directory *subdir;
+ bool ret;
+
+ if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
+ return;
+
+ subdir = make_subdir(directory, name);
+ assert(directory == subdir->parent);
+
+ ret = updateDirectory(subdir, st);
+ if (!ret)
+ delete_directory(subdir);
+ } else {
+ g_debug("update: %s is not a directory, archive or music", name);
+ }
+}
+
+/* we don't look at "." / ".." nor files with newlines in their name */
+static bool skip_path(const char *path)
+{
+ return (path[0] == '.' && path[1] == 0) ||
+ (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
+ strchr(path, '\n') != NULL;
+}
+
+static bool
+skip_symlink(const struct directory *directory, const char *utf8_name)
+{
+#ifndef WIN32
+ char buffer[MPD_PATH_MAX];
+ char *path_fs;
+ const char *p;
+ ssize_t ret;
+
+ path_fs = map_directory_child_fs(directory, utf8_name);
+ if (path_fs == NULL)
+ return true;
+
+ ret = readlink(path_fs, buffer, sizeof(buffer));
+ g_free(path_fs);
+ if (ret < 0)
+ /* don't skip if this is not a symlink */
+ return errno != EINVAL;
+
+ if (!follow_inside_symlinks && !follow_outside_symlinks) {
+ /* ignore all symlinks */
+ return true;
+ } else if (follow_inside_symlinks && follow_outside_symlinks) {
+ /* consider all symlinks */
+ return false;
+ }
+
+ if (buffer[0] == '/')
+ return !follow_outside_symlinks;
+
+ p = buffer;
+ while (*p == '.') {
+ if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) {
+ /* "../" moves to parent directory */
+ directory = directory->parent;
+ if (directory == NULL) {
+ /* we have moved outside the music
+ directory - skip this symlink
+ if such symlinks are not allowed */
+ return !follow_outside_symlinks;
+ }
+ p += 3;
+ } else if (G_IS_DIR_SEPARATOR(p[1]))
+ /* eliminate "./" */
+ p += 2;
+ else
+ break;
+ }
+
+ /* we are still in the music directory, so this symlink points
+ to a song which is already in the database - skip according
+ to the follow_inside_symlinks param*/
+ return !follow_inside_symlinks;
+#else
+ /* no symlink checking on WIN32 */
+
+ (void)directory;
+ (void)utf8_name;
+
+ return false;
+#endif
+}
+
+static bool
+updateDirectory(struct directory *directory, const struct stat *st)
+{
+ DIR *dir;
+ struct dirent *ent;
+ char *path_fs, *exclude_path_fs;
+ GSList *exclude_list;
+
+ assert(S_ISDIR(st->st_mode));
+
+ directory_set_stat(directory, st);
+
+ path_fs = map_directory_fs(directory);
+ if (path_fs == NULL)
+ return false;
+
+ dir = opendir(path_fs);
+ if (!dir) {
+ g_warning("Failed to open directory %s: %s",
+ path_fs, g_strerror(errno));
+ g_free(path_fs);
+ return false;
+ }
+
+ exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL);
+ exclude_list = exclude_list_load(exclude_path_fs);
+ g_free(exclude_path_fs);
+
+ g_free(path_fs);
+
+ if (exclude_list != NULL)
+ remove_excluded_from_directory(directory, exclude_list);
+
+ removeDeletedFromDirectory(directory);
+
+ while ((ent = readdir(dir))) {
+ char *utf8;
+ struct stat st2;
+
+ if (skip_path(ent->d_name) ||
+ exclude_list_check(exclude_list, ent->d_name))
+ continue;
+
+ utf8 = fs_charset_to_utf8(ent->d_name);
+ if (utf8 == NULL)
+ continue;
+
+ if (skip_symlink(directory, utf8)) {
+ delete_name_in(directory, utf8);
+ g_free(utf8);
+ continue;
+ }
+
+ if (stat_directory_child(directory, utf8, &st2) == 0)
+ updateInDirectory(directory, utf8, &st2);
+ else
+ delete_name_in(directory, utf8);
+
+ g_free(utf8);
+ }
+
+ exclude_list_free(exclude_list);
+
+ closedir(dir);
+
+ directory->mtime = st->st_mtime;
+
+ return true;
+}
+
+static struct directory *
+directory_make_child_checked(struct directory *parent, const char *path)
+{
+ struct directory *directory;
+ char *base;
+ struct stat st;
+ struct song *conflicting;
+
+ directory = directory_get_child(parent, path);
+ if (directory != NULL)
+ return directory;
+
+ base = g_path_get_basename(path);
+
+ if (stat_directory_child(parent, base, &st) < 0 ||
+ inodeFoundInParent(parent, st.st_ino, st.st_dev)) {
+ g_free(base);
+ return NULL;
+ }
+
+ /* if we're adding directory paths, make sure to delete filenames
+ with potentially the same name */
+ conflicting = songvec_find(&parent->songs, base);
+ if (conflicting)
+ delete_song(parent, conflicting);
+
+ g_free(base);
+
+ directory = directory_new_child(parent, path);
+ directory_set_stat(directory, &st);
+ return directory;
+}
+
+static struct directory *
+addParentPathToDB(const char *utf8path)
+{
+ struct directory *directory = db_get_root();
+ char *duplicated = g_strdup(utf8path);
+ char *slash = duplicated;
+
+ while ((slash = strchr(slash, '/')) != NULL) {
+ *slash = 0;
+
+ directory = directory_make_child_checked(directory,
+ duplicated);
+ if (directory == NULL || slash == NULL)
+ break;
+
+ *slash++ = '/';
+ }
+
+ g_free(duplicated);
+ return directory;
+}
+
+static void
+updatePath(const char *path)
+{
+ struct directory *parent;
+ char *name;
+ struct stat st;
+
+ parent = addParentPathToDB(path);
+ if (parent == NULL)
+ return;
+
+ name = g_path_get_basename(path);
+
+ if (stat_directory_child(parent, name, &st) == 0)
+ updateInDirectory(parent, name, &st);
+ else
+ delete_name_in(parent, name);
+
+ g_free(name);
+}
+
+bool
+update_walk(const char *path, bool discard)
+{
+ walk_discard = discard;
+ modified = false;
+
+ if (path != NULL && !isRootDirectory(path)) {
+ updatePath(path);
+ } else {
+ struct directory *directory = db_get_root();
+ struct stat st;
+
+ if (stat_directory(directory, &st) == 0)
+ updateDirectory(directory, &st);
+ }
+
+ return modified;
+}
diff --git a/src/uri.h b/src/uri.h
index 1a12cc557..46d36308b 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -20,14 +20,18 @@
#ifndef MPD_URI_H
#define MPD_URI_H
+#include <glib.h>
+
#include <stdbool.h>
/**
* Checks whether the specified URI has a schema in the form
* "scheme://".
*/
+G_GNUC_PURE
bool uri_has_scheme(const char *uri);
+G_GNUC_PURE
const char *
uri_get_suffix(const char *uri);
@@ -37,6 +41,7 @@ uri_get_suffix(const char *uri);
* NULL if nothing needs to be removed, or if the URI is not
* recognized.
*/
+G_GNUC_MALLOC
char *
uri_remove_auth(const char *uri);
diff --git a/src/utils.c b/src/utils.c
index fc27b13c9..f8051a41d 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -44,7 +44,7 @@
char *parsePath(char *path)
{
#ifndef WIN32
- if (path[0] != '/' && path[0] != '~') {
+ if (!g_path_is_absolute(path) && path[0] != '~') {
g_warning("\"%s\" is not an absolute path", path);
return NULL;
} else if (path[0] == '~') {
@@ -102,43 +102,15 @@ char *parsePath(char *path)
#endif
}
-int set_nonblocking(int fd)
+bool
+string_array_contains(const char *const* haystack, const char *needle)
{
-#ifdef WIN32
- u_long val = 1;
- int retval;
- int lasterr = 0;
- retval = ioctlsocket(fd, FIONBIO, &val);
- if(retval == SOCKET_ERROR)
- g_error("Error: ioctlsocket could not set FIONBIO;"
- " Error %d on socket %d", lasterr = WSAGetLastError(), fd);
- if(lasterr == 10038)
- g_debug("Code-up error! Attempt to set non-blocking I/O on "
- "something that is not a Winsock2 socket. This can't "
- "be done on Windows!\n");
- return retval;
-#else
- int ret, flags;
-
- assert(fd >= 0);
-
- while ((flags = fcntl(fd, F_GETFL)) < 0 && errno == EINTR) ;
- if (flags < 0)
- return flags;
-
- flags |= O_NONBLOCK;
- while ((ret = fcntl(fd, F_SETFL, flags)) < 0 && errno == EINTR) ;
- return ret;
-#endif
-}
+ assert(haystack != NULL);
+ assert(needle != NULL);
-int stringFoundInStringArray(const char *const*array, const char *suffix)
-{
- while (array && *array) {
- if (g_ascii_strcasecmp(*array, suffix) == 0)
- return 1;
- array++;
- }
+ for (; *haystack != NULL; ++haystack)
+ if (g_ascii_strcasecmp(*haystack, needle) == 0)
+ return true;
- return 0;
+ return false;
}
diff --git a/src/utils.h b/src/utils.h
index d114003be..9d891be4a 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -20,6 +20,8 @@
#ifndef MPD_UTILS_H
#define MPD_UTILS_H
+#include <stdbool.h>
+
#ifndef assert_static
/* Compile time assertion developed by Ralf Holly */
/* http://pera-software.com/articles/compile-time-assertions.pdf */
@@ -31,8 +33,15 @@
char *parsePath(char *path);
-int set_nonblocking(int fd);
-
-int stringFoundInStringArray(const char *const*array, const char *suffix);
+/**
+ * Checks whether a string array contains the specified string.
+ *
+ * @param haystack a NULL terminated list of strings
+ * @param needle the string to search for; the comparison is
+ * case-insensitive for ASCII characters
+ * @return true if found
+ */
+bool
+string_array_contains(const char *const* haystack, const char *needle);
#endif
diff --git a/src/volume.c b/src/volume.c
index e7fa20a62..1be0705af 100644
--- a/src/volume.c
+++ b/src/volume.c
@@ -26,6 +26,8 @@
#include "output_all.h"
#include "mixer_control.h"
#include "mixer_all.h"
+#include "mixer_type.h"
+#include "event_pipe.h"
#include <glib.h>
@@ -39,132 +41,39 @@
#define SW_VOLUME_STATE "sw_volume: "
-static enum {
- VOLUME_MIXER_TYPE_SOFTWARE,
- VOLUME_MIXER_TYPE_HARDWARE,
- VOLUME_MIXER_TYPE_DISABLED,
-} volume_mixer_type = VOLUME_MIXER_TYPE_HARDWARE;
-
-static int volume_software_set = 100;
+static unsigned volume_software_set = 100;
/** the cached hardware mixer value; invalid if negative */
static int last_hardware_volume = -1;
/** the age of #last_hardware_volume */
static GTimer *hardware_volume_timer;
-void volume_finish(void)
-{
- if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE)
- g_timer_destroy(hardware_volume_timer);
-}
-
-/**
- * Finds the first audio_output configuration section with the
- * specified type.
- */
-static struct config_param *
-find_output_config(const char *type)
-{
- struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_AUDIO_OUTPUT,
- param)) != NULL) {
- const char *param_type =
- config_get_block_string(param, "type", NULL);
- if (param_type != NULL && strcmp(param_type, type) == 0)
- return param;
- }
-
- return NULL;
-}
-
/**
- * Copy a (top-level) legacy mixer configuration parameter to the
- * audio_output section.
+ * Handler for #PIPE_EVENT_MIXER.
*/
static void
-mixer_copy_legacy_param(const char *type, const char *name)
+mixer_event_callback(void)
{
- const struct config_param *param;
- struct config_param *output;
- const struct block_param *bp;
-
- /* see if the deprecated configuration exists */
-
- param = config_get_param(name);
- if (param == NULL)
- return;
-
- g_warning("deprecated option '%s' found, moving to '%s' audio output",
- name, type);
-
- /* determine the configuration section */
-
- output = find_output_config(type);
- if (output == NULL) {
- /* if there is no output configuration at all, create
- a new and empty configuration section for the
- legacy mixer */
-
- if (config_get_next_param(CONF_AUDIO_OUTPUT, NULL) != NULL)
- /* there is an audio_output configuration, but
- it does not match the mixer_type setting */
- g_error("no '%s' audio output found", type);
-
- output = config_new_param(NULL, param->line);
- config_add_block_param(output, "type", type, param->line);
- config_add_block_param(output, "name", type, param->line);
- config_add_param(CONF_AUDIO_OUTPUT, output);
- }
-
- bp = config_get_block_param(output, name);
- if (bp != NULL)
- g_error("the '%s' audio output already has a '%s' setting",
- type, name);
-
- /* duplicate the parameter in the configuration section */
+ /* flush the hardware volume cache */
+ last_hardware_volume = -1;
- config_add_block_param(output, name, param->value, param->line);
+ /* notify clients */
+ idle_add(IDLE_MIXER);
}
-static void
-mixer_reconfigure(const char *type)
+void volume_finish(void)
{
- mixer_copy_legacy_param(type, CONF_MIXER_DEVICE);
- mixer_copy_legacy_param(type, CONF_MIXER_CONTROL);
+ g_timer_destroy(hardware_volume_timer);
}
void volume_init(void)
{
- const struct config_param *param = config_get_param(CONF_MIXER_TYPE);
- //hw mixing is by default
- if (param) {
- if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) {
- volume_mixer_type = VOLUME_MIXER_TYPE_SOFTWARE;
- mixer_disable_all();
- } else if (strcmp(param->value, VOLUME_MIXER_DISABLED) == 0) {
- volume_mixer_type = VOLUME_MIXER_TYPE_DISABLED;
- mixer_disable_all();
- } else if (strcmp(param->value, VOLUME_MIXER_HARDWARE) == 0) {
- //nothing to do
- } else {
- //fallback to old config behaviour
- if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) {
- mixer_reconfigure(param->value);
- } else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) {
- mixer_reconfigure(param->value);
- } else {
- g_error("unknown mixer type %s at line %i\n",
- param->value, param->line);
- }
- }
- }
+ hardware_volume_timer = g_timer_new();
- if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE)
- hardware_volume_timer = g_timer_new();
+ event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback);
}
-static int hardware_volume_get(void)
+int volume_level_get(void)
{
assert(hardware_volume_timer != NULL);
@@ -178,101 +87,60 @@ static int hardware_volume_get(void)
return last_hardware_volume;
}
-static int software_volume_get(void)
-{
- return volume_software_set;
-}
-
-int volume_level_get(void)
-{
- switch (volume_mixer_type) {
- case VOLUME_MIXER_TYPE_SOFTWARE:
- return software_volume_get();
- case VOLUME_MIXER_TYPE_HARDWARE:
- return hardware_volume_get();
- case VOLUME_MIXER_TYPE_DISABLED:
- return -1;
- }
-
- /* unreachable */
- assert(false);
- return -1;
-}
-
-static bool software_volume_change(int change, bool rel)
+static bool software_volume_change(unsigned volume)
{
- int new = change;
-
- if (rel)
- new += volume_software_set;
+ assert(volume <= 100);
- if (new > 100)
- new = 100;
- else if (new < 0)
- new = 0;
-
- volume_software_set = new;
-
- /*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */
- if (new >= 100)
- new = PCM_VOLUME_1;
- else if (new <= 0)
- new = 0;
- else
- new = pcm_float_to_volume((exp(new / 25.0) - 1) /
- (54.5981500331F - 1));
-
- setPlayerSoftwareVolume(new);
+ volume_software_set = volume;
+ mixer_all_set_software_volume(volume);
return true;
}
-static bool hardware_volume_change(int change, bool rel)
+static bool hardware_volume_change(unsigned volume)
{
/* reset the cache */
last_hardware_volume = -1;
- return mixer_all_set_volume(change, rel);
+ return mixer_all_set_volume(volume);
}
-bool volume_level_change(int change, bool rel)
+bool volume_level_change(unsigned volume)
{
+ assert(volume <= 100);
+
+ volume_software_set = volume;
+
idle_add(IDLE_MIXER);
- switch (volume_mixer_type) {
- case VOLUME_MIXER_TYPE_HARDWARE:
- return hardware_volume_change(change, rel);
- case VOLUME_MIXER_TYPE_SOFTWARE:
- return software_volume_change(change, rel);
- default:
- return true;
- }
+ return hardware_volume_change(volume);
}
-void read_sw_volume_state(FILE *fp)
+bool
+read_sw_volume_state(const char *line)
{
- char buf[sizeof(SW_VOLUME_STATE) + sizeof("100") - 1];
char *end = NULL;
long int sv;
- if (volume_mixer_type != VOLUME_MIXER_TYPE_SOFTWARE)
- return;
- while (fgets(buf, sizeof(buf), fp)) {
- if (!g_str_has_prefix(buf, SW_VOLUME_STATE))
- continue;
+ if (!g_str_has_prefix(line, SW_VOLUME_STATE))
+ return false;
- g_strchomp(buf);
- sv = strtol(buf + strlen(SW_VOLUME_STATE), &end, 10);
- if (G_LIKELY(!*end))
- software_volume_change(sv, 0);
- else
- g_warning("Can't parse software volume: %s\n", buf);
- return;
- }
+ line += sizeof(SW_VOLUME_STATE) - 1;
+ sv = strtol(line, &end, 10);
+ if (*end == 0 && sv >= 0 && sv <= 100)
+ software_volume_change(sv);
+ else
+ g_warning("Can't parse software volume: %s\n", line);
+ return true;
}
void save_sw_volume_state(FILE *fp)
{
- if (volume_mixer_type == VOLUME_MIXER_TYPE_SOFTWARE)
- fprintf(fp, SW_VOLUME_STATE "%d\n", volume_software_set);
+ fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set);
+}
+
+unsigned
+sw_volume_state_get_hash(void)
+{
+ return volume_software_set;
}
diff --git a/src/volume.h b/src/volume.h
index 99d31da4e..66e7c43f0 100644
--- a/src/volume.h
+++ b/src/volume.h
@@ -23,22 +23,26 @@
#include <stdbool.h>
#include <stdio.h>
-#define VOLUME_MIXER_OSS "oss"
-#define VOLUME_MIXER_ALSA "alsa"
-#define VOLUME_MIXER_SOFTWARE "software"
-#define VOLUME_MIXER_HARDWARE "hardware"
-#define VOLUME_MIXER_DISABLED "disabled"
-
void volume_init(void);
void volume_finish(void);
int volume_level_get(void);
-bool volume_level_change(int change, bool rel);
+bool volume_level_change(unsigned volume);
-void read_sw_volume_state(FILE *fp);
+bool
+read_sw_volume_state(const char *line);
void save_sw_volume_state(FILE *fp);
+/**
+ * Generates a hash number for the current state of the software
+ * volume control. This is used by timer_save_state_file() to
+ * determine whether the state has changed and the state file should
+ * be saved.
+ */
+unsigned
+sw_volume_state_get_hash(void);
+
#endif