chiark / gitweb /
Fix a few of the things the Clang static analyzer detects:
[disorder] / server / play.c
index a0bd8093622f3647fa8ee004a329e8e3e5bb8daa..99ef4837326d5acf1229b840a381d5a251736f76 100644 (file)
@@ -39,6 +39,7 @@ static int start_child(struct queue_entry *q,
 static int prepare_child(struct queue_entry *q, 
                          const struct pbgc_params *params,
                          void attribute((unused)) *bgdata);
+static void ensure_next_scratch(ev_source *ev);
 
 /** @brief File descriptor of our end of the socket to the speaker */
 static int speaker_fd = -1;
@@ -59,8 +60,7 @@ static int speaker_terminated(ev_source attribute((unused)) *ev,
                              int attribute((unused)) status,
                              const struct rusage attribute((unused)) *rusage,
                              void attribute((unused)) *u) {
-  fatal(0, "speaker subprocess %s",
-       wstat(status));
+  disorder_fatal(0, "speaker subprocess %s", wstat(status));
 }
 
 /** @brief Called when we get a message from the speaker process */
@@ -83,16 +83,34 @@ static int speaker_readable(ev_source *ev, int fd,
   case SM_FINISHED:                    /* scratched the playing track */
   case SM_STILLBORN:                   /* scratched too early */
   case SM_UNKNOWN:                     /* scratched WAY too early */
-    if(playing && !strcmp(sm.id, playing->id))
+    if(playing && !strcmp(sm.id, playing->id)) {
+      if((playing->state == playing_unplayed
+          || playing->state == playing_started)
+         && sm.type == SM_FINISHED)
+        playing->state = playing_ok;
       finished(ev);
+    }
     break;
   case SM_PLAYING:
     /* track ID is playing, DATA seconds played */
     D(("SM_PLAYING %s %ld", sm.id, sm.data));
     playing->sofar = sm.data;
     break;
+  case SM_ARRIVED: {
+    /* track ID is now prepared */
+    struct queue_entry *q;
+    for(q = qhead.next; q != &qhead && strcmp(q->id, sm.id); q = q->next)
+      ;
+    if(q && q->preparing) {
+      q->preparing = 0;
+      q->prepared = 1;
+      /* We might be waiting to play the now-prepared track */
+      play(ev);
+    }
+    break;
+  }
   default:
-    error(0, "unknown speaker message type %d", sm.type);
+    disorder_error(0, "unknown speaker message type %d", sm.type);
   }
   return 0;
 }
