aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/COMMANDS21
-rw-r--r--src/Makefile.am2
-rw-r--r--src/audio.c3
-rw-r--r--src/client.c83
-rw-r--r--src/client.h14
-rw-r--r--src/command.c12
-rw-r--r--src/idle.c56
-rw-r--r--src/idle.h64
-rw-r--r--src/main.c9
-rw-r--r--src/player_control.c15
-rw-r--r--src/playlist.c10
-rw-r--r--src/storedPlaylist.c11
-rw-r--r--src/update.c5
-rw-r--r--src/volume.c3
14 files changed, 305 insertions, 3 deletions
diff --git a/doc/COMMANDS b/doc/COMMANDS
index 19ad373b1..718dcf24e 100644
--- a/doc/COMMANDS
+++ b/doc/COMMANDS
@@ -265,6 +265,27 @@ volume <int change>
change volume by amount _change_
NOTE: volume command is deprecated, use setvol instead
+idle
+ Waits until there is a noteworthy change in one or more of
+ MPD's subsystems. As soon as there is one, it lists all
+ changed systems in a line in the format "changed: SUBSYSTEM",
+ where SUBSYSTEM is one of the following:
+
+ database: the song database has been updated
+ stored_playlist: a stored playlist has been modified, renamed,
+ created or deleted
+ playlist: the current playlist has been modified
+ player: the player has been started, stopped or seeked
+ mixer: the volume has been changed
+ output: an audio output has been enabled or disabled
+ options: options like "repeat", "random", "crossfade"
+
+ While a client waits for "idle" results, the server disables
+ timeouts, allowing a client to wait for events as long as mpd
+ runs. The "idle" command can be canceled by sending a new
+ command.
+
+
COMMAND LIST
------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 2151daa77..6135c41bb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -45,6 +45,7 @@ mpd_headers = \
buffer2array.h \
charConv.h \
command.h \
+ idle.h \
condition.h \
conf.h \
crossfade.h \
@@ -130,6 +131,7 @@ mpd_SOURCES = \
buffer2array.c \
charConv.c \
command.c \
+ idle.c \
condition.c \
conf.c \
crossfade.c \
diff --git a/src/audio.c b/src/audio.c
index ce62df46d..4305235d0 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -24,6 +24,7 @@
#include "log.h"
#include "path.h"
#include "client.h"
+#include "idle.h"
#include "utils.h"
#include "os_compat.h"
@@ -379,6 +380,7 @@ int enableAudioDevice(unsigned int device)
return -1;
audioDeviceStates[device] = true;
+ idle_add(IDLE_OUTPUT);
return 0;
}
@@ -389,6 +391,7 @@ int disableAudioDevice(unsigned int device)
return -1;
audioDeviceStates[device] = false;
+ idle_add(IDLE_OUTPUT);
return 0;
}
diff --git a/src/client.c b/src/client.c
index e8f93b556..1dcec5af2 100644
--- a/src/client.c
+++ b/src/client.c
@@ -27,6 +27,7 @@
#include "ioops.h"
#include "main_notify.h"
#include "dlist.h"
+#include "idle.h"
#include "../config.h"
@@ -87,6 +88,13 @@ struct client {
size_t send_buf_used; /* bytes used this instance */
size_t send_buf_size; /* bytes usable this instance */
size_t send_buf_alloc; /* bytes actually allocated */
+
+ /** 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;
};
static LIST_HEAD(clients);
@@ -409,6 +417,9 @@ static int client_input_received(struct client *client, int bytesRead)
int ret;
char *buf_tail = &(client->buffer[client->bufferLength - 1]);
+ /* any input from the client makes it leave "idle" mode */
+ client->idle_waiting = false;
+
while (bytesRead > 0) {
client->bufferLength++;
bytesRead--;
@@ -635,7 +646,9 @@ void client_manager_expire(void)
if (client_is_expired(client)) {
DEBUG("client %i: expired\n", client->num);
client_close(client);
- } else if (time(NULL) - client->lastTime >
+ } else if (!client->idle_waiting && /* idle clients
+ never expire */
+ time(NULL) - client->lastTime >
client_timeout) {
DEBUG("client %i: timeout\n", client->num);
client_close(client);
@@ -807,3 +820,71 @@ mpd_fprintf void client_printf(struct client *client, const char *fmt, ...)
client_vprintf(client, fmt, args);
va_end(args);
}
+
+static const char *const idle_names[] = {
+ "database",
+ "stored_playlist",
+ "playlist",
+ "player",
+ "mixer",
+ "output",
+ "options",
+};
+
+/**
+ * Send "idle" response to this client.
+ */
+static void
+client_idle_notify(struct client *client)
+{
+ unsigned flags, i;
+
+ assert(client->idle_waiting);
+ assert(client->idle_flags != 0);
+
+ flags = client->idle_flags;
+ client->idle_flags = 0;
+ client->idle_waiting = false;
+
+ for (i = 0; i < sizeof(idle_names) / sizeof(idle_names[0]); ++i) {
+ assert(idle_names[i] != NULL);
+
+ if (flags & (1 << i))
+ client_printf(client, "changed: %s\n",
+ idle_names[i]);
+ }
+
+ client_puts(client, "OK\n");
+ client->lastTime = time(NULL);
+}
+
+void client_manager_idle_add(unsigned flags)
+{
+ struct client *client;
+
+ assert(flags != 0);
+
+ list_for_each_entry(client, &clients, siblings) {
+ if (client_is_expired(client))
+ continue;
+
+ client->idle_flags |= flags;
+ if (client->idle_waiting) {
+ client_idle_notify(client);
+ client_write_output(client);
+ }
+ }
+}
+
+bool client_idle_wait(struct client *client)
+{
+ assert(!client->idle_waiting);
+
+ client->idle_waiting = true;
+
+ if (client->idle_flags != 0) {
+ client_idle_notify(client);
+ return true;
+ } else
+ return false;
+}
diff --git a/src/client.h b/src/client.h
index 0d9f2e76a..50238d9f0 100644
--- a/src/client.h
+++ b/src/client.h
@@ -21,6 +21,7 @@
#include "gcc.h"
+#include <stdbool.h>
#include <stddef.h>
#include <stdarg.h>
#include <sys/socket.h>
@@ -60,4 +61,17 @@ void client_vprintf(struct client *client, const char *fmt, va_list args);
*/
mpd_fprintf void client_printf(struct client *client, const char *fmt, ...);
+/**
+ * Adds the specified idle flags to all clients and immediately sends
+ * notifications to all waiting clients.
+ */
+void client_manager_idle_add(unsigned flags);
+
+/**
+ * Checks whether the client has pending idle flags. If yes, they are
+ * sent immediately and "true" is returned". If no, it puts the
+ * client into waiting mode and returns false.
+ */
+bool client_idle_wait(struct client *client);
+
#endif
diff --git a/src/command.c b/src/command.c
index 14ba2c300..1037c812d 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1239,6 +1239,17 @@ static int handlePlaylistAdd(struct client *client,
return print_playlist_result(client, result);
}
+static int
+handle_idle(struct client *client,
+ mpd_unused int argc, mpd_unused char *argv[])
+{
+ /* enable "idle" mode on this client */
+ client_idle_wait(client);
+
+ /* return value is "1" so the caller won't print "OK" */
+ return 1;
+}
+
void initCommands(void)
{
commandList = makeList(free, 1);
@@ -1307,6 +1318,7 @@ void initCommands(void)
addCommand(COMMAND_TAGTYPES, PERMISSION_READ, 0, 0, handleTagTypes);
addCommand(COMMAND_COUNT, PERMISSION_READ, 2, -1, handleCount);
addCommand(COMMAND_RENAME, PERMISSION_CONTROL, 2, 2, handleRename);
+ addCommand("idle", PERMISSION_READ, 0, 0, handle_idle);
sortList(commandList);
}
diff --git a/src/idle.c b/src/idle.c
new file mode 100644
index 000000000..c779d0a91
--- /dev/null
+++ b/src/idle.c
@@ -0,0 +1,56 @@
+/* the Music Player Daemon (MPD)
+ * Copyright (C) 2008 Max Kellermann <max@duempel.org>
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * Support library for the "idle" command.
+ *
+ */
+
+#include "idle.h"
+#include "main_notify.h"
+
+#include <assert.h>
+#include <pthread.h>
+
+static unsigned idle_flags;
+static pthread_mutex_t idle_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+void
+idle_add(unsigned flags)
+{
+ assert(flags != 0);
+
+ pthread_mutex_lock(&idle_mutex);
+ idle_flags |= flags;
+ pthread_mutex_unlock(&idle_mutex);
+
+ wakeup_main_task();
+}
+
+unsigned
+idle_get(void)
+{
+ unsigned flags;
+
+ pthread_mutex_lock(&idle_mutex);
+ flags = idle_flags;
+ idle_flags = 0;
+ pthread_mutex_unlock(&idle_mutex);
+
+ return flags;
+}
diff --git a/src/idle.h b/src/idle.h
new file mode 100644
index 000000000..69756b153
--- /dev/null
+++ b/src/idle.h
@@ -0,0 +1,64 @@
+/* the Music Player Daemon (MPD)
+ * Copyright (C) 2008 Max Kellermann <max@duempel.org>
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * Support library for the "idle" command.
+ *
+ */
+
+#ifndef MPD_IDLE_H
+#define MPD_IDLE_H
+
+enum {
+ /** song database has been updated*/
+ IDLE_DATABASE = 0x1,
+
+ /** a stored playlist has been modified, created, deleted or
+ renamed */
+ IDLE_STORED_PLAYLIST = 0x2,
+
+ /** the current playlist has been modified */
+ IDLE_PLAYLIST = 0x4,
+
+ /** the player state has changed: play, stop, pause, seek, ... */
+ IDLE_PLAYER = 0x8,
+
+ /** the volume has been modified */
+ IDLE_MIXER = 0x10,
+
+ /** an audio output device has been enabled or disabled */
+ IDLE_OUTPUT = 0x20,
+
+ /** options have changed: crossfade, random, repeat, ... */
+ IDLE_OPTIONS = 0x40,
+};
+
+/**
+ * Adds idle flag (with bitwise "or") and queues notifications to all
+ * clients.
+ */
+void
+idle_add(unsigned flags);
+
+/**
+ * Atomically reads and resets the global idle flags value.
+ */
+unsigned
+idle_get(void);
+
+#endif
diff --git a/src/main.c b/src/main.c
index b4f821008..1a7e73254 100644
--- a/src/main.c
+++ b/src/main.c
@@ -17,6 +17,7 @@
*/
#include "client.h"
+#include "idle.h"
#include "command.h"
#include "playlist.h"
#include "database.h"
@@ -445,9 +446,17 @@ int main(int argc, char *argv[])
while (COMMAND_RETURN_KILL != client_manager_io() &&
COMMAND_RETURN_KILL != handlePendingSignals()) {
+ unsigned flags;
+
syncPlayerAndPlaylist();
client_manager_expire();
reap_update_task();
+
+ /* send "idle" notificaions to all subscribed
+ clients */
+ flags = idle_get();
+ if (flags != 0)
+ client_manager_idle_add(flags);
}
write_state_file();
diff --git a/src/player_control.c b/src/player_control.c
index e7935f80f..086ef505a 100644
--- a/src/player_control.c
+++ b/src/player_control.c
@@ -21,6 +21,7 @@
#include "log.h"
#include "tag.h"
#include "song.h"
+#include "idle.h"
#include "os_compat.h"
#include "main_notify.h"
@@ -61,6 +62,8 @@ playerPlay(struct song *song)
pc.next_song = song;
player_command(PLAYER_COMMAND_PLAY);
+
+ idle_add(IDLE_PLAYER);
}
void pc_cancel(void)
@@ -71,17 +74,23 @@ void pc_cancel(void)
void playerWait(void)
{
player_command(PLAYER_COMMAND_CLOSE_AUDIO);
+
+ idle_add(IDLE_PLAYER);
}
void playerKill(void)
{
player_command(PLAYER_COMMAND_EXIT);
+
+ idle_add(IDLE_PLAYER);
}
void playerPause(void)
{
- if (pc.state != PLAYER_STATE_STOP)
+ if (pc.state != PLAYER_STATE_STOP) {
player_command(PLAYER_COMMAND_PAUSE);
+ idle_add(IDLE_PLAYER);
+ }
}
void playerSetPause(int pause_flag)
@@ -185,6 +194,8 @@ playerSeek(struct song *song, float seek_time)
if (pc.error == PLAYER_ERROR_NOERROR) {
pc.seekWhere = seek_time;
player_command(PLAYER_COMMAND_SEEK);
+
+ idle_add(IDLE_PLAYER);
}
return 0;
@@ -200,6 +211,8 @@ void setPlayerCrossFade(float crossFadeInSeconds)
if (crossFadeInSeconds < 0)
crossFadeInSeconds = 0;
pc.crossFade = crossFadeInSeconds;
+
+ idle_add(IDLE_OPTIONS);
}
void setPlayerSoftwareVolume(int volume)
diff --git a/src/playlist.c b/src/playlist.c
index b160202bf..3498ae7bb 100644
--- a/src/playlist.c
+++ b/src/playlist.c
@@ -34,6 +34,7 @@
#include "state_file.h"
#include "storedPlaylist.h"
#include "ack.h"
+#include "idle.h"
#include "os_compat.h"
#define PLAYLIST_STATE_STOP 0
@@ -87,6 +88,8 @@ static void incrPlaylistVersion(void)
playlist.version = 1;
}
+
+ idle_add(IDLE_PLAYLIST);
}
void playlistVersionChange(void)
@@ -504,6 +507,8 @@ static void syncPlaylistWithQueue(void)
if (pc.next_song == NULL && playlist.queued != -1) {
playlist.current = playlist.queued;
playlist.queued = -1;
+
+ idle_add(IDLE_PLAYER);
}
}
@@ -951,6 +956,8 @@ void setPlaylistRepeatStatus(bool status)
clearPlayerQueue();
playlist.repeat = status;
+
+ idle_add(IDLE_OPTIONS);
}
enum playlist_result moveSongInPlaylist(int from, int to)
@@ -1123,6 +1130,8 @@ void setPlaylistRandomStatus(bool status)
}
} else
orderPlaylist();
+
+ idle_add(IDLE_OPTIONS);
}
void previousSongInPlaylist(void)
@@ -1219,6 +1228,7 @@ enum playlist_result savePlaylist(const char *utf8file)
while (fclose(fp) && errno == EINTR) ;
+ idle_add(IDLE_STORED_PLAYLIST);
return PLAYLIST_RESULT_SUCCESS;
}
diff --git a/src/storedPlaylist.c b/src/storedPlaylist.c
index 3d5b8286f..fb0027599 100644
--- a/src/storedPlaylist.c
+++ b/src/storedPlaylist.c
@@ -24,6 +24,7 @@
#include "utils.h"
#include "ls.h"
#include "database.h"
+#include "idle.h"
#include "os_compat.h"
static ListNode *nodeOfStoredPlaylist(List *list, int idx)
@@ -192,6 +193,7 @@ static int moveSongInStoredPlaylist(List *list, int src, int dest)
}
}
+ idle_add(IDLE_STORED_PLAYLIST);
return 0;
}
@@ -212,6 +214,8 @@ moveSongInStoredPlaylistByPath(const char *utf8path, int src, int dest)
result = writeStoredPlaylistToPath(list, utf8path);
freeList(list);
+
+ idle_add(IDLE_STORED_PLAYLIST);
return result;
}
@@ -231,6 +235,8 @@ removeAllFromStoredPlaylistByPath(const char *utf8path)
return PLAYLIST_RESULT_ERRNO;
while (fclose(file) != 0 && errno == EINTR);
+
+ idle_add(IDLE_STORED_PLAYLIST);
return PLAYLIST_RESULT_SUCCESS;
}
@@ -262,6 +268,8 @@ removeOneSongFromStoredPlaylistByPath(const char *utf8path, int pos)
result = writeStoredPlaylistToPath(list, utf8path);
freeList(list);
+
+ idle_add(IDLE_STORED_PLAYLIST);
return result;
}
@@ -299,6 +307,8 @@ appendSongToStoredPlaylistByPath(const char *utf8path, struct song *song)
playlist_print_song(file, song);
while (fclose(file) != 0 && errno == EINTR);
+
+ idle_add(IDLE_STORED_PLAYLIST);
return PLAYLIST_RESULT_SUCCESS;
}
@@ -325,5 +335,6 @@ renameStoredPlaylist(const char *utf8from, const char *utf8to)
if (rename(from, to) < 0)
return PLAYLIST_RESULT_ERRNO;
+ idle_add(IDLE_STORED_PLAYLIST);
return PLAYLIST_RESULT_SUCCESS;
}
diff --git a/src/update.c b/src/update.c
index 219806a4c..4017842b9 100644
--- a/src/update.c
+++ b/src/update.c
@@ -30,6 +30,7 @@
#include "main_notify.h"
#include "condition.h"
#include "update.h"
+#include "idle.h"
static enum update_progress {
UPDATE_PROGRESS_IDLE = 0,
@@ -549,8 +550,10 @@ void reap_update_task(void)
if (pthread_join(update_thr, NULL))
FATAL("error joining update thread: %s\n", strerror(errno));
- if (modified)
+ if (modified) {
playlistVersionChange();
+ idle_add(IDLE_DATABASE);
+ }
if (update_paths_nr) {
char *path = update_paths[0];
diff --git a/src/volume.c b/src/volume.c
index bf3e58f02..cca5860e3 100644
--- a/src/volume.c
+++ b/src/volume.c
@@ -21,6 +21,7 @@
#include "log.h"
#include "player_control.h"
#include "utils.h"
+#include "idle.h"
#include "os_compat.h"
#include "../config.h"
@@ -490,6 +491,8 @@ static int changeSoftwareVolume(int change, int rel)
int changeVolumeLevel(int change, int rel)
{
+ idle_add(IDLE_MIXER);
+
switch (volume_mixerType) {
#ifdef HAVE_ALSA
case VOLUME_MIXER_TYPE_ALSA: