aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_format.h64
-rw-r--r--src/audio_parser.c10
-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.c51
-rw-r--r--src/cmdline.h13
-rw-r--r--src/command.c236
-rw-r--r--src/command.h6
-rw-r--r--src/conf.c494
-rw-r--r--src/conf.h70
-rw-r--r--src/daemon.c105
-rw-r--r--src/daemon.h37
-rw-r--r--src/dbUtils.c24
-rw-r--r--src/dbUtils.h4
-rw-r--r--src/decoder/_flac_common.c5
-rw-r--r--src/decoder/audiofile_plugin.c8
-rw-r--r--src/decoder/faad_plugin.c6
-rw-r--r--src/decoder/ffmpeg_plugin.c18
-rw-r--r--src/decoder/flac_plugin.c14
-rw-r--r--src/decoder/mad_plugin.c10
-rw-r--r--src/decoder/mikmod_plugin.c4
-rw-r--r--src/decoder/modplug_plugin.c6
-rw-r--r--src/decoder/mp4ff_plugin.c6
-rw-r--r--src/decoder/mpcdec_plugin.c4
-rw-r--r--src/decoder/mpg123_decoder_plugin.c210
-rw-r--r--src/decoder/sidplay_plugin.cxx278
-rw-r--r--src/decoder/sndfile_decoder_plugin.c244
-rwxr-xr-x[-rw-r--r--]src/decoder/vorbis_plugin.c5
-rw-r--r--src/decoder/wavpack_plugin.c6
-rw-r--r--src/decoder_api.c40
-rw-r--r--src/decoder_control.c52
-rw-r--r--src/decoder_control.h97
-rw-r--r--src/decoder_internal.c27
-rw-r--r--src/decoder_list.c8
-rw-r--r--src/decoder_thread.c47
-rw-r--r--src/directory_save.c5
-rw-r--r--src/encoder/twolame_encoder.c299
-rw-r--r--src/encoder_list.c4
-rw-r--r--src/event_pipe.h2
-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/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.c163
-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/lastfm_input_plugin.c339
-rw-r--r--src/input_stream.c4
-rw-r--r--src/listen.c23
-rw-r--r--src/listen.h7
-rw-r--r--src/ls.c2
-rw-r--r--src/main.c115
-rw-r--r--src/mapper.c44
-rw-r--r--src/mapper.h2
-rw-r--r--src/mixer/software_mixer_plugin.c124
-rw-r--r--src/mixer/software_mixer_plugin.h33
-rw-r--r--src/mixer_all.c79
-rw-r--r--src/mixer_all.h21
-rw-r--r--src/mixer_control.c13
-rw-r--r--src/mixer_control.h3
-rw-r--r--src/mixer_list.h1
-rw-r--r--src/mixer_type.c38
-rw-r--r--src/mixer_type.h47
-rw-r--r--src/output/alsa_plugin.c52
-rw-r--r--src/output/openal_plugin.c273
-rw-r--r--src/output/recorder_output_plugin.c214
-rw-r--r--src/output_control.c18
-rw-r--r--src/output_init.c116
-rw-r--r--src/output_internal.h22
-rw-r--r--src/output_list.c8
-rw-r--r--src/output_state.c47
-rw-r--r--src/output_state.h5
-rw-r--r--src/output_thread.c204
-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/player_control.c11
-rw-r--r--src/player_control.h3
-rw-r--r--src/player_thread.c113
-rw-r--r--src/playlist.c62
-rw-r--r--src/playlist.h103
-rw-r--r--src/playlist_control.c40
-rw-r--r--src/playlist_edit.c162
-rw-r--r--src/playlist_global.c18
-rw-r--r--src/playlist_internal.h2
-rw-r--r--src/playlist_print.c2
-rw-r--r--src/playlist_save.c4
-rw-r--r--src/playlist_state.c63
-rw-r--r--src/playlist_state.h5
-rw-r--r--src/replay_gain.c47
-rw-r--r--src/song_print.c21
-rw-r--r--src/song_save.c41
-rw-r--r--src/song_save.h16
-rw-r--r--src/state_file.c45
-rw-r--r--src/sticker.c47
-rw-r--r--src/sticker.h8
-rw-r--r--src/tag.c2
-rw-r--r--src/tag.h2
-rw-r--r--src/tag_ape.c64
-rw-r--r--src/tag_id3.c298
-rw-r--r--src/tokenizer.c221
-rw-r--r--src/tokenizer.h83
-rw-r--r--src/update.c808
-rw-r--r--src/update.h14
-rw-r--r--src/update_internal.h67
-rw-r--r--src/update_queue.c65
-rw-r--r--src/update_remove.c93
-rw-r--r--src/update_walk.c759
-rw-r--r--src/volume.c212
-rw-r--r--src/volume.h11
142 files changed, 8990 insertions, 3415 deletions
diff --git a/src/audio_format.h b/src/audio_format.h
index 64087d070..a88fc3a4c 100644
--- a/src/audio_format.h
+++ b/src/audio_format.h
@@ -23,19 +23,68 @@
#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;
@@ -88,7 +137,8 @@ static inline bool audio_format_equals(const struct audio_format *a,
{
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;
}
/**
@@ -104,20 +154,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..d29f5f449 100644
--- a/src/audio_parser.c
+++ b/src/audio_parser.c
@@ -41,6 +41,8 @@ audio_format_parse(struct audio_format *dest, const char *src, GError **error)
{
char *endptr;
unsigned long value;
+ uint32_t rate;
+ uint8_t bits, channels;
audio_format_clear(dest);
@@ -61,7 +63,7 @@ audio_format_parse(struct audio_format *dest, const char *src, GError **error)
return false;
}
- dest->sample_rate = value;
+ rate = value;
/* parse sample format */
@@ -81,7 +83,7 @@ audio_format_parse(struct audio_format *dest, const char *src, GError **error)
return false;
}
- dest->bits = value;
+ bits = value;
/* parse channel count */
@@ -93,7 +95,9 @@ audio_format_parse(struct audio_format *dest, const char *src, GError **error)
return false;
}
- dest->channels = value;
+ channels = value;
+
+ audio_format_init(dest, rate, bits, channels);
return true;
}
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..9e3919152 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -35,10 +35,15 @@
#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");
+}
+
G_GNUC_NORETURN
static void version(void)
{
@@ -77,26 +82,26 @@ 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,9 +112,8 @@ 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);
@@ -133,18 +137,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 +152,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..165e21c96 100644
--- a/src/command.c
+++ b/src/command.c
@@ -32,7 +32,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"
@@ -58,7 +58,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"
@@ -400,7 +399,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 +412,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 +420,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;
}
@@ -470,7 +469,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,30 +479,32 @@ 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),
+ 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)(getPlayerCrossFade() + 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();
client_printf(client,
COMMAND_STATUS_TIME ": %i:%i\n"
+ "elapsed: %1.3f\n"
COMMAND_STATUS_BITRATE ": %li\n"
COMMAND_STATUS_AUDIO ": %u:%u:%u\n",
getPlayerElapsedTime(), getPlayerTotalTime(),
+ pc.elapsed_time,
getPlayerBitRate(),
af->sample_rate, af->bits, af->channels);
}
@@ -520,12 +521,12 @@ handle_status(struct client *client,
getPlayerErrorStr());
}
- 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 +570,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 +606,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 +616,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 +632,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 +651,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 +672,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 +680,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;
}
@@ -869,6 +870,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 +1018,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 +1066,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 +1076,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 +1098,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 +1106,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 +1135,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 +1153,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 +1171,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 +1189,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;
}
@@ -1241,7 +1273,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 +1287,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 +1301,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 +1315,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 +1330,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 +1345,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);
}
@@ -1684,6 +1716,7 @@ static const struct command commands[] = {
{ "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 +1752,7 @@ 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 },
+ { "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 +1772,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 +1925,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 */
- 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++;
+ current_command = argv[0];
+
+ if (argc >= (int)G_N_ELEMENTS(argv)) {
+ command_error(client, ACK_ERROR_ARG, "Too many arguments");
+ current_command = NULL;
+ return COMMAND_RETURN_ERROR;
}
+ if (*line != 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "%s", error->message);
+ current_command = NULL;
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ /* look up and invoke the command handler */
+
+ cmd = command_checked_lookup(client, client_get_permission(client),
+ argc, argv);
+ if (cmd)
+ ret = cmd->handler(client, argc, argv);
+
+ current_command = NULL;
command_list_num = 0;
+
return ret;
}
diff --git a/src/command.h b/src/command.h
index 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..d78f44760 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,90 @@
#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 = "filter", true, true },
+};
+
+static bool
+string_array_contains(const char *const* array, const char *value)
+{
+ for (const char *const* x = array; *x; x++)
+ if (g_ascii_strcasecmp(*x, value) == 0)
+ return true;
+
+ return false;
+}
-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 +136,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 +157,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 +177,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 +247,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 +345,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 (*line != '{') {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: '{' expected", count);
+ return false;
+ }
- 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);
+ 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);
- } else
- param = config_new_param(array[1], count);
+
+ 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 +476,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 +532,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 +601,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..ef10b9f82 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"
@@ -69,9 +68,6 @@
#define CONF_INPUT "input"
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
-#define CONF_BOOL_UNSET -1
-#define CONF_BOOL_INVALID -2
-
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
@@ -79,6 +75,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 +89,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 +148,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 +180,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 +193,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/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/dbUtils.c b/src/dbUtils.c
index f89148c1b..88122649e 100644
--- a/src/dbUtils.c
+++ b/src/dbUtils.c
@@ -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;
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..09f7269bd 100644
--- a/src/decoder/_flac_common.c
+++ b/src/decoder/_flac_common.c
@@ -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:
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 27b0c2507..6bead85fd 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;
}
@@ -352,13 +354,15 @@ ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
{
struct tag *tag = (struct tag *) ctx->tag;
- const AVFormatContext *f = ctx->format_context;
+ AVFormatContext *f = ctx->format_context;
tag->time = 0;
if (f->duration != (int64_t)AV_NOPTS_VALUE)
tag->time = f->duration / AV_TIME_BASE;
#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");
if (!ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author"))
ffmpeg_copy_metadata(tag, f->metadata,
diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c
index 1d7a9f868..89a812f52 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 = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
+ char* cs_filename;
+ FILE* cs_file;
#endif /* libcue */
FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO);
FLAC__StreamMetadata* cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET);
@@ -328,6 +330,18 @@ 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)
diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_plugin.c
index 1ef7183fa..c5287564f 100644
--- a/src/decoder/mad_plugin.c
+++ b/src/decoder/mad_plugin.c
@@ -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..e7b7bfb03 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) *
diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_plugin.c
index f636f2fa6..6c375e6a0 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,7 +184,7 @@ 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)
diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c
index cf9382904..d2c63f983 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",
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..7ffeb1155
--- /dev/null
+++ b/src/decoder/mpg123_decoder_plugin.c
@@ -0,0 +1,210 @@
+/*
+ * 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 <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..d97abd656 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_ITEM_TITLE, tag_title);
+ g_free(tag_title);
+ } else
+ tag_add_item(tag, TAG_ITEM_TITLE, title);
+
+ /* artist */
if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]);
+ /* track */
+ char *track=g_strdup_printf("%d", song_num);
+ tag_add_item(tag, TAG_ITEM_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..4cc64459f
--- /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_ITEM_TITLE, p);
+
+ p = sf_get_string(sf, SF_STR_ARTIST);
+ if (p != NULL)
+ tag_add_item(tag, TAG_ITEM_ARTIST, p);
+
+ p = sf_get_string(sf, SF_STR_DATE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_ITEM_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..39a075176 100644..100755
--- a/src/decoder/vorbis_plugin.c
+++ b/src/decoder/vorbis_plugin.c
@@ -324,8 +324,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 +377,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..f3d701144 100644
--- a/src/decoder/wavpack_plugin.c
+++ b/src/decoder/wavpack_plugin.c
@@ -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..4cff9916c 100644
--- a/src/decoder_api.c
+++ b/src/decoder_api.c
@@ -57,7 +57,10 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
dc.seekable = seekable;
dc.total_time = total_time;
+ decoder_lock();
dc.state = DECODE_STATE_DECODE;
+ decoder_unlock();
+
notify_signal(&pc.notify);
g_debug("audio_format=%u:%u:%u, seekable=%s",
@@ -88,6 +91,8 @@ enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder)
void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
{
+ decoder_lock();
+
assert(dc.command != DECODE_COMMAND_NONE);
assert(dc.command != DECODE_COMMAND_SEEK ||
dc.seek_error || decoder->seeking);
@@ -105,6 +110,8 @@ void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
}
dc.command = DECODE_COMMAND_NONE;
+ decoder_unlock();
+
notify_signal(&pc.notify);
}
@@ -225,21 +232,24 @@ decoder_data(struct decoder *decoder,
struct replay_gain_info *replay_gain_info)
{
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);
- if (dc.command == DECODE_COMMAND_STOP ||
- dc.command == DECODE_COMMAND_SEEK ||
+ decoder_lock();
+ cmd = dc.command;
+ decoder_unlock();
+
+ 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;
@@ -259,14 +269,15 @@ decoder_data(struct decoder *decoder,
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.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) {
@@ -301,8 +312,7 @@ 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);
else if (normalizationEnabled)
diff --git a/src/decoder_control.c b/src/decoder_control.c
index 44bb63e15..3b993431c 100644
--- a/src/decoder_control.c
+++ b/src/decoder_control.c
@@ -18,6 +18,7 @@
*/
#include "decoder_control.h"
+#include "notify.h"
#include <assert.h>
@@ -25,36 +26,63 @@ struct decoder_control dc;
void dc_init(void)
{
- notify_init(&dc.notify);
+ dc.mutex = g_mutex_new();
+ dc.cond = g_cond_new();
+
dc.state = DECODE_STATE_STOP;
dc.command = DECODE_COMMAND_NONE;
}
void dc_deinit(void)
{
- notify_deinit(&dc.notify);
+ g_cond_free(dc.cond);
+ g_mutex_free(dc.mutex);
}
-void
-dc_command_wait(struct notify *notify)
+static void
+dc_command_wait_locked(struct notify *notify)
{
while (dc.command != DECODE_COMMAND_NONE) {
- notify_signal(&dc.notify);
+ decoder_signal();
+ decoder_unlock();
+
notify_wait(notify);
+
+ decoder_lock();
}
}
+void
+dc_command_wait(struct notify *notify)
+{
+ decoder_lock();
+ dc_command_wait_locked(notify);
+ decoder_unlock();
+}
+
static void
-dc_command(struct notify *notify, enum decoder_command cmd)
+dc_command_locked(struct notify *notify, enum decoder_command cmd)
{
dc.command = cmd;
- dc_command_wait(notify);
+ dc_command_wait_locked(notify);
+}
+
+static void
+dc_command(struct notify *notify, enum decoder_command cmd)
+{
+ decoder_lock();
+ dc_command_locked(notify, cmd);
+ decoder_unlock();
}
static void dc_command_async(enum decoder_command cmd)
{
+ decoder_lock();
+
dc.command = cmd;
- notify_signal(&dc.notify);
+ decoder_signal();
+
+ decoder_unlock();
}
void
@@ -80,15 +108,19 @@ dc_start_async(struct song *song)
void
dc_stop(struct notify *notify)
{
+ decoder_lock();
+
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(notify, DECODE_COMMAND_STOP);
if (dc.state != DECODE_STATE_STOP && dc.state != DECODE_STATE_ERROR)
- dc_command(notify, DECODE_COMMAND_STOP);
+ dc_command_locked(notify, DECODE_COMMAND_STOP);
+
+ decoder_unlock();
}
bool
diff --git a/src/decoder_control.h b/src/decoder_control.h
index 703ea256c..7e861f970 100644
--- a/src/decoder_control.h
+++ b/src/decoder_control.h
@@ -22,13 +22,16 @@
#include "decoder_command.h"
#include "audio_format.h"
-#include "notify.h"
+
+#include <glib.h>
#include <assert.h>
#define DECODE_TYPE_FILE 0
#define DECODE_TYPE_URL 1
+struct notify;
+
enum decoder_state {
DECODE_STATE_STOP = 0,
DECODE_STATE_START,
@@ -48,14 +51,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;
@@ -80,6 +94,46 @@ void dc_init(void);
void dc_deinit(void);
+/**
+ * Locks the #decoder_control object.
+ */
+static inline void
+decoder_lock(void)
+{
+ g_mutex_lock(dc.mutex);
+}
+
+/**
+ * Unlocks the #decoder_control object.
+ */
+static inline void
+decoder_unlock(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ g_cond_signal(dc.cond);
+}
+
static inline bool decoder_is_idle(void)
{
return (dc.state == DECODE_STATE_STOP ||
@@ -100,6 +154,39 @@ static inline bool decoder_has_failed(void)
return dc.state == DECODE_STATE_ERROR;
}
+static inline bool decoder_lock_is_idle(void)
+{
+ bool ret;
+
+ decoder_lock();
+ ret = decoder_is_idle();
+ decoder_unlock();
+
+ return ret;
+}
+
+static inline bool decoder_lock_is_starting(void)
+{
+ bool ret;
+
+ decoder_lock();
+ ret = decoder_is_starting();
+ decoder_unlock();
+
+ return ret;
+}
+
+static inline bool decoder_lock_has_failed(void)
+{
+ bool ret;
+
+ decoder_lock();
+ ret = decoder_has_failed();
+ decoder_unlock();
+
+ return ret;
+}
+
static inline struct song *
decoder_current_song(void)
{
diff --git a/src/decoder_internal.c b/src/decoder_internal.c
index 4a56fa5f3..1b064d0aa 100644
--- a/src/decoder_internal.c
+++ b/src/decoder_internal.c
@@ -28,6 +28,24 @@
#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 input_stream *is)
+{
+ int ret;
+
+ decoder_unlock();
+ ret = input_stream_buffer(is) > 0;
+ decoder_lock();
+
+ return ret;
+}
+
+/**
* All chunks are full of decoded data; wait for the player to free
* one.
*/
@@ -38,9 +56,12 @@ need_chunks(struct input_stream *is, bool do_wait)
dc.command == DECODE_COMMAND_SEEK)
return dc.command;
- if ((is == NULL || input_stream_buffer(is) <= 0) && do_wait) {
- notify_wait(&dc.notify);
+ if ((is == NULL || decoder_input_buffer(is) <= 0) && do_wait) {
+ decoder_wait();
+
+ decoder_unlock();
notify_signal(&pc.notify);
+ decoder_lock();
return dc.command;
}
@@ -63,7 +84,9 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
if (decoder->chunk != NULL)
return decoder->chunk;
+ decoder_lock();
cmd = need_chunks(is, true);
+ decoder_unlock();
} while (cmd == DECODE_COMMAND_NONE);
return NULL;
diff --git a/src/decoder_list.c b/src/decoder_list.c
index a42585e34..d30611e1b 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;
@@ -47,6 +49,9 @@ static 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
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
index 2b1a6299a..be37896c1 100644
--- a/src/decoder_thread.c
+++ b/src/decoder_thread.c
@@ -49,11 +49,15 @@ decoder_stream_decode(const struct decoder_plugin *plugin,
assert(input_stream->ready);
assert(dc.state == DECODE_STATE_START);
+ decoder_unlock();
+
/* 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);
+ decoder_lock();
+
assert(dc.state == DECODE_STATE_START ||
dc.state == DECODE_STATE_DECODE);
@@ -73,8 +77,12 @@ decoder_file_decode(const struct decoder_plugin *plugin,
assert(path[0] == '/');
assert(dc.state == DECODE_STATE_START);
+ decoder_unlock();
+
decoder_plugin_file_decode(plugin, decoder, path);
+ decoder_lock();
+
assert(dc.state == DECODE_STATE_START ||
dc.state == DECODE_STATE_DECODE);
@@ -103,28 +111,40 @@ static void decoder_run_song(const struct song *song, const char *uri)
dc.state = DECODE_STATE_START;
dc.command = DECODE_COMMAND_NONE;
+
+ decoder_unlock();
notify_signal(&pc.notify);
+ decoder_lock();
/* wait for the input stream to become ready; its metadata
will be available then */
while (!input_stream.ready) {
if (dc.command == DECODE_COMMAND_STOP) {
+ decoder_unlock();
input_stream_close(&input_stream);
+ decoder_lock();
dc.state = DECODE_STATE_STOP;
return;
}
+ decoder_unlock();
ret = input_stream_buffer(&input_stream);
if (ret < 0) {
input_stream_close(&input_stream);
+ decoder_lock();
dc.state = DECODE_STATE_ERROR;
return;
}
+
+ decoder_lock();
}
if (dc.command == DECODE_COMMAND_STOP) {
+ decoder_unlock();
input_stream_close(&input_stream);
+ decoder_lock();
+
dc.state = DECODE_STATE_STOP;
return;
}
@@ -179,7 +199,10 @@ static void decoder_run_song(const struct song *song, const char *uri)
const char *s = uri_get_suffix(uri);
while ((plugin = decoder_plugin_from_suffix(s, next++))) {
if (plugin->file_decode != NULL) {
+ decoder_unlock();
input_stream_close(&input_stream);
+ decoder_lock();
+
close_instream = false;
ret = decoder_file_decode(plugin,
&decoder, uri);
@@ -191,7 +214,13 @@ static void decoder_run_song(const struct song *song, const char *uri)
been closed before
decoder_file_decode() -
reopen it */
- if (input_stream_open(&input_stream, uri))
+ bool success;
+
+ decoder_unlock();
+ success = input_stream_open(&input_stream, uri);
+ decoder_lock();
+
+ if (success)
close_instream = true;
else
continue;
@@ -205,6 +234,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
}
}
+ decoder_unlock();
+
pcm_convert_deinit(&decoder.conv_state);
/* flush the last chunk */
@@ -223,6 +254,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
if (decoder.decoder_tag != NULL)
tag_free(decoder.decoder_tag);
+ decoder_lock();
+
dc.state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR;
}
@@ -249,6 +282,8 @@ static void decoder_run(void)
static gpointer decoder_task(G_GNUC_UNUSED gpointer arg)
{
+ decoder_lock();
+
do {
assert(dc.state == DECODE_STATE_STOP ||
dc.state == DECODE_STATE_ERROR);
@@ -259,20 +294,28 @@ static gpointer decoder_task(G_GNUC_UNUSED gpointer arg)
decoder_run();
dc.command = DECODE_COMMAND_NONE;
+
+ decoder_unlock();
notify_signal(&pc.notify);
+ decoder_lock();
break;
case DECODE_COMMAND_STOP:
dc.command = DECODE_COMMAND_NONE;
+
+ decoder_unlock();
notify_signal(&pc.notify);
+ decoder_lock();
break;
case DECODE_COMMAND_NONE:
- notify_wait(&dc.notify);
+ decoder_wait();
break;
}
} while (dc.command != DECODE_COMMAND_NONE || !dc.quit);
+ decoder_unlock();
+
return NULL;
}
diff --git a/src/directory_save.c b/src/directory_save.c
index 132508447..cb76b225f 100644
--- a/src/directory_save.c
+++ b/src/directory_save.c
@@ -138,7 +138,10 @@ directory_load(FILE *fp, struct directory *directory, GError **error)
if (!success)
return false;
} else if (g_str_has_prefix(buffer, SONG_BEGIN)) {
- readSongInfoIntoList(fp, &directory->songs, directory);
+ success = songvec_load(fp, &directory->songs,
+ directory, error);
+ if (!success)
+ return false;
} else {
g_set_error(error, directory_quark(), 0,
"Malformed line: %s", buffer);
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..2016d4cba 100644
--- a/src/encoder_list.c
+++ b/src/encoder_list.c
@@ -25,6 +25,7 @@
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[] = {
#ifdef ENABLE_VORBIS_ENCODER
@@ -33,6 +34,9 @@ static const struct encoder_plugin *encoder_plugins[] = {
#ifdef ENABLE_LAME_ENCODER
&lame_encoder_plugin,
#endif
+#ifdef ENABLE_TWOLAME_ENCODER
+ &twolame_encoder_plugin,
+#endif
NULL
};
diff --git a/src/event_pipe.h b/src/event_pipe.h
index ecb7ec9e8..33f92d440 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 */
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/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..da5a215f8
--- /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_MS = 5000,
+};
+
+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(INOTIFY_UPDATE_DELAY_MS,
+ 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..d5feec3e0
--- /dev/null
+++ b/src/inotify_source.c
@@ -0,0 +1,163 @@
+/*
+ * 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 <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();
+ 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/lastfm_input_plugin.c b/src/input/lastfm_input_plugin.c
index 0039f7069..4de62dc3b 100644
--- a/src/input/lastfm_input_plugin.c
+++ b/src/input/lastfm_input_plugin.c
@@ -18,26 +18,72 @@
*/
#include "input/lastfm_input_plugin.h"
-#include "input/curl_input_plugin.h"
#include "input_plugin.h"
+#include "tag.h"
#include "conf.h"
+#include <stdlib.h>
#include <string.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_lastfm"
-static const char *lastfm_user, *lastfm_password;
+static struct lastfm_data {
+ char *user;
+ char *md5;
+} lastfm_data;
+
+struct lastfm_input {
+ /* our very own plugin wrapper */
+ struct input_plugin wrap_plugin;
+
+ /* pointer to input stream's plugin */
+ const struct input_plugin *plugin;
+
+ /* pointer to input stream's data */
+ void *data;
+
+ /* current track's tag */
+ struct tag *tag;
+};
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);
+ const char *passwd = config_get_block_string(param, "password", NULL);
+ const char *user = config_get_block_string(param, "user", NULL);
+ if (passwd == NULL || user == NULL)
+ return false;
+
+#if GLIB_CHECK_VERSION(2,16,0)
+ lastfm_data.user = g_uri_escape_string(user, NULL, false);
+#else
+ lastfm_data.user = g_strdup(user);
+#endif
+
+#if GLIB_CHECK_VERSION(2,16,0)
+ if (strlen(passwd) != 32)
+ lastfm_data.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
+ passwd, strlen(passwd));
+ else
+#endif
+ lastfm_data.md5 = g_strdup(passwd);
+
+ return true;
+}
- return lastfm_user != NULL && lastfm_password != NULL;
+static void
+lastfm_input_finish(void)
+{
+ g_free(lastfm_data.user);
+ g_free(lastfm_data.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)
{
@@ -78,6 +124,12 @@ lastfm_get(const char *url)
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)
{
@@ -98,10 +150,191 @@ lastfm_find(const char *response, const char *name)
}
}
+/**
+ * Replace XML's five predefined entities with the equivalant characters.
+ * glib doesn't seem to have code to do this, even in the xml parser.
+ * We don't manage numerical character references such as &#nnnn; or &#xhhhh;.
+ * @param value XML text to decode.
+ * @return decoded string, which must be freed with g_free.
+ * @todo make sure this is ok for utf-8.
+ */
+static char *
+lastfm_xmldecode(const char *value)
+{
+ struct entity {
+ const char *text;
+ char repl;
+ } entities[] = {
+ {"&amp;", '&'},
+ {"&quot;", '"'},
+ {"&apos;", '\''},
+ {"&gt;", '>'},
+ {"&lt;", '<'}
+ };
+ char *txt = g_strdup(value);
+ unsigned int i;
+
+ for (i = 0; i < sizeof(entities)/sizeof(entities[0]); ++i) {
+ char *p;
+ int slen = strlen(entities[i].text);
+ while ((p = strstr(txt, entities[i].text))) {
+ *p = entities[i].repl;
+ g_strlcpy(p + 1, p + slen, strlen(p) - slen);
+ }
+ }
+ return txt;
+}
+
+/**
+ * Extract the text between xml start and end tags specified by param tag.
+ * Caveat: This function does not handle nested tags properly.
+ * @param response XML to extract text from.
+ * @param tag name of tag of which text should be extracted from.
+ * @return text between tags specified by param tag, NULL on error; Must be freed with g_free.
+ */
+static char *
+lastfm_xmltag(const char *response, const char *tag)
+{
+ char *tn = g_strconcat("<", tag, ">", NULL);
+ char *p, *q;
+
+ if (!(p = strstr(response, tn))) {
+ g_free(tn);
+ return NULL;
+ }
+
+ p += strlen(tn);
+ g_free(tn);
+
+ tn = g_strconcat("</", tag, ">", NULL);
+
+ if (!(q = strstr(p, tn))) {
+ g_free(tn);
+ return NULL;
+ }
+
+ g_free(tn);
+
+ return g_strndup(p, q - p);
+}
+
+/**
+ * Parses xspf track and generates mpd tag.
+ * @return tag which must be freed with tag_free.
+ */
+static struct tag *
+lastfm_read_tag(const char *response)
+{
+ struct tagalias {
+ enum tag_type type;
+ const char *xmltag;
+ } aliases[] = {
+ {TAG_ITEM_ARTIST, "creator"},
+ {TAG_ITEM_TITLE, "title"},
+ {TAG_ITEM_ALBUM, "album"}
+ };
+ struct tag *tag = tag_new();
+ unsigned int i;
+ char *track_time = lastfm_xmltag(response, "duration");
+
+ if (track_time != NULL) {
+ int mtime = strtol(track_time, 0, 0);
+ g_free(track_time);
+
+ /* make sure to round up */
+ tag->time = ((mtime + 999) / 1000);
+ }
+ else
+ tag->time = 0;
+
+ for (i = 0; i < sizeof(aliases)/sizeof(aliases[0]); ++i) {
+ char *p, *value = lastfm_xmltag(response, aliases[i].xmltag);
+ if (value == NULL)
+ continue;
+
+ p = lastfm_xmldecode(value);
+ g_free(value);
+ value = p;
+
+ tag_add_item(tag, aliases[i].type, value);
+ g_free(value);
+ }
+ return tag;
+}
+
+static size_t
+lastfm_input_read_wrap(struct input_stream *is, void *ptr, size_t size)
+{
+ size_t ret;
+ struct lastfm_input *d = is->data;
+ is->data = d->data;
+ ret = (* d->plugin->read)(is, ptr, size);
+ is->data = d;
+ return ret;
+}
+
+static bool
+lastfm_input_eof_wrap(struct input_stream *is)
+{
+ bool ret;
+ struct lastfm_input *d = is->data;
+ is->data = d->data;
+ ret = (* d->plugin->eof)(is);
+ is->data = d;
+ return ret;
+}
+
+static bool
+lastfm_input_seek_wrap(struct input_stream *is, off_t offset, int whence)
+{
+ bool ret;
+ struct lastfm_input *d = is->data;
+ is->data = d->data;
+ ret = (* d->plugin->seek)(is, offset, whence);
+ is->data = d;
+ return ret;
+}
+
+static int
+lastfm_input_buffer_wrap(struct input_stream *is)
+{
+ int ret;
+ struct lastfm_input *d = is->data;
+ is->data = d->data;
+ ret = (* d->plugin->buffer)(is);
+ is->data = d;
+ return ret;
+}
+
+static struct tag *
+lastfm_input_tag(struct input_stream *is)
+{
+ struct lastfm_input *d = is->data;
+ struct tag *tag = d->tag;
+ d->tag = NULL;
+ return tag;
+}
+
+static void
+lastfm_input_close(struct input_stream *is)
+{
+ struct lastfm_input *d = is->data;
+
+ if (is->plugin->close) {
+ is->data = d->data;
+ is->plugin = d->plugin;
+ (* is->plugin->close)(is);
+ }
+
+ if (d->tag)
+ tag_free(d->tag);
+ g_free(d);
+}
+
static bool
lastfm_input_open(struct input_stream *is, const char *url)
{
- char *md5, *p, *q, *response, *session, *stream_url;
+ char *p, *q, *response, *track, *session, *stream_url;
bool success;
if (strncmp(url, "lastfm://", 9) != 0)
@@ -109,27 +342,11 @@ lastfm_input_open(struct input_stream *is, const char *url)
/* handshake */
-#if GLIB_CHECK_VERSION(2,16,0)
- q = g_uri_escape_string(lastfm_user, NULL, false);
-#else
- q = g_strdup(lastfm_username);
-#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);
+ "username=", lastfm_data.user, "&"
+ "passwordmd5=", lastfm_data.md5, "&"
+ "debug=0&partner=", NULL);
response = lastfm_get(p);
g_free(p);
@@ -148,9 +365,9 @@ lastfm_input_open(struct input_stream *is, const char *url)
}
#if GLIB_CHECK_VERSION(2,16,0)
- q = g_uri_escape_string(session, NULL, false);
- g_free(session);
- session = q;
+ q = g_uri_escape_string(session, NULL, false);
+ g_free(session);
+ session = q;
#endif
/* "adjust" last.fm radio */
@@ -195,35 +412,71 @@ lastfm_input_open(struct input_stream *is, const char *url)
return false;
}
- p = strstr(response, "<location>");
- if (p == NULL) {
- g_free(response);
- g_free(stream_url);
- return false;
- }
+ /* From here on, we only care about the first track, extract that
+ *
+ * Note: if you want to get information about the next track (needed
+ * for continuous playback) extract the other track info here too.
+ */
- p += 10;
- q = strchr(p, '<');
+ g_free(stream_url);
+ track = lastfm_xmltag(response, "track");
+ g_free(response);
- if (q == NULL) {
- g_free(response);
- g_free(stream_url);
+ /* If there are no tracks in the tracklist, it's possible that the
+ * station doesn't have enough content.
+ */
+
+ if (track == NULL)
return false;
- }
- g_free(stream_url);
- stream_url = g_strndup(p, q - p);
- g_free(response);
+ stream_url = lastfm_xmltag(track, "location");
+ if (stream_url == NULL) {
+ g_free(track);
+ return false;
+ }
/* now really open the last.fm radio stream */
success = input_stream_open(is, stream_url);
+ if (success) {
+ /* instantiate our transparent wrapper plugin
+ * this is needed so that the backend knows what functions are
+ * properly available.
+ */
+
+ struct lastfm_input *d = g_new0(struct lastfm_input, 1);
+ d->wrap_plugin.name = "lastfm";
+ d->wrap_plugin.open = lastfm_input_open;
+ d->wrap_plugin.close = lastfm_input_close;
+ d->wrap_plugin.read = lastfm_input_read_wrap;
+ d->wrap_plugin.eof = lastfm_input_eof_wrap;
+ d->wrap_plugin.tag = lastfm_input_tag;
+ if (is->seekable)
+ d->wrap_plugin.seek = lastfm_input_seek_wrap;
+ if (is->plugin->buffer)
+ d->wrap_plugin.buffer = lastfm_input_buffer_wrap;
+
+ d->tag = lastfm_read_tag(track);
+ d->plugin = is->plugin;
+ d->data = is->data;
+
+ /* give the backend our wrapper plugin */
+
+ is->plugin = &d->wrap_plugin;
+ is->data = d;
+ }
+
g_free(stream_url);
+ g_free(track);
+
return success;
}
const struct input_plugin lastfm_input_plugin = {
.name = "lastfm",
.init = lastfm_input_init,
+ .finish = lastfm_input_finish,
.open = lastfm_input_open,
+ .close = lastfm_input_close,
+ .tag = lastfm_input_tag,
};
diff --git a/src/input_stream.c b/src/input_stream.c
index 69dc644a2..6c07da98f 100644
--- a/src/input_stream.c
+++ b/src/input_stream.c
@@ -27,7 +27,7 @@
#include "input/archive_input_plugin.h"
#endif
-#ifdef HAVE_CURL
+#ifdef ENABLE_CURL
#include "input/curl_input_plugin.h"
#endif
@@ -46,7 +46,7 @@ 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
diff --git a/src/listen.c b/src/listen.c
index 98108d9da..4728e7c7a 100644
--- a/src/listen.c
+++ b/src/listen.c
@@ -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)
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/ls.c b/src/ls.c
index fd8f22fd4..9ed083e2e 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -32,7 +32,7 @@
* connected by IPC socket.
*/
static const char *remoteUrlPrefixes[] = {
-#ifdef HAVE_CURL
+#ifdef ENABLE_CURL
"http://",
#endif
#ifdef ENABLE_LASTFM
diff --git a/src/main.c b/src/main.c
index 5035a4836..bb0d09eaf 100644
--- a/src/main.c
+++ b/src/main.c
@@ -55,6 +55,7 @@
#include "dirvec.h"
#include "songvec.h"
#include "tag_pool.h"
+#include "inotify_update.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
@@ -90,13 +91,40 @@ GMainLoop *main_loop;
struct notify main_notify;
+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 GLIB_CHECK_VERSION(2,14,0)
+ if (music_dir == NULL)
+ music_dir = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
+#endif
+
+ 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
* succeeded, and the caller should create the database after the
* 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 +143,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,6 +161,29 @@ 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
@@ -228,9 +271,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();
@@ -251,19 +296,25 @@ int main(int argc, char *argv[])
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();
@@ -275,9 +326,9 @@ int main(int argc, char *argv[])
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 +336,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();
@@ -303,7 +352,7 @@ int main(int argc, char *argv[])
daemonize(options.daemon);
- setup_log_output(options.stdOutput);
+ setup_log_output(options.log_stderr);
initSigHandlers();
@@ -312,15 +361,19 @@ 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));
+ if (mapper_has_music_directory())
+ mpd_inotify_init();
+
+ config_global_check();
/* run the main loop */
@@ -330,12 +383,14 @@ int main(int argc, char *argv[])
g_main_loop_unref(main_loop);
+ mpd_inotify_finish();
+
state_file_finish();
playerKill();
finishZeroconf();
client_manager_deinit();
listen_global_finish();
- finishPlaylist();
+ playlist_global_finish();
start = clock();
db_finish();
diff --git a/src/mapper.c b/src/mapper.c
index aac7c0c48..9a122d068 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)
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/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c
new file mode 100644
index 000000000..661334e1b
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.c
@@ -0,0 +1,124 @@
+/*
+ * 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 const struct config_param *param)
+{
+ 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 bool
+software_mixer_open(struct mixer *data)
+{
+ struct software_mixer *sm = (struct software_mixer *)data;
+
+ (void)sm;
+ return true;
+}
+
+static void
+software_mixer_close(struct mixer *data)
+{
+ struct software_mixer *sm = (struct software_mixer *)data;
+
+ (void)sm;
+}
+
+static int
+software_mixer_get_volume(struct mixer *mixer)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ return sm->volume;
+}
+
+static bool
+software_mixer_set_volume(struct mixer *mixer, unsigned volume)
+{
+ 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,
+ .open = software_mixer_open,
+ .close = software_mixer_close,
+ .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..cd05eec85 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>
@@ -70,12 +73,13 @@ 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;
assert(i < audio_output_count());
+ assert(volume <= 100);
output = audio_output_get(i);
if (!output->enabled)
@@ -85,31 +89,74 @@ 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;
- }
-
- if (volume > 100)
- volume = 100;
- else if (volume < 0)
- volume = 0;
-
return mixer_set_volume(mixer, volume);
}
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);
+}
+
+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);
+ }
+}
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..927a1276c 100644
--- a/src/mixer_control.c
+++ b/src/mixer_control.c
@@ -28,24 +28,11 @@
#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)
{
struct mixer *mixer;
- //mixers are disabled (by using software volume)
- if (!mixers_enabled) {
- return NULL;
- }
assert(plugin != NULL);
mixer = plugin->init(param);
diff --git a/src/mixer_control.h b/src/mixer_control.h
index 0f73e8f75..b8997a795 100644
--- a/src/mixer_control.h
+++ b/src/mixer_control.h
@@ -31,9 +31,6 @@ 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);
diff --git a/src/mixer_list.h b/src/mixer_list.h
index 7db4a00d8..4e81c57b2 100644
--- a/src/mixer_list.h
+++ b/src/mixer_list.h
@@ -25,6 +25,7 @@
#ifndef MPD_MIXER_LIST_H
#define MPD_MIXER_LIST_H
+extern const struct mixer_plugin software_mixer_plugin;
extern const struct mixer_plugin alsa_mixer;
extern const struct mixer_plugin oss_mixer;
extern const struct mixer_plugin pulse_mixer;
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..f271668b1 100644
--- a/src/output/alsa_plugin.c
+++ b/src/output/alsa_plugin.c
@@ -183,6 +183,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 +221,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 +248,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 +292,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,
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/recorder_output_plugin.c b/src/output/recorder_output_plugin.c
new file mode 100644
index 000000000..413e5d0d1
--- /dev/null
+++ b/src/output/recorder_output_plugin.c
@@ -0,0 +1,214 @@
+/*
+ * 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 <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 = creat(recorder->path, 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_control.c b/src/output_control.c
index 16c0dbb75..ef77bf4fa 100644
--- a/src/output_control.c
+++ b/src/output_control.c
@@ -23,6 +23,7 @@
#include "output_thread.h"
#include "mixer_control.h"
#include "mixer_plugin.h"
+#include "filter_plugin.h"
#include <assert.h>
#include <stdlib.h>
@@ -93,26 +94,13 @@ 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);
@@ -195,4 +183,6 @@ void audio_output_finish(struct audio_output *ao)
notify_deinit(&ao->notify);
g_mutex_free(ao->mutex);
+
+ filter_free(ao->filter);
}
diff --git a/src/output_init.c b/src/output_init.c
index 927424324..66444e9a5 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,28 +64,81 @@ 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(const struct config_param *param,
+ const struct mixer_plugin *plugin,
+ struct filter *filter_chain)
+{
+ 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, param);
+
+ case MIXER_TYPE_SOFTWARE:
+ mixer = mixer_new(&software_mixer_plugin, 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)
{
- const char *format;
const struct audio_output_plugin *plugin = NULL;
if (param) {
- const char *type = NULL;
+ const char *p;
- type = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
- if (type == NULL) {
+ p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
+ if (p == NULL) {
g_set_error(error, 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);
+ "No such audio output plugin: %s", p);
return false;
}
@@ -89,8 +150,16 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
return false;
}
- format = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
+ p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
NULL);
+ ao->config_audio_format = p != NULL;
+ if (p != NULL) {
+ bool success =
+ audio_format_parse(&ao->out_audio_format,
+ p, error);
+ if (!success)
+ return false;
+ }
} else {
g_warning("No \"%s\" defined in config file\n",
CONF_AUDIO_OUTPUT);
@@ -103,7 +172,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
plugin->name);
ao->name = "default detected output";
- format = NULL;
+ ao->config_audio_format = false;
}
ao->plugin = plugin;
@@ -112,17 +181,10 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
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);
@@ -136,11 +198,17 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
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(param, plugin->mixer_plugin,
+ ao->filter);
+
+ /* 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..4eb77cc49 100644
--- a/src/output_internal.h
+++ b/src/output_internal.h
@@ -21,7 +21,6 @@
#define MPD_OUTPUT_INTERNAL_H
#include "audio_format.h"
-#include "pcm_convert.h"
#include "notify.h"
#include <time.h>
@@ -29,6 +28,13 @@
enum audio_output_command {
AO_COMMAND_NONE = 0,
AO_COMMAND_OPEN,
+
+ /**
+ * This command is invoked when the input audio format
+ * changes.
+ */
+ AO_COMMAND_REOPEN,
+
AO_COMMAND_CLOSE,
AO_COMMAND_PAUSE,
AO_COMMAND_CANCEL,
@@ -107,7 +113,19 @@ 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 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.
+ */
+ struct filter *convert_filter;
/**
* The thread handle, or NULL if the output thread isn't
diff --git a/src/output_list.c b/src/output_list.c
index 81de16649..476701a1a 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 mvp_output_plugin;
extern const struct audio_output_plugin jackPlugin;
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
@@ -73,6 +78,9 @@ const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef ENABLE_HTTPD_OUTPUT
&httpd_output_plugin,
#endif
+#ifdef ENABLE_RECORDER_OUTPUT
+ &recorder_output_plugin,
+#endif
NULL
};
diff --git a/src/output_state.c b/src/output_state.c
index c7e6c8579..5efae3626 100644
--- a/src/output_state.c
+++ b/src/output_state.c
@@ -49,35 +49,34 @@ saveAudioDevicesState(FILE *fp)
}
}
-void
-readAudioDevicesState(FILE *fp)
+bool
+readAudioDevicesState(const char *line)
{
- char buffer[1024];
-
- while (fgets(buffer, sizeof(buffer), fp)) {
- char *c, *name;
- struct audio_output *ao;
+ long value;
+ char *endptr;
+ const char *name;
+ struct audio_output *ao;
- g_strchomp(buffer);
+ if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
+ return false;
- if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE))
- continue;
+ line += sizeof(AUDIO_DEVICE_STATE) - 1;
- c = strchr(buffer, ':');
- if (!c || !(++c))
- goto errline;
+ value = strtol(line, &endptr, 10);
+ if (*endptr != ':' || (value != 0 && value != 1))
+ return false;
- name = strchr(c, ':');
- if (!name || !(++name))
- goto errline;
+ if (value != 0)
+ /* state is "enabled": no-op */
+ return true;
- ao = audio_output_find(name);
- if (ao != NULL && atoi(c) == 0)
- ao->enabled = false;
-
- continue;
-errline:
- /* nonfatal */
- g_warning("invalid line in state_file: %s\n", buffer);
+ name = endptr + 1;
+ ao = audio_output_find(name);
+ if (ao == NULL) {
+ g_debug("Ignoring device state for '%s'", name);
+ return true;
}
+
+ ao->enabled = false;
+ return true;
}
diff --git a/src/output_state.h b/src/output_state.h
index 8592574ab..b171fed18 100644
--- a/src/output_state.h
+++ b/src/output_state.h
@@ -25,10 +25,11 @@
#ifndef OUTPUT_STATE_H
#define OUTPUT_STATE_H
+#include <stdbool.h>
#include <stdio.h>
-void
-readAudioDevicesState(FILE *fp);
+bool
+readAudioDevicesState(const char *line);
void
saveAudioDevicesState(FILE *fp);
diff --git a/src/output_thread.c b/src/output_thread.c
index 785ac808f..e1f20e580 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>
@@ -41,6 +43,73 @@ static void ao_command_finished(struct audio_output *ao)
}
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);
+
+ /* 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;
+ }
+
+ if (!ao->config_audio_format)
+ ao->out_audio_format = *filter_audio_format;
+
+ success = ao_plugin_open(ao->plugin, ao->data,
+ &ao->out_audio_format,
+ &error);
+
+ 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);
+
+ g_mutex_lock(ao->mutex);
+ ao->open = true;
+ g_mutex_unlock(ao->mutex);
+
+ 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)
{
assert(ao->open);
@@ -53,11 +122,69 @@ ao_close(struct audio_output *ao)
g_mutex_unlock(ao->mutex);
ao_plugin_close(ao->plugin, ao->data);
- pcm_convert_deinit(&ao->convert_state);
+ filter_close(ao->filter);
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;
+
+ g_mutex_lock(ao->mutex);
+ ao->chunk = NULL;
+ ao->open = false;
+ g_mutex_unlock(ao->mutex);
+
+ ao_plugin_close(ao->plugin, ao->data);
+
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, &ao->out_audio_format);
+}
+
+static void
+ao_reopen(struct audio_output *ao)
+{
+ if (!ao->config_audio_format) {
+ if (ao->open) {
+ const struct music_pipe *mp = ao->pipe;
+ ao_close(ao);
+ 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;
+ }
+
+ 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,6 +192,8 @@ 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);
@@ -75,18 +204,19 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
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_plugin_cancel(ao->plugin, ao->data);
+ ao_close(ao);
+
+ /* 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) {
@@ -182,8 +312,6 @@ static void ao_pause(struct audio_output *ao)
static gpointer audio_output_task(gpointer arg)
{
struct audio_output *ao = arg;
- bool ret;
- GError *error;
while (1) {
switch (ao->command) {
@@ -191,47 +319,12 @@ static gpointer audio_output_task(gpointer arg)
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);
-
- g_mutex_lock(ao->mutex);
- ao->open = true;
- g_mutex_unlock(ao->mutex);
-
- 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();
- }
+ ao_open(ao);
+ ao_command_finished(ao);
+ break;
+ case AO_COMMAND_REOPEN:
+ ao_reopen(ao);
ao_command_finished(ao);
break;
@@ -244,6 +337,7 @@ static gpointer audio_output_task(gpointer arg)
ao_plugin_cancel(ao->plugin, ao->data);
ao_close(ao);
+ filter_close(ao->filter);
ao_command_finished(ao);
break;
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/player_control.c b/src/player_control.c
index ac4b006dd..df80ac4ff 100644
--- a/src/player_control.c
+++ b/src/player_control.c
@@ -40,7 +40,6 @@ void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play)
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)
@@ -253,16 +252,6 @@ void setPlayerCrossFade(float crossFadeInSeconds)
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)
{
return pc.total_play_time;
diff --git a/src/player_control.h b/src/player_control.h
index b1f7481cd..0cc3c73a8 100644
--- a/src/player_control.h
+++ b/src/player_control.h
@@ -81,7 +81,6 @@ struct player_control {
struct song *errored_song;
volatile double seek_where;
float cross_fade_seconds;
- uint16_t software_volume;
double total_play_time;
};
@@ -145,8 +144,6 @@ void setPlayerCrossFade(float crossFadeInSeconds);
float getPlayerCrossFade(void);
-void setPlayerSoftwareVolume(int volume);
-
double getPlayerTotalPlayTime(void);
static inline const struct audio_format *
diff --git a/src/player_thread.c b/src/player_thread.c
index 7fc55d3d1..b501937b6 100644
--- a/src/player_thread.c
+++ b/src/player_thread.c
@@ -93,12 +93,6 @@ struct player {
* The current audio format for the audio outputs.
*/
struct audio_format play_audio_format;
-
- /**
- * Coefficient for converting a PCM buffer size into a time
- * span.
- */
- double size_to_time;
};
static struct music_buffer *player_buffer;
@@ -141,7 +135,7 @@ player_wait_for_decoder(struct player *player)
{
dc_command_wait(&pc.notify);
- if (decoder_has_failed()) {
+ if (decoder_lock_has_failed()) {
assert(dc.next_song == NULL || dc.next_song->url != NULL);
pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_FILE;
@@ -180,10 +174,14 @@ player_check_decoder_startup(struct player *player)
{
assert(player->decoder_starting);
+ decoder_lock();
+
if (decoder_has_failed()) {
/* the decoder failed */
assert(dc.next_song == NULL || dc.next_song->url != NULL);
+ decoder_unlock();
+
pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_FILE;
@@ -191,6 +189,8 @@ player_check_decoder_startup(struct player *player)
} else if (!decoder_is_starting()) {
/* the decoder is ready and ok */
+ decoder_unlock();
+
if (audio_format_defined(&player->play_audio_format) &&
!audio_output_all_wait(1))
/* the output devices havn't finished playing
@@ -200,8 +200,6 @@ player_check_decoder_startup(struct player *player)
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->decoder_starting = false;
if (!player->paused &&
@@ -227,6 +225,7 @@ player_check_decoder_startup(struct player *player)
} else {
/* the decoder is not yet ready; wait
some more */
+ decoder_unlock();
notify_wait(&pc.notify);
return true;
@@ -414,6 +413,31 @@ static void player_process_command(struct player *player)
}
}
+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
@@ -421,31 +445,12 @@ static void player_process_command(struct player *player)
*/
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);
@@ -455,18 +460,6 @@ play_chunk(struct song *song, struct music_chunk *chunk,
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)) {
@@ -475,7 +468,8 @@ play_chunk(struct song *song, struct music_chunk *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;
}
@@ -525,13 +519,20 @@ play_next_chunk(struct player *player)
music_buffer_return(player_buffer, other_chunk);
} else {
/* there are not enough decoded chunks yet */
+
+ decoder_lock();
+
if (decoder_is_idle()) {
/* the decoder isn't running, abort
cross fading */
+ decoder_unlock();
+
player->xfade = XFADE_DISABLED;
} else {
/* wait for the decoder */
- notify_signal(&dc.notify);
+ decoder_signal();
+ decoder_unlock();
+
notify_wait(&pc.notify);
return true;
@@ -546,8 +547,7 @@ 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);
@@ -563,10 +563,12 @@ play_next_chunk(struct player *player)
/* 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 */
+ decoder_lock();
if (!decoder_is_idle() &&
music_pipe_size(dc.pipe) <= (pc.buffered_before_play +
music_buffer_size(player_buffer) * 3) / 4)
- notify_signal(&dc.notify);
+ decoder_signal();
+ decoder_unlock();
return true;
}
@@ -581,8 +583,14 @@ play_next_chunk(struct player *player)
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;
@@ -608,7 +616,6 @@ static void do_play(void)
.xfade = XFADE_UNKNOWN,
.cross_fading = false,
.cross_fade_chunks = 0,
- .size_to_time = 0.0,
};
player.pipe = music_pipe_new();
@@ -643,7 +650,7 @@ static void do_play(void)
prevent stuttering on slow machines */
if (music_pipe_size(player.pipe) < pc.buffered_before_play &&
- !decoder_is_idle()) {
+ !decoder_lock_is_idle()) {
/* not enough decoded buffer space yet */
if (!player.paused &&
@@ -678,7 +685,7 @@ static void do_play(void)
*/
#endif
- if (decoder_is_idle() && player.queued) {
+ if (decoder_lock_is_idle() && player.queued) {
/* the decoder has finished the current song;
make it decode the next song */
assert(pc.next_song != NULL);
@@ -691,7 +698,7 @@ static void do_play(void)
if (dc.pipe != NULL && dc.pipe != player.pipe &&
player.xfade == XFADE_UNKNOWN &&
- !decoder_is_starting()) {
+ !decoder_lock_is_starting()) {
/* enable cross fading in this song? if yes,
calculate how many chunks will be required
for it */
@@ -729,7 +736,7 @@ static void do_play(void)
if (!player_song_border(&player))
break;
- } else if (decoder_is_idle()) {
+ } else if (decoder_lock_is_idle()) {
/* check the size of the pipe again, because
the decoder thread may have added something
since we last checked */
diff --git a/src/playlist.c b/src/playlist.c
index 35c09329a..162103c2e 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);
@@ -98,7 +99,8 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order)
* 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 +111,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 +180,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;
@@ -197,13 +199,14 @@ playPlaylistOrderNumber(struct playlist *playlist, int 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
@@ -215,11 +218,11 @@ void syncPlayerAndPlaylist(struct playlist *playlist)
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,7 +236,7 @@ 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;
@@ -251,37 +254,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 +300,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 +311,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 +327,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 +337,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 +373,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 +390,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 +414,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..311163e8b 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 '#'
@@ -94,9 +93,11 @@ struct playlist {
/** 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 +117,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 +131,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
-deleteFromPlaylistById(struct playlist *playlist, unsigned song);
+playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end);
-void stopPlaylist(struct playlist *playlist);
+enum playlist_result
+playlist_delete_id(struct playlist *playlist, unsigned song);
+
+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_control.c b/src/playlist_control.c
index 4359611fd..cfbcd19f1 100644
--- a/src/playlist_control.c
+++ b/src/playlist_control.c
@@ -38,7 +38,7 @@ enum {
PLAYLIST_PREV_UNLESS_ELAPSED = 10,
};
-void stopPlaylist(struct playlist *playlist)
+void playlist_stop(struct playlist *playlist)
{
if (!playlist->playing)
return;
@@ -68,7 +68,7 @@ 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;
@@ -115,28 +115,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 +157,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,19 +174,19 @@ 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;
@@ -196,20 +196,20 @@ void previousSongInPlaylist(struct playlist *playlist)
/* re-start playing the current song (just like the
"prev" button on CD players) */
- playPlaylistOrderNumber(playlist, playlist->current);
+ playlist_play_order(playlist, playlist->current);
} else {
if (playlist->current > 0) {
/* play the preceding song */
- playPlaylistOrderNumber(playlist,
+ playlist_play_order(playlist,
playlist->current - 1);
} else if (playlist->queue.repeat) {
/* play the last song in "repeat" mode */
- playPlaylistOrderNumber(playlist,
+ playlist_play_order(playlist,
queue_length(&playlist->queue) - 1);
} else {
/* re-start playing the current song if it's
the first one */
- playPlaylistOrderNumber(playlist, playlist->current);
+ playlist_play_order(playlist, playlist->current);
}
}
@@ -217,7 +217,7 @@ void previousSongInPlaylist(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)
{
const struct song *queued;
unsigned i;
@@ -241,7 +241,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 +259,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..473305f17 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,19 +205,16 @@ 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);
@@ -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_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_save.c b/src/playlist_save.c
index 776d3c385..103a810fb 100644
--- a/src/playlist_save.c
+++ b/src/playlist_save.c
@@ -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..f288a5738 100644
--- a/src/playlist_state.c
+++ b/src/playlist_state.c
@@ -66,9 +66,15 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
playlist->current));
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
getPlayerElapsedTime());
- } else
+ } 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,
@@ -109,8 +115,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,44 +124,43 @@ 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
(&
@@ -172,24 +177,26 @@ 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);
+ 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();
}
+
+ return true;
}
diff --git a/src/playlist_state.h b/src/playlist_state.h
index 989430264..7ed7e8c8e 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,7 @@ 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);
#endif
diff --git a/src/replay_gain.c b/src/replay_gain.c
index bcb501e54..d21b94e0a 100644
--- a/src/replay_gain.c
+++ b/src/replay_gain.c
@@ -38,6 +38,7 @@ 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)
{
@@ -73,6 +74,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 +136,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/song_print.c b/src/song_print.c
index 64ab9f6b1..2c4da6cb0 100644
--- a/src/song_print.c
+++ b/src/song_print.c
@@ -50,6 +50,27 @@ song_print_info(struct client *client, struct song *song)
{
song_print_url(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_save.c b/src/song_save.c
index 8f4e1614d..3e54ce222 100644
--- a/src/song_save.c
+++ b/src/song_save.c
@@ -34,6 +34,12 @@
#define SONG_KEY "key: "
#define SONG_MTIME "mtime: "
+static GQuark
+song_save_quark(void)
+{
+ return g_quark_from_static_string("song_save");
+}
+
static void
song_save_url(FILE *fp, struct song *song)
{
@@ -70,7 +76,7 @@ void songvec_save(FILE *fp, struct songvec *sv)
}
static void
-insertSongIntoList(struct songvec *sv, struct song *newsong)
+commit_song(struct songvec *sv, struct song *newsong)
{
struct song *existing = songvec_find(sv, newsong->url);
@@ -93,7 +99,7 @@ insertSongIntoList(struct songvec *sv, struct song *newsong)
}
static char *
-matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType)
+parse_tag_value(char *buffer, enum tag_type *type_r)
{
int i;
@@ -102,7 +108,7 @@ matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType)
if (0 == strncmp(tag_item_names[i], buffer, len) &&
buffer[len] == ':') {
- *itemType = i;
+ *type_r = i;
return g_strchug(buffer + len + 1);
}
}
@@ -110,12 +116,13 @@ matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType)
return NULL;
}
-void readSongInfoIntoList(FILE *fp, struct songvec *sv,
- struct directory *parent)
+bool
+songvec_load(FILE *fp, struct songvec *sv, struct directory *parent,
+ GError **error_r)
{
char buffer[MPD_PATH_MAX + 1024];
struct song *song = NULL;
- enum tag_type itemType;
+ enum tag_type type;
const char *value;
while (fgets(buffer, sizeof(buffer), fp) &&
@@ -124,24 +131,26 @@ void readSongInfoIntoList(FILE *fp, struct songvec *sv,
if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) {
if (song)
- insertSongIntoList(sv, song);
+ commit_song(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");
+ g_set_error(error_r, song_save_quark(), 0,
+ "Problems reading song info");
+ return false;
} else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) {
/* we don't need this info anymore */
- } else if ((value = matchesAnMpdTagItemKey(buffer,
- &itemType)) != NULL) {
+ } else if ((value = parse_tag_value(buffer,
+ &type)) != NULL) {
if (!song->tag) {
song->tag = tag_new();
tag_begin_add(song->tag);
}
- tag_add_item(song->tag, itemType, value);
+ tag_add_item(song->tag, type, value);
} else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) {
if (!song->tag) {
song->tag = tag_new();
@@ -151,11 +160,15 @@ void readSongInfoIntoList(FILE *fp, struct songvec *sv,
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)]));
+ } else {
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", buffer);
+ return false;
}
- else
- g_error("unknown line in db: %s", buffer);
}
if (song)
- insertSongIntoList(sv, song);
+ commit_song(sv, song);
+
+ return true;
}
diff --git a/src/song_save.h b/src/song_save.h
index 370e42730..36e03584e 100644
--- a/src/song_save.h
+++ b/src/song_save.h
@@ -20,6 +20,9 @@
#ifndef MPD_SONG_SAVE_H
#define MPD_SONG_SAVE_H
+#include <glib.h>
+
+#include <stdbool.h>
#include <stdio.h>
struct songvec;
@@ -27,7 +30,16 @@ struct directory;
void songvec_save(FILE *fp, struct songvec *sv);
-void readSongInfoIntoList(FILE * fp, struct songvec *sv,
- struct directory *parent);
+/**
+ * Loads songs from the input file and add the to the specified
+ * directory.
+ *
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success, false on error
+ */
+bool
+songvec_load(FILE *file, struct songvec *sv, struct directory *parent,
+ GError **error_r);
#endif
diff --git a/src/state_file.c b/src/state_file.c
index 9c6475cc8..18548d03c 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,15 +31,6 @@
#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 */
@@ -47,11 +39,11 @@ static guint save_state_source_id;
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,8 +52,9 @@ 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);
+ saveAudioDevicesState(fp);
+ playlist_state_save(fp, &g_playlist);
while(fclose(fp) && errno == EINTR) /* nothing */;
}
@@ -69,12 +62,13 @@ state_file_write(void)
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,9 +76,15 @@ 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) ||
+ readAudioDevicesState(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 */;
@@ -119,11 +119,14 @@ state_file_init(const char *path)
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/sticker.c b/src/sticker.c
index 0d30fbb70..4cccd9399 100644
--- a/src/sticker.c
+++ b/src/sticker.c
@@ -72,50 +72,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..5d473322e 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -43,8 +43,10 @@ static struct {
const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
"Artist",
+ [TAG_ARTIST_SORT] = "ArtistSort",
"Album",
"AlbumArtist",
+ [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort",
"Title",
"Track",
"Name",
diff --git a/src/tag.h b/src/tag.h
index 4b72dd187..451e13112 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -32,8 +32,10 @@
*/
enum tag_type {
TAG_ITEM_ARTIST,
+ TAG_ARTIST_SORT,
TAG_ITEM_ALBUM,
TAG_ITEM_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST_SORT,
TAG_ITEM_TITLE,
TAG_ITEM_TRACK,
TAG_ITEM_NAME,
diff --git a/src/tag_ape.c b/src/tag_ape.c
index 7cbf32208..e3b848bfc 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_ITEM_TITLE] = "title",
+ [TAG_ITEM_ARTIST] = "artist",
+ [TAG_ITEM_ALBUM] = "album",
+ [TAG_ITEM_COMMENT] = "comment",
+ [TAG_ITEM_GENRE] = "genre",
+ [TAG_ITEM_TRACK] = "track",
+ [TAG_ITEM_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..c78983615 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_ITEM_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_ITEM_ARTIST);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST,
+ TAG_ITEM_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_ITEM_TITLE);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM);
+ tag_id3_import_text(ret, tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK);
+ tag_id3_import_text(ret, tag, ID3_FRAME_YEAR, TAG_ITEM_DATE);
+ tag_id3_import_text(ret, tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE);
+ tag_id3_import_text(ret, tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER);
+ tag_id3_import_text(ret, tag, "TPE3", TAG_ITEM_PERFORMER);
+ tag_id3_import_text(ret, tag, "TPE4", TAG_ITEM_PERFORMER);
+ tag_id3_import_comment(ret, tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT);
+ tag_id3_import_text(ret, tag, ID3_FRAME_DISC, TAG_ITEM_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/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 593198cb9..8570d8ab3 100644
--- a/src/update.c
+++ b/src/update.c
@@ -17,45 +17,23 @@
* 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 +43,17 @@ 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;
+struct notify update_notify;
-#endif
+/* XXX this flag is passed to update_task() */
+static bool discard;
unsigned
isUpdatingDB(void)
@@ -98,688 +61,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);
- if (song == NULL)
- return true;
-
- // shouldn't be necessary but it's there..
- song->mtime = st->st_mtime;
-
- song->tag = plugin->tag_dup(map_directory_child_fs(contdir, vtrack));
-
- songvec_add(&contdir->songs, song);
- song = NULL;
-
- modified = true;
-
- 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) &&
- 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 || skip_symlink(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())
@@ -790,7 +76,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;
@@ -798,15 +85,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);
@@ -814,48 +104,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;
}
/**
@@ -863,22 +125,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;
@@ -888,23 +153,18 @@ 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)
{
+ update_walk_global_finish();
+ update_remove_global_finish();
+
notify_deinit(&update_notify);
}
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..14701feb5
--- /dev/null
+++ b/src/update_internal.h
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+/** used by the main thread to notify the update thread */
+extern struct notify update_notify;
+
+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..b79e95ae0
--- /dev/null
+++ b/src/update_walk.c
@@ -0,0 +1,759 @@
+/*
+ * 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 "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_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);
+ if (song == NULL)
+ return true;
+
+ // shouldn't be necessary but it's there..
+ song->mtime = st->st_mtime;
+
+ song->tag = plugin->tag_dup(map_directory_child_fs(contdir, vtrack));
+
+ songvec_add(&contdir->songs, song);
+ song = NULL;
+
+ modified = true;
+
+ 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)
+ 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))
+ 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 || skip_symlink(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);
+}
+
+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/volume.c b/src/volume.c
index e7fa20a62..5f4a66837 100644
--- a/src/volume.c
+++ b/src/volume.c
@@ -26,6 +26,7 @@
#include "output_all.h"
#include "mixer_control.h"
#include "mixer_all.h"
+#include "mixer_type.h"
#include <glib.h>
@@ -39,13 +40,7 @@
#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;
@@ -54,117 +49,15 @@ 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.
- */
-static void
-mixer_copy_legacy_param(const char *type, const char *name)
-{
- 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 */
-
- config_add_block_param(output, name, param->value, param->line);
-}
-
-static void
-mixer_reconfigure(const char *type)
-{
- 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);
- }
- }
- }
-
- if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE)
- hardware_volume_timer = g_timer_new();
+ hardware_volume_timer = g_timer_new();
}
-static int hardware_volume_get(void)
+int volume_level_get(void)
{
assert(hardware_volume_timer != NULL);
@@ -178,101 +71,54 @@ static int hardware_volume_get(void)
return last_hardware_volume;
}
-static int software_volume_get(void)
+static bool software_volume_change(unsigned volume)
{
- return volume_software_set;
-}
+ assert(volume <= 100);
-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)
-{
- int new = change;
-
- if (rel)
- new += volume_software_set;
-
- 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);
}
diff --git a/src/volume.h b/src/volume.h
index 99d31da4e..0db231ef6 100644
--- a/src/volume.h
+++ b/src/volume.h
@@ -23,21 +23,16 @@
#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);