chiark / gitweb /
eclient integer callbacks now get errors instead of using generic
[disorder] / lib / eclient.c
index fe03c99..1fc0bfe 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2006, 2007 Richard Kettlewell
+ * Copyright (C) 2006-2008 Richard Kettlewell
  *
  * 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
  * @brief Client code for event-driven programs
  */
 
-#include <config.h>
-#include "types.h"
+#include "common.h"
 
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <sys/un.h>
-#include <string.h>
-#include <stdio.h>
 #include <unistd.h>
 #include <errno.h>
 #include <netdb.h>
-#include <stdlib.h>
-#include <assert.h>
 #include <inttypes.h>
 #include <stddef.h>
+#include <time.h>
 
 #include "log.h"
 #include "mem.h"
@@ -58,6 +54,9 @@
 
 /* TODO: more commands */
 
+/** @brief How often to send data to the server when receiving logs */
+#define LOG_PROD_INTERVAL 10
+
 /* Types *********************************************************************/
 
 /** @brief Client state */
@@ -110,7 +109,7 @@ struct operation {
 
 /** @brief Client structure */
 struct disorder_eclient {
-  const char *ident;
+  char *ident;
   int fd;                               /**< @brief connection to server */
   enum client_state state;              /**< @brief current state */
   int authenticated;                    /**< @brief true when authenicated */
@@ -125,22 +124,32 @@ struct disorder_eclient {
   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 */
+
+  const disorder_eclient_log_callbacks *log_callbacks;
+  /**< @brief log callbacks
+   *
+   * Once disorder_eclient_log() has been issued this is always set.  When we
+   * re-connect it is checked to re-issue the log command.
+   */
   void *log_v;                          /**< @brief user data */
   unsigned long statebits;              /**< @brief latest state */
+
+  time_t last_prod;
+  /**< @brief last time we sent a prod
+   *
+   * When we are receiving log data we send a "prod" byte to the server from
+   * time to time so that we detect broken connections reasonably quickly.  The
+   * server just ignores these bytes.
+   */
+
+  /** @brief Protocol version */
+  int protocol;
 };
 
 /* Forward declarations ******************************************************/
 
-static int start_connect(void *cc,
-                        const struct sockaddr *sa,
-                        socklen_t len,
-                        const char *ident);
+static int start_connect(disorder_eclient *c);
 static void process_line(disorder_eclient *c, char *line);
-static int start_connect(void *cc,
-                        const struct sockaddr *sa,
-                        socklen_t len,
-                        const char *ident);
 static void maybe_connected(disorder_eclient *c);
 static void authbanner_opcallback(disorder_eclient *c,
                                   struct operation *op);
@@ -218,10 +227,6 @@ 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;
 }
 
@@ -308,6 +313,7 @@ static int protocol_error(disorder_eclient *c, struct operation *op,
  */
 void disorder_eclient_polled(disorder_eclient *c, unsigned mode) {
   struct operation *op;
+  time_t now;
   
   D(("disorder_eclient_polled fd=%d state=%s mode=[%s %s]",
      c->fd, states[c->state],
@@ -321,7 +327,12 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) {
 
   if(c->state == state_disconnected) {
     D(("state_disconnected"));
-    with_sockaddr(c, start_connect);
+    /* If there is no password yet then we cannot connect */
+    if(!config->password) {
+      comms_error(c, "no password is configured");
+      return;
+    }
+    start_connect(c);
     /* might now be state_disconnected (on error), state_connecting (slow
      * connect) or state_connected (fast connect).  If state_disconnected then
      * we just rely on a periodic callback from the event loop sometime. */
@@ -376,6 +387,14 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) {
       c->callbacks->report(c->u, 0);
   }
 
+  /* Queue up a byte to send */
+  if(c->state == state_log
+     && c->output.nvec == 0
+     && time(&now) - c->last_prod > LOG_PROD_INTERVAL) {
+    put(c, "x", 1);
+    c->last_prod = now;
+  }
+  
   if(c->state == state_cmdresponse
      || c->state == state_body
      || c->state == state_log) {
@@ -439,14 +458,13 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) {
 }
 
 /** @brief Called to start connecting */
-static int start_connect(void *cc,
-                        const struct sockaddr *sa,
-                        socklen_t len,
-                        const char *ident) {
-  disorder_eclient *c = cc;
+static int start_connect(disorder_eclient *c) {
+  struct sockaddr *sa;
+  socklen_t len;
 
   D(("start_connect"));
-  c->ident = xstrdup(ident);
+  if((len = find_server(&sa, &c->ident)) == (socklen_t)-1)
+    return comms_error(c, "cannot look up server"); /* TODO better error */
   if(c->fd != -1) {
     xclose(c->fd);
     c->fd = -1;
@@ -455,6 +473,7 @@ static int start_connect(void *cc,
     return comms_error(c, "socket: %s", strerror(errno));
   c->eof = 0;
   nonblock(c->fd);
+  cloexec(c->fd);
   if(connect(c->fd, sa, len) < 0) {
     switch(errno) {
     case EINTR:
@@ -465,7 +484,7 @@ static int start_connect(void *cc,
       return 0;
     default:
       /* Signal the error to the caller. */
-      return comms_error(c, "connecting to %s: %s", ident, strerror(errno));
+      return comms_error(c, "connecting to %s: %s", c->ident, strerror(errno));
     }
   } else
     c->state = state_connected;
@@ -500,6 +519,7 @@ static void maybe_connected(disorder_eclient *c) {
 
 /* Authentication ************************************************************/
 
+/** @brief Called with the greeting from the server */
 static void authbanner_opcallback(disorder_eclient *c,
                                   struct operation *op) {
   size_t nonce_len;
@@ -507,7 +527,7 @@ static void authbanner_opcallback(disorder_eclient *c,
   const char *res;
   char **rvec;
   int nrvec;
-  const char *algo = "SHA1";
+  const char *protocol, *algorithm, *challenge;
   
   D(("authbanner_opcallback"));
   if(c->rc / 100 != 2
@@ -518,15 +538,38 @@ static void authbanner_opcallback(disorder_eclient *c,
     disorder_eclient_close(c);
     return;
   }
-  if(nrvec > 1) {
-    algo = *rvec++;
-    --nrvec;
+  switch(nrvec) {
+  case 1:
+    protocol = "1";
+    algorithm = "sha1";
+    challenge = *rvec++;
+    break;
+  case 2:
+    protocol = "1";
+    algorithm = *rvec++;
+    challenge = *rvec++;
+    break;
+  case 3:
+    protocol = *rvec++;
+    algorithm = *rvec++;
+    challenge = *rvec++;
+    break;
+  default:
+    protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
+    disorder_eclient_close(c);
+    return;
+  }
+  c->protocol = atoi(protocol);
+  if(c->protocol < 1 || c->protocol > 2) {
+    protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
+    disorder_eclient_close(c);
+    return;
   }
-  nonce = unhex(rvec[0], &nonce_len);
-  res = authhash(nonce, nonce_len, config->password, algo);
+  nonce = unhex(challenge, &nonce_len);
+  res = authhash(nonce, nonce_len, config->password, algorithm);
   if(!res) {
     protocol_error(c, op, c->rc, "%s: unknown authentication algorithm '%s'",
-                   c->ident, algo);
+                   c->ident, algorithm);
     disorder_eclient_close(c);
     return;
   }
@@ -535,6 +578,7 @@ static void authbanner_opcallback(disorder_eclient *c,
                 (char *)0);
 }
 
+/** @brief Called with the response to the @c user command */
 static void authuser_opcallback(disorder_eclient *c,
                                 struct operation *op) {
   char *r;
@@ -790,38 +834,71 @@ static void stash_command(disorder_eclient *c,
 
 /* Command support ***********************************************************/
 
-/* for commands with a simple string response */ 
+static const char *errorstring(disorder_eclient *c) {
+  char *s;
+
+  byte_xasprintf(&s, "%s: %s: %d", c->ident, c->line, c->rc);
+  return s;
+}
+
+/* for commands with a quoted string response */ 
 static void string_response_opcallback(disorder_eclient *c,
                                        struct operation *op) {
+  disorder_eclient_string_response *completed
+    = (disorder_eclient_string_response *)op->completed;
+    
   D(("string_response_callback"));
-  if(c->rc / 100 == 2) {
-    if(op->completed)
-      ((disorder_eclient_string_response *)op->completed)(op->v, c->line + 4);
+  if(c->rc / 100 == 2 || c->rc == 555) {
+    if(op->completed) {
+      if(c->rc == 555)
+        completed(op->v, NULL, NULL);
+      else if(c->protocol >= 2) {
+        char **rr = split(c->line + 4, 0, SPLIT_QUOTES, 0, 0);
+        
+        if(rr && *rr)
+          completed(op->v, NULL, *rr);
+        else
+          /* TODO error message a is bit lame but generally indicates a server
+           * bug rather than anything the user can address */
+          completed(op->v, "error parsing response", NULL);
+      } else
+        completed(op->v, NULL, c->line + 4);
+    }
   } else
-    protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
+    completed(op->v, errorstring(c), NULL);
 }
 
 /* for commands with a simple integer response */ 
 static void integer_response_opcallback(disorder_eclient *c,
                                         struct operation *op) {
+  disorder_eclient_integer_response *completed
+    = (disorder_eclient_integer_response *)op->completed;
+
   D(("string_response_callback"));
   if(c->rc / 100 == 2) {
-    if(op->completed)
-      ((disorder_eclient_integer_response *)op->completed)
-        (op->v, strtol(c->line + 4, 0, 10));
+    long n;
+    int e;
+
+    e = xstrtol(&n, c->line + 4, 0, 10);
+    if(e)
+      completed(op->v, strerror(e), 0);
+    else
+      completed(op->v, 0, n);
   } else
-    protocol_error(c, op,  c->rc, "%s: %s", c->ident, c->line);
+    completed(op->v, errorstring(c), 0);
 }
 
 /* for commands with no response */
 static void no_response_opcallback(disorder_eclient *c,
                                    struct operation *op) {
+  disorder_eclient_no_response *completed
+    = (disorder_eclient_no_response *)op->completed;
+
   D(("no_response_callback"));
-  if(c->rc / 100 == 2) {
-    if(op->completed)
-      ((disorder_eclient_no_response *)op->completed)(op->v);
-  } else
-    protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
+  if(c->rc / 100 == 2)
+    completed(op->v, NULL);
+  else
+    completed(op->v, errorstring(c));
 }
 
 /* error callback for queue_unmarshall */
@@ -829,6 +906,7 @@ static void eclient_queue_error(const char *msg,
                                 void *u) {
   struct operation *op = u;
 
+  /* TODO don't use protocol_error here */
   protocol_error(op->client, op, -1, "error parsing queue entry: %s", msg);
 }
 
@@ -854,6 +932,7 @@ static void queue_response_opcallback(disorder_eclient *c,
     if(op->completed)
       ((disorder_eclient_queue_response *)op->completed)(op->v, qh);
   } else
+    /* TODO don't use protocol_error here */
     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
 } 
 
@@ -874,12 +953,14 @@ static void playing_response_opcallback(disorder_eclient *c,
       q = 0;
       break;
     default:
+      /* TODO don't use protocol_error here */
       protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
       return;
     }
     if(op->completed)
       ((disorder_eclient_queue_response *)op->completed)(op->v, q);
   } else
+    /* TODO don't use protocol_error here */
     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
 }
 
@@ -893,6 +974,7 @@ static void list_response_opcallback(disorder_eclient *c,
                                                         c->vec.nvec,
                                                         c->vec.vec);
   } else
+    /* TODO don't use protocol_error here */
     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
 }
 
@@ -905,12 +987,14 @@ static void volume_response_opcallback(disorder_eclient *c,
   if(c->rc / 100 == 2) {
     if(op->completed) {
       if(sscanf(c->line + 4, "%d %d", &l, &r) != 2 || l < 0 || r < 0)
+        /* TODO don't use protocol_error here */
         protocol_error(c, op, -1, "%s: invalid volume response: %s",
                        c->ident, c->line);
       else
         ((disorder_eclient_volume_response *)op->completed)(op->v, l, r);
     }
   } else
+    /* TODO don't use protocol_error here */
     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
 }
 
@@ -1188,6 +1272,7 @@ static void rtp_response_opcallback(disorder_eclient *c,
       ((disorder_eclient_list_response *)op->completed)(op->v, nvec, vec);
     }
   } else
+    /* TODO don't use protocol_error here */
     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
 }
 
@@ -1206,6 +1291,86 @@ int disorder_eclient_rtp_address(disorder_eclient *c,
                 "rtp-address", (char *)0);
 }
 
+/** @brief Get the list of users
+ * @param c Client
+ * @param completed Called with list of users
+ * @param v Passed to @p completed
+ *
+ * The user list is not sorted in any particular order.
+ */
+int disorder_eclient_users(disorder_eclient *c,
+                           disorder_eclient_list_response *completed,
+                           void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v,
+                "users", (char *)0);
+}
+
+/** @brief Delete a user
+ * @param c Client
+ * @param completed Called on completion
+ * @param user User to delete
+ * @param v Passed to @p completed
+ */
+int disorder_eclient_deluser(disorder_eclient *c,
+                             disorder_eclient_no_response *completed,
+                             const char *user,
+                             void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, 
+                "deluser", user, (char *)0);
+}
+
+/** @brief Get a user property
+ * @param c Client
+ * @param completed Called on completion
+ * @param user User to look up
+ * @param property Property to look up
+ * @param v Passed to @p completed
+ */
+int disorder_eclient_userinfo(disorder_eclient *c,
+                              disorder_eclient_string_response *completed,
+                              const char *user,
+                              const char *property,
+                              void *v) {
+  return simple(c, string_response_opcallback,  (void (*)())completed, v, 
+                "userinfo", user, property, (char *)0);
+}
+
+/** @brief Modify a user property
+ * @param c Client
+ * @param completed Called on completion
+ * @param user User to modify
+ * @param property Property to modify
+ * @param value New property value
+ * @param v Passed to @p completed
+ */
+int disorder_eclient_edituser(disorder_eclient *c,
+                              disorder_eclient_no_response *completed,
+                              const char *user,
+                              const char *property,
+                              const char *value,
+                              void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, 
+                "edituser", user, property, value, (char *)0);
+}
+
+/** @brief Create a new user
+ * @param c Client
+ * @param completed Called on completion
+ * @param user User to create
+ * @param password Initial password
+ * @param rights Initial rights or NULL
+ * @param v Passed to @p completed
+ */
+int disorder_eclient_adduser(disorder_eclient *c,
+                             disorder_eclient_no_response *completed,
+                             const char *user,
+                             const char *password,
+                             const char *rights,
+                             void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, 
+                "adduser", user, password, rights, (char *)0);
+}
+
 /* Log clients ***************************************************************/
 
 /** @brief Monitor the server log
@@ -1231,6 +1396,7 @@ int disorder_eclient_log(disorder_eclient *c,
     c->log_callbacks->state(c->log_v, c->statebits);
   stash_command(c, 0/*queuejump*/, log_opcallback, 0/*completed*/, v,
                 "log", (char *)0);
+  disorder_eclient_polled(c, 0);
   return 0;
 }
 
@@ -1245,6 +1411,7 @@ static void log_opcallback(disorder_eclient *c,
 /* error callback for log line parsing */
 static void logline_error(const char *msg, void *u) {
   disorder_eclient *c = u;
+  /* TODO don't use protocol_error here */
   protocol_error(c, c->ops, -1, "error parsing log line: %s", msg);
 }
 
@@ -1260,11 +1427,12 @@ static void logline(disorder_eclient *c, const char *line) {
                                          * reported */
   if(sscanf(vec[0], "%"SCNxMAX, &when) != 1) {
     /* probably the wrong side of a format change */
+    /* TODO don't use protocol_error here */
     protocol_error(c, c->ops, -1, "invalid log timestamp '%s'", vec[0]);
     return;
   }
   /* TODO: do something with the time */
-  n = TABLE_FIND(logentry_handlers, struct logentry_handler, name, vec[1]);
+  n = TABLE_FIND(logentry_handlers, name, vec[1]);
   if(n < 0) return;                     /* probably a future command */
   vec += 2;
   nvec -= 2;
@@ -1275,33 +1443,33 @@ 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->completed)
+    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->failed)
+    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,
                            int attribute((unused)) nvec, char **vec) {
-  if(!c->log_callbacks->moved) return;
-  c->log_callbacks->moved(c->log_v, vec[0]);
+  if(c->log_callbacks->moved)
+    c->log_callbacks->moved(c->log_v, vec[0]);
 }
 
 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->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);
 }
