chiark / gitweb /
Merge branch 'protogen'
authorRichard Kettlewell <rjk@terraraq.org.uk>
Sun, 7 Aug 2011 16:45:36 +0000 (17:45 +0100)
committerRichard Kettlewell <rjk@terraraq.org.uk>
Sun, 7 Aug 2011 16:45:36 +0000 (17:45 +0100)
30 files changed:
README.developers
cgi/actions.c
cgi/disorder-cgi.h
cgi/lookup.c
cgi/macros-disorder.c
clients/disorder.c
disobedience/choose-menu.c
disobedience/choose.c
disobedience/choose.h
disobedience/control.c
disobedience/globals.c
disobedience/lookup.c
disobedience/playlists.c
disobedience/properties.c
disobedience/queue-menu.c
disobedience/queue.c
doc/disorder_protocol.5.in
lib/Makefile.am
lib/client-common.c
lib/client-common.h
lib/client-stubs.c [new file with mode: 0644]
lib/client-stubs.h [new file with mode: 0644]
lib/client.c
lib/client.h
lib/eclient-stubs.c [new file with mode: 0644]
lib/eclient-stubs.h [new file with mode: 0644]
lib/eclient.c
lib/eclient.h
scripts/Makefile.am
scripts/protocol [new file with mode: 0755]

index 20cd7d9b6adecc0fc059697581753291a9b9c657..692dd12be2bd7d42021b47a0c1829e3f2f8a3c49 100644 (file)
@@ -9,13 +9,12 @@ Dependencies:
 
    * On Debian and derivatives this should work:
 
-     apt-get install gcc libc-dev automake autoconf libtool libgtk2.0-dev \
+     apt-get install gcc libc6-dev automake autoconf libtool libgtk2.0-dev \
                     libgc-dev libgcrypt-dev libpcre3-dev libvorbis-dev \
-                    libao-dev libmad0-dev libasound2-dev libdb4.3-dev \
+                    libao-dev libmad0-dev libasound2-dev libdb4.5-dev \
                     libflac-dev vorbis-tools wget libsamplerate0-dev
 
-     On lenny use libdb4.5-deb.  libdb4.6 does not work (and configure will
-     refuse to use it).
+     libdb4.6 does not work (and configure will refuse to use it).
 
    * On FreeBSD you'll need at least these packages:
        autotools bash flac mad boehm-gc db43 gmake gsed libao libgcrypt wget
@@ -107,14 +106,11 @@ The Server:
    * To add a new command:
      1) Add a new c_ function and table entry in server/server.c
      2) Document the new command in doc/disorder_protocol.5.in
-     3) Add a new function to lib/client.c
+     3) Add a new function to scripts/protocol.
      4) Add a new function to lib/eclient.c
      5) Add a new function to python/disorder.py.in
      6) Add a new command to clients/disorder.c and update doc/disorder.1.in
      7) Add a new test somewhere in tests/*.py
-     Depending on the purpose of the command it may be acceptable to leave out
-     some of the client side work - for instance commands only ever used by the
-     web interface are not implemented in lib/eclient.c.
 
    * See disorder_protocol(5) for details of how the status code is
      constructed, and the existing commands for examples.
@@ -127,10 +123,13 @@ The Server:
 
 Web Interface:
 
+   * The support targets are current Firefox, Chrome, IE and Safari.  Obscure,
+     obsolete or text-only browsers are not of significant interest.
+
    * The web interface does not use Javascript or Flash and I would like to
-     keep it that way; Javascript might be acceptable but it must degrade
-     gracefuly if disabled.  Clever use of CSS is OK provided it works well on
-     the mainstream browsers.
+     keep it that way.  Javascript is likely to be acceptable if useful, but it
+     should degrade gracefuly if unavailable.  Clever use of CSS is OK provided
+     it works well on the mainstream browsers.
 
    * Update templates/help.tmpl for any changes you make.
 
@@ -148,7 +147,7 @@ Disobedience:
      a problem for Disobedience than the server.  Use the GLIB event loop to
      deal with long-running operations if you do need any.
 
-   * Update doc/disobedience.1.in for any changes you make.
+   * Update the contents of disobedience/manual/ for any changes you make.
 
 New Platforms:
 
index f6755b0317ad2ca080c1d28098e3902df38cb559..584da76c040a88c0ea8988c17366c7ae86201acd 100644 (file)
@@ -240,16 +240,17 @@ static void act_play(void) {
   char **tracks;
   int ntracks, n;
   struct tracksort_data *tsd;
+  char *id;
   
   if(dcgi_client) {
     if((track = cgi_get("track"))) {
-      disorder_play(dcgi_client, track);
+      disorder_play(dcgi_client, track, &id);
     } else if((dir = cgi_get("dir"))) {
       if(disorder_files(dcgi_client, dir, 0, &tracks, &ntracks))
         ntracks = 0;
       tsd = tracksort_init(ntracks, tracks, "track");
       for(n = 0; n < ntracks; ++n)
-        disorder_play(dcgi_client, tsd[n].track);
+        disorder_play(dcgi_client, tsd[n].track, &id);
     }
   }
   redirect(0);
index 77885781dcf8e31061d720fb92debb7b8c8c12a5..e9deab21c295b997d1f4e6d55a5eeb5d1f7cbc3c 100644 (file)
@@ -91,8 +91,8 @@ extern struct queue_entry *dcgi_queue;
 extern struct queue_entry *dcgi_playing;
 extern struct queue_entry *dcgi_recent;
 
-extern int dcgi_volume_left;
-extern int dcgi_volume_right;
+extern long dcgi_volume_left;
+extern long dcgi_volume_right;
 
 extern char **dcgi_new;
 extern int dcgi_nnew;
index fd94900b9b1c39dfb935276b3be3a736d906b39a..2b6d59432a43d93b0d1087d543e08c4f7c8f1824 100644 (file)
@@ -33,8 +33,8 @@ struct queue_entry *dcgi_queue;
 struct queue_entry *dcgi_playing;
 struct queue_entry *dcgi_recent;
 
-int dcgi_volume_left;
-int dcgi_volume_right;
+long dcgi_volume_left;
+long dcgi_volume_right;
 
 char **dcgi_new;
 int dcgi_nnew;
@@ -68,7 +68,7 @@ void dcgi_lookup(unsigned want) {
     queuemap_add(dcgi_playing);
   }
   if(need & DCGI_NEW)
-    disorder_new_tracks(dcgi_client, &dcgi_new, &dcgi_nnew, 0);
+    disorder_new_tracks(dcgi_client, 0, &dcgi_new, &dcgi_nnew);
   if(need & DCGI_RECENT) {
     /* we need to reverse the order of the list */
     disorder_recent(dcgi_client, &r);
