chiark / gitweb /
Low level playlist database operations: read, createwrite, list, delete.
authorRichard Kettlewell <rjk@greenend.org.uk>
Sat, 19 Jul 2008 17:47:08 +0000 (18:47 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sat, 19 Jul 2008 17:47:08 +0000 (18:47 +0100)
Totally untested, lots more to do.

lib/Makefile.am
lib/configuration.c
lib/configuration.h
lib/trackdb-int.h
lib/trackdb-playlists.c [new file with mode: 0644]
lib/trackdb.c
lib/trackdb.h

index f2943cc62b76aab98cd3737e3ae298d0195e08ba..9df944f90bc7d071f446a79e199caa78a310c656 100644 (file)
@@ -80,6 +80,7 @@ libdisorder_a_SOURCES=charset.c charset.h             \
        table.c table.h                                 \
        timeval.h                                       \
        $(TRACKDB) trackdb.h trackdb-int.h              \
+       trackdb-playlists.c                             \
        trackname.c trackorder.c trackname.h            \
        tracksort.c                                     \
        url.h url.c                                     \
index 540084cd0654cedd2f4beef276037d84feeabf99..2e75ecbaaa440832e5e4aa111fa175d7c9a96308 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_max) ,    &type_integer,          validate_positive },
   { C(plugins),          &type_string_accum,     validate_isdir },
   { C(prefsync),         &type_integer,          validate_positive },
   { C(queue_pad),        &type_integer,          validate_positive },
