From a3e3d2c9506d17b3e19e205535ec263ee75178c9 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 14 Oct 2008 22:38:14 +0200 Subject: command: added command "idle" "idle" waits until something noteworthy happens on the server, e.g. song change, playlist modified, database updated. This allows clients to keep up to date without polling. --- src/Makefile.am | 2 ++ src/audio.c | 3 ++ src/client.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/client.h | 14 +++++++++ src/command.c | 12 ++++++++ src/idle.c | 56 +++++++++++++++++++++++++++++++++++ src/idle.h | 64 ++++++++++++++++++++++++++++++++++++++++ src/main.c | 9 ++++++ src/player_control.c | 15 +++++++++- src/playlist.c | 10 +++++++ src/storedPlaylist.c | 11 +++++++ src/update.c | 5 +++- src/volume.c | 3 ++ 13 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 src/idle.c create mode 100644 src/idle.h (limited to 'src') 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 #include #include #include @@ -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 + * 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 +#include + +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 + * 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: -- cgit v1.2.3