chiark / gitweb /
server half of noticed.db
authorRichard Kettlewell <rjk@greenend.org.uk>
Tue, 2 Oct 2007 17:03:01 +0000 (18:03 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Tue, 2 Oct 2007 17:03:01 +0000 (18:03 +0100)
14 files changed:
clients/disorder.c
doc/disorder_config.5.in
doc/disorder_protocol.5.in
lib/client.c
lib/client.h
lib/configuration.c
lib/configuration.h
lib/eclient.c
lib/eclient.h
server/rescan.c
server/server.c
server/trackdb-int.h
server/trackdb.c
server/trackdb.h

index eae5a8fb9881d1e6f7cff8b98ac48c89dbc783e3..5ccb625812b4b937ca43e97dca1015bfd5f45ce5 100644 (file)
@@ -35,6 +35,7 @@
 #include <unistd.h>
 #include <assert.h>
 #include <pcre.h>
+#include <ctype.h>
 
 #include "configuration.h"
 #include "syscalls.h"
@@ -378,6 +379,26 @@ static void cf_unset_global(disorder_client *c, char **argv) {
   if(disorder_unset_global(c, argv[0])) exit(EXIT_FAILURE);
 }
 
+static int isarg_integer(const char *s) {
+  if(!*s) return 0;
+  while(*s) {
+    if(!isdigit((unsigned char)*s))
+      return 0;
+    ++s;
+  }
+  return 1;
+}
+
+static void cf_new(disorder_client *c,
+                  char **argv) {
+  char **vec;
+
+  if(disorder_new_tracks(c, &vec, 0, argv[0] ? atoi(argv[0]) : 0))
+    exit(EXIT_FAILURE);
+  while(*vec)
+    xprintf("%s\n", nullcheck(utf82mb(*vec++)));
+}
+
 static const struct command {
   const char *name;
   int min, max;
@@ -415,6 +436,8 @@ static const struct command {
                       "Copy event log to stdout" },
   { "move",           2, 2, cf_move, 0, "TRACK DELTA",
                       "Move a track in the queue" },
+  { "new",            0, 1, cf_new, isarg_integer, "[MAX]",
+                      "Get the most recently added MAX tracks" },
   { "part",           3, 3, cf_part, 0, "TRACK CONTEXT PART",
                       "Find a track name part" },
   { "pause",          0, 0, cf_pause, 0, "",
index efb7a306a19701d92347c8bfc12c7574f9714174..9009624b637b0b498ac9bcad5000a18028848a8a 100644 (file)
@@ -268,6 +268,10 @@ is not massively CPU intensive by today's standards but depends on reasonably
 timely scheduling.  If you have limited CPU then it might help to set this to a
 small negative value.  The default is 0.
 .TP
+.B noticed_history
+The maximum days that a track can survive in the database of newly added
+tracks.  The default is 31.
+.TP
 .B player \fIPATTERN\fR \fIMODULE\fR [\fIOPTIONS.. [\fB--\fR]] \fIARGS\fR...
 Specifies the player for files matching the glob \fIPATTERN\fR.  \fIMODULE\fR
 specifies which plugin module to use.
index 60390f2326d31ff01bce493137633a4b8403520d..100e746e050f7317bece580412397b3020aa6044 100644 (file)
@@ -109,6 +109,10 @@ the queue.  If \fITARGET\fR is listed in the ID list then the tracks are moved
 to just after the first non-listed track before it, or to the head if there is
 no such track.
 .TP
+.B new \fR[\fIMAX\fR]
+Sends the most recently added \fIMAX\fR tracks in a response body.  If the
+argument is ommitted, all recently added tracks are listed.
+.TP
 .B nop
 Do nothing.  Used by
 .BR disobedience (1)
index b012841545798887667393be2b728e667c66b427..d68d9d76a9ded92d3932cb762c7f4410a5db4d11 100644 (file)
@@ -600,6 +600,22 @@ int disorder_tags(disorder_client *c,
   return disorder_simple_list(c, vecp, nvecp, "tags", (char *)0);
 }
 
+/** @brief Get recentl added tracks
+ * @param c Client
+ * @param vecp Where to store pointer to list
+ * @param nvecp Where to store count
+ * @param max Maximum tracks to fetch, or 0 for all available
+ * @return 0 on success, non-0 on error
+ */
+int disorder_new_tracks(disorder_client *c,
+                       char ***vecp, int *nvecp,
+                       int max) {
+  char limit[32];
+
+  sprintf(limit, "%d", max);
+  return disorder_simple_list(c, vecp, nvecp, "new", limit, (char *)0);
+}
+
 int disorder_set_global(disorder_client *c,
                        const char *key, const char *value) {
   return disorder_simple(c, 0, "set-global", key, value, (char *)0);
index 433e81c9d6fe1c418650411b73fb8f54d5b8d937..a7d0ff972f2d6e977f67541f2fbaf3d08c04cc57 100644 (file)
@@ -181,6 +181,11 @@ int disorder_unset_global(disorder_client *c, const char *key);
 int disorder_get_global(disorder_client *c, const char *key, char **valuep);
 /* get/unset/set global prefs */
 
+int disorder_new_tracks(disorder_client *c,
+                       char ***vecp, int *nvecp,
+                       int max);
+/* get new tracks */
+
 #endif /* CLIENT_H */
 
 /*
index 69b8bb7e9affc261dde6774e2c5ce18b0c156bc4..f75845cbc1468926f335b2df2da3f5e7f51743ec 100644 (file)
@@ -857,6 +857,7 @@ static const struct conf conf[] = {
   { C(nice_rescan),      &type_integer,          validate_non_negative },
   { C(nice_server),      &type_integer,          validate_any },
   { C(nice_speaker),     &type_integer,          validate_any },
+  { C(noticed_history),  &type_integer,          validate_positive },
   { C(password),         &type_string,           validate_any },
   { C(player),           &type_stringlist_accum, validate_player },
   { C(plugins),          &type_string_accum,     validate_isdir },
@@ -992,6 +993,7 @@ static struct config *config_default(void) {
   c->speaker_backend = -1;
   c->multicast_ttl = 1;
   c->authorization_algorithm = xstrdup("sha1");
+  c->noticed_history = 31;
   return c;
 }
 
index a2033c2dde941806aa9788eff309d37f1926b8e1..476a917a33391e36f6ebc0a0b0064db0bf7f830f 100644 (file)
@@ -114,6 +114,9 @@ struct config {
   /** @brief Maximum number of recent tracks to record in history */
   long history;
 
+  /** @brief Expiry limit for noticed.db */
+  long noticed_history;
+  
   /** @brief Trusted users */
   struct stringlist trust;
 
index cefa5e31bf16f7848e2dd89b5949a9ac16e1a2c5..2d57d73be68a54859a22de881e52423b4c6325af 100644 (file)
@@ -1156,6 +1156,25 @@ int disorder_eclient_nop(disorder_eclient *c,
                 "nop", (char *)0);
 }
 
+/** @brief Get the last @p max added tracks
+ * @param c Client
+ * @param completed Called with list
+ * @param max Number of tracks to get, 0 for all
+ * @param v Passed to @p completed
+ *
+ * The first track in the list is the most recently added.
+ */
+int disorder_eclient_new_tracks(disorder_eclient *c,
+                                disorder_eclient_list_response *completed,
+                                int max,
+                                void *v) {
+  char limit[32];
+
+  sprintf(limit, "%d", max);
+  return simple(c, list_response_opcallback, (void (*)())completed, v,
+                "new", limit, (char *)0);
+}
+
 /* Log clients ***************************************************************/
 
 /** @brief Monitor the server log
index a81cfe45148a3f2d6b06f9ca5cc4cf9ea50454cc..47feee3e61c0b9fbd8cd6a332b49b685ba1dda8d 100644 (file)
@@ -316,6 +316,10 @@ int disorder_eclient_nop(disorder_eclient *c,
                          disorder_eclient_no_response *completed,
                          void *v);
 
+int disorder_eclient_new_tracks(disorder_eclient *c,
+                                disorder_eclient_list_response *completed,
+                                int max,
+                                void *v);
 #endif
 
 /*
index 8a29300fde24a16f7d86c9d5f2d73dd750947867..45f5a650178dd615b313d4acabce594f948050ae 100644 (file)
@@ -303,6 +303,11 @@ static void do_all(void (*fn)(const struct collection *c)) {
    */
 }
 
+/** @brief Expire noticed.db */
+static void expire_noticed(void) {
+  error(0, "expire_noticed not implemented yet TODO");
+}
+
 int main(int argc, char **argv) {
   int n;
   struct sigaction sa;
@@ -340,6 +345,8 @@ int main(int argc, char **argv) {
     do_all(rescan_collection);
     /* Check that every track still exists */
     recheck_collection(0);
+    /* Expire noticed.db */
+    expire_noticed();
   }
   else {
     /* Rescan specified collections */
index 6ee11c962c221daa067e0fa53563fa62a547a6cb..c5fb0108f3516bfd5794f09199fa02723701358d 100644 (file)
@@ -914,6 +914,22 @@ static int c_nop(struct conn *c,
   return 1;
 }
 
+static int c_new(struct conn *c,
+                char **vec,
+                int nvec) {
+  char **tracks = trackdb_new(0, nvec > 0 ? atoi(vec[0]) : INT_MAX);
+
+  sink_printf(ev_writer_sink(c->w), "253 New track list follows\n");
+  while(*tracks) {
+    sink_printf(ev_writer_sink(c->w), "%s%s\n",
+               **tracks == '.' ? "." : "", *tracks);
+    ++tracks;
+  }
+  sink_writes(ev_writer_sink(c->w), ".\n");
+  return 1;                            /* completed */
+
+}
+
 #define C_AUTH         0001            /* must be authenticated */
 #define C_TRUSTED      0002            /* must be trusted user */
 
@@ -937,6 +953,7 @@ static const struct command {
   { "log",            0, 0,       c_log,            C_AUTH },
   { "move",           2, 2,       c_move,           C_AUTH },
   { "moveafter",      1, INT_MAX, c_moveafter,      C_AUTH },
+  { "new",            0, 1,       c_new,            C_AUTH },
   { "nop",            0, 0,       c_nop,            C_AUTH },
   { "part",           3, 3,       c_part,           C_AUTH },
   { "pause",          0, 0,       c_pause,          C_AUTH },
index 81cd261332fb3365bb46466095d6d6a7ae45d573..1ba890b9340bb263ad38fc41b5e3fb822902cf70 100644 (file)
@@ -26,6 +26,7 @@ extern DB_ENV *trackdb_env;
 extern DB *trackdb_tracksdb;
 extern DB *trackdb_prefsdb;
 extern DB *trackdb_searchdb;
+extern DB *trackdb_noticeddb;
 
 DBC *trackdb_opencursor(DB *db, DB_TXN *tid);
 /* open a transaction */
@@ -87,6 +88,7 @@ int trackdb_scan(const char *root,
  * EINTR to cancel the scan. */
 
 /* fill KEY in with S, returns KEY */
+
 static inline DBT *make_key(DBT *key, const char *s) {
   memset(key, 0, sizeof *key);
   key->data = (void *)s;
index fabb203a45f05fd99d7674063678ae5096c643c3..07fdd28906018fecd23c282cb7677caf4eda0f09 100644 (file)
@@ -33,6 +33,7 @@
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <time.h>
+#include <arpa/inet.h>
 
 #include "event.h"
 #include "mem.h"
@@ -65,6 +66,9 @@ static int trackdb_alltags_tid(DB_TXN *tid, char ***taglistp);
 static int trackdb_get_global_tid(const char *name,
                                   DB_TXN *tid,
                                   const char **rp);
+static char **trackdb_new_tid(int *ntracksp,
+                              int maxtracks,
+                              DB_TXN *tid);
 
 const struct cache_type cache_files_type = { 86400 };
 unsigned long cache_files_hits, cache_files_misses;
@@ -78,6 +82,7 @@ DB *trackdb_prefsdb;                  /* preferences */
 DB *trackdb_searchdb;                  /* the search database */
 DB *trackdb_tagsdb;                    /* the tags database */
 DB *trackdb_globaldb;                   /* global preferences */
+DB *trackdb_noticeddb;                   /* when track noticed */
 static pid_t db_deadlock_pid = -1;      /* deadlock manager PID */
 static pid_t rescan_pid = -1;           /* rescanner PID */
 static int initialized, opened;         /* state */
@@ -247,6 +252,8 @@ void trackdb_open(void) {
                            DB_DUP|DB_DUPSORT, DB_HASH, DB_CREATE, 0666);
   trackdb_prefsdb = open_db("prefs.db", 0, DB_HASH, DB_CREATE, 0666);
   trackdb_globaldb = open_db("global.db", 0, DB_HASH, DB_CREATE, 0666);
+  trackdb_noticeddb = open_db("noticed.db",
+                             DB_DUPSORT, DB_BTREE, DB_CREATE, 0666);
   D(("opened databases"));
 }
 
@@ -267,6 +274,8 @@ void trackdb_close(void) {
     fatal(0, "error closing prefs.db: %s", db_strerror(err));
   if((err = trackdb_globaldb->close(trackdb_globaldb, 0)))
     fatal(0, "error closing global.db: %s", db_strerror(err));
+  if((err = trackdb_noticeddb->close(trackdb_noticeddb, 0)))
+    fatal(0, "error closing noticed.db: %s", db_strerror(err));
   trackdb_tracksdb = trackdb_searchdb = trackdb_prefsdb = 0;
   trackdb_tagsdb = trackdb_globaldb = 0;
   D(("closed databases"));
@@ -673,7 +682,9 @@ done:
 
 /* trackdb_notice() **********************************************************/
 
-/* notice a track */
+/** @brief notice a possiby new  track
+ * @return @c DB_NOTFOUND if new, 0 if already known
+ */
 int trackdb_notice(const char *track,
                    const char *path) {
   int err;
@@ -691,6 +702,9 @@ int trackdb_notice(const char *track,
   return err;
 }
 
+/** @brief notice a possiby new  track
+ * @return @c DB_NOTFOUND if new, 0 if already known, @c DB_LOCK_DEADLOCK also
+ */
 int trackdb_notice_tid(const char *track,
                        const char *path,
                        DB_TXN *tid) {
@@ -698,13 +712,13 @@ int trackdb_notice_tid(const char *track,
   struct kvp *t, *a, *p;
   int t_changed, ret;
   char *alias, **w;
-  
+
   /* notice whether the tracks.db entry changes */
   t_changed = 0;
   /* get any existing tracks entry */
   if((err = gettrackdata(track, &t, &p, 0, 0, tid)) == DB_LOCK_DEADLOCK)
     return err;
-  ret = err;
+  ret = err;                            /* 0 or DB_NOTFOUND */
   /* this is a real track */
   t_changed += kvp_set(&t, "_alias_for", 0);
   t_changed += kvp_set(&t, "_path", path);
@@ -731,6 +745,24 @@ int trackdb_notice_tid(const char *track,
   /* only store the tracks.db entry if it has changed */
   if(t_changed && (err = trackdb_putdata(trackdb_tracksdb, track, t, tid, 0)))
     return err;
+  if(ret == DB_NOTFOUND) {
+    uint32_t timestamp[2];
+    time_t now;
+    DBT key, data;
+
+    time(&now);
+    timestamp[0] = htonl((uint64_t)now >> 32);
+    timestamp[1] = htonl((uint32_t)now);
+    memset(&key, 0, sizeof key);
+    key.data = timestamp;
+    key.size = sizeof timestamp;
+    switch(err = trackdb_noticeddb->put(trackdb_noticeddb, tid, &key,
+                                        make_key(&data, track), 0)) {
+    case 0: break;
+    case DB_LOCK_DEADLOCK: return err;
+    default: fatal(0, "error updating noticed.db: %s", db_strerror(err));
+    }
+  }
   return ret;
 }
 
@@ -1826,6 +1858,68 @@ static int trackdb_get_global_tid(const char *name,
   }
 }
 
+/** @brief Retrieve the most recently added tracks
+ * @param ntracksp Where to put count, or 0
+ * @param maxtracks Maximum number of tracks to retrieve
+ * @return null-terminated array of track names
+ *
+ * The most recently added track is first in the array.
+ */
+char **trackdb_new(int *ntracksp,
+                   int maxtracks) {
+  DB_TXN *tid;
+  char **tracks;
+
+  for(;;) {
+    tid = trackdb_begin_transaction();
+    tracks = trackdb_new_tid(ntracksp, maxtracks, tid);
+    if(tracks)
+      break;
+    trackdb_abort_transaction(tid);
+  }
+  trackdb_commit_transaction(tid);
+  return tracks;
+}
+
+/** @brief Retrieve the most recently added tracks
+ * @param ntracksp Where to put count, or 0
+ * @param maxtracks Maximum number of tracks to retrieve, or 0 for all
+ * @param tid Transaction ID
+ * @return null-terminated array of track names, or NULL on deadlock
+ *
+ * The most recently added track is first in the array.
+ */
+static char **trackdb_new_tid(int *ntracksp,
+                              int maxtracks,
+                              DB_TXN *tid) {
+  DBC *c;
+  DBT k, d;
+  int err = 0;
+  struct vector tracks[1];
+
+  vector_init(tracks);
+  c = trackdb_opencursor(trackdb_noticeddb, tid);
+  while((maxtracks <= 0 || tracks->nvec < maxtracks)
+        && !(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_PREV)))
+    vector_append(tracks, xstrndup(d.data, d.size));
+  switch(err) {
+  case 0:                               /* hit maxtracks */
+  case DB_NOTFOUND:                     /* ran out of tracks */
+    break;
+  case DB_LOCK_DEADLOCK:
+    trackdb_closecursor(c);
+    return 0;
+  default:
+    fatal(0, "error reading noticed.db: %s", db_strerror(err));
+  }
+  if((err = trackdb_closecursor(c)))
+    return 0;                           /* deadlock */
+  vector_terminate(tracks);
+  if(ntracksp)
+    *ntracksp = tracks->nvec;
+  return tracks->vec;
+}
+
 /* tidying up ****************************************************************/
 
 void trackdb_gc(void) {
index d142f8a7cc86e33507d13ee71e2057736dc274ff..3e7976aaebdb2ec756b5f658cec5f75bf0c4a42c 100644 (file)
@@ -117,6 +117,8 @@ void trackdb_set_global(const char *name,
 const char *trackdb_get_global(const char *name);
 /* get a global pref */
 
+char **trackdb_new(int *ntracksp, int maxtracks);
+
 #endif /* TRACKDB_H */
 
 /*