@@ -1198,6 +1199,7 @@ static struct config *config_default(void) {
   c->reminder_interval = 600;          /* 10m */
   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 */
   /* Default stopwords */
   if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
     exit(1);
index ab8917a87d486db428c52e95485bdbfa237de3bf..d269beb84cdb1c4626d610e80c3f7c13778ae196 100644 (file)
@@ -185,6 +185,9 @@ struct config {
    */
   int api;
 
+  /** @brief Maximum size of a playlist */
+  long playlist_max;
+
 /* These values had better be non-negative */
 #define BACKEND_ALSA 0                 /**< Use ALSA (Linux only) */
 #define BACKEND_COMMAND 1              /**< Execute a command */
index 32a6ca4fc2436490ead8ef76fb196b861e6767d6..943140630f5e294974c367b5b381fd4511e0f26f 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <db.h>
 
+#include "trackdb.h"
 #include "kvp.h"
 
 struct vector;                          /* forward declaration */
@@ -37,6 +38,7 @@ extern DB *trackdb_noticeddb;
 extern DB *trackdb_globaldb;
 extern DB *trackdb_usersdb;
 extern DB *trackdb_scheduledb;
+extern DB *trackdb_playlistsdb;
 
 DBC *trackdb_opencursor(DB *db, DB_TXN *tid);
 /* open a transaction */
@@ -152,6 +154,7 @@ int trackdb_get_global_tid(const char *name,
 
 char **parsetags(const char *s);
 int tag_intersection(char **a, char **b);
+int valid_username(const char *user);
 
 #endif /* TRACKDB_INT_H */
 
diff --git a/lib/trackdb-playlists.c b/lib/trackdb-playlists.c
new file mode 100644 (file)
index 0000000..1492f8c
--- /dev/null
@@ -0,0 +1,464 @@
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+/** @file lib/trackdb-playlists.c
+ * @brief Track database playlist support
+ *
+ * This file implements reading and modification of playlists, including access
+ * control, but not locking or event logging (at least yet).
+ */
+#include "common.h"
+
+#include <errno.h>
+
+#include "trackdb-int.h"
+#include "mem.h"
+#include "log.h"
+#include "configuration.h"
+#include "vector.h"
+
+static int trackdb_playlist_get_tid(const char *name,
+                                    const char *who,
+                                    char ***tracksp,
+                                    int *ntracksp,
+                                    char **sharep,
+                                    DB_TXN *tid);
+static int trackdb_playlist_set_tid(const char *name,
+                                    const char *who,
+                                    char **tracks,
+                                    int ntracks,
+                                    const char *share,
+                                    DB_TXN *tid);
+static int trackdb_playlist_list_tid(const char *who,
+                                     char ***playlistsp,
+                                     int *nplaylistsp,
+                                     DB_TXN *tid);
+static int trackdb_playlist_delete_tid(const char *name,
+                                       const char *who,
+                                       DB_TXN *tid);
+
+/** @brief Parse a playlist name
+ * @param name Playlist name
+ * @param ownerp Where to put owner, or NULL
+ * @param sharep Where to put default sharing, or NULL
+ * @return 0 on success, -1 on error
+ *
+ * Playlists take the form USER.PLAYLIST or just PLAYLIST.  The PLAYLIST part
+ * is alphanumeric and nonempty.  USER is a username (see valid_username()).
+ */
+int playlist_parse_name(const char *name,
+                        char **ownerp,
+                        char **sharep) {
+  const char *dot = strchr(name, '.'), *share;
+  char *owner;
+
+  if(dot) {
+    /* Owned playlist */
+    owner = xstrndup(name, dot - name);
+    if(!valid_username(owner))
+      return -1;
+    if(!valid_username(dot + 1))
+      return -1;
+    share = "private";
+  } else {
+    /* Shared playlist */
+    if(!valid_username(name))
+      return -1;
+    owner = 0;
+    share = "public";
+  }
+  if(ownerp)
+    *ownerp = owner;
+  if(sharep)
+    *sharep = xstrdup(share);
+  return 0;
+}
+
+/** @brief Check read access rights
+ * @param name Playlist name
+ * @param who Who wants to read
+ * @param share Playlist share status
+ */
+static int playlist_may_read(const char *name,
+                             const char *who,
+                             const char *share) {
+  char *owner;
+  
+  if(!playlist_parse_name(name, &owner, 0))
+    return 0;
+  /* Anyone can read shared playlists */
+  if(!owner)
+    return 1;
+  /* You can always read playlists you own */
+  if(!strcmp(owner, who))
+    return 1;
+  /* You can read public playlists */
+  if(!strcmp(share, "public"))
+    return 1;
+  /* Anything else is prohibited */
+  return 0;
+}
+
+/** @brief Check modify access rights
+ * @param name Playlist name
+ * @param who Who wants to modify
+ * @param share Playlist share status
+ */
+static int playlist_may_write(const char *name,
+                              const char *who,
+                              const char attribute((unused)) *share) {
+  char *owner;
+  
+  if(!playlist_parse_name(name, &owner, 0))
+    return 0;
+  /* Anyone can modify shared playlists */
+  if(!owner)
+    return 1;
+  /* You can always modify playlists you own */
+  if(!strcmp(owner, who))
+    return 1;
+  /* Anything else is prohibited */
+  return 0;
+}
+
+/** @brief Get playlist data
+ * @param name Name of playlist
+ * @param who Who wants to know
+ * @param tracksp Where to put list of tracks, or NULL
+ * @param ntracksp Where to put count of tracks, or NULL
+ * @param sharep Where to put sharing type, or NULL
+ * @return 0 on success, non-0 on error
+ *
+ * Possible return values:
+ * - @c 0 on success
+ * - @c DB_NOTFOUND 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
+ */
+int trackdb_playlist_get(const char *name,
+                         const char *who,
+                         char ***tracksp,
+                         int *ntracksp,
+                         char **sharep) {
+  int e;
+
+  if(!playlist_parse_name(name, 0, 0)) {
+    error(0, "invalid playlist name '%s'", name);
+    return EINVAL;
+  }
+  WITH_TRANSACTION(trackdb_playlist_get_tid(name, who,
+                                            tracksp, ntracksp, sharep,
+                                            tid));
+  return e;
+}
+
+static int trackdb_playlist_get_tid(const char *name,
+                                    const char *who,
+                                    char ***tracksp,
+                                    int *ntracksp,
+                                    char **sharep,
+                                    DB_TXN *tid) {
+  struct kvp *k;
+  int e, ntracks;
+  const char *s;
+
+  if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
+    return e;
+  /* Get sharability */
+  if(!(s = kvp_get(k, "sharing"))) {
+    error(0, "playlist '%s' has no 'sharing' key", name);
+    s = "private";
+  }
+  /* Check the read is allowed */
+  if(!playlist_may_read(name, who, s))
+    return EACCES;
+  /* Return sharability */
+  if(sharep)
+    *sharep = xstrdup(s);
+  /* Get track count */
+  if(!(s = kvp_get(k, "count"))) {
+    error(0, "playlist '%s' has no 'count' key", name);
+    s = "0";
+  }
+  ntracks = atoi(s);
+  if(ntracks < 0) {
+    error(0, "playlist '%s' has negative count", name);
+    ntracks = 0;
+  }
+  /* Return track count */
+  if(ntracksp)
+    *ntracksp = ntracks;
+  if(tracksp) {
+    /* Get track list */
+    char **tracks = xcalloc(ntracks + 1, sizeof (char *));
+    char b[16];
+
+    for(int n = 0; n < ntracks; ++n) {
+      snprintf(b, sizeof b, "%d", n);
+      if(!(s = kvp_get(k, b))) {
+        error(0, "playlist '%s' lacks track %d", name, n);
+        s = "unknown";
+      }
+      tracks[n] = xstrdup(s);
+    }
+    tracks[ntracks] = 0;
+    /* Return track list */
+    *tracksp = tracks;
+  }
+  return 0;
+}
+
+/** @brief Modify or create a playlist
+ * @param name Playlist name
+ * @param tracks List of tracks to set, or NULL to leave alone
+ * @param ntracks Length of @p tracks
+ * @param share Sharing status, or NULL to leave alone
+ * @return 0 on success, non-0 on error
+ *
+ * If the playlist exists it is just modified.
+ *
+ * If the playlist does not exist it is created.  The default set of tracks is
+ * none, and the default sharing is private (if it is an owned one) or shared
+ * (otherwise).
+ *
+ * Possible return values:
+ * - @c 0 on success
+ * - @c EINVAL if the playlist name is invalid
+ * - @c EACCES if the playlist cannot be modified by @p who
+ */
+int trackdb_playlist_set(const char *name,
+                         const char *who,
+                         char **tracks,
+                         int ntracks,
+                         const char *share) {
+  int e;
+  char *owner;
+  
+  if(!playlist_parse_name(name, &owner, 0)) {
+    error(0, "invalid playlist name '%s'", name);
+    return EINVAL;
+  }
+  /* Check valid share types */
+  if(share) {
+    if(owner) {
+      /* Playlists with an owner must be public or private */
+      if(strcmp(share, "public")
+         && strcmp(share, "private")) {
+        error(0, "playlist '%s' must be public or private", name);
+        return EINVAL;
+      }
+    } else {
+      /* Playlists with no owner must be shared */
+      if(strcmp(share, "shared")) {
+        error(0, "playlist '%s' must be shared", name);
+        return EINVAL;
+      }
+    }        
+  }
+  /* We've checked as much as we can for now, now go and attempt the change */
+  WITH_TRANSACTION(trackdb_playlist_set_tid(name, who, tracks, ntracks, share,
+                                            tid));
+  return e;
+}
+
+static int trackdb_playlist_set_tid(const char *name,
+                                    const char *who,
+                                    char **tracks,
+                                    int ntracks,
+                                    const char *share,
+                                    DB_TXN *tid) {
+  struct kvp *k;
+  int e;
+  const char *s;
+
+  if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid))
+     && e != DB_NOTFOUND)
+    return e;
+  /* If the playlist doesn't exist set some defaults */
+  if(e == DB_NOTFOUND) {
+    char *defshare, *owner;
+
+    if(playlist_parse_name(name, &owner, &defshare))
+      return EINVAL;
+    /* Can't create a non-shared playlist belonging to someone else.  In fact
+     * this should be picked up by playlist_may_write() below but it's clearer
+     * to do it here. */
+    if(owner && strcmp(owner, who))
+      return EACCES;
+    k = 0;
+    kvp_set(&k, "count", 0);
+    kvp_set(&k, "sharing", defshare);
+  }
+  /* Check that the modification is allowed */
+  if(!(s = kvp_get(k, "sharing"))) {
+    error(0, "playlist '%s' has no 'sharing' key", name);
+    s = "private";
+  }
+  if(!playlist_may_write(name, who, s))
+    return EACCES;
+  /* Set the new values */
+  if(share)
+    kvp_set(&k, "share", share);
+  if(tracks) {
+    char b[16];
+    int oldcount, n;
+
+    /* Sanity check track count */
+    if(ntracks < 0 || ntracks > config->playlist_max) {
+      error(0, "invalid track count %d", ntracks);
+      return EINVAL;
+    }
+    /* Set the tracks */
+    for(n = 0; n < ntracks; ++n) {
+      snprintf(b, sizeof b, "%d", n);
+      kvp_set(&k, b, tracks[n]);
+    }
+    /* Get the old track count */
+    if((s = kvp_get(k, "count")))
+      oldcount = atoi(s);
+    else
+      oldcount = 0;
+    /* Delete old slots */
+    for(; n < oldcount; ++n) {
+      snprintf(b, sizeof b, "%d", n);
+      kvp_set(&k, b, NULL);
+    }
+    /* Set the new count */
+    snprintf(b, sizeof b, "%d", ntracks);
+    kvp_set(&k, "count", b);
+  }
+  /* Store the resulting record */
+  return trackdb_putdata(trackdb_playlistsdb, name, k, tid, 0);
+}
+
+/** @brief Get a list of playlists
+ * @param who Who wants to know
+ * @param playlistsp Where to put list of playlists
+ * @param nplaylistsp Where to put count of playlists, or NULL
+ */
+void trackdb_playlist_list(const char *who,
+                           char ***playlistsp,
+                           int *nplaylistsp) {
+  int e;
+
+  WITH_TRANSACTION(trackdb_playlist_list_tid(who, playlistsp, nplaylistsp,
+                                             tid));
+}
+
+static int trackdb_playlist_list_tid(const char *who,
+                                     char ***playlistsp,
+                                     int *nplaylistsp,
+                                     DB_TXN *tid) {
+  struct vector v[1];
+  DBC *c;
+  DBT k[1], d[1];
+  int e;
+
+  vector_init(v);
+  c = trackdb_opencursor(trackdb_playlistsdb, tid);
+  memset(k, 0, sizeof k);
+  while(!(e = c->c_get(c, k, prepare_data(d), DB_NEXT))) {
+    char *name = xstrndup(k->data, k->size), *owner;
+    const char *share = kvp_get(kvp_urldecode(d->data, d->size),
+                                "share");
+
+    /* Extract owner; malformed names are skipped */
+    if(playlist_parse_name(name, &owner, 0)) {
+      error(0, "invalid playlist name '%s' found in database", name);
+      continue;
+    }
+    /* Always list public and shared playlists
+     * Only list private ones to their owner
+     * Don't list anything else
+     */
+    if(!strcmp(share, "public")
+       || !strcmp(share, "shared")
+       || (!strcmp(share, "private")
+           && owner && !strcmp(owner, who)))
+      vector_append(v, name);
+  }
+  switch(e) {
+  case DB_NOTFOUND:
+    break;
+  case DB_LOCK_DEADLOCK:
+    return e;
+  default:
+    fatal(0, "c->c_get: %s", db_strerror(e));
+  }
+  vector_terminate(v);
+  if(playlistsp)
+    *playlistsp = v->vec;
+  if(nplaylistsp)
+    *nplaylistsp = v->nvec;
+  return 0;
+}
+
+/** @brief Delete a playlist
+ * @param name Playlist name
+ * @param who Who is deleting it
+ * @return 0 on success, non-0 on error
+ *
+ * Possible return values:
+ * - @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
+ */
+int trackdb_playlist_delete(const char *name,
+                            const char *who) {
+  int e;
+  char *owner;
+  
+  if(!playlist_parse_name(name, &owner, 0)) {
+    error(0, "invalid playlist name '%s'", name);
+    return EINVAL;
+  }
+  /* 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));
+  return e;
+}
+
+static int trackdb_playlist_delete_tid(const char *name,
+                                       const char *who,
+                                       DB_TXN *tid) {
+  struct kvp *k;
+  int e;
+  const char *s;
+
+  if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
+    return e;
+  /* Check that modification is allowed */
+  if(!(s = kvp_get(k, "sharing"))) {
+    error(0, "playlist '%s' has no 'sharing' key", name);
+    s = "private";
+  }
+  if(!playlist_may_write(name, who, s))
+    return EACCES;
+  /* Delete the playlist */
+  return trackdb_delkey(trackdb_playlistsdb, name, tid);
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 718970a417c0c976da070a36b890f4cc6d4e6db6..a364446c8c23f7461752c589f6ef5cc5c9e9b5b6 100644 (file)
@@ -159,6 +159,13 @@ DB *trackdb_scheduledb;
  */
 DB *trackdb_usersdb;
 
+/** @brief The playlists database
+ * - Keys are playlist names
+ * - Values are encoded key-value pairs
+ * - Data is user data and cannot be reconstructed
+ */
+DB *trackdb_playlistsdb;
+
 static pid_t db_deadlock_pid = -1;      /* deadlock manager PID */
 static pid_t rescan_pid = -1;           /* rescanner PID */
 static int initialized, opened;         /* state */
@@ -468,6 +475,7 @@ void trackdb_open(int flags) {
   trackdb_noticeddb = open_db("noticed.db",
                              DB_DUPSORT, DB_BTREE, dbflags, 0666);
   trackdb_scheduledb = open_db("schedule.db", 0, DB_HASH, dbflags, 0666);
+  trackdb_playlistsdb = open_db("playlists.db", 0, DB_HASH, dbflags, 0666);
   if(!trackdb_existing_database) {
     /* Stash the database version */
     char buf[32];
@@ -502,6 +510,8 @@ void trackdb_close(void) {
     fatal(0, "error closing schedule.db: %s", db_strerror(err));
   if((err = trackdb_usersdb->close(trackdb_usersdb, 0)))
     fatal(0, "error closing users.db: %s", db_strerror(err));
+  if((err = trackdb_playlistsdb->close(trackdb_playlistsdb, 0)))
+    fatal(0, "error closing playlists.db: %s", db_strerror(err));
   trackdb_tracksdb = trackdb_searchdb = trackdb_prefsdb = 0;
   trackdb_tagsdb = trackdb_globaldb = 0;
   D(("closed databases"));
@@ -2551,8 +2561,10 @@ static int trusted(const char *user) {
  * Currently we only allow the letters and digits in ASCII.  We could be more
  * liberal than this but it is a nice simple test.  It is critical that
  * semicolons are never allowed.
+ *
+ * NB also used by playlist_parse_name() to validate playlist names!
  */
-static int valid_username(const char *user) {
+int valid_username(const char *user) {
   if(!*user)
     return 0;
   while(*user) {
index e3d61b564262f8d12dd16d9c80c9e024231ec378..97c761ee7c178041fb1cf5fd9810b452edf0ab6c 100644 (file)
@@ -186,6 +186,25 @@ void trackdb_add_rescanned(void (*rescanned)(void *ru),
                            void *ru);
 int trackdb_rescan_underway(void);
 
+int playlist_parse_name(const char *name,
+                        char **ownerp,
+                        char **sharep);
+int trackdb_playlist_get(const char *name,
+                         const char *who,
+                         char ***tracksp,
+                         int *ntracksp,
+                         char **sharep);
+int trackdb_playlist_set(const char *name,
+                         const char *who,
+                         char **tracks,
+                         int ntracks,
+                         const char *share);
+void trackdb_playlist_list(const char *who,
+                           char ***playlistsp,
+                           int *nplaylistsp);
+int trackdb_playlist_delete(const char *name,
+                            const char *who);
+
 #endif /* TRACKDB_H */
 
 /*