@@ -104,7 +122,7 @@ void speaker_setup(ev_source *ev) {
   struct speaker_message sm;
 
   if(socketpair(PF_UNIX, SOCK_DGRAM, 0, sp) < 0)
-    fatal(errno, "error calling socketpair");
+    disorder_fatal(errno, "error calling socketpair");
   if(!(pid = xfork())) {
     exitfn = _exit;
     ev_signal_atfork(ev);
@@ -124,7 +142,7 @@ void speaker_setup(ev_source *ev) {
           log_default == &log_syslog ? "--syslog" : "--no-syslog",
           (char *)0);
 #endif
-    fatal(errno, "error invoking %s", SPEAKER);
+    disorder_fatal(errno, "error invoking %s", SPEAKER);
   }
   ev_child(ev, pid, 0, speaker_terminated, 0);
   speaker_fd = sp[1];
@@ -134,7 +152,7 @@ void speaker_setup(ev_source *ev) {
   speaker_recv(speaker_fd, &sm);
   nonblock(speaker_fd);
   if(ev_fd(ev, ev_read, speaker_fd, speaker_readable, 0, "speaker read") < 0)
-    fatal(0, "error registering speaker socket fd");
+    disorder_fatal(0, "error registering speaker socket fd");
 }
 
 /** @brief Tell the speaker to reload its configuration */
@@ -196,7 +214,9 @@ static void finished(ev_source *ev) {
  * some time before the speaker reports it as finished) or when a non-raw
  * (i.e. non-speaker) player terminates.  In the latter case it's imaginable
  * that the OS has buffered the last few samples.
- * 
+ *
+ * NB.  The finished track might NOT be in the queue (yet) - it might be a
+ * pre-chosen scratch.
  */
 static int player_finished(ev_source *ev,
                           pid_t pid,
@@ -233,7 +253,7 @@ static int player_finished(ev_source *ev,
   /* Regardless we always report and record the status and do cleanup for
    * prefork calls. */
   if(status)
-    error(0, "player for %s %s", q->track, wstat(status));
+    disorder_error(0, "player for %s %s", q->track, wstat(status));
   if(q->type & DISORDER_PLAYER_PREFORK)
     play_cleanup(q->pl, q->data);
   q->wstat = status;
@@ -267,7 +287,7 @@ static const struct stringlist *find_player(const struct queue_entry *q) {
  * @return @ref START_OK, @ref START_HARDFAIL or @ref START_SOFTFAIL
  *
  * This makes @p actually start playing.  It calls prepare() if necessary and
- * either sends an @ref SM_START command or invokes the player itself in a
+ * either sends an @ref SM_PLAY command or invokes the player itself in a
  * subprocess.
  *
  * It's up to the caller to set @ref playing and @c playing->state (this might
@@ -327,7 +347,7 @@ static int start_child(struct queue_entry *q,
     if(*params->waitdevice) {
       n = ao_driver_id(params->waitdevice);
       if(n == -1)
-        fatal(0, "invalid libao driver: %s", params->waitdevice);
+        disorder_fatal(0, "invalid libao driver: %s", params->waitdevice);
     } else
       n = ao_default_driver_id();
     /* Make up a format. */
@@ -372,7 +392,7 @@ int prepare(ev_source *ev,
   if(q->pid >= 0)
     return START_OK;
   /* If the track is already prepared, do nothing */
-  if(q->prepared)
+  if(q->prepared || q->preparing)
     return START_OK;
   /* Find the player plugin */
   if(!(player = find_player(q)) < 0) 
@@ -381,10 +401,12 @@ int prepare(ev_source *ev,
   q->type = play_get_type(q->pl);
   if((q->type & DISORDER_PLAYER_TYPEMASK) != DISORDER_PLAYER_RAW)
     return START_OK;                    /* Not a raw player */
-  const int rc = play_background(ev, player, q, prepare_child, NULL);
+  int rc = play_background(ev, player, q, prepare_child, NULL);
   if(rc == START_OK) {
     ev_child(ev, q->pid, 0, player_finished, q);
-    q->prepared = 1;
+    q->preparing = 1;
+    /* Actually the track is still "in flight" */
+    rc = START_SOFTFAIL;
   }
   return rc;
 }
@@ -404,7 +426,7 @@ static int prepare_child(struct queue_entry *q,
   /* np will be the pipe to disorder-normalize */
   int np[2];
   if(socketpair(PF_UNIX, SOCK_STREAM, 0, np) < 0)
-    fatal(errno, "error calling socketpair");
+    disorder_fatal(errno, "error calling socketpair");
   /* Beware of the Leopard!  On OS X 10.5.x, the order of the shutdown
    * calls here DOES MATTER.  If you do the SHUT_WR first then the SHUT_RD
    * fails with "Socket is not connected".  I think this is a bug but
@@ -430,15 +452,15 @@ static int prepare_child(struct queue_entry *q,
                "%s/speaker/socket", config->home);
       int sfd = xsocket(PF_UNIX, SOCK_STREAM, 0);
       if(connect(sfd, (const struct sockaddr *)&addr, sizeof addr) < 0)
-        fatal(errno, "connecting to %s", addr.sun_path);
+        disorder_fatal(errno, "connecting to %s", addr.sun_path);
       /* Send the ID, with a NATIVE-ENDIAN 32 bit length */
       uint32_t l = strlen(q->id);
       if(write(sfd, &l, sizeof l) < 0
          || write(sfd, q->id, l) < 0)
-        fatal(errno, "writing to %s", addr.sun_path);
+        disorder_fatal(errno, "writing to %s", addr.sun_path);
       /* Await the ack */
       if (read(sfd, &l, 1) < 0) 
-        fatal(errno, "reading ack from %s", addr.sun_path);
+        disorder_fatal(errno, "reading ack from %s", addr.sun_path);
       /* Plumbing */
       xdup2(np[0], 0);
       xdup2(sfd, 1);
@@ -451,7 +473,7 @@ static int prepare_child(struct queue_entry *q,
              log_default == &log_syslog ? "--syslog" : "--no-syslog",
              "--config", configfile,
              (char *)0);
-      fatal(errno, "executing disorder-normalize");
+      disorder_fatal(errno, "executing disorder-normalize");
       /* End of the great-grandchild of disorderd */
     }
     /* Back in the grandchild of disorderd */
@@ -468,7 +490,7 @@ static int prepare_child(struct queue_entry *q,
   char buffer[64];
   snprintf(buffer, sizeof buffer, "DISORDER_RAW_FD=%d", np[1]);
   if(putenv(buffer) < 0)
-    fatal(errno, "error calling putenv");
+    disorder_fatal(errno, "error calling putenv");
   /* Close all the FDs we don't need */
   xclose(np[0]);
   /* Start the decoder itself */
@@ -515,7 +537,7 @@ static void chosen_random_track(ev_source *ev,
   if(!track)
     return;
   /* Add the track to the queue */
-  q = queue_add(track, 0, WHERE_END, origin_random);
+  q = queue_add(track, 0, WHERE_END, NULL, origin_random);
   D(("picked %p (%s) at random", (void *)q, q->track));
   queue_write();
   /* Maybe a track can now be played */
@@ -610,6 +632,8 @@ void play(ev_source *ev) {
      * potentially be a just-added random track. */
     if(qhead.next != &qhead)
       prepare(ev, qhead.next);
+    /* Make sure there is a prepared scratch */
+    ensure_next_scratch(ev);
     break;
   }
 }
@@ -657,12 +681,27 @@ void disable_random(const char *who) {
 
 /* Scratching --------------------------------------------------------------- */
 
+/** @brief Track to play next time something is scratched */
+static struct queue_entry *next_scratch;
+
+/** @brief Ensure there isa prepared scratch */
+static void ensure_next_scratch(ev_source *ev) {
+  if(next_scratch)                      /* There's one already */
+    return;
+  if(!config->scratch.n)                /* There are no scratches */
+    return;
+  int r = rand() * (double)config->scratch.n / (RAND_MAX + 1.0);
+  next_scratch = queue_add(config->scratch.s[r], NULL,
+                           WHERE_NOWHERE, NULL, origin_scratch);
+  if(ev)
+    prepare(ev, next_scratch);
+}
+
 /** @brief Scratch a track
- * @param User responsible (or NULL)
- * @param Track ID (or NULL for current)
+ * @param who User responsible (or NULL)
+ * @param id Track ID (or NULL for current)
  */
 void scratch(const char *who, const char *id) {
-  struct queue_entry *q;
   struct speaker_message sm;
 
   D(("scratch playing=%p state=%d id=%s playing->id=%s",
@@ -693,11 +732,20 @@ void scratch(const char *who, const char *id) {
       speaker_send(speaker_fd, &sm);
       D(("sending SM_CANCEL for %s", playing->id));
     }
-    /* put a scratch track onto the front of the queue (but don't
-     * bother if playing is disabled) */
-    if(playing_is_enabled() && config->scratch.n) {
-      int r = rand() * (double)config->scratch.n / (RAND_MAX + 1.0);
-      q = queue_add(config->scratch.s[r], who, WHERE_START, origin_scratch);
+    /* If playing is enabled then add a scratch to the queue.  Having a scratch
+     * appear in the queue when further play is disabled is weird and
+     * contradicts implicit assumptions made elsewhere, so we try to avoid
+     * it. */
+    if(playing_is_enabled()) {
+      /* Try to make sure there is a scratch */
+      ensure_next_scratch(NULL);
+      /* Insert it at the head of the queue */
+      if(next_scratch){
+        next_scratch->submitter = who;
+        queue_insert_entry(&qhead, next_scratch);
+        eventlog_raw("queue", queue_marshall(next_scratch), (const char *)0);
+        next_scratch = NULL;
+      }
     }
     notify_scratch(playing->track, playing->submitter, who,
                   xtime(0) - playing->played);
@@ -743,11 +791,11 @@ int pause_playing(const char *who) {
   case DISORDER_PLAYER_STANDALONE:
     if(!(playing->type & DISORDER_PLAYER_PAUSES)) {
     default:
-      error(0,  "cannot pause because player is not powerful enough");
+      disorder_error(0,  "cannot pause because player is not powerful enough");
       return -1;
     }
     if(play_pause(playing->pl, &played, playing->data)) {
-      error(0, "player indicates it cannot pause");
+      disorder_error(0, "player indicates it cannot pause");
       return -1;
     }
     xtime(&playing->lastpaused);
@@ -760,7 +808,8 @@ int pause_playing(const char *who) {
     speaker_send(speaker_fd, &sm);
     break;
   }
-  if(who) info("paused by %s", who);
+  if(who)
+    disorder_info("paused by %s", who);
   notify_pause(playing->track, who);
   paused = 1;
   if(playing->state == playing_started)
@@ -792,7 +841,7 @@ void resume_playing(const char *who) {
     speaker_send(speaker_fd, &sm);
     break;
   }
-  if(who) info("resumed by %s", who);
+  if(who) disorder_info("resumed by %s", who);
   notify_resume(playing->track, who);
   if(playing->state == playing_paused)
     playing->state = playing_started;