Code

mpdclient: implement password for asynchronous connect
[ncmpc.git] / src / mpdclient.c
index 4c480d781357a72d961a854514d4258cc78954f0..6a81a0bd8ac20bd6cc2708f3438ad194a28ba643 100644 (file)
 #include "gidle.h"
 #include "charset.h"
 
+#ifdef ENABLE_ASYNC_CONNECT
+#include "aconnect.h"
+#endif
+
 #include <mpd/client.h>
 
 #include <assert.h>
 
+static gboolean
+mpdclient_enter_idle_callback(gpointer user_data)
+{
+       struct mpdclient *c = user_data;
+       assert(c->enter_idle_source_id != 0);
+       assert(c->source != NULL);
+       assert(!c->idle);
+
+       c->enter_idle_source_id = 0;
+       c->idle = mpd_glib_enter(c->source);
+       return false;
+}
+
+static void
+mpdclient_schedule_enter_idle(struct mpdclient *c)
+{
+       assert(c != NULL);
+       assert(c->source != NULL);
+
+       if (c->enter_idle_source_id == 0)
+               /* automatically re-enter MPD "idle" mode */
+               c->enter_idle_source_id =
+                       g_idle_add(mpdclient_enter_idle_callback, c);
+}
+
+static void
+mpdclient_cancel_enter_idle(struct mpdclient *c)
+{
+       if (c->enter_idle_source_id != 0) {
+               g_source_remove(c->enter_idle_source_id);
+               c->enter_idle_source_id = 0;
+       }
+}
+
 static void
 mpdclient_invoke_error_callback(enum mpd_error error,
                                const char *message)
@@ -43,6 +81,51 @@ mpdclient_invoke_error_callback(enum mpd_error error,
        g_free(allocated);
 }
 
+static void
+mpdclient_invoke_error_callback1(struct mpdclient *c)
+{
+       assert(c != NULL);
+       assert(c->connection != NULL);
+
+       struct mpd_connection *connection = c->connection;
+
+       enum mpd_error error = mpd_connection_get_error(connection);
+       assert(error != MPD_ERROR_SUCCESS);
+
+       mpdclient_invoke_error_callback(error,
+                                       mpd_connection_get_error_message(connection));
+}
+
+static void
+mpdclient_gidle_callback(enum mpd_error error,
+                        gcc_unused enum mpd_server_error server_error,
+                        const char *message, enum mpd_idle events,
+                        void *ctx)
+{
+       struct mpdclient *c = ctx;
+
+       c->idle = false;
+
+       assert(mpdclient_is_connected(c));
+
+       if (error != MPD_ERROR_SUCCESS) {
+               mpdclient_invoke_error_callback(error, message);
+               mpdclient_disconnect(c);
+               mpdclient_lost_callback();
+               return;
+       }
+
+       c->events |= events;
+       mpdclient_update(c);
+
+       mpdclient_idle_callback(c->events);
+
+       c->events = 0;
+
+       if (c->source != NULL)
+               mpdclient_schedule_enter_idle(c);
+}
+
 /****************************************************************************/
 /*** mpdclient functions ****************************************************/
 /****************************************************************************/
@@ -62,19 +145,64 @@ mpdclient_handle_error(struct mpdclient *c)
        mpdclient_invoke_error_callback(error,
                                        mpd_connection_get_error_message(c->connection));
 
-       if (!mpd_connection_clear_error(c->connection))
+       if (!mpd_connection_clear_error(c->connection)) {
                mpdclient_disconnect(c);
+               mpdclient_lost_callback();
+       }
 
        return false;
 }
 
+#ifdef ENABLE_ASYNC_CONNECT
+#ifndef WIN32
+
+static bool
+is_local_socket(const char *host)
+{
+       return *host == '/' || *host == '@';
+}
+
+static bool
+settings_is_local_socket(const struct mpd_settings *settings)
+{
+       const char *host = mpd_settings_get_host(settings);
+       return host != NULL && is_local_socket(host);
+}
+
+#endif
+#endif
+
 struct mpdclient *
-mpdclient_new(void)
+mpdclient_new(const gchar *host, unsigned port,
+             unsigned timeout_ms, const gchar *password)
 {
        struct mpdclient *c = g_new0(struct mpdclient, 1);
+
+#ifdef ENABLE_ASYNC_CONNECT
+       c->settings = mpd_settings_new(host, port, timeout_ms,
+                                      NULL, NULL);
+       if (c->settings == NULL)
+               g_error("Out of memory");
+
+#ifndef WIN32
+       c->settings2 = host == NULL && port == 0 &&
+               settings_is_local_socket(c->settings)
+               ? mpd_settings_new(host, 6600, timeout_ms, NULL, NULL)
+               : NULL;
+#endif
+
+#else
+       c->host = host;
+       c->port = port;
+#endif
+
+       c->timeout_ms = timeout_ms;
+       c->password = password;
+
        playlist_init(&c->playlist);
        c->volume = -1;
        c->events = 0;
+       c->playing = false;
 
        return c;
 }
