<p>This file documents recent user-visible changes to DisOrder.</p>
-<h2>Changes up to version 3.1</h2>
+<h2>Changes up to version 4.1</h2>
+
+<div class=section>
+
+ <h3>Disobedience</h3>
+
+ <div class=section>
+
+ <p>Disobedience has been largely rewritten:</p>
+
+ <ul>
+
+ <li>All the tabs now use native GTK+ list/tree widgets, resulting in
+ greater speed in some cases and more consistency with other GTK+
+ applications.</li>
+
+ <li>You can now use type-ahead find in the choose tab. The initiation
+ of a search is delayed slightly to avoid lots of updates when you're
+ half way through entering search terms.</li>
+
+ <li>The choose tab now shows track lengths.</li>
+
+ <li>Many buttons are now more reliably made insensitive when they can't
+ be used.</li>
+
+ <li>You can now play tracks off the recent tab.</li>
+
+ </ul>
+
+ <p>Disobedience attempts to cope with servers from older versions, up to
+ a point, but this is not well tested and it's best to keep the server
+ fully up to date.</p>
+
+ </div>
+
+ <h3>Server</h3>
+
+ <div class=section>
+
+ <p>When a track shares a directory with its alias, the real track name is
+ now returned instead of the alias (the opposite way round to the previous
+ behaviour).</p>
+
+ </div>
+</div>
+
+<h2>Changes up to version 4.0.2</h2>
+
+<div class=section>
+
+ <p>Corrected web browser linked from Disobedience.</p>
+
+</div>
+
+<h2>Changes up to version 4.0.1</h2>
+
+<div class=section>
+
+ <p>Libtool and Automake now install the CGI correctly. As part of this,
+ <tt>cgidir</tt> has been renamed to <tt>cgiexecdir</tt>. The configure
+ script will report an error if you try to use the old name.</p>
+
+</div>
+
+<h2>Changes up to version 4.0</h2>
<div class=section>
libao 0.8.6
libasound 1.0.13
libFLAC 1.1.2
- GNU C 4.1.2
- GNU Make 3.81
- GNU Sed 4.1.5
+ GNU C 4.1.2 }
+ GNU Make 3.81 } Non-GNU versions will NOT work
+ GNU Sed 4.1.5 }
Python 2.4.4 (optional)
GTK+ 2.8.20 (if you want the GTK+ client)
GLIB 2.12.4 (if you want the GTK+ client)
If configure cannot guess where your web server keeps its HTML documents and
CGI programs, you may have to tell it, for instance:
- ./configure cgidir=/whatever/cgi-bin httpdir=/whatever/htdocs
+ ./configure cgiexecdir=/whatever/cgi-bin httpdir=/whatever/htdocs
See README.client for setting up a standalone client (or read the
disobedience man page).
disorder setup-guest --no-online-registration
-3. Try it out. You should be able to perform read-only operations straight
- away, and after visiting the 'Login' page to authenticate, perform other
- operations like adding a track to the queue.
+3. Try it out. The url will be (something like):
+
+ http://localhost/cgi-bin/disorder
+
+ You should be able to perform read-only operations straight away, and after
+ visiting the 'Login' page to authenticate, perform other operations like
+ adding a track to the queue.
4. If you run into problems, always look at the appropriate error log; the
message you see in your web browser will usually not be sufficient to
libao-dev libmad0-dev libasound2-dev libdb4.3-dev \
libflac-dev
+ (Use the bzr from backports, the one in etch is obsolete.)
+
* On FreeBSD you'll need at least these packages:
autotools
bash
keep it that way. Clever use of CSS is OK provided it works well on the
mainstream browsers.
- * I know that the web template syntax is rather nasty. Perhaps it will be
- improved in a future version.
-
- * Update templates/help.html for any changes you make.
+ * Update templates/help.tmpl for any changes you make.
Disobedience:
(But if your new feature only makes sense on a given platform then
obviously its new dependencies don't need to be available elsewhere.)
- * GCC is stated as a dependency. In fact the code is mostly standard C,
- with C99 initializers, long long and possibly the occasional // comment as
- the main departures from C89. Additional GCCisms will be accepted if it's
- impractical to avoid them. At least one active user is still using GCC
- 2.95, so extensions that only appear in later versions are to be avoided
- for the time being.
+ * GCCisms such as typeof and C99isms such as mixed declarations and named
+ structure initializers are used; the configure script asks for -std=gnu99
+ by default. Some supported platforms are still on GCC 4.0.
* Please submit patches either using 'diff -u', or by publishing a bzr
branch somewhere I can get at it.
If you install from .deb files then much of this work is automated.
-* 3.0 -> 3.1
+* 3.0 -> 4.0
If you customized any of the templates, you will pretty much have to start from
scratch as the web interface has been rewritten. See disorder.cgi(8) for a
# USA
#
-cgi_PROGRAMS=disorder
+cgiexec_PROGRAMS=disorder
AM_CPPFLAGS=-I${top_srcdir}/lib -I../lib
$(LIBPCRE) $(LIBGCRYPT) $(LIBDL) $(LIBDB)
disorder_LDFLAGS=-export-dynamic
disorder_DEPENDENCIES=../lib/libdisorder.a
+
+install-exec-hook:
+ $(LIBTOOL) --mode=finish $(DESTDIR)$(cgiexecdir)
const char *track, *dir;
char **tracks;
int ntracks, n;
- struct dcgi_entry *e;
+ struct tracksort_data *tsd;
if(dcgi_client) {
if((track = cgi_get("track"))) {
} else if((dir = cgi_get("dir"))) {
if(disorder_files(dcgi_client, dir, 0, &tracks, &ntracks))
ntracks = 0;
- /* TODO use tracksort_init */
- e = xmalloc(ntracks * sizeof (struct dcgi_entry));
- for(n = 0; n < ntracks; ++n) {
- e[n].track = tracks[n];
- e[n].sort = trackname_transform("track", tracks[n], "sort");
- e[n].display = trackname_transform("track", tracks[n], "display");
- }
- qsort(e, ntracks, sizeof (struct dcgi_entry), dcgi_compare_entry);
+ tsd = tracksort_init(ntracks, tracks, "track");
for(n = 0; n < ntracks; ++n)
- disorder_play(dcgi_client, e[n].track);
+ disorder_play(dcgi_client, tsd[n].track);
}
}
redirect(0);
}
/* We could well do better address validation but for now we'll just do the
* minimum */
- /* TODO use email_valid() */
- if(!strchr(email, '@')) {
+ if(!email_valid(email)) {
login_error("bademail");
return;
}
}
} else
password = password2 = 0;
- /* TODO use email_valid() */
- if(email && !strchr(email, '@')) {
+ if(email && !email_valid(email)) {
login_error("bademail");
return;
}
if(!(found = mx_find(p, 0/*report*/)))
fatal(errno, "cannot find %s", p);
if(header) {
- if(printf("Content-Type: text/html\n"
+ if(printf("Content-Type: text/html; charset=UTF-8\n"
"%s\n"
"\n", dcgi_cookie_header()) < 0)
fatal(errno, "error writing to stdout");
/* TODO we could make disorder/ACTION equivalent to disorder?action=ACTION */
if(getenv("PATH_INFO")) {
/* TODO it might be nice to link back to the right place... */
- printf("Content-Type: text/html\n");
+ printf("Content-Type: text/html; charset=UTF-8\n");
printf("Status: 404\n");
printf("\n");
printf("<p>Sorry, PATH_INFO not supported.</p>\n");
extern const char *dcgi_error_string;
extern const char *dcgi_status_string;
-/** @brief Entry in a list of tracks or directories */
-struct dcgi_entry {
- /** @brief Track name */
- const char *track;
- /** @brief Sort key */
- const char *sort;
- /** @brief Display key */
- const char *display;
-};
-
/** @brief Compare two @ref entry objects */
int dcgi_compare_entry(const void *a, const void *b);
return sink_writes(output, cgi_sgmlquote(url)) < 0 ? -1 : 0;
}
-/** @brief Compare two @ref entry objects */
-int dcgi_compare_entry(const void *a, const void *b) {
- const struct dcgi_entry *ea = a, *eb = b;
-
- return compare_tracks(ea->sort, eb->sort,
- ea->display, eb->display,
- ea->track, eb->track);
-}
-
/** @brief Implementation of exp_tracks() and exp_dirs() */
static int exp__files_dirs(int nargs,
const struct mx_node **args,
char **tracks, *dir, *re;
int n, ntracks, rc;
const struct mx_node *m;
- struct dcgi_entry *e;
+ struct tracksort_data *tsd;
if((rc = mx_expandstr(args[0], &dir, u, "argument #0 (DIR)")))
return rc;
if(fn(dcgi_client, dir, re, &tracks, &ntracks))
return 0;
/* Sort it. NB trackname_transform() does not go to the server. */
- /* TODO use tracksort_init */
- e = xcalloc(ntracks, sizeof *e);
- for(n = 0; n < ntracks; ++n) {
- e[n].track = tracks[n];
- e[n].sort = trackname_transform(type, tracks[n], "sort");
- e[n].display = trackname_transform(type, tracks[n], "display");
- }
- qsort(e, ntracks, sizeof (struct dcgi_entry), dcgi_compare_entry);
+ tsd = tracksort_init(ntracks, tracks, type);
/* Expand the subsiduary templates. We chuck in @sort and @display because
* it is particularly easy to do so. */
for(n = 0; n < ntracks; ++n)
if((rc = mx_expand(mx_rewritel(m,
"index", make_index(n),
"parity", n % 2 ? "odd" : "even",
- "track", e[n].track,
+ "track", tsd[n].track,
"first", n == 0 ? "true" : "false",
"last", n + 1 == ntracks ? "false" : "true",
- "sort", e[n].sort,
- "display", e[n].display,
+ "sort", tsd[n].sort,
+ "display", tsd[n].display,
(char *)0),
output, u)))
return rc;
# USA
#
-AC_INIT([disorder], [3.0+], [richard+disorder@sfere.greenend.org.uk])
+AC_INIT([disorder], [4.1], [richard+disorder@sfere.greenend.org.uk])
AC_CONFIG_AUX_DIR([config.aux])
-AM_INIT_AUTOMAKE(disorder, [3.0+])
+AM_INIT_AUTOMAKE(disorder, [4.1])
AC_CONFIG_SRCDIR([server/disorderd.c])
AM_CONFIG_HEADER([config.h])
fi
done
])
- if test "$rjk_cv_cgidir" = "not found"; then
+ if test "$rjk_cv_cgiexecdir" = "not found"; then
AC_MSG_ERROR([cannot identify httpd documentroot. Set httpdir on configure command line])
fi
httpdir="$rjk_cv_httpdir"
fi
- if test -z "$cgidir"; then
- AC_CACHE_CHECK([for CGI directory],[rjk_cv_cgidir],[
- rjk_cv_cgidir="not found"
+ if test ! -z "$cgidir"; then
+ # This is a bit harsh but should stop any disasters
+ AC_MSG_ERROR([cgidir has been renamed to cgiexecdir])
+ fi
+ if test -z "$cgiexecdir"; then
+ AC_CACHE_CHECK([for CGI directory],[rjk_cv_cgiexecdir],[
+ rjk_cv_cgiexecdir="not found"
for dir in /usr/lib/cgi-bin \
/Library/WebServer/CGI-Executables \
/srv/www/cgi-bin \
/usr/local/www/cgi-bin \
/usr/local/www/*/cgi-bin; do
if test -d "$dir"; then
- rjk_cv_cgidir="$dir"
+ rjk_cv_cgiexecdir="$dir"
break
fi
done
])
- if test "$rjk_cv_cgidir" = "not found"; then
- AC_MSG_ERROR([cannot identify CGI install directory. Set cgidir on configure command line])
+ if test "$rjk_cv_cgiexecdir" = "not found"; then
+ AC_MSG_ERROR([cannot identify CGI install directory. Set cgiexecdir on configure command line])
fi
- cgidir="$rjk_cv_cgidir"
+ cgiexecdir="$rjk_cv_cgiexecdir"
fi
fi
-AC_ARG_VAR([cgidir], [location of cgi-bin directory, e.g. /usr/lib/cgi-bin])
+AC_ARG_VAR([cgiexecdir], [location of cgi-bin directory, e.g. /usr/lib/cgi-bin])
AC_ARG_VAR([httpdir], [location of http document root, e.g. /var/www/htdocs])
if test -z "$pkghttpdir"; then
pkghttpdir='$(httpdir)/disorder'
Debian package for DisOrder
===========================
-The web interface should now start working automatically. Test it at
-http://YOURHOSTNAME/cgi-bin/disorder/disorder If it doesn't work,
-always look at the web server error log.
+The web interface should now start working automatically. Test it at:
- -- Richard Kettlewell <rjk@greenend.org.uk>, Sat, 29 Mar 2008 16:20:20 +0000
+ http://YOURHOSTNAME/cgi-bin/disorder
+
+You will need to create yourself a user.
+
+If it doesn't work, always look at the web server error log and at
+/var/log/daemon.log.
+
+ -- Richard Kettlewell <rjk@greenend.org.uk>, Sun, 15 Jun 2008 12:15:50 +0100
-disorder (3.0.99.dev) unstable; urgency=low
+disorder (4.1) unstable; urgency=low
- * Bodge version number
+ * DisOrder 4.1
- -- Richard Kettlewell <rjk@greenend.org.uk> Sun, 18 May 2008 21:30:14 +0100
+ -- Richard Kettlewell <rjk@greenend.org.uk> Sat, 28 Jun 2008 14:35:20 +0100
+
+disorder (4.0.2) unstable; urgency=low
+
+ * Correct web browser linkage from Disobedience.
+
+ -- Richard Kettlewell <rjk@greenend.org.uk> Sun, 15 Jun 2008 14:44:22 +0100
+
+disorder (4.0.1) unstable; urgency=low
+
+ * Version 4.0.1
+ * Update READMEs
+ * Make group modification noisier
+ * Makefile fiddling for Automake/Libtool's benefit
+
+ -- Richard Kettlewell <richard@dekabrach.anjou.terraraq.org.uk> Sun, 15 Jun 2008 12:22:52 +0100
+
+disorder (4.0) unstable; urgency=low
+
+ * Version 4.0
+
+ -- Richard Kettlewell <rjk@greenend.org.uk> Sun, 8 Jun 2008 14:44:05 +0100
disorder (3.0) unstable; urgency=low
--no-create-home jukebox
# If it happens that there's no audio group we don't fail; perhaps only
# network play was required.
- adduser --quiet jukebox audio || true
+ adduser jukebox audio || true
}
configure_init_d() {
# USA
#
-cgidir=/usr/lib/cgi-bin
+cgiexecdir=/usr/lib/cgi-bin
httpdir=/var/www
browser=x-www-browser
# Options to configure. This can be overridden by the caller if necessary.
-CONFIGURE=--prefix=/usr --sysconfdir=/etc --localstatedir=/var/lib --mandir=/usr/share/man --with-browser=$browser cgidir="${cgidir}" httpdir="${httpdir}"
+CONFIGURE=--prefix=/usr --sysconfdir=/etc --localstatedir=/var/lib --mandir=/usr/share/man --with-browser=${browser} cgiexecdir="${cgiexecdir}" httpdir="${httpdir}"
# Set DEB_BUILD_OPTIONS=noopt to produce a non-optimized build.
ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
rm -f debian/disorder-server/usr/share/man/man5/disorder_protocol.5
$(MKDIR) debian/disorder-server/etc/disorder
$(MKDIR) debian/disorder-server/etc/init.d
- $(MKDIR) debian/disorder-server/usr/lib/cgi-bin
+ $(MKDIR) debian/disorder-server${cgiexecdir}
$(MKDIR) debian/disorder-server/var/lib/disorder
$(INSTALL_SCRIPT) examples/disorder.init \
debian/disorder-server/etc/init.d/disorder
$(INSTALL_DATA) debian/etc.disorder.options.user \
debian/disorder-server/etc/disorder/options.user
$(LIBTOOL) --mode=install $(INSTALL_PROGRAM) cgi/disorder \
- $(shell pwd)/debian/disorder-server/usr/lib/cgi-bin/disorder
+ $(shell pwd)/debian/disorder-server${cgiexecdir}/disorder
dpkg-shlibdeps -Tdebian/substvars.disorder-server \
- debian/disorder-server/usr/lib/cgi-bin/disorder \
+ debian/disorder-server${cgiexecdir}/disorder \
debian/disorder-server/usr/sbin/* \
debian/disorder-server/usr/lib/disorder/*.so*
rm -rf debian/disorder-server/usr/share/doc/disorder-server
GdkEventButton *event,
gpointer user_data);
void choose_play_completed(void attribute((unused)) *v,
- const char *error);
+ const char *err);
char *choose_get_track(GtkTreeIter *iter);
char *choose_get_sort(GtkTreeIter *iter);
char *choose_get_display(GtkTreeIter *iter);
char **events = split(icons[n].events, 0, 0, 0, 0);
while(*events)
event_register(*events++, icon_changed, &icons[n]);
+ event_register("connected-changed", icon_changed, &icons[n]);
}
/* create the adjustments for the volume control */
volume_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, goesupto,
/* Called when we think the choose tree might need updating */
void play_completed(void *v,
- const char *error);
+ const char *err);
/* Login details */
{ DISORDER_RANDOM_ENABLED, "random-changed" },
{ DISORDER_TRACK_PAUSED, "pause-changed" },
{ DISORDER_PLAYING, "playing-changed" },
+ { DISORDER_CONNECTED, "connected-changed" },
};
#define NSTATE_EVENTS (sizeof state_events / sizeof *state_events)
GtkItemFactory *mainmenufactory;
static void about_popup_got_version(void *v,
- const char *error,
+ const char *err,
const char *value);
/** @brief Called when the quit option is activated
static const char *get_edited_namepart(struct prefdata *f);
static void set_edited_namepart(struct prefdata *f, const char *value);
static void set_namepart(struct prefdata *f, const char *value);
-static void set_namepart_completed(void *v, const char *error);
+static void set_namepart_completed(void *v, const char *err);
static void kickoff_string(struct prefdata *f);
static void completed_string(struct prefdata *f);
static void set_edited_boolean(struct prefdata *f, const char *value);
static void set_boolean(struct prefdata *f, const char *value);
-static void prefdata_completed(void *v, const char *error, const char *value);
+static void prefdata_completed(void *v, const char *err, const char *value);
static void prefdata_onerror(struct callbackdata *cbd,
int code,
const char *msg);
time_t last_playing;
static void queue_completed(void *v,
- const char *error,
+ const char *err,
struct queue_entry *q);
static void playing_completed(void *v,
- const char *error,
+ const char *err,
struct queue_entry *q);
/** @brief Called when either the actual queue or the playing track change */
/** @brief Pop-up menu for recently played list */
static struct menuitem recent_menuitems[] = {
{ "Track properties", ql_properties_activate, ql_properties_sensitive,0, 0 },
+ { "Play track", ql_play_activate, ql_play_sensitive, 0, 0 },
{ "Select all tracks", ql_selectall_activate, ql_selectall_sensitive, 0, 0 },
{ "Deselect all tracks", ql_selectnone_activate, ql_selectnone_sensitive, 0, 0 },
};
users_reporter = gtk_label_new("");
gtk_label_set_ellipsize(GTK_LABEL(users_reporter), PANGO_ELLIPSIZE_END);
gtk_misc_set_alignment(GTK_MISC(users_reporter), 0.99, 0);
+ g_signal_connect(users_reporter, "destroy",
+ G_CALLBACK(gtk_widget_destroyed), &users_reporter);
}
return users_reporter;
}
gtk_window_present(GTK_WINDOW(users_window));
return;
}
+ /* Destroy old widgets */
+ if(users_reporter)
+ gtk_widget_destroy(users_reporter);
/* Create the window */
users_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_style(users_window, tool_style);
/** @brief Trivial completion callback
* @param v User data
- * @param error Error string or NULL on succes
+ * @param err Error string or NULL on succes
*/
typedef void disorder_eclient_no_response(void *v,
- const char *error);
+ const char *err);
/** @brief String result completion callback
* @param v User data
- * @param error Error string or NULL on succes
+ * @param err Error string or NULL on succes
* @param value Result or NULL
*
* @p error will be NULL on success. In this case @p value will be the result
* @p error will be non-NULL on failure. In this case @p value is always NULL.
*/
typedef void disorder_eclient_string_response(void *v,
- const char *error,
+ const char *err,
const char *value);
/** @brief String result completion callback
* @param v User data
- * @param error Error string or NULL on succes
+ * @param err Error string or NULL on succes
* @param value Result or 0
*
* @p error will be NULL on success. In this case @p value will be the result.
* @p error will be non-NULL on failure. In this case @p value is always 0.
*/
typedef void disorder_eclient_integer_response(void *v,
- const char *error,
+ const char *err,
long value);
/** @brief Volume completion callback
* @param v User data
- * @param error Error string or NULL on success
+ * @param err Error string or NULL on success
* @param l Left channel volume
* @param r Right channel volume
*
* 0.
*/
typedef void disorder_eclient_volume_response(void *v,
- const char *error,
+ const char *err,
int l, int r);
/** @brief Queue request completion callback
* @param v User data
- * @param error Error string or NULL on success
+ * @param err Error string or NULL on success
* @param q Head of queue data list
*
* @p error will be NULL on success. In this case @p q will be the (head of
* be ignored in the error case.
*/
typedef void disorder_eclient_queue_response(void *v,
- const char *error,
+ const char *err,
struct queue_entry *q);
/** @brief List request completion callback
* @param v User data
- * @param error Error string or NULL on success
+ * @param err Error string or NULL on success
* @param nvec Number of elements in response list
* @param vec Pointer to response list
*
* be 0 and NULL.
*/
typedef void disorder_eclient_list_response(void *v,
- const char *error,
+ const char *err,
int nvec, char **vec);
disorder_eclient *disorder_eclient_new(const disorder_eclient_callbacks *cb,
#include "unicode.h"
#include "unidata.h"
#include "base64.h"
+#include "sendmail.h"
#define RESCAN "disorder-rescan"
#define DEADLOCK "disorder-deadlock"
/* generic db routines *******************************************************/
-/* fetch and decode a database entry. Returns 0, DB_NOTFOUND or
- * DB_LOCK_DEADLOCK. */
+/** @brief Fetch and decode a database entry
+ * @param db Database
+ * @param track Track name
+ * @param kp Where to put decoded list (or NULL if you don't care)
+ * @param tid Owning transaction
+ * @return 0, @c DB_NOTFOUND or @c DB_LOCK_DEADLOCK
+ */
int trackdb_getdata(DB *db,
const char *track,
struct kvp **kp,
switch(err = db->get(db, tid, make_key(&key, track),
prepare_data(&data), 0)) {
case 0:
- *kp = kvp_urldecode(data.data, data.size);
+ if(kp)
+ *kp = kvp_urldecode(data.data, data.size);
return 0;
case DB_NOTFOUND:
- *kp = 0;
+ if(kp)
+ *kp = 0;
return err;
case DB_LOCK_DEADLOCK:
error(0, "error querying database: %s", db_strerror(err));
char *ptr;
int err;
size_t l, last_dir_len = 0;
- char *last_dir = 0, *track, *alias;
+ char *last_dir = 0, *track;
struct kvp *p;
dl = strlen(dir);
if((err = trackdb_getdata(trackdb_prefsdb,
track, &p, tid)) == DB_LOCK_DEADLOCK)
goto deadlocked;
+ /* There's an awkward question here...
+ *
+ * If a track shares a directory with its alias then we could
+ * do one of three things:
+ * - report both. Looks ridiculuous in most UIs.
+ * - report just the alias. Remarkably inconvenient to write
+ * UI code for!
+ * - report just the real name. Ugly if the UI doesn't prettify
+ * names via the name parts.
+ */
+#if 1
+ /* If this file is an alias for a track in the same directory then we
+ * skip it */
+ struct kvp *t = kvp_urldecode(d.data, d.size);
+ const char *alias_target = kvp_get(t, "_alias_for");
+ if(!(alias_target
+ && !strcmp(d_dirname(alias_target),
+ d_dirname(track))))
+ if(track_matches(dl, k.data, k.size, re))
+ vector_append(v, track);
+#else
/* if this file has an alias in the same directory then we skip it */
+ char *alias;
if((err = compute_alias(&alias, track, p, tid)))
goto deadlocked;
if(!(alias && !strcmp(d_dirname(alias), d_dirname(track))))
if(track_matches(dl, k.data, k.size, re))
vector_append(v, track);
+#endif
}
}
err = cursor->c_get(cursor, &k, &d, DB_NEXT);
* @return null-terminated array of track names, or NULL on deadlock
*
* The most recently added track is first in the array.
- *
- * TODO: exclude tracks that have been deleted again.
- *
*/
static char **trackdb_new_tid(int *ntracksp,
int maxtracks,
DBT k, d;
int err = 0;
struct vector tracks[1];
+ hash *h = hash_new(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));
+ && !(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_PREV))) {
+ char *const track = xstrndup(d.data, d.size);
+ /* Don't add any track more than once */
+ if(hash_add(h, track, "", HASH_INSERT))
+ continue;
+ /* See if the track still exists */
+ err = trackdb_getdata(trackdb_tracksdb, track, NULL/*kp*/, tid);
+ if(err == DB_NOTFOUND)
+ continue; /* It doesn't, skip it */
+ if(err == DB_LOCK_DEADLOCK)
+ break; /* Doh */
+ vector_append(tracks, track);
+ }
switch(err) {
case 0: /* hit maxtracks */
case DB_NOTFOUND: /* ran out of tracks */
}
} else if(!strcmp(key, "email")) {
if(*value) {
- if(!strchr(value, '@')) {
- error(0, "invalid email address '%s' for user '%s'", user, value);
+ if(!email_valid(value)) {
+ error(0, "invalid email address '%s' for user '%s'", value, user);
return -1;
}
} else
echo "(enter one or more directories separated by spaces)"
read -r roots
ok=true
+ anyroots=false
for root in $roots; do
if [ ! -d $root ]; then
echo "'$root' does not exist"
ok=false
+ else
+ anyroots=true
fi
done
- if $ok; then
+ if $anyroots && $ok; then
break
fi
done
fi
if [ -z "$encoding" ]; then
- echo
- echo "What filesystem encoding should I assume for track names?"
- echo "(e.g. UTF-8, ISO-8859-1, ...)"
- read -r encoding
+ while :; do
+ echo
+ echo "What filesystem encoding should I assume for track names?"
+ echo "(e.g. UTF-8, ISO-8859-1, ...)"
+ read -r encoding
+ if [ ! -z "$encoding" ]; then
+ break
+ fi
+ done
fi
if [ -z "$port" ]; then
none )
break
;;
- [^0-9] )
+ [^0-9] | "" )
echo "'$port' is not a valid port number"
continue
;;
none )
break
;;
- [^0-9] )
+ [^0-9] | "" )
echo "'$mcast_port' is not a valid port number"
continue
;;
echo
echo "Proposed DisOrder setup:"
echo " Music directory: $roots"
-if [ $port = none ]; then
+if [ "$port" = none ]; then
echo " Do not listen on a TCP port"
else
echo " TCP port to listen on: $port"
sink_writes(ev_writer_sink(c->w), "550 Cannot send a reminder email\n");
return 1;
}
- /* TODO use email_valid() */
if(!(email = kvp_get(k, "email"))
- || !strchr(email, '@')) {
+ || !email_valid(email)) {
error(0, "user '%s' has no valid email address", vec[0]);
sink_writes(ev_writer_sink(c->w), "550 Cannot send a reminder email\n");
return 1;
static void log_params(snd_pcm_hw_params_t *hwparams,
snd_pcm_sw_params_t *swparams) {
snd_pcm_uframes_t f;
- unsigned u;
return; /* too verbose */
if(hwparams) {
info("sw silence_size=%lu", (unsigned long)f);
snd_pcm_sw_params_get_silence_threshold(swparams, &f);
info("sw silence_threshold=%lu", (unsigned long)f);
- snd_pcm_sw_params_get_sleep_min(swparams, &u);
- info("sw sleep_min=%lu", (unsigned long)u);
+#if HAVE_SND_PCM_SW_PARAMS_GET_SLEEP_MIN
+ {
+ unsigned u;
+
+ snd_pcm_sw_params_get_sleep_min(swparams, &u);
+ info("sw sleep_min=%lu", (unsigned long)u);
+ }
+#endif
snd_pcm_sw_params_get_start_threshold(swparams, &f);
info("sw start_threshold=%lu", (unsigned long)f);
snd_pcm_sw_params_get_stop_threshold(swparams, &f);
info("sw stop_threshold=%lu", (unsigned long)f);
+#if HAVE_SND_PCM_SW_PARAMS_GET_XFER_ALIGN
snd_pcm_sw_params_get_xfer_align(swparams, &f);
info("sw xfer_align=%lu", (unsigned long)f);
+#endif
}
}
<p class=entry>
<a href="@url?action=choose&dir=@urlquote{@track}">
<img class=button src="@image{directory}" alt="">
- @display
+ @quote{@display}
</a>
</p>}
</div>
}@#
<a href="@url?action=play&track=@urlquote{@track}&back=@urlquote{@thisurl}"
title="@label{choose.play}">
- @display
+ @quote{@display}
</a>
@if{@eq{@trackstate{@track}}{playing}}
{[<b>playing</b>]}
AM_CPPFLAGS=-I${top_srcdir}/lib -I../lib
-disorder_udplog_SOURCES=udplog.c
-disorder_udplog_LDADD=$(LIBOBJS) ../lib/libdisorder.a
+disorder_udplog_SOURCES=udplog.c ../lib/memgc.c
+disorder_udplog_LDADD=$(LIBOBJS) ../lib/libdisorder.a $(LIBGC)
disorder_udplog_DEPENDENCIES=../lib/libdisorder.a
TESTS=cookie.py dbversion.py dump.py files.py play.py queue.py \