chiark / gitweb /
More commands.
[disorder] / scripts / protocol
CommitLineData
200adb00
RK
1#! /usr/bin/perl -w
2#
3# This file is part of DisOrder.
4# Copyright (C) 2010 Richard Kettlewell
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19use strict;
20
ec9c0462
RK
21# This file contains the definition of the disorder protocol, plus
22# code to generates stubs for it in the various supported languages.
23#
24# At the time of writing it is a work in progress!
25
26#
27# Types:
28#
29# string A (Unicode) string.
30# integer An integer. Decimal on the wire.
31# boolean True or false. "yes" or "no" on the wire.
32# list In commands: a list of strings in the command.
33# In returns: a list of lines in the response.
34# body In commands: a list of strings as a command body.
35# In returns: a list of strings as a response body.
36# queue In returns: a list of queue entries in a response body.
37# queue-one In returns: a queue entry in the response.
38#
39
200adb00
RK
40# Variables and utilities -----------------------------------------------------
41
42our @h = ();
43our @c = ();
44
45sub Write {
46 my $path = shift;
47 my $lines = shift;
48
49 (open(F, ">$path")
50 and print F @$lines
51 and close F)
7788b7c7 52 or die "$0: $path: $!\n";
200adb00
RK
53}
54
55# Command classes -------------------------------------------------------------
56
50d905eb
RK
57sub c_in_decl {
58 my $arg = shift;
59
60 my $type = $arg->[0];
61 my $name = $arg->[1];
62 if($type eq 'string') {
63 return "const char *$name";
64 } elsif($type eq 'integer') {
65 return "long $name";
0bc1d67c 66 } elsif($type eq 'list' or $type eq 'body') {
08af2413
RK
67 return ("char **$name",
68 "int n$name");
50d905eb 69 } else {
ec9c0462 70 die "$0: c_in_decl: unknown type '$type'\n";
50d905eb
RK
71 }
72}
73
74sub c_out_decl {
75 my $arg = shift;
76
830d5c43 77 return () unless defined $arg;
50d905eb
RK
78 my $type = $arg->[0];
79 my $name = $arg->[1];
80 if($type eq 'string') {
830d5c43 81 return ("char **${name}p");
50d905eb 82 } elsif($type eq 'integer') {
830d5c43
RK
83 return ("long *${name}p");
84 } elsif($type eq 'boolean') {
85 return ("int *${name}p");
ec9c0462 86 } elsif($type eq 'list' or $type eq 'body') {
830d5c43
RK
87 return ("char ***${name}p",
88 "int *n${name}p");
ec9c0462 89 } elsif($type eq 'queue' or $type eq 'queue-one') {
08af2413 90 return ("struct queue_entry **${name}p");
f4522fa7
RK
91 } elsif($type eq 'user') {
92 return ();
50d905eb 93 } else {
ec9c0462 94 die "$0: c_out_decl: unknown type '$type'\n";
50d905eb
RK
95 }
96}
97
98sub c_param_docs {
99 my $args = shift;
08af2413
RK
100 my @d = ();
101 for my $arg (@$args) {
0bc1d67c 102 if($arg->[0] eq 'body' or $arg->[0] eq 'list') {
08af2413
RK
103 push(@d,
104 " * \@param $arg->[1] $arg->[2]\n",
105 " * \@param n$arg->[1] Length of $arg->[1]\n");
106 } else {
107 push(@d, " * \@param $arg->[1] $arg->[2]\n");
108 }
109 }
110 return @d;
50d905eb
RK
111}
112
830d5c43
RK
113sub c_return_docs {
114 my $return = shift;
115 return () unless defined $return;
116 my $type = $return->[0];
117 my $name = $return->[1];
118 my $descr = $return->[2];
119 if($type eq 'string'
120 or $type eq 'integer'
121 or $type eq 'boolean') {
122 return (" * \@param ${name}p $descr\n");
ec9c0462 123 } elsif($type eq 'list' or $type eq 'body') {
830d5c43
RK
124 return (" * \@param ${name}p $descr\n",
125 " * \@param n${name}p Number of elements in ${name}p\n");
ec9c0462 126 } elsif($type eq 'queue' or $type eq 'queue-one') {
08af2413 127 return (" * \@param ${name}p $descr\n");
f4522fa7
RK
128 } elsif($type eq 'user') {
129 return ();
830d5c43 130 } else {
ec9c0462 131 die "$0: c_return_docs: unknown type '$type'\n";
830d5c43 132 }
7788b7c7
RK
133}
134
830d5c43
RK
135# simple(CMD, SUMMARY, DETAIL,
136# [[TYPE,NAME,DESCR], [TYPE,NAME,DESCR], ...],
08af2413 137# [RETURN-TYPE, RETURN-NAME, RETURN_DESCR])
830d5c43 138sub simple {
7788b7c7
RK
139 my $cmd = shift;
140 my $summary = shift;
141 my $detail = shift;
142 my $args = shift;
143 my $return = shift;
144
ec9c0462 145 print STDERR "Processing $cmd... ";
7788b7c7
RK
146 my $cmdc = $cmd;
147 $cmdc =~ s/-/_/g;
148 # Synchronous C API
ec9c0462 149 print STDERR "H ";
7788b7c7
RK
150 push(@h, "/** \@brief $summary\n",
151 " *\n",
152 " * $detail\n",
153 " *\n",
08af2413 154 " * \@param c Client\n",
830d5c43
RK
155 c_param_docs($args),
156 c_return_docs($return),
7788b7c7
RK
157 " * \@return 0 on success, non-0 on error\n",
158 " */\n",
50d905eb
RK
159 "int disorder_$cmdc(",
160 join(", ", "disorder_client *c",
161 map(c_in_decl($_), @$args),
830d5c43
RK
162 c_out_decl($return)),
163 ");\n\n");
ec9c0462 164 print STDERR "C ";
50d905eb
RK
165 push(@c, "int disorder_$cmdc(",
166 join(", ", "disorder_client *c",
167 map(c_in_decl($_), @$args),
830d5c43
RK
168 c_out_decl($return)),
169 ") {\n");
170 if(!defined $return) {
08af2413
RK
171 my @cargs = ();
172 for my $arg (@$args) {
0bc1d67c
RK
173 if($arg->[0] eq 'body' or $arg->[0] eq 'list') {
174 push(@cargs, "disorder_$arg->[0]", $arg->[1], "n$arg->[1]");
175 } elsif($arg->[0] eq 'string') {
08af2413 176 push(@cargs, $arg->[1]);
0bc1d67c
RK
177 } elsif($arg->[0] eq 'integer') {
178 push(@cargs, "buf_$arg->[1]");
179 push(@c, " char buf_$arg->[1]\[16];\n",
180 " byte_snprintf(buf_$arg->[1], sizeof buf_$arg->[1], \"%ld\", $arg->[1]);\n");
181 } else {
182 die "$0: unsupported arg type '$arg->[0]' for '$cmd'\n";
08af2413
RK
183 }
184 }
185 push(@c, " return disorder_simple(",
186 join(", ", "c", 0, "\"$cmd\"", @cargs, "(char *)0"),
187 ");\n");
830d5c43
RK
188 } elsif($return->[0] eq 'string') {
189 push(@c, " return dequote(disorder_simple(c, $return->[1]p, \"$cmd\"",
190 map(", $_->[1]", @$args),
191 ", (char *)0), $return->[1]p);\n");
192 } elsif($return->[0] eq 'boolean') {
193 push(@c, " char *v;\n",
194 " int rc;\n",
195 " if((rc = disorder_simple(c, &v, \"$cmd\"",
196 map(", $_->[1]", @$args),
197 ", (char *)0)))\n",
198 " return rc;\n",
199 " return boolean(\"$cmd\", v, $return->[1]p);\n");
200 } elsif($return->[0] eq 'integer') {
201 push(@c, " char *v;\n",
202 " int rc;\n",
203 "\n",
204 " if((rc = disorder_simple(c, &v, \"$cmd\"",
205 map(", $_->[1]", @$args),
206 ", (char *)0)))\n",
207 " return rc;\n",
208 " *$return->[1]p = atol(v);\n",
209 " xfree(v);\n",
210 " return 0;\n");
f4522fa7
RK
211 } elsif($return->[0] eq 'user') {
212 push(@c, " char *u;\n",
213 " int rc;\n",
214 " if((rc = disorder_simple(c, &u, \"$cmd\"",
215 map(", $_->[1]", @$args),
216 " )))\n",
217 " return rc;\n",
218 " c->user = u;\n",
219 " return 0;\n");
ec9c0462 220 } elsif($return->[0] eq 'body') {
830d5c43
RK
221 push(@c, " return disorder_simple_list(c, $return->[1]p, n$return->[1]p, \"$cmd\"",
222 map(", $_->[1]", @$args),
223 ", (char *)0);\n");
08af2413 224 } elsif($return->[0] eq 'queue') {
ec9c0462
RK
225 push(@c, " return somequeue(c, \"$cmd\", $return->[1]p);\n");
226 } elsif($return->[0] eq 'queue-one') {
227 push(@c, " return onequeue(c, \"$cmd\", $return->[1]p);\n");
830d5c43 228 } else {
ec9c0462 229 die "$0: C API: unknown type '$return->[0]' for '$cmd'\n";
830d5c43
RK
230 }
231 push(@c, "}\n\n");
7788b7c7
RK
232
233 # Asynchronous C API
234 # TODO
235
236 # Python API
237 # TODO
238
239 # Java API
240 # TODO
ec9c0462 241 print STDERR "\n";
7788b7c7
RK
242}
243
200adb00
RK
244# TODO other command classes
245
246# Front matter ----------------------------------------------------------------
247
248our @gpl = ("/*\n",
7788b7c7
RK
249 " * This file is part of DisOrder.\n",
250 " * Copyright (C) 2010 Richard Kettlewell\n",
251 " *\n",
252 " * This program is free software: you can redistribute it and/or modify\n",
253 " * it under the terms of the GNU General Public License as published by\n",
254 " * the Free Software Foundation, either version 3 of the License, or\n",
255 " * (at your option) any later version.\n",
256 " *\n",
257 " * This program is distributed in the hope that it will be useful,\n",
258 " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n",
259 " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n",
260 " * GNU General Public License for more details.\n",
261 " *\n",
262 " * You should have received a copy of the GNU General Public License\n",
263 " * along with this program. If not, see <http://www.gnu.org/licenses/>.\n",
264 " */\n");
200adb00
RK
265
266
267push(@h, @gpl,
268 "#ifndef CLIENT_STUBS_H\n",
269 "#define CLIENT_STUBS_H\n",
270 "\n");
271
272push(@c, @gpl,
273 "\n");
274
275# The protocol ----------------------------------------------------------------
276
96b1cf08
RK
277simple("adopt",
278 "Adopt a track",
279 "Makes the calling user owner of a randomly picked track.",
50d905eb 280 [["string", "id", "Track ID"]]);
200adb00 281
96b1cf08
RK
282simple("adduser",
283 "Create a user",
284 "Create a new user. Requires the 'admin' right. Email addresses etc must be filled in in separate commands.",
50d905eb
RK
285 [["string", "user", "New username"],
286 ["string", "password", "Initial password"],
287 ["string", "rights", "Initial rights (optional)"]]);
200adb00 288
830d5c43
RK
289simple("allfiles",
290 "List files and directories in a directory",
291 "See 'files' and 'dirs' for more specific lists.",
292 [["string", "dir", "Directory to list (optional)"],
293 ["string", "re", "Regexp that results must match (optional)"]],
ec9c0462 294 ["body", "files", "List of matching files and directories"]);
200adb00 295
f4522fa7
RK
296simple("confirm",
297 "Confirm registration",
298 "The confirmation string must have been created with 'register'. The username is returned so the caller knows who they are.",
299 [["string", "confirmation", "Confirmation string"]],
300 ["user"]);
301
302simple("cookie",
303 "Log in with a cookie",
304 "The cookie must have been created with 'make-cookie'. The username is returned so the caller knows who they are.",
305 [["string", "cookie", "Cookie string"]],
306 ["user"]);
200adb00 307
96b1cf08
RK
308simple("deluser",
309 "Delete user",
310 "Requires the 'admin' right.",
50d905eb 311 [["string", "user", "User to delete"]]);
200adb00 312
830d5c43
RK
313simple("dirs",
314 "List directories in a directory",
315 "",
316 [["string", "dir", "Directory to list (optional)"],
317 ["string", "re", "Regexp that results must match (optional)"]],
ec9c0462 318 ["body", "files", "List of matching directories"]);
200adb00 319
96b1cf08
RK
320simple("disable",
321 "Disable play",
322 "Play will stop at the end of the current track, if one is playing. Requires the 'global prefs' right.",
323 []);
324
325simple("edituser",
326 "Set a user property",
327 "With the 'admin' right you can do anything. Otherwise you need the 'userinfo' right and can only set 'email' and 'password'.",
50d905eb
RK
328 [["string", "username", "User to modify"],
329 ["string", "property", "Property name"],
330 ["string", "value", "New property value"]]);
96b1cf08
RK
331
332simple("enable",
333 "Enable play",
334 "Requires the 'global prefs' right.",
335 []);
336
830d5c43
RK
337simple("enabled",
338 "Detect whether play is enabled",
339 "",
340 [],
341 ["boolean", "enabled", "1 if play is enabled and 0 otherwise"]);
342
343simple("exists",
344 "Test whether a track exists",
345 "",
346 [["string", "track", "Track name"]],
347 ["boolean", "exists", "1 if the track exists and 0 otherwise"]);
348
349simple("files",
350 "List files in a directory",
351 "",
352 [["string", "dir", "Directory to list (optional)"],
353 ["string", "re", "Regexp that results must match (optional)"]],
ec9c0462 354 ["body", "files", "List of matching files"]);
830d5c43
RK
355
356simple("get",
7788b7c7
RK
357 "Get a track preference",
358 "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.",
50d905eb
RK
359 [["string", "track", "Track name"],
360 ["string", "pref", "Preference name"]],
830d5c43 361 ["string", "value", "Preference value"]);
200adb00 362
830d5c43 363simple("get-global",
7788b7c7
RK
364 "Get a global preference",
365 "If the preference does exist not then a null value is returned.",
50d905eb 366 [["string", "pref", "Global preference name"]],
830d5c43 367 ["string", "value", "Preference value"]);
200adb00 368
830d5c43
RK
369simple("length",
370 "Get a track's length",
371 "If the track does not exist an error is returned.",
372 [["string", "track", "Track name"]],
373 ["integer", "length", "Track length in seconds"]);
200adb00
RK
374
375# TODO log
376
830d5c43 377simple("make-cookie",
7788b7c7
RK
378 "Create a login cookie for this user",
379 "The cookie may be redeemed via the 'cookie' command",
380 [],
830d5c43 381 ["string", "cookie", "Newly created cookie"]);
200adb00 382
0bc1d67c
RK
383simple("move",
384 "Move a track",
385 "Requires one of the 'move mine', 'move random' or 'move any' rights depending on how the track came to be added to the queue.",
386 [["string", "track", "Track ID or name"],
387 ["integer", "delta", "How far to move the track towards the head of the queue"]]);
200adb00 388
0bc1d67c
RK
389simple("moveafter",
390 "Move multiple tracks",
391 "Requires one of the 'move mine', 'move random' or 'move any' rights depending on how the track came to be added to the queue.",
392 [["string", "target", "Move after this track, or to head if \"\""],
393 ["list", "ids", "List of tracks to move by ID"]]);
200adb00
RK
394
395# TODO new
396
96b1cf08
RK
397simple("nop",
398 "Do nothing",
399 "Used as a keepalive. No authentication required.",
400 []);
200adb00 401
830d5c43 402simple("part",
7788b7c7
RK
403 "Get a track name part",
404 "If the name part cannot be constructed an empty string is returned.",
50d905eb
RK
405 [["string", "track", "Track name"],
406 ["string", "context", "Context (\"sort\" or \"display\")"],
407 ["string", "part", "Name part (\"artist\", \"album\" or \"title\")"]],
830d5c43 408 ["string", "part", "Value of name part"]);
200adb00 409
96b1cf08
RK
410simple("pause",
411 "Pause the currently playing track",
412 "Requires the 'pause' right.",
413 []);
200adb00 414
830d5c43 415simple("play",
00861dcb
RK
416 "Play a track",
417 "Requires the 'play' right.",
50d905eb 418 [["string", "track", "Track to play"]],
830d5c43 419 ["string", "id", "Queue ID of new track"]);
00861dcb 420
0bc1d67c
RK
421simple("playafter",
422 "Play multiple tracks",
423 "Requires the 'play' right.",
424 [["string", "target", "Insert into queue after this track, or at head if \"\""],
425 ["list", "tracks", "List of track names to play"]]);
200adb00 426
ec9c0462
RK
427simple("playing",
428 "Retrieve the playing track",
429 "",
430 [],
431 ["queue-one", "playing", "Details of the playing track"]);
200adb00 432
96b1cf08
RK
433simple("playlist-delete",
434 "Delete a playlist",
435 "Requires the 'play' right and permission to modify the playlist.",
50d905eb 436 [["string", "playlist", "Playlist to delete"]]);
200adb00 437
830d5c43
RK
438simple("playlist-get",
439 "List the contents of a playlist",
440 "Requires the 'read' right and oermission to read the playlist.",
441 [["string", "playlist", "Playlist name"]],
ec9c0462 442 ["body", "tracks", "List of tracks in playlist"]);
200adb00 443
830d5c43 444simple("playlist-get-share",
7788b7c7
RK
445 "Get a playlist's sharing status",
446 "Requires the 'read' right and permission to read the playlist.",
50d905eb 447 [["string", "playlist", "Playlist to read"]],
830d5c43 448 ["string", "share", "Sharing status (\"public\", \"private\" or \"shared\")"]);
200adb00 449
3680ef53
RK
450simple("playlist-lock",
451 "Lock a playlist",
452 "Requires the 'play' right and permission to modify the playlist. A given connection may lock at most one playlist.",
50d905eb 453 [["string", "playlist", "Playlist to delete"]]);
3680ef53 454
08af2413
RK
455simple("playlist-set",
456 "Set the contents of a playlist",
457 "Requires the 'play' right and permission to modify the playlist, which must be locked.",
458 [["string", "playlist", "Playlist to modify"],
0bc1d67c 459 ["body", "tracks", "New list of tracks for playlist"]]);
08af2413 460
96b1cf08
RK
461simple("playlist-set-share",
462 "Set a playlist's sharing status",
7788b7c7 463 "Requires the 'play' right and permission to modify the playlist.",
50d905eb
RK
464 [["string", "playlist", "Playlist to modify"],
465 ["string", "share", "New sharing status (\"public\", \"private\" or \"shared\")"]]);
200adb00 466
96b1cf08
RK
467simple("playlist-unlock",
468 "Unlock the locked playlist playlist",
469 "The playlist to unlock is implicit in the connection.",
470 []);
200adb00 471
830d5c43
RK
472simple("playlists",
473 "List playlists",
474 "Requires the 'read' right. Only playlists that you have permission to read are returned.",
475 [],
ec9c0462 476 ["body", "playlists", "Playlist names"]);
200adb00
RK
477
478# TODO prefs
479
08af2413
RK
480simple("queue",
481 "List the queue",
482 "",
483 [],
484 ["queue", "queue", "Current queue contents"]);
200adb00 485
96b1cf08
RK
486simple("random-disable",
487 "Disable random play",
488 "Requires the 'global prefs' right.",
489 []);
490
491simple("random-enable",
492 "Enable random play",
493 "Requires the 'global prefs' right.",
494 []);
200adb00 495
830d5c43
RK
496simple("random-enabled",
497 "Detect whether random play is enabled",
498 "Random play counts as enabled even if play is disabled.",
499 [],
500 ["boolean", "enabled", "1 if random play is enabled and 0 otherwise"]);
200adb00 501
08af2413
RK
502simple("recent",
503 "List recently played tracks",
504 "",
505 [],
506 ["queue", "recent", "Recently played tracks"]);
200adb00 507
96b1cf08
RK
508simple("reconfigure",
509 "Re-read configuraiton file.",
510 "Requires the 'admin' right.",
511 []);
200adb00 512
830d5c43 513simple("register",
7788b7c7
RK
514 "Register a new user",
515 "Requires the 'register' right which is usually only available to the 'guest' user. Redeem the confirmation string via 'confirm' to complete registration.",
50d905eb
RK
516 [["string", "username", "Requested new username"],
517 ["string", "password", "Requested initial password"],
518 ["string", "email", "New user's email address"]],
830d5c43 519 ["string", "confirmation", "Confirmation string"]);
200adb00 520
96b1cf08
RK
521simple("reminder",
522 "Send a password reminder.",
523 "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.",
50d905eb 524 [["string", "username", "User to remind"]]);
200adb00 525
96b1cf08
RK
526simple("remove",
527 "Remove a track form the queue.",
528 "Requires one of the 'remove mine', 'remove random' or 'remove any' rights depending on how the track came to be added to the queue.",
50d905eb 529 [["string", "id", "Track ID"]]);
200adb00 530
96b1cf08
RK
531simple("rescan",
532 "Rescan all collections for new or obsolete tracks.",
533 "Requires the 'rescan' right.",
7788b7c7 534 []); # TODO wait/fresh flags
200adb00 535
830d5c43 536simple("resolve",
7788b7c7
RK
537 "Resolve a track name",
538 "Converts aliases to non-alias track names",
50d905eb 539 [["string", "track", "Track name (might be an alias)"]],
830d5c43 540 ["string", "resolved", "Resolve track name (definitely not an alias)"]);
200adb00 541
96b1cf08
RK
542simple("resume",
543 "Resume the currently playing track",
544 "Requires the 'pause' right.",
545 []);
200adb00 546
96b1cf08
RK
547simple("revoke",
548 "Revoke a cookie.",
549 "It will not subsequently be possible to log in with the cookie.",
08af2413 550 []);
200adb00
RK
551
552# TODO rtp-address
553
96b1cf08
RK
554simple("scratch",
555 "Terminate the playing track.",
556 "Requires one of the 'scratch mine', 'scratch random' or 'scratch any' rights depending on how the track came to be added to the queue.",
50d905eb 557 [["string", "id", "Track ID (optional)"]]);
200adb00
RK
558
559# TODO schedule-add
560
96b1cf08
RK
561simple("schedule-del",
562 "Delete a scheduled event.",
563 "Users can always delete their own scheduled events; with the admin right you can delete any event.",
50d905eb 564 [["string", "event", "ID of event to delete"]]);
200adb00
RK
565
566# TODO schedule-get
567
830d5c43
RK
568simple("schedule-list",
569 "List scheduled events",
570 "This just lists IDs. Use 'schedule-get' to retrieve more detail",
571 [],
ec9c0462 572 ["body", "ids", "List of event IDs"]);
200adb00 573
830d5c43
RK
574simple("search",
575 "Search for tracks",
576 "Terms are either keywords or tags formatted as 'tag:TAG-NAME'.",
577 [["string", "terms", "List of search terms"]],
ec9c0462 578 ["body", "tracks", "List of matching tracks"]);
200adb00 579
96b1cf08
RK
580simple("set",
581 "Set a track preference",
582 "Requires the 'prefs' right.",
50d905eb
RK
583 [["string", "track", "Track name"],
584 ["string", "pref", "Preference name"],
585 ["string", "value", "New value"]]);
200adb00 586
96b1cf08
RK
587simple("set-global",
588 "Set a global preference",
589 "Requires the 'global prefs' right.",
50d905eb
RK
590 [["string", "pref", "Preference name"],
591 ["string", "value", "New value"]]);
200adb00 592
eea34c08
RK
593simple("shutdown",
594 "Request server shutdown",
595 "Requires the 'admin' right.",
596 []);
7788b7c7 597
830d5c43
RK
598simple("stats",
599 "Get server statistics",
600 "The details of what the server reports are not really defined. The returned strings are intended to be printed out one to a line..",
601 [],
ec9c0462 602 ["body", "stats", "List of server information strings."]);
200adb00 603
830d5c43
RK
604simple("tags",
605 "Get a list of known tags",
606 "Only tags which apply to at least one track are returned.",
607 [],
ec9c0462 608 ["body", "tags", "List of tags"]);
200adb00 609
96b1cf08
RK
610simple("unset",
611 "Unset a track preference",
612 "Requires the 'prefs' right.",
50d905eb
RK
613 [["string", "track", "Track name"],
614 ["string", "pref", "Preference name"]]);
200adb00 615
96b1cf08
RK
616simple("unset-global",
617 "Set a global preference",
618 "Requires the 'global prefs' right.",
50d905eb 619 [["string", "pref", "Preference name"]]);
200adb00 620
50d905eb 621# 'user' only used for authentication
200adb00 622
830d5c43 623simple("userinfo",
7788b7c7
RK
624 "Get a user property.",
625 "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.",
50d905eb
RK
626 [["string", "username", "User to read"],
627 ["string", "property", "Property to read"]],
830d5c43 628 ["string", "value", "Value of property"]);
200adb00 629
830d5c43
RK
630simple("users",
631 "Get a list of users",
632 "",
633 [],
ec9c0462 634 ["body", "users", "List of users"]);
200adb00 635
830d5c43 636simple("version",
7788b7c7
RK
637 "Get the server version",
638 "",
639 [],
830d5c43 640 ["string", "version", "Server version string"]);
200adb00
RK
641
642# TODO volume
643
644# End matter ------------------------------------------------------------------
645
646push(@h, "#endif\n");
647
648# Write it all out ------------------------------------------------------------
649
7788b7c7
RK
650Write("lib/client-stubs.h", \@h);
651Write("lib/client-stubs.c", \@c);