@@ -86,12 +214,81 @@ mpdclient_free(struct mpdclient *c)
 
        mpdclient_playlist_free(&c->playlist);
 
+#ifdef ENABLE_ASYNC_CONNECT
+       mpd_settings_free(c->settings);
+
+#ifndef WIN32
+       if (c->settings2 != NULL)
+               mpd_settings_free(c->settings2);
+#endif
+#endif
+
        g_free(c);
 }
 
+static char *
+settings_name(const struct mpd_settings *settings)
+{
+       assert(settings != NULL);
+
+       const char *host = mpd_settings_get_host(settings);
+       if (host == NULL)
+               host = "unknown";
+
+       if (host[0] == '/')
+               return g_strdup(host);
+
+       unsigned port = mpd_settings_get_port(settings);
+       if (port == 0 || port == 6600)
+               return g_strdup(host);
+
+       return g_strdup_printf("%s:%u", host, port);
+}
+
+char *
+mpdclient_settings_name(const struct mpdclient *c)
+{
+       assert(c != NULL);
+
+#ifdef ENABLE_ASYNC_CONNECT
+       return settings_name(c->settings);
+#else
+       struct mpd_settings *settings =
+               mpd_settings_new(c->host, c->port, 0, NULL, NULL);
+       if (settings == NULL)
+               return g_strdup("unknown");
+
+       char *name = settings_name(settings);
+       mpd_settings_free(settings);
+       return name;
+#endif
+}
+
+static void
+mpdclient_status_free(struct mpdclient *c)
+{
+       if (c->status == NULL)
+               return;
+
+       mpd_status_free(c->status);
+       c->status = NULL;
+
+       c->volume = -1;
+       c->playing = false;
+}
+
 void
 mpdclient_disconnect(struct mpdclient *c)
 {
+#ifdef ENABLE_ASYNC_CONNECT
+       if (c->async_connect != NULL) {
+               aconnect_cancel(c->async_connect);
+               c->async_connect = NULL;
+       }
+#endif
+
+       mpdclient_cancel_enter_idle(c);
+
        if (c->source != NULL) {
                mpd_glib_free(c->source);
                c->source = NULL;
@@ -104,9 +301,7 @@ mpdclient_disconnect(struct mpdclient *c)
        }
        c->connection = NULL;
 
-       if (c->status)
-               mpd_status_free(c->status);
-       c->status = NULL;
+       mpdclient_status_free(c);
 
        playlist_clear(&c->playlist);
 
@@ -117,65 +312,155 @@ mpdclient_disconnect(struct mpdclient *c)
        c->events |= MPD_IDLE_ALL;
 }
 