index 29835bbc849c196fcea60124fc55b10969cf6bde..1ef1ee1f3d0ed4f8e2db80197f0b0ecd6b87e41e 100644 (file)
@@ -159,10 +159,11 @@ static int exp_part(int nargs,
       return 0;
   }
   if(dcgi_client
-     && !disorder_part(dcgi_client, (char **)&s,
+     && !disorder_part(dcgi_client,
                        track,
                        !strcmp(context, "short") ? "display" : context,
-                       part)) {
+                       part,
+                       (char **)&s)) {
     if(!strcmp(context, "short"))
       s = truncate_for_display(s, config->short_display);
     return sink_writes(output, cgi_sgmlquote(s)) < 0 ? -1 : 0;
@@ -457,7 +458,7 @@ static int exp_volume(int attribute((unused)) nargs,
                       struct sink *output,
                       void attribute((unused)) *u) {
   dcgi_lookup(DCGI_VOLUME);
-  return sink_printf(output, "%d",
+  return sink_printf(output, "%ld",
                      !strcmp(args[0], "left")
                          ? dcgi_volume_left : dcgi_volume_right) < 0 ? -1 : 0;
 }
@@ -625,7 +626,7 @@ static int exp_trackstate(int attribute((unused)) nargs,
 
   if(!dcgi_client)
     return 0;
-  if(disorder_resolve(dcgi_client, &track, args[0]))
+  if(disorder_resolve(dcgi_client, args[0], &track))
     return 0;
   dcgi_lookup(DCGI_PLAYING);
   if(dcgi_playing && !strcmp(track, dcgi_playing->track))
@@ -661,7 +662,7 @@ static int exp_resolve(int attribute((unused)) nargs,
                        void attribute((unused)) *u) {
   char *r;
 
-  if(dcgi_client && !disorder_resolve(dcgi_client, &r, args[0]))
+  if(dcgi_client && !disorder_resolve(dcgi_client, args[0], &r))
     return sink_writes(output, r) < 0 ? -1 : 0;
   return 0;
 }
@@ -930,7 +931,7 @@ static int exp_dirs(int nargs,
                     const struct mx_node **args,
                     struct sink *output,
                     void *u) {
-  return exp__files_dirs(nargs, args, output, u, "dir", disorder_directories);
+  return exp__files_dirs(nargs, args, output, u, "dir", disorder_dirs);
 }
 
 static int exp__search_shim(disorder_client *c, const char *terms,
index 64694ef836cf7b0f29519756e733902411ed93ad..b401c62bbdb4204d054eedadf00b6b3592b2ba06 100644 (file)
@@ -138,8 +138,9 @@ static void cf_playing(char attribute((unused)) **argv) {
 }
 
 static void cf_play(char **argv) {
+  char *id;
   while(*argv)
-    if(disorder_play(getclient(), *argv++)) exit(EXIT_FAILURE);
+    if(disorder_play(getclient(), *argv++, &id)) exit(EXIT_FAILURE);
 }
 
 static void cf_remove(char **argv) {
@@ -244,7 +245,7 @@ static int isarg_regexp(const char *s) {
 }
 
 static void cf_dirs(char **argv) {
-  cf_somelist(argv, disorder_directories);
+  cf_somelist(argv, disorder_dirs);
 }
 
 static void cf_files(char **argv) {
@@ -316,10 +317,10 @@ static void cf_stats(char attribute((unused)) **argv) {
 }
 
 static void cf_get_volume(char attribute((unused)) **argv) {
-  int l, r;
+  long l, r;
 
   if(disorder_get_volume(getclient(), &l, &r)) exit(EXIT_FAILURE);
-  xprintf("%d %d\n", l, r);
+  xprintf("%ld %ld\n", l, r);
 }
 
 static void cf_set_volume(char **argv) {
@@ -344,7 +345,7 @@ static void cf_move(char **argv) {
 static void cf_part(char **argv) {
   char *s;
 
-  if(disorder_part(getclient(), &s, argv[0], argv[1], argv[2])) exit(EXIT_FAILURE);
+  if(disorder_part(getclient(), argv[0], argv[1], argv[2], &s)) exit(EXIT_FAILURE);
   xprintf("%s\n", nullcheck(utf82mb_f(s)));
 }
 
@@ -359,7 +360,7 @@ static void cf_authorize(char **argv) {
 static void cf_resolve(char **argv) {
   char *track;
 
-  if(disorder_resolve(getclient(), &track, argv[0])) exit(EXIT_FAILURE);
+  if(disorder_resolve(getclient(), argv[0], &track)) exit(EXIT_FAILURE);
   xprintf("%s\n", nullcheck(utf82mb_f(track)));
 }
 
@@ -415,7 +416,7 @@ static int isarg_integer(const char *s) {
 static void cf_new(char **argv) {
   char **vec;
 
-  if(disorder_new_tracks(getclient(), &vec, 0, argv[0] ? atoi(argv[0]) : 0))
+  if(disorder_new_tracks(getclient(), argv[0] ? atol(argv[0]) : 0, &vec, 0))
     exit(EXIT_FAILURE);
   while(*vec)
     xprintf("%s\n", nullcheck(utf82mb(*vec++)));
@@ -586,31 +587,27 @@ static void cf_schedule_del(char **argv) {
 }
 
 static void cf_schedule_play(char **argv) {
-  if(disorder_schedule_add(getclient(),
-                          dateparse(argv[0]),
-                          argv[1],
-                          "play",
-                          argv[2]))
+  if(disorder_schedule_add_play(getclient(),
+                                dateparse(argv[0]),
+                                argv[1],
+                                argv[2]))
     exit(EXIT_FAILURE);
 }
 
 static void cf_schedule_set_global(char **argv) {
-  if(disorder_schedule_add(getclient(),
-                          dateparse(argv[0]),
-                          argv[1],
-                          "set-global",
-                          argv[2],
-                          argv[3]))
+  if(disorder_schedule_add_set_global(getclient(),
+                                      dateparse(argv[0]),
+                                      argv[1],
+                                      argv[2],
+                                      argv[3]))
     exit(EXIT_FAILURE);
 }
 
 static void cf_schedule_unset_global(char **argv) {
-  if(disorder_schedule_add(getclient(),
-                          dateparse(argv[0]),
-                          argv[1],
-                          "set-global",
-                          argv[2],
-                          (char *)0))
+  if(disorder_schedule_add_unset_global(getclient(),
+                                        dateparse(argv[0]),
+                                        argv[1],
+                                        argv[2]))
     exit(EXIT_FAILURE);
 }
 
index b0f59b5dad7deb3b6b23f104c6ca8061362f1243..b4aa0ce5acf051e510ab660bc9ccc2468a8c5bad 100644 (file)
@@ -29,7 +29,9 @@ static void choose_playchildren_callback(GtkTreeModel *model,
 static void choose_playchildren_received(void *v,
                                          const char *err,
                                          int nvec, char **vec);
-static void choose_playchildren_played(void *v, const char *err);
+static void choose_playchildren_played(void *v,
+                                       const char *err,
+                                       const char *id);
 
 /** @brief Popup menu */
 static GtkWidget *choose_menu;
@@ -116,7 +118,7 @@ static void choose_play_activate(GtkMenuItem attribute((unused)) *item,
                                       choose_gather_selected_files_callback,
                                       v);
   for(int n = 0; n < v->nvec; ++n)
-    disorder_eclient_play(client, v->vec[n], choose_play_completed, 0);
+    disorder_eclient_play(client, choose_play_completed, v->vec[n], 0);
 }
   
 static int choose_properties_sensitive(void *extra) {
@@ -250,11 +252,12 @@ static void choose_playchildren_received(void attribute((unused)) *v,
     return;
   }
   for(int n = 0; n < nvec; ++n)
-    disorder_eclient_play(client, vec[n], choose_playchildren_played, NULL);
+    disorder_eclient_play(client, choose_playchildren_played, vec[n], NULL);
 }
 
 static void choose_playchildren_played(void attribute((unused)) *v,
-                                       const char *err) {
+                                       const char *err,
+                                       const char attribute((unused)) *id) {
   if(err) {
     popup_protocol_error(0, err);
     return;
index a1d50c1a62c18f584af0f74435f9bd533f435be8..13a1da00d822d2019ddd7be960ba357409db3f08 100644 (file)
@@ -364,7 +364,8 @@ static void choose_files_completed(void *v,
 }
 
 void choose_play_completed(void attribute((unused)) *v,
-                           const char *err) {
+                           const char *err,
+                           const char attribute((unused)) *id) {
   if(err)
     popup_protocol_error(0, err);
 }
@@ -386,7 +387,7 @@ static void choose_state_toggled
   const char *track = choose_get_track(it);
   if(queued(track))
     return;
-  disorder_eclient_play(client, track, choose_play_completed, 0);
+  disorder_eclient_play(client, choose_play_completed, track, 0);
   
 }
 
index 521333211ec45ee328cfcd9c409bf84749cd68b1..9496abdc493c7fd01cd9dc53e5b42107d722ae70 100644 (file)
@@ -62,8 +62,9 @@ struct choosedata *choose_path_to_data(GtkTreePath *path);
 gboolean choose_button_event(GtkWidget *widget,
                              GdkEventButton *event,
                              gpointer user_data);
-void choose_play_completed(void attribute((unused)) *v,
-                           const char *err);
+void choose_play_completed(void *v,
+                           const char *err,
+                           const char *id);
 char *choose_get_track(GtkTreeIter *iter);
 char *choose_get_sort(GtkTreeIter *iter);
 char *choose_get_display(GtkTreeIter *iter);
index f45492f1e191ed767f273fe452d3f18c26721462..1cdc190b4ea504af212a2b374925b9ee561fcdf6 100644 (file)
@@ -468,9 +468,7 @@ static void toggled_menu(GtkCheckMenuItem attribute((unused)) *menuitem,
 
 /** @brief Called when a volume command completes */
 static void volume_completed(void attribute((unused)) *v,
-                             const char *err,
-                             int attribute((unused)) l,
-                             int attribute((unused)) r) {
+                             const char *err) {
   if(err)
     popup_protocol_error(0, err);
   /* We don't set the UI's notion of the volume here, it is set from the log
@@ -498,10 +496,10 @@ static void volume_adjusted(GtkAdjustment attribute((unused)) *a,
     if(backend && backend->set_volume)
       backend->set_volume(&l, &r);
   } else
-    disorder_eclient_volume(client, volume_completed,
-                            nearbyint(left(v, b) * 100),
-                            nearbyint(right(v, b) * 100),
-                            0);
+    disorder_eclient_set_volume(client, volume_completed,
+                                nearbyint(left(v, b) * 100),
+                                nearbyint(right(v, b) * 100),
+                                0);
 }
 
 /** @brief Formats the volume value */
index 2734c320f66cef621a8db0a14dadec1d4f5536f5..a6b4ca4bd5bb1c52dd700f8de2b6efb487e5b02f 100644 (file)
@@ -217,9 +217,9 @@ void popup_globals(void) {
 }
 
 /** @brief Called when any global pref changes */
-static void globals_pref_changed(const char *event,
+static void globals_pref_changed(const char attribute((unused)) *event,
                                 void *eventdata,
-                                void *callbackdata) {
+                                void attribute((unused)) *callbackdata) {
   const char *pref = eventdata;
   if(!globals_window)
     return;                    /* not paying attention */
index 68939aef2915f015b925c5699e196a70017ef18c..21bacc47a613431458c5f14b6c23e236dbdfe141 100644 (file)
@@ -74,8 +74,8 @@ static void namepart_fill(const char *track,
   D(("namepart_fill %s %s %s %s", track, context, part, key));
   ++namepart_lookups_outstanding;
   D(("namepart_lookups_outstanding -> %d\n", namepart_lookups_outstanding));
-  disorder_eclient_namepart(client, namepart_completed,
-                            track, context, part, (void *)key);
+  disorder_eclient_part(client, namepart_completed,
+                        track, context, part, (void *)key);
 }
 
 /** @brief Look up a namepart
index 3b38bc7755787259f8cfba0a1ef5683c97ce41f4..c5273aebad253df8111828b446a0f47f8a33b316 100644 (file)
@@ -253,7 +253,8 @@ static int playlistcmp(const void *ap, const void *bp) {
 /* Playlists menu ----------------------------------------------------------- */
 
 static void playlist_menu_playing(void attribute((unused)) *v,
-                                  const char *err) {
+                                  const char *err,
+                                  const char attribute((unused)) *id) {
   if(err)
     popup_submsg(playlist_window, GTK_MESSAGE_ERROR, err);
 }
@@ -270,7 +271,7 @@ static void playlist_menu_received_content(void attribute((unused)) *v,
     return;
   }
   for(int n = 0; n < nvec; ++n)
-    disorder_eclient_play(client, vec[n], playlist_menu_playing, NULL);
+    disorder_eclient_play(client, playlist_menu_playing, vec[n], NULL);
 }
 
 /** @brief Called to activate a playlist
index fb39ea4f23de17212b3e103f6ed6b387b0409bca..ded862b77940ebad610696b1d3da60fb56505bb7 100644 (file)
@@ -338,8 +338,8 @@ static void kickoff_namepart(struct prefdata *f) {
    * wanted was the underlying preference, but in fact it should always match
    * and will supply a sane default without having to know how to parse tracks
    * names (which implies knowing collection roots). */
-  disorder_eclient_namepart(client, prefdata_completed,
-                            f->track, "display", f->p->part, f);
+  disorder_eclient_part(client, prefdata_completed,
+                        f->track, "display", f->p->part, f);
 }
 
 static void completed_namepart(struct prefdata *f) {
index ebf85553e688539f994318dad9cb2e669c0504d9..faf16cd67d3e297906664b90a6e6ada45e954d1f 100644 (file)
@@ -129,7 +129,7 @@ static void ql_remove_activate_callback(GtkTreeModel *model,
   struct queue_entry *q = ql_iter_to_q(model, iter);
 
   if(q != playing_track)
-    disorder_eclient_remove(client, q->id, ql_remove_completed, q);
+    disorder_eclient_remove(client, ql_remove_completed, q->id, q);
 }
 
 void ql_remove_activate(GtkMenuItem attribute((unused)) *menuitem,
@@ -148,7 +148,8 @@ int ql_play_sensitive(void *extra) {
     && gtk_tree_selection_count_selected_rows(ql->selection) > 0;
 }
 
-static void ql_play_completed(void attribute((unused)) *v, const char *err) {
+static void ql_play_completed(void attribute((unused)) *v, const char *err,
+                              const char attribute((unused)) *id) {
   if(err)
     popup_protocol_error(0, err);
 }
@@ -159,7 +160,7 @@ static void ql_play_activate_callback(GtkTreeModel *model,
                                       gpointer attribute((unused)) data) {
   struct queue_entry *q = ql_iter_to_q(model, iter);
 
-  disorder_eclient_play(client, q->track, ql_play_completed, q);
+  disorder_eclient_play(client, ql_play_completed, q->track, q);
 }
 
 void ql_play_activate(GtkMenuItem attribute((unused)) *menuitem,
index c495bd85cc6471fb47ee58f0f8cbf6c3363d36a7..95ff0d5efe73d05beb9874b8f9a51c0ff16eea28 100644 (file)
@@ -205,9 +205,10 @@ static void queue_drop(struct queuelike attribute((unused)) *ql,
     /* Tell the server to move them.  The log will tell us about the change (if
      * indeed it succeeds!), so no need to rearrange the model now. */
     disorder_eclient_moveafter(client,
+                               queue_drop_completed,
                                after_me ? after_me->id : "",
-                               ntracks, (const char **)ids,
-                               queue_drop_completed, NULL);
+                               (char **)ids, ntracks,
+                               NULL);
   } else {
     /* You can't tell the server to insert after the playing track by ID, you
      * have to send "". */
@@ -215,9 +216,10 @@ static void queue_drop(struct queuelike attribute((unused)) *ql,
       after_me = NULL;
     /* Play the tracks */
     disorder_eclient_playafter(client,
+                               queue_drop_completed,
                                after_me ? after_me->id : "",
-                               ntracks, (const char **)tracks,
-                               queue_drop_completed, NULL);
+                               (char **)tracks, ntracks,
+                               NULL);
   }
 }
 
index b9ed984ed2518bc5b0fe655282c0c2c7baeb8e56..58a7e8f078cbe39d47652b5489db195afb11e15f 100644 (file)
@@ -75,7 +75,8 @@ If \fIREGEXP\fR is present only matching files and directories are returned.
 .B confirm \fICONFIRMATION
 Confirm user registration.
 \fICONFIRMATION\fR is as returned from \fBregister\fR below.
-This command can be used without logging in.
+This command logs the user in.
+The response contains the logged-in username.
 .TP
 .B cookie \fICOOKIE
 Log a user back in using a cookie created with \fBmake\-cookie\fR.
@@ -340,9 +341,9 @@ Resolve a track name, i.e. if this is an alias then return the real track name.
 Resume the current track after a \fBpause\fR command.
 Requires the \fBpause\fR right.
 .TP
-.B revoke \fBcookie\fR
-Revoke a cookie previously created with \fBmake\-cookie\fR.
-It will not be possible to use this cookie in the future.
+.B revoke
+Revoke the current login's cookie.
+It will not be possible to use the cookie in the future.
 .TP
 .B rtp\-address
 Report the RTP broadcast (or multicast) address, in the form \fIADDRESS
@@ -436,6 +437,10 @@ Requires the \fBprefs\fR right.
 Set a global preference.
 Requires the \fBglobal prefs\fR right.
 .TP
+.B shutdown
+Requests server shutdown.
+Requires the \fBadmin\fR right.
+.TP
 .B stats
 Send server statistics in plain text in a response body.
 .TP
index eea622032829866a1af95a9ed135fd8d5d140d74..c8ac6a944caf28505e9d927d5c9b0f85b2d76ed2 100644 (file)
@@ -1,17 +1,17 @@
 #
 # This file is part of DisOrder.
-# Copyright (C) 2004-2009 Richard Kettlewell
+# Copyright (C) 2004-2010 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 3 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, see <http://www.gnu.org/licenses/>.
 #
@@ -35,7 +35,7 @@ libdisorder_a_SOURCES=charset.c charsetf.c charset.h  \
        byte-order.h                                    \
        cache.c cache.h                                 \
        cgi.c cgi.h                                     \
-       client.c client.h                               \
+       client.c client.h client-stubs.h                \
        client-common.c client-common.h                 \
        configuration.c configuration.h                 \
        cookies.c cookies.h                             \
@@ -134,10 +134,12 @@ definitions.h: Makefile
 defs.o: definitions.h versionstring.h
 defs.lo: definitions.h versionstring.h
 
+client.o: client-stubs.c
+
 rebuild-unicode:
        cd ${srcdir} && ${top_srcdir}/scripts/make-unidata
 
 CLEANFILES=definitions.h definitions.h.new version-string versionstring.h \
           *.gcda *.gcov *.gcno *.c.html index.html
 
-EXTRA_DIST=trackdb.c trackdb-stub.c
+EXTRA_DIST=trackdb.c trackdb-stub.c client-stubs.c
index d5c61ce62f6f08ee650fc4802843a81a8c38f00b..476117ce93fe524bcbd6027452ea510622430e9d 100644 (file)
@@ -74,6 +74,11 @@ socklen_t find_server(struct config *c,
   return len;
 }
 
+const char disorder__body[1];
+const char disorder__list[1];
+const char disorder__integer[1];
+const char disorder__time[1];
+
 /*
 Local Variables:
 c-basic-offset:2
index 736ac6f209a363cc936ec4b9af39c25e8e2b8483..26db71564faf091e7011694698bc83570d7b11ad 100644 (file)
 
 socklen_t find_server(struct config *c, struct sockaddr **sap, char **namep);
 
+/** @brief Marker for a command body */
+extern const char disorder__body[1];
+
+/** @brief Marker for a list of args */
+extern const char disorder__list[1];
+
+/** @brief Marker for an integer */
+extern const char disorder__integer[1];
+
+/** @brief Marker for a timestamp */
+extern const char disorder__time[1];
+
 #endif /* CLIENT_COMMON_H */
 
 /*
diff --git a/lib/client-stubs.c b/lib/client-stubs.c
new file mode 100644 (file)
index 0000000..10a1fb2
--- /dev/null
@@ -0,0 +1,480 @@
+/*
+ * Automatically generated file, see scripts/protocol
+ *
+ * DO NOT EDIT.
+ */
+/*
+ * This file is part of DisOrder.
+ * Copyright (C) 2010-11 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+/** @file lib/client-stubs.c
+ * @brief Generated client API implementation
+ */
+
+int disorder_adopt(disorder_client *c, const char *id) {
+  return disorder_simple(c, NULL, "adopt", id, (char *)NULL);
+}
+
+int disorder_adduser(disorder_client *c, const char *user, const char *password, const char *rights) {
+  return disorder_simple(c, NULL, "adduser", user, password, rights, (char *)NULL);
+}
+
+int disorder_allfiles(disorder_client *c, const char *dir, const char *re, char ***filesp, int *nfilesp) {
+  int rc = disorder_simple(c, NULL, "allfiles", dir, re, (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, filesp, nfilesp))
+    return -1;
+  return 0;
+}
+
+int disorder_confirm(disorder_client *c, const char *confirmation) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "confirm", confirmation, (char *)NULL);
+  if(rc)
+    return rc;
+  c->user = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_cookie(disorder_client *c, const char *cookie) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "cookie", cookie, (char *)NULL);
+  if(rc)
+    return rc;
+  c->user = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_deluser(disorder_client *c, const char *user) {
+  return disorder_simple(c, NULL, "deluser", user, (char *)NULL);
+}
+
+int disorder_dirs(disorder_client *c, const char *dir, const char *re, char ***filesp, int *nfilesp) {
+  int rc = disorder_simple(c, NULL, "dirs", dir, re, (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, filesp, nfilesp))
+    return -1;
+  return 0;
+}
+
+int disorder_disable(disorder_client *c) {
+  return disorder_simple(c, NULL, "disable", (char *)NULL);
+}
+
+int disorder_edituser(disorder_client *c, const char *username, const char *property, const char *value) {
+  return disorder_simple(c, NULL, "edituser", username, property, value, (char *)NULL);
+}
+
+int disorder_enable(disorder_client *c) {
+  return disorder_simple(c, NULL, "enable", (char *)NULL);
+}
+
+int disorder_enabled(disorder_client *c, int *enabledp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "enabled", (char *)NULL);
+  if(rc)
+    return rc;
+  if(boolean("enabled", v[0], enabledp))
+    return -1;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_exists(disorder_client *c, const char *track, int *existsp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "exists", track, (char *)NULL);
+  if(rc)
+    return rc;
+  if(boolean("exists", v[0], existsp))
+    return -1;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_files(disorder_client *c, const char *dir, const char *re, char ***filesp, int *nfilesp) {
+  int rc = disorder_simple(c, NULL, "files", dir, re, (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, filesp, nfilesp))
+    return -1;
+  return 0;
+}
+
+int disorder_get(disorder_client *c, const char *track, const char *pref, char **valuep) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "get", track, pref, (char *)NULL);
+  if(rc)
+    return rc;
+  *valuep = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_get_global(disorder_client *c, const char *pref, char **valuep) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "get-global", pref, (char *)NULL);
+  if(rc)
+    return rc;
+  *valuep = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_length(disorder_client *c, const char *track, long *lengthp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "length", track, (char *)NULL);
+  if(rc)
+    return rc;
+  *lengthp = atol(v[0]);
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_make_cookie(disorder_client *c, char **cookiep) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "make-cookie", (char *)NULL);
+  if(rc)
+    return rc;
+  *cookiep = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_move(disorder_client *c, const char *track, long delta) {
+  return disorder_simple(c, NULL, "move", track, disorder__integer, delta, (char *)NULL);
+}
+
+int disorder_moveafter(disorder_client *c, const char *target, char **ids, int nids) {
+  return disorder_simple(c, NULL, "moveafter", target, disorder__list, ids, nids, (char *)NULL);
+}
+
+int disorder_new_tracks(disorder_client *c, long max, char ***tracksp, int *ntracksp) {
+  int rc = disorder_simple(c, NULL, "new", disorder__integer, max, (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, tracksp, ntracksp))
+    return -1;
+  return 0;
+}
+
+int disorder_nop(disorder_client *c) {
+  return disorder_simple(c, NULL, "nop", (char *)NULL);
+}
+
+int disorder_part(disorder_client *c, const char *track, const char *context, const char *part, char **partp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "part", track, context, part, (char *)NULL);
+  if(rc)
+    return rc;
+  *partp = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_pause(disorder_client *c) {
+  return disorder_simple(c, NULL, "pause", (char *)NULL);
+}
+
+int disorder_play(disorder_client *c, const char *track, char **idp) {
+  return disorder_simple(c, idp, "play", track, (char *)NULL);
+}
+
+int disorder_playafter(disorder_client *c, const char *target, char **tracks, int ntracks) {
+  return disorder_simple(c, NULL, "playafter", target, disorder__list, tracks, ntracks, (char *)NULL);
+}
+
+int disorder_playing(disorder_client *c, struct queue_entry **playingp) {
+  return onequeue(c, "playing", playingp);
+}
+
+int disorder_playlist_delete(disorder_client *c, const char *playlist) {
+  return disorder_simple(c, NULL, "playlist-delete", playlist, (char *)NULL);
+}
+
+int disorder_playlist_get(disorder_client *c, const char *playlist, char ***tracksp, int *ntracksp) {
+  int rc = disorder_simple(c, NULL, "playlist-get", playlist, (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, tracksp, ntracksp))
+    return -1;
+  return 0;
+}
+
+int disorder_playlist_get_share(disorder_client *c, const char *playlist, char **sharep) {
+  return disorder_simple(c, sharep, "playlist-get-share", playlist, (char *)NULL);
+}
+
+int disorder_playlist_lock(disorder_client *c, const char *playlist) {
+  return disorder_simple(c, NULL, "playlist-lock", playlist, (char *)NULL);
+}
+
+int disorder_playlist_set(disorder_client *c, const char *playlist, char **tracks, int ntracks) {
+  return disorder_simple(c, NULL, "playlist-set", playlist, disorder__body, tracks, ntracks, (char *)NULL);
+}
+
+int disorder_playlist_set_share(disorder_client *c, const char *playlist, const char *share) {
+  return disorder_simple(c, NULL, "playlist-set-share", playlist, share, (char *)NULL);
+}
+
+int disorder_playlist_unlock(disorder_client *c) {
+  return disorder_simple(c, NULL, "playlist-unlock", (char *)NULL);
+}
+
+int disorder_playlists(disorder_client *c, char ***playlistsp, int *nplaylistsp) {
+  int rc = disorder_simple(c, NULL, "playlists", (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, playlistsp, nplaylistsp))
+    return -1;
+  return 0;
+}
+
+int disorder_prefs(disorder_client *c, const char *track, struct kvp **prefsp) {
+  return pairlist(c, prefsp, "prefs", track, (char *)NULL);
+}
+
+int disorder_queue(disorder_client *c, struct queue_entry **queuep) {
+  int rc = disorder_simple(c, NULL, "queue", (char *)NULL);
+  if(rc)
+    return rc;
+  if(readqueue(c, queuep))
+    return -1;
+  return 0;
+}
+
+int disorder_random_disable(disorder_client *c) {
+  return disorder_simple(c, NULL, "random-disable", (char *)NULL);
+}
+
+int disorder_random_enable(disorder_client *c) {
+  return disorder_simple(c, NULL, "random-enable", (char *)NULL);
+}
+
+int disorder_random_enabled(disorder_client *c, int *enabledp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "random-enabled", (char *)NULL);
+  if(rc)
+    return rc;
+  if(boolean("random-enabled", v[0], enabledp))
+    return -1;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_recent(disorder_client *c, struct queue_entry **recentp) {
+  int rc = disorder_simple(c, NULL, "recent", (char *)NULL);
+  if(rc)
+    return rc;
+  if(readqueue(c, recentp))
+    return -1;
+  return 0;
+}
+
+int disorder_reconfigure(disorder_client *c) {
+  return disorder_simple(c, NULL, "reconfigure", (char *)NULL);
+}
+
+int disorder_register(disorder_client *c, const char *username, const char *password, const char *email, char **confirmationp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "register", username, password, email, (char *)NULL);
+  if(rc)
+    return rc;
+  *confirmationp = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_reminder(disorder_client *c, const char *username) {
+  return disorder_simple(c, NULL, "reminder", username, (char *)NULL);
+}
+
+int disorder_remove(disorder_client *c, const char *id) {
+  return disorder_simple(c, NULL, "remove", id, (char *)NULL);
+}
+
+int disorder_rescan(disorder_client *c) {
+  return disorder_simple(c, NULL, "rescan", (char *)NULL);
+}
+
+int disorder_resolve(disorder_client *c, const char *track, char **resolvedp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "resolve", track, (char *)NULL);
+  if(rc)
+    return rc;
+  *resolvedp = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_resume(disorder_client *c) {
+  return disorder_simple(c, NULL, "resume", (char *)NULL);
+}
+
+int disorder_revoke(disorder_client *c) {
+  return disorder_simple(c, NULL, "revoke", (char *)NULL);
+}
+
+int disorder_rtp_address(disorder_client *c, char **addressp, char **portp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 2, "rtp-address", (char *)NULL);
+  if(rc)
+    return rc;
+  *addressp = v[0];
+  v[0] = NULL;
+  *portp = v[1];
+  v[1] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_scratch(disorder_client *c, const char *id) {
+  return disorder_simple(c, NULL, "scratch", id, (char *)NULL);
+}
+
+int disorder_schedule_add_play(disorder_client *c, time_t when, const char *priority, const char *track) {
+  return disorder_simple(c, NULL, "schedule-add", disorder__time, when, priority, "play", track, (char *)NULL);
+}
+
+int disorder_schedule_add_set_global(disorder_client *c, time_t when, const char *priority, const char *pref, const char *value) {
+  return disorder_simple(c, NULL, "schedule-add", disorder__time, when, priority, "set-global", pref, value, (char *)NULL);
+}
+
+int disorder_schedule_add_unset_global(disorder_client *c, time_t when, const char *priority, const char *pref) {
+  return disorder_simple(c, NULL, "schedule-add", disorder__time, when, priority, "set-global", pref, (char *)NULL);
+}
+
+int disorder_schedule_del(disorder_client *c, const char *event) {
+  return disorder_simple(c, NULL, "schedule-del", event, (char *)NULL);
+}
+
+int disorder_schedule_get(disorder_client *c, const char *id, struct kvp **actiondatap) {
+  return pairlist(c, actiondatap, "schedule-get", id, (char *)NULL);
+}
+
+int disorder_schedule_list(disorder_client *c, char ***idsp, int *nidsp) {
+  int rc = disorder_simple(c, NULL, "schedule-list", (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, idsp, nidsp))
+    return -1;
+  return 0;
+}
+
+int disorder_search(disorder_client *c, const char *terms, char ***tracksp, int *ntracksp) {
+  int rc = disorder_simple(c, NULL, "search", terms, (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, tracksp, ntracksp))
+    return -1;
+  return 0;
+}
+
+int disorder_set(disorder_client *c, const char *track, const char *pref, const char *value) {
+  return disorder_simple(c, NULL, "set", track, pref, value, (char *)NULL);
+}
+
+int disorder_set_global(disorder_client *c, const char *pref, const char *value) {
+  return disorder_simple(c, NULL, "set-global", pref, value, (char *)NULL);
+}
+
+int disorder_shutdown(disorder_client *c) {
+  return disorder_simple(c, NULL, "shutdown", (char *)NULL);
+}
+
+int disorder_stats(disorder_client *c, char ***statsp, int *nstatsp) {
+  int rc = disorder_simple(c, NULL, "stats", (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, statsp, nstatsp))
+    return -1;
+  return 0;
+}
+
+int disorder_tags(disorder_client *c, char ***tagsp, int *ntagsp) {
+  int rc = disorder_simple(c, NULL, "tags", (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, tagsp, ntagsp))
+    return -1;
+  return 0;
+}
+
+int disorder_unset(disorder_client *c, const char *track, const char *pref) {
+  return disorder_simple(c, NULL, "unset", track, pref, (char *)NULL);
+}
+
+int disorder_unset_global(disorder_client *c, const char *pref) {
+  return disorder_simple(c, NULL, "unset-global", pref, (char *)NULL);
+}
+
+int disorder_userinfo(disorder_client *c, const char *username, const char *property, char **valuep) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "userinfo", username, property, (char *)NULL);
+  if(rc)
+    return rc;
+  *valuep = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_users(disorder_client *c, char ***usersp, int *nusersp) {
+  int rc = disorder_simple(c, NULL, "users", (char *)NULL);
+  if(rc)
+    return rc;
+  if(readlist(c, usersp, nusersp))
+    return -1;
+  return 0;
+}
+
+int disorder_version(disorder_client *c, char **versionp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 1, "version", (char *)NULL);
+  if(rc)
+    return rc;
+  *versionp = v[0];
+  v[0] = NULL;
+  free_strings(nv, v);
+  return 0;
+}
+
+int disorder_set_volume(disorder_client *c, long left, long right) {
+  return disorder_simple(c, NULL, "volume", disorder__integer, left, disorder__integer, right, (char *)NULL);
+}
+
+int disorder_get_volume(disorder_client *c, long *leftp, long *rightp) {
+  char **v;
+  int nv, rc = disorder_simple_split(c, &v, &nv, 2, "volume", (char *)NULL);
+  if(rc)
+    return rc;
+  *leftp = atol(v[0]);
+  *rightp = atol(v[1]);
+  free_strings(nv, v);
+  return 0;
+}
+
diff --git a/lib/client-stubs.h b/lib/client-stubs.h
new file mode 100644 (file)
index 0000000..1962df2
--- /dev/null
@@ -0,0 +1,773 @@
+/*
+ * Automatically generated file, see scripts/protocol
+ *
+ * DO NOT EDIT.
+ */
+/*
+ * This file is part of DisOrder.
+ * Copyright (C) 2010-11 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef CLIENT_STUBS_H
+#define CLIENT_STUBS_H
+/** @file lib/client-stubs.h
+ * @brief Generated client API
+ *
+ * Don't include this file directly - use @ref client.h instead.
+ */
+
+/** @brief Adopt a track
+ *
+ * Makes the calling user owner of a randomly picked track.
+ *
+ * @param c Client
+ * @param id Track ID
+ * @return 0 on success, non-0 on error
+ */
+int disorder_adopt(disorder_client *c, const char *id);
+
+/** @brief Create a user
+ *
+ * Create a new user.  Requires the 'admin' right.  Email addresses etc must be filled in in separate commands.
+ *
+ * @param c Client
+ * @param user New username
+ * @param password Initial password
+ * @param rights Initial rights (optional)
+ * @return 0 on success, non-0 on error
+ */
+int disorder_adduser(disorder_client *c, const char *user, const char *password, const char *rights);
+
+/** @brief List files and directories in a directory
+ *
+ * See 'files' and 'dirs' for more specific lists.
+ *
+ * @param c Client
+ * @param dir Directory to list (optional)
+ * @param re Regexp that results must match (optional)
+ * @param filesp List of matching files and directories
+ * @param nfilesp Number of elements in filesp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_allfiles(disorder_client *c, const char *dir, const char *re, char ***filesp, int *nfilesp);
+
+/** @brief Confirm registration
+ *
+ * The confirmation string must have been created with 'register'.  The username is returned so the caller knows who they are.
+ *
+ * @param c Client
+ * @param confirmation Confirmation string
+ * @return 0 on success, non-0 on error
+ */
+int disorder_confirm(disorder_client *c, const char *confirmation);
+
+/** @brief Log in with a cookie
+ *
+ * The cookie must have been created with 'make-cookie'.  The username is returned so the caller knows who they are.
+ *
+ * @param c Client
+ * @param cookie Cookie string
+ * @return 0 on success, non-0 on error
+ */
+int disorder_cookie(disorder_client *c, const char *cookie);
+
+/** @brief Delete user
+ *
+ * Requires the 'admin' right.
+ *
+ * @param c Client
+ * @param user User to delete
+ * @return 0 on success, non-0 on error
+ */
+int disorder_deluser(disorder_client *c, const char *user);
+
+/** @brief List directories in a directory
+ *
+ * 
+ *
+ * @param c Client
+ * @param dir Directory to list (optional)
+ * @param re Regexp that results must match (optional)
+ * @param filesp List of matching directories
+ * @param nfilesp Number of elements in filesp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_dirs(disorder_client *c, const char *dir, const char *re, char ***filesp, int *nfilesp);
+
+/** @brief Disable play
+ *
+ * Play will stop at the end of the current track, if one is playing.  Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_disable(disorder_client *c);
+
+/** @brief Set a user property
+ *
+ * With the 'admin' right you can do anything.  Otherwise you need the 'userinfo' right and can only set 'email' and 'password'.
+ *
+ * @param c Client
+ * @param username User to modify
+ * @param property Property name
+ * @param value New property value
+ * @return 0 on success, non-0 on error
+ */
+int disorder_edituser(disorder_client *c, const char *username, const char *property, const char *value);
+
+/** @brief Enable play
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_enable(disorder_client *c);
+
+/** @brief Detect whether play is enabled
+ *
+ * 
+ *
+ * @param c Client
+ * @param enabledp 1 if play is enabled and 0 otherwise
+ * @return 0 on success, non-0 on error
+ */
+int disorder_enabled(disorder_client *c, int *enabledp);
+
+/** @brief Test whether a track exists
+ *
+ * 
+ *
+ * @param c Client
+ * @param track Track name
+ * @param existsp 1 if the track exists and 0 otherwise
+ * @return 0 on success, non-0 on error
+ */
+int disorder_exists(disorder_client *c, const char *track, int *existsp);
+
+/** @brief List files in a directory
+ *
+ * 
+ *
+ * @param c Client
+ * @param dir Directory to list (optional)
+ * @param re Regexp that results must match (optional)
+ * @param filesp List of matching files
+ * @param nfilesp Number of elements in filesp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_files(disorder_client *c, const char *dir, const char *re, char ***filesp, int *nfilesp);
+
+/** @brief Get a track preference
+ *
+ * If the track does not exist that is an error.  If the track exists but the preference does not then a null value is returned.
+ *
+ * @param c Client
+ * @param track Track name
+ * @param pref Preference name
+ * @param valuep Preference value
+ * @return 0 on success, non-0 on error
+ */
+int disorder_get(disorder_client *c, const char *track, const char *pref, char **valuep);
+
+/** @brief Get a global preference
+ *
+ * If the preference does exist not then a null value is returned.
+ *
+ * @param c Client
+ * @param pref Global preference name
+ * @param valuep Preference value
+ * @return 0 on success, non-0 on error
+ */
+int disorder_get_global(disorder_client *c, const char *pref, char **valuep);
+
+/** @brief Get a track's length
+ *
+ * If the track does not exist an error is returned.
+ *
+ * @param c Client
+ * @param track Track name
+ * @param lengthp Track length in seconds
+ * @return 0 on success, non-0 on error
+ */
+int disorder_length(disorder_client *c, const char *track, long *lengthp);
+
+/** @brief Create a login cookie for this user
+ *
+ * The cookie may be redeemed via the 'cookie' command
+ *
+ * @param c Client
+ * @param cookiep Newly created cookie
+ * @return 0 on success, non-0 on error
+ */
+int disorder_make_cookie(disorder_client *c, char **cookiep);
+
+/** @brief Move a track
+ *
+ * Requires one of the 'move mine', 'move random' or 'move any' rights depending on how the track came to be added to the queue.
+ *
+ * @param c Client
+ * @param track Track ID or name
+ * @param delta How far to move the track towards the head of the queue
+ * @return 0 on success, non-0 on error
+ */
+int disorder_move(disorder_client *c, const char *track, long delta);
+
+/** @brief Move multiple tracks
+ *
+ * Requires one of the 'move mine', 'move random' or 'move any' rights depending on how the track came to be added to the queue.
+ *
+ * @param c Client
+ * @param target Move after this track, or to head if ""
+ * @param ids List of tracks to move by ID
+ * @param nids Length of ids
+ * @return 0 on success, non-0 on error
+ */
+int disorder_moveafter(disorder_client *c, const char *target, char **ids, int nids);
+
+/** @brief List recently added tracks
+ *
+ * 
+ *
+ * @param c Client
+ * @param max Maximum tracks to fetch, or 0 for all available
+ * @param tracksp Recently added tracks
+ * @param ntracksp Number of elements in tracksp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_new_tracks(disorder_client *c, long max, char ***tracksp, int *ntracksp);
+
+/** @brief Do nothing
+ *
+ * Used as a keepalive.  No authentication required.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_nop(disorder_client *c);
+
+/** @brief Get a track name part
+ *
+ * If the name part cannot be constructed an empty string is returned.
+ *
+ * @param c Client
+ * @param track Track name
+ * @param context Context ("sort" or "display")
+ * @param part Name part ("artist", "album" or "title")
+ * @param partp Value of name part
+ * @return 0 on success, non-0 on error
+ */
+int disorder_part(disorder_client *c, const char *track, const char *context, const char *part, char **partp);
+
+/** @brief Pause the currently playing track
+ *
+ * Requires the 'pause' right.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_pause(disorder_client *c);
+
+/** @brief Play a track
+ *
+ * Requires the 'play' right.
+ *
+ * @param c Client
+ * @param track Track to play
+ * @param idp Queue ID of new track
+ * @return 0 on success, non-0 on error
+ */
+int disorder_play(disorder_client *c, const char *track, char **idp);
+
+/** @brief Play multiple tracks
+ *
+ * Requires the 'play' right.
+ *
+ * @param c Client
+ * @param target Insert into queue after this track, or at head if ""
+ * @param tracks List of track names to play
+ * @param ntracks Length of tracks
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playafter(disorder_client *c, const char *target, char **tracks, int ntracks);
+
+/** @brief Retrieve the playing track
+ *
+ * 
+ *
+ * @param c Client
+ * @param playingp Details of the playing track
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playing(disorder_client *c, struct queue_entry **playingp);
+
+/** @brief Delete a playlist
+ *
+ * Requires the 'play' right and permission to modify the playlist.
+ *
+ * @param c Client
+ * @param playlist Playlist to delete
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playlist_delete(disorder_client *c, const char *playlist);
+
+/** @brief List the contents of a playlist
+ *
+ * Requires the 'read' right and oermission to read the playlist.
+ *
+ * @param c Client
+ * @param playlist Playlist name
+ * @param tracksp List of tracks in playlist
+ * @param ntracksp Number of elements in tracksp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playlist_get(disorder_client *c, const char *playlist, char ***tracksp, int *ntracksp);
+
+/** @brief Get a playlist's sharing status
+ *
+ * Requires the 'read' right and permission to read the playlist.
+ *
+ * @param c Client
+ * @param playlist Playlist to read
+ * @param sharep Sharing status ("public", "private" or "shared")
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playlist_get_share(disorder_client *c, const char *playlist, char **sharep);
+
+/** @brief Lock a playlist
+ *
+ * Requires the 'play' right and permission to modify the playlist.  A given connection may lock at most one playlist.
+ *
+ * @param c Client
+ * @param playlist Playlist to delete
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playlist_lock(disorder_client *c, const char *playlist);
+
+/** @brief Set the contents of a playlist
+ *
+ * Requires the 'play' right and permission to modify the playlist, which must be locked.
+ *
+ * @param c Client
+ * @param playlist Playlist to modify
+ * @param tracks New list of tracks for playlist
+ * @param ntracks Length of tracks
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playlist_set(disorder_client *c, const char *playlist, char **tracks, int ntracks);
+
+/** @brief Set a playlist's sharing status
+ *
+ * Requires the 'play' right and permission to modify the playlist.
+ *
+ * @param c Client
+ * @param playlist Playlist to modify
+ * @param share New sharing status ("public", "private" or "shared")
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playlist_set_share(disorder_client *c, const char *playlist, const char *share);
+
+/** @brief Unlock the locked playlist playlist
+ *
+ * The playlist to unlock is implicit in the connection.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playlist_unlock(disorder_client *c);
+
+/** @brief List playlists
+ *
+ * Requires the 'read' right.  Only playlists that you have permission to read are returned.
+ *
+ * @param c Client
+ * @param playlistsp Playlist names
+ * @param nplaylistsp Number of elements in playlistsp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_playlists(disorder_client *c, char ***playlistsp, int *nplaylistsp);
+
+/** @brief Get all the preferences for a track
+ *
+ * 
+ *
+ * @param c Client
+ * @param track Track name
+ * @param prefsp Track preferences
+ * @return 0 on success, non-0 on error
+ */
+int disorder_prefs(disorder_client *c, const char *track, struct kvp **prefsp);
+
+/** @brief List the queue
+ *
+ * 
+ *
+ * @param c Client
+ * @param queuep Current queue contents
+ * @return 0 on success, non-0 on error
+ */
+int disorder_queue(disorder_client *c, struct queue_entry **queuep);
+
+/** @brief Disable random play
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_random_disable(disorder_client *c);
+
+/** @brief Enable random play
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_random_enable(disorder_client *c);
+
+/** @brief Detect whether random play is enabled
+ *
+ * Random play counts as enabled even if play is disabled.
+ *
+ * @param c Client
+ * @param enabledp 1 if random play is enabled and 0 otherwise
+ * @return 0 on success, non-0 on error
+ */
+int disorder_random_enabled(disorder_client *c, int *enabledp);
+
+/** @brief List recently played tracks
+ *
+ * 
+ *
+ * @param c Client
+ * @param recentp Recently played tracks
+ * @return 0 on success, non-0 on error
+ */
+int disorder_recent(disorder_client *c, struct queue_entry **recentp);
+
+/** @brief Re-read configuraiton file.
+ *
+ * Requires the 'admin' right.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_reconfigure(disorder_client *c);
+
+/** @brief Register a new user
+ *
+ * Requires the 'register' right which is usually only available to the 'guest' user.  Redeem the confirmation string via 'confirm' to complete registration.
+ *
+ * @param c Client
+ * @param username Requested new username
+ * @param password Requested initial password
+ * @param email New user's email address
+ * @param confirmationp Confirmation string
+ * @return 0 on success, non-0 on error
+ */
+int disorder_register(disorder_client *c, const char *username, const char *password, const char *email, char **confirmationp);
+
+/** @brief Send a password reminder.
+ *
+ * If the user has no valid email address, or no password, or a reminder has been sent too recently, then no reminder will be sent.
+ *
+ * @param c Client
+ * @param username User to remind
+ * @return 0 on success, non-0 on error
+ */
+int disorder_reminder(disorder_client *c, const char *username);
+
+/** @brief Remove a track form the queue.
+ *
+ * Requires one of the 'remove mine', 'remove random' or 'remove any' rights depending on how the track came to be added to the queue.
+ *
+ * @param c Client
+ * @param id Track ID
+ * @return 0 on success, non-0 on error
+ */
+int disorder_remove(disorder_client *c, const char *id);
+
+/** @brief Rescan all collections for new or obsolete tracks.
+ *
+ * Requires the 'rescan' right.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_rescan(disorder_client *c);
+
+/** @brief Resolve a track name
+ *
+ * Converts aliases to non-alias track names
+ *
+ * @param c Client
+ * @param track Track name (might be an alias)
+ * @param resolvedp Resolve track name (definitely not an alias)
+ * @return 0 on success, non-0 on error
+ */
+int disorder_resolve(disorder_client *c, const char *track, char **resolvedp);
+
+/** @brief Resume the currently playing track
+ *
+ * Requires the 'pause' right.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_resume(disorder_client *c);
+
+/** @brief Revoke a cookie.
+ *
+ * It will not subsequently be possible to log in with the cookie.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_revoke(disorder_client *c);
+
+/** @brief Get the server's RTP address information
+ *
+ * 
+ *
+ * @param c Client
+ * @param addressp Where to store hostname or address
+ * @param portp Where to store service name or port number
+ * @return 0 on success, non-0 on error
+ */
+int disorder_rtp_address(disorder_client *c, char **addressp, char **portp);
+
+/** @brief Terminate the playing track.
+ *
+ * Requires one of the 'scratch mine', 'scratch random' or 'scratch any' rights depending on how the track came to be added to the queue.
+ *
+ * @param c Client
+ * @param id Track ID (optional)
+ * @return 0 on success, non-0 on error
+ */
+int disorder_scratch(disorder_client *c, const char *id);
+
+/** @brief Schedule a track to play in the future
+ *
+ * 
+ *
+ * @param c Client
+ * @param when When to play the track
+ * @param priority Event priority ("normal" or "junk")
+ * @param track Track to play
+ * @return 0 on success, non-0 on error
+ */
+int disorder_schedule_add_play(disorder_client *c, time_t when, const char *priority, const char *track);
+
+/** @brief Schedule a global setting to be changed in the future
+ *
+ * 
+ *
+ * @param c Client
+ * @param when When to change the setting
+ * @param priority Event priority ("normal" or "junk")
+ * @param pref Global preference to set
+ * @param value New value of global preference
+ * @return 0 on success, non-0 on error
+ */
+int disorder_schedule_add_set_global(disorder_client *c, time_t when, const char *priority, const char *pref, const char *value);
+
+/** @brief Schedule a global setting to be unset in the future
+ *
+ * 
+ *
+ * @param c Client
+ * @param when When to change the setting
+ * @param priority Event priority ("normal" or "junk")
+ * @param pref Global preference to set
+ * @return 0 on success, non-0 on error
+ */
+int disorder_schedule_add_unset_global(disorder_client *c, time_t when, const char *priority, const char *pref);
+
+/** @brief Delete a scheduled event.
+ *
+ * Users can always delete their own scheduled events; with the admin right you can delete any event.
+ *
+ * @param c Client
+ * @param event ID of event to delete
+ * @return 0 on success, non-0 on error
+ */
+int disorder_schedule_del(disorder_client *c, const char *event);
+
+/** @brief Get the details of scheduled event
+ *
+ * 
+ *
+ * @param c Client
+ * @param id Event ID
+ * @param actiondatap Details of event
+ * @return 0 on success, non-0 on error
+ */
+int disorder_schedule_get(disorder_client *c, const char *id, struct kvp **actiondatap);
+
+/** @brief List scheduled events
+ *
+ * This just lists IDs.  Use 'schedule-get' to retrieve more detail
+ *
+ * @param c Client
+ * @param idsp List of event IDs
+ * @param nidsp Number of elements in idsp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_schedule_list(disorder_client *c, char ***idsp, int *nidsp);
+
+/** @brief Search for tracks
+ *
+ * Terms are either keywords or tags formatted as 'tag:TAG-NAME'.
+ *
+ * @param c Client
+ * @param terms List of search terms
+ * @param tracksp List of matching tracks
+ * @param ntracksp Number of elements in tracksp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_search(disorder_client *c, const char *terms, char ***tracksp, int *ntracksp);
+
+/** @brief Set a track preference
+ *
+ * Requires the 'prefs' right.
+ *
+ * @param c Client
+ * @param track Track name
+ * @param pref Preference name
+ * @param value New value
+ * @return 0 on success, non-0 on error
+ */
+int disorder_set(disorder_client *c, const char *track, const char *pref, const char *value);
+
+/** @brief Set a global preference
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @param pref Preference name
+ * @param value New value
+ * @return 0 on success, non-0 on error
+ */
+int disorder_set_global(disorder_client *c, const char *pref, const char *value);
+
+/** @brief Request server shutdown
+ *
+ * Requires the 'admin' right.
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_shutdown(disorder_client *c);
+
+/** @brief Get server statistics
+ *
+ * The details of what the server reports are not really defined.  The returned strings are intended to be printed out one to a line.
+ *
+ * @param c Client
+ * @param statsp List of server information strings.
+ * @param nstatsp Number of elements in statsp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_stats(disorder_client *c, char ***statsp, int *nstatsp);
+
+/** @brief Get a list of known tags
+ *
+ * Only tags which apply to at least one track are returned.
+ *
+ * @param c Client
+ * @param tagsp List of tags
+ * @param ntagsp Number of elements in tagsp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_tags(disorder_client *c, char ***tagsp, int *ntagsp);
+
+/** @brief Unset a track preference
+ *
+ * Requires the 'prefs' right.
+ *
+ * @param c Client
+ * @param track Track name
+ * @param pref Preference name
+ * @return 0 on success, non-0 on error
+ */
+int disorder_unset(disorder_client *c, const char *track, const char *pref);
+
+/** @brief Set a global preference
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @param pref Preference name
+ * @return 0 on success, non-0 on error
+ */
+int disorder_unset_global(disorder_client *c, const char *pref);
+
+/** @brief Get a user property.
+ *
+ * If the user does not exist an error is returned, if the user exists but the property does not then a null value is returned.
+ *
+ * @param c Client
+ * @param username User to read
+ * @param property Property to read
+ * @param valuep Value of property
+ * @return 0 on success, non-0 on error
+ */
+int disorder_userinfo(disorder_client *c, const char *username, const char *property, char **valuep);
+
+/** @brief Get a list of users
+ *
+ * 
+ *
+ * @param c Client
+ * @param usersp List of users
+ * @param nusersp Number of elements in usersp
+ * @return 0 on success, non-0 on error
+ */
+int disorder_users(disorder_client *c, char ***usersp, int *nusersp);
+
+/** @brief Get the server version
+ *
+ * 
+ *
+ * @param c Client
+ * @param versionp Server version string
+ * @return 0 on success, non-0 on error
+ */
+int disorder_version(disorder_client *c, char **versionp);
+
+/** @brief Set the volume
+ *
+ * 
+ *
+ * @param c Client
+ * @param left Left channel volume
+ * @param right Right channel volume
+ * @return 0 on success, non-0 on error
+ */
+int disorder_set_volume(disorder_client *c, long left, long right);
+
+/** @brief Get the volume
+ *
+ * 
+ *
+ * @param c Client
+ * @param leftp Left channel volume
+ * @param rightp Right channel volume
+ * @return 0 on success, non-0 on error
+ */
+int disorder_get_volume(disorder_client *c, long *leftp, long *rightp);
+
+#endif
index 2cbcfa76201324f07dc0668975b5dd5c51a98291..987ab01edaf07cde9d23e7e3fe9234cf397d20cc 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004-2009 Richard Kettlewell
+ * Copyright (C) 2004-2010 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
@@ -155,8 +155,6 @@ static int check_response(disorder_client *c, char **rp) {
  * @param c Client
  * @param rp Where to store result, or NULL
  * @param cmd Command
- * @param body Body or NULL
- * @param nbody Length of body or -1
  * @param ap Arguments (UTF-8), terminated by (char *)0
  * @return 0 on success, non-0 on error
  *
@@ -168,22 +166,35 @@ static int check_response(disorder_client *c, char **rp) {
  * NB that the response will NOT be converted to the local encoding
  * nor will quotes be stripped.  See dequote().
  *
- * If @p body is not NULL then the body is sent immediately after the
- * command.  @p nbody should be the number of lines or @c -1 to count
- * them if @p body is NULL-terminated.
+ * Put @ref disorder__body in the argument list followed by a char **
+ * and int giving the body to follow the command.  If the int is @c -1
+ * then the list is assumed to be NULL-terminated.  This may be used
+ * only once.
+ *
+ * Put @ref disorder__list in the argument list followed by a char **
+ * and int giving a list of arguments to include.  If the int is @c -1
+ * then the list is assumed to be NULL-terminated.  This may be used
+ * any number of times.
+ *
+ * Put @ref disorder__integer in the argument list followed by a long to
+ * send its value in decimal.  This may be used any number of times.
+ *
+ * Put @ref disorder__time in the argument list followed by a time_t
+ * to send its value in decimal.  This may be used any number of
+ * times.
  *
  * Usually you would call this via one of the following interfaces:
  * - disorder_simple()
- * - disorder_simple_body()
- * - disorder_simple_list()
  */
 static int disorder_simple_v(disorder_client *c,
                             char **rp,
                             const char *cmd,
-                             char **body, int nbody,
                              va_list ap) {
   const char *arg;
   struct dynstr d;
+  char **body = NULL;
+  int nbody = 0;
+  int has_body = 0;
 
   if(!c->fpout) {
     c->last = "not connected";
@@ -194,8 +205,37 @@ static int disorder_simple_v(disorder_client *c,
     dynstr_init(&d);
     dynstr_append_string(&d, cmd);
     while((arg = va_arg(ap, const char *))) {
-      dynstr_append(&d, ' ');
-      dynstr_append_string(&d, quoteutf8(arg));
+      if(arg == disorder__body) {
+       body = va_arg(ap, char **);
+       nbody = va_arg(ap, int);
+       has_body = 1;
+      } else if(arg == disorder__list) {
+       char **list = va_arg(ap, char **);
+       int nlist = va_arg(ap, int);
+       if(nlist < 0) {
+         for(nlist = 0; list[nlist]; ++nlist)
+           ;
+       }
+       for(int n = 0; n < nlist; ++n) {
+         dynstr_append(&d, ' ');
+         dynstr_append_string(&d, quoteutf8(arg));
+       }
+      } else if(arg == disorder__integer) {
+       long n = va_arg(ap, long);
+       char buffer[16];
+       snprintf(buffer, sizeof buffer, "%ld", n);
+       dynstr_append(&d, ' ');
+       dynstr_append_string(&d, buffer);
+      } else if(arg == disorder__time) {
+       time_t n = va_arg(ap, time_t);
+       char buffer[16];
+       snprintf(buffer, sizeof buffer, "%lld", (long long)n);
+       dynstr_append(&d, ' ');
+       dynstr_append_string(&d, buffer);
+      } else {
+       dynstr_append(&d, ' ');
+       dynstr_append_string(&d, quoteutf8(arg));
+      }
     }
     dynstr_append(&d, '\n');
     dynstr_terminate(&d);
@@ -203,7 +243,7 @@ static int disorder_simple_v(disorder_client *c,
     if(fputs(d.vec, c->fpout) < 0)
       goto write_error;
     xfree(d.vec);
-    if(body) {
+    if(has_body) {
       if(nbody < 0)
         for(nbody = 0; body[nbody]; ++nbody)
           ;
@@ -253,31 +293,61 @@ static int disorder_simple(disorder_client *c,
   int ret;
 
   va_start(ap, cmd);
-  ret = disorder_simple_v(c, rp, cmd, 0, 0, ap);
+  ret = disorder_simple_v(c, rp, cmd, ap);
   va_end(ap);
   return ret;
 }
 
-/** @brief Issue a command with a body and parse a simple response
+/** @brief Issue a command and split the response
  * @param c Client
- * @param rp Where to store result, or NULL (UTF-8)
- * @param body Pointer to body
- * @param nbody Size of body
+ * @param vecp Where to store results
+ * @param nvecp Where to store count of results
+ * @param expected Expected count (or -1 to not check)
  * @param cmd Command
  * @return 0 on success, non-0 on error
  *
- * See disorder_simple().
+ * The remaining arguments are command arguments, terminated by (char
+ * *)0.  They should be in UTF-8.
+ *
+ * 5xx responses count as errors.
+ *
+ * @p rp will NOT be filled in for xx9 responses (where it is just
+ * commentary for a command where it would normally be meaningful).
+ *
+ * NB that the response will NOT be converted to the local encoding
+ * nor will quotes be stripped.  See dequote().
  */
-static int disorder_simple_body(disorder_client *c,
-                                char **rp,
-                                char **body, int nbody,
-                                const char *cmd, ...) {
+static int disorder_simple_split(disorder_client *c,
+                                char ***vecp,
+                                int *nvecp,
+                                int expected,
+                                const char *cmd, ...) {
   va_list ap;
   int ret;
+  char *r;
+  char **vec;
+  int nvec;
 
   va_start(ap, cmd);
-  ret = disorder_simple_v(c, rp, cmd, body, nbody, ap);
+  ret = disorder_simple_v(c, &r, cmd, ap);
   va_end(ap);
+  if(!ret) {
+    vec = split(r, &nvec, SPLIT_QUOTES, 0, 0);
+    xfree(r);
+    if(expected < 0 || nvec == expected) {
+      *vecp = vec;
+      *nvecp = nvec;
+    } else {
+      disorder_error(0, "malformed reply to %s", cmd);
+      c->last = "malformed reply";
+      ret = -1;
+      free_strings(nvec, vec);
+    }
+  }
+  if(ret) {
+    *vecp = NULL;
+    *nvecp = 0;
+  }
   return ret;
 }
 
@@ -522,113 +592,24 @@ int disorder_close(disorder_client *c) {
   return ret;
 }
 
-/** @brief Play a track
- * @param c Client
- * @param track Track to play (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_play(disorder_client *c, const char *track) {
-  return disorder_simple(c, 0, "play", track, (char *)0);
-}
-
-/** @brief Remove a track
- * @param c Client
- * @param track Track to remove (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_remove(disorder_client *c, const char *track) {
-  return disorder_simple(c, 0, "remove", track, (char *)0);
-}
-
-/** @brief Move a track
- * @param c Client
- * @param track Track to move (UTF-8)
- * @param delta Distance to move by
- * @return 0 on success, non-0 on error
- */
-int disorder_move(disorder_client *c, const char *track, int delta) {
-  char d[16];
-
-  byte_snprintf(d, sizeof d, "%d", delta);
-  return disorder_simple(c, 0, "move", track, d, (char *)0);
-}
-
-/** @brief Enable play
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_enable(disorder_client *c) {
-  return disorder_simple(c, 0, "enable", (char *)0);
-}
-
-/** @brief Disable play
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_disable(disorder_client *c) {
-  return disorder_simple(c, 0, "disable", (char *)0);
-}
-
-/** @brief Scratch the currently playing track
- * @param id Playing track ID or NULL (UTF-8)
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_scratch(disorder_client *c, const char *id) {
-  return disorder_simple(c, 0, "scratch", id, (char *)0);
-}
-
-/** @brief Shut down the server
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_shutdown(disorder_client *c) {
-  return disorder_simple(c, 0, "shutdown", (char *)0);
-}
-
-/** @brief Make the server re-read its configuration
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_reconfigure(disorder_client *c) {
-  return disorder_simple(c, 0, "reconfigure", (char *)0);
-}
-
-/** @brief Rescan tracks
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_rescan(disorder_client *c) {
-  return disorder_simple(c, 0, "rescan", (char *)0);
-}
-
-/** @brief Get server version number
- * @param c Client
- * @param rp Where to store version string (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_version(disorder_client *c, char **rp) {
-  return dequote(disorder_simple(c, rp, "version", (char *)0), rp);
-}
-
 static void client_error(const char *msg,
                         void attribute((unused)) *u) {
   disorder_error(0, "error parsing reply: %s", msg);
 }
 
-/** @brief Get currently playing track
+/** @brief Get a single queue entry
  * @param c Client
+ * @param cmd Command
  * @param qp Where to store track information
  * @return 0 on success, non-0 on error
- *
- * @p qp gets NULL if no track is playing.
  */
-int disorder_playing(disorder_client *c, struct queue_entry **qp) {
+static int onequeue(disorder_client *c, const char *cmd,
+                   struct queue_entry **qp) {
   char *r;
   struct queue_entry *q;
   int rc;
 
-  if((rc = disorder_simple(c, &r, "playing", (char *)0)))
+  if((rc = disorder_simple(c, &r, cmd, (char *)0)))
     return rc;
   if(r) {
     q = xmalloc(sizeof *q);
@@ -641,14 +622,11 @@ int disorder_playing(disorder_client *c, struct queue_entry **qp) {
 }
 
 /** @brief Fetch the queue, recent list, etc */
-static int disorder_somequeue(disorder_client *c,
-                             const char *cmd, struct queue_entry **qp) {
+static int readqueue(disorder_client *c,
+                    struct queue_entry **qp) {
   struct queue_entry *qh, **qt = &qh, *q;
   char *l;
-  int rc;
 
-  if((rc = disorder_simple(c, 0, cmd, (char *)0)))
-    return rc;
   while(inputline(c->ident, c->fpin, &l, '\n') >= 0) {
     if(!strcmp(l, ".")) {
       *qt = 0;
@@ -667,34 +645,12 @@ static int disorder_somequeue(disorder_client *c,
     byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
     disorder_error(errno, "error reading %s", c->ident);
   } else {
-    c->last = "input error: unexpxected EOF";
+    c->last = "input error: unexpected EOF";
     disorder_error(0, "error reading %s: unexpected EOF", c->ident);
   }
   return -1;
 }
 
-/** @brief Get recently played tracks
- * @param c Client
- * @param qp Where to store track information
- * @return 0 on success, non-0 on error
- *
- * The last entry in the list is the most recently played track.
- */
-int disorder_recent(disorder_client *c, struct queue_entry **qp) {
-  return disorder_somequeue(c, "recent", qp);
-}
-
-/** @brief Get queue
- * @param c Client
- * @param qp Where to store track information
- * @return 0 on success, non-0 on error
- *
- * The first entry in the list will be played next.
- */
-int disorder_queue(disorder_client *c, struct queue_entry **qp) {
-  return disorder_somequeue(c, "queue", qp);
-}
-
 /** @brief Read a dot-stuffed list
  * @param c Client
  * @param vecp Where to store list (UTF-8)
@@ -730,72 +686,6 @@ static int readlist(disorder_client *c, char ***vecp, int *nvecp) {
   return -1;
 }
 
-/** @brief Issue a comamnd and get a list response
- * @param c Client
- * @param vecp Where to store list (UTF-8)
- * @param nvecp Where to store number of items, or NULL
- * @param cmd Command
- * @return 0 on success, non-0 on error
- *
- * The remaining arguments are command arguments, terminated by (char
- * *)0.  They should be in UTF-8.
- *
- * 5xx responses count as errors.
- *
- * See disorder_simple().
- */
-static int disorder_simple_list(disorder_client *c,
-                               char ***vecp, int *nvecp,
-                               const char *cmd, ...) {
-  va_list ap;
-  int ret;
-
-  va_start(ap, cmd);
-  ret = disorder_simple_v(c, 0, cmd, 0, 0, ap);
-  va_end(ap);
-  if(ret) return ret;
-  return readlist(c, vecp, nvecp);
-}
-
-/** @brief List directories below @p dir
- * @param c Client
- * @param dir Directory to list, or NULL for root (UTF-8)
- * @param re Regexp that results must match, or NULL (UTF-8)
- * @param vecp Where to store list (UTF-8)
- * @param nvecp Where to store number of items, or NULL
- * @return 0 on success, non-0 on error
- */
-int disorder_directories(disorder_client *c, const char *dir, const char *re,
-                        char ***vecp, int *nvecp) {
-  return disorder_simple_list(c, vecp, nvecp, "dirs", dir, re, (char *)0);
-}
-
-/** @brief List files below @p dir
- * @param c Client
- * @param dir Directory to list, or NULL for root (UTF-8)
- * @param re Regexp that results must match, or NULL (UTF-8)
- * @param vecp Where to store list (UTF-8)
- * @param nvecp Where to store number of items, or NULL
- * @return 0 on success, non-0 on error
- */
-int disorder_files(disorder_client *c, const char *dir, const char *re,
-                  char ***vecp, int *nvecp) {
-  return disorder_simple_list(c, vecp, nvecp, "files", dir, re, (char *)0);
-}
-
-/** @brief List files and directories below @p dir
- * @param c Client
- * @param dir Directory to list, or NULL for root (UTF-8)
- * @param re Regexp that results must match, or NULL (UTF-8)
- * @param vecp Where to store list (UTF-8)
- * @param nvecp Where to store number of items, or NULL
- * @return 0 on success, non-0 on error
- */
-int disorder_allfiles(disorder_client *c, const char *dir, const char *re,
-                     char ***vecp, int *nvecp) {
-  return disorder_simple_list(c, vecp, nvecp, "allfiles", dir, re, (char *)0);
-}
-
 /** @brief Return the user we logged in with
  * @param c Client
  * @return User name (owned by @p c, don't modify)
@@ -804,65 +694,36 @@ char *disorder_user(disorder_client *c) {
   return c->user;
 }
 
-/** @brief Set a track preference
- * @param c Client
- * @param track Track name (UTF-8)
- * @param key Preference name (UTF-8)
- * @param value Preference value (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_set(disorder_client *c, const char *track,
-                const char *key, const char *value) {
-  return disorder_simple(c, 0, "set", track, key, value, (char *)0);
-}
-
-/** @brief Unset a track preference
- * @param c Client
- * @param track Track name (UTF-8)
- * @param key Preference name (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_unset(disorder_client *c, const char *track,
-                  const char *key) {
-  return disorder_simple(c, 0, "unset", track, key, (char *)0);
-}
-
-/** @brief Get a track preference
- * @param c Client
- * @param track Track name (UTF-8)
- * @param key Preference name (UTF-8)
- * @param valuep Where to store preference value (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_get(disorder_client *c,
-                const char *track, const char *key, char **valuep) {
-  return dequote(disorder_simple(c, valuep, "get", track, key, (char *)0),
-                valuep);
-}
-
-static void pref_error_handler(const char *msg,
+static void pairlist_error_handler(const char *msg,
                               void attribute((unused)) *u) {
-  disorder_error(0, "error handling 'prefs' reply: %s", msg);
+  disorder_error(0, "error handling key-value pair reply: %s", msg);
 }
 
-/** @brief Get all preferences for a trcak
+/** @brief Get a list of key-value pairs
  * @param c Client
- * @param track Track name
  * @param kp Where to store linked list of preferences
+ * @param cmd Command
+ * @param ... Arguments
  * @return 0 on success, non-0 on error
  */
-int disorder_prefs(disorder_client *c, const char *track, struct kvp **kp) {
+static int pairlist(disorder_client *c, struct kvp **kp, const char *cmd, ...) {
   char **vec, **pvec;
   int nvec, npvec, n, rc;
   struct kvp *k;
+  va_list ap;
 
-  if((rc = disorder_simple_list(c, &vec, &nvec, "prefs", track, (char *)0)))
+  va_start(ap, cmd);
+  rc = disorder_simple_v(c, 0, cmd, ap);
+  va_end(ap);
+  if(rc)
     return rc;
+  if((rc = readlist(c, &vec, &nvec)))
+     return rc;
   for(n = 0; n < nvec; ++n) {
-    if(!(pvec = split(vec[n], &npvec, SPLIT_QUOTES, pref_error_handler, 0)))
+    if(!(pvec = split(vec[n], &npvec, SPLIT_QUOTES, pairlist_error_handler, 0)))
       return -1;
     if(npvec != 2) {
-      pref_error_handler("malformed response", 0);
+      pairlist_error_handler("malformed response", 0);
       return -1;
     }
     *kp = k = xmalloc(sizeof *k);
@@ -893,142 +754,6 @@ static int boolean(const char *cmd, const char *value,
   return 0;
 }
 
-/** @brief Test whether a track exists
- * @param c Client
- * @param track Track name (UTF-8)
- * @param existsp Where to store result (non-0 iff does exist)
- * @return 0 on success, non-0 on error
- */
-int disorder_exists(disorder_client *c, const char *track, int *existsp) {
-  char *v;
-  int rc;
-
-  if((rc = disorder_simple(c, &v, "exists", track, (char *)0)))
-    return rc;
-  return boolean("exists", v, existsp);
-}
-
-/** @brief Test whether playing is enabled
- * @param c Client
- * @param enabledp Where to store result (non-0 iff enabled)
- * @return 0 on success, non-0 on error
- */
-int disorder_enabled(disorder_client *c, int *enabledp) {
-  char *v;
-  int rc;
-
-  if((rc = disorder_simple(c, &v, "enabled", (char *)0)))
-    return rc;
-  return boolean("enabled", v, enabledp);
-}
-
-/** @brief Get the length of a track
- * @param c Client
- * @param track Track name (UTF-8)
- * @param valuep Where to store length in seconds
- * @return 0 on success, non-0 on error
- *
- * If the length is unknown 0 is returned.
- */
-int disorder_length(disorder_client *c, const char *track,
-                   long *valuep) {
-  char *value;
-  int rc;
-
-  if((rc = disorder_simple(c, &value, "length", track, (char *)0)))
-    return rc;
-  *valuep = atol(value);
-  return 0;
-}
-
-/** @brief Search for tracks
- * @param c Client
- * @param terms Search terms (UTF-8)
- * @param vecp Where to store list (UTF-8)
- * @param nvecp Where to store number of items, or NULL
- * @return 0 on success, non-0 on error
- */
-int disorder_search(disorder_client *c, const char *terms,
-                   char ***vecp, int *nvecp) {
-  return disorder_simple_list(c, vecp, nvecp, "search", terms, (char *)0);
-}
-
-/** @brief Enable random play
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_random_enable(disorder_client *c) {
-  return disorder_simple(c, 0, "random-enable", (char *)0);
-}
-
-/** @brief Disable random play
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_random_disable(disorder_client *c) {
-  return disorder_simple(c, 0, "random-disable", (char *)0);
-}
-
-/** @brief Test whether random play is enabled
- * @param c Client
- * @param enabledp Where to store result (non-0 iff enabled)
- * @return 0 on success, non-0 on error
- */
-int disorder_random_enabled(disorder_client *c, int *enabledp) {
-  char *v;
-  int rc;
-
-  if((rc = disorder_simple(c, &v, "random-enabled", (char *)0)))
-    return rc;
-  return boolean("random-enabled", v, enabledp);
-}
-
-/** @brief Get server stats
- * @param c Client
- * @param vecp Where to store list (UTF-8)
- * @param nvecp Where to store number of items, or NULL
- * @return 0 on success, non-0 on error
- */
-int disorder_stats(disorder_client *c,
-                  char ***vecp, int *nvecp) {
-  return disorder_simple_list(c, vecp, nvecp, "stats", (char *)0);
-}
-
-/** @brief Set volume
- * @param c Client
- * @param left New left channel value
- * @param right New right channel value
- * @return 0 on success, non-0 on error
- */
-int disorder_set_volume(disorder_client *c, int left, int right) {
-  char *ls, *rs;
-
-  if(byte_asprintf(&ls, "%d", left) < 0
-     || byte_asprintf(&rs, "%d", right) < 0)
-    return -1;
-  return disorder_simple(c, 0, "volume", ls, rs, (char *)0);
-}
-
-/** @brief Get volume
- * @param c Client
- * @param left Where to store left channel value
- * @param right Where to store right channel value
- * @return 0 on success, non-0 on error
- */
-int disorder_get_volume(disorder_client *c, int *left, int *right) {
-  char *r;
-  int rc;
-
-  if((rc = disorder_simple(c, &r, "volume", (char *)0)))
-    return rc;
-  if(sscanf(r, "%d %d", left, right) != 2) {
-    c->last = "malformed volume response";
-    disorder_error(0, "error parsing response to 'volume': '%s'", r);
-    return -1;
-  }
-  return 0;
-}
-
 /** @brief Log to a sink
  * @param c Client
  * @param s Sink to write log lines to
@@ -1050,438 +775,7 @@ int disorder_log(disorder_client *c, struct sink *s) {
   return 0;
 }
 
-/** @brief Look up a track name part
- * @param c Client
- * @param partp Where to store result (UTF-8)
- * @param track Track name (UTF-8)
- * @param context Context (usually "sort" or "display") (UTF-8)
- * @param part Track part (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_part(disorder_client *c, char **partp,
-                 const char *track, const char *context, const char *part) {
-  return dequote(disorder_simple(c, partp, "part",
-                                track, context, part, (char *)0), partp);
-}
-
-/** @brief Resolve aliases
- * @param c Client
- * @param trackp Where to store canonical name (UTF-8)
- * @param track Track name (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_resolve(disorder_client *c, char **trackp, const char *track) {
-  return dequote(disorder_simple(c, trackp, "resolve", track, (char *)0),
-                trackp);
-}
-
-/** @brief Pause the current track
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_pause(disorder_client *c) {
-  return disorder_simple(c, 0, "pause", (char *)0);
-}
-
-/** @brief Resume the current track
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_resume(disorder_client *c) {
-  return disorder_simple(c, 0, "resume", (char *)0);
-}
-
-/** @brief List all known tags
- * @param c Client
- * @param vecp Where to store list (UTF-8)
- * @param nvecp Where to store number of items, or NULL
- * @return 0 on success, non-0 on error
- */
-int disorder_tags(disorder_client *c,
-                  char ***vecp, int *nvecp) {
-  return disorder_simple_list(c, vecp, nvecp, "tags", (char *)0);
-}
-
-/** @brief List all known users
- * @param c Client
- * @param vecp Where to store list (UTF-8)
- * @param nvecp Where to store number of items, or NULL
- * @return 0 on success, non-0 on error
- */
-int disorder_users(disorder_client *c,
-                  char ***vecp, int *nvecp) {
-  return disorder_simple_list(c, vecp, nvecp, "users", (char *)0);
-}
-
-/** @brief Get recently added tracks
- * @param c Client
- * @param vecp Where to store pointer to list (UTF-8)
- * @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);
-}
-
-/** @brief Set a global preference
- * @param c Client
- * @param key Preference name (UTF-8)
- * @param value Preference value (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_set_global(disorder_client *c,
-                       const char *key, const char *value) {
-  return disorder_simple(c, 0, "set-global", key, value, (char *)0);
-}
-
-/** @brief Unset a global preference
- * @param c Client
- * @param key Preference name (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_unset_global(disorder_client *c, const char *key) {
-  return disorder_simple(c, 0, "unset-global", key, (char *)0);
-}
-
-/** @brief Get a global preference
- * @param c Client
- * @param key Preference name (UTF-8)
- * @param valuep Where to store preference value (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_get_global(disorder_client *c, const char *key, char **valuep) {
-  return dequote(disorder_simple(c, valuep, "get-global", key, (char *)0),
-                valuep);
-}
-
-/** @brief Get server's RTP address information
- * @param c Client
- * @param addressp Where to store address (UTF-8)
- * @param portp Where to store port (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_rtp_address(disorder_client *c, char **addressp, char **portp) {
-  char *r;
-  int rc, n;
-  char **vec;
-
-  if((rc = disorder_simple(c, &r, "rtp-address", (char *)0)))
-    return rc;
-  vec = split(r, &n, SPLIT_QUOTES, 0, 0);
-  if(n != 2) {
-    c->last = "malformed RTP address";
-    disorder_error(0, "malformed rtp-address reply");
-    return -1;
-  }
-  *addressp = vec[0];
-  *portp = vec[1];
-  return 0;
-}
-
-/** @brief Create a user
- * @param c Client
- * @param user Username
- * @param password Password
- * @param rights Initial rights or NULL to use default
- * @return 0 on success, non-0 on error
- */
-int disorder_adduser(disorder_client *c,
-                    const char *user, const char *password,
-                    const char *rights) {
-  return disorder_simple(c, 0, "adduser", user, password, rights, (char *)0);
-}
-
-/** @brief Delete a user
- * @param c Client
- * @param user Username
- * @return 0 on success, non-0 on error
- */
-int disorder_deluser(disorder_client *c, const char *user) {
-  return disorder_simple(c, 0, "deluser", user, (char *)0);
-}
-
-/** @brief Get user information
- * @param c Client
- * @param user Username
- * @param key Property name (UTF-8)
- * @param valuep Where to store value (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_userinfo(disorder_client *c, const char *user, const char *key,
-                     char **valuep) {
-  return dequote(disorder_simple(c, valuep, "userinfo", user, key, (char *)0),
-                valuep);
-}
-
-/** @brief Set user information
- * @param c Client
- * @param user Username
- * @param key Property name (UTF-8)
- * @param value New property value (UTF-8)
- * @return 0 on success, non-0 on error
- */
-int disorder_edituser(disorder_client *c, const char *user,
-                     const char *key, const char *value) {
-  return disorder_simple(c, 0, "edituser", user, key, value, (char *)0);
-}
-
-/** @brief Register a user
- * @param c Client
- * @param user Username
- * @param password Password
- * @param email Email address (UTF-8)
- * @param confirmp Where to store confirmation string
- * @return 0 on success, non-0 on error
- */
-int disorder_register(disorder_client *c, const char *user,
-                     const char *password, const char *email,
-                     char **confirmp) {
-  return dequote(disorder_simple(c, confirmp, "register",
-                                user, password, email, (char *)0),
-                confirmp);
-}
-
-/** @brief Confirm a user
- * @param c Client
- * @param confirm Confirmation string
- * @return 0 on success, non-0 on error
- */
-int disorder_confirm(disorder_client *c, const char *confirm) {
-  char *u;
-  int rc;
-  
-  if(!(rc = dequote(disorder_simple(c, &u, "confirm", confirm, (char *)0),
-                   &u)))
-    c->user = u;
-  return rc;
-}
-
-/** @brief Make a cookie for this login
- * @param c Client
- * @param cookiep Where to store cookie string
- * @return 0 on success, non-0 on error
- */
-int disorder_make_cookie(disorder_client *c, char **cookiep) {
-  return dequote(disorder_simple(c, cookiep, "make-cookie", (char *)0),
-                cookiep);
-}
-
-/** @brief Revoke the cookie used by this session
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_revoke(disorder_client *c) {
-  return disorder_simple(c, 0, "revoke", (char *)0);
-}
-
-/** @brief Request a password reminder email
- * @param c Client
- * @param user Username
- * @return 0 on success, non-0 on error
- */
-int disorder_reminder(disorder_client *c, const char *user) {
-  return disorder_simple(c, 0, "reminder", user, (char *)0);
-}
-
-/** @brief List scheduled events
- * @param c Client
- * @param idsp Where to put list of event IDs
- * @param nidsp Where to put count of event IDs, or NULL
- * @return 0 on success, non-0 on error
- */
-int disorder_schedule_list(disorder_client *c, char ***idsp, int *nidsp) {
-  return disorder_simple_list(c, idsp, nidsp, "schedule-list", (char *)0);
-}
-
-/** @brief Delete a scheduled event
- * @param c Client
- * @param id Event ID to delete
- * @return 0 on success, non-0 on error
- */
-int disorder_schedule_del(disorder_client *c, const char *id) {
-  return disorder_simple(c, 0, "schedule-del", id, (char *)0);
-}
-
-/** @brief Get details of a scheduled event
- * @param c Client
- * @param id Event ID
- * @param actiondatap Where to put details
- * @return 0 on success, non-0 on error
- */
-int disorder_schedule_get(disorder_client *c, const char *id,
-                         struct kvp **actiondatap) {
-  char **lines, **bits;
-  int rc, nbits;
-
-  *actiondatap = 0;
-  if((rc = disorder_simple_list(c, &lines, NULL,
-                               "schedule-get", id, (char *)0)))
-    return rc;
-  while(*lines) {
-    if(!(bits = split(*lines++, &nbits, SPLIT_QUOTES, 0, 0))) {
-      disorder_error(0, "invalid schedule-get reply: cannot split line");
-      return -1;
-    }
-    if(nbits != 2) {
-      disorder_error(0, "invalid schedule-get reply: wrong number of fields");
-      return -1;
-    }
-    kvp_set(actiondatap, bits[0], bits[1]);
-  }
-  return 0;
-}
-
-/** @brief Add a scheduled event
- * @param c Client
- * @param when When to trigger the event
- * @param priority Event priority ("normal" or "junk")
- * @param action What action to perform
- * @param ... Action-specific arguments
- * @return 0 on success, non-0 on error
- *
- * For action @c "play" the next argument is the track.
- *
- * For action @c "set-global" next argument is the global preference name
- * and the final argument the value to set it to, or (char *)0 to unset it.
- */
-int disorder_schedule_add(disorder_client *c,
-                         time_t when,
-                         const char *priority,
-                         const char *action,
-                         ...) {
-  va_list ap;
-  char when_str[64];
-  int rc;
-
-  snprintf(when_str, sizeof when_str, "%lld", (long long)when);
-  va_start(ap, action);
-  if(!strcmp(action, "play"))
-    rc = disorder_simple(c, 0, "schedule-add", when_str, priority,
-                        action, va_arg(ap, char *),
-                        (char *)0);
-  else if(!strcmp(action, "set-global")) {
-    const char *key = va_arg(ap, char *);
-    const char *value = va_arg(ap, char *);
-    rc = disorder_simple(c, 0,"schedule-add",  when_str, priority,
-                        action, key, value,
-                        (char *)0);
-  } else
-    disorder_fatal(0, "unknown action '%s'", action);
-  va_end(ap);
-  return rc;
-}
-
-/** @brief Adopt a track
- * @param c Client
- * @param id Track ID to adopt
- * @return 0 on success, non-0 on error
- */
-int disorder_adopt(disorder_client *c, const char *id) {
-  return disorder_simple(c, 0, "adopt", id, (char *)0);
-}
-
-/** @brief Delete a playlist
- * @param c Client
- * @param playlist Playlist to delete
- * @return 0 on success, non-0 on error
- */
-int disorder_playlist_delete(disorder_client *c,
-                             const char *playlist) {
-  return disorder_simple(c, 0, "playlist-delete", playlist, (char *)0);
-}
-
-/** @brief Get the contents of a playlist
- * @param c Client
- * @param playlist Playlist to get
- * @param tracksp Where to put list of tracks
- * @param ntracksp Where to put count of tracks
- * @return 0 on success, non-0 on error
- */
-int disorder_playlist_get(disorder_client *c, const char *playlist,
-                          char ***tracksp, int *ntracksp) {
-  return disorder_simple_list(c, tracksp, ntracksp,
-                              "playlist-get", playlist, (char *)0);
-}
-
-/** @brief List all readable playlists
- * @param c Client
- * @param playlistsp Where to put list of playlists
- * @param nplaylistsp Where to put count of playlists
- * @return 0 on success, non-0 on error
- */
-int disorder_playlists(disorder_client *c,
-                       char ***playlistsp, int *nplaylistsp) {
-  return disorder_simple_list(c, playlistsp, nplaylistsp,
-                              "playlists", (char *)0);
-}
-
-/** @brief Get the sharing status of a playlist
- * @param c Client
- * @param playlist Playlist to inspect
- * @param sharep Where to put sharing status
- * @return 0 on success, non-0 on error
- *
- * Possible @p sharep values are @c public, @c private and @c shared.
- */
-int disorder_playlist_get_share(disorder_client *c, const char *playlist,
-                                char **sharep) {
-  return disorder_simple(c, sharep,
-                         "playlist-get-share", playlist, (char *)0);
-}
-
-/** @brief Get the sharing status of a playlist
- * @param c Client
- * @param playlist Playlist to modify
- * @param share New sharing status
- * @return 0 on success, non-0 on error
- *
- * Possible @p share values are @c public, @c private and @c shared.
- */
-int disorder_playlist_set_share(disorder_client *c, const char *playlist,
-                                const char *share) {
-  return disorder_simple(c, 0,
-                         "playlist-set-share", playlist, share, (char *)0);
-}
-
-/** @brief Lock a playlist for modifications
- * @param c Client
- * @param playlist Playlist to lock
- * @return 0 on success, non-0 on error
- */
-int disorder_playlist_lock(disorder_client *c, const char *playlist) {
-  return disorder_simple(c, 0,
-                         "playlist-lock", playlist, (char *)0);
-}
-
-/** @brief Unlock the locked playlist
- * @param c Client
- * @return 0 on success, non-0 on error
- */
-int disorder_playlist_unlock(disorder_client *c) {
-  return disorder_simple(c, 0,
-                         "playlist-unlock", (char *)0);
-}
-
-/** @brief Set the contents of a playlst
- * @param c Client
- * @param playlist Playlist to modify
- * @param tracks List of tracks
- * @param ntracks Length of @p tracks (or -1 to count up to the first NULL)
- * @return 0 on success, non-0 on error
- */
-int disorder_playlist_set(disorder_client *c,
-                          const char *playlist,
-                          char **tracks,
-                          int ntracks) {
-  return disorder_simple_body(c, 0, tracks, ntracks,
-                              "playlist-set", playlist, (char *)0);
-}
+#include "client-stubs.c"
 
 /*
 Local Variables:
index f7ff7289198a2107c3d33753d33918e72dcd79d0..2513a89e382d756f4e8b1e1bf8c174d19ee521bd 100644 (file)
@@ -18,6 +18,9 @@
 /** @file lib/client.h
  * @brief Simple C client
  *
+ * See @ref lib/client-stubs.h for the (generated) per-command entry
+ * points.
+ *
  * See @ref lib/eclient.h for an asynchronous-capable client
  * implementation.
  */
@@ -47,107 +50,11 @@ int disorder_connect_generic(struct config *conf,
                              const char *password,
                              const char *cookie);
 int disorder_close(disorder_client *c);
-int disorder_version(disorder_client *c, char **versionp);
-int disorder_play(disorder_client *c, const char *track);
-int disorder_remove(disorder_client *c, const char *track);
-int disorder_move(disorder_client *c, const char *track, int delta);
-int disorder_enable(disorder_client *c);
-int disorder_disable(disorder_client *c);
-int disorder_scratch(disorder_client *c, const char *id);
-int disorder_shutdown(disorder_client *c);
-int disorder_reconfigure(disorder_client *c);
-int disorder_rescan(disorder_client *c);
-int disorder_playing(disorder_client *c, struct queue_entry **qp);
-int disorder_recent(disorder_client *c, struct queue_entry **qp);
-int disorder_queue(disorder_client *c, struct queue_entry **qp);
-int disorder_directories(disorder_client *c, const char *dir, const char *re,
-                        char ***vecp, int *nvecp);
-int disorder_files(disorder_client *c, const char *dir, const char *re,
-                  char ***vecp, int *nvecp);
-int disorder_allfiles(disorder_client *c, const char *dir, const char *re,
-                     char ***vecp, int *nvecp);
 char *disorder_user(disorder_client *c);
-int disorder_exists(disorder_client *c, const char *track, int *existsp);
-int disorder_enabled(disorder_client *c, int *enabledp);
-int disorder_set(disorder_client *c, const char *track,
-                const char *key, const char *value);
-int disorder_unset(disorder_client *c, const char *track,
-                  const char *key);
-int disorder_get(disorder_client *c, const char *track, const char *key,
-                char **valuep);
-int disorder_prefs(disorder_client *c, const char *track,
-                  struct kvp **kp);
-int disorder_length(disorder_client *c, const char *track,
-                   long *valuep);
-int disorder_search(disorder_client *c, const char *terms,
-                   char ***vecp, int *nvecp);
-int disorder_random_enable(disorder_client *c);
-int disorder_random_disable(disorder_client *c);
-int disorder_random_enabled(disorder_client *c, int *enabledp);
-int disorder_stats(disorder_client *c,
-                  char ***vecp, int *nvecp);
-int disorder_set_volume(disorder_client *c, int left, int right);
-int disorder_get_volume(disorder_client *c, int *left, int *right);
 int disorder_log(disorder_client *c, struct sink *s);
-int disorder_part(disorder_client *c, char **partp,
-                 const char *track, const char *context, const char *part);
-int disorder_resolve(disorder_client *c, char **trackp, const char *track);
-int disorder_pause(disorder_client *c);
-int disorder_resume(disorder_client *c);
-int disorder_tags(disorder_client *c,
-                  char ***vecp, int *nvecp);
-int disorder_set_global(disorder_client *c,
-                       const char *key, const char *value);
-int disorder_unset_global(disorder_client *c, const char *key);
-int disorder_get_global(disorder_client *c, const char *key, char **valuep);
-int disorder_new_tracks(disorder_client *c,
-                       char ***vecp, int *nvecp,
-                       int max);
-int disorder_rtp_address(disorder_client *c, char **addressp, char **portp);
-int disorder_adduser(disorder_client *c,
-                    const char *user, const char *password,
-                    const char *rights);
-int disorder_deluser(disorder_client *c, const char *user);
-int disorder_userinfo(disorder_client *c, const char *user, const char *key,
-                     char **valuep);
-int disorder_edituser(disorder_client *c, const char *user,
-                     const char *key, const char *value);
-int disorder_users(disorder_client *c,
-                  char ***vecp, int *nvecp);
-int disorder_register(disorder_client *c, const char *user,
-                     const char *password, const char *email,
-                     char **confirmp);
-int disorder_confirm(disorder_client *c, const char *confirm);
-int disorder_make_cookie(disorder_client *c, char **cookiep);
 const char *disorder_last(disorder_client *c);
-int disorder_revoke(disorder_client *c);
-int disorder_reminder(disorder_client *c, const char *user);
-int disorder_schedule_list(disorder_client *c, char ***idsp, int *nidsp);
-int disorder_schedule_del(disorder_client *c, const char *id);
-int disorder_schedule_get(disorder_client *c, const char *id,
-                         struct kvp **actiondatap);
-int disorder_schedule_add(disorder_client *c,
-                         time_t when,
-                         const char *priority,
-                         const char *action,
-                         ...);
-int disorder_adopt(disorder_client *c, const char *id);
-int disorder_playlist_delete(disorder_client *c,
-                             const char *playlist);
-int disorder_playlist_get(disorder_client *c, const char *playlist,
-                          char ***tracksp, int *ntracksp);
-int disorder_playlists(disorder_client *c,
-                       char ***playlistsp, int *nplaylists);
-int disorder_playlist_get_share(disorder_client *c, const char *playlist,
-                                char **sharep);
-int disorder_playlist_set_share(disorder_client *c, const char *playlist,
-                                const char *share);
-int disorder_playlist_lock(disorder_client *c, const char *playlist);
-int disorder_playlist_unlock(disorder_client *c);
-int disorder_playlist_set(disorder_client *c,
-                          const char *playlist,
-                          char **tracks,
-                          int ntracks);
+
+#include "client-stubs.h"
 
 #endif /* CLIENT_H */
 
diff --git a/lib/eclient-stubs.c b/lib/eclient-stubs.c
new file mode 100644 (file)
index 0000000..63f5163
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * Automatically generated file, see scripts/protocol
+ *
+ * DO NOT EDIT.
+ */
+/*
+ * This file is part of DisOrder.
+ * Copyright (C) 2010-11 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+/** @file lib/client-stubs.c
+ * @brief Generated asynchronous client API implementation
+ */
+
+int disorder_eclient_adopt(disorder_eclient *c, disorder_eclient_no_response *completed, const char *id, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "adopt", id, (char *)0);
+}
+
+int disorder_eclient_adduser(disorder_eclient *c, disorder_eclient_no_response *completed, const char *user, const char *password, const char *rights, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "adduser", user, password, rights, (char *)0);
+}
+
+int disorder_eclient_allfiles(disorder_eclient *c, disorder_eclient_list_response *completed, const char *dir, const char *re, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "allfiles", dir, re, (char *)0);
+}
+
+int disorder_eclient_deluser(disorder_eclient *c, disorder_eclient_no_response *completed, const char *user, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "deluser", user, (char *)0);
+}
+
+int disorder_eclient_dirs(disorder_eclient *c, disorder_eclient_list_response *completed, const char *dir, const char *re, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "dirs", dir, re, (char *)0);
+}
+
+int disorder_eclient_disable(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "disable", (char *)0);
+}
+
+int disorder_eclient_edituser(disorder_eclient *c, disorder_eclient_no_response *completed, const char *username, const char *property, const char *value, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "edituser", username, property, value, (char *)0);
+}
+
+int disorder_eclient_enable(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "enable", (char *)0);
+}
+
+int disorder_eclient_enabled(disorder_eclient *c, disorder_eclient_integer_response *completed, void *v) {
+  return simple(c, integer_response_opcallback, (void (*)())completed, v, "enabled", (char *)0);
+}
+
+int disorder_eclient_exists(disorder_eclient *c, disorder_eclient_integer_response *completed, const char *track, void *v) {
+  return simple(c, integer_response_opcallback, (void (*)())completed, v, "exists", track, (char *)0);
+}
+
+int disorder_eclient_files(disorder_eclient *c, disorder_eclient_list_response *completed, const char *dir, const char *re, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "files", dir, re, (char *)0);
+}
+
+int disorder_eclient_get(disorder_eclient *c, disorder_eclient_string_response *completed, const char *track, const char *pref, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "get", track, pref, (char *)0);
+}
+
+int disorder_eclient_get_global(disorder_eclient *c, disorder_eclient_string_response *completed, const char *pref, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "get-global", pref, (char *)0);
+}
+
+int disorder_eclient_length(disorder_eclient *c, disorder_eclient_integer_response *completed, const char *track, void *v) {
+  return simple(c, integer_response_opcallback, (void (*)())completed, v, "length", track, (char *)0);
+}
+
+int disorder_eclient_make_cookie(disorder_eclient *c, disorder_eclient_string_response *completed, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "make-cookie", (char *)0);
+}
+
+int disorder_eclient_move(disorder_eclient *c, disorder_eclient_no_response *completed, const char *track, long delta, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "move", track, disorder__integer, delta, (char *)0);
+}
+
+int disorder_eclient_moveafter(disorder_eclient *c, disorder_eclient_no_response *completed, const char *target, char **ids, int nids, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "moveafter", target, disorder__list, ids, nids, (char *)0);
+}
+
+int disorder_eclient_new_tracks(disorder_eclient *c, disorder_eclient_list_response *completed, long max, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "new", disorder__integer, max, (char *)0);
+}
+
+int disorder_eclient_nop(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "nop", (char *)0);
+}
+
+int disorder_eclient_part(disorder_eclient *c, disorder_eclient_string_response *completed, const char *track, const char *context, const char *part, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "part", track, context, part, (char *)0);
+}
+
+int disorder_eclient_pause(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "pause", (char *)0);
+}
+
+int disorder_eclient_play(disorder_eclient *c, disorder_eclient_string_response *completed, const char *track, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "play", track, (char *)0);
+}
+
+int disorder_eclient_playafter(disorder_eclient *c, disorder_eclient_no_response *completed, const char *target, char **tracks, int ntracks, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "playafter", target, disorder__list, tracks, ntracks, (char *)0);
+}
+
+int disorder_eclient_playing(disorder_eclient *c, disorder_eclient_playing_response *completed, void *v) {
+  return simple(c, playing_response_opcallback, (void (*)())completed, v, "playing", (char *)0);
+}
+
+int disorder_eclient_playlist_delete(disorder_eclient *c, disorder_eclient_no_response *completed, const char *playlist, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "playlist-delete", playlist, (char *)0);
+}
+
+int disorder_eclient_playlist_get(disorder_eclient *c, disorder_eclient_list_response *completed, const char *playlist, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "playlist-get", playlist, (char *)0);
+}
+
+int disorder_eclient_playlist_get_share(disorder_eclient *c, disorder_eclient_string_response *completed, const char *playlist, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "playlist-get-share", playlist, (char *)0);
+}
+
+int disorder_eclient_playlist_lock(disorder_eclient *c, disorder_eclient_no_response *completed, const char *playlist, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "playlist-lock", playlist, (char *)0);
+}
+
+int disorder_eclient_playlist_set(disorder_eclient *c, disorder_eclient_no_response *completed, const char *playlist, char **tracks, int ntracks, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "playlist-set", playlist, disorder__body, tracks, ntracks, (char *)0);
+}
+
+int disorder_eclient_playlist_set_share(disorder_eclient *c, disorder_eclient_no_response *completed, const char *playlist, const char *share, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "playlist-set-share", playlist, share, (char *)0);
+}
+
+int disorder_eclient_playlist_unlock(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "playlist-unlock", (char *)0);
+}
+
+int disorder_eclient_playlists(disorder_eclient *c, disorder_eclient_list_response *completed, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "playlists", (char *)0);
+}
+
+int disorder_eclient_queue(disorder_eclient *c, disorder_eclient_queue_response *completed, void *v) {
+  return simple(c, queue_response_opcallback, (void (*)())completed, v, "queue", (char *)0);
+}
+
+int disorder_eclient_random_disable(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "random-disable", (char *)0);
+}
+
+int disorder_eclient_random_enable(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "random-enable", (char *)0);
+}
+
+int disorder_eclient_random_enabled(disorder_eclient *c, disorder_eclient_integer_response *completed, void *v) {
+  return simple(c, integer_response_opcallback, (void (*)())completed, v, "random-enabled", (char *)0);
+}
+
+int disorder_eclient_recent(disorder_eclient *c, disorder_eclient_queue_response *completed, void *v) {
+  return simple(c, queue_response_opcallback, (void (*)())completed, v, "recent", (char *)0);
+}
+
+int disorder_eclient_reconfigure(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "reconfigure", (char *)0);
+}
+
+int disorder_eclient_register(disorder_eclient *c, disorder_eclient_string_response *completed, const char *username, const char *password, const char *email, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "register", username, password, email, (char *)0);
+}
+
+int disorder_eclient_reminder(disorder_eclient *c, disorder_eclient_no_response *completed, const char *username, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "reminder", username, (char *)0);
+}
+
+int disorder_eclient_remove(disorder_eclient *c, disorder_eclient_no_response *completed, const char *id, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "remove", id, (char *)0);
+}
+
+int disorder_eclient_rescan(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "rescan", (char *)0);
+}
+
+int disorder_eclient_resolve(disorder_eclient *c, disorder_eclient_string_response *completed, const char *track, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "resolve", track, (char *)0);
+}
+
+int disorder_eclient_resume(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "resume", (char *)0);
+}
+
+int disorder_eclient_revoke(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "revoke", (char *)0);
+}
+
+int disorder_eclient_scratch(disorder_eclient *c, disorder_eclient_no_response *completed, const char *id, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "scratch", id, (char *)0);
+}
+
+int disorder_eclient_schedule_add_play(disorder_eclient *c, disorder_eclient_no_response *completed, time_t when, const char *priority, const char *track, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "schedule-add", disorder__time, when, priority, "play", track, (char *)0);
+}
+
+int disorder_eclient_schedule_add_set_global(disorder_eclient *c, disorder_eclient_no_response *completed, time_t when, const char *priority, const char *pref, const char *value, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "schedule-add", disorder__time, when, priority, "set-global", pref, value, (char *)0);
+}
+
+int disorder_eclient_schedule_add_unset_global(disorder_eclient *c, disorder_eclient_no_response *completed, time_t when, const char *priority, const char *pref, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "schedule-add", disorder__time, when, priority, "set-global", pref, (char *)0);
+}
+
+int disorder_eclient_schedule_del(disorder_eclient *c, disorder_eclient_no_response *completed, const char *event, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "schedule-del", event, (char *)0);
+}
+
+int disorder_eclient_schedule_list(disorder_eclient *c, disorder_eclient_list_response *completed, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "schedule-list", (char *)0);
+}
+
+int disorder_eclient_search(disorder_eclient *c, disorder_eclient_list_response *completed, const char *terms, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "search", terms, (char *)0);
+}
+
+int disorder_eclient_set(disorder_eclient *c, disorder_eclient_no_response *completed, const char *track, const char *pref, const char *value, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "set", track, pref, value, (char *)0);
+}
+
+int disorder_eclient_set_global(disorder_eclient *c, disorder_eclient_no_response *completed, const char *pref, const char *value, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "set-global", pref, value, (char *)0);
+}
+
+int disorder_eclient_shutdown(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "shutdown", (char *)0);
+}
+
+int disorder_eclient_stats(disorder_eclient *c, disorder_eclient_list_response *completed, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "stats", (char *)0);
+}
+
+int disorder_eclient_tags(disorder_eclient *c, disorder_eclient_list_response *completed, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "tags", (char *)0);
+}
+
+int disorder_eclient_unset(disorder_eclient *c, disorder_eclient_no_response *completed, const char *track, const char *pref, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "unset", track, pref, (char *)0);
+}
+
+int disorder_eclient_unset_global(disorder_eclient *c, disorder_eclient_no_response *completed, const char *pref, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "unset-global", pref, (char *)0);
+}
+
+int disorder_eclient_userinfo(disorder_eclient *c, disorder_eclient_string_response *completed, const char *username, const char *property, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "userinfo", username, property, (char *)0);
+}
+
+int disorder_eclient_users(disorder_eclient *c, disorder_eclient_list_response *completed, void *v) {
+  return simple(c, list_response_opcallback, (void (*)())completed, v, "users", (char *)0);
+}
+
+int disorder_eclient_version(disorder_eclient *c, disorder_eclient_string_response *completed, void *v) {
+  return simple(c, string_response_opcallback, (void (*)())completed, v, "version", (char *)0);
+}
+
+int disorder_eclient_set_volume(disorder_eclient *c, disorder_eclient_no_response *completed, long left, long right, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "volume", disorder__integer, left, disorder__integer, right, (char *)0);
+}
+
+int disorder_eclient_get_volume(disorder_eclient *c, disorder_eclient_pair_integer_response *completed, void *v) {
+  return simple(c, pair_integer_response_opcallback, (void (*)())completed, v, "volume", (char *)0);
+}
+
diff --git a/lib/eclient-stubs.h b/lib/eclient-stubs.h
new file mode 100644 (file)
index 0000000..af3b389
--- /dev/null
@@ -0,0 +1,807 @@
+/*
+ * Automatically generated file, see scripts/protocol
+ *
+ * DO NOT EDIT.
+ */
+/*
+ * This file is part of DisOrder.
+ * Copyright (C) 2010-11 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef ECLIENT_STUBS_H
+#define ECLIENT_STUBS_H
+/** @file lib/client-stubs.h
+ * @brief Generated asynchronous client API
+ *
+ * Don't include this file directly - use @ref client.h instead.
+ */
+
+/** @brief Adopt a track
+ *
+ * Makes the calling user owner of a randomly picked track.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param id Track ID
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_adopt(disorder_eclient *c, disorder_eclient_no_response *completed, const char *id, void *v);
+
+/** @brief Create a user
+ *
+ * Create a new user.  Requires the 'admin' right.  Email addresses etc must be filled in in separate commands.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param user New username
+ * @param password Initial password
+ * @param rights Initial rights (optional)
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_adduser(disorder_eclient *c, disorder_eclient_no_response *completed, const char *user, const char *password, const char *rights, void *v);
+
+/** @brief List files and directories in a directory
+ *
+ * See 'files' and 'dirs' for more specific lists.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param dir Directory to list (optional)
+ * @param re Regexp that results must match (optional)
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_allfiles(disorder_eclient *c, disorder_eclient_list_response *completed, const char *dir, const char *re, void *v);
+
+/** @brief Delete user
+ *
+ * Requires the 'admin' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param user User to delete
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_deluser(disorder_eclient *c, disorder_eclient_no_response *completed, const char *user, void *v);
+
+/** @brief List directories in a directory
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param dir Directory to list (optional)
+ * @param re Regexp that results must match (optional)
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_dirs(disorder_eclient *c, disorder_eclient_list_response *completed, const char *dir, const char *re, void *v);
+
+/** @brief Disable play
+ *
+ * Play will stop at the end of the current track, if one is playing.  Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_disable(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Set a user property
+ *
+ * With the 'admin' right you can do anything.  Otherwise you need the 'userinfo' right and can only set 'email' and 'password'.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param username User to modify
+ * @param property Property name
+ * @param value New property value
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_edituser(disorder_eclient *c, disorder_eclient_no_response *completed, const char *username, const char *property, const char *value, void *v);
+
+/** @brief Enable play
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_enable(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Detect whether play is enabled
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_enabled(disorder_eclient *c, disorder_eclient_integer_response *completed, void *v);
+
+/** @brief Test whether a track exists
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param track Track name
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_exists(disorder_eclient *c, disorder_eclient_integer_response *completed, const char *track, void *v);
+
+/** @brief List files in a directory
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param dir Directory to list (optional)
+ * @param re Regexp that results must match (optional)
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_files(disorder_eclient *c, disorder_eclient_list_response *completed, const char *dir, const char *re, void *v);
+
+/** @brief Get a track preference
+ *
+ * If the track does not exist that is an error.  If the track exists but the preference does not then a null value is returned.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param track Track name
+ * @param pref Preference name
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_get(disorder_eclient *c, disorder_eclient_string_response *completed, const char *track, const char *pref, void *v);
+
+/** @brief Get a global preference
+ *
+ * If the preference does exist not then a null value is returned.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param pref Global preference name
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_get_global(disorder_eclient *c, disorder_eclient_string_response *completed, const char *pref, void *v);
+
+/** @brief Get a track's length
+ *
+ * If the track does not exist an error is returned.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param track Track name
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_length(disorder_eclient *c, disorder_eclient_integer_response *completed, const char *track, void *v);
+
+/** @brief Create a login cookie for this user
+ *
+ * The cookie may be redeemed via the 'cookie' command
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_make_cookie(disorder_eclient *c, disorder_eclient_string_response *completed, void *v);
+
+/** @brief Move a track
+ *
+ * Requires one of the 'move mine', 'move random' or 'move any' rights depending on how the track came to be added to the queue.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param track Track ID or name
+ * @param delta How far to move the track towards the head of the queue
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_move(disorder_eclient *c, disorder_eclient_no_response *completed, const char *track, long delta, void *v);
+
+/** @brief Move multiple tracks
+ *
+ * Requires one of the 'move mine', 'move random' or 'move any' rights depending on how the track came to be added to the queue.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param target Move after this track, or to head if ""
+ * @param ids List of tracks to move by ID
+ * @param nids Length of ids
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_moveafter(disorder_eclient *c, disorder_eclient_no_response *completed, const char *target, char **ids, int nids, void *v);
+
+/** @brief List recently added tracks
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param max Maximum tracks to fetch, or 0 for all available
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_new_tracks(disorder_eclient *c, disorder_eclient_list_response *completed, long max, void *v);
+
+/** @brief Do nothing
+ *
+ * Used as a keepalive.  No authentication required.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_nop(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Get a track name part
+ *
+ * If the name part cannot be constructed an empty string is returned.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param track Track name
+ * @param context Context ("sort" or "display")
+ * @param part Name part ("artist", "album" or "title")
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_part(disorder_eclient *c, disorder_eclient_string_response *completed, const char *track, const char *context, const char *part, void *v);
+
+/** @brief Pause the currently playing track
+ *
+ * Requires the 'pause' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_pause(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Play a track
+ *
+ * Requires the 'play' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param track Track to play
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_play(disorder_eclient *c, disorder_eclient_string_response *completed, const char *track, void *v);
+
+/** @brief Play multiple tracks
+ *
+ * Requires the 'play' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param target Insert into queue after this track, or at head if ""
+ * @param tracks List of track names to play
+ * @param ntracks Length of tracks
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playafter(disorder_eclient *c, disorder_eclient_no_response *completed, const char *target, char **tracks, int ntracks, void *v);
+
+/** @brief Retrieve the playing track
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playing(disorder_eclient *c, disorder_eclient_playing_response *completed, void *v);
+
+/** @brief Delete a playlist
+ *
+ * Requires the 'play' right and permission to modify the playlist.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param playlist Playlist to delete
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playlist_delete(disorder_eclient *c, disorder_eclient_no_response *completed, const char *playlist, void *v);
+
+/** @brief List the contents of a playlist
+ *
+ * Requires the 'read' right and oermission to read the playlist.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param playlist Playlist name
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playlist_get(disorder_eclient *c, disorder_eclient_list_response *completed, const char *playlist, void *v);
+
+/** @brief Get a playlist's sharing status
+ *
+ * Requires the 'read' right and permission to read the playlist.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param playlist Playlist to read
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playlist_get_share(disorder_eclient *c, disorder_eclient_string_response *completed, const char *playlist, void *v);
+
+/** @brief Lock a playlist
+ *
+ * Requires the 'play' right and permission to modify the playlist.  A given connection may lock at most one playlist.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param playlist Playlist to delete
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playlist_lock(disorder_eclient *c, disorder_eclient_no_response *completed, const char *playlist, void *v);
+
+/** @brief Set the contents of a playlist
+ *
+ * Requires the 'play' right and permission to modify the playlist, which must be locked.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param playlist Playlist to modify
+ * @param tracks New list of tracks for playlist
+ * @param ntracks Length of tracks
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playlist_set(disorder_eclient *c, disorder_eclient_no_response *completed, const char *playlist, char **tracks, int ntracks, void *v);
+
+/** @brief Set a playlist's sharing status
+ *
+ * Requires the 'play' right and permission to modify the playlist.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param playlist Playlist to modify
+ * @param share New sharing status ("public", "private" or "shared")
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playlist_set_share(disorder_eclient *c, disorder_eclient_no_response *completed, const char *playlist, const char *share, void *v);
+
+/** @brief Unlock the locked playlist playlist
+ *
+ * The playlist to unlock is implicit in the connection.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playlist_unlock(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief List playlists
+ *
+ * Requires the 'read' right.  Only playlists that you have permission to read are returned.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_playlists(disorder_eclient *c, disorder_eclient_list_response *completed, void *v);
+
+/** @brief List the queue
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_queue(disorder_eclient *c, disorder_eclient_queue_response *completed, void *v);
+
+/** @brief Disable random play
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_random_disable(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Enable random play
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_random_enable(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Detect whether random play is enabled
+ *
+ * Random play counts as enabled even if play is disabled.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_random_enabled(disorder_eclient *c, disorder_eclient_integer_response *completed, void *v);
+
+/** @brief List recently played tracks
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_recent(disorder_eclient *c, disorder_eclient_queue_response *completed, void *v);
+
+/** @brief Re-read configuraiton file.
+ *
+ * Requires the 'admin' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_reconfigure(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Register a new user
+ *
+ * Requires the 'register' right which is usually only available to the 'guest' user.  Redeem the confirmation string via 'confirm' to complete registration.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param username Requested new username
+ * @param password Requested initial password
+ * @param email New user's email address
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_register(disorder_eclient *c, disorder_eclient_string_response *completed, const char *username, const char *password, const char *email, void *v);
+
+/** @brief Send a password reminder.
+ *
+ * If the user has no valid email address, or no password, or a reminder has been sent too recently, then no reminder will be sent.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param username User to remind
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_reminder(disorder_eclient *c, disorder_eclient_no_response *completed, const char *username, void *v);
+
+/** @brief Remove a track form the queue.
+ *
+ * Requires one of the 'remove mine', 'remove random' or 'remove any' rights depending on how the track came to be added to the queue.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param id Track ID
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_remove(disorder_eclient *c, disorder_eclient_no_response *completed, const char *id, void *v);
+
+/** @brief Rescan all collections for new or obsolete tracks.
+ *
+ * Requires the 'rescan' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_rescan(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Resolve a track name
+ *
+ * Converts aliases to non-alias track names
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param track Track name (might be an alias)
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_resolve(disorder_eclient *c, disorder_eclient_string_response *completed, const char *track, void *v);
+
+/** @brief Resume the currently playing track
+ *
+ * Requires the 'pause' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_resume(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Revoke a cookie.
+ *
+ * It will not subsequently be possible to log in with the cookie.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_revoke(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Terminate the playing track.
+ *
+ * Requires one of the 'scratch mine', 'scratch random' or 'scratch any' rights depending on how the track came to be added to the queue.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param id Track ID (optional)
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_scratch(disorder_eclient *c, disorder_eclient_no_response *completed, const char *id, void *v);
+
+/** @brief Schedule a track to play in the future
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param when When to play the track
+ * @param priority Event priority ("normal" or "junk")
+ * @param track Track to play
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_schedule_add_play(disorder_eclient *c, disorder_eclient_no_response *completed, time_t when, const char *priority, const char *track, void *v);
+
+/** @brief Schedule a global setting to be changed in the future
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param when When to change the setting
+ * @param priority Event priority ("normal" or "junk")
+ * @param pref Global preference to set
+ * @param value New value of global preference
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_schedule_add_set_global(disorder_eclient *c, disorder_eclient_no_response *completed, time_t when, const char *priority, const char *pref, const char *value, void *v);
+
+/** @brief Schedule a global setting to be unset in the future
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param when When to change the setting
+ * @param priority Event priority ("normal" or "junk")
+ * @param pref Global preference to set
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_schedule_add_unset_global(disorder_eclient *c, disorder_eclient_no_response *completed, time_t when, const char *priority, const char *pref, void *v);
+
+/** @brief Delete a scheduled event.
+ *
+ * Users can always delete their own scheduled events; with the admin right you can delete any event.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param event ID of event to delete
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_schedule_del(disorder_eclient *c, disorder_eclient_no_response *completed, const char *event, void *v);
+
+/** @brief List scheduled events
+ *
+ * This just lists IDs.  Use 'schedule-get' to retrieve more detail
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_schedule_list(disorder_eclient *c, disorder_eclient_list_response *completed, void *v);
+
+/** @brief Search for tracks
+ *
+ * Terms are either keywords or tags formatted as 'tag:TAG-NAME'.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param terms List of search terms
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_search(disorder_eclient *c, disorder_eclient_list_response *completed, const char *terms, void *v);
+
+/** @brief Set a track preference
+ *
+ * Requires the 'prefs' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param track Track name
+ * @param pref Preference name
+ * @param value New value
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_set(disorder_eclient *c, disorder_eclient_no_response *completed, const char *track, const char *pref, const char *value, void *v);
+
+/** @brief Set a global preference
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param pref Preference name
+ * @param value New value
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_set_global(disorder_eclient *c, disorder_eclient_no_response *completed, const char *pref, const char *value, void *v);
+
+/** @brief Request server shutdown
+ *
+ * Requires the 'admin' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_shutdown(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Get server statistics
+ *
+ * The details of what the server reports are not really defined.  The returned strings are intended to be printed out one to a line.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_stats(disorder_eclient *c, disorder_eclient_list_response *completed, void *v);
+
+/** @brief Get a list of known tags
+ *
+ * Only tags which apply to at least one track are returned.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_tags(disorder_eclient *c, disorder_eclient_list_response *completed, void *v);
+
+/** @brief Unset a track preference
+ *
+ * Requires the 'prefs' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param track Track name
+ * @param pref Preference name
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_unset(disorder_eclient *c, disorder_eclient_no_response *completed, const char *track, const char *pref, void *v);
+
+/** @brief Set a global preference
+ *
+ * Requires the 'global prefs' right.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param pref Preference name
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_unset_global(disorder_eclient *c, disorder_eclient_no_response *completed, const char *pref, void *v);
+
+/** @brief Get a user property.
+ *
+ * If the user does not exist an error is returned, if the user exists but the property does not then a null value is returned.
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param username User to read
+ * @param property Property to read
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_userinfo(disorder_eclient *c, disorder_eclient_string_response *completed, const char *username, const char *property, void *v);
+
+/** @brief Get a list of users
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_users(disorder_eclient *c, disorder_eclient_list_response *completed, void *v);
+
+/** @brief Get the server version
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_version(disorder_eclient *c, disorder_eclient_string_response *completed, void *v);
+
+/** @brief Set the volume
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param left Left channel volume
+ * @param right Right channel volume
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_set_volume(disorder_eclient *c, disorder_eclient_no_response *completed, long left, long right, void *v);
+
+/** @brief Get the volume
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_get_volume(disorder_eclient *c, disorder_eclient_pair_integer_response *completed, void *v);
+
+#endif
index d03306e852347b8d726f3ef9bf116ccb1922109d..d768f57446bf1e4b906f875dc33b8906b8ecb03b 100644 (file)
@@ -876,8 +876,31 @@ static void vstash_command(disorder_eclient *c,
   if(cmd) {
     vector_init(&vec);
     vector_append(&vec, (char *)cmd);
-    while((arg = va_arg(ap, char *)))
-      vector_append(&vec, arg);
+    while((arg = va_arg(ap, char *))) {
+      if(arg == disorder__list) {
+       char **list = va_arg(ap, char **);
+       int nlist = va_arg(ap, int);
+       if(nlist < 0) {
+         for(nlist = 0; list[nlist]; ++nlist)
+           ;
+       }
+        vector_append_many(&vec, list, nlist);
+      } else if(arg == disorder__body) {
+       body = va_arg(ap, char **);
+       nbody = va_arg(ap, int);
+      } else if(arg == disorder__integer) {
+        long n = va_arg(ap, long);
+        char buffer[16];
+        snprintf(buffer, sizeof buffer, "%ld", n);
+        vector_append(&vec, xstrdup(buffer));
+      } else if(arg == disorder__time) {
+        time_t n = va_arg(ap, time_t);
+        char buffer[16];
+        snprintf(buffer, sizeof buffer, "%lld", (long long)n);
+        vector_append(&vec, xstrdup(buffer));
+      } else
+        vector_append(&vec, arg);
+    }
     stash_command_vector(c, queuejump, opcallback, completed, v, 
                          nbody, body, vec.nvec, vec.vec);
   } else
@@ -1059,16 +1082,16 @@ static void list_response_opcallback(disorder_eclient *c,
 }
 
 /* for volume */
