chiark / gitweb /
Implement most of the playlist commands in the server. playlist-set
authorRichard Kettlewell <rjk@greenend.org.uk>
Sun, 20 Jul 2008 14:56:51 +0000 (15:56 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sun, 20 Jul 2008 14:56:51 +0000 (15:56 +0100)
is not yet implemented.  None of this is tested yet.

lib/configuration.c
lib/configuration.h
lib/trackdb-playlists.c
server/server.c

index 2e75ecbaaa440832e5e4aa111fa175d7c9a96308..934e52b698535fd039d86082376a4dbf048836d4 100644 (file)
@@ -952,6 +952,7 @@ static const struct conf conf[] = {
   { C(noticed_history),  &type_integer,          validate_positive },
   { C(password),         &type_string,           validate_any },
   { C(player),           &type_stringlist_accum, validate_player },
+  { C(playlist_lock_timeout), &type_integer,     validate_positive },
   { C(playlist_max) ,    &type_integer,          validate_positive },
   { C(plugins),          &type_string_accum,     validate_isdir },
   { C(prefsync),         &type_integer,          validate_positive },
@@ -1200,6 +1201,7 @@ static struct config *config_default(void) {
   c->new_bias_age = 7 * 86400;         /* 1 week */
   c->new_bias = 9000000;               /* 100 times the base weight */
   c->playlist_max = INT_MAX;            /* effectively no limit */
+  c->playlist_lock_timeout = 10;        /* 10s */
   /* Default stopwords */
   if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
     exit(1);
index d269beb84cdb1c4626d610e80c3f7c13778ae196..b94b684f2345630c228ed56abda8f6a0d4a87aaf 100644 (file)
@@ -188,6 +188,9 @@ struct config {
   /** @brief Maximum size of a playlist */
   long playlist_max;
 
+  /** @brief Maximum lifetime of a playlist lock */
+  long playlist_lock_timeout;
+
 /* These values had better be non-negative */
 #define BACKEND_ALSA 0                 /**< Use ALSA (Linux only) */
 #define BACKEND_COMMAND 1              /**< Execute a command */
index 1492f8c4c13beebbf3ab34aac8655d471ff132a0..77f7c132dcbd14a1a951d28f0fae195ae4eaeadd 100644 (file)
@@ -147,7 +147,7 @@ static int playlist_may_write(const char *name,
  *
  * Possible return values:
  * - @c 0 on success
- * - @c DB_NOTFOUND if the playlist doesn't exist
+ * - @c ENOENT if the playlist doesn't exist
  * - @c EINVAL if the playlist name is invalid
  * - @c EACCES if the playlist cannot be read by @p who
  */
@@ -165,6 +165,9 @@ int trackdb_playlist_get(const char *name,
   WITH_TRANSACTION(trackdb_playlist_get_tid(name, who,
                                             tracksp, ntracksp, sharep,
                                             tid));
+  /* Don't expose libdb error codes too much */
+  if(e == DB_NOTFOUND)
+    e = ENOENT;
   return e;
 }
 
@@ -237,6 +240,10 @@ static int trackdb_playlist_get_tid(const char *name,
  * none, and the default sharing is private (if it is an owned one) or shared
  * (otherwise).
  *
+ * If neither @c tracks nor @c share are set then we only do an access check.
+ * The database is never modified (even to create the playlist) in this
+ * situation.
+ *
  * Possible return values:
  * - @c 0 on success
  * - @c EINVAL if the playlist name is invalid
@@ -312,6 +319,9 @@ static int trackdb_playlist_set_tid(const char *name,
   }
   if(!playlist_may_write(name, who, s))
     return EACCES;
+  /* If no change was requested then don't even create */
+  if(!share && !tracks)
+    return 0;
   /* Set the new values */
   if(share)
     kvp_set(&k, "share", share);
@@ -418,7 +428,7 @@ static int trackdb_playlist_list_tid(const char *who,
  * - @c 0 on success
  * - @c EINVAL if the playlist name is invalid
  * - @c EACCES if the playlist cannot be modified by @p who
- * - @c DB_NOTFOUND if the playlist doesn't exist
+ * - @c ENOENT if the playlist doesn't exist
  */
 int trackdb_playlist_delete(const char *name,
                             const char *who) {
@@ -431,6 +441,8 @@ int trackdb_playlist_delete(const char *name,
   }
   /* We've checked as much as we can for now, now go and attempt the change */
   WITH_TRANSACTION(trackdb_playlist_delete_tid(name, who, tid));
+  if(e == DB_NOTFOUND)
+    e = ENOENT;
   return e;
 }
 
index c4c889a448f909d6b9bcff26dc14d90d6760735b..f95176c1cdd9cd6338d2b21cc2448ce9fff1d20d 100644 (file)
@@ -74,6 +74,10 @@ struct conn {
   struct conn *next;
   /** @brief True if pending rescan had 'wait' set */
   int rescan_wait;
+  /** @brief Playlist that this connection locks */
+  const char *locked_playlist;
+  /** @brief When that playlist was locked */
+  time_t locked_when;
 };
 
 /** @brief Linked list of connections */
@@ -1026,21 +1030,25 @@ static int c_resolve(struct conn *c,
   return 1;
 }
 
-static int c_tags(struct conn *c,
-                 char attribute((unused)) **vec,
-                 int attribute((unused)) nvec) {
-  char **tags = trackdb_alltags();
-  
-  sink_printf(ev_writer_sink(c->w), "253 Tag list follows\n");
-  while(*tags) {
+static int list_response(struct conn *c,
+                         const char *reply,
+                         char **list) {
+  sink_printf(ev_writer_sink(c->w), "253 %s\n", reply);
+  while(*list) {
     sink_printf(ev_writer_sink(c->w), "%s%s\n",
-               **tags == '.' ? "." : "", *tags);
-    ++tags;
+               **list == '.' ? "." : "", *list);
+    ++list;
   }
   sink_writes(ev_writer_sink(c->w), ".\n");
   return 1;                            /* completed */
 }
 
+static int c_tags(struct conn *c,
+                 char attribute((unused)) **vec,
+                 int attribute((unused)) nvec) {
+  return list_response(c, "Tag list follows", trackdb_alltags());
+}
+
 static int c_set_global(struct conn *c,
                        char **vec,
                        int attribute((unused)) nvec) {
@@ -1307,17 +1315,7 @@ static int c_userinfo(struct conn *c,
 static int c_users(struct conn *c,
                   char attribute((unused)) **vec,
                   int attribute((unused)) nvec) {
-  /* TODO de-dupe with c_tags */
-  char **users = trackdb_listusers();
-
-  sink_writes(ev_writer_sink(c->w), "253 User list follows\n");
-  while(*users) {
-    sink_printf(ev_writer_sink(c->w), "%s%s\n",
-               **users == '.' ? "." : "", *users);
-    ++users;
-  }
-  sink_writes(ev_writer_sink(c->w), ".\n");
-  return 1;                            /* completed */
+  return list_response(c, "User list follows", trackdb_listusers());
 }
 
 /** @brief Base64 mapping table for confirmation strings
@@ -1575,6 +1573,132 @@ static int c_schedule_add(struct conn *c,
   return 1;
 }
 
+static int playlist_response(struct conn *c,
+                             int err) {
+  switch(err) {
+  case 0:
+    assert(!"cannot cope with success");
+  case EACCES:
+    sink_writes(ev_writer_sink(c->w), "550 Access denied\n");
+    break;
+  case EINVAL:
+    sink_writes(ev_writer_sink(c->w), "550 Invalid playlist name\n");
+    break;
+  case ENOENT:
+    sink_writes(ev_writer_sink(c->w), "555 No such playlist\n");
+    break;
+  default:
+    sink_writes(ev_writer_sink(c->w), "550 Error accessing playlist\n");
+    break;
+  }
+  return 1;
+}
+
+static int c_playlist_get(struct conn *c,
+                         char **vec,
+                         int attribute((unused)) nvec) {
+  char **tracks;
+  int err;
+
+  if(!(err = trackdb_playlist_get(vec[0], c->who, &tracks, 0, 0)))
+    return list_response(c, "Playlist contents follows", tracks);
+  else
+    return playlist_response(c, err);
+}
+
+static int c_playlist_set(struct conn *c,
+                         char **vec,
+                         int attribute((unused)) nvec) {
+  /* TODO */
+}
+
+static int c_playlist_get_share(struct conn *c,
+                                char **vec,
+                                int attribute((unused)) nvec) {
+  char *share;
+  int err;
+
+  if(!(err = trackdb_playlist_get(vec[0], c->who, 0, 0, &share))) {
+    sink_printf(ev_writer_sink(c->w), "252 %s", quoteutf8(share));
+    return 1;
+  } else
+    return playlist_response(c, err);
+}
+
+static int c_playlist_set_share(struct conn *c,
+                                char **vec,
+                                int attribute((unused)) nvec) {
+  int err;
+
+  if(!(err = trackdb_playlist_set(vec[0], c->who, 0, 0, vec[1]))) {
+    sink_printf(ev_writer_sink(c->w), "250 OK");
+    return 1;
+  } else
+    return playlist_response(c, err);
+}
+
+static int c_playlists(struct conn *c,
+                       char attribute((unused)) **vec,
+                       int attribute((unused)) nvec) {
+  char **p;
+
+  trackdb_playlist_list(c->who, &p, 0);
+  return list_response(c, "List of playlists follows", p);
+}
+
+static int c_playlist_delete(struct conn *c,
+                             char **vec,
+                             int attribute((unused)) nvec) {
+  int err;
+  
+  if(!(err = trackdb_playlist_delete(vec[0], c->who))) {
+    sink_writes(ev_writer_sink(c->w), "250 OK");
+    return 1;
+  } else
+    return playlist_response(c, err);
+}
+
+static int c_playlist_lock(struct conn *c,
+                           char **vec,
+                           int attribute((unused)) nvec) {
+  int err;
+  struct conn *cc;
+
+  /* Check we're allowed to modify this playlist */
+  if((err = trackdb_playlist_set(vec[0], c->who, 0, 0, 0)))
+    return playlist_response(c, err);
+  /* If we hold a lock don't allow a new one */
+  if(c->locked_playlist) {
+    sink_writes(ev_writer_sink(c->w), "550 Already holding a lock\n");
+    return 1;
+  }
+  /* See if some other connection locks the same playlist */
+  for(cc = connections; cc; cc = cc->next)
+    if(cc->locked_playlist && !strcmp(cc->locked_playlist, vec[0]))
+      break;
+  if(cc) {
+    /* TODO: implement config->playlist_lock_timeout */
+    sink_writes(ev_writer_sink(c->w), "550 Already locked\n");
+    return 1;
+  }
+  c->locked_playlist = xstrdup(vec[0]);
+  time(&c->locked_when);
+  sink_writes(ev_writer_sink(c->w), "250 Acquired lock\n");
+  return 1;
+}
+
+static int c_playlist_unlock(struct conn *c,
+                             char **vec,
+                             int attribute((unused)) nvec) {
+  if(!c->locked_playlist) {
+    sink_writes(ev_writer_sink(c->w), "550 Not holding a lock\n");
+    return 1;
+  }
+  c->locked_playlist = 0;
+  sink_writes(ev_writer_sink(c->w), "250 Released lock\n");
+  return 1;
+}
+
 static const struct command {
   /** @brief Command name */
   const char *name;
@@ -1620,6 +1744,14 @@ static const struct command {
   { "pause",          0, 0,       c_pause,          RIGHT_PAUSE },
   { "play",           1, 1,       c_play,           RIGHT_PLAY },
   { "playing",        0, 0,       c_playing,        RIGHT_READ },
+  { "playlist-delete",    1, 1,   c_playlist_delete,    RIGHT_PLAY },
+  { "playlist-get",       1, 1,   c_playlist_get,       RIGHT_PLAY },
+  { "playlist-get-share", 1, 1,   c_playlist_get_share, RIGHT_PLAY },
+  { "playlist-lock",      1, 1,   c_playlist_lock,      RIGHT_PLAY },
+  { "playlist-set",       1, 1,   c_playlist_set,       RIGHT_PLAY },
+  { "playlist-set-share", 2, 2,   c_playlist_set_share, RIGHT_PLAY },
+  { "playlist-unlock",    0, 0,   c_playlist_unlock,    RIGHT_PLAY },
+  { "playlists",          0, 0,   c_playlists,          RIGHT_PLAY },
   { "prefs",          1, 1,       c_prefs,          RIGHT_READ },
   { "queue",          0, 0,       c_queue,          RIGHT_READ },
   { "random-disable", 0, 0,       c_random_disable, RIGHT_GLOBAL_PREFS },