#include <unistd.h>
#include <assert.h>
#include <pcre.h>
+#include <ctype.h>
#include "configuration.h"
#include "syscalls.h"
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;
"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, "",
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.
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)
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);
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 */
/*
{ 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 },
c->speaker_backend = -1;
c->multicast_ttl = 1;
c->authorization_algorithm = xstrdup("sha1");
+ c->noticed_history = 31;
return c;
}
/** @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;
"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
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
/*
*/
}
+/** @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;
do_all(rescan_collection);
/* Check that every track still exists */
recheck_collection(0);
+ /* Expire noticed.db */
+ expire_noticed();
}
else {
/* Rescan specified collections */
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 */
{ "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 },
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 */
* 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;
#include <sys/time.h>
#include <sys/resource.h>
#include <time.h>
+#include <arpa/inet.h>
#include "event.h"
#include "mem.h"
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;
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 */
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"));
}
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"));
/* 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;
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) {
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);
/* 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;
}
}
}
+/** @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) {
const char *trackdb_get_global(const char *name);
/* get a global pref */
+char **trackdb_new(int *ntracksp, int maxtracks);
+
#endif /* TRACKDB_H */
/*