-static void volume_response_opcallback(disorder_eclient *c,
-                                       struct operation *op) {
-  disorder_eclient_volume_response *completed
-    = (disorder_eclient_volume_response *)op->completed;
-  int l, r;
+static void pair_integer_response_opcallback(disorder_eclient *c,
+                                             struct operation *op) {
+  disorder_eclient_pair_integer_response *completed
+    = (disorder_eclient_pair_integer_response *)op->completed;
+  long l, r;
 
   D(("volume_response_callback"));
   if(c->rc / 100 == 2) {
     if(op->completed) {
-      if(sscanf(c->line + 4, "%d %d", &l, &r) != 2 || l < 0 || r < 0)
+      if(sscanf(c->line + 4, "%ld %ld", &l, &r) != 2 || l < 0 || r < 0)
         completed(op->v, "cannot parse volume response", 0, 0);
       else
         completed(op->v, 0, l, r);
@@ -1092,316 +1115,12 @@ static int simple(disorder_eclient *c,
   return 0;
 }
 
-static int simple_body(disorder_eclient *c,
-                       operation_callback *opcallback,
-                       void (*completed)(),
-                       void *v,
-                       int nbody,
-                       char **body,
-                       const char *cmd, ...) {
-  va_list ap;
-
-  va_start(ap, cmd);
-  vstash_command(c, 0/*queuejump*/, opcallback, completed, v, nbody, body, cmd, ap);
-  va_end(ap);
-  /* Give the state machine a kick, since we might be in state_idle */
-  disorder_eclient_polled(c, 0);
-  return 0;
-}
-
 /* Commands ******************************************************************/
-int disorder_eclient_version(disorder_eclient *c,
-                             disorder_eclient_string_response *completed,
-                             void *v) {
-  return simple(c, string_response_opcallback, (void (*)())completed, v,
-                "version", (char *)0);
-}
-
-int disorder_eclient_namepart(disorder_eclient *c,
-                              disorder_eclient_string_response *completed,
-                              const char *track,
-                              const char *context,
-                              const char *part,
-                              void *v) {
-  return simple(c, string_response_opcallback, (void (*)())completed, v,
-                "part", track, context, part, (char *)0);
-}
-
-int disorder_eclient_play(disorder_eclient *c,
-                          const char *track,
-                          disorder_eclient_no_response *completed,
-                          void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "play", track, (char *)0);
-}
-
-int disorder_eclient_playafter(disorder_eclient *c,
-                               const char *target,
-                               int ntracks,
-                               const char **tracks,
-                               disorder_eclient_no_response *completed,
-                               void *v) {
-  struct vector vec;
-  int n;
-
-  if(!target)
-    target = "";
-  vector_init(&vec);
-  vector_append(&vec, (char *)"playafter");
-  vector_append(&vec, (char *)target);
-  for(n = 0; n < ntracks; ++n)
-    vector_append(&vec, (char *)tracks[n]);
-  stash_command_vector(c, 0/*queuejump*/, no_response_opcallback, completed, v,
-                       -1, 0, vec.nvec, vec.vec);
-  disorder_eclient_polled(c, 0);
-  return 0;
-}
-
-int disorder_eclient_pause(disorder_eclient *c,
-                           disorder_eclient_no_response *completed,
-                           void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "pause", (char *)0);
-}
-
-int disorder_eclient_resume(disorder_eclient *c,
-                            disorder_eclient_no_response *completed,
-                            void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "resume", (char *)0);
-}
-
-int disorder_eclient_scratch(disorder_eclient *c,
-                             const char *id,
-                             disorder_eclient_no_response *completed,
-                             void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "scratch", id, (char *)0);
-}
 
 int disorder_eclient_scratch_playing(disorder_eclient *c,
                                      disorder_eclient_no_response *completed,
                                      void *v) {
-  return disorder_eclient_scratch(c, 0, completed, v);
-}
-
-int disorder_eclient_remove(disorder_eclient *c,
-                            const char *id,
-                            disorder_eclient_no_response *completed,
-                            void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "remove", id, (char *)0);
-}
-
-int disorder_eclient_moveafter(disorder_eclient *c,
-                               const char *target,
-                               int nids,
-                               const char **ids,
-                               disorder_eclient_no_response *completed,
-                               void *v) {
-  struct vector vec;
-  int n;
-
-  vector_init(&vec);
-  vector_append(&vec, (char *)"moveafter");
-  vector_append(&vec, (char *)target);
-  for(n = 0; n < nids; ++n)
-    vector_append(&vec, (char *)ids[n]);
-  stash_command_vector(c, 0/*queuejump*/, no_response_opcallback, completed, v,
-                       -1, 0, vec.nvec, vec.vec);
-  disorder_eclient_polled(c, 0);
-  return 0;
-}
-
-int disorder_eclient_recent(disorder_eclient *c,
-                            disorder_eclient_queue_response *completed,
-                            void *v) {
-  return simple(c, queue_response_opcallback, (void (*)())completed, v,
-                "recent", (char *)0);
-}
-
-int disorder_eclient_queue(disorder_eclient *c,
-                            disorder_eclient_queue_response *completed,
-                            void *v) {
-  return simple(c, queue_response_opcallback, (void (*)())completed, v,
-                "queue", (char *)0);
-}
-
-int disorder_eclient_files(disorder_eclient *c,
-                           disorder_eclient_list_response *completed,
-                           const char *dir,
-                           const char *re,
-                           void *v) {
-  return simple(c, list_response_opcallback, (void (*)())completed, v,
-                "files", dir, re, (char *)0);
-}
-
-int disorder_eclient_dirs(disorder_eclient *c,
-                          disorder_eclient_list_response *completed,
-                          const char *dir,
-                          const char *re,
-                          void *v) {
-  return simple(c, list_response_opcallback, (void (*)())completed, v,
-                "dirs", dir, re, (char *)0);
-}
-
-int disorder_eclient_playing(disorder_eclient *c,
-                             disorder_eclient_queue_response *completed,
-                             void *v) {
-  return simple(c, playing_response_opcallback, (void (*)())completed, v,
-                "playing", (char *)0);
-}
-
-int disorder_eclient_length(disorder_eclient *c,
-                            disorder_eclient_integer_response *completed,
-                            const char *track,
-                            void *v) {
-  return simple(c, integer_response_opcallback, (void (*)())completed, v,
-                "length", track, (char *)0);
-}
-
-int disorder_eclient_volume(disorder_eclient *c,
-                            disorder_eclient_volume_response *completed,
-                            int l, int r,
-                            void *v) {
-  char sl[64], sr[64];
-
-  if(l < 0 && r < 0) {
-    return simple(c, volume_response_opcallback, (void (*)())completed, v,
-                  "volume", (char *)0);
-  } else if(l >= 0 && r >= 0) {
-    assert(l <= 100);
-    assert(r <= 100);
-    byte_snprintf(sl, sizeof sl, "%d", l);
-    byte_snprintf(sr, sizeof sr, "%d", r);
-    return simple(c, volume_response_opcallback, (void (*)())completed, v,
-                  "volume", sl, sr, (char *)0);
-  } else {
-    assert(!"invalid arguments to disorder_eclient_volume");
-    return -1;                          /* gcc is being dim */
-  }
-}
-
-int disorder_eclient_enable(disorder_eclient *c,
-                            disorder_eclient_no_response *completed,
-                            void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "enable", (char *)0);
-}
-
-int disorder_eclient_disable(disorder_eclient *c,
-                             disorder_eclient_no_response *completed,
-                             void *v){
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "disable", (char *)0);
-}
-
-int disorder_eclient_random_enable(disorder_eclient *c,
-                                   disorder_eclient_no_response *completed,
-                                   void *v){
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "random-enable", (char *)0);
-}
-
-int disorder_eclient_random_disable(disorder_eclient *c,
-                                    disorder_eclient_no_response *completed,
-                                    void *v){
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "random-disable", (char *)0);
-}
-
-int disorder_eclient_get(disorder_eclient *c,
-                         disorder_eclient_string_response *completed,
-                         const char *track, const char *pref,
-                         void *v) {
-  return simple(c, string_response_opcallback, (void (*)())completed, v, 
-                "get", track, pref, (char *)0);
-}
-
-int disorder_eclient_set(disorder_eclient *c,
-                         disorder_eclient_no_response *completed,
-                         const char *track, const char *pref, 
-                         const char *value,
-                         void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v, 
-                "set", track, pref, value, (char *)0);
-}
-
-int disorder_eclient_unset(disorder_eclient *c,
-                           disorder_eclient_no_response *completed,
-                           const char *track, const char *pref, 
-                           void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v, 
-                "unset", track, pref, (char *)0);
-}
-
-int disorder_eclient_resolve(disorder_eclient *c,
-                             disorder_eclient_string_response *completed,
-                             const char *track,
-                             void *v) {
-  return simple(c, string_response_opcallback,  (void (*)())completed, v, 
-                "resolve", track, (char *)0);
-}
-
-int disorder_eclient_search(disorder_eclient *c,
-                            disorder_eclient_list_response *completed,
-                            const char *terms,
-                            void *v) {
-  if(!split(terms, 0, SPLIT_QUOTES, 0, 0)) return -1;
-  return simple(c, list_response_opcallback, (void (*)())completed, v,
-                "search", terms, (char *)0);
-}
-
-int disorder_eclient_nop(disorder_eclient *c,
-                         disorder_eclient_no_response *completed,
-                         void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v, 
-                "nop", (char *)0);
-}
-
-int disorder_eclient_get_global(disorder_eclient *c,
-                                disorder_eclient_string_response *completed,
-                                const char *pref,
-                                void *v) {
-  return simple(c, string_response_opcallback, (void (*)())completed, v,
-                "get-global", pref, (char *)0);
-}
-
-int disorder_eclient_set_global(disorder_eclient *c,
-                                disorder_eclient_no_response *completed,
-                                const char *pref,
-                                const char *value,
-                                void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "set-global", pref, value, (char *)0);
-}
-
-int disorder_eclient_unset_global(disorder_eclient *c,
-                                  disorder_eclient_no_response *completed,
-                                  const char *pref,
-                                  void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v,
-                "unset-global", pref, (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);
+  return disorder_eclient_scratch(c, completed, 0, v);
 }
 
 static void rtp_response_opcallback(disorder_eclient *c,
@@ -1436,217 +1155,6 @@ int disorder_eclient_rtp_address(disorder_eclient *c,
                 "rtp-address", (char *)0);
 }
 
