From: Max Kellermann Date: Sat, 3 Oct 2009 23:18:35 +0000 (+0200) Subject: mpdclient: use the "idle" command to reduce CPU and network usage X-Git-Tag: release-0.16~169 X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=e25d7523c092a56b404e385daa8eba228c3b3074;p=ncmpc.git mpdclient: use the "idle" command to reduce CPU and network usage This patch makes mpdclient and the rest of ncmpc use the new mpd_glib_source class. When mpdclient_get_connection() is called, it sends the "noidle" command to MPD. Upon mpdclient_put_connection() (which is called before returning control back to GLib), mpdclient.c re-enters "idle" mode. The "idle" event handler lives in main.c, and replaces timer_mpd_update(). Currently, the update timer is still used when MPD is playing, to keep the progress bar and the time up to date. This is subject to change soon. Servers which do not support "idle" are detected automatically. --- diff --git a/NEWS b/NEWS index 1019253..a20eb42 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,7 @@ ncmpc 0.16 - not yet released * optimize "add+play song" with addid/playid * handle stderr messages from lyrics plugins * search: eliminate duplicate results +* use the "idle" command to reduce CPU and network usage ncmpc 0.15 - 2009-09-24 diff --git a/src/main.c b/src/main.c index e5afa35..2bc1b62 100644 --- a/src/main.c +++ b/src/main.c @@ -20,6 +20,7 @@ #include "config.h" #include "ncmpc.h" #include "mpdclient.h" +#include "gidle.h" #include "charset.h" #include "options.h" #include "command.h" @@ -155,6 +156,12 @@ catch_sigwinch(G_GNUC_UNUSED int sig) timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL); } +static void +idle_callback(enum mpd_error error, + G_GNUC_UNUSED enum mpd_server_error server_error, + const char *message, enum mpd_idle events, + G_GNUC_UNUSED void *ctx); + static gboolean timer_mpd_update(gpointer data); @@ -181,7 +188,10 @@ disable_update_timer(void) static bool should_enable_update_timer(void) { - return mpdclient_is_connected(mpd) + return (mpdclient_is_connected(mpd) && + (mpd->source == NULL || /* "idle" not supported */ + (mpd->status != NULL && + mpd_status_get_state(mpd->status) == MPD_STATE_PLAY))) #ifndef NCMPC_MINI || options.display_time #endif @@ -268,6 +278,11 @@ timer_reconnect(G_GNUC_UNUSED gpointer data) } #endif + if (mpd_connection_cmp_server_version(connection, + 0, 14, 0) >= 0) + mpd->source = mpd_glib_new(connection, + idle_callback, mpd); + screen_status_printf(_("Connected to %s"), options.host != NULL ? options.host : "localhost"); @@ -294,6 +309,67 @@ check_reconnect(void) NULL); } +/** + * This function is called by the gidle.c library when MPD sends us an + * idle event (or when the connectiond dies). + */ +static void +idle_callback(enum mpd_error error, enum mpd_server_error server_error, + const char *message, enum mpd_idle events, + void *ctx) +{ + struct mpdclient *c = ctx; + struct mpd_connection *connection; + + c->idle = false; + + connection = mpdclient_get_connection(c); + assert(connection != NULL); + + if (error != MPD_ERROR_SUCCESS) { + char *allocated; + + if (error == MPD_ERROR_SERVER && + server_error == MPD_SERVER_ERROR_UNKNOWN_CMD) { + /* the "idle" command is not supported - fall + back to timer based polling */ + mpd_glib_free(c->source); + c->source = NULL; + auto_update_timer(); + return; + } + + if (error == MPD_ERROR_SERVER) + message = allocated = utf8_to_locale(message); + else + allocated = NULL; + screen_status_message(message); + g_free(allocated); + screen_bell(); + doupdate(); + + mpdclient_disconnect(c); + reconnect_source_id = g_timeout_add(1000, timer_reconnect, + NULL); + return; + } + + c->events |= events; + mpdclient_update(c); + +#ifndef NCMPC_MINI + if (options.enable_xterm_title) + update_xterm_title(); +#endif + + screen_update(mpd); + c->events = 0; + + mpdclient_put_connection(c); + check_reconnect(); + auto_update_timer(); +} + static gboolean timer_mpd_update(G_GNUC_UNUSED gpointer data) { diff --git a/src/mpdclient.c b/src/mpdclient.c index 54ce2c7..a96dead 100644 --- a/src/mpdclient.c +++ b/src/mpdclient.c @@ -24,6 +24,7 @@ #include "options.h" #include "strfsong.h" #include "utils.h" +#include "gidle.h" #include @@ -115,6 +116,12 @@ mpdclient_free(struct mpdclient *c) void mpdclient_disconnect(struct mpdclient *c) { + if (c->source != NULL) { + mpd_glib_free(c->source); + c->source = NULL; + c->idle = false; + } + if (c->connection) mpd_connection_free(c->connection); c->connection = NULL; @@ -172,9 +179,10 @@ mpdclient_update(struct mpdclient *c) if (connection == NULL) return false; - /* always announce these options as long as we don't have real + /* always announce these options as long as we don't have "idle" support */ - c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS; + if (c->source == NULL) + c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS; /* free the old status */ if (c->status) @@ -185,7 +193,8 @@ mpdclient_update(struct mpdclient *c) if (c->status == NULL) return mpdclient_handle_error(c); - if (c->update_id != mpd_status_get_update_id(c->status)) { + if (c->source == NULL && + c->update_id != mpd_status_get_update_id(c->status)) { c->events |= MPD_IDLE_UPDATE; if (c->update_id > 0) @@ -194,14 +203,16 @@ mpdclient_update(struct mpdclient *c) c->update_id = mpd_status_get_update_id(c->status); - if (c->volume != mpd_status_get_volume(c->status)) + if (c->source == NULL && + c->volume != mpd_status_get_volume(c->status)) c->events |= MPD_IDLE_MIXER; c->volume = mpd_status_get_volume(c->status); /* check if the playlist needs an update */ if (c->playlist.version != mpd_status_get_queue_version(c->status)) { - c->events |= MPD_IDLE_PLAYLIST; + if (c->source == NULL) + c->events |= MPD_IDLE_PLAYLIST; if (!playlist_is_empty(&c->playlist)) retval = mpdclient_playlist_update_changes(c); @@ -222,12 +233,23 @@ mpdclient_update(struct mpdclient *c) struct mpd_connection * mpdclient_get_connection(struct mpdclient *c) { + if (c->source != NULL && c->idle) { + c->idle = false; + mpd_glib_leave(c->source); + } + return c->connection; } void -mpdclient_put_connection(G_GNUC_UNUSED struct mpdclient *c) +mpdclient_put_connection(struct mpdclient *c) { + assert(c->source == NULL || c->connection != NULL); + + if (c->source != NULL && !c->idle) { + c->idle = true; + mpd_glib_enter(c->source); + } } diff --git a/src/mpdclient.h b/src/mpdclient.h index b6027da..d9b568f 100644 --- a/src/mpdclient.h +++ b/src/mpdclient.h @@ -12,6 +12,23 @@ struct mpdclient { struct mpdclient_playlist playlist; struct mpd_connection *connection; + + /** + * If this object is non-NULL, it tracks idle events. It is + * automatically called by mpdclient_get_connection() and + * mpdclient_put_connection(). It is not created by the + * mpdclient library; the user of this library has to + * initialize it. However, it is freed when the MPD + * connection is closed. + */ + struct mpd_glib_source *source; + + /** + * This attribute is true when the connection is currently in + * "idle" mode, and the #mpd_glib_source waits for an event. + */ + bool idle; + struct mpd_status *status; const struct mpd_song *song;