#
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.
+# integer An integer. 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.
+# 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.
+#
+
# Variables and utilities -----------------------------------------------------
our @h = ();
return "const char *$name";
} elsif($type eq 'integer') {
return "long $name";
+ } elsif($type eq 'list' or $type eq 'body') {
+ return ("char **$name",
+ "int n$name");
} else {
- die "$0: unknown type '$type'\n";
+ die "$0: c_in_decl: unknown type '$type'\n";
}
}
return ("long *${name}p");
} elsif($type eq 'boolean') {
return ("int *${name}p");
- } elsif($type eq 'list') {
+ } elsif($type eq 'list' or $type eq 'body') {
return ("char ***${name}p",
"int *n${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: unknown type '$type'\n";
+ die "$0: c_out_decl: unknown type '$type'\n";
}
}
sub c_param_docs {
my $args = shift;
- return map(" * \@param $_->[1] $_->[2]\n", @$args);
+ my @d = ();
+ for my $arg (@$args) {
+ if($arg->[0] eq 'body' or $arg->[0] eq 'list') {
+ push(@d,
+ " * \@param $arg->[1] $arg->[2]\n",
+ " * \@param n$arg->[1] Length of $arg->[1]\n");
+ } else {
+ push(@d, " * \@param $arg->[1] $arg->[2]\n");
+ }
+ }
+ return @d;
}
sub c_return_docs {
or $type eq 'integer'
or $type eq 'boolean') {
return (" * \@param ${name}p $descr\n");
- } elsif($type eq 'list') {
+ } elsif($type eq 'list' or $type eq 'body') {
return (" * \@param ${name}p $descr\n",
" * \@param n${name}p Number of elements in ${name}p\n");
+ } elsif($type eq 'queue' or $type eq 'queue-one') {
+ return (" * \@param ${name}p $descr\n");
+ } elsif($type eq 'user') {
+ return ();
} else {
- die "$0: unknown return type '$type'\n";
+ die "$0: c_return_docs: unknown type '$type'\n";
}
}
# simple(CMD, SUMMARY, DETAIL,
# [[TYPE,NAME,DESCR], [TYPE,NAME,DESCR], ...],
-# [RETURN-TYPE, RETURN-NAME, RETURN_DESCR)
+# [RETURN-TYPE, RETURN-NAME, RETURN_DESCR])
sub simple {
my $cmd = shift;
my $summary = shift;
my $args = shift;
my $return = shift;
+ print STDERR "Processing $cmd... ";
my $cmdc = $cmd;
$cmdc =~ s/-/_/g;
# 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($return),
" * \@return 0 on success, non-0 on error\n",
map(c_in_decl($_), @$args),
c_out_decl($return)),
");\n\n");
+ print STDERR "C ";
push(@c, "int disorder_$cmdc(",
join(", ", "disorder_client *c",
map(c_in_decl($_), @$args),
c_out_decl($return)),
") {\n");
if(!defined $return) {
- push(@c, " return disorder_simple(c, 0, \"$cmd\"",
- map(", $_->[1]", @$args),
- ", (char *)0);\n",
- );
+ 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') {
+ push(@cargs, "buf_$arg->[1]");
+ push(@c, " char buf_$arg->[1]\[16];\n",
+ " byte_snprintf(buf_$arg->[1], sizeof buf_$arg->[1], \"%ld\", $arg->[1]);\n");
+ } else {
+ die "$0: unsupported arg type '$arg->[0]' for '$cmd'\n";
+ }
+ }
+ push(@c, " return disorder_simple(",
+ join(", ", "c", 0, "\"$cmd\"", @cargs, "(char *)0"),
+ ");\n");
} elsif($return->[0] eq 'string') {
push(@c, " return dequote(disorder_simple(c, $return->[1]p, \"$cmd\"",
map(", $_->[1]", @$args),
" *$return->[1]p = atol(v);\n",
" xfree(v);\n",
" return 0;\n");
- } elsif($return->[0] eq 'list') {
+ } elsif($return->[0] eq 'user') {
+ push(@c, " char *u;\n",
+ " int rc;\n",
+ " if((rc = disorder_simple(c, &u, \"$cmd\"",
+ map(", $_->[1]", @$args),
+ " )))\n",
+ " return rc;\n",
+ " c->user = u;\n",
+ " return 0;\n");
+ } elsif($return->[0] eq 'body') {
push(@c, " return disorder_simple_list(c, $return->[1]p, n$return->[1]p, \"$cmd\"",
map(", $_->[1]", @$args),
", (char *)0);\n");
+ } elsif($return->[0] eq 'queue') {
+ push(@c, " return somequeue(c, \"$cmd\", $return->[1]p);\n");
+ } elsif($return->[0] eq 'queue-one') {
+ push(@c, " return onequeue(c, \"$cmd\", $return->[1]p);\n");
} else {
- die "$0: unknown return type '$return->[0]' for '$cmd'\n";
+ die "$0: C API: unknown type '$return->[0]' for '$cmd'\n";
}
push(@c, "}\n\n");
# Java API
# TODO
-}
-
-# string_login(CMD, SUMMARY, DETAIL, [[TYPE,NAME,DESCR], [TYPE,NAME,DESCR], ...])
-#
-# Like string(), but the server returns a username, which we squirrel
-# away rather than returning to the caller.
-sub string_login {
- my $cmd = shift;
- my $summary = shift;
- my $detail = shift;
- my $args = shift;
- my $return = shift;
-
- my $cmdc = $cmd;
- $cmdc =~ s/-/_/g;
- # Synchronous C API
- push(@h, "/** \@brief $summary\n",
- " *\n",
- " * $detail\n",
- " *\n",
- c_param_docs($args),
- " * \@return 0 on success, non-0 on error\n",
- " */\n",
- "int disorder_$cmdc(",
- join(", ", "disorder_client *c",
- map(c_in_decl($_), @$args)),
- ");\n");
- push(@c, "int disorder_$cmdc(",
- join(", ", "disorder_client *c",
- map(c_in_decl($_), @$args)),
- ") {\n",
- " char *u;\n",
- " int rc;\n",
- " if((rc = disorder_simple(c, &u, \"$cmd\"",
- map(", $_->[1]", @$args),
- " )))\n",
- " return rc;\n",
- " c->user = u;\n",
- " return 0;\n",
- "}\n\n");
-
- # Asynchronous C API
- # TODO
-
- # Python API
- # TODO
-
- # Java API
- # TODO
+ print STDERR "\n";
}
# TODO other command classes
"See 'files' and 'dirs' for more specific lists.",
[["string", "dir", "Directory to list (optional)"],
["string", "re", "Regexp that results must match (optional)"]],
- ["list", "files", "List of matching files and directories"]);
+ ["body", "files", "List of matching files and directories"]);
-string_login("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"]]);
+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"]);
-string_login("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"]]);
+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",
"",
[["string", "dir", "Directory to list (optional)"],
["string", "re", "Regexp that results must match (optional)"]],
- ["list", "files", "List of matching directories"]);
+ ["body", "files", "List of matching directories"]);
simple("disable",
"Disable play",
"",
[["string", "dir", "Directory to list (optional)"],
["string", "re", "Regexp that results must match (optional)"]],
- ["list", "files", "List of matching files"]);
+ ["body", "files", "List of matching files"]);
simple("get",
"Get a track preference",
[],
["string", "cookie", "Newly created cookie"]);
-# TODO move
+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"]]);
-# TODO moveafter
+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"]]);
# TODO new
[["string", "track", "Track to play"]],
["string", "id", "Queue ID of new track"]);
-# TODO playafter
+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"]]);
-# TODO playing
+simple("playing",
+ "Retrieve the playing track",
+ "",
+ [],
+ ["queue-one", "playing", "Details of the playing track"]);
simple("playlist-delete",
"Delete a playlist",
"List the contents of a playlist",
"Requires the 'read' right and oermission to read the playlist.",
[["string", "playlist", "Playlist name"]],
- ["list", "tracks", "List of tracks in playlist"]);
+ ["body", "tracks", "List of tracks in playlist"]);
simple("playlist-get-share",
"Get a playlist's sharing status",
"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.",
"List playlists",
"Requires the 'read' right. Only playlists that you have permission to read are returned.",
[],
- ["list", "playlists", "Playlist names"]);
+ ["body", "playlists", "Playlist names"]);
# TODO prefs
-# TODO queue
+simple("queue",
+ "List the queue",
+ "",
+ [],
+ ["queue", "queue", "Current queue contents"]);
simple("random-disable",
"Disable random play",
[],
["boolean", "enabled", "1 if random play is enabled and 0 otherwise"]);
-# TODO recent
+simple("recent",
+ "List recently played tracks",
+ "",
+ [],
+ ["queue", "recent", "Recently played tracks"]);
simple("reconfigure",
"Re-read configuraiton file.",
simple("revoke",
"Revoke a cookie.",
"It will not subsequently be possible to log in with the cookie.",
- []); # TODO fix docs!
+ []);
# TODO rtp-address
"List scheduled events",
"This just lists IDs. Use 'schedule-get' to retrieve more detail",
[],
- ["list", "ids", "List of event IDs"]);
+ ["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"]],
- ["list", "tracks", "List of matching tracks"]);
+ ["body", "tracks", "List of matching tracks"]);
simple("set",
"Set a track preference",
"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..",
[],
- ["list", "stats", "List of server information strings."]);
+ ["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.",
[],
- ["list", "tags", "List of tags"]);
+ ["body", "tags", "List of tags"]);
simple("unset",
"Unset a track preference",
"Get a list of users",
"",
[],
- ["list", "users", "List of users"]);
+ ["body", "users", "List of users"]);
simple("version",
"Get the server version",