chiark / gitweb /
more control state fiddling, starts nicely when server down now
[disorder] / lib / eclient.c
index 519e6038b04eecf4022caf0b7c010d97e7e9777e..8efefb6f069783169d7e28ab3a7baf19e88fa511 100644 (file)
@@ -17,6 +17,9 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  * USA
  */
+/** @file lib/eclient.c
+ * @brief Client code for event-driven programs
+ */
 
 #include <config.h>
 #include "types.h"
 
 /* Types *********************************************************************/
 
+/** @brief Client state */
 enum client_state {
-  state_disconnected,                   /* not connected */
-  state_connecting,                     /* waiting for connect() */
-  state_connected,                      /* connected but not authenticated */
-  state_idle,                           /* not doing anything */
-  state_cmdresponse,                    /* waiting for command resonse */
-  state_body,                           /* accumulating body */
-  state_log,                            /* monitoring log */
+  state_disconnected,          /**< @brief not connected */
+  state_connecting,            /**< @brief waiting for connect() */
+  state_connected,             /**< @brief connected but not authenticated */
+  state_idle,                  /**< @brief not doing anything */
+  state_cmdresponse,           /**< @brief waiting for command resonse */
+  state_body,                  /**< @brief accumulating body */
+  state_log,                   /**< @brief monitoring log */
 };
 