-bool
-mpdclient_connect(struct mpdclient *c,
-                 const gchar *host,
-                 gint port,
-                 unsigned timeout_ms,
-                 const gchar *password)
+static bool
+mpdclient_connected(struct mpdclient *c,
+                   struct mpd_connection *connection)
 {
-       /* close any open connection */
-       if (c->connection)
-               mpdclient_disconnect(c);
-
-       /* connect to MPD */
-       c->connection = mpd_connection_new(host, port, timeout_ms);
-       if (c->connection == NULL)
-               g_error("Out of memory");
+       c->connection = connection;
 
-       if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
-               mpdclient_handle_error(c);
+       if (mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS) {
+               mpdclient_invoke_error_callback1(c);
                mpdclient_disconnect(c);
+               mpdclient_failed_callback();
                return false;
        }
 
+#ifdef ENABLE_ASYNC_CONNECT
+       if (c->timeout_ms > 0)
+               mpd_connection_set_timeout(connection, c->timeout_ms);
+#endif
+
        /* send password */
-       if (password != NULL && !mpd_run_password(c->connection, password)) {
-               mpdclient_handle_error(c);
+       if (c->password != NULL &&
+           !mpd_run_password(connection, c->password)) {
+               mpdclient_invoke_error_callback1(c);
                mpdclient_disconnect(c);
+               mpdclient_failed_callback();
                return false;
        }
 
-       c->source = mpd_glib_new(c->connection,
-                                mpdclient_idle_callback, c);
+       c->source = mpd_glib_new(connection,
+                                mpdclient_gidle_callback, c);
+       mpdclient_schedule_enter_idle(c);
 
        ++c->connection_id;
 
+       mpdclient_connected_callback();
        return true;
 }
 
+#ifdef ENABLE_ASYNC_CONNECT
+
+static void
+mpdclient_aconnect_start(struct mpdclient *c,
+                        const struct mpd_settings *settings);
+
+static const struct mpd_settings *
+mpdclient_get_settings(const struct mpdclient *c)
+{
+#ifndef WIN32
+       if (c->connecting2)
+               return c->settings2;
+#endif
+
+       return c->settings;
+}
+
+static void
+mpdclient_connect_success(struct mpd_connection *connection, void *ctx)
+{
+       struct mpdclient *c = ctx;
+       assert(c->async_connect != NULL);
+       c->async_connect = NULL;
+
+       const char *password =
+               mpd_settings_get_password(mpdclient_get_settings(c));
+       if (password != NULL && !mpd_run_password(connection, password)) {
+               mpdclient_error_callback(mpd_connection_get_error_message(connection));
+               mpd_connection_free(connection);
+               mpdclient_failed_callback();
+               return;
+       }
+
+       mpdclient_connected(c, connection);
+}
+
+static void
+mpdclient_connect_error(const char *message, void *ctx)
+{
+       struct mpdclient *c = ctx;
+       assert(c->async_connect != NULL);
+       c->async_connect = NULL;
+
+#ifndef WIN32
+       if (!c->connecting2 && c->settings2 != NULL) {
+               c->connecting2 = true;
+               mpdclient_aconnect_start(c, c->settings2);
+               return;
+       }
+#endif
+
+       mpdclient_error_callback(message);
+       mpdclient_failed_callback();
+}
+
+static const struct aconnect_handler mpdclient_connect_handler = {
+       .success = mpdclient_connect_success,
+       .error = mpdclient_connect_error,
+};
+
+static void
+mpdclient_aconnect_start(struct mpdclient *c,
+                        const struct mpd_settings *settings)
+{
+       aconnect_start(&c->async_connect,
+                      mpd_settings_get_host(settings),
+                      mpd_settings_get_port(settings),
+                      &mpdclient_connect_handler, c);
+}
+
+#endif
+
+void
+mpdclient_connect(struct mpdclient *c)
+{
+       /* close any open connection */
+       mpdclient_disconnect(c);
+
+#ifdef ENABLE_ASYNC_CONNECT
+#ifndef WIN32
+       c->connecting2 = false;
+#endif
+       mpdclient_aconnect_start(c, c->settings);
+#else
+       /* connect to MPD */
+       struct mpd_connection *connection =
+               mpd_connection_new(c->host, c->port, c->timeout_ms);
+       if (connection == NULL)
+               g_error("Out of memory");
+
+       mpdclient_connected(c, connection);
+#endif
+}
+
 bool
 mpdclient_update(struct mpdclient *c)
 {
        struct mpd_connection *connection = mpdclient_get_connection(c);
 
-       c->volume = -1;
-
        if (connection == NULL)
                return false;
 
        /* free the old status */
-       if (c->status)
-               mpd_status_free(c->status);
+       mpdclient_status_free(c);
 
        /* retrieve new status */
        c->status = mpd_run_status(connection);
        if (c->status == NULL)
                return mpdclient_handle_error(c);
 
-       c->update_id = mpd_status_get_update_id(c->status);
-
        c->volume = mpd_status_get_volume(c->status);
+       c->playing = mpd_status_get_state(c->status) == MPD_STATE_PLAY;
 
        /* check if the playlist needs an update */
        if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
@@ -204,21 +489,13 @@ mpdclient_get_connection(struct mpdclient *c)
        if (c->source != NULL && c->idle) {
                c->idle = false;
                mpd_glib_leave(c->source);
+
+               mpdclient_schedule_enter_idle(c);
        }
 
        return c->connection;
 }
 
-void
-mpdclient_put_connection(struct mpdclient *c)
-{
-       assert(c->source == NULL || c->connection != NULL);
-
-       if (c->source != NULL && !c->idle) {
-               c->idle = mpd_glib_enter(c->source);
-       }
-}
-
 static struct mpd_status *
 mpdclient_recv_status(struct mpdclient *c)
 {
@@ -318,39 +595,26 @@ mpdclient_cmd_volume(struct mpdclient *c, gint value)
 bool
 mpdclient_cmd_volume_up(struct mpdclient *c)
 {
+       if (c->volume < 0 || c->volume >= 100)
+               return true;
+
        struct mpd_connection *connection = mpdclient_get_connection(c);
        if (connection == NULL)
                return false;
 
-       if (c->status == NULL ||
-           mpd_status_get_volume(c->status) == -1)
-               return true;
-
-       if (c->volume < 0)
-               c->volume = mpd_status_get_volume(c->status);
-
-       if (c->volume >= 100)
-               return true;
-
        return mpdclient_cmd_volume(c, ++c->volume);
 }
 
 bool
 mpdclient_cmd_volume_down(struct mpdclient *c)
 {
+       if (c->volume <= 0)
+               return true;
+
        struct mpd_connection *connection = mpdclient_get_connection(c);
        if (connection == NULL)
                return false;
 
-       if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
-               return true;
-
-       if (c->volume < 0)
-               c->volume = mpd_status_get_volume(c->status);
-
-       if (c->volume <= 0)
-               return true;
-
        return mpdclient_cmd_volume(c, --c->volume);
 }