+ 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);