-/** @brief Get the list of users
- * @param c Client
- * @param completed Called with list of users
- * @param v Passed to @p completed
- *
- * The user list is not sorted in any particular order.
- */
-int disorder_eclient_users(disorder_eclient *c,
-                           disorder_eclient_list_response *completed,
-                           void *v) {
-  return simple(c, list_response_opcallback, (void (*)())completed, v,
-                "users", (char *)0);
-}
-
-/** @brief Delete a user
- * @param c Client
- * @param completed Called on completion
- * @param user User to delete
- * @param v Passed to @p completed
- */
-int disorder_eclient_deluser(disorder_eclient *c,
-                             disorder_eclient_no_response *completed,
-                             const char *user,
-                             void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v, 
-                "deluser", user, (char *)0);
-}
-
-/** @brief Get a user property
- * @param c Client
- * @param completed Called on completion
- * @param user User to look up
- * @param property Property to look up
- * @param v Passed to @p completed
- */
-int disorder_eclient_userinfo(disorder_eclient *c,
-                              disorder_eclient_string_response *completed,
-                              const char *user,
-                              const char *property,
-                              void *v) {
-  return simple(c, string_response_opcallback,  (void (*)())completed, v, 
-                "userinfo", user, property, (char *)0);
-}
-
-/** @brief Modify a user property
- * @param c Client
- * @param completed Called on completion
- * @param user User to modify
- * @param property Property to modify
- * @param value New property value
- * @param v Passed to @p completed
- */
-int disorder_eclient_edituser(disorder_eclient *c,
-                              disorder_eclient_no_response *completed,
-                              const char *user,
-                              const char *property,
-                              const char *value,
-                              void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v, 
-                "edituser", user, property, value, (char *)0);
-}
-
-/** @brief Create a new user
- * @param c Client
- * @param completed Called on completion
- * @param user User to create
- * @param password Initial password
- * @param rights Initial rights or NULL
- * @param v Passed to @p completed
- */
-int disorder_eclient_adduser(disorder_eclient *c,
-                             disorder_eclient_no_response *completed,
-                             const char *user,
-                             const char *password,
-                             const char *rights,
-                             void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v, 
-                "adduser", user, password, rights, (char *)0);
-}
-
-/** @brief Adopt a track
- * @param c Client
- * @param completed Called on completion
- * @param id Track ID
- * @param v Passed to @p completed
- */
-int disorder_eclient_adopt(disorder_eclient *c,
-                           disorder_eclient_no_response *completed,
-                           const char *id,
-                           void *v) {
-  return simple(c, no_response_opcallback, (void (*)())completed, v, 
-                "adopt", id, (char *)0);
-}
-
-/** @brief Get the list of playlists
- * @param c Client
- * @param completed Called with list of playlists
- * @param v Passed to @p completed
- *
- * The playlist list is not sorted in any particular order.
- */
-int disorder_eclient_playlists(disorder_eclient *c,
-                               disorder_eclient_list_response *completed,
-                               void *v) {
-  return simple(c, list_response_opcallback, (void (*)())completed, v,
-                "playlists", (char *)0);
-}
-
-/** @brief Delete a playlist
- * @param c Client
- * @param completed Called on completion
- * @param playlist Playlist to delete
- * @param v Passed to @p completed
- */
-int disorder_eclient_playlist_delete(disorder_eclient *c,
-                                     disorder_eclient_no_response *completed,
-                                     const char *playlist,
-                                     void *v) {
-  return simple(c, no_response_opcallback,  (void (*)())completed, v,
-                "playlist-delete", playlist, (char *)0);
-}
-
-/** @brief Lock a playlist
- * @param c Client
- * @param completed Called on completion
- * @param playlist Playlist to lock
- * @param v Passed to @p completed
- */
-int disorder_eclient_playlist_lock(disorder_eclient *c,
-                                   disorder_eclient_no_response *completed,
-                                   const char *playlist,
-                                   void *v) {
-  return simple(c, no_response_opcallback,  (void (*)())completed, v,
-                "playlist-lock", playlist, (char *)0);
-}
-
-/** @brief Unlock the locked a playlist
- * @param c Client
- * @param completed Called on completion
- * @param v Passed to @p completed
- */
-int disorder_eclient_playlist_unlock(disorder_eclient *c,
-                                     disorder_eclient_no_response *completed,
-                                     void *v) {
-  return simple(c, no_response_opcallback,  (void (*)())completed, v,
-                "playlist-unlock", (char *)0);
-}
-
-/** @brief Set a playlist's sharing
- * @param c Client
- * @param completed Called on completion
- * @param playlist Playlist to modify
- * @param sharing @c "public" or @c "private"
- * @param v Passed to @p completed
- */
-int disorder_eclient_playlist_set_share(disorder_eclient *c,
-                                        disorder_eclient_no_response *completed,
-                                        const char *playlist,
-                                        const char *sharing,
-                                        void *v) {
-  return simple(c, no_response_opcallback,  (void (*)())completed, v,
-                "playlist-set-share", playlist, sharing, (char *)0);
-}
-
-/** @brief Get a playlist's sharing
- * @param c Client
- * @param completed Called with sharing status
- * @param playlist Playlist to inspect
- * @param v Passed to @p completed
- */
-int disorder_eclient_playlist_get_share(disorder_eclient *c,
-                                        disorder_eclient_string_response *completed,
-                                        const char *playlist,
-                                        void *v) {
-  return simple(c, string_response_opcallback,  (void (*)())completed, v,
-                "playlist-get-share", playlist, (char *)0);
-}
-
-/** @brief Set a playlist
- * @param c Client
- * @param completed Called on completion
- * @param playlist Playlist to modify
- * @param tracks List of tracks
- * @param ntracks Number of tracks
- * @param v Passed to @p completed
- */
-int disorder_eclient_playlist_set(disorder_eclient *c,
-                                  disorder_eclient_no_response *completed,
-                                  const char *playlist,
-                                  char **tracks,
-                                  int ntracks,
-                                  void *v) {
-  return simple_body(c, no_response_opcallback, (void (*)())completed, v,
-                     ntracks, tracks,
-                     "playlist-set", playlist, (char *)0);
-}
-
-/** @brief Get a playlist's contents
- * @param c Client
- * @param completed Called with playlist contents
- * @param playlist Playlist to inspect
- * @param v Passed to @p completed
- */
-int disorder_eclient_playlist_get(disorder_eclient *c,
-                                  disorder_eclient_list_response *completed,
-                                  const char *playlist,
-                                  void *v) {
-  return simple(c, list_response_opcallback,  (void (*)())completed, v,
-                "playlist-get", playlist, (char *)0);
-}
-
 /* Log clients ***************************************************************/
 
 /** @brief Monitor the server log
@@ -1954,6 +1462,8 @@ static void logentry_global_pref(disorder_eclient *c,
     c->log_callbacks->global_pref(c->log_v, vec[0], nvec > 1 ? vec[1] : 0);
 }
 
+#include "eclient-stubs.c"
+
 /*
 Local Variables:
 c-basic-offset:2
index c44c5dc1dd645568cd6a2b3eed6a017f16d18d34..876e2c6d3af17c774f8d37f7f5928834fc49a1bb 100644 (file)
@@ -17,6 +17,8 @@
  */
 /** @file lib/eclient.h
  * @brief Client code for event-driven programs
+ *
+ * See @ref lib/eclient-stubs.h for the (generated) per-command entry points.
  */
 
 #ifndef ECLIENT_H
