chiark / gitweb /
Merge branch 'mdw/gstdecode'
[disorder] / scripts / protocol
index 13c9f3ccf7ba007cb74251bf83aa0c49afdd26a1..f2f9a3d5275bb8cabcc990189ddb6cfff9588026 100755 (executable)
@@ -1,7 +1,7 @@
 #! /usr/bin/perl -w
 #
 # This file is part of DisOrder.
-# Copyright (C) 2010-11 Richard Kettlewell
+# Copyright (C) 2010-11, 13 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
@@ -45,6 +45,55 @@ use strict;
 
 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)
 #
@@ -145,6 +194,7 @@ sub c_param_docs {
 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];
@@ -154,20 +204,25 @@ sub c_return_docs {
            or $type eq 'integer'
            or $type eq 'time'
            or $type eq 'boolean') {
-            return (" * \@param ${name}p $descr\n");
+            push(@docs,
+                " * \@param ${name}p $descr\n");
         } 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");
+            push(@docs,
+                " * \@param ${name}p $descr\n",
+                " * \@param n${name}p Number of elements in ${name}p\n");
         } elsif($type eq 'pair-list') {
-            return (" * \@param ${name}p $descr\n");
+            push(@docs,
+                " * \@param ${name}p $descr\n");
         } elsif($type eq 'queue' or $type eq 'queue-one') {
-            return (" * \@param ${name}p $descr\n");
+            push(@docs,
+                " * \@param ${name}p $descr\n");
         } elsif($type eq 'user') {
-            return ();
+           # nothing
         } else {
             die "$0: c_return_docs: unknown type '$type'\n";
         }
     }
+    return @docs;
 }
 
 # simple(CMD, SUMMARY, DETAIL,
@@ -193,6 +248,22 @@ sub simple {
         $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",
@@ -215,26 +286,6 @@ sub simple {
                    map(c_in_decl($_), @$args),
                     map(c_out_decl($_), @$returns)),
         ") {\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");
-        } elsif($arg->[0] eq 'time') {
-            push(@cargs, "buf_$arg->[1]");
-            push(@c, "  char buf_$arg->[1]\[16];\n",
-                 "  byte_snprintf(buf_$arg->[1], sizeof buf_$arg->[1], \"%lld\", (long long)$arg->[1]);\n");
-        } elsif($arg->[0] eq 'literal') {
-            push(@cargs, "\"$arg->[1]\"");
-        } else {
-            die "$0: unsupported arg type '$arg->[0]' for '$cmd'\n";
-        }
-    }
     if(!defined $returns or scalar @$returns == 0) {
         # Simple case
        push(@c, "  return disorder_simple(",
@@ -262,7 +313,7 @@ sub simple {
                   "(char *)NULL"),
              ");\n");
     } else {
-        my $split = 0;
+        my $expected = 0;
         for(my $n = 0; $n < scalar @$returns; ++$n) {
             my $return = $returns->[$n];
             my $type = $return->[0];
@@ -272,39 +323,44 @@ sub simple {
                or $type eq 'integer'
                or $type eq 'time'
                or $type eq 'user') {
-                $split = 1;
+               ++$expected;
             }
         }
-        if($split) {
-            push(@c, "  char **v, *r;\n",
-                 "  int nv;\n");
-        }
-        push(@c, 
-             "  int rc = disorder_simple(",
-             join(", ",
-                  "c",
-                  $split ? "&r" : "NULL",
-                  "\"$cmd\"",
-                  @cargs,
-                  "(char *)NULL"),
-             ");\n",
-             "  if(rc)\n",
-             "    return rc;\n");
-        if($split) {
-            push(@c,
-                 "  v = split(r, &nv, SPLIT_QUOTES, 0, 0);\n",
-                 "  if(nv != ", scalar @$returns, ") {\n",
-                 "    disorder_error(0, \"malformed reply to %s\", \"$cmd\");\n",
-                 "    return -1;\n",
-                 "  }\n");
-        }
+        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");
+                     "  *${name}p = v[$n];\n",
+                    "  v[$n] = NULL;\n");
             } elsif($type eq 'boolean') {
                 push(@c,
                      "  if(boolean(\"$cmd\", v[$n], ${name}p))\n",
@@ -317,7 +373,8 @@ sub simple {
                      "  *${name}p = atoll(v[$n]);\n");
             } elsif($type eq 'user') {
                 push(@c,
-                     "  c->user = v[$n];\n");
+                     "  c->user = v[$n];\n",
+                    "  v[$n] = NULL;\n");
             } elsif($type eq 'body') {
                 push(@c,
                      "  if(readlist(c, ${name}p, n${name}p))\n",
@@ -330,13 +387,58 @@ sub simple {
                 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");
-        # TODO xfree unconsumed split output
     }
     push(@c, "}\n\n");
 
     # Asynchronous C API
-    # TODO
+    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
@@ -378,9 +480,33 @@ our @gpl = ("/*\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 ----------------------------------------------------------------
@@ -675,6 +801,17 @@ simple("rtp-address",
        [["string", "address", "Where to store hostname or address"],
         ["string", "port", "Where to store service name or port number"]]);
 
+simple("rtp-cancel",
+       "Cancel RTP stream",
+       "",
+       []);
+
+simple("rtp-request",
+       "Request a unicast RTP stream",
+       "",
+       [["string", "address", "Destination address"],
+        ["string", "port", "Destination 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.",
@@ -807,7 +944,17 @@ simple(["volume", "get_volume"],
 
 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);
+}