Code

mpdclient: use the "idle" command to reduce CPU and network usage
authorMax Kellermann <max@duempel.org>
Sat, 3 Oct 2009 23:18:35 +0000 (01:18 +0200)
committerMax Kellermann <max@duempel.org>
Sat, 3 Oct 2009 23:18:35 +0000 (01:18 +0200)
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.

NEWS
src/main.c
src/mpdclient.c
src/mpdclient.h

diff --git a/NEWS b/NEWS
index 1019253de2797b11ecbd57d4cdc546d43fcf710d..a20eb42a4795ddadba414109538665fa1a86047e 100644 (file)
--- 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
index e5afa359728fafaf8d3a3667ab7d56b8ff6c8b2f..2bc1b624e70767c330bfbaf014d539e2152d97cd 100644 (file)
@@ -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)
 {
index 54ce2c7145b7dc4b95a7a277a54874337108f8eb..a96deaddf73dd8fab691f937e37960153dd7d03f 100644 (file)
@@ -24,6 +24,7 @@
 #include "options.h"
 #include "strfsong.h"
 #include "utils.h"
+#include "gidle.h"
 
 #include <mpd/client.h>
 
@@ -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);
+       }
 }
 
 
index b6027da7be83c43a1db3704e5cce02b8610e7213..d9b568f0c82a10c8e2e04425138bdf0e485823ad 100644 (file)
@@ -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;