@@ -243,7 +245,7 @@ typedef void disorder_eclient_string_response(void *v,
                                               const char *err,
                                               const char *value);
 
-/** @brief String result completion callback
+/** @brief Integer result completion callback
  * @param v User data
  * @param err Error string or NULL on succes
  * @param value Result or 0
@@ -267,9 +269,9 @@ typedef void disorder_eclient_integer_response(void *v,
  * @p error will be non-NULL on failure.  In this case @p l and @p r are always
  * 0.
  */
-typedef void disorder_eclient_volume_response(void *v,
-                                              const char *err,
-                                              int l, int r);
+typedef void disorder_eclient_pair_integer_response(void *v,
+                                                    const char *err,
+                                                    long l, long r);
 
 /** @brief Queue request completion callback
  * @param v User data
@@ -287,6 +289,8 @@ typedef void disorder_eclient_queue_response(void *v,
                                              const char *err,
                                              struct queue_entry *q);
 
+#define disorder_eclient_playing_response disorder_eclient_queue_response
+
 /** @brief List request completion callback
  * @param v User data
  * @param err Error string or NULL on success
@@ -317,249 +321,25 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode);
 /* Should be called when c's FD is readable and/or writable, and in any case
  * from time to time (so that retries work). */
 
-int disorder_eclient_version(disorder_eclient *c,
-                             disorder_eclient_string_response *completed,
-                             void *v);
-/* fetch the server version */
-
-int disorder_eclient_play(disorder_eclient *c,
-                          const char *track,
-                          disorder_eclient_no_response *completed,
-                          void *v);
-/* add a track to the queue */
-
-int disorder_eclient_playafter(disorder_eclient *c,
-                               const char *target,
-                               int ntracks,
-                               const char **tracks,
-                               disorder_eclient_no_response *completed,
-                               void *v);
-/* insert multiple tracks to an arbitrary point in the queue */
-
-int disorder_eclient_pause(disorder_eclient *c,
-                           disorder_eclient_no_response *completed,
-                           void *v);
-/* add a track to the queue */
-
-int disorder_eclient_resume(disorder_eclient *c,
-                            disorder_eclient_no_response *completed,
-                            void *v);
-/* add a track to the queue */
-
-int disorder_eclient_scratch(disorder_eclient *c,
-                             const char *id,
-                             disorder_eclient_no_response *completed,
-                             void *v);
-/* scratch a track by ID */
-
 int disorder_eclient_scratch_playing(disorder_eclient *c,
                                      disorder_eclient_no_response *completed,
                                      void *v);
 /* scratch the playing track whatever it is */
 