+/** @brief Names for @ref client_state */
 static const char *const states[] = {
   "disconnected",
   "connecting",
@@ -79,42 +84,50 @@ static const char *const states[] = {
 
 struct operation;                       /* forward decl */
 
+/** @brief Type of an operation callback */
 typedef void operation_callback(disorder_eclient *c, struct operation *op);
 
-/* A pending operation.  This can be either a command or part of the
- * authentication protocol.  In the former case new commands are appended to
- * the list, in the latter case they are inserted at the front. */
+/** @brief A pending operation.
+ *
+ * This can be either a command or part of the authentication protocol.  In the
+ * former case new commands are appended to the list, in the latter case they
+ * are inserted at the front. */
 struct operation {
-  struct operation *next;               /* next operation */
-  char *cmd;                            /* command to send or 0 */
-  operation_callback *opcallback;       /* internal completion callback */
-  void (*completed)();                  /* user completion callback or 0 */
-  void *v;                              /* data for COMPLETED */
-  disorder_eclient *client;             /* owning client */
-  int sent;                             /* true if sent to server */
+  struct operation *next;          /**< @brief next operation */
+  char *cmd;                       /**< @brief command to send or 0 */
+  operation_callback *opcallback;  /**< @brief internal completion callback */
+  void (*completed)();             /**< @brief user completion callback or 0 */
+  void *v;                         /**< @brief data for COMPLETED */
+  disorder_eclient *client;        /**< @brief owning client */
+
+  /** @brief true if sent to server
+   *
+   * This is cleared by disorder_eclient_close(), forcing all queued
+   * commands to be transparently resent.
+   */
+  int sent;
 };
 
+/** @brief Client structure */
 struct disorder_eclient {
   const char *ident;
-  int fd;
-  enum client_state state;              /* current state */
-  int authenticated;                    /* true when authenicated */
-  struct dynstr output;                 /* output buffer */
-  struct dynstr input;                  /* input buffer */
-  int eof;                              /* input buffer is at EOF */
-  /* error reporting callbacks */
-  const disorder_eclient_callbacks *callbacks;
-  void *u;
-  /* operation queuue */
-  struct operation *ops, **opstail;
+  int fd;                               /**< @brief connection to server */
+  enum client_state state;              /**< @brief current state */
+  int authenticated;                    /**< @brief true when authenicated */
+  struct dynstr output;                 /**< @brief output buffer */
+  struct dynstr input;                  /**< @brief input buffer */
+  int eof;                              /**< @brief input buffer is at EOF */
+  const disorder_eclient_callbacks *callbacks; /**< @brief error callbacks */
+  void *u;                              /**< @brief user data */
+  struct operation *ops;                /**< @brief queue of operations */
+  struct operation **opstail;           /**< @brief queue tail */
   /* accumulated response */
-  int rc;                               /* response code */
-  char *line;                           /* complete line */
-  struct vector vec;                    /* body */
-  /* log client callback */
-  const disorder_eclient_log_callbacks *log_callbacks;
-  void *log_v;
-  unsigned long statebits;              /* current state */
+  int rc;                               /**< @brief response code */
+  char *line;                           /**< @brief complete line */
+  struct vector vec;                    /**< @brief body */
+  const disorder_eclient_log_callbacks *log_callbacks; /**< @brief log callbacks */
+  void *log_v;                          /**< @brief user data */
+  unsigned long statebits;              /**< @brief latest state */
 };
 
 /* Forward declarations ******************************************************/
@@ -160,13 +173,18 @@ static void logentry_volume(disorder_eclient *c, int nvec, char **vec);
 
 /* Tables ********************************************************************/
 
-static const struct logentry_handler {
-  const char *name;
-  int min, max;
+/** @brief One possible log entry */
+struct logentry_handler {
+  const char *name;                     /**< @brief Entry name */
+  int min;                              /**< @brief Minimum arguments */
+  int max;                              /**< @brief Maximum arguments */
   void (*handler)(disorder_eclient *c,
                   int nvec,
-                  char **vec);
-} logentry_handlers[] = {
+                  char **vec);          /**< @brief Handler function */
+};
+
+/** @brief Table for parsing log entries */
+static const struct logentry_handler logentry_handlers[] = {
 #define LE(X, MIN, MAX) { #X, MIN, MAX, logentry_##X }
   LE(completed, 1, 1),
   LE(failed, 2, 2),
@@ -183,6 +201,10 @@ static const struct logentry_handler {
 
 /* Setup and teardown ********************************************************/
 
+/** @brief Create a new client
+ *
+ * Does NOT connect the client - connections are made (and re-made) on demand.
+ */
 disorder_eclient *disorder_eclient_new(const disorder_eclient_callbacks *cb,
                                        void *u) {
   disorder_eclient *c = xmalloc(sizeof *c);
@@ -194,9 +216,20 @@ disorder_eclient *disorder_eclient_new(const disorder_eclient_callbacks *cb,
   vector_init(&c->vec);
   dynstr_init(&c->input);
   dynstr_init(&c->output);
+  if(!config->password) {
+    error(0, "no password set");
+    return 0;
+  }
   return c;
 }
 
+/** @brief Disconnect a client
+ * @param c Client to disconnect
+ *
+ * NB that this routine just disconnnects the TCP connection.  It does not
+ * destroy the client!  If you continue to use it then it will attempt to
+ * reconnect.
+ */
 void disorder_eclient_close(disorder_eclient *c) {
   struct operation *op;
 
@@ -207,6 +240,7 @@ void disorder_eclient_close(disorder_eclient *c) {
     xclose(c->fd);
     c->fd = -1;
     c->state = state_disconnected;
+    c->statebits = 0;
   }
   c->output.nvec = 0;
   c->input.nvec = 0;
@@ -215,11 +249,23 @@ void disorder_eclient_close(disorder_eclient *c) {
   /* We'll need to resend all operations */
   for(op = c->ops; op; op = op->next)
     op->sent = 0;
+  /* Drop our use a hint that we're disconnected */
+  if(c->log_callbacks && c->log_callbacks->state)
+    c->log_callbacks->state(c->log_v, c->statebits);
+}
+
+/** @brief Return current state */
+unsigned long disorder_eclient_state(const disorder_eclient *c) {
+  return c->statebits | (c->state > state_connected ? DISORDER_CONNECTED : 0);
 }
 
 /* Error reporting ***********************************************************/
 
-/* called when a connection error occurs */
+/** @brief called when a connection error occurs
+ *
+ * After this called we will be disconnected (by disorder_eclient_close()),
+ * so there will be a reconnection before any commands can be sent.
+ */
 static int comms_error(disorder_eclient *c, const char *fmt, ...) {
   va_list ap;
   char *s;
@@ -233,7 +279,7 @@ static int comms_error(disorder_eclient *c, const char *fmt, ...) {
   return -1;
 }
 
-/* called when the server reports an error */
+/** @brief called when the server reports an error */
 static int protocol_error(disorder_eclient *c, struct operation *op,
                           int code, const char *fmt, ...) {
   va_list ap;
@@ -249,9 +295,18 @@ static int protocol_error(disorder_eclient *c, struct operation *op,
 
 /* State machine *************************************************************/
 
+/** @brief Called when there's something to do
+ * @param c Client
+ * @param mode bitmap of @ref DISORDER_POLL_READ and/or @ref DISORDER_POLL_WRITE.
+ *
+ * This should be called from by your code when the file descriptor is readable
+ * or writable (as requested by the @c poll callback, see @ref
+ * disorder_eclient_callbacks) and in any case from time to time (with @p mode
+ * = 0) to allow for retries to work.
+ */
 void disorder_eclient_polled(disorder_eclient *c, unsigned mode) {
   struct operation *op;
-
+  
   D(("disorder_eclient_polled fd=%d state=%s mode=[%s %s]",
      c->fd, states[c->state],
      mode & DISORDER_POLL_READ ? "READ" : "",
@@ -381,7 +436,7 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) {
   if(c->fd != -1) c->callbacks->poll(c->u, c, c->fd, mode);
 }
 
-/* Called to start connecting */
+/** @brief Called to start connecting */
 static int start_connect(void *cc,
                         const struct sockaddr *sa,
                         socklen_t len,
@@ -415,7 +470,7 @@ static int start_connect(void *cc,
   return 0;
 }
 
-/* Called when maybe connected */
+/** @brief Called when poll triggers while waiting for a connection */
 static void maybe_connected(disorder_eclient *c) {
   /* We either connected, or got an error. */
   int err;
@@ -430,8 +485,14 @@ static void maybe_connected(disorder_eclient *c) {
     comms_error(c, "connecting to %s: %s", c->ident, strerror(err));
     /* sets state_disconnected */
   } else {
+    char *r;
+    
     /* The connection succeeded */
     c->state = state_connected;
+    byte_xasprintf(&r, "connected to %s", c->ident);
+    c->callbacks->report(c->u, r);
+    /* If this is a log client we expect to get a bunch of updates from the
+     * server straight away */
   }
 }
 
@@ -459,6 +520,8 @@ static void authbanner_opcallback(disorder_eclient *c,
 
 static void authuser_opcallback(disorder_eclient *c,
                                 struct operation *op) {
+  char *r;
+
   D(("authuser_opcallback"));
   if(c->rc / 100 != 2) {
     /* Wrong password or something.  We cannot proceed. */
@@ -468,6 +531,8 @@ static void authuser_opcallback(disorder_eclient *c,
   }
   /* OK, we're authenticated now. */
   c->authenticated = 1;
+  byte_xasprintf(&r, "authenticated with %s", c->ident);
+  c->callbacks->report(c->u, r);
   if(c->log_callbacks && !(c->ops && c->ops->opcallback == log_opcallback))
     /* We are a log client, switch to logging mode */
     stash_command(c, 0/*queuejump*/, log_opcallback, 0/*completed*/, c->log_v,
@@ -1069,14 +1134,36 @@ int disorder_eclient_search(disorder_eclient *c,
                 "search", terms, (char *)0);
 }
 
+int disorder_eclient_nop(disorder_eclient *c,
+                         disorder_eclient_no_response *completed,
+                         void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, 
+                "nop", (char *)0);
+}
+
 /* Log clients ***************************************************************/
 
+/** @brief Monitor the server log
+ * @param c Client
+ * @param callbacks Functions to call when anything happens
+ * @param v Passed to @p callbacks functions
+ *
+ * Once a client is being used for logging it cannot be used for anything else.
+ * There is magic in authuser_opcallback() to re-submit the @c log command
+ * after reconnection.
+ *
+ * NB that the @c state callback may be called from within this function,
+ * i.e. not solely later on from the event loop callback.
+ */
 int disorder_eclient_log(disorder_eclient *c,
                          const disorder_eclient_log_callbacks *callbacks,
                          void *v) {
   if(c->log_callbacks) return -1;
   c->log_callbacks = callbacks;
   c->log_v = v;
+  /* Repoort initial state */
+  if(c->log_callbacks->state)
+    c->log_callbacks->state(c->log_v, c->statebits);
   stash_command(c, 0/*queuejump*/, log_opcallback, 0/*completed*/, v,
                 "log", (char *)0);
   return 0;
@@ -1124,13 +1211,19 @@ static void logline(disorder_eclient *c, const char *line) {
 static void logentry_completed(disorder_eclient *c,
                                int attribute((unused)) nvec, char **vec) {
   if(!c->log_callbacks->completed) return;
+  c->statebits &= ~DISORDER_PLAYING;
   c->log_callbacks->completed(c->log_v, vec[0]);
+  if(c->log_callbacks->state)
+    c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
 }
 
 static void logentry_failed(disorder_eclient *c,
                             int attribute((unused)) nvec, char **vec) {
   if(!c->log_callbacks->failed)return;
+  c->statebits &= ~DISORDER_PLAYING;
   c->log_callbacks->failed(c->log_v, vec[0], vec[1]);
+  if(c->log_callbacks->state)
+    c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
 }
 
 static void logentry_moved(disorder_eclient *c,
@@ -1142,7 +1235,10 @@ static void logentry_moved(disorder_eclient *c,
 static void logentry_playing(disorder_eclient *c,
                              int attribute((unused)) nvec, char **vec) {
   if(!c->log_callbacks->playing) return;
+  c->statebits |= DISORDER_PLAYING;
   c->log_callbacks->playing(c->log_v, vec[0], vec[1]);
+  if(c->log_callbacks->state)
+    c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
 }
 
 static void logentry_queue(disorder_eclient *c,
@@ -1182,7 +1278,10 @@ static void logentry_removed(disorder_eclient *c,
 static void logentry_scratched(disorder_eclient *c,
                                int attribute((unused)) nvec, char **vec) {
   if(!c->log_callbacks->scratched) return;
+  c->statebits &= ~DISORDER_PLAYING;
   c->log_callbacks->scratched(c->log_v, vec[0], vec[1]);
+  if(c->log_callbacks->state)
+    c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
 }
 
 static const struct {
@@ -1209,7 +1308,7 @@ static void logentry_state(disorder_eclient *c,
       break;
     }
   if(!c->log_callbacks->state) return;
-  c->log_callbacks->state(c->log_v, c->statebits);
+  c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
 }
 
 static void logentry_volume(disorder_eclient *c,
@@ -1225,6 +1324,45 @@ static void logentry_volume(disorder_eclient *c,
   c->log_callbacks->volume(c->log_v, (int)l, (int)r);
 }
 
+/** @brief Convert @p statebits to a string */
+char *disorder_eclient_interpret_state(unsigned long statebits) {
+  struct dynstr d[1];
+  size_t n;
+
+  static const struct {
+    unsigned long bit;
+    const char *name;
+  } bits[] = {
+    { DISORDER_PLAYING_ENABLED, "playing_enabled" },
+    { DISORDER_RANDOM_ENABLED, "random_enabled" },
+    { DISORDER_TRACK_PAUSED, "track_paused" },
+    { DISORDER_PLAYING, "playing" },
+    { DISORDER_CONNECTED, "connected" },
+  };
+#define NBITS (sizeof bits / sizeof *bits)
+
+  dynstr_init(d);
+  if(!statebits)
+    dynstr_append(d, '0');
+  for(n = 0; n < NBITS; ++n)
+    if(statebits & bits[n].bit) {
+      if(d->nvec)
+        dynstr_append(d, '|');
+      dynstr_append_string(d, bits[n].name);
+      statebits ^= bits[n].bit;
+    }
+  if(statebits) {
+    char s[20];
+
+    if(d->nvec)
+      dynstr_append(d, '|');
+    sprintf(s, "%#lx", statebits);
+    dynstr_append_string(d, s);
+  }
+  dynstr_terminate(d);
+  return d->vec;
+}
+
 /*
 Local Variables:
 c-basic-offset:2