@@ -1310,7 +1478,7 @@ static void logentry_queue(disorder_eclient *c,
                            int attribute((unused)) nvec, char **vec) {
   struct queue_entry *q;
 
-  if(!c->log_callbacks->completed) return;
+  if(!c->log_callbacks->queue) return;
   q = xmalloc(sizeof *q);
   if(queue_unmarshall_vec(q, nvec, vec, eclient_queue_error, c))
     return;                             /* bogus */
@@ -1330,28 +1498,28 @@ static void logentry_recent_added(disorder_eclient *c,
 
 static void logentry_recent_removed(disorder_eclient *c,
                                     int attribute((unused)) nvec, char **vec) {
-  if(!c->log_callbacks->recent_removed) return;
-  c->log_callbacks->recent_removed(c->log_v, vec[0]);
+  if(c->log_callbacks->recent_removed)
+    c->log_callbacks->recent_removed(c->log_v, vec[0]);
 }
 
 static void logentry_removed(disorder_eclient *c,
                              int attribute((unused)) nvec, char **vec) {
-  if(!c->log_callbacks->removed) return;
-  c->log_callbacks->removed(c->log_v, vec[0], vec[1]);
+  if(c->log_callbacks->removed)
+    c->log_callbacks->removed(c->log_v, vec[0], vec[1]);
 }
 
 static void logentry_rescanned(disorder_eclient *c,
                                int attribute((unused)) nvec,
                                char attribute((unused)) **vec) {
-  if(!c->log_callbacks->rescanned) return;
-  c->log_callbacks->rescanned(c->log_v);
+  if(c->log_callbacks->rescanned)
+    c->log_callbacks->rescanned(c->log_v);
 }
 
 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->scratched)
+    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);
 }
@@ -1382,8 +1550,8 @@ static void logentry_state(disorder_eclient *c,
       c->statebits &= ~statestrings[n].bit;
       break;
     }
-  if(!c->log_callbacks->state) return;
-  c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
+  if(c->log_callbacks->state) 
+    c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
 }
 
 static void logentry_volume(disorder_eclient *c,