-int disorder_eclient_remove(disorder_eclient *c,
-                            const char *id,
-                            disorder_eclient_no_response *completed,
-                            void *v);
-/* remove a track from the queue */
-
-int disorder_eclient_moveafter(disorder_eclient *c,
-                               const char *target,
-                               int nids,
-                               const char **ids,
-                               disorder_eclient_no_response *completed,
-                               void *v);
-/* move tracks within the queue */
-
-int disorder_eclient_playing(disorder_eclient *c,
-                             disorder_eclient_queue_response *completed,
-                             void *v);
-/* find the currently playing track (0 for none) */
-
-int disorder_eclient_queue(disorder_eclient *c,
-                           disorder_eclient_queue_response *completed,
-                           void *v);
-/* list recently played tracks */
-
-int disorder_eclient_recent(disorder_eclient *c,
-                            disorder_eclient_queue_response *completed,
-                            void *v);
-/* list recently played tracks */
-
-int disorder_eclient_files(disorder_eclient *c,
-                           disorder_eclient_list_response *completed,
-                           const char *dir,
-                           const char *re,
-                           void *v);
-/* list files in a directory, matching RE if not a null pointer */
-
-int disorder_eclient_dirs(disorder_eclient *c,
-                          disorder_eclient_list_response *completed,
-                          const char *dir,
-                          const char *re,
-                          void *v);
-/* list directories in a directory, matching RE if not a null pointer */
-
-int disorder_eclient_namepart(disorder_eclient *c,
-                              disorder_eclient_string_response *completed,
-                              const char *track,
-                              const char *context,
-                              const char *part,
-                              void *v);
-/* look up a track name part */
-
-int disorder_eclient_length(disorder_eclient *c,
-                            disorder_eclient_integer_response *completed,
-                            const char *track,
-                            void *v);
-/* look up a track name length */
-
-int disorder_eclient_volume(disorder_eclient *c,
-                            disorder_eclient_volume_response *callback,
-                            int l, int r,
-                            void *v);
-/* If L and R are both -ve gets the volume.
- * If neither are -ve then sets the volume.
- * Otherwise asserts!
- */
-
-int disorder_eclient_enable(disorder_eclient *c,
-                            disorder_eclient_no_response *callback,
-                            void *v);
-int disorder_eclient_disable(disorder_eclient *c,
-                             disorder_eclient_no_response *callback,
-                             void *v);
-int disorder_eclient_random_enable(disorder_eclient *c,
-                                   disorder_eclient_no_response *callback,
-                                   void *v);
-int disorder_eclient_random_disable(disorder_eclient *c,
-                                    disorder_eclient_no_response *callback,
-                                    void *v);
-/* Enable/disable play/random play */
-
-int disorder_eclient_resolve(disorder_eclient *c,
-                             disorder_eclient_string_response *completed,
-                             const char *track,
-                             void *v);
-/* Resolve aliases */
-
 int disorder_eclient_log(disorder_eclient *c,
                          const disorder_eclient_log_callbacks *callbacks,
                          void *v);
 /* Make this a log client (forever - it automatically becomes one again upon
  * reconnection) */
 
-int disorder_eclient_get(disorder_eclient *c,
-                         disorder_eclient_string_response *completed,
-                         const char *track, const char *pref,
-                         void *v);
-int disorder_eclient_set(disorder_eclient *c,
-                         disorder_eclient_no_response *completed,
-                         const char *track, const char *pref, 
-                         const char *value,
-                         void *v);
-int disorder_eclient_unset(disorder_eclient *c,
-                           disorder_eclient_no_response *completed,
-                           const char *track, const char *pref, 
-                           void *v);
-/* Get/set preference values */
-
-int disorder_eclient_get_global(disorder_eclient *c,
-                                disorder_eclient_string_response *completed,
-                                const char *pref,
-                                void *v);
-int disorder_eclient_set_global(disorder_eclient *c,
-                                disorder_eclient_no_response *completed,
-                                const char *pref,
-                                const char *value,
-                                void *v);
-int disorder_eclient_unset_global(disorder_eclient *c,
-                                  disorder_eclient_no_response *completed,
-                                  const char *pref,
-                                  void *v);
-/* Get/set global prefs */
-
-int disorder_eclient_search(disorder_eclient *c,
-                            disorder_eclient_list_response *completed,
-                            const char *terms,
-                            void *v);
-
-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);
-
 int disorder_eclient_rtp_address(disorder_eclient *c,
                                  disorder_eclient_list_response *completed,
                                  void *v);
 
-int disorder_eclient_users(disorder_eclient *c,
-                           disorder_eclient_list_response *completed,
-                           void *v);
-int disorder_eclient_deluser(disorder_eclient *c,
-                             disorder_eclient_no_response *completed,
-                             const char *user,
-                             void *v);
-int disorder_eclient_userinfo(disorder_eclient *c,
-                              disorder_eclient_string_response *completed,
-                              const char *user,
-                              const char *property,
-                              void *v);
-int disorder_eclient_edituser(disorder_eclient *c,
-                              disorder_eclient_no_response *completed,
-                              const char *user,
-                              const char *property,
-                              const char *value,
-                              void *v);
-int disorder_eclient_adduser(disorder_eclient *c,
-                             disorder_eclient_no_response *completed,
-                             const char *user,
-                             const char *password,
-                             const char *rights,
-                             void *v);
 void disorder_eclient_enable_connect(disorder_eclient *c);
 void disorder_eclient_disable_connect(disorder_eclient *c);
-int disorder_eclient_adopt(disorder_eclient *c,
-                           disorder_eclient_no_response *completed,
-                           const char *id,
-                           void *v);  
-int disorder_eclient_playlists(disorder_eclient *c,
-                               disorder_eclient_list_response *completed,
-                               void *v);
-int disorder_eclient_playlist_delete(disorder_eclient *c,
-                                     disorder_eclient_no_response *completed,
-                                     const char *playlist,
-                                     void *v);
-int disorder_eclient_playlist_lock(disorder_eclient *c,
-                                   disorder_eclient_no_response *completed,
-                                   const char *playlist,
-                                   void *v);
-int disorder_eclient_playlist_unlock(disorder_eclient *c,
-                                     disorder_eclient_no_response *completed,
-                                     void *v);
-int disorder_eclient_playlist_set_share(disorder_eclient *c,
-                                        disorder_eclient_no_response *completed,
-                                        const char *playlist,
-                                        const char *sharing,
-                                        void *v);
-int disorder_eclient_playlist_get_share(disorder_eclient *c,
-                                        disorder_eclient_string_response *completed,
-                                        const char *playlist,
-                                        void *v);
-int disorder_eclient_playlist_set(disorder_eclient *c,
-                                  disorder_eclient_no_response *completed,
-                                  const char *playlist,
-                                  char **tracks,
-                                  int ntracks,
-                                  void *v);
-int disorder_eclient_playlist_get(disorder_eclient *c,
-                                  disorder_eclient_list_response *completed,
-                                  const char *playlist,
-                                  void *v);
+
+#include "eclient-stubs.h"
 
 #endif
 
index 3d9e6331b53194ab64f3f1d3693fd4f23ef17a17..d5d8c42325c14c09db31e9f1e332fb8c318772cb 100644 (file)
@@ -25,6 +25,6 @@ include ${top_srcdir}/scripts/sedfiles.make
 
 EXTRA_DIST=htmlman sedfiles.make text2c oggrename make-unidata fix-names \
        format-gcov-report make-version-string setup.in teardown.in macro-docs \
-       setversion
+       setversion protocol
 
 CLEANFILES=$(SEDFILES)
diff --git a/scripts/protocol b/scripts/protocol
new file mode 100755 (executable)
index 0000000..dec33e2
--- /dev/null
@@ -0,0 +1,949 @@
+#! /usr/bin/perl -w
+#
+# This file is part of DisOrder.
+# Copyright (C) 2010-11 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 3 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, see <http://www.gnu.org/licenses/>.
+#
+use strict;
+
+# This file contains the definition of the disorder protocol, plus
+# code to generates stubs for it in the various supported languages.
+#
+# At the time of writing it is a work in progress!
+
+#
+# Types:
+#
+#    string         A (Unicode) string.
+#    string-raw     A string that is not subject to de-quoting (return only)
+#    integer        An integer.  Decimal on the wire.
+#    time           A timestamp.  Decimal on the wire.
+#    boolean        True or false.  "yes" or "no" on the wire.
+#    list           In commands: a list of strings in the command.
+#                   In returns: a list of lines in the response.
+#    pair-list      In returns: a list of key-value pairs in a response body.
+#    body           In commands: a list of strings as a command body.
+#                   In returns: a list of strings as a response body.
+#    queue          In returns: a list of queue entries in a response body.
+#    queue-one      In returns: a queue entry in the response.
+#    literal        Constant string sent in sequence
+#
+
+# Variables and utilities -----------------------------------------------------
+
+our @h = ();
+our @c = ();
+our @ah = ();
+our @ac = ();
+our @missing = ();
+
+# Mapping of return type sequences to eclient callbacks
+our @eclient_return = (
+    ["no_response" => []],
+    ["string_response" => ["string"]],
+    ["string_response" => ["string-raw"]],
+    ["integer_response" => ["integer"]],
+    ["integer_response" => ["boolean"]],
+    ["time_response" => ["time"]],
+    ["pair_integer_response" => ["integer", "integer"]],
+    ["queue_response" => ["queue"]],
+    ["playing_response" => ["queue-one"]],
+    ["list_response" => ["body"]],
+    );
+
+# eclient_response_matces(RETURNS, VARIANT)
+#
+# Return true if VARIANT matches RETURNS
+sub eclient_response_matches {
+    my $returns = shift;
+    my $variant = shift;
+    my $types = $variant->[1];
+    if(scalar @$returns != scalar @$types) { return 0; }
+    for my $n (0 .. $#$returns) {
+       my $return = $returns->[$n];
+       my $type = $return->[0];
+       if($type ne $types->[$n]) { return 0; }
+    }
+    return 1;
+}
+
+# find_eclient_type(RETURNS)
+#
+# Find the result type for an eclient call
+sub find_eclient_response {
+    my $returns = shift;
+    if(!defined $returns) {
+       $returns = [];
+    }
+    for my $variant (@eclient_return) {
+       if(eclient_response_matches($returns, $variant)) {
+           return $variant->[0];
+       }
+    }
+    return undef;
+}
+
+# Write(PATH, LINES)
+#
+# Write array ref LINES to file PATH.
+sub Write {
+    my $path = shift;
+    my $lines = shift;
+
+    (open(F, ">$path")
+     and print F @$lines
+     and close F)
+        or die "$0: $path: $!\n";
+}
+
+# Command classes -------------------------------------------------------------
+
+# c_in_decl([TYPE, NAME])
+#
+# Return the C declaration for an input parameter of type TYPE with
+# name NAME.
+sub c_in_decl {
+    my $arg = shift;
+
+    my $type = $arg->[0];
+    my $name = $arg->[1];
+    if($type eq 'string') {
+       return "const char *$name";
+    } elsif($type eq 'integer') {
+       return "long $name";
+    } elsif($type eq 'time') {
+       return "time_t $name";
+    } elsif($type eq 'list' or $type eq 'body') {
+       return ("char **$name",
+               "int n$name");
+    } elsif($type eq 'literal') {
+        return ();
+    } else {
+       die "$0: c_in_decl: unknown type '$type'\n";
+    }
+}
+
+# c_out_decl([TYPE, NAME])
+#
+# Return the C declaration for an output (reference) parameter of type
+# TYPE with name NAME.
+sub c_out_decl {
+    my $arg = shift;
+
+    return () unless defined $arg;
+    my $type = $arg->[0];
+    my $name = $arg->[1];
+    if($type eq 'string' or $type eq 'string-raw') {
+       return ("char **${name}p");
+    } elsif($type eq 'integer') {
+       return ("long *${name}p");
+    } elsif($type eq 'time') {
+       return ("time_t *${name}p");
+    } elsif($type eq 'boolean') {
+       return ("int *${name}p");
+    } elsif($type eq 'list' or $type eq 'body') {
+       return ("char ***${name}p",
+               "int *n${name}p");
+    } elsif($type eq 'pair-list') {
+       return ("struct kvp **${name}p");
+    } elsif($type eq 'queue' or $type eq 'queue-one') {
+       return ("struct queue_entry **${name}p");
+    } elsif($type eq 'user') {
+       return ();
+    } else {
+       die "$0: c_out_decl: unknown type '$type'\n";
+    }
+}
+
+# c_param_docs([TYPE, NAME})
+#
+# Return the doc string for a C input parameter.
+sub c_param_docs {
+    my $args = shift;
+    my @d = ();
+    for my $arg (@$args) {
+        my $type = $arg->[0];
+        my $name = $arg->[1];
+        my $description = $arg->[2];
+       if($type eq 'body' or $type eq 'list') {
+           push(@d,
+                " * \@param $name $description\n",
+                " * \@param n$name Length of $name\n");
+       } elsif($type ne 'literal') {
+           push(@d, " * \@param $name $description\n");
+       }
+    }
+    return @d;
+}
+
+# c_param_docs([TYPE, NAME})
+#
+# Return the doc string for a C output parameter.
+sub c_return_docs {
+    my $returns = shift;
+    return () unless defined $returns;
+    my @docs = ();
+    for my $return (@$returns) {
+        my $type = $return->[0];
+        my $name = $return->[1];
+        my $descr = $return->[2];
+        if($type eq 'string'
+           or $type eq 'string-raw'
+           or $type eq 'integer'
+           or $type eq 'time'
+           or $type eq 'boolean') {
+            push(@docs,
+                " * \@param ${name}p $descr\n");
+        } elsif($type eq 'list' or $type eq 'body') {
+            push(@docs,
+                " * \@param ${name}p $descr\n",
+                " * \@param n${name}p Number of elements in ${name}p\n");
+        } elsif($type eq 'pair-list') {
+            push(@docs,
+                " * \@param ${name}p $descr\n");
+        } elsif($type eq 'queue' or $type eq 'queue-one') {
+            push(@docs,
+                " * \@param ${name}p $descr\n");
+        } elsif($type eq 'user') {
+           # nothing
+        } else {
+            die "$0: c_return_docs: unknown type '$type'\n";
+        }
+    }
+    return @docs;
+}
+
+# simple(CMD, SUMMARY, DETAIL,
+#        [[TYPE,NAME,DESCR], [TYPE,NAME,DESCR], ...],
+#        [[RETURN-TYPE, RETURN-NAME, RETURN_DESCR]])
+#
+# CMD is normally just the name of the command, but can
+# be [COMMAND,FUNCTION] if the function name should differ
+# from the protocol command.
+sub simple {
+    my $cmd = shift;
+    my $summary = shift;
+    my $detail = shift;
+    my $args = shift;
+    my $returns = shift;
+
+    my $cmdc;
+    if(ref $cmd eq 'ARRAY') {
+        $cmdc = $$cmd[1];
+        $cmd = $$cmd[0];
+    } else {
+        $cmdc = $cmd;
+        $cmdc =~ s/-/_/g;
+    }
+    print STDERR "Processing $cmd... ";
+    # C argument types
+    my @cargs = ();
+    for my $arg (@$args) {
+        if($arg->[0] eq 'body' or $arg->[0] eq 'list') {
+            push(@cargs, "disorder__$arg->[0]", $arg->[1], "n$arg->[1]");
+        } elsif($arg->[0] eq 'string') {
+            push(@cargs, $arg->[1]);
+        } elsif($arg->[0] eq 'integer'
+               or $arg->[0] eq 'time') {
+            push(@cargs, "disorder__$arg->[0]", "$arg->[1]");
+        } elsif($arg->[0] eq 'literal') {
+            push(@cargs, "\"$arg->[1]\"");
+        } else {
+            die "$0: unsupported arg type '$arg->[0]' for '$cmd'\n";
+        }
+    }
+    # Synchronous C API
+    print STDERR "H ";
+    push(@h, "/** \@brief $summary\n",
+         " *\n",
+         " * $detail\n",
+         " *\n",
+        " * \@param c Client\n",
+         c_param_docs($args),
+        c_return_docs($returns),
+         " * \@return 0 on success, non-0 on error\n",
+         " */\n",
+         "int disorder_$cmdc(",
+        join(", ", "disorder_client *c",
+                   map(c_in_decl($_), @$args),
+                   map(c_out_decl($_), @$returns)),
+         ");\n\n");
+    print STDERR "C ";
+    push(@c, "int disorder_$cmdc(",
+        join(", ", "disorder_client *c",
+                   map(c_in_decl($_), @$args),
+                    map(c_out_decl($_), @$returns)),
+        ") {\n");
+    if(!defined $returns or scalar @$returns == 0) {
+        # Simple case
+       push(@c, "  return disorder_simple(",
+            join(", ", "c", "NULL", "\"$cmd\"", @cargs, "(char *)NULL"),
+            ");\n");
+    } elsif(scalar @$returns == 1
+            and $returns->[0]->[0] eq 'queue-one') {
+        # Special case
+        my $return = $$returns[0];
+       push(@c, "  return onequeue(c, \"$cmd\", $return->[1]p);\n");
+    } elsif(scalar @$returns == 1
+            and $returns->[0]->[0] eq 'string-raw') {
+        # Special case
+        my $return = $$returns[0];
+       push(@c, "  return disorder_simple(",
+            join(", ", "c", "$return->[1]p", "\"$cmd\"", @cargs, "(char *)NULL"),
+            ");\n");
+    } elsif(scalar @$returns == 1
+            and $returns->[0]->[0] eq 'pair-list') {
+        # Special case
+        my $return = $$returns[0];
+       push(@c, "  return pairlist(",
+             join(", ", "c", "$return->[1]p", "\"$cmd\"",
+                  @cargs,
+                  "(char *)NULL"),
+             ");\n");
+    } else {
+        my $expected = 0;
+        for(my $n = 0; $n < scalar @$returns; ++$n) {
+            my $return = $returns->[$n];
+            my $type = $return->[0];
+            my $name = $return->[1];
+            if($type eq 'string'
+               or $type eq 'boolean'
+               or $type eq 'integer'
+               or $type eq 'time'
+               or $type eq 'user') {
+               ++$expected;
+            }
+        }
+        if($expected) {
+            push(@c, "  char **v;\n",
+                "  int nv, rc = disorder_simple_split(",
+                join(", ",
+                     "c",
+                     "&v",
+                     "&nv",
+                     $expected,
+                     "\"$cmd\"",
+                     @cargs,
+                     "(char *)NULL"),
+                ");\n",
+                "  if(rc)\n",
+                "    return rc;\n");
+        } else {
+           push(@c,
+                "  int rc = disorder_simple(",
+                join(", ",
+                     "c",
+                     "NULL",
+                     "\"$cmd\"",
+                     @cargs,
+                     "(char *)NULL"),
+                ");\n",
+                "  if(rc)\n",
+                "    return rc;\n");
+       }
+        for(my $n = 0; $n < scalar @$returns; ++$n) {
+            my $return = $returns->[$n];
+            my $type = $return->[0];
+            my $name = $return->[1];
+            if($type eq 'string') {
+                push(@c,
+                     "  *${name}p = v[$n];\n",
+                    "  v[$n] = NULL;\n");
+            } elsif($type eq 'boolean') {
+                push(@c,
+                     "  if(boolean(\"$cmd\", v[$n], ${name}p))\n",
+                     "    return -1;\n");
+            } elsif($type eq 'integer') {
+                push(@c,
+                     "  *${name}p = atol(v[$n]);\n");
+            } elsif($type eq 'time') {
+                push(@c,
+                     "  *${name}p = atoll(v[$n]);\n");
+            } elsif($type eq 'user') {
+                push(@c,
+                     "  c->user = v[$n];\n",
+                    "  v[$n] = NULL;\n");
+            } elsif($type eq 'body') {
+                push(@c,
+                     "  if(readlist(c, ${name}p, n${name}p))\n",
+                     "    return -1;\n");
+            } elsif($type eq 'queue') {
+                push(@c,
+                     "  if(readqueue(c, ${name}p))\n",
+                     "    return -1;\n");
+            } else {
+                die "$0: C API: unknown return type '$type' for '$name'\n";
+            }
+        }
+       if($expected) {
+           push(@c,
+                "  free_strings(nv, v);\n");
+       }
+        push(@c, "  return 0;\n");
+    }
+    push(@c, "}\n\n");
+
+    # Asynchronous C API
+    my $variant = find_eclient_response($returns);
+    if(defined $variant) {
+       print STDERR "AH ";
+       push(@ah,
+            "/** \@brief $summary\n",
+            " *\n",
+            " * $detail\n",
+            " *\n",
+            " * \@param c Client\n",
+            " * \@param completed Called upon completion\n",
+            c_param_docs($args),
+            " * \@param v Passed to \@p completed\n",
+            " * \@return 0 if the command was queued successfuly, non-0 on error\n",
+            " */\n",
+            "int disorder_eclient_$cmdc(",
+            join(", ", "disorder_eclient *c",
+                 "disorder_eclient_$variant *completed",
+                 map(c_in_decl($_), @$args),
+                 "void *v"),
+            ");\n\n");
+
+       print STDERR "AC ";
+       push(@ac,
+            "int disorder_eclient_$cmdc(",
+            join(", ", "disorder_eclient *c",
+                 "disorder_eclient_$variant *completed",
+                 map(c_in_decl($_), @$args),
+                 "void *v"),
+            ") {\n");
+       push(@ac, "  return simple(",
+            join(", ", 
+                 "c",
+                 "${variant}_opcallback",
+                 "(void (*)())completed",
+                 "v",
+                 "\"$cmd\"",
+                 @cargs,
+                 "(char *)0"),
+            ");\n");
+       push(@ac, "}\n\n");
+    } else {
+       push(@missing, "disorder_eclient_$cmdc");
+    }
+
+    # Python API
+    # TODO
+
+    # Java API
+    # TODO
+    print STDERR "\n";
+}
+
+# TODO other command classes
+
+# Front matter ----------------------------------------------------------------
+
+our @generated = ("/*\n",
+                  " * Automatically generated file, see scripts/protocol\n",
+                  " *\n",
+                  " * DO NOT EDIT.\n",
+                  " */\n");
+
+our @gpl = ("/*\n",
+            " * This file is part of DisOrder.\n",
+            " * Copyright (C) 2010-11 Richard Kettlewell\n",
+            " *\n",
+            " * This program is free software: you can redistribute it and/or modify\n",
+            " * it under the terms of the GNU General Public License as published by\n",
+            " * the Free Software Foundation, either version 3 of the License, or\n",
+            " * (at your option) any later version.\n",
+            " *\n",
+            " * This program is distributed in the hope that it will be useful,\n",
+            " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n",
+            " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n",
+            " * GNU General Public License for more details.\n",
+            " *\n",
+            " * You should have received a copy of the GNU General Public License\n",
+            " * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n",
+            " */\n");
+
+
+push(@h, @generated, @gpl,
+     "#ifndef CLIENT_STUBS_H\n",
+     "#define CLIENT_STUBS_H\n",
+     "/** \@file lib/client-stubs.h\n",
+     " * \@brief Generated client API\n",
+     " *\n",
+     " * Don't include this file directly - use \@ref lib/client.h instead.\n",
+     " */\n",
+     "\n");
+
+push(@c, @generated, @gpl,
+     "/** \@file lib/client-stubs.c\n",
+     " * \@brief Generated client API implementation\n",
+     " */\n",
+     "\n");
+
+push(@ah, @generated, @gpl,
+     "#ifndef ECLIENT_STUBS_H\n",
+     "#define ECLIENT_STUBS_H\n",
+     "/** \@file lib/client-stubs.h\n",
+     " * \@brief Generated asynchronous client API\n",
+     " *\n",
+     " * Don't include this file directly - use \@ref lib/eclient.h instead.\n",
+     " */\n",
+     "\n");
+
+push(@ac, @generated, @gpl,
+     "/** \@file lib/client-stubs.c\n",
+     " * \@brief Generated asynchronous client API implementation\n",
+     " */\n",
+     "\n");
+
+# The protocol ----------------------------------------------------------------
+
+simple("adopt",
+       "Adopt a track",
+       "Makes the calling user owner of a randomly picked track.",
+       [["string", "id", "Track ID"]]);
+
+simple("adduser",
+       "Create a user",
+       "Create a new user.  Requires the 'admin' right.  Email addresses etc must be filled in in separate commands.",
+       [["string", "user", "New username"],
+        ["string", "password", "Initial password"],
+        ["string", "rights", "Initial rights (optional)"]]);
+
+simple("allfiles",
+       "List files and directories in a directory",
+       "See 'files' and 'dirs' for more specific lists.",
+       [["string", "dir", "Directory to list (optional)"],
+       ["string", "re", "Regexp that results must match (optional)"]],
+       [["body", "files", "List of matching files and directories"]]);
+
+simple("confirm",
+       "Confirm registration",
+       "The confirmation string must have been created with 'register'.  The username is returned so the caller knows who they are.",
+       [["string", "confirmation", "Confirmation string"]],
+       [["user"]]);
+
+simple("cookie",
+       "Log in with a cookie",
+       "The cookie must have been created with 'make-cookie'.  The username is returned so the caller knows who they are.",
+       [["string", "cookie", "Cookie string"]],
+       [["user"]]);
+
+simple("deluser",
+       "Delete user",
+       "Requires the 'admin' right.",
+       [["string", "user", "User to delete"]]);
+
+simple("dirs",
+       "List directories in a directory",
+       "",
+       [["string", "dir", "Directory to list (optional)"],
+       ["string", "re", "Regexp that results must match (optional)"]],
+       [["body", "files", "List of matching directories"]]);
+
+simple("disable",
+       "Disable play",
+       "Play will stop at the end of the current track, if one is playing.  Requires the 'global prefs' right.",
+       []);
+
+simple("edituser",
+       "Set a user property",
+       "With the 'admin' right you can do anything.  Otherwise you need the 'userinfo' right and can only set 'email' and 'password'.",
+       [["string", "username", "User to modify"],
+        ["string", "property", "Property name"],
+       ["string", "value", "New property value"]]);
+
+simple("enable",
+       "Enable play",
+       "Requires the 'global prefs' right.",
+       []);
+
+simple("enabled",
+       "Detect whether play is enabled",
+       "",
+       [],
+       [["boolean", "enabled", "1 if play is enabled and 0 otherwise"]]);
+
+simple("exists",
+       "Test whether a track exists",
+       "",
+       [["string", "track", "Track name"]],
+       [["boolean", "exists", "1 if the track exists and 0 otherwise"]]);
+
+simple("files",
+       "List files in a directory",
+       "",
+       [["string", "dir", "Directory to list (optional)"],
+       ["string", "re", "Regexp that results must match (optional)"]],
+       [["body", "files", "List of matching files"]]);
+
+simple("get",
+       "Get a track preference",
+       "If the track does not exist that is an error.  If the track exists but the preference does not then a null value is returned.",
+       [["string", "track", "Track name"],
+        ["string", "pref", "Preference name"]],
+       [["string", "value", "Preference value"]]);
+
+simple("get-global",
+       "Get a global preference",
+       "If the preference does exist not then a null value is returned.",
+       [["string", "pref", "Global preference name"]],
+       [["string", "value", "Preference value"]]);
+
+simple("length",
+       "Get a track's length",
+       "If the track does not exist an error is returned.",
+       [["string", "track", "Track name"]],
+       [["integer", "length", "Track length in seconds"]]);
+
+# TODO log
+
+simple("make-cookie",
+       "Create a login cookie for this user",
+       "The cookie may be redeemed via the 'cookie' command",
+       [],
+       [["string", "cookie", "Newly created cookie"]]);
+
+simple("move",
+       "Move a track",
+       "Requires one of the 'move mine', 'move random' or 'move any' rights depending on how the track came to be added to the queue.",
+       [["string", "track", "Track ID or name"],
+       ["integer", "delta", "How far to move the track towards the head of the queue"]]);
+
+simple("moveafter",
+       "Move multiple tracks",
+       "Requires one of the 'move mine', 'move random' or 'move any' rights depending on how the track came to be added to the queue.",
+       [["string", "target", "Move after this track, or to head if \"\""],
+       ["list", "ids", "List of tracks to move by ID"]]);
+
+simple(["new", "new_tracks"],
+       "List recently added tracks",
+       "",
+       [["integer", "max", "Maximum tracks to fetch, or 0 for all available"]],
+       [["body", "tracks", "Recently added tracks"]]);
+
+simple("nop",
+       "Do nothing",
+       "Used as a keepalive.  No authentication required.",
+       []);
+
+simple("part",
+       "Get a track name part",
+       "If the name part cannot be constructed an empty string is returned.",
+       [["string", "track", "Track name"],
+        ["string", "context", "Context (\"sort\" or \"display\")"],
+        ["string", "part", "Name part (\"artist\", \"album\" or \"title\")"]],
+       [["string", "part", "Value of name part"]]);
+
+simple("pause",
+       "Pause the currently playing track",
+       "Requires the 'pause' right.",
+       []);
+
+simple("play",
+       "Play a track",
+       "Requires the 'play' right.",
+       [["string", "track", "Track to play"]],
+       [["string-raw", "id", "Queue ID of new track"]]);
+
+simple("playafter",
+       "Play multiple tracks",
+       "Requires the 'play' right.",
+       [["string", "target", "Insert into queue after this track, or at head if \"\""],
+       ["list", "tracks", "List of track names to play"]]);
+
+simple("playing",
+       "Retrieve the playing track",
+       "",
+       [],
+       [["queue-one", "playing", "Details of the playing track"]]);
+
+simple("playlist-delete",
+       "Delete a playlist",
+       "Requires the 'play' right and permission to modify the playlist.",
+       [["string", "playlist", "Playlist to delete"]]);
+
+simple("playlist-get",
+       "List the contents of a playlist",
+       "Requires the 'read' right and oermission to read the playlist.",
+       [["string", "playlist", "Playlist name"]],
+       [["body", "tracks", "List of tracks in playlist"]]);
+
+simple("playlist-get-share",
+       "Get a playlist's sharing status",
+       "Requires the 'read' right and permission to read the playlist.",
+       [["string", "playlist", "Playlist to read"]],
+       [["string-raw", "share", "Sharing status (\"public\", \"private\" or \"shared\")"]]);
+
+simple("playlist-lock",
+       "Lock a playlist",
+       "Requires the 'play' right and permission to modify the playlist.  A given connection may lock at most one playlist.",
+       [["string", "playlist", "Playlist to delete"]]);
+
+simple("playlist-set",
+       "Set the contents of a playlist",
+       "Requires the 'play' right and permission to modify the playlist, which must be locked.",
+       [["string", "playlist", "Playlist to modify"],
+       ["body", "tracks", "New list of tracks for playlist"]]);
+
+simple("playlist-set-share",
+       "Set a playlist's sharing status",
+       "Requires the 'play' right and permission to modify the playlist.",
+       [["string", "playlist", "Playlist to modify"],
+        ["string", "share", "New sharing status (\"public\", \"private\" or \"shared\")"]]);
+
+simple("playlist-unlock",
+       "Unlock the locked playlist playlist",
+       "The playlist to unlock is implicit in the connection.",
+       []);
+
+simple("playlists",
+       "List playlists",
+       "Requires the 'read' right.  Only playlists that you have permission to read are returned.",
+       [],
+       [["body", "playlists", "Playlist names"]]);
+
+simple("prefs",
+       "Get all the preferences for a track",
+       "",
+       [["string", "track", "Track name"]],
+       [["pair-list", "prefs", "Track preferences"]]);
+
+simple("queue",
+       "List the queue",
+       "",
+       [],
+       [["queue", "queue", "Current queue contents"]]);
+
+simple("random-disable",
+       "Disable random play",
+       "Requires the 'global prefs' right.",
+       []);
+
+simple("random-enable",
+       "Enable random play",
+       "Requires the 'global prefs' right.",
+       []);
+
+simple("random-enabled",
+       "Detect whether random play is enabled",
+       "Random play counts as enabled even if play is disabled.",
+       [],
+       [["boolean", "enabled", "1 if random play is enabled and 0 otherwise"]]);
+
+simple("recent",
+       "List recently played tracks",
+       "",
+       [],
+       [["queue", "recent", "Recently played tracks"]]);
+
+simple("reconfigure",
+       "Re-read configuraiton file.",
+       "Requires the 'admin' right.",
+       []);
+
+simple("register",
+       "Register a new user",
+       "Requires the 'register' right which is usually only available to the 'guest' user.  Redeem the confirmation string via 'confirm' to complete registration.",
+       [["string", "username", "Requested new username"],
+        ["string", "password", "Requested initial password"],
+        ["string", "email", "New user's email address"]],
+       [["string", "confirmation", "Confirmation string"]]);
+
+simple("reminder",
+       "Send a password reminder.",
+       "If the user has no valid email address, or no password, or a reminder has been sent too recently, then no reminder will be sent.",
+       [["string", "username", "User to remind"]]);
+
+simple("remove",
+       "Remove a track form the queue.",
+       "Requires one of the 'remove mine', 'remove random' or 'remove any' rights depending on how the track came to be added to the queue.",
+       [["string", "id", "Track ID"]]);
+
+simple("rescan",
+       "Rescan all collections for new or obsolete tracks.",
+       "Requires the 'rescan' right.",
+       []);     # TODO wait/fresh flags
+
+simple("resolve",
+       "Resolve a track name",
+       "Converts aliases to non-alias track names",
+       [["string", "track", "Track name (might be an alias)"]],
+       [["string", "resolved", "Resolve track name (definitely not an alias)"]]);
+
+simple("resume",
+       "Resume the currently playing track",
+       "Requires the 'pause' right.",
+       []);
+
+simple("revoke",
+       "Revoke a cookie.",
+       "It will not subsequently be possible to log in with the cookie.",
+       []);
+
+simple("rtp-address",
+       "Get the server's RTP address information",
+       "",
+       [],
+       [["string", "address", "Where to store hostname or address"],
+        ["string", "port", "Where to store service name or port number"]]);
+
+simple("scratch",
+       "Terminate the playing track.",
+       "Requires one of the 'scratch mine', 'scratch random' or 'scratch any' rights depending on how the track came to be added to the queue.",
+       [["string", "id", "Track ID (optional)"]]);
+
+simple(["schedule-add", "schedule_add_play"],
+       "Schedule a track to play in the future",
+       "",
+       [["time", "when", "When to play the track"],
+        ["string", "priority", "Event priority (\"normal\" or \"junk\")"],
+        ["literal", "play", ""],
+        ["string", "track", "Track to play"]]);
+
+simple(["schedule-add", "schedule_add_set_global"],
+       "Schedule a global setting to be changed in the future",
+       "",
+       [["time", "when", "When to change the setting"],
+        ["string", "priority", "Event priority (\"normal\" or \"junk\")"],
+        ["literal", "set-global", ""],
+        ["string", "pref", "Global preference to set"],
+        ["string", "value", "New value of global preference"]]);
+
+simple(["schedule-add", "schedule_add_unset_global"],
+       "Schedule a global setting to be unset in the future",
+       "",
+       [["time", "when", "When to change the setting"],
+        ["string", "priority", "Event priority (\"normal\" or \"junk\")"],
+        ["literal", "set-global", ""],
+        ["string", "pref", "Global preference to set"]]);
+
+simple("schedule-del",
+       "Delete a scheduled event.",
+       "Users can always delete their own scheduled events; with the admin right you can delete any event.",
+       [["string", "event", "ID of event to delete"]]);
+
+simple("schedule-get",
+       "Get the details of scheduled event",
+       "",
+       [["string", "id", "Event ID"]],
+       [["pair-list", "actiondata", "Details of event"]]);
+
+simple("schedule-list",
+       "List scheduled events",
+       "This just lists IDs.  Use 'schedule-get' to retrieve more detail",
+       [],
+       [["body", "ids", "List of event IDs"]]);
+
+simple("search",
+       "Search for tracks",
+       "Terms are either keywords or tags formatted as 'tag:TAG-NAME'.",
+       [["string", "terms", "List of search terms"]],
+       [["body", "tracks", "List of matching tracks"]]);
+
+simple("set",
+       "Set a track preference",
+       "Requires the 'prefs' right.",
+       [["string", "track", "Track name"],
+        ["string", "pref", "Preference name"],
+       ["string", "value", "New value"]]);
+
+simple("set-global",
+       "Set a global preference",
+       "Requires the 'global prefs' right.",
+       [["string", "pref", "Preference name"],
+       ["string", "value", "New value"]]);
+
+simple("shutdown",
+       "Request server shutdown",
+       "Requires the 'admin' right.",
+       []);
+
+simple("stats",
+       "Get server statistics",
+       "The details of what the server reports are not really defined.  The returned strings are intended to be printed out one to a line.",
+       [],
+       [["body", "stats", "List of server information strings."]]);
+
+simple("tags",
+       "Get a list of known tags",
+       "Only tags which apply to at least one track are returned.",
+       [],
+       [["body", "tags", "List of tags"]]);
+
+simple("unset",
+       "Unset a track preference",
+       "Requires the 'prefs' right.",
+       [["string", "track", "Track name"],
+        ["string", "pref", "Preference name"]]);
+
+simple("unset-global",
+       "Set a global preference",
+       "Requires the 'global prefs' right.",
+       [["string", "pref", "Preference name"]]);
+
+# 'user' only used for authentication
+
+simple("userinfo",
+       "Get a user property.",
+       "If the user does not exist an error is returned, if the user exists but the property does not then a null value is returned.",
+       [["string", "username", "User to read"],
+        ["string", "property", "Property to read"]],
+       [["string", "value", "Value of property"]]);
+
+simple("users",
+       "Get a list of users",
+       "",
+       [],
+       [["body", "users", "List of users"]]);
+
+simple("version",
+       "Get the server version",
+       "",
+       [],
+       [["string", "version", "Server version string"]]);
+
+simple(["volume", "set_volume"],
+       "Set the volume",
+       "",
+       [["integer", "left", "Left channel volume"],
+        ["integer", "right", "Right channel volume"]]);
+
+simple(["volume", "get_volume"],
+       "Get the volume",
+       "",
+       [],
+       [["integer", "left", "Left channel volume"],
+        ["integer", "right", "Right channel volume"]]);
+
+# End matter ------------------------------------------------------------------
+
+push(@h, "#endif\n");
+
+push(@ah, "#endif\n");
+
+# Write it all out ------------------------------------------------------------
+
+Write("lib/client-stubs.h", \@h);
+Write("lib/client-stubs.c", \@c);
+
+Write("lib/eclient-stubs.h", \@ah);
+Write("lib/eclient-stubs.c", \@ac);
+
+if(scalar @missing) {
+  print "Missing:\n";
+  print map("  $_\n